MCP 3D Printer Server
by DMontgomery40
Verified
- node_modules
- three
- src
- animation
import { AnimationAction } from './AnimationAction.js';
import { EventDispatcher } from '../core/EventDispatcher.js';
import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
import { PropertyBinding } from './PropertyBinding.js';
import { PropertyMixer } from './PropertyMixer.js';
import { AnimationClip } from './AnimationClip.js';
import { NormalAnimationBlendMode } from '../constants.js';
const _controlInterpolantsResultBuffer = new Float32Array( 1 );
class AnimationMixer extends EventDispatcher {
constructor( root ) {
super();
this._root = root;
this._initMemoryManager();
this._accuIndex = 0;
this.time = 0;
this.timeScale = 1.0;
}
_bindAction( action, prototypeAction ) {
const root = action._localRoot || this._root,
tracks = action._clip.tracks,
nTracks = tracks.length,
bindings = action._propertyBindings,
interpolants = action._interpolants,
rootUuid = root.uuid,
bindingsByRoot = this._bindingsByRootAndName;
let bindingsByName = bindingsByRoot[ rootUuid ];
if ( bindingsByName === undefined ) {
bindingsByName = {};
bindingsByRoot[ rootUuid ] = bindingsByName;
}
for ( let i = 0; i !== nTracks; ++ i ) {
const track = tracks[ i ],
trackName = track.name;
let binding = bindingsByName[ trackName ];
if ( binding !== undefined ) {
++ binding.referenceCount;
bindings[ i ] = binding;
} else {
binding = bindings[ i ];
if ( binding !== undefined ) {
// existing binding, make sure the cache knows
if ( binding._cacheIndex === null ) {
++ binding.referenceCount;
this._addInactiveBinding( binding, rootUuid, trackName );
}
continue;
}
const path = prototypeAction && prototypeAction.
_propertyBindings[ i ].binding.parsedPath;
binding = new PropertyMixer(
PropertyBinding.create( root, trackName, path ),
track.ValueTypeName, track.getValueSize() );
++ binding.referenceCount;
this._addInactiveBinding( binding, rootUuid, trackName );
bindings[ i ] = binding;
}
interpolants[ i ].resultBuffer = binding.buffer;
}
}
_activateAction( action ) {
if ( ! this._isActiveAction( action ) ) {
if ( action._cacheIndex === null ) {
// this action has been forgotten by the cache, but the user
// appears to be still using it -> rebind
const rootUuid = ( action._localRoot || this._root ).uuid,
clipUuid = action._clip.uuid,
actionsForClip = this._actionsByClip[ clipUuid ];
this._bindAction( action,
actionsForClip && actionsForClip.knownActions[ 0 ] );
this._addInactiveAction( action, clipUuid, rootUuid );
}
const bindings = action._propertyBindings;
// increment reference counts / sort out state
for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
const binding = bindings[ i ];
if ( binding.useCount ++ === 0 ) {
this._lendBinding( binding );
binding.saveOriginalState();
}
}
this._lendAction( action );
}
}
_deactivateAction( action ) {
if ( this._isActiveAction( action ) ) {
const bindings = action._propertyBindings;
// decrement reference counts / sort out state
for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
const binding = bindings[ i ];
if ( -- binding.useCount === 0 ) {
binding.restoreOriginalState();
this._takeBackBinding( binding );
}
}
this._takeBackAction( action );
}
}
// Memory manager
_initMemoryManager() {
this._actions = []; // 'nActiveActions' followed by inactive ones
this._nActiveActions = 0;
this._actionsByClip = {};
// inside:
// {
// knownActions: Array< AnimationAction > - used as prototypes
// actionByRoot: AnimationAction - lookup
// }
this._bindings = []; // 'nActiveBindings' followed by inactive ones
this._nActiveBindings = 0;
this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
this._controlInterpolants = []; // same game as above
this._nActiveControlInterpolants = 0;
const scope = this;
this.stats = {
actions: {
get total() {
return scope._actions.length;
},
get inUse() {
return scope._nActiveActions;
}
},
bindings: {
get total() {
return scope._bindings.length;
},
get inUse() {
return scope._nActiveBindings;
}
},
controlInterpolants: {
get total() {
return scope._controlInterpolants.length;
},
get inUse() {
return scope._nActiveControlInterpolants;
}
}
};
}
// Memory management for AnimationAction objects
_isActiveAction( action ) {
const index = action._cacheIndex;
return index !== null && index < this._nActiveActions;
}
_addInactiveAction( action, clipUuid, rootUuid ) {
const actions = this._actions,
actionsByClip = this._actionsByClip;
let actionsForClip = actionsByClip[ clipUuid ];
if ( actionsForClip === undefined ) {
actionsForClip = {
knownActions: [ action ],
actionByRoot: {}
};
action._byClipCacheIndex = 0;
actionsByClip[ clipUuid ] = actionsForClip;
} else {
const knownActions = actionsForClip.knownActions;
action._byClipCacheIndex = knownActions.length;
knownActions.push( action );
}
action._cacheIndex = actions.length;
actions.push( action );
actionsForClip.actionByRoot[ rootUuid ] = action;
}
_removeInactiveAction( action ) {
const actions = this._actions,
lastInactiveAction = actions[ actions.length - 1 ],
cacheIndex = action._cacheIndex;
lastInactiveAction._cacheIndex = cacheIndex;
actions[ cacheIndex ] = lastInactiveAction;
actions.pop();
action._cacheIndex = null;
const clipUuid = action._clip.uuid,
actionsByClip = this._actionsByClip,
actionsForClip = actionsByClip[ clipUuid ],
knownActionsForClip = actionsForClip.knownActions,
lastKnownAction =
knownActionsForClip[ knownActionsForClip.length - 1 ],
byClipCacheIndex = action._byClipCacheIndex;
lastKnownAction._byClipCacheIndex = byClipCacheIndex;
knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
knownActionsForClip.pop();
action._byClipCacheIndex = null;
const actionByRoot = actionsForClip.actionByRoot,
rootUuid = ( action._localRoot || this._root ).uuid;
delete actionByRoot[ rootUuid ];
if ( knownActionsForClip.length === 0 ) {
delete actionsByClip[ clipUuid ];
}
this._removeInactiveBindingsForAction( action );
}
_removeInactiveBindingsForAction( action ) {
const bindings = action._propertyBindings;
for ( let i = 0, n = bindings.length; i !== n; ++ i ) {
const binding = bindings[ i ];
if ( -- binding.referenceCount === 0 ) {
this._removeInactiveBinding( binding );
}
}
}
_lendAction( action ) {
// [ active actions | inactive actions ]
// [ active actions >| inactive actions ]
// s a
// <-swap->
// a s
const actions = this._actions,
prevIndex = action._cacheIndex,
lastActiveIndex = this._nActiveActions ++,
firstInactiveAction = actions[ lastActiveIndex ];
action._cacheIndex = lastActiveIndex;
actions[ lastActiveIndex ] = action;
firstInactiveAction._cacheIndex = prevIndex;
actions[ prevIndex ] = firstInactiveAction;
}
_takeBackAction( action ) {
// [ active actions | inactive actions ]
// [ active actions |< inactive actions ]
// a s
// <-swap->
// s a
const actions = this._actions,
prevIndex = action._cacheIndex,
firstInactiveIndex = -- this._nActiveActions,
lastActiveAction = actions[ firstInactiveIndex ];
action._cacheIndex = firstInactiveIndex;
actions[ firstInactiveIndex ] = action;
lastActiveAction._cacheIndex = prevIndex;
actions[ prevIndex ] = lastActiveAction;
}
// Memory management for PropertyMixer objects
_addInactiveBinding( binding, rootUuid, trackName ) {
const bindingsByRoot = this._bindingsByRootAndName,
bindings = this._bindings;
let bindingByName = bindingsByRoot[ rootUuid ];
if ( bindingByName === undefined ) {
bindingByName = {};
bindingsByRoot[ rootUuid ] = bindingByName;
}
bindingByName[ trackName ] = binding;
binding._cacheIndex = bindings.length;
bindings.push( binding );
}
_removeInactiveBinding( binding ) {
const bindings = this._bindings,
propBinding = binding.binding,
rootUuid = propBinding.rootNode.uuid,
trackName = propBinding.path,
bindingsByRoot = this._bindingsByRootAndName,
bindingByName = bindingsByRoot[ rootUuid ],
lastInactiveBinding = bindings[ bindings.length - 1 ],
cacheIndex = binding._cacheIndex;
lastInactiveBinding._cacheIndex = cacheIndex;
bindings[ cacheIndex ] = lastInactiveBinding;
bindings.pop();
delete bindingByName[ trackName ];
if ( Object.keys( bindingByName ).length === 0 ) {
delete bindingsByRoot[ rootUuid ];
}
}
_lendBinding( binding ) {
const bindings = this._bindings,
prevIndex = binding._cacheIndex,
lastActiveIndex = this._nActiveBindings ++,
firstInactiveBinding = bindings[ lastActiveIndex ];
binding._cacheIndex = lastActiveIndex;
bindings[ lastActiveIndex ] = binding;
firstInactiveBinding._cacheIndex = prevIndex;
bindings[ prevIndex ] = firstInactiveBinding;
}
_takeBackBinding( binding ) {
const bindings = this._bindings,
prevIndex = binding._cacheIndex,
firstInactiveIndex = -- this._nActiveBindings,
lastActiveBinding = bindings[ firstInactiveIndex ];
binding._cacheIndex = firstInactiveIndex;
bindings[ firstInactiveIndex ] = binding;
lastActiveBinding._cacheIndex = prevIndex;
bindings[ prevIndex ] = lastActiveBinding;
}
// Memory management of Interpolants for weight and time scale
_lendControlInterpolant() {
const interpolants = this._controlInterpolants,
lastActiveIndex = this._nActiveControlInterpolants ++;
let interpolant = interpolants[ lastActiveIndex ];
if ( interpolant === undefined ) {
interpolant = new LinearInterpolant(
new Float32Array( 2 ), new Float32Array( 2 ),
1, _controlInterpolantsResultBuffer );
interpolant.__cacheIndex = lastActiveIndex;
interpolants[ lastActiveIndex ] = interpolant;
}
return interpolant;
}
_takeBackControlInterpolant( interpolant ) {
const interpolants = this._controlInterpolants,
prevIndex = interpolant.__cacheIndex,
firstInactiveIndex = -- this._nActiveControlInterpolants,
lastActiveInterpolant = interpolants[ firstInactiveIndex ];
interpolant.__cacheIndex = firstInactiveIndex;
interpolants[ firstInactiveIndex ] = interpolant;
lastActiveInterpolant.__cacheIndex = prevIndex;
interpolants[ prevIndex ] = lastActiveInterpolant;
}
// return an action for a clip optionally using a custom root target
// object (this method allocates a lot of dynamic memory in case a
// previously unknown clip/root combination is specified)
clipAction( clip, optionalRoot, blendMode ) {
const root = optionalRoot || this._root,
rootUuid = root.uuid;
let clipObject = typeof clip === 'string' ? AnimationClip.findByName( root, clip ) : clip;
const clipUuid = clipObject !== null ? clipObject.uuid : clip;
const actionsForClip = this._actionsByClip[ clipUuid ];
let prototypeAction = null;
if ( blendMode === undefined ) {
if ( clipObject !== null ) {
blendMode = clipObject.blendMode;
} else {
blendMode = NormalAnimationBlendMode;
}
}
if ( actionsForClip !== undefined ) {
const existingAction = actionsForClip.actionByRoot[ rootUuid ];
if ( existingAction !== undefined && existingAction.blendMode === blendMode ) {
return existingAction;
}
// we know the clip, so we don't have to parse all
// the bindings again but can just copy
prototypeAction = actionsForClip.knownActions[ 0 ];
// also, take the clip from the prototype action
if ( clipObject === null )
clipObject = prototypeAction._clip;
}
// clip must be known when specified via string
if ( clipObject === null ) return null;
// allocate all resources required to run it
const newAction = new AnimationAction( this, clipObject, optionalRoot, blendMode );
this._bindAction( newAction, prototypeAction );
// and make the action known to the memory manager
this._addInactiveAction( newAction, clipUuid, rootUuid );
return newAction;
}
// get an existing action
existingAction( clip, optionalRoot ) {
const root = optionalRoot || this._root,
rootUuid = root.uuid,
clipObject = typeof clip === 'string' ?
AnimationClip.findByName( root, clip ) : clip,
clipUuid = clipObject ? clipObject.uuid : clip,
actionsForClip = this._actionsByClip[ clipUuid ];
if ( actionsForClip !== undefined ) {
return actionsForClip.actionByRoot[ rootUuid ] || null;
}
return null;
}
// deactivates all previously scheduled actions
stopAllAction() {
const actions = this._actions,
nActions = this._nActiveActions;
for ( let i = nActions - 1; i >= 0; -- i ) {
actions[ i ].stop();
}
return this;
}
// advance the time and update apply the animation
update( deltaTime ) {
deltaTime *= this.timeScale;
const actions = this._actions,
nActions = this._nActiveActions,
time = this.time += deltaTime,
timeDirection = Math.sign( deltaTime ),
accuIndex = this._accuIndex ^= 1;
// run active actions
for ( let i = 0; i !== nActions; ++ i ) {
const action = actions[ i ];
action._update( time, deltaTime, timeDirection, accuIndex );
}
// update scene graph
const bindings = this._bindings,
nBindings = this._nActiveBindings;
for ( let i = 0; i !== nBindings; ++ i ) {
bindings[ i ].apply( accuIndex );
}
return this;
}
// Allows you to seek to a specific time in an animation.
setTime( timeInSeconds ) {
this.time = 0; // Zero out time attribute for AnimationMixer object;
for ( let i = 0; i < this._actions.length; i ++ ) {
this._actions[ i ].time = 0; // Zero out time attribute for all associated AnimationAction objects.
}
return this.update( timeInSeconds ); // Update used to set exact time. Returns "this" AnimationMixer object.
}
// return this mixer's root target object
getRoot() {
return this._root;
}
// free all resources specific to a particular clip
uncacheClip( clip ) {
const actions = this._actions,
clipUuid = clip.uuid,
actionsByClip = this._actionsByClip,
actionsForClip = actionsByClip[ clipUuid ];
if ( actionsForClip !== undefined ) {
// note: just calling _removeInactiveAction would mess up the
// iteration state and also require updating the state we can
// just throw away
const actionsToRemove = actionsForClip.knownActions;
for ( let i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
const action = actionsToRemove[ i ];
this._deactivateAction( action );
const cacheIndex = action._cacheIndex,
lastInactiveAction = actions[ actions.length - 1 ];
action._cacheIndex = null;
action._byClipCacheIndex = null;
lastInactiveAction._cacheIndex = cacheIndex;
actions[ cacheIndex ] = lastInactiveAction;
actions.pop();
this._removeInactiveBindingsForAction( action );
}
delete actionsByClip[ clipUuid ];
}
}
// free all resources specific to a particular root target object
uncacheRoot( root ) {
const rootUuid = root.uuid,
actionsByClip = this._actionsByClip;
for ( const clipUuid in actionsByClip ) {
const actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
action = actionByRoot[ rootUuid ];
if ( action !== undefined ) {
this._deactivateAction( action );
this._removeInactiveAction( action );
}
}
const bindingsByRoot = this._bindingsByRootAndName,
bindingByName = bindingsByRoot[ rootUuid ];
if ( bindingByName !== undefined ) {
for ( const trackName in bindingByName ) {
const binding = bindingByName[ trackName ];
binding.restoreOriginalState();
this._removeInactiveBinding( binding );
}
}
}
// remove a targeted clip from the cache
uncacheAction( clip, optionalRoot ) {
const action = this.existingAction( clip, optionalRoot );
if ( action !== null ) {
this._deactivateAction( action );
this._removeInactiveAction( action );
}
}
}
export { AnimationMixer };