AnimationMixer.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860
  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. const _controlInterpolantsResultBuffer = new Float32Array( 1 );
  9. /**
  10. * `AnimationMixer` is a player for animations on a particular object in
  11. * the scene. When multiple objects in the scene are animated independently,
  12. * one `AnimationMixer` may be used for each object.
  13. */
  14. class AnimationMixer extends EventDispatcher {
  15. /**
  16. * Constructs a new animation mixer.
  17. *
  18. * @param {Object3D} root - The object whose animations shall be played by this mixer.
  19. */
  20. constructor( root ) {
  21. super();
  22. this._root = root;
  23. this._initMemoryManager();
  24. this._accuIndex = 0;
  25. /**
  26. * The global mixer time (in seconds; starting with `0` on the mixer's creation).
  27. *
  28. * @type {number}
  29. * @default 0
  30. */
  31. this.time = 0;
  32. /**
  33. * A scaling factor for the global time.
  34. *
  35. * Note: Setting this member to `0` and later back to `1` is a
  36. * possibility to pause/unpause all actions that are controlled by this
  37. * mixer.
  38. *
  39. * @type {number}
  40. * @default 1
  41. */
  42. this.timeScale = 1.0;
  43. if ( typeof __THREE_DEVTOOLS__ !== 'undefined' ) {
  44. __THREE_DEVTOOLS__.dispatchEvent( new CustomEvent( 'observe', { detail: this } ) );
  45. }
  46. }
  47. _bindAction( action, prototypeAction ) {
  48. const root = action._localRoot || this._root,
  49. tracks = action._clip.tracks,
  50. nTracks = tracks.length,
  51. bindings = action._propertyBindings,
  52. interpolants = action._interpolants,
  53. rootUuid = root.uuid,
  54. bindingsByRoot = this._bindingsByRootAndName;
  55. let bindingsByName = bindingsByRoot[ rootUuid ];
  56. if ( bindingsByName === undefined ) {
  57. bindingsByName = {};
  58. bindingsByRoot[ rootUuid ] = bindingsByName;
  59. }
  60. for ( let i = 0; i !== nTracks; ++ i ) {
  61. const track = tracks[ i ],
  62. trackName = track.name;
  63. let binding = bindingsByName[ trackName ];
  64. if ( binding !== undefined ) {
  65. ++ binding.referenceCount;
  66. bindings[ i ] = binding;
  67. } else {
  68. binding = bindings[ i ];
  69. if ( binding !== undefined ) {
  70. // existing binding, make sure the cache knows
  71. if ( binding._cacheIndex === null ) {
  72. ++ binding.referenceCount;
  73. this._addInactiveBinding( binding, rootUuid, trackName );
  74. }
  75. continue;
  76. }
  77. const path = prototypeAction && prototypeAction.
  78. _propertyBindings[ i ].binding.parsedPath;
  79. binding = new PropertyMixer(
  80. PropertyBinding.create( root, trackName, path ),
  81. track.ValueTypeName, track.getValueSize() );
  82. ++ binding.referenceCount;
  83. this._addInactiveBinding( binding, rootUuid, trackName );
  84. bindings[ i ] = binding;
  85. }
  86. interpolants[ i ].resultBuffer = binding.buffer;
  87. }
  88. }
  89. _activateAction( action ) {
  90. if ( ! this._isActiveAction( action ) ) {
  91. if ( action._cacheIndex === null ) {
  92. // this action has been forgotten by the cache, but the user
  93. // appears to be still using it -> rebind
  94. const rootUuid = ( action._localRoot || this._root ).uuid,
  95. clipUuid = action._clip.uuid,
  96. actionsForClip = this._actionsByClip[ clipUuid ];
  97. this._bindAction( action,
  98. actionsForClip && actionsForClip.knownActions[ 0 ] );
  99. this._addInactiveAction( action, clipUuid, rootUuid );
  100. }
  101. const bindings = action._propertyBindings;
  102. // increment reference counts / sort out state
  103. for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
  104. const binding = bindings[ i ];
  105. if ( binding.useCount ++ === 0 ) {
  106. this._lendBinding( binding );
  107. binding.saveOriginalState();
  108. }
  109. }
  110. this._lendAction( action );
  111. }
  112. }
  113. _deactivateAction( action ) {
  114. if ( this._isActiveAction( action ) ) {
  115. const bindings = action._propertyBindings;
  116. // decrement reference counts / sort out state
  117. for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
  118. const binding = bindings[ i ];
  119. if ( -- binding.useCount === 0 ) {
  120. binding.restoreOriginalState();
  121. this._takeBackBinding( binding );
  122. }
  123. }
  124. this._takeBackAction( action );
  125. }
  126. }
  127. // Memory manager
  128. _initMemoryManager() {
  129. this._actions = []; // 'nActiveActions' followed by inactive ones
  130. this._nActiveActions = 0;
  131. this._actionsByClip = {};
  132. // inside:
  133. // {
  134. // knownActions: Array< AnimationAction > - used as prototypes
  135. // actionByRoot: AnimationAction - lookup
  136. // }
  137. this._bindings = []; // 'nActiveBindings' followed by inactive ones
  138. this._nActiveBindings = 0;
  139. this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
  140. this._controlInterpolants = []; // same game as above
  141. this._nActiveControlInterpolants = 0;
  142. const scope = this;
  143. this.stats = {
  144. actions: {
  145. get total() {
  146. return scope._actions.length;
  147. },
  148. get inUse() {
  149. return scope._nActiveActions;
  150. }
  151. },
  152. bindings: {
  153. get total() {
  154. return scope._bindings.length;
  155. },
  156. get inUse() {
  157. return scope._nActiveBindings;
  158. }
  159. },
  160. controlInterpolants: {
  161. get total() {
  162. return scope._controlInterpolants.length;
  163. },
  164. get inUse() {
  165. return scope._nActiveControlInterpolants;
  166. }
  167. }
  168. };
  169. }
  170. // Memory management for AnimationAction objects
  171. _isActiveAction( action ) {
  172. const index = action._cacheIndex;
  173. return index !== null && index < this._nActiveActions;
  174. }
  175. _addInactiveAction( action, clipUuid, rootUuid ) {
  176. const actions = this._actions,
  177. actionsByClip = this._actionsByClip;
  178. let actionsForClip = actionsByClip[ clipUuid ];
  179. if ( actionsForClip === undefined ) {
  180. actionsForClip = {
  181. knownActions: [ action ],
  182. actionByRoot: {}
  183. };
  184. action._byClipCacheIndex = 0;
  185. actionsByClip[ clipUuid ] = actionsForClip;
  186. } else {
  187. const knownActions = actionsForClip.knownActions;
  188. action._byClipCacheIndex = knownActions.length;
  189. knownActions.push( action );
  190. }
  191. action._cacheIndex = actions.length;
  192. actions.push( action );
  193. actionsForClip.actionByRoot[ rootUuid ] = action;
  194. }
  195. _removeInactiveAction( action ) {
  196. const actions = this._actions,
  197. lastInactiveAction = actions[ actions.length - 1 ],
  198. cacheIndex = action._cacheIndex;
  199. lastInactiveAction._cacheIndex = cacheIndex;
  200. actions[ cacheIndex ] = lastInactiveAction;
  201. actions.pop();
  202. action._cacheIndex = null;
  203. const clipUuid = action._clip.uuid,
  204. actionsByClip = this._actionsByClip,
  205. actionsForClip = actionsByClip[ clipUuid ],
  206. knownActionsForClip = actionsForClip.knownActions,
  207. lastKnownAction =
  208. knownActionsForClip[ knownActionsForClip.length - 1 ],
  209. byClipCacheIndex = action._byClipCacheIndex;
  210. lastKnownAction._byClipCacheIndex = byClipCacheIndex;
  211. knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
  212. knownActionsForClip.pop();
  213. action._byClipCacheIndex = null;
  214. const actionByRoot = actionsForClip.actionByRoot,
  215. rootUuid = ( action._localRoot || this._root ).uuid;
  216. delete actionByRoot[ rootUuid ];
  217. if ( knownActionsForClip.length === 0 ) {
  218. delete actionsByClip[ clipUuid ];
  219. }
  220. this._removeInactiveBindingsForAction( action );
  221. }
  222. _removeInactiveBindingsForAction( action ) {
  223. const bindings = action._propertyBindings;
  224. for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
  225. const binding = bindings[ i ];
  226. if ( -- binding.referenceCount === 0 ) {
  227. this._removeInactiveBinding( binding );
  228. }
  229. }
  230. }
  231. _lendAction( action ) {
  232. // [ active actions | inactive actions ]
  233. // [ active actions >| inactive actions ]
  234. // s a
  235. // <-swap->
  236. // a s
  237. const actions = this._actions,
  238. prevIndex = action._cacheIndex,
  239. lastActiveIndex = this._nActiveActions ++,
  240. firstInactiveAction = actions[ lastActiveIndex ];
  241. action._cacheIndex = lastActiveIndex;
  242. actions[ lastActiveIndex ] = action;
  243. firstInactiveAction._cacheIndex = prevIndex;
  244. actions[ prevIndex ] = firstInactiveAction;
  245. }
  246. _takeBackAction( action ) {
  247. // [ active actions | inactive actions ]
  248. // [ active actions |< inactive actions ]
  249. // a s
  250. // <-swap->
  251. // s a
  252. const actions = this._actions,
  253. prevIndex = action._cacheIndex,
  254. firstInactiveIndex = -- this._nActiveActions,
  255. lastActiveAction = actions[ firstInactiveIndex ];
  256. action._cacheIndex = firstInactiveIndex;
  257. actions[ firstInactiveIndex ] = action;
  258. lastActiveAction._cacheIndex = prevIndex;
  259. actions[ prevIndex ] = lastActiveAction;
  260. }
  261. // Memory management for PropertyMixer objects
  262. _addInactiveBinding( binding, rootUuid, trackName ) {
  263. const bindingsByRoot = this._bindingsByRootAndName,
  264. bindings = this._bindings;
  265. let bindingByName = bindingsByRoot[ rootUuid ];
  266. if ( bindingByName === undefined ) {
  267. bindingByName = {};
  268. bindingsByRoot[ rootUuid ] = bindingByName;
  269. }
  270. bindingByName[ trackName ] = binding;
  271. binding._cacheIndex = bindings.length;
  272. bindings.push( binding );
  273. }
  274. _removeInactiveBinding( binding ) {
  275. const bindings = this._bindings,
  276. propBinding = binding.binding,
  277. rootUuid = propBinding.rootNode.uuid,
  278. trackName = propBinding.path,
  279. bindingsByRoot = this._bindingsByRootAndName,
  280. bindingByName = bindingsByRoot[ rootUuid ],
  281. lastInactiveBinding = bindings[ bindings.length - 1 ],
  282. cacheIndex = binding._cacheIndex;
  283. lastInactiveBinding._cacheIndex = cacheIndex;
  284. bindings[ cacheIndex ] = lastInactiveBinding;
  285. bindings.pop();
  286. delete bindingByName[ trackName ];
  287. if ( Object.keys( bindingByName ).length === 0 ) {
  288. delete bindingsByRoot[ rootUuid ];
  289. }
  290. }
  291. _lendBinding( binding ) {
  292. const bindings = this._bindings,
  293. prevIndex = binding._cacheIndex,
  294. lastActiveIndex = this._nActiveBindings ++,
  295. firstInactiveBinding = bindings[ lastActiveIndex ];
  296. binding._cacheIndex = lastActiveIndex;
  297. bindings[ lastActiveIndex ] = binding;
  298. firstInactiveBinding._cacheIndex = prevIndex;
  299. bindings[ prevIndex ] = firstInactiveBinding;
  300. }
  301. _takeBackBinding( binding ) {
  302. const bindings = this._bindings,
  303. prevIndex = binding._cacheIndex,
  304. firstInactiveIndex = -- this._nActiveBindings,
  305. lastActiveBinding = bindings[ firstInactiveIndex ];
  306. binding._cacheIndex = firstInactiveIndex;
  307. bindings[ firstInactiveIndex ] = binding;
  308. lastActiveBinding._cacheIndex = prevIndex;
  309. bindings[ prevIndex ] = lastActiveBinding;
  310. }
  311. // Memory management of Interpolants for weight and time scale
  312. _lendControlInterpolant() {
  313. const interpolants = this._controlInterpolants,
  314. lastActiveIndex = this._nActiveControlInterpolants ++;
  315. let interpolant = interpolants[ lastActiveIndex ];
  316. if ( interpolant === undefined ) {
  317. interpolant = new LinearInterpolant(
  318. new Float32Array( 2 ), new Float32Array( 2 ),
  319. 1, _controlInterpolantsResultBuffer );
  320. interpolant.__cacheIndex = lastActiveIndex;
  321. interpolants[ lastActiveIndex ] = interpolant;
  322. }
  323. return interpolant;
  324. }
  325. _takeBackControlInterpolant( interpolant ) {
  326. const interpolants = this._controlInterpolants,
  327. prevIndex = interpolant.__cacheIndex,
  328. firstInactiveIndex = -- this._nActiveControlInterpolants,
  329. lastActiveInterpolant = interpolants[ firstInactiveIndex ];
  330. interpolant.__cacheIndex = firstInactiveIndex;
  331. interpolants[ firstInactiveIndex ] = interpolant;
  332. lastActiveInterpolant.__cacheIndex = prevIndex;
  333. interpolants[ prevIndex ] = lastActiveInterpolant;
  334. }
  335. /**
  336. * Returns an instance of {@link AnimationAction} for the passed clip.
  337. *
  338. * If an action fitting the clip and root parameters doesn't yet exist, it
  339. * will be created by this method. Calling this method several times with the
  340. * same clip and root parameters always returns the same action.
  341. *
  342. * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip.
  343. * @param {Object3D} [optionalRoot] - An alternative root object.
  344. * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode.
  345. * @return {?AnimationAction} The animation action.
  346. */
  347. clipAction( clip, optionalRoot, blendMode ) {
  348. const root = optionalRoot || this._root,
  349. rootUuid = root.uuid;
  350. let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip;
  351. const clipUuid = clipObject !== null ? clipObject.uuid : clip;
  352. const actionsForClip = this._actionsByClip[ clipUuid ];
  353. let prototypeAction = null;
  354. if ( blendMode === undefined ) {
  355. if ( clipObject !== null ) {
  356. blendMode = clipObject.blendMode;
  357. } else {
  358. blendMode = NormalAnimationBlendMode;
  359. }
  360. }
  361. if ( actionsForClip !== undefined ) {
  362. const existingAction = actionsForClip.actionByRoot[ rootUuid ];
  363. if ( existingAction !== undefined && existingAction.blendMode === blendMode ) {
  364. return existingAction;
  365. }
  366. // we know the clip, so we don't have to parse all
  367. // the bindings again but can just copy
  368. prototypeAction = actionsForClip.knownActions[ 0 ];
  369. // also, take the clip from the prototype action
  370. if ( clipObject === null )
  371. clipObject = prototypeAction._clip;
  372. }
  373. // clip must be known when specified via string
  374. if ( clipObject === null ) return null;
  375. // allocate all resources required to run it
  376. const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode );
  377. this._bindAction( newAction, prototypeAction );
  378. // and make the action known to the memory manager
  379. this._addInactiveAction( newAction, clipUuid, rootUuid );
  380. return newAction;
  381. }
  382. /**
  383. * Returns an existing animation action for the passed clip.
  384. *
  385. * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip.
  386. * @param {Object3D} [optionalRoot] - An alternative root object.
  387. * @return {?AnimationAction} The animation action. Returns `null` if no action was found.
  388. */
  389. existingAction( clip, optionalRoot ) {
  390. const root = optionalRoot || this._root,
  391. rootUuid = root.uuid,
  392. clipObject = typeof clip === 'string' ?
  393. AnimationClip.findByName( root, clip ) : clip,
  394. clipUuid = clipObject ? clipObject.uuid : clip,
  395. actionsForClip = this._actionsByClip[ clipUuid ];
  396. if ( actionsForClip !== undefined ) {
  397. return actionsForClip.actionByRoot[ rootUuid ] || null;
  398. }
  399. return null;
  400. }
  401. /**
  402. * Deactivates all previously scheduled actions on this mixer.
  403. *
  404. * @return {AnimationMixer} A reference to this animation mixer.
  405. */
  406. stopAllAction() {
  407. const actions = this._actions,
  408. nActions = this._nActiveActions;
  409. for ( let i = nActions - 1; i >= 0; -- i ) {
  410. actions[ i ].stop();
  411. }
  412. return this;
  413. }
  414. /**
  415. * Advances the global mixer time and updates the animation.
  416. *
  417. * This is usually done in the render loop by passing the delta
  418. * time from {@link Clock} or {@link Timer}.
  419. *
  420. * @param {number} deltaTime - The delta time in seconds.
  421. * @return {AnimationMixer} A reference to this animation mixer.
  422. */
  423. update( deltaTime ) {
  424. deltaTime *= this.timeScale;
  425. const actions = this._actions,
  426. nActions = this._nActiveActions,
  427. time = this.time += deltaTime,
  428. timeDirection = Math.sign( deltaTime ),
  429. accuIndex = this._accuIndex ^= 1;
  430. // run active actions
  431. for ( let i = 0; i !== nActions; ++ i ) {
  432. const action = actions[ i ];
  433. action._update( time, deltaTime, timeDirection, accuIndex );
  434. }
  435. // update scene graph
  436. const bindings = this._bindings,
  437. nBindings = this._nActiveBindings;
  438. for ( let i = 0; i !== nBindings; ++ i ) {
  439. bindings[ i ].apply( accuIndex );
  440. }
  441. return this;
  442. }
  443. /**
  444. * Sets the global mixer to a specific time and updates the animation accordingly.
  445. *
  446. * This is useful when you need to jump to an exact time in an animation. The
  447. * input parameter will be scaled by {@link AnimationMixer#timeScale}
  448. *
  449. * @param {number} time - The time to set in seconds.
  450. * @return {AnimationMixer} A reference to this animation mixer.
  451. */
  452. setTime( time ) {
  453. this.time = 0; // Zero out time attribute for AnimationMixer object;
  454. for ( let i = 0; i < this._actions.length; i ++ ) {
  455. this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects.
  456. }
  457. return this.update( time ); // Update used to set exact time. Returns "this" AnimationMixer object.
  458. }
  459. /**
  460. * Returns this mixer's root object.
  461. *
  462. * @return {Object3D} The mixer's root object.
  463. */
  464. getRoot() {
  465. return this._root;
  466. }
  467. /**
  468. * Deallocates all memory resources for a clip. Before using this method make
  469. * sure to call {@link AnimationAction#stop} for all related actions.
  470. *
  471. * @param {AnimationClip} clip - The clip to uncache.
  472. */
  473. uncacheClip( clip ) {
  474. const actions = this._actions,
  475. clipUuid = clip.uuid,
  476. actionsByClip = this._actionsByClip,
  477. actionsForClip = actionsByClip[ clipUuid ];
  478. if ( actionsForClip !== undefined ) {
  479. // note: just calling _removeInactiveAction would mess up the
  480. // iteration state and also require updating the state we can
  481. // just throw away
  482. const actionsToRemove = actionsForClip.knownActions;
  483. for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
  484. const action = actionsToRemove[ i ];
  485. this._deactivateAction( action );
  486. const cacheIndex = action._cacheIndex,
  487. lastInactiveAction = actions[ actions.length - 1 ];
  488. action._cacheIndex = null;
  489. action._byClipCacheIndex = null;
  490. lastInactiveAction._cacheIndex = cacheIndex;
  491. actions[ cacheIndex ] = lastInactiveAction;
  492. actions.pop();
  493. this._removeInactiveBindingsForAction( action );
  494. }
  495. delete actionsByClip[ clipUuid ];
  496. }
  497. }
  498. /**
  499. * Deallocates all memory resources for a root object. Before using this
  500. * method make sure to call {@link AnimationAction#stop} for all related
  501. * actions or alternatively {@link AnimationMixer#stopAllAction} when the
  502. * mixer operates on a single root.
  503. *
  504. * @param {Object3D} root - The root object to uncache.
  505. */
  506. uncacheRoot( root ) {
  507. const rootUuid = root.uuid,
  508. actionsByClip = this._actionsByClip;
  509. for ( const clipUuid in actionsByClip ) {
  510. const actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
  511. action = actionByRoot[ rootUuid ];
  512. if ( action !== undefined ) {
  513. this._deactivateAction( action );
  514. this._removeInactiveAction( action );
  515. }
  516. }
  517. const bindingsByRoot = this._bindingsByRootAndName,
  518. bindingByName = bindingsByRoot[ rootUuid ];
  519. if ( bindingByName !== undefined ) {
  520. for ( const trackName in bindingByName ) {
  521. const binding = bindingByName[ trackName ];
  522. binding.restoreOriginalState();
  523. this._removeInactiveBinding( binding );
  524. }
  525. }
  526. }
  527. /**
  528. * Deallocates all memory resources for an action. The action is identified by the
  529. * given clip and an optional root object. Before using this method make
  530. * sure to call {@link AnimationAction#stop} to deactivate the action.
  531. *
  532. * @param {AnimationClip|string} clip - An animation clip or alternatively the name of the animation clip.
  533. * @param {Object3D} [optionalRoot] - An alternative root object.
  534. */
  535. uncacheAction( clip, optionalRoot ) {
  536. const action = this.existingAction( clip, optionalRoot );
  537. if ( action !== null ) {
  538. this._deactivateAction( action );
  539. this._removeInactiveAction( action );
  540. }
  541. }
  542. }
  543. export { AnimationMixer };