1
0
Fork 0

Collision callback

This commit is contained in:
Pabloader 2025-12-16 20:43:46 +00:00
parent ee9c4b0290
commit a38b5f81cd
5 changed files with 119 additions and 20 deletions

View File

@ -60,15 +60,59 @@ const getCompiler = async (): Promise<CompilerWithFlags> => {
} }
async function instantiate(url: string) { async function instantiate(url: string) {
if (typeof WebAssembly.Function !== "function") {
const typeCodes = {
i32: 0x7f,
i64: 0x7e,
f32: 0x7d,
f64: 0x7c
};
type tc = keyof typeof typeCodes;
// @ts-ignore
WebAssembly.Function = function ({
parameters = [],
results = [],
}: { parameters: tc[], results: tc[] }, func: Function) {
const
resultAmount = results.length,
parameterAmount = parameters.length;
const bytes = new Uint8Array([
// Headers (Magic and Version)
0x00, 0x61, 0x73, 0x6d, 0x01, 0x00, 0x00, 0x00,
// Type section
0x01, (4 + parameterAmount + resultAmount), 0x01, 0x60, // Section ID, Type section length, Count, Function
parameterAmount, ...parameters.map(p => typeCodes[p]), // Parameter types
resultAmount, ...results.map(r => typeCodes[r]), // Result types
// Import and Exports
0x02, 0x07, 0x01, 0x01, 0x65, 0x01, 0x66, 0x00, 0x00,
0x07, 0x05, 0x01, 0x01, 0x66, 0x00, 0x00
]);
// Compile and instantiate
const module = new WebAssembly.Module(bytes);
const instance = new WebAssembly.Instance(module, {
e: {
f: func
}
});
return instance.exports.f;
};
}
const memory = new WebAssembly.Memory({ const memory = new WebAssembly.Memory({
initial: 32, initial: 32,
}); });
const table = new WebAssembly.Table({ initial: 1, element: 'anyfunc' });
let data = new DataView(memory.buffer); let data = new DataView(memory.buffer);
const decoder = new TextDecoder(); const decoder = new TextDecoder();
let buf = ''; let buf = '';
let errBuf = ''; let errBuf = '';
const { instance } = await WebAssembly.instantiateStreaming(fetch(url), { const { instance } = await WebAssembly.instantiateStreaming(fetch(url), {
env: { memory }, env: { memory, __indirect_function_table: table },
wasi_snapshot_preview1: { wasi_snapshot_preview1: {
random_get: (ptr: number, length: number) => { random_get: (ptr: number, length: number) => {
for (let i = 0; i < length; i++) { for (let i = 0; i < length; i++) {
@ -115,6 +159,7 @@ async function instantiate(url: string) {
return { return {
...instance.exports, ...instance.exports,
memory, memory,
table,
get data() { return data; }, get data() { return data; },
}; };
} }
@ -178,6 +223,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
'--lto-O3', '--lto-O3',
'--no-entry', '--no-entry',
'--import-memory', '--import-memory',
'--import-table',
].map(f => `-Wl,${f}`); ].map(f => `-Wl,${f}`);
const linkResult = await $`${cc.cc} ${flags} -std=gnu23 ${linkFlags} -lstdc++ -nostartfiles -o ${wasmPath} ${objPath} ${stdlib}`; const linkResult = await $`${cc.cc} ${flags} -std=gnu23 ${linkFlags} -lstdc++ -nostartfiles -o ${wasmPath} ${objPath} ${stdlib}`;

View File

@ -10,7 +10,7 @@
#define MAX_BODIES 1000 #define MAX_BODIES 1000
////////// Structs ////////// Types
typedef struct rigid_body_t { typedef struct rigid_body_t {
// Public // Public
@ -33,6 +33,8 @@ typedef struct rigid_body_t {
struct rigid_body_t* next; struct rigid_body_t* next;
} rigid_body; } rigid_body;
typedef void (*rigid_body_collision_callback_t)(rigid_body* a, rigid_body* b);
////////// Prototypes ////////// Prototypes
void rigid_body_resolve_collision(rigid_body* rb); void rigid_body_resolve_collision(rigid_body* rb);
@ -43,6 +45,7 @@ float point_to_line_dist(vec2 point, vec2 line_point, vec2 line_norm);
static rigid_body* rigid_body_head = NULL; static rigid_body* rigid_body_head = NULL;
static rigid_body* rigid_body_tail = NULL; static rigid_body* rigid_body_tail = NULL;
static rigid_body_collision_callback_t rigid_body_collision_callback = NULL;
////////// Functions ////////// Functions
@ -139,6 +142,10 @@ EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx,
rb->force.y += fy; rb->force.y += fy;
} }
EXPORT(rigid_body_set_collision_callback) void rigid_body_set_collision_callback(rigid_body_collision_callback_t callback) {
rigid_body_collision_callback = callback;
}
void rigid_body_resolve_collision(rigid_body* rb) { void rigid_body_resolve_collision(rigid_body* rb) {
rigid_body* current = rb->next; rigid_body* current = rb->next;
int is_static = isinf(rb->mass); int is_static = isinf(rb->mass);
@ -161,6 +168,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) {
return; return;
} }
if (rigid_body_collision_callback) {
rigid_body_collision_callback(rb1, rb2);
}
vec2 n = vec2_normalize(d); 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);
@ -206,6 +217,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) {
return; return;
} }
if (rigid_body_collision_callback) {
rigid_body_collision_callback(rb1, rb2);
}
vec2 n = rb1->plane.normal; vec2 n = rb1->plane.normal;
vec2 n_overlap = vec2_mul(n, overlap); vec2 n_overlap = vec2_mul(n, overlap);

View File

@ -10,6 +10,7 @@ namespace Physics {
bodies.add(body); bodies.add(body);
return body; return body;
} }
export function newPlane(x: number, y: number, nx: number, ny: number): number { export function newPlane(x: number, y: number, nx: number, ny: number): number {
const body = E.rigid_body_new_plane(x, y, nx, ny); const body = E.rigid_body_new_plane(x, y, nx, ny);
bodies.add(body); bodies.add(body);
@ -60,6 +61,11 @@ namespace Physics {
export function getBodies() { export function getBodies() {
return Array.from(bodies).map(getBody); return Array.from(bodies).map(getBody);
} }
export function setCollisionCallback(cb: (a: number, b: number) => void) {
const cbPtr = E.table.grow(1, new WebAssembly.Function({ parameters: ['i32', 'i32'], results: [] }, cb));
E.rigid_body_set_collision_callback(cbPtr);
}
} }
export default Physics; export default Physics;

View File

@ -1,8 +1,19 @@
import { createCanvas } from "@common/display/canvas"; import { createCanvas } from "@common/display/canvas";
import { getRealPoint } from "@common/dom";
import { gameLoop } from "@common/game"; import { gameLoop } from "@common/game";
import Physics from "@common/physics"; import Physics from "@common/physics";
let lastTick = performance.now();
function update() {
const dt = (performance.now() - lastTick) / 1000;
if (dt < 1) {
Physics.update(dt);
}
lastTick = performance.now();
setTimeout(update, 5);
}
const setup = () => { const setup = () => {
const canvas = createCanvas(640, 480); const canvas = createCanvas(640, 480);
const ctx = canvas.getContext('2d'); const ctx = canvas.getContext('2d');
@ -10,23 +21,13 @@ const setup = () => {
throw new Error('Failed to get canvas context'); throw new Error('Failed to get canvas context');
} }
ctx.imageSmoothingEnabled = false;
canvas.addEventListener('contextmenu', e => { canvas.addEventListener('contextmenu', e => {
e.stopPropagation(); e.stopPropagation();
e.preventDefault(); e.preventDefault();
})
canvas.addEventListener('mousedown', (event) => {
event.stopPropagation();
event.preventDefault();
return false;
}); });
canvas.addEventListener('mouseup', (event) => { canvas.addEventListener('mouseup', () => {
event.stopPropagation(); Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
event.preventDefault();
const { x, y } = getRealPoint(canvas, event);
console.log(event.button);
const mass = event.button === 0 ? Math.random() : Number.POSITIVE_INFINITY;
const r = event.button === 0 ? 16 : 4;
Physics.newCircle(x, y, r, mass);
return false; return false;
}); });
@ -35,6 +36,8 @@ const setup = () => {
Physics.newPlane(0, canvas.height, 0, -1); Physics.newPlane(0, canvas.height, 0, -1);
Physics.newPlane(canvas.width, 0, -1, 0); Physics.newPlane(canvas.width, 0, -1, 0);
update();
return { canvas, ctx }; return { canvas, ctx };
} }
@ -43,15 +46,35 @@ const frame = (dt: number, state: ReturnType<typeof setup>) => {
Physics.update(dt); Physics.update(dt);
const { ctx, canvas } = state; const { ctx, canvas } = state;
ctx.clearRect(0, 0, canvas.width, canvas.height); ctx.fillStyle = `black`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (const { id, x, y, radius } of Physics.getBodies()) { for (const { id, x, y, radius } of Physics.getBodies()) {
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`; ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
if (radius) { if (radius) {
ctx.beginPath(); fillCircle(ctx, x, y, radius);
ctx.arc(x, y, radius, 0, Math.PI * 2);
ctx.fill();
} }
} }
} }
function fillCircle(ctx: CanvasRenderingContext2D, xc: number, yc: number, r: number) {
// because default circle fill is antialiased
// https://stackoverflow.com/a/45745753
xc = xc | 0;
yc = yc | 0;
r = r | 0;
var x = r, y = 0, cd = 0;
// middle line
ctx.fillRect(xc - x, yc, r << 1, 1);
while (x > y) {
cd -= (--x) - (++y);
if (cd < 0) cd += x++;
ctx.fillRect(xc - y, yc - x, y << 1, 1); // upper 1/4
ctx.fillRect(xc - x, yc - y, x << 1, 1); // upper 2/4
ctx.fillRect(xc - x, yc + y, x << 1, 1); // lower 3/4
ctx.fillRect(xc - y, yc + x, y << 1, 1); // lower 4/4
}
}
export default gameLoop(setup, frame); export default gameLoop(setup, frame);

9
src/types.d.ts vendored
View File

@ -3,6 +3,13 @@ type Rect = [number, number, number, number];
type RunGame = () => Promise<void>; type RunGame = () => Promise<void>;
declare namespace WebAssembly {
type tc = 'i32' | 'i64' | 'f32' | 'f64';
export class Function {
constructor(args: { parameters: tc[], results: tc[] }, func: Function);
}
}
declare module '*.module.css' { declare module '*.module.css' {
const classes: { [key: string]: string }; const classes: { [key: string]: string };
export default classes; export default classes;
@ -43,6 +50,7 @@ declare module "*.wasm" {
declare module "*.c" { declare module "*.c" {
const instance: { const instance: {
memory: WebAssembly.Memory; memory: WebAssembly.Memory;
table: WebAssembly.Table;
data: DataView; data: DataView;
[x: string]: (...args: any) => any; [x: string]: (...args: any) => any;
@ -52,6 +60,7 @@ declare module "*.c" {
declare module "*.cpp" { declare module "*.cpp" {
const instance: { const instance: {
memory: WebAssembly.Memory; memory: WebAssembly.Memory;
table: WebAssembly.Table;
data: DataView; data: DataView;
[x: string]: (...args: any) => any; [x: string]: (...args: any) => any;