Files
esengine/thirdparty/rapier.js/testbed2d/src/Testbed.ts
2025-12-03 16:24:08 +08:00

190 lines
5.6 KiB
TypeScript

import {Graphics} from "./Graphics";
import {Gui} from "./Gui";
import type {DebugInfos} from "./Gui";
import {xxhash128} from "hash-wasm";
import type * as RAPIER from "@dimforge/rapier2d";
type RAPIER_API = typeof import("@dimforge/rapier2d");
type Builders = Map<string, (RAPIER: RAPIER_API, testbed: Testbed) => void>;
class SimulationParameters {
backend: string;
prevBackend: string;
demo: string;
numSolverIters: number;
running: boolean;
stepping: boolean;
debugRender: boolean;
step: () => void;
restart: () => void;
takeSnapshot: () => void;
restoreSnapshot: () => void;
backends: Array<string>;
builders: Builders;
debugInfos: boolean;
constructor(backends: Array<string>, builders: Builders) {
this.backend = "rapier";
this.prevBackend = "rapier";
this.demo = "collision groups";
this.numSolverIters = 4;
this.running = true;
this.stepping = false;
this.debugRender = false;
this.step = function () {};
this.restart = function () {};
this.takeSnapshot = function () {};
this.restoreSnapshot = function () {};
this.backends = backends;
this.builders = builders;
this.debugInfos = false;
}
}
export class Testbed {
RAPIER: RAPIER_API;
gui: Gui;
graphics: Graphics;
inhibitLookAt: boolean;
parameters: SimulationParameters;
demoToken: number;
mouse: {x: number; y: number};
events: RAPIER.EventQueue;
world: RAPIER.World;
preTimestepAction?: (gfx: Graphics) => void;
stepId: number;
prevDemo: string;
lastMessageTime: number;
snap: Uint8Array;
snapStepId: number;
constructor(RAPIER: RAPIER_API, builders: Builders) {
let backends = ["rapier"];
this.RAPIER = RAPIER;
let parameters = new SimulationParameters(backends, builders);
this.gui = new Gui(this, parameters);
this.graphics = new Graphics();
this.inhibitLookAt = false;
this.parameters = parameters;
this.demoToken = 0;
this.mouse = {x: 0, y: 0};
this.events = new RAPIER.EventQueue(true);
this.switchToDemo(builders.keys().next().value);
window.addEventListener("mousemove", (event) => {
this.mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
this.mouse.y = 1 - (event.clientY / window.innerHeight) * 2;
});
}
setpreTimestepAction(action: (gfx: Graphics) => void) {
this.preTimestepAction = action;
}
setWorld(world: RAPIER.World) {
document.onkeyup = null;
document.onkeydown = null;
this.preTimestepAction = null;
this.world = world;
this.world.numSolverIterations = this.parameters.numSolverIters;
this.demoToken += 1;
this.stepId = 0;
this.gui.resetTiming();
world.forEachCollider((coll) => {
this.graphics.addCollider(this.RAPIER, world, coll);
});
this.lastMessageTime = new Date().getTime();
}
lookAt(pos: Parameters<Graphics["lookAt"]>[0]) {
if (!this.inhibitLookAt) {
this.graphics.lookAt(pos);
}
this.inhibitLookAt = false;
}
switchToDemo(demo: string) {
if (demo == this.prevDemo) {
this.inhibitLookAt = true;
}
this.prevDemo = demo;
this.graphics.reset();
this.stepId = 0;
this.parameters.prevBackend = this.parameters.backend;
this.parameters.builders.get(demo)(this.RAPIER, this);
}
switchToBackend(backend: string) {
this.switchToDemo(this.parameters.demo);
}
takeSnapshot() {
this.snap = this.world.takeSnapshot();
this.snapStepId = this.stepId;
}
restoreSnapshot() {
if (!!this.snap) {
this.world.free();
this.world = this.RAPIER.World.restoreSnapshot(this.snap);
this.stepId = this.snapStepId;
}
}
run() {
if (this.parameters.running || this.parameters.stepping) {
this.world.numSolverIterations = this.parameters.numSolverIters;
if (!!this.preTimestepAction) {
this.preTimestepAction(this.graphics);
}
let t0 = new Date().getTime();
this.world.step(this.events);
this.gui.setTiming(new Date().getTime() - t0);
this.stepId += 1;
if (!!this.parameters.debugInfos) {
let t0 = performance.now();
let snapshot = this.world.takeSnapshot();
let t1 = performance.now();
let snapshotTime = t1 - t0;
let debugInfos: DebugInfos = {
token: this.demoToken,
stepId: this.stepId,
worldHash: "",
worldHashTime: 0,
snapshotTime: 0,
};
t0 = performance.now();
xxhash128(snapshot).then((hash) => {
debugInfos.worldHash = hash;
t1 = performance.now();
let worldHashTime = t1 - t0;
debugInfos.worldHashTime = worldHashTime;
debugInfos.snapshotTime = snapshotTime;
this.gui.setDebugInfos(debugInfos);
});
}
}
if (this.parameters.stepping) {
this.parameters.running = false;
this.parameters.stepping = false;
}
this.gui.stats.begin();
this.graphics.render(this.world, this.parameters.debugRender);
this.gui.stats.end();
requestAnimationFrame(() => this.run());
}
}