MMDAnimationHelper.js 20 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018
  1. /**
  2. * @author takahiro / https://github.com/takahirox
  3. *
  4. * MMDAnimationHelper handles animation of MMD assets loaded by MMDLoader
  5. * with MMD special features as IK, Grant, and Physics.
  6. *
  7. * Dependencies
  8. * - ammo.js https://github.com/kripken/ammo.js
  9. * - THREE.MMDPhysics
  10. * - THREE.CCDIKSolver
  11. *
  12. * TODO
  13. * - more precise grant skinning support.
  14. */
  15. THREE.MMDAnimationHelper = ( function () {
  16. /**
  17. * @param {Object} params - (optional)
  18. * @param {boolean} params.sync - Whether animation durations of added objects are synched. Default is true.
  19. * @param {Number} params.afterglow - Default is 0.0.
  20. * @param {boolean} params resetPhysicsOnLoop - Default is true.
  21. */
  22. function MMDAnimationHelper( params ) {
  23. params = params || {};
  24. this.meshes = [];
  25. this.camera = null;
  26. this.cameraTarget = new THREE.Object3D();
  27. this.cameraTarget.name = 'target';
  28. this.audio = null;
  29. this.audioManager = null;
  30. this.objects = new WeakMap();
  31. this.configuration = {
  32. sync: params.sync !== undefined
  33. ? params.sync : true,
  34. afterglow: params.afterglow !== undefined
  35. ? params.afterglow : 0.0,
  36. resetPhysicsOnLoop: params.resetPhysicsOnLoop !== undefined
  37. ? params.resetPhysicsOnLoop : true
  38. };
  39. this.enabled = {
  40. animation: true,
  41. ik: true,
  42. grant: true,
  43. physics: true,
  44. cameraAnimation: true
  45. };
  46. // experimental
  47. this.sharedPhysics = false;
  48. this.masterPhysics = null;
  49. }
  50. MMDAnimationHelper.prototype = {
  51. constructor: MMDAnimationHelper,
  52. /**
  53. * Adds an Three.js Object to helper and setups animation.
  54. * The anmation durations of added objects are synched
  55. * if this.configuration.sync is true.
  56. *
  57. * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object
  58. * @param {Object} params - (optional)
  59. * @param {THREE.AnimationClip|Array<THREE.AnimationClip>} params.animation - Only for THREE.SkinnedMesh and THREE.Camera. Default is undefined.
  60. * @param {boolean} params.physics - Only for THREE.SkinnedMesh. Default is true.
  61. * @param {Number} params.delayTime - Only for THREE.Audio. Default is 0.0.
  62. * @return {THREE.MMDAnimationHelper}
  63. */
  64. add: function ( object, params ) {
  65. params = params || {};
  66. if ( object.isSkinnedMesh ) {
  67. this._addMesh( object, params );
  68. } else if ( object.isCamera ) {
  69. this._setupCamera( object, params );
  70. } else if ( object.type === 'Audio' ) {
  71. this._setupAudio( object, params );
  72. } else {
  73. throw new Error( 'THREE.MMDAnimationHelper.add: '
  74. + 'accepts only '
  75. + 'THREE.SkinnedMesh or '
  76. + 'THREE.Camera or '
  77. + 'THREE.Audio instance.' );
  78. }
  79. if ( this.configuration.sync ) this._syncDuration();
  80. return this;
  81. },
  82. /**
  83. * Removes an Three.js Object from helper.
  84. *
  85. * @param {THREE.SkinnedMesh|THREE.Camera|THREE.Audio} object
  86. * @return {THREE.MMDAnimationHelper}
  87. */
  88. remove: function ( object ) {
  89. if ( object.isSkinnedMesh ) {
  90. this._removeMesh( object );
  91. } else if ( object.isCamera ) {
  92. this._clearCamera( object );
  93. } else if ( object.type === 'Audio' ) {
  94. this._clearAudio( object );
  95. } else {
  96. throw new Error( 'THREE.MMDAnimationHelper.remove: '
  97. + 'accepts only '
  98. + 'THREE.SkinnedMesh or '
  99. + 'THREE.Camera or '
  100. + 'THREE.Audio instance.' );
  101. }
  102. if ( this.configuration.sync ) this._syncDuration();
  103. return this;
  104. },
  105. /**
  106. * Updates the animation.
  107. *
  108. * @param {Number} delta
  109. * @return {THREE.MMDAnimationHelper}
  110. */
  111. update: function ( delta ) {
  112. if ( this.audioManager !== null ) this.audioManager.control( delta );
  113. for ( var i = 0; i < this.meshes.length; i ++ ) {
  114. this._animateMesh( this.meshes[ i ], delta );
  115. }
  116. if ( this.sharedPhysics ) this._updateSharedPhysics( delta );
  117. if ( this.camera !== null ) this._animateCamera( this.camera, delta );
  118. return this;
  119. },
  120. /**
  121. * Changes the pose of SkinnedMesh as VPD specifies.
  122. *
  123. * @param {THREE.SkinnedMesh} mesh
  124. * @param {Object} vpd - VPD content parsed MMDParser
  125. * @param {Object} params - (optional)
  126. * @param {boolean} params.resetPose - Default is true.
  127. * @param {boolean} params.ik - Default is true.
  128. * @param {boolean} params.grant - Default is true.
  129. * @return {THREE.MMDAnimationHelper}
  130. */
  131. pose: function ( mesh, vpd, params ) {
  132. params = params || {};
  133. if ( params.resetPose !== false ) mesh.pose();
  134. var bones = mesh.skeleton.bones;
  135. var boneParams = vpd.bones;
  136. var boneNameDictionary = {};
  137. for ( var i = 0, il = bones.length; i < il; i ++ ) {
  138. boneNameDictionary[ bones[ i ].name ] = i;
  139. }
  140. var vector = new THREE.Vector3();
  141. var quaternion = new THREE.Quaternion();
  142. for ( var i = 0, il = boneParams.length; i < il; i ++ ) {
  143. var boneParam = boneParams[ i ];
  144. var boneIndex = boneNameDictionary[ boneParam.name ];
  145. if ( boneIndex === undefined ) continue;
  146. var bone = bones[ boneIndex ];
  147. bone.position.add( vector.fromArray( boneParam.translation ) );
  148. bone.quaternion.multiply( quaternion.fromArray( boneParam.quaternion ) );
  149. }
  150. mesh.updateMatrixWorld( true );
  151. if ( params.ik !== false ) {
  152. var solver = this._createCCDIKSolver( mesh );
  153. solver.update( params.saveOriginalBonesBeforeIK ); // this param is experimental
  154. }
  155. if ( params.grant !== false ) {
  156. var solver = this.createGrantSolver( mesh );
  157. solver.update();
  158. }
  159. return this;
  160. },
  161. /**
  162. * Enabes/Disables an animation feature.
  163. *
  164. * @param {string} key
  165. * @param {boolean} enebled
  166. * @return {THREE.MMDAnimationHelper}
  167. */
  168. enable: function ( key, enabled ) {
  169. if ( this.enabled[ key ] === undefined ) {
  170. throw new Error( 'THREE.MMDAnimationHelper.enable: '
  171. + 'unknown key ' + key );
  172. }
  173. this.enabled[ key ] = enabled;
  174. if ( key === 'physics' ) {
  175. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  176. this._optimizeIK( this.meshes[ i ], enabled );
  177. }
  178. }
  179. return this;
  180. },
  181. /**
  182. * Creates an GrantSolver instance.
  183. *
  184. * @param {THREE.SkinnedMesh} mesh
  185. * @return {GrantSolver}
  186. */
  187. createGrantSolver: function ( mesh ) {
  188. return new GrantSolver( mesh );
  189. },
  190. // private methods
  191. _addMesh: function ( mesh, params ) {
  192. if ( this.meshes.indexOf( mesh ) >= 0 ) {
  193. throw new Error( 'THREE.MMDAnimationHelper._addMesh: '
  194. + 'SkinnedMesh \'' + mesh.name + '\' has already been added.' );
  195. }
  196. this.meshes.push( mesh );
  197. this.objects.set( mesh, { looped: false } );
  198. // workaround until I make IK and Physics Animation plugin
  199. this._initBackupBones( mesh );
  200. if ( params.animation !== undefined ) {
  201. this._setupMeshAnimation( mesh, params.animation );
  202. }
  203. if ( params.physics !== false ) {
  204. this._setupMeshPhysics( mesh, params );
  205. }
  206. return this;
  207. },
  208. _setupCamera: function ( camera, params ) {
  209. if ( this.camera === camera ) {
  210. throw new Error( 'THREE.MMDAnimationHelper._setupCamera: '
  211. + 'Camera \'' + camera.name + '\' has already been set.' );
  212. }
  213. if ( this.camera ) this.clearCamera( this.camera );
  214. this.camera = camera;
  215. camera.add( this.cameraTarget );
  216. this.objects.set( camera, {} );
  217. if ( params.animation !== undefined ) {
  218. this._setupCameraAnimation( camera, params.animation )
  219. }
  220. return this;
  221. },
  222. _setupAudio: function ( audio, params ) {
  223. if ( this.audio === audio ) {
  224. throw new Error( 'THREE.MMDAnimationHelper._setupAudio: '
  225. + 'Audio \'' + audio.name + '\' has already been set.' );
  226. }
  227. if ( this.audio ) this.clearAudio( this.audio );
  228. this.audio = audio;
  229. this.audioManager = new AudioManager( audio, params );
  230. this.objects.set( this.audioManager, {
  231. duration: this.audioManager.duration
  232. } );
  233. return this;
  234. },
  235. _removeMesh: function ( mesh ) {
  236. var found = false;
  237. var writeIndex = 0;
  238. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  239. if ( this.meshes[ i ] === mesh ) {
  240. this.objects.delete( mesh );
  241. found = true;
  242. continue;
  243. }
  244. this.meshes[ writeIndex ++ ] = this.meshes[ i ];
  245. }
  246. if ( ! found ) {
  247. throw new Error( 'THREE.MMDAnimationHelper._removeMesh: '
  248. + 'SkinnedMesh \'' + mesh.name + '\' has not been added yet.' );
  249. }
  250. this.meshes.length = writeIndex;
  251. return this;
  252. },
  253. _clearCamera: function ( camera ) {
  254. if ( camera !== this.camera ) {
  255. throw new Error( 'THREE.MMDAnimationHelper._clearCamera: '
  256. + 'Camera \'' + camera.name + '\' has not been set yet.' );
  257. }
  258. this.camera.remove( this.cameraTarget );
  259. this.params.delete( this.camera );
  260. this.camera = null;
  261. return this;
  262. },
  263. _clearAudio: function ( audio ) {
  264. if ( audio !== this.audio ) {
  265. throw new Error( 'THREE.MMDAnimationHelper._clearAudio: '
  266. + 'Audio \'' + audio.name + '\' has not been set yet.' );
  267. }
  268. this.objects.delete( this.audioManager );
  269. this.audio = null;
  270. this.audioManager = null;
  271. return this;
  272. },
  273. _setupMeshAnimation: function ( mesh, animation ) {
  274. var animations = Array.isArray( animation )
  275. ? animation : [ animation ];
  276. var objects = this.objects.get( mesh );
  277. objects.mixer = new THREE.AnimationMixer( mesh );
  278. for ( var i = 0, il = animations.length; i < il; i ++ ) {
  279. objects.mixer.clipAction( animations[ i ] ).play();
  280. }
  281. // TODO: find a workaround not to access ._clip looking like a private property
  282. objects.mixer.addEventListener( 'loop', function ( event ) {
  283. var tracks = event.action._clip.tracks;
  284. if ( tracks.length > 0 &&
  285. tracks[ 0 ].name.slice( 0, 6 ) !== '.bones' ) return;
  286. objects.looped = true;
  287. } );
  288. objects.ikSolver = this._createCCDIKSolver( mesh );
  289. objects.grantSolver = this.createGrantSolver( mesh );
  290. return this;
  291. },
  292. _setupCameraAnimation: function ( camera, animation ) {
  293. var animations = Array.isArray( animation )
  294. ? animation : [ animation ];
  295. var objects = this.objects.get( camera );
  296. objects.mixer = new THREE.AnimationMixer( camera );
  297. for ( var i = 0, il = animations.length; i < il; i ++ ) {
  298. objects.mixer.clipAction( animations[ i ] ).play();
  299. }
  300. },
  301. _setupMeshPhysics: function ( mesh, params ) {
  302. params = Object.assign( {}, params );
  303. var objects = this.objects.get( mesh );
  304. // shared physics is experimental
  305. if ( params.world === undefined && this.sharedPhysics ) {
  306. var masterPhysics = this._getMasterPhysics();
  307. if ( masterPhysics !== null ) world = masterPhysics.world;
  308. }
  309. var warmup = params.warmup !== undefined ? params.warmup : 60;
  310. objects.physics = this._createMMDPhysics( mesh, params );
  311. if ( objects.mixer && params.animationWarmup !== false ) {
  312. this._animateMesh( mesh, 0 );
  313. objects.physics.reset();
  314. }
  315. objects.physics.warmup( warmup );
  316. this._optimizeIK( mesh, true );
  317. },
  318. _animateMesh: function ( mesh, delta ) {
  319. var objects = this.objects.get( mesh );
  320. var mixer = objects.mixer;
  321. var ikSolver = objects.ikSolver;
  322. var grantSolver = objects.grantSolver;
  323. var physics = objects.physics;
  324. var looped = objects.looped;
  325. if ( mixer && this.enabled.animation ) {
  326. // restore/backupBones are workaround
  327. // until I make IK, Grant, and Physics Animation plugin
  328. this._restoreBones( mesh );
  329. mixer.update( delta );
  330. this._backupBones( mesh );
  331. }
  332. if ( ikSolver && this.enabled.ik ) {
  333. ikSolver.update();
  334. }
  335. if ( grantSolver && this.enabled.grant ) {
  336. grantSolver.update();
  337. }
  338. if ( looped === true && this.enabled.physics ) {
  339. if ( physics && this.configuration.resetPhysicsOnLoop ) physics.reset();
  340. objects.looped = false;
  341. }
  342. if ( physics && this.enabled.physics && ! this.sharedPhysics ) {
  343. physics.update( delta );
  344. }
  345. },
  346. _animateCamera: function ( camera, delta ) {
  347. var mixer = this.objects.get( camera ).mixer;
  348. if ( mixer && this.enabled.cameraAnimation ) {
  349. mixer.update( delta );
  350. camera.updateProjectionMatrix();
  351. camera.up.set( 0, 1, 0 );
  352. camera.up.applyQuaternion( camera.quaternion );
  353. camera.lookAt( this.cameraTarget.position );
  354. }
  355. },
  356. _optimizeIK: function ( mesh, physicsEnabled ) {
  357. var iks = mesh.geometry.iks;
  358. var bones = mesh.geometry.bones;
  359. for ( var i = 0, il = iks.length; i < il; i ++ ) {
  360. var ik = iks[ i ];
  361. var links = ik.links;
  362. for ( var j = 0, jl = links.length; j < jl; j ++ ) {
  363. var link = links[ j ];
  364. if ( physicsEnabled === true ) {
  365. // disable IK of the bone the corresponding rigidBody type of which is 1 or 2
  366. // because its rotation will be overriden by physics
  367. link.enabled = bones[ link.index ].rigidBodyType > 0 ? false : true;
  368. } else {
  369. link.enabled = true;
  370. }
  371. }
  372. }
  373. },
  374. _createCCDIKSolver: function ( mesh ) {
  375. if ( THREE.CCDIKSolver === undefined ) {
  376. throw new Error( 'THREE.MMDAnimationHelper: Import THREE.CCDIKSolver.' );
  377. }
  378. return new THREE.CCDIKSolver( mesh );
  379. },
  380. _createMMDPhysics: function ( mesh, params ) {
  381. if ( THREE.MMDPhysics === undefined ) {
  382. throw new Error( 'THREE.MMDPhysics: Import THREE.MMDPhysics.' );
  383. }
  384. return new THREE.MMDPhysics( mesh, params );
  385. },
  386. /*
  387. * Detects the longest duration and then sets it to them to sync.
  388. * TODO: Not to access private properties ( ._actions and ._clip )
  389. */
  390. _syncDuration: function () {
  391. var max = 0.0;
  392. var objects = this.objects;
  393. var meshes = this.meshes;
  394. var camera = this.camera;
  395. var audioManager = this.audioManager;
  396. // get the longest duration
  397. for ( var i = 0, il = meshes.length; i < il; i ++ ) {
  398. var mixer = this.objects.get( meshes[ i ] ).mixer;
  399. if ( mixer === undefined ) continue;
  400. for ( var j = 0; j < mixer._actions.length; j ++ ) {
  401. var clip = mixer._actions[ j ]._clip;
  402. if ( ! objects.has( clip ) ) {
  403. objects.set( clip, {
  404. duration: clip.duration
  405. } )
  406. }
  407. max = Math.max( max, objects.get( clip ).duration );
  408. }
  409. }
  410. if ( camera !== null ) {
  411. var mixer = this.objects.get( camera ).mixer;
  412. if ( mixer !== undefined ) {
  413. for ( var i = 0, il = mixer._actions.length; i < il; i ++ ) {
  414. var clip = mixer._actions[ i ]._clip;
  415. if ( ! objects.has( clip ) ) {
  416. objects.set( clip, {
  417. duration: clip.duration
  418. } )
  419. }
  420. max = Math.max( max, objects.get( clip ).duration );
  421. }
  422. }
  423. }
  424. if ( audioManager !== null ) {
  425. max = Math.max( max, objects.get( audioManager ).duration );
  426. }
  427. max += this.configuration.afterglow;
  428. // update the duration
  429. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  430. var mixer = this.objects.get( this.meshes[ i ] ).mixer;
  431. if ( mixer === undefined ) continue;
  432. for ( var j = 0, jl = mixer._actions.length; j < jl; j ++ ) {
  433. mixer._actions[ j ]._clip.duration = max;
  434. }
  435. }
  436. if ( camera !== null ) {
  437. var mixer = this.objects.get( camera ).mixer;
  438. if ( mixer !== undefined ) {
  439. for ( var i = 0, il = mixer._actions.length; i < il; i ++ ) {
  440. mixer._actions[ i ]._clip.duration = max;
  441. }
  442. }
  443. }
  444. if ( audioManager !== null ) {
  445. audioManager.duration = max;
  446. }
  447. },
  448. // workaround
  449. /*
  450. * Note: These following three functions are workaround for r74dev.
  451. * THREE.PropertyMixer.apply() seems to save values into buffer cache
  452. * when mixer.update() is called.
  453. * ikSolver.update() and physics.update() change bone position/quaternion
  454. * without mixer.update() then buffer cache will be inconsistent.
  455. * So trying to avoid buffer cache inconsistency by doing
  456. * backup bones position/quaternion right after mixer.update() call
  457. * and then restore them after rendering.
  458. */
  459. _initBackupBones: function ( mesh ) {
  460. var backupBones = [];
  461. for ( var i = 0, il = mesh.skeleton.bones.length; i < il; i ++ ) {
  462. backupBones.push( mesh.skeleton.bones[ i ].clone() );
  463. }
  464. this.objects.get( mesh ).backupBones = backupBones;
  465. },
  466. _backupBones: function ( mesh ) {
  467. var objects = this.objects.get( mesh );
  468. objects.backupBoneIsSaved = true;
  469. var backupBones = objects.backupBones;
  470. for ( var i = 0, il = mesh.skeleton.bones.length; i < il; i ++ ) {
  471. var backupBone = backupBones[ i ];
  472. var bone = mesh.skeleton.bones[ i ];
  473. backupBone.position.copy( bone.position );
  474. backupBone.quaternion.copy( bone.quaternion );
  475. }
  476. },
  477. _restoreBones: function ( mesh ) {
  478. var objects = this.objects.get( mesh );
  479. if ( objects.backupBoneIsSaved !== true ) return;
  480. objects.backupBoneIsSaved = false;
  481. var backupBones = objects.backupBones;
  482. for ( var i = 0, il = mesh.skeleton.bones.length; i < il; i ++ ) {
  483. var bone = mesh.skeleton.bones[ i ];
  484. var backupBone = backupBones[ i ];
  485. bone.position.copy( backupBone.position );
  486. bone.quaternion.copy( backupBone.quaternion );
  487. }
  488. },
  489. // experimental
  490. _getMasterPhysics: function () {
  491. if ( this.masterPhysics !== null ) return this.masterPhysics;
  492. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  493. var physics = this.meshes[ i ].physics;
  494. if ( physics !== undefined && physics !== null ) {
  495. this.masterPhysics = physics;
  496. return this.masterPhysics;
  497. }
  498. }
  499. return null;
  500. },
  501. _updateSharedPhysics: function ( delta ) {
  502. if ( this.meshes.length === 0 || ! this.enabled.physics || ! this.sharedPhysics ) return;
  503. var physics = this._getMasterPhysics();
  504. if ( physics === null ) return;
  505. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  506. var p = this.meshes[ i ].physics;
  507. if ( p !== null && p !== undefined ) {
  508. p.updateRigidBodies();
  509. }
  510. }
  511. physics.stepSimulation( delta );
  512. for ( var i = 0, il = this.meshes.length; i < il; i ++ ) {
  513. var p = this.meshes[ i ].physics;
  514. if ( p !== null && p !== undefined ) {
  515. p.updateBones();
  516. }
  517. }
  518. }
  519. };
  520. //
  521. /**
  522. * @param {THREE.Audio} audio
  523. * @param {Object} params - (optional)
  524. * @param {Nuumber} params.delayTime
  525. */
  526. function AudioManager( audio, params ) {
  527. params = params || {};
  528. this.audio = audio;
  529. this.elapsedTime = 0.0;
  530. this.currentTime = 0.0;
  531. this.delayTime = params.delayTime !== undefined
  532. ? params.delayTime : 0.0;
  533. this.audioDuration = this.audio.buffer.duration;
  534. this.duration = this.audioDuration + this.delayTime;
  535. }
  536. AudioManager.prototype = {
  537. constructor: AudioManager,
  538. /**
  539. * @param {Number} delta
  540. * @return {AudioManager}
  541. */
  542. control: function ( delta ) {
  543. this.elapsed += delta;
  544. this.currentTime += delta;
  545. if ( this._shouldStopAudio() ) this.audio.stop();
  546. if ( this._shouldStartAudio() ) this.audio.play();
  547. return this;
  548. },
  549. // private methods
  550. _shouldStartAudio: function () {
  551. if ( this.audio.isPlaying ) return false;
  552. while ( this.currentTime >= this.duration ) {
  553. this.currentTime -= this.duration;
  554. }
  555. if ( this.currentTime < this.delayTime ) return false;
  556. this.audio.startTime = this.currentTime - this.delayTime;
  557. return true;
  558. },
  559. _shouldStopAudio: function () {
  560. return this.audio.isPlaying &&
  561. this.currentTime >= this.duration;
  562. }
  563. };
  564. /**
  565. * @param {THREE.SkinnedMesh} mesh
  566. */
  567. function GrantSolver( mesh ) {
  568. this.mesh = mesh;
  569. }
  570. GrantSolver.prototype = {
  571. constructor: GrantSolver,
  572. /**
  573. * @return {GrantSolver}
  574. */
  575. update: function () {
  576. var quaternion = new THREE.Quaternion();
  577. return function () {
  578. for ( var i = 0, il = this.mesh.geometry.grants.length; i < il; i ++ ) {
  579. var grant = this.mesh.geometry.grants[ i ];
  580. var bone = this.mesh.skeleton.bones[ grant.index ];
  581. var parentBone = this.mesh.skeleton.bones[ grant.parentIndex ];
  582. if ( grant.isLocal ) {
  583. // TODO: implement
  584. if ( grant.affectPosition ) {
  585. }
  586. // TODO: implement
  587. if ( grant.affectRotation ) {
  588. }
  589. } else {
  590. // TODO: implement
  591. if ( grant.affectPosition ) {
  592. }
  593. if ( grant.affectRotation ) {
  594. quaternion.set( 0, 0, 0, 1 );
  595. quaternion.slerp( parentBone.quaternion, grant.ratio );
  596. bone.quaternion.multiply( quaternion );
  597. }
  598. }
  599. }
  600. this;
  601. };
  602. }()
  603. };
  604. return MMDAnimationHelper;
  605. } )();