Fix engine bugs
This commit is contained in:
parent
65660cc639
commit
8cde9818f4
|
|
@ -36,6 +36,8 @@ typedef struct {
|
||||||
typedef size_t rigid_body_index;
|
typedef size_t rigid_body_index;
|
||||||
typedef void (*rigid_body_collision_callback_t)(rigid_body_index a, rigid_body_index b);
|
typedef void (*rigid_body_collision_callback_t)(rigid_body_index a, rigid_body_index b);
|
||||||
|
|
||||||
|
constexpr float COLLISION_EPSILON = 1e-6f;
|
||||||
|
|
||||||
////////// Prototypes
|
////////// Prototypes
|
||||||
|
|
||||||
void rigid_body_resolve_collision(rigid_body_index rb);
|
void rigid_body_resolve_collision(rigid_body_index rb);
|
||||||
|
|
@ -49,18 +51,72 @@ static rigid_body* rigid_bodies = NULL;
|
||||||
static size_t rigid_bodies_cap = 0;
|
static size_t rigid_bodies_cap = 0;
|
||||||
static rigid_body_collision_callback_t rigid_body_collision_callback = NULL;
|
static rigid_body_collision_callback_t rigid_body_collision_callback = NULL;
|
||||||
|
|
||||||
|
static vec2 gravity = {0, 0};
|
||||||
|
|
||||||
|
// Queued during the solve and dispatched only after it finishes, so callbacks may
|
||||||
|
// free or create bodies (reallocating `rigid_bodies`) without invalidating the body
|
||||||
|
// pointers held mid-solve.
|
||||||
|
typedef struct {
|
||||||
|
rigid_body_index a;
|
||||||
|
rigid_body_index b;
|
||||||
|
} collision_event;
|
||||||
|
|
||||||
|
static collision_event* collision_events = NULL;
|
||||||
|
static size_t collision_events_count = 0;
|
||||||
|
static size_t collision_events_cap = 0;
|
||||||
|
|
||||||
////////// Functions
|
////////// Functions
|
||||||
|
|
||||||
inline static rigid_body* rb_get(rigid_body_index idx) {
|
inline static rigid_body* rb_get(rigid_body_index idx) {
|
||||||
return (rigid_bodies + (idx));
|
return (rigid_bodies + (idx));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void collision_event_push(rigid_body_index a, rigid_body_index b) {
|
||||||
|
if (collision_events_count == collision_events_cap) {
|
||||||
|
size_t new_cap = collision_events_cap ? collision_events_cap * 2 : 16;
|
||||||
|
collision_event* grown = realloc(collision_events, new_cap * sizeof(collision_event));
|
||||||
|
if (!grown) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
collision_events = grown;
|
||||||
|
collision_events_cap = new_cap;
|
||||||
|
}
|
||||||
|
collision_events[collision_events_count].a = a;
|
||||||
|
collision_events[collision_events_count].b = b;
|
||||||
|
collision_events_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rigid_body_dispatch_collisions() {
|
||||||
|
if (rigid_body_collision_callback) {
|
||||||
|
for (size_t i = 0; i < collision_events_count; i++) {
|
||||||
|
rigid_body_collision_callback(collision_events[i].a, collision_events[i].b);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
collision_events_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void rigid_body_integrate(rigid_body* rb, float dt) {
|
||||||
|
if (!isinf(rb->mass)) {
|
||||||
|
rb->vel.x += (rb->force.x / rb->mass + gravity.x) * dt;
|
||||||
|
rb->vel.y += (rb->force.y / rb->mass + gravity.y) * dt;
|
||||||
|
|
||||||
|
rb->pos.x += rb->vel.x * dt;
|
||||||
|
rb->pos.y += rb->vel.y * dt;
|
||||||
|
}
|
||||||
|
|
||||||
|
rb->force.x = 0;
|
||||||
|
rb->force.y = 0;
|
||||||
|
}
|
||||||
|
|
||||||
JS_EXPORT rigid_body* rigid_body_get(rigid_body_index idx) {
|
JS_EXPORT rigid_body* rigid_body_get(rigid_body_index idx) {
|
||||||
return rb_get(idx);
|
return rb_get(idx);
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
||||||
rigid_body_index idx = rigid_body_find_empty();
|
rigid_body_index idx = rigid_body_find_empty();
|
||||||
|
if (idx == SIZE_MAX) {
|
||||||
|
return SIZE_MAX; // allocation failed
|
||||||
|
}
|
||||||
rigid_body* rb = rb_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
rb->type = TYPE_EMPTY;
|
rb->type = TYPE_EMPTY;
|
||||||
|
|
||||||
|
|
@ -80,6 +136,9 @@ size_t rigid_body_new(float x, float y, float vx, float vy, float mass) {
|
||||||
|
|
||||||
JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, float vy, float mass, float radius) {
|
JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, float vy, float mass, float radius) {
|
||||||
rigid_body_index idx = rigid_body_new(x, y, vx, vy, mass);
|
rigid_body_index idx = rigid_body_new(x, y, vx, vy, mass);
|
||||||
|
if (idx == SIZE_MAX) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
rigid_body* rb = rb_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
|
|
||||||
rb->type = TYPE_CIRCLE;
|
rb->type = TYPE_CIRCLE;
|
||||||
|
|
@ -90,6 +149,9 @@ JS_EXPORT rigid_body_index rigid_body_new_circle(float x, float y, float vx, flo
|
||||||
|
|
||||||
JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, float ny) {
|
JS_EXPORT rigid_body_index rigid_body_new_plane(float x, float y, float nx, float ny) {
|
||||||
rigid_body_index idx = rigid_body_new(x, y, 0, 0, INFINITY);
|
rigid_body_index idx = rigid_body_new(x, y, 0, 0, INFINITY);
|
||||||
|
if (idx == SIZE_MAX) {
|
||||||
|
return idx;
|
||||||
|
}
|
||||||
rigid_body* rb = rb_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
|
|
||||||
rb->type = TYPE_PLANE;
|
rb->type = TYPE_PLANE;
|
||||||
|
|
@ -103,19 +165,9 @@ JS_EXPORT void rigid_body_free(rigid_body_index idx) {
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_update(rigid_body_index idx, float dt) {
|
JS_EXPORT void rigid_body_update(rigid_body_index idx, float dt) {
|
||||||
|
rigid_body_integrate(rb_get(idx), dt);
|
||||||
rigid_body_resolve_collision(idx);
|
rigid_body_resolve_collision(idx);
|
||||||
rigid_body* rb = rb_get(idx);
|
rigid_body_dispatch_collisions();
|
||||||
|
|
||||||
if (!isinf(rb->mass)) {
|
|
||||||
rb->vel.x += rb->force.x * dt / rb->mass;
|
|
||||||
rb->vel.y += rb->force.y * dt / rb->mass;
|
|
||||||
|
|
||||||
rb->pos.x += rb->vel.x * dt;
|
|
||||||
rb->pos.y += rb->vel.y * dt;
|
|
||||||
}
|
|
||||||
|
|
||||||
rb->force.x = 0;
|
|
||||||
rb->force.y = 0;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_update_all(float dt) {
|
JS_EXPORT void rigid_body_update_all(float dt) {
|
||||||
|
|
@ -124,8 +176,18 @@ JS_EXPORT void rigid_body_update_all(float dt) {
|
||||||
if (current->type == TYPE_EMPTY) {
|
if (current->type == TYPE_EMPTY) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
rigid_body_update(idx, dt);
|
rigid_body_integrate(current, dt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (rigid_body_index idx = 0; idx < rigid_bodies_cap; idx++) {
|
||||||
|
rigid_body* current = rb_get(idx);
|
||||||
|
if (current->type == TYPE_EMPTY) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
rigid_body_resolve_collision(idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
rigid_body_dispatch_collisions();
|
||||||
}
|
}
|
||||||
|
|
||||||
JS_EXPORT void rigid_body_add_force(rigid_body_index idx, float fx, float fy) {
|
JS_EXPORT void rigid_body_add_force(rigid_body_index idx, float fx, float fy) {
|
||||||
|
|
@ -148,6 +210,11 @@ JS_EXPORT void rigid_body_set_collision_callback(rigid_body_collision_callback_t
|
||||||
rigid_body_collision_callback = callback;
|
rigid_body_collision_callback = callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
JS_EXPORT void rigid_body_set_gravity(float gx, float gy) {
|
||||||
|
gravity.x = gx;
|
||||||
|
gravity.y = gy;
|
||||||
|
}
|
||||||
|
|
||||||
void rigid_body_resolve_collision(rigid_body_index idx) {
|
void rigid_body_resolve_collision(rigid_body_index idx) {
|
||||||
rigid_body* rb = rb_get(idx);
|
rigid_body* rb = rb_get(idx);
|
||||||
int is_static = isinf(rb->mass);
|
int is_static = isinf(rb->mass);
|
||||||
|
|
@ -171,11 +238,11 @@ void rigid_body_handle_collision(rigid_body_index idx1, rigid_body_index idx2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rigid_body_collision_callback) {
|
collision_event_push(idx1, idx2);
|
||||||
rigid_body_collision_callback(idx1, idx2);
|
|
||||||
}
|
// Coincident centres have no separation axis; pick an arbitrary one.
|
||||||
|
vec2 n = (distance > COLLISION_EPSILON) ? vec2_div(d, distance) : (vec2){1.0f, 0.0f};
|
||||||
|
|
||||||
vec2 n = vec2_normalize(d);
|
|
||||||
float factor = (isinf(rb1->mass) || isinf(rb2->mass)) ? 1 : 0.5;
|
float factor = (isinf(rb1->mass) || isinf(rb2->mass)) ? 1 : 0.5;
|
||||||
vec2 n_overlap = vec2_mul(n, overlap * factor);
|
vec2 n_overlap = vec2_mul(n, overlap * factor);
|
||||||
|
|
||||||
|
|
@ -189,12 +256,18 @@ void rigid_body_handle_collision(rigid_body_index idx1, rigid_body_index idx2) {
|
||||||
float v1n = vec2_dot(rb1->vel, n);
|
float v1n = vec2_dot(rb1->vel, n);
|
||||||
float v2n = vec2_dot(rb2->vel, n);
|
float v2n = vec2_dot(rb2->vel, n);
|
||||||
|
|
||||||
|
// Skip the impulse if the bodies are already separating along the normal.
|
||||||
|
if (v1n - v2n <= 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
float v1n_new = v1n;
|
float v1n_new = v1n;
|
||||||
float v2n_new = v2n;
|
float v2n_new = v2n;
|
||||||
|
|
||||||
if (!isinf(rb1->mass) && !isinf(rb2->mass)) {
|
if (!isinf(rb1->mass) && !isinf(rb2->mass)) {
|
||||||
v1n_new = v1n - 2.0f * (v1n - v2n) * rb1->mass / (rb1->mass + rb2->mass);
|
// Elastic collision: each body weighted by the *other* body's mass.
|
||||||
v2n_new = v2n + 2.0f * (v1n - v2n) * rb2->mass / (rb1->mass + rb2->mass);
|
v1n_new = v1n - 2.0f * (v1n - v2n) * rb2->mass / (rb1->mass + rb2->mass);
|
||||||
|
v2n_new = v2n + 2.0f * (v1n - v2n) * rb1->mass / (rb1->mass + rb2->mass);
|
||||||
} else if (isinf(rb1->mass)) {
|
} else if (isinf(rb1->mass)) {
|
||||||
v2n_new = -v2n;
|
v2n_new = -v2n;
|
||||||
} else if (isinf(rb2->mass)) {
|
} else if (isinf(rb2->mass)) {
|
||||||
|
|
@ -220,15 +293,17 @@ void rigid_body_handle_collision(rigid_body_index idx1, rigid_body_index idx2) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (rigid_body_collision_callback) {
|
collision_event_push(idx1, idx2);
|
||||||
rigid_body_collision_callback(idx1, idx2);
|
|
||||||
}
|
|
||||||
|
|
||||||
vec2 n = rb1->plane.normal;
|
vec2 n = rb1->plane.normal;
|
||||||
vec2 n_overlap = vec2_mul(n, overlap);
|
vec2 n_overlap = vec2_mul(n, overlap);
|
||||||
|
|
||||||
rb2->pos = vec2_add(rb2->pos, n_overlap);
|
rb2->pos = vec2_add(rb2->pos, n_overlap);
|
||||||
rb2->vel = vec2_reflect(rb2->vel, n);
|
|
||||||
|
// Reflect only if the circle is moving into the plane.
|
||||||
|
if (vec2_dot(rb2->vel, n) < 0) {
|
||||||
|
rb2->vel = vec2_reflect(rb2->vel, n);
|
||||||
|
}
|
||||||
} else if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_PLANE) {
|
} else if (rb1->type == TYPE_CIRCLE && rb2->type == TYPE_PLANE) {
|
||||||
rigid_body_handle_collision(idx2, idx1);
|
rigid_body_handle_collision(idx2, idx1);
|
||||||
}
|
}
|
||||||
|
|
@ -248,14 +323,23 @@ rigid_body_index rigid_body_find_empty() {
|
||||||
return idx;
|
return idx;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
size_t new_cap = rigid_bodies_cap * 2;
|
size_t old_cap = rigid_bodies_cap;
|
||||||
rigid_bodies = realloc(rigid_bodies, new_cap * sizeof(rigid_body));
|
size_t new_cap = old_cap * 2;
|
||||||
memset(rigid_bodies + rigid_bodies_cap, 0, rigid_bodies_cap * sizeof(rigid_body));
|
rigid_body* grown = realloc(rigid_bodies, new_cap * sizeof(rigid_body));
|
||||||
|
if (!grown) {
|
||||||
|
return SIZE_MAX; // keep the old buffer intact; caller handles failure
|
||||||
|
}
|
||||||
|
rigid_bodies = grown;
|
||||||
|
memset(rigid_bodies + old_cap, 0, (new_cap - old_cap) * sizeof(rigid_body));
|
||||||
rigid_bodies_cap = new_cap;
|
rigid_bodies_cap = new_cap;
|
||||||
return rigid_body_find_empty();
|
return old_cap; // first slot of the freshly grown region
|
||||||
} else {
|
} else {
|
||||||
|
rigid_body* allocated = malloc(2 * sizeof(rigid_body));
|
||||||
|
if (!allocated) {
|
||||||
|
return SIZE_MAX;
|
||||||
|
}
|
||||||
|
rigid_bodies = allocated;
|
||||||
rigid_bodies_cap = 2;
|
rigid_bodies_cap = 2;
|
||||||
rigid_bodies = malloc(rigid_bodies_cap * sizeof(rigid_body));
|
|
||||||
memset(rigid_bodies, 0, rigid_bodies_cap * sizeof(rigid_body));
|
memset(rigid_bodies, 0, rigid_bodies_cap * sizeof(rigid_body));
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -26,6 +26,10 @@ namespace Physics {
|
||||||
E.rigid_body_add_global_force(fx, fy);
|
E.rigid_body_add_global_force(fx, fy);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function setGravity(gx: number, gy: number) {
|
||||||
|
E.rigid_body_set_gravity(gx, gy);
|
||||||
|
}
|
||||||
|
|
||||||
export function update(dt: number) {
|
export function update(dt: number) {
|
||||||
E.rigid_body_update_all(dt);
|
E.rigid_body_update_all(dt);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -130,6 +130,7 @@ const setup = (): State => {
|
||||||
Physics.newPlane(0, 0, 0, 1);
|
Physics.newPlane(0, 0, 0, 1);
|
||||||
Physics.newPlane(canvas.width, 0, -1, 0);
|
Physics.newPlane(canvas.width, 0, -1, 0);
|
||||||
|
|
||||||
|
Physics.setGravity(0, 100);
|
||||||
Physics.setCollisionCallback((a, b) => onCollision(state, a, b));
|
Physics.setCollisionCallback((a, b) => onCollision(state, a, b));
|
||||||
|
|
||||||
update();
|
update();
|
||||||
|
|
@ -137,10 +138,7 @@ const setup = (): State => {
|
||||||
return state;
|
return state;
|
||||||
}
|
}
|
||||||
|
|
||||||
const frame = (dt: number, state: State) => {
|
const frame = (_dt: number, state: State) => {
|
||||||
Physics.addGlobalForce(0, 100);
|
|
||||||
Physics.update(dt);
|
|
||||||
|
|
||||||
const { ctx, canvas } = state;
|
const { ctx, canvas } = state;
|
||||||
ctx.fillStyle = `#111`;
|
ctx.fillStyle = `#111`;
|
||||||
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
ctx.fillRect(0, 0, canvas.width, canvas.height);
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue