2
0

MMDAnimationHelper.js 26 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230
  1. /**
  2. * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader
  3. * with MMD special features as IK, Grant, and Physics.
  4. *
  5. * Dependencies
  6. * - ammo.js https://github.com/kripken/ammo.js
  7. * - THREE.MMDPhysics
  8. * - THREE.CCDIKSolver
  9. *
  10. * TODO
  11. * - more precise grant skinning support.
  12. */
  13. THREE.MMDAnimationHelper = ( function () {
  14. /**
  15. * @param {Object} params - (optional)
  16. * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true.
  17. * @param {Number} params.afterglow - Default is 0.0.
  18. * @param {boolean} params.resetPhysicsOnLoop - Default is true.
  19. */
  20. function MMDAnimationHelper( params ) {
  21. params = params || {};
  22. this.meshes = [];
  23. this.camera = null;
  24. this.cameraTarget = new THREE.Object3D();
  25. this.cameraTarget.name = 'target';
  26. this.audio = null;
  27. this.audioManager = null;
  28. this.objects = new WeakMap();
  29. this.configuration = {
  30. sync: params.sync !== undefined
  31. ? params.sync : true,
  32. afterglow: params.afterglow !== undefined
  33. ? params.afterglow : 0.0,
  34. resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined
  35. ? params.resetPhysicsOnLoop : true,
  36. pmxAnimation: params.pmxAnimation !== undefined
  37. ? params.pmxAnimation : false
  38. };
  39. this.enabled = {
  40. animation: true,
  41. ik: true,
  42. grant: true,
  43. physics: true,
  44. cameraAnimation: true
  45. };
  46. this.onBeforePhysics = function ( /* mesh */ ) {};
  47. // experimental
  48. this.sharedPhysics = false;
  49. this.masterPhysics = null;
  50. }
  51. MMDAnimationHelper.prototype = {
  52. constructor: MMDAnimationHelper,
  53. /**
  54. * Adds an Three.js Object to helper and setups animation.
  55. * The anmation durations of added objects are synched
  56. * if this.configuration.sync is true.
  57. *
  58. * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object
  59. * @param {Object} params - (optional)
  60. * @param {THREE.AnimationClip|Array<THREE.AnimationClip>} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined.
  61. * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true.
  62. * @param {Integer} params.warmup - Only for THREE.SkinnedMesh and physics is true. Default is 60.
  63. * @param {Number} params.unitStep - Only for THREE.SkinnedMesh and physics is true. Default is 1 / 65.
  64. * @param {Integer} params.maxStepNum - Only for THREE.SkinnedMesh and physics is true. Default is 3.
  65. * @param {THREE.Vector3} params.gravity - Only for THREE.SkinnedMesh and physics is true. Default ( 0, - 9.8 * 10, 0 ).
  66. * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0.
  67. * @return {THREE.MMDAnimationHelper}
  68. */
  69. add: function ( object, params ) {
  70. params = params || {};
  71. if ( object.isSkinnedMesh ) {
  72. this._addMesh( object, params );
  73. } else if ( object.isCamera ) {
  74. this._setupCamera( object, params );
  75. } else if ( object.type === 'Audio' ) {
  76. this._setupAudio( object, params );
  77. } else {
  78. throw new Error( 'THREE.MMDAnimationHelper.add: '
  79. + 'accepts only '
  80. + 'THREE.SkinnedMesh or '
  81. + 'THREE.Camera or '
  82. + 'THREE.Audio instance.' );
  83. }
  84. if ( this.configuration.sync ) this._syncDuration();
  85. return this;
  86. },
  87. /**
  88. * Removes an Three.js Object from helper.
  89. *
  90. * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object
  91. * @return {THREE.MMDAnimationHelper}
  92. */
  93. remove: function ( object ) {
  94. if ( object.isSkinnedMesh ) {
  95. this._removeMesh( object );
  96. } else if ( object.isCamera ) {
  97. this._clearCamera( object );
  98. } else if ( object.type === 'Audio' ) {
  99. this._clearAudio( object );
  100. } else {
  101. throw new Error( 'THREE.MMDAnimationHelper.remove: '
  102. + 'accepts only '
  103. + 'THREE.SkinnedMesh or '
  104. + 'THREE.Camera or '
  105. + 'THREE.Audio instance.' );
  106. }
  107. if ( this.configuration.sync ) this._syncDuration();
  108. return this;
  109. },
  110. /**
  111. * Updates the animation.
  112. *
  113. * @param {Number} delta
  114. * @return {THREE.MMDAnimationHelper}
  115. */
  116. update: function ( delta ) {
  117. if ( this.audioManager !== null ) this.audioManager.control( delta );
  118. for ( var i = 0; i < this.meshes.length; i ++ ) {
  119. this._animateMesh( this.meshes[ i ], delta );
  120. }
  121. if ( this.sharedPhysics ) this._updateSharedPhysics( delta );
  122. if ( this.camera !== null ) this._animateCamera( this.camera, delta );
  123. return this;
  124. },
  125. /**
  126. * Changes the pose of SkinnedMesh as VPD specifies.
  127. *
  128. * @param {THREE.SkinnedMesh} mesh
  129. * @param {Object} vpd - VPD content parsed MMDParser
  130. * @param {Object} params - (optional)
  131. * @param {boolean} params.resetPose - Default is true.
  132. * @param {boolean} params.ik - Default is true.
  133. * @param {boolean} params.grant - Default is true.
  134. * @return {THREE.MMDAnimationHelper}
  135. */
  136. pose: function ( mesh, vpd, params ) {
  137. params = params || {};
  138. if ( params.resetPose !== false ) mesh.pose();
  139. var bones = mesh.skeleton.bones;
  140. var boneParams = vpd.bones;
  141. var boneNameDictionary = {};
  142. for ( var i = 0, il = bones.length; i < il; i ++ ) {
  143. boneNameDictionary[ bones[ i ].name ] = i;
  144. }
  145. var vector = new THREE.Vector3();
  146. var quaternion = new THREE.Quaternion();
  147. for ( var i = 0, il = boneParams.length; i < il; i ++ ) {
  148. var boneParam = boneParams[ i ];
  149. var boneIndex = boneNameDictionary[ boneParam.name ];
  150. if ( boneIndex === undefined ) continue;
  151. var bone = bones[ boneIndex ];
  152. bone.position.add( vector.fromArray( boneParam.translation ) );
  153. bone.quaternion.multiply( quaternion.fromArray( boneParam.quaternion ) );
  154. }
  155. mesh.updateMatrixWorld( true );
  156. // PMX animation system special path
  157. if ( this.configuration.pmxAnimation &&
  158. mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) {
  159. var sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() );
  160. var ikSolver = params.ik !== false ? this._createCCDIKSolver( mesh ) : null;
  161. var grantSolver = params.grant !== false ? this.createGrantSolver( mesh ) : null;
  162. this._animatePMXMesh( mesh, sortedBonesData, ikSolver, grantSolver );
  163. } else {
  164. if ( params.ik !== false ) {
  165. this._createCCDIKSolver( mesh ).update();
  166. }
  167. if ( params.grant !== false ) {
  168. this.createGrantSolver( mesh ).update();
  169. }
  170. }
  171. return this;
  172. },
  173. /**
  174. * Enabes/Disables an animation feature.
  175. *
  176. * @param {string} key
  177. * @param {boolean} enabled
  178. * @return {THREE.MMDAnimationHelper}
  179. */
  180. enable: function ( key, enabled ) {
  181. if ( this.enabled[ key ] === undefined ) {
  182. throw new Error( 'THREE.MMDAnimationHelper.enable: '
  183. + 'unknown key ' + key );
  184. }
  185. this.enabled[ key ] = enabled;
  186. if ( key === 'physics' ) {
  187. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  188. this._optimizeIK( this.meshes[ i ], enabled );
  189. }
  190. }
  191. return this;
  192. },
  193. /**
  194. * Creates an GrantSolver instance.
  195. *
  196. * @param {THREE.SkinnedMesh} mesh
  197. * @return {GrantSolver}
  198. */
  199. createGrantSolver: function ( mesh ) {
  200. return new GrantSolver( mesh, mesh.geometry.userData.MMD.grants );
  201. },
  202. // private methods
  203. _addMesh: function ( mesh, params ) {
  204. if ( this.meshes.indexOf( mesh ) >= 0 ) {
  205. throw new Error( 'THREE.MMDAnimationHelper._addMesh: '
  206. + 'SkinnedMesh \'' + mesh.name + '\' has already been added.' );
  207. }
  208. this.meshes.push( mesh );
  209. this.objects.set( mesh, { looped: false } );
  210. this._setupMeshAnimation( mesh, params.animation );
  211. if ( params.physics !== false ) {
  212. this._setupMeshPhysics( mesh, params );
  213. }
  214. return this;
  215. },
  216. _setupCamera: function ( camera, params ) {
  217. if ( this.camera === camera ) {
  218. throw new Error( 'THREE.MMDAnimationHelper._setupCamera: '
  219. + 'Camera \'' + camera.name + '\' has already been set.' );
  220. }
  221. if ( this.camera ) this.clearCamera( this.camera );
  222. this.camera = camera;
  223. camera.add( this.cameraTarget );
  224. this.objects.set( camera, {} );
  225. if ( params.animation !== undefined ) {
  226. this._setupCameraAnimation( camera, params.animation );
  227. }
  228. return this;
  229. },
  230. _setupAudio: function ( audio, params ) {
  231. if ( this.audio === audio ) {
  232. throw new Error( 'THREE.MMDAnimationHelper._setupAudio: '
  233. + 'Audio \'' + audio.name + '\' has already been set.' );
  234. }
  235. if ( this.audio ) this.clearAudio( this.audio );
  236. this.audio = audio;
  237. this.audioManager = new AudioManager( audio, params );
  238. this.objects.set( this.audioManager, {
  239. duration: this.audioManager.duration
  240. } );
  241. return this;
  242. },
  243. _removeMesh: function ( mesh ) {
  244. var found = false;
  245. var writeIndex = 0;
  246. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  247. if ( this.meshes[ i ] === mesh ) {
  248. this.objects.delete( mesh );
  249. found = true;
  250. continue;
  251. }
  252. this.meshes[ writeIndex ++ ] = this.meshes[ i ];
  253. }
  254. if ( ! found ) {
  255. throw new Error( 'THREE.MMDAnimationHelper._removeMesh: '
  256. + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.' );
  257. }
  258. this.meshes.length = writeIndex;
  259. return this;
  260. },
  261. _clearCamera: function ( camera ) {
  262. if ( camera !== this.camera ) {
  263. throw new Error( 'THREE.MMDAnimationHelper._clearCamera: '
  264. + 'Camera \'' + camera.name + '\' has not been set yet.' );
  265. }
  266. this.camera.remove( this.cameraTarget );
  267. this.objects.delete( this.camera );
  268. this.camera = null;
  269. return this;
  270. },
  271. _clearAudio: function ( audio ) {
  272. if ( audio !== this.audio ) {
  273. throw new Error( 'THREE.MMDAnimationHelper._clearAudio: '
  274. + 'Audio \'' + audio.name + '\' has not been set yet.' );
  275. }
  276. this.objects.delete( this.audioManager );
  277. this.audio = null;
  278. this.audioManager = null;
  279. return this;
  280. },
  281. _setupMeshAnimation: function ( mesh, animation ) {
  282. var objects = this.objects.get( mesh );
  283. if ( animation !== undefined ) {
  284. var animations = Array.isArray( animation )
  285. ? animation : [ animation ];
  286. objects.mixer = new THREE.AnimationMixer( mesh );
  287. for ( var i = 0, il = animations.length; i < il; i ++ ) {
  288. objects.mixer.clipAction( animations[ i ] ).play();
  289. }
  290. // TODO: find a workaround not to access ._clip looking like a private property
  291. objects.mixer.addEventListener( 'loop', function ( event ) {
  292. var tracks = event.action._clip.tracks;
  293. if ( tracks.length > 0 &&
  294. tracks[ 0 ].name.slice( 0, 6 ) !== '.bones' ) return;
  295. objects.looped = true;
  296. } );
  297. }
  298. objects.ikSolver = this._createCCDIKSolver( mesh );
  299. objects.grantSolver = this.createGrantSolver( mesh );
  300. return this;
  301. },
  302. _setupCameraAnimation: function ( camera, animation ) {
  303. var animations = Array.isArray( animation )
  304. ? animation : [ animation ];
  305. var objects = this.objects.get( camera );
  306. objects.mixer = new THREE.AnimationMixer( camera );
  307. for ( var i = 0, il = animations.length; i < il; i ++ ) {
  308. objects.mixer.clipAction( animations[ i ] ).play();
  309. }
  310. },
  311. _setupMeshPhysics: function ( mesh, params ) {
  312. var objects = this.objects.get( mesh );
  313. // shared physics is experimental
  314. if ( params.world === undefined && this.sharedPhysics ) {
  315. var masterPhysics = this._getMasterPhysics();
  316. if ( masterPhysics !== null ) world = masterPhysics.world; // eslint-disable-line no-undef
  317. }
  318. objects.physics = this._createMMDPhysics( mesh, params );
  319. if ( objects.mixer && params.animationWarmup !== false ) {
  320. this._animateMesh( mesh, 0 );
  321. objects.physics.reset();
  322. }
  323. objects.physics.warmup( params.warmup !== undefined ? params.warmup : 60 );
  324. this._optimizeIK( mesh, true );
  325. },
  326. _animateMesh: function ( mesh, delta ) {
  327. var objects = this.objects.get( mesh );
  328. var mixer = objects.mixer;
  329. var ikSolver = objects.ikSolver;
  330. var grantSolver = objects.grantSolver;
  331. var physics = objects.physics;
  332. var looped = objects.looped;
  333. if ( mixer && this.enabled.animation ) {
  334. // alternate solution to save/restore bones but less performant?
  335. //mesh.pose();
  336. //this._updatePropertyMixersBuffer( mesh );
  337. this._restoreBones( mesh );
  338. mixer.update( delta );
  339. this._saveBones( mesh );
  340. // PMX animation system special path
  341. if ( this.configuration.pmxAnimation &&
  342. mesh.geometry.userData.MMD && mesh.geometry.userData.MMD.format === 'pmx' ) {
  343. if ( ! objects.sortedBonesData ) objects.sortedBonesData = this._sortBoneDataArray( mesh.geometry.userData.MMD.bones.slice() );
  344. this._animatePMXMesh(
  345. mesh,
  346. objects.sortedBonesData,
  347. ikSolver && this.enabled.ik ? ikSolver : null,
  348. grantSolver && this.enabled.grant ? grantSolver : null
  349. );
  350. } else {
  351. if ( ikSolver && this.enabled.ik ) {
  352. mesh.updateMatrixWorld( true );
  353. ikSolver.update();
  354. }
  355. if ( grantSolver && this.enabled.grant ) {
  356. grantSolver.update();
  357. }
  358. }
  359. }
  360. if ( looped === true && this.enabled.physics ) {
  361. if ( physics && this.configuration.resetPhysicsOnLoop ) physics.reset();
  362. objects.looped = false;
  363. }
  364. if ( physics && this.enabled.physics && ! this.sharedPhysics ) {
  365. this.onBeforePhysics( mesh );
  366. physics.update( delta );
  367. }
  368. },
  369. // Sort bones in order by 1. transformationClass and 2. bone index.
  370. // In PMX animation system, bone transformations should be processed
  371. // in this order.
  372. _sortBoneDataArray: function ( boneDataArray ) {
  373. return boneDataArray.sort( function ( a, b ) {
  374. if ( a.transformationClass !== b.transformationClass ) {
  375. return a.transformationClass - b.transformationClass;
  376. } else {
  377. return a.index - b.index;
  378. }
  379. } );
  380. },
  381. // PMX Animation system is a bit too complex and doesn't great match to
  382. // Three.js Animation system. This method attempts to simulate it as much as
  383. // possible but doesn't perfectly simulate.
  384. // This method is more costly than the regular one so
  385. // you are recommended to set constructor parameter "pmxAnimation: true"
  386. // only if your PMX model animation doesn't work well.
  387. // If you need better method you would be required to write your own.
  388. _animatePMXMesh: function () {
  389. // Keep working quaternions for less GC
  390. var quaternions = [];
  391. var quaternionIndex = 0;
  392. function getQuaternion() {
  393. if ( quaternionIndex >= quaternions.length ) {
  394. quaternions.push( new THREE.Quaternion() );
  395. }
  396. return quaternions[ quaternionIndex ++ ];
  397. }
  398. // Save rotation whose grant and IK are already applied
  399. // used by grant children
  400. var grantResultMap = new Map();
  401. function updateOne( mesh, boneIndex, ikSolver, grantSolver ) {
  402. var bones = mesh.skeleton.bones;
  403. var bonesData = mesh.geometry.userData.MMD.bones;
  404. var boneData = bonesData[ boneIndex ];
  405. var bone = bones[ boneIndex ];
  406. // Return if already updated by being referred as a grant parent.
  407. if ( grantResultMap.has( boneIndex ) ) return;
  408. var quaternion = getQuaternion();
  409. // Initialize grant result here to prevent infinite loop.
  410. // If it's referred before updating with actual result later
  411. // result without applyting IK or grant is gotten
  412. // but better than composing of infinite loop.
  413. grantResultMap.set( boneIndex, quaternion.copy( bone.quaternion ) );
  414. // @TODO: Support global grant and grant position
  415. if ( grantSolver && boneData.grant &&
  416. ! boneData.grant.isLocal && boneData.grant.affectRotation ) {
  417. var parentIndex = boneData.grant.parentIndex;
  418. var ratio = boneData.grant.ratio;
  419. if ( ! grantResultMap.has( parentIndex ) ) {
  420. updateOne( mesh, parentIndex, ikSolver, grantSolver );
  421. }
  422. grantSolver.addGrantRotation( bone, grantResultMap.get( parentIndex ), ratio );
  423. }
  424. if ( ikSolver && boneData.ik ) {
  425. // @TODO: Updating world matrices every time solving an IK bone is
  426. // costly. Optimize if possible.
  427. mesh.updateMatrixWorld( true );
  428. ikSolver.updateOne( boneData.ik );
  429. // No confident, but it seems the grant results with ik links should be updated?
  430. var links = boneData.ik.links;
  431. for ( var i = 0, il = links.length; i < il; i ++ ) {
  432. var link = links[ i ];
  433. if ( link.enabled === false ) continue;
  434. var linkIndex = link.index;
  435. if ( grantResultMap.has( linkIndex ) ) {
  436. grantResultMap.set( linkIndex, grantResultMap.get( linkIndex ).copy( bones[ linkIndex ].quaternion ) );
  437. }
  438. }
  439. }
  440. // Update with the actual result here
  441. quaternion.copy( bone.quaternion );
  442. }
  443. return function ( mesh, sortedBonesData, ikSolver, grantSolver ) {
  444. quaternionIndex = 0;
  445. grantResultMap.clear();
  446. for ( var i = 0, il = sortedBonesData.length; i < il; i ++ ) {
  447. updateOne( mesh, sortedBonesData[ i ].index, ikSolver, grantSolver );
  448. }
  449. mesh.updateMatrixWorld( true );
  450. return this;
  451. };
  452. }(),
  453. _animateCamera: function ( camera, delta ) {
  454. var mixer = this.objects.get( camera ).mixer;
  455. if ( mixer && this.enabled.cameraAnimation ) {
  456. mixer.update( delta );
  457. camera.updateProjectionMatrix();
  458. camera.up.set( 0, 1, 0 );
  459. camera.up.applyQuaternion( camera.quaternion );
  460. camera.lookAt( this.cameraTarget.position );
  461. }
  462. },
  463. _optimizeIK: function ( mesh, physicsEnabled ) {
  464. var iks = mesh.geometry.userData.MMD.iks;
  465. var bones = mesh.geometry.userData.MMD.bones;
  466. for ( var i = 0, il = iks.length; i < il; i ++ ) {
  467. var ik = iks[ i ];
  468. var links = ik.links;
  469. for ( var j = 0, jl = links.length; j < jl; j ++ ) {
  470. var link = links[ j ];
  471. if ( physicsEnabled === true ) {
  472. // disable IK of the bone the corresponding rigidBody type of which is 1 or 2
  473. // because its rotation will be overriden by physics
  474. link.enabled = bones[ link.index ].rigidBodyType > 0 ? false : true;
  475. } else {
  476. link.enabled = true;
  477. }
  478. }
  479. }
  480. },
  481. _createCCDIKSolver: function ( mesh ) {
  482. if ( THREE.CCDIKSolver === undefined ) {
  483. throw new Error( 'THREE.MMDAnimationHelper: Import THREE.CCDIKSolver.' );
  484. }
  485. return new THREE.CCDIKSolver( mesh, mesh.geometry.userData.MMD.iks );
  486. },
  487. _createMMDPhysics: function ( mesh, params ) {
  488. if ( THREE.MMDPhysics === undefined ) {
  489. throw new Error( 'THREE.MMDPhysics: Import THREE.MMDPhysics.' );
  490. }
  491. return new THREE.MMDPhysics(
  492. mesh,
  493. mesh.geometry.userData.MMD.rigidBodies,
  494. mesh.geometry.userData.MMD.constraints,
  495. params );
  496. },
  497. /*
  498. * Detects the longest duration and then sets it to them to sync.
  499. * TODO: Not to access private properties ( ._actions and ._clip )
  500. */
  501. _syncDuration: function () {
  502. var max = 0.0;
  503. var objects = this.objects;
  504. var meshes = this.meshes;
  505. var camera = this.camera;
  506. var audioManager = this.audioManager;
  507. // get the longest duration
  508. for ( var i = 0, il = meshes.length; i < il; i ++ ) {
  509. var mixer = this.objects.get( meshes[ i ] ).mixer;
  510. if ( mixer === undefined ) continue;
  511. for ( var j = 0; j < mixer._actions.length; j ++ ) {
  512. var clip = mixer._actions[ j ]._clip;
  513. if ( ! objects.has( clip ) ) {
  514. objects.set( clip, {
  515. duration: clip.duration
  516. } );
  517. }
  518. max = Math.max( max, objects.get( clip ).duration );
  519. }
  520. }
  521. if ( camera !== null ) {
  522. var mixer = this.objects.get( camera ).mixer;
  523. if ( mixer !== undefined ) {
  524. for ( var i = 0, il = mixer._actions.length; i < il; i ++ ) {
  525. var clip = mixer._actions[ i ]._clip;
  526. if ( ! objects.has( clip ) ) {
  527. objects.set( clip, {
  528. duration: clip.duration
  529. } );
  530. }
  531. max = Math.max( max, objects.get( clip ).duration );
  532. }
  533. }
  534. }
  535. if ( audioManager !== null ) {
  536. max = Math.max( max, objects.get( audioManager ).duration );
  537. }
  538. max += this.configuration.afterglow;
  539. // update the duration
  540. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  541. var mixer = this.objects.get( this.meshes[ i ] ).mixer;
  542. if ( mixer === undefined ) continue;
  543. for ( var j = 0, jl = mixer._actions.length; j < jl; j ++ ) {
  544. mixer._actions[ j ]._clip.duration = max;
  545. }
  546. }
  547. if ( camera !== null ) {
  548. var mixer = this.objects.get( camera ).mixer;
  549. if ( mixer !== undefined ) {
  550. for ( var i = 0, il = mixer._actions.length; i < il; i ++ ) {
  551. mixer._actions[ i ]._clip.duration = max;
  552. }
  553. }
  554. }
  555. if ( audioManager !== null ) {
  556. audioManager.duration = max;
  557. }
  558. },
  559. // workaround
  560. _updatePropertyMixersBuffer: function ( mesh ) {
  561. var mixer = this.objects.get( mesh ).mixer;
  562. var propertyMixers = mixer._bindings;
  563. var accuIndex = mixer._accuIndex;
  564. for ( var i = 0, il = propertyMixers.length; i < il; i ++ ) {
  565. var propertyMixer = propertyMixers[ i ];
  566. var buffer = propertyMixer.buffer;
  567. var stride = propertyMixer.valueSize;
  568. var offset = ( accuIndex + 1 ) * stride;
  569. propertyMixer.binding.getValue( buffer, offset );
  570. }
  571. },
  572. /*
  573. * Avoiding these two issues by restore/save bones before/after mixer animation.
  574. *
  575. * 1. PropertyMixer used by AnimationMixer holds cache value in .buffer.
  576. * Calculating IK, Grant, and Physics after mixer animation can break
  577. * the cache coherency.
  578. *
  579. * 2. Applying Grant two or more times without reset the posing breaks model.
  580. */
  581. _saveBones: function ( mesh ) {
  582. var objects = this.objects.get( mesh );
  583. var bones = mesh.skeleton.bones;
  584. var backupBones = objects.backupBones;
  585. if ( backupBones === undefined ) {
  586. backupBones = new Float32Array( bones.length * 7 );
  587. objects.backupBones = backupBones;
  588. }
  589. for ( var i = 0, il = bones.length; i < il; i ++ ) {
  590. var bone = bones[ i ];
  591. bone.position.toArray( backupBones, i * 7 );
  592. bone.quaternion.toArray( backupBones, i * 7 + 3 );
  593. }
  594. },
  595. _restoreBones: function ( mesh ) {
  596. var objects = this.objects.get( mesh );
  597. var backupBones = objects.backupBones;
  598. if ( backupBones === undefined ) return;
  599. var bones = mesh.skeleton.bones;
  600. for ( var i = 0, il = bones.length; i < il; i ++ ) {
  601. var bone = bones[ i ];
  602. bone.position.fromArray( backupBones, i * 7 );
  603. bone.quaternion.fromArray( backupBones, i * 7 + 3 );
  604. }
  605. },
  606. // experimental
  607. _getMasterPhysics: function () {
  608. if ( this.masterPhysics !== null ) return this.masterPhysics;
  609. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  610. var physics = this.meshes[ i ].physics;
  611. if ( physics !== undefined && physics !== null ) {
  612. this.masterPhysics = physics;
  613. return this.masterPhysics;
  614. }
  615. }
  616. return null;
  617. },
  618. _updateSharedPhysics: function ( delta ) {
  619. if ( this.meshes.length === 0 || ! this.enabled.physics || ! this.sharedPhysics ) return;
  620. var physics = this._getMasterPhysics();
  621. if ( physics === null ) return;
  622. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  623. var p = this.meshes[ i ].physics;
  624. if ( p !== null && p !== undefined ) {
  625. p.updateRigidBodies();
  626. }
  627. }
  628. physics.stepSimulation( delta );
  629. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  630. var p = this.meshes[ i ].physics;
  631. if ( p !== null && p !== undefined ) {
  632. p.updateBones();
  633. }
  634. }
  635. }
  636. };
  637. //
  638. /**
  639. * @param {THREE.Audio} audio
  640. * @param {Object} params - (optional)
  641. * @param {Nuumber} params.delayTime
  642. */
  643. function AudioManager( audio, params ) {
  644. params = params || {};
  645. this.audio = audio;
  646. this.elapsedTime = 0.0;
  647. this.currentTime = 0.0;
  648. this.delayTime = params.delayTime !== undefined
  649. ? params.delayTime : 0.0;
  650. this.audioDuration = this.audio.buffer.duration;
  651. this.duration = this.audioDuration + this.delayTime;
  652. }
  653. AudioManager.prototype = {
  654. constructor: AudioManager,
  655. /**
  656. * @param {Number} delta
  657. * @return {AudioManager}
  658. */
  659. control: function ( delta ) {
  660. this.elapsed += delta;
  661. this.currentTime += delta;
  662. if ( this._shouldStopAudio() ) this.audio.stop();
  663. if ( this._shouldStartAudio() ) this.audio.play();
  664. return this;
  665. },
  666. // private methods
  667. _shouldStartAudio: function () {
  668. if ( this.audio.isPlaying ) return false;
  669. while ( this.currentTime >= this.duration ) {
  670. this.currentTime -= this.duration;
  671. }
  672. if ( this.currentTime < this.delayTime ) return false;
  673. // 'duration' can be bigger than 'audioDuration + delayTime' because of sync configuration
  674. if ( ( this.currentTime - this.delayTime ) > this.audioDuration ) return false;
  675. return true;
  676. },
  677. _shouldStopAudio: function () {
  678. return this.audio.isPlaying &&
  679. this.currentTime >= this.duration;
  680. }
  681. };
  682. /**
  683. * Solver for Grant (Fuyo in Japanese. I just google translated because
  684. * Fuyo may be MMD specific term and may not be common word in 3D CG terms.)
  685. * Grant propagates a bone's transform to other bones transforms even if
  686. * they are not children.
  687. * @param {THREE.SkinnedMesh} mesh
  688. * @param {Array<Object>} grants
  689. */
  690. function GrantSolver( mesh, grants ) {
  691. this.mesh = mesh;
  692. this.grants = grants || [];
  693. }
  694. GrantSolver.prototype = {
  695. constructor: GrantSolver,
  696. /**
  697. * Solve all the grant bones
  698. * @return {GrantSolver}
  699. */
  700. update: function () {
  701. var grants = this.grants;
  702. for ( var i = 0, il = grants.length; i < il; i ++ ) {
  703. this.updateOne( grants[ i ] );
  704. }
  705. return this;
  706. },
  707. /**
  708. * Solve a grant bone
  709. * @param {Object} grant - grant parameter
  710. * @return {GrantSolver}
  711. */
  712. updateOne: function ( grant ) {
  713. var bones = this.mesh.skeleton.bones;
  714. var bone = bones[ grant.index ];
  715. var parentBone = bones[ grant.parentIndex ];
  716. if ( grant.isLocal ) {
  717. // TODO: implement
  718. if ( grant.affectPosition ) {
  719. }
  720. // TODO: implement
  721. if ( grant.affectRotation ) {
  722. }
  723. } else {
  724. // TODO: implement
  725. if ( grant.affectPosition ) {
  726. }
  727. if ( grant.affectRotation ) {
  728. this.addGrantRotation( bone, parentBone.quaternion, grant.ratio );
  729. }
  730. }
  731. return this;
  732. },
  733. addGrantRotation: function () {
  734. var quaternion = new THREE.Quaternion();
  735. return function ( bone, q, ratio ) {
  736. quaternion.set( 0, 0, 0, 1 );
  737. quaternion.slerp( q, ratio );
  738. bone.quaternion.multiply( quaternion );
  739. return this;
  740. };
  741. }()
  742. };
  743. return MMDAnimationHelper;
  744. } )();