AABB physics
This commit is contained in:
parent
b085e7ec6a
commit
479ebbf9b9
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 };
|
||||
|
|
|
|||
Loading…
Reference in New Issue