UNPKG

16.4 kBJavaScriptView Raw
1import { AnimationAction } from './AnimationAction.js';
2import { EventDispatcher } from '../core/EventDispatcher.js';
3import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
4import { PropertyBinding } from './PropertyBinding.js';
5import { PropertyMixer } from './PropertyMixer.js';
6import { AnimationClip } from './AnimationClip.js';
7
8/**
9 *
10 * Player for AnimationClips.
11 *
12 *
13 * @author Ben Houston / http://clara.io/
14 * @author David Sarno / http://lighthaus.us/
15 * @author tschw
16 */
17
18function AnimationMixer( root ) {
19
20 this._root = root;
21 this._initMemoryManager();
22 this._accuIndex = 0;
23
24 this.time = 0;
25
26 this.timeScale = 1.0;
27
28}
29
30AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
31
32 constructor: AnimationMixer,
33
34 _bindAction: function ( action, prototypeAction ) {
35
36 var root = action._localRoot || this._root,
37 tracks = action._clip.tracks,
38 nTracks = tracks.length,
39 bindings = action._propertyBindings,
40 interpolants = action._interpolants,
41 rootUuid = root.uuid,
42 bindingsByRoot = this._bindingsByRootAndName,
43 bindingsByName = bindingsByRoot[ rootUuid ];
44
45 if ( bindingsByName === undefined ) {
46
47 bindingsByName = {};
48 bindingsByRoot[ rootUuid ] = bindingsByName;
49
50 }
51
52 for ( var i = 0; i !== nTracks; ++ i ) {
53
54 var track = tracks[ i ],
55 trackName = track.name,
56 binding = bindingsByName[ trackName ];
57
58 if ( binding !== undefined ) {
59
60 bindings[ i ] = binding;
61
62 } else {
63
64 binding = bindings[ i ];
65
66 if ( binding !== undefined ) {
67
68 // existing binding, make sure the cache knows
69
70 if ( binding._cacheIndex === null ) {
71
72 ++ binding.referenceCount;
73 this._addInactiveBinding( binding, rootUuid, trackName );
74
75 }
76
77 continue;
78
79 }
80
81 var path = prototypeAction && prototypeAction.
82 _propertyBindings[ i ].binding.parsedPath;
83
84 binding = new PropertyMixer(
85 PropertyBinding.create( root, trackName, path ),
86 track.ValueTypeName, track.getValueSize() );
87
88 ++ binding.referenceCount;
89 this._addInactiveBinding( binding, rootUuid, trackName );
90
91 bindings[ i ] = binding;
92
93 }
94
95 interpolants[ i ].resultBuffer = binding.buffer;
96
97 }
98
99 },
100
101 _activateAction: function ( action ) {
102
103 if ( ! this._isActiveAction( action ) ) {
104
105 if ( action._cacheIndex === null ) {
106
107 // this action has been forgotten by the cache, but the user
108 // appears to be still using it -> rebind
109
110 var rootUuid = ( action._localRoot || this._root ).uuid,
111 clipUuid = action._clip.uuid,
112 actionsForClip = this._actionsByClip[ clipUuid ];
113
114 this._bindAction( action,
115 actionsForClip && actionsForClip.knownActions[ 0 ] );
116
117 this._addInactiveAction( action, clipUuid, rootUuid );
118
119 }
120
121 var bindings = action._propertyBindings;
122
123 // increment reference counts / sort out state
124 for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
125
126 var binding = bindings[ i ];
127
128 if ( binding.useCount ++ === 0 ) {
129
130 this._lendBinding( binding );
131 binding.saveOriginalState();
132
133 }
134
135 }
136
137 this._lendAction( action );
138
139 }
140
141 },
142
143 _deactivateAction: function ( action ) {
144
145 if ( this._isActiveAction( action ) ) {
146
147 var bindings = action._propertyBindings;
148
149 // decrement reference counts / sort out state
150 for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
151
152 var binding = bindings[ i ];
153
154 if ( -- binding.useCount === 0 ) {
155
156 binding.restoreOriginalState();
157 this._takeBackBinding( binding );
158
159 }
160
161 }
162
163 this._takeBackAction( action );
164
165 }
166
167 },
168
169 // Memory manager
170
171 _initMemoryManager: function () {
172
173 this._actions = []; // 'nActiveActions' followed by inactive ones
174 this._nActiveActions = 0;
175
176 this._actionsByClip = {};
177 // inside:
178 // {
179 // knownActions: Array< AnimationAction > - used as prototypes
180 // actionByRoot: AnimationAction - lookup
181 // }
182
183
184 this._bindings = []; // 'nActiveBindings' followed by inactive ones
185 this._nActiveBindings = 0;
186
187 this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
188
189
190 this._controlInterpolants = []; // same game as above
191 this._nActiveControlInterpolants = 0;
192
193 var scope = this;
194
195 this.stats = {
196
197 actions: {
198 get total() {
199
200 return scope._actions.length;
201
202 },
203 get inUse() {
204
205 return scope._nActiveActions;
206
207 }
208 },
209 bindings: {
210 get total() {
211
212 return scope._bindings.length;
213
214 },
215 get inUse() {
216
217 return scope._nActiveBindings;
218
219 }
220 },
221 controlInterpolants: {
222 get total() {
223
224 return scope._controlInterpolants.length;
225
226 },
227 get inUse() {
228
229 return scope._nActiveControlInterpolants;
230
231 }
232 }
233
234 };
235
236 },
237
238 // Memory management for AnimationAction objects
239
240 _isActiveAction: function ( action ) {
241
242 var index = action._cacheIndex;
243 return index !== null && index < this._nActiveActions;
244
245 },
246
247 _addInactiveAction: function ( action, clipUuid, rootUuid ) {
248
249 var actions = this._actions,
250 actionsByClip = this._actionsByClip,
251 actionsForClip = actionsByClip[ clipUuid ];
252
253 if ( actionsForClip === undefined ) {
254
255 actionsForClip = {
256
257 knownActions: [ action ],
258 actionByRoot: {}
259
260 };
261
262 action._byClipCacheIndex = 0;
263
264 actionsByClip[ clipUuid ] = actionsForClip;
265
266 } else {
267
268 var knownActions = actionsForClip.knownActions;
269
270 action._byClipCacheIndex = knownActions.length;
271 knownActions.push( action );
272
273 }
274
275 action._cacheIndex = actions.length;
276 actions.push( action );
277
278 actionsForClip.actionByRoot[ rootUuid ] = action;
279
280 },
281
282 _removeInactiveAction: function ( action ) {
283
284 var actions = this._actions,
285 lastInactiveAction = actions[ actions.length - 1 ],
286 cacheIndex = action._cacheIndex;
287
288 lastInactiveAction._cacheIndex = cacheIndex;
289 actions[ cacheIndex ] = lastInactiveAction;
290 actions.pop();
291
292 action._cacheIndex = null;
293
294
295 var clipUuid = action._clip.uuid,
296 actionsByClip = this._actionsByClip,
297 actionsForClip = actionsByClip[ clipUuid ],
298 knownActionsForClip = actionsForClip.knownActions,
299
300 lastKnownAction =
301 knownActionsForClip[ knownActionsForClip.length - 1 ],
302
303 byClipCacheIndex = action._byClipCacheIndex;
304
305 lastKnownAction._byClipCacheIndex = byClipCacheIndex;
306 knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
307 knownActionsForClip.pop();
308
309 action._byClipCacheIndex = null;
310
311
312 var actionByRoot = actionsForClip.actionByRoot,
313 rootUuid = ( action._localRoot || this._root ).uuid;
314
315 delete actionByRoot[ rootUuid ];
316
317 if ( knownActionsForClip.length === 0 ) {
318
319 delete actionsByClip[ clipUuid ];
320
321 }
322
323 this._removeInactiveBindingsForAction( action );
324
325 },
326
327 _removeInactiveBindingsForAction: function ( action ) {
328
329 var bindings = action._propertyBindings;
330 for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
331
332 var binding = bindings[ i ];
333
334 if ( -- binding.referenceCount === 0 ) {
335
336 this._removeInactiveBinding( binding );
337
338 }
339
340 }
341
342 },
343
344 _lendAction: function ( action ) {
345
346 // [ active actions | inactive actions ]
347 // [ active actions >| inactive actions ]
348 // s a
349 // <-swap->
350 // a s
351
352 var actions = this._actions,
353 prevIndex = action._cacheIndex,
354
355 lastActiveIndex = this._nActiveActions ++,
356
357 firstInactiveAction = actions[ lastActiveIndex ];
358
359 action._cacheIndex = lastActiveIndex;
360 actions[ lastActiveIndex ] = action;
361
362 firstInactiveAction._cacheIndex = prevIndex;
363 actions[ prevIndex ] = firstInactiveAction;
364
365 },
366
367 _takeBackAction: function ( action ) {
368
369 // [ active actions | inactive actions ]
370 // [ active actions |< inactive actions ]
371 // a s
372 // <-swap->
373 // s a
374
375 var actions = this._actions,
376 prevIndex = action._cacheIndex,
377
378 firstInactiveIndex = -- this._nActiveActions,
379
380 lastActiveAction = actions[ firstInactiveIndex ];
381
382 action._cacheIndex = firstInactiveIndex;
383 actions[ firstInactiveIndex ] = action;
384
385 lastActiveAction._cacheIndex = prevIndex;
386 actions[ prevIndex ] = lastActiveAction;
387
388 },
389
390 // Memory management for PropertyMixer objects
391
392 _addInactiveBinding: function ( binding, rootUuid, trackName ) {
393
394 var bindingsByRoot = this._bindingsByRootAndName,
395 bindingByName = bindingsByRoot[ rootUuid ],
396
397 bindings = this._bindings;
398
399 if ( bindingByName === undefined ) {
400
401 bindingByName = {};
402 bindingsByRoot[ rootUuid ] = bindingByName;
403
404 }
405
406 bindingByName[ trackName ] = binding;
407
408 binding._cacheIndex = bindings.length;
409 bindings.push( binding );
410
411 },
412
413 _removeInactiveBinding: function ( binding ) {
414
415 var bindings = this._bindings,
416 propBinding = binding.binding,
417 rootUuid = propBinding.rootNode.uuid,
418 trackName = propBinding.path,
419 bindingsByRoot = this._bindingsByRootAndName,
420 bindingByName = bindingsByRoot[ rootUuid ],
421
422 lastInactiveBinding = bindings[ bindings.length - 1 ],
423 cacheIndex = binding._cacheIndex;
424
425 lastInactiveBinding._cacheIndex = cacheIndex;
426 bindings[ cacheIndex ] = lastInactiveBinding;
427 bindings.pop();
428
429 delete bindingByName[ trackName ];
430
431 remove_empty_map: {
432
433 for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars
434
435 delete bindingsByRoot[ rootUuid ];
436
437 }
438
439 },
440
441 _lendBinding: function ( binding ) {
442
443 var bindings = this._bindings,
444 prevIndex = binding._cacheIndex,
445
446 lastActiveIndex = this._nActiveBindings ++,
447
448 firstInactiveBinding = bindings[ lastActiveIndex ];
449
450 binding._cacheIndex = lastActiveIndex;
451 bindings[ lastActiveIndex ] = binding;
452
453 firstInactiveBinding._cacheIndex = prevIndex;
454 bindings[ prevIndex ] = firstInactiveBinding;
455
456 },
457
458 _takeBackBinding: function ( binding ) {
459
460 var bindings = this._bindings,
461 prevIndex = binding._cacheIndex,
462
463 firstInactiveIndex = -- this._nActiveBindings,
464
465 lastActiveBinding = bindings[ firstInactiveIndex ];
466
467 binding._cacheIndex = firstInactiveIndex;
468 bindings[ firstInactiveIndex ] = binding;
469
470 lastActiveBinding._cacheIndex = prevIndex;
471 bindings[ prevIndex ] = lastActiveBinding;
472
473 },
474
475
476 // Memory management of Interpolants for weight and time scale
477
478 _lendControlInterpolant: function () {
479
480 var interpolants = this._controlInterpolants,
481 lastActiveIndex = this._nActiveControlInterpolants ++,
482 interpolant = interpolants[ lastActiveIndex ];
483
484 if ( interpolant === undefined ) {
485
486 interpolant = new LinearInterpolant(
487 new Float32Array( 2 ), new Float32Array( 2 ),
488 1, this._controlInterpolantsResultBuffer );
489
490 interpolant.__cacheIndex = lastActiveIndex;
491 interpolants[ lastActiveIndex ] = interpolant;
492
493 }
494
495 return interpolant;
496
497 },
498
499 _takeBackControlInterpolant: function ( interpolant ) {
500
501 var interpolants = this._controlInterpolants,
502 prevIndex = interpolant.__cacheIndex,
503
504 firstInactiveIndex = -- this._nActiveControlInterpolants,
505
506 lastActiveInterpolant = interpolants[ firstInactiveIndex ];
507
508 interpolant.__cacheIndex = firstInactiveIndex;
509 interpolants[ firstInactiveIndex ] = interpolant;
510
511 lastActiveInterpolant.__cacheIndex = prevIndex;
512 interpolants[ prevIndex ] = lastActiveInterpolant;
513
514 },
515
516 _controlInterpolantsResultBuffer: new Float32Array( 1 ),
517
518 // return an action for a clip optionally using a custom root target
519 // object (this method allocates a lot of dynamic memory in case a
520 // previously unknown clip/root combination is specified)
521 clipAction: function ( clip, optionalRoot ) {
522
523 var root = optionalRoot || this._root,
524 rootUuid = root.uuid,
525
526 clipObject = typeof clip === 'string' ?
527 AnimationClip.findByName( root, clip ) : clip,
528
529 clipUuid = clipObject !== null ? clipObject.uuid : clip,
530
531 actionsForClip = this._actionsByClip[ clipUuid ],
532 prototypeAction = null;
533
534 if ( actionsForClip !== undefined ) {
535
536 var existingAction =
537 actionsForClip.actionByRoot[ rootUuid ];
538
539 if ( existingAction !== undefined ) {
540
541 return existingAction;
542
543 }
544
545 // we know the clip, so we don't have to parse all
546 // the bindings again but can just copy
547 prototypeAction = actionsForClip.knownActions[ 0 ];
548
549 // also, take the clip from the prototype action
550 if ( clipObject === null )
551 clipObject = prototypeAction._clip;
552
553 }
554
555 // clip must be known when specified via string
556 if ( clipObject === null ) return null;
557
558 // allocate all resources required to run it
559 var newAction = new AnimationAction( this, clipObject, optionalRoot );
560
561 this._bindAction( newAction, prototypeAction );
562
563 // and make the action known to the memory manager
564 this._addInactiveAction( newAction, clipUuid, rootUuid );
565
566 return newAction;
567
568 },
569
570 // get an existing action
571 existingAction: function ( clip, optionalRoot ) {
572
573 var root = optionalRoot || this._root,
574 rootUuid = root.uuid,
575
576 clipObject = typeof clip === 'string' ?
577 AnimationClip.findByName( root, clip ) : clip,
578
579 clipUuid = clipObject ? clipObject.uuid : clip,
580
581 actionsForClip = this._actionsByClip[ clipUuid ];
582
583 if ( actionsForClip !== undefined ) {
584
585 return actionsForClip.actionByRoot[ rootUuid ] || null;
586
587 }
588
589 return null;
590
591 },
592
593 // deactivates all previously scheduled actions
594 stopAllAction: function () {
595
596 var actions = this._actions,
597 nActions = this._nActiveActions,
598 bindings = this._bindings,
599 nBindings = this._nActiveBindings;
600
601 this._nActiveActions = 0;
602 this._nActiveBindings = 0;
603
604 for ( var i = 0; i !== nActions; ++ i ) {
605
606 actions[ i ].reset();
607
608 }
609
610 for ( var i = 0; i !== nBindings; ++ i ) {
611
612 bindings[ i ].useCount = 0;
613
614 }
615
616 return this;
617
618 },
619
620 // advance the time and update apply the animation
621 update: function ( deltaTime ) {
622
623 deltaTime *= this.timeScale;
624
625 var actions = this._actions,
626 nActions = this._nActiveActions,
627
628 time = this.time += deltaTime,
629 timeDirection = Math.sign( deltaTime ),
630
631 accuIndex = this._accuIndex ^= 1;
632
633 // run active actions
634
635 for ( var i = 0; i !== nActions; ++ i ) {
636
637 var action = actions[ i ];
638
639 action._update( time, deltaTime, timeDirection, accuIndex );
640
641 }
642
643 // update scene graph
644
645 var bindings = this._bindings,
646 nBindings = this._nActiveBindings;
647
648 for ( var i = 0; i !== nBindings; ++ i ) {
649
650 bindings[ i ].apply( accuIndex );
651
652 }
653
654 return this;
655
656 },
657
658 // return this mixer's root target object
659 getRoot: function () {
660
661 return this._root;
662
663 },
664
665 // free all resources specific to a particular clip
666 uncacheClip: function ( clip ) {
667
668 var actions = this._actions,
669 clipUuid = clip.uuid,
670 actionsByClip = this._actionsByClip,
671 actionsForClip = actionsByClip[ clipUuid ];
672
673 if ( actionsForClip !== undefined ) {
674
675 // note: just calling _removeInactiveAction would mess up the
676 // iteration state and also require updating the state we can
677 // just throw away
678
679 var actionsToRemove = actionsForClip.knownActions;
680
681 for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
682
683 var action = actionsToRemove[ i ];
684
685 this._deactivateAction( action );
686
687 var cacheIndex = action._cacheIndex,
688 lastInactiveAction = actions[ actions.length - 1 ];
689
690 action._cacheIndex = null;
691 action._byClipCacheIndex = null;
692
693 lastInactiveAction._cacheIndex = cacheIndex;
694 actions[ cacheIndex ] = lastInactiveAction;
695 actions.pop();
696
697 this._removeInactiveBindingsForAction( action );
698
699 }
700
701 delete actionsByClip[ clipUuid ];
702
703 }
704
705 },
706
707 // free all resources specific to a particular root target object
708 uncacheRoot: function ( root ) {
709
710 var rootUuid = root.uuid,
711 actionsByClip = this._actionsByClip;
712
713 for ( var clipUuid in actionsByClip ) {
714
715 var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
716 action = actionByRoot[ rootUuid ];
717
718 if ( action !== undefined ) {
719
720 this._deactivateAction( action );
721 this._removeInactiveAction( action );
722
723 }
724
725 }
726
727 var bindingsByRoot = this._bindingsByRootAndName,
728 bindingByName = bindingsByRoot[ rootUuid ];
729
730 if ( bindingByName !== undefined ) {
731
732 for ( var trackName in bindingByName ) {
733
734 var binding = bindingByName[ trackName ];
735 binding.restoreOriginalState();
736 this._removeInactiveBinding( binding );
737
738 }
739
740 }
741
742 },
743
744 // remove a targeted clip from the cache
745 uncacheAction: function ( clip, optionalRoot ) {
746
747 var action = this.existingAction( clip, optionalRoot );
748
749 if ( action !== null ) {
750
751 this._deactivateAction( action );
752 this._removeInactiveAction( action );
753
754 }
755
756 }
757
758} );
759
760
761export { AnimationMixer };