separate physics logic from game.c

This commit is contained in:
jacob 2025-01-10 15:44:07 -06:00
parent f4d8ec73e7
commit 0716ebd398
11 changed files with 1593 additions and 1613 deletions

View File

@ -20,6 +20,7 @@
#include "draw.h"
#include "math.h"
#include "renderer.h"
#include "phys.h"
struct exit_callback {
app_exit_callback_func *func;
@ -213,7 +214,8 @@ void app_entry_point(void)
struct mixer_startup_receipt mixer_sr = mixer_startup();
struct sound_startup_receipt sound_sr = sound_startup(&work_sr, &asset_cache_sr, &resource_sr);
struct draw_startup_receipt draw_sr = draw_startup(&renderer_sr, &font_sr);
struct game_startup_receipt game_sr = game_startup(&mixer_sr, &sprite_sr, &sound_sr);
struct phys_startup_receipt phys_sr = phys_startup();
struct game_startup_receipt game_sr = game_startup(&mixer_sr, &sprite_sr, &sound_sr, &phys_sr);
struct user_startup_receipt user_sr = user_startup(&work_sr, &renderer_sr, &font_sr, &sprite_sr, &draw_sr, &game_sr, &asset_cache_sr, &mixer_sr, &window);
struct playback_startup_receipt playback_sr = playback_startup(&mixer_sr);

View File

@ -852,6 +852,88 @@ struct collider_closest_points_result collider_closest_points(struct collider_sh
return res;
}
/* ========================== *
* Time of impact
* ========================== */
/* Takes 2 shapes and their xforms at t=0 and t=1.
* Returns time of impact in range [0, 1]. */
f32 collider_time_of_impact(struct collider_shape *c0, struct collider_shape *c1,
struct xform xf0_t0, struct xform xf1_t0,
struct xform xf0_t1, struct xform xf1_t1,
f32 tolerance, u32 max_iterations)
{
f32 t0 = 0;
f32 t1 = 1;
f32 t0_sep = 0;
f32 t1_sep = 0;
f32 t = 0;
f32 t_sep = F32_INFINITY;
/* Find direction p0 -> p1 at t=0 */
struct v2 dir;
struct v2 dir_neg;
{
struct collider_closest_points_result closest_points_res = collider_closest_points(c0, c1, xf0_t0, xf1_t0);
if (closest_points_res.colliding) {
/* Shapes are penetrating at t=0 */
return 0;
}
dir = v2_sub(closest_points_res.p1, closest_points_res.p0);
t0_sep = v2_len(dir);
dir = v2_div(dir, t0_sep); /* Normalize */
dir_neg = v2_neg(dir);
}
{
struct v2 p0 = collider_support_point(c0, xf0_t1, dir);
struct v2 p1 = collider_support_point(c1, xf1_t1, dir_neg);
t1_sep = v2_dot(dir, v2_sub(p1, p0));
if (t1_sep > 0) {
/* Shapes are not penetrating at t=1 */
return 1;
}
}
u32 iteration = 0;
while (math_fabs(t_sep) > tolerance) {
if (iteration >= max_iterations) {
break;
}
/* Use mix of bisection & false position method to find root
* (as described in https://box2d.org/files/ErinCatto_ContinuousCollision_GDC2013.pdf) */
if (iteration & 1) {
/* Bisect */
t = (t1 + t0) / 2.0;
} else {
/* False position (fastest for linear case) */
f32 m = (t1_sep - t0_sep) / (t1 - t0);
t = (-t1_sep / m) + t1;
}
struct xform xf0 = xform_lerp(xf0_t0, xf0_t1, t);
struct xform xf1 = xform_lerp(xf1_t0, xf1_t1, t);
struct v2 p0 = collider_support_point(c0, xf0, dir);
struct v2 p1 = collider_support_point(c1, xf1, dir_neg);
t_sep = v2_dot(dir, v2_sub(p1, p0));
/* Update bracket */
if (t_sep > 0) {
t0 = t;
t0_sep = t_sep;
} else {
t1 = t;
t1_sep = t_sep;
}
++iteration;
}
return t;
}
/* ========================== *
* Debug functions
* TODO: Remove these

View File

@ -5,13 +5,6 @@
extern u32 collider_debug_steps;
#endif
struct v2 collider_support_point(struct collider_shape *a, struct xform xf, struct v2 dir);
#if 0
/* Returns simple true or false indicating shape collision */
b32 collider_collision_boolean(struct collider_shape *shape0, struct collider_shape *shape1);
#endif
struct collider_menkowski_point {
struct v2 p; /* Menkowski difference point */
struct v2 s0; /* Support point of first shape in dir */
@ -47,8 +40,6 @@ struct collider_collision_points_result {
struct v2 a0, b0, a1, b1; /* Clipping faces */
};
struct collider_collision_points_result collider_collision_points(struct collider_shape *shape0, struct collider_shape *shape1, struct xform xf0, struct xform xf1);
struct collider_closest_points_result {
struct v2 p0, p1;
b32 colliding;
@ -59,8 +50,14 @@ struct collider_closest_points_result {
struct collider_prototype prototype;
};
struct v2 collider_support_point(struct collider_shape *a, struct xform xf, struct v2 dir);
struct collider_collision_points_result collider_collision_points(struct collider_shape *shape0, struct collider_shape *shape1, struct xform xf0, struct xform xf1);
struct collider_closest_points_result collider_closest_points(struct collider_shape *shape0, struct collider_shape *shape1, struct xform xf0, struct xform xf1);
f32 collider_time_of_impact(struct collider_shape *c0, struct collider_shape *c1, struct xform xf0_t0, struct xform xf1_t0, struct xform xf0_t1, struct xform xf1_t1, f32 tolerance, u32 max_iterations);
struct v2_array menkowski(struct arena *arena, struct collider_shape *shape0, struct collider_shape *shape1, struct xform xf0, struct xform xf1, u32 detail);
struct v2_array cloud(struct arena *arena, struct collider_shape *shape0, struct collider_shape *shape1, struct xform xf0, struct xform xf1);

View File

@ -491,6 +491,11 @@ struct renderer_handle {
u64 v[1];
};
struct entity_handle {
u64 idx;
u64 gen;
};
/* ========================== *
* Tag structs
* ========================== */

View File

@ -397,3 +397,125 @@ void entity_unlink_parent(struct entity *ent)
ent->prev = entity_nil_handle();
ent->next = entity_nil_handle();
}
/* ========================== *
* Entity lookup
* ========================== */
struct entity_lookup entity_lookup_alloc(u64 num_buckets)
{
ASSERT(num_buckets > 0);
struct entity_lookup l = ZI;
l.arena = arena_alloc(GIGABYTE(64));
l.buckets = arena_push_array_zero(&l.arena, struct entity_lookup_bucket, num_buckets);
l.num_buckets = num_buckets;
return l;
}
void entity_lookup_release(struct entity_lookup *l)
{
arena_release(&l->arena);
}
struct entity_lookup_entry *entity_lookup_get(struct entity_lookup *l, struct entity_lookup_key key)
{
u64 index = key.hash % l->num_buckets;
struct entity_lookup_bucket *bucket = &l->buckets[index];
struct entity_lookup_entry *res = NULL;
for (struct entity_lookup_entry *e = bucket->first; e; e = e->next) {
if (e->key.hash == key.hash) {
res = e;
break;
}
}
return res;
}
void entity_lookup_set(struct entity_lookup *l, struct entity_lookup_key key, struct entity_handle handle)
{
u64 index = key.hash % l->num_buckets;
struct entity_lookup_bucket *bucket = &l->buckets[index];
struct entity_lookup_entry *prev = NULL;
struct entity_lookup_entry **slot = &bucket->first;
while (*slot) {
if ((*slot)->key.hash == key.hash) {
break;
}
prev = *slot;
slot = &(*slot)->next;
}
struct entity_lookup_entry *entry = *slot;
if (entry) {
/* Set existing entry */
entry->entity = 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 entity_lookup_entry);
}
MEMZERO_STRUCT(entry);
entry->key = key;
entry->entity = handle;
if (prev) {
entry->prev = prev;
prev->next = entry;
}
bucket->last = entry;
*slot = entry;
}
}
void entity_lookup_remove(struct entity_lookup *l, struct entity_lookup_entry *entry)
{
struct entity_lookup_bucket *bucket = &l->buckets[entry->key.hash % l->num_buckets];
struct entity_lookup_entry *prev = entry->prev;
struct entity_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;
}
struct entity_lookup_key entity_lookup_key_from_two_handles(struct entity_handle h0, struct entity_handle h1)
{
struct entity_lookup_key key = ZI;
struct buffer b0 = BUFFER_FROM_STRUCT(&h0);
struct buffer b1 = BUFFER_FROM_STRUCT(&h1);
key.hash = hash_fnv64(HASH_FNV64_BASIS, b0);
key.hash = hash_fnv64(key.hash, b1);
return key;
}
/* ========================== *
* Activate
* ========================== */
void entity_activate(struct entity *ent, u64 current_tick_id)
{
entity_enable_prop(ent, ENTITY_PROP_ACTIVE);
ent->activation_tick = current_tick_id;
++ent->continuity_gen;
}

View File

@ -3,6 +3,7 @@
#include "sprite.h"
#include "mixer.h"
#include "phys.h"
enum entity_prop {
ENTITY_PROP_NONE,
@ -44,11 +45,6 @@ enum entity_prop {
ENTITY_PROP_COUNT
};
struct entity_handle {
u64 idx;
u64 gen;
};
struct entity_store {
struct arena arena;
u64 allocated;
@ -58,154 +54,29 @@ struct entity_store {
struct entity *entities;
};
/* TODO: Remove this */
#include "collider.h"
#include "math.h"
struct contact_point {
/* Contact point in local space of each entity */
struct v2 point_local_e0;
struct v2 point_local_e1;
u32 id; /* ID generated during clipping */
f32 starting_separation; /* How far are original points along normal */
f32 normal_impulse; /* Accumulated impulse along normal */
f32 tangent_impulse; /* Accumulated impulse along tangent */
f32 inv_normal_mass;
f32 inv_tangent_mass;
/* Debugging */
struct v2 dbg_pt;
struct entity_lookup_key {
u64 hash;
};
struct contact_constraint {
u64 last_updated_tick; /* To avoid checking collisions for the same constraint twice in one tick */
b32 skip_solve;
struct entity_handle e0;
struct entity_handle e1;
f32 inv_m0;
f32 inv_m1;
f32 inv_i0;
f32 inv_i1;
struct v2 normal; /* Normal vector of collision from e0 -> e1 */
u64 last_iteration;
struct contact_point points[2];
u32 num_points;
f32 friction;
struct math_spring_result softness;
f32 pushout_velocity;
struct entity_lookup_entry {
struct entity_lookup_key key;
struct entity_handle entity;
struct entity_lookup_entry *next;
struct entity_lookup_entry *prev;
};
struct collision_debug {
struct entity_handle e0;
struct entity_handle e1;
struct collider_collision_points_result res;
struct contact_point points[2];
u32 num_points;
struct v2 closest0;
struct v2 closest1;
struct xform xf0;
struct xform xf1;
struct entity_lookup_bucket {
struct entity_lookup_entry *first;
struct entity_lookup_entry *last;
};
struct motor_joint_def {
struct entity_handle e0;
struct entity_handle e1;
f32 correction_rate;
f32 max_force;
f32 max_torque;
struct entity_lookup {
struct arena arena;
struct entity_lookup_bucket *buckets;
u64 num_buckets;
struct entity_lookup_entry *first_free_entry;
};
struct motor_joint {
struct entity_handle e0;
struct entity_handle e1;
f32 correction_rate;
f32 max_force;
f32 max_torque;
f32 inv_m0;
f32 inv_m1;
f32 inv_i0;
f32 inv_i1;
struct v2 linear_impulse;
f32 angular_impulse;
struct v2 point_local_e0;
struct v2 point_local_e1;
struct xform linear_mass_xf;
f32 angular_mass;
};
INLINE struct motor_joint motor_joint_from_def(struct motor_joint_def def)
{
struct motor_joint res = ZI;
res.e0 = def.e0;
res.e1 = def.e1;
res.correction_rate = clamp_f32(def.correction_rate, 0, 1);
res.max_force = def.max_force;
res.max_torque = def.max_torque;
return res;
}
struct mouse_joint {
struct entity_handle target;
struct v2 point_local_start;
struct v2 point_local_end;
struct math_spring_result linear_softness;
struct math_spring_result angular_softness;
f32 max_force;
f32 inv_m;
f32 inv_i;
struct v2 linear_impulse;
f32 angular_impulse;
struct xform linear_mass_xf;
};
struct hit_event {
struct entity_handle e0;
struct entity_handle e1;
struct v2 point;
struct v2 normal;
struct v2 vrel; /* Relative velocity */
};
struct entity {
/* ====================================================================== */
/* Metadata */
@ -229,50 +100,38 @@ struct entity {
struct entity_handle first;
struct entity_handle last;
/* TODO: Remove this (testing) */
i32 colliding;
struct collision_debug collision_debug_data;
/* ====================================================================== */
/* Collider */
struct collider_shape local_collider;
#if COLLIDER_DEBUG
i32 colliding;
struct phys_collision_debug collision_debug_data;
#endif
/* ====================================================================== */
/* Contact constraint */
/* ENTITY_PROP_CONSTRAINT_CONTACT */
struct contact_constraint contact_constraint_data;
struct phys_contact_constraint contact_constraint_data;
/* ====================================================================== */
/* Motor joint */
/* ENTITY_PROP_MOTOR_JOINT */
struct motor_joint motor_joint_data;
struct phys_motor_joint motor_joint_data;
/* ====================================================================== */
/* Mouse joint */
/* ENTITY_PROP_MOUSE_JOINT */
struct mouse_joint mouse_joint_data;
struct phys_mouse_joint mouse_joint_data;
/* ====================================================================== */
/* Hit event */
struct hit_event hit_event;
struct phys_hit_event hit_event;
/* ====================================================================== */
/* Activation */
@ -503,4 +362,15 @@ struct entity *entity_find_first_match_all(struct entity_store *store, struct en
void entity_link_parent(struct entity *parent, struct entity *child);
void entity_unlink_parent(struct entity *ent);
/* Lookup */
struct entity_lookup entity_lookup_alloc(u64 num_buckets);
void entity_lookup_release(struct entity_lookup *l);
struct entity_lookup_entry *entity_lookup_get(struct entity_lookup *l, struct entity_lookup_key key);
void entity_lookup_set(struct entity_lookup *l, struct entity_lookup_key key, struct entity_handle handle);
void entity_lookup_remove(struct entity_lookup *l, struct entity_lookup_entry *entry);
struct entity_lookup_key entity_lookup_key_from_two_handles(struct entity_handle h0, struct entity_handle h1);
/* Activate */
void entity_activate(struct entity *ent, u64 current_tick_id);
#endif

1457
src/game.c

File diff suppressed because it is too large Load Diff

View File

@ -5,6 +5,7 @@ struct world;
struct mixer_startup_receipt;
struct sprite_startup_receipt;
struct sound_startup_receipt;
struct phys_startup_receipt;
enum game_cmd_state {
GAME_CMD_STATE_STOP = -1,
@ -53,7 +54,8 @@ struct game_cmd_array {
struct game_startup_receipt { i32 _; };
struct game_startup_receipt game_startup(struct mixer_startup_receipt *mixer_sr,
struct sprite_startup_receipt *sheet_sr,
struct sound_startup_receipt *sound_sr);
struct sound_startup_receipt *sound_sr,
struct phys_startup_receipt *phys_sr);
void game_get_latest_tick(struct world *dest);
u64 game_get_latest_tick_id(void);

1134
src/phys.c Normal file

File diff suppressed because it is too large Load Diff

171
src/phys.h Normal file
View File

@ -0,0 +1,171 @@
#ifndef PHYS_H
#define PHYS_H
#include "collider.h"
#include "math.h"
struct entity_store;
struct entity_lookup;
struct phys_hit_event {
struct entity_handle e0;
struct entity_handle e1;
struct v2 point;
struct v2 normal;
struct v2 vrel; /* Relative velocity */
};
/* ========================== *
* Startup
* ========================== */
struct phys_startup_receipt { i32 _; };
struct phys_startup_receipt phys_startup(void);
/* ========================== *
* Contact
* ========================== */
struct phys_contact_point {
/* Contact point in local space of each entity */
struct v2 point_local_e0;
struct v2 point_local_e1;
u32 id; /* ID generated during clipping */
f32 starting_separation; /* How far are original points along normal */
f32 normal_impulse; /* Accumulated impulse along normal */
f32 tangent_impulse; /* Accumulated impulse along tangent */
f32 inv_normal_mass;
f32 inv_tangent_mass;
/* Debugging */
struct v2 dbg_pt;
};
struct phys_contact_constraint {
u64 last_updated_tick; /* To avoid checking collisions for the same constraint twice in one tick */
b32 skip_solve;
struct entity_handle e0;
struct entity_handle e1;
f32 inv_m0;
f32 inv_m1;
f32 inv_i0;
f32 inv_i1;
struct v2 normal; /* Normal vector of collision from e0 -> e1 */
u64 last_iteration;
struct phys_contact_point points[2];
u32 num_points;
f32 friction;
struct math_spring_result softness;
f32 pushout_velocity;
};
struct phys_collision_debug {
struct entity_handle e0;
struct entity_handle e1;
struct collider_collision_points_result res;
struct phys_contact_point points[2];
u32 num_points;
struct v2 closest0;
struct v2 closest1;
struct xform xf0;
struct xform xf1;
};
void phys_create_contacts(struct entity_store *store, struct entity_lookup *contact_lookup, struct entity_lookup *debug_lookup, u64 tick_id);
void phys_prepare_contacts(struct entity_store *store, struct entity_lookup *contact_lookup, struct entity_lookup *debug_lookup);
void phys_warm_start_contacts(struct entity_store *store);
void phys_solve_contacts(struct entity_store *store, f32 dt, b32 apply_bias);
/* ========================== *
* Motor joint
* ========================== */
struct phys_motor_joint_def {
struct entity_handle e0;
struct entity_handle e1;
f32 correction_rate;
f32 max_force;
f32 max_torque;
};
struct phys_motor_joint {
struct entity_handle e0;
struct entity_handle e1;
f32 correction_rate;
f32 max_force;
f32 max_torque;
f32 inv_m0;
f32 inv_m1;
f32 inv_i0;
f32 inv_i1;
struct v2 linear_impulse;
f32 angular_impulse;
struct v2 point_local_e0;
struct v2 point_local_e1;
struct xform linear_mass_xf;
f32 angular_mass;
};
struct phys_motor_joint motor_joint_from_def(struct phys_motor_joint_def def);
void phys_prepare_motor_joints(struct entity_store *store);
void phys_warm_start_motor_joints(struct entity_store *store);
void phys_solve_motor_joints(struct entity_store *store, f32 dt);
/* ========================== *
* Mouse joint
* ========================== */
struct phys_mouse_joint {
struct entity_handle target;
struct v2 point_local_start;
struct v2 point_local_end;
struct math_spring_result linear_softness;
struct math_spring_result angular_softness;
f32 max_force;
f32 inv_m;
f32 inv_i;
struct v2 linear_impulse;
f32 angular_impulse;
struct xform linear_mass_xf;
};
void phys_create_mouse_joints(struct entity_store *store, struct v2 cursor, b32 start_dragging, b32 stop_dragging);
void phys_prepare_mouse_joints(struct entity_store *store);
void phys_warm_start_mouse_joints(struct entity_store *store);
void phys_solve_mouse_joints(struct entity_store *store, f32 dt);
/* ========================== *
* Earliest time of impact
* ========================== */
f32 phys_determine_earliest_toi_for_bullets(struct entity_store *store, f32 step_dt, f32 tolerance, u32 max_iterations);
/* ========================== *
* Integration
* ========================== */
void phys_integrate_forces(struct entity_store *store, f32 dt);
void phys_integrate_velocities(struct entity_store *store, f32 dt);
/* ========================== *
* Step
* ========================== */
void phys_step(struct entity_store *store, f32 step_dt, struct entity_lookup *contact_lookup, struct entity_lookup *collision_debug_lookup, u64 tick_id, struct v2 dbg_cursor_pos, b32 dbg_start_dragging, b32 dbg_stop_dragging);
#endif

View File

@ -1072,11 +1072,11 @@ INTERNAL void user_update(void)
/* Draw constraint */
#if 1
if (entity_has_prop(ent, ENTITY_PROP_COLLISION_DEBUG)) {
struct collision_debug *data = &ent->collision_debug_data;
struct phys_collision_debug *data = &ent->collision_debug_data;
struct collider_collision_points_result collider_res = data->res;
//struct contact_constraint *data = &entity_from_handle(store, data->contact_constraint_ent)->contact_constraint_data;
//struct contact_constraint *data = &data->contact_constraint_data;
//struct phys_contact_constraint *data = &entity_from_handle(store, data->contact_constraint_ent)->contact_constraint_data;
//struct phys_contact_constraint *data = &data->contact_constraint_data;
struct entity *e0 = entity_from_handle(store, data->e0);
struct entity *e1 = entity_from_handle(store, data->e1);
struct collider_shape e0_collider = e0->local_collider;
@ -1216,7 +1216,7 @@ INTERNAL void user_update(void)
{
f32 radius = 5;
for (u32 i = 0; i < data->num_points; ++i) {
struct contact_point point = data->points[i];
struct phys_contact_point point = data->points[i];
#if 0
struct v2 p0 = xform_mul_v2(e0_xf, contact.point_local_e0);
struct v2 p1 = xform_mul_v2(e1_xf, contact.point_local_e1);