import * as THREE from 'three' ; import { mat4InvertCompat } from '../utils/mat4InvertCompat' ; import { getWorldQuaternionLite } from '../utils/math' ; import { Matrix4InverseCache } from '../utils/Matrix4InverseCache' ; import { VRMSpringBoneColliderMesh } from './VRMSpringBoneColliderGroup' ; import { VRMSpringBoneParameters } from './VRMSpringBoneParameters' ; const IDENTITY_MATRIX4 = Object . freeze ( new THREE . Matrix4 ( ) ) ; const IDENTITY_QUATERNION = Object . freeze ( new THREE . Quaternion ( ) ) ; const _v3A = new THREE . Vector3 ( ) ; const _v3B = new THREE . Vector3 ( ) ; const _v3C = new THREE . Vector3 ( ) ; const _quatA = new THREE . Quaternion ( ) ; const _matA = new THREE . Matrix4 ( ) ; const _matB = new THREE . Matrix4 ( ) ; /**
 * A class represents a single spring bone of a VRM.
 * It should be managed by a [[VRMSpringBoneManager]].
 */ export class VRMSpringBone { /**
     * Radius of the bone, will be used for collision.
     */ public radius : number ; /**
     * Stiffness force of the bone. Increasing the value = faster convergence (feels "harder").
     * On UniVRM, its range on GUI is between `0.0` and `4.0` .
     */ public stiffnessForce : number ; /**
     * Power of the gravity against this bone.
     * The "power" used in here is very far from scientific physics term...
     */ public gravityPower : number ; /**
     * Direction of the gravity against this bone.
     * Usually it should be normalized.
     */ public gravityDir : THREE . Vector3 ; /**
     * Drag force of the bone. Increasing the value = less oscillation (feels "heavier").
     * On UniVRM, its range on GUI is between `0.0` and `1.0` .
     */ public dragForce : number ; /**
     * Collider groups attached to this bone.
     */ public colliders : VRMSpringBoneColliderMesh [ ] ; /**
     * An Object3D attached to this bone.
     */ public readonly bone : THREE . Object3D ; /**
     * Current position of child tail, in world unit. Will be used for verlet integration.
     */ protected _currentTail = new THREE . Vector3 ( ) ; /**
     * Previous position of child tail, in world unit. Will be used for verlet integration.
     */ protected _prevTail = new THREE . Vector3 ( ) ; /**
     * Next position of child tail, in world unit. Will be used for verlet integration.
     * Actually used only in [[update]] and it's kind of temporary variable.
     */ protected _nextTail = new THREE . Vector3 ( ) ; /**
     * Initial axis of the bone, in local unit.
     */ protected _boneAxis = new THREE . Vector3 ( ) ; /**
     * Length of the bone in relative space unit. Will be used for normalization in update loop.
     * It's same as local unit length unless there are scale transformation in world matrix.
     */ protected _centerSpaceBoneLength : number ; /**
     * Position of this bone in relative space, kind of a temporary variable.
     */ protected _centerSpacePosition = new THREE . Vector3 ( ) ; /**
     * This springbone will be calculated based on the space relative from this object.
     * If this is `null`, springbone will be calculated in world space.
     */ protected _center : THREE . Object3D | null = null ; public get center ( ) : THREE . Object3D | null { return this . _center ; } public set center ( center ) { this . _getMatrixCenterToWorld ( _matA ) ; this . _currentTail . applyMatrix4 ( _matA ) ; this . _prevTail . applyMatrix4 ( _matA ) ; this . _nextTail . applyMatrix4 ( _matA ) ; if ( this . _center ?. userData . inverseCacheProxy ) { ( this . _center . userData . inverseCacheProxy as Matrix4InverseCache ) . revert ( ) ; delete this . _center . userData . inverseCacheProxy ; } this . _center = center ; if ( this . _center ) { if ( ! this . _center . userData . inverseCacheProxy ) { this . _center . userData . inverseCacheProxy = new Matrix4InverseCache ( this . _center . matrixWorld ) ; } } this . _getMatrixWorldToCenter ( _matA ) ; this . _currentTail . applyMatrix4 ( _matA ) ; this . _prevTail . applyMatrix4 ( _matA ) ; this . _nextTail . applyMatrix4 ( _matA ) ; _matA . multiply ( this . bone . matrixWorld ) ; this . _centerSpacePosition . setFromMatrixPosition ( _matA ) ; this . _centerSpaceBoneLength = _v3A . copy ( this . _initialLocalChildPosition ) . applyMatrix4 ( _matA ) . sub ( this . _centerSpacePosition ) . length ( ) ; } /**
     * Rotation of parent bone, in world unit.
     * We should update this constantly in [[update]].
     */ private _parentWorldRotation = new THREE . Quaternion ( ) ; /**
     * Initial state of the local matrix of the bone.
     */ private _initialLocalMatrix = new THREE . Matrix4 ( ) ; /**
     * Initial state of the rotation of the bone.
     */ private _initialLocalRotation = new THREE . Quaternion ( ) ; /**
     * Initial state of the position of its child.
     */ private _initialLocalChildPosition = new THREE . Vector3 ( ) ; param bone param params constructor ( bone , params = { } ) { this . bone = bone ; this . bone . matrixAutoUpdate = false ; this . radius = params . radius ?? 0.02 ; this . stiffnessForce = params . stiffnessForce ?? 1.0 ; this . gravityDir = params . gravityDir ? new THREE . Vector3 ( ) . copy ( params . gravityDir ) : new THREE . Vector3 ( ) . set ( 0.0 , - 1.0 , 0.0 ) ; this . gravityPower = params . gravityPower ?? 0.0 ; this . dragForce = params . dragForce ?? 0.4 ; this . colliders = params . colliders ?? [ ] ; this . _centerSpacePosition . setFromMatrixPosition ( this . bone . matrixWorld ) ; this . _initialLocalMatrix . copy ( this . bone . matrix ) ; this . _initialLocalRotation . copy ( this . bone . quaternion ) ; if ( this . bone . children . length === 0 ) { this . _initialLocalChildPosition . copy ( this . bone . position ) . normalize ( ) . multiplyScalar ( 0.07 ) ; } else { const firstChild = this . bone . children [ 0 ] ; this . _initialLocalChildPosition . copy ( firstChild . position ) ; } this . bone . localToWorld ( this . _currentTail . copy ( this . _initialLocalChildPosition ) ) ; this . _prevTail . copy ( this . _currentTail ) ; this . _nextTail . copy ( this . _currentTail ) ; this . _boneAxis . copy ( this . _initialLocalChildPosition ) . normalize ( ) ; this . _centerSpaceBoneLength = _v3A . copy ( this . _initialLocalChildPosition ) . applyMatrix4 ( this . bone . matrixWorld ) . sub ( this . _centerSpacePosition ) . length ( ) ; this . center = params . center ?? null ; } /**
     * Reset the state of this bone.
     * You might want to call [[VRMSpringBoneManager.reset]] instead.
     */ public reset ( ) { this . bone . quaternion . copy ( this . _initialLocalRotation ) ; this . bone . updateMatrix ( ) ; this . bone . matrixWorld . multiplyMatrices ( this . _getParentMatrixWorld ( ) , this . bone . matrix ) ; this . _centerSpacePosition . setFromMatrixPosition ( this . bone . matrixWorld ) ; this . bone . localToWorld ( this . _currentTail . copy ( this . _initialLocalChildPosition ) ) ; this . _prevTail . copy ( this . _currentTail ) ; this . _nextTail . copy ( this . _currentTail ) ; } param delta public update ( delta ) { if ( delta <= 0 ) return ; this . bone . matrixWorld . multiplyMatrices ( this . _getParentMatrixWorld ( ) , this . bone . matrix ) ; if ( this . bone . parent ) { getWorldQuaternionLite ( this . bone . parent , this . _parentWorldRotation ) ; } else { this . _parentWorldRotation . copy ( IDENTITY_QUATERNION ) ; } this . _getMatrixWorldToCenter ( _matA ) ; _matA . multiply ( this . bone . matrixWorld ) ; this . _centerSpacePosition . setFromMatrixPosition ( _matA ) ; this . _getMatrixWorldToCenter ( _matB ) ; _matB . multiply ( this . _getParentMatrixWorld ( ) ) ; const stiffness = this . stiffnessForce * delta ; const external = _v3B . copy ( this . gravityDir ) . multiplyScalar ( this . gravityPower * delta ) ; this . _nextTail . copy ( this . _currentTail ) . add ( _v3A . copy ( this . _currentTail ) . sub ( this . _prevTail ) . multiplyScalar ( 1 - this . dragForce ) ) . add ( _v3A . copy ( this . _boneAxis ) . applyMatrix4 ( this . _initialLocalMatrix ) . applyMatrix4 ( _matB ) . sub ( this . _centerSpacePosition ) . normalize ( ) . multiplyScalar ( stiffness ) ) . add ( external ) ; this . _nextTail . sub ( this . _centerSpacePosition ) . normalize ( ) . multiplyScalar ( this . _centerSpaceBoneLength ) . add ( this . _centerSpacePosition ) ; this . _collision ( this . _nextTail ) ; this . _prevTail . copy ( this . _currentTail ) ; this . _currentTail . copy ( this . _nextTail ) ; const initialCenterSpaceMatrixInv = mat4InvertCompat ( _matA . copy ( _matB . multiply ( this . _initialLocalMatrix ) ) ) ; const applyRotation = _quatA . setFromUnitVectors ( this . _boneAxis , _v3A . copy ( this . _nextTail ) . applyMatrix4 ( initialCenterSpaceMatrixInv ) . normalize ( ) ) ; this . bone . quaternion . copy ( this . _initialLocalRotation ) . multiply ( applyRotation ) ; this . bone . updateMatrix ( ) ; this . bone . matrixWorld . multiplyMatrices ( this . _getParentMatrixWorld ( ) , this . bone . matrix ) ; } param tail private _collision ( tail ) { this . colliders . forEach ( ( collider ) => { this . _getMatrixWorldToCenter ( _matA ) ; _matA . multiply ( collider . matrixWorld ) ; const colliderCenterSpacePosition = _v3A . setFromMatrixPosition ( _matA ) ; const colliderRadius = collider . geometry . boundingSphere ! . radius ; const r = this . radius + colliderRadius ; if ( tail . distanceToSquared ( colliderCenterSpacePosition ) <= r * r ) { const normal = _v3B . subVectors ( tail , colliderCenterSpacePosition ) . normalize ( ) ; const posFromCollider = _v3C . addVectors ( colliderCenterSpacePosition , normal . multiplyScalar ( r ) ) ; tail . copy ( posFromCollider . sub ( this . _centerSpacePosition ) . normalize ( ) . multiplyScalar ( this . _centerSpaceBoneLength ) . add ( this . _centerSpacePosition ) ) ; } } ) ; } param target private _getMatrixCenterToWorld ( target ) { if ( this . _center ) { target . copy ( this . _center . matrixWorld ) ; } else { target . identity ( ) ; } return target ; } param target private _getMatrixWorldToCenter ( target ) { if ( this . _center ) { target . copy ( ( this . _center . userData . inverseCacheProxy as Matrix4InverseCache ) . inverse ) ; } else { target . identity ( ) ; } return target ; } /**
     * Returns the world matrix of its parent object.
     */ private _getParentMatrixWorld ( ) { return this . bone . parent ? this . bone . parent . matrixWorld : IDENTITY_MATRIX4 ; } }