respond to collisions using callbacks rather than events

This commit is contained in:
jacob 2025-01-12 09:11:30 -06:00
parent 0ef72e1b23
commit b66f130e9c
4 changed files with 101 additions and 106 deletions

View File

@ -22,8 +22,6 @@ enum entity_prop {
ENTITY_PROP_MOUSE_JOINT, ENTITY_PROP_MOUSE_JOINT,
ENTITY_PROP_SENSOR, ENTITY_PROP_SENSOR,
ENTITY_PROP_HIT_EVENT,
ENTITY_PROP_PLAYER_CONTROLLED, ENTITY_PROP_PLAYER_CONTROLLED,
ENTITY_PROP_CAMERA, ENTITY_PROP_CAMERA,
ENTITY_PROP_CAMERA_ACTIVE, ENTITY_PROP_CAMERA_ACTIVE,
@ -128,11 +126,6 @@ struct entity {
/* ENTITY_PROP_MOUSE_JOINT */ /* ENTITY_PROP_MOUSE_JOINT */
struct phys_mouse_joint mouse_joint_data; struct phys_mouse_joint mouse_joint_data;
/* ====================================================================== */
/* Hit event */
struct phys_hit_event hit_event;
/* ====================================================================== */ /* ====================================================================== */
/* Activation */ /* Activation */

View File

@ -323,6 +323,50 @@ INTERNAL void release_entities_with_prop(enum entity_prop prop)
scratch_end(scratch); scratch_end(scratch);
} }
/* ========================== *
* Respond to physics collisions
* ========================== */
INTERNAL PHYS_COLLISION_CALLBACK_FUNC_DEF(on_collision, array)
{
struct entity_store *store = G.tick.entity_store;
struct entity *root = entity_from_handle(store, store->root);
for (u64 i = 0; i < array.count; ++i) {
struct phys_collision_data *data = &array.a[i];
struct entity *e0 = entity_from_handle(store, data->e0);
struct entity *e1 = entity_from_handle(store, data->e1);
if (entity_is_valid_and_active(e0) && entity_is_valid_and_active(e1)) {
/* Bullet hit entity */
if (entity_has_prop(e0, ENTITY_PROP_BULLET) || entity_has_prop(e1, ENTITY_PROP_BULLET)) {
struct entity *bullet = entity_has_prop(e0, ENTITY_PROP_BULLET) ? e0 : e1;
struct entity *target = e0 == bullet ? e1 : e0;
(UNUSED)bullet;
(UNUSED)target;
/* Disable bullet */
entity_enable_prop(bullet, ENTITY_PROP_RELEASE_BEFORE_PUBLISH);
entity_disable_prop(bullet, ENTITY_PROP_ACTIVE);
/* Create test blood */
/* TODO: Remove this */
{
struct xform xf = XFORM_TRS(.t = data->point);
struct entity *decal = entity_alloc(root);
decal->sprite = sprite_tag_from_path(STR("res/graphics/blood.ase"));
entity_set_xform(decal, xf);
entity_enable_prop(decal, ENTITY_PROP_PHYSICAL_KINEMATIC);
decal->linear_velocity = v2_mul(v2_norm(data->normal), 0.5f);
decal->angular_velocity = 1 - (((f32)sys_rand_u32() / (f32)U32_MAX) * 2);
}
}
}
}
}
/* ========================== * /* ========================== *
* Update * Update
* ========================== */ * ========================== */
@ -925,6 +969,7 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
ctx.tick_id = G.tick.tick_id; ctx.tick_id = G.tick.tick_id;
ctx.store = store; ctx.store = store;
ctx.contact_lookup = &G.contact_lookup; ctx.contact_lookup = &G.contact_lookup;
ctx.pre_solve_callback = on_collision;
#if COLLIDER_DEBUG #if COLLIDER_DEBUG
ctx.debug_lookup = &G.collision_debug_lookup; ctx.debug_lookup = &G.collision_debug_lookup;
#endif #endif
@ -945,74 +990,6 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
phys_step(&ctx, dt); phys_step(&ctx, dt);
} }
/* ========================== *
* Respond to hit events
* ========================== */
{
struct temp_arena temp = arena_temp_begin(scratch.arena);
/* Sort hit events by dt (to ensure early time of impact collisions are processed first) */
struct phys_hit_event_node *first_sorted = NULL;
for (u64 entity_index = 0; entity_index < store->reserved; ++entity_index) {
struct entity *ent = &store->entities[entity_index];
if (!entity_is_valid_and_active(ent)) continue;
if (!entity_has_prop(ent, ENTITY_PROP_HIT_EVENT)) continue;
struct phys_hit_event *e = &ent->hit_event;
struct phys_hit_event_node *next = first_sorted;
struct phys_hit_event_node *prev = NULL;
while (next) {
if (e->dt < next->e->dt) {
break;
}
prev = next;
next = next->next;
}
struct phys_hit_event_node *n = arena_push_zero(temp.arena, struct phys_hit_event_node);
n->e = e;
n->next = next;
if (prev) {
prev->next = n;
} else {
first_sorted = n;
}
}
/* Process events */
for (struct phys_hit_event_node *n = first_sorted; n; n = n->next) {
struct phys_hit_event *event = n->e;
struct entity *e0 = entity_from_handle(store, event->e0);
struct entity *e1 = entity_from_handle(store, event->e1);
if (entity_is_valid_and_active(e0) && entity_is_valid_and_active(e1)) {
/* Bullet hit entity */
if (entity_has_prop(e0, ENTITY_PROP_BULLET) || entity_has_prop(e1, ENTITY_PROP_BULLET)) {
struct entity *bullet = entity_has_prop(e0, ENTITY_PROP_BULLET) ? e0 : e1;
struct entity *target = e0 == bullet ? e1 : e0;
(UNUSED)bullet;
(UNUSED)target;
entity_enable_prop(bullet, ENTITY_PROP_RELEASE_BEFORE_PUBLISH);
entity_disable_prop(bullet, ENTITY_PROP_ACTIVE);
/* Create test blood */
/* TODO: Remove this */
{
struct xform xf = XFORM_TRS(.t = event->point);
struct entity *decal = entity_alloc(root);
decal->sprite = sprite_tag_from_path(STR("res/graphics/blood.ase"));
entity_set_xform(decal, xf);
entity_enable_prop(decal, ENTITY_PROP_PHYSICAL_KINEMATIC);
decal->linear_velocity = v2_mul(v2_norm(event->normal), 0.5f);
decal->angular_velocity = 1 - (((f32)sys_rand_u32() / (f32)U32_MAX) * 2);
}
}
}
}
arena_temp_end(temp);
}
/* ========================== * /* ========================== *
* Initialize bullet kinematics from sources * Initialize bullet kinematics from sources
* ========================== */ * ========================== */

View File

@ -1,4 +1,6 @@
#include "phys.h" #include "phys.h"
#include "math.h"
#include "scratch.h"
#include "entity.h" #include "entity.h"
GLOBAL struct { GLOBAL struct {
@ -37,9 +39,11 @@ struct phys_startup_receipt phys_startup(void)
* Contact * Contact
* ========================== */ * ========================== */
void phys_create_contacts(struct phys_ctx *ctx, f32 elapsed_dt) struct phys_collision_data_array phys_create_contacts(struct arena *arena, struct phys_ctx *ctx, f32 elapsed_dt)
{ {
__prof; __prof;
struct phys_collision_data_array res = ZI;
res.a = arena_dry_push(arena, struct phys_collision_data);
u64 tick_id = ctx->tick_id; u64 tick_id = ctx->tick_id;
struct entity_lookup *contact_lookup = ctx->contact_lookup; struct entity_lookup *contact_lookup = ctx->contact_lookup;
struct entity_lookup *debug_lookup = ctx->debug_lookup; struct entity_lookup *debug_lookup = ctx->debug_lookup;
@ -115,23 +119,21 @@ void phys_create_contacts(struct phys_ctx *ctx, f32 elapsed_dt)
struct phys_contact_constraint *constraint = NULL; struct phys_contact_constraint *constraint = NULL;
if (collider_res.num_points > 0) { if (collider_res.num_points > 0) {
if (!entity_is_valid_and_active(constraint_ent)) { if (!entity_is_valid_and_active(constraint_ent)) {
/* Create hit event */ /* Push collision data */
{ if (ctx->pre_solve_callback || ctx->post_solve_callback) {
struct entity *event = entity_alloc(root); struct phys_collision_data *data = arena_push_zero(arena, struct phys_collision_data);
entity_enable_prop(event, ENTITY_PROP_HIT_EVENT); ++res.count;
event->hit_event.e0 = e0->handle; data->e0 = e0->handle;
event->hit_event.e1 = e1->handle; data->e1 = e1->handle;
event->hit_event.normal = collider_res.normal; data->normal = collider_res.normal;
event->hit_event.dt = elapsed_dt; data->dt = elapsed_dt;
entity_enable_prop(event, ENTITY_PROP_RELEASE_BEFORE_PUBLISH);
entity_enable_prop(event, ENTITY_PROP_ACTIVE);
/* Calculate point */ /* Calculate point */
struct v2 point = collider_res.points[0].point; struct v2 point = collider_res.points[0].point;
if (collider_res.num_points > 1) { if (collider_res.num_points > 1) {
point = v2_add(point, v2_mul(v2_sub(collider_res.points[1].point, point), 0.5f)); point = v2_add(point, v2_mul(v2_sub(collider_res.points[1].point, point), 0.5f));
} }
event->hit_event.point = point; data->point = point;
/* Calculate relative velocity */ /* Calculate relative velocity */
struct v2 vrel; struct v2 vrel;
@ -146,7 +148,7 @@ void phys_create_contacts(struct phys_ctx *ctx, f32 elapsed_dt)
struct v2 vel1 = v2_add(v1, v2_perp_mul(vcp1, w1)); struct v2 vel1 = v2_add(v1, v2_perp_mul(vcp1, w1));
vrel = v2_sub(vel0, vel1); vrel = v2_sub(vel0, vel1);
} }
event->hit_event.vrel = vrel; data->vrel = vrel;
} }
/* Create constraint */ /* Create constraint */
@ -275,6 +277,7 @@ void phys_create_contacts(struct phys_ctx *ctx, f32 elapsed_dt)
#endif #endif
} }
} }
return res;
} }
void phys_prepare_contacts(struct phys_ctx *ctx) void phys_prepare_contacts(struct phys_ctx *ctx)
@ -1125,6 +1128,7 @@ void phys_step(struct phys_ctx *ctx, f32 timestep)
f32 remaining_dt = timestep; f32 remaining_dt = timestep;
while (remaining_dt > 0) { while (remaining_dt > 0) {
__profscope(step); __profscope(step);
struct temp_arena scratch = scratch_begin_no_conflict();
/* TOI */ /* TOI */
f32 step_dt = remaining_dt; f32 step_dt = remaining_dt;
{ {
@ -1141,13 +1145,18 @@ void phys_step(struct phys_ctx *ctx, f32 timestep)
#endif #endif
} }
phys_create_contacts(ctx, timestep - remaining_dt); struct phys_collision_data_array collision_data = phys_create_contacts(scratch.arena, ctx, timestep - remaining_dt);
phys_create_mouse_joints(ctx); phys_create_mouse_joints(ctx);
phys_prepare_contacts(ctx); phys_prepare_contacts(ctx);
phys_prepare_motor_joints(ctx); phys_prepare_motor_joints(ctx);
phys_prepare_mouse_joints(ctx); phys_prepare_mouse_joints(ctx);
if (ctx->pre_solve_callback) {
__profscope(pre_solve_callback);
ctx->pre_solve_callback(collision_data);
}
f32 substep_dt = step_dt / GAME_PHYSICS_SUBSTEPS; f32 substep_dt = step_dt / GAME_PHYSICS_SUBSTEPS;
for (u32 i = 0; i < GAME_PHYSICS_SUBSTEPS; ++i) { for (u32 i = 0; i < GAME_PHYSICS_SUBSTEPS; ++i) {
__profscope(substep); __profscope(substep);
@ -1169,5 +1178,12 @@ void phys_step(struct phys_ctx *ctx, f32 timestep)
phys_solve_contacts(ctx, substep_dt, false); /* Relaxation */ phys_solve_contacts(ctx, substep_dt, false); /* Relaxation */
#endif #endif
} }
if (ctx->post_solve_callback) {
__profscope(post_solve_callback);
ctx->post_solve_callback(collision_data);
}
scratch_end(scratch);
} }
} }

View File

@ -7,19 +7,8 @@
struct entity_store; struct entity_store;
struct entity_lookup; struct entity_lookup;
/* Structure containing data used for a single physics step */ struct phys_collision_data {
struct phys_ctx { struct entity_handle constraint_ent;
u64 tick_id;
struct entity_store *store;
struct entity_lookup *contact_lookup;
struct entity_lookup *debug_lookup;
struct v2 dbg_cursor_pos;
b32 dbg_start_dragging;
b32 dbg_stop_dragging;
};
struct phys_hit_event {
struct entity_handle e0; struct entity_handle e0;
struct entity_handle e1; struct entity_handle e1;
struct v2 point; struct v2 point;
@ -28,9 +17,29 @@ struct phys_hit_event {
f32 dt; /* How much time elapsed in the step when this event occurred (this will equal the physics timestep unless an early time of impact collision occurred) */ f32 dt; /* How much time elapsed in the step when this event occurred (this will equal the physics timestep unless an early time of impact collision occurred) */
}; };
struct phys_hit_event_node { struct phys_collision_data_array {
struct phys_hit_event *e; struct phys_collision_data *a;
struct phys_hit_event_node *next; u64 count;
};
struct phys_collision_data;
#define PHYS_COLLISION_CALLBACK_FUNC_DEF(name, arg) void name(struct phys_collision_data_array arg)
typedef PHYS_COLLISION_CALLBACK_FUNC_DEF(phys_collision_callback_func, data);
/* Structure containing data used for a single physics step */
struct phys_ctx {
u64 tick_id;
struct entity_store *store;
struct entity_lookup *contact_lookup;
phys_collision_callback_func *pre_solve_callback;
phys_collision_callback_func *post_solve_callback;
struct entity_lookup *debug_lookup;
struct v2 dbg_cursor_pos;
b32 dbg_start_dragging;
b32 dbg_stop_dragging;
}; };
/* ========================== * /* ========================== *
@ -97,7 +106,7 @@ struct phys_collision_debug {
struct xform xf1; struct xform xf1;
}; };
void phys_create_contacts(struct phys_ctx *ctx, f32 elapsed_dt); struct phys_collision_data_array phys_create_contacts(struct arena *arena, struct phys_ctx *ctx, f32 elapsed_dt);
void phys_prepare_contacts(struct phys_ctx *ctx); void phys_prepare_contacts(struct phys_ctx *ctx);
void phys_warm_start_contacts(struct phys_ctx *ctx); void phys_warm_start_contacts(struct phys_ctx *ctx);
void phys_solve_contacts(struct phys_ctx *ctx, f32 dt, b32 apply_bias); void phys_solve_contacts(struct phys_ctx *ctx, f32 dt, b32 apply_bias);