AnimationMixer.js 16 KB

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