#include "boid-playground.hpp" #include "raygui.h" #include "world.hpp" struct VerticalLayout { float x, y; float gap; }; static Rectangle rect_with_offset(Rectangle rect, Vector2 offset) { return { rect.x + offset.x, rect.y + offset.y, rect.width, rect.height }; } static Rectangle rect_with_offset(Rectangle rect, float x, float y) { return { rect.x + x, rect.y + y, rect.width, rect.height }; } static int gui_valuebox_float(Rectangle rect, const char *text, float *value, float min_value, float max_value, bool edit_mode) { int int_value = *value; int result = GuiValueBox(rect, text, &int_value, min_value, max_value, edit_mode); *value = int_value; return result; } static void gui_valuebox_float(Rectangle rect, const char *text, float *value, float min_value, float max_value, bool *edit_mode) { if (gui_valuebox_float(rect, text, value, min_value, max_value, *edit_mode)) { *edit_mode = !*edit_mode; } } static Rectangle next_in_layout(VerticalLayout *layout, float width, float height, float offset_x = 0) { Rectangle rect = { layout->x + offset_x, layout->y, width, height }; layout->y += height + layout->gap; return rect; } static void ui_draw_control_panel(World *world, Visuals *visuals, UI *ui, Rectangle rect) { float panel_height = rect.height; ui->show_panel = !GuiWindowBox(rect, "Control panel"); float group_height = panel_height - 45; GuiGroupBox({ 30, 55, 180, group_height }, "Visuals"); { VerticalLayout layout = { .x = 40, .y = 65, .gap = 8 }; GuiCheckBox(next_in_layout(&layout, 15, 15), "Show world outline", &visuals->draw_world_outline); GuiCheckBox(next_in_layout(&layout, 15, 15), "Show direction", &visuals->draw_boid_direction); GuiCheckBox(next_in_layout(&layout, 15, 15), "Show view cone", &visuals->draw_view_cone); GuiCheckBox(next_in_layout(&layout, 15, 15), "Show separation radius", &visuals->draw_separation_radius); GuiCheckBox(next_in_layout(&layout, 15, 15), "Show collision rays", &visuals->draw_collision_avoidance_rays); // TODO: Visualize cohesion, alignment and separation forces // GuiCheckBox(next_in_layout(&layout, 15, 15), "Show pulling forces", &visuals->draw_pulling_forces); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "Boid size", &visuals->boid_edge_size, 2.5, 50); Rectangle boid_color_rect = next_in_layout(&layout, 50, 50); GuiColorPicker(boid_color_rect, NULL, &visuals->boid_color); GuiLabel(rect_with_offset({ 80, 10, 80, 16 }, boid_color_rect.x, boid_color_rect.y), "Boid color"); Rectangle bg_color_rect = next_in_layout(&layout, 50, 50); GuiColorPicker(bg_color_rect, NULL, &visuals->bg_color); GuiLabel(rect_with_offset({ 80, 10, 80, 16 }, bg_color_rect.x, bg_color_rect.y), "BG color"); // TODO: Add showing out of bounds boids } GuiGroupBox({ 220, 55, 220, group_height }, "Boid"); { VerticalLayout layout = { .x = 230, .y = 65, .gap = 8 }; GuiCheckBox(next_in_layout(&layout, 15, 15), "Looping walls", &world->looping_walls); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "Separation radius", &world->separation_radius, 2.5, 150); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "View radius", &world->view_radius, 2.5, 150); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "View angle", &world->view_angle, 0, 2*PI); gui_valuebox_float(next_in_layout(&layout, 50, 15, 60), "Min speed", &world->min_speed, 0, 1000, &ui->min_speed_edit); gui_valuebox_float(next_in_layout(&layout, 50, 15, 60), "Max speed", &world->max_speed, 0, 1000, &ui->max_speed_edit); gui_valuebox_float(next_in_layout(&layout, 50, 15, 60), "Steer speed", &world->max_steer_speed, 0, 1000, &ui->steer_speed_edit); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "Collision distance", &world->collision_avoidance_distance, 5, 100); GuiSlider(next_in_layout(&layout, 100, 15), NULL, "Collision ray angle", &world->collision_avoidance_ray_angle, 0, PI); GuiSpinner(next_in_layout(&layout, 100, 15, 95), "Collision ray count", &world->collision_avoidance_ray_count, 1, 10, false); } GuiGroupBox({ 450, 55, 220, group_height }, "Flock"); { VerticalLayout layout = { .x = 605, .y = 65, .gap = 8 }; GuiCheckBox(next_in_layout(&layout, 15, 15, -145), "Freeze", &world->freeze); GuiSlider(next_in_layout(&layout, 100, 15, -145), NULL, "Boid count", &ui->target_boid_count, 10, MAX_BOIDS); world_adjust_boid_count(world, (int)ui->target_boid_count); gui_valuebox_float(next_in_layout(&layout, 50, 15), "Alignment strength", &world->alignment_strength, 0, 100, &ui->alignment_strength_edit); gui_valuebox_float(next_in_layout(&layout, 50, 15), "Cohesion strength", &world->cohesion_strength, 0, 100, &ui->cohesion_strength_edit); gui_valuebox_float(next_in_layout(&layout, 50, 15), "Separation strength", &world->separation_strength, 0, 100, &ui->separation_strength_edit); gui_valuebox_float(next_in_layout(&layout, 50, 15), "Collision avoidance strength", &world->collision_avoidance_strength, 0, 100, &ui->collision_avoidance_strength_edit); } } static void ui_draw_controls_guide(Rectangle rect) { GuiPanel(rect, "Controls"); VerticalLayout layout = { .x = rect.x + 10, .y = rect.y + 30, .gap = 6 }; GuiLabel(next_in_layout(&layout, 250, 12), "R - reset camera view"); GuiLabel(next_in_layout(&layout, 250, 12), "Scroll middle mouse - zoom camera"); GuiLabel(next_in_layout(&layout, 250, 12), "Hold middle mouse & move - move camera"); } static void ui_draw(World *world, Visuals *visuals, UI *ui) { if (!ui->show_panel) { ui->show_panel = GuiButton({ 20, 20, 30, 30 }, GuiIconText(ICON_PENCIL_BIG, "")); return; } ui_draw_control_panel(world, visuals, ui, { 20, 20, 660, 315 }); ui_draw_controls_guide({ 20, 340, 275, 120 }); float window_width = GetScreenWidth(); float window_height = GetScreenHeight(); DrawFPS(window_width - 90, 10); char boid_label[128] = { 0 }; snprintf(boid_label, sizeof(boid_label), "%lu boids", world->boids.size()); DrawText(boid_label, window_width - 125, 35, 20, GREEN); DrawText("Beware!", 20, window_height - 50, 20, RED); DrawText("May crash sometimes", 20, window_height - 30, 20, RED); }