#include "sim_step.h" #include "sim_ent.h" #include "sim.h" #include "sys.h" #include "util.h" #include "sprite.h" #include "math.h" #include "scratch.h" #include "atomic.h" #include "app.h" #include "log.h" #include "phys.h" #include "collider.h" #include "rng.h" #include "space.h" #include "bitbuff.h" #include "host.h" /* ========================== * * Ent lookup * ========================== */ struct sim_lookup sim_lookup_alloc(u64 num_buckets) { ASSERT(num_buckets > 0); struct sim_lookup l = ZI; l.arena = arena_alloc(GIGABYTE(64)); l.num_buckets = num_buckets; sim_lookup_reset(&l); return l; } void sim_lookup_release(struct sim_lookup *l) { arena_release(&l->arena); } void sim_lookup_reset(struct sim_lookup *l) { arena_reset(&l->arena); l->buckets = arena_push_array_zero(&l->arena, struct sim_lookup_bucket, l->num_buckets); l->first_free_entry = NULL; } struct sim_lookup_entry *sim_lookup_get(struct sim_lookup *l, struct sim_lookup_key key) { u64 index = key.hash % l->num_buckets; struct sim_lookup_bucket *bucket = &l->buckets[index]; struct sim_lookup_entry *res = NULL; for (struct sim_lookup_entry *e = bucket->first; e; e = e->next) { if (e->key.hash == key.hash) { res = e; break; } } return res; } void sim_lookup_set(struct sim_lookup *l, struct sim_lookup_key key, struct sim_ent_handle handle) { u64 index = key.hash % l->num_buckets; struct sim_lookup_bucket *bucket = &l->buckets[index]; struct sim_lookup_entry *prev = NULL; struct sim_lookup_entry **slot = &bucket->first; while (*slot) { if ((*slot)->key.hash == key.hash) { break; } prev = *slot; slot = &(*slot)->next; } struct sim_lookup_entry *entry = *slot; if (entry) { /* Set existing entry */ entry->ent = handle; } else { /* Allocate entry */ if (l->first_free_entry) { entry = l->first_free_entry; l->first_free_entry->prev = NULL; l->first_free_entry = entry->next; } else { entry = arena_push(&l->arena, struct sim_lookup_entry); } MEMZERO_STRUCT(entry); entry->key = key; entry->ent = handle; if (prev) { entry->prev = prev; prev->next = entry; } bucket->last = entry; *slot = entry; } } void sim_lookup_remove(struct sim_lookup *l, struct sim_lookup_entry *entry) { struct sim_lookup_bucket *bucket = &l->buckets[entry->key.hash % l->num_buckets]; struct sim_lookup_entry *prev = entry->prev; struct sim_lookup_entry *next = entry->next; if (prev) { prev->next = next; } else { bucket->first = next; } if (next) { next->prev = prev; } else { bucket->last = prev; } if (l->first_free_entry) { l->first_free_entry->prev = entry; } entry->next = l->first_free_entry; entry->prev = NULL; l->first_free_entry = entry; } struct sim_lookup_key sim_lookup_key_from_client_and_ent_handles(struct sim_client_handle client_handle, struct sim_ent_handle ent_handle) { struct sim_lookup_key key = ZI; struct string b0 = STRING_FROM_STRUCT(&client_handle); struct string b1 = STRING_FROM_STRUCT(&ent_handle); key.hash = hash_fnv64(HASH_FNV64_BASIS, b0); key.hash = hash_fnv64(key.hash, b1); return key; } struct sim_lookup_key sim_lookup_key_from_two_handles(struct sim_ent_handle h0, struct sim_ent_handle h1) { struct sim_lookup_key key = ZI; struct string b0 = STRING_FROM_STRUCT(&h0); struct string b1 = STRING_FROM_STRUCT(&h1); key.hash = hash_fnv64(HASH_FNV64_BASIS, b0); key.hash = hash_fnv64(key.hash, b1); return key; } struct sim_lookup_key sim_lookup_key_from_client_handle(struct sim_client_handle handle) { struct sim_lookup_key key = ZI; key.hash = hash_fnv64(HASH_FNV64_BASIS, STRING_FROM_STRUCT(&handle)); return key; } /* ========================== * * Sim accel * ========================== */ struct sim_accel sim_accel_alloc(void) { struct sim_accel accel = ZI; accel.space = space_alloc(1, 256); accel.remote_lookup = sim_lookup_alloc(4096); accel.client_lookup = sim_lookup_alloc(4096); accel.contact_lookup = sim_lookup_alloc(4096); #if COLLIDER_DEBUG accel.collision_debug_lookup = sim_lookup_alloc(4096); #endif return accel; } void sim_accel_release(struct sim_accel *accel) { #if COLLIDER_DEBUG sim_lookup_release(&accel->collision_debug_lookup); #endif sim_lookup_release(&accel->contact_lookup); sim_lookup_release(&accel->client_lookup); sim_lookup_release(&accel->remote_lookup); space_release(accel->space); } void sim_accel_rebuild(struct sim_snapshot *ss, struct sim_accel *accel) { /* FIXME: Rebuild collision debug lookup */ space_reset(accel->space); sim_lookup_reset(&accel->remote_lookup); sim_lookup_reset(&accel->client_lookup); sim_lookup_reset(&accel->contact_lookup); /* Reset ent space handles */ for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; MEMZERO_STRUCT(&ent->space_handle); } /* NOTE: Not rebuilding space since it'll happen during phys step */ /* Rebuild lookup tables */ for (u64 sim_ent_index = 0; sim_ent_index < ss->num_ents_reserved; ++sim_ent_index) { struct sim_ent *ent = &ss->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_REMOTE)) { struct sim_lookup_key key = sim_lookup_key_from_client_and_ent_handles(ent->remote_client, ent->remote_ent); sim_lookup_set(&accel->remote_lookup, key, ent->handle); } if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { struct sim_lookup_key key = sim_lookup_key_from_client_handle(ent->client_handle); sim_lookup_set(&accel->client_lookup, key, ent->handle); } if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTACT_CONSTRAINT)) { struct sim_lookup_key contact_lookup_key = sim_lookup_key_from_two_handles(ent->contact_constraint_data.e0, ent->contact_constraint_data.e1); sim_lookup_set(&accel->contact_lookup, contact_lookup_key, ent->handle); } } } /* ========================== * * Test * ========================== */ /* TODO: Remove this */ INTERNAL void spawn_test_entities(struct sim_snapshot *world, struct v2 offset) { struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); root->mass_unscaled = F32_INFINITY; root->inertia_unscaled = F32_INFINITY; /* Enemy */ { struct sim_ent *e = sim_ent_alloc(root); struct v2 pos = V2(1, -2); pos = v2_add(pos, offset); f32 r = 0; struct v2 size = V2(1, 1); struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); sim_ent_set_xform(e, xf); e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); e->sprite_collider_slice = LIT("shape"); e->layer = SIM_LAYER_SHOULDERS; sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); e->mass_unscaled = 10; e->inertia_unscaled = 10; e->linear_ground_friction = 250; e->angular_ground_friction = 200; } /* Big box */ #if 1 { struct sim_ent *e = sim_ent_alloc(root); struct v2 pos = V2(1, -0.5); pos = v2_add(pos, offset); f32 r = 0; struct v2 size = V2(0.5, 0.25); struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); sim_ent_set_xform(e, xf); e->sprite = sprite_tag_from_path(LIT("res/graphics/box.ase")); e->sprite_collider_slice = LIT("shape"); e->layer = SIM_LAYER_SHOULDERS; sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); e->mass_unscaled = 100; e->inertia_unscaled = 50; e->linear_ground_friction = 100; e->angular_ground_friction = 50; } #endif /* Tiny box */ #if 0 { struct sim_ent *e = sim_ent_alloc(root); struct v2 pos = V2(1, -0.5); pos = v2_add(pos, offset); f32 r = PI / 4; struct v2 size = V2(0.5, 0.25); struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); sim_ent_set_xform(e, xf); e->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); e->sprite_collider_slice = LIT("shape"); e->layer = SIM_LAYER_SHOULDERS; sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); e->mass_unscaled = 0.5; e->inertia_unscaled = 1000; e->linear_ground_friction = 0.001; } #endif } INTERNAL struct sim_ent *spawn_test_player(struct sim_snapshot *world) { struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); /* Player */ struct sim_ent *player_ent = sim_ent_nil(); //if (!ctx->extra_spawn) { { struct sim_ent *e = sim_ent_alloc(root); struct v2 pos = V2(1, -1); //struct v2 size = V2(0.5, 0.5); //struct v2 size = V2(0.5, 0.25); struct v2 size = V2(1.0, 1.0); //f32 r = PI / 4; f32 r = 0; { sim_ent_enable_prop(e, SIM_ENT_PROP_TEST); e->sprite = sprite_tag_from_path(LIT("res/graphics/tim.ase")); e->mass_unscaled = 10; e->inertia_unscaled = 5; } //e->sprite = sprite_tag_from_path(LIT("res/graphics/box_rounded.ase")); //e->sprite_span_name = LIT("idle.unarmed"); //e->sprite_span_name = LIT("idle.one_handed"); e->sprite_span_name = LIT("idle.two_handed"); e->layer = SIM_LAYER_SHOULDERS; e->local_collider.points[0] = V2(0, 0); e->local_collider.count = 1; e->local_collider.radius = 0.15f; struct xform xf = XFORM_TRS(.t = pos, .r = r, .s = size); //xf.bx.y = -1.f; sim_ent_set_xform(e, xf); e->linear_ground_friction = 250; e->angular_ground_friction = 200; e->friction = 0; //e->control_force = 500; e->control_force = 500; e->control_force_max_speed = 4; //e->control_torque = 5000; e->control_torque = F32_INFINITY; sim_ent_enable_prop(e, SIM_ENT_PROP_PHYSICAL_DYNAMIC); player_ent = e; } /* Player weapon */ if (player_ent->valid) { struct sim_ent *e = sim_ent_alloc(player_ent); e->sprite = sprite_tag_from_path(LIT("res/graphics/gun.ase")); sim_ent_enable_prop(e, SIM_ENT_PROP_ATTACHED); e->attach_slice = LIT("attach.wep"); e->layer = SIM_LAYER_RELATIVE_WEAPON; sim_ent_enable_prop(e, SIM_ENT_PROP_WEAPON); e->trigger_delay = 1.0f / 10.0f; //e->trigger_delay = 1.0f / 100.0f; player_ent->equipped = e->handle; } return player_ent; } INTERNAL struct sim_ent *spawn_test_player_camera(struct sim_snapshot *world, struct sim_ent *player_ent) { struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); struct sim_ent *camera_ent = sim_ent_nil(); if (player_ent->valid) { camera_ent = sim_ent_alloc(root); sim_ent_set_xform(camera_ent, XFORM_IDENT); sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA); sim_ent_enable_prop(camera_ent, SIM_ENT_PROP_CAMERA_ACTIVE); camera_ent->camera_follow = player_ent->handle; f32 width = (f32)DEFAULT_CAMERA_WIDTH; f32 height = (f32)DEFAULT_CAMERA_HEIGHT; camera_ent->camera_quad_xform = XFORM_TRS(.s = V2(width, height)); } return camera_ent; } INTERNAL void test_clear_level(struct sim_snapshot *world) { for (u64 j = 0; j < world->num_ents_reserved; ++j) { struct sim_ent *ent = &world->ents[j]; if (ent->valid) { sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE); } } } /* ========================== * * Release entities * ========================== */ #if 0 INTERNAL void release_entities_with_prop(struct sim_snapshot *ss_blended, enum sim_ent_prop prop) { struct temp_arena scratch = scratch_begin_no_conflict(); struct space *space = ss_blended->space; struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *); u64 ents_to_release_count = 0; for (u64 ent_index = 0; ent_index < ss_blended->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &ss_blended->ents[ent_index]; if (ent->valid && sim_ent_has_prop(ent, prop)) { *arena_push(scratch.arena, struct sim_ent *) = ent; ++ents_to_release_count; } } /* Release references */ for (u64 i = 0; i < ents_to_release_count; ++i) { struct sim_ent *ent = ents_to_release[i]; /* Release space entry */ { struct space_entry *space_entry = space_entry_from_handle(space, ent->space_handle); if (space_entry->valid) { space_entry_release(space_entry); } } } /* Release from snapshot */ /* TODO: Breadth first iteration to only release parent entities (since * child entities will be released along with parent anyway) */ for (u64 i = 0; i < ents_to_release_count; ++i) { struct sim_ent *ent = ents_to_release[i]; if (ent->is_top && !ent->is_root) { sim_ent_release(ent); } } scratch_end(scratch); } #else INTERNAL void release_entities_with_prop(struct sim_snapshot *world, enum sim_ent_prop prop) { struct temp_arena scratch = scratch_begin_no_conflict(); struct sim_ent **ents_to_release = arena_dry_push(scratch.arena, struct sim_ent *); u64 ents_to_release_count = 0; for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (ent->valid && sim_ent_has_prop(ent, prop)) { *arena_push(scratch.arena, struct sim_ent *) = ent; ++ents_to_release_count; } } /* Release from snapshot */ /* TODO: Breadth first iteration to only release parent entities (since * child entities will be released along with parent anyway) */ for (u64 i = 0; i < ents_to_release_count; ++i) { struct sim_ent *ent = ents_to_release[i]; if (ent->is_top && !ent->is_root) { sim_ent_release(ent); } } scratch_end(scratch); } #endif /* ========================== * * Respond to physics collisions * ========================== */ INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, collision_data_array, step_ctx) { struct sim_snapshot *world = step_ctx->world; struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); for (u64 i = 0; i < collision_data_array.count; ++i) { struct phys_collision_data *data = &collision_data_array.a[i]; struct phys_contact_constraint *constraint = data->constraint; struct sim_ent *e0 = sim_ent_from_handle(world, data->e0); struct sim_ent *e1 = sim_ent_from_handle(world, data->e1); if (sim_ent_is_valid_and_active(e0) && sim_ent_is_valid_and_active(e1)) { /* Bullet hit entity */ if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET) || sim_ent_has_prop(e1, SIM_ENT_PROP_BULLET)) { struct v2 normal = data->normal; /* Impact normal */ struct v2 vrel = v2_neg(data->vrel); /* Impact velocity */ struct sim_ent *target = e0; struct sim_ent *bullet = e1; if (sim_ent_has_prop(e0, SIM_ENT_PROP_BULLET)) { target = e1; bullet = e0; normal = v2_neg(normal); vrel = v2_neg(vrel); } struct sim_ent *src = sim_ent_from_handle(world, bullet->bullet_src); if (bullet->bullet_has_hit || sim_ent_handle_eq(src->top, target->top)) { /* Ignore collision if bullet already spent or if weapon and * target share same top level parent */ /* NOTE: Since bullet is most likely just a sensor skip_solve is probably already true */ constraint->skip_solve = true; } else { struct v2 point = data->point; /* Update bullet */ bullet->bullet_has_hit = true; sim_ent_enable_prop(bullet, SIM_ENT_PROP_RELEASE); /* Update tracer */ struct sim_ent *tracer = sim_ent_from_handle(world, bullet->bullet_tracer); if (sim_ent_is_valid_and_active(tracer)) { struct xform xf = sim_ent_get_xform(tracer); xf.og = point; sim_ent_set_xform(tracer, xf); sim_ent_set_linear_velocity(tracer, V2(0, 0)); } /* Update target */ struct v2 knockback = v2_mul(v2_norm(vrel), bullet->bullet_knockback); sim_ent_apply_linear_impulse(target, knockback, point); /* Create test blood */ /* TODO: Remove this */ { struct xform xf = XFORM_TRS(.t = point, .r = rng_rand_f32(0, TAU)); struct sim_ent *decal = sim_ent_alloc(root); decal->sprite = sprite_tag_from_path(LIT("res/graphics/blood.ase")); decal->sprite_tint = RGBA_32_F(1, 1, 1, 0.25f); decal->layer = SIM_LAYER_FLOOR_DECALS; sim_ent_set_xform(decal, xf); f32 perp_range = 0.5; struct v2 linear_velocity = v2_mul(normal, 0.5); linear_velocity = v2_add(linear_velocity, v2_mul(v2_perp(normal), rng_rand_f32(-perp_range, perp_range))); f32 angular_velocity_range = 5; f32 angular_velocity = rng_rand_f32(-angular_velocity_range, angular_velocity_range); sim_ent_enable_prop(decal, SIM_ENT_PROP_PHYSICAL_KINEMATIC); sim_ent_set_linear_velocity(decal, linear_velocity); sim_ent_set_angular_velocity(decal, angular_velocity); decal->linear_damping = 5.0f; decal->angular_damping = 5.0f; } } } } } } /* ========================== * * Update * ========================== */ void sim_step(struct sim_step_ctx *ctx) { __prof; struct temp_arena scratch = scratch_begin_no_conflict(); struct sim_snapshot *world = ctx->world; i64 sim_dt_ns = ctx->sim_dt_ns; /* ========================== * * Begin frame * ========================== */ //sys_sleep_precise(rng_rand_f32(0, 0.050)); //sys_sleep_precise(0.050); world->sim_dt_ns = max_i64(0, sim_dt_ns); world->sim_time_ns += world->sim_dt_ns; f32 sim_dt = SECONDS_FROM_NS(world->sim_dt_ns); struct sprite_scope *sprite_frame_scope = sprite_scope_begin(); struct sim_ent *root = sim_ent_from_handle(world, SIM_ENT_ROOT_HANDLE); /* ========================== * * Release entities * ========================== */ release_entities_with_prop(world, SIM_ENT_PROP_RELEASE); if (ctx->is_master) { /* ========================== * * Activate entities * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!ent->valid) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ACTIVE)) { u64 atick = ent->activation_tick; if (atick != 0 || world->tick >= atick) { sim_ent_activate(ent, world->tick); } } } /* ========================== * * Spawn test entities * ========================== */ /* TODO: remove this (testing) */ /* Initialize entities */ { static b32 run = 0; if (!run) { run = 1; spawn_test_entities(world, V2(0, 0)); } } /* ========================== * * Reset triggered entities * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK)) { sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGER_NEXT_TICK); sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); } else if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) { sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK); } } /* ========================== * * Process client cmds * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *cmd_ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(cmd_ent)) continue; if (sim_ent_has_prop(cmd_ent, SIM_ENT_PROP_CMD_CONTROL)) { struct sim_ent *client_ent = sim_ent_from_handle(world, cmd_ent->cmd_client); if (sim_ent_is_valid_and_active(client_ent)) { /* Process control cmd for client */ struct sim_control old_control = client_ent->client_control; struct sim_control *control = &client_ent->client_control; *control = cmd_ent->cmd_control; { client_ent->client_dbg_drag_start = false; client_ent->client_dbg_drag_stop = false; if (v2_len_sq(control->move) > 1) { /* Cap movement vector magnitude at 1 */ control->move = v2_norm(control->move); } /* Determine cursor pos from focus */ { struct sim_ent_handle client_control_ent_handle = client_ent->client_control_ent; struct sim_ent *client_control_ent = sim_ent_from_handle(world, client_control_ent_handle); if (client_control_ent->valid || sim_ent_handle_eq(client_control_ent_handle, SIM_ENT_NIL_HANDLE)) { /* Only update cursor pos if focus ent is valid (or nil) */ client_ent->client_cursor_pos = v2_add(sim_ent_get_xform(client_control_ent).og, client_ent->client_control.focus); } } u32 flags = control->flags; if (flags & SIM_CONTROL_FLAG_DRAG) { if (!(old_control.flags & SIM_CONTROL_FLAG_DRAG)) { client_ent->client_dbg_drag_start = true; } } else { if (old_control.flags & SIM_CONTROL_FLAG_DRAG) { client_ent->client_dbg_drag_stop = true; } } if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) { test_clear_level(world); } } if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) { logf_info("Spawning (test)"); u32 count = 1; f32 spread = 1; for (u32 j = 0; j < count; ++j) { spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); } } } } } } } #if 0 for (struct sim_snapshot_list_node *n = cmd_snapshots->first; n; n = n->next) { struct sim_snapshot *cmd_snapshot = n->ss; struct sim_client_handle client_handle = cmd_snapshot->producer_client; struct sim_lookup_key key = sim_lookup_key_from_client_handle(client_handle); struct sim_lookup_entry *client_entry = sim_lookup_get(&accel->client_lookup, key); struct sim_ent *client_ent = client_entry ? sim_ent_from_handle(world, client_entry->ent) : sim_ent_nil(); /* Create client ent if it doesn't exist */ /* FIXME: Client ent never released upon disconnect */ if (!client_ent->valid) { client_ent = sim_ent_alloc(root); client_ent->client_handle = client_handle; sim_ent_enable_prop(client_ent, SIM_ENT_PROP_CLIENT); if (cmd_snapshot->producer_client_is_local) { sim_ent_enable_prop(client_ent, SIM_ENT_PROP_LOCAL_CLIENT); } sim_ent_enable_prop(client_ent, SIM_ENT_PROP_ACTIVE); /* FIXME: Remove from lookup table when client disconnects */ sim_lookup_set(&accel->client_lookup, key, client_ent->handle); } /* Process client control */ /* TODO: Create client cmd ents here and process them separately */ struct sim_control old_control = client_ent->client_control; struct sim_control *control = &client_ent->client_control; *control = cmd_snapshot->control; { client_ent->client_dbg_drag_start = false; client_ent->client_dbg_drag_stop = false; if (v2_len_sq(control->move) > 1) { /* Cap movement vector magnitude at 1 */ control->move = v2_norm(control->move); } /* Determine cursor pos from focus */ { struct sim_ent_handle client_control_ent_handle = client_ent->client_control_ent; struct sim_ent *client_control_ent = sim_ent_from_handle(world, client_control_ent_handle); if (client_control_ent->valid || sim_ent_handle_eq(client_control_ent_handle, SIM_ENT_NIL_HANDLE)) { /* Only update cursor pos if focus ent is valid (or nil) */ client_ent->client_cursor_pos = v2_add(sim_ent_get_xform(client_control_ent).og, client_ent->client_control.focus); } } u32 flags = control->flags; if (flags & SIM_CONTROL_FLAG_DRAG) { if (!(old_control.flags & SIM_CONTROL_FLAG_DRAG)) { client_ent->client_dbg_drag_start = true; } } else { if (old_control.flags & SIM_CONTROL_FLAG_DRAG) { client_ent->client_dbg_drag_stop = true; } } if (flags & SIM_CONTROL_FLAG_CLEAR_ALL) { if (!(old_control.flags & SIM_CONTROL_FLAG_CLEAR_ALL)) { test_clear_level(world); } } if (flags & SIM_CONTROL_FLAG_SPAWN_TEST) { if (!(old_control.flags & SIM_CONTROL_FLAG_SPAWN_TEST)) { logf_info("Spawning (test)"); u32 count = 1; f32 spread = 1; for (u32 j = 0; j < count; ++j) { spawn_test_entities(world, V2(0, (((f32)j / (f32)count) - 0.5) * spread)); } } } } } #endif /* ========================== * * Create client player ents * ========================== */ for (u64 i = 0; i < world->num_ents_reserved; ++i) { struct sim_ent *ent = &world->ents[i]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_CLIENT)) { /* FIXME: Ents never released when client disconnects */ struct sim_ent *control_ent = sim_ent_from_handle(world, ent->client_control_ent); if (!control_ent->valid) { control_ent = spawn_test_player(world); sim_ent_enable_prop(control_ent, SIM_ENT_PROP_CONTROLLED); ent->client_control_ent = control_ent->handle; control_ent->controlling_client = ent->handle; } struct sim_ent *camera_ent = sim_ent_from_handle(world, ent->client_camera_ent); if (!camera_ent->valid) { camera_ent = spawn_test_player_camera(world, control_ent); ent->client_camera_ent = camera_ent->handle; } } } /* ========================== * * Update entity control from client control * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { struct sim_ent *client_ent = sim_ent_from_handle(world, ent->controlling_client); if (client_ent->valid) { ent->control = client_ent->client_control; /* TODO: Move this */ if (ent->control.flags & SIM_CONTROL_FLAG_FIRE) { sim_ent_enable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); } else { sim_ent_disable_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED); } } } } /* ========================== * * Update entities from sprite * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sprite_tag_is_nil(ent->sprite)) continue; struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); /* Update animation */ { struct sprite_sheet_span span = sprite_sheet_get_span(sheet, ent->sprite_span_name); if (ent->animation_last_frame_change_time_ns == 0) { ent->animation_last_frame_change_time_ns = SECONDS_FROM_NS(world->sim_time_ns); } f64 time_in_frame = SECONDS_FROM_NS(world->sim_time_ns - ent->animation_last_frame_change_time_ns); u64 frame_index = ent->animation_frame; if (frame_index < span.start || frame_index > span.end) { frame_index = span.start; } if (span.end > span.start + 1) { struct sprite_sheet_frame frame = sprite_sheet_get_frame(sheet, frame_index); while (time_in_frame > frame.duration) { time_in_frame -= frame.duration; ++frame_index; if (frame_index > span.end) { /* Loop animation */ frame_index = span.start; } frame = sprite_sheet_get_frame(sheet, frame_index); ent->animation_last_frame_change_time_ns = world->sim_time_ns; } } ent->animation_frame = frame_index; } /* Update sprite local xform */ { struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("pivot"), ent->animation_frame); struct v2 sprite_size = v2_div(sheet->frame_size, (f32)IMAGE_PIXELS_PER_UNIT); struct v2 dir = v2_mul_v2(sprite_size, slice.dir); f32 rot = v2_angle(dir) + PI / 2; struct xform xf = XFORM_IDENT; xf = xform_rotated(xf, -rot); xf = xform_scaled(xf, sprite_size); xf = xform_translated(xf, v2_neg(slice.center)); ent->sprite_local_xform = xf; } /* Update collider from sprite */ if (ent->sprite_collider_slice.len > 0) { struct xform cxf = ent->sprite_local_xform; struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, ent->sprite_collider_slice, ent->animation_frame); ent->local_collider = collider_from_quad(xform_mul_quad(cxf, quad_from_rect(slice.rect))); } /* Test collider */ #if 0 if (sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) { //if ((true)) { #if 0 ent->local_collider.points[0] = V2(0, 0); ent->local_collider.count = 1; ent->local_collider.radius = 0.5; #elif 0 ent->local_collider.points[0] = v2_with_len(V2(0.08f, 0.17f), 0.15f); ent->local_collider.points[1] = v2_with_len(V2(-0.07f, -0.2f), 0.15f); ent->local_collider.count = 2; ent->local_collider.radius = 0.075f; #elif 1 #if 0 /* "Bad" winding order */ ent->local_collider.points[0] = V2(-0.15, 0.15); ent->local_collider.points[1] = V2(0.15, 0.15); ent->local_collider.points[2] = V2(0, -0.15); #else ent->local_collider.points[0] = V2(0, -0.15); ent->local_collider.points[1] = V2(0.15, 0.15); ent->local_collider.points[2] = V2(-0.15, 0.15); #endif ent->local_collider.count = 3; ent->local_collider.radius = 0.25; //ent->local_collider.radius = math_fabs(math_sin(ctx->tick.time) / 3); #else //ent->local_collider.radius = 0.5; ent->local_collider.radius = 0.25; //ent->local_collider.radius = 0.; #endif } #endif } /* ========================== * * Update attachments * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_ATTACHED)) continue; struct sim_ent *parent = sim_ent_from_handle(world, ent->parent); struct sprite_tag parent_sprite = parent->sprite; struct sprite_sheet *parent_sheet = sprite_sheet_from_tag_await(sprite_frame_scope, parent_sprite); struct xform parent_sprite_xf = parent->sprite_local_xform; struct sprite_sheet_slice attach_slice = sprite_sheet_get_slice(parent_sheet, ent->attach_slice, parent->animation_frame); struct v2 attach_pos = xform_mul_v2(parent_sprite_xf, attach_slice.center); struct v2 attach_dir = xform_basis_mul_v2(parent_sprite_xf, attach_slice.dir); struct xform xf = sim_ent_get_local_xform(ent); xf.og = attach_pos; xf = xform_basis_with_rotation_world(xf, v2_angle(attach_dir) + PI / 2); sim_ent_set_local_xform(ent, xf); } /* ========================== * * Test * ========================== */ #if 0 for (u64 ent_index = 0; ent_index < ss_blended->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &ss_blended->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TEST)) continue; #if 0 if (!ent->test_initialized) { ent->test_initialized = true; ent->test_start_local_xform = sim_ent_get_local_xform(ent); ent->test_start_sprite_xform = ent->sprite_local_xform; } f32 t = (f32)time; struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); f32 r = t * 3; struct v2 s = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); (UNUSED)og; (UNUSED)r; (UNUSED)s; og = v2_add(og, ent->test_start_local_xform.og); r += xform_get_rotation(ent->test_start_local_xform); s = v2_add(s, xform_get_scale(ent->test_start_local_xform)); struct xform xf = sim_ent_get_local_xform(ent); xf.og = og; xf = xform_rotated_to(xf, r); xf = xform_scaled_to(xf, s); sim_ent_set_local_xform(ent, xf); #else f32 t = (f32)time; struct v2 og = v2_mul(V2(math_cos(t), math_sin(t)), 3); f32 rot = t * PI / 3; struct v2 scale = V2(1 + (math_fabs(math_sin(t * 5)) * 3), 1); (UNUSED)og; (UNUSED)rot; (UNUSED)scale; struct xform xf = sim_ent_get_local_xform(ent); xf = xform_rotated_to(xf, rot); xf = xform_scaled_to(xf, scale); sim_ent_set_local_xform(ent, xf); #endif } #endif /* ========================== * * Trigger equipped * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERING_EQUIPPED)) { struct sim_ent *eq = sim_ent_from_handle(world, ent->equipped); if (sim_ent_is_valid_and_active(eq)) { sim_ent_enable_prop(eq, SIM_ENT_PROP_TRIGGERED_THIS_TICK); } } } /* ========================== * * Process triggered entities * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRIGGERED_THIS_TICK)) continue; if ((world->sim_time_ns - ent->last_triggered_ns < NS_FROM_SECONDS(ent->trigger_delay)) && ent->last_triggered_ns != 0) continue; ent->last_triggered_ns = world->sim_time_ns; /* Fire weapon */ if (sim_ent_has_prop(ent, SIM_ENT_PROP_WEAPON)) { struct sprite_tag sprite = ent->sprite; u32 animation_frame = ent->animation_frame; struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, sprite); struct xform sprite_local_xform = ent->sprite_local_xform; struct sprite_sheet_slice out_slice = sprite_sheet_get_slice(sheet, LIT("out"), animation_frame); struct v2 rel_pos = xform_mul_v2(sprite_local_xform, out_slice.center); struct v2 rel_dir = xform_basis_mul_v2(sprite_local_xform, out_slice.dir); /* Spawn bullet */ struct sim_ent *bullet; { bullet = sim_ent_alloc(root); bullet->bullet_src = ent->handle; bullet->bullet_src_pos = rel_pos; bullet->bullet_src_dir = rel_dir; //bullet->bullet_impulse = 0.75f; bullet->bullet_impulse = 2.0f; bullet->bullet_knockback = 10; bullet->mass_unscaled = 0.04f; bullet->inertia_unscaled = 0.00001f; bullet->layer = SIM_LAYER_BULLETS; #if 1 /* Point collider */ bullet->local_collider.points[0] = V2(0, 0); bullet->local_collider.count = 1; #else bullet->sprite = sprite_tag_from_path(LIT("res/graphics/bullet.ase")); bullet->sprite_collider_slice = LIT("shape"); #endif sim_ent_enable_prop(bullet, SIM_ENT_PROP_BULLET); sim_ent_enable_prop(bullet, SIM_ENT_PROP_SENSOR); } /* Spawn tracer */ { struct sim_ent *tracer = sim_ent_alloc(root); tracer->tracer_fade_duration = 0.025f; tracer->layer = SIM_LAYER_TRACERS; sim_ent_enable_prop(tracer, SIM_ENT_PROP_TRACER); bullet->bullet_tracer = tracer->handle; } } } /* ========================== * * Create motor joints from control move * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->move_joint); if (!sim_ent_is_valid_and_active(joint_ent)) { joint_ent = sim_ent_alloc(root); joint_ent->mass_unscaled = F32_INFINITY; joint_ent->inertia_unscaled = F32_INFINITY; sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); ent->move_joint = joint_ent->handle; struct phys_motor_joint_def def = ZI; def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ def.e1 = ent->handle; def.correction_rate = 0; def.max_force = ent->control_force; def.max_torque = 0; joint_ent->motor_joint_data = phys_motor_joint_from_def(def); } sim_ent_set_xform(joint_ent, XFORM_IDENT); /* Reset joint ent position */ sim_ent_set_linear_velocity(joint_ent, v2_mul(v2_clamp_len(ent->control.move, 1), ent->control_force_max_speed)); } } /* ========================== * * Create motor joints from control focus (aim) * ========================== */ #if SIM_PLAYER_AIM for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (sim_ent_has_prop(ent, SIM_ENT_PROP_CONTROLLED)) { struct xform xf = sim_ent_get_xform(ent); struct xform sprite_xf = xform_mul(xf, ent->sprite_local_xform); /* Retrieve / create aim joint */ struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->aim_joint); if (!sim_ent_is_valid_and_active(joint_ent)) { joint_ent = sim_ent_alloc(root); joint_ent->mass_unscaled = F32_INFINITY; joint_ent->inertia_unscaled = F32_INFINITY; sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); /* Since we'll be setting velocity manually */ sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); ent->aim_joint = joint_ent->handle; struct phys_motor_joint_def def = ZI; def.e0 = joint_ent->handle; /* Re-using joint entity as e0 */ def.e1 = ent->handle; def.max_force = 0; def.max_torque = ent->control_torque; joint_ent->motor_joint_data = phys_motor_joint_from_def(def); } /* Set correction rate dynamically since motor velocity is only set for one frame */ joint_ent->motor_joint_data.correction_rate = 10 * sim_dt; /* Solve for final angle using law of sines */ f32 new_angle; { struct v2 ent_pos = xf.og; struct v2 focus_pos = v2_add(ent_pos, ent->control.focus); struct v2 sprite_hold_pos; struct v2 sprite_hold_dir; { struct sprite_sheet *sheet = sprite_sheet_from_tag_await(sprite_frame_scope, ent->sprite); struct sprite_sheet_slice slice = sprite_sheet_get_slice(sheet, LIT("attach.wep"), ent->animation_frame); sprite_hold_pos = slice.center; sprite_hold_dir = slice.dir; } struct v2 hold_dir = xform_basis_mul_v2(sprite_xf, sprite_hold_dir); struct v2 hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); if (v2_eq(hold_pos, ent_pos)) { /* If hold pos is same as origin (E.G if pivot is being used as hold pos), then move hold pos forward a tad to avoid issue */ sprite_hold_pos = v2_add(sprite_hold_pos, V2(0, -1)); hold_pos = xform_mul_v2(sprite_xf, sprite_hold_pos); } f32 forward_hold_angle_offset; { struct xform xf_unrotated = xform_basis_with_rotation_world(xf, 0); struct v2 hold_pos_unrotated = xform_mul_v2(xf_unrotated, xform_mul_v2(ent->sprite_local_xform, sprite_hold_pos)); forward_hold_angle_offset = v2_angle_from_dirs(V2(0, -1), v2_sub(hold_pos_unrotated, xf_unrotated.og)); } struct v2 hold_ent_dir = v2_sub(ent_pos, hold_pos); struct v2 focus_ent_dir = v2_sub(ent_pos, focus_pos); f32 hold_ent_len = v2_len(hold_ent_dir); f32 focus_ent_len = v2_len(focus_ent_dir); f32 final_hold_angle_btw_ent_and_focus = v2_angle_from_dirs(hold_ent_dir, hold_dir); f32 final_focus_angle_btw_ent_and_hold = math_asin((math_sin(final_hold_angle_btw_ent_and_focus) * hold_ent_len) / focus_ent_len); f32 final_ent_angle_btw_focus_and_hold = PI - (final_focus_angle_btw_ent_and_hold + final_hold_angle_btw_ent_and_focus); new_angle = math_unwind_angle(v2_angle_from_dirs(V2(0, -1), v2_sub(focus_pos, ent_pos)) + final_ent_angle_btw_focus_and_hold - forward_hold_angle_offset); } f32 new_vel = 0; if (!F32_IS_NAN(new_angle)) { const f32 angle_error_allowed = 0.001f; struct xform joint_xf = sim_ent_get_xform(joint_ent); f32 diff = math_unwind_angle(new_angle - xform_get_rotation(joint_xf)); if (math_fabs(diff) > angle_error_allowed) { /* Instantly snap joint ent to new angle */ new_vel = diff / sim_dt; } } sim_ent_set_angular_velocity(joint_ent, new_vel); } } #endif /* ========================== * * Create motor joints from ground friction (gravity) * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; struct sim_ent *joint_ent = sim_ent_from_handle(world, ent->ground_friction_joint); struct phys_motor_joint_def def = ZI; def.e0 = root->handle; def.e1 = ent->handle; def.correction_rate = 0; def.max_force = ent->linear_ground_friction; def.max_torque = ent->angular_ground_friction; if (joint_ent->motor_joint_data.max_force != def.max_force || joint_ent->motor_joint_data.max_torque != def.max_torque) { if (!sim_ent_is_valid_and_active(joint_ent)) { joint_ent = sim_ent_alloc(root); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOTOR_JOINT); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); joint_ent->motor_joint_data = phys_motor_joint_from_def(def); ent->ground_friction_joint = joint_ent->handle; } } } /* ========================== * * Create mouse joints from client debug drag * ========================== */ for (u64 i = 0; i < world->num_ents_reserved; ++i) { struct sim_ent *client_ent = &world->ents[i]; if (!sim_ent_is_valid_and_active(client_ent)) continue; if (!sim_ent_has_prop(client_ent, SIM_ENT_PROP_CLIENT)) continue; struct v2 cursor = client_ent->client_cursor_pos; b32 start_dragging = client_ent->client_dbg_drag_start; b32 stop_dragging = client_ent->client_dbg_drag_stop; struct sim_ent *joint_ent = sim_ent_from_handle(world, client_ent->client_dbg_drag_joint_ent); struct sim_ent *target_ent = sim_ent_from_handle(world, joint_ent->mouse_joint_data.target); if (start_dragging) { struct xform mouse_xf = xform_from_pos(cursor); struct collider_shape mouse_shape = ZI; mouse_shape.points[0] = V2(0, 0); mouse_shape.count = 1; for (u64 sim_ent_index = 0; sim_ent_index < world->num_ents_reserved; ++sim_ent_index) { struct sim_ent *ent = &world->ents[sim_ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_PHYSICAL_DYNAMIC)) continue; struct collider_shape ent_collider = ent->local_collider; if (ent_collider.count > 0) { struct xform ent_xf = sim_ent_get_xform(ent); /* TODO: Can just use boolean GJK */ struct collider_collision_points_result res = collider_collision_points(&ent_collider, &mouse_shape, ent_xf, mouse_xf); if (res.num_points > 0) { target_ent = ent; break; } } } } if (stop_dragging) { target_ent = sim_ent_nil(); } if (sim_ent_is_valid_and_active(target_ent)) { if (!sim_ent_is_valid_and_active(joint_ent)) { /* FIXME: Joint ent may never release */ joint_ent = sim_ent_alloc(root); joint_ent->mass_unscaled = F32_INFINITY; joint_ent->inertia_unscaled = F32_INFINITY; client_ent->client_dbg_drag_joint_ent = joint_ent->handle; sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_MOUSE_JOINT); sim_ent_enable_prop(joint_ent, SIM_ENT_PROP_ACTIVE); } struct xform xf = sim_ent_get_xform(target_ent); f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf)); struct phys_mouse_joint_def def = ZI; def.target = target_ent->handle; if (sim_ent_handle_eq(joint_ent->mouse_joint_data.target, target_ent->handle)) { def.point_local_start = joint_ent->mouse_joint_data.point_local_start; } else { def.point_local_start = xform_invert_mul_v2(xf, cursor); } def.point_local_end = xform_invert_mul_v2(xf, cursor); def.max_force = mass * 1000; joint_ent->mouse_joint_data = phys_mouse_joint_from_def(def); } else if (sim_ent_is_valid_and_active(joint_ent)) { joint_ent->mouse_joint_data.target = target_ent->handle; } } /* ========================== * * Physics step * ========================== */ { struct phys_step_ctx phys = ZI; phys.sim_step_ctx = ctx; phys.pre_solve_callback = on_collision; phys_step(&phys, sim_dt); } /* ========================== * * Update tracers * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_TRACER)) continue; struct v2 end = sim_ent_get_xform(ent).og; struct v2 tick_velocity = v2_mul(ent->tracer_start_velocity, sim_dt); struct v2 gradient_start = v2_add(ent->tracer_gradient_start, tick_velocity); struct v2 gradient_end = v2_add(ent->tracer_gradient_end, tick_velocity); if (v2_dot(tick_velocity, v2_sub(gradient_start, end)) > 0) { /* Tracer has disappeared */ sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE); } ent->tracer_gradient_start = gradient_start; ent->tracer_gradient_end = gradient_end; } /* ========================== * * Initialize bullet kinematics from sources * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_BULLET)) continue; if (ent->activation_tick == world->tick) { struct sim_ent *src = sim_ent_from_handle(world, ent->bullet_src); struct xform src_xf = sim_ent_get_xform(src); struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos); struct v2 impulse = xform_basis_mul_v2(src_xf, ent->bullet_src_dir); impulse = v2_with_len(impulse, ent->bullet_impulse); #if 0 /* Add shooter velocity to bullet */ { /* TODO: Add angular velocity as well? */ struct sim_ent *top = sim_ent_from_handle(ss_blended, src->top); impulse = v2_add(impulse, v2_mul(top->linear_velocity, dt)); } #endif struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(impulse) + PI / 2); sim_ent_set_xform(ent, xf); sim_ent_enable_prop(ent, SIM_ENT_PROP_PHYSICAL_KINEMATIC); sim_ent_apply_linear_impulse_to_center(ent, impulse); /* Initialize tracer */ struct sim_ent *tracer = sim_ent_from_handle(world, ent->bullet_tracer); if (sim_ent_is_valid_and_active(tracer)) { sim_ent_set_xform(tracer, xf); sim_ent_enable_prop(tracer, SIM_ENT_PROP_PHYSICAL_KINEMATIC); sim_ent_set_linear_velocity(tracer, ent->linear_velocity); tracer->tracer_start = pos; tracer->tracer_start_velocity = ent->linear_velocity; tracer->tracer_gradient_end = pos; tracer->tracer_gradient_start = v2_sub(pos, v2_mul(ent->linear_velocity, tracer->tracer_fade_duration)); } /* Spawn quake */ { struct sim_ent *quake = sim_ent_alloc(root); sim_ent_set_xform(quake, XFORM_POS(pos)); quake->quake_intensity = 0.2f; quake->quake_fade = quake->quake_intensity / 0.1f; sim_ent_enable_prop(quake, SIM_ENT_PROP_QUAKE); } } } /* ========================== * * Update cameras * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_CAMERA)) continue; struct xform xf = sim_ent_get_xform(ent); /* Camera follow */ { struct sim_ent *follow = sim_ent_from_handle(world, ent->camera_follow); f32 aspect_ratio = 1.0; { struct xform quad_xf = xform_mul(sim_ent_get_xform(ent), ent->camera_quad_xform); struct v2 camera_size = xform_get_scale(quad_xf); if (!v2_is_zero(camera_size)) { aspect_ratio = camera_size.x / camera_size.y; } } f32 ratio_y = 0.33f; f32 ratio_x = ratio_y / aspect_ratio; struct v2 camera_focus_dir = v2_mul_v2(follow->control.focus, V2(ratio_x, ratio_y)); struct v2 camera_focus_pos = v2_add(sim_ent_get_xform(follow).og, camera_focus_dir); ent->camera_xform_target = xf; ent->camera_xform_target.og = camera_focus_pos; /* Lerp camera */ if (ent->camera_applied_lerp_continuity_gen_plus_one == ent->camera_lerp_continuity_gen + 1) { f32 t = 1 - math_pow(2.f, -20.f * (f32)sim_dt); xf = xform_lerp(xf, ent->camera_xform_target, t); } else { /* Skip lerp */ xf = ent->camera_xform_target; } ent->camera_applied_lerp_continuity_gen_plus_one = ent->camera_lerp_continuity_gen + 1; } /* Camera shake */ { /* TODO: Update based on distance to quake */ ent->shake = 0; for (u64 quake_ent_index = 0; quake_ent_index < world->num_ents_reserved; ++quake_ent_index) { struct sim_ent *quake = &world->ents[quake_ent_index]; if (!sim_ent_is_valid_and_active(quake)) continue; if (!sim_ent_has_prop(quake, SIM_ENT_PROP_QUAKE)) continue; ent->shake += quake->quake_intensity; } } sim_ent_set_xform(ent, xf); } /* ========================== * * Update quakes * ========================== */ for (u64 ent_index = 0; ent_index < world->num_ents_reserved; ++ent_index) { struct sim_ent *ent = &world->ents[ent_index]; if (!sim_ent_is_valid_and_active(ent)) continue; if (!sim_ent_has_prop(ent, SIM_ENT_PROP_QUAKE)) continue; ent->quake_intensity = max_f32(0, ent->quake_intensity - (ent->quake_fade * sim_dt)); if (ent->quake_intensity <= 0) { sim_ent_enable_prop(ent, SIM_ENT_PROP_RELEASE); } } /* ========================== * * Update relative layers * ========================== */ { struct temp_arena temp = arena_temp_begin(scratch.arena); struct sim_ent **stack = arena_push(temp.arena, struct sim_ent *); u64 stack_count = 1; *stack = root; while (stack_count > 0) { struct sim_ent *parent; arena_pop(temp.arena, struct sim_ent *, &parent); --stack_count; i32 parent_layer = parent->final_layer; for (struct sim_ent *child = sim_ent_from_handle(world, parent->first); child->valid; child = sim_ent_from_handle(world, child->next)) { if (sim_ent_is_valid_and_active(child)) { child->final_layer = parent_layer + child->layer; *arena_push(temp.arena, struct sim_ent *) = child; ++stack_count; } } } arena_temp_end(temp); } } /* ========================== * * Release entities * ========================== */ release_entities_with_prop(world, SIM_ENT_PROP_RELEASE); /* ========================== * * End frame * ========================== */ sprite_scope_end(sprite_frame_scope); scratch_end(scratch); }