1
0
Fork 0

AABB physics

This commit is contained in:
Pabloader 2026-05-31 12:28:52 +00:00
parent b085e7ec6a
commit 479ebbf9b9
2 changed files with 96 additions and 0 deletions

View File

@ -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.

View File

@ -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 };