AABB physics
This commit is contained in:
parent
b085e7ec6a
commit
479ebbf9b9
|
|
@ -10,6 +10,7 @@ typedef enum : uint8_t {
|
||||||
TYPE_EMPTY = 0,
|
TYPE_EMPTY = 0,
|
||||||
TYPE_CIRCLE = 1,
|
TYPE_CIRCLE = 1,
|
||||||
TYPE_PLANE = 2,
|
TYPE_PLANE = 2,
|
||||||
|
TYPE_AABB = 3,
|
||||||
TYPE_COUNT,
|
TYPE_COUNT,
|
||||||
} body_type;
|
} body_type;
|
||||||
|
|
||||||
|
|
@ -29,6 +30,9 @@ typedef struct {
|
||||||
struct {
|
struct {
|
||||||
vec2 normal;
|
vec2 normal;
|
||||||
} plane;
|
} plane;
|
||||||
|
struct {
|
||||||
|
vec2 half; // half-extents along x and y
|
||||||
|
} aabb;
|
||||||
};
|
};
|
||||||
// Private
|
// Private
|
||||||
vec2 force;
|
vec2 force;
|
||||||
|
|
@ -170,6 +174,19 @@ JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, floa
|
||||||
return idx;
|
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) {
|
JS_EXPORT void rigid_body_free(rigid_body_index idx) {
|
||||||
memset(rb_get(idx), 0, sizeof(rigid_body));
|
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};
|
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
|
// Indexed [a->type][b->type] in canonical order (a->type >= b->type); a NULL entry
|
||||||
// means the pair does not collide.
|
// means the pair does not collide.
|
||||||
static contact_fn contact_table[TYPE_COUNT][TYPE_COUNT] = {
|
static contact_fn contact_table[TYPE_COUNT][TYPE_COUNT] = {
|
||||||
[TYPE_CIRCLE][TYPE_CIRCLE] = contact_circle_circle,
|
[TYPE_CIRCLE][TYPE_CIRCLE] = contact_circle_circle,
|
||||||
[TYPE_PLANE][TYPE_CIRCLE] = contact_plane_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.
|
// `n` points from a toward b; infinite-mass bodies are treated as immovable.
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,7 @@ import E from './engine.c';
|
||||||
namespace Physics {
|
namespace Physics {
|
||||||
const TYPE_CIRCLE = 1;
|
const TYPE_CIRCLE = 1;
|
||||||
const TYPE_PLANE = 2;
|
const TYPE_PLANE = 2;
|
||||||
|
const TYPE_AABB = 3;
|
||||||
|
|
||||||
export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number {
|
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);
|
const body = E.rigid_body_new_circle(x, y, 0, 0, mass, radius);
|
||||||
|
|
@ -14,6 +15,11 @@ namespace Physics {
|
||||||
return body;
|
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) {
|
export function deleteBody(body: number) {
|
||||||
E.rigid_body_free(body);
|
E.rigid_body_free(body);
|
||||||
}
|
}
|
||||||
|
|
@ -52,6 +58,10 @@ namespace Physics {
|
||||||
const nx = E.data.getFloat32(ptr + 24, true);
|
const nx = E.data.getFloat32(ptr + 24, true);
|
||||||
const ny = E.data.getFloat32(ptr + 28, true);
|
const ny = E.data.getFloat32(ptr + 28, true);
|
||||||
return { id: ptr, type, x, y, vx, vy, mass, nx, ny };
|
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 };
|
return { id: ptr, type, x, y, vx, vy, mass };
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue