add repeat drawing of world on looping walls

This commit is contained in:
Rokas Puzonas 2023-08-06 23:12:44 +03:00
parent a98f423873
commit f6d001e9de
4 changed files with 473 additions and 110 deletions

View File

@ -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<Vector2> points;
};
struct ChunkGrid {
BoidList *data;
int width;
int height;
float chunk_size;
};
struct World {
Vector2 size;
std::vector<Boid> boids;
std::vector<Obstacle> 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;
};

View File

@ -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();

View File

@ -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);

View File

@ -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();
}