From 262c307271f350ce8a5379c39cc0b33999d434ee Mon Sep 17 00:00:00 2001 From: Captain Cool <69457423+Capta1nCool@users.noreply.github.com> Date: Tue, 23 Jul 2024 13:47:13 +0530 Subject: [PATCH] Add GLTF to Convex Hull Collision and Trimesh Examples (#285) Co-authored-by: Thierry Berger --- testbed3d/src/demos/glbToTrimesh.ts | 68 ++++++++++++++++++++ testbed3d/src/demos/glbtoConvexHull.ts | 68 ++++++++++++++++++++ testbed3d/src/index.ts | 4 ++ testbed3d/static/suzanne_blender_monkey.glb | Bin 0 -> 60820 bytes 4 files changed, 140 insertions(+) create mode 100644 testbed3d/src/demos/glbToTrimesh.ts create mode 100644 testbed3d/src/demos/glbtoConvexHull.ts create mode 100644 testbed3d/static/suzanne_blender_monkey.glb diff --git a/testbed3d/src/demos/glbToTrimesh.ts b/testbed3d/src/demos/glbToTrimesh.ts new file mode 100644 index 00000000..fa65a7cd --- /dev/null +++ b/testbed3d/src/demos/glbToTrimesh.ts @@ -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); +} diff --git a/testbed3d/src/demos/glbtoConvexHull.ts b/testbed3d/src/demos/glbtoConvexHull.ts new file mode 100644 index 00000000..a4d2619e --- /dev/null +++ b/testbed3d/src/demos/glbtoConvexHull.ts @@ -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); +} diff --git a/testbed3d/src/index.ts b/testbed3d/src/index.ts index 6d00175f..d72a0528 100644 --- a/testbed3d/src/index.ts +++ b/testbed3d/src/index.ts @@ -12,6 +12,8 @@ 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 glbToTrimesh from "./demos/glbToTrimesh"; +import * as glbToConvexHull from "./demos/glbtoConvexHull"; import("@dimforge/rapier3d").then((RAPIER) => { let builders = new Map([ @@ -28,6 +30,8 @@ import("@dimforge/rapier3d").then((RAPIER) => { ["platform", Platform.initWorld], ["pyramid", Pyramid.initWorld], ["triangle mesh", Trimesh.initWorld], + ["GLTF to convexHull", glbToConvexHull.initWorld], + ["GLTF to trimesh", glbToTrimesh.initWorld], ]); let testbed = new Testbed(RAPIER, builders); testbed.run(); diff --git a/testbed3d/static/suzanne_blender_monkey.glb b/testbed3d/static/suzanne_blender_monkey.glb new file mode 100644 index 0000000000000000000000000000000000000000..6b8819b0e9103c3192b0ec5991642c3900f09231 GIT binary patch literal 60820 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