AnimationMixer.js 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747
  1. import { AnimationAction } from './AnimationAction';
  2. import { EventDispatcher } from '../core/EventDispatcher';
  3. import { LinearInterpolant } from '../math/interpolants/LinearInterpolant';
  4. import { PropertyBinding } from './PropertyBinding';
  5. import { PropertyMixer } from './PropertyMixer';
  6. import { AnimationClip } from './AnimationClip';
  7. /**
  8. *
  9. * Player for AnimationClips.
  10. *
  11. *
  12. * @author Ben Houston / http://clara.io/
  13. * @author David Sarno / http://lighthaus.us/
  14. * @author tschw
  15. */
  16. function AnimationMixer( root ) {
  17. this._root = root;
  18. this._initMemoryManager();
  19. this._accuIndex = 0;
  20. this.time = 0;
  21. this.timeScale = 1.0;
  22. }
  23. Object.assign( AnimationMixer.prototype, EventDispatcher.prototype, {
  24. // return an action for a clip optionally using a custom root target
  25. // object (this method allocates a lot of dynamic memory in case a
  26. // previously unknown clip/root combination is specified)
  27. clipAction: function( clip, optionalRoot ) {
  28. var root = optionalRoot || this._root,
  29. rootUuid = root.uuid,
  30. clipObject = typeof clip === 'string' ?
  31. AnimationClip.findByName( root, clip ) : clip,
  32. clipUuid = clipObject !== null ? clipObject.uuid : clip,
  33. actionsForClip = this._actionsByClip[ clipUuid ],
  34. prototypeAction = null;
  35. if ( actionsForClip !== undefined ) {
  36. var existingAction =
  37. actionsForClip.actionByRoot[ rootUuid ];
  38. if ( existingAction !== undefined ) {
  39. return existingAction;
  40. }
  41. // we know the clip, so we don't have to parse all
  42. // the bindings again but can just copy
  43. prototypeAction = actionsForClip.knownActions[ 0 ];
  44. // also, take the clip from the prototype action
  45. if ( clipObject === null )
  46. clipObject = prototypeAction._clip;
  47. }
  48. // clip must be known when specified via string
  49. if ( clipObject === null ) return null;
  50. // allocate all resources required to run it
  51. var newAction = new AnimationMixer._Action( this, clipObject, optionalRoot );
  52. this._bindAction( newAction, prototypeAction );
  53. // and make the action known to the memory manager
  54. this._addInactiveAction( newAction, clipUuid, rootUuid );
  55. return newAction;
  56. },
  57. // get an existing action
  58. existingAction: function( clip, optionalRoot ) {
  59. var root = optionalRoot || this._root,
  60. rootUuid = root.uuid,
  61. clipObject = typeof clip === 'string' ?
  62. AnimationClip.findByName( root, clip ) : clip,
  63. clipUuid = clipObject ? clipObject.uuid : clip,
  64. actionsForClip = this._actionsByClip[ clipUuid ];
  65. if ( actionsForClip !== undefined ) {
  66. return actionsForClip.actionByRoot[ rootUuid ] || null;
  67. }
  68. return null;
  69. },
  70. // deactivates all previously scheduled actions
  71. stopAllAction: function() {
  72. var actions = this._actions,
  73. nActions = this._nActiveActions,
  74. bindings = this._bindings,
  75. nBindings = this._nActiveBindings;
  76. this._nActiveActions = 0;
  77. this._nActiveBindings = 0;
  78. for ( var i = 0; i !== nActions; ++ i ) {
  79. actions[ i ].reset();
  80. }
  81. for ( var i = 0; i !== nBindings; ++ i ) {
  82. bindings[ i ].useCount = 0;
  83. }
  84. return this;
  85. },
  86. // advance the time and update apply the animation
  87. update: function( deltaTime ) {
  88. deltaTime *= this.timeScale;
  89. var actions = this._actions,
  90. nActions = this._nActiveActions,
  91. time = this.time += deltaTime,
  92. timeDirection = Math.sign( deltaTime ),
  93. accuIndex = this._accuIndex ^= 1;
  94. // run active actions
  95. for ( var i = 0; i !== nActions; ++ i ) {
  96. var action = actions[ i ];
  97. if ( action.enabled ) {
  98. action._update( time, deltaTime, timeDirection, accuIndex );
  99. }
  100. }
  101. // update scene graph
  102. var bindings = this._bindings,
  103. nBindings = this._nActiveBindings;
  104. for ( var i = 0; i !== nBindings; ++ i ) {
  105. bindings[ i ].apply( accuIndex );
  106. }
  107. return this;
  108. },
  109. // return this mixer's root target object
  110. getRoot: function() {
  111. return this._root;
  112. },
  113. // free all resources specific to a particular clip
  114. uncacheClip: function( clip ) {
  115. var actions = this._actions,
  116. clipUuid = clip.uuid,
  117. actionsByClip = this._actionsByClip,
  118. actionsForClip = actionsByClip[ clipUuid ];
  119. if ( actionsForClip !== undefined ) {
  120. // note: just calling _removeInactiveAction would mess up the
  121. // iteration state and also require updating the state we can
  122. // just throw away
  123. var actionsToRemove = actionsForClip.knownActions;
  124. for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
  125. var action = actionsToRemove[ i ];
  126. this._deactivateAction( action );
  127. var cacheIndex = action._cacheIndex,
  128. lastInactiveAction = actions[ actions.length - 1 ];
  129. action._cacheIndex = null;
  130. action._byClipCacheIndex = null;
  131. lastInactiveAction._cacheIndex = cacheIndex;
  132. actions[ cacheIndex ] = lastInactiveAction;
  133. actions.pop();
  134. this._removeInactiveBindingsForAction( action );
  135. }
  136. delete actionsByClip[ clipUuid ];
  137. }
  138. },
  139. // free all resources specific to a particular root target object
  140. uncacheRoot: function( root ) {
  141. var rootUuid = root.uuid,
  142. actionsByClip = this._actionsByClip;
  143. for ( var clipUuid in actionsByClip ) {
  144. var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
  145. action = actionByRoot[ rootUuid ];
  146. if ( action !== undefined ) {
  147. this._deactivateAction( action );
  148. this._removeInactiveAction( action );
  149. }
  150. }
  151. var bindingsByRoot = this._bindingsByRootAndName,
  152. bindingByName = bindingsByRoot[ rootUuid ];
  153. if ( bindingByName !== undefined ) {
  154. for ( var trackName in bindingByName ) {
  155. var binding = bindingByName[ trackName ];
  156. binding.restoreOriginalState();
  157. this._removeInactiveBinding( binding );
  158. }
  159. }
  160. },
  161. // remove a targeted clip from the cache
  162. uncacheAction: function( clip, optionalRoot ) {
  163. var action = this.existingAction( clip, optionalRoot );
  164. if ( action !== null ) {
  165. this._deactivateAction( action );
  166. this._removeInactiveAction( action );
  167. }
  168. }
  169. } );
  170. AnimationMixer._Action = AnimationAction._new;
  171. // Implementation details:
  172. Object.assign( AnimationMixer.prototype, {
  173. _bindAction: function( action, prototypeAction ) {
  174. var root = action._localRoot || this._root,
  175. tracks = action._clip.tracks,
  176. nTracks = tracks.length,
  177. bindings = action._propertyBindings,
  178. interpolants = action._interpolants,
  179. rootUuid = root.uuid,
  180. bindingsByRoot = this._bindingsByRootAndName,
  181. bindingsByName = bindingsByRoot[ rootUuid ];
  182. if ( bindingsByName === undefined ) {
  183. bindingsByName = {};
  184. bindingsByRoot[ rootUuid ] = bindingsByName;
  185. }
  186. for ( var i = 0; i !== nTracks; ++ i ) {
  187. var track = tracks[ i ],
  188. trackName = track.name,
  189. binding = bindingsByName[ trackName ];
  190. if ( binding !== undefined ) {
  191. bindings[ i ] = binding;
  192. } else {
  193. binding = bindings[ i ];
  194. if ( binding !== undefined ) {
  195. // existing binding, make sure the cache knows
  196. if ( binding._cacheIndex === null ) {
  197. ++ binding.referenceCount;
  198. this._addInactiveBinding( binding, rootUuid, trackName );
  199. }
  200. continue;
  201. }
  202. var path = prototypeAction && prototypeAction.
  203. _propertyBindings[ i ].binding.parsedPath;
  204. binding = new PropertyMixer(
  205. PropertyBinding.create( root, trackName, path ),
  206. track.ValueTypeName, track.getValueSize() );
  207. ++ binding.referenceCount;
  208. this._addInactiveBinding( binding, rootUuid, trackName );
  209. bindings[ i ] = binding;
  210. }
  211. interpolants[ i ].resultBuffer = binding.buffer;
  212. }
  213. },
  214. _activateAction: function( action ) {
  215. if ( ! this._isActiveAction( action ) ) {
  216. if ( action._cacheIndex === null ) {
  217. // this action has been forgotten by the cache, but the user
  218. // appears to be still using it -> rebind
  219. var rootUuid = ( action._localRoot || this._root ).uuid,
  220. clipUuid = action._clip.uuid,
  221. actionsForClip = this._actionsByClip[ clipUuid ];
  222. this._bindAction( action,
  223. actionsForClip && actionsForClip.knownActions[ 0 ] );
  224. this._addInactiveAction( action, clipUuid, rootUuid );
  225. }
  226. var bindings = action._propertyBindings;
  227. // increment reference counts / sort out state
  228. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  229. var binding = bindings[ i ];
  230. if ( binding.useCount ++ === 0 ) {
  231. this._lendBinding( binding );
  232. binding.saveOriginalState();
  233. }
  234. }
  235. this._lendAction( action );
  236. }
  237. },
  238. _deactivateAction: function( action ) {
  239. if ( this._isActiveAction( action ) ) {
  240. var bindings = action._propertyBindings;
  241. // decrement reference counts / sort out state
  242. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  243. var binding = bindings[ i ];
  244. if ( -- binding.useCount === 0 ) {
  245. binding.restoreOriginalState();
  246. this._takeBackBinding( binding );
  247. }
  248. }
  249. this._takeBackAction( action );
  250. }
  251. },
  252. // Memory manager
  253. _initMemoryManager: function() {
  254. this._actions = []; // 'nActiveActions' followed by inactive ones
  255. this._nActiveActions = 0;
  256. this._actionsByClip = {};
  257. // inside:
  258. // {
  259. // knownActions: Array< _Action > - used as prototypes
  260. // actionByRoot: _Action - lookup
  261. // }
  262. this._bindings = []; // 'nActiveBindings' followed by inactive ones
  263. this._nActiveBindings = 0;
  264. this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
  265. this._controlInterpolants = []; // same game as above
  266. this._nActiveControlInterpolants = 0;
  267. var scope = this;
  268. this.stats = {
  269. actions: {
  270. get total() { return scope._actions.length; },
  271. get inUse() { return scope._nActiveActions; }
  272. },
  273. bindings: {
  274. get total() { return scope._bindings.length; },
  275. get inUse() { return scope._nActiveBindings; }
  276. },
  277. controlInterpolants: {
  278. get total() { return scope._controlInterpolants.length; },
  279. get inUse() { return scope._nActiveControlInterpolants; }
  280. }
  281. };
  282. },
  283. // Memory management for _Action objects
  284. _isActiveAction: function( action ) {
  285. var index = action._cacheIndex;
  286. return index !== null && index < this._nActiveActions;
  287. },
  288. _addInactiveAction: function( action, clipUuid, rootUuid ) {
  289. var actions = this._actions,
  290. actionsByClip = this._actionsByClip,
  291. actionsForClip = actionsByClip[ clipUuid ];
  292. if ( actionsForClip === undefined ) {
  293. actionsForClip = {
  294. knownActions: [ action ],
  295. actionByRoot: {}
  296. };
  297. action._byClipCacheIndex = 0;
  298. actionsByClip[ clipUuid ] = actionsForClip;
  299. } else {
  300. var knownActions = actionsForClip.knownActions;
  301. action._byClipCacheIndex = knownActions.length;
  302. knownActions.push( action );
  303. }
  304. action._cacheIndex = actions.length;
  305. actions.push( action );
  306. actionsForClip.actionByRoot[ rootUuid ] = action;
  307. },
  308. _removeInactiveAction: function( action ) {
  309. var actions = this._actions,
  310. lastInactiveAction = actions[ actions.length - 1 ],
  311. cacheIndex = action._cacheIndex;
  312. lastInactiveAction._cacheIndex = cacheIndex;
  313. actions[ cacheIndex ] = lastInactiveAction;
  314. actions.pop();
  315. action._cacheIndex = null;
  316. var clipUuid = action._clip.uuid,
  317. actionsByClip = this._actionsByClip,
  318. actionsForClip = actionsByClip[ clipUuid ],
  319. knownActionsForClip = actionsForClip.knownActions,
  320. lastKnownAction =
  321. knownActionsForClip[ knownActionsForClip.length - 1 ],
  322. byClipCacheIndex = action._byClipCacheIndex;
  323. lastKnownAction._byClipCacheIndex = byClipCacheIndex;
  324. knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
  325. knownActionsForClip.pop();
  326. action._byClipCacheIndex = null;
  327. var actionByRoot = actionsForClip.actionByRoot,
  328. rootUuid = ( actions._localRoot || this._root ).uuid;
  329. delete actionByRoot[ rootUuid ];
  330. if ( knownActionsForClip.length === 0 ) {
  331. delete actionsByClip[ clipUuid ];
  332. }
  333. this._removeInactiveBindingsForAction( action );
  334. },
  335. _removeInactiveBindingsForAction: function( action ) {
  336. var bindings = action._propertyBindings;
  337. for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
  338. var binding = bindings[ i ];
  339. if ( -- binding.referenceCount === 0 ) {
  340. this._removeInactiveBinding( binding );
  341. }
  342. }
  343. },
  344. _lendAction: function( action ) {
  345. // [ active actions | inactive actions ]
  346. // [ active actions >| inactive actions ]
  347. // s a
  348. // <-swap->
  349. // a s
  350. var actions = this._actions,
  351. prevIndex = action._cacheIndex,
  352. lastActiveIndex = this._nActiveActions ++,
  353. firstInactiveAction = actions[ lastActiveIndex ];
  354. action._cacheIndex = lastActiveIndex;
  355. actions[ lastActiveIndex ] = action;
  356. firstInactiveAction._cacheIndex = prevIndex;
  357. actions[ prevIndex ] = firstInactiveAction;
  358. },
  359. _takeBackAction: function( action ) {
  360. // [ active actions | inactive actions ]
  361. // [ active actions |< inactive actions ]
  362. // a s
  363. // <-swap->
  364. // s a
  365. var actions = this._actions,
  366. prevIndex = action._cacheIndex,
  367. firstInactiveIndex = -- this._nActiveActions,
  368. lastActiveAction = actions[ firstInactiveIndex ];
  369. action._cacheIndex = firstInactiveIndex;
  370. actions[ firstInactiveIndex ] = action;
  371. lastActiveAction._cacheIndex = prevIndex;
  372. actions[ prevIndex ] = lastActiveAction;
  373. },
  374. // Memory management for PropertyMixer objects
  375. _addInactiveBinding: function( binding, rootUuid, trackName ) {
  376. var bindingsByRoot = this._bindingsByRootAndName,
  377. bindingByName = bindingsByRoot[ rootUuid ],
  378. bindings = this._bindings;
  379. if ( bindingByName === undefined ) {
  380. bindingByName = {};
  381. bindingsByRoot[ rootUuid ] = bindingByName;
  382. }
  383. bindingByName[ trackName ] = binding;
  384. binding._cacheIndex = bindings.length;
  385. bindings.push( binding );
  386. },
  387. _removeInactiveBinding: function( binding ) {
  388. var bindings = this._bindings,
  389. propBinding = binding.binding,
  390. rootUuid = propBinding.rootNode.uuid,
  391. trackName = propBinding.path,
  392. bindingsByRoot = this._bindingsByRootAndName,
  393. bindingByName = bindingsByRoot[ rootUuid ],
  394. lastInactiveBinding = bindings[ bindings.length - 1 ],
  395. cacheIndex = binding._cacheIndex;
  396. lastInactiveBinding._cacheIndex = cacheIndex;
  397. bindings[ cacheIndex ] = lastInactiveBinding;
  398. bindings.pop();
  399. delete bindingByName[ trackName ];
  400. remove_empty_map: {
  401. for ( var _ in bindingByName ) break remove_empty_map;
  402. delete bindingsByRoot[ rootUuid ];
  403. }
  404. },
  405. _lendBinding: function( binding ) {
  406. var bindings = this._bindings,
  407. prevIndex = binding._cacheIndex,
  408. lastActiveIndex = this._nActiveBindings ++,
  409. firstInactiveBinding = bindings[ lastActiveIndex ];
  410. binding._cacheIndex = lastActiveIndex;
  411. bindings[ lastActiveIndex ] = binding;
  412. firstInactiveBinding._cacheIndex = prevIndex;
  413. bindings[ prevIndex ] = firstInactiveBinding;
  414. },
  415. _takeBackBinding: function( binding ) {
  416. var bindings = this._bindings,
  417. prevIndex = binding._cacheIndex,
  418. firstInactiveIndex = -- this._nActiveBindings,
  419. lastActiveBinding = bindings[ firstInactiveIndex ];
  420. binding._cacheIndex = firstInactiveIndex;
  421. bindings[ firstInactiveIndex ] = binding;
  422. lastActiveBinding._cacheIndex = prevIndex;
  423. bindings[ prevIndex ] = lastActiveBinding;
  424. },
  425. // Memory management of Interpolants for weight and time scale
  426. _lendControlInterpolant: function() {
  427. var interpolants = this._controlInterpolants,
  428. lastActiveIndex = this._nActiveControlInterpolants ++,
  429. interpolant = interpolants[ lastActiveIndex ];
  430. if ( interpolant === undefined ) {
  431. interpolant = new LinearInterpolant(
  432. new Float32Array( 2 ), new Float32Array( 2 ),
  433. 1, this._controlInterpolantsResultBuffer );
  434. interpolant.__cacheIndex = lastActiveIndex;
  435. interpolants[ lastActiveIndex ] = interpolant;
  436. }
  437. return interpolant;
  438. },
  439. _takeBackControlInterpolant: function( interpolant ) {
  440. var interpolants = this._controlInterpolants,
  441. prevIndex = interpolant.__cacheIndex,
  442. firstInactiveIndex = -- this._nActiveControlInterpolants,
  443. lastActiveInterpolant = interpolants[ firstInactiveIndex ];
  444. interpolant.__cacheIndex = firstInactiveIndex;
  445. interpolants[ firstInactiveIndex ] = interpolant;
  446. lastActiveInterpolant.__cacheIndex = prevIndex;
  447. interpolants[ prevIndex ] = lastActiveInterpolant;
  448. },
  449. _controlInterpolantsResultBuffer: new Float32Array( 1 )
  450. } );
  451. export { AnimationMixer };