contact lookup, joint / contact removal

This commit is contained in:
jacob 2024-10-30 18:34:45 -05:00
parent f5e45d6ba1
commit bfa1f1e065
2 changed files with 226 additions and 101 deletions

View File

@ -79,6 +79,7 @@ struct contact_point {
};
struct contact_constraint {
u64 last_updated_tick; /* To avoid checking collisions for the same constraint twice in one tick */
struct entity_handle e0;
struct entity_handle e1;
f32 inv_m0;

View File

@ -12,6 +12,24 @@
#include "log.h"
#include "collider.h"
struct contact_lookup_entry {
u64 hash;
struct entity_handle contact_ent_handle;
struct contact_lookup_entry *next;
struct contact_lookup_entry *prev;
};
struct contact_lookup_bucket {
struct contact_lookup_entry *first;
struct contact_lookup_entry *last;
};
struct contact_lookup {
struct arena arena;
struct contact_lookup_bucket buckets[4096];
struct contact_lookup_entry *first_free_entry;
};
GLOBAL struct {
struct atomic_i32 game_thread_shutdown;
struct sys_thread game_thread;
@ -37,6 +55,9 @@ GLOBAL struct {
struct math_spring_result mouse_joint_angular_softness;
f32 mouse_joint_max_force;
/* Bookkeeping structures */
struct contact_lookup contact_lookup;
/* Ticks */
struct sys_mutex prev_tick_mutex;
struct atomic_u64 prev_tick_id;
@ -143,6 +164,112 @@ INTERNAL void activate_now(struct entity *ent)
++ent->continuity_gen;
}
/* ========================== *
* Contact lookup
* ========================== */
INTERNAL void contact_lookup_alloc(struct contact_lookup *l)
{
MEMZERO_STRUCT(l);
l->arena = arena_alloc(GIGABYTE(64));
}
INTERNAL void contact_lookup_release(struct contact_lookup *l)
{
arena_release(&l->arena);
}
INTERNAL u64 contact_lookup_hash_from_entities(struct entity_handle h0, struct entity_handle h1)
{
struct buffer b0 = BUFFER_FROM_STRUCT(&h0);
struct buffer b1 = BUFFER_FROM_STRUCT(&h1);
u64 hash = hash_fnv64(HASH_FNV64_BASIS, b0);
hash = hash_fnv64(hash, b1);
return hash;
}
INTERNAL struct contact_lookup_entry *contact_lookup_get(struct contact_lookup *l, u64 hash)
{
u64 index = hash % ARRAY_COUNT(l->buckets);
struct contact_lookup_bucket *bucket = &l->buckets[index];
struct contact_lookup_entry *res = NULL;
for (struct contact_lookup_entry *e = bucket->first; e; e = e->next) {
if (e->hash == hash) {
res = e;
break;
}
}
return res;
}
INTERNAL void contact_lookup_set(struct contact_lookup *l, u64 hash, struct entity_handle handle)
{
u64 index = hash % ARRAY_COUNT(l->buckets);
struct contact_lookup_bucket *bucket = &l->buckets[index];
struct contact_lookup_entry *prev = NULL;
struct contact_lookup_entry **slot = &bucket->first;
while (*slot) {
if ((*slot)->hash == hash) {
break;
}
prev = *slot;
slot = &(*slot)->next;
}
struct contact_lookup_entry *entry = *slot;
if (entry) {
/* Set existing entry */
entry->contact_ent_handle = 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 contact_lookup_entry);
}
MEMZERO_STRUCT(entry);
entry->hash = hash;
entry->contact_ent_handle = handle;
if (prev) {
entry->prev = prev;
prev->next = entry;
}
bucket->last = entry;
*slot = entry;
}
}
INTERNAL void contact_lookup_remove(struct contact_lookup *l, struct contact_lookup_entry *entry)
{
struct contact_lookup_bucket *bucket = &l->buckets[entry->hash % ARRAY_COUNT(l->buckets)];
struct contact_lookup_entry *prev = entry->prev;
struct contact_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;
}
/* ========================== *
* Reset
* ========================== */
@ -152,7 +279,11 @@ INTERNAL void reset_world(void)
if (G.tick.entity_store) {
/* Release world */
world_release(&G.tick);
/* Release bookkeeping */
contact_lookup_release(&G.contact_lookup);
}
/* Create bookkeeping */
contact_lookup_alloc(&G.contact_lookup);
/* Re-create world */
world_alloc(&G.tick);
G.tick.continuity_gen = atomic_u64_eval(&G.prev_tick_continuity_gen) + 1;
@ -225,7 +356,6 @@ INTERNAL void spawn_test_entities(f32 offset)
entity_set_xform(e, xf);
e->linear_ground_friction = 250;e->angular_ground_friction = 200;
//e->control_force = 500;
@ -415,126 +545,109 @@ INTERNAL void spawn_test_entities(f32 offset)
INTERNAL void create_contacts(void)
{
/* TODO: Remove this */
/* FIXME: Dict has leaks: Entries never removed, even when constraint is no longer valid, or either entities are no longer valid. */
static u64 constraint_iteration = 0;
++constraint_iteration;
static struct arena dict_arena = ZI;
static struct fixed_dict dict = ZI;
if (dict.buckets_count == 0) {
dict_arena = arena_alloc(GIGABYTE(64));
dict = fixed_dict_init(&dict_arena, 4096);
}
/* FIXME: I think it's technically possible for constraint entities to swap between iterations */
struct entity_store *store = G.tick.entity_store;
struct entity *root = G.root;
for (u64 e0_index = 0; e0_index < store->reserved; ++e0_index) {
struct entity *e0 = &store->entities[e0_index];
if (!entity_is_valid_and_active(e0)) continue;
if (!entity_has_prop(e0, ENTITY_PROP_PHYSICAL)) continue;
if (e0->local_collider.count <= 0) continue;
for (u64 check0_index = 0; check0_index < store->reserved; ++check0_index) {
struct entity *check0 = &store->entities[check0_index];
if (!entity_is_valid_and_active(check0)) continue;
if (!entity_has_prop(check0, ENTITY_PROP_PHYSICAL)) continue;
if (check0->local_collider.count <= 0) continue;
struct xform e0_xf = entity_get_xform(e0);
struct collider_shape e0_collider = e0->local_collider;
struct xform check0_xf = entity_get_xform(check0);
struct collider_shape check0_collider = check0->local_collider;
for (u64 e1_index = 0; e1_index < store->reserved; ++e1_index) {
struct entity *e1 = &store->entities[e1_index];
if (e1 == e0) continue;
if (!entity_is_valid_and_active(e1)) continue;
if (!entity_has_prop(e1, ENTITY_PROP_PHYSICAL)) continue;
if (e1->local_collider.count <= 0) continue;
for (u64 check1_index = 0; check1_index < store->reserved; ++check1_index) {
struct entity *check1 = &store->entities[check1_index];
if (check1 == check0) continue;
if (!entity_is_valid_and_active(check1)) continue;
if (!entity_has_prop(check1, ENTITY_PROP_PHYSICAL)) continue;
if (check1->local_collider.count <= 0) continue;
/* TODO: Remove this (temporary stop to prevent double-constraint creation) */
if (e0_index >= e1_index) {
continue;
/* Deterministic entity order based on index */
struct entity *e0;
struct entity *e1;
struct xform e0_xf;
struct xform e1_xf;
struct collider_shape e0_collider;
struct collider_shape e1_collider;
if (check0_index < check1_index) {
e0 = check0;
e1 = check1;
e0_xf = check0_xf;
e1_xf = entity_get_xform(check1);
e0_collider = check0_collider;
e1_collider = check1->local_collider;
} else {
e0 = check1;
e1 = check0;
e0_xf = entity_get_xform(check1);
e1_xf = check0_xf;
e0_collider = check1->local_collider;
e1_collider = check0_collider;
}
u64 constraint_hash;
struct string constraint_key;
struct entity_handle *entry;
struct entity *constraint_ent = NULL;
{
{
struct entity_handle h0 = e0->handle;
struct entity_handle h1 = e1->handle;
constraint_hash = hash_fnv64(HASH_FNV64_BASIS, BUFFER_FROM_STRUCT(&h0));
constraint_hash = hash_fnv64(constraint_hash, BUFFER_FROM_STRUCT(&h1));
constraint_key = STRING_FROM_BUFFER(BUFFER_FROM_STRUCT(&constraint_hash));
}
u64 lookup_hash = contact_lookup_hash_from_entities(e0->handle, e1->handle);
struct contact_lookup_entry *entry = contact_lookup_get(&G.contact_lookup, lookup_hash);
entry = fixed_dict_get(&dict, constraint_key);
struct entity *constraint_ent = entity_nil();
if (entry) {
struct entity *t = entity_from_handle(store, *entry);
if (entity_is_valid_and_active(t)) {
if (t->contact_constraint_data.last_iteration == constraint_iteration) {
/* Constraint has already been computed this iteration */
constraint_ent = entity_from_handle(store, entry->contact_ent_handle);
if (entity_is_valid_and_active(constraint_ent)) {
if (constraint_ent->contact_constraint_data.last_iteration >= G.tick.tick_id) {
/* Already processed constraint this iteration */
continue;
} else {
t->contact_constraint_data.last_iteration = constraint_iteration;
constraint_ent = t;
++constraint_ent->contact_constraint_data.last_iteration;
}
} else {
/* Constraint entity no longer valid */
fixed_dict_set(&dict_arena, &dict, constraint_key, NULL);
continue;
}
/* Constraint ent no longer valid, delete entry */
contact_lookup_remove(&G.contact_lookup, entry);
entry = NULL;
}
}
/* Calculate collision */
struct xform e1_xf = entity_get_xform(e1);
struct collider_collision_points_result res = collider_collision_points(&e0_collider, &e1->local_collider, e0_xf, e1_xf);
struct collider_collision_points_result collider_res = collider_collision_points(&e0_collider, &e1_collider, e0_xf, e1_xf);
/* Parts of algorithm are hard-coded to support 2 contact points */
CT_ASSERT(ARRAY_COUNT(constraint_ent->contact_constraint_data.points) == 2);
CT_ASSERT(ARRAY_COUNT(res.points) == 2);
CT_ASSERT(ARRAY_COUNT(collider_res.points) == 2);
/* TODO: Move this down */
if (res.num_points > 0 || COLLIDER_DEBUG) {
if (!constraint_ent) {
if (collider_res.num_points > 0) {
if (!entity_is_valid_and_active(constraint_ent)) {
constraint_ent = entity_alloc(root);
constraint_ent->contact_constraint_data.e1 = e1->handle;
constraint_ent->contact_constraint_data.e0 = e0->handle;
/* TODO: Should we recalculate normal as more contact points are added? */
entity_enable_prop(constraint_ent, ENTITY_PROP_CONTACT_CONSTRAINT);
activate_now(constraint_ent);
if (entry) {
*entry = constraint_ent->handle;
} else {
entry = arena_push(&dict_arena, struct entity_handle);
*entry = constraint_ent->handle;
fixed_dict_set(&dict_arena, &dict, constraint_key, entry);
if (!entry) {
contact_lookup_set(&G.contact_lookup, lookup_hash, constraint_ent->handle);
}
}
struct contact_constraint *constraint = &constraint_ent->contact_constraint_data;
constraint->normal = res.normal;
constraint->normal = collider_res.normal;
#if 0
/* TODO: Remove this (debugging) */
#if COLLIDER_DEBUG
{
constraint->res = res;
constraint->res = collider_res;
constraint->dbg_xf0 = e0_xf;
constraint->dbg_xf1 = e1_xf;
if (constraint->num_points == 0) {
if (res.num_points > 0) {
if (collider_res.num_points > 0) {
++e0->colliding;
++e1->colliding;
}
} else {
if (res.num_points == 0) {
if (collider_res.num_points == 0) {
--e0->colliding;
--e1->colliding;
}
}
}
#endif
}
if (res.num_points > 0) {
struct contact_constraint *constraint = &constraint_ent->contact_constraint_data;
constraint->friction = math_sqrt(e0->friction * e1->friction);
@ -543,8 +656,8 @@ INTERNAL void create_contacts(void)
struct contact_point *old = &constraint->points[i];
u32 id = old->id;
b32 found = false;
for (u32 j = 0; j < res.num_points; ++j) {
if (res.points[j].id == id) {
for (u32 j = 0; j < collider_res.num_points; ++j) {
if (collider_res.points[j].id == id) {
found = true;
break;
}
@ -557,8 +670,8 @@ INTERNAL void create_contacts(void)
}
/* Update / insert returned contacts */
for (u32 i = 0; i < res.num_points; ++i) {
struct collider_collision_point *res_point = &res.points[i];
for (u32 i = 0; i < collider_res.num_points; ++i) {
struct collider_collision_point *res_point = &collider_res.points[i];
struct v2 point = res_point->point;
f32 sep = res_point->separation;
u32 id = res_point->id;
@ -589,9 +702,7 @@ INTERNAL void create_contacts(void)
contact->dbg_pt = point;
#endif
}
} else if (constraint_ent) {
} else if (constraint_ent->valid) {
constraint_ent->contact_constraint_data.num_points = 0;
}
}
@ -665,6 +776,10 @@ INTERNAL void prepare_contacts(void)
contact->tangent_impulse = 0;
#endif
}
} else {
/* Mark constraint for removal */
entity_disable_prop(constraint_ent, ENTITY_PROP_ACTIVE);
entity_enable_prop(constraint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME);
}
}
}
@ -903,6 +1018,10 @@ INTERNAL void prepare_motor_joints(void)
joint->linear_impulse = V2(0, 0);
joint->angular_impulse = 0;
#endif
} else {
/* Mark joint for removal */
entity_disable_prop(joint_ent, ENTITY_PROP_ACTIVE);
entity_enable_prop(joint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME);
}
}
}
@ -1013,10 +1132,6 @@ INTERNAL void solve_motor_joints(f32 dt)
}
}
#if 1
/* ========================== *
@ -1049,9 +1164,8 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds)
struct v2 cursor = G.user_cursor;
/* TODO: Remove this */
static struct entity_handle target_ent_handle = ZI;
static struct entity_handle joint_ent_handle = ZI;
struct entity *joint_ent = entity_find_first_match_one(store, ENTITY_PROP_MOUSE_JOINT);
struct entity *target_ent = entity_from_handle(store, joint_ent->mouse_joint_data.target);
if (start_dragging) {
struct xform mouse_xf = xform_from_pos(cursor);
@ -1067,22 +1181,18 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds)
struct collider_shape ent_collider = ent->local_collider;
if (ent_collider.count > 0) {
struct xform ent_xf = entity_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_handle = ent->handle;
target_ent = ent;
break;
}
}
}
} else if (stop_dragging) {
target_ent_handle = entity_nil_handle();
target_ent = entity_nil();
}
struct entity *target_ent = entity_from_handle(store, target_ent_handle);
struct entity *joint_ent = entity_from_handle(store, joint_ent_handle);
if (entity_is_valid_and_active(target_ent)) {
if (!entity_is_valid_and_active(joint_ent)) {
joint_ent = entity_alloc(root);
@ -1091,16 +1201,15 @@ INTERNAL void create_mouse_joints(struct game_cmd_array game_cmds)
entity_enable_prop(joint_ent, ENTITY_PROP_PHYSICAL);
entity_enable_prop(joint_ent, ENTITY_PROP_MOUSE_JOINT);
entity_enable_prop(joint_ent, ENTITY_PROP_ACTIVE);
joint_ent_handle = joint_ent->handle;
}
struct mouse_joint *joint = &joint_ent->mouse_joint_data;
struct xform xf = entity_get_xform(target_ent);
f32 mass = target_ent->mass_unscaled * math_fabs(xform_get_determinant(xf));
if (!entity_handle_eq(joint->target, target_ent_handle)) {
if (!entity_handle_eq(joint->target, target_ent->handle)) {
joint->point_local_start = xform_invert_mul_v2(xf, cursor);
joint->target = target_ent_handle;
joint->target = target_ent->handle;
}
joint->point_local_end = xform_invert_mul_v2(xf, cursor);
@ -1152,8 +1261,11 @@ INTERNAL void prepare_mouse_joints(void)
joint->linear_impulse = V2(0, 0);
joint->angular_impulse = 0;
#endif
} else {
/* Mark joint for removal */
entity_disable_prop(joint_ent, ENTITY_PROP_ACTIVE);
entity_enable_prop(joint_ent, ENTITY_PROP_RELEASE_AT_END_OF_FRAME);
}
}
}
@ -2176,6 +2288,18 @@ INTERNAL void game_update(struct game_cmd_array game_cmds)
for (u64 i = 0; i < ents_to_release_count; ++i) {
struct entity *ent = ents_to_release[i];
if (ent->valid) {
/* Remove contact constraint from lookup */
if (entity_has_prop(ent, ENTITY_PROP_CONTACT_CONSTRAINT)) {
u64 hash = contact_lookup_hash_from_entities(ent->contact_constraint_data.e0, ent->contact_constraint_data.e1);
struct contact_lookup_entry *entry = contact_lookup_get(&G.contact_lookup, hash);
if (entry) {
contact_lookup_remove(&G.contact_lookup, entry);
} else {
ASSERT(false); /* This should always exist */
}
}
/* Release */
entity_release(store, ent);
}
}