AnimationMixer.js 16 KB

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