Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ export default [
GPUTexture: 'readonly',
GPUMapMode: 'readonly',
QUnit: 'readonly',
Ammo: 'readonly',
XRRigidTransform: 'readonly',
XRMediaBinding: 'readonly',
CodeMirror: 'readonly',
Expand Down
6 changes: 6 additions & 0 deletions examples/files.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
1 change: 1 addition & 0 deletions examples/jsm/Addons.js
Original file line number Diff line number Diff line change
Expand Up @@ -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';

Expand Down
366 changes: 366 additions & 0 deletions examples/jsm/physics/AmmoPhysics.js
Original file line number Diff line number Diff line change
@@ -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 };
Loading