AnimationMixer.js 16 KB

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