Parcourir la source

Added Jolt physics example (#28023)

* Examples: Added JoltPhysics.js example.

* JoltPhysics.js: Added restitution.

* Examples: Updated screenshot.

* JoltPhysics: Improve convex radius for box shapes.

---------

Co-authored-by: Mugen87 <[email protected]>
mrdoob il y a 1 an
Parent
commit
01ee1a73c0

+ 1 - 0
examples/files.json

@@ -434,6 +434,7 @@
 		"physics_ammo_rope",
 		"physics_ammo_rope",
 		"physics_ammo_terrain",
 		"physics_ammo_terrain",
 		"physics_ammo_volume",
 		"physics_ammo_volume",
+		"physics_jolt_instancing",
 		"physics_rapier_instancing"
 		"physics_rapier_instancing"
 	],
 	],
 	"misc": [
 	"misc": [

+ 281 - 0
examples/jsm/physics/JoltPhysics.js

@@ -0,0 +1,281 @@
+import { Clock, Vector3, Quaternion, Matrix4 } from 'three';
+
+const JOLT_PATH = 'https://cdn.jsdelivr.net/npm/[email protected]/dist/jolt-physics.wasm-compat.js';
+
+const frameRate = 60;
+
+let Jolt = null;
+
+function getShape( geometry ) {
+
+	const parameters = geometry.parameters;
+
+	// TODO change type to is*
+
+	if ( geometry.type === 'BoxGeometry' ) {
+
+		const sx = parameters.width !== undefined ? parameters.width / 2 : 0.5;
+		const sy = parameters.height !== undefined ? parameters.height / 2 : 0.5;
+		const sz = parameters.depth !== undefined ? parameters.depth / 2 : 0.5;
+
+		return new Jolt.BoxShape( new Jolt.Vec3( sx, sy, sz ), 0.05 * Math.min( sx, sy, sz ), null );
+
+	} else if ( geometry.type === 'SphereGeometry' || geometry.type === 'IcosahedronGeometry' ) {
+
+		const radius = parameters.radius !== undefined ? parameters.radius : 1;
+
+		return new Jolt.SphereShape( radius, null );
+
+	}
+
+	return null;
+
+}
+
+// Object layers
+const LAYER_NON_MOVING = 0;
+const LAYER_MOVING = 1;
+const NUM_OBJECT_LAYERS = 2;
+
+function setupCollisionFiltering( settings ) {
+
+	let objectFilter = new Jolt.ObjectLayerPairFilterTable( NUM_OBJECT_LAYERS );
+	objectFilter.EnableCollision( LAYER_NON_MOVING, LAYER_MOVING );
+	objectFilter.EnableCollision( LAYER_MOVING, LAYER_MOVING );
+
+	const BP_LAYER_NON_MOVING = new Jolt.BroadPhaseLayer( 0 );
+	const BP_LAYER_MOVING = new Jolt.BroadPhaseLayer( 1 );
+	const NUM_BROAD_PHASE_LAYERS = 2;
+
+	let bpInterface = new Jolt.BroadPhaseLayerInterfaceTable( NUM_OBJECT_LAYERS, NUM_BROAD_PHASE_LAYERS );
+	bpInterface.MapObjectToBroadPhaseLayer( LAYER_NON_MOVING, BP_LAYER_NON_MOVING );
+	bpInterface.MapObjectToBroadPhaseLayer( LAYER_MOVING, BP_LAYER_MOVING );
+
+	settings.mObjectLayerPairFilter = objectFilter;
+	settings.mBroadPhaseLayerInterface = bpInterface;
+	settings.mObjectVsBroadPhaseLayerFilter = new Jolt.ObjectVsBroadPhaseLayerFilterTable( settings.mBroadPhaseLayerInterface, NUM_BROAD_PHASE_LAYERS, settings.mObjectLayerPairFilter, NUM_OBJECT_LAYERS );
+
+};
+
+async function JoltPhysics() {
+
+	if ( Jolt === null ) {
+
+		const { default: initJolt } = await import( JOLT_PATH );
+		Jolt = await initJolt();
+
+	}
+
+	const settings = new Jolt.JoltSettings();
+	setupCollisionFiltering( settings );
+
+	const jolt = new Jolt.JoltInterface( settings );
+	Jolt.destroy( settings );
+
+	const physicsSystem = jolt.GetPhysicsSystem();
+	const bodyInterface = physicsSystem.GetBodyInterface();
+
+	const meshes = [];
+	const meshMap = new WeakMap();
+
+	const _position = new Vector3();
+	const _quaternion = new Quaternion();
+	const _scale = new Vector3( 1, 1, 1 );
+
+	const _matrix = new Matrix4();
+
+	function addScene( scene ) {
+
+		scene.traverse( function ( child ) {
+
+			if ( child.isMesh ) {
+
+				const physics = child.userData.physics;
+
+				if ( physics ) {
+
+					addMesh( child, physics.mass, physics.restitution );
+
+				}
+
+			}
+
+		} );
+
+	}
+
+	function addMesh( mesh, mass = 0, restitution = 0 ) {
+
+		const shape = getShape( mesh.geometry );
+
+		if ( shape === null ) return;
+
+		const body = mesh.isInstancedMesh
+							? createInstancedBody( mesh, mass, restitution, shape )
+							: createBody( mesh.position, mesh.quaternion, mass, restitution, shape );
+
+		if ( mass > 0 ) {
+
+			meshes.push( mesh );
+			meshMap.set( mesh, body );
+
+		}
+
+	}
+
+	function createInstancedBody( mesh, mass, restitution, shape ) {
+
+		const array = mesh.instanceMatrix.array;
+
+		const bodies = [];
+
+		for ( let i = 0; i < mesh.count; i ++ ) {
+
+			const position = _position.fromArray( array, i * 16 + 12 );
+			const quaternion = _quaternion.setFromRotationMatrix( _matrix.fromArray( array, i * 16 ) ); // TODO Copilot did this
+			bodies.push( createBody( position, quaternion, mass, restitution, shape ) );
+
+		}
+
+		return bodies;
+
+	}
+
+	function createBody( position, rotation, mass, restitution, shape ) {
+
+		const pos = new Jolt.Vec3( position.x, position.y, position.z );
+		const rot = new Jolt.Quat( rotation.x, rotation.y, rotation.z, rotation.w );
+
+		const motion = mass > 0 ? Jolt.EMotionType_Dynamic : Jolt.EMotionType_Static;
+		const layer = mass > 0 ? LAYER_MOVING : LAYER_NON_MOVING;
+
+		const creationSettings = new Jolt.BodyCreationSettings( shape, pos, rot, motion, layer );
+		creationSettings.mRestitution = restitution;
+
+		const body = bodyInterface.CreateBody( creationSettings );
+
+		bodyInterface.AddBody( body.GetID(), Jolt.EActivation_Activate );
+
+		Jolt.destroy( creationSettings );
+
+		return body;
+
+	}
+
+	function setMeshPosition( mesh, position, index = 0 ) {
+
+		if ( mesh.isInstancedMesh ) {
+
+			const bodies = meshMap.get( mesh );
+
+			const body = bodies[ index ];
+
+			bodyInterface.RemoveBody( body.GetID() );
+			bodyInterface.DestroyBody( body.GetID() );
+
+			const physics = mesh.userData.physics;
+
+			let shape = body.GetShape();
+			let body2 = createBody( position, { x: 0, y: 0, z: 0, w: 1 }, physics.mass, physics.restitution, shape );
+
+			bodies[ index ] = body2;
+
+		} else {
+
+			// TODO: Implement this
+
+		}
+
+	}
+
+	function setMeshVelocity( mesh, velocity, index = 0 ) {
+
+		/*
+		let body = meshMap.get( mesh );
+
+		if ( mesh.isInstancedMesh ) {
+
+			body = body[ index ];
+
+		}
+
+		body.setLinvel( velocity );
+		*/
+
+	}
+
+	//
+
+	const clock = new Clock();
+
+	function step() {
+
+		let deltaTime = clock.getDelta();
+
+		// Don't go below 30 Hz to prevent spiral of death
+		deltaTime = Math.min( deltaTime, 1.0 / 30.0 );
+
+		// When running below 55 Hz, do 2 steps instead of 1
+		const numSteps = deltaTime > 1.0 / 55.0 ? 2 : 1;
+
+		// Step the physics world
+		jolt.Step( deltaTime, numSteps );
+
+		//
+
+		for ( let i = 0, l = meshes.length; i < l; i ++ ) {
+
+			const mesh = meshes[ i ];
+
+			if ( mesh.isInstancedMesh ) {
+
+				const array = mesh.instanceMatrix.array;
+				const bodies = meshMap.get( mesh );
+
+				for ( let j = 0; j < bodies.length; j ++ ) {
+
+					const body = bodies[ j ];
+
+					const position = body.GetPosition();
+					const quaternion = body.GetRotation();
+
+					_position.set( position.GetX(), position.GetY(), position.GetZ() );
+					_quaternion.set( quaternion.GetX(), quaternion.GetY(), quaternion.GetZ(), quaternion.GetW() );
+
+					_matrix.compose( _position, _quaternion, _scale ).toArray( array, j * 16 );
+
+				}
+
+				mesh.instanceMatrix.needsUpdate = true;
+				mesh.computeBoundingSphere();
+
+			} else {
+
+				const body = meshMap.get( mesh );
+
+				const position = body.GetPosition();
+				const rotation = body.GetRotation();
+
+				mesh.position.set( position.GetX(), position.GetY(), position.GetZ() );
+				mesh.quaternion.set( rotation.GetX(), rotation.GetY(), rotation.GetZ(), rotation.GetW() );
+
+			}
+
+		}
+
+	}
+
+	// animate
+
+	setInterval( step, 1000 / frameRate );
+
+	return {
+		addScene: addScene,
+		addMesh: addMesh,
+		setMeshPosition: setMeshPosition,
+		setMeshVelocity: setMeshVelocity
+	};
+
+}
+
+export { JoltPhysics };

+ 164 - 0
examples/physics_jolt_instancing.html

@@ -0,0 +1,164 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js physics - jolt instancing</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<link type="text/css" rel="stylesheet" href="main.css">
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="https://threejs.org" target="_blank" rel="noopener">three.js</a> physics - <a href="https://github.com/jrouwe/JoltPhysics.js/" target="_blank">jolt</a> instancing
+		</div>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import { JoltPhysics } from 'three/addons/physics/JoltPhysics.js';
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			let camera, scene, renderer, stats;
+			let physics, position;
+
+			let boxes, spheres;
+
+			init();
+
+			async function init() {
+
+				physics = await JoltPhysics();
+				position = new THREE.Vector3();
+
+				//
+
+				camera = new THREE.PerspectiveCamera( 50, window.innerWidth / window.innerHeight, 0.1, 100 );
+				camera.position.set( - 1, 1.5, 2 );
+				camera.lookAt( 0, 0.5, 0 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0x666666 );
+
+				const hemiLight = new THREE.HemisphereLight();
+				scene.add( hemiLight );
+
+				const dirLight = new THREE.DirectionalLight( 0xffffff, 3 );
+				dirLight.position.set( 5, 5, 5 );
+				dirLight.castShadow = true;
+				dirLight.shadow.camera.zoom = 2;
+				scene.add( dirLight );
+
+				const floor = new THREE.Mesh(
+					new THREE.BoxGeometry( 10, 5, 10 ),
+					new THREE.ShadowMaterial( { color: 0x444444 } )
+				);
+				floor.position.y = - 2.5;
+				floor.receiveShadow = true;
+				floor.userData.physics = { mass: 0 };
+				scene.add( floor );
+
+				//
+
+				const material = new THREE.MeshLambertMaterial();
+
+				const matrix = new THREE.Matrix4();
+				const color = new THREE.Color();
+
+				// Boxes
+
+				const geometryBox = new THREE.BoxGeometry( 0.075, 0.075, 0.075 );
+				boxes = new THREE.InstancedMesh( geometryBox, material, 400 );
+				boxes.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame
+				boxes.castShadow = true;
+				boxes.receiveShadow = true;
+				boxes.userData.physics = { mass: 1 };
+				scene.add( boxes );
+
+				for ( let i = 0; i < boxes.count; i ++ ) {
+
+					matrix.setPosition( Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5 );
+					boxes.setMatrixAt( i, matrix );
+					boxes.setColorAt( i, color.setHex( 0xffffff * Math.random() ) );
+
+				}
+
+				// Spheres
+
+				const geometrySphere = new THREE.IcosahedronGeometry( 0.05, 4 );
+				spheres = new THREE.InstancedMesh( geometrySphere, material, 400 );
+				spheres.instanceMatrix.setUsage( THREE.DynamicDrawUsage ); // will be updated every frame
+				spheres.castShadow = true;
+				spheres.receiveShadow = true;
+				spheres.userData.physics = { mass: 1 };
+				scene.add( spheres );
+
+				for ( let i = 0; i < spheres.count; i ++ ) {
+
+					matrix.setPosition( Math.random() - 0.5, Math.random() * 2, Math.random() - 0.5 );
+					spheres.setMatrixAt( i, matrix );
+					spheres.setColorAt( i, color.setHex( 0xffffff * Math.random() ) );
+
+				}
+
+				physics.addScene( scene );
+
+				//
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.shadowMap.enabled = true;
+				document.body.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				document.body.appendChild( stats.dom );
+
+				//
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.y = 0.5;
+				controls.update();
+
+				animate();
+
+				setInterval( () => {
+
+					let index = Math.floor( Math.random() * boxes.count );
+
+					position.set( 0, Math.random() + 1, 0 );
+					physics.setMeshPosition( boxes, position, index );
+
+					//
+
+					index = Math.floor( Math.random() * spheres.count );
+
+					position.set( 0, Math.random() + 1, 0 );
+					physics.setMeshPosition( spheres, position, index );
+
+				}, 1000 / 60 );
+
+			}
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				renderer.render( scene, camera );
+
+				stats.update();
+
+			}
+
+		</script>
+	</body>
+</html>

BIN
examples/screenshots/physics_jolt_instancing.jpg


+ 1 - 0
test/e2e/puppeteer.js

@@ -108,6 +108,7 @@ const exceptionList = [
 	// TODO: implement determinism for setTimeout and setInterval
 	// TODO: implement determinism for setTimeout and setInterval
 	// could it fix some examples from above?
 	// could it fix some examples from above?
 	'physics_rapier_instancing',
 	'physics_rapier_instancing',
+	'physics_jolt_instancing',
 
 
 	// Awaiting for WebGL backend support
 	// Awaiting for WebGL backend support
 	'webgpu_clearcoat',
 	'webgpu_clearcoat',