chore: 添加第三方依赖库

This commit is contained in:
yhh
2025-12-03 16:24:08 +08:00
parent cb1b171216
commit 83aee02540
172 changed files with 27480 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
webpack.config2d.js
src
*.tgz

View File

@@ -0,0 +1,32 @@
{
"author": "sebcrozet <developer@crozet.re>",
"name": "rapier-testbed3d",
"version": "0.1.0",
"description": "JavaScript testbed for rapier.",
"license": "Apache-2.0",
"main": "dist/rapier-testbed3d.js",
"scripts": {
"build": "rimraf dist pkg && webpack",
"start": "rimraf dist pkg && webpack serve --open --node-env development"
},
"dependencies": {
"@dimforge/rapier3d": "file:../builds/rapier3d",
"hash-wasm": "^4.12.0",
"lil-gui": "^0.17.0",
"seedrandom": "^3.0.5",
"stats.js": "^0.17.0",
"three": "^0.146.0"
},
"devDependencies": {
"@types/seedrandom": "^3.0.2",
"@types/stats.js": "^0.17.0",
"@types/three": "^0.144.0",
"copy-webpack-plugin": "^11.0.0",
"rimraf": "^3.0.2",
"ts-loader": "^9.4.1",
"typescript": "^4.8.4",
"webpack": "^5.74.0",
"webpack-cli": "^4.10.0",
"webpack-dev-server": "^4.11.1"
}
}

View File

@@ -0,0 +1,6 @@
#!/bin/bash
npm run build
rsync -av --delete-after dist/ crozet@ssh.cluster003.hosting.ovh.net:/home/crozet/rapier/demos3d
# rsync -av dist/ammo.wasm.wasm crozet@ssh.cluster003.hosting.ovh.net:/home/crozet/rapier/ammo.wasm.wasm
# rsync -av dist/physx.release.wasm crozet@ssh.cluster003.hosting.ovh.net:/home/crozet/rapier/physx.release.wasm

View File

@@ -0,0 +1,576 @@
import * as THREE from "three";
import {OrbitControls} from "three/examples/jsm/controls/OrbitControls";
import RAPIER from "@dimforge/rapier3d";
const BOX_INSTANCE_INDEX = 0;
const BALL_INSTANCE_INDEX = 1;
const CYLINDER_INSTANCE_INDEX = 2;
const CONE_INSTANCE_INDEX = 3;
var dummy = new THREE.Object3D();
var kk = 0;
interface InstanceDesc {
groupId: number;
instanceId: number;
elementId: number;
highlighted: boolean;
scale?: THREE.Vector3;
}
type RAPIER_API = typeof import("@dimforge/rapier3d");
// NOTE: this is a very naive voxels -> mesh conversion. Proper
// conversions should use something like greedy meshing instead.
function genVoxelsGeometry(collider: RAPIER.Collider) {
// Clear the cached shape so it gets recomputed from the source of truth,
// and so well be sure that the data contain grid coordinates even if the
// voxels were initialized with floating points.
collider.clearShapeCache();
let shape = collider.shape as RAPIER.Voxels;
let gridCoords = shape.data;
let sz = shape.voxelSize;
let vertices = [];
let indices = [];
let i: number;
for (i = 0; i < gridCoords.length; i += 3) {
let minx = gridCoords[i] * sz.x;
let miny = gridCoords[i + 1] * sz.y;
let minz = gridCoords[i + 2] * sz.z;
let maxx = minx + sz.x;
let maxy = miny + sz.y;
let maxz = minz + sz.z;
let k: number = vertices.length / 3;
vertices.push(minx, miny, maxz);
vertices.push(minx, miny, minz);
vertices.push(maxx, miny, minz);
vertices.push(maxx, miny, maxz);
vertices.push(minx, maxy, maxz);
vertices.push(minx, maxy, minz);
vertices.push(maxx, maxy, minz);
vertices.push(maxx, maxy, maxz);
indices.push(k + 4, k + 5, k + 0);
indices.push(k + 5, k + 1, k + 0);
indices.push(k + 5, k + 6, k + 1);
indices.push(k + 6, k + 2, k + 1);
indices.push(k + 6, k + 7, k + 3);
indices.push(k + 2, k + 6, k + 3);
indices.push(k + 7, k + 4, k + 0);
indices.push(k + 3, k + 7, k + 0);
indices.push(k + 0, k + 1, k + 2);
indices.push(k + 3, k + 0, k + 2);
indices.push(k + 7, k + 6, k + 5);
indices.push(k + 4, k + 7, k + 5);
}
return {
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices),
};
}
function genHeightfieldGeometry(collider: RAPIER.Collider) {
let heights = collider.heightfieldHeights();
let nrows = collider.heightfieldNRows();
let ncols = collider.heightfieldNCols();
let scale = collider.heightfieldScale();
let vertices = [];
let indices = [];
let eltWX = 1.0 / nrows;
let eltWY = 1.0 / ncols;
let i: number;
let j: number;
for (j = 0; j <= ncols; ++j) {
for (i = 0; i <= nrows; ++i) {
let x = (j * eltWX - 0.5) * scale.x;
let y = heights[j * (nrows + 1) + i] * scale.y;
let z = (i * eltWY - 0.5) * scale.z;
vertices.push(x, y, z);
}
}
for (j = 0; j < ncols; ++j) {
for (i = 0; i < nrows; ++i) {
let i1 = (i + 0) * (ncols + 1) + (j + 0);
let i2 = (i + 0) * (ncols + 1) + (j + 1);
let i3 = (i + 1) * (ncols + 1) + (j + 0);
let i4 = (i + 1) * (ncols + 1) + (j + 1);
indices.push(i1, i3, i2);
indices.push(i3, i4, i2);
}
}
return {
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices),
};
}
export class Graphics {
raycaster: THREE.Raycaster;
highlightedCollider: null | number;
coll2instance: Map<number, InstanceDesc>;
coll2mesh: Map<number, THREE.Mesh>;
rb2colls: Map<number, Array<RAPIER.Collider>>;
colorIndex: number;
colorPalette: Array<number>;
scene: THREE.Scene;
camera: THREE.PerspectiveCamera;
renderer: THREE.WebGLRenderer;
light: THREE.PointLight;
lines: THREE.LineSegments;
controls: OrbitControls;
instanceGroups: Array<Array<THREE.InstancedMesh>>;
constructor() {
this.raycaster = new THREE.Raycaster();
this.highlightedCollider = null;
this.coll2instance = new Map();
this.coll2mesh = new Map();
this.rb2colls = new Map();
this.colorIndex = 0;
this.colorPalette = [0xf3d9b1, 0x98c1d9, 0x053c5e, 0x1f7a8c, 0xff0000];
this.scene = new THREE.Scene();
this.camera = new THREE.PerspectiveCamera(
45,
window.innerWidth / window.innerHeight,
0.1,
10000,
);
this.renderer = new THREE.WebGLRenderer({antialias: true});
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.setClearColor(0x292929, 1);
// High pixel Ratio make the rendering extremely slow, so we cap it.
const pixelRatio = window.devicePixelRatio
? Math.min(window.devicePixelRatio, 1.5)
: 1;
this.renderer.setPixelRatio(pixelRatio);
document.body.appendChild(this.renderer.domElement);
let ambientLight = new THREE.AmbientLight(0x606060);
this.scene.add(ambientLight);
this.light = new THREE.PointLight(0xffffff, 1, 1000);
this.scene.add(this.light);
// For the debug-renderer.
{
let material = new THREE.LineBasicMaterial({
color: 0xffffff,
vertexColors: true,
});
let geometry = new THREE.BufferGeometry();
this.lines = new THREE.LineSegments(geometry, material);
this.scene.add(this.lines);
}
let me = this;
function onWindowResize() {
if (!!me.camera) {
me.camera.aspect = window.innerWidth / window.innerHeight;
me.camera.updateProjectionMatrix();
me.renderer.setSize(window.innerWidth, window.innerHeight);
}
}
window.addEventListener("resize", onWindowResize, false);
this.controls = new OrbitControls(
this.camera,
this.renderer.domElement,
);
this.controls.enableDamping = true;
this.controls.dampingFactor = 0.2;
this.controls.maxPolarAngle = Math.PI / 2;
this.initInstances();
}
initInstances() {
this.instanceGroups = [];
this.instanceGroups.push(
this.colorPalette.map((color) => {
let box = new THREE.BoxGeometry(2.0, 2.0, 2.0);
let mat = new THREE.MeshPhongMaterial({
color: color,
flatShading: true,
});
return new THREE.InstancedMesh(box, mat, 1000);
}),
);
this.instanceGroups.push(
this.colorPalette.map((color) => {
let ball = new THREE.SphereGeometry(1.0);
let mat = new THREE.MeshPhongMaterial({
color: color,
flatShading: true,
});
return new THREE.InstancedMesh(ball, mat, 1000);
}),
);
this.instanceGroups.push(
this.colorPalette.map((color) => {
let cylinder = new THREE.CylinderGeometry(1.0, 1.0);
let mat = new THREE.MeshPhongMaterial({
color: color,
flatShading: true,
});
return new THREE.InstancedMesh(cylinder, mat, 100);
}),
);
this.instanceGroups.push(
this.colorPalette.map((color) => {
let cone = new THREE.ConeGeometry(1.0, 1.0);
let mat = new THREE.MeshPhongMaterial({
color: color,
flatShading: true,
});
return new THREE.InstancedMesh(cone, mat, 100);
}),
);
this.instanceGroups.forEach((groups) => {
groups.forEach((instance) => {
instance.userData.elementId2coll = new Map();
instance.count = 0;
instance.instanceMatrix.setUsage(THREE.DynamicDrawUsage);
this.scene.add(instance);
});
});
}
render(world: RAPIER.World, debugRender: boolean) {
kk += 1;
this.controls.update();
// if (kk % 100 == 0) {
// console.log(this.camera.position);
// console.log(this.controls.target);
// }
this.light.position.set(
this.camera.position.x,
this.camera.position.y,
this.camera.position.z,
);
if (debugRender) {
let buffers = world.debugRender();
this.lines.visible = true;
this.lines.geometry.setAttribute(
"position",
new THREE.BufferAttribute(buffers.vertices, 3),
);
this.lines.geometry.setAttribute(
"color",
new THREE.BufferAttribute(buffers.colors, 4),
);
} else {
this.lines.visible = false;
}
this.updatePositions(world);
this.renderer.render(this.scene, this.camera);
}
rayAtMousePosition(pos: {x: number; y: number}) {
this.raycaster.setFromCamera(pos, this.camera);
return this.raycaster.ray;
}
lookAt(pos: {
target: {x: number; y: number; z: number};
eye: {x: number; y: number; z: number};
}) {
this.camera.position.set(pos.eye.x, pos.eye.y, pos.eye.z);
this.controls.target.set(pos.target.x, pos.target.y, pos.target.z);
this.controls.update();
}
highlightInstanceId() {
return this.colorPalette.length - 1;
}
highlightCollider(handle: number) {
if (handle == this.highlightedCollider)
// Avoid flickering when moving the mouse on a single collider.
return;
if (this.highlightedCollider != null) {
let desc = this.coll2instance.get(this.highlightedCollider);
if (!!desc) {
desc.highlighted = false;
this.instanceGroups[desc.groupId][
this.highlightInstanceId()
].count = 0;
}
}
if (handle != null) {
let desc = this.coll2instance.get(handle);
if (!!desc) {
if (desc.instanceId != 0)
// Don't highlight static/kinematic bodies.
desc.highlighted = true;
}
}
this.highlightedCollider = handle;
}
updatePositions(world: RAPIER.World) {
world.forEachCollider((elt) => {
let gfx = this.coll2instance.get(elt.handle);
let translation = elt.translation();
let rotation = elt.rotation();
if (!!gfx) {
let instance = this.instanceGroups[gfx.groupId][gfx.instanceId];
dummy.scale.set(gfx.scale.x, gfx.scale.y, gfx.scale.z);
dummy.position.set(translation.x, translation.y, translation.z);
dummy.quaternion.set(
rotation.x,
rotation.y,
rotation.z,
rotation.w,
);
dummy.updateMatrix();
instance.setMatrixAt(gfx.elementId, dummy.matrix);
let highlightInstance =
this.instanceGroups[gfx.groupId][
this.highlightInstanceId()
];
if (gfx.highlighted) {
highlightInstance.count = 1;
highlightInstance.setMatrixAt(0, dummy.matrix);
}
instance.instanceMatrix.needsUpdate = true;
highlightInstance.instanceMatrix.needsUpdate = true;
}
let mesh = this.coll2mesh.get(elt.handle);
if (!!mesh) {
mesh.position.set(translation.x, translation.y, translation.z);
mesh.quaternion.set(
rotation.x,
rotation.y,
rotation.z,
rotation.w,
);
mesh.updateMatrix();
}
});
}
reset() {
this.instanceGroups.forEach((groups) => {
groups.forEach((instance) => {
instance.userData.elementId2coll = new Map();
instance.count = 0;
});
});
this.coll2mesh.forEach((mesh) => {
this.scene.remove(mesh);
});
this.coll2instance = new Map();
this.rb2colls = new Map();
this.colorIndex = 0;
}
// applyModifications(RAPIER: RAPIER_API, world: RAPIER.World, modifications) {
// if (!!modifications) {
// modifications.addCollider.forEach(coll => {
// let collider = world.getCollider(coll.handle);
// this.addCollider(RAPIER, world, collider);
// });
// modifications.removeRigidBody.forEach(body => {
// if (!!this.rb2colls.get(body.handle)) {
// this.rb2colls.get(body.handle).forEach(coll => this.removeCollider(coll));
// this.rb2colls.delete(body.handle);
// }
// });
// }
// }
removeRigidBody(body: RAPIER.RigidBody) {
if (!!this.rb2colls.get(body.handle)) {
this.rb2colls
.get(body.handle)
.forEach((coll) => this.removeCollider(coll));
this.rb2colls.delete(body.handle);
}
}
removeCollider(collider: RAPIER.Collider) {
let gfx = this.coll2instance.get(collider.handle);
let instance = this.instanceGroups[gfx.groupId][gfx.instanceId];
if (instance.count > 1) {
let coll2 = instance.userData.elementId2coll.get(
instance.count - 1,
);
instance.userData.elementId2coll.delete(instance.count - 1);
instance.userData.elementId2coll.set(gfx.elementId, coll2);
let gfx2 = this.coll2instance.get(coll2.handle);
gfx2.elementId = gfx.elementId;
}
instance.count -= 1;
this.coll2instance.delete(collider.handle);
}
addCollider(
RAPIER: RAPIER_API,
world: RAPIER.World,
collider: RAPIER.Collider,
) {
this.colorIndex =
(this.colorIndex + 1) % (this.colorPalette.length - 2);
let parent = collider.parent();
if (!this.rb2colls.get(parent.handle)) {
this.rb2colls.set(parent.handle, [collider]);
} else {
this.rb2colls.get(parent.handle).push(collider);
}
let instance;
let instanceDesc: InstanceDesc = {
groupId: 0,
instanceId: parent.isFixed() ? 0 : this.colorIndex + 1,
elementId: 0,
highlighted: false,
};
switch (collider.shapeType()) {
case RAPIER.ShapeType.Cuboid:
let hext = collider.halfExtents();
instance =
this.instanceGroups[BOX_INSTANCE_INDEX][
instanceDesc.instanceId
];
instanceDesc.groupId = BOX_INSTANCE_INDEX;
instanceDesc.scale = new THREE.Vector3(hext.x, hext.y, hext.z);
break;
case RAPIER.ShapeType.Ball:
let rad = collider.radius();
instance =
this.instanceGroups[BALL_INSTANCE_INDEX][
instanceDesc.instanceId
];
instanceDesc.groupId = BALL_INSTANCE_INDEX;
instanceDesc.scale = new THREE.Vector3(rad, rad, rad);
break;
case RAPIER.ShapeType.Cylinder:
case RAPIER.ShapeType.RoundCylinder:
let cyl_rad = collider.radius();
let cyl_height = collider.halfHeight() * 2.0;
instance =
this.instanceGroups[CYLINDER_INSTANCE_INDEX][
instanceDesc.instanceId
];
instanceDesc.groupId = CYLINDER_INSTANCE_INDEX;
instanceDesc.scale = new THREE.Vector3(
cyl_rad,
cyl_height,
cyl_rad,
);
break;
case RAPIER.ShapeType.Cone:
let cone_rad = collider.radius();
let cone_height = collider.halfHeight() * 2.0;
instance =
this.instanceGroups[CONE_INSTANCE_INDEX][
instanceDesc.instanceId
];
instanceDesc.groupId = CONE_INSTANCE_INDEX;
instanceDesc.scale = new THREE.Vector3(
cone_rad,
cone_height,
cone_rad,
);
break;
case RAPIER.ShapeType.TriMesh:
case RAPIER.ShapeType.HeightField:
case RAPIER.ShapeType.ConvexPolyhedron:
case RAPIER.ShapeType.RoundConvexPolyhedron:
case RAPIER.ShapeType.Voxels:
let geometry = new THREE.BufferGeometry();
let vertices;
let indices;
if (collider.shapeType() == RAPIER.ShapeType.HeightField) {
let g = genHeightfieldGeometry(collider);
vertices = g.vertices;
indices = g.indices;
} else if (collider.shapeType() == RAPIER.ShapeType.Voxels) {
let g = genVoxelsGeometry(collider);
vertices = g.vertices;
indices = g.indices;
} else {
vertices = collider.vertices();
indices = collider.indices();
}
geometry.setIndex(Array.from(indices));
geometry.setAttribute(
"position",
new THREE.BufferAttribute(vertices, 3),
);
let color = parent.isFixed() ? 0 : this.colorIndex + 1;
let material = new THREE.MeshPhongMaterial({
color: this.colorPalette[color],
side: THREE.DoubleSide,
flatShading: true,
});
let mesh = new THREE.Mesh(geometry, material);
this.scene.add(mesh);
this.coll2mesh.set(collider.handle, mesh);
return;
default:
console.log("Unknown shape to render.");
return;
}
if (!!instance) {
instanceDesc.elementId = instance.count;
instance.userData.elementId2coll.set(instance.count, collider);
instance.count += 1;
}
let highlightInstance =
this.instanceGroups[instanceDesc.groupId][
this.highlightInstanceId()
];
highlightInstance.count = 0;
let t = collider.translation();
let r = collider.rotation();
dummy.position.set(t.x, t.y, t.z);
dummy.quaternion.set(r.x, r.y, r.z, r.w);
dummy.scale.set(
instanceDesc.scale.x,
instanceDesc.scale.y,
instanceDesc.scale.z,
);
dummy.updateMatrix();
instance.setMatrixAt(instanceDesc.elementId, dummy.matrix);
instance.instanceMatrix.needsUpdate = true;
this.coll2instance.set(collider.handle, instanceDesc);
}
}

View File

@@ -0,0 +1,167 @@
import GUI from "lil-gui";
import * as Stats from "stats.js";
import type {Testbed} from "./Testbed";
export interface DebugInfos {
token: number;
stepId: number;
worldHash: string;
worldHashTime: number;
snapshotTime: number;
timingStep: number;
timingCollisionDetection: number;
timingBroadPhase: number;
timingNarrowPhase: number;
timingSolver: number;
timingVelocityAssembly: number;
timingVelocityResolution: number;
timingVelocityUpdate: number;
timingVelocityWriteback: number;
timingCcd: number;
timingCcdToiComputation: number;
timingCcdBroadPhase: number;
timingCcdNarrowPhase: number;
timingCcdSolver: number;
timingIslandConstruction: number;
timingUserChanges: number;
}
export class Gui {
stats: Stats;
rapierVersion: string;
maxTimePanelValue: number;
stepTimePanel: Stats.Panel;
gui: GUI;
debugText: HTMLDivElement;
constructor(testbed: Testbed, simulationParameters: Testbed["parameters"]) {
// Timings
this.stats = new Stats();
this.rapierVersion = testbed.RAPIER.version();
this.maxTimePanelValue = 16.0;
this.stepTimePanel = this.stats.addPanel(
new Stats.Panel("ms (step)", "#ff8", "#221"),
);
this.stats.showPanel(this.stats.dom.children.length - 1);
document.body.appendChild(this.stats.dom);
var backends = simulationParameters.backends;
var demos = Array.from(simulationParameters.builders.keys());
var me = this;
// For configuring simulation parameters.
this.gui = new GUI({
title: "Rapier JS demos",
});
var currDemo = this.gui
.add(simulationParameters, "demo", demos)
.onChange((demo: string) => {
testbed.switchToDemo(demo);
});
this.gui
.add(simulationParameters, "numSolverIters", 0, 20)
.step(1)
.listen();
this.gui
.add(simulationParameters, "debugInfos")
.listen()
.onChange((value: boolean) => {
me.debugText.style.visibility = value ? "visible" : "hidden";
});
this.gui.add(simulationParameters, "debugRender").listen();
this.gui.add(simulationParameters, "running").listen();
this.gui.add(simulationParameters, "step");
simulationParameters.step = () => {
simulationParameters.stepping = true;
};
this.gui.add(simulationParameters, "takeSnapshot");
simulationParameters.takeSnapshot = () => {
testbed.takeSnapshot();
};
this.gui.add(simulationParameters, "restoreSnapshot");
simulationParameters.restoreSnapshot = () => {
testbed.restoreSnapshot();
};
this.gui.add(simulationParameters, "restart");
simulationParameters.restart = () => {
testbed.switchToDemo(currDemo.getValue());
};
/*
* Block of text for debug infos.
*/
this.debugText = document.createElement("div");
this.debugText.style.position = "absolute";
this.debugText.innerHTML = "";
this.debugText.style.top = 50 + "px";
this.debugText.style.visibility = "visible";
this.debugText.style.color = "#fff";
document.body.appendChild(this.debugText);
}
setDebugInfos(infos: DebugInfos) {
let text = "Version " + this.rapierVersion;
text += "<br/>[Step " + infos.stepId + "]";
if (infos.worldHash) {
text +=
"<br/>World hash (xxHash128): " + infos.worldHash.toString();
text +=
"<br/>World hash time (xxHash128): " +
infos.worldHashTime +
"ms";
text += "<br/>Snapshot time: " + infos.snapshotTime + "ms";
}
text += "<br/>timingStep: " + infos.timingStep + "ms";
text +=
"<br/>timingCollisionDetection: " +
infos.timingCollisionDetection +
"ms";
text += "<br/>timingBroadPhase: " + infos.timingBroadPhase + "ms";
text += "<br/>timingNarrowPhase: " + infos.timingNarrowPhase + "ms";
text += "<br/>timingSolver: " + infos.timingSolver + "ms";
text +=
"<br/>timingVelocityAssembly: " +
infos.timingVelocityAssembly +
"ms";
text +=
"<br/>timingVelocityResolution: " +
infos.timingVelocityResolution +
"ms";
text +=
"<br/>timingVelocityUpdate: " + infos.timingVelocityUpdate + "ms";
text +=
"<br/>timingVelocityWriteback: " +
infos.timingVelocityWriteback +
"ms";
text += "<br/>timingCcd: " + infos.timingCcd + "ms";
text +=
"<br/>timingCcdToiComputation: " +
infos.timingCcdToiComputation +
"ms";
text += "<br/>timingCcdBroadPhase: " + infos.timingCcdBroadPhase + "ms";
text +=
"<br/>timingCcdNarrowPhase: " + infos.timingCcdNarrowPhase + "ms";
text += "<br/>timingCcdSolver: " + infos.timingCcdSolver + "ms";
text +=
"<br/>timingIslandConstruction: " +
infos.timingIslandConstruction +
"ms";
text += "<br/>timingUserChanges: " + infos.timingUserChanges + "ms";
this.debugText.innerHTML = text;
}
setTiming(timing: number) {
if (!!timing) {
this.maxTimePanelValue = Math.max(this.maxTimePanelValue, timing);
this.stepTimePanel.update(timing, this.maxTimePanelValue);
}
}
resetTiming() {
this.maxTimePanelValue = 1.0;
this.stepTimePanel.update(0.0, 16.0);
}
}

View File

@@ -0,0 +1,211 @@
import {Graphics} from "./Graphics";
import {Gui} from "./Gui";
import type {DebugInfos} from "./Gui";
import {xxhash128} from "hash-wasm";
import type * as RAPIER from "@dimforge/rapier3d";
type RAPIER_API = typeof import("@dimforge/rapier3d");
type Builders = Map<string, (RAPIER: RAPIER_API, testbed: Testbed) => void>;
class SimulationParameters {
backend: string;
prevBackend: string;
demo: string;
numSolverIters: number;
running: boolean;
stepping: boolean;
debugInfos: boolean;
debugRender: boolean;
step: () => void;
restart: () => void;
takeSnapshot: () => void;
restoreSnapshot: () => void;
backends: Array<string>;
builders: Builders;
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 = () => {};
this.restart = () => {};
this.takeSnapshot = () => {};
this.restoreSnapshot = () => {};
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.onkeydown = null; // Reset key events.
document.onkeyup = null; // Reset key events.
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.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,
timingStep: this.world.timingStep(),
timingCollisionDetection:
this.world.timingCollisionDetection(),
timingBroadPhase: this.world.timingBroadPhase(),
timingNarrowPhase: this.world.timingNarrowPhase(),
timingSolver: this.world.timingSolver(),
timingVelocityAssembly: this.world.timingVelocityAssembly(),
timingVelocityResolution:
this.world.timingVelocityResolution(),
timingVelocityUpdate: this.world.timingVelocityUpdate(),
timingVelocityWriteback:
this.world.timingVelocityWriteback(),
timingCcd: this.world.timingCcd(),
timingCcdToiComputation:
this.world.timingCcdToiComputation(),
timingCcdBroadPhase: this.world.timingCcdBroadPhase(),
timingCcdNarrowPhase: this.world.timingCcdNarrowPhase(),
timingCcdSolver: this.world.timingCcdSolver(),
timingIslandConstruction:
this.world.timingIslandConstruction(),
timingUserChanges: this.world.timingUserChanges(),
};
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());
}
}

View File

@@ -0,0 +1,75 @@
import type RAPIER from "@dimforge/rapier3d";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function createWall(
RAPIER: RAPIER_API,
testbed: Testbed,
world: RAPIER.World,
offset: {x: number; y: number; z: number},
stackHeight: number,
) {
let i, j;
let shiftY = 1.0;
let shiftZ = 2.0;
for (i = 0; i < stackHeight; ++i) {
for (j = i; j < stackHeight; ++j) {
let x = offset.x;
let y = i * shiftY + offset.y;
let z =
(i * shiftZ) / 2.0 + (j - i) * shiftZ + offset.z - stackHeight;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(0.5, 0.5, 1.0);
world.createCollider(colliderDesc, body);
}
}
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let groundHeight = 0.1;
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(30.0, 0.1, 30.0);
world.createCollider(colliderDesc, body);
let numX = 5;
let numZ = 8;
let shiftY = groundHeight + 0.5;
let i;
for (i = 0; i < numX; ++i) {
let x = i * 6.0;
createWall(RAPIER, testbed, world, {x: x, y: shiftY, z: 0.0}, numZ);
}
// A very fast rigid-body with CCD enabled.
// Create dynamic cube.
bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(-20.0, shiftY + 2.0, 0.0)
.setLinvel(1000.0, 0.0, 0.0)
.setCcdEnabled(true);
body = world.createRigidBody(bodyDesc);
colliderDesc = RAPIER.ColliderDesc.ball(1.0).setDensity(10.0);
world.createCollider(colliderDesc, body);
testbed.setWorld(world);
let cameraPosition = {
eye: {x: -31.96000000000001, y: 19.730000000000008, z: -27.86},
target: {x: -0.0505, y: -0.4126, z: -0.0229},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,106 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(15.0, 0.1, 15.0);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let rad = 0.5;
let num = 5;
let i, j, k;
let shift = rad * 2.5;
let center = num * rad;
let height = 5.0;
for (i = 0; i < num; ++i) {
for (j = i; j < num; ++j) {
for (k = i; k < num; ++k) {
let x = (i * shift) / 2.0 + (k - i) * shift - center;
let y = (i * shift) / 2.0 + height;
let z = (i * shift) / 2.0 + (j - i) * shift - center;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad / 2.0,
rad,
);
world.createCollider(colliderDesc, body);
}
}
}
// Character.
let characterDesc =
RAPIER.RigidBodyDesc.kinematicPositionBased().setTranslation(
-10.0,
4.0,
-10.0,
);
let character = world.createRigidBody(characterDesc);
let characterColliderDesc = RAPIER.ColliderDesc.cylinder(1.2, 0.6);
let characterCollider = world.createCollider(
characterColliderDesc,
character,
);
let characterController = world.createCharacterController(0.1);
characterController.enableAutostep(0.7, 0.3, true);
characterController.enableSnapToGround(0.7);
let speed = 0.2;
let movementDirection = {x: 0.0, y: -speed, z: 0.0};
let updateCharacter = () => {
characterController.computeColliderMovement(
characterCollider,
movementDirection,
);
let movement = characterController.computedMovement();
let newPos = character.translation();
newPos.x += movement.x;
newPos.y += movement.y;
newPos.z += movement.z;
character.setNextKinematicTranslation(newPos);
};
testbed.setWorld(world);
testbed.setpreTimestepAction(updateCharacter);
document.onkeydown = function (event: KeyboardEvent) {
if (event.key == "ArrowUp") movementDirection.x = speed;
if (event.key == "ArrowDown") movementDirection.x = -speed;
if (event.key == "ArrowLeft") movementDirection.z = -speed;
if (event.key == "ArrowRight") movementDirection.z = speed;
if (event.key == " ") movementDirection.y = speed;
};
document.onkeyup = function (event: KeyboardEvent) {
if (event.key == "ArrowUp") movementDirection.x = 0.0;
if (event.key == "ArrowDown") movementDirection.x = 0.0;
if (event.key == "ArrowLeft") movementDirection.z = 0.0;
if (event.key == "ArrowRight") movementDirection.z = 0.0;
if (event.key == " ") movementDirection.y = -speed; // Gravity
};
let cameraPosition = {
eye: {x: -40.0, y: 19.730000000000008, z: 0.0},
target: {x: 0.0, y: -0.4126, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,73 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let groundBody = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(5.0, 0.1, 5.0);
world.createCollider(colliderDesc, groundBody);
// Setup groups.
let group1 = 0x00010001;
let group2 = 0x00020002;
// Add one floor that collides with the first group only.
colliderDesc = RAPIER.ColliderDesc.cuboid(1.0, 0.1, 1.0)
.setTranslation(0.0, 1.0, 0.0)
.setCollisionGroups(group1);
world.createCollider(colliderDesc, groundBody);
// Add one floor that collides with the second group only.
colliderDesc = RAPIER.ColliderDesc.cuboid(1.0, 0.1, 1.0)
.setTranslation(0.0, 2.0, 0.0)
.setCollisionGroups(group2);
world.createCollider(colliderDesc, groundBody);
// Dynamic cubes.
let num = 8;
let rad = 0.1;
let shift = rad * 2.0;
let centerx = shift * (num / 2);
let centery = 2.5;
let centerz = shift * (num / 2);
let i, j, k;
for (j = 0; j < 4; j++) {
for (i = 0; i < num; i++) {
for (k = 0; k < num; k++) {
let x = i * shift - centerx;
let y = j * shift + centery;
let z = k * shift - centerz;
// Alternate between the green and blue groups.
let group = k % 2 == 0 ? group1 : group2;
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad,
rad,
).setCollisionGroups(group);
world.createCollider(colliderDesc, body);
}
}
}
testbed.setWorld(world);
let cameraPosition = {
eye: {x: 10.0, y: 5.0, z: 10.0},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,111 @@
import type {Testbed} from "../Testbed";
import seedrandom from "seedrandom";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function generateTriMesh(nsubdivs: number, wx: number, wy: number, wz: number) {
let vertices = [];
let indices = [];
let elementWidth = 1.0 / nsubdivs;
let rng = seedrandom("trimesh");
let i, j;
for (i = 0; i <= nsubdivs; ++i) {
for (j = 0; j <= nsubdivs; ++j) {
let x = (j * elementWidth - 0.5) * wx;
let y = rng() * wy;
let z = (i * elementWidth - 0.5) * wz;
vertices.push(x, y, z);
}
}
for (i = 0; i < nsubdivs; ++i) {
for (j = 0; j < nsubdivs; ++j) {
let i1 = (i + 0) * (nsubdivs + 1) + (j + 0);
let i2 = (i + 0) * (nsubdivs + 1) + (j + 1);
let i3 = (i + 1) * (nsubdivs + 1) + (j + 0);
let i4 = (i + 1) * (nsubdivs + 1) + (j + 1);
indices.push(i1, i3, i2);
indices.push(i3, i4, i2);
}
}
return {
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices),
};
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let trimesh = generateTriMesh(20, 40.0, 4.0, 40.0);
let colliderDesc = RAPIER.ColliderDesc.trimesh(
trimesh.vertices,
trimesh.indices,
);
world.createCollider(colliderDesc, body);
/*
* Create the polyhedra
*/
let num = 5;
let scale = 2.0;
let border_rad = 0.1;
let shift = border_rad * 2.0 + scale;
let centerx = shift * (num / 2);
let centery = shift / 2.0;
let centerz = shift * (num / 2);
let rng = seedrandom("convexPolyhedron");
let i, j, k, l;
for (j = 0; j < 15; ++j) {
for (i = 0; i < num; ++i) {
for (k = 0; k < num; ++k) {
let x = i * shift - centerx;
let y = j * shift + centery + 3.0;
let z = k * shift - centerz;
let vertices = [];
for (l = 0; l < 10; ++l) {
vertices.push(rng() * scale, rng() * scale, rng() * scale);
}
let v = new Float32Array(vertices);
// Build the rigid body.
bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
body = world.createRigidBody(bodyDesc);
colliderDesc = RAPIER.ColliderDesc.roundConvexHull(
v,
border_rad,
);
world.createCollider(colliderDesc, body);
}
}
}
testbed.setWorld(world);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,42 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, 0.0, 0.0);
let world = new RAPIER.World(gravity);
/*
* Create the cubes
*/
let num = 10;
let rad = 0.2;
let subdiv = 1.0 / num;
let i;
for (i = 0; i < num; ++i) {
let x = Math.sin(i * subdiv * Math.PI * 2.0);
let y = Math.cos(i * subdiv * Math.PI * 2.0);
// Build the rigid body.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(x, y, 0.0)
.setLinvel(x * 10.0, y * 10.0, 0.0)
.setAngvel(new RAPIER.Vector3(0.0, 0.0, 100.0))
.setLinearDamping((i + 1) * subdiv * 10.0)
.setAngularDamping((num - i) * subdiv * 10.0);
let body = world.createRigidBody(bodyDesc);
// Build the collider.
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, body);
}
testbed.setWorld(world);
let cameraPosition = {
eye: {x: 0, y: 2.0, z: 20},
target: {x: 0, y: 2.0, z: 0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,78 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
let removableBodies = new Array();
// Create Ground.
let groundBodyDesc = RAPIER.RigidBodyDesc.fixed();
let groundBody = world.createRigidBody(groundBodyDesc);
let groundColliderDesc = RAPIER.ColliderDesc.cuboid(40.0, 0.1, 40.0);
world.createCollider(groundColliderDesc, groundBody);
// Dynamic cubes.
let rad = 1.0;
let j = 0;
let spawn_interval = 5;
let spawnBodies = (graphics: Testbed["graphics"]) => {
j += 1;
if (j % spawn_interval != 0) {
return;
}
let bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setLinvel(0.0, 15.0, 0.0)
.setTranslation(0.0, 10.0, 0.0);
let colliderDesc;
switch ((j / spawn_interval) % 4) {
case 0:
colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
break;
case 1:
colliderDesc = RAPIER.ColliderDesc.ball(rad);
break;
case 2:
colliderDesc = RAPIER.ColliderDesc.roundCylinder(
rad,
rad,
rad / 10.0,
);
break;
case 3:
colliderDesc = RAPIER.ColliderDesc.cone(rad, rad);
break;
}
let body = world.createRigidBody(bodyDesc);
let collider = world.createCollider(colliderDesc, body);
graphics.addCollider(RAPIER, world, collider);
removableBodies.push(body);
// We reached the max number, delete the oldest rigid-body.
if (removableBodies.length > 400) {
let rb = removableBodies[0];
world.removeRigidBody(rb);
graphics.removeRigidBody(rb);
removableBodies.shift();
}
};
testbed.setWorld(world);
testbed.setpreTimestepAction(spawnBodies);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 10.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,68 @@
import type {Testbed} from "../Testbed";
import {Vector3, Object3D, Mesh, BufferGeometry, BufferAttribute} from "three";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
testbed.parameters.debugRender = true;
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let groundBody = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(5.0, 0.1, 5.0);
world.createCollider(colliderDesc, groundBody);
// Adding the 3d model
let loader = new GLTFLoader();
loader.load("./suzanne_blender_monkey.glb", (gltf) => {
gltf.scene.position.set(0, 1.2, 0);
gltf.scene.scale.set(3, 3, 3);
testbed.graphics.scene.add(gltf.scene);
gltf.scene.updateMatrixWorld(true); // ensure world matrix is up to date
gltf.scene.traverse((child: Object3D) => {
if ((child as Mesh).isMesh && (child as Mesh).geometry) {
const mesh = child as Mesh;
const geometry = mesh.geometry as BufferGeometry;
const vertices: number[] = [];
const indices = new Uint32Array(geometry.index!.array); // assume index is non-null
const positionAttribute = geometry.getAttribute(
"position",
) as BufferAttribute;
mesh.updateWorldMatrix(true, true);
const v = new Vector3();
for (let i = 0, l = positionAttribute.count; i < l; i++) {
v.fromBufferAttribute(positionAttribute, i);
v.applyMatrix4(mesh.matrixWorld);
vertices.push(v.x, v.y, v.z);
}
const verticesArray = new Float32Array(vertices);
const rigidBodyDesc = RAPIER.RigidBodyDesc.fixed();
const rigidBody = world.createRigidBody(rigidBodyDesc);
const colliderDesc = RAPIER.ColliderDesc.trimesh(
verticesArray,
indices,
);
world.createCollider(colliderDesc, rigidBody);
}
});
});
testbed.setWorld(world);
let cameraPosition = {
eye: {x: 10.0, y: 5.0, z: 10.0},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,68 @@
import type {Testbed} from "../Testbed";
import {
Vector3,
Object3D,
Mesh,
BufferGeometry,
BufferAttribute,
TriangleStripDrawMode,
} from "three";
import {GLTFLoader} from "three/examples/jsm/loaders/GLTFLoader";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let groundBody = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(5.0, 0.1, 5.0);
world.createCollider(colliderDesc, groundBody);
// Adding the 3d model
let loader = new GLTFLoader();
loader.load("./suzanne_blender_monkey.glb", (gltf) => {
gltf.scene.position.set(0, 1.2, 0);
gltf.scene.scale.set(3, 3, 3);
testbed.graphics.scene.add(gltf.scene);
testbed.parameters.debugRender = true;
gltf.scene.updateMatrixWorld(true); // ensure world matrix is up to date
const v = new Vector3();
const positions: number[] = [];
gltf.scene.traverse((child: Object3D) => {
if ((child as Mesh).isMesh && (child as Mesh).geometry) {
const mesh = child as Mesh;
const geometry = mesh.geometry as BufferGeometry;
const positionAttribute = geometry.getAttribute(
"position",
) as BufferAttribute;
for (let i = 0, l = positionAttribute.count; i < l; i++) {
v.fromBufferAttribute(positionAttribute, i);
v.applyMatrix4(mesh.matrixWorld);
positions.push(v.x, v.y, v.z);
}
}
});
const rigidBodyDesc = RAPIER.RigidBodyDesc.fixed();
const rigidBody = world.createRigidBody(rigidBodyDesc);
const colliderDesc = RAPIER.ColliderDesc.convexHull(
new Float32Array(positions),
);
world.createCollider(colliderDesc, rigidBody);
});
testbed.setWorld(world);
let cameraPosition = {
eye: {x: 10.0, y: 5.0, z: 10.0},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,126 @@
import seedrandom from "seedrandom";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function generateHeightfield(nsubdivs: number) {
let heights = [];
let rng = seedrandom("heightfield");
let i, j;
for (i = 0; i <= nsubdivs; ++i) {
for (j = 0; j <= nsubdivs; ++j) {
heights.push(rng());
}
}
return new Float32Array(heights);
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let nsubdivs = 20;
let scale = new RAPIER.Vector3(70.0, 4.0, 70.0);
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let heights = generateHeightfield(nsubdivs);
let colliderDesc = RAPIER.ColliderDesc.heightfield(
nsubdivs,
nsubdivs,
heights,
scale,
);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let num = 4;
let numy = 10;
let rad = 1.0;
let shift = rad * 2.0 + rad;
let centery = shift / 2.0;
let offset = -num * (rad * 2.0 + rad) * 0.5;
let i, j, k;
for (j = 0; j < numy; ++j) {
for (i = 0; i < num; ++i) {
for (k = 0; k < num; ++k) {
let x = i * shift + offset;
let y = j * shift + centery + 3.0;
let z = k * shift + offset;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc;
switch (j % 5) {
case 0:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad,
rad,
);
break;
case 1:
colliderDesc = RAPIER.ColliderDesc.ball(rad);
break;
case 2:
colliderDesc = RAPIER.ColliderDesc.roundCylinder(
rad,
rad,
rad / 10.0,
);
break;
case 3:
colliderDesc = RAPIER.ColliderDesc.cone(rad, rad);
break;
case 4:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad / 2.0,
rad / 2.0,
);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(rad, 0.0, 0.0);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(-rad, 0.0, 0.0);
break;
}
world.createCollider(colliderDesc, body);
}
}
offset -= 0.05 * rad * (num - 1.0);
}
testbed.setWorld(world);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,292 @@
import type RAPIER from "@dimforge/rapier3d";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function createPrismaticJoints(
RAPIER: RAPIER_API,
world: RAPIER.World,
origin: RAPIER.Vector,
num: number,
) {
let rad = 0.4;
let shift = 1.0;
let groundDesc = RAPIER.RigidBodyDesc.fixed().setTranslation(
origin.x,
origin.y,
origin.z,
);
let currParent = world.createRigidBody(groundDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, currParent);
let i;
let z;
for (i = 0; i < num; ++i) {
z = origin.z + (i + 1) * shift;
let rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
origin.x,
origin.y,
z,
);
let currChild = world.createRigidBody(rigidBodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, currChild);
let axis;
if (i % 2 == 0) {
axis = new RAPIER.Vector3(1.0, 1.0, 0.0);
} else {
axis = new RAPIER.Vector3(-1.0, 1.0, 0.0);
}
z = new RAPIER.Vector3(0.0, 0.0, 1.0);
let prism = RAPIER.JointData.prismatic(
new RAPIER.Vector3(0.0, 0.0, 0.0),
new RAPIER.Vector3(0.0, 0.0, -shift),
axis,
);
prism.limitsEnabled = true;
prism.limits = [-2.0, 2.0];
world.createImpulseJoint(prism, currParent, currChild, true);
currParent = currChild;
}
}
function createRevoluteJoints(
RAPIER: RAPIER_API,
world: RAPIER.World,
origin: RAPIER.Vector3,
num: number,
) {
let rad = 0.4;
let shift = 2.0;
let groundDesc = RAPIER.RigidBodyDesc.fixed().setTranslation(
origin.x,
origin.y,
0.0,
);
let currParent = world.createRigidBody(groundDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, currParent);
let i, k;
let z;
for (i = 0; i < num; ++i) {
// Create four bodies.
z = origin.z + i * shift * 2.0 + shift;
let positions = [
new RAPIER.Vector3(origin.x, origin.y, z),
new RAPIER.Vector3(origin.x + shift, origin.y, z),
new RAPIER.Vector3(origin.x + shift, origin.y, z + shift),
new RAPIER.Vector3(origin.x, origin.y, z + shift),
];
let parents = [currParent, currParent, currParent, currParent];
for (k = 0; k < 4; ++k) {
let rigidBodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
positions[k].x,
positions[k].y,
positions[k].z,
);
let rigidBody = world.createRigidBody(rigidBodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, rigidBody);
parents[k] = rigidBody;
}
// Setup four joints.
let o = new RAPIER.Vector3(0.0, 0.0, 0.0);
let x = new RAPIER.Vector3(1.0, 0.0, 0.0);
z = new RAPIER.Vector3(0.0, 0.0, 1.0);
let revs = [
RAPIER.JointData.revolute(
o,
new RAPIER.Vector3(0.0, 0.0, -shift),
z,
),
RAPIER.JointData.revolute(
o,
new RAPIER.Vector3(-shift, 0.0, 0.0),
x,
),
RAPIER.JointData.revolute(
o,
new RAPIER.Vector3(0.0, 0.0, -shift),
z,
),
RAPIER.JointData.revolute(
o,
new RAPIER.Vector3(shift, 0.0, 0.0),
x,
),
];
world.createImpulseJoint(revs[0], currParent, parents[0], true);
world.createImpulseJoint(revs[1], parents[0], parents[1], true);
world.createImpulseJoint(revs[2], parents[1], parents[2], true);
world.createImpulseJoint(revs[3], parents[2], parents[3], true);
currParent = parents[3];
}
}
function createFixedJoints(
RAPIER: RAPIER_API,
world: RAPIER.World,
origin: RAPIER.Vector3,
num: number,
) {
let rad = 0.4;
let shift = 1.0;
let i, k;
let parents = [];
for (k = 0; k < num; ++k) {
for (i = 0; i < num; ++i) {
let fk = k;
let fi = i;
// NOTE: the num - 2 test is to avoid two consecutive
// fixed bodies. Because physx will crash if we add
// a joint between these.
let bodyType;
if (i == 0 && ((k % 4 == 0 && k != num - 2) || k == num - 1)) {
bodyType = RAPIER.RigidBodyType.Fixed;
} else {
bodyType = RAPIER.RigidBodyType.Dynamic;
}
let rigidBody = new RAPIER.RigidBodyDesc(bodyType).setTranslation(
origin.x + fk * shift,
origin.y,
origin.z + fi * shift,
);
let child = world.createRigidBody(rigidBody);
let colliderDesc = RAPIER.ColliderDesc.ball(rad);
world.createCollider(colliderDesc, child);
// Vertical joint.
if (i > 0) {
let parent = parents[parents.length - 1];
let params = RAPIER.JointData.fixed(
new RAPIER.Vector3(0.0, 0.0, 0.0),
new RAPIER.Quaternion(0.0, 0.0, 0.0, 1.0),
new RAPIER.Vector3(0.0, 0.0, -shift),
new RAPIER.Quaternion(0.0, 0.0, 0.0, 1.0),
);
world.createImpulseJoint(params, parent, child, true);
}
// Horizontal joint.
if (k > 0) {
let parent_index = parents.length - num;
let parent = parents[parent_index];
let params = RAPIER.JointData.fixed(
new RAPIER.Vector3(0.0, 0.0, 0.0),
new RAPIER.Quaternion(0.0, 0.0, 0.0, 1.0),
new RAPIER.Vector3(-shift, 0.0, 0.0),
new RAPIER.Quaternion(0.0, 0.0, 0.0, 1.0),
);
world.createImpulseJoint(params, parent, child, true);
}
parents.push(child);
}
}
}
function createBallJoints(
RAPIER: RAPIER_API,
world: RAPIER.World,
num: number,
) {
let rad = 0.4;
let shift = 1.0;
let i, k;
let parents = [];
for (k = 0; k < num; ++k) {
for (i = 0; i < num; ++i) {
let fk = k;
let fi = i;
let bodyType;
if (i == 0 && (k % 4 == 0 || k == num - 1)) {
bodyType = RAPIER.RigidBodyType.Fixed;
} else {
bodyType = RAPIER.RigidBodyType.Dynamic;
}
let bodyDesc = new RAPIER.RigidBodyDesc(bodyType).setTranslation(
fk * shift,
0.0,
fi * shift,
);
let child = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.ball(rad);
world.createCollider(colliderDesc, child);
// Vertical joint.
let o = new RAPIER.Vector3(0.0, 0.0, 0.0);
if (i > 0) {
let parent = parents[parents.length - 1];
let params = RAPIER.JointData.spherical(
o,
new RAPIER.Vector3(0.0, 0.0, -shift),
);
world.createImpulseJoint(params, parent, child, true);
}
// Horizontal joint.
if (k > 0) {
let parent_index = parents.length - num;
let parent = parents[parent_index];
let params = RAPIER.JointData.spherical(
o,
new RAPIER.Vector3(-shift, 0.0, 0.0),
);
world.createImpulseJoint(params, parent, child, true);
}
parents.push(child);
}
}
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
createPrismaticJoints(
RAPIER,
world,
new RAPIER.Vector3(20.0, 10.0, 0.0),
5,
);
createFixedJoints(RAPIER, world, new RAPIER.Vector3(0.0, 10.0, 0.0), 5);
createRevoluteJoints(RAPIER, world, new RAPIER.Vector3(20.0, 0.0, 0.0), 3);
createBallJoints(RAPIER, world, 15);
testbed.setWorld(world);
let cameraPosition = {
eye: {x: 15.0, y: 5.0, z: 42.0},
target: {x: 13.0, y: 1.0, z: 1.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,139 @@
import type RAPIER from "@dimforge/rapier3d";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function buildBlock(
RAPIER: RAPIER_API,
world: RAPIER.World,
halfExtents: RAPIER.Vector,
shift: RAPIER.Vector,
numx: number,
numy: number,
numz: number,
) {
let half_extents_zyx = {
x: halfExtents.z,
y: halfExtents.y,
z: halfExtents.x,
};
let dimensions = [halfExtents, half_extents_zyx];
let blockWidth = 2.0 * halfExtents.z * numx;
let blockHeight = 2.0 * halfExtents.y * numy;
let spacing = (halfExtents.z * numx - halfExtents.x) / (numz - 1.0);
let i;
let j;
let k;
for (i = 0; i < numy; ++i) {
[numx, numz] = [numz, numx];
let dim = dimensions[i % 2];
let y = dim.y * i * 2.0;
for (j = 0; j < numx; ++j) {
let x = i % 2 == 0 ? spacing * j * 2.0 : dim.x * j * 2.0;
for (k = 0; k < numz; ++k) {
let z = i % 2 == 0 ? dim.z * k * 2.0 : spacing * k * 2.0;
// Build the rigid body.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x + dim.x + shift.x,
y + dim.y + shift.y,
z + dim.z + shift.z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(
dim.x,
dim.y,
dim.z,
);
world.createCollider(colliderDesc, body);
}
}
}
// Close the top.
let dim = {x: halfExtents.z, y: halfExtents.x, z: halfExtents.y};
for (i = 0; i < blockWidth / (dim.x * 2.0); ++i) {
for (j = 0; j < blockWidth / (dim.z * 2.0); ++j) {
// Build the rigid body.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
i * dim.x * 2.0 + dim.x + shift.x,
dim.y + shift.y + blockHeight,
j * dim.z * 2.0 + dim.z + shift.z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(dim.x, dim.y, dim.z);
world.createCollider(colliderDesc, body);
}
}
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let groundSize = 50.0;
let groundHeight = 0.1;
let bodyDesc = RAPIER.RigidBodyDesc.fixed().setTranslation(
0.0,
-groundHeight,
0.0,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(
groundSize,
groundHeight,
groundSize,
);
world.createCollider(colliderDesc, body);
// Keva tower.
let halfExtents = new RAPIER.Vector3(0.1, 0.5, 2.0);
let blockHeight = 0.0;
// These should only be set to odd values otherwise
// the blocks won't align in the nicest way.
let numyArr = [0, 3, 5, 5, 7, 9];
let numBlocksBuilt = 0;
let i;
for (i = 5; i >= 1; --i) {
let numx = i;
let numy = numyArr[i];
let numz = numx * 3 + 1;
let blockWidth = numx * halfExtents.z * 2.0;
buildBlock(
RAPIER,
world,
halfExtents,
new RAPIER.Vector3(
-blockWidth / 2.0,
blockHeight,
-blockWidth / 2.0,
),
numx,
numy,
numz,
);
blockHeight += numy * halfExtents.y * 2.0 + halfExtents.x * 2.0;
numBlocksBuilt += numx * numy * numz;
}
testbed.setWorld(world);
let cameraPosition = {
eye: {
x: -70.38553832116718,
y: 17.893810295517365,
z: 29.34767842147597,
},
target: {
x: 0.5890869353464383,
y: 3.132044603021203,
z: -0.2899937806661885,
},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,58 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
/*
* The ground
*/
let ground_size = 1.7;
let ground_height = 0.1;
let bodyDesc = RAPIER.RigidBodyDesc.fixed().setTranslation(
0.0,
-ground_height,
0.0,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(
ground_size,
ground_height,
ground_size,
);
world.createCollider(colliderDesc, body);
/*
* A rectangle that only rotates along the `x` axis.
*/
bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(0.0, 3.0, 0.0)
.lockTranslations()
.enabledRotations(true, false, false);
body = world.createRigidBody(bodyDesc);
colliderDesc = RAPIER.ColliderDesc.cuboid(0.2, 0.6, 2.0);
world.createCollider(colliderDesc, body);
/*
* A cylinder that cannot rotate.
*/
bodyDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(0.2, 5.0, 0.4)
.lockRotations();
body = world.createRigidBody(bodyDesc);
colliderDesc = RAPIER.ColliderDesc.cylinder(0.6, 0.4);
world.createCollider(colliderDesc, body);
/*
* Setup the testbed.
*/
testbed.setWorld(world);
let cameraPosition = {
eye: {x: -10.0, y: 3.0, z: 0.0},
target: {x: 0.0, y: 3.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,127 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(15.0, 0.1, 15.0);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let rad = 0.5;
let num = 5;
let i, j, k;
let shift = rad * 2.5;
let center = num * rad;
let height = 5.0;
for (i = 0; i < num; ++i) {
for (j = i; j < num; ++j) {
for (k = i; k < num; ++k) {
let x = (i * shift) / 2.0 + (k - i) * shift - center;
let y = (i * shift) / 2.0 + height;
let z = (i * shift) / 2.0 + (j - i) * shift - center;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad / 2.0,
rad,
);
world.createCollider(colliderDesc, body);
}
}
}
// Character.
let characterDesc = RAPIER.RigidBodyDesc.dynamic()
.setTranslation(-10.0, 4.0, -10.0)
.setGravityScale(10.0)
.setSoftCcdPrediction(10.0);
let character = world.createRigidBody(characterDesc);
let characterColliderDesc = RAPIER.ColliderDesc.cylinder(1.2, 0.6);
world.createCollider(characterColliderDesc, character);
let pidController = world.createPidController(
60.0,
0.0,
1.0,
RAPIER.PidAxesMask.AllAng,
);
let speed = 0.2;
let movementDirection = {x: 0.0, y: 0.0, z: 0.0};
let targetVelocity = {x: 0.0, y: 0.0, z: 0.0};
let targetRotation = new RAPIER.Quaternion(0.0, 0.0, 0.0, 1.0);
let updateCharacter = () => {
if (
movementDirection.x == 0.0 &&
movementDirection.y == 0.0 &&
movementDirection.z == 0.0
) {
// Only adjust the rotation, but let translation.
pidController.setAxes(RAPIER.PidAxesMask.AllAng);
} else if (movementDirection.y == 0.0) {
// Dont control the linear Y axis so the player can fall down due to gravity.
pidController.setAxes(
RAPIER.PidAxesMask.AllAng |
RAPIER.PidAxesMask.LinX |
RAPIER.PidAxesMask.LinZ,
);
} else {
pidController.setAxes(RAPIER.PidAxesMask.All);
}
let targetPoint = character.translation();
targetPoint.x += movementDirection.x;
targetPoint.y += movementDirection.y;
targetPoint.z += movementDirection.z;
pidController.applyLinearCorrection(
character,
targetPoint,
targetVelocity,
);
pidController.applyAngularCorrection(
character,
targetRotation,
targetVelocity,
);
};
testbed.setWorld(world);
testbed.setpreTimestepAction(updateCharacter);
document.onkeydown = function (event: KeyboardEvent) {
if (event.key == "ArrowUp") movementDirection.x = speed;
if (event.key == "ArrowDown") movementDirection.x = -speed;
if (event.key == "ArrowLeft") movementDirection.z = -speed;
if (event.key == "ArrowRight") movementDirection.z = speed;
if (event.key == " ") movementDirection.y = speed;
};
document.onkeyup = function (event: KeyboardEvent) {
if (event.key == "ArrowUp") movementDirection.x = 0.0;
if (event.key == "ArrowDown") movementDirection.x = 0.0;
if (event.key == "ArrowLeft") movementDirection.z = 0.0;
if (event.key == "ArrowRight") movementDirection.z = 0.0;
if (event.key == " ") movementDirection.y = 0.0;
};
let cameraPosition = {
eye: {x: -40.0, y: 19.730000000000008, z: 0.0},
target: {x: 0.0, y: -0.4126, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,153 @@
import seedrandom from "seedrandom";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function generateTriMesh(nsubdivs: number, wx: number, wy: number, wz: number) {
let vertices = [];
let indices = [];
let elementWidth = 1.0 / nsubdivs;
let rng = seedrandom("trimesh");
let i, j;
for (i = 0; i <= nsubdivs; ++i) {
for (j = 0; j <= nsubdivs; ++j) {
let x = (j * elementWidth - 0.5) * wx;
let y = rng() * wy;
let z = (i * elementWidth - 0.5) * wz;
vertices.push(x, y, z);
}
}
for (i = 0; i < nsubdivs; ++i) {
for (j = 0; j < nsubdivs; ++j) {
let i1 = (i + 0) * (nsubdivs + 1) + (j + 0);
let i2 = (i + 0) * (nsubdivs + 1) + (j + 1);
let i3 = (i + 1) * (nsubdivs + 1) + (j + 0);
let i4 = (i + 1) * (nsubdivs + 1) + (j + 1);
indices.push(i1, i3, i2);
indices.push(i3, i4, i2);
}
}
return {
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices),
};
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.kinematicVelocityBased();
let platformBody = world.createRigidBody(bodyDesc);
let trimesh = generateTriMesh(20, 70.0, 4.0, 70.0);
let colliderDesc = RAPIER.ColliderDesc.trimesh(
trimesh.vertices,
trimesh.indices,
);
world.createCollider(colliderDesc, platformBody);
let t = 0.0;
let movePlatform = () => {
t += 0.016;
let dy = Math.sin(t) * 10.0;
let dang = Math.sin(t) * 0.2;
platformBody.setLinvel({x: 0.0, y: dy, z: 0.0}, true);
platformBody.setAngvel({x: 0.0, y: dang, z: 0.0}, true);
};
// Dynamic cubes.
let num = 4;
let numy = 10;
let rad = 1.0;
let shift = rad * 2.0 + rad;
let centery = shift / 2.0;
let offset = -num * (rad * 2.0 + rad) * 0.5;
let i, j, k;
for (j = 0; j < numy; ++j) {
for (i = 0; i < num; ++i) {
for (k = 0; k < num; ++k) {
let x = i * shift + offset;
let y = j * shift + centery + 3.0;
let z = k * shift + offset;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc;
switch (j % 5) {
case 0:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad,
rad,
);
break;
case 1:
colliderDesc = RAPIER.ColliderDesc.ball(rad);
break;
case 2:
colliderDesc = RAPIER.ColliderDesc.roundCylinder(
rad,
rad,
rad / 10.0,
);
break;
case 3:
colliderDesc = RAPIER.ColliderDesc.cone(rad, rad);
break;
case 4:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad / 2.0,
rad / 2.0,
);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(rad, 0.0, 0.0);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(-rad, 0.0, 0.0);
break;
}
world.createCollider(colliderDesc, body);
}
}
offset -= 0.05 * rad * (num - 1.0);
}
testbed.setWorld(world);
testbed.setpreTimestepAction(movePlatform);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,51 @@
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(30.0, 0.1, 30.0);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let rad = 0.5;
let num = 10;
let i, j, k;
let shift = rad * 2.5;
let center = num * rad;
let height = 10.0;
for (i = 0; i < num; ++i) {
for (j = i; j < num; ++j) {
for (k = i; k < num; ++k) {
let x =
(i * shift) / 2.0 + (k - i) * shift - height * rad - center;
let y = i * shift + height;
let z =
(i * shift) / 2.0 + (j - i) * shift - height * rad - center;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc = RAPIER.ColliderDesc.cuboid(rad, rad, rad);
world.createCollider(colliderDesc, body);
}
}
}
testbed.setWorld(world);
let cameraPosition = {
eye: {x: -31.96000000000001, y: 19.730000000000008, z: -27.86},
target: {x: -0.0505, y: -0.4126, z: -0.0229},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,143 @@
import seedrandom from "seedrandom";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function generateTriMesh(nsubdivs: number, wx: number, wy: number, wz: number) {
let vertices = [];
let indices = [];
let elementWidth = 1.0 / nsubdivs;
let rng = seedrandom("trimesh");
let i, j;
for (i = 0; i <= nsubdivs; ++i) {
for (j = 0; j <= nsubdivs; ++j) {
let x = (j * elementWidth - 0.5) * wx;
let y = rng() * wy;
let z = (i * elementWidth - 0.5) * wz;
vertices.push(x, y, z);
}
}
for (i = 0; i < nsubdivs; ++i) {
for (j = 0; j < nsubdivs; ++j) {
let i1 = (i + 0) * (nsubdivs + 1) + (j + 0);
let i2 = (i + 0) * (nsubdivs + 1) + (j + 1);
let i3 = (i + 1) * (nsubdivs + 1) + (j + 0);
let i4 = (i + 1) * (nsubdivs + 1) + (j + 1);
indices.push(i1, i3, i2);
indices.push(i3, i4, i2);
}
}
return {
vertices: new Float32Array(vertices),
indices: new Uint32Array(indices),
};
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let trimesh = generateTriMesh(20, 70.0, 4.0, 70.0);
let colliderDesc = RAPIER.ColliderDesc.trimesh(
trimesh.vertices,
trimesh.indices,
);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let num = 4;
let numy = 10;
let rad = 1.0;
let shift = rad * 2.0 + rad;
let centery = shift / 2.0;
let offset = -num * (rad * 2.0 + rad) * 0.5;
let i, j, k;
for (j = 0; j < numy; ++j) {
for (i = 0; i < num; ++i) {
for (k = 0; k < num; ++k) {
let x = i * shift + offset;
let y = j * shift + centery + 3.0;
let z = k * shift + offset;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc;
switch (j % 5) {
case 0:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad,
rad,
);
break;
case 1:
colliderDesc = RAPIER.ColliderDesc.ball(rad);
break;
case 2:
colliderDesc = RAPIER.ColliderDesc.roundCylinder(
rad,
rad,
rad / 10.0,
);
break;
case 3:
colliderDesc = RAPIER.ColliderDesc.cone(rad, rad);
break;
case 4:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad / 2.0,
rad / 2.0,
);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(rad, 0.0, 0.0);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(-rad, 0.0, 0.0);
break;
}
world.createCollider(colliderDesc, body);
}
}
offset -= 0.05 * rad * (num - 1.0);
}
testbed.setWorld(world);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,130 @@
import seedrandom from "seedrandom";
import type {Testbed} from "../Testbed";
type RAPIER_API = typeof import("@dimforge/rapier3d");
function generateVoxels(n: number) {
let points = [];
let i, j;
for (i = 0; i <= n; ++i) {
for (j = 0; j <= n; ++j) {
let y =
Math.max(
-0.8,
Math.min(
Math.sin((i / n) * 10.0) * Math.cos((j / n) * 10.0),
0.8,
),
) * 8.0;
points.push(i - n / 2.0, y, j - n / 2.0);
}
}
return {
points: new Float32Array(points),
voxelSize: {x: 1.0, y: 1.2, z: 1.5},
};
}
export function initWorld(RAPIER: RAPIER_API, testbed: Testbed) {
let gravity = new RAPIER.Vector3(0.0, -9.81, 0.0);
let world = new RAPIER.World(gravity);
// Create Ground.
let bodyDesc = RAPIER.RigidBodyDesc.fixed();
let body = world.createRigidBody(bodyDesc);
let voxels = generateVoxels(100);
let colliderDesc = RAPIER.ColliderDesc.voxels(
voxels.points,
voxels.voxelSize,
);
world.createCollider(colliderDesc, body);
// Dynamic cubes.
let num = 10;
let numy = 4;
let rad = 1.0;
let shift = rad * 2.0 + rad;
let centery = shift / 2.0;
let offset = -num * (rad * 2.0 + rad) * 0.5;
let i, j, k;
for (j = 0; j < numy; ++j) {
for (i = 0; i < num; ++i) {
for (k = 0; k < num; ++k) {
let x = i * shift + offset;
let y = j * shift + centery + 10.0;
let z = k * shift + offset;
// Create dynamic cube.
let bodyDesc = RAPIER.RigidBodyDesc.dynamic().setTranslation(
x,
y,
z,
);
let body = world.createRigidBody(bodyDesc);
let colliderDesc;
switch (j % 5) {
case 0:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad,
rad,
rad,
);
break;
case 1:
colliderDesc = RAPIER.ColliderDesc.ball(rad);
break;
case 2:
colliderDesc = RAPIER.ColliderDesc.roundCylinder(
rad,
rad,
rad / 10.0,
);
break;
case 3:
colliderDesc = RAPIER.ColliderDesc.cone(rad, rad);
break;
case 4:
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad / 2.0,
rad / 2.0,
);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(rad, 0.0, 0.0);
world.createCollider(colliderDesc, body);
colliderDesc = RAPIER.ColliderDesc.cuboid(
rad / 2.0,
rad,
rad / 2.0,
).setTranslation(-rad, 0.0, 0.0);
break;
}
world.createCollider(colliderDesc, body);
}
}
offset -= 0.05 * rad * (num - 1.0);
}
testbed.setWorld(world);
let cameraPosition = {
eye: {
x: -88.48024008669711,
y: 46.911325612198354,
z: 83.56055570254844,
},
target: {x: 0.0, y: 0.0, z: 0.0},
};
testbed.lookAt(cameraPosition);
}

View File

@@ -0,0 +1,42 @@
import {Testbed} from "./Testbed";
import * as Trimesh from "./demos/trimesh";
import * as Voxels from "./demos/voxels";
import * as CollisionGroups from "./demos/collisionGroups";
import * as Pyramid from "./demos/pyramid";
import * as Keva from "./demos/keva";
import * as Joints from "./demos/joints";
import * as Fountain from "./demos/fountain";
import * as Damping from "./demos/damping";
import * as Heightfield from "./demos/heightfield";
import * as LockedRotations from "./demos/lockedRotations";
import * as ConvexPolyhedron from "./demos/convexPolyhedron";
import * as CCD from "./demos/ccd";
import * as Platform from "./demos/platform";
import * as CharacterController from "./demos/characterController";
import * as PidController from "./demos/pidController";
import * as glbToTrimesh from "./demos/glbToTrimesh";
import * as glbToConvexHull from "./demos/glbtoConvexHull";
import("@dimforge/rapier3d").then((RAPIER) => {
let builders = new Map([
["collision groups", CollisionGroups.initWorld],
["character controller", CharacterController.initWorld],
["convex polyhedron", ConvexPolyhedron.initWorld],
["CCD", CCD.initWorld],
["damping", Damping.initWorld],
["fountain", Fountain.initWorld],
["heightfield", Heightfield.initWorld],
["joints", Joints.initWorld],
["keva tower", Keva.initWorld],
["locked rotations", LockedRotations.initWorld],
["pid controller", PidController.initWorld],
["platform", Platform.initWorld],
["pyramid", Pyramid.initWorld],
["triangle mesh", Trimesh.initWorld],
["voxels", Voxels.initWorld],
["GLTF to convexHull", glbToConvexHull.initWorld],
["GLTF to trimesh", glbToTrimesh.initWorld],
]);
let testbed = new Testbed(RAPIER, builders);
testbed.run();
});

View File

@@ -0,0 +1,3 @@
RewriteEngine On
RewriteCond %{SERVER_PORT} 80
RewriteRule ^(.*)$ https://www.rapier.rs/$1 [R,L]

View File

@@ -0,0 +1,18 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>Rapier3D JS bindings demo</title>
<style>
body {
margin: 0;
}
canvas {
display: block;
}
</style>
</head>
<body>
<script src="index.js"></script>
</body>
</html>

Binary file not shown.

View File

@@ -0,0 +1,11 @@
{
"compilerOptions": {
"outDir": "./dist/",
"noImplicitAny": true,
"module": "es2022",
"target": "es2022",
"allowJs": true,
"moduleResolution": "node",
"allowSyntheticDefaultImports": true
}
}

View File

@@ -0,0 +1,55 @@
const webpack = require("webpack");
const path = require("path");
const CopyPlugin = require("copy-webpack-plugin");
const isDev = process.env.NODE_ENV === "development";
const dist = path.resolve(__dirname, "dist");
/**
* @type {import('webpack-dev-server').Configuration}
*/
const devServer = {
static: {
directory: dist,
},
allowedHosts: "all",
compress: true,
};
/**
* @type {import('webpack').Configuration}
*/
const webpackConfig = {
mode: isDev ? "development" : "production",
entry: "./src/index.ts",
devtool: "inline-source-map",
output: {
path: dist,
filename: "index.js",
},
resolve: {
extensions: [".ts", ".js"],
},
devServer,
performance: false,
experiments: {
asyncWebAssembly: true,
syncWebAssembly: true,
},
module: {
rules: [
{
test: /\.tsx?$/,
use: "ts-loader",
exclude: /node_modules/,
},
],
},
plugins: [
new CopyPlugin({
patterns: [{from: "static", to: dist}],
}),
],
};
module.exports = webpackConfig;