Created
January 8, 2026 19:48
-
-
Save gabrieldechichi/17e13f9e2e8d8e5abb88019ab9efdc15 to your computer and use it in GitHub Desktop.
demo_ecs_boids.c
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #include "context.h" | |
| #include "lib/thread_context.h" | |
| #include "lib/typedefs.h" | |
| #include "lib/string_builder.h" | |
| #include "os/os.h" | |
| #include "lib/math.h" | |
| #include "lib/hash.h" | |
| #include "lib/random.h" | |
| #include "gpu_backend.h" | |
| #include "renderer.h" | |
| #include "camera.h" | |
| #include "input.h" | |
| #include "app.h" | |
| #include "mesh.h" | |
| #include "assets.h" | |
| #include "ui_system.h" | |
| #include "shaders/fish_vs.h" | |
| #include "shaders/fish_instanced_vs.h" | |
| #include "shaders/fish_fs.h" | |
| #define NUM_BOIDS 100000 | |
| #define NUM_TARGETS 2 | |
| #define NUM_OBSTACLES 1 | |
| #define INSTANCE_BATCH_SIZE (1024 * 16) | |
| #define NUM_INSTANCE_BATCHES ((NUM_BOIDS + INSTANCE_BATCH_SIZE - 1) / INSTANCE_BATCH_SIZE) | |
| #define GRID_SIZE 8192 | |
| #define MAX_PER_BUCKET 256 | |
| #define CELL_SIZE 8.0f | |
| #define BOID_SEPARATION_WEIGHT 1.0f | |
| #define BOID_ALIGNMENT_WEIGHT 1.0f | |
| #define BOID_TARGET_WEIGHT 2.0f | |
| #define BOID_OBSTACLE_AVERSION_DISTANCE 30.0f | |
| #define BOID_MOVE_SPEED 25.0f | |
| HZ_ECS_COMPONENT() | |
| typedef struct { f32 x, y, z; } Position; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { f32 x, y, z; } Heading; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { u32 index; } BoidIndex; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { u8 dummy; } BoidTag; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { f32 x, y, z; } Scale; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { mat4 value; } LocalToWorld; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { | |
| GpuMesh_Handle mesh; | |
| Material_Handle material; | |
| } MeshRenderer; | |
| HZ_ECS_COMPONENT() | |
| typedef struct { | |
| const struct SampledAnimationClip *clip; | |
| f32 current_time; | |
| vec3 *dest_position; | |
| } AnimationPlayer; | |
| typedef struct SampledAnimationClip { | |
| f32 sample_rate; | |
| i32 frame_count; | |
| const vec3 *positions; | |
| const versor *rotations; | |
| } SampledAnimationClip; | |
| typedef struct { | |
| f32 px, py, pz; | |
| f32 hx, hy, hz; | |
| } BoidBucketEntry; | |
| typedef struct { | |
| u32 count; | |
| f32 sum_align_x, sum_align_y, sum_align_z; | |
| f32 sum_sep_x, sum_sep_y, sum_sep_z; | |
| i32 nearest_target_idx; | |
| i32 nearest_obstacle_idx; | |
| f32 nearest_obstacle_dist; | |
| BoidBucketEntry entries[MAX_PER_BUCKET]; | |
| } BoidBucket; | |
| typedef struct { | |
| EcsWorld world; | |
| AssetSystem assets; | |
| InputSystem input; | |
| Camera camera; | |
| UISystem ui; | |
| UIFont font; | |
| GpuMesh_Handle fish_mesh; | |
| Material_Handle fish_material; | |
| GpuTexture fish_albedo_tex, fish_tint_tex, fish_metallic_gloss_tex; | |
| GpuTexture shark_albedo_tex, shark_metallic_gloss_tex; | |
| vec3 target_positions[NUM_TARGETS]; | |
| vec3 obstacle_positions[NUM_OBSTACLES]; | |
| EcsEntity target_entities[NUM_TARGETS]; | |
| EcsEntity obstacle_entities[NUM_OBSTACLES]; | |
| BoidBucket buckets[GRID_SIZE]; | |
| mat4 instance_data[NUM_BOIDS]; | |
| InstanceBuffer_Handle instance_buffers[NUM_INSTANCE_BATCHES]; | |
| f32 total_time; | |
| } GameState; | |
| global GameState state = {0}; | |
| // hardcoded animation and scene data (extracted from Unity) | |
| #include "./Shark_animation.c" | |
| #include "./Target01_animation.c" | |
| #include "./Target02_animation.c" | |
| #include "./exported_scene_loader.c" | |
| void sample_animation_position(const SampledAnimationClip *clip, f32 time, vec3 out_pos) { | |
| f32 duration = clip->sample_rate * (f32)(clip->frame_count - 1); | |
| while (time >= duration) time -= duration; | |
| if (time < 0.0f) time = 0.0f; | |
| f32 frame_f = time / clip->sample_rate; | |
| i32 frame0 = (i32)frame_f; | |
| i32 frame1 = frame0 + 1; | |
| if (frame1 >= clip->frame_count) frame1 = clip->frame_count - 1; | |
| f32 t = frame_f - (f32)frame0; | |
| const vec3 *p0 = &clip->positions[frame0]; | |
| const vec3 *p1 = &clip->positions[frame1]; | |
| out_pos[0] = (*p0)[0] + t * ((*p1)[0] - (*p0)[0]); | |
| out_pos[1] = (*p0)[1] + t * ((*p1)[1] - (*p0)[1]); | |
| out_pos[2] = (*p0)[2] + t * ((*p1)[2] - (*p0)[2]); | |
| } | |
| void sample_animation_rotation(const SampledAnimationClip *clip, f32 time, versor out_rot) { | |
| f32 duration = clip->sample_rate * (f32)(clip->frame_count - 1); | |
| while (time >= duration) time -= duration; | |
| if (time < 0.0f) time = 0.0f; | |
| f32 frame_f = time / clip->sample_rate; | |
| i32 frame0 = (i32)frame_f; | |
| i32 frame1 = frame0 + 1; | |
| if (frame1 >= clip->frame_count) frame1 = clip->frame_count - 1; | |
| f32 t = frame_f - (f32)frame0; | |
| glm_quat_slerp((f32 *)&clip->rotations[frame0], (f32 *)&clip->rotations[frame1], t, out_rot); | |
| } | |
| HZ_ECS_SYSTEM() | |
| void play_animations_system(EcsCtx *ctx, | |
| Position *positions, | |
| AnimationPlayer *players, | |
| u32 count) { | |
| f32 dt = ctx->delta_time; | |
| if (dt > 0.05f) dt = 0.05f; | |
| for (u32 i = 0; i < count; i++) { | |
| AnimationPlayer *player = &players[i]; | |
| player->current_time += dt; | |
| f32 duration = player->clip->sample_rate * (f32)(player->clip->frame_count - 1); | |
| while (player->current_time >= duration) { | |
| player->current_time -= duration; | |
| } | |
| vec3 sampled_pos; | |
| sample_animation_position(player->clip, player->current_time, sampled_pos); | |
| positions[i].x = sampled_pos[0]; | |
| positions[i].y = sampled_pos[1]; | |
| positions[i].z = sampled_pos[2]; | |
| if (player->dest_position) { | |
| (*player->dest_position)[0] = sampled_pos[0]; | |
| (*player->dest_position)[1] = sampled_pos[1]; | |
| (*player->dest_position)[2] = sampled_pos[2]; | |
| } | |
| } | |
| } | |
| HZ_ECS_SYSTEM() | |
| void BuildAnimatedTransformSystem(EcsCtx *ctx, | |
| const Position *positions, | |
| const AnimationPlayer *players, | |
| const Scale *scales, | |
| LocalToWorld *transforms, | |
| u32 count) { | |
| for (u32 i = 0; i < count; i++) { | |
| const Position *pos = &positions[i]; | |
| const AnimationPlayer *player = &players[i]; | |
| const Scale *scale = &scales[i]; | |
| versor anim_rot; | |
| sample_animation_rotation(player->clip, player->current_time, anim_rot); | |
| quaternion fish_orient; | |
| quat_from_euler(VEC3(RAD(90), RAD(180), 0), fish_orient); | |
| versor rot; | |
| glm_quat_mul(anim_rot, fish_orient, rot); | |
| mat4 model; | |
| glm_mat4_identity(model); | |
| glm_translate(model, (vec3){pos->x, pos->y, pos->z}); | |
| mat4 rot_mat; | |
| glm_quat_mat4(rot, rot_mat); | |
| glm_mat4_mul(model, rot_mat, model); | |
| glm_scale(model, (vec3){scale->x, scale->y, scale->z}); | |
| glm_mat4_copy(model, transforms[i].value); | |
| } | |
| } | |
| HZ_ECS_SYSTEM() | |
| void render_mesh_system(EcsCtx *ctx, | |
| const LocalToWorld *transforms, | |
| const MeshRenderer *renderers, | |
| u32 count) { | |
| for (u32 i = 0; i < count; i++) { | |
| renderer_draw_mesh(renderers[i].mesh, renderers[i].material, transforms[i].value); | |
| } | |
| } | |
| HZ_ECS_SYSTEM(filter = BoidTag) | |
| void insert_boids_system(EcsCtx *ctx, | |
| const Position *positions, | |
| const Heading *headings, | |
| const BoidIndex *indices, | |
| u32 count) { | |
| for (u32 i = 0; i < count; i++) { | |
| f32 px = positions[i].x, py = positions[i].y, pz = positions[i].z; | |
| u32 hash = spatial_hash_3f(px, py, pz, CELL_SIZE) % GRID_SIZE; | |
| u32 slot = ins_atomic_u32_inc_eval(&state.buckets[hash].count) - 1; | |
| if (slot < MAX_PER_BUCKET) { | |
| BoidBucketEntry *entry = &state.buckets[hash].entries[slot]; | |
| entry->px = px; | |
| entry->py = py; | |
| entry->pz = pz; | |
| entry->hx = headings[i].x; | |
| entry->hy = headings[i].y; | |
| entry->hz = headings[i].z; | |
| } | |
| } | |
| } | |
| HZ_ECS_SYSTEM(iter_mode = range, iter_count = GRID_SIZE, sync = barrier) | |
| void merge_cells_system(EcsCtx *ctx, u32 count) { | |
| for (u32 i = 0; i < count; i++) { | |
| i32 bucket_idx = ctx->offset + i; | |
| BoidBucket *bucket = &state.buckets[bucket_idx]; | |
| if (bucket->count == 0) continue; | |
| u32 n = bucket->count; | |
| if (n > MAX_PER_BUCKET) n = MAX_PER_BUCKET; | |
| f32 sum_ax = 0, sum_ay = 0, sum_az = 0; | |
| f32 sum_sx = 0, sum_sy = 0, sum_sz = 0; | |
| for (u32 j = 0; j < n; j++) { | |
| BoidBucketEntry *e = &bucket->entries[j]; | |
| sum_ax += e->hx; sum_ay += e->hy; sum_az += e->hz; | |
| sum_sx += e->px; sum_sy += e->py; sum_sz += e->pz; | |
| } | |
| bucket->sum_align_x = sum_ax; | |
| bucket->sum_align_y = sum_ay; | |
| bucket->sum_align_z = sum_az; | |
| bucket->sum_sep_x = sum_sx; | |
| bucket->sum_sep_y = sum_sy; | |
| bucket->sum_sep_z = sum_sz; | |
| f32 first_px = bucket->entries[0].px; | |
| f32 first_py = bucket->entries[0].py; | |
| f32 first_pz = bucket->entries[0].pz; | |
| f32 nearest_target_dist_sq = 1e18f; | |
| i32 nearest_target_idx = 0; | |
| for (i32 t = 0; t < NUM_TARGETS; t++) { | |
| f32 dx = state.target_positions[t][0] - first_px; | |
| f32 dy = state.target_positions[t][1] - first_py; | |
| f32 dz = state.target_positions[t][2] - first_pz; | |
| f32 dist_sq = dx * dx + dy * dy + dz * dz; | |
| if (dist_sq < nearest_target_dist_sq) { | |
| nearest_target_dist_sq = dist_sq; | |
| nearest_target_idx = t; | |
| } | |
| } | |
| bucket->nearest_target_idx = nearest_target_idx; | |
| f32 nearest_obs_dist_sq = 1e18f; | |
| i32 nearest_obs_idx = 0; | |
| for (i32 o = 0; o < NUM_OBSTACLES; o++) { | |
| f32 dx = state.obstacle_positions[o][0] - first_px; | |
| f32 dy = state.obstacle_positions[o][1] - first_py; | |
| f32 dz = state.obstacle_positions[o][2] - first_pz; | |
| f32 dist_sq = dx * dx + dy * dy + dz * dz; | |
| if (dist_sq < nearest_obs_dist_sq) { | |
| nearest_obs_dist_sq = dist_sq; | |
| nearest_obs_idx = o; | |
| } | |
| } | |
| bucket->nearest_obstacle_idx = nearest_obs_idx; | |
| bucket->nearest_obstacle_dist = sqrtf(nearest_obs_dist_sq); | |
| } | |
| } | |
| HZ_ECS_SYSTEM(filter = BoidTag, depends_on = merge_cells_system) | |
| void steer_boids_system(EcsCtx *ctx, | |
| Position *positions, | |
| Heading *headings, | |
| u32 count) { | |
| f32 dt = ctx->delta_time; | |
| if (dt > 0.05f) dt = 0.05f; | |
| for (u32 i = 0; i < count; i++) { | |
| f32 px = positions[i].x, py = positions[i].y, pz = positions[i].z; | |
| f32 forward_x = headings[i].x, forward_y = headings[i].y, forward_z = headings[i].z; | |
| u32 hash = spatial_hash_3f(px, py, pz, CELL_SIZE) % GRID_SIZE; | |
| BoidBucket *bucket = &state.buckets[hash]; | |
| u32 n = bucket->count; | |
| if (n > MAX_PER_BUCKET) n = MAX_PER_BUCKET; | |
| f32 alignment_x, alignment_y, alignment_z; | |
| f32 separation_x, separation_y, separation_z; | |
| i32 neighbor_count; | |
| if (n == 0) { | |
| neighbor_count = 1; | |
| alignment_x = forward_x; alignment_y = forward_y; alignment_z = forward_z; | |
| separation_x = px; separation_y = py; separation_z = pz; | |
| } else { | |
| neighbor_count = (i32)n; | |
| alignment_x = bucket->sum_align_x; | |
| alignment_y = bucket->sum_align_y; | |
| alignment_z = bucket->sum_align_z; | |
| separation_x = bucket->sum_sep_x; | |
| separation_y = bucket->sum_sep_y; | |
| separation_z = bucket->sum_sep_z; | |
| } | |
| f32 inv_count = 1.0f / (f32)neighbor_count; | |
| i32 nearest_obstacle_idx = bucket->nearest_obstacle_idx; | |
| f32 obs_x = state.obstacle_positions[nearest_obstacle_idx][0]; | |
| f32 obs_y = state.obstacle_positions[nearest_obstacle_idx][1]; | |
| f32 obs_z = state.obstacle_positions[nearest_obstacle_idx][2]; | |
| f32 nearest_obstacle_dist = bucket->nearest_obstacle_dist; | |
| i32 nearest_target_idx = bucket->nearest_target_idx; | |
| f32 tgt_x = state.target_positions[nearest_target_idx][0]; | |
| f32 tgt_y = state.target_positions[nearest_target_idx][1]; | |
| f32 tgt_z = state.target_positions[nearest_target_idx][2]; | |
| // Alignment | |
| f32 avg_ax = alignment_x * inv_count, avg_ay = alignment_y * inv_count, avg_az = alignment_z * inv_count; | |
| f32 align_dx = avg_ax - forward_x, align_dy = avg_ay - forward_y, align_dz = avg_az - forward_z; | |
| f32 align_len = sqrtf(align_dx * align_dx + align_dy * align_dy + align_dz * align_dz); | |
| f32 align_rx = 0, align_ry = 0, align_rz = 0; | |
| if (align_len > 0.0001f) { | |
| f32 inv = BOID_ALIGNMENT_WEIGHT / align_len; | |
| align_rx = align_dx * inv; align_ry = align_dy * inv; align_rz = align_dz * inv; | |
| } | |
| // Separation | |
| f32 sep_dx = px * (f32)neighbor_count - separation_x; | |
| f32 sep_dy = py * (f32)neighbor_count - separation_y; | |
| f32 sep_dz = pz * (f32)neighbor_count - separation_z; | |
| f32 sep_len = sqrtf(sep_dx * sep_dx + sep_dy * sep_dy + sep_dz * sep_dz); | |
| f32 sep_rx = 0, sep_ry = 0, sep_rz = 0; | |
| if (sep_len > 0.0001f) { | |
| f32 inv = BOID_SEPARATION_WEIGHT / sep_len; | |
| sep_rx = sep_dx * inv; sep_ry = sep_dy * inv; sep_rz = sep_dz * inv; | |
| } | |
| // Target seeking | |
| f32 target_dx = tgt_x - px, target_dy = tgt_y - py, target_dz = tgt_z - pz; | |
| f32 target_len = sqrtf(target_dx * target_dx + target_dy * target_dy + target_dz * target_dz); | |
| f32 target_rx = 0, target_ry = 0, target_rz = 0; | |
| if (target_len > 0.0001f) { | |
| f32 inv = BOID_TARGET_WEIGHT / target_len; | |
| target_rx = target_dx * inv; target_ry = target_dy * inv; target_rz = target_dz * inv; | |
| } | |
| // Obstacle avoidance | |
| f32 obs_dx = px - obs_x, obs_dy = py - obs_y, obs_dz = pz - obs_z; | |
| f32 obs_len = sqrtf(obs_dx * obs_dx + obs_dy * obs_dy + obs_dz * obs_dz); | |
| f32 avoid_hx = 0, avoid_hy = 0, avoid_hz = 0; | |
| if (obs_len > 0.0001f) { | |
| f32 inv = 1.0f / obs_len; | |
| f32 nx = obs_dx * inv, ny = obs_dy * inv, nz = obs_dz * inv; | |
| avoid_hx = (obs_x + nx * BOID_OBSTACLE_AVERSION_DISTANCE) - px; | |
| avoid_hy = (obs_y + ny * BOID_OBSTACLE_AVERSION_DISTANCE) - py; | |
| avoid_hz = (obs_z + nz * BOID_OBSTACLE_AVERSION_DISTANCE) - pz; | |
| } | |
| // Combined steering | |
| f32 normal_x = align_rx + sep_rx + target_rx; | |
| f32 normal_y = align_ry + sep_ry + target_ry; | |
| f32 normal_z = align_rz + sep_rz + target_rz; | |
| f32 normal_len = sqrtf(normal_x * normal_x + normal_y * normal_y + normal_z * normal_z); | |
| if (normal_len > 0.0001f) { | |
| f32 inv = 1.0f / normal_len; | |
| normal_x *= inv; normal_y *= inv; normal_z *= inv; | |
| } else { | |
| normal_x = forward_x; normal_y = forward_y; normal_z = forward_z; | |
| } | |
| f32 target_fx, target_fy, target_fz; | |
| if (nearest_obstacle_dist - BOID_OBSTACLE_AVERSION_DISTANCE < 0) { | |
| target_fx = avoid_hx; target_fy = avoid_hy; target_fz = avoid_hz; | |
| } else { | |
| target_fx = normal_x; target_fy = normal_y; target_fz = normal_z; | |
| } | |
| f32 new_hx = forward_x + dt * (target_fx - forward_x); | |
| f32 new_hy = forward_y + dt * (target_fy - forward_y); | |
| f32 new_hz = forward_z + dt * (target_fz - forward_z); | |
| f32 new_len = sqrtf(new_hx * new_hx + new_hy * new_hy + new_hz * new_hz); | |
| if (new_len > 0.0001f) { | |
| f32 inv = 1.0f / new_len; | |
| new_hx *= inv; new_hy *= inv; new_hz *= inv; | |
| } | |
| f32 move_dist = BOID_MOVE_SPEED * dt; | |
| positions[i].x = px + new_hx * move_dist; | |
| positions[i].y = py + new_hy * move_dist; | |
| positions[i].z = pz + new_hz * move_dist; | |
| headings[i].x = new_hx; | |
| headings[i].y = new_hy; | |
| headings[i].z = new_hz; | |
| } | |
| } | |
| HZ_ECS_SYSTEM(filter = BoidTag) | |
| void build_matrices_system(EcsCtx *ctx, | |
| const Position *positions, | |
| const Heading *headings, | |
| const BoidIndex *indices, | |
| u32 count) { | |
| for (u32 i = 0; i < count; i++) { | |
| u32 idx = indices[i].index; | |
| mat4 *model = &state.instance_data[idx]; | |
| vec3 pos = {positions[i].x, positions[i].y, positions[i].z}; | |
| vec3 dir = {headings[i].x, headings[i].y, headings[i].z}; | |
| quaternion heading_rot; | |
| quat_look_at_dir(dir, heading_rot); | |
| quaternion fish_orient; | |
| quat_from_euler(VEC3(RAD(90), RAD(180), 0), fish_orient); | |
| quaternion rot; | |
| glm_quat_mul(heading_rot, fish_orient, rot); | |
| vec3 scale = {0.01f, 0.01f, 0.01f}; | |
| mat_trs(pos, rot, scale, *model); | |
| } | |
| } | |
| void app_init(AppMemory *memory) { | |
| if (!is_main_thread()) return; | |
| AppContext *app_ctx = app_ctx_current(); | |
| ecs_world_init(&state.world, &app_ctx->arena); | |
| ECS_SYSTEM(&state.world, play_animations_system); | |
| ECS_SYSTEM(&state.world, build_animated_transforms_system); | |
| ECS_SYSTEM(&state.world, render_mesh_system); | |
| ECS_SYSTEM(&state.world, insert_boids_system); | |
| ECS_SYSTEM(&state.world, merge_cells_system); | |
| ECS_SYSTEM(&state.world, steer_boids_system); | |
| ECS_SYSTEM(&state.world, build_matrices_system); | |
| state.input = input_init(); | |
| state.camera = camera_init(VEC3(0, 11.6, -0.4), VEC3(RAD(-1.066f), 0, 0), 60.0f); | |
| renderer_init(&app_ctx->arena, app_ctx->num_threads, | |
| (u32)memory->canvas_width, (u32)memory->canvas_height, 4); | |
| asset_system_init(&state.assets, 64); | |
| exported_scene_init(&state.assets); | |
| state.ui = ui_init(&app_ctx->arena, &state.assets); | |
| state.font = ui_load_font(&state.ui, &app_ctx->arena, "public/Roboto-Regular.hza"); | |
| state.fish_albedo_tex = gpu_make_texture("public/fishAlbedo2.png"); | |
| state.fish_tint_tex = gpu_make_texture("public/tints.png"); | |
| state.fish_metallic_gloss_tex = gpu_make_texture("public/fishMetallicGloss.png"); | |
| state.shark_albedo_tex = gpu_make_texture("public/SharkAlbedo.png"); | |
| state.shark_metallic_gloss_tex = gpu_make_texture("public/SharkMetallicGloss.png"); | |
| //TODO: need to add support for "run once" systems | |
| //alternatively this could be split by all threads if were not early exiting above | |
| f32 spawn_radius = 15.0f; | |
| vec3 spawn_center = {20.0f, 5.0f, -120.0f}; | |
| for (i32 i = 0; i < NUM_BOIDS; i++) { | |
| EcsEntity e = ecs_entity_new(&state.world); | |
| UnityRandom rng = unity_random_new((u32)(i + 1) * 0x9F6ABC1u); | |
| f32 rx = unity_random_next_f32(&rng) - 0.5f; | |
| f32 ry = unity_random_next_f32(&rng) - 0.5f; | |
| f32 rz = unity_random_next_f32(&rng) - 0.5f; | |
| f32 len = sqrtf(rx * rx + ry * ry + rz * rz); | |
| f32 hx = 0, hy = 1, hz = 0; | |
| if (len > 0.0001f) { | |
| f32 inv = 1.0f / len; | |
| hx = rx * inv; hy = ry * inv; hz = rz * inv; | |
| } | |
| ecs_set(&state.world, e, Position, { | |
| .x = spawn_center[0] + hx * spawn_radius, | |
| .y = spawn_center[1] + hy * spawn_radius, | |
| .z = spawn_center[2] + hz * spawn_radius | |
| }); | |
| ecs_set(&state.world, e, Heading, {.x = hx, .y = hy, .z = hz}); | |
| ecs_set(&state.world, e, BoidIndex, {.index = (u32)i}); | |
| ecs_add(&state.world, e, BoidTag); | |
| } | |
| //TODO: (same as above) | |
| const SampledAnimationClip *target_clips[NUM_TARGETS] = {&Target01_animation, &Target02_animation}; | |
| for (i32 i = 0; i < NUM_TARGETS; i++) { | |
| EcsEntity e = ecs_entity_new(&state.world); | |
| state.target_entities[i] = e; | |
| vec3 initial_pos; | |
| sample_animation_position(target_clips[i], 0.0f, initial_pos); | |
| ecs_set(&state.world, e, Position, {.x = initial_pos[0], .y = initial_pos[1], .z = initial_pos[2]}); | |
| ecs_set(&state.world, e, AnimationPlayer, { | |
| .clip = target_clips[i], | |
| .current_time = 0.0f, | |
| .dest_position = &state.target_positions[i], | |
| }); | |
| glm_vec3_copy(initial_pos, state.target_positions[i]); | |
| } | |
| //TODO: (same as above) | |
| for (i32 i = 0; i < NUM_OBSTACLES; i++) { | |
| EcsEntity e = ecs_entity_new(&state.world); | |
| state.obstacle_entities[i] = e; | |
| vec3 initial_pos; | |
| sample_animation_position(&Shark_animation, 0.0f, initial_pos); | |
| ecs_set(&state.world, e, Position, {.x = initial_pos[0], .y = initial_pos[1], .z = initial_pos[2]}); | |
| ecs_set(&state.world, e, AnimationPlayer, { | |
| .clip = &Shark_animation, | |
| .current_time = 0.0f, | |
| .dest_position = &state.obstacle_positions[i], | |
| }); | |
| glm_vec3_copy(initial_pos, state.obstacle_positions[i]); | |
| } | |
| // TODO: reusable system for instanced rendering? | |
| for (u32 i = 0; i < NUM_INSTANCE_BATCHES; i++) { | |
| state.instance_buffers[i] = renderer_create_instance_buffer(&(InstanceBufferDesc){ | |
| .stride = sizeof(mat4), | |
| .max_instances = INSTANCE_BATCH_SIZE, | |
| }); | |
| } | |
| LOG_INFO("Boids demo initialized: % boids", FMT_UINT(NUM_BOIDS)); | |
| } | |
| void app_update_and_render(AppMemory *memory) { | |
| state.total_time = memory->total_time; | |
| // clear spatial hash | |
| // TODO: PreUpdate/Update/PostUpdate systems? | |
| Range_u64 range = lane_range(GRID_SIZE); | |
| for (u64 i = range.min; i < range.max; i++) { | |
| state.buckets[i].count = 0; | |
| } | |
| asset_system_update(&state.assets); | |
| exported_scene_update(&state.assets); | |
| if (is_main_thread()) { | |
| input_update(&state.input, &memory->input_events, memory->total_time); | |
| camera_update(&state.camera, memory->canvas_width, memory->canvas_height); | |
| } | |
| renderer_begin_frame(state.camera.view, state.camera.proj, | |
| (GpuColor){2.0f / 255.0f, 94.0f / 255.0f, 131.0f / 255.0f, 1.0f}, | |
| memory->total_time); | |
| ecs_progress(&state.world, memory->dt); | |
| exported_scene_draw(); | |
| // draw boids | |
| Range_u64 batch_range = lane_range(NUM_INSTANCE_BATCHES); | |
| for (u64 batch = batch_range.min; batch < batch_range.max; batch++) { | |
| u32 start = (u32)batch * INSTANCE_BATCH_SIZE; | |
| u32 count = INSTANCE_BATCH_SIZE; | |
| if (start + count > NUM_BOIDS) count = NUM_BOIDS - start; | |
| renderer_update_instance_buffer(state.instance_buffers[batch], &state.instance_data[start], count); | |
| renderer_draw_mesh_instanced(state.fish_mesh, state.fish_material, state.instance_buffers[batch]); | |
| } | |
| renderer_end_frame(); | |
| if (is_main_thread()) { | |
| input_end_frame(&state.input); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment