diff --git a/src/entity.c b/src/entity.c index 77f438a8..3a35c9c5 100644 --- a/src/entity.c +++ b/src/entity.c @@ -15,6 +15,8 @@ READONLY struct entity _g_entity_nil = { .valid = false, .local_xform = XFORM_IDENT_NOCAST, .cached_global_xform = XFORM_IDENT_NOCAST, + .cached_global_xform_dirty = false, + .verlet_xform = XFORM_IDENT_NOCAST, .sprite_local_xform = XFORM_IDENT_NOCAST, .sprite_tint = COLOR_WHITE }; @@ -24,6 +26,7 @@ GLOBAL READONLY struct entity g_entity_default = { .local_xform = XFORM_IDENT_NOCAST, .cached_global_xform = XFORM_IDENT_NOCAST, .cached_global_xform_dirty = true, + .verlet_xform = XFORM_IDENT_NOCAST, .sprite_local_xform = XFORM_IDENT_NOCAST, .sprite_tint = COLOR_WHITE }; diff --git a/src/entity.h b/src/entity.h index a34bbd7b..d19fd50b 100644 --- a/src/entity.h +++ b/src/entity.h @@ -11,7 +11,7 @@ enum entity_prop { ENTITY_PROP_RELEASE_NEXT_TICK, - ENTITY_PROP_KINEMATIC, + ENTITY_PROP_PHYSICAL, ENTITY_PROP_PLAYER_CONTROLLED, ENTITY_PROP_CAMERA, @@ -103,9 +103,14 @@ struct entity { /* ====================================================================== */ /* Physics */ - /* ENTITY_PROP_KINEMATIC */ - struct v2 acceleration; - struct v2 velocity; + /* ENTITY_PROP_PHYSICAL */ + + /* Xform of the entity from the previous frame (used to calculate velocity) */ + struct xform verlet_xform; + + /* Calculated */ + struct v2 final_velocity; + struct v2 final_acceleration; /* ====================================================================== */ /* Sprite */ diff --git a/src/game.c b/src/game.c index 06173ec2..e77fa248 100644 --- a/src/game.c +++ b/src/game.c @@ -50,8 +50,9 @@ struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr, /* Initialize ticks */ world_alloc(&G.tick); world_alloc(&G.prev_tick); - G.prev_tick_mutex = sys_mutex_alloc(); + /* FIXME: Make the world struct itself readonly as well */ arena_set_readonly(&G.prev_tick.entity_store->arena); + G.prev_tick_mutex = sys_mutex_alloc(); G.tick.timescale = GAME_TIMESCALE; G.game_thread = sys_thread_alloc(&game_thread_entry_point, NULL, STR("[P2] Game thread")); @@ -124,12 +125,12 @@ INTERNAL void spawn_test_entities(void) entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); e->player_max_speed = 4.f; - e->player_acceleration = 20.0f; + e->player_acceleration = 20; e->control.focus = V2(0, -1); player_ent = e; - entity_enable_prop(e, ENTITY_PROP_KINEMATIC); + entity_enable_prop(e, ENTITY_PROP_PHYSICAL); } /* Weapon */ @@ -201,6 +202,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) G.tick.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.tick.timescale); G.tick.time += G.tick.dt; + f64 dt = G.tick.dt; f64 time = G.tick.time; struct entity_store *store = G.tick.entity_store; struct entity *root = entity_from_handle(store, store->root); @@ -284,6 +286,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) if (atick != 0 || G.tick.tick_id >= atick) { entity_enable_prop(ent, ENTITY_PROP_ACTIVE); ent->activation_tick = G.tick.tick_id; + ent->verlet_xform = entity_get_xform(ent); ++ent->continuity_gen; } } @@ -481,14 +484,13 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } /* ========================== * - * Activate triggers + * Trigger equipped * ========================== */ for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; - /* Trigger equipped */ if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) { struct entity *eq = entity_from_handle(store, ent->equipped); if (eq->valid) { @@ -499,7 +501,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } /* ========================== * - * Fire triggers + * Process triggered entities * ========================== */ for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { @@ -541,15 +543,26 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) * Simulate entity physics * ========================== */ + /* TODO: Run physics on top-level entities, and then calculate child xforms as part of physics step (rather than caching at each xform_get). */ for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) { struct entity *ent = &store->entities[entity_index]; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; + if (!entity_has_prop(ent, entity_has_prop(ent, ENTITY_PROP_PHYSICAL))) continue; + + struct xform xf = entity_get_xform(ent); + + /* Calculate velocity from old position */ + + struct v2 velocity = V2(0, 0); + { + velocity = v2_sub(xf.og, ent->verlet_xform.og); + velocity = v2_div(velocity, dt); + ent->verlet_xform = xf; + } /* Player angle */ if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { - struct xform xf = entity_get_xform(ent); - /* Solve for final angle using law of sines */ f32 final_xf_angle; { @@ -598,36 +611,33 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) if (!F32_IS_NAN(final_xf_angle)) { const f32 angle_error_allowed = 0.001; if (math_fabs(final_xf_angle - v2_angle(xf.bx)) > angle_error_allowed) { - entity_set_xform(ent, xform_with_rotation(xf, final_xf_angle)); + xf = xform_with_rotation(xf, final_xf_angle); } } } - /* Player movement */ - - if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { - f32 max_speed = ent->player_max_speed; - f32 acceleration_rate = ent->player_acceleration; - acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */ - struct v2 target_velocity = v2_mul(ent->control.move, max_speed); - struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity); - ent->acceleration = v2_mul(target_acceleration, acceleration_rate); + /* TODO: Accumulate forces to calculate acceleration */ + struct v2 acceleration = V2(0, 0); + { + /* Player movement */ + if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { + f32 max_speed = ent->player_max_speed; + f32 acceleration_rate = ent->player_acceleration; + acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */ + struct v2 target_velocity = v2_mul(ent->control.move, max_speed); + struct v2 target_acceleration = v2_sub(target_velocity, velocity); + acceleration = v2_mul(target_acceleration, acceleration_rate); + } } /* Integrate acceleration & velocity */ + struct v2 acceleration_tick = v2_mul(acceleration, dt * dt); + struct v2 velocity_tick = v2_add(v2_mul(velocity, dt), acceleration_tick); + xf.og = v2_add(xf.og, velocity_tick); - if (entity_has_prop(ent, ENTITY_PROP_KINEMATIC)) { - f32 dt = (f32)G.tick.dt; - struct xform xf = entity_get_xform(ent); - - /* Integrate acceleration to find velocity */ - ent->velocity = v2_add(ent->velocity, v2_mul(ent->acceleration, dt)); - - /* Integrate velocity to find position */ - xf.og = v2_add(xf.og, v2_mul(ent->velocity, dt)); - - entity_set_xform(ent, xf); - } + ent->final_velocity = velocity; + ent->final_acceleration = acceleration; + entity_set_xform(ent, xf); } /* ========================== * @@ -640,7 +650,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) /* FIXME: Apply src entity velocity to bullet velocity */ - if (entity_has_prop(ent, ENTITY_PROP_BULLET) && !entity_has_prop(ent, ENTITY_PROP_KINEMATIC)) { + if (entity_has_prop(ent, ENTITY_PROP_BULLET) && !entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) { struct entity *src = entity_from_handle(store, ent->bullet_src); struct xform src_xf = entity_get_xform(src); @@ -648,12 +658,20 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) struct v2 vec = xform_basis_mul_v2(src_xf, ent->bullet_src_dir); vec = v2_mul(v2_norm(vec), ent->bullet_impulse); +#if 0 + { + struct v2 src_velocity = v2_sub(src_xf.og, src->verlet_xform.og); + src_velocity = v2_div(src_velocity, dt); + vec = v2_add(vec, src_velocity); + } +#endif + struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vec) + PI / 2); entity_set_xform(ent, xf); - ent->velocity = vec; + ent->verlet_xform = XFORM_POS(v2_sub(pos, v2_mul(vec, dt))); - entity_enable_prop(ent, ENTITY_PROP_KINEMATIC); + entity_enable_prop(ent, ENTITY_PROP_PHYSICAL); } } @@ -735,7 +753,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) * Publish tick * ========================== */ - /* Update cached global xforms to ensure they're accurate in published tick */ + /* Update cached global xforms to ensure they're accurate in published tick */ { struct temp_arena temp = arena_temp_begin(scratch.arena); diff --git a/src/user.c b/src/user.c index b8fa1c4a..0473bdc1 100644 --- a/src/user.c +++ b/src/user.c @@ -387,8 +387,8 @@ INTERNAL void debug_draw_movement(struct entity *ent) struct xform xf = entity_get_xform(ent); struct v2 pos = xform_mul_v2(G.world_view, xf.og); - struct v2 vel_ray = xform_basis_mul_v2(G.world_view, ent->velocity); - struct v2 acc_ray = xform_basis_mul_v2(G.world_view, ent->acceleration); + struct v2 vel_ray = xform_basis_mul_v2(G.world_view, ent->final_velocity); + struct v2 acc_ray = xform_basis_mul_v2(G.world_view, ent->final_acceleration); draw_solid_arrow_ray(G.viewport_canvas, pos, vel_ray, thickness, arrow_len, color_vel); draw_solid_arrow_ray(G.viewport_canvas, pos, acc_ray, thickness, arrow_len, color_acc); @@ -468,8 +468,10 @@ INTERNAL void user_update(void) e->local_xform = xform_lerp(e0->local_xform, e1->local_xform, tick_blend); e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, tick_blend); - e->acceleration = v2_lerp(e0->acceleration, e1->acceleration, tick_blend); - e->velocity = v2_lerp(e0->velocity, e1->velocity, tick_blend); + e->verlet_xform = xform_lerp(e0->verlet_xform, e1->verlet_xform, tick_blend); + e->final_acceleration = v2_lerp(e0->final_acceleration, e1->final_acceleration, tick_blend); + e->final_velocity = v2_lerp(e0->final_velocity, e1->final_velocity, tick_blend); + e->player_acceleration = math_lerp(e0->player_acceleration, e1->player_acceleration, tick_blend); e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend);