Collision callback
This commit is contained in:
parent
ee9c4b0290
commit
a38b5f81cd
|
|
@ -60,15 +60,59 @@ const getCompiler = async (): Promise<CompilerWithFlags> => {
|
|||
}
|
||||
|
||||
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({
|
||||
initial: 32,
|
||||
});
|
||||
const table = new WebAssembly.Table({ initial: 1, element: 'anyfunc' });
|
||||
let data = new DataView(memory.buffer);
|
||||
const decoder = new TextDecoder();
|
||||
let buf = '';
|
||||
let errBuf = '';
|
||||
const { instance } = await WebAssembly.instantiateStreaming(fetch(url), {
|
||||
env: { memory },
|
||||
env: { memory, __indirect_function_table: table },
|
||||
wasi_snapshot_preview1: {
|
||||
random_get: (ptr: number, length: number) => {
|
||||
for (let i = 0; i < length; i++) {
|
||||
|
|
@ -115,6 +159,7 @@ async function instantiate(url: string) {
|
|||
return {
|
||||
...instance.exports,
|
||||
memory,
|
||||
table,
|
||||
get data() { return data; },
|
||||
};
|
||||
}
|
||||
|
|
@ -178,6 +223,7 @@ const wasmPlugin = ({ production }: WasmLoaderConfig = {}): BunPlugin => {
|
|||
'--lto-O3',
|
||||
'--no-entry',
|
||||
'--import-memory',
|
||||
'--import-table',
|
||||
].map(f => `-Wl,${f}`);
|
||||
|
||||
const linkResult = await $`${cc.cc} ${flags} -std=gnu23 ${linkFlags} -lstdc++ -nostartfiles -o ${wasmPath} ${objPath} ${stdlib}`;
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@
|
|||
|
||||
#define MAX_BODIES 1000
|
||||
|
||||
////////// Structs
|
||||
////////// Types
|
||||
|
||||
typedef struct rigid_body_t {
|
||||
// Public
|
||||
|
|
@ -33,6 +33,8 @@ typedef struct rigid_body_t {
|
|||
struct rigid_body_t* next;
|
||||
} rigid_body;
|
||||
|
||||
typedef void (*rigid_body_collision_callback_t)(rigid_body* a, rigid_body* b);
|
||||
|
||||
////////// Prototypes
|
||||
|
||||
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_tail = NULL;
|
||||
static rigid_body_collision_callback_t rigid_body_collision_callback = NULL;
|
||||
|
||||
////////// Functions
|
||||
|
||||
|
|
@ -139,6 +142,10 @@ EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx,
|
|||
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) {
|
||||
rigid_body* current = rb->next;
|
||||
int is_static = isinf(rb->mass);
|
||||
|
|
@ -161,6 +168,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (rigid_body_collision_callback) {
|
||||
rigid_body_collision_callback(rb1, rb2);
|
||||
}
|
||||
|
||||
vec2 n = vec2_normalize(d);
|
||||
float factor = (isinf(rb1->mass) || isinf(rb2->mass)) ? 1 : 0.5;
|
||||
vec2 n_overlap = vec2_mul(n, overlap * factor);
|
||||
|
|
@ -206,6 +217,10 @@ void rigid_body_handle_collision(rigid_body* rb1, rigid_body* rb2) {
|
|||
return;
|
||||
}
|
||||
|
||||
if (rigid_body_collision_callback) {
|
||||
rigid_body_collision_callback(rb1, rb2);
|
||||
}
|
||||
|
||||
vec2 n = rb1->plane.normal;
|
||||
vec2 n_overlap = vec2_mul(n, overlap);
|
||||
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ namespace Physics {
|
|||
bodies.add(body);
|
||||
return body;
|
||||
}
|
||||
|
||||
export function newPlane(x: number, y: number, nx: number, ny: number): number {
|
||||
const body = E.rigid_body_new_plane(x, y, nx, ny);
|
||||
bodies.add(body);
|
||||
|
|
@ -60,6 +61,11 @@ namespace Physics {
|
|||
export function getBodies() {
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,19 @@
|
|||
import { createCanvas } from "@common/display/canvas";
|
||||
import { getRealPoint } from "@common/dom";
|
||||
import { gameLoop } from "@common/game";
|
||||
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 canvas = createCanvas(640, 480);
|
||||
const ctx = canvas.getContext('2d');
|
||||
|
|
@ -10,23 +21,13 @@ const setup = () => {
|
|||
throw new Error('Failed to get canvas context');
|
||||
}
|
||||
|
||||
ctx.imageSmoothingEnabled = false;
|
||||
canvas.addEventListener('contextmenu', e => {
|
||||
e.stopPropagation();
|
||||
e.preventDefault();
|
||||
})
|
||||
canvas.addEventListener('mousedown', (event) => {
|
||||
event.stopPropagation();
|
||||
event.preventDefault();
|
||||
return false;
|
||||
});
|
||||
canvas.addEventListener('mouseup', (event) => {
|
||||
event.stopPropagation();
|
||||
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);
|
||||
canvas.addEventListener('mouseup', () => {
|
||||
Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
|
||||
return false;
|
||||
});
|
||||
|
||||
|
|
@ -35,6 +36,8 @@ const setup = () => {
|
|||
Physics.newPlane(0, canvas.height, 0, -1);
|
||||
Physics.newPlane(canvas.width, 0, -1, 0);
|
||||
|
||||
update();
|
||||
|
||||
return { canvas, ctx };
|
||||
}
|
||||
|
||||
|
|
@ -43,15 +46,35 @@ const frame = (dt: number, state: ReturnType<typeof setup>) => {
|
|||
Physics.update(dt);
|
||||
|
||||
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()) {
|
||||
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
|
||||
if (radius) {
|
||||
ctx.beginPath();
|
||||
ctx.arc(x, y, radius, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
fillCircle(ctx, x, y, radius);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
|
|
@ -3,6 +3,13 @@ type Rect = [number, number, number, number];
|
|||
|
||||
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' {
|
||||
const classes: { [key: string]: string };
|
||||
export default classes;
|
||||
|
|
@ -43,6 +50,7 @@ declare module "*.wasm" {
|
|||
declare module "*.c" {
|
||||
const instance: {
|
||||
memory: WebAssembly.Memory;
|
||||
table: WebAssembly.Table;
|
||||
data: DataView;
|
||||
|
||||
[x: string]: (...args: any) => any;
|
||||
|
|
@ -52,6 +60,7 @@ declare module "*.c" {
|
|||
declare module "*.cpp" {
|
||||
const instance: {
|
||||
memory: WebAssembly.Memory;
|
||||
table: WebAssembly.Table;
|
||||
data: DataView;
|
||||
|
||||
[x: string]: (...args: any) => any;
|
||||
|
|
|
|||
Loading…
Reference in New Issue