Ver Fonte

Examples: Use CDN version of Ammo.js. (#33799)

Michael Herzog há 3 dias atrás
pai
commit
895d9bdcee

+ 1 - 0
eslint.config.js

@@ -63,6 +63,7 @@ export default [
 				GPUTexture: 'readonly',
 				GPUMapMode: 'readonly',
 				QUnit: 'readonly',
+				Ammo: 'readonly',
 				XRRigidTransform: 'readonly',
 				XRMediaBinding: 'readonly',
 				CodeMirror: 'readonly',

+ 6 - 0
examples/files.json

@@ -552,6 +552,12 @@
 		"games_fps"
 	],
 	"physics": [
+		"physics_ammo_break",
+		"physics_ammo_cloth",
+		"physics_ammo_instancing",
+		"physics_ammo_rope",
+		"physics_ammo_terrain",
+		"physics_ammo_volume",
 		"physics_jolt_instancing",
 		"physics_rapier_basic",
 		"physics_rapier_instancing",

+ 1 - 0
examples/jsm/Addons.js

@@ -171,6 +171,7 @@ export * from './objects/Sky.js';
 export * from './objects/Water.js';
 export { Water as Water2 } from './objects/Water2.js';
 
+export * from './physics/AmmoPhysics.js';
 export * from './physics/JoltPhysics.js';
 export * from './physics/RapierPhysics.js';
 

+ 366 - 0
examples/jsm/physics/AmmoPhysics.js

@@ -0,0 +1,366 @@
+const AMMO_PATH = 'https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js';
+
+/**
+ * @classdesc Can be used to include Ammo.js as a Physics engine into
+ * `three.js` apps. The API can be initialized via:
+ * ```js
+ * const physics = await AmmoPhysics();
+ * ```
+ * The component automatically imports Ammo.js from a CDN so make sure
+ * to use the component with an active Internet connection.
+ *
+ * @name AmmoPhysics
+ * @class
+ * @hideconstructor
+ * @three_import import { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.js';
+ */
+async function AmmoPhysics() {
+
+	if ( typeof Ammo === 'undefined' ) {
+
+		await new Promise( ( resolve, reject ) => {
+
+			const script = document.createElement( 'script' );
+			script.src = AMMO_PATH;
+			script.onload = resolve;
+			script.onerror = reject;
+			document.head.appendChild( script );
+
+		} );
+
+	}
+
+	const AmmoLib = await Ammo();
+
+	const frameRate = 60;
+
+	const collisionConfiguration = new AmmoLib.btDefaultCollisionConfiguration();
+	const dispatcher = new AmmoLib.btCollisionDispatcher( collisionConfiguration );
+	const broadphase = new AmmoLib.btDbvtBroadphase();
+	const solver = new AmmoLib.btSequentialImpulseConstraintSolver();
+	const world = new AmmoLib.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
+	world.setGravity( new AmmoLib.btVector3( 0, - 9.8, 0 ) );
+
+	const worldTransform = new AmmoLib.btTransform();
+
+	//
+
+	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;
+
+			const shape = new AmmoLib.btBoxShape( new AmmoLib.btVector3( sx, sy, sz ) );
+			shape.setMargin( 0.05 );
+
+			return shape;
+
+		} else if ( geometry.type === 'SphereGeometry' || geometry.type === 'IcosahedronGeometry' ) {
+
+			const radius = parameters.radius !== undefined ? parameters.radius : 1;
+
+			const shape = new AmmoLib.btSphereShape( radius );
+			shape.setMargin( 0.05 );
+
+			return shape;
+
+		}
+
+		console.error( 'AmmoPhysics: Unsupported geometry type:', geometry.type );
+
+		return null;
+
+	}
+
+	const meshes = [];
+	const meshMap = new WeakMap();
+
+	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 ) {
+
+			if ( mesh.isInstancedMesh ) {
+
+				handleInstancedMesh( mesh, shape, mass, restitution );
+
+			} else if ( mesh.isMesh ) {
+
+				handleMesh( mesh, shape, mass, restitution );
+
+			}
+
+		}
+
+	}
+
+	function handleMesh( mesh, shape, mass, restitution ) {
+
+		const position = mesh.position;
+		const quaternion = mesh.quaternion;
+
+		const transform = new AmmoLib.btTransform();
+		transform.setIdentity();
+		transform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
+		transform.setRotation( new AmmoLib.btQuaternion( quaternion.x, quaternion.y, quaternion.z, quaternion.w ) );
+
+		const motionState = new AmmoLib.btDefaultMotionState( transform );
+
+		const localInertia = new AmmoLib.btVector3( 0, 0, 0 );
+		shape.calculateLocalInertia( mass, localInertia );
+
+		const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia );
+		rbInfo.set_m_restitution( restitution );
+
+		const body = new AmmoLib.btRigidBody( rbInfo );
+		// body.setFriction( 4 );
+		world.addRigidBody( body );
+
+		if ( mass > 0 ) {
+
+			meshes.push( mesh );
+			meshMap.set( mesh, body );
+
+		}
+
+
+	}
+
+	function handleInstancedMesh( mesh, shape, mass, restitution ) {
+
+		const array = mesh.instanceMatrix.array;
+
+		const bodies = [];
+
+		for ( let i = 0; i < mesh.count; i ++ ) {
+
+			const index = i * 16;
+
+			const transform = new AmmoLib.btTransform();
+			transform.setFromOpenGLMatrix( array.slice( index, index + 16 ) );
+
+			const motionState = new AmmoLib.btDefaultMotionState( transform );
+
+			const localInertia = new AmmoLib.btVector3( 0, 0, 0 );
+			shape.calculateLocalInertia( mass, localInertia );
+
+			const rbInfo = new AmmoLib.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia );
+			rbInfo.set_m_restitution( restitution );
+
+			const body = new AmmoLib.btRigidBody( rbInfo );
+			world.addRigidBody( body );
+
+			bodies.push( body );
+
+		}
+
+		if ( mass > 0 ) {
+
+			meshes.push( mesh );
+
+			meshMap.set( mesh, bodies );
+
+		}
+
+	}
+
+	//
+
+	function setMeshPosition( mesh, position, index = 0 ) {
+
+		if ( mesh.isInstancedMesh ) {
+
+			const bodies = meshMap.get( mesh );
+			const body = bodies[ index ];
+
+			body.setAngularVelocity( new AmmoLib.btVector3( 0, 0, 0 ) );
+			body.setLinearVelocity( new AmmoLib.btVector3( 0, 0, 0 ) );
+
+			worldTransform.setIdentity();
+			worldTransform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
+			body.setWorldTransform( worldTransform );
+
+		} else if ( mesh.isMesh ) {
+
+			const body = meshMap.get( mesh );
+
+			body.setAngularVelocity( new AmmoLib.btVector3( 0, 0, 0 ) );
+			body.setLinearVelocity( new AmmoLib.btVector3( 0, 0, 0 ) );
+
+			worldTransform.setIdentity();
+			worldTransform.setOrigin( new AmmoLib.btVector3( position.x, position.y, position.z ) );
+			body.setWorldTransform( worldTransform );
+
+		}
+
+	}
+
+	//
+
+	let lastTime = 0;
+
+	function step() {
+
+		const time = performance.now();
+
+		if ( lastTime > 0 ) {
+
+			const delta = ( time - lastTime ) / 1000;
+
+			world.stepSimulation( delta, 10 );
+
+			//
+
+			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 motionState = body.getMotionState();
+						motionState.getWorldTransform( worldTransform );
+
+						const position = worldTransform.getOrigin();
+						const quaternion = worldTransform.getRotation();
+
+						compose( position, quaternion, array, j * 16 );
+
+					}
+
+					mesh.instanceMatrix.needsUpdate = true;
+					mesh.computeBoundingSphere();
+
+				} else if ( mesh.isMesh ) {
+
+					const body = meshMap.get( mesh );
+
+					const motionState = body.getMotionState();
+					motionState.getWorldTransform( worldTransform );
+
+					const position = worldTransform.getOrigin();
+					const quaternion = worldTransform.getRotation();
+					mesh.position.set( position.x(), position.y(), position.z() );
+					mesh.quaternion.set( quaternion.x(), quaternion.y(), quaternion.z(), quaternion.w() );
+
+				}
+
+			}
+
+		}
+
+		lastTime = time;
+
+	}
+
+	// animate
+
+	setInterval( step, 1000 / frameRate );
+
+	return {
+		/**
+		 * Adds the given scene to this physics simulation. Only meshes with a
+		 * `physics` object in their {@link Object3D#userData} field will be honored.
+		 * The object can be used to store the mass of the mesh. E.g.:
+		 * ```js
+		 * box.userData.physics = { mass: 1 };
+		 * ```
+		 *
+		 * @method
+		 * @name AmmoPhysics#addScene
+		 * @param {Object3D} scene The scene or any type of 3D object to add.
+		 */
+		addScene: addScene,
+
+		/**
+		 * Adds the given mesh to this physics simulation.
+		 *
+		 * @method
+		 * @name AmmoPhysics#addMesh
+		 * @param {Mesh} mesh The mesh to add.
+		 * @param {number} [mass=0] The mass in kg of the mesh.
+		 * @param {number} [restitution=0] The restitution of the mesh, usually from 0 to 1. Represents how "bouncy" objects are when they collide with each other.
+		 */
+		addMesh: addMesh,
+
+		/**
+		 * Set the position of the given mesh which is part of the physics simulation. Calling this
+		 * method will reset the current simulated velocity of the mesh.
+		 *
+		 * @method
+		 * @name AmmoPhysics#setMeshPosition
+		 * @param {Mesh} mesh The mesh to update the position for.
+		 * @param {Vector3} position - The new position.
+		 * @param {number} [index=0] - If the mesh is instanced, the index represents the instanced ID.
+		 */
+		setMeshPosition: setMeshPosition
+		// addCompoundMesh
+	};
+
+}
+
+function compose( position, quaternion, array, index ) {
+
+	const x = quaternion.x(), y = quaternion.y(), z = quaternion.z(), w = quaternion.w();
+	const x2 = x + x, y2 = y + y, z2 = z + z;
+	const xx = x * x2, xy = x * y2, xz = x * z2;
+	const yy = y * y2, yz = y * z2, zz = z * z2;
+	const wx = w * x2, wy = w * y2, wz = w * z2;
+
+	array[ index + 0 ] = ( 1 - ( yy + zz ) );
+	array[ index + 1 ] = ( xy + wz );
+	array[ index + 2 ] = ( xz - wy );
+	array[ index + 3 ] = 0;
+
+	array[ index + 4 ] = ( xy - wz );
+	array[ index + 5 ] = ( 1 - ( xx + zz ) );
+	array[ index + 6 ] = ( yz + wx );
+	array[ index + 7 ] = 0;
+
+	array[ index + 8 ] = ( xz + wy );
+	array[ index + 9 ] = ( yz - wx );
+	array[ index + 10 ] = ( 1 - ( xx + yy ) );
+	array[ index + 11 ] = 0;
+
+	array[ index + 12 ] = position.x();
+	array[ index + 13 ] = position.y();
+	array[ index + 14 ] = position.z();
+	array[ index + 15 ] = 1;
+
+}
+
+export { AmmoPhysics };

+ 606 - 0
examples/physics_ammo_break.html

@@ -0,0 +1,606 @@
+<html lang="en">
+	<head>
+		<title>Convex object breaking example</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta property="og:title" content="Convex object breaking example">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_break.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_break.jpg">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+
+	<div id="info">Physics threejs demo with convex objects breaking in real time<br />Press mouse to throw balls and move the camera.</div>
+	<div id="container"></div>
+
+	<script src="https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js"></script>
+
+	<script type="importmap">
+		{
+			"imports": {
+				"three": "../build/three.module.js",
+				"three/addons/": "./jsm/"
+			}
+		}
+	</script>
+
+	<script type="module">
+		import * as THREE from 'three';
+
+		import Stats from 'three/addons/libs/stats.module.js';
+
+		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+		import { ConvexObjectBreaker } from 'three/addons/misc/ConvexObjectBreaker.js';
+		import { ConvexGeometry } from 'three/addons/geometries/ConvexGeometry.js';
+
+		// - Global variables -
+
+		// Graphics variables
+		let container, stats;
+		let camera, controls, scene, renderer;
+		let textureLoader;
+		const timer = new THREE.Timer();
+		timer.connect( document );
+
+		const mouseCoords = new THREE.Vector2();
+		const raycaster = new THREE.Raycaster();
+		const ballMaterial = new THREE.MeshPhongMaterial( { color: 0x202020 } );
+
+		// Physics variables
+		const gravityConstant = 7.8;
+		let collisionConfiguration;
+		let dispatcher;
+		let broadphase;
+		let solver;
+		let physicsWorld;
+		const margin = 0.05;
+
+		const convexBreaker = new ConvexObjectBreaker();
+
+		// Rigid bodies include all movable objects
+		const rigidBodies = [];
+
+		const pos = new THREE.Vector3();
+		const quat = new THREE.Quaternion();
+		let transformAux1;
+		let tempBtVec3_1;
+
+		const objectsToRemove = [];
+
+		for ( let i = 0; i < 500; i ++ ) {
+
+			objectsToRemove[ i ] = null;
+
+		}
+
+		let numObjectsToRemove = 0;
+
+		const impactPoint = new THREE.Vector3();
+		const impactNormal = new THREE.Vector3();
+
+		// - Main code -
+
+		Ammo().then( function ( AmmoLib ) {
+
+			Ammo = AmmoLib;
+
+			init();
+
+		} );
+
+
+		// - Functions -
+
+		function init() {
+
+			initGraphics();
+
+			initPhysics();
+
+			createObjects();
+
+			initInput();
+
+		}
+
+		function initGraphics() {
+
+			container = document.getElementById( 'container' );
+
+			camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0xbfd1e5 );
+
+			camera.position.set( - 14, 8, 16 );
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setAnimationLoop( animate );
+			renderer.shadowMap.enabled = true;
+			container.appendChild( renderer.domElement );
+
+			controls = new OrbitControls( camera, renderer.domElement );
+			controls.target.set( 0, 2, 0 );
+			controls.update();
+
+			textureLoader = new THREE.TextureLoader();
+
+			const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
+			scene.add( ambientLight );
+
+			const light = new THREE.DirectionalLight( 0xffffff, 3 );
+			light.position.set( - 10, 18, 5 );
+			light.castShadow = true;
+			const d = 14;
+			light.shadow.camera.left = - d;
+			light.shadow.camera.right = d;
+			light.shadow.camera.top = d;
+			light.shadow.camera.bottom = - d;
+
+			light.shadow.camera.near = 2;
+			light.shadow.camera.far = 50;
+
+			light.shadow.mapSize.x = 1024;
+			light.shadow.mapSize.y = 1024;
+
+			scene.add( light );
+
+			stats = new Stats();
+			stats.domElement.style.position = 'absolute';
+			stats.domElement.style.top = '0px';
+			container.appendChild( stats.domElement );
+
+			//
+
+			window.addEventListener( 'resize', onWindowResize );
+
+		}
+
+		function initPhysics() {
+
+			// Physics configuration
+
+			collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
+			dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
+			broadphase = new Ammo.btDbvtBroadphase();
+			solver = new Ammo.btSequentialImpulseConstraintSolver();
+			physicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
+			physicsWorld.setGravity( new Ammo.btVector3( 0, - gravityConstant, 0 ) );
+
+			transformAux1 = new Ammo.btTransform();
+			tempBtVec3_1 = new Ammo.btVector3( 0, 0, 0 );
+
+		}
+
+		function createObject( mass, halfExtents, pos, quat, material ) {
+
+			const object = new THREE.Mesh( new THREE.BoxGeometry( halfExtents.x * 2, halfExtents.y * 2, halfExtents.z * 2 ), material );
+			object.position.copy( pos );
+			object.quaternion.copy( quat );
+			convexBreaker.prepareBreakableObject( object, mass, new THREE.Vector3(), new THREE.Vector3(), true );
+			createDebrisFromBreakableObject( object );
+
+		}
+
+		function createObjects() {
+
+			// Ground
+			pos.set( 0, - 0.5, 0 );
+			quat.set( 0, 0, 0, 1 );
+			const ground = createParalellepipedWithPhysics( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
+			ground.receiveShadow = true;
+			textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+				texture.wrapS = THREE.RepeatWrapping;
+				texture.wrapT = THREE.RepeatWrapping;
+				texture.repeat.set( 40, 40 );
+				ground.material.map = texture;
+				ground.material.needsUpdate = true;
+
+			} );
+
+			// Tower 1
+			const towerMass = 1000;
+			const towerHalfExtents = new THREE.Vector3( 2, 5, 2 );
+			pos.set( - 8, 5, 0 );
+			quat.set( 0, 0, 0, 1 );
+			createObject( towerMass, towerHalfExtents, pos, quat, createMaterial( 0xB03014 ) );
+
+			// Tower 2
+			pos.set( 8, 5, 0 );
+			quat.set( 0, 0, 0, 1 );
+			createObject( towerMass, towerHalfExtents, pos, quat, createMaterial( 0xB03214 ) );
+
+			//Bridge
+			const bridgeMass = 100;
+			const bridgeHalfExtents = new THREE.Vector3( 7, 0.2, 1.5 );
+			pos.set( 0, 10.2, 0 );
+			quat.set( 0, 0, 0, 1 );
+			createObject( bridgeMass, bridgeHalfExtents, pos, quat, createMaterial( 0xB3B865 ) );
+
+			// Stones
+			const stoneMass = 120;
+			const stoneHalfExtents = new THREE.Vector3( 1, 2, 0.15 );
+			const numStones = 8;
+			quat.set( 0, 0, 0, 1 );
+			for ( let i = 0; i < numStones; i ++ ) {
+
+				pos.set( 0, 2, 15 * ( 0.5 - i / ( numStones + 1 ) ) );
+
+				createObject( stoneMass, stoneHalfExtents, pos, quat, createMaterial( 0xB0B0B0 ) );
+
+			}
+
+			// Mountain
+			const mountainMass = 860;
+			const mountainHalfExtents = new THREE.Vector3( 4, 5, 4 );
+			pos.set( 5, mountainHalfExtents.y * 0.5, - 7 );
+			quat.set( 0, 0, 0, 1 );
+			const mountainPoints = [];
+			mountainPoints.push( new THREE.Vector3( mountainHalfExtents.x, - mountainHalfExtents.y, mountainHalfExtents.z ) );
+			mountainPoints.push( new THREE.Vector3( - mountainHalfExtents.x, - mountainHalfExtents.y, mountainHalfExtents.z ) );
+			mountainPoints.push( new THREE.Vector3( mountainHalfExtents.x, - mountainHalfExtents.y, - mountainHalfExtents.z ) );
+			mountainPoints.push( new THREE.Vector3( - mountainHalfExtents.x, - mountainHalfExtents.y, - mountainHalfExtents.z ) );
+			mountainPoints.push( new THREE.Vector3( 0, mountainHalfExtents.y, 0 ) );
+			const mountain = new THREE.Mesh( new ConvexGeometry( mountainPoints ), createMaterial( 0xB03814 ) );
+			mountain.position.copy( pos );
+			mountain.quaternion.copy( quat );
+			convexBreaker.prepareBreakableObject( mountain, mountainMass, new THREE.Vector3(), new THREE.Vector3(), true );
+			createDebrisFromBreakableObject( mountain );
+
+		}
+
+		function createParalellepipedWithPhysics( sx, sy, sz, mass, pos, quat, material ) {
+
+			const object = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
+			const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
+			shape.setMargin( margin );
+
+			createRigidBody( object, shape, mass, pos, quat );
+
+			return object;
+
+		}
+
+		function createDebrisFromBreakableObject( object ) {
+
+			object.castShadow = true;
+			object.receiveShadow = true;
+
+			const shape = createConvexHullPhysicsShape( object.geometry.attributes.position.array );
+			shape.setMargin( margin );
+
+			const body = createRigidBody( object, shape, object.userData.mass, null, null, object.userData.velocity, object.userData.angularVelocity );
+
+			// Set pointer back to the three object only in the debris objects
+			const btVecUserData = new Ammo.btVector3( 0, 0, 0 );
+			btVecUserData.threeObject = object;
+			body.setUserPointer( btVecUserData );
+
+		}
+
+		function removeDebris( object ) {
+
+			scene.remove( object );
+
+			physicsWorld.removeRigidBody( object.userData.physicsBody );
+
+		}
+
+		function createConvexHullPhysicsShape( coords ) {
+
+			const shape = new Ammo.btConvexHullShape();
+
+			for ( let i = 0, il = coords.length; i < il; i += 3 ) {
+
+				tempBtVec3_1.setValue( coords[ i ], coords[ i + 1 ], coords[ i + 2 ] );
+				const lastOne = ( i >= ( il - 3 ) );
+				shape.addPoint( tempBtVec3_1, lastOne );
+
+			}
+
+			return shape;
+
+		}
+
+		function createRigidBody( object, physicsShape, mass, pos, quat, vel, angVel ) {
+
+			if ( pos ) {
+
+				object.position.copy( pos );
+
+			} else {
+
+				pos = object.position;
+
+			}
+
+			if ( quat ) {
+
+				object.quaternion.copy( quat );
+
+			} else {
+
+				quat = object.quaternion;
+
+			}
+
+			const transform = new Ammo.btTransform();
+			transform.setIdentity();
+			transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+			transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
+			const motionState = new Ammo.btDefaultMotionState( transform );
+
+			const localInertia = new Ammo.btVector3( 0, 0, 0 );
+			physicsShape.calculateLocalInertia( mass, localInertia );
+
+			const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
+			rbInfo.set_m_restitution( 0.5 );
+			rbInfo.set_m_friction( 0.5 );
+
+			const body = new Ammo.btRigidBody( rbInfo );
+
+			if ( vel ) {
+
+				body.setLinearVelocity( new Ammo.btVector3( vel.x, vel.y, vel.z ) );
+
+			}
+
+			if ( angVel ) {
+
+				body.setAngularVelocity( new Ammo.btVector3( angVel.x, angVel.y, angVel.z ) );
+
+			}
+
+			object.userData.physicsBody = body;
+			object.userData.collided = false;
+
+			scene.add( object );
+
+			if ( mass > 0 ) {
+
+				rigidBodies.push( object );
+
+				// Disable deactivation
+				body.setActivationState( 4 );
+
+			}
+
+			physicsWorld.addRigidBody( body );
+
+			return body;
+
+		}
+
+		function createRandomColor() {
+
+			return Math.floor( Math.random() * ( 1 << 24 ) );
+
+		}
+
+		function createMaterial( color ) {
+
+			color = color || createRandomColor();
+			return new THREE.MeshPhongMaterial( { color: color } );
+
+		}
+
+		function initInput() {
+
+			window.addEventListener( 'pointerdown', function ( event ) {
+
+				mouseCoords.set(
+					( event.clientX / window.innerWidth ) * 2 - 1,
+					- ( event.clientY / window.innerHeight ) * 2 + 1
+				);
+
+				raycaster.setFromCamera( mouseCoords, camera );
+
+				// Creates a ball and throws it
+				const ballMass = 35;
+				const ballRadius = 0.4;
+
+				const ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 14, 10 ), ballMaterial );
+				ball.castShadow = true;
+				ball.receiveShadow = true;
+				const ballShape = new Ammo.btSphereShape( ballRadius );
+				ballShape.setMargin( margin );
+				pos.copy( raycaster.ray.direction );
+				pos.add( raycaster.ray.origin );
+				quat.set( 0, 0, 0, 1 );
+				const ballBody = createRigidBody( ball, ballShape, ballMass, pos, quat );
+
+				pos.copy( raycaster.ray.direction );
+				pos.multiplyScalar( 24 );
+				ballBody.setLinearVelocity( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+
+			} );
+
+		}
+
+		function onWindowResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+
+		function animate() {
+
+			timer.update();
+
+			render();
+			stats.update();
+
+		}
+
+		function render() {
+
+			const deltaTime = timer.getDelta();
+
+			updatePhysics( deltaTime );
+
+			renderer.render( scene, camera );
+
+		}
+
+		function updatePhysics( deltaTime ) {
+
+			// Step world
+			physicsWorld.stepSimulation( deltaTime, 10 );
+
+			// Update rigid bodies
+			for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
+
+				const objThree = rigidBodies[ i ];
+				const objPhys = objThree.userData.physicsBody;
+				const ms = objPhys.getMotionState();
+
+				if ( ms ) {
+
+					ms.getWorldTransform( transformAux1 );
+					const p = transformAux1.getOrigin();
+					const q = transformAux1.getRotation();
+					objThree.position.set( p.x(), p.y(), p.z() );
+					objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
+
+					objThree.userData.collided = false;
+
+				}
+
+			}
+
+			for ( let i = 0, il = dispatcher.getNumManifolds(); i < il; i ++ ) {
+
+				const contactManifold = dispatcher.getManifoldByIndexInternal( i );
+				const rb0 = Ammo.castObject( contactManifold.getBody0(), Ammo.btRigidBody );
+				const rb1 = Ammo.castObject( contactManifold.getBody1(), Ammo.btRigidBody );
+
+				const threeObject0 = Ammo.castObject( rb0.getUserPointer(), Ammo.btVector3 ).threeObject;
+				const threeObject1 = Ammo.castObject( rb1.getUserPointer(), Ammo.btVector3 ).threeObject;
+
+				if ( ! threeObject0 && ! threeObject1 ) {
+
+					continue;
+
+				}
+
+				const userData0 = threeObject0 ? threeObject0.userData : null;
+				const userData1 = threeObject1 ? threeObject1.userData : null;
+
+				const breakable0 = userData0 ? userData0.breakable : false;
+				const breakable1 = userData1 ? userData1.breakable : false;
+
+				const collided0 = userData0 ? userData0.collided : false;
+				const collided1 = userData1 ? userData1.collided : false;
+
+				if ( ( ! breakable0 && ! breakable1 ) || ( collided0 && collided1 ) ) {
+
+					continue;
+
+				}
+
+				let contact = false;
+				let maxImpulse = 0;
+				for ( let j = 0, jl = contactManifold.getNumContacts(); j < jl; j ++ ) {
+
+					const contactPoint = contactManifold.getContactPoint( j );
+
+					if ( contactPoint.getDistance() < 0 ) {
+
+						contact = true;
+						const impulse = contactPoint.getAppliedImpulse();
+
+						if ( impulse > maxImpulse ) {
+
+							maxImpulse = impulse;
+							const pos = contactPoint.get_m_positionWorldOnB();
+							const normal = contactPoint.get_m_normalWorldOnB();
+							impactPoint.set( pos.x(), pos.y(), pos.z() );
+							impactNormal.set( normal.x(), normal.y(), normal.z() );
+
+						}
+
+						break;
+
+					}
+
+				}
+
+				// If no point has contact, abort
+				if ( ! contact ) continue;
+
+				// Subdivision
+
+				const fractureImpulse = 250;
+
+				if ( breakable0 && ! collided0 && maxImpulse > fractureImpulse ) {
+
+					const debris = convexBreaker.subdivideByImpact( threeObject0, impactPoint, impactNormal, 1, 2 );
+
+					const numObjects = debris.length;
+					for ( let j = 0; j < numObjects; j ++ ) {
+
+						const vel = rb0.getLinearVelocity();
+						const angVel = rb0.getAngularVelocity();
+						const fragment = debris[ j ];
+						fragment.userData.velocity.set( vel.x(), vel.y(), vel.z() );
+						fragment.userData.angularVelocity.set( angVel.x(), angVel.y(), angVel.z() );
+
+						createDebrisFromBreakableObject( fragment );
+
+					}
+
+					objectsToRemove[ numObjectsToRemove ++ ] = threeObject0;
+					userData0.collided = true;
+
+				}
+
+				if ( breakable1 && ! collided1 && maxImpulse > fractureImpulse ) {
+
+					const debris = convexBreaker.subdivideByImpact( threeObject1, impactPoint, impactNormal, 1, 2 );
+
+					const numObjects = debris.length;
+					for ( let j = 0; j < numObjects; j ++ ) {
+
+						const vel = rb1.getLinearVelocity();
+						const angVel = rb1.getAngularVelocity();
+						const fragment = debris[ j ];
+						fragment.userData.velocity.set( vel.x(), vel.y(), vel.z() );
+						fragment.userData.angularVelocity.set( angVel.x(), angVel.y(), angVel.z() );
+
+						createDebrisFromBreakableObject( fragment );
+
+					}
+
+					objectsToRemove[ numObjectsToRemove ++ ] = threeObject1;
+					userData1.collided = true;
+
+				}
+
+			}
+
+			for ( let i = 0; i < numObjectsToRemove; i ++ ) {
+
+				removeDebris( objectsToRemove[ i ] );
+
+			}
+
+			numObjectsToRemove = 0;
+
+		}
+
+		</script>
+
+	</body>
+</html>

+ 473 - 0
examples/physics_ammo_cloth.html

@@ -0,0 +1,473 @@
+<html lang="en">
+	<head>
+		<title>Ammo.js softbody cloth demo</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta property="og:title" content="Ammo.js softbody cloth demo">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_cloth.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_cloth.jpg">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="info">Ammo.js physics soft body cloth demo<br>Press Q or A to move the arm.</div>
+		<div id="container"></div>
+
+		<script src="https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			// Graphics variables
+			let container, stats;
+			let camera, controls, scene, renderer;
+			let textureLoader;
+			const timer = new THREE.Timer();
+			timer.connect( document );
+
+			// Physics variables
+			const gravityConstant = - 9.8;
+			let physicsWorld;
+			const rigidBodies = [];
+			const margin = 0.05;
+			let hinge;
+			let cloth;
+			let transformAux1;
+
+			let armMovement = 0;
+
+			Ammo().then( function ( AmmoLib ) {
+
+				Ammo = AmmoLib;
+
+				init();
+
+			} );
+
+
+			function init() {
+
+				initGraphics();
+
+				initPhysics();
+
+				createObjects();
+
+				initInput();
+
+			}
+
+			function initGraphics() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xbfd1e5 );
+
+				camera.position.set( - 12, 7, 4 );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				textureLoader = new THREE.TextureLoader();
+
+				const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
+				scene.add( ambientLight );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 3 );
+				light.position.set( - 7, 10, 15 );
+				light.castShadow = true;
+				const d = 10;
+				light.shadow.camera.left = - d;
+				light.shadow.camera.right = d;
+				light.shadow.camera.top = d;
+				light.shadow.camera.bottom = - d;
+
+				light.shadow.camera.near = 2;
+				light.shadow.camera.far = 50;
+
+				light.shadow.mapSize.x = 1024;
+				light.shadow.mapSize.y = 1024;
+
+				light.shadow.bias = - 0.003;
+				scene.add( light );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function initPhysics() {
+
+				// Physics configuration
+
+				const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
+				const dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
+				const broadphase = new Ammo.btDbvtBroadphase();
+				const solver = new Ammo.btSequentialImpulseConstraintSolver();
+				const softBodySolver = new Ammo.btDefaultSoftBodySolver();
+				physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
+				physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+				physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+
+				transformAux1 = new Ammo.btTransform();
+
+			}
+
+			function createObjects() {
+
+				const pos = new THREE.Vector3();
+				const quat = new THREE.Quaternion();
+
+				// Ground
+				pos.set( 0, - 0.5, 0 );
+				quat.set( 0, 0, 0, 1 );
+				const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
+				ground.castShadow = true;
+				ground.receiveShadow = true;
+				textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+					texture.colorSpace = THREE.SRGBColorSpace;
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.repeat.set( 40, 40 );
+					ground.material.map = texture;
+					ground.material.needsUpdate = true;
+
+	} );
+
+				// Wall
+				const brickMass = 0.5;
+				const brickLength = 1.2;
+				const brickDepth = 0.6;
+				const brickHeight = brickLength * 0.5;
+				const numBricksLength = 6;
+				const numBricksHeight = 8;
+				const z0 = - numBricksLength * brickLength * 0.5;
+				pos.set( 0, brickHeight * 0.5, z0 );
+				quat.set( 0, 0, 0, 1 );
+				for ( let j = 0; j < numBricksHeight; j ++ ) {
+
+					const oddRow = ( j % 2 ) == 1;
+
+					pos.z = z0;
+
+					if ( oddRow ) {
+
+						pos.z -= 0.25 * brickLength;
+
+					}
+
+					const nRow = oddRow ? numBricksLength + 1 : numBricksLength;
+
+					for ( let i = 0; i < nRow; i ++ ) {
+
+						let brickLengthCurrent = brickLength;
+						let brickMassCurrent = brickMass;
+
+						if ( oddRow && ( i == 0 || i == nRow - 1 ) ) {
+
+							brickLengthCurrent *= 0.5;
+							brickMassCurrent *= 0.5;
+
+						}
+
+						const brick = createParalellepiped( brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createMaterial() );
+						brick.castShadow = true;
+						brick.receiveShadow = true;
+
+						if ( oddRow && ( i == 0 || i == nRow - 2 ) ) {
+
+							pos.z += 0.75 * brickLength;
+
+						} else {
+
+							pos.z += brickLength;
+
+						}
+
+					}
+
+					pos.y += brickHeight;
+
+				}
+
+				// The cloth
+				// Cloth graphic object
+				const clothWidth = 4;
+				const clothHeight = 3;
+				const clothNumSegmentsZ = clothWidth * 5;
+				const clothNumSegmentsY = clothHeight * 5;
+				const clothPos = new THREE.Vector3( - 3, 3, 2 );
+
+				const clothGeometry = new THREE.PlaneGeometry( clothWidth, clothHeight, clothNumSegmentsZ, clothNumSegmentsY );
+				clothGeometry.rotateY( Math.PI * 0.5 );
+				clothGeometry.translate( clothPos.x, clothPos.y + clothHeight * 0.5, clothPos.z - clothWidth * 0.5 );
+
+				const clothMaterial = new THREE.MeshLambertMaterial( { color: 0xFFFFFF, side: THREE.DoubleSide } );
+				cloth = new THREE.Mesh( clothGeometry, clothMaterial );
+				cloth.castShadow = true;
+				cloth.receiveShadow = true;
+				scene.add( cloth );
+				textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+					texture.colorSpace = THREE.SRGBColorSpace;
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.repeat.set( clothNumSegmentsZ, clothNumSegmentsY );
+					cloth.material.map = texture;
+					cloth.material.needsUpdate = true;
+
+				} );
+
+				// Cloth physic object
+				const softBodyHelpers = new Ammo.btSoftBodyHelpers();
+				const clothCorner00 = new Ammo.btVector3( clothPos.x, clothPos.y + clothHeight, clothPos.z );
+				const clothCorner01 = new Ammo.btVector3( clothPos.x, clothPos.y + clothHeight, clothPos.z - clothWidth );
+				const clothCorner10 = new Ammo.btVector3( clothPos.x, clothPos.y, clothPos.z );
+				const clothCorner11 = new Ammo.btVector3( clothPos.x, clothPos.y, clothPos.z - clothWidth );
+				const clothSoftBody = softBodyHelpers.CreatePatch( physicsWorld.getWorldInfo(), clothCorner00, clothCorner01, clothCorner10, clothCorner11, clothNumSegmentsZ + 1, clothNumSegmentsY + 1, 0, true );
+				const sbConfig = clothSoftBody.get_m_cfg();
+				sbConfig.set_viterations( 10 );
+				sbConfig.set_piterations( 10 );
+
+				clothSoftBody.setTotalMass( 0.9, false );
+				Ammo.castObject( clothSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin * 3 );
+				physicsWorld.addSoftBody( clothSoftBody, 1, - 1 );
+				cloth.userData.physicsBody = clothSoftBody;
+				// Disable deactivation
+				clothSoftBody.setActivationState( 4 );
+
+				// The base
+				const armMass = 2;
+				const armLength = 3 + clothWidth;
+				const pylonHeight = clothPos.y + clothHeight;
+				const baseMaterial = new THREE.MeshPhongMaterial( { color: 0x606060 } );
+				pos.set( clothPos.x, 0.1, clothPos.z - armLength );
+				quat.set( 0, 0, 0, 1 );
+				const base = createParalellepiped( 1, 0.2, 1, 0, pos, quat, baseMaterial );
+				base.castShadow = true;
+				base.receiveShadow = true;
+				pos.set( clothPos.x, 0.5 * pylonHeight, clothPos.z - armLength );
+				const pylon = createParalellepiped( 0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial );
+				pylon.castShadow = true;
+				pylon.receiveShadow = true;
+				pos.set( clothPos.x, pylonHeight + 0.2, clothPos.z - 0.5 * armLength );
+				const arm = createParalellepiped( 0.4, 0.4, armLength + 0.4, armMass, pos, quat, baseMaterial );
+				arm.castShadow = true;
+				arm.receiveShadow = true;
+
+				// Glue the cloth to the arm
+				const influence = 0.5;
+				clothSoftBody.appendAnchor( 0, arm.userData.physicsBody, false, influence );
+				clothSoftBody.appendAnchor( clothNumSegmentsZ, arm.userData.physicsBody, false, influence );
+
+				// Hinge constraint to move the arm
+				const pivotA = new Ammo.btVector3( 0, pylonHeight * 0.5, 0 );
+				const pivotB = new Ammo.btVector3( 0, - 0.2, - armLength * 0.5 );
+				const axis = new Ammo.btVector3( 0, 1, 0 );
+				hinge = new Ammo.btHingeConstraint( pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true );
+				physicsWorld.addConstraint( hinge, true );
+
+			}
+
+			function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {
+
+				const threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
+				const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
+				shape.setMargin( margin );
+
+				createRigidBody( threeObject, shape, mass, pos, quat );
+
+				return threeObject;
+
+			}
+
+			function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
+
+				threeObject.position.copy( pos );
+				threeObject.quaternion.copy( quat );
+
+				const transform = new Ammo.btTransform();
+				transform.setIdentity();
+				transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+				transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
+				const motionState = new Ammo.btDefaultMotionState( transform );
+
+				const localInertia = new Ammo.btVector3( 0, 0, 0 );
+				physicsShape.calculateLocalInertia( mass, localInertia );
+
+				const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
+				const body = new Ammo.btRigidBody( rbInfo );
+
+				threeObject.userData.physicsBody = body;
+
+				scene.add( threeObject );
+
+				if ( mass > 0 ) {
+
+					rigidBodies.push( threeObject );
+
+					// Disable deactivation
+					body.setActivationState( 4 );
+
+				}
+
+				physicsWorld.addRigidBody( body );
+
+			}
+
+			function createRandomColor() {
+
+				return Math.floor( Math.random() * ( 1 << 24 ) );
+
+			}
+
+			function createMaterial() {
+
+				return new THREE.MeshPhongMaterial( { color: createRandomColor() } );
+
+			}
+
+			function initInput() {
+
+				window.addEventListener( 'keydown', function ( event ) {
+
+					switch ( event.keyCode ) {
+
+						// Q
+						case 81:
+							armMovement = 1;
+							break;
+
+							// A
+						case 65:
+							armMovement = - 1;
+							break;
+
+					}
+
+				} );
+
+				window.addEventListener( 'keyup', function () {
+
+					armMovement = 0;
+
+				} );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				timer.update();
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const deltaTime = timer.getDelta();
+
+				updatePhysics( deltaTime );
+
+				renderer.render( scene, camera );
+
+			}
+
+			function updatePhysics( deltaTime ) {
+
+				// Hinge control
+				hinge.enableAngularMotor( true, 0.8 * armMovement, 50 );
+
+				// Step world
+				physicsWorld.stepSimulation( deltaTime, 10 );
+
+				// Update cloth
+				const softBody = cloth.userData.physicsBody;
+				const clothPositions = cloth.geometry.attributes.position.array;
+				const numVerts = clothPositions.length / 3;
+				const nodes = softBody.get_m_nodes();
+				let indexFloat = 0;
+
+				for ( let i = 0; i < numVerts; i ++ ) {
+
+					const node = nodes.at( i );
+					const nodePos = node.get_m_x();
+					clothPositions[ indexFloat ++ ] = nodePos.x();
+					clothPositions[ indexFloat ++ ] = nodePos.y();
+					clothPositions[ indexFloat ++ ] = nodePos.z();
+
+				}
+
+				cloth.geometry.computeVertexNormals();
+				cloth.geometry.attributes.position.needsUpdate = true;
+				cloth.geometry.attributes.normal.needsUpdate = true;
+
+				// Update rigid bodies
+				for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
+
+					const objThree = rigidBodies[ i ];
+					const objPhys = objThree.userData.physicsBody;
+					const ms = objPhys.getMotionState();
+					if ( ms ) {
+
+						ms.getWorldTransform( transformAux1 );
+						const p = transformAux1.getOrigin();
+						const q = transformAux1.getRotation();
+						objThree.position.set( p.x(), p.y(), p.z() );
+						objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
+
+					}
+
+				}
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 175 - 0
examples/physics_ammo_instancing.html

@@ -0,0 +1,175 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js physics - ammo.js instancing</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
+		<meta property="og:title" content="three.js physics - ammo.js instancing">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_instancing.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_instancing.jpg">
+		<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 - ammo.js 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 { AmmoPhysics } from 'three/addons/physics/AmmoPhysics.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 AmmoPhysics();
+				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 shadowPlane = new THREE.Mesh(
+					new THREE.PlaneGeometry( 10, 10 ),
+					new THREE.ShadowMaterial( {
+						color: 0x444444
+					} ),
+				);
+				shadowPlane.rotation.x = - Math.PI / 2;
+				shadowPlane.receiveShadow = true;
+				scene.add( shadowPlane );
+
+				const floorCollider = new THREE.Mesh(
+					new THREE.BoxGeometry( 10, 5, 10 ),
+					new THREE.MeshBasicMaterial( { color: 0x666666 } )
+				);
+				floorCollider.position.y = - 2.5;
+				floorCollider.userData.physics = { mass: 0 };
+				floorCollider.visible = false;
+				scene.add( floorCollider );
+
+				//
+
+				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.setAnimationLoop( animate );
+				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();
+
+				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() {
+
+				renderer.render( scene, camera );
+
+				stats.update();
+
+			}
+
+		</script>
+	</body>
+</html>

+ 494 - 0
examples/physics_ammo_rope.html

@@ -0,0 +1,494 @@
+<html lang="en">
+	<head>
+		<title>Amjs softbody rope demo</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta property="og:title" content="Amjs softbody rope demo">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_rope.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_rope.jpg">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+	<div id="info">Ammo.js physics soft body rope demo<br>Press Q or A to move the arm.</div>
+	<div id="container"></div>
+
+	<script src="https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js"></script>
+
+	<script type="importmap">
+		{
+			"imports": {
+				"three": "../build/three.module.js",
+				"three/addons/": "./jsm/"
+			}
+		}
+	</script>
+
+	<script type="module">
+		import * as THREE from 'three';
+
+		import Stats from 'three/addons/libs/stats.module.js';
+
+		import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+		// Graphics variables
+		let container, stats;
+		let camera, controls, scene, renderer;
+		let textureLoader;
+		const timer = new THREE.Timer();
+		timer.connect( document );
+
+		// Physics variables
+		const gravityConstant = - 9.8;
+		let collisionConfiguration;
+		let dispatcher;
+		let broadphase;
+		let solver;
+		let softBodySolver;
+		let physicsWorld;
+		const rigidBodies = [];
+		const margin = 0.05;
+		let hinge;
+		let rope;
+		let transformAux1;
+
+		let armMovement = 0;
+
+		Ammo().then( function ( AmmoLib ) {
+
+			Ammo = AmmoLib;
+
+			init();
+
+		} );
+
+		function init() {
+
+			initGraphics();
+
+			initPhysics();
+
+			createObjects();
+
+			initInput();
+
+		}
+
+		function initGraphics() {
+
+			container = document.getElementById( 'container' );
+
+			camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+			scene = new THREE.Scene();
+			scene.background = new THREE.Color( 0xbfd1e5 );
+
+			camera.position.set( - 7, 5, 8 );
+
+			renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
+			renderer.setSize( window.innerWidth, window.innerHeight );
+			renderer.setAnimationLoop( animate );
+			renderer.shadowMap.enabled = true;
+			container.appendChild( renderer.domElement );
+
+			controls = new OrbitControls( camera, renderer.domElement );
+			controls.target.set( 0, 2, 0 );
+			controls.update();
+
+			textureLoader = new THREE.TextureLoader();
+
+			const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
+			scene.add( ambientLight );
+
+			const light = new THREE.DirectionalLight( 0xffffff, 3 );
+			light.position.set( - 10, 10, 5 );
+			light.castShadow = true;
+			const d = 10;
+			light.shadow.camera.left = - d;
+			light.shadow.camera.right = d;
+			light.shadow.camera.top = d;
+			light.shadow.camera.bottom = - d;
+
+			light.shadow.camera.near = 2;
+			light.shadow.camera.far = 50;
+
+			light.shadow.mapSize.x = 1024;
+			light.shadow.mapSize.y = 1024;
+
+			scene.add( light );
+
+			stats = new Stats();
+			stats.domElement.style.position = 'absolute';
+			stats.domElement.style.top = '0px';
+			container.appendChild( stats.domElement );
+
+			//
+
+			window.addEventListener( 'resize', onWindowResize );
+
+		}
+
+		function initPhysics() {
+
+			// Physics configuration
+
+			collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
+			dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
+			broadphase = new Ammo.btDbvtBroadphase();
+			solver = new Ammo.btSequentialImpulseConstraintSolver();
+			softBodySolver = new Ammo.btDefaultSoftBodySolver();
+			physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
+			physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+			physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+
+			transformAux1 = new Ammo.btTransform();
+
+		}
+
+		function createObjects() {
+
+			const pos = new THREE.Vector3();
+			const quat = new THREE.Quaternion();
+
+			// Ground
+			pos.set( 0, - 0.5, 0 );
+			quat.set( 0, 0, 0, 1 );
+			const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
+			ground.castShadow = true;
+			ground.receiveShadow = true;
+			textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+				texture.colorSpace = THREE.SRGBColorSpace;
+				texture.wrapS = THREE.RepeatWrapping;
+				texture.wrapT = THREE.RepeatWrapping;
+				texture.repeat.set( 40, 40 );
+				ground.material.map = texture;
+				ground.material.needsUpdate = true;
+
+			} );
+
+
+			// Ball
+			const ballMass = 1.2;
+			const ballRadius = 0.6;
+
+			const ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 20, 20 ), new THREE.MeshPhongMaterial( { color: 0x202020 } ) );
+			ball.castShadow = true;
+			ball.receiveShadow = true;
+			const ballShape = new Ammo.btSphereShape( ballRadius );
+			ballShape.setMargin( margin );
+			pos.set( - 3, 2, 0 );
+			quat.set( 0, 0, 0, 1 );
+			createRigidBody( ball, ballShape, ballMass, pos, quat );
+			ball.userData.physicsBody.setFriction( 0.5 );
+
+			// Wall
+			const brickMass = 0.5;
+			const brickLength = 1.2;
+			const brickDepth = 0.6;
+			const brickHeight = brickLength * 0.5;
+			const numBricksLength = 6;
+			const numBricksHeight = 8;
+			const z0 = - numBricksLength * brickLength * 0.5;
+			pos.set( 0, brickHeight * 0.5, z0 );
+			quat.set( 0, 0, 0, 1 );
+
+			for ( let j = 0; j < numBricksHeight; j ++ ) {
+
+				const oddRow = ( j % 2 ) == 1;
+
+				pos.z = z0;
+
+				if ( oddRow ) {
+
+					pos.z -= 0.25 * brickLength;
+
+				}
+
+				const nRow = oddRow ? numBricksLength + 1 : numBricksLength;
+
+				for ( let i = 0; i < nRow; i ++ ) {
+
+					let brickLengthCurrent = brickLength;
+					let brickMassCurrent = brickMass;
+					if ( oddRow && ( i == 0 || i == nRow - 1 ) ) {
+
+						brickLengthCurrent *= 0.5;
+						brickMassCurrent *= 0.5;
+
+					}
+
+					const brick = createParalellepiped( brickDepth, brickHeight, brickLengthCurrent, brickMassCurrent, pos, quat, createMaterial() );
+					brick.castShadow = true;
+					brick.receiveShadow = true;
+
+					if ( oddRow && ( i == 0 || i == nRow - 2 ) ) {
+
+						pos.z += 0.75 * brickLength;
+
+					} else {
+
+						pos.z += brickLength;
+
+					}
+
+				}
+
+				pos.y += brickHeight;
+
+			}
+
+			// The rope
+			// Rope graphic object
+			const ropeNumSegments = 10;
+			const ropeLength = 4;
+			const ropeMass = 3;
+			const ropePos = ball.position.clone();
+			ropePos.y += ballRadius;
+
+			const segmentLength = ropeLength / ropeNumSegments;
+			const ropeGeometry = new THREE.BufferGeometry();
+			const ropeMaterial = new THREE.LineBasicMaterial( { color: 0x000000 } );
+			const ropePositions = [];
+			const ropeIndices = [];
+
+			for ( let i = 0; i < ropeNumSegments + 1; i ++ ) {
+
+				ropePositions.push( ropePos.x, ropePos.y + i * segmentLength, ropePos.z );
+
+			}
+
+			for ( let i = 0; i < ropeNumSegments; i ++ ) {
+
+				ropeIndices.push( i, i + 1 );
+
+			}
+
+			ropeGeometry.setIndex( new THREE.BufferAttribute( new Uint16Array( ropeIndices ), 1 ) );
+			ropeGeometry.setAttribute( 'position', new THREE.BufferAttribute( new Float32Array( ropePositions ), 3 ) );
+			ropeGeometry.computeBoundingSphere();
+			rope = new THREE.LineSegments( ropeGeometry, ropeMaterial );
+			rope.castShadow = true;
+			rope.receiveShadow = true;
+			scene.add( rope );
+
+			// Rope physic object
+			const softBodyHelpers = new Ammo.btSoftBodyHelpers();
+			const ropeStart = new Ammo.btVector3( ropePos.x, ropePos.y, ropePos.z );
+			const ropeEnd = new Ammo.btVector3( ropePos.x, ropePos.y + ropeLength, ropePos.z );
+			const ropeSoftBody = softBodyHelpers.CreateRope( physicsWorld.getWorldInfo(), ropeStart, ropeEnd, ropeNumSegments - 1, 0 );
+			const sbConfig = ropeSoftBody.get_m_cfg();
+			sbConfig.set_viterations( 10 );
+			sbConfig.set_piterations( 10 );
+			ropeSoftBody.setTotalMass( ropeMass, false );
+			Ammo.castObject( ropeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin * 3 );
+			physicsWorld.addSoftBody( ropeSoftBody, 1, - 1 );
+			rope.userData.physicsBody = ropeSoftBody;
+			// Disable deactivation
+			ropeSoftBody.setActivationState( 4 );
+
+			// The base
+			const armMass = 2;
+			const armLength = 3;
+			const pylonHeight = ropePos.y + ropeLength;
+			const baseMaterial = new THREE.MeshPhongMaterial( { color: 0x606060 } );
+			pos.set( ropePos.x, 0.1, ropePos.z - armLength );
+			quat.set( 0, 0, 0, 1 );
+			const base = createParalellepiped( 1, 0.2, 1, 0, pos, quat, baseMaterial );
+			base.castShadow = true;
+			base.receiveShadow = true;
+			pos.set( ropePos.x, 0.5 * pylonHeight, ropePos.z - armLength );
+			const pylon = createParalellepiped( 0.4, pylonHeight, 0.4, 0, pos, quat, baseMaterial );
+			pylon.castShadow = true;
+			pylon.receiveShadow = true;
+			pos.set( ropePos.x, pylonHeight + 0.2, ropePos.z - 0.5 * armLength );
+			const arm = createParalellepiped( 0.4, 0.4, armLength + 0.4, armMass, pos, quat, baseMaterial );
+			arm.castShadow = true;
+			arm.receiveShadow = true;
+
+			// Glue the rope extremes to the ball and the arm
+			const influence = 1;
+			ropeSoftBody.appendAnchor( 0, ball.userData.physicsBody, true, influence );
+			ropeSoftBody.appendAnchor( ropeNumSegments, arm.userData.physicsBody, true, influence );
+
+			// Hinge constraint to move the arm
+			const pivotA = new Ammo.btVector3( 0, pylonHeight * 0.5, 0 );
+			const pivotB = new Ammo.btVector3( 0, - 0.2, - armLength * 0.5 );
+			const axis = new Ammo.btVector3( 0, 1, 0 );
+			hinge = new Ammo.btHingeConstraint( pylon.userData.physicsBody, arm.userData.physicsBody, pivotA, pivotB, axis, axis, true );
+			physicsWorld.addConstraint( hinge, true );
+
+
+		}
+
+		function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {
+
+			const threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
+			const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
+			shape.setMargin( margin );
+
+			createRigidBody( threeObject, shape, mass, pos, quat );
+
+			return threeObject;
+
+		}
+
+		function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
+
+			threeObject.position.copy( pos );
+			threeObject.quaternion.copy( quat );
+
+			const transform = new Ammo.btTransform();
+			transform.setIdentity();
+			transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+			transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
+			const motionState = new Ammo.btDefaultMotionState( transform );
+
+			const localInertia = new Ammo.btVector3( 0, 0, 0 );
+			physicsShape.calculateLocalInertia( mass, localInertia );
+
+			const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
+			const body = new Ammo.btRigidBody( rbInfo );
+
+			threeObject.userData.physicsBody = body;
+
+			scene.add( threeObject );
+
+			if ( mass > 0 ) {
+
+				rigidBodies.push( threeObject );
+
+				// Disable deactivation
+				body.setActivationState( 4 );
+
+			}
+
+			physicsWorld.addRigidBody( body );
+
+		}
+
+		function createRandomColor() {
+
+			return Math.floor( Math.random() * ( 1 << 24 ) );
+
+		}
+
+		function createMaterial() {
+
+			return new THREE.MeshPhongMaterial( { color: createRandomColor() } );
+
+		}
+
+		function initInput() {
+
+			window.addEventListener( 'keydown', function ( event ) {
+
+				switch ( event.keyCode ) {
+
+					// Q
+					case 81:
+						armMovement = 1;
+						break;
+
+					// A
+					case 65:
+						armMovement = - 1;
+						break;
+
+				}
+
+			} );
+
+			window.addEventListener( 'keyup', function () {
+
+				armMovement = 0;
+
+			} );
+
+		}
+
+		function onWindowResize() {
+
+			camera.aspect = window.innerWidth / window.innerHeight;
+			camera.updateProjectionMatrix();
+
+			renderer.setSize( window.innerWidth, window.innerHeight );
+
+		}
+
+		function animate() {
+
+			timer.update();
+
+			render();
+			stats.update();
+
+		}
+
+		function render() {
+
+			const deltaTime = timer.getDelta();
+
+			updatePhysics( deltaTime );
+
+			renderer.render( scene, camera );
+
+		}
+
+		function updatePhysics( deltaTime ) {
+
+			// Hinge control
+			hinge.enableAngularMotor( true, 1.5 * armMovement, 50 );
+
+			// Step world
+			physicsWorld.stepSimulation( deltaTime, 10 );
+
+			// Update rope
+			const softBody = rope.userData.physicsBody;
+			const ropePositions = rope.geometry.attributes.position.array;
+			const numVerts = ropePositions.length / 3;
+			const nodes = softBody.get_m_nodes();
+			let indexFloat = 0;
+
+			for ( let i = 0; i < numVerts; i ++ ) {
+
+				const node = nodes.at( i );
+				const nodePos = node.get_m_x();
+				ropePositions[ indexFloat ++ ] = nodePos.x();
+				ropePositions[ indexFloat ++ ] = nodePos.y();
+				ropePositions[ indexFloat ++ ] = nodePos.z();
+
+			}
+
+			rope.geometry.attributes.position.needsUpdate = true;
+
+			// Update rigid bodies
+			for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
+
+				const objThree = rigidBodies[ i ];
+				const objPhys = objThree.userData.physicsBody;
+				const ms = objPhys.getMotionState();
+				if ( ms ) {
+
+					ms.getWorldTransform( transformAux1 );
+					const p = transformAux1.getOrigin();
+					const q = transformAux1.getRotation();
+					objThree.position.set( p.x(), p.y(), p.z() );
+					objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
+
+				}
+
+			}
+
+		}
+
+		</script>
+
+	</body>
+</html>

+ 452 - 0
examples/physics_ammo_terrain.html

@@ -0,0 +1,452 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>Ammo.js terrain heightfield demo</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta property="og:title" content="Ammo.js terrain heightfield demo">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_terrain.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_terrain.jpg">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="container"></div>
+		<div id="info">Ammo.js physics terrain heightfield demo</div>
+
+		<script src="https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+
+			// Heightfield parameters
+			const terrainWidthExtents = 100;
+			const terrainDepthExtents = 100;
+			const terrainWidth = 128;
+			const terrainDepth = 128;
+			const terrainHalfWidth = terrainWidth / 2;
+			const terrainHalfDepth = terrainDepth / 2;
+			const terrainMaxHeight = 8;
+			const terrainMinHeight = - 2;
+
+			// Graphics variables
+			let container, stats;
+			let camera, scene, renderer;
+			let terrainMesh;
+			const timer = new THREE.Timer();
+			timer.connect( document );
+
+			// Physics variables
+			let collisionConfiguration;
+			let dispatcher;
+			let broadphase;
+			let solver;
+			let physicsWorld;
+			const dynamicObjects = [];
+			let transformAux1;
+
+			let heightData = null;
+			let ammoHeightData = null;
+
+			let time = 0;
+			const objectTimePeriod = 3;
+			let timeNextSpawn = time + objectTimePeriod;
+			const maxNumObjects = 30;
+
+			Ammo().then( function ( AmmoLib ) {
+
+				Ammo = AmmoLib;
+
+				init();
+
+			} );
+
+			function init() {
+
+				heightData = generateHeight( terrainWidth, terrainDepth, terrainMinHeight, terrainMaxHeight );
+
+				initGraphics();
+
+				initPhysics();
+
+			}
+
+			function initGraphics() {
+
+				container = document.getElementById( 'container' );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xbfd1e5 );
+
+				camera.position.y = heightData[ terrainHalfWidth + terrainHalfDepth * terrainWidth ] * ( terrainMaxHeight - terrainMinHeight ) + 5;
+
+				camera.position.z = terrainDepthExtents / 2;
+				camera.lookAt( 0, 0, 0 );
+
+				const controls = new OrbitControls( camera, renderer.domElement );
+				controls.enableZoom = false;
+
+				const geometry = new THREE.PlaneGeometry( terrainWidthExtents, terrainDepthExtents, terrainWidth - 1, terrainDepth - 1 );
+				geometry.rotateX( - Math.PI / 2 );
+
+				const vertices = geometry.attributes.position.array;
+
+				for ( let i = 0, j = 0, l = vertices.length; i < l; i ++, j += 3 ) {
+
+					// j + 1 because it is the y component that we modify
+					vertices[ j + 1 ] = heightData[ i ];
+
+				}
+
+				geometry.computeVertexNormals();
+
+				const groundMaterial = new THREE.MeshPhongMaterial( { color: 0xC7C7C7 } );
+				terrainMesh = new THREE.Mesh( geometry, groundMaterial );
+				terrainMesh.receiveShadow = true;
+				terrainMesh.castShadow = true;
+
+				scene.add( terrainMesh );
+
+				const textureLoader = new THREE.TextureLoader();
+				textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.repeat.set( terrainWidth - 1, terrainDepth - 1 );
+					groundMaterial.map = texture;
+					groundMaterial.needsUpdate = true;
+
+				} );
+
+				const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
+				scene.add( ambientLight );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 3 );
+				light.position.set( 100, 100, 50 );
+				light.castShadow = true;
+				const dLight = 200;
+				const sLight = dLight * 0.25;
+				light.shadow.camera.left = - sLight;
+				light.shadow.camera.right = sLight;
+				light.shadow.camera.top = sLight;
+				light.shadow.camera.bottom = - sLight;
+
+				light.shadow.camera.near = dLight / 30;
+				light.shadow.camera.far = dLight;
+
+				light.shadow.mapSize.x = 1024 * 2;
+				light.shadow.mapSize.y = 1024 * 2;
+
+				scene.add( light );
+
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function initPhysics() {
+
+				// Physics configuration
+
+				collisionConfiguration = new Ammo.btDefaultCollisionConfiguration();
+				dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
+				broadphase = new Ammo.btDbvtBroadphase();
+				solver = new Ammo.btSequentialImpulseConstraintSolver();
+				physicsWorld = new Ammo.btDiscreteDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration );
+				physicsWorld.setGravity( new Ammo.btVector3( 0, - 6, 0 ) );
+
+				// Create the terrain body
+
+				const groundShape = createTerrainShape();
+				const groundTransform = new Ammo.btTransform();
+				groundTransform.setIdentity();
+				// Shifts the terrain, since bullet re-centers it on its bounding box.
+				groundTransform.setOrigin( new Ammo.btVector3( 0, ( terrainMaxHeight + terrainMinHeight ) / 2, 0 ) );
+				const groundMass = 0;
+				const groundLocalInertia = new Ammo.btVector3( 0, 0, 0 );
+				const groundMotionState = new Ammo.btDefaultMotionState( groundTransform );
+				const groundBody = new Ammo.btRigidBody( new Ammo.btRigidBodyConstructionInfo( groundMass, groundMotionState, groundShape, groundLocalInertia ) );
+				physicsWorld.addRigidBody( groundBody );
+
+				transformAux1 = new Ammo.btTransform();
+
+			}
+
+			function generateHeight( width, depth, minHeight, maxHeight ) {
+
+				// Generates the height data (a sinus wave)
+
+				const size = width * depth;
+				const data = new Float32Array( size );
+
+				const hRange = maxHeight - minHeight;
+				const w2 = width / 2;
+				const d2 = depth / 2;
+				const phaseMult = 12;
+
+				let p = 0;
+
+				for ( let j = 0; j < depth; j ++ ) {
+
+					for ( let i = 0; i < width; i ++ ) {
+
+						const radius = Math.sqrt(
+							Math.pow( ( i - w2 ) / w2, 2.0 ) +
+								Math.pow( ( j - d2 ) / d2, 2.0 ) );
+
+						const height = ( Math.sin( radius * phaseMult ) + 1 ) * 0.5 * hRange + minHeight;
+
+						data[ p ] = height;
+
+						p ++;
+
+					}
+
+				}
+
+				return data;
+
+			}
+
+			function createTerrainShape() {
+
+				// This parameter is not really used, since we are using PHY_FLOAT height data type and hence it is ignored
+				const heightScale = 1;
+
+				// Up axis = 0 for X, 1 for Y, 2 for Z. Normally 1 = Y is used.
+				const upAxis = 1;
+
+				// hdt, height data type. "PHY_FLOAT" is used. Possible values are "PHY_FLOAT", "PHY_UCHAR", "PHY_SHORT"
+				const hdt = 'PHY_FLOAT';
+
+				// Set this to your needs (inverts the triangles)
+				const flipQuadEdges = false;
+
+				// Creates height data buffer in Ammo heap
+				ammoHeightData = Ammo._malloc( 4 * terrainWidth * terrainDepth );
+
+				// Copy the javascript height data array to the Ammo one.
+				let p = 0;
+				let p2 = 0;
+
+				for ( let j = 0; j < terrainDepth; j ++ ) {
+
+					for ( let i = 0; i < terrainWidth; i ++ ) {
+
+						// write 32-bit float data to memory
+						Ammo.HEAPF32[ ammoHeightData + p2 >> 2 ] = heightData[ p ];
+
+						p ++;
+
+						// 4 bytes/float
+						p2 += 4;
+
+					}
+
+				}
+
+				// Creates the heightfield physics shape
+				const heightFieldShape = new Ammo.btHeightfieldTerrainShape(
+					terrainWidth,
+					terrainDepth,
+					ammoHeightData,
+					heightScale,
+					terrainMinHeight,
+					terrainMaxHeight,
+					upAxis,
+					hdt,
+					flipQuadEdges
+				);
+
+				// Set horizontal scale
+				const scaleX = terrainWidthExtents / ( terrainWidth - 1 );
+				const scaleZ = terrainDepthExtents / ( terrainDepth - 1 );
+				heightFieldShape.setLocalScaling( new Ammo.btVector3( scaleX, 1, scaleZ ) );
+
+				heightFieldShape.setMargin( 0.05 );
+
+				return heightFieldShape;
+
+			}
+
+			function generateObject() {
+
+				const numTypes = 4;
+				const objectType = Math.ceil( Math.random() * numTypes );
+
+				let threeObject = null;
+				let shape = null;
+
+				const objectSize = 3;
+				const margin = 0.05;
+
+				let radius, height;
+
+				switch ( objectType ) {
+
+					case 1:
+						// Sphere
+						radius = 1 + Math.random() * objectSize;
+						threeObject = new THREE.Mesh( new THREE.SphereGeometry( radius, 20, 20 ), createObjectMaterial() );
+						shape = new Ammo.btSphereShape( radius );
+						shape.setMargin( margin );
+						break;
+					case 2:
+						// Box
+						const sx = 1 + Math.random() * objectSize;
+						const sy = 1 + Math.random() * objectSize;
+						const sz = 1 + Math.random() * objectSize;
+						threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), createObjectMaterial() );
+						shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
+						shape.setMargin( margin );
+						break;
+					case 3:
+						// Cylinder
+						radius = 1 + Math.random() * objectSize;
+						height = 1 + Math.random() * objectSize;
+						threeObject = new THREE.Mesh( new THREE.CylinderGeometry( radius, radius, height, 20, 1 ), createObjectMaterial() );
+						shape = new Ammo.btCylinderShape( new Ammo.btVector3( radius, height * 0.5, radius ) );
+						shape.setMargin( margin );
+						break;
+					default:
+						// Cone
+						radius = 1 + Math.random() * objectSize;
+						height = 2 + Math.random() * objectSize;
+						threeObject = new THREE.Mesh( new THREE.ConeGeometry( radius, height, 20, 2 ), createObjectMaterial() );
+						shape = new Ammo.btConeShape( radius, height );
+						break;
+
+				}
+
+				threeObject.position.set( ( Math.random() - 0.5 ) * terrainWidth * 0.6, terrainMaxHeight + objectSize + 2, ( Math.random() - 0.5 ) * terrainDepth * 0.6 );
+
+				const mass = objectSize * 5;
+				const localInertia = new Ammo.btVector3( 0, 0, 0 );
+				shape.calculateLocalInertia( mass, localInertia );
+				const transform = new Ammo.btTransform();
+				transform.setIdentity();
+				const pos = threeObject.position;
+				transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+				const motionState = new Ammo.btDefaultMotionState( transform );
+				const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, shape, localInertia );
+				const body = new Ammo.btRigidBody( rbInfo );
+
+				threeObject.userData.physicsBody = body;
+
+				threeObject.receiveShadow = true;
+				threeObject.castShadow = true;
+
+				scene.add( threeObject );
+				dynamicObjects.push( threeObject );
+
+				physicsWorld.addRigidBody( body );
+
+
+
+			}
+
+			function createObjectMaterial() {
+
+				const c = Math.floor( Math.random() * ( 1 << 24 ) );
+				return new THREE.MeshPhongMaterial( { color: c } );
+
+			}
+
+			function animate() {
+
+				timer.update();
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const deltaTime = timer.getDelta();
+
+				if ( dynamicObjects.length < maxNumObjects && time > timeNextSpawn ) {
+
+					generateObject();
+					timeNextSpawn = time + objectTimePeriod;
+
+				}
+
+				updatePhysics( deltaTime );
+
+				renderer.render( scene, camera );
+
+				time += deltaTime;
+
+			}
+
+			function updatePhysics( deltaTime ) {
+
+				physicsWorld.stepSimulation( deltaTime, 10 );
+
+				// Update objects
+				for ( let i = 0, il = dynamicObjects.length; i < il; i ++ ) {
+
+					const objThree = dynamicObjects[ i ];
+					const objPhys = objThree.userData.physicsBody;
+					const ms = objPhys.getMotionState();
+					if ( ms ) {
+
+						ms.getWorldTransform( transformAux1 );
+						const p = transformAux1.getOrigin();
+						const q = transformAux1.getRotation();
+						objThree.position.set( p.x(), p.y(), p.z() );
+						objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
+
+					}
+
+				}
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 515 - 0
examples/physics_ammo_volume.html

@@ -0,0 +1,515 @@
+<html lang="en">
+	<head>
+		<title>Ammo.js softbody volume demo</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<meta property="og:title" content="Ammo.js softbody volume demo">
+		<meta property="og:type" content="website">
+		<meta property="og:url" content="https://threejs.org/examples/physics_ammo_volume.html">
+		<meta property="og:image" content="https://threejs.org/examples/screenshots/physics_ammo_volume.jpg">
+		<link type="text/css" rel="stylesheet" href="main.css">
+		<style>
+			body {
+				color: #333;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="info">
+			Ammo.js physics soft body volume demo<br/>
+			Click to throw a ball
+		</div>
+		<div id="container"></div>
+
+		<script src="https://cdn.jsdelivr.net/gh/kripken/ammo.js@79190a1f03845794b1bba1777f30037349967658/builds/ammo.wasm.js"></script>
+
+		<script type="importmap">
+			{
+				"imports": {
+					"three": "../build/three.module.js",
+					"three/addons/": "./jsm/"
+				}
+			}
+		</script>
+
+		<script type="module">
+
+			import * as THREE from 'three';
+
+			import Stats from 'three/addons/libs/stats.module.js';
+
+			import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
+			import * as BufferGeometryUtils from 'three/addons/utils/BufferGeometryUtils.js';
+
+			// Graphics variables
+			let container, stats;
+			let camera, controls, scene, renderer;
+			let textureLoader;
+			const timer = new THREE.Timer();
+			timer.connect( document );
+			let clickRequest = false;
+			const mouseCoords = new THREE.Vector2();
+			const raycaster = new THREE.Raycaster();
+			const ballMaterial = new THREE.MeshPhongMaterial( { color: 0x202020 } );
+			const pos = new THREE.Vector3();
+			const quat = new THREE.Quaternion();
+
+			// Physics variables
+			const gravityConstant = - 9.8;
+			let physicsWorld;
+			const rigidBodies = [];
+			const softBodies = [];
+			const margin = 0.05;
+			let transformAux1;
+			let softBodyHelpers;
+
+			Ammo().then( function ( AmmoLib ) {
+
+				Ammo = AmmoLib;
+
+				init();
+
+			} );
+
+			function init() {
+
+				initGraphics();
+
+				initPhysics();
+
+				createObjects();
+
+				initInput();
+
+			}
+
+			function initGraphics() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 60, window.innerWidth / window.innerHeight, 0.2, 2000 );
+
+				scene = new THREE.Scene();
+				scene.background = new THREE.Color( 0xbfd1e5 );
+
+				camera.position.set( - 7, 5, 8 );
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				renderer.setAnimationLoop( animate );
+				renderer.shadowMap.enabled = true;
+				container.appendChild( renderer.domElement );
+
+				controls = new OrbitControls( camera, renderer.domElement );
+				controls.target.set( 0, 2, 0 );
+				controls.update();
+
+				textureLoader = new THREE.TextureLoader();
+
+				const ambientLight = new THREE.AmbientLight( 0xbbbbbb );
+				scene.add( ambientLight );
+
+				const light = new THREE.DirectionalLight( 0xffffff, 3 );
+				light.position.set( - 10, 10, 5 );
+				light.castShadow = true;
+				const d = 20;
+				light.shadow.camera.left = - d;
+				light.shadow.camera.right = d;
+				light.shadow.camera.top = d;
+				light.shadow.camera.bottom = - d;
+
+				light.shadow.camera.near = 2;
+				light.shadow.camera.far = 50;
+
+				light.shadow.mapSize.x = 1024;
+				light.shadow.mapSize.y = 1024;
+
+				scene.add( light );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+
+				window.addEventListener( 'resize', onWindowResize );
+
+			}
+
+			function initPhysics() {
+
+				// Physics configuration
+
+				const collisionConfiguration = new Ammo.btSoftBodyRigidBodyCollisionConfiguration();
+				const dispatcher = new Ammo.btCollisionDispatcher( collisionConfiguration );
+				const broadphase = new Ammo.btDbvtBroadphase();
+				const solver = new Ammo.btSequentialImpulseConstraintSolver();
+				const softBodySolver = new Ammo.btDefaultSoftBodySolver();
+				physicsWorld = new Ammo.btSoftRigidDynamicsWorld( dispatcher, broadphase, solver, collisionConfiguration, softBodySolver );
+				physicsWorld.setGravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+				physicsWorld.getWorldInfo().set_m_gravity( new Ammo.btVector3( 0, gravityConstant, 0 ) );
+
+				transformAux1 = new Ammo.btTransform();
+				softBodyHelpers = new Ammo.btSoftBodyHelpers();
+
+			}
+
+			function createObjects() {
+
+				// Ground
+				pos.set( 0, - 0.5, 0 );
+				quat.set( 0, 0, 0, 1 );
+				const ground = createParalellepiped( 40, 1, 40, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
+				ground.castShadow = true;
+				ground.receiveShadow = true;
+				textureLoader.load( 'textures/grid.png', function ( texture ) {
+
+					texture.colorSpace = THREE.SRGBColorSpace;
+					texture.wrapS = THREE.RepeatWrapping;
+					texture.wrapT = THREE.RepeatWrapping;
+					texture.repeat.set( 40, 40 );
+					ground.material.map = texture;
+					ground.material.needsUpdate = true;
+
+				} );
+
+				// Create soft volumes
+				const volumeMass = 15;
+
+				const sphereGeometry = new THREE.SphereGeometry( 1.5, 40, 25 );
+				sphereGeometry.translate( 5, 5, 0 );
+				createSoftVolume( sphereGeometry, volumeMass, 250 );
+
+				const boxGeometry = new THREE.BoxGeometry( 1, 1, 5, 4, 4, 20 );
+				boxGeometry.translate( - 2, 5, 0 );
+				createSoftVolume( boxGeometry, volumeMass, 120 );
+
+				// Ramp
+				pos.set( 3, 1, 0 );
+				quat.setFromAxisAngle( new THREE.Vector3( 0, 0, 1 ), 30 * Math.PI / 180 );
+				const obstacle = createParalellepiped( 10, 1, 4, 0, pos, quat, new THREE.MeshPhongMaterial( { color: 0x606060 } ) );
+				obstacle.castShadow = true;
+				obstacle.receiveShadow = true;
+
+			}
+
+			function processGeometry( bufGeometry ) {
+
+				// Ony consider the position values when merging the vertices
+				const posOnlyBufGeometry = new THREE.BufferGeometry();
+				posOnlyBufGeometry.setAttribute( 'position', bufGeometry.getAttribute( 'position' ) );
+				posOnlyBufGeometry.setIndex( bufGeometry.getIndex() );
+
+				// Merge the vertices so the triangle soup is converted to indexed triangles
+				const indexedBufferGeom = BufferGeometryUtils.mergeVertices( posOnlyBufGeometry );
+
+				// Create index arrays mapping the indexed vertices to bufGeometry vertices
+				mapIndices( bufGeometry, indexedBufferGeom );
+
+			}
+
+			function isEqual( x1, y1, z1, x2, y2, z2 ) {
+
+				const delta = 0.000001;
+				return Math.abs( x2 - x1 ) < delta &&
+						Math.abs( y2 - y1 ) < delta &&
+						Math.abs( z2 - z1 ) < delta;
+
+			}
+
+			function mapIndices( bufGeometry, indexedBufferGeom ) {
+
+				// Creates ammoVertices, ammoIndices and ammoIndexAssociation in bufGeometry
+
+				const vertices = bufGeometry.attributes.position.array;
+				const idxVertices = indexedBufferGeom.attributes.position.array;
+				const indices = indexedBufferGeom.index.array;
+
+				const numIdxVertices = idxVertices.length / 3;
+				const numVertices = vertices.length / 3;
+
+				bufGeometry.ammoVertices = idxVertices;
+				bufGeometry.ammoIndices = indices;
+				bufGeometry.ammoIndexAssociation = [];
+
+				for ( let i = 0; i < numIdxVertices; i ++ ) {
+
+					const association = [];
+					bufGeometry.ammoIndexAssociation.push( association );
+
+					const i3 = i * 3;
+
+					for ( let j = 0; j < numVertices; j ++ ) {
+
+						const j3 = j * 3;
+						if ( isEqual( idxVertices[ i3 ], idxVertices[ i3 + 1 ], idxVertices[ i3 + 2 ],
+							vertices[ j3 ], vertices[ j3 + 1 ], vertices[ j3 + 2 ] ) ) {
+
+							association.push( j3 );
+
+						}
+
+					}
+
+				}
+
+			}
+
+			function createSoftVolume( bufferGeom, mass, pressure ) {
+
+				processGeometry( bufferGeom );
+
+				const volume = new THREE.Mesh( bufferGeom, new THREE.MeshPhongMaterial( { color: 0xFFFFFF } ) );
+				volume.castShadow = true;
+				volume.receiveShadow = true;
+				volume.frustumCulled = false;
+				scene.add( volume );
+
+				textureLoader.load( 'textures/colors.png', function ( texture ) {
+
+					volume.material.map = texture;
+					volume.material.needsUpdate = true;
+
+				} );
+
+				// Volume physic object
+
+				const volumeSoftBody = softBodyHelpers.CreateFromTriMesh(
+					physicsWorld.getWorldInfo(),
+					bufferGeom.ammoVertices,
+					bufferGeom.ammoIndices,
+					bufferGeom.ammoIndices.length / 3,
+					true );
+
+				const sbConfig = volumeSoftBody.get_m_cfg();
+				sbConfig.set_viterations( 40 );
+				sbConfig.set_piterations( 40 );
+
+				// Soft-soft and soft-rigid collisions
+				sbConfig.set_collisions( 0x11 );
+
+				// Friction
+				sbConfig.set_kDF( 0.1 );
+				// Damping
+				sbConfig.set_kDP( 0.01 );
+				// Pressure
+				sbConfig.set_kPR( pressure );
+				// Stiffness
+				volumeSoftBody.get_m_materials().at( 0 ).set_m_kLST( 0.9 );
+				volumeSoftBody.get_m_materials().at( 0 ).set_m_kAST( 0.9 );
+
+				volumeSoftBody.setTotalMass( mass, false );
+				Ammo.castObject( volumeSoftBody, Ammo.btCollisionObject ).getCollisionShape().setMargin( margin );
+				physicsWorld.addSoftBody( volumeSoftBody, 1, - 1 );
+				volume.userData.physicsBody = volumeSoftBody;
+				// Disable deactivation
+				volumeSoftBody.setActivationState( 4 );
+
+				softBodies.push( volume );
+
+			}
+
+			function createParalellepiped( sx, sy, sz, mass, pos, quat, material ) {
+
+				const threeObject = new THREE.Mesh( new THREE.BoxGeometry( sx, sy, sz, 1, 1, 1 ), material );
+				const shape = new Ammo.btBoxShape( new Ammo.btVector3( sx * 0.5, sy * 0.5, sz * 0.5 ) );
+				shape.setMargin( margin );
+
+				createRigidBody( threeObject, shape, mass, pos, quat );
+
+				return threeObject;
+
+			}
+
+			function createRigidBody( threeObject, physicsShape, mass, pos, quat ) {
+
+				threeObject.position.copy( pos );
+				threeObject.quaternion.copy( quat );
+
+				const transform = new Ammo.btTransform();
+				transform.setIdentity();
+				transform.setOrigin( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+				transform.setRotation( new Ammo.btQuaternion( quat.x, quat.y, quat.z, quat.w ) );
+				const motionState = new Ammo.btDefaultMotionState( transform );
+
+				const localInertia = new Ammo.btVector3( 0, 0, 0 );
+				physicsShape.calculateLocalInertia( mass, localInertia );
+
+				const rbInfo = new Ammo.btRigidBodyConstructionInfo( mass, motionState, physicsShape, localInertia );
+				const body = new Ammo.btRigidBody( rbInfo );
+
+				threeObject.userData.physicsBody = body;
+
+				scene.add( threeObject );
+
+				if ( mass > 0 ) {
+
+					rigidBodies.push( threeObject );
+
+					// Disable deactivation
+					body.setActivationState( 4 );
+
+				}
+
+				physicsWorld.addRigidBody( body );
+
+				return body;
+
+			}
+
+			function initInput() {
+
+				window.addEventListener( 'pointerdown', function ( event ) {
+
+					if ( ! clickRequest ) {
+
+						mouseCoords.set(
+							( event.clientX / window.innerWidth ) * 2 - 1,
+							- ( event.clientY / window.innerHeight ) * 2 + 1
+						);
+
+						clickRequest = true;
+
+					}
+
+				} );
+
+			}
+
+			function processClick() {
+
+				if ( clickRequest ) {
+
+					raycaster.setFromCamera( mouseCoords, camera );
+
+					// Creates a ball
+					const ballMass = 3;
+					const ballRadius = 0.4;
+
+					const ball = new THREE.Mesh( new THREE.SphereGeometry( ballRadius, 18, 16 ), ballMaterial );
+					ball.castShadow = true;
+					ball.receiveShadow = true;
+					const ballShape = new Ammo.btSphereShape( ballRadius );
+					ballShape.setMargin( margin );
+					pos.copy( raycaster.ray.direction );
+					pos.add( raycaster.ray.origin );
+					quat.set( 0, 0, 0, 1 );
+					const ballBody = createRigidBody( ball, ballShape, ballMass, pos, quat );
+					ballBody.setFriction( 0.5 );
+
+					pos.copy( raycaster.ray.direction );
+					pos.multiplyScalar( 14 );
+					ballBody.setLinearVelocity( new Ammo.btVector3( pos.x, pos.y, pos.z ) );
+
+					clickRequest = false;
+
+				}
+
+			}
+
+			function onWindowResize() {
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function animate() {
+
+				timer.update();
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				const deltaTime = timer.getDelta();
+
+				updatePhysics( deltaTime );
+
+				processClick();
+
+				renderer.render( scene, camera );
+
+			}
+
+			function updatePhysics( deltaTime ) {
+
+				// Step world
+				physicsWorld.stepSimulation( deltaTime, 10 );
+
+				// Update soft volumes
+				for ( let i = 0, il = softBodies.length; i < il; i ++ ) {
+
+					const volume = softBodies[ i ];
+					const geometry = volume.geometry;
+					const softBody = volume.userData.physicsBody;
+					const volumePositions = geometry.attributes.position.array;
+					const volumeNormals = geometry.attributes.normal.array;
+					const association = geometry.ammoIndexAssociation;
+					const numVerts = association.length;
+					const nodes = softBody.get_m_nodes();
+					for ( let j = 0; j < numVerts; j ++ ) {
+
+						const node = nodes.at( j );
+						const nodePos = node.get_m_x();
+						const x = nodePos.x();
+						const y = nodePos.y();
+						const z = nodePos.z();
+						const nodeNormal = node.get_m_n();
+						const nx = nodeNormal.x();
+						const ny = nodeNormal.y();
+						const nz = nodeNormal.z();
+
+						const assocVertex = association[ j ];
+
+						for ( let k = 0, kl = assocVertex.length; k < kl; k ++ ) {
+
+							let indexVertex = assocVertex[ k ];
+							volumePositions[ indexVertex ] = x;
+							volumeNormals[ indexVertex ] = nx;
+							indexVertex ++;
+							volumePositions[ indexVertex ] = y;
+							volumeNormals[ indexVertex ] = ny;
+							indexVertex ++;
+							volumePositions[ indexVertex ] = z;
+							volumeNormals[ indexVertex ] = nz;
+
+						}
+
+					}
+
+					geometry.attributes.position.needsUpdate = true;
+					geometry.attributes.normal.needsUpdate = true;
+
+				}
+
+				// Update rigid bodies
+				for ( let i = 0, il = rigidBodies.length; i < il; i ++ ) {
+
+					const objThree = rigidBodies[ i ];
+					const objPhys = objThree.userData.physicsBody;
+					const ms = objPhys.getMotionState();
+					if ( ms ) {
+
+						ms.getWorldTransform( transformAux1 );
+						const p = transformAux1.getOrigin();
+						const q = transformAux1.getRotation();
+						objThree.position.set( p.x(), p.y(), p.z() );
+						objThree.quaternion.set( q.x(), q.y(), q.z(), q.w() );
+
+					}
+
+				}
+
+			}
+
+		</script>
+
+	</body>
+</html>

BIN
examples/screenshots/physics_ammo_break.jpg


BIN
examples/screenshots/physics_ammo_cloth.jpg


BIN
examples/screenshots/physics_ammo_instancing.jpg


BIN
examples/screenshots/physics_ammo_rope.jpg


BIN
examples/screenshots/physics_ammo_terrain.jpg


BIN
examples/screenshots/physics_ammo_volume.jpg


+ 6 - 0
examples/tags.json

@@ -7,6 +7,12 @@
 	"misc_controls_transform": [ "scale", "rotate", "translate" ],
 	"misc_exporter_gcode": [ "community" ],
 	"misc_raycaster_helper": [ "community" ],
+	"physics_ammo_break": [ "community" ],
+	"physics_ammo_cloth": [ "integration", "community" ],
+	"physics_ammo_instancing": [ "community" ],
+	"physics_ammo_rope": [ "community" ],
+	"physics_ammo_terrain": [ "community" ],
+	"physics_ammo_volume": [ "community" ],
 	"physics_jolt_instancing": [ "community" ],
 	"physics_rapier_instancing": [ "community" ],
 	"physics_rapier_basic": [ "community" ],

+ 13 - 0
manual/en/physics.html

@@ -71,6 +71,7 @@
                 </p>
 
                 <ul>
+                    <li><b>AmmoPhysics:</b> A wrapper for Ammo.js (Bullet Physics).</li>
                     <li><b>JoltPhysics:</b> A wrapper for Jolt Physics.</li>
                     <li><b>RapierPhysics:</b> A wrapper for Rapier.</li>
                 </ul>
@@ -84,6 +85,7 @@
                     Examples
                 </h4>
                 <ul>
+                    <li><a href="https://threejs.org/examples/physics_ammo_instancing.html" target="_blank">physics / ammo / instancing</a></li>
                     <li><a href="https://threejs.org/examples/physics_jolt_instancing.html" target="_blank">physics / jolt / instancing</a></li>
                     <li><a href="https://threejs.org/examples/physics_rapier_instancing.html" target="_blank">physics / rapier / instancing</a></li>
                 </ul>
@@ -132,6 +134,17 @@
                     to handle the WASM memory management and interaction with the physics API directly.
                 </p>
 
+                <h4>
+                    Examples
+                </h4>
+                <ul>
+                    <li><a href="https://threejs.org/examples/physics_ammo_break.html" target="_blank">physics / ammo / break</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_cloth.html" target="_blank">physics / ammo / cloth</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_rope.html" target="_blank">physics / ammo / rope</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_terrain.html" target="_blank">physics / ammo / terrain</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_volume.html" target="_blank">physics / ammo / volume</a></li>
+                </ul>
+
                 <h4>
                     Projects
                 </h4>

+ 13 - 0
manual/zh/physics.html

@@ -70,6 +70,7 @@
                 </p>
 
                 <ul>
+                    <li><b>AmmoPhysics:</b>Ammo.js(Bullet 物理)的封装。</li>
                     <li><b>JoltPhysics:</b>Jolt Physics 的封装。</li>
                     <li><b>RapierPhysics:</b>Rapier 的封装。</li>
                 </ul>
@@ -82,6 +83,7 @@
                     示例
                 </h4>
                 <ul>
+                    <li><a href="https://threejs.org/examples/physics_ammo_instancing.html" target="_blank">physics / ammo / instancing</a></li>
                     <li><a href="https://threejs.org/examples/physics_jolt_instancing.html" target="_blank">physics / jolt / instancing</a></li>
                     <li><a href="https://threejs.org/examples/physics_rapier_instancing.html" target="_blank">physics / rapier / instancing</a></li>
                 </ul>
@@ -129,6 +131,17 @@
                     需要处理 WASM 内存管理及与物理 API 的直接交互。
                 </p>
 
+                <h4>
+                    示例
+                </h4>
+                <ul>
+                    <li><a href="https://threejs.org/examples/physics_ammo_break.html" target="_blank">physics / ammo / break</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_cloth.html" target="_blank">physics / ammo / cloth</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_rope.html" target="_blank">physics / ammo / rope</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_terrain.html" target="_blank">physics / ammo / terrain</a></li>
+                    <li><a href="https://threejs.org/examples/physics_ammo_volume.html" target="_blank">physics / ammo / volume</a></li>
+                </ul>
+
                 <h4>
                     项目
                 </h4>

粤ICP备19079148号