diff --git a/src/world.cpp b/src/world.cpp index 5b5224e..86a6603 100644 --- a/src/world.cpp +++ b/src/world.cpp @@ -465,78 +465,6 @@ static inline void append_many_b2b_cmp(World *world, BoidList *local_boids, boid } } -// TODO: Dear god :O, this name. -static inline void world_compute_local_boids_looping_top_edge(BoidList *local_boids, World *world, ChunkGrid *chunks, uboid_t **static_chunks, boid_cmp *b2b_cmps, int *b2b_cmps_count, int neighbour_y) { - Vector2 to_offset = { 0, -world->size.y }; - - for (int x = 1; x < chunks->width; x++) { - size_t chunk_idx = chunkgrid_get_idx(chunks, x, 0); - BoidList *chunk = &chunks->data[chunk_idx]; - uboid_t *chunk_boids = static_chunks[chunk_idx]; - - for (int ox = -1; ox <= 1; ox++) { - int neighbour_x = x+ox; - if (neighbour_x < 0 || neighbour_x >= chunks->width) continue; - size_t neighbour_idx = chunkgrid_get_idx(chunks, neighbour_x, neighbour_y); - BoidList *neighbour_chunk = &chunks->data[neighbour_idx]; - uboid_t *neighbour_boids = static_chunks[neighbour_idx]; - - for (int i = 0; i < chunk->count; i++) { - uboid_t from_boid = chunk_boids[i]; - append_many_b2b_cmp(world, local_boids, b2b_cmps, b2b_cmps_count, to_offset, chunk_boids[i], neighbour_boids, neighbour_chunk->count); - } - } - } -} - -// TODO: Dear god :O, this name. -static inline void world_compute_local_boids_looping_left_edge(BoidList *local_boids, World *world, ChunkGrid *chunks, uboid_t **static_chunks, boid_cmp *b2b_cmps, int *b2b_cmps_count, int neighbour_x) { - Vector2 to_offset = { -world->size.x, 0 }; - - for (int y = 1; y < chunks->height; y++) { - size_t chunk_idx = chunkgrid_get_idx(chunks, 0, y); - BoidList *chunk = &chunks->data[chunk_idx]; - uboid_t *chunk_boids = static_chunks[chunk_idx]; - - for (int oy = -1; oy <= 1; oy++) { - int neighbour_y = y+oy; - if (neighbour_y < 0 || neighbour_y >= chunks->height) continue; - size_t neighbour_idx = chunkgrid_get_idx(chunks, neighbour_x, neighbour_y); - BoidList *neighbour_chunk = &chunks->data[neighbour_idx]; - uboid_t *neighbour_boids = static_chunks[neighbour_idx]; - - for (int i = 0; i < chunk->count; i++) { - uboid_t from_boid = chunk_boids[i]; - append_many_b2b_cmp(world, local_boids, b2b_cmps, b2b_cmps_count, to_offset, chunk_boids[i], neighbour_boids, neighbour_chunk->count); - } - } - } -} - -// TODO: Dear god :O, this name. PLEASE DO SOMETHING -static inline void world_compute_local_boids_looping_top_left(BoidList *local_boids, World *world, ChunkGrid *chunks, uboid_t **static_chunks, boid_cmp *b2b_cmps, int *b2b_cmps_count, int neighbour_x) { - Vector2 to_offset = { -world->size.x, 0 }; - - for (int y = 1; y < chunks->height; y++) { - size_t chunk_idx = chunkgrid_get_idx(chunks, 0, y); - BoidList *chunk = &chunks->data[chunk_idx]; - uboid_t *chunk_boids = static_chunks[chunk_idx]; - - for (int oy = -1; oy <= 1; oy++) { - int neighbour_y = y+oy; - if (neighbour_y < 0 || neighbour_y >= chunks->height) continue; - size_t neighbour_idx = chunkgrid_get_idx(chunks, neighbour_x, neighbour_y); - BoidList *neighbour_chunk = &chunks->data[neighbour_idx]; - uboid_t *neighbour_boids = static_chunks[neighbour_idx]; - - for (int i = 0; i < chunk->count; i++) { - uboid_t from_boid = chunk_boids[i]; - append_many_b2b_cmp(world, local_boids, b2b_cmps, b2b_cmps_count, to_offset, chunk_boids[i], neighbour_boids, neighbour_chunk->count); - } - } - } -} - static void world_compute_local_boids(BoidList *local_boids, World *world, ChunkGrid *chunks) { Boid *boids = world->boids.data(); int boid_count = world->boids.size(); @@ -596,22 +524,7 @@ static void world_compute_local_boids(BoidList *local_boids, World *world, Chunk } } - // For checking neighbours on the edge of the wall - if (world->looping_walls) { - // TODO: Rewrite a nicer version here. - - // world_compute_local_boids_looping_top_edge(local_boids, world, chunks, static_chunks, b2b_cmps, &b2b_cmps_count, chunks->height-1); - // world_compute_local_boids_looping_left_edge(local_boids, world, chunks, static_chunks, b2b_cmps, &b2b_cmps_count, chunks->width-1); - // - // if (fmod(world->size.y, chunks->chunk_size) >= 1) { - // world_compute_local_boids_looping_top_edge(local_boids, world, chunks, static_chunks, b2b_cmps, &b2b_cmps_count, chunks->height-2); - // } - // if (fmod(world->size.x, chunks->chunk_size) >= 1) { - // world_compute_local_boids_looping_left_edge(local_boids, world, chunks, static_chunks, b2b_cmps, &b2b_cmps_count, chunks->width-2); - // } - - // TODO: Check top left chunk - } + // TODO: Check for neighbours between world borders if (b2b_cmps_count > 0) { world_calc_distances_and_angles(world, local_boids, b2b_cmps, &b2b_cmps_count); @@ -869,37 +782,61 @@ static Rectangle rect_matrix_mul(Rectangle rect, Matrix *mat) { return result; } -static void DrawTextureTiled(Texture2D texture, Rectangle source, Rectangle dest, Vector2 scale, bool y_flipped) { +static void DrawTextureTiled(Texture2D texture, Vector2 texture_size, Vector2 source_size, Rectangle dest, Vector2 scale, bool y_flipped) { if ((texture.id <= 0)) return; - if ((source.width == 0) || (source.height == 0)) return; + if ((source_size.x == 0) || (source_size.y == 0)) return; - float tile_width = source.width * scale.x; - float tile_height = source.height * scale.y; + float tile_width = source_size.x * scale.x; + float tile_height = source_size.y * scale.y; - float left_tile_edge = ceil(dest.x / tile_width) * tile_width; - float ox = dest.x - left_tile_edge; + if (texture_size.x == source_size.x && texture_size.y == source_size.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); + 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); + } + + SetTextureWrap(texture, TEXTURE_WRAP_REPEAT); + DrawTexturePro(texture, { + ox / scale.x, + oy / scale.y, + source_size.x * scale.x, + source_size.y * scale.y * (y_flipped ? -1 : 1) + }, dest, + { 0, 0 }, + 0, + WHITE + ); } else { - float top_tile_edge = ceil(dest.y / tile_height) * tile_height; - oy = (dest.y - top_tile_edge); - } + float from_iy = floor(dest.y / tile_height) * tile_height; + float to_iy = ceil((dest.y + dest.height) / tile_height) * tile_height; - 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 - ); + float from_ix = floor(dest.x / tile_width) * tile_width; + float to_ix = ceil((dest.x + dest.width) / tile_width) * tile_width; + + // TODO: This loop will overdraw on the edges a bit + for (float iy = from_iy; iy < to_iy; iy += tile_height) { + for (float ix = from_ix; ix < to_ix; ix += tile_width) { + DrawTexturePro(texture, + { + 0, + texture_size.y-source_size.y, + source_size.x, + source_size.y * (y_flipped ? -1 : 1) + }, + { ix, iy, tile_width, tile_height }, + { 0, 0 }, + 0, WHITE); + } + } + } } static bool is_rect_inside_rect(Rectangle outer, Rectangle inner) { @@ -1041,52 +978,73 @@ static void draw_boid_triangles(World *world, Visuals *visuals, Rectangle chunk_ 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; +// https://stackoverflow.com/a/466242 +static uint32_t next_two_power(uint32_t v) { + v--; + v |= v >> 1; + v |= v >> 2; + v |= v >> 4; + v |= v >> 8; + v |= v >> 16; + v++; + return v; +} +static Vector2 nearest_square_two_power(Vector2 vec) { + int size = ceil(MAX(vec.x, vec.y)); + size = next_two_power(size); + return { (float)size, (float)size }; +} + +static Rectangle get_visible_screen_rect() { 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); + return screen_rect; +} - 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); +static void draw_boids_directly_visible(World *world, Visuals *visuals, Rectangle screen_rect, Vector2 *vertices) { + 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); + // TODO: Add padding to visible area, so boids a bit offscreen could be rendered and still be seen + rlDrawRenderBatchActive(); + glUseProgram(visuals->boid_shader.id); + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(rlGetMatrixModelview(), 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; + draw_boid_triangles(world, visuals, visible_chunks_rect, { 0, 0 }, vertices); + rlDisableVertexArray(); + glUseProgram(0); +} - 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; +static void draw_boids_repeating_nearby(World *world, Visuals *visuals, Rectangle screen_rect, Vector2 *vertices) { + float bottom_edge = screen_rect.y + screen_rect.height; + float right_edge = screen_rect.x + screen_rect.width; - Rectangle visible_worlds[4]; - int visible_worlds_cnt = 0; + 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; + + // Because the width of the screen is less than the width of the world. + // At most 4 worlds separate worlds can be visible at once. + // Determine what sub-rectangles of each world is visible and save it the list. + 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++; + visible_worlds_cnt = 1; } else if (world_oy1 == world_oy2) { visible_worlds[0] = { screen_rect.x, @@ -1100,7 +1058,7 @@ static void draw_boids(World *world, Visuals *visuals) { (screen_rect.x + screen_rect.width) - world_ox2, screen_rect.height }; - visible_worlds_cnt+=2; + visible_worlds_cnt = 2; } else if (world_ox1 == world_ox2) { visible_worlds[0] = { screen_rect.x, @@ -1114,7 +1072,7 @@ static void draw_boids(World *world, Visuals *visuals) { screen_rect.width, (screen_rect.y + screen_rect.height) - world_oy2, }; - visible_worlds_cnt+=2; + visible_worlds_cnt = 2; } else { visible_worlds[0] = { screen_rect.x, @@ -1140,79 +1098,121 @@ static void draw_boids(World *world, Visuals *visuals) { right_edge - world_ox2, bottom_edge - world_oy2, }; - visible_worlds_cnt+=4; + visible_worlds_cnt = 4; } + } - rlDrawRenderBatchActive(); + // TODO: Add padding to visible area, so boids a bit offscreen could be rendered and still be seen + rlDrawRenderBatchActive(); + glUseProgram(visuals->boid_shader.id); + Vector4 color = ColorNormalize(visuals->boid_color); + Matrix mvp = MatrixMultiply(rlGetMatrixModelview(), 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); +} + +static void draw_boids_repeating(World *world, Visuals *visuals, Rectangle screen_rect, Vector2 *vertices) { + Matrix model_view = rlGetMatrixModelview(); + Vector2 scale = { model_view.m0, model_view.m5 }; + + Vector2 render_size = Vector2Multiply(world->size, scale); + + RenderTexture2D *frame_buffer = &visuals->boid_shader.frame_buffer; +#ifdef __EMSCRIPTEN__ + // Render buffer on WebGL must be a use powers of 2 + Vector2 frame_buffer_size = nearest_square_two_power(render_size); +#else + Vector2 frame_buffer_size = render_size; +#endif + 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); + } + + // TODO: Account for boids which are right on the edge of the world and should be seen on the other side + 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, rlGetMatrixProjection()); + Matrix render_projection = { + 2.0f/frame_buffer_size.x, 0, 0, -1, + 0, -2.0f/frame_buffer_size.y, 0, 1, + 0, 0, -2, -1, + 0, 0, 0, 1 + }; + Matrix mvp = MatrixMultiply(model_view_no_trans, render_projection); 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); - } + 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, + frame_buffer_size, + render_size, + screen_rect, + Vector2Invert(scale), + true); +} + +static void draw_boids(World *world, Visuals *visuals) { + RPROF_START("Draw boids"); + int boid_count = world->boids.size(); + float chunk_size = world->chunks.chunk_size; + + Rectangle screen_rect = get_visible_screen_rect(); + Matrix model_view = rlGetMatrixModelview(); + 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) { + // Case 1: World is not repeating, so just draw the boids which are visible + draw_boids_directly_visible(world, visuals, screen_rect, vertices); + } else if (scale.x >= 1 && scale.y >= 1) { + // Case 2: World is repeating, but each bodi will at most be rendered once + draw_boids_repeating_nearby(world, visuals, screen_rect, vertices); } 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); + // Case 3: World is repeating, any boid could be rendered many times + draw_boids_repeating(world, visuals, screen_rect, vertices); } RPROF_STOP(); }