Skip to content

⬅️ Back to Table of Contents

📄 AnimationObjectGroup.js

📊 Analysis Summary

Metric Count
🔧 Functions 5
🧱 Classes 1
📦 Imports 2
📊 Variables & Constants 69

📚 Table of Contents

🛠️ File Location:

📂 src/animation/AnimationObjectGroup.js

📦 Imports

Name Source
PropertyBinding ./PropertyBinding.js
generateUUID ../math/MathUtils.js

Variables & Constants

Name Type Kind Value Exported
indices {} let/var {}
scope this let/var this
objects any let/var this._objects
indicesByUUID {} let/var this._indicesByUUID
paths any[] let/var this._paths
parsedPaths any[] let/var this._parsedPaths
bindings any[] let/var this._bindings
nBindings number let/var bindings.length
knownObject any let/var undefined
nObjects any let/var objects.length
nCachedObjects number let/var this.nCachedObjects_
object any let/var arguments[ i ]
uuid any let/var object.uuid
index any let/var indicesByUUID[ uuid ]
firstActiveIndex number let/var -- nCachedObjects
lastCachedObject any let/var objects[ firstActiveIndex ]
bindingsForPath any let/var bindings[ j ]
lastCached any let/var bindingsForPath[ firstActiveIndex ]
binding any let/var bindingsForPath[ index ]
objects any let/var this._objects
indicesByUUID {} let/var this._indicesByUUID
bindings any[] let/var this._bindings
nBindings number let/var bindings.length
nCachedObjects number let/var this.nCachedObjects_
object any let/var arguments[ i ]
uuid any let/var object.uuid
index any let/var indicesByUUID[ uuid ]
lastCachedIndex number let/var nCachedObjects ++
firstActiveObject any let/var objects[ lastCachedIndex ]
bindingsForPath any let/var bindings[ j ]
firstActive any let/var bindingsForPath[ lastCachedIndex ]
binding any let/var bindingsForPath[ index ]
objects any let/var this._objects
indicesByUUID {} let/var this._indicesByUUID
bindings any[] let/var this._bindings
nBindings number let/var bindings.length
nCachedObjects number let/var this.nCachedObjects_
nObjects any let/var objects.length
object any let/var arguments[ i ]
uuid any let/var object.uuid
index any let/var indicesByUUID[ uuid ]
firstActiveIndex number let/var -- nCachedObjects
lastCachedObject any let/var objects[ firstActiveIndex ]
lastIndex number let/var -- nObjects
lastObject any let/var objects[ lastIndex ]
bindingsForPath any let/var bindings[ j ]
lastCached any let/var bindingsForPath[ firstActiveIndex ]
last any let/var bindingsForPath[ lastIndex ]
lastIndex number let/var -- nObjects
lastObject any let/var objects[ lastIndex ]
bindingsForPath any let/var bindings[ j ]
indicesByPath {} let/var this._bindingsIndicesByPath
index any let/var indicesByPath[ path ]
bindings any[] let/var this._bindings
paths any[] let/var this._paths
parsedPaths any[] let/var this._parsedPaths
objects any let/var this._objects
nObjects any let/var objects.length
nCachedObjects number let/var this.nCachedObjects_
bindingsForPath any[] let/var new Array( nObjects )
object any let/var objects[ i ]
indicesByPath {} let/var this._bindingsIndicesByPath
index any let/var indicesByPath[ path ]
paths any[] let/var this._paths
parsedPaths any[] let/var this._parsedPaths
bindings any[] let/var this._bindings
lastBindingsIndex number let/var bindings.length - 1
lastBindings any let/var bindings[ lastBindingsIndex ]
lastBindingsPath any let/var path[ lastBindingsIndex ]

Functions

AnimationObjectGroup.add(): void

JSDoc:

/**
     * Adds an arbitrary number of objects to this animation group.
     *
     * @param {...Object3D} arguments - The 3D objects to add.
     */

Returns: void

Calls:

  • objects.push
  • bindings[ j ].push
  • console.error

Internal Comments:

// unknown object -> add it to the ACTIVE region (x3)
// accounting is done, now do the same for all bindings (x2)
// move existing object to the ACTIVE region (x2)
// since we do not bother to create new bindings (x3)
// for objects that are cached, the binding may (x3)
// or may not exist (x3)

Code
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;

    }

AnimationObjectGroup.remove(): void

JSDoc:

/**
     * Removes an arbitrary number of objects to this animation group
     *
     * @param {...Object3D} arguments - The 3D objects to remove.
     */

Returns: void

Internal Comments:

// move existing object into the CACHED region (x2)
// accounting is done, now do the same for all bindings

Code
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;

    }

AnimationObjectGroup.uncache(): void

JSDoc:

/**
     * Deallocates all memory resources for the passed 3D objects of this animation group.
     *
     * @param {...Object3D} arguments - The 3D objects to uncache.
     */

Returns: void

Calls:

  • objects.pop
  • bindingsForPath.pop

Internal Comments:

// object is cached, shrink the CACHED region (x2)
// last cached object takes this object's place (x4)
// last object goes to the activated slot and pop (x4)
// accounting is done, now do the same for all bindings (x2)
// object is active, just swap with the last and pop (x2)

Code
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;

    }

AnimationObjectGroup.subscribe_(path: any, parsedPath: any): any

Parameters:

  • path any
  • parsedPath any

Returns: any

Calls:

  • paths.push
  • parsedPaths.push
  • bindings.push

Internal Comments:

// returns an array of bindings for the given path that is changed (x2)
// according to the contained objects in the group (x2)

Code
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;

    }

AnimationObjectGroup.unsubscribe_(path: any): void

Parameters:

  • path any

Returns: void

Calls:

  • bindings.pop
  • parsedPaths.pop
  • paths.pop

Internal Comments:

// tells the group to forget about a property path and no longer (x2)
// update the array previously obtained with 'subscribe_' (x2)

Code
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();

        }

    }

Classes

AnimationObjectGroup

Class Code
class AnimationObjectGroup {

    /**
     * Constructs a new animation group.
     *
     * @param {...Object3D} arguments - An arbitrary number of 3D objects that share the same animation state.
     */
    constructor() {

        /**
         * This flag can be used for type testing.
         *
         * @type {boolean}
         * @readonly
         * @default true
         */
        this.isAnimationObjectGroup = true;

        /**
         * The UUID of the 3D object.
         *
         * @type {string}
         * @readonly
         */
        this.uuid = 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;

            }

        };

    }

    /**
     * Adds an arbitrary number of objects to this animation group.
     *
     * @param {...Object3D} arguments - The 3D objects to add.
     */
    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;

    }

    /**
     * Removes an arbitrary number of objects to this animation group
     *
     * @param {...Object3D} arguments - The 3D objects to remove.
     */
    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;

    }

    /**
     * Deallocates all memory resources for the passed 3D objects of this animation group.
     *
     * @param {...Object3D} arguments - The 3D objects to uncache.
     */
    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();

        }

    }

}

Methods

add(): void
Code
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(): void
Code
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;

    }
uncache(): void
Code
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;

    }
subscribe_(path: any, parsedPath: any): any
Code
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: any): void
Code
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();

        }

    }