starry / backend /libs /three /animation /AnimationObjectGroup.js
k-l-lambda's picture
feat: add Python ML services (CPU mode) with model download
2b7aae2
import { PropertyBinding } from './PropertyBinding.js';
import * as MathUtils from '../math/MathUtils.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.
*/
class AnimationObjectGroup {
constructor() {
this.uuid = MathUtils.generateUUID();
// cached objects followed by the active ones
this._objects = Array.prototype.slice.call(arguments);
this.nCachedObjects_ = 0; // threshold
// note: read by PropertyBinding.Composite
const indices = {};
this._indicesByUUID = indices; // for bookkeeping
for (let 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
const scope = this;
this.stats = {
objects: {
get total() {
return scope._objects.length;
},
get inUse() {
return this.total - scope.nCachedObjects_;
},
},
get bindingsPerObject() {
return scope._bindings.length;
},
};
}
add() {
const objects = this._objects,
indicesByUUID = this._indicesByUUID,
paths = this._paths,
parsedPaths = this._parsedPaths,
bindings = this._bindings,
nBindings = bindings.length;
let knownObject = undefined,
nObjects = objects.length,
nCachedObjects = this.nCachedObjects_;
for (let i = 0, n = arguments.length; i !== n; ++i) {
const object = arguments[i],
uuid = object.uuid;
let 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 (let 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
const 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 (let j = 0, m = nBindings; j !== m; ++j) {
const bindingsForPath = bindings[j],
lastCached = bindingsForPath[firstActiveIndex];
let 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() {
const objects = this._objects,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
let nCachedObjects = this.nCachedObjects_;
for (let i = 0, n = arguments.length; i !== n; ++i) {
const object = arguments[i],
uuid = object.uuid,
index = indicesByUUID[uuid];
if (index !== undefined && index >= nCachedObjects) {
// move existing object into the CACHED region
const 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 (let j = 0, m = nBindings; j !== m; ++j) {
const bindingsForPath = bindings[j],
firstActive = bindingsForPath[lastCachedIndex],
binding = bindingsForPath[index];
bindingsForPath[index] = firstActive;
bindingsForPath[lastCachedIndex] = binding;
}
}
} // for arguments
this.nCachedObjects_ = nCachedObjects;
}
// remove & forget
uncache() {
const objects = this._objects,
indicesByUUID = this._indicesByUUID,
bindings = this._bindings,
nBindings = bindings.length;
let nCachedObjects = this.nCachedObjects_,
nObjects = objects.length;
for (let i = 0, n = arguments.length; i !== n; ++i) {
const 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
const 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 (let j = 0, m = nBindings; j !== m; ++j) {
const 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
const lastIndex = --nObjects,
lastObject = objects[lastIndex];
if (lastIndex > 0) {
indicesByUUID[lastObject.uuid] = index;
}
objects[index] = lastObject;
objects.pop();
// accounting is done, now do the same for all bindings
for (let j = 0, m = nBindings; j !== m; ++j) {
const 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_(path, parsedPath) {
// returns an array of bindings for the given path that is changed
// according to the contained objects in the group
const indicesByPath = this._bindingsIndicesByPath;
let index = indicesByPath[path];
const bindings = this._bindings;
if (index !== undefined) return bindings[index];
const 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 (let i = nCachedObjects, n = objects.length; i !== n; ++i) {
const object = objects[i];
bindingsForPath[i] = new PropertyBinding(object, path, parsedPath);
}
return bindingsForPath;
}
unsubscribe_(path) {
// tells the group to forget about a property path and no longer
// update the array previously obtained with 'subscribe_'
const indicesByPath = this._bindingsIndicesByPath,
index = indicesByPath[path];
if (index !== undefined) {
const 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();
}
}
}
AnimationObjectGroup.prototype.isAnimationObjectGroup = true;
export { AnimationObjectGroup };