start verlet integration

This commit is contained in:
jacob 2024-08-18 22:44:55 -05:00
parent ebc1fdf258
commit 83b0b3f255
4 changed files with 70 additions and 42 deletions

View File

@ -15,6 +15,8 @@ READONLY struct entity _g_entity_nil = {
.valid = false, .valid = false,
.local_xform = XFORM_IDENT_NOCAST, .local_xform = XFORM_IDENT_NOCAST,
.cached_global_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_local_xform = XFORM_IDENT_NOCAST,
.sprite_tint = COLOR_WHITE .sprite_tint = COLOR_WHITE
}; };
@ -24,6 +26,7 @@ GLOBAL READONLY struct entity g_entity_default = {
.local_xform = XFORM_IDENT_NOCAST, .local_xform = XFORM_IDENT_NOCAST,
.cached_global_xform = XFORM_IDENT_NOCAST, .cached_global_xform = XFORM_IDENT_NOCAST,
.cached_global_xform_dirty = true, .cached_global_xform_dirty = true,
.verlet_xform = XFORM_IDENT_NOCAST,
.sprite_local_xform = XFORM_IDENT_NOCAST, .sprite_local_xform = XFORM_IDENT_NOCAST,
.sprite_tint = COLOR_WHITE .sprite_tint = COLOR_WHITE
}; };

View File

@ -11,7 +11,7 @@ enum entity_prop {
ENTITY_PROP_RELEASE_NEXT_TICK, ENTITY_PROP_RELEASE_NEXT_TICK,
ENTITY_PROP_KINEMATIC, ENTITY_PROP_PHYSICAL,
ENTITY_PROP_PLAYER_CONTROLLED, ENTITY_PROP_PLAYER_CONTROLLED,
ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA,
@ -103,9 +103,14 @@ struct entity {
/* ====================================================================== */ /* ====================================================================== */
/* Physics */ /* Physics */
/* ENTITY_PROP_KINEMATIC */ /* ENTITY_PROP_PHYSICAL */
struct v2 acceleration;
struct v2 velocity; /* 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 */ /* Sprite */

View File

@ -50,8 +50,9 @@ struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
/* Initialize ticks */ /* Initialize ticks */
world_alloc(&G.tick); world_alloc(&G.tick);
world_alloc(&G.prev_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); arena_set_readonly(&G.prev_tick.entity_store->arena);
G.prev_tick_mutex = sys_mutex_alloc();
G.tick.timescale = GAME_TIMESCALE; G.tick.timescale = GAME_TIMESCALE;
G.game_thread = sys_thread_alloc(&game_thread_entry_point, NULL, STR("[P2] Game thread")); 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); entity_enable_prop(e, ENTITY_PROP_PLAYER_CONTROLLED);
e->player_max_speed = 4.f; e->player_max_speed = 4.f;
e->player_acceleration = 20.0f; e->player_acceleration = 20;
e->control.focus = V2(0, -1); e->control.focus = V2(0, -1);
player_ent = e; player_ent = e;
entity_enable_prop(e, ENTITY_PROP_KINEMATIC); entity_enable_prop(e, ENTITY_PROP_PHYSICAL);
} }
/* Weapon */ /* 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.dt = max_f64(0.0, (1.0 / GAME_FPS) * G.tick.timescale);
G.tick.time += G.tick.dt; G.tick.time += G.tick.dt;
f64 dt = G.tick.dt;
f64 time = G.tick.time; f64 time = G.tick.time;
struct entity_store *store = G.tick.entity_store; struct entity_store *store = G.tick.entity_store;
struct entity *root = entity_from_handle(store, store->root); 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) { if (atick != 0 || G.tick.tick_id >= atick) {
entity_enable_prop(ent, ENTITY_PROP_ACTIVE); entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
ent->activation_tick = G.tick.tick_id; ent->activation_tick = G.tick.tick_id;
ent->verlet_xform = entity_get_xform(ent);
++ent->continuity_gen; ++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) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue;
/* Trigger equipped */
if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) { if (entity_has_prop(ent, ENTITY_PROP_TRIGGERING_EQUIPPED)) {
struct entity *eq = entity_from_handle(store, ent->equipped); struct entity *eq = entity_from_handle(store, ent->equipped);
if (eq->valid) { 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) { 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 * 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) { for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index]; struct entity *ent = &store->entities[entity_index];
if (!(ent->valid && entity_has_prop(ent, ENTITY_PROP_ACTIVE))) continue; 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 */ /* Player angle */
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
struct xform xf = entity_get_xform(ent);
/* Solve for final angle using law of sines */ /* Solve for final angle using law of sines */
f32 final_xf_angle; 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)) { if (!F32_IS_NAN(final_xf_angle)) {
const f32 angle_error_allowed = 0.001; const f32 angle_error_allowed = 0.001;
if (math_fabs(final_xf_angle - v2_angle(xf.bx)) > angle_error_allowed) { 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 */ /* TODO: Accumulate forces to calculate acceleration */
struct v2 acceleration = V2(0, 0);
if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) { {
f32 max_speed = ent->player_max_speed; /* Player movement */
f32 acceleration_rate = ent->player_acceleration; if (entity_has_prop(ent, ENTITY_PROP_PLAYER_CONTROLLED)) {
acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */ f32 max_speed = ent->player_max_speed;
struct v2 target_velocity = v2_mul(ent->control.move, max_speed); f32 acceleration_rate = ent->player_acceleration;
struct v2 target_acceleration = v2_sub(target_velocity, ent->velocity); acceleration_rate = clamp_f32(acceleration_rate, 0, GAME_FPS); /* Can't integrate acceleration rate higher than FPS */
ent->acceleration = v2_mul(target_acceleration, acceleration_rate); 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 */ /* 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)) { ent->final_velocity = velocity;
f32 dt = (f32)G.tick.dt; ent->final_acceleration = acceleration;
struct xform xf = entity_get_xform(ent); entity_set_xform(ent, xf);
/* 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);
}
} }
/* ========================== * /* ========================== *
@ -640,7 +650,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
/* FIXME: Apply src entity velocity to bullet velocity */ /* 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 entity *src = entity_from_handle(store, ent->bullet_src);
struct xform src_xf = entity_get_xform(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); struct v2 vec = xform_basis_mul_v2(src_xf, ent->bullet_src_dir);
vec = v2_mul(v2_norm(vec), ent->bullet_impulse); 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); struct xform xf = XFORM_TRS(.t = pos, .r = v2_angle(vec) + PI / 2);
entity_set_xform(ent, xf); 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 * 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); struct temp_arena temp = arena_temp_begin(scratch.arena);

View File

@ -387,8 +387,8 @@ INTERNAL void debug_draw_movement(struct entity *ent)
struct xform xf = entity_get_xform(ent); struct xform xf = entity_get_xform(ent);
struct v2 pos = xform_mul_v2(G.world_view, xf.og); 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 vel_ray = xform_basis_mul_v2(G.world_view, ent->final_velocity);
struct v2 acc_ray = xform_basis_mul_v2(G.world_view, ent->acceleration); 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, vel_ray, thickness, arrow_len, color_vel);
draw_solid_arrow_ray(G.viewport_canvas, pos, acc_ray, thickness, arrow_len, color_acc); 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->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->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->verlet_xform = xform_lerp(e0->verlet_xform, e1->verlet_xform, tick_blend);
e->velocity = v2_lerp(e0->velocity, e1->velocity, 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->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); e->control.move = v2_lerp(e0->control.move, e1->control.move, tick_blend);