Spaces:
Running
Running
import { PropertyBinding } from './PropertyBinding.js'; | |
import { _Math } from '../math/Math.js'; | |
/** | |
* | |
* A group of objects that receives a shared animation state. | |
* | |
* Usage: | |
* | |
* - Add objects you would otherwise pass as 'root' to the | |
* constructor or the .clipAction method of AnimationMixer. | |
* | |
* - Instead pass this object as 'root'. | |
* | |
* - You can also add and remove objects later when the mixer | |
* is running. | |
* | |
* Note: | |
* | |
* Objects of this class appear as one object to the mixer, | |
* so cache control of the individual objects must be done | |
* on the group. | |
* | |
* Limitation: | |
* | |
* - The animated properties must be compatible among the | |
* all objects in the group. | |
* | |
* - A single property can either be controlled through a | |
* target group or directly, but not both. | |
* | |
* @author tschw | |
*/ | |
function AnimationObjectGroup() { | |
this.uuid = _Math.generateUUID(); | |
// cached objects followed by the active ones | |
this._objects = Array.prototype.slice.call( arguments ); | |
this.nCachedObjects_ = 0; // threshold | |
// note: read by PropertyBinding.Composite | |
var indices = {}; | |
this._indicesByUUID = indices; // for bookkeeping | |
for ( var i = 0, n = arguments.length; i !== n; ++ i ) { | |
indices[ arguments[ i ].uuid ] = i; | |
} | |
this._paths = []; // inside: string | |
this._parsedPaths = []; // inside: { we don't care, here } | |
this._bindings = []; // inside: Array< PropertyBinding > | |
this._bindingsIndicesByPath = {}; // inside: indices in these arrays | |
var scope = this; | |
this.stats = { | |
objects: { | |
get total() { | |
return scope._objects.length; | |
}, | |
get inUse() { | |
return this.total - scope.nCachedObjects_; | |
} | |
}, | |
get bindingsPerObject() { | |
return scope._bindings.length; | |
} | |
}; | |
} | |
Object.assign( AnimationObjectGroup.prototype, { | |
isAnimationObjectGroup: true, | |
add: function () { | |
var objects = this._objects, | |
nObjects = objects.length, | |
nCachedObjects = this.nCachedObjects_, | |
indicesByUUID = this._indicesByUUID, | |
paths = this._paths, | |
parsedPaths = this._parsedPaths, | |
bindings = this._bindings, | |
nBindings = bindings.length, | |
knownObject = undefined; | |
for ( var i = 0, n = arguments.length; i !== n; ++ i ) { | |
var object = arguments[ i ], | |
uuid = object.uuid, | |
index = indicesByUUID[ uuid ]; | |
if ( index === undefined ) { | |
// unknown object -> add it to the ACTIVE region | |
index = nObjects ++; | |
indicesByUUID[ uuid ] = index; | |
objects.push( object ); | |
// accounting is done, now do the same for all bindings | |
for ( var j = 0, m = nBindings; j !== m; ++ j ) { | |
bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) ); | |
} | |
} else if ( index < nCachedObjects ) { | |
knownObject = objects[ index ]; | |
// move existing object to the ACTIVE region | |
var firstActiveIndex = -- nCachedObjects, | |
lastCachedObject = objects[ firstActiveIndex ]; | |
indicesByUUID[ lastCachedObject.uuid ] = index; | |
objects[ index ] = lastCachedObject; | |
indicesByUUID[ uuid ] = firstActiveIndex; | |
objects[ firstActiveIndex ] = object; | |
// accounting is done, now do the same for all bindings | |
for ( var j = 0, m = nBindings; j !== m; ++ j ) { | |
var bindingsForPath = bindings[ j ], | |
lastCached = bindingsForPath[ firstActiveIndex ], | |
binding = bindingsForPath[ index ]; | |
bindingsForPath[ index ] = lastCached; | |
if ( binding === undefined ) { | |
// since we do not bother to create new bindings | |
// for objects that are cached, the binding may | |
// or may not exist | |
binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ); | |
} | |
bindingsForPath[ firstActiveIndex ] = binding; | |
} | |
} else if ( objects[ index ] !== knownObject ) { | |
console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' + | |
'detected. Clean the caches or recreate your infrastructure when reloading scenes.' ); | |
} // else the object is already where we want it to be | |
} // for arguments | |
this.nCachedObjects_ = nCachedObjects; | |
}, | |
remove: function () { | |
var objects = this._objects, | |
nCachedObjects = this.nCachedObjects_, | |
indicesByUUID = this._indicesByUUID, | |
bindings = this._bindings, | |
nBindings = bindings.length; | |
for ( var i = 0, n = arguments.length; i !== n; ++ i ) { | |
var object = arguments[ i ], | |
uuid = object.uuid, | |
index = indicesByUUID[ uuid ]; | |
if ( index !== undefined && index >= nCachedObjects ) { | |
// move existing object into the CACHED region | |
var lastCachedIndex = nCachedObjects ++, | |
firstActiveObject = objects[ lastCachedIndex ]; | |
indicesByUUID[ firstActiveObject.uuid ] = index; | |
objects[ index ] = firstActiveObject; | |
indicesByUUID[ uuid ] = lastCachedIndex; | |
objects[ lastCachedIndex ] = object; | |
// accounting is done, now do the same for all bindings | |
for ( var j = 0, m = nBindings; j !== m; ++ j ) { | |
var bindingsForPath = bindings[ j ], | |
firstActive = bindingsForPath[ lastCachedIndex ], | |
binding = bindingsForPath[ index ]; | |
bindingsForPath[ index ] = firstActive; | |
bindingsForPath[ lastCachedIndex ] = binding; | |
} | |
} | |
} // for arguments | |
this.nCachedObjects_ = nCachedObjects; | |
}, | |
// remove & forget | |
uncache: function () { | |
var objects = this._objects, | |
nObjects = objects.length, | |
nCachedObjects = this.nCachedObjects_, | |
indicesByUUID = this._indicesByUUID, | |
bindings = this._bindings, | |
nBindings = bindings.length; | |
for ( var i = 0, n = arguments.length; i !== n; ++ i ) { | |
var object = arguments[ i ], | |
uuid = object.uuid, | |
index = indicesByUUID[ uuid ]; | |
if ( index !== undefined ) { | |
delete indicesByUUID[ uuid ]; | |
if ( index < nCachedObjects ) { | |
// object is cached, shrink the CACHED region | |
var firstActiveIndex = -- nCachedObjects, | |
lastCachedObject = objects[ firstActiveIndex ], | |
lastIndex = -- nObjects, | |
lastObject = objects[ lastIndex ]; | |
// last cached object takes this object's place | |
indicesByUUID[ lastCachedObject.uuid ] = index; | |
objects[ index ] = lastCachedObject; | |
// last object goes to the activated slot and pop | |
indicesByUUID[ lastObject.uuid ] = firstActiveIndex; | |
objects[ firstActiveIndex ] = lastObject; | |
objects.pop(); | |
// accounting is done, now do the same for all bindings | |
for ( var j = 0, m = nBindings; j !== m; ++ j ) { | |
var bindingsForPath = bindings[ j ], | |
lastCached = bindingsForPath[ firstActiveIndex ], | |
last = bindingsForPath[ lastIndex ]; | |
bindingsForPath[ index ] = lastCached; | |
bindingsForPath[ firstActiveIndex ] = last; | |
bindingsForPath.pop(); | |
} | |
} else { | |
// object is active, just swap with the last and pop | |
var lastIndex = -- nObjects, | |
lastObject = objects[ lastIndex ]; | |
indicesByUUID[ lastObject.uuid ] = index; | |
objects[ index ] = lastObject; | |
objects.pop(); | |
// accounting is done, now do the same for all bindings | |
for ( var j = 0, m = nBindings; j !== m; ++ j ) { | |
var bindingsForPath = bindings[ j ]; | |
bindingsForPath[ index ] = bindingsForPath[ lastIndex ]; | |
bindingsForPath.pop(); | |
} | |
} // cached or active | |
} // if object is known | |
} // for arguments | |
this.nCachedObjects_ = nCachedObjects; | |
}, | |
// Internal interface used by befriended PropertyBinding.Composite: | |
subscribe_: function ( path, parsedPath ) { | |
// returns an array of bindings for the given path that is changed | |
// according to the contained objects in the group | |
var indicesByPath = this._bindingsIndicesByPath, | |
index = indicesByPath[ path ], | |
bindings = this._bindings; | |
if ( index !== undefined ) return bindings[ index ]; | |
var paths = this._paths, | |
parsedPaths = this._parsedPaths, | |
objects = this._objects, | |
nObjects = objects.length, | |
nCachedObjects = this.nCachedObjects_, | |
bindingsForPath = new Array( nObjects ); | |
index = bindings.length; | |
indicesByPath[ path ] = index; | |
paths.push( path ); | |
parsedPaths.push( parsedPath ); | |
bindings.push( bindingsForPath ); | |
for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) { | |
var object = objects[ i ]; | |
bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath ); | |
} | |
return bindingsForPath; | |
}, | |
unsubscribe_: function ( path ) { | |
// tells the group to forget about a property path and no longer | |
// update the array previously obtained with 'subscribe_' | |
var indicesByPath = this._bindingsIndicesByPath, | |
index = indicesByPath[ path ]; | |
if ( index !== undefined ) { | |
var paths = this._paths, | |
parsedPaths = this._parsedPaths, | |
bindings = this._bindings, | |
lastBindingsIndex = bindings.length - 1, | |
lastBindings = bindings[ lastBindingsIndex ], | |
lastBindingsPath = path[ lastBindingsIndex ]; | |
indicesByPath[ lastBindingsPath ] = index; | |
bindings[ index ] = lastBindings; | |
bindings.pop(); | |
parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ]; | |
parsedPaths.pop(); | |
paths[ index ] = paths[ lastBindingsIndex ]; | |
paths.pop(); | |
} | |
} | |
} ); | |
export { AnimationObjectGroup }; | |