author Yannick Deubel (https://github.com/yandeu)
 *  copyright license https import { logger } from '@enable3d/common/dist/logger' ; import PhysicsBody from '@enable3d/common/dist/physicsBody' ; import { ExtendedObject3D } from '@enable3d/common/dist/extendedObject3D' ; export { ExtendedObject3D } ; import { ExtendedMesh } from '@enable3d/common/dist/extendedMesh' ; export { ExtendedMesh } ; import { ExtendedGroup } from '@enable3d/common/dist/extendedGroup' ; export { ExtendedGroup } ; import Shapes from './shapes' ; import Constraints from './constraints' ; import { Events } from '@yandeu/events' ; import { Geometry } from './externals' ; import { BufferGeometry , Euler , Matrix4 , Quaternion , REVISION , Scene , Vector3 } from 'three' ; const { createHACDShapes , createHullShape , createTriMeshShape , createVHACDShapes , iterateGeometries } = require ( './three-to-ammo' ) ; import { createTorusShape } from './torusShape' ; import Factories from '@enable3d/common/dist/factories' ; import { CollisionEvents } from './collisionEvents' ; import DebugDrawer from './debugDrawer' ; import { ConvexObjectBreaker } from './convexObjectBreaker' ; import { PhysicsLoader } from '@enable3d/common/dist/physicsLoader' ; import DefaultMaterial from '@enable3d/common/dist/defaultMaterial' ; export { PhysicsLoader } ; import * as Types from '@enable3d/common/dist/types' ; import { AllHitsRaycaster , ClosestRaycaster } from './raycaster/raycaster' ; export { ClosestRaycaster , AllHitsRaycaster } ; export { Types } ; export { Clock } from './lib/Clock' ; class AmmoPhysics extends Events { public worldTransform ! : Ammo . btTransform ; public factory ! : Factories ; public isHeadless : boolean ; public rigidBodies : ExtendedObject3D [ ] = [ ] ; protected earlierDetectedCollisions : { combinedName : string ; collision : boolean ; } [ ] = [ ] ; protected gravity : { x : number ; y : number ; z : number ; } ; protected tmpEuler : Euler ; protected tmpQuaternion : Quaternion ; protected tmpVector3 : Vector3 ; protected tmpVector3a : Vector3 ; protected tmpMatrix4 : Matrix4 ; protected tmpMatrix4a : Matrix4 ; protected tmpBtVector3 : Ammo . btVector3 ; protected tmpBtQuaternion : Ammo . btQuaternion ; public physicsWorld ! : Ammo . btSoftRigidDynamicsWorld ; protected debugDrawer ! : DebugDrawer ; private convexBreaker : any ; protected addRigidBody ! : ( threeObject : ExtendedObject3D , physicsShape : any , mass : any , pos : any , quat : any ) => void ; private objectsToRemove ! : any [ ] ; private numObjectsToRemove ! : number ; protected emptyV3 : Vector3 ; protected impactPoint : Vector3 ; protected impactNormal : Vector3 ; protected defaultMaterial ! : DefaultMaterial ; private shapes ! : Shapes ; private constraints ! : Constraints ; public collisionEvents ! : CollisionEvents ; private readonly complexShapes = [ 'plane' , 'hull' , 'hacd' , 'vhacd' , 'convexMesh' , 'concaveMesh' ] ; constructor ( public scene , public config = { } ) { super ( ) ; this . gravity = config . gravity || { x : 0 , y : - 9.81 , z : 0 } ; this . isHeadless = scene === 'headless' ? true : false ; this . tmpEuler = new Euler ( ) ; this . tmpQuaternion = new Quaternion ( ) ; this . tmpVector3 = new Vector3 ( ) ; this . tmpVector3a = new Vector3 ( ) ; this . tmpMatrix4 = new Matrix4 ( ) ; this . tmpMatrix4a = new Matrix4 ( ) ; this . tmpBtVector3 = new Ammo . btVector3 ( ) ; this . tmpBtQuaternion = new Ammo . btQuaternion ( 0 , 0 , 0 , 1 ) ; this . emptyV3 = new Vector3 ( ) ; this . impactPoint = new Vector3 ( ) ; this . impactNormal = new Vector3 ( ) ; if ( scene !== 'headless' ) { this . defaultMaterial = new DefaultMaterial ( ) ; } this . start ( ) ; } deprecated get tmpTrans ( ) { console . warn ( 'Use worldTransform instead of tmpTrans.' ) ; return this . worldTransform ; } deprecated set tmpTrans ( transform ) { console . warn ( 'Use worldTransform instead of tmpTrans.' ) ; this . worldTransform = transform ; } /** Destroys a physics body. */ public destroy ( body ) { const b = Object . keys ( body ) . includes ( 'body' ) ? body . body : body ; if ( typeof b ?. ammo === 'undefined' ) return ; let obj = b . ammo . threeObject as ExtendedObject3D ; const name = obj . name ; if ( name && obj ) { if ( obj ?. body ?. ammo ) { ! obj . body . isSoftBody ? this . physicsWorld . removeRigidBody ( obj . body . ammo ) : this . physicsWorld . removeSoftBody ( obj . body . ammo as any ) ; obj . body . destructor ( ) ; obj . body = undefined ; obj . hasBody = false ; delete b . ammo . threeObject ; for ( let i = 0 ; i < this . rigidBodies . length ; i ++ ) { if ( this . rigidBodies [ i ] . name === name ) { this . rigidBodies . splice ( i , 1 ) ; i -- ; } } } } if ( this . scene === 'headless' && obj ) obj = null ; } protected setup ( ) { this . worldTransform = new Ammo . btTransform ( ) ; if ( typeof this . config . setupPhysicsWorld === 'function' ) this . physicsWorld = this . config . setupPhysicsWorld ( ) as any ; else this . physicsWorld = this . setupPhysicsWorld ( ) ; if ( this . scene !== 'headless' ) { this . convexBreaker = new ConvexObjectBreaker ( ) ; this . objectsToRemove = [ ] ; this . numObjectsToRemove = 0 ; for ( let i = 0 ; i < 500 ; i ++ ) { this . objectsToRemove [ i ] = null ; } } this . collisionEvents = new CollisionEvents ( ) ; this . factory = new Factories ( this . scene ) ; this . shapes = new Shapes ( this . factory , ( object , config ? ) => this . addExisting ( object , config ) ) ; this . constraints = new Constraints ( this . worldTransform , this . physicsWorld ) ; if ( this . scene !== 'headless' ) this . debugDrawer = new DebugDrawer ( this . scene , this . physicsWorld , { } ) ; } public updateDebugger ( ) { if ( this . scene === 'headless' ) return ; if ( this . debugDrawer && this . debugDrawer . enabled ) this . debugDrawer . update ( ) ; } protected setupPhysicsWorld ( ) { const g = this . gravity ; const { softBodies = false } = this . config ; let physicsWorld ; if ( ! softBodies ) { const 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 ) ; } if ( softBodies ) { const 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 ( g . x , g . y , g . z ) ) ; return physicsWorld ; } private createDebrisFromBreakableObject ( object , parent ) { if ( this . scene === 'headless' ) return ; object . material = parent . material ; object . shape = 'hull' ; object . fragmentDepth = parent . fragmentDepth + 1 ; object . name = `${ parent . name }__DEBRIS_${ object . id }` ; this . scene . add ( object ) ; this . addExisting ( object , { autoCenter : true } ) ; object . body . fractureImpulse = parent . body . fractureImpulse ; object . body . breakable = false ; setTimeout ( ( ) => { object . body . breakable = true ; } , 2500 ) ; } private removeDebris ( object ) { if ( this . scene === 'headless' ) return ; this . scene . remove ( object ) ; this . destroy ( object ) ; } public update ( delta ) { this . updatePhysics ( delta ) ; this . detectCollisions ( ) ; } private updatePhysics ( delta ) { const deltaTime = delta / 1000 ; this . physicsWorld . stepSimulation ( deltaTime , this . config . maxSubSteps || 4 , this . config . fixedTimeStep || 1 / 60 ) ; for ( let i = 0 ; i < this . rigidBodies . length ; i ++ ) { const objThree = this . rigidBodies [ i ] ; const objPhys = objThree . body . ammo ; const ms = objPhys . getMotionState ( ) ; if ( ms ) { ms . getWorldTransform ( this . worldTransform ) ; if ( objThree . body . didUpdate ) { if ( objThree . body . _emitUpdateEvents ) objThree . body . eventEmitter . emit ( 'update' ) ; objThree . body . didUpdate = false ; } if ( objThree . body . ammo . isKinematicObject ( ) && objThree . body . needUpdate ) { objThree . getWorldQuaternion ( this . tmpQuaternion ) ; objThree . getWorldPosition ( this . tmpVector3 ) ; this . tmpBtVector3 . setValue ( this . tmpVector3 . x , this . tmpVector3 . y , this . tmpVector3 . z ) ; this . tmpBtQuaternion . setValue ( this . tmpQuaternion . x , this . tmpQuaternion . y , this . tmpQuaternion . z , this . tmpQuaternion . w ) ; this . worldTransform . setOrigin ( this . tmpBtVector3 ) ; this . worldTransform . setRotation ( this . tmpBtQuaternion ) ; ms . setWorldTransform ( this . worldTransform ) ; objThree . body . needUpdate = false ; } else if ( objThree . body . skipUpdate ) { } else if ( ! objThree . body . ammo . isStaticObject ( ) ) { const p = this . worldTransform . getOrigin ( ) ; const q = this . worldTransform . getRotation ( ) ; const o = objThree . body . offset ; if ( objThree . body . ignoreScale ) { this . tmpVector3a . set ( objThree . scale . x , objThree . scale . y , objThree . scale . z ) ; } else { const scale = objThree . body . ammo . getCollisionShape ( ) . getLocalScaling ( ) ; this . tmpVector3a . set ( scale . x ( ) , scale . y ( ) , scale . z ( ) ) ; } this . tmpVector3 . set ( p . x ( ) + o . x , p . y ( ) + o . y , p . z ( ) + o . z ) ; this . tmpQuaternion . set ( q . x ( ) , q . y ( ) , q . z ( ) , q . w ( ) ) ; this . tmpMatrix4 . compose ( this . tmpVector3 , this . tmpQuaternion , this . tmpVector3a ) ; if ( objThree . parent ) { if ( parseInt ( REVISION ) >= 123 ) this . tmpMatrix4a . copy ( objThree . parent . matrixWorld ) . invert ( ) ; else this . tmpMatrix4a . getInverse ( objThree . parent . matrixWorld ) ; } else { this . tmpMatrix4a . identity ( ) ; } this . tmpMatrix4a . multiply ( this . tmpMatrix4 ) ; this . tmpMatrix4a . decompose ( objThree . position , objThree . quaternion , objThree . scale ) ; } } } } private detectCollisions ( ) { const detectedCollisions = [ ] ; this . impactPoint . set ( 0 , 0 , 0 ) ; this . impactNormal . set ( 0 , 0 , 0 ) ; const dispatcher = this . physicsWorld . getDispatcher ( ) ; const numManifolds = dispatcher . getNumManifolds ( ) ; for ( let i = 0 ; i < numManifolds ; i ++ ) { const contactManifold = dispatcher . getManifoldByIndexInternal ( i ) ; const numContacts = contactManifold . getNumContacts ( ) ; const rb0 = Ammo . castObject ( contactManifold . getBody0 ( ) , Ammo . btRigidBody ) ; const rb1 = Ammo . castObject ( contactManifold . getBody1 ( ) , Ammo . btRigidBody ) ; const threeObject0 = rb0 . threeObject as ExtendedObject3D ; const threeObject1 = rb1 . threeObject as ExtendedObject3D ; if ( ! threeObject0 || ! threeObject1 ) { continue ; } if ( rb0 . name === '' && rb1 . name === '' ) continue ; /**
             * Get some information
             */ const checkCollisions0 = threeObject0 . body ?. checkCollisions ; const checkCollisions1 = threeObject1 . body ?. checkCollisions ; const breakable0 = threeObject0 . body . breakable ; const breakable1 = threeObject1 . body . breakable ; const fractureImpulse0 = threeObject0 . body . fractureImpulse ; const fractureImpulse1 = threeObject1 . body . fractureImpulse ; const checkCollisions = checkCollisions0 || checkCollisions1 ; const checkBreakable = breakable0 || breakable1 ; if ( typeof threeObject0 . fragmentDepth === 'undefined' ) threeObject0 . fragmentDepth = 0 ; if ( typeof threeObject1 . fragmentDepth === 'undefined' ) threeObject1 . fragmentDepth = 0 ; if ( ! checkCollisions && ! checkBreakable ) continue ; let contact = false ; let maxImpulse = 0 ; let event = 'start' ; for ( let j = 0 ; j < numContacts ; j ++ ) { const contactPoint = contactManifold . getContactPoint ( j ) ; const distance = contactPoint . getDistance ( ) ; if ( distance <= 0 ) { contact = true ; const impulse = contactPoint . getAppliedImpulse ( ) ; const impactPoint = contactPoint . get_m_positionWorldOnB ( ) ; const impactNormal = contactPoint . get_m_normalWorldOnB ( ) ; if ( checkCollisions0 || checkCollisions1 ) { const names = [ threeObject0 . uuid , threeObject1 . uuid ] . sort ( ) ; const combinedName = `${ names [ 0 ] }__${ names [ 1 ] }` ; if ( this . earlierDetectedCollisions . find ( el => el . combinedName === combinedName ) ) event = 'collision' ; if ( ! detectedCollisions . find ( el => el . combinedName === combinedName ) ) { detectedCollisions . push ( { combinedName , collision : true } ) ; this . collisionEvents . emit ( 'collision' , { bodies : [ threeObject0 , threeObject1 ] , event } ) ; } } if ( impulse >= maxImpulse ) { maxImpulse = impulse ; if ( breakable0 || breakable1 ) { this . impactPoint . set ( impactPoint . x ( ) , impactPoint . y ( ) , impactPoint . z ( ) ) ; this . impactNormal . set ( impactNormal . x ( ) , impactNormal . y ( ) , impactNormal . z ( ) ) ; } } break ; } } if ( ! contact ) continue ; if ( ! checkBreakable ) continue ; /**
             * check for breakable objects (subdivision)
             */ const MAX_FRAGMENT_DEPTH = 2 ; this . emptyV3 . set ( 0 , 0 , 0 ) ; threeObject0 . userData . ammoPhysicsData = { mass : 1 , velocity : this . emptyV3 , angularVelocity : this . emptyV3 , breakable : breakable0 , physicsBody : rb0 } ; threeObject1 . userData . ammoPhysicsData = { mass : 1 , velocity : this . emptyV3 , angularVelocity : this . emptyV3 , breakable : breakable1 , physicsBody : rb1 } ; if ( breakable0 && maxImpulse > fractureImpulse0 && threeObject0 . fragmentDepth < MAX_FRAGMENT_DEPTH ) { const debris = this . convexBreaker . subdivideByImpact ( threeObject0 , this . impactPoint , this . 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 ] as ExtendedObject3D ; fragment . userData . ammoPhysicsData . velocity . set ( vel . x ( ) , vel . y ( ) , vel . z ( ) ) ; fragment . userData . ammoPhysicsData . angularVelocity . set ( angVel . x ( ) , angVel . y ( ) , angVel . z ( ) ) ; this . createDebrisFromBreakableObject ( fragment , threeObject0 ) ; } this . objectsToRemove [ this . numObjectsToRemove ++ ] = threeObject0 ; } if ( breakable1 && maxImpulse > fractureImpulse1 && threeObject1 . fragmentDepth < MAX_FRAGMENT_DEPTH ) { const debris = this . convexBreaker . subdivideByImpact ( threeObject1 , this . impactPoint , this . 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 ] as ExtendedObject3D ; fragment . userData . ammoPhysicsData . velocity . set ( vel . x ( ) , vel . y ( ) , vel . z ( ) ) ; fragment . userData . ammoPhysicsData . angularVelocity . set ( angVel . x ( ) , angVel . y ( ) , angVel . z ( ) ) ; this . createDebrisFromBreakableObject ( fragment , threeObject1 ) ; } this . objectsToRemove [ this . numObjectsToRemove ++ ] = threeObject1 ; } } for ( let i = 0 ; i < this . numObjectsToRemove ; i ++ ) { this . removeDebris ( this . objectsToRemove [ i ] ) ; } this . numObjectsToRemove = 0 ; this . earlierDetectedCollisions . forEach ( el => { const { combinedName } = el ; if ( ! detectedCollisions . find ( el => el . combinedName === combinedName ) ) { const split = combinedName . split ( '__' ) ; const obj0 = this . rigidBodies . find ( obj => obj . uuid === split [ 0 ] ) ; const obj1 = this . rigidBodies . find ( obj => obj . uuid === split [ 1 ] ) ; const event = 'end' ; if ( obj0 && obj1 ) this . collisionEvents . emit ( 'collision' , { bodies : [ obj0 , obj1 ] , event } ) ; } } ) ; this . earlierDetectedCollisions = [ ... detectedCollisions ] ; } public setGravity ( x = 0 , y = - 9.8 , z = 0 ) { this . tmpBtVector3 . setValue ( x , y , z ) ; this . physicsWorld . setGravity ( this . tmpBtVector3 ) ; } public get debug ( ) { if ( this . isHeadless ) return null ; return { enable : ( ) => { this . debugDrawer . enable ( ) ; } , mode : ( debugMode = 1 ) => { this . debugDrawer . setDebugMode ( debugMode ) ; } , disable : ( ) => { this . debugDrawer . disable ( ) ; } } ; } private start ( ) { if ( typeof Ammo === 'undefined' ) { logger ( 'Are you sure you included ammo.js?' ) ; return ; } if ( typeof Ammo === 'function' ) Ammo ( ) . then ( ( ) => { this . setup ( ) ; } ) ; else this . setup ( ) ; } public get add ( ) { return { collider : ( object1 , object2 , eventCallback ) => this . collisionEvents . addCollider ( object1 , object2 , eventCallback ) , constraints : this . constraints . addConstraints , existing : ( object , config ? ) => this . addExisting ( object , config ) , plane : ( planeConfig = { } , materialConfig = { } ) => this . shapes . addPlane ( planeConfig , materialConfig ) , sphere : ( sphereConfig = { } , materialConfig = { } ) => this . shapes . addSphere ( sphereConfig , materialConfig ) , ground : ( groundConfig = { } , materialConfig = { } ) => this . shapes . addGround ( groundConfig , materialConfig ) , box : ( boxConfig = { } , materialConfig = { } ) => this . shapes . addBox ( boxConfig , materialConfig ) , cylinder : ( cylinderConfig = { } , materialConfig = { } ) => this . shapes . addCylinder ( cylinderConfig , materialConfig ) , cone : ( coneConfig = { } , materialConfig = { } ) => this . shapes . addCone ( coneConfig , materialConfig ) , torus : ( torusConfig = { } , materialConfig = { } ) => this . shapes . addTorus ( torusConfig , materialConfig ) , extrude : ( extrudeConfig , materialConfig = { } ) => this . shapes . addExtrude ( extrudeConfig , materialConfig ) , param type raycaster : ( type = 'closest' ) => { if ( type === 'closest' ) return new ClosestRaycaster ( this ) as ClosestRaycaster ; else return new AllHitsRaycaster ( this ) as AllHitsRaycaster ; } } ; } private prepareThreeObjectForCollisionShape ( object , config = { } ) { const { autoCenter = false } = config ; const defaultParams = { width : 1 , height : 1 , depth : 1 , radius : 1 , radiusTop : 1 , radiusBottom : 1 , tube : 0.4 , tubularSegments : 6 } ; let shape = 'unknown' ; const type = object . geometry ?. type || 'unknown' ; if ( /box/i . test ( type ) ) shape = 'box' ; else if ( /cone/i . test ( type ) ) shape = 'cone' ; else if ( /cylinder/i . test ( type ) ) shape = 'cylinder' ; else if ( /extrude/i . test ( type ) ) shape = 'extrude' ; else if ( /plane/i . test ( type ) ) shape = 'plane' ; else if ( /sphere/i . test ( type ) ) shape = 'sphere' ; else if ( /torus/i . test ( type ) ) shape = 'torus' ; let params = { ... defaultParams , ... object ?. geometry ?. parameters } ; if ( config . shape ) { params = { ... defaultParams , ... config } ; shape = config . shape ; } else if ( object . shape ) { shape = object . shape ; } ; ( Object . keys ( params ) as ( keyof typeof defaultParams ) [ ] ) . forEach ( key => { if ( typeof params [ key ] === 'undefined' && defaultParams [ key ] ) { params [ key ] = defaultParams [ key ] ; } } ) ; if ( autoCenter ) object . geometry . center ( ) ; if ( shape === 'cylinder' ) params . radius = config . radius || params . radiusTop ; if ( shape === 'extrude' ) shape = 'hacd' ; if ( shape === 'mesh' || shape === 'convex' ) shape = 'convexMesh' ; if ( shape === 'concave' ) shape = 'concaveMesh' ; if ( shape === 'unknown' ) { logger ( `Shape for ${ object ?. name } not recognized! Will fallback to box.` ) ; shape = 'box' ; } return { shape , params , object } ; } public createCollisionShape ( shape , params , object ? ) { const quat = object ?. quaternion ? object ?. quaternion : new Quaternion ( 0 , 0 , 0 , 1 ) ; const { axis = 'y' } = params ; const btHalfExtents = new Ammo . btVector3 ( ) ; const geometry = object ?. geometry as Geometry ; if ( object && geometry ?. isGeometry ) { object . geometry = new BufferGeometry ( ) . fromGeometry ( geometry ) ; } const extractData = ( object ) => { const matrixWorld = new Matrix4 ( ) . elements ; const vertices = [ ] ; const matrices = [ ] ; const indexes = [ ] ; iterateGeometries ( object , { } , ( vertexArray , matrixArray , indexArray ) => { vertices . push ( vertexArray ) ; matrices . push ( matrixArray ) ; indexes . push ( indexArray ) ; } ) ; return { vertices , matrices , indexes , matrixWorld } ; } ; let d = { } as any ; if ( this . complexShapes . indexOf ( shape ) !== - 1 ) d = extractData ( object ) ; let collisionShape ; switch ( shape ) { case 'box' : btHalfExtents . setValue ( params . width / 2 , params . height / 2 , params . depth / 2 ) ; collisionShape = new Ammo . btBoxShape ( btHalfExtents ) ; break ; case 'sphere' : collisionShape = new Ammo . btSphereShape ( params . radius ) ; break ; case 'cylinder' : switch ( axis ) { case 'y' : btHalfExtents . setValue ( params . radius , params . height / 2 , params . radius ) ; collisionShape = new Ammo . btCylinderShape ( btHalfExtents ) ; break ; case 'x' : btHalfExtents . setValue ( params . height / 2 , params . radius , params . radius ) ; collisionShape = new Ammo . btCylinderShapeX ( btHalfExtents ) ; break ; case 'z' : btHalfExtents . setValue ( params . radius , params . radius , params . height / 2 ) ; collisionShape = new Ammo . btCylinderShapeZ ( btHalfExtents ) ; break ; } break ; case 'cone' : switch ( axis ) { case 'y' : collisionShape = new Ammo . btConeShape ( params . radius , params . height ) ; break ; case 'x' : collisionShape = new Ammo . btConeShapeX ( params . radius , params . height ) ; break ; case 'z' : collisionShape = new Ammo . btConeShapeZ ( params . radius , params . height ) ; break ; } break ; case 'capsule' : switch ( axis ) { case 'y' : collisionShape = new Ammo . btCapsuleShape ( params . radius , params . height ) ; break ; case 'x' : collisionShape = new Ammo . btCapsuleShapeX ( params . radius , params . height ) ; break ; case 'z' : collisionShape = new Ammo . btCapsuleShapeZ ( params . radius , params . height ) ; break ; } break ; case 'torus' : collisionShape = createTorusShape ( params , quat ) ; break ; case 'plane' : collisionShape = createTriMeshShape ( d . vertices , d . matrices , d . indexes , d . matrixWorld , { ... params , concave : false } ) ; break ; case 'hull' : collisionShape = createHullShape ( d . vertices , d . matrices , d . matrixWorld , params ) ; break ; case 'hacd' : collisionShape = createHACDShapes ( d . vertices , d . matrices , d . indexes , d . matrixWorld , params ) ; break ; case 'vhacd' : collisionShape = createVHACDShapes ( d . vertices , d . matrices , d . indexes , d . matrixWorld , params ) ; break ; case 'convexMesh' : collisionShape = createTriMeshShape ( d . vertices , d . matrices , d . indexes , d . matrixWorld , { ... params , concave : false } ) ; break ; case 'concaveMesh' : collisionShape = createTriMeshShape ( d . vertices , d . matrices , d . indexes , d . matrixWorld , { ... params , concave : true } ) ; break ; } Ammo . destroy ( btHalfExtents ) ; const { x , y , z } = params ; if ( x || y || z ) { collisionShape . _compoundOffset = { x : x || 0 , y : y || 0 , z : z || 0 } ; } if ( Array . isArray ( collisionShape ) ) collisionShape = this . mergeCollisionShapesToCompoundShape ( collisionShape ) ; return collisionShape as Ammo . btCollisionShape ; } public mergeCollisionShapesToCompoundShape ( collisionShapes ) { const compoundShape = new Ammo . btCompoundShape ( ) ; collisionShapes . forEach ( shape => { const { _childOffset , _compoundOffset } = shape ; if ( _childOffset ) { const { pos , quat , scale , margin } = _childOffset ; const transform = this . applyPosQuatScaleMargin ( shape , pos , quat , scale , margin ) ; compoundShape . addChildShape ( transform , shape ) ; } else if ( _compoundOffset ) { const transform = new Ammo . btTransform ( ) ; transform . setIdentity ( ) ; transform . setOrigin ( new Ammo . btVector3 ( _compoundOffset . x , _compoundOffset . y , _compoundOffset . z ) ) ; compoundShape . addChildShape ( transform , shape ) ; } else { const transform = new Ammo . btTransform ( ) ; transform . setIdentity ( ) ; compoundShape . addChildShape ( transform , shape ) ; } } ) ; return compoundShape ; } protected addExisting ( object , config = { } ) { const { hasBody } = object ; if ( hasBody ) { logger ( `Object "${ object . name }" already has a physical body!` ) ; return ; } const pos = new Vector3 ( ) ; const quat = new Quaternion ( ) ; const scale = new Vector3 ( ) ; object . getWorldPosition ( pos ) ; object . getWorldQuaternion ( quat ) ; object . getWorldScale ( scale ) ; const isStaticObject = ( config . collisionFlags || 0 ) . toString ( 2 ) . slice ( - 1 ) === '1' ; const isKinematicObject = ( config . collisionFlags || 0 ) . toString ( 2 ) . slice ( - 2 , - 1 ) === '1' ; const { shape = 'unknown' , compound = [ ] , mass = isStaticObject || isKinematicObject ? 0 : 1 , collisionFlags = 0 , collisionGroup = 1 , collisionMask = - 1 , offset = undefined , breakable = false , addChildren = true , margin = 0.01 , ignoreScale = false , fractureImpulse = 1 } = config ; if ( ignoreScale ) scale . set ( 1 , 1 , 1 ) ; if ( compound . length >= 1 ) { const collisionShapes = compound . map ( ( s ) => this . createCollisionShape ( s . shape , s ) ) ; const compoundShape = this . mergeCollisionShapesToCompoundShape ( collisionShapes ) ; const localTransform = this . applyPosQuatScaleMargin ( compoundShape , pos , quat , scale , margin ) ; const rigidBody = this . collisionShapeToRigidBody ( compoundShape , localTransform , mass , isKinematicObject ) ; this . addRigidBodyToWorld ( object , rigidBody , collisionFlags , collisionGroup , collisionMask , offset ) ; object . body . breakable = breakable ; object . body . fractureImpulse = fractureImpulse ; object . body . ignoreScale = ignoreScale ; return ; } const collisionShapes = [ ] ; if ( shape !== 'unknown' || object . isMesh ) { const p = this . prepareThreeObjectForCollisionShape ( object , config ) ; const cs = this . createCollisionShape ( p . shape , p . params , p . object ) ; collisionShapes . push ( cs ) ; } if ( shape === 'unknown' && addChildren && object . children . length >= 1 ) { object . children . forEach ( ( child ) => { if ( child . isMesh ) { const p = this . prepareThreeObjectForCollisionShape ( child ) ; const cs = this . createCollisionShape ( p . shape , p . params , p . object ) ; cs . _childOffset = { pos : child . position . clone ( ) , quat : child . quaternion . clone ( ) , scale : child . scale . clone ( ) , margin : margin } ; collisionShapes . push ( cs ) ; } } ) ; } if ( collisionShapes . length === 0 ) { const p = this . prepareThreeObjectForCollisionShape ( object , config ) ; const cs = this . createCollisionShape ( p . shape , p . params , p . object ) ; collisionShapes . push ( cs ) ; } const collisionShape = collisionShapes . length === 1 ? collisionShapes [ 0 ] : this . mergeCollisionShapesToCompoundShape ( collisionShapes ) ; const localTransform = this . applyPosQuatScaleMargin ( collisionShape , pos , quat , scale , margin ) ; const rigidBody = this . collisionShapeToRigidBody ( collisionShape , localTransform , mass , isKinematicObject ) ; this . addRigidBodyToWorld ( object , rigidBody , collisionFlags , collisionGroup , collisionMask , offset ) ; object . body . breakable = breakable ; object . body . fractureImpulse = fractureImpulse ; object . body . ignoreScale = ignoreScale ; } public addRigidBodyToWorld ( object , rigidBody , collisionFlags , collisionGroup , collisionMask , offset ? ) { this . rigidBodies . push ( object ) ; this . physicsWorld . addRigidBody ( rigidBody , collisionGroup , collisionMask ) ; const ptr = Object . values ( rigidBody ) [ 0 ] ; if ( ! object . name ) object . name = `object-${ object . id }` ; rigidBody . name = object . name ; object . body = new PhysicsBody ( this , rigidBody ) ; object . hasBody = true ; object . ptr = ptr ; rigidBody . threeObject = object ; if ( offset ) object . body . offset = { x : 0 , y : 0 , z : 0 , ... offset } ; object . body . setCollisionFlags ( collisionFlags ) ; } public applyPosQuatScaleMargin ( collisionShape , pos = new Vector3 ( ) , quat = new Quaternion ( ) , scale = new Vector3 ( ) , margin = 0.01 ) { collisionShape . setMargin ( margin ) ; const rotation = new Ammo . btQuaternion ( 0 , 0 , 0 , 1 ) ; rotation . setValue ( quat . x , quat . y , quat . z , quat . w ) ; const localTransform = new Ammo . btTransform ( ) ; localTransform . setIdentity ( ) ; localTransform . getOrigin ( ) . setValue ( pos . x , pos . y , pos . z ) ; localTransform . setRotation ( rotation ) ; Ammo . destroy ( rotation ) ; const localScale = new Ammo . btVector3 ( scale . x , scale . y , scale . z ) ; collisionShape . setLocalScaling ( localScale ) ; Ammo . destroy ( localScale ) ; return localTransform ; } public collisionShapeToRigidBody ( collisionShape , localTransform , mass , disableDeactivation ) { const motionState = new Ammo . btDefaultMotionState ( localTransform ) ; const localInertia = new Ammo . btVector3 ( 0 , 0 , 0 ) ; if ( mass > 0 ) collisionShape . calculateLocalInertia ( mass , localInertia ) ; const rbInfo = new Ammo . btRigidBodyConstructionInfo ( mass , motionState , collisionShape , localInertia ) ; const rigidBody = new Ammo . btRigidBody ( rbInfo ) ; if ( mass > 0 || disableDeactivation ) rigidBody . setActivationState ( 4 ) ; return rigidBody ; } } export { AmmoPhysics } ;