AnimationMixer.js 16 KB

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