From f6d001e9de6e77b134b1b2e44f163c6222556cf0 Mon Sep 17 00:00:00 2001 From: Rokas Puzonas Date: Sun, 6 Aug 2023 23:12:44 +0300 Subject: [PATCH] add repeat drawing of world on looping walls --- src/boid-playground.hpp | 24 +- src/main.cpp | 59 ++--- src/ui.cpp | 10 +- src/world.cpp | 490 ++++++++++++++++++++++++++++++++++------ 4 files changed, 473 insertions(+), 110 deletions(-) diff --git a/src/boid-playground.hpp b/src/boid-playground.hpp index 6118577..5902bb0 100644 --- a/src/boid-playground.hpp +++ b/src/boid-playground.hpp @@ -18,6 +18,8 @@ typedef uint16_t uboid_t; #define MAX_BOIDS (1 << (sizeof(uboid_t)*8)) +#include "boid-list.hpp" + struct Boid { Vector2 pos; Vector2 dir; @@ -29,11 +31,20 @@ struct Obstacle { std::vector points; }; +struct ChunkGrid { + BoidList *data; + int width; + int height; + float chunk_size; +}; + struct World { Vector2 size; std::vector boids; std::vector obstacles; + ChunkGrid chunks; + MemoryArena frame_arena; float view_radius = 10; float view_angle = PI*1.5; @@ -51,7 +62,6 @@ struct World { float collision_avoidance_ray_angle = PI/1.5; int collision_avoidance_ray_count = 3; - // TODO: Function `get_boids_in_view_cone` doesn't work as expected with looping walls bool looping_walls = true; bool freeze = false; @@ -70,13 +80,15 @@ struct Visuals { int32_t vertex_position_loc; int32_t frag_color_loc; int32_t mvp_loc; + int32_t chunk_offset_loc; uint32_t vao; uint32_t vbo; + RenderTexture2D frame_buffer; } boid_shader; - bool show_control_panel = true; + bool draw_world_outline = false; bool draw_boid_direction = false; bool draw_view_cone = false; bool draw_collision_avoidance_rays = false; @@ -87,6 +99,8 @@ struct Visuals { struct UI { float target_boid_count; + bool show_panel = true; + bool min_speed_edit = false; bool max_speed_edit = false; bool steer_speed_edit = false; @@ -96,3 +110,9 @@ struct UI { bool separation_strength_edit = false; bool collision_avoidance_strength_edit = false; }; + +struct App { + World world; + Visuals visuals; + UI ui; +}; diff --git a/src/main.cpp b/src/main.cpp index 2ee3ee8..a39cc7c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -17,9 +17,9 @@ #define FRAMERATE 60 #define TIME_PER_FRAME (1.0/FRAMERATE) -static World g_world; -static Visuals g_visuals; -static UI g_ui; +static App g_app; + +// TODO: Add prompt for restarting program in WASM on crash // TODO: Add controls window @@ -35,26 +35,16 @@ int main() { SetTraceLogLevel(LOG_TRACE); - int screen_width = 1280; - int screen_height = 720; - - InitWindow(screen_width, screen_height, "Boid Playground"); + InitWindow(1280, 720, "Boid Playground"); SetWindowState(FLAG_VSYNC_HINT | FLAG_WINDOW_RESIZABLE); GuiLoadStyleDefault(); rprof_init(); - world_init(&g_world, screen_width, screen_height); - - float border = g_world.collision_avoidance_distance; - for (int i = 0; i < 10000; i++) { - Boid boid; - boid_rand_init(&g_world, &boid, border); - g_world.boids.push_back(boid); - } - g_ui.target_boid_count = g_world.boids.size(); - visuals_init(&g_world, &g_visuals); + app_init(&g_app, GetScreenWidth(), GetScreenHeight()); + world_spawn_boids(&g_app.world, 10000); + g_app.ui.target_boid_count = g_app.world.boids.size(); #ifdef __EMSCRIPTEN__ emscripten_set_main_loop(UpdateDrawFrame, 0, 1); @@ -65,13 +55,12 @@ int main() { } #endif - world_free(&g_world); - visuals_cleanup(&g_visuals); + app_cleanup(&g_app); CloseWindow(); rprof_end(); - rprof_output(NULL); + rprof_output(rprof_cmp_by_exclusive_duration); return 0; } @@ -79,23 +68,23 @@ int main() { static void profiling_test() { rprof_init(); { - world_init(&g_world, 1280, 720); + World world; + world_init(&world, 1280, 720); SetRandomSeed(10); - float border = g_visuals.boid_edge_size; for (int i = 0; i < 50000; i++) { Boid boid; - boid_rand_init(&g_world, &boid, border); - g_world.boids.push_back(boid); + boid_rand_init(&world, &boid, 5); + world.boids.push_back(boid); } for (int i = 0; i < FRAMERATE; i++) { - world_update(&g_world, TIME_PER_FRAME); + // world_update(&world, TIME_PER_FRAME); } - printf("arena: %ld (%.03fMiB)\n", g_world.frame_arena.offset, (float)g_world.frame_arena.offset / 1024 / 1024); - world_free(&g_world); + printf("arena: %ld (%.03fMiB)\n", world.frame_arena.offset, (float)world.frame_arena.offset / 1024 / 1024); + world_cleanup(&world); } rprof_end(); @@ -109,10 +98,10 @@ static void profiling_test() { } void UpdateDrawFrame() { - float dt = GetFrameTime(); + arena_clear(&g_app.world.frame_arena); + g_app.world.size = { (float)GetScreenWidth(), (float)GetScreenHeight() }; - g_world.size.x = GetScreenWidth(); - g_world.size.y = GetScreenHeight(); + float dt = GetFrameTime(); RPROF_START("Update"); #ifdef PLATFORM_WEB @@ -122,18 +111,18 @@ void UpdateDrawFrame() { world_update(&g_world, dt); } #else - world_update(&g_world, dt); + world_update(&g_app, &g_app.world, dt); #endif - visuals_update(&g_world, &g_visuals); + visuals_update(&g_app.world, &g_app.visuals); RPROF_STOP(); // Draw BeginDrawing(); RPROF_START("Draw"); - ClearBackground(g_visuals.bg_color); + ClearBackground(g_app.visuals.bg_color); - world_draw(&g_world, &g_visuals); - ui_draw(&g_world, &g_visuals, &g_ui); + world_draw(&g_app.world, &g_app.visuals); + ui_draw(&g_app.world, &g_app.visuals, &g_app.ui); RPROF_STOP(); EndDrawing(); diff --git a/src/ui.cpp b/src/ui.cpp index c1fe963..e976b68 100644 --- a/src/ui.cpp +++ b/src/ui.cpp @@ -35,13 +35,13 @@ static Rectangle next_in_layout(VerticalLayout *layout, float width, float heigh } static void ui_draw(World *world, Visuals *visuals, UI *ui) { - if (!visuals->show_control_panel) { - visuals->show_control_panel = GuiButton({ 20, 20, 30, 30 }, GuiIconText(ICON_PENCIL_BIG, "")); + if (!ui->show_panel) { + ui->show_panel = GuiButton({ 20, 20, 30, 30 }, GuiIconText(ICON_PENCIL_BIG, "")); return; } - float panel_height = 310; - visuals->show_control_panel = !GuiWindowBox({ 20, 20, 660, panel_height }, "Control panel"); + float panel_height = 335; + ui->show_panel = !GuiWindowBox({ 20, 20, 660, panel_height }, "Control panel"); float group_height = panel_height - 45; @@ -49,6 +49,7 @@ static void ui_draw(World *world, Visuals *visuals, UI *ui) { { 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); @@ -89,6 +90,7 @@ static void ui_draw(World *world, Visuals *visuals, UI *ui) { 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); diff --git a/src/world.cpp b/src/world.cpp index 2c908bb..8d26a4b 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -1,4 +1,5 @@ #include "boid-playground.hpp" +#include "raylib.h" #if defined(PLATFORM_DESKTOP) #if defined(GRAPHICS_API_OPENGL_ES2) @@ -60,10 +61,11 @@ #define BOID_VERT_SHADER \ GLSL_VERT_SHADER_HEADER \ GLSL_IN "vec2 vertexPosition;\n" \ + "uniform vec2 chunkOffset;\n" \ "uniform mat4 mvp;\n" \ "void main()\n" \ "{\n" \ - " gl_Position = mvp*vec4(vertexPosition.x, vertexPosition.y, 0.0, 1.0);\n" \ + " gl_Position = mvp*vec4(vertexPosition.x+chunkOffset.x, vertexPosition.y+chunkOffset.y, 0.0, 1.0);\n" \ "}\0" #define BOID_FRAG_SHADER \ @@ -187,7 +189,7 @@ static void world_init(World *world, float width, float height) { world->size = { width, height }; } -static void world_free(World *world) { +static void world_cleanup(World *world) { arena_free(&world->frame_arena); } @@ -205,6 +207,7 @@ static int visuals_init(World *world, Visuals *visuals) { visuals->boid_shader.vertex_position_loc = glGetAttribLocation(shader_id, "vertexPosition"); visuals->boid_shader.frag_color_loc = glGetUniformLocation(shader_id, "fragColor"); visuals->boid_shader.mvp_loc = glGetUniformLocation(shader_id, "mvp"); + visuals->boid_shader.chunk_offset_loc = glGetUniformLocation(shader_id, "chunkOffset"); visuals->boid_shader.vao = rlLoadVertexArray(); unsigned int vertex_position_buffer = 0; @@ -213,6 +216,8 @@ static int visuals_init(World *world, Visuals *visuals) { glGenBuffers(1, &visuals->boid_shader.vbo); rlDisableVertexArray(); + visuals->boid_shader.frame_buffer = LoadRenderTexture(world->size.x, world->size.y); + return 0; } @@ -222,6 +227,16 @@ static void visuals_cleanup(Visuals *visuals) { rlUnloadShaderProgram(visuals->boid_shader.id); } +static void app_init(App *app, float width, float height) { + world_init(&app->world, width, height); + visuals_init(&app->world, &app->visuals); +} + +static void app_cleanup(App *app) { + world_cleanup(&app->world); + visuals_cleanup(&app->visuals); +} + // --------------------- Utils ----------------------- void world_adjust_boid_count(World *world, int new_boid_count) { @@ -242,15 +257,24 @@ void world_adjust_boid_count(World *world, int new_boid_count) { } } +static void world_spawn_boids(World *world, size_t count) { + float border = 0; + if (world->looping_walls) { + border = world->collision_avoidance_distance; + } + + for (int i = 0; i < count; i++) { + Boid boid; + boid_rand_init(world, &boid, border); + world->boids.push_back(boid); + } +} + // --------------------- Update ----------------------- -struct ChunkGrid { - BoidList *data; - int width; - int height; -}; - static size_t chunkgrid_get_idx(ChunkGrid *grid, int x, int y) { + DEBUG_ASSERT(0 <= x && x < grid->width && "Chunk X is out of bounds"); + DEBUG_ASSERT(0 <= y && y < grid->height && "Chunk Y is out of bounds"); return y * grid->width + x; } @@ -258,10 +282,11 @@ static BoidList *chunkgrid_get(ChunkGrid *grid, int x, int y) { return &grid->data[chunkgrid_get_idx(grid, x, y)]; } -static void chunkgrid_init(MemoryArena *arena, ChunkGrid *grid, int width, int height) { +static void chunkgrid_init(MemoryArena *arena, ChunkGrid *grid, int width, int height, int chunk_size) { grid->data = (BoidList*)arena_malloc(arena, width * height * sizeof(BoidList)); grid->width = width; grid->height = height; + grid->chunk_size = chunk_size; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { @@ -478,6 +503,27 @@ static void world_compute_local_boids(BoidList *local_boids, World *world, Chunk RPROF_STOP(); } +static void world_place_boids_into_chunks(World *world) { + MemoryArena *arena = &world->frame_arena; + float chunk_size = std::max(world->view_radius, 1.0f); + int chunks_wide = std::ceil(world->size.x / chunk_size); + int chunks_high = std::ceil(world->size.y / chunk_size); + + RPROF_START("Alloc chunks"); + chunkgrid_init(arena, &world->chunks, chunks_wide, chunks_high, chunk_size); + RPROF_STOP(); + + RPROF_START("Assign boids to chunks"); + for (int i = 0; i < world->boids.size(); i++) { + Boid *boid = &world->boids[i]; + int x = Clamp(boid->pos.x / chunk_size, 0, chunks_wide-1); + int y = Clamp(boid->pos.y / chunk_size, 0, chunks_high-1); + + boid_list_append(arena, chunkgrid_get(&world->chunks, x, y), i); + } + RPROF_STOP(); +} + static BoidList* world_compute_local_boids(World *world) { Boid *boids = world->boids.data(); int boid_count = world->boids.size(); @@ -490,27 +536,8 @@ static BoidList* world_compute_local_boids(World *world) { } RPROF_STOP(); - float chunk_size = std::max(world->view_radius, 1.0f); - int chunks_wide = std::ceil(world->size.x / chunk_size) + 1; - int chunks_high = std::ceil(world->size.y / chunk_size) + 1; - - RPROF_START("Alloc chunks"); - ChunkGrid chunks; - chunkgrid_init(arena, &chunks, chunks_wide, chunks_high); - RPROF_STOP(); - - RPROF_START("Assign boids to chunks"); - for (int i = 0; i < boid_count; i++) { - Boid *boid = &boids[i]; - int x = boid->pos.x / chunk_size; - int y = boid->pos.y / chunk_size; - - boid_list_append(arena, chunkgrid_get(&chunks, x, y), i); - } - RPROF_STOP(); - RPROF_START("world_compute_local_boids()"); - world_compute_local_boids(all_local_boids, world, &chunks); + world_compute_local_boids(all_local_boids, world, &world->chunks); RPROF_STOP(); return all_local_boids; @@ -537,15 +564,14 @@ static void aggregate_flock_modifiers(World *world, BoidList *flock, Boid *boid, *center = vector2_div_value(*center, flock->count); } -static void world_update(World *world, float dt) { +static void world_update(App *app, World *world, float dt) { if (world->freeze) return; - arena_clear(&world->frame_arena); - Boid *boids = world->boids.data(); int boid_count = world->boids.size(); DEBUG_ASSERT(boid_count <= MAX_BOIDS); + world_place_boids_into_chunks(world); BoidList *list_of_local_boids = world_compute_local_boids(world); RPROF_START("Apply forces"); @@ -629,6 +655,11 @@ static void world_update(World *world, float dt) { } } RPROF_STOP(); + + // TODO: Maybe calling 'world_place_boids_into_chunks' twice can be avoided? + // Currently it is needed, because drawing relies that each boid is in its own correct chunk when drawing + // looping world + world_place_boids_into_chunks(world); } // --------------------- Draw ------------------------ @@ -704,44 +735,114 @@ static void draw_circle_sector(Vector2 center, float radius, float start_angle, rlEnd(); } -static void draw_boids(World *world, Visuals *visuals) { - float boid_length = visuals->boid_edge_size * std::sqrt(3)/2; - float boid_width = visuals->boid_edge_size * 0.6; - int boid_count = world->boids.size(); - int vertex_count = boid_count*3; - Vector2 *vertices = (Vector2*)arena_malloc(&world->frame_arena, vertex_count); +static Vector3 vec3_matrix_mul(Vector3 vec3, Matrix *mat) { + Vector3 result; + result.x = mat->m0*vec3.x + mat->m4*vec3.y + mat->m8*vec3.z + mat->m12; + result.y = mat->m1*vec3.x + mat->m5*vec3.y + mat->m9*vec3.z + mat->m13; + result.z = mat->m2*vec3.x + mat->m6*vec3.y + mat->m10*vec3.z + mat->m14; + return result; +} - for (int i = 0; i < boid_count; i++) { - Boid *boid = &world->boids[i]; - Vector2 *boid_vertices = &vertices[3*i]; +static Rectangle rect_matrix_mul(Rectangle rect, Matrix *mat) { + Rectangle result; + result.x = mat->m0*rect.x + mat->m4*rect.y + mat->m12; + result.y = mat->m1*rect.x + mat->m5*rect.y + mat->m13; + result.width = mat->m0*rect.width + mat->m4*rect.height; + result.height = mat->m1*rect.width + mat->m5*rect.height; + return result; +} - Vector2 triangle[] = { - { boid_length*2/3.0f, 0 }, - { -boid_length*1/3.0f, -boid_width/2 }, - { -boid_length*1/3.0f, boid_width/2 }, - }; - float facing = std::atan2(boid->dir.y, boid->dir.x); - float facing_cos = cos(facing); - float facing_sin = sin(facing); - for (int j = 0; j < 3; j++) { - boid_vertices[j].x = boid->pos.x + (triangle[j].x * facing_cos - triangle[j].y * facing_sin); - boid_vertices[j].y = boid->pos.y + (triangle[j].x * facing_sin + triangle[j].y * facing_cos); - } +static void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 scale, bool y_flipped) { + if ((texture.id <= 0)) return; + if ((source.width == 0) || (source.height == 0)) return; + + float tile_width = source.width * scale.x; + float tile_height = source.height * scale.y; + + float left_tile_edge = ceil(dest.x / tile_width) * tile_width; + float ox = dest.x - left_tile_edge; + + float oy = 0; + if (y_flipped) { + float bottom_edge = (dest.y + dest.height); + float bottom_tile_edge = floor(bottom_edge / tile_height) * tile_height; + oy = (bottom_tile_edge - bottom_edge); + } else { + float top_tile_edge = ceil(dest.y / tile_height) * tile_height; + oy = (dest.y - top_tile_edge); } - rlDrawRenderBatchActive(); + SetTextureWrap(texture, TEXTURE_WRAP_REPEAT); + DrawTexturePro(texture, { + source.x + ox / scale.x, + source.y + oy / scale.y, + source.width * scale.x, + source.height * scale.y * (y_flipped ? -1 : 1) + }, dest, + { 0, 0 }, + 0, + WHITE + ); +} +static bool is_rect_inside_rect(Rectangle outer, Rectangle inner) { + float inner_right_edge = inner.x + inner.width; + float outer_right_edge = outer.x + outer.width; + + float inner_bottom_edge = inner.y + inner.height; + float outer_bottom_edge = outer.y + outer.height; + + return (outer.x <= inner.x) && (outer.y <= inner.y) && (inner_right_edge <= outer_right_edge) && (inner_bottom_edge <= outer_bottom_edge); +} + +static void print_matrix(Matrix mat) { + printf("[%f,%f,%f,%f,\n", mat.m0, mat.m1, mat.m2, mat.m3); + printf(" %f,%f,%f,%f,\n", mat.m4, mat.m5, mat.m6, mat.m7); + printf(" %f,%f,%f,%f,\n", mat.m8, mat.m9, mat.m10, mat.m11); + printf(" %f,%f,%f,%f]\n", mat.m12, mat.m13, mat.m14, mat.m15); +} + +static void print_rect(Rectangle rect) { + printf("[%f,%f,%f,%f]\n", rect.x, rect.y, rect.width, rect.height); +} + +static Matrix matrix_no_translation(Matrix mat) { + mat.m12 = 0; + mat.m13 = 0; + mat.m14 = 0; + return mat; +} + +static void compute_boid_vertices(Vector2 *boid_vertices, Boid *boid, Visuals *visuals) { + float boid_length = visuals->boid_edge_size * std::sqrt(3)/2; + float boid_width = visuals->boid_edge_size * 0.6; + + Vector2 triangle[] = { + { boid_length*2/3.0f, 0 }, + { -boid_length*1/3.0f, -boid_width/2 }, + { -boid_length*1/3.0f, boid_width/2 }, + }; + float facing = std::atan2(boid->dir.y, boid->dir.x); + float facing_cos = cos(facing); + float facing_sin = sin(facing); + for (int i = 0; i < 3; i++) { + boid_vertices[i].x = boid->pos.x + (triangle[i].x * facing_cos - triangle[i].y * facing_sin); + boid_vertices[i].y = boid->pos.y + (triangle[i].x * facing_sin + triangle[i].y * facing_cos); + } +} + +static void render_boids(Visuals *visuals, Vector2 *vertices, int vertex_count) { int32_t vertex_position_loc = visuals->boid_shader.vertex_position_loc; int32_t frag_color_loc = visuals->boid_shader.frag_color_loc; int32_t mvp_loc = visuals->boid_shader.mvp_loc; uint32_t vao = visuals->boid_shader.vao; uint32_t vbo = visuals->boid_shader.vbo; - Color color = visuals->boid_color; + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(rlGetMatrixModelview(), rlGetMatrixProjection()); glUseProgram(visuals->boid_shader.id); - Matrix modelViewProjection = MatrixMultiply(rlGetMatrixModelview(), rlGetMatrixProjection()); - glUniformMatrix4fv(mvp_loc, 1, false, MatrixToFloat(modelViewProjection)); - glUniform4f(frag_color_loc, (float)color.r/255, (float)color.g/255, (float)color.b/255, (float)color.a/255); + glUniformMatrix4fv(mvp_loc, 1, false, MatrixToFloat(mvp)); + glUniform4f(frag_color_loc, color.x, color.y, color.z, color.w); rlEnableVertexArray(vao); glBindBuffer(GL_ARRAY_BUFFER, vbo); @@ -755,6 +856,250 @@ static void draw_boids(World *world, Visuals *visuals) { glUseProgram(0); } +static Rectangle rect_min_overlap(Rectangle a, Rectangle b) { + float left_edge = MAX(a.x, b.x); + float top_edge = MAX(a.y, b.y); + float right_edge = MIN(a.x + a.width, b.x + b.width); + float bottom_edge = MIN(a.y + a.height, b.y + b.height); + return { + left_edge, + top_edge, + right_edge - left_edge, + bottom_edge - top_edge + }; +} + +static Rectangle to_chunk_bounds(Rectangle rect, ChunkGrid *grid) { + float chunk_size = grid->chunk_size; + + float x = floor(rect.x / chunk_size); + float y = floor(rect.y / chunk_size); + return { + x, + y, + fmin(ceil((rect.x + rect.width) / chunk_size) - x, (float)grid->width), + fmin(ceil((rect.y + rect.height) / chunk_size) - y, (float)grid->height) + }; +} + +static int get_vertices_for_chunks(World *world, Visuals *visuals, Vector2 *vertices, Rectangle chunks_rect) { + if (chunks_rect.width == 0) return 0; + if (chunks_rect.height == 0) return 0; + + int count = 0; + + int from_x = chunks_rect.x; + int to_x = chunks_rect.x + chunks_rect.width; + int from_y = chunks_rect.y; + int to_y = chunks_rect.y + chunks_rect.height; + for (int y = from_y; y < to_y; y++) { + for (int x = from_x; x < to_x; x++) { + BoidList *chunk = chunkgrid_get(&world->chunks, x, y); + + uboid_t boid = 0; + auto it = boid_list_get_iterator(chunk); + while (boid_list_iterator_next(&it, &boid)) { + compute_boid_vertices(&vertices[3*count], &world->boids[boid], visuals); + count++; + } + } + } + + return count; +} + +// TODO: pass temporary arena instead of 'Vector2 *temp_vertices' +static void draw_boid_triangles(World *world, Visuals *visuals, Rectangle chunk_rect, Vector2 offset, Vector2 *temp_vertices) { + int rendered_count = get_vertices_for_chunks(world, visuals, temp_vertices, chunk_rect); + int vertex_count = rendered_count*3; + int32_t vertex_position_loc = visuals->boid_shader.vertex_position_loc; + + glBindBuffer(GL_ARRAY_BUFFER, visuals->boid_shader.vbo); + glUniform2f(visuals->boid_shader.chunk_offset_loc, offset.x, offset.y); + glBufferData(GL_ARRAY_BUFFER, vertex_count*sizeof(Vector2), temp_vertices, GL_DYNAMIC_DRAW); + glVertexAttribPointer(vertex_position_loc, 2, GL_FLOAT, 0, 0, NULL); + rlEnableVertexAttribute(vertex_position_loc); + + glDrawArrays(GL_TRIANGLES, 0, vertex_count); + glBindBuffer(GL_ARRAY_BUFFER, 0); +} + +static void draw_boids(World *world, Visuals *visuals) { + int boid_count = world->boids.size(); + float chunk_size = world->chunks.chunk_size; + + Matrix model_view = rlGetMatrixModelview(); + Matrix model_view_inv = MatrixInvert(model_view); + Rectangle screen_rect = rect_matrix_mul({ + 0, 0, (float)GetScreenWidth(), (float)GetScreenHeight(), + }, &model_view_inv); + + RPROF_START("Draw boids"); + Vector2 scale = { model_view.m0, model_view.m5 }; + Vector2 *vertices = (Vector2*)arena_malloc(&world->frame_arena, sizeof(Vector2)*boid_count*3); + + if (!world->looping_walls) { + Rectangle world_rect = { 0, 0, world->size.x, world->size.y }; + Rectangle visible_world_rect = rect_min_overlap(world_rect, screen_rect); + Rectangle visible_chunks_rect = to_chunk_bounds(visible_world_rect, &world->chunks); + + rlDrawRenderBatchActive(); + glUseProgram(visuals->boid_shader.id); + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(model_view, rlGetMatrixProjection()); + rlEnableVertexArray(visuals->boid_shader.vao); + glBindBuffer(GL_ARRAY_BUFFER, visuals->boid_shader.vbo); + glUniformMatrix4fv(visuals->boid_shader.mvp_loc, 1, false, MatrixToFloat(mvp)); + glUniform4f(visuals->boid_shader.frag_color_loc, color.x, color.y, color.z, color.w); + glBindBuffer(GL_ARRAY_BUFFER, 0); + + draw_boid_triangles(world, visuals, visible_chunks_rect, { 0, 0 }, vertices); + rlDisableVertexArray(); + glUseProgram(0); + } else if (scale.x >= 1 && scale.y >= 1) { + float bottom_edge = screen_rect.y + screen_rect.height; + float right_edge = screen_rect.x + screen_rect.width; + + float world_ox1 = floor(screen_rect.x / world->size.x) * world->size.x; + float world_oy1 = floor(screen_rect.y / world->size.y) * world->size.y; + float world_ox2 = floor(right_edge / world->size.x) * world->size.x; + float world_oy2 = floor(bottom_edge / world->size.y) * world->size.y; + + Rectangle visible_worlds[4]; + int visible_worlds_cnt = 0; + if (world_ox1 == world_ox2 && world_oy1 == world_oy2) { + visible_worlds[0] = screen_rect; + visible_worlds_cnt++; + } else if (world_oy1 == world_oy2) { + visible_worlds[0] = { + screen_rect.x, + screen_rect.y, + world_ox2 - screen_rect.x, + screen_rect.height + }; + visible_worlds[1] = { + world_ox2, + screen_rect.y, + (screen_rect.x + screen_rect.width) - world_ox2, + screen_rect.height + }; + visible_worlds_cnt+=2; + } else if (world_ox1 == world_ox2) { + visible_worlds[0] = { + screen_rect.x, + screen_rect.y, + screen_rect.width, + world_oy2 - screen_rect.y, + }; + visible_worlds[1] = { + screen_rect.x, + world_oy2, + screen_rect.width, + (screen_rect.y + screen_rect.height) - world_oy2, + }; + visible_worlds_cnt+=2; + } else { + visible_worlds[0] = { + screen_rect.x, + screen_rect.y, + world_ox2 - screen_rect.x, + world_oy2 - screen_rect.y, + }; + visible_worlds[1] = { + world_ox2, + screen_rect.y, + right_edge - world_ox2, + world_oy2 - screen_rect.y, + }; + visible_worlds[2] = { + screen_rect.x, + world_oy2, + world_ox2 - screen_rect.x, + bottom_edge - world_oy2, + }; + visible_worlds[3] = { + world_ox2, + world_oy2, + right_edge - world_ox2, + bottom_edge - world_oy2, + }; + visible_worlds_cnt+=4; + } + + rlDrawRenderBatchActive(); + glUseProgram(visuals->boid_shader.id); + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(model_view, rlGetMatrixProjection()); + glUniformMatrix4fv(visuals->boid_shader.mvp_loc, 1, false, MatrixToFloat(mvp)); + glUniform4f(visuals->boid_shader.frag_color_loc, color.x, color.y, color.z, color.w); + + rlEnableVertexArray(visuals->boid_shader.vao); + for (int i = 0; i < visible_worlds_cnt; i++) { + Rectangle visible_rect = visible_worlds[i]; + DEBUG_ASSERT(visible_rect.x <= world->size.x); + DEBUG_ASSERT(visible_rect.y <= world->size.y); + + float offset_x = 0; + float offset_y = 0; + if (visible_rect.x < 0) { + offset_x -= world->size.x; + visible_rect.x += world->size.x; + } else if (visible_rect.x >= world->size.x) { + offset_x += world->size.x; + visible_rect.x -= world->size.x; + } + if (visible_rect.y < 0) { + offset_y -= world->size.y; + visible_rect.y += world->size.y; + } else if (visible_rect.y >= world->size.y) { + offset_y += world->size.y; + visible_rect.y -= world->size.y; + } + Rectangle visible_chunks = to_chunk_bounds(visible_rect, &world->chunks); + + draw_boid_triangles(world, visuals, visible_chunks, { offset_x, offset_y }, vertices); + } + rlDisableVertexArray(); + glUseProgram(0); + } else { + Vector2 frame_buffer_size = Vector2Multiply(world->size, scale); + + RenderTexture2D *frame_buffer = &visuals->boid_shader.frame_buffer; + if (fabs(frame_buffer->texture.width - frame_buffer_size.x) >= 1 || fabs(frame_buffer->texture.height - frame_buffer_size.y) >= 1) { + UnloadRenderTexture(visuals->boid_shader.frame_buffer); + visuals->boid_shader.frame_buffer = LoadRenderTexture(frame_buffer_size.x, frame_buffer_size.y); + } + + Matrix projection = rlGetMatrixProjection(); + rlDrawRenderBatchActive(); + BeginTextureMode(*frame_buffer); + Matrix model_view_no_trans = matrix_no_translation(model_view); + rlSetMatrixModelview(model_view_no_trans); + ClearBackground(visuals->bg_color); + glUseProgram(visuals->boid_shader.id); + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(model_view_no_trans, rlGetMatrixProjection()); + glUniformMatrix4fv(visuals->boid_shader.mvp_loc, 1, false, MatrixToFloat(mvp)); + glUniform4f(visuals->boid_shader.frag_color_loc, color.x, color.y, color.z, color.w); + + rlEnableVertexArray(visuals->boid_shader.vao); + draw_boid_triangles(world, visuals, { 0, 0, (float)world->chunks.width, (float)world->chunks.height }, { 0, 0 }, vertices); + rlDisableVertexArray(); + glUseProgram(0); + EndTextureMode(); + + rlSetMatrixModelview(model_view); + rlSetMatrixProjection(projection); + + DrawTextureTiled(frame_buffer->texture, + { 0, 0, frame_buffer_size.x, frame_buffer_size.y }, + screen_rect, + Vector2Invert(scale), + true); + } + RPROF_STOP(); +} + static void visuals_update(World *world, Visuals *visuals) { Camera2D *camera = &visuals->camera; @@ -786,24 +1131,23 @@ static void visuals_update(World *world, Visuals *visuals) { const float zoom_speed = 0.125f; camera->zoom = camera->zoom*(1+zoom_speed*wheel); - if (camera->zoom < zoom_speed) camera->zoom = zoom_speed; + camera->zoom = MAX(zoom_speed*2.5, camera->zoom); + } + + if (world->looping_walls) { + camera->target.x = fmod(camera->target.x + world->size.x, world->size.x); + camera->target.y = fmod(camera->target.y + world->size.y, world->size.y); } } static void world_draw(World *world, Visuals *visuals) { BeginMode2D(visuals->camera); + draw_boids(world, visuals); + for (int i = 0; i < world->obstacles.size(); i++) { draw_obstacle(&world->obstacles[i], GRAY); } - Color border_color = RED; - float world_width = world->size.x; - float world_height = world->size.y; - DrawLine(0, -1, world_width, -1, RED); - DrawLine(-1, 0, -1, world_height, RED); - DrawLine(0, world_height+1, world_width, world_height+1, RED); - DrawLine(world_width+1, 0, world_width+1, world_height, RED); - if (visuals->draw_view_cone) { Color view_cone_color = Fade(GRAY, 0.4); for (int i = 0; i < world->boids.size(); i++) { @@ -829,8 +1173,6 @@ static void world_draw(World *world, Visuals *visuals) { } } - draw_boids(world, visuals); - for (int i = 0; i < boid_count; i++) { Boid *boid = &world->boids[i]; if (visuals->draw_boid_direction) { @@ -839,5 +1181,15 @@ static void world_draw(World *world, Visuals *visuals) { DrawLine(boid->pos.x, boid->pos.y, look_pos.x, look_pos.y, RED); } } + + if (visuals->draw_world_outline) { + Color border_color = RED; + float world_width = world->size.x; + float world_height = world->size.y; + DrawLine(0, 0, world_width, 0, RED); + DrawLine(0, 0, 0, world_height, RED); + DrawLine(0, world_height, world_width, world_height, RED); + DrawLine(world_width, 0, world_width, world_height, RED); + } EndMode2D(); }