1
0
Fork 0
This commit is contained in:
Pabloader 2025-12-17 09:07:46 +00:00
parent a38b5f81cd
commit 307be47251
4 changed files with 126 additions and 21 deletions

View File

@ -142,6 +142,16 @@ EXPORT(rigid_body_add_force) void rigid_body_add_force(rigid_body* rb, float fx,
rb->force.y += fy;
}
EXPORT(rigid_body_add_global_force) void rigid_body_add_global_force(float fx, float fy) {
rigid_body* current = rigid_body_head;
while (current) {
rigid_body_add_force(current, fx, fy);
current = current->next;
}
}
EXPORT(rigid_body_set_collision_callback) void rigid_body_set_collision_callback(rigid_body_collision_callback_t callback) {
rigid_body_collision_callback = callback;
}

View File

@ -3,22 +3,18 @@ import E from './engine.c';
namespace Physics {
const TYPE_CIRCLE = 1;
const TYPE_PLANE = 2;
const bodies = new Set<number>();
export function newCircle(x: number, y: number, radius: number, mass: number = 1.0): number {
const body = E.rigid_body_new_circle(x, y, 0, 0, mass, radius);
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);
return body;
}
export function deleteBody(body: number) {
bodies.delete(body);
E.rigid_body_free(body);
}
@ -27,9 +23,7 @@ namespace Physics {
}
export function addGlobalForce(fx: number, fy: number) {
for (const body of bodies) {
E.rigid_body_add_force(body, fx, fy);
}
E.rigid_body_add_global_force(fx, fy);
}
export function update(dt: number) {
@ -58,10 +52,6 @@ namespace Physics {
return { id: body, x, y, vx, vy, mass };
}
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);

View File

@ -32,6 +32,8 @@ export const shuffle = <T>(array: T[]): T[] => {
return shuffledArray;
}
export const mapNumber = (num: number, inMin: number, inMax: number, outMin: number, outMax: number) =>
(num - inMin) * (outMax - outMin) / (inMax - inMin) + outMin;
export function zip<T1, T2, T3>(a1: Iterable<T1>, a2: Iterable<T2>, a3: Iterable<T3>): Generator<[T1, T2, T3]>;
export function zip<T1, T2>(a1: Iterable<T1>, a2: Iterable<T2>): Generator<[T1, T2]>;

View File

@ -1,7 +1,30 @@
import { createCanvas } from "@common/display/canvas";
import { gameLoop } from "@common/game";
import Physics from "@common/physics";
import { mapNumber } from "@common/utils";
interface Ball {
body: number;
damage: number;
health: number;
}
interface Peg {
body: number;
health: number;
}
interface State extends Record<string, unknown> {
canvas: HTMLCanvasElement;
ctx: CanvasRenderingContext2D;
bottom: number;
balls: Set<Ball>;
pegs: Set<Peg>;
pegHealth: number;
ballHealth: number;
ballDamage: number;
}
let lastTick = performance.now();
function update() {
@ -13,46 +36,126 @@ function update() {
setTimeout(update, 5);
}
const killBody = (state: State, body: Peg | Ball) => {
state.balls.delete(body as Ball);
state.pegs.delete(body as Peg);
Physics.deleteBody(body.body);
}
const setup = () => {
const onCollision = (state: State, b1: number, b2: number) => {
const ball = state.balls.values().find(b => b.body === b1 || b.body === b2);
const peg = state.pegs.values().find(b => b.body === b1 || b.body === b2);
if (ball && (b1 === state.bottom || b2 === state.bottom)) {
killBody(state, ball);
} else if (peg && ball) {
peg.health -= ball.damage;
ball.health -= 1;
if (peg.health <= 0) {
killBody(state, peg);
}
if (ball.health <= 0) {
killBody(state, ball);
}
}
}
const generatePegs = (state: State) => {
const { canvas } = state;
state.pegs.clear();
const gap = 45;
const vgap = gap * Math.cos(Math.PI / 6);
let offset = 0;
for (let y = canvas.height / 3; y <= canvas.height - gap; y += vgap) {
for (let i = offset; i <= canvas.width / 2; i += gap) {
const x1 = canvas.width / 2 + i;
state.pegs.add({
body: Physics.newCircle(x1, y, 3, Number.POSITIVE_INFINITY),
health: state.pegHealth,
});
const x2 = canvas.width / 2 - i;
if (Math.abs(x1 - x2) < gap) continue;
state.pegs.add({
body: Physics.newCircle(x2, y, 3, Number.POSITIVE_INFINITY),
health: state.pegHealth,
});
}
offset = gap / 2 - offset;
}
}
const setup = (): State => {
const canvas = createCanvas(640, 480);
const ctx = canvas.getContext('2d');
if (!ctx) {
throw new Error('Failed to get canvas context');
}
const state: State = {
canvas,
ctx,
bottom: 0,
pegs: new Set(),
balls: new Set(),
pegHealth: 3,
ballHealth: 10,
ballDamage: 1,
};
ctx.imageSmoothingEnabled = false;
canvas.addEventListener('contextmenu', e => {
e.stopPropagation();
e.preventDefault();
});
canvas.addEventListener('mouseup', () => {
Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
const body = Physics.newCircle(canvas.width / 2 + Math.random() - 0.5, 50, 10, 1);
const health = state.ballHealth;
const damage = state.ballDamage;
const ball: Ball = {
body,
health,
damage,
}
state.balls.add(ball);
return false;
});
generatePegs(state);
state.bottom = Physics.newPlane(0, canvas.height, 0, -1);
Physics.newPlane(0, 0, 1, 0);
Physics.newPlane(0, 0, 0, 1);
Physics.newPlane(0, canvas.height, 0, -1);
Physics.newPlane(canvas.width, 0, -1, 0);
Physics.setCollisionCallback((a, b) => onCollision(state, a, b));
update();
return { canvas, ctx };
return state;
}
const frame = (dt: number, state: ReturnType<typeof setup>) => {
const frame = (dt: number, state: State) => {
Physics.addGlobalForce(0, 100);
Physics.update(dt);
const { ctx, canvas } = state;
ctx.fillStyle = `black`;
ctx.fillStyle = `#111`;
ctx.fillRect(0, 0, canvas.width, canvas.height);
for (const { id, x, y, radius } of Physics.getBodies()) {
for (const peg of state.pegs) {
const { x, y, radius } = Physics.getBody(peg.body);
if (!radius) continue;
ctx.fillStyle = `hsl(0, 0%, ${mapNumber(peg.health, 0, state.pegHealth, 0, 100)}%)`;
fillCircle(ctx, x, y, radius);
}
for (const ball of state.balls) {
const { id, x, y, radius } = Physics.getBody(ball.body);
if (!radius) continue;
ctx.fillStyle = `hsl(${id % 360}, 100%, 50%)`;
if (radius) {
fillCircle(ctx, x, y, radius);
}
fillCircle(ctx, x, y, radius);
}
}