From 10c9c833ba5acabe892d12e5dc5ab4a11134065d Mon Sep 17 00:00:00 2001 From: jacob Date: Mon, 19 Aug 2024 19:20:55 -0500 Subject: [PATCH] better impulse & force application --- src/entity.c | 5 +- src/entity.h | 13 +--- src/game.c | 196 +++++++++++++++++++-------------------------------- src/user.c | 15 ++-- 4 files changed, 87 insertions(+), 142 deletions(-) diff --git a/src/entity.c b/src/entity.c index 86999428..1618e9fd 100644 --- a/src/entity.c +++ b/src/entity.c @@ -111,6 +111,7 @@ INTERNAL struct entity *entity_alloc_internal(struct entity_store *store) struct entity *entity_alloc(struct entity *parent) { + ASSERT(parent->valid); struct entity_store *store = entity_get_store(parent); struct entity *e = entity_alloc_internal(store); entity_link_parent(e, parent); @@ -137,7 +138,9 @@ INTERNAL void entity_release_internal(struct entity_store *store, struct entity void entity_release(struct entity_store *store, struct entity *ent) { - entity_unlink_parent(ent); + if (ent->parent.gen) { + entity_unlink_parent(ent); + } entity_release_internal(store, ent); } diff --git a/src/entity.h b/src/entity.h index 347067e5..36f27f19 100644 --- a/src/entity.h +++ b/src/entity.h @@ -90,32 +90,23 @@ struct entity { /* ====================================================================== */ /* Control */ + f32 control_move_force; /* How much force is applied to achieve desired control movement */ struct { struct v2 move; struct v2 focus; } control; - /* ====================================================================== */ - /* Player control */ - - /* ENTITY_PROP_PLAYER_CONTROLLED */ - f32 player_max_speed; - f32 player_acceleration; - /* ====================================================================== */ /* Physics */ /* ENTITY_PROP_PHYSICAL */ f32 density; + f32 ground_friction; /* 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; - /* ====================================================================== */ /* Impulse */ diff --git a/src/game.c b/src/game.c index 115d443c..c1ceea81 100644 --- a/src/game.c +++ b/src/game.c @@ -124,9 +124,10 @@ INTERNAL void spawn_test_entities(void) e->sprite_span_name = STR("idle.two_handed"); entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED); - e->player_max_speed = 4.f; - e->player_acceleration = 20; + e->control_move_force = 50; e->control.focus = V2(0, -1); + e->ground_friction = 12; + e->density = 1.0; player_ent = e; @@ -477,7 +478,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) entity_enable_prop(eq, ENTITY_PROP_TRIGGERED_THIS_TICK); } } - } /* ========================== * @@ -528,120 +528,101 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; struct v2 move = ent->control.move; - if (!v2_eq(move, V2(0, 0))) { - struct xform xf = entity_get_xform(ent); - - struct v2 tick_velocity = v2_sub(xf.og, ent->verlet_xform.og); - - /* 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, v2_div(tick_velocity, dt)); - struct v2 acceleration = v2_mul(target_acceleration, acceleration_rate); - - /* Create force */ - struct entity *f = entity_alloc(ent); - entity_enable_prop(f, ENTITY_PROP_FORCE); - f->force = acceleration; - activate_now(f); - } - } - } - - /* ========================== * - * Create constant forces - * ========================== */ - - /* TODO: Just do this in simulation processing */ - - 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_PROP_PHYSICAL)) continue; - - struct xform xf = entity_get_xform(ent); - struct v2 velocity = v2_div(v2_sub(xf.og, ent->verlet_xform.og), dt); - - /* Ground friction */ - { + if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { struct entity *f = entity_alloc(ent); - - f32 force_len = -v2_len(velocity) * 8; - entity_enable_prop(f, ENTITY_PROP_FORCE); - f->force = v2_mul(v2_norm(velocity), force_len); - + f->force = v2_mul(move, ent->control_move_force); activate_now(f); } } /* ========================== * - * Apply impulses to verlet xform + * Create ground friction force * ========================== */ + /* TODO: Do this globally rather than creating entities for constant forces */ 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_PROP_PHYSICAL)) continue; + if (ent->ground_friction != 0) { + struct xform verlet_xform = ent->verlet_xform; + struct xform xf = entity_get_xform(ent); + struct v2 velocity = v2_div(v2_sub(xf.og, verlet_xform.og), dt); - /* FIXME: Why does marking all ents with ENTITY_PROP_RELEASE here raise exception */ - - if (entity_has_prop(ent, ENTITY_PROP_IMPULSE)) { - struct entity *parent = entity_from_handle(store, ent->parent); - if (entity_has_prop(parent, ENTITY_PROP_ACTIVE)) { - struct v2 tick_velocity = v2_mul(ent->impulse, dt); - parent->verlet_xform.og = v2_sub(parent->verlet_xform.og, tick_velocity); - entity_enable_prop(ent, ENTITY_PROP_RELEASE); + /* Ground friction */ + if (!v2_eq(velocity, V2(0, 0))) { + /* FIXME: Incorrect behavior at low FPS & low entity density */ + const f32 clamp_epsilon = 0.025; + f32 velocity_len = v2_len(velocity); + if (velocity_len >= clamp_epsilon) { + f32 force_len = -velocity_len * ent->ground_friction; + struct entity *force = entity_alloc(ent); + entity_enable_prop(force, ENTITY_PROP_FORCE); + force->force = v2_mul(v2_norm(velocity), force_len); + activate_now(force); + } else { + /* If velocity is below clamp_epsilon, stop entity movement. */ + struct entity *impulse = entity_alloc(ent); + entity_enable_prop(impulse, ENTITY_PROP_IMPULSE); + impulse->impulse = v2_neg(velocity); + activate_now(impulse); + } } } } /* ========================== * - * Apply forces to verlet xform - * ========================== */ - - 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; - - f32 density = ent->density; - f32 mass = density; /* TODO: Real mass calculation */ - - if (entity_has_prop(ent, ENTITY_PROP_FORCE)) { - struct entity *parent = entity_from_handle(store, ent->parent); - if (entity_has_prop(parent, ENTITY_PROP_ACTIVE)) { - struct v2 acceleration = v2_div(ent->force, mass); - struct v2 tick_acceleration = v2_mul(acceleration, dt * dt); - parent->verlet_xform.og = v2_sub(parent->verlet_xform.og, tick_acceleration); - entity_enable_prop(ent, ENTITY_PROP_RELEASE); - } - } - } - - /* ========================== * - * Simulate entity physics + * Simulate 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_PROP_PHYSICAL)) continue; + if (entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) { + f32 density = ent->density; + f32 mass = density; /* TODO: Real mass calculation */ + struct v2 acceleration = V2(0, 0); - struct xform xf = entity_get_xform(ent); + /* Apply impulses */ + for (struct entity *child = entity_from_handle(store, ent->first); child->valid; child = entity_from_handle(store, child->next)) { + if (entity_has_prop(child, ENTITY_PROP_IMPULSE)) { + acceleration = v2_add(acceleration, child->impulse); + entity_enable_prop(child, ENTITY_PROP_RELEASE); + } + } - /* Calculate velocity from old position */ - struct v2 tick_velocity = V2(0, 0); - { - tick_velocity = v2_sub(xf.og, ent->verlet_xform.og); + /* Apply forces */ + for (struct entity *child = entity_from_handle(store, ent->first); child->valid; child = entity_from_handle(store, child->next)) { + if (entity_has_prop(child, ENTITY_PROP_FORCE)) { + struct v2 force_acceleration = v2_mul(v2_div(child->force, mass), dt); + acceleration = v2_add(acceleration, force_acceleration); + entity_enable_prop(child, ENTITY_PROP_RELEASE); + } + } + + /* Verlet integration */ + struct xform xf = entity_get_xform(ent); + struct v2 tick_velocity = v2_sub(xf.og, ent->verlet_xform.og); ent->verlet_xform = xf; + xf.og = v2_add(xf.og, v2_add(tick_velocity, v2_mul(acceleration, dt))); + entity_set_xform(ent, xf); } + } + + /* ========================== * + * Player aim + * ========================== */ + + 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; - /* 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; { @@ -693,52 +674,22 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) xf = xform_with_rotation(xf, final_xf_angle); } } + + entity_set_xform(ent, xf); } - - /* Apply velocity */ - xf.og = v2_add(xf.og, tick_velocity); - - ent->final_velocity = v2_div(tick_velocity, dt); - ent->final_acceleration = v2_div(v2_sub(v2_sub(xf.og, ent->verlet_xform.og), tick_velocity), dt); - entity_set_xform(ent, xf); } /* ========================== * * Initialize bullet kinematics from sources * ========================== */ -#if 0 - 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; - - /* FIXME: Apply src entity velocity to bullet velocity */ - /* TODO: Use impulse */ - - 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); - - struct v2 pos = xform_mul_v2(src_xf, ent->bullet_src_pos); - struct v2 vec = xform_basis_mul_v2(src_xf, ent->bullet_src_dir); - vec = v2_mul(v2_norm(vec), ent->bullet_impulse); - - struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vec) + PI / 2); - entity_set_xform(ent, xf); - - ent->verlet_xform = XFORM_POS(v2_sub(pos, v2_mul(vec, dt))); - - entity_enable_prop(ent, ENTITY_PROP_PHYSICAL); - } - } -#else 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; /* FIXME: Apply src entity velocity to bullet velocity */ - if (entity_has_prop(ent, ENTITY_PROP_BULLET) && !entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) { + if (entity_has_prop(ent, ENTITY_PROP_BULLET) && ent->activation_tick == G.tick.tick_id) { struct entity *src = entity_from_handle(store, ent->bullet_src); struct xform src_xf = entity_get_xform(src); @@ -749,7 +700,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(velocity) + PI / 2); entity_set_xform(ent, xf); ent->verlet_xform = xf; - entity_enable_prop(ent, ENTITY_PROP_PHYSICAL); /* Create impulse */ @@ -758,7 +708,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) entity_enable_prop(impulse, ENTITY_PROP_IMPULSE); } } -#endif /* ========================== * * Update camera position @@ -857,7 +806,10 @@ INTERNAL void game_update(struct game_cmd_array game_cmds) } for (u64 i = 0; i < ents_to_release_count; ++i) { - entity_release(store, ents_to_release[i]); + struct entity *ent = ents_to_release[i]; + if (ent->valid) { + entity_release(store, ent); + } } arena_temp_end(temp); diff --git a/src/user.c b/src/user.c index 82f62992..1eda779c 100644 --- a/src/user.c +++ b/src/user.c @@ -381,16 +381,14 @@ INTERNAL void debug_draw_movement(struct entity *ent) f32 arrow_len = 15.f; u32 color_vel = RGBA_32_F(1, 0.5, 0, 1); - u32 color_acc = RGBA_32_F(1, 1, 0.5, 1); struct xform xf = entity_get_xform(ent); + struct v2 velocity = v2_div(v2_sub(xf.og, ent->verlet_xform.og), G.world.dt); struct v2 pos = xform_mul_v2(G.world_view, xf.og); - 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); + struct v2 vel_ray = xform_basis_mul_v2(G.world_view, velocity); 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); } INTERNAL void user_update(void) @@ -443,6 +441,7 @@ INTERNAL void user_update(void) /* Blend world globals */ G.world.time = math_lerp64(t0->time, t1->time, (f64)tick_blend); + G.world.dt = math_lerp64(t0->dt, t1->dt, (f64)tick_blend); /* Blend entities */ u64 num_entities = min_u64(t0->entity_store->reserved, t1->entity_store->reserved); @@ -466,10 +465,8 @@ INTERNAL void user_update(void) e->cached_global_xform = xform_lerp(e0->cached_global_xform, e1->cached_global_xform, 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_force = math_lerp(e0->control_move_force, e1->control_move_force, tick_blend); e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend); e->control.focus = v2_lerp(e0->control.focus, e1->control.focus, tick_blend); @@ -896,7 +893,9 @@ INTERNAL void user_update(void) } #endif - debug_draw_movement(ent); + if (entity_has_prop(ent, ENTITY_PROP_PHYSICAL)) { + debug_draw_movement(ent); + } if (!skip_debug_draw_transform) { debug_draw_xform(xf);