feat: 添加跨平台运行时、资产系统和UI适配功能 (#256)

* feat(platform-common): 添加WASM加载器和环境检测API

* feat(rapier2d): 新增Rapier2D WASM绑定包

* feat(physics-rapier2d): 添加跨平台WASM加载器

* feat(asset-system): 添加运行时资产目录和bundle格式

* feat(asset-system-editor): 新增编辑器资产管理包

* feat(editor-core): 添加构建系统和模块管理

* feat(editor-app): 重构浏览器预览使用import maps

* feat(platform-web): 添加BrowserRuntime和资产读取

* feat(engine): 添加材质系统和着色器管理

* feat(material): 新增材质系统和着色器编辑器

* feat(tilemap): 增强tilemap编辑器和动画系统

* feat(modules): 添加module.json配置

* feat(core): 添加module.json和类型定义更新

* chore: 更新依赖和构建配置

* refactor(plugins): 更新插件模板使用ModuleManifest

* chore: 添加第三方依赖库

* chore: 移除BehaviourTree-ai和ecs-astar子模块

* docs: 更新README和文档主题样式

* fix: 修复Rust文档测试和添加rapier2d WASM绑定

* fix(tilemap-editor): 修复画布高DPI屏幕分辨率适配问题

* feat(ui): 添加UI屏幕适配系统(CanvasScaler/SafeArea)

* fix(ecs-engine-bindgen): 添加缺失的ecs-framework-math依赖

* fix: 添加缺失的包依赖修复CI构建

* fix: 修复CodeQL检测到的代码问题

* fix: 修复构建错误和缺失依赖

* fix: 修复类型检查错误

* fix(material-system): 修复tsconfig配置支持TypeScript项目引用

* fix(editor-core): 修复Rollup构建配置添加tauri external

* fix: 修复CodeQL检测到的代码问题

* fix: 修复CodeQL检测到的代码问题
This commit is contained in:
YHH
2025-12-03 22:15:22 +08:00
committed by GitHub
parent caf7622aa0
commit 63f006ab62
496 changed files with 77601 additions and 4067 deletions

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);
}