contact lookup, joint / contact removal
This commit is contained in:
parent
f5e45d6ba1
commit
bfa1f1e065
@ -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;
|
||||
|
||||
326
src/game.c
326
src/game.c
@ -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);
|
||||
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 */
|
||||
continue;
|
||||
} else {
|
||||
t->contact_constraint_data.last_iteration = constraint_iteration;
|
||||
constraint_ent = t;
|
||||
}
|
||||
} else {
|
||||
/* Constraint entity no longer valid */
|
||||
fixed_dict_set(&dict_arena, &dict, constraint_key, NULL);
|
||||
struct entity *constraint_ent = entity_nil();
|
||||
if (entry) {
|
||||
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 {
|
||||
++constraint_ent->contact_constraint_data.last_iteration;
|
||||
}
|
||||
} else {
|
||||
/* 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);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user