#ifdef WITH_PHYSICS #include "iron_physics.h" #include "engine.h" #include #include #include #include #include #define GRAVITY -9.81f #define MAX_BVH_DEPTH 20 #define MAX_SPHERES 32 #define MAX_BOXES 32 #define MAX_BODIES (MAX_SPHERES + MAX_BOXES) #define MAX_TERRAIN_RES 1024 #define SPHERE_TAG 0x10000 #define BOX_TAG 0x20000 #define TERRAIN_TAG 0x40000 #define SLOT_MASK 0xffff typedef struct { vec4_t min; vec4_t max; } aabb_t; typedef struct { vec4_t position; vec4_t velocity; float radius; float mass; vec4_t sleep_pos; float sleep_timer; int sleeping; int active; } sphere_t; typedef struct { vec4_t position; vec4_t velocity; quat_t rotation; vec4_t angular; vec4_t half; float mass; float inv_inertia; vec4_t sleep_pos; quat_t sleep_rot; float sleep_timer; int sleeping; int active; } box_t; typedef struct { vec4_t v0; vec4_t v1; vec4_t v2; vec4_t normal; aabb_t bounds; } triangle_t; typedef struct bvh_node { aabb_t bounds; struct bvh_node *left; struct bvh_node *right; triangle_t *triangles; int num_tris; int is_leaf; } bvh_node_t; typedef struct { bvh_node_t *root; } mesh_t; typedef struct { float *heights; int res_x; int res_y; vec4_t min; float size_x; float size_y; float min_h; float max_h; int active; } terrain_t; static sphere_t spheres[MAX_SPHERES]; static box_t boxes[MAX_BOXES]; static physics_pair_t pairs[MAX_BODIES]; static float pair_best[MAX_BODIES]; static physics_pair_t null_pair; static mesh_t mesh; static terrain_t terrain; static aabb_t root_bounds = {{-10, -10, -10}, {10, 10, 10}}; static float physics_bounciness = 0.0f; static float physics_friction = 0.01f; static vec4_t physics_gravity = {0.0f, 0.0f, GRAVITY}; static inline int box_pair(int slot) { return MAX_SPHERES + slot; } static inline aabb_t merge_aabbs(aabb_t a, aabb_t b) { return (aabb_t){.min = {fminf(a.min.x, b.min.x), fminf(a.min.y, b.min.y), fminf(a.min.z, b.min.z)}, .max = {fmaxf(a.max.x, b.max.x), fmaxf(a.max.y, b.max.y), fmaxf(a.max.z, b.max.z)}}; } static inline float min3(float a, float b, float c) { return fminf(a, fminf(b, c)); } static inline float max3(float a, float b, float c) { return fmaxf(a, fmaxf(b, c)); } static inline int sphere_aabb_intersect(sphere_t *s, aabb_t *a) { float x = fmaxf(a->min.x, fminf(s->position.x, a->max.x)); float y = fmaxf(a->min.y, fminf(s->position.y, a->max.y)); float z = fmaxf(a->min.z, fminf(s->position.z, a->max.z)); float dx = x - s->position.x, dy = y - s->position.y, dz = z - s->position.z; return dx * dx + dy * dy + dz * dz <= s->radius * s->radius; } static int compare_triangles(const void *a, const void *b) { triangle_t *ta = (triangle_t *)a; triangle_t *tb = (triangle_t *)b; vec4_t ca = vec4_mult(vec4_add(vec4_add(ta->v0, ta->v1), ta->v2), 1.0f / 3.0f); vec4_t cb = vec4_mult(vec4_add(vec4_add(tb->v0, tb->v1), tb->v2), 1.0f / 3.0f); return (ca.x > cb.x) - (ca.x < cb.x); } static bvh_node_t *create_bvh_node(triangle_t *tris, int num_tris, int depth) { bvh_node_t *node = (bvh_node_t *)malloc(sizeof(bvh_node_t)); *node = (bvh_node_t){.is_leaf = 1}; if (num_tris <= 1 || depth >= MAX_BVH_DEPTH) { node->num_tris = num_tris; node->bounds = root_bounds; if (num_tris) { node->triangles = (triangle_t *)malloc(num_tris * sizeof(triangle_t)); memcpy(node->triangles, tris, num_tris * sizeof(triangle_t)); // A leaf at the depth limit can hold many triangles, so cover them all node->bounds = tris[0].bounds; for (int i = 1; i < num_tris; i++) { node->bounds = merge_aabbs(node->bounds, tris[i].bounds); } } return node; } qsort(tris, num_tris, sizeof(triangle_t), compare_triangles); int mid = num_tris / 2; node->is_leaf = 0; node->left = create_bvh_node(tris, mid, depth + 1); node->right = create_bvh_node(tris + mid, num_tris - mid, depth + 1); node->bounds = merge_aabbs(node->left->bounds, node->right->bounds); return node; } static void report_contact(int index, float depth, vec4_t point, vec4_t normal) { if (depth <= pair_best[index]) { return; } pair_best[index] = depth; pairs[index] = (physics_pair_t){point.x, point.y, point.z, normal.x, normal.y, normal.z}; } static void collide_sphere_triangle(sphere_t *s, int si, triangle_t *t) { if (!sphere_aabb_intersect(s, &t->bounds)) { return; } vec4_t to_sphere = vec4_sub(s->position, t->v0); float dist = vec4_dot(to_sphere, t->normal); if (dist < 0.0f || dist > s->radius) { return; } vec4_t p = vec4_sub(s->position, vec4_mult(t->normal, dist)); vec4_t e0 = vec4_sub(t->v1, t->v0), e1 = vec4_sub(t->v2, t->v1), e2 = vec4_sub(t->v0, t->v2); vec4_t c0 = vec4_sub(p, t->v0), c1 = vec4_sub(p, t->v1), c2 = vec4_sub(p, t->v2); if (vec4_dot(t->normal, vec4_cross(e0, c0)) >= 0 && vec4_dot(t->normal, vec4_cross(e1, c1)) >= 0 && vec4_dot(t->normal, vec4_cross(e2, c2)) >= 0) { float orig_dist = dist; if (!s->sleeping) { s->position = vec4_add(s->position, vec4_mult(t->normal, s->radius - dist)); float v_dot_n = vec4_dot(s->velocity, t->normal); if (v_dot_n < 0.0f) { vec4_t n_vel = vec4_mult(t->normal, v_dot_n); vec4_t t_vel = vec4_sub(s->velocity, n_vel); s->velocity = vec4_add(vec4_mult(n_vel, -physics_bounciness), vec4_mult(t_vel, 1.0f - physics_friction)); } } vec4_t contact_point = vec4_sub(s->position, vec4_mult(t->normal, s->radius)); report_contact(si, s->radius - orig_dist, contact_point, t->normal); } } static void query_bvh(sphere_t *s, int si, bvh_node_t *n) { if (!n || !sphere_aabb_intersect(s, &n->bounds)) { return; } if (n->is_leaf) { for (int i = 0; i < n->num_tris; i++) { collide_sphere_triangle(s, si, &n->triangles[i]); } } else { query_bvh(s, si, n->left); query_bvh(s, si, n->right); } } static void free_bvh(bvh_node_t *n) { if (!n) { return; } if (n->is_leaf) { free(n->triangles); } else { free_bvh(n->left); free_bvh(n->right); } free(n); } static inline int body_is_sphere(void *body) { return ((uintptr_t)body & SPHERE_TAG) != 0; } static inline int body_is_box(void *body) { return ((uintptr_t)body & BOX_TAG) != 0; } static inline int body_is_terrain(void *body) { return ((uintptr_t)body & TERRAIN_TAG) != 0; } static inline int body_slot(void *body) { return (int)((uintptr_t)body & SLOT_MASK); } #define CONTACT_FRICTION 0.6f #define ANGULAR_DAMPING 0.4f #define SPHERE_RESTITUTION 0.3f #define SLEEP_DISTANCE 0.002f #define SLEEP_TURN 0.9999f // Quaternion dot, a little under a degree #define SLEEP_TIME 0.4f #define REST_DAMPING 6.0f #define PENETRATION_SLOP 0.002f #define CONTACT_TOLERANCE 0.0001f typedef struct { vec4_t *position; vec4_t *velocity; vec4_t *angular; float inv_mass; float inv_inertia; float *sleep_timer; int *sleeping; } rigid_t; static vec4_t rigid_zero; static float rigid_no_timer = SLEEP_TIME; // The static world is always settled static int rigid_never_sleeps; static rigid_t rigid_static(void) { return (rigid_t){&rigid_zero, &rigid_zero, &rigid_zero, 0.0f, 0.0f, &rigid_no_timer, &rigid_never_sleeps}; } static rigid_t rigid_box(box_t *b) { float inv_mass = b->mass > 0.0f && !b->sleeping ? 1.0f / b->mass : 0.0f; return (rigid_t){&b->position, &b->velocity, &b->angular, inv_mass, b->sleeping ? 0.0f : b->inv_inertia, &b->sleep_timer, &b->sleeping}; } static rigid_t rigid_sphere(sphere_t *s) { float inv_mass = s->mass > 0.0f && !s->sleeping ? 1.0f / s->mass : 0.0f; return (rigid_t){&s->position, &s->velocity, &rigid_zero, inv_mass, 0.0f, &s->sleep_timer, &s->sleeping}; } static void rigid_wake(rigid_t body) { if (!*body.sleeping) { return; } *body.sleeping = 0; *body.sleep_timer = 0.0f; } static void sphere_wake(sphere_t *s) { if (!s->sleeping) { return; } s->sleeping = 0; s->sleep_timer = 0.0f; } static inline int rigid_on_the_move(rigid_t body) { return *body.sleep_timer == 0.0f; } typedef struct { vec4_t *position; quat_t *rotation; // NULL for a body whose orientation is not simulated vec4_t *velocity; vec4_t *angular; vec4_t *anchor_pos; quat_t *anchor_rot; float *timer; int *sleeping; } settling_t; static void sleep_update(settling_t s, int touching, float dt) { if (*s.timer == 0.0f) { // Start of a new window, take the pose to compare against *s.anchor_pos = *s.position; if (s.rotation != NULL) { *s.anchor_rot = *s.rotation; } } int still = touching && vec4_len(vec4_sub(*s.position, *s.anchor_pos)) <= SLEEP_DISTANCE; if (still && s.rotation != NULL) { still = fabsf(quat_dot(*s.rotation, *s.anchor_rot)) >= SLEEP_TURN; } if (!still) { *s.timer = 0.0f; return; } *s.timer += dt; if (*s.timer < SLEEP_TIME) { return; } *s.velocity = (vec4_t){0.0f, 0.0f, 0.0f}; *s.angular = (vec4_t){0.0f, 0.0f, 0.0f}; *s.sleeping = 1; } static void apply_impulse(rigid_t body, vec4_t r, vec4_t impulse) { if (body.inv_mass > 0.0f) { *body.velocity = vec4_add(*body.velocity, vec4_mult(impulse, body.inv_mass)); } if (body.inv_inertia > 0.0f) { *body.angular = vec4_add(*body.angular, vec4_mult(vec4_cross(r, impulse), body.inv_inertia)); } } static void resolve_contact_pair(rigid_t a, rigid_t b, vec4_t point, vec4_t n, float depth) { float inv_sum = a.inv_mass + b.inv_mass; if (inv_sum <= 0.0f) { // Both static return; } const float correction = 0.6f; float push = depth - PENETRATION_SLOP; if (push > 0.0f) { if (a.inv_mass > 0.0f) { *a.position = vec4_add(*a.position, vec4_mult(n, push * correction * a.inv_mass / inv_sum)); } if (b.inv_mass > 0.0f) { *b.position = vec4_sub(*b.position, vec4_mult(n, push * correction * b.inv_mass / inv_sum)); } } vec4_t ra = vec4_sub(point, *a.position); vec4_t rb = vec4_sub(point, *b.position); vec4_t rel = vec4_sub(vec4_add(*a.velocity, vec4_cross(*a.angular, ra)), vec4_add(*b.velocity, vec4_cross(*b.angular, rb))); if (rigid_on_the_move(a) || rigid_on_the_move(b)) { rigid_wake(a); rigid_wake(b); } float closing = vec4_dot(rel, n); if (closing >= 0.0f) { // Already moving apart return; } vec4_t ran = vec4_cross(ra, n); vec4_t rbn = vec4_cross(rb, n); float denom = inv_sum + a.inv_inertia * vec4_dot(ran, ran) + b.inv_inertia * vec4_dot(rbn, rbn); float impulse = -(1.0f + physics_bounciness) * closing / denom; apply_impulse(a, ra, vec4_mult(n, impulse)); apply_impulse(b, rb, vec4_mult(n, -impulse)); vec4_t tangent = vec4_sub(rel, vec4_mult(n, closing)); float sliding = vec4_len(tangent); if (sliding < 0.0001f) { return; } tangent = vec4_mult(tangent, 1.0f / sliding); vec4_t rat = vec4_cross(ra, tangent); vec4_t rbt = vec4_cross(rb, tangent); float denom_t = inv_sum + a.inv_inertia * vec4_dot(rat, rat) + b.inv_inertia * vec4_dot(rbt, rbt); float stop = sliding / denom_t; float limit = CONTACT_FRICTION * impulse; if (stop > limit) { stop = limit; } apply_impulse(a, ra, vec4_mult(tangent, -stop)); apply_impulse(b, rb, vec4_mult(tangent, stop)); } static inline quat_t quat_conj(quat_t q) { return (quat_t){-q.x, -q.y, -q.z, q.w}; } static vec4_t box_corner(box_t *b, int c) { vec4_t local = {(c & 1) ? b->half.x : -b->half.x, (c & 2) ? b->half.y : -b->half.y, (c & 4) ? b->half.z : -b->half.z}; return vec4_add(b->position, vec4_apply_quat(local, b->rotation)); } static inline vec4_t box_axis(box_t *b, int i) { vec4_t unit = {i == 0 ? 1.0f : 0.0f, i == 1 ? 1.0f : 0.0f, i == 2 ? 1.0f : 0.0f}; return vec4_apply_quat(unit, b->rotation); } static float box_extent(box_t *b, vec4_t axis) { return fabsf(b->half.x * vec4_dot(box_axis(b, 0), axis)) + fabsf(b->half.y * vec4_dot(box_axis(b, 1), axis)) + fabsf(b->half.z * vec4_dot(box_axis(b, 2), axis)); } static int box_contains_point(box_t *b, vec4_t point) { vec4_t local = vec4_apply_quat(vec4_sub(point, b->position), quat_conj(b->rotation)); return fabsf(local.x) <= b->half.x + CONTACT_TOLERANCE && fabsf(local.y) <= b->half.y + CONTACT_TOLERANCE && fabsf(local.z) <= b->half.z + CONTACT_TOLERANCE; } static int box_contact_normal(box_t *a, box_t *b, vec4_t *out_normal, float *out_depth) { vec4_t delta = vec4_sub(a->position, b->position); vec4_t best_axis = {0.0f, 0.0f, 1.0f}; float best = 0.0f; for (int i = 0; i < 6; i++) { vec4_t axis = i < 3 ? box_axis(a, i) : box_axis(b, i - 3); float overlap = box_extent(a, axis) + box_extent(b, axis) - fabsf(vec4_dot(delta, axis)); if (overlap < 0.0f) { return 0; // A gap along this direction, so the boxes are apart } if (i == 0 || overlap < best) { best = overlap; best_axis = axis; } } *out_normal = vec4_dot(delta, best_axis) < 0.0f ? vec4_mult(best_axis, -1.0f) : best_axis; *out_depth = best; return 1; } static int box_point_depth(box_t *b, vec4_t point, vec4_t *out_normal, float *out_depth) { vec4_t local = vec4_apply_quat(vec4_sub(point, b->position), quat_conj(b->rotation)); float dx = b->half.x - fabsf(local.x); float dy = b->half.y - fabsf(local.y); float dz = b->half.z - fabsf(local.z); if (dx <= 0.0f || dy <= 0.0f || dz <= 0.0f) { return 0; } vec4_t axis = {0.0f, 0.0f, 0.0f}; if (dx <= dy && dx <= dz) { axis.x = local.x < 0.0f ? -1.0f : 1.0f; *out_depth = dx; } else if (dy <= dz) { axis.y = local.y < 0.0f ? -1.0f : 1.0f; *out_depth = dy; } else { axis.z = local.z < 0.0f ? -1.0f : 1.0f; *out_depth = dz; } *out_normal = vec4_apply_quat(axis, b->rotation); return 1; } static vec4_t box_closest_point(box_t *b, vec4_t point) { vec4_t local = vec4_apply_quat(vec4_sub(point, b->position), quat_conj(b->rotation)); local.x = fmaxf(-b->half.x, fminf(local.x, b->half.x)); local.y = fmaxf(-b->half.y, fminf(local.y, b->half.y)); local.z = fmaxf(-b->half.z, fminf(local.z, b->half.z)); return vec4_add(b->position, vec4_apply_quat(local, b->rotation)); } typedef struct { vec4_t position; vec4_t velocity; vec4_t angular; int woke; int count; } contact_sum_t; static void contact_sum_add(contact_sum_t *sum, box_t *solved, box_t *start) { sum->position = vec4_add(sum->position, vec4_sub(solved->position, start->position)); sum->velocity = vec4_add(sum->velocity, vec4_sub(solved->velocity, start->velocity)); sum->angular = vec4_add(sum->angular, vec4_sub(solved->angular, start->angular)); sum->woke |= start->sleeping && !solved->sleeping; } static void contact_sum_apply(contact_sum_t *sum, box_t *b) { float share = 1.0f / sum->count; b->position = vec4_add(b->position, vec4_mult(sum->position, share)); b->velocity = vec4_add(b->velocity, vec4_mult(sum->velocity, share)); b->angular = vec4_add(b->angular, vec4_mult(sum->angular, share)); if (sum->woke) { b->sleeping = 0; b->sleep_timer = 0.0f; } } static void collide_box_corners(int ai, int bi) { box_t start_a = boxes[ai]; box_t start_b = boxes[bi]; vec4_t n; float overlap; if (!box_contact_normal(&start_a, &start_b, &n, &overlap)) { return; } // Where the face of b that a has to be pushed back out through sits along n float surface = vec4_dot(start_b.position, n) + box_extent(&start_b, n); contact_sum_t sum_a = {0}; contact_sum_t sum_b = {0}; for (int c = 0; c < 8; c++) { vec4_t corner = box_corner(&start_a, c); if (!box_contains_point(&start_b, corner)) { continue; } // Never push a corner further than it takes to part the two boxes float depth = surface - vec4_dot(corner, n); if (depth <= 0.0f) { continue; } if (depth > overlap) { depth = overlap; } box_t solved_a = start_a; box_t solved_b = start_b; resolve_contact_pair(rigid_box(&solved_a), rigid_box(&solved_b), corner, n, depth); contact_sum_add(&sum_a, &solved_a, &start_a); contact_sum_add(&sum_b, &solved_b, &start_b); sum_a.count++; sum_b.count++; report_contact(box_pair(ai), depth, corner, n); report_contact(box_pair(bi), depth, corner, vec4_mult(n, -1.0f)); } if (sum_a.count == 0) { return; } contact_sum_apply(&sum_a, &boxes[ai]); contact_sum_apply(&sum_b, &boxes[bi]); } static void collide_box_box(int i, int j) { box_t *a = &boxes[i]; box_t *b = &boxes[j]; if (a->mass == 0.0f && b->mass == 0.0f) { return; } if (vec4_len(vec4_sub(b->position, a->position)) > vec4_len(a->half) + vec4_len(b->half)) { return; } collide_box_corners(i, j); collide_box_corners(j, i); } static void collide_box_sphere(int bi, int si) { box_t *b = &boxes[bi]; sphere_t *s = &spheres[si]; vec4_t closest = box_closest_point(b, s->position); vec4_t delta = vec4_sub(s->position, closest); float dist = vec4_len(delta); vec4_t n; // Points from the box towards the sphere float depth; if (dist > 0.0001f) { if (dist >= s->radius) { return; } n = vec4_mult(delta, 1.0f / dist); depth = s->radius - dist; } else { // Center is inside the box if (!box_point_depth(b, s->position, &n, &depth)) { return; } depth += s->radius; } vec4_t point = vec4_sub(s->position, vec4_mult(n, s->radius)); resolve_contact_pair(rigid_sphere(s), rigid_box(b), point, n, depth); report_contact(box_pair(bi), depth, point, vec4_mult(n, -1.0f)); } static void collide_sphere_sphere(int i, int j) { sphere_t *a = &spheres[i]; sphere_t *b = &spheres[j]; vec4_t delta = vec4_sub(a->position, b->position); float dist = vec4_len(delta); float min_dist = a->radius + b->radius; if (dist >= min_dist || dist < 0.0001f) { return; } float inv_a = a->mass > 0.0f && !a->sleeping ? 1.0f / a->mass : 0.0f; float inv_b = b->mass > 0.0f && !b->sleeping ? 1.0f / b->mass : 0.0f; float inv_sum = inv_a + inv_b; if (inv_sum <= 0.0f) { // Both static or both resting return; } vec4_t n = vec4_mult(delta, 1.0f / dist); float overlap = min_dist - dist - PENETRATION_SLOP; if (overlap > 0.0f) { a->position = vec4_add(a->position, vec4_mult(n, overlap * inv_a / inv_sum)); b->position = vec4_sub(b->position, vec4_mult(n, overlap * inv_b / inv_sum)); } float closing = vec4_dot(a->velocity, n) - vec4_dot(b->velocity, n); if (a->sleep_timer == 0.0f || b->sleep_timer == 0.0f) { sphere_wake(a); sphere_wake(b); } if (closing < 0.0f) { float impulse = -(1.0f + SPHERE_RESTITUTION) * closing / inv_sum; a->velocity = vec4_add(a->velocity, vec4_mult(n, impulse * inv_a)); b->velocity = vec4_sub(b->velocity, vec4_mult(n, impulse * inv_b)); } } static int terrain_sample(float x, float y, float *out_height, vec4_t *out_normal) { if (!terrain.active) { return 0; } float cell_x = terrain.size_x / (terrain.res_x - 1); float cell_y = terrain.size_y / (terrain.res_y - 1); float fx = (x - terrain.min.x) / cell_x; float fy = (y - terrain.min.y) / cell_y; if (fx < 0.0f || fy < 0.0f || fx > terrain.res_x - 1 || fy > terrain.res_y - 1) { return 0; } int ix = (int)fx; int iy = (int)fy; if (ix > terrain.res_x - 2) { ix = terrain.res_x - 2; } if (iy > terrain.res_y - 2) { iy = terrain.res_y - 2; } float tx = fx - ix; float ty = fy - iy; float h00 = terrain.heights[iy * terrain.res_x + ix]; float h10 = terrain.heights[iy * terrain.res_x + ix + 1]; float h01 = terrain.heights[(iy + 1) * terrain.res_x + ix]; float h11 = terrain.heights[(iy + 1) * terrain.res_x + ix + 1]; float h0 = h00 + (h10 - h00) * tx; float h1 = h01 + (h11 - h01) * tx; *out_height = terrain.min.z + h0 + (h1 - h0) * ty; // Slope of the cell float dx = ((h10 - h00) + (h11 - h01)) * 0.5f / cell_x; float dy = ((h01 - h00) + (h11 - h10)) * 0.5f / cell_y; vec4_t n = {-dx, -dy, 1.0f}; *out_normal = vec4_mult(n, 1.0f / vec4_len(n)); return 1; } static int ray_clip_slab(float origin, float dir, float lo, float hi, float *t0, float *t1) { if (fabsf(dir) < 1e-6f) { return origin >= lo && origin <= hi; } float ta = (lo - origin) / dir; float tb = (hi - origin) / dir; if (ta > tb) { float tmp = ta; ta = tb; tb = tmp; } if (ta > *t0) { *t0 = ta; } if (tb < *t1) { *t1 = tb; } return *t0 <= *t1; } int physics_terrain_raycast(vec4_t origin, vec4_t dir, vec4_t *hit) { if (!terrain.active) { return 0; } float len = vec4_len(dir); if (len == 0.0f) { return 0; } vec4_t d = vec4_mult(dir, 1.0f / len); const float pad = 0.001f; float t0 = 0.0f; float t1 = FLT_MAX; if (!ray_clip_slab(origin.x, d.x, terrain.min.x - pad, terrain.min.x + terrain.size_x + pad, &t0, &t1) || !ray_clip_slab(origin.y, d.y, terrain.min.y - pad, terrain.min.y + terrain.size_y + pad, &t0, &t1) || !ray_clip_slab(origin.z, d.z, terrain.min.z + terrain.min_h - pad, terrain.min.z + terrain.max_h + pad, &t0, &t1)) { return 0; } if (t1 < 0.0f) { return 0; } if (t0 < 0.0f) { t0 = 0.0f; } float cell_x = terrain.size_x / (terrain.res_x - 1); float cell_y = terrain.size_y / (terrain.res_y - 1); float step = cell_x < cell_y ? cell_x : cell_y; if (step <= 0.0f) { return 0; } float ground = 0.0f; vec4_t normal; float above = t0; int above_valid = 0; for (float t = t0;; t += step) { if (t > t1) { t = t1; } vec4_t p = vec4_add(origin, vec4_mult(d, t)); if (terrain_sample(p.x, p.y, &ground, &normal)) { if (p.z <= ground) { if (above_valid) { float lo = above; float hi = t; for (int i = 0; i < 24; ++i) { float mid = (lo + hi) * 0.5f; vec4_t q = vec4_add(origin, vec4_mult(d, mid)); if (terrain_sample(q.x, q.y, &ground, &normal) && q.z <= ground) { hi = mid; } else { lo = mid; } } p = vec4_add(origin, vec4_mult(d, hi)); if (!terrain_sample(p.x, p.y, &ground, &normal)) { return 0; } } *hit = (vec4_t){p.x, p.y, ground, 1.0f}; return 1; } above = t; above_valid = 1; } else { above_valid = 0; } if (t >= t1) { break; } } return 0; } static inline float depth_along_normal(float drop, vec4_t normal) { return drop * normal.z; } static void collide_box_terrain(int bi) { box_t *b = &boxes[bi]; if (b->mass == 0.0f) { return; } box_t start = *b; contact_sum_t sum = {0}; for (int c = 0; c < 8; c++) { vec4_t corner = box_corner(&start, c); float ground; vec4_t normal; if (!terrain_sample(corner.x, corner.y, &ground, &normal)) { continue; } float drop = ground - corner.z; if (drop <= 0.0f) { continue; } float depth = depth_along_normal(drop, normal); box_t solved = start; resolve_contact_pair(rigid_box(&solved), rigid_static(), corner, normal, depth); contact_sum_add(&sum, &solved, &start); sum.count++; report_contact(box_pair(bi), depth, corner, normal); } if (sum.count > 0) { contact_sum_apply(&sum, b); } } static void collide_sphere_terrain(int si) { sphere_t *s = &spheres[si]; if (s->mass == 0.0f) { return; } float ground; vec4_t normal; if (!terrain_sample(s->position.x, s->position.y, &ground, &normal)) { return; } float drop = ground - (s->position.z - s->radius); if (drop <= 0.0f) { return; } float depth = depth_along_normal(drop, normal); vec4_t point = {s->position.x, s->position.y, ground}; resolve_contact_pair(rigid_sphere(s), rigid_static(), point, normal, depth); report_contact(si, depth, point, normal); } static void terrain_clear() { free(terrain.heights); memset(&terrain, 0, sizeof(terrain)); } static void mesh_clear() { free_bvh(mesh.root); mesh.root = NULL; } void physics_world_create() { physics_world_destroy(); memset(spheres, 0, sizeof(spheres)); memset(boxes, 0, sizeof(boxes)); memset(pairs, 0, sizeof(pairs)); } void physics_world_destroy() { mesh_clear(); terrain_clear(); } void physics_world_update() { const int sub_steps = 8; float dt = sys_delta() / sub_steps; memset(pairs, 0, sizeof(pairs)); memset(pair_best, 0, sizeof(pair_best)); for (int step = 0; step < sub_steps; step++) { // Sphere-mesh collision for (int i = 0; i < MAX_SPHERES; i++) { sphere_t *s = &spheres[i]; if (!s->active || s->mass == 0.0f) { continue; } if (!s->sleeping) { // A sleeping body still collides, it just no longer moves s->velocity = vec4_add(s->velocity, vec4_mult(physics_gravity, dt)); s->position = vec4_add(s->position, vec4_mult(s->velocity, dt)); } query_bvh(s, i, mesh.root); collide_sphere_terrain(i); } // Sphere-sphere collision for (int i = 0; i < MAX_SPHERES; i++) { if (!spheres[i].active) { continue; } for (int j = i + 1; j < MAX_SPHERES; j++) { if (spheres[j].active) { collide_sphere_sphere(i, j); } } } for (int i = 0; i < MAX_BOXES; i++) { box_t *b = &boxes[i]; if (!b->active || b->mass == 0.0f) { continue; } if (!b->sleeping) { // A sleeping body still collides, it just no longer moves b->velocity = vec4_add(b->velocity, vec4_mult(physics_gravity, dt)); b->position = vec4_add(b->position, vec4_mult(b->velocity, dt)); // Turn the orientation by the angular velocity quat_t spin = {b->angular.x, b->angular.y, b->angular.z, 0.0f}; quat_t dq = quat_mult(spin, b->rotation); b->rotation.x += dq.x * 0.5f * dt; b->rotation.y += dq.y * 0.5f * dt; b->rotation.z += dq.z * 0.5f * dt; b->rotation.w += dq.w * 0.5f * dt; b->rotation = quat_norm(b->rotation); // Bleed off spin, so a box that has come to rest stops twitching b->angular = vec4_mult(b->angular, 1.0f - fminf(1.0f, ANGULAR_DAMPING * dt)); if (b->sleep_timer > 0.0f) { float drain = 1.0f - fminf(1.0f, REST_DAMPING * dt); b->velocity = vec4_mult(b->velocity, drain); b->angular = vec4_mult(b->angular, drain); } } collide_box_terrain(i); } // Box-box collision for (int i = 0; i < MAX_BOXES; i++) { if (!boxes[i].active) { continue; } for (int j = i + 1; j < MAX_BOXES; j++) { if (boxes[j].active) { collide_box_box(i, j); } } } // Box-sphere collision for (int i = 0; i < MAX_BOXES; i++) { if (!boxes[i].active) { continue; } for (int j = 0; j < MAX_SPHERES; j++) { if (spheres[j].active) { collide_box_sphere(i, j); } } } } // Put bodies that have settled on something to sleep, so they stop entirely float frame_dt = sys_delta(); for (int i = 0; i < MAX_SPHERES; i++) { sphere_t *s = &spheres[i]; if (s->active && s->mass != 0.0f && !s->sleeping) { settling_t settling = {&s->position, NULL, &s->velocity, &rigid_zero, &s->sleep_pos, NULL, &s->sleep_timer, &s->sleeping}; sleep_update(settling, pair_best[i] > 0.0f, frame_dt); } } for (int i = 0; i < MAX_BOXES; i++) { box_t *b = &boxes[i]; if (b->active && b->mass != 0.0f && !b->sleeping) { settling_t settling = {&b->position, &b->rotation, &b->velocity, &b->angular, &b->sleep_pos, &b->sleep_rot, &b->sleep_timer, &b->sleeping}; sleep_update(settling, pair_best[box_pair(i)] > 0.0f, frame_dt); } } // Write the simulated state back into the scene for (int i = 0; i < scene_meshes->length; i++) { mesh_object_t *mo = scene_meshes->buffer[i]; physics_body_t *body = mo->base->_->body; if (body != NULL) { physics_body_update(body); } } } physics_pair_t *physics_world_get_contact(void *body) { int slot = body_slot(body); if (body_is_box(body)) { return &pairs[box_pair(slot)]; } if (body_is_sphere(body)) { return &pairs[slot]; } return &null_pair; } physics_pair_t_array_t *physics_get_contact_pairs(physics_body_t *body) { physics_pair_t_array_t *result = any_array_create_from_raw((void *[]){}, 0); physics_pair_t *p = physics_world_get_contact(body->_body); if (p->pos_a_x != 0 || p->pos_a_y != 0 || p->pos_a_z != 0) { any_array_push(result, p); } return result; } static inline vec4_t mesh_vertex(i16_array_t *pa, uint32_t index, float scale) { return (vec4_t){pa->buffer[index * 4] * scale, pa->buffer[index * 4 + 1] * scale, pa->buffer[index * 4 + 2] * scale}; } static inline vec4_t mesh_vertex_scaled(i16_array_t *pa, uint32_t index, float sx, float sy, float sz) { return (vec4_t){pa->buffer[index * 4] * sx, pa->buffer[index * 4 + 1] * sy, pa->buffer[index * 4 + 2] * sz}; } static void heightfield_fill_holes(float *heights, int res_x, int res_y) { int count = res_x * res_y; int *queue = (int *)malloc(sizeof(int) * count); int head = 0, tail = 0; for (int i = 0; i < count; i++) { if (heights[i] > -FLT_MAX) { queue[tail++] = i; } } if (tail == 0) { free(queue); for (int i = 0; i < count; i++) { heights[i] = 0.0f; } return; } while (head < tail) { int i = queue[head++]; int x = i % res_x; int y = i / res_x; float h = heights[i]; int neighbors[4] = {x > 0 ? i - 1 : -1, x < res_x - 1 ? i + 1 : -1, y > 0 ? i - res_x : -1, y < res_y - 1 ? i + res_x : -1}; for (int n = 0; n < 4; n++) { int ni = neighbors[n]; if (ni >= 0 && heights[ni] == -FLT_MAX) { heights[ni] = h; queue[tail++] = ni; } } } free(queue); } static physics_heightfield_t heightfield_from_mesh(i16_array_t *pa, u32_array_t *ia, float scale_x, float scale_y, float scale_z) { if (pa == NULL || ia == NULL) { return (physics_heightfield_t){0}; } int num_verts = pa->length / 4; int num_tris = ia->length / 3; if (num_verts < 3 || num_tris < 1) { return (physics_heightfield_t){0}; } float min_x = FLT_MAX, min_y = FLT_MAX, max_x = -FLT_MAX, max_y = -FLT_MAX; for (int i = 0; i < num_verts; i++) { vec4_t v = mesh_vertex_scaled(pa, i, scale_x, scale_y, scale_z); min_x = fminf(min_x, v.x); min_y = fminf(min_y, v.y); max_x = fmaxf(max_x, v.x); max_y = fmaxf(max_y, v.y); } float size_x = max_x - min_x; float size_y = max_y - min_y; if (size_x <= 0.0f || size_y <= 0.0f) { return (physics_heightfield_t){0}; } int res = (int)(sqrtf(num_tris / 2.0f) + 0.5f) * 2 + 1; if (res > MAX_TERRAIN_RES) { res = MAX_TERRAIN_RES; } float cell_x = size_x / (res - 1); float cell_y = size_y / (res - 1); float *heights = (float *)malloc(sizeof(float) * res * res); for (int i = 0; i < res * res; i++) { heights[i] = -FLT_MAX; } for (int t = 0; t < num_tris; t++) { vec4_t v0 = mesh_vertex_scaled(pa, ia->buffer[t * 3], scale_x, scale_y, scale_z); vec4_t v1 = mesh_vertex_scaled(pa, ia->buffer[t * 3 + 1], scale_x, scale_y, scale_z); vec4_t v2 = mesh_vertex_scaled(pa, ia->buffer[t * 3 + 2], scale_x, scale_y, scale_z); float area = (v1.y - v2.y) * (v0.x - v2.x) + (v2.x - v1.x) * (v0.y - v2.y); if (fabsf(area) < 1e-12f) { continue; } int ix0 = (int)ceilf((min3(v0.x, v1.x, v2.x) - min_x) / cell_x); int ix1 = (int)floorf((max3(v0.x, v1.x, v2.x) - min_x) / cell_x); int iy0 = (int)ceilf((min3(v0.y, v1.y, v2.y) - min_y) / cell_y); int iy1 = (int)floorf((max3(v0.y, v1.y, v2.y) - min_y) / cell_y); ix0 = ix0 < 0 ? 0 : ix0; iy0 = iy0 < 0 ? 0 : iy0; ix1 = ix1 > res - 1 ? res - 1 : ix1; iy1 = iy1 > res - 1 ? res - 1 : iy1; for (int iy = iy0; iy <= iy1; iy++) { for (int ix = ix0; ix <= ix1; ix++) { float px = min_x + ix * cell_x; float py = min_y + iy * cell_y; float b0 = ((v1.y - v2.y) * (px - v2.x) + (v2.x - v1.x) * (py - v2.y)) / area; float b1 = ((v2.y - v0.y) * (px - v2.x) + (v0.x - v2.x) * (py - v2.y)) / area; float b2 = 1.0f - b0 - b1; if (b0 < -1e-5f || b1 < -1e-5f || b2 < -1e-5f) { continue; } float h = b0 * v0.z + b1 * v1.z + b2 * v2.z; if (h > heights[iy * res + ix]) { heights[iy * res + ix] = h; } } } } heightfield_fill_holes(heights, res, res); return (physics_heightfield_t){.heights = heights, .res_x = res, .res_y = res, .min_x = min_x, .min_y = min_y, .size_x = size_x, .size_y = size_y}; } static void *body_create(int shape, float mass, float dimx, float dimy, float dimz, float x, float y, float z, void *posa, void *inda, float scale_pos) { if (shape == PHYSICS_SHAPE_TERRAIN) { physics_heightfield_t *field = posa; if (field == NULL || field->heights == NULL || field->res_x < 2 || field->res_y < 2) { return NULL; } int count = field->res_x * field->res_y; float *heights = (float *)malloc(sizeof(float) * count); memcpy(heights, field->heights, sizeof(float) * count); float min_h = heights[0]; float max_h = heights[0]; for (int i = 1; i < count; ++i) { if (heights[i] < min_h) { min_h = heights[i]; } if (heights[i] > max_h) { max_h = heights[i]; } } terrain_clear(); terrain = (terrain_t){.heights = heights, .res_x = field->res_x, .res_y = field->res_y, .min = {x + field->min_x, y + field->min_y, z}, .size_x = field->size_x, .size_y = field->size_y, .min_h = min_h, .max_h = max_h, .active = 1}; return (void *)(uintptr_t)TERRAIN_TAG; } if (shape == PHYSICS_SHAPE_BOX && posa == NULL) { int slot = 0; while (slot < MAX_BOXES && boxes[slot].active) { slot++; } if (slot == MAX_BOXES) { return NULL; } vec4_t half = {dimx / 2.0f, dimy / 2.0f, dimz / 2.0f}; float inertia = mass / 3.0f * (half.x * half.x + half.y * half.y + half.z * half.z) * (2.0f / 3.0f); boxes[slot] = (box_t){.position = {x, y, z}, .rotation = {0.0f, 0.0f, 0.0f, 1.0f}, .half = half, .mass = mass, .inv_inertia = mass > 0.0f && inertia > 0.0f ? 1.0f / inertia : 0.0f, .active = 1}; return (void *)(uintptr_t)(slot | BOX_TAG); } if (shape == PHYSICS_SHAPE_SPHERE) { int slot = 0; while (slot < MAX_SPHERES && spheres[slot].active) { slot++; } if (slot == MAX_SPHERES) { return NULL; } spheres[slot] = (sphere_t){.position = {x, y, z}, .radius = dimx / 2.0f, .mass = mass, .active = 1}; return (void *)(uintptr_t)(slot | SPHERE_TAG); } i16_array_t *pa = posa; u32_array_t *ia = inda; int num_tris = ia->length / 3; triangle_t *tris = (triangle_t *)malloc(num_tris * sizeof(triangle_t)); float scale = (1.0 / 32767.0) * scale_pos; for (int i = 0; i < num_tris; i++) { vec4_t v0 = mesh_vertex(pa, ia->buffer[i * 3], scale); vec4_t v1 = mesh_vertex(pa, ia->buffer[i * 3 + 1], scale); vec4_t v2 = mesh_vertex(pa, ia->buffer[i * 3 + 2], scale); vec4_t cross = vec4_cross(vec4_sub(v1, v0), vec4_sub(v2, v0)); tris[i].v0 = v0; tris[i].v1 = v1; tris[i].v2 = v2; tris[i].normal = vec4_mult(cross, 1.0f / vec4_len(cross)); tris[i].bounds = (aabb_t){.min = {min3(v0.x, v1.x, v2.x), min3(v0.y, v1.y, v2.y), min3(v0.z, v1.z, v2.z)}, .max = {max3(v0.x, v1.x, v2.x), max3(v0.y, v1.y, v2.y), max3(v0.z, v1.z, v2.z)}}; } mesh_clear(); mesh.root = create_bvh_node(tris, num_tris, 0); free(tris); return NULL; } static box_t null_body; typedef struct { vec4_t *position; vec4_t *velocity; float *mass; int *active; float *sleep_timer; int *sleeping; } body_ref_t; static body_ref_t body_ref(void *body) { int slot = body_slot(body); if (body_is_box(body) && slot < MAX_BOXES) { box_t *b = &boxes[slot]; return (body_ref_t){&b->position, &b->velocity, &b->mass, &b->active, &b->sleep_timer, &b->sleeping}; } if (body_is_sphere(body) && slot < MAX_SPHERES) { sphere_t *s = &spheres[slot]; return (body_ref_t){&s->position, &s->velocity, &s->mass, &s->active, &s->sleep_timer, &s->sleeping}; } return (body_ref_t){&null_body.position, &null_body.velocity, &null_body.mass, &null_body.active, &null_body.sleep_timer, &null_body.sleeping}; } static void body_wake(void *body) { body_ref_t ref = body_ref(body); *ref.sleeping = 0; *ref.sleep_timer = 0.0f; } static void wake_all() { for (int i = 0; i < MAX_SPHERES; i++) { sphere_wake(&spheres[i]); } for (int i = 0; i < MAX_BOXES; i++) { boxes[i].sleeping = 0; boxes[i].sleep_timer = 0.0f; } } static vec4_t object_world_scale(object_t *obj) { vec4_t scale = obj->transform->scale; if (obj->parent != NULL) { scale.x *= obj->parent->transform->scale.x; scale.y *= obj->parent->transform->scale.y; scale.z *= obj->parent->transform->scale.z; } return scale; } static bool mesh_bounds(object_t *obj, vec4_t *center, vec4_t *extent) { // The object origin is not necessarily the middle of the geometry if (obj->ext == NULL || !string_equals(obj->ext_type, "mesh_object_t")) { return false; } mesh_object_t *mo = obj->ext; i16_array_t *pa = mesh_data_get_vertex_array(mo->data, "pos")->values; int num_verts = pa->length / 4; if (num_verts < 1) { return false; } int16_t min[3] = {pa->buffer[0], pa->buffer[1], pa->buffer[2]}; int16_t max[3] = {pa->buffer[0], pa->buffer[1], pa->buffer[2]}; for (int i = 1; i < num_verts; i++) { for (int c = 0; c < 3; c++) { int16_t v = pa->buffer[i * 4 + c]; min[c] = v < min[c] ? v : min[c]; max[c] = v > max[c] ? v : max[c]; } } vec4_t scale = object_world_scale(obj); float unpack = (1.0f / 32767.0f) * mo->data->scale_pos; float sc[3] = {unpack * scale.x, unpack * scale.y, unpack * scale.z}; *center = (vec4_t){(min[0] + max[0]) * 0.5f * sc[0], (min[1] + max[1]) * 0.5f * sc[1], (min[2] + max[2]) * 0.5f * sc[2], 0.0f}; *extent = (vec4_t){(max[0] - min[0]) * sc[0], (max[1] - min[1]) * sc[1], (max[2] - min[2]) * sc[2], 0.0f}; return true; } physics_body_t *physics_body_create(object_t *obj, physics_shape_t shape, float mass) { physics_body_t *body = ALLOC_INIT(physics_body_t, {0}); body->shape = shape; body->mass = mass; body->obj = obj; obj->_->body = body; transform_compute_dim(obj->transform); body->dimx = obj->transform->dim.x; body->dimy = obj->transform->dim.y; body->dimz = obj->transform->dim.z; if (shape == PHYSICS_SHAPE_BOX || shape == PHYSICS_SHAPE_SPHERE) { vec4_t center, extent; if (mesh_bounds(obj, ¢er, &extent)) { body->dimx = extent.x; body->dimy = extent.y; body->dimz = extent.z; body->offset = center; } } float scale_pos = 1.0f; void *posa = NULL; u32_array_t *inda = NULL; physics_heightfield_t field = {0}; if (shape == PHYSICS_SHAPE_MESH || shape == PHYSICS_SHAPE_TERRAIN) { mesh_object_t *mo = obj->ext; mesh_data_t *data = mo->data; vec4_t scale = object_world_scale(obj); i16_array_t *pa = mesh_data_get_vertex_array(data, "pos")->values; inda = data->index_array; scale_pos = scale.x * data->scale_pos; if (shape == PHYSICS_SHAPE_TERRAIN) { float unpack = (1.0f / 32767.0f) * data->scale_pos; field = heightfield_from_mesh(pa, inda, unpack * scale.x, unpack * scale.y, unpack * scale.z); posa = &field; } else { posa = pa; } } vec4_t loc = obj->transform->loc; vec4_t off = vec4_apply_quat(body->offset, obj->transform->rot); body->_body = body_create(shape, mass, body->dimx, body->dimy, body->dimz, loc.x + off.x, loc.y + off.y, loc.z + off.z, posa, inda, scale_pos); free(field.heights); if (shape == PHYSICS_SHAPE_BOX) { // Start out at the object rotation physics_body_sync_transform(body); } return body; } void physics_body_set_mass(physics_body_t *body, float mass) { body->mass = mass; *body_ref(body->_body).mass = mass; body_wake(body->_body); } void physics_body_apply_impulse(void *body, vec4_t impulse) { vec4_t *vel = body_ref(body).velocity; vel->x += impulse.x; vel->y += impulse.y; vel->z += impulse.z; body_wake(body); } void physics_body_get_pos(void *body, vec4_t *pos) { vec4_t *p = body_ref(body).position; pos->x = p->x; pos->y = p->y; pos->z = p->z; } void physics_body_get_rot(void *body, quat_t *rot) { if (body_is_box(body)) { *rot = boxes[body_slot(body)].rotation; } } void physics_body_get_velocity(void *body, vec4_t *vel) { vec4_t *v = body_ref(body).velocity; vel->x = v->x; vel->y = v->y; vel->z = v->z; } void physics_body_set_velocity(void *body, float x, float y, float z) { vec4_t *vel = body_ref(body).velocity; vel->x = x; vel->y = y; vel->z = z; body_wake(body); } void physics_body_sync_transform(physics_body_t *body) { transform_t *transform = body->obj->transform; vec4_t off = vec4_apply_quat(body->offset, transform->rot); vec4_t *p = body_ref(body->_body).position; p->x = transform->loc.x + off.x; p->y = transform->loc.y + off.y; p->z = transform->loc.z + off.z; if (body_is_box(body->_body)) { boxes[body_slot(body->_body)].rotation = transform->rot; } body_wake(body->_body); } void physics_body_set_rotation(physics_body_t *body, quat_t rot) { if (!body_is_box(body->_body)) { return; } box_t *b = &boxes[body_slot(body->_body)]; b->rotation = rot; b->angular = (vec4_t){0.0f, 0.0f, 0.0f}; body_wake(body->_body); } void physics_body_update(physics_body_t *body) { if (body->shape == PHYSICS_SHAPE_MESH || body->shape == PHYSICS_SHAPE_TERRAIN) { return; // Static collider, the object drives the shape } transform_t *transform = body->obj->transform; vec4_t pos = transform->loc; physics_body_get_pos(body->_body, &pos); physics_body_get_rot(body->_body, &transform->rot); vec4_t off = vec4_apply_quat(body->offset, transform->rot); transform->loc.x = pos.x - off.x; transform->loc.y = pos.y - off.y; transform->loc.z = pos.z - off.z; transform_build_matrix(transform); } void physics_body_remove(physics_body_t *body) { if (body == NULL) { return; } body->obj->_->body = NULL; wake_all(); // Whatever was resting on this body has to fall now if (body->shape == PHYSICS_SHAPE_MESH) { mesh_clear(); return; } if (body_is_terrain(body->_body)) { terrain_clear(); return; } *body_ref(body->_body).active = 0; } float physics_body_get_speed(physics_body_t *body) { return vec4_len(*body_ref(body->_body).velocity); } void physics_set_friction(float v) { physics_friction = v * 0.1f; } void physics_set_bounciness(float v) { physics_bounciness = v; } void physics_set_gravity(float x, float y, float z) { physics_gravity = (vec4_t){x, y, z}; wake_all(); } #endif