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) {
|
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}`;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
|
|
@ -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);
|
||||||
|
|
@ -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;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue