Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic physics implementation for OMI_collider and OMI_physics_body #9

Draft
wants to merge 2 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions examples/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ <h3>Examples:</h3>
<li>
<a href="./audio-emitter.html" target="iframe"> Audio Emitter </a>
</li>
<li>
<a href="./physics.html" target="iframe"> Physics </a>
</li>
<li>
<a href="./outdoor-festival.html" target="iframe"> Outdoor Festival </a>
</li>
Expand Down
13 changes: 13 additions & 0 deletions examples/physics.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>three-omi | Examples: Physics</title>
<link rel="stylesheet" href="./styles.css" />
</head>
<body>
<canvas id="canvas"></canvas>
<script src="./src/physics.ts" type="module"></script>
</body>
</html>
Binary file added examples/public/cubemaps/venice_sunset_1k.hdr
Binary file not shown.
Binary file added examples/public/models/pda.glb
Binary file not shown.
162 changes: 162 additions & 0 deletions examples/src/physics.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
import {
Scene,
PerspectiveCamera,
WebGLRenderer,
ACESFilmicToneMapping,
sRGBEncoding,
Clock,
EquirectangularReflectionMapping,
Mesh,
PlaneBufferGeometry,
MeshBasicMaterial,
Vector3,
Quaternion,
Object3D,
BoxBufferGeometry,
MeshStandardMaterial
} from "three";
import { RGBELoader } from "three/examples/jsm/loaders/RGBELoader";
import { OrbitControls } from "three/examples/jsm/controls/OrbitControls";
import { GLTFLoader } from "three/examples/jsm/loaders/GLTFLoader";
import * as Rapier from "@dimforge/rapier3d-compat";

interface GameObject {
object3d: Object3D,
rigidBody: Rapier.RigidBody,
helper: Object3D
}

async function createMeshObject(physicsWorld: Rapier.World, scene: Scene): Promise<GameObject> {
const gltfLoader = new GLTFLoader();
const { scene: mesh } = await gltfLoader
.loadAsync(new URL("/models/pda.glb", import.meta.url).href);

mesh.updateMatrixWorld();

const trimesh = mesh.getObjectByName("physicsBody") as Mesh;
trimesh.visible = false;
const trimeshGeometry = trimesh.geometry;

const indices = trimeshGeometry.getIndex().clone().array as Uint32Array;
const vertices = trimeshGeometry.getAttribute("position").clone().array as Float32Array;

mesh.position.y = 2;
mesh.rotation.y = Math.PI;
scene.add(mesh);

const rigidBodyDesc = Rapier.RigidBodyDesc.newDynamic();
rigidBodyDesc.mass = 1;
rigidBodyDesc.setTranslation(mesh.position.x, mesh.position.y, mesh.position.z);
rigidBodyDesc.setRotation(mesh.quaternion);

const rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);

const trimeshColliderDesc = Rapier.ColliderDesc.trimesh(vertices, indices);
physicsWorld.createCollider(trimeshColliderDesc, rigidBody.handle);

const helper = new Mesh(trimeshGeometry, new MeshBasicMaterial({ color: 0xFFFF00, wireframe: true }));
scene.add(helper);

return {
object3d: mesh,
rigidBody,
helper
};
}

function createCubeObject(physicsWorld: Rapier.World, scene: Scene): GameObject {
const cube = new Mesh(new BoxBufferGeometry(0.5, 0.5, 0.5), new MeshStandardMaterial());
cube.position.y = 3;
scene.add(cube);

const rigidBodyDesc = Rapier.RigidBodyDesc.newDynamic();
rigidBodyDesc.mass = 1;
rigidBodyDesc.setTranslation(cube.position.x, cube.position.y, cube.position.z);
rigidBodyDesc.setRotation(cube.quaternion);

const rigidBody = physicsWorld.createRigidBody(rigidBodyDesc);

const colliderDesc = Rapier.ColliderDesc.cuboid(0.25, 0.25, 0.25);
physicsWorld.createCollider(colliderDesc, rigidBody.handle);

const helper = new Mesh(cube.geometry, new MeshBasicMaterial({ color: 0xFFFF00, wireframe: true }));
scene.add(helper);

return {
object3d: cube,
rigidBody,
helper
};
}

async function main() {
await Rapier.init();

const physicsWorld = new Rapier.World(new Rapier.Vector3(0, -9.8, 0));

const rgbeLoader = new RGBELoader();
const envMap = await rgbeLoader.loadAsync(new URL("/cubemaps/venice_sunset_1k.hdr", import.meta.url).href);
envMap.mapping = EquirectangularReflectionMapping;

const canvas = document.getElementById("canvas");
const scene = new Scene();
scene.environment = envMap;
scene.background = envMap;
const camera = new PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.01, 1000);
camera.position.set(-5, 3, 15);
camera.zoom = 5.8;
scene.add(camera);

new OrbitControls(camera, canvas);

const gameObjects: GameObject[] = [];

const mesh = await createMeshObject(physicsWorld, scene);
gameObjects.push(mesh);

const cube = createCubeObject(physicsWorld, scene);
gameObjects.push(cube);

const plane = new Mesh(new PlaneBufferGeometry(10, 10, 1, 1,).rotateX(-Math.PI / 2), new MeshBasicMaterial({ color: 0xffffff }));
scene.add(plane);

const groundColliderDesc = Rapier.ColliderDesc.cuboid(5, 0.05, 5);
groundColliderDesc.setTranslation(0, -0.05, 0);
physicsWorld.createCollider(groundColliderDesc);

const clock = new Clock();

const renderer = new WebGLRenderer({ antialias: true, canvas });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.physicallyCorrectLights = true;
renderer.toneMapping = ACESFilmicToneMapping;
renderer.toneMappingExposure = 1;
renderer.outputEncoding = sRGBEncoding;
renderer.setAnimationLoop(() => {
const dt = clock.getDelta();
physicsWorld.timestep = dt;
physicsWorld.step();

for (let i = 0; i < gameObjects.length; i++) {
const { object3d, rigidBody, helper } = gameObjects[i];
object3d.position.copy(rigidBody.translation() as Vector3);
object3d.quaternion.copy(rigidBody.rotation() as Quaternion);
helper.position.copy(object3d.position);
helper.quaternion.copy(object3d.quaternion);
}

renderer.render(scene, camera);
});

function onResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('resize', onResize);
onResize();
}

main().catch(console.error);
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,15 @@
"preview-site": "vite preview"
},
"devDependencies": {
"@dimforge/rapier3d-compat": "^0.7.6",
"@types/three": "^0.137.0",
"simple-dropzone": "^0.8.1",
"three": "^0.137.0",
"typescript": "^4.5.5",
"vite": "^2.4.4"
},
"peerDependencies": {
"@dimforge/rapier3d-compat": ">= 0.7.6",
"three": ">= 0.127.0"
}
}