AnimationMixer.js 28 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399
  1. /**
  2. *
  3. * Player for AnimationClips.
  4. *
  5. *
  6. * @author Ben Houston / http://clara.io/
  7. * @author David Sarno / http://lighthaus.us/
  8. * @author tschw
  9. */
  10. THREE.AnimationMixer = function( root ) {
  11. this._root = root;
  12. this._initMemoryManager();
  13. this._accuIndex = 0;
  14. this.time = 0;
  15. this.timeScale = 1.0;
  16. };
  17. THREE.AnimationMixer.prototype = {
  18. constructor: THREE.AnimationMixer,
  19. // return an action for a clip optionally using a custom root target
  20. // object (this method allocates a lot of dynamic memory in case a
  21. // previously unknown clip/root combination is specified)
  22. clipAction: function( clip, optionalRoot ) {
  23. var root = optionalRoot || this._root,
  24. rootUuid = root.uuid,
  25. clipName = ( typeof clip === 'string' ) ? clip : clip.name,
  26. clipObject = ( clip !== clipName ) ? clip : null,
  27. actionsForClip = this._actionsByClip[ clipName ],
  28. prototypeAction;
  29. if ( actionsForClip !== undefined ) {
  30. var existingAction =
  31. actionsForClip.actionByRoot[ rootUuid ];
  32. if ( existingAction !== undefined ) {
  33. return existingAction;
  34. }
  35. // we know the clip, so we don't have to parse all
  36. // the bindings again but can just copy
  37. prototypeAction = actionsForClip.knownActions[ 0 ];
  38. // also, take the clip from the prototype action
  39. clipObject = prototypeAction._clip;
  40. if ( clip !== clipName && clip !== clipObject ) {
  41. throw new Error(
  42. "Different clips with the same name detected!" );
  43. }
  44. }
  45. // clip must be known when specified via string
  46. if ( clipObject === null ) return null;
  47. // allocate all resources required to run it
  48. var newAction = new THREE.
  49. AnimationMixer._Action( this, clipObject, optionalRoot );
  50. this._bindAction( newAction, prototypeAction );
  51. // and make the action known to the memory manager
  52. this._addInactiveAction( newAction, clipName, rootUuid );
  53. return newAction;
  54. },
  55. // get an existing action
  56. existingAction: function( clip, optionalRoot ) {
  57. var root = optionalRoot || this._root,
  58. rootUuid = root.uuid,
  59. clipName = ( typeof clip === 'string' ) ? clip : clip.name,
  60. actionsForClip = this._actionsByClip[ clipName ];
  61. if ( actionsForClip !== undefined ) {
  62. return actionsForClip.actionByRoot[ rootUuid ] || null;
  63. }
  64. return null;
  65. },
  66. // deactivates all previously scheduled actions
  67. stopAllAction: function() {
  68. var actions = this._actions,
  69. nActions = this._nActiveActions,
  70. bindings = this._bindings,
  71. nBindings = this._nActiveBindings;
  72. this._nActiveActions = 0;
  73. this._nActiveBindings = 0;
  74. for ( var i = 0; i !== nActions; ++ i ) {
  75. actions[ i ].reset();
  76. }
  77. for ( var i = 0; i !== nBindings; ++ i ) {
  78. bindings[ i ].useCount = 0;
  79. }
  80. return this;
  81. },
  82. // advance the time and update apply the animation
  83. update: function( deltaTime ) {
  84. deltaTime *= this.timeScale;
  85. var actions = this._actions,
  86. nActions = this._nActiveActions,
  87. time = this.time += deltaTime,
  88. timeDirection = Math.sign( deltaTime ),
  89. accuIndex = this._accuIndex ^= 1;
  90. // run active actions
  91. for ( var i = 0; i !== nActions; ++ i ) {
  92. var action = actions[ i ];
  93. if ( action.enabled ) {
  94. action._update( time, deltaTime, timeDirection, accuIndex );
  95. }
  96. }
  97. // update scene graph
  98. var bindings = this._bindings,
  99. nBindings = this._nActiveBindings;
  100. for ( var i = 0; i !== nBindings; ++ i ) {
  101. bindings[ i ].apply( accuIndex );
  102. }
  103. return this;
  104. },
  105. // return this mixer's root target object
  106. getRoot: function() {
  107. return this._root;
  108. },
  109. // free all resources specific to a particular clip
  110. uncacheClip: function( clip ) {
  111. var actions = this._actions,
  112. clipName = clip.name,
  113. actionsByClip = this._actionsByClip,
  114. actionsForClip = actionsByClip[ clipName ];
  115. if ( actionsForClip !== undefined ) {
  116. // note: just calling _removeInactiveAction would mess up the
  117. // iteration state and also require updating the state we can
  118. // just throw away
  119. var actionsToRemove = actionsForClip.knownActions;
  120. for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
  121. var action = actionsToRemove[ i ];
  122. this._deactivateAction( action );
  123. var cacheIndex = action._cacheIndex,
  124. lastInactiveAction = actions[ actions.length - 1 ];
  125. action._cacheIndex = null;
  126. action._byClipCacheIndex = null;
  127. lastInactiveAction._cacheIndex = cacheIndex;
  128. actions[ cacheIndex ] = lastInactiveAction;
  129. actions.pop();
  130. this._removeInactiveBindingsForAction( action );
  131. }
  132. delete actionsByClip[ clipName ];
  133. }
  134. },
  135. // free all resources specific to a particular root target object
  136. uncacheRoot: function( root ) {
  137. var rootUuid = root.uuid,
  138. actionsByClip = this._actionsByClip;
  139. for ( var clipName in actionsByClip ) {
  140. var actionByRoot = actionsByClip[ clipName ].actionByRoot,
  141. action = actionByRoot[ rootUuid ];
  142. if ( action !== undefined ) {
  143. this._deactivateAction( action );
  144. this._removeInactiveAction( action );
  145. }
  146. }
  147. var bindingsByRoot = this._bindingsByRootAndName,
  148. bindingByName = bindingsByRoot[ rootUuid ];
  149. if ( bindingByName !== undefined ) {
  150. for ( var trackName in bindingByName ) {
  151. var binding = bindingByName[ trackName ];
  152. binding.restoreOriginalState();
  153. this._removeInactiveBinding( binding );
  154. }
  155. }
  156. },
  157. // remove a targeted clip from the cache
  158. uncacheAction: function( clip, optionalRoot ) {
  159. var action = this.existingAction( clip, optionalRoot );
  160. if ( action !== null ) {
  161. this._deactivateAction( action );
  162. this._removeInactiveAction( action );
  163. }
  164. }
  165. };
  166. THREE.EventDispatcher.prototype.apply( THREE.AnimationMixer.prototype );
  167. THREE.AnimationMixer._Action =
  168. function( mixer, clip, localRoot ) {
  169. this._mixer = mixer;
  170. this._clip = clip;
  171. this._localRoot = localRoot || null;
  172. var tracks = clip.tracks,
  173. nTracks = tracks.length,
  174. interpolants = new Array( nTracks );
  175. var interpolantSettings = {
  176. endingStart: THREE.ZeroCurvatureEnding,
  177. endingEnd: THREE.ZeroCurvatureEnding
  178. };
  179. for ( var i = 0; i !== nTracks; ++ i ) {
  180. var interpolant = tracks[ i ].createInterpolant( null );
  181. interpolants[ i ] = interpolant;
  182. interpolant.settings = interpolantSettings
  183. }
  184. this._interpolantSettings = interpolantSettings;
  185. this._interpolants = interpolants; // bound by the mixer
  186. // inside: PropertyMixer (managed by the mixer)
  187. this._propertyBindings = new Array( nTracks );
  188. this._cacheIndex = null; // for the memory manager
  189. this._byClipCacheIndex = null; // for the memory manager
  190. this._timeScaleInterpolant = null;
  191. this._weightInterpolant = null;
  192. this.loop = THREE.LoopRepeat;
  193. this._loopCount = -1;
  194. // global mixer time when the action is to be started
  195. // it's set back to 'null' upon start of the action
  196. this._startTime = null;
  197. // scaled local time of the action
  198. // gets clamped or wrapped to 0..clip.duration according to loop
  199. this.time = 0;
  200. this.timeScale = 1;
  201. this._effectiveTimeScale = 1;
  202. this.weight = 1;
  203. this._effectiveWeight = 1;
  204. this.repetitions = Infinity; // no. of repetitions when looping
  205. this.paused = false; // false -> zero effective time scale
  206. this.enabled = true; // true -> zero effective weight
  207. this.clampWhenFinished = false; // keep feeding the last frame?
  208. this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate
  209. this.zeroSlopeAtEnd = true; // clips for start, loop and end
  210. };
  211. THREE.AnimationMixer._Action.prototype = {
  212. constructor: THREE.AnimationMixer._Action,
  213. // State & Scheduling
  214. play: function() {
  215. this._mixer._activateAction( this );
  216. return this;
  217. },
  218. stop: function() {
  219. this._mixer._deactivateAction( this );
  220. return this.reset();
  221. },
  222. reset: function() {
  223. this.paused = false;
  224. this.enabled = true;
  225. this.time = 0; // restart clip
  226. this._loopCount = -1; // forget previous loops
  227. this._startTime = null; // forget scheduling
  228. return this.stopFading().stopWarping();
  229. },
  230. isRunning: function() {
  231. var start = this._startTime;
  232. return this.enabled && ! this.paused && this.timeScale !== 0 &&
  233. this._startTime === null && this._mixer._isActiveAction( this )
  234. },
  235. // return true when play has been called
  236. isScheduled: function() {
  237. return this._mixer._isActiveAction( this );
  238. },
  239. startAt: function( time ) {
  240. this._startTime = time;
  241. return this;
  242. },
  243. setLoop: function( mode, repetitions ) {
  244. this.loop = mode;
  245. this.repetitions = repetitions;
  246. return this;
  247. },
  248. // Weight
  249. // set the weight stopping any scheduled fading
  250. // although .enabled = false yields an effective weight of zero, this
  251. // method does *not* change .enabled, because it would be confusing
  252. setEffectiveWeight: function( weight ) {
  253. this.weight = weight;
  254. // note: same logic as when updated at runtime
  255. this._effectiveWeight = this.enabled ? weight : 0;
  256. return this.stopFading();
  257. },
  258. // return the weight considering fading and .enabled
  259. getEffectiveWeight: function() {
  260. return this._effectiveWeight;
  261. },
  262. fadeIn: function( duration ) {
  263. return this._scheduleFading( duration, 0, 1 );
  264. },
  265. fadeOut: function( duration ) {
  266. return this._scheduleFading( duration, 1, 0 );
  267. },
  268. crossFadeFrom: function( fadeOutAction, duration, warp ) {
  269. var mixer = this._mixer;
  270. fadeOutAction.fadeOut( duration );
  271. this.fadeIn( duration );
  272. if( warp ) {
  273. var fadeInDuration = this._clip.duration,
  274. fadeOutDuration = fadeOutAction._clip.duration,
  275. startEndRatio = fadeOutDuration / fadeInDuration,
  276. endStartRatio = fadeInDuration / fadeOutDuration;
  277. fadeOutAction.warp( 1.0, startEndRatio, duration );
  278. this.warp( endStartRatio, 1.0, duration );
  279. }
  280. return this;
  281. },
  282. crossFadeTo: function( fadeInAction, duration, warp ) {
  283. return fadeInAction.crossFadeFrom( this, duration, warp );
  284. },
  285. stopFading: function() {
  286. var weightInterpolant = this._weightInterpolant;
  287. if ( weightInterpolant !== null ) {
  288. this._weightInterpolant = null;
  289. this._mixer._takeBackControlInterpolant( weightInterpolant );
  290. }
  291. return this;
  292. },
  293. // Time Scale Control
  294. // set the weight stopping any scheduled warping
  295. // although .paused = true yields an effective time scale of zero, this
  296. // method does *not* change .paused, because it would be confusing
  297. setEffectiveTimeScale: function( timeScale ) {
  298. this.timeScale = timeScale;
  299. this._effectiveTimeScale = this.paused ? 0 :timeScale;
  300. return this.stopWarping();
  301. },
  302. // return the time scale considering warping and .paused
  303. getEffectiveTimeScale: function() {
  304. return this._effectiveTimeScale;
  305. },
  306. setDuration: function( duration ) {
  307. this.timeScale = this._clip.duration / duration;
  308. return this.stopWarping();
  309. },
  310. syncWith: function( action ) {
  311. this.time = action.time;
  312. this.timeScale = action.timeScale;
  313. return this.stopWarping();
  314. },
  315. halt: function( duration ) {
  316. return this.warp( this._currentTimeScale, 0, duration );
  317. },
  318. warp: function( startTimeScale, endTimeScale, duration ) {
  319. var mixer = this._mixer, now = mixer.time,
  320. interpolant = this._timeScaleInterpolant,
  321. timeScale = this.timeScale;
  322. if ( interpolant === null ) {
  323. interpolant = mixer._lendControlInterpolant(),
  324. this._timeScaleInterpolant = interpolant;
  325. }
  326. var times = interpolant.parameterPositions,
  327. values = interpolant.sampleValues;
  328. times[ 0 ] = now;
  329. times[ 1 ] = now + duration;
  330. values[ 0 ] = startTimeScale / timeScale;
  331. values[ 1 ] = endTimeScale / timeScale;
  332. return this;
  333. },
  334. stopWarping: function() {
  335. var timeScaleInterpolant = this._timeScaleInterpolant;
  336. if ( timeScaleInterpolant !== null ) {
  337. this._timeScaleInterpolant = null;
  338. this._mixer._takeBackControlInterpolant( timeScaleInterpolant );
  339. }
  340. return this;
  341. },
  342. // Object Accessors
  343. getMixer: function() {
  344. return this._mixer;
  345. },
  346. getClip: function() {
  347. return this._clip;
  348. },
  349. getRoot: function() {
  350. return this._localRoot || this._mixer._root;
  351. },
  352. // Interna
  353. _update: function( time, deltaTime, timeDirection, accuIndex ) {
  354. // called by the mixer
  355. var startTime = this._startTime;
  356. if ( startTime !== null ) {
  357. // check for scheduled start of action
  358. var timeRunning = ( time - startTime ) * timeDirection;
  359. if ( timeRunning < 0 || timeDirection === 0 ) {
  360. return; // yet to come / don't decide when delta = 0
  361. }
  362. // start
  363. this._startTime = null; // unschedule
  364. deltaTime = timeDirection * timeRunning;
  365. }
  366. // apply time scale and advance time
  367. deltaTime *= this._updateTimeScale( time );
  368. var clipTime = this._updateTime( deltaTime );
  369. // note: _updateTime may disable the action resulting in
  370. // an effective weight of 0
  371. var weight = this._updateWeight( time );
  372. if ( weight > 0 ) {
  373. var interpolants = this._interpolants;
  374. var propertyMixers = this._propertyBindings;
  375. for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {
  376. interpolants[ j ].evaluate( clipTime );
  377. propertyMixers[ j ].accumulate( accuIndex, weight );
  378. }
  379. }
  380. },
  381. _updateWeight: function( time ) {
  382. var weight = 0;
  383. if ( this.enabled ) {
  384. weight = this.weight;
  385. var interpolant = this._weightInterpolant;
  386. if ( interpolant !== null ) {
  387. var interpolantValue = interpolant.evaluate( time )[ 0 ];
  388. weight *= interpolantValue;
  389. if ( time > interpolant.parameterPositions[ 1 ] ) {
  390. this.stopFading();
  391. if ( interpolantValue === 0 ) {
  392. // faded out, disable
  393. this.enabled = false;
  394. }
  395. }
  396. }
  397. }
  398. this._effectiveWeight = weight;
  399. return weight;
  400. },
  401. _updateTimeScale: function( time ) {
  402. var timeScale = 0;
  403. if ( ! this.paused ) {
  404. timeScale = this.timeScale;
  405. var interpolant = this._timeScaleInterpolant;
  406. if ( interpolant !== null ) {
  407. var interpolantValue = interpolant.evaluate( time )[ 0 ];
  408. timeScale *= interpolantValue;
  409. if ( time > interpolant.parameterPositions[ 1 ] ) {
  410. this.stopWarping();
  411. if ( timeScale === 0 ) {
  412. // motion has halted, pause
  413. this.pause = true;
  414. } else {
  415. // warp done - apply final time scale
  416. this.timeScale = timeScale;
  417. }
  418. }
  419. }
  420. }
  421. this._effectiveTimeScale = timeScale;
  422. return timeScale;
  423. },
  424. _updateTime: function( deltaTime ) {
  425. var time = this.time + deltaTime;
  426. if ( deltaTime === 0 ) return time;
  427. var duration = this._clip.duration,
  428. loop = this.loop,
  429. loopCount = this._loopCount,
  430. pingPong = false;
  431. switch ( loop ) {
  432. case THREE.LoopOnce:
  433. if ( loopCount === -1 ) {
  434. // just started
  435. this.loopCount = 0;
  436. this._setEndings( true, true, false );
  437. }
  438. if ( time >= duration ) {
  439. time = duration;
  440. } else if ( time < 0 ) {
  441. time = 0;
  442. } else break;
  443. // reached the end
  444. if ( this.clampWhenFinished ) this.pause = true;
  445. else this.enabled = false;
  446. this._mixer.dispatchEvent( {
  447. type: 'finished', action: this,
  448. direction: deltaTime < 0 ? -1 : 1
  449. } );
  450. break;
  451. case THREE.LoopPingPong:
  452. pingPong = true;
  453. case THREE.LoopRepeat:
  454. if ( loopCount === -1 ) {
  455. // just started
  456. if ( deltaTime > 0 ) {
  457. loopCount = 0;
  458. this._setEndings(
  459. true, this.repetitions === 0, pingPong );
  460. } else {
  461. // when looping in reverse direction, the initial
  462. // transition through zero counts as a repetition,
  463. // so leave loopCount at -1
  464. this._setEndings(
  465. this.repetitions === 0, true, pingPong );
  466. }
  467. }
  468. if ( time >= duration || time < 0 ) {
  469. // wrap around
  470. var loopDelta = Math.floor( time / duration ); // signed
  471. time -= duration * loopDelta;
  472. loopCount += Math.abs( loopDelta );
  473. var pending = this.repetitions - loopCount;
  474. if ( pending < 0 ) {
  475. // stop (switch state, clamp time, fire event)
  476. if ( this.clampWhenFinished ) this.paused = true;
  477. else this.enabled = false;
  478. time = deltaTime > 0 ? duration : 0;
  479. this._mixer.dispatchEvent( {
  480. type: 'finished', action: this,
  481. direction: deltaTime > 0 ? 1 : -1
  482. } );
  483. break;
  484. } else if ( pending === 0 ) {
  485. // transition to last round
  486. var atStart = deltaTime < 0;
  487. this._setEndings( atStart, ! atStart, pingPong );
  488. } else {
  489. this._setEndings( false, false, pingPong );
  490. }
  491. this._loopCount = loopCount;
  492. this._mixer.dispatchEvent( {
  493. type: 'loop', action: this, loopDelta: loopDelta
  494. } );
  495. }
  496. if ( loop === THREE.LoopPingPong && ( loopCount & 1 ) === 1 ) {
  497. // invert time for the "pong round"
  498. this.time = time;
  499. return duration - time;
  500. }
  501. break;
  502. }
  503. this.time = time;
  504. return time;
  505. },
  506. _setEndings: function( atStart, atEnd, pingPong ) {
  507. var settings = this._interpolantSettings;
  508. if ( pingPong ) {
  509. settings.endingStart = THREE.ZeroSlopeEnding;
  510. settings.endingEnd = THREE.ZeroSlopeEnding;
  511. } else {
  512. // assuming for LoopOnce atStart == atEnd == true
  513. if ( atStart ) {
  514. settings.endingStart = this.zeroSlopeAtStart ?
  515. THREE.ZeroSlopeEnding : THREE.ZeroCurvatureEnding;
  516. } else {
  517. settings.endingStart = THREE.WrapAroundEnding;
  518. }
  519. if ( atEnd ) {
  520. settings.endingEnd = this.zeroSlopeAtEnd ?
  521. THREE.ZeroSlopeEnding : THREE.ZeroCurvatureEnding;
  522. } else {
  523. settings.endingEnd = THREE.WrapAroundEnding;
  524. }
  525. }
  526. },
  527. _scheduleFading: function( duration, weightNow, weightThen ) {
  528. var mixer = this._mixer, now = mixer.time,
  529. interpolant = this._weightInterpolant;
  530. if ( interpolant === null ) {
  531. interpolant = mixer._lendControlInterpolant(),
  532. this._weightInterpolant = interpolant;
  533. }
  534. var times = interpolant.parameterPositions,
  535. values = interpolant.sampleValues;
  536. times[ 0 ] = now; values[ 0 ] = weightNow;
  537. times[ 1 ] = now + duration; values[ 1 ] = weightThen;
  538. return this;
  539. }
  540. };
  541. // Implementation details:
  542. Object.assign( THREE.AnimationMixer.prototype, {
  543. _bindAction: function( action, prototypeAction ) {
  544. var root = action._localRoot || this._root,
  545. tracks = action._clip.tracks,
  546. nTracks = tracks.length,
  547. bindings = action._propertyBindings,
  548. interpolants = action._interpolants,
  549. rootUuid = root.uuid,
  550. bindingsByRoot = this._bindingsByRootAndName,
  551. bindingsByName = bindingsByRoot[ rootUuid ];
  552. if ( bindingsByName === undefined ) {
  553. bindingsByName = {};
  554. bindingsByRoot[ rootUuid ] = bindingsByName;
  555. }
  556. for ( var i = 0; i !== nTracks; ++ i ) {
  557. var track = tracks[ i ],
  558. trackName = track.name,
  559. binding = bindingsByName[ trackName ];
  560. if ( binding !== undefined ) {
  561. bindings[ i ] = binding;
  562. } else {
  563. binding = bindings[ i ];
  564. if ( binding !== undefined ) {
  565. // existing binding, make sure the cache knows
  566. if ( binding._cacheIndex === null ) {
  567. ++ binding.referenceCount;
  568. this._addInactiveBinding( binding, rootUuid, trackName );
  569. }
  570. continue;
  571. }
  572. var path = prototypeAction && prototypeAction.
  573. _propertyBindings[ i ].binding.parsedPath;
  574. binding = new THREE.PropertyMixer(
  575. THREE.PropertyBinding.create( root, trackName, path ),
  576. track.ValueTypeName, track.getValueSize() );
  577. ++ binding.referenceCount;
  578. this._addInactiveBinding( binding, rootUuid, trackName );
  579. bindings[ i ] = binding;
  580. }
  581. interpolants[ i ].resultBuffer = binding.buffer;
  582. }
  583. },
  584. _activateAction: function( action ) {
  585. if ( ! this._isActiveAction( action ) ) {
  586. if ( action._cacheIndex === null ) {
  587. // this action has been forgotten by the cache, but the user
  588. // appears to be still using it -> rebind
  589. var rootUuid = ( action._localRoot || this._root ).uuid,
  590. clipName = action._clip.name,
  591. actionsForClip = this._actionsByClip[ clipName ];
  592. this._bindAction( action,
  593. actionsForClip && actionsForClip.knownActions[ 0 ] );
  594. this._addInactiveAction( action, clipName, rootUuid );
  595. }
  596. var bindings = action._propertyBindings;
  597. // increment reference counts / sort out state
  598. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  599. var binding = bindings[ i ];
  600. if ( binding.useCount ++ === 0 ) {
  601. this._lendBinding( binding );
  602. binding.saveOriginalState();
  603. }
  604. }
  605. this._lendAction( action );
  606. }
  607. },
  608. _deactivateAction: function( action ) {
  609. if ( this._isActiveAction( action ) ) {
  610. var bindings = action._propertyBindings;
  611. // decrement reference counts / sort out state
  612. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  613. var binding = bindings[ i ];
  614. if ( -- binding.useCount === 0 ) {
  615. binding.restoreOriginalState();
  616. this._takeBackBinding( binding );
  617. }
  618. }
  619. this._takeBackAction( action );
  620. }
  621. },
  622. // Memory manager
  623. _initMemoryManager: function() {
  624. this._actions = []; // 'nActiveActions' followed by inactive ones
  625. this._nActiveActions = 0;
  626. this._actionsByClip = {};
  627. // inside:
  628. // {
  629. // knownActions: Array< _Action > - used as prototypes
  630. // actionByRoot: _Action - lookup
  631. // }
  632. this._bindings = []; // 'nActiveBindings' followed by inactive ones
  633. this._nActiveBindings = 0;
  634. this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
  635. this._controlInterpolants = []; // same game as above
  636. this._nActiveControlInterpolants = 0;
  637. var scope = this;
  638. this.stats = {
  639. actions: {
  640. get total() { return scope._actions.length; },
  641. get inUse() { return scope._nActiveActions; }
  642. },
  643. bindings: {
  644. get total() { return scope._bindings.length; },
  645. get inUse() { return scope._nActiveBindings; }
  646. },
  647. controlInterpolants: {
  648. get total() { return scope._controlInterpolants.length; },
  649. get inUse() { return scope._nActiveControlInterpolants; }
  650. }
  651. };
  652. },
  653. // Memory management for _Action objects
  654. _isActiveAction: function( action ) {
  655. var index = action._cacheIndex;
  656. return index !== null && index < this._nActiveActions;
  657. },
  658. _addInactiveAction: function( action, clipName, rootUuid ) {
  659. var actions = this._actions,
  660. actionsByClip = this._actionsByClip,
  661. actionsForClip = actionsByClip[ clipName ];
  662. if ( actionsForClip === undefined ) {
  663. actionsForClip = {
  664. knownActions: [ action ],
  665. actionByRoot: {}
  666. };
  667. action._byClipCacheIndex = 0;
  668. actionsByClip[ clipName ] = actionsForClip;
  669. } else {
  670. var knownActions = actionsForClip.knownActions;
  671. action._byClipCacheIndex = knownActions.length;
  672. knownActions.push( action );
  673. }
  674. action._cacheIndex = actions.length;
  675. actions.push( action );
  676. actionsForClip.actionByRoot[ rootUuid ] = action;
  677. },
  678. _removeInactiveAction: function( action ) {
  679. var actions = this._actions,
  680. lastInactiveAction = actions[ actions.length - 1 ],
  681. cacheIndex = action._cacheIndex;
  682. lastInactiveAction._cacheIndex = cacheIndex;
  683. actions[ cacheIndex ] = lastInactiveAction;
  684. actions.pop();
  685. action._cacheIndex = null;
  686. var clipName = action._clip.name,
  687. actionsByClip = this._actionsByClip,
  688. actionsForClip = actionsByClip[ clipName ],
  689. knownActionsForClip = actionsForClip.knownActions,
  690. lastKnownAction =
  691. knownActionsForClip[ knownActionsForClip.length - 1 ],
  692. byClipCacheIndex = action._byClipCacheIndex;
  693. lastKnownAction._byClipCacheIndex = byClipCacheIndex;
  694. knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
  695. knownActionsForClip.pop();
  696. action._byClipCacheIndex = null;
  697. var actionByRoot = actionsForClip.actionByRoot,
  698. rootUuid = ( actions._localRoot || this._root ).uuid;
  699. delete actionByRoot[ rootUuid ];
  700. if ( knownActionsForClip.length === 0 ) {
  701. delete actionsByClip[ clipName ];
  702. }
  703. this._removeInactiveBindingsForAction( action );
  704. },
  705. _removeInactiveBindingsForAction: function( action ) {
  706. var bindings = action._propertyBindings;
  707. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  708. var binding = bindings[ i ];
  709. if ( -- binding.referenceCount === 0 ) {
  710. this._removeInactiveBinding( binding );
  711. }
  712. }
  713. },
  714. _lendAction: function( action ) {
  715. // [ active actions | inactive actions ]
  716. // [ active actions >| inactive actions ]
  717. // s a
  718. // <-swap->
  719. // a s
  720. var actions = this._actions,
  721. prevIndex = action._cacheIndex,
  722. lastActiveIndex = this._nActiveActions ++,
  723. firstInactiveAction = actions[ lastActiveIndex ];
  724. action._cacheIndex = lastActiveIndex;
  725. actions[ lastActiveIndex ] = action;
  726. firstInactiveAction._cacheIndex = prevIndex;
  727. actions[ prevIndex ] = firstInactiveAction;
  728. },
  729. _takeBackAction: function( action ) {
  730. // [ active actions | inactive actions ]
  731. // [ active actions |< inactive actions ]
  732. // a s
  733. // <-swap->
  734. // s a
  735. var actions = this._actions,
  736. prevIndex = action._cacheIndex,
  737. firstInactiveIndex = -- this._nActiveActions,
  738. lastActiveAction = actions[ firstInactiveIndex ];
  739. action._cacheIndex = firstInactiveIndex;
  740. actions[ firstInactiveIndex ] = action;
  741. lastActiveAction._cacheIndex = prevIndex;
  742. actions[ prevIndex ] = lastActiveAction;
  743. },
  744. // Memory management for PropertyMixer objects
  745. _addInactiveBinding: function( binding, rootUuid, trackName ) {
  746. var bindingsByRoot = this._bindingsByRootAndName,
  747. bindingByName = bindingsByRoot[ rootUuid ],
  748. bindings = this._bindings;
  749. if ( bindingByName === undefined ) {
  750. bindingByName = {};
  751. bindingsByRoot[ rootUuid ] = bindingByName;
  752. }
  753. bindingByName[ trackName ] = binding;
  754. binding._cacheIndex = bindings.length;
  755. bindings.push( binding );
  756. },
  757. _removeInactiveBinding: function( binding ) {
  758. var bindings = this._bindings,
  759. propBinding = binding.binding,
  760. rootUuid = propBinding.rootNode.uuid,
  761. trackName = propBinding.path,
  762. bindingsByRoot = this._bindingsByRootAndName,
  763. bindingByName = bindingsByRoot[ rootUuid ],
  764. lastInactiveBinding = bindings[ bindings.length - 1 ],
  765. cacheIndex = binding._cacheIndex;
  766. lastInactiveBinding._cacheIndex = cacheIndex;
  767. bindings[ cacheIndex ] = lastInactiveBinding;
  768. bindings.pop();
  769. delete bindingByName[ trackName ];
  770. remove_empty_map: {
  771. for ( var _ in bindingByName ) break remove_empty_map;
  772. delete bindingsByRoot[ rootUuid ];
  773. }
  774. },
  775. _lendBinding: function( binding ) {
  776. var bindings = this._bindings,
  777. prevIndex = binding._cacheIndex,
  778. lastActiveIndex = this._nActiveBindings ++,
  779. firstInactiveBinding = bindings[ lastActiveIndex ];
  780. binding._cacheIndex = lastActiveIndex;
  781. bindings[ lastActiveIndex ] = binding;
  782. firstInactiveBinding._cacheIndex = prevIndex;
  783. bindings[ prevIndex ] = firstInactiveBinding;
  784. },
  785. _takeBackBinding: function( binding ) {
  786. var bindings = this._bindings,
  787. prevIndex = binding._cacheIndex,
  788. firstInactiveIndex = -- this._nActiveBindings,
  789. lastActiveBinding = bindings[ firstInactiveIndex ];
  790. binding._cacheIndex = firstInactiveIndex;
  791. bindings[ firstInactiveIndex ] = binding;
  792. lastActiveBinding._cacheIndex = prevIndex;
  793. bindings[ prevIndex ] = lastActiveBinding;
  794. },
  795. // Memory management of Interpolants for weight and time scale
  796. _lendControlInterpolant: function() {
  797. var interpolants = this._controlInterpolants,
  798. lastActiveIndex = this._nActiveControlInterpolants ++,
  799. interpolant = interpolants[ lastActiveIndex ];
  800. if ( interpolant === undefined ) {
  801. interpolant = new THREE.LinearInterpolant(
  802. new Float32Array( 2 ), new Float32Array( 2 ),
  803. 1, this._controlInterpolantsResultBuffer );
  804. interpolant.__cacheIndex = lastActiveIndex;
  805. interpolants[ lastActiveIndex ] = interpolant;
  806. }
  807. return interpolant;
  808. },
  809. _takeBackControlInterpolant: function( interpolant ) {
  810. var interpolants = this._controlInterpolants,
  811. prevIndex = interpolant.__cacheIndex,
  812. firstInactiveIndex = -- this._nActiveControlInterpolants,
  813. lastActiveInterpolant = interpolants[ firstInactiveIndex ];
  814. interpolant.__cacheIndex = firstInactiveIndex;
  815. interpolants[ firstInactiveIndex ] = interpolant;
  816. lastActiveInterpolant.__cacheIndex = prevIndex;
  817. interpolants[ prevIndex ] = lastActiveInterpolant;
  818. },
  819. _controlInterpolantsResultBuffer: new Float32Array( 1 )
  820. } );