1
0
Fork 0

Fix engine bugs

This commit is contained in:
Pabloader 2026-05-31 11:37:33 +00:00
parent 65660cc639
commit 8cde9818f4
3 changed files with 118 additions and 32 deletions

View File

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

View File

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

View File

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