diff --git a/src/common/physics/engine.c b/src/common/physics/engine.c index 8327968..ef61997 100644 --- a/src/common/physics/engine.c +++ b/src/common/physics/engine.c @@ -10,6 +10,7 @@ typedef enum : uint8_t { TYPE_EMPTY = 0, TYPE_CIRCLE = 1, TYPE_PLANE = 2, + TYPE_AABB = 3, TYPE_COUNT, } body_type; @@ -29,6 +30,9 @@ typedef struct { struct { vec2 normal; } plane; + struct { + vec2 half; // half-extents along x and y + } aabb; }; // Private vec2 force; @@ -170,6 +174,19 @@ JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, floa return idx; } +JS_EXPORT rigid_body_index rigid_body_new_aabb(float x, float y, float vx, float vy, float mass, float hx, float hy) { + rigid_body_index idx = rigid_body_new(x, y, vx, vy, mass); + if (idx == SIZE_MAX) { + return idx; + } + rigid_body* rb = rb_get(idx); + + rb->type = TYPE_AABB; + rb->aabb.half = (vec2){hx, hy}; + + return idx; +} + JS_EXPORT void rigid_body_free(rigid_body_index idx) { memset(rb_get(idx), 0, sizeof(rigid_body)); } @@ -264,11 +281,80 @@ static contact contact_plane_circle(const rigid_body* a, const rigid_body* b) { return (contact){.hit = true, .normal = a->plane.normal, .overlap = overlap}; } +static contact contact_aabb_circle(const rigid_body* a, const rigid_body* b) { + // a: box, b: circle. + vec2 half = a->aabb.half; + vec2 d = vec2_sub(b->pos, a->pos); + + // Closest point on the box to the circle centre, expressed relative to the centre. + vec2 closest = { + fmaxf(-half.x, fminf(d.x, half.x)), + fmaxf(-half.y, fminf(d.y, half.y)), + }; + vec2 diff = vec2_sub(d, closest); + float dist = vec2_mag(diff); + + if (dist > COLLISION_EPSILON) { + float overlap = b->circle.radius - dist; + if (overlap < 0) { + return (contact){.hit = false}; + } + // Normal points box -> circle. + return (contact){.hit = true, .normal = vec2_div(diff, dist), .overlap = overlap}; + } + + // Centre is inside the box; push out along the axis of least penetration. + float px = half.x - fabsf(d.x); + float py = half.y - fabsf(d.y); + if (px < py) { + vec2 n = {copysignf(1.0f, d.x), 0.0f}; + return (contact){.hit = true, .normal = n, .overlap = px + b->circle.radius}; + } + vec2 n = {0.0f, copysignf(1.0f, d.y)}; + return (contact){.hit = true, .normal = n, .overlap = py + b->circle.radius}; +} + +static contact contact_aabb_plane(const rigid_body* a, const rigid_body* b) { + // a: box, b: plane (static). + vec2 normal = b->plane.normal; + + // Projection of the box half-extents onto the plane normal. + float reach = a->aabb.half.x * fabsf(normal.x) + a->aabb.half.y * fabsf(normal.y); + float overlap = reach - point_to_line_dist(a->pos, b->pos, normal); + if (overlap < 0) { + return (contact){.hit = false}; + } + + // Normal points box -> plane, so resolution pushes the box out along +plane.normal. + return (contact){.hit = true, .normal = vec2_mul(normal, -1.0f), .overlap = overlap}; +} + +static contact contact_aabb_aabb(const rigid_body* a, const rigid_body* b) { + vec2 d = vec2_sub(b->pos, a->pos); + + float ox = (a->aabb.half.x + b->aabb.half.x) - fabsf(d.x); + float oy = (a->aabb.half.y + b->aabb.half.y) - fabsf(d.y); + if (ox < 0 || oy < 0) { + return (contact){.hit = false}; + } + + // Separate along the axis of least penetration; normal points a -> b. + if (ox < oy) { + vec2 n = {copysignf(1.0f, d.x), 0.0f}; + return (contact){.hit = true, .normal = n, .overlap = ox}; + } + vec2 n = {0.0f, copysignf(1.0f, d.y)}; + return (contact){.hit = true, .normal = n, .overlap = oy}; +} + // Indexed [a->type][b->type] in canonical order (a->type >= b->type); a NULL entry // means the pair does not collide. static contact_fn contact_table[TYPE_COUNT][TYPE_COUNT] = { [TYPE_CIRCLE][TYPE_CIRCLE] = contact_circle_circle, [TYPE_PLANE][TYPE_CIRCLE] = contact_plane_circle, + [TYPE_AABB][TYPE_CIRCLE] = contact_aabb_circle, + [TYPE_AABB][TYPE_PLANE] = contact_aabb_plane, + [TYPE_AABB][TYPE_AABB] = contact_aabb_aabb, }; // `n` points from a toward b; infinite-mass bodies are treated as immovable. diff --git a/src/common/physics/index.ts b/src/common/physics/index.ts index 7531eb5..347acdd 100644 --- a/src/common/physics/index.ts +++ b/src/common/physics/index.ts @@ -3,6 +3,7 @@ import E from './engine.c'; namespace Physics { const TYPE_CIRCLE = 1; const TYPE_PLANE = 2; + const TYPE_AABB = 3; export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number { const body = E.rigid_body_new_circle(x, y, 0, 0, mass, radius); @@ -14,6 +15,11 @@ namespace Physics { return body; } + export function newAABB(x: number, y: number, hx: number, hy: number, mass: number = 1.0): number { + const body = E.rigid_body_new_aabb(x, y, 0, 0, mass, hx, hy); + return body; + } + export function deleteBody(body: number) { E.rigid_body_free(body); } @@ -52,6 +58,10 @@ namespace Physics { const nx = E.data.getFloat32(ptr + 24, true); const ny = E.data.getFloat32(ptr + 28, true); return { id: ptr, type, x, y, vx, vy, mass, nx, ny }; + } else if (type === TYPE_AABB) { + const hx = E.data.getFloat32(ptr + 24, true); + const hy = E.data.getFloat32(ptr + 28, true); + return { id: ptr, type, x, y, vx, vy, mass, hx, hy }; } return { id: ptr, type, x, y, vx, vy, mass };