UNPKG

@babylonjs/viewer

Version:

The Babylon Viewer aims to simplify a specific but common Babylon.js use case: loading, viewing, and interacting with a 3D model.

2,520 lines 108 kB
import { ay as Animation, M as Matrix, bX as _StaticOffsetValueColor4, bY as _StaticOffsetValueColor3, bZ as _StaticOffsetValueSize, b_ as _StaticOffsetValueVector2, b$ as _StaticOffsetValueVector3, c0 as _StaticOffsetValueQuaternion, bn as PrecisionDate, O as Observable, aO as TmpVectors, Q as Quaternion, V as Vector3, D as Scene, U as UniqueIdGenerator, G as EngineStore, br as Tags } from './index-D8dpgsp6.esm.js';
import { B as Bone } from './bone-CQ8yNhG-.esm.js';

/**
 * Defines a runtime animation
 */
class RuntimeAnimation {
    /**
     * Gets the current frame of the runtime animation
     */
    get currentFrame() {
        return this._currentFrame;
    }
    /**
     * Gets the weight of the runtime animation
     */
    get weight() {
        return this._weight;
    }
    /**
     * Gets the current value of the runtime animation
     */
    get currentValue() {
        return this._currentValue;
    }
    /**
     * Gets or sets the target path of the runtime animation
     */
    get targetPath() {
        return this._targetPath;
    }
    /**
     * Gets the actual target of the runtime animation
     */
    get target() {
        return this._currentActiveTarget;
    }
    /**
     * Gets the additive state of the runtime animation
     */
    get isAdditive() {
        return this._host && this._host.isAdditive;
    }
    /**
     * Create a new RuntimeAnimation object
     * @param target defines the target of the animation
     * @param animation defines the source animation object
     * @param scene defines the hosting scene
     * @param host defines the initiating Animatable
     */
    constructor(target, animation, scene, host) {
        this._events = new Array();
        /**
         * The current frame of the runtime animation
         */
        this._currentFrame = 0;
        /**
         * The original value of the runtime animation
         */
        this._originalValue = new Array();
        /**
         * The original blend value of the runtime animation
         */
        this._originalBlendValue = null;
        /**
         * The offsets cache of the runtime animation
         */
        this._offsetsCache = {};
        /**
         * The high limits cache of the runtime animation
         */
        this._highLimitsCache = {};
        /**
         * Specifies if the runtime animation has been stopped
         */
        this._stopped = false;
        /**
         * The blending factor of the runtime animation
         */
        this._blendingFactor = 0;
        /**
         * The current value of the runtime animation
         */
        this._currentValue = null;
        this._currentActiveTarget = null;
        this._directTarget = null;
        /**
         * The target path of the runtime animation
         */
        this._targetPath = "";
        /**
         * The weight of the runtime animation
         */
        this._weight = 1.0;
        /**
         * The absolute frame offset of the runtime animation
         */
        this._absoluteFrameOffset = 0;
        /**
         * The previous elapsed time (since start of animation) of the runtime animation
         */
        this._previousElapsedTime = 0;
        this._yoyoDirection = 1;
        /**
         * The previous absolute frame of the runtime animation (meaning, without taking into account the from/to values, only the elapsed time and the fps)
         */
        this._previousAbsoluteFrame = 0;
        this._targetIsArray = false;
        /** @internal */
        this._coreRuntimeAnimation = null;
        this._animation = animation;
        this._target = target;
        this._scene = scene;
        this._host = host;
        this._activeTargets = [];
        animation._runtimeAnimations.push(this);
        // State
        this._animationState = {
            key: 0,
            repeatCount: 0,
            loopMode: this._getCorrectLoopMode(),
        };
        if (this._animation.dataType === Animation.ANIMATIONTYPE_MATRIX) {
            this._animationState.workValue = Matrix.Zero();
        }
        // Limits
        this._keys = this._animation.getKeys();
        this._minFrame = this._keys[0].frame;
        this._maxFrame = this._keys[this._keys.length - 1].frame;
        this._minValue = this._keys[0].value;
        this._maxValue = this._keys[this._keys.length - 1].value;
        // Add a start key at frame 0 if missing
        if (this._minFrame !== 0) {
            const newKey = { frame: 0, value: this._minValue };
            this._keys.splice(0, 0, newKey);
        }
        // Check data
        if (this._target instanceof Array) {
            let index = 0;
            for (const target of this._target) {
                this._preparePath(target, index);
                this._getOriginalValues(index);
                index++;
            }
            this._targetIsArray = true;
        }
        else {
            this._preparePath(this._target);
            this._getOriginalValues();
            this._targetIsArray = false;
            this._directTarget = this._activeTargets[0];
        }
        // Cloning events locally
        const events = animation.getEvents();
        if (events && events.length > 0) {
            for (const e of events) {
                this._events.push(e._clone());
            }
        }
        this._enableBlending = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.enableBlending : this._animation.enableBlending;
    }
    _preparePath(target, targetIndex = 0) {
        const targetPropertyPath = this._animation.targetPropertyPath;
        if (targetPropertyPath.length > 1) {
            let property = target;
            for (let index = 0; index < targetPropertyPath.length - 1; index++) {
                const name = targetPropertyPath[index];
                property = property[name];
                if (property === undefined) {
                    throw new Error(`Invalid property (${name}) in property path (${targetPropertyPath.join(".")})`);
                }
            }
            this._targetPath = targetPropertyPath[targetPropertyPath.length - 1];
            this._activeTargets[targetIndex] = property;
        }
        else {
            this._targetPath = targetPropertyPath[0];
            this._activeTargets[targetIndex] = target;
        }
        if (this._activeTargets[targetIndex][this._targetPath] === undefined) {
            throw new Error(`Invalid property (${this._targetPath}) in property path (${targetPropertyPath.join(".")})`);
        }
    }
    /**
     * Gets the animation from the runtime animation
     */
    get animation() {
        return this._animation;
    }
    /**
     * Resets the runtime animation to the beginning
     * @param restoreOriginal defines whether to restore the target property to the original value
     */
    reset(restoreOriginal = false) {
        if (restoreOriginal) {
            if (this._target instanceof Array) {
                let index = 0;
                for (const target of this._target) {
                    if (this._originalValue[index] !== undefined) {
                        this._setValue(target, this._activeTargets[index], this._originalValue[index], -1, index);
                    }
                    index++;
                }
            }
            else {
                if (this._originalValue[0] !== undefined) {
                    this._setValue(this._target, this._directTarget, this._originalValue[0], -1, 0);
                }
            }
        }
        this._offsetsCache = {};
        this._highLimitsCache = {};
        this._currentFrame = 0;
        this._blendingFactor = 0;
        // Events
        for (let index = 0; index < this._events.length; index++) {
            this._events[index].isDone = false;
        }
    }
    /**
     * Specifies if the runtime animation is stopped
     * @returns Boolean specifying if the runtime animation is stopped
     */
    isStopped() {
        return this._stopped;
    }
    /**
     * Disposes of the runtime animation
     */
    dispose() {
        const index = this._animation.runtimeAnimations.indexOf(this);
        if (index > -1) {
            this._animation.runtimeAnimations.splice(index, 1);
        }
    }
    /**
     * Apply the interpolated value to the target
     * @param currentValue defines the value computed by the animation
     * @param weight defines the weight to apply to this value (Defaults to 1.0)
     */
    setValue(currentValue, weight) {
        if (this._targetIsArray) {
            for (let index = 0; index < this._target.length; index++) {
                const target = this._target[index];
                this._setValue(target, this._activeTargets[index], currentValue, weight, index);
            }
            return;
        }
        this._setValue(this._target, this._directTarget, currentValue, weight, 0);
    }
    _getOriginalValues(targetIndex = 0) {
        let originalValue;
        const target = this._activeTargets[targetIndex];
        if (target.getLocalMatrix && this._targetPath === "_matrix") {
            // For bones
            originalValue = target.getLocalMatrix();
        }
        else {
            originalValue = target[this._targetPath];
        }
        if (originalValue && originalValue.clone) {
            this._originalValue[targetIndex] = originalValue.clone();
        }
        else {
            this._originalValue[targetIndex] = originalValue;
        }
    }
    _registerTargetForLateAnimationBinding(runtimeAnimation, originalValue) {
        const target = runtimeAnimation.target;
        this._scene._registeredForLateAnimationBindings.pushNoDuplicate(target);
        if (!target._lateAnimationHolders) {
            target._lateAnimationHolders = {};
        }
        if (!target._lateAnimationHolders[runtimeAnimation.targetPath]) {
            target._lateAnimationHolders[runtimeAnimation.targetPath] = {
                totalWeight: 0,
                totalAdditiveWeight: 0,
                animations: [],
                additiveAnimations: [],
                originalValue: originalValue,
            };
        }
        if (runtimeAnimation.isAdditive) {
            target._lateAnimationHolders[runtimeAnimation.targetPath].additiveAnimations.push(runtimeAnimation);
            target._lateAnimationHolders[runtimeAnimation.targetPath].totalAdditiveWeight += runtimeAnimation.weight;
        }
        else {
            target._lateAnimationHolders[runtimeAnimation.targetPath].animations.push(runtimeAnimation);
            target._lateAnimationHolders[runtimeAnimation.targetPath].totalWeight += runtimeAnimation.weight;
        }
    }
    _setValue(target, destination, currentValue, weight, targetIndex) {
        // Set value
        this._currentActiveTarget = destination;
        this._weight = weight;
        if (this._enableBlending && this._blendingFactor <= 1.0) {
            if (!this._originalBlendValue) {
                const originalValue = destination[this._targetPath];
                if (originalValue.clone) {
                    this._originalBlendValue = originalValue.clone();
                }
                else {
                    this._originalBlendValue = originalValue;
                }
            }
            if (this._originalBlendValue.m) {
                // Matrix
                if (Animation.AllowMatrixDecomposeForInterpolation) {
                    if (this._currentValue) {
                        Matrix.DecomposeLerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
                    }
                    else {
                        this._currentValue = Matrix.DecomposeLerp(this._originalBlendValue, currentValue, this._blendingFactor);
                    }
                }
                else {
                    if (this._currentValue) {
                        Matrix.LerpToRef(this._originalBlendValue, currentValue, this._blendingFactor, this._currentValue);
                    }
                    else {
                        this._currentValue = Matrix.Lerp(this._originalBlendValue, currentValue, this._blendingFactor);
                    }
                }
            }
            else {
                this._currentValue = Animation._UniversalLerp(this._originalBlendValue, currentValue, this._blendingFactor);
            }
            const blendingSpeed = target && target.animationPropertiesOverride ? target.animationPropertiesOverride.blendingSpeed : this._animation.blendingSpeed;
            this._blendingFactor += blendingSpeed;
        }
        else {
            if (!this._currentValue) {
                if (currentValue?.clone) {
                    this._currentValue = currentValue.clone();
                }
                else {
                    this._currentValue = currentValue;
                }
            }
            else if (this._currentValue.copyFrom) {
                this._currentValue.copyFrom(currentValue);
            }
            else {
                this._currentValue = currentValue;
            }
        }
        if (weight !== -1) {
            this._registerTargetForLateAnimationBinding(this, this._originalValue[targetIndex]);
        }
        else {
            if (this._animationState.loopMode === Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT) {
                if (this._currentValue.addToRef) {
                    this._currentValue.addToRef(this._originalValue[targetIndex], destination[this._targetPath]);
                }
                else {
                    destination[this._targetPath] = this._originalValue[targetIndex] + this._currentValue;
                }
            }
            else {
                destination[this._targetPath] = this._currentValue;
            }
        }
        if (target.markAsDirty) {
            target.markAsDirty(this._animation.targetProperty);
        }
    }
    /**
     * Gets the loop pmode of the runtime animation
     * @returns Loop Mode
     */
    _getCorrectLoopMode() {
        if (this._target && this._target.animationPropertiesOverride) {
            return this._target.animationPropertiesOverride.loopMode;
        }
        return this._animation.loopMode;
    }
    /**
     * Move the current animation to a given frame
     * @param frame defines the frame to move to
     * @param weight defines the weight to apply to the animation (-1.0 by default)
     */
    goToFrame(frame, weight = -1) {
        const keys = this._animation.getKeys();
        if (frame < keys[0].frame) {
            frame = keys[0].frame;
        }
        else if (frame > keys[keys.length - 1].frame) {
            frame = keys[keys.length - 1].frame;
        }
        // Need to reset animation events
        const events = this._events;
        if (events.length) {
            for (let index = 0; index < events.length; index++) {
                if (!events[index].onlyOnce) {
                    // reset events in the future
                    events[index].isDone = events[index].frame < frame;
                }
            }
        }
        this._currentFrame = frame;
        const currentValue = this._animation._interpolate(frame, this._animationState);
        this.setValue(currentValue, weight);
    }
    /**
     * @internal Internal use only
     */
    _prepareForSpeedRatioChange(newSpeedRatio) {
        const newAbsoluteFrame = (this._previousElapsedTime * (this._animation.framePerSecond * newSpeedRatio)) / 1000.0;
        this._absoluteFrameOffset = this._previousAbsoluteFrame - newAbsoluteFrame;
    }
    /**
     * Execute the current animation
     * @param elapsedTimeSinceAnimationStart defines the elapsed time (in milliseconds) since the animation was started
     * @param from defines the lower frame of the animation range
     * @param to defines the upper frame of the animation range
     * @param loop defines if the current animation must loop
     * @param speedRatio defines the current speed ratio
     * @param weight defines the weight of the animation (default is -1 so no weight)
     * @returns a boolean indicating if the animation is running
     */
    animate(elapsedTimeSinceAnimationStart, from, to, loop, speedRatio, weight = -1) {
        const animation = this._animation;
        const targetPropertyPath = animation.targetPropertyPath;
        if (!targetPropertyPath || targetPropertyPath.length < 1) {
            this._stopped = true;
            return false;
        }
        let returnValue = true;
        let currentFrame;
        const events = this._events;
        let frameRange = 0;
        if (!this._coreRuntimeAnimation) {
            // Check limits
            if (from < this._minFrame || from > this._maxFrame) {
                from = this._minFrame;
            }
            if (to < this._minFrame || to > this._maxFrame) {
                to = this._maxFrame;
            }
            frameRange = to - from;
            let offsetValue;
            // Compute the frame according to the elapsed time and the fps of the animation ("from" and "to" are not factored in!)
            let absoluteFrame = (elapsedTimeSinceAnimationStart * (animation.framePerSecond * speedRatio)) / 1000.0 + this._absoluteFrameOffset;
            let highLimitValue = 0;
            // Apply the yoyo function if required
            let yoyoLoop = false;
            const yoyoMode = loop && this._animationState.loopMode === Animation.ANIMATIONLOOPMODE_YOYO;
            if (yoyoMode) {
                const position = (absoluteFrame - from) / frameRange;
                // Apply the yoyo curve
                const sin = Math.sin(position * Math.PI);
                const yoyoPosition = Math.abs(sin);
                // Map the yoyo position back to the range
                absoluteFrame = yoyoPosition * frameRange + from;
                const direction = sin >= 0 ? 1 : -1;
                if (this._yoyoDirection !== direction) {
                    yoyoLoop = true;
                }
                this._yoyoDirection = direction;
            }
            this._previousElapsedTime = elapsedTimeSinceAnimationStart;
            this._previousAbsoluteFrame = absoluteFrame;
            if (!loop && to >= from && ((absoluteFrame >= frameRange && speedRatio > 0) || (absoluteFrame <= 0 && speedRatio < 0))) {
                // If we are out of range and not looping get back to caller
                returnValue = false;
                highLimitValue = animation._getKeyValue(this._maxValue);
            }
            else if (!loop && from >= to && ((absoluteFrame <= frameRange && speedRatio < 0) || (absoluteFrame >= 0 && speedRatio > 0))) {
                returnValue = false;
                highLimitValue = animation._getKeyValue(this._minValue);
            }
            else if (this._animationState.loopMode !== Animation.ANIMATIONLOOPMODE_CYCLE) {
                const keyOffset = to.toString() + from.toString();
                if (!this._offsetsCache[keyOffset]) {
                    this._animationState.repeatCount = 0;
                    this._animationState.loopMode = Animation.ANIMATIONLOOPMODE_CYCLE; // force a specific codepath in animation._interpolate()!
                    const fromValue = animation._interpolate(from, this._animationState);
                    const toValue = animation._interpolate(to, this._animationState);
                    this._animationState.loopMode = this._getCorrectLoopMode();
                    switch (animation.dataType) {
                        // Float
                        case Animation.ANIMATIONTYPE_FLOAT:
                            this._offsetsCache[keyOffset] = toValue - fromValue;
                            break;
                        // Quaternion
                        case Animation.ANIMATIONTYPE_QUATERNION:
                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                            break;
                        // Vector3
                        case Animation.ANIMATIONTYPE_VECTOR3:
                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                            break;
                        // Vector2
                        case Animation.ANIMATIONTYPE_VECTOR2:
                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                            break;
                        // Size
                        case Animation.ANIMATIONTYPE_SIZE:
                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                            break;
                        // Color3
                        case Animation.ANIMATIONTYPE_COLOR3:
                            this._offsetsCache[keyOffset] = toValue.subtract(fromValue);
                            break;
                    }
                    this._highLimitsCache[keyOffset] = toValue;
                }
                highLimitValue = this._highLimitsCache[keyOffset];
                offsetValue = this._offsetsCache[keyOffset];
            }
            if (offsetValue === undefined) {
                switch (animation.dataType) {
                    // Float
                    case Animation.ANIMATIONTYPE_FLOAT:
                        offsetValue = 0;
                        break;
                    // Quaternion
                    case Animation.ANIMATIONTYPE_QUATERNION:
                        offsetValue = _StaticOffsetValueQuaternion;
                        break;
                    // Vector3
                    case Animation.ANIMATIONTYPE_VECTOR3:
                        offsetValue = _StaticOffsetValueVector3;
                        break;
                    // Vector2
                    case Animation.ANIMATIONTYPE_VECTOR2:
                        offsetValue = _StaticOffsetValueVector2;
                        break;
                    // Size
                    case Animation.ANIMATIONTYPE_SIZE:
                        offsetValue = _StaticOffsetValueSize;
                        break;
                    // Color3
                    case Animation.ANIMATIONTYPE_COLOR3:
                        offsetValue = _StaticOffsetValueColor3;
                        break;
                    case Animation.ANIMATIONTYPE_COLOR4:
                        offsetValue = _StaticOffsetValueColor4;
                        break;
                }
            }
            // Compute value
            if (this._host && this._host.syncRoot) {
                // If we must sync with an animatable, calculate the current frame based on the frame of the root animatable
                const syncRoot = this._host.syncRoot;
                const hostNormalizedFrame = (syncRoot.masterFrame - syncRoot.fromFrame) / (syncRoot.toFrame - syncRoot.fromFrame);
                currentFrame = from + frameRange * hostNormalizedFrame;
            }
            else {
                if ((absoluteFrame > 0 && from > to) || (absoluteFrame < 0 && from < to)) {
                    currentFrame = returnValue && frameRange !== 0 ? to + (absoluteFrame % frameRange) : from;
                }
                else {
                    currentFrame = returnValue && frameRange !== 0 ? from + (absoluteFrame % frameRange) : to;
                }
            }
            // Reset event/state if looping
            if ((!yoyoMode && ((speedRatio > 0 && this.currentFrame > currentFrame) || (speedRatio < 0 && this.currentFrame < currentFrame))) || (yoyoMode && yoyoLoop)) {
                this._onLoop();
                // Need to reset animation events
                for (let index = 0; index < events.length; index++) {
                    if (!events[index].onlyOnce) {
                        // reset event, the animation is looping
                        events[index].isDone = false;
                    }
                }
                this._animationState.key = speedRatio > 0 ? 0 : animation.getKeys().length - 1;
            }
            this._currentFrame = currentFrame;
            this._animationState.repeatCount = frameRange === 0 ? 0 : (absoluteFrame / frameRange) >> 0;
            this._animationState.highLimitValue = highLimitValue;
            this._animationState.offsetValue = offsetValue;
        }
        else {
            frameRange = to - from;
            currentFrame = this._coreRuntimeAnimation.currentFrame;
            this._currentFrame = currentFrame;
            this._animationState.repeatCount = this._coreRuntimeAnimation._animationState.repeatCount;
            this._animationState.highLimitValue = this._coreRuntimeAnimation._animationState.highLimitValue;
            this._animationState.offsetValue = this._coreRuntimeAnimation._animationState.offsetValue;
        }
        const currentValue = animation._interpolate(currentFrame, this._animationState);
        // Set value
        this.setValue(currentValue, weight);
        // Check events
        if (events.length) {
            for (let index = 0; index < events.length; index++) {
                // Make sure current frame has passed event frame and that event frame is within the current range
                // Also, handle both forward and reverse animations
                if ((frameRange >= 0 && currentFrame >= events[index].frame && events[index].frame >= from) ||
                    (frameRange < 0 && currentFrame <= events[index].frame && events[index].frame <= from)) {
                    const event = events[index];
                    if (!event.isDone) {
                        // If event should be done only once, remove it.
                        if (event.onlyOnce) {
                            events.splice(index, 1);
                            index--;
                        }
                        event.isDone = true;
                        event.action(currentFrame);
                    } // Don't do anything if the event has already been done.
                }
            }
        }
        if (!returnValue) {
            this._stopped = true;
        }
        return returnValue;
    }
}

/**
 * Class used to store an actual running animation
 */
class Animatable {
    /**
     * Gets the root Animatable used to synchronize and normalize animations
     */
    get syncRoot() {
        return this._syncRoot;
    }
    /**
     * Gets the current frame of the first RuntimeAnimation
     * Used to synchronize Animatables
     */
    get masterFrame() {
        if (this._runtimeAnimations.length === 0) {
            return 0;
        }
        return this._runtimeAnimations[0].currentFrame;
    }
    /**
     * Gets or sets the animatable weight (-1.0 by default meaning not weighted)
     */
    get weight() {
        return this._weight;
    }
    set weight(value) {
        if (value === -1) {
            // -1 is ok and means no weight
            this._weight = -1;
            return;
        }
        // Else weight must be in [0, 1] range
        this._weight = Math.min(Math.max(value, 0), 1.0);
    }
    /**
     * Gets or sets the speed ratio to apply to the animatable (1.0 by default)
     */
    get speedRatio() {
        return this._speedRatio;
    }
    set speedRatio(value) {
        for (let index = 0; index < this._runtimeAnimations.length; index++) {
            const animation = this._runtimeAnimations[index];
            animation._prepareForSpeedRatioChange(value);
        }
        this._speedRatio = value;
        // Resync _manualJumpDelay in case goToFrame was called before speedRatio was set.
        if (this._goToFrame !== null) {
            this.goToFrame(this._goToFrame);
        }
    }
    /**
     * Gets the elapsed time since the animatable started in milliseconds
     */
    get elapsedTime() {
        return this._localDelayOffset === null ? 0 : this._scene._animationTime - this._localDelayOffset;
    }
    /**
     * Creates a new Animatable
     * @param scene defines the hosting scene
     * @param target defines the target object
     * @param fromFrame defines the starting frame number (default is 0)
     * @param toFrame defines the ending frame number (default is 100)
     * @param loopAnimation defines if the animation must loop (default is false)
     * @param speedRatio defines the factor to apply to animation speed (default is 1)
     * @param onAnimationEnd defines a callback to call when animation ends if it is not looping
     * @param animations defines a group of animation to add to the new Animatable
     * @param onAnimationLoop defines a callback to call when animation loops
     * @param isAdditive defines whether the animation should be evaluated additively
     * @param playOrder defines the order in which this animatable should be processed in the list of active animatables (default: 0)
     */
    constructor(scene, 
    /** defines the target object */
    target, 
    /** [0] defines the starting frame number (default is 0) */
    fromFrame = 0, 
    /** [100] defines the ending frame number (default is 100) */
    toFrame = 100, 
    /** [false] defines if the animation must loop (default is false)  */
    loopAnimation = false, speedRatio = 1.0, 
    /** defines a callback to call when animation ends if it is not looping */
    onAnimationEnd, animations, 
    /** defines a callback to call when animation loops */
    onAnimationLoop, 
    /** [false] defines whether the animation should be evaluated additively */
    isAdditive = false, 
    /** [0] defines the order in which this animatable should be processed in the list of active animatables (default: 0) */
    playOrder = 0) {
        this.target = target;
        this.fromFrame = fromFrame;
        this.toFrame = toFrame;
        this.loopAnimation = loopAnimation;
        this.onAnimationEnd = onAnimationEnd;
        this.onAnimationLoop = onAnimationLoop;
        this.isAdditive = isAdditive;
        this.playOrder = playOrder;
        this._localDelayOffset = null;
        this._pausedDelay = null;
        this._manualJumpDelay = null;
        /** @hidden */
        this._runtimeAnimations = new Array();
        this._paused = false;
        this._speedRatio = 1;
        this._weight = -1;
        this._previousWeight = -1;
        this._syncRoot = null;
        this._frameToSyncFromJump = null;
        this._goToFrame = null;
        /**
         * Gets or sets a boolean indicating if the animatable must be disposed and removed at the end of the animation.
         * This will only apply for non looping animation (default is true)
         */
        this.disposeOnEnd = true;
        /**
         * Gets a boolean indicating if the animation has started
         */
        this.animationStarted = false;
        /**
         * Observer raised when the animation ends
         */
        this.onAnimationEndObservable = new Observable();
        /**
         * Observer raised when the animation loops
         */
        this.onAnimationLoopObservable = new Observable();
        this._scene = scene;
        if (animations) {
            this.appendAnimations(target, animations);
        }
        this._speedRatio = speedRatio;
        scene._activeAnimatables.push(this);
    }
    // Methods
    /**
     * Synchronize and normalize current Animatable with a source Animatable
     * This is useful when using animation weights and when animations are not of the same length
     * @param root defines the root Animatable to synchronize with (null to stop synchronizing)
     * @returns the current Animatable
     */
    syncWith(root) {
        this._syncRoot = root;
        if (root) {
            // Make sure this animatable will animate after the root
            const index = this._scene._activeAnimatables.indexOf(this);
            if (index > -1) {
                this._scene._activeAnimatables.splice(index, 1);
                this._scene._activeAnimatables.push(this);
            }
        }
        return this;
    }
    /**
     * Gets the list of runtime animations
     * @returns an array of RuntimeAnimation
     */
    getAnimations() {
        return this._runtimeAnimations;
    }
    /**
     * Adds more animations to the current animatable
     * @param target defines the target of the animations
     * @param animations defines the new animations to add
     */
    appendAnimations(target, animations) {
        for (let index = 0; index < animations.length; index++) {
            const animation = animations[index];
            const newRuntimeAnimation = new RuntimeAnimation(target, animation, this._scene, this);
            newRuntimeAnimation._onLoop = () => {
                this.onAnimationLoopObservable.notifyObservers(this);
                if (this.onAnimationLoop) {
                    this.onAnimationLoop();
                }
            };
            this._runtimeAnimations.push(newRuntimeAnimation);
        }
    }
    /**
     * Gets the source animation for a specific property
     * @param property defines the property to look for
     * @returns null or the source animation for the given property
     */
    getAnimationByTargetProperty(property) {
        const runtimeAnimations = this._runtimeAnimations;
        for (let index = 0; index < runtimeAnimations.length; index++) {
            if (runtimeAnimations[index].animation.targetProperty === property) {
                return runtimeAnimations[index].animation;
            }
        }
        return null;
    }
    /**
     * Gets the runtime animation for a specific property
     * @param property defines the property to look for
     * @returns null or the runtime animation for the given property
     */
    getRuntimeAnimationByTargetProperty(property) {
        const runtimeAnimations = this._runtimeAnimations;
        for (let index = 0; index < runtimeAnimations.length; index++) {
            if (runtimeAnimations[index].animation.targetProperty === property) {
                return runtimeAnimations[index];
            }
        }
        return null;
    }
    /**
     * Resets the animatable to its original state
     */
    reset() {
        const runtimeAnimations = this._runtimeAnimations;
        for (let index = 0; index < runtimeAnimations.length; index++) {
            runtimeAnimations[index].reset(true);
        }
        this._localDelayOffset = null;
        this._pausedDelay = null;
    }
    /**
     * Allows the animatable to blend with current running animations
     * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-blending
     * @param blendingSpeed defines the blending speed to use
     */
    enableBlending(blendingSpeed) {
        const runtimeAnimations = this._runtimeAnimations;
        for (let index = 0; index < runtimeAnimations.length; index++) {
            runtimeAnimations[index].animation.enableBlending = true;
            runtimeAnimations[index].animation.blendingSpeed = blendingSpeed;
        }
    }
    /**
     * Disable animation blending
     * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-blending
     */
    disableBlending() {
        const runtimeAnimations = this._runtimeAnimations;
        for (let index = 0; index < runtimeAnimations.length; index++) {
            runtimeAnimations[index].animation.enableBlending = false;
        }
    }
    /**
     * Jump directly to a given frame
     * @param frame defines the frame to jump to
     * @param useWeight defines whether the animation weight should be applied to the image to be jumped to (false by default)
     */
    goToFrame(frame, useWeight = false) {
        const runtimeAnimations = this._runtimeAnimations;
        if (runtimeAnimations[0]) {
            const fps = runtimeAnimations[0].animation.framePerSecond;
            this._frameToSyncFromJump = this._frameToSyncFromJump ?? runtimeAnimations[0].currentFrame;
            const delay = this.speedRatio === 0 ? 0 : (((frame - this._frameToSyncFromJump) / fps) * 1000) / this.speedRatio;
            this._manualJumpDelay = -delay;
        }
        for (let index = 0; index < runtimeAnimations.length; index++) {
            runtimeAnimations[index].goToFrame(frame, useWeight ? this._weight : -1);
        }
        this._goToFrame = frame;
    }
    /**
     * Returns true if the animations for this animatable are paused
     */
    get paused() {
        return this._paused;
    }
    /**
     * Pause the animation
     */
    pause() {
        if (this._paused) {
            return;
        }
        this._paused = true;
    }
    /**
     * Restart the animation
     */
    restart() {
        this._paused = false;
    }
    _raiseOnAnimationEnd() {
        if (this.onAnimationEnd) {
            this.onAnimationEnd();
        }
        this.onAnimationEndObservable.notifyObservers(this);
    }
    /**
     * Stop and delete the current animation
     * @param animationName defines a string used to only stop some of the runtime animations instead of all
     * @param targetMask a function that determines if the animation should be stopped based on its target (all animations will be stopped if both this and animationName are empty)
     * @param useGlobalSplice if true, the animatables will be removed by the caller of this function (false by default)
     * @param skipOnAnimationEnd defines if the system should not raise onAnimationEnd. Default is false
     */
    stop(animationName, targetMask, useGlobalSplice = false, skipOnAnimationEnd = false) {
        if (animationName || targetMask) {
            const idx = this._scene._activeAnimatables.indexOf(this);
            if (idx > -1) {
                const runtimeAnimations = this._runtimeAnimations;
                for (let index = runtimeAnimations.length - 1; index >= 0; index--) {
                    const runtimeAnimation = runtimeAnimations[index];
                    if (animationName && runtimeAnimation.animation.name != animationName) {
                        continue;
                    }
                    if (targetMask && !targetMask(runtimeAnimation.target)) {
                        continue;
                    }
                    runtimeAnimation.dispose();
                    runtimeAnimations.splice(index, 1);
                }
                if (runtimeAnimations.length == 0) {
                    if (!useGlobalSplice) {
                        this._scene._activeAnimatables.splice(idx, 1);
                    }
                    if (!skipOnAnimationEnd) {
                        this._raiseOnAnimationEnd();
                    }
                }
            }
        }
        else {
            const index = this._scene._activeAnimatables.indexOf(this);
            if (index > -1) {
                if (!useGlobalSplice) {
                    this._scene._activeAnimatables.splice(index, 1);
                }
                const runtimeAnimations = this._runtimeAnimations;
                for (let index = 0; index < runtimeAnimations.length; index++) {
                    runtimeAnimations[index].dispose();
                }
                this._runtimeAnimations.length = 0;
                if (!skipOnAnimationEnd) {
                    this._raiseOnAnimationEnd();
                }
            }
        }
    }
    /**
     * Wait asynchronously for the animation to end
     * @returns a promise which will be fulfilled when the animation ends
     */
    async waitAsync() {
        return await new Promise((resolve) => {
            this.onAnimationEndObservable.add(() => {
                resolve(this);
            }, undefined, undefined, this, true);
        });
    }
    /**
     * @internal
     */
    _animate(delay) {
        if (this._paused) {
            this.animationStarted = false;
            if (this._pausedDelay === null) {
                this._pausedDelay = delay;
            }
            return true;
        }
        if (this._localDelayOffset === null) {
            this._localDelayOffset = delay;
            this._pausedDelay = null;
        }
        else if (this._pausedDelay !== null) {
            this._localDelayOffset += delay - this._pausedDelay;
            this._pausedDelay = null;
        }
        if (this._manualJumpDelay !== null) {
            this._localDelayOffset += this.speedRatio < 0 ? -this._manualJumpDelay : this._manualJumpDelay;
            this._manualJumpDelay = null;
            this._frameToSyncFromJump = null;
        }
        this._goToFrame = null;
        if (!Animatable.ProcessPausedAnimatables && this._weight === 0 && this._previousWeight === 0) {
            // We consider that an animatable with a weight === 0 is "actively" paused
            return true;
        }
        this._previousWeight = this._weight;
        // Animating
        let running = false;
        const runtimeAnimations = this._runtimeAnimations;
        let index;
        for (index = 0; index < runtimeAnimations.length; index++) {
            const animation = runtimeAnimations[index];
            const isRunning = animation.animate(delay - this._localDelayOffset, this.fromFrame, this.toFrame, this.loopAnimation, this._speedRatio, this._weight);
            running = running || isRunning;
        }
        this.animationStarted = running;
        if (!running) {
            if (this.disposeOnEnd) {
                // Remove from active animatables
                index = this._scene._activeAnimatables.indexOf(this);
                this._scene._activeAnimatables.splice(index, 1);
                // Dispose all runtime animations
                for (index = 0; index < runtimeAnimations.length; index++) {
                    runtimeAnimations[index].dispose();
                }
            }
            this._raiseOnAnimationEnd();
            if (this.disposeOnEnd) {
                this.onAnimationEnd = null;
                this.onAnimationLoop = null;
                this.onAnimationLoopObservable.clear();
                this.onAnimationEndObservable.clear();
            }
        }
        return running;
    }
}
/**
 * If true, the animatable will be processed even if it is considered actively paused (weight of 0 and previous weight of 0).
 * This can be used to force the full processing of paused animatables in the animation engine.
 * Default is false.
 */
Animatable.ProcessPausedAnimatables = false;
/** @internal */
function ProcessLateAnimationBindingsForMatrices(holder) {
    if (holder.totalWeight === 0 && holder.totalAdditiveWeight === 0) {
        return holder.originalValue;
    }
    let normalizer = 1.0;
    const finalPosition = TmpVectors.Vector3[0];
    const finalScaling = TmpVectors.Vector3[1];
    const finalQuaternion = TmpVectors.Quaternion[0];
    let startIndex = 0;
    const originalAnimation = holder.animations[0];
    const originalValue = holder.originalValue;
    let scale = 1;
    let skipOverride = false;
    if (holder.totalWeight < 1.0) {
        // We need to mix the original value in
        scale = 1.0 - holder.totalWeight;
        originalValue.decompose(finalScaling, finalQuaternion, finalPosition);
    }
    else {
        startIndex = 1;
        // We need to normalize the weights
        normalizer = holder.totalWeight;
        scale = originalAnimation.weight / normalizer;
        if (scale == 1) {
            if (holder.totalAdditiveWeight) {
                skipOverride = true;
            }
            else {
                return originalAnimation.currentValue;
            }
        }
        originalAnimation.currentValue.decompose(finalScaling, finalQuaternion, finalPosition);
    }
    // Add up the override animations
    if (!skipOverride) {
        finalScaling.scaleInPlace(scale);
        finalPosition.scaleInPlace(scale);
        finalQuaternion.scaleInPlace(scale);
        for (let animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
            const runtimeAnimation = holder.animations[animIndex];
            if (runtimeAnimation.weight === 0) {
                continue;
            }
            scale = runtimeAnimation.weight / normalizer;
            const currentPosition = TmpVectors.Vector3[2];
            const currentScaling = TmpVectors.Vector3[3];
            const currentQuaternion = TmpVectors.Quaternion[1];
            runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
            currentScaling.scaleAndAddToRef(scale, finalScaling);
            currentQuaternion.scaleAndAddToRef(Quaternion.Dot(finalQuaternion, currentQuaternion) > 0 ? scale : -scale, finalQuaternion);
            currentPosition.scaleAndAddToRef(scale, finalPosition);
        }
        finalQuaternion.normalize();
    }
    // Add up the additive animations
    for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
        const runtimeAnimation = holder.additiveAnimations[animIndex];
        if (runtimeAnimation.weight === 0) {
            continue;
        }
        const currentPosition = TmpVectors.Vector3[2];
        const currentScaling = TmpVectors.Vector3[3];
        const currentQuaternion = TmpVectors.Quaternion[1];
        runtimeAnimation.currentValue.decompose(currentScaling, currentQuaternion, currentPosition);
        currentScaling.multiplyToRef(finalScaling, currentScaling);
        Vector3.LerpToRef(finalScaling, currentScaling, runtimeAnimation.weight, finalScaling);
        finalQuaternion.multiplyToRef(currentQuaternion, currentQuaternion);
        Quaternion.SlerpToRef(finalQuaternion, currentQuaternion, runtimeAnimation.weight, finalQuaternion);
        currentPosition.scaleAndAddToRef(runtimeAnimation.weight, finalPosition);
    }
    const workValue = originalAnimation ? originalAnimation._animationState.workValue : TmpVectors.Matrix[0].clone();
    Matrix.ComposeToRef(finalScaling, finalQuaternion, finalPosition, workValue);
    return workValue;
}
/** @internal */
function ProcessLateAnimationBindingsForQuaternions(holder, refQuaternion) {
    if (holder.totalWeight === 0 && holder.totalAdditiveWeight === 0) {
        return refQuaternion;
    }
    const originalAnimation = holder.animations[0];
    const originalValue = holder.originalValue;
    let cumulativeQuaternion = refQuaternion;
    if (holder.totalWeight === 0 && holder.totalAdditiveWeight > 0) {
        cumulativeQuaternion.copyFrom(originalValue);
    }
    else if (holder.animations.length === 1) {
        Quaternion.SlerpToRef(originalValue, originalAnimation.currentValue, Math.min(1.0, holder.totalWeight), cumulativeQuaternion);
        if (holder.totalAdditiveWeight === 0) {
            return cumulativeQuaternion;
        }
    }
    else if (holder.animations.length > 1) {
        // Add up the override animations
        let normalizer = 1.0;
        let quaternions;
        let weights;
        if (holder.totalWeight < 1.0) {
            const scale = 1.0 - holder.totalWeight;
            quaternions = [];
            weights = [];
            quaternions.push(originalValue);
            weights.push(scale);
        }
        else {
            if (holder.animations.length === 2) {
                // Slerp as soon as we can
                Quaternion.SlerpToRef(holder.animations[0].currentValue, holder.animations[1].currentValue, holder.animations[1].weight / holder.totalWeight, refQuaternion);
                if (holder.totalAdditiveWeight === 0) {
                    return refQuaternion;
                }
            }
            quaternions = [];
            weights = [];
            normalizer = holder.totalWeight;
        }
        for (let animIndex = 0; animIndex < holder.animations.length; animIndex++) {
            const runtimeAnimation = holder.animations[animIndex];
            quaternions.push(runtimeAnimation.currentValue);
            weights.push(runtimeAnimation.weight / normalizer);
        }
        // https://gamedev.stackexchange.com/questions/62354/method-for-interpolation-between-3-quaternions
        let cumulativeAmount = 0;
        for (let index = 0; index < quaternions.length;) {
            if (!index) {
                Quaternion.SlerpToRef(quaternions[index], quaternions[index + 1], weights[index + 1] / (weights[index] + weights[index + 1]), refQuaternion);
                cumulativeQuaternion = refQuaternion;
                cumulativeAmount = weights[index] + weights[index + 1];
                index += 2;
                continue;
            }
            cumulativeAmount += weights[index];
            Quaternion.SlerpToRef(cumulativeQuaternion, quaternions[index], weights[index] / cumulativeAmount, cumulativeQuaternion);
            index++;
        }
    }
    // Add up the additive animations
    for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
        const runtimeAnimation = holder.additiveAnimations[animIndex];
        if (runtimeAnimation.weight === 0) {
            continue;
        }
        cumulativeQuaternion.multiplyToRef(runtimeAnimation.currentValue, TmpVectors.Quaternion[0]);
        Quaternion.SlerpToRef(cumulativeQuaternion, TmpVectors.Quaternion[0], runtimeAnimation.weight, cumulativeQuaternion);
    }
    return cumulativeQuaternion;
}
/** @internal */
function ProcessLateAnimationBindings(scene) {
    if (!scene._registeredForLateAnimationBindings.length) {
        return;
    }
    for (let index = 0; index < scene._registeredForLateAnimationBindings.length; index++) {
        const target = scene._registeredForLateAnimationBindings.data[index];
        for (const path in target._lateAnimationHolders) {
            const holder = target._lateAnimationHolders[path];
            const originalAnimation = holder.animations[0];
            const originalValue = holder.originalValue;
            if (originalValue === undefined || originalValue === null) {
                continue;
            }
            const matrixDecomposeMode = Animation.AllowMatrixDecomposeForInterpolation && originalValue.m; // ie. data is matrix
            let finalValue = target[path];
            if (matrixDecomposeMode) {
                finalValue = ProcessLateAnimationBindingsForMatrices(holder);
            }
            else {
                const quaternionMode = originalValue.w !== undefined;
                if (quaternionMode) {
                    finalValue = ProcessLateAnimationBindingsForQuaternions(holder, finalValue || Quaternion.Identity());
                }
                else {
                    let startIndex = 0;
                    let normalizer = 1.0;
                    const originalAnimationIsLoopRelativeFromCurrent = originalAnimation && originalAnimation._animationState.loopMode === Animation.ANIMATIONLOOPMODE_RELATIVE_FROM_CURRENT;
                    if (holder.totalWeight < 1.0) {
                        // We need to mix the original value in
                        if (originalAnimationIsLoopRelativeFromCurrent) {
                            finalValue = originalValue.clone ? originalValue.clone() : originalValue;
                        }
                        else if (originalAnimation && originalValue.scale) {
                            finalValue = originalValue.scale(1.0 - holder.totalWeight);
                        }
                        else if (originalAnimation) {
                            finalValue = originalValue * (1.0 - holder.totalWeight);
                        }
                        else if (originalValue.clone) {
                            finalValue = originalValue.clone();
                        }
                        else {
                            finalValue = originalValue;
                        }
                    }
                    else if (originalAnimation) {
                        // We need to normalize the weights
                        normalizer = holder.totalWeight;
                        const scale = originalAnimation.weight / normalizer;
                        if (scale !== 1) {
                            if (originalAnimation.currentValue.scale) {
                                finalValue = originalAnimation.currentValue.scale(scale);
                            }
                            else {
                                finalValue = originalAnimation.currentValue * scale;
                            }
                        }
                        else {
                            finalValue = originalAnimation.currentValue;
                        }
                        if (originalAnimationIsLoopRelativeFromCurrent) {
                            if (finalValue.addToRef) {
                                finalValue.addToRef(originalValue, finalValue);
                            }
                            else {
                                finalValue += originalValue;
                            }
                        }
                        startIndex = 1;
                    }
                    // Add up the override animations
                    for (let animIndex = startIndex; animIndex < holder.animations.length; animIndex++) {
                        const runtimeAnimation = holder.animations[animIndex];
                        const scale = runtimeAnimation.weight / normalizer;
                        if (!scale) {
                            continue;
                        }
                        else if (runtimeAnimation.currentValue.scaleAndAddToRef) {
                            runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
                        }
                        else {
                            finalValue += runtimeAnimation.currentValue * scale;
                        }
                    }
                    // Add up the additive animations
                    for (let animIndex = 0; animIndex < holder.additiveAnimations.length; animIndex++) {
                        const runtimeAnimation = holder.additiveAnimations[animIndex];
                        const scale = runtimeAnimation.weight;
                        if (!scale) {
                            continue;
                        }
                        else if (runtimeAnimation.currentValue.scaleAndAddToRef) {
                            runtimeAnimation.currentValue.scaleAndAddToRef(scale, finalValue);
                        }
                        else {
                            finalValue += runtimeAnimation.currentValue * scale;
                        }
                    }
                }
            }
            target[path] = finalValue;
        }
        target._lateAnimationHolders = {};
    }
    scene._registeredForLateAnimationBindings.reset();
}
/**
 * Initialize all the inter dependecies between the animations and Scene and Bone
 * @param sceneClass defines the scene prototype to use
 * @param boneClass defines the bone prototype to use
 */
function AddAnimationExtensions(sceneClass, boneClass) {
    if (boneClass) {
        boneClass.prototype.copyAnimationRange = function (source, rangeName, frameOffset, rescaleAsRequired = false, skelDimensionsRatio = null) {
            // all animation may be coming from a library skeleton, so may need to create animation
            if (this.animations.length === 0) {
                this.animations.push(new Animation(this.name, "_matrix", source.animations[0].framePerSecond, Animation.ANIMATIONTYPE_MATRIX, 0));
                this.animations[0].setKeys([]);
            }
            // get animation info / verify there is such a range from the source bone
            const sourceRange = source.animations[0].getRange(rangeName);
            if (!sourceRange) {
                return false;
            }
            const from = sourceRange.from;
            const to = sourceRange.to;
            const sourceKeys = source.animations[0].getKeys();
            // rescaling prep
            const sourceBoneLength = source.length;
            const sourceParent = source.getParent();
            const parent = this.getParent();
            const parentScalingReqd = rescaleAsRequired && sourceParent && sourceBoneLength && this.length && sourceBoneLength !== this.length;
            const parentRatio = parentScalingReqd && parent && sourceParent ? parent.length / sourceParent.length : 1;
            const dimensionsScalingReqd = rescaleAsRequired && !parent && skelDimensionsRatio && (skelDimensionsRatio.x !== 1 || skelDimensionsRatio.y !== 1 || skelDimensionsRatio.z !== 1);
            const destKeys = this.animations[0].getKeys();
            // loop vars declaration
            let orig;
            let origTranslation;
            let mat;
            for (let key = 0, nKeys = sourceKeys.length; key < nKeys; key++) {
                orig = sourceKeys[key];
                if (orig.frame >= from && orig.frame <= to) {
                    if (rescaleAsRequired) {
                        mat = orig.value.clone();
                        // scale based on parent ratio, when bone has parent
                        if (parentScalingReqd) {
                            origTranslation = mat.getTranslation();
                            mat.setTranslation(origTranslation.scaleInPlace(parentRatio));
                            // scale based on skeleton dimension ratio when root bone, and value is passed
                        }
                        else if (dimensionsScalingReqd && skelDimensionsRatio) {
                            origTranslation = mat.getTranslation();
                            mat.setTranslation(origTranslation.multiplyInPlace(skelDimensionsRatio));
                            // use original when root bone, and no data for skelDimensionsRatio
                        }
                        else {
                            mat = orig.value;
                        }
                    }
                    else {
                        mat = orig.value;
                    }
                    destKeys.push({ frame: orig.frame + frameOffset, value: mat });
                }
            }
            this.animations[0].createRange(rangeName, from + frameOffset, to + frameOffset);
            return true;
        };
    }
    if (!sceneClass) {
        return;
    }
    sceneClass.prototype._animate = function (customDeltaTime) {
        if (!this.animationsEnabled) {
            return;
        }
        // Getting time
        const now = PrecisionDate.Now;
        if (!this._animationTimeLast) {
            if (this._pendingData.length > 0) {
                return;
            }
            this._animationTimeLast = now;
        }
        this.deltaTime = customDeltaTime !== undefined ? customDeltaTime : this.useConstantAnimationDeltaTime ? 16.0 : (now - this._animationTimeLast) * this.animationTimeScale;
        this._animationTimeLast = now;
        const animatables = this._activeAnimatables;
        if (animatables.length === 0) {
            return;
        }
        this._animationTime += this.deltaTime;
        const animationTime = this._animationTime;
        for (let index = 0; index < animatables.length; index++) {
            const animatable = animatables[index];
            if (!animatable._animate(animationTime) && animatable.disposeOnEnd) {
                index--; // Array was updated
            }
        }
        // Late animation bindings
        ProcessLateAnimationBindings(this);
    };
    sceneClass.prototype.sortActiveAnimatables = function () {
        this._activeAnimatables.sort((a, b) => {
            return a.playOrder - b.playOrder;
        });
    };
    sceneClass.prototype.beginWeightedAnimation = function (target, from, to, weight = 1.0, loop, speedRatio = 1.0, onAnimationEnd, animatable, targetMask, onAnimationLoop, isAdditive = false) {
        const returnedAnimatable = this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, false, targetMask, onAnimationLoop, isAdditive);
        returnedAnimatable.weight = weight;
        return returnedAnimatable;
    };
    sceneClass.prototype.beginAnimation = function (target, from, to, loop, speedRatio = 1.0, onAnimationEnd, animatable, stopCurrent = true, targetMask, onAnimationLoop, isAdditive = false) {
        // get speed speedRatio, to and from, based on the sign and value(s)
        if (speedRatio < 0) {
            const tmp = from;
            from = to;
            to = tmp;
            speedRatio = -speedRatio;
        }
        // if from > to switch speed ratio
        if (from > to) {
            speedRatio = -speedRatio;
        }
        if (stopCurrent) {
            this.stopAnimation(target, undefined, targetMask);
        }
        if (!animatable) {
            animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, undefined, onAnimationLoop, isAdditive);
        }
        const shouldRunTargetAnimations = targetMask ? targetMask(target) : true;
        // Local animations
        if (target.animations && shouldRunTargetAnimations) {
            animatable.appendAnimations(target, target.animations);
        }
        // Children animations
        if (target.getAnimatables) {
            const animatables = target.getAnimatables();
            for (let index = 0; index < animatables.length; index++) {
                this.beginAnimation(animatables[index], from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, onAnimationLoop);
            }
        }
        animatable.reset();
        return animatable;
    };
    sceneClass.prototype.beginHierarchyAnimation = function (target, directDescendantsOnly, from, to, loop, speedRatio = 1.0, onAnimationEnd, animatable, stopCurrent = true, targetMask, onAnimationLoop, isAdditive = false) {
        const children = target.getDescendants(directDescendantsOnly);
        const result = [];
        result.push(this.beginAnimation(target, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, undefined, isAdditive));
        for (const child of children) {
            result.push(this.beginAnimation(child, from, to, loop, speedRatio, onAnimationEnd, animatable, stopCurrent, targetMask, undefined, isAdditive));
        }
        return result;
    };
    sceneClass.prototype.beginDirectAnimation = function (target, animations, from, to, loop, speedRatio = 1.0, onAnimationEnd, onAnimationLoop, isAdditive = false) {
        // get speed speedRatio, to and from, based on the sign and value(s)
        if (speedRatio < 0) {
            const tmp = from;
            from = to;
            to = tmp;
            speedRatio = -speedRatio;
        }
        // if from > to switch speed ratio
        if (from > to) {
            speedRatio = -speedRatio;
        }
        const animatable = new Animatable(this, target, from, to, loop, speedRatio, onAnimationEnd, animations, onAnimationLoop, isAdditive);
        return animatable;
    };
    sceneClass.prototype.beginDirectHierarchyAnimation = function (target, directDescendantsOnly, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive = false) {
        const children = target.getDescendants(directDescendantsOnly);
        const result = [];
        result.push(this.beginDirectAnimation(target, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive));
        for (const child of children) {
            result.push(this.beginDirectAnimation(child, animations, from, to, loop, speedRatio, onAnimationEnd, onAnimationLoop, isAdditive));
        }
        return result;
    };
    sceneClass.prototype.getAnimatableByTarget = function (target) {
        for (let index = 0; index < this._activeAnimatables.length; index++) {
            if (this._activeAnimatables[index].target === target) {
                return this._activeAnimatables[index];
            }
        }
        return null;
    };
    sceneClass.prototype.getAllAnimatablesByTarget = function (target) {
        const result = [];
        for (let index = 0; index < this._activeAnimatables.length; index++) {
            if (this._activeAnimatables[index].target === target) {
                result.push(this._activeAnimatables[index]);
            }
        }
        return result;
    };
    sceneClass.prototype.stopAnimation = function (target, animationName, targetMask) {
        const animatables = this.getAllAnimatablesByTarget(target);
        for (const animatable of animatables) {
            animatable.stop(animationName, targetMask);
        }
    };
    sceneClass.prototype.stopAllAnimations = function () {
        if (this._activeAnimatables) {
            for (let i = 0; i < this._activeAnimatables.length; i++) {
                this._activeAnimatables[i].stop(undefined, undefined, true);
            }
            this._activeAnimatables.length = 0;
        }
        for (const group of this.animationGroups) {
            group.stop();
        }
    };
}

// Connect everything!
AddAnimationExtensions(Scene, Bone);

/**
 * This class defines the direct association between an animation and a target
 */
class TargetedAnimation {
    /**
     * Returns the string "TargetedAnimation"
     * @returns "TargetedAnimation"
     */
    getClassName() {
        return "TargetedAnimation";
    }
    /**
     * Creates a new targeted animation
     * @param parent The animation group to which the animation belongs
     */
    constructor(parent) {
        this.parent = parent;
        /**
         * Gets or sets the unique id of the targeted animation
         */
        this.uniqueId = UniqueIdGenerator.UniqueId;
    }
    /**
     * Serialize the object
     * @returns the JSON object representing the current entity
     */
    serialize() {
        const serializationObject = {};
        serializationObject.animation = this.animation.serialize();
        serializationObject.targetId = this.target.id;
        return serializationObject;
    }
}
/**
 * Use this class to create coordinated animations on multiple targets
 */
class AnimationGroup {
    /**
     * Gets or sets the mask associated with this animation group. This mask is used to filter which objects should be animated.
     */
    get mask() {
        return this._mask;
    }
    set mask(value) {
        if (this._mask === value) {
            return;
        }
        this._mask = value;
        this.syncWithMask(true);
    }
    /**
     * Makes sure that the animations are either played or stopped according to the animation group mask.
     * Note however that the call won't have any effect if the animation group has not been started yet.
     * @param forceUpdate If true, forces to loop over the animatables even if no mask is defined (used internally, you shouldn't need to use it). Default: false.
     */
    syncWithMask(forceUpdate = false) {
        if (!this.mask && !forceUpdate) {
            this._numActiveAnimatables = this._targetedAnimations.length;
            return;
        }
        this._numActiveAnimatables = 0;
        for (let i = 0; i < this._animatables.length; ++i) {
            const animatable = this._animatables[i];
            if (!this.mask || this.mask.disabled || this.mask.retainsTarget(animatable.target.name)) {
                this._numActiveAnimatables++;
                if (animatable.paused) {
                    animatable.restart();
                }
            }
            else {
                if (!animatable.paused) {
                    animatable.pause();
                }
            }
        }
    }
    /**
     * Removes all animations for the targets not retained by the animation group mask.
     * Use this function if you know you won't need those animations anymore and if you want to free memory.
     */
    removeUnmaskedAnimations() {
        if (!this.mask || this.mask.disabled) {
            return;
        }
        // Removes all animatables (in case the animation group has already been started)
        for (let i = 0; i < this._animatables.length; ++i) {
            const animatable = this._animatables[i];
            if (!this.mask.retainsTarget(animatable.target.name)) {
                animatable.stop();
                this._animatables.splice(i, 1);
                --i;
            }
        }
        // Removes the targeted animations
        for (let index = 0; index < this._targetedAnimations.length; index++) {
            const targetedAnimation = this._targetedAnimations[index];
            if (!this.mask.retainsTarget(targetedAnimation.target.name)) {
                this._targetedAnimations.splice(index, 1);
                --index;
            }
        }
    }
    /**
     * Gets or sets the first frame
     */
    get from() {
        return this._from;
    }
    set from(value) {
        if (this._from === value) {
            return;
        }
        this._from = value;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.fromFrame = this._from;
        }
    }
    /**
     * Gets or sets the last frame
     */
    get to() {
        return this._to;
    }
    set to(value) {
        if (this._to === value) {
            return;
        }
        this._to = value;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.toFrame = this._to;
        }
    }
    /**
     * Define if the animations are started
     */
    get isStarted() {
        return this._isStarted;
    }
    /**
     * Gets a value indicating that the current group is playing
     */
    get isPlaying() {
        return this._isStarted && !this._isPaused;
    }
    /**
     * Gets or sets the speed ratio to use for all animations
     */
    get speedRatio() {
        return this._speedRatio;
    }
    /**
     * Gets or sets the speed ratio to use for all animations
     */
    set speedRatio(value) {
        if (this._speedRatio === value) {
            return;
        }
        this._speedRatio = value;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.speedRatio = this._speedRatio;
        }
    }
    /**
     * Gets or sets if all animations should loop or not
     */
    get loopAnimation() {
        return this._loopAnimation;
    }
    set loopAnimation(value) {
        if (this._loopAnimation === value) {
            return;
        }
        this._loopAnimation = value;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.loopAnimation = this._loopAnimation;
        }
    }
    /**
     * Gets or sets if all animations should be evaluated additively
     */
    get isAdditive() {
        return this._isAdditive;
    }
    set isAdditive(value) {
        if (this._isAdditive === value) {
            return;
        }
        this._isAdditive = value;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.isAdditive = this._isAdditive;
        }
    }
    /**
     * Gets or sets the weight to apply to all animations of the group
     */
    get weight() {
        return this._weight;
    }
    set weight(value) {
        if (this._weight === value) {
            return;
        }
        this._weight = value;
        this.setWeightForAllAnimatables(this._weight);
    }
    /**
     * Gets the targeted animations for this animation group
     */
    get targetedAnimations() {
        return this._targetedAnimations;
    }
    /**
     * returning the list of animatables controlled by this animation group.
     */
    get animatables() {
        return this._animatables;
    }
    /**
     * Gets the list of target animations
     */
    get children() {
        return this._targetedAnimations;
    }
    /**
     * Gets or sets the order of play of the animation group (default: 0)
     */
    get playOrder() {
        return this._playOrder;
    }
    set playOrder(value) {
        if (this._playOrder === value) {
            return;
        }
        this._playOrder = value;
        if (this._animatables.length > 0) {
            for (let i = 0; i < this._animatables.length; i++) {
                this._animatables[i].playOrder = this._playOrder;
            }
            this._scene.sortActiveAnimatables();
        }
    }
    /**
     * Allows the animations of the animation group to blend with current running animations
     * Note that a null value means that each animation will use their own existing blending configuration (Animation.enableBlending)
     */
    get enableBlending() {
        return this._enableBlending;
    }
    set enableBlending(value) {
        if (this._enableBlending === value) {
            return;
        }
        this._enableBlending = value;
        if (value !== null) {
            for (let i = 0; i < this._targetedAnimations.length; ++i) {
                this._targetedAnimations[i].animation.enableBlending = value;
            }
        }
    }
    /**
     * Gets or sets the animation blending speed
     * Note that a null value means that each animation will use their own existing blending configuration (Animation.blendingSpeed)
     */
    get blendingSpeed() {
        return this._blendingSpeed;
    }
    set blendingSpeed(value) {
        if (this._blendingSpeed === value) {
            return;
        }
        this._blendingSpeed = value;
        if (value !== null) {
            for (let i = 0; i < this._targetedAnimations.length; ++i) {
                this._targetedAnimations[i].animation.blendingSpeed = value;
            }
        }
    }
    /**
     * Gets the length (in seconds) of the animation group
     * This function assumes that all animations are played at the same framePerSecond speed!
     * Note: you can only call this method after you've added at least one targeted animation!
     * @param from Starting frame range (default is AnimationGroup.from)
     * @param to Ending frame range (default is AnimationGroup.to)
     * @returns The length in seconds
     */
    getLength(from, to) {
        from = from ?? this._from;
        to = to ?? this._to;
        const fps = this.targetedAnimations[0].animation.framePerSecond * this._speedRatio;
        return (to - from) / fps;
    }
    /**
     * Merge the array of animation groups into a new animation group
     * @param animationGroups List of animation groups to merge
     * @param disposeSource If true, animation groups will be disposed after being merged (default: true)
     * @param normalize If true, animation groups will be normalized before being merged, so that all animations have the same "from" and "to" frame (default: false)
     * @param weight Weight for the new animation group. If not provided, it will inherit the weight from the first animation group of the array
     * @returns The new animation group or null if no animation groups were passed
     */
    static MergeAnimationGroups(animationGroups, disposeSource = true, normalize = false, weight) {
        if (animationGroups.length === 0) {
            return null;
        }
        weight = weight ?? animationGroups[0].weight;
        let beginFrame = Number.MAX_VALUE;
        let endFrame = -Number.MAX_VALUE;
        if (normalize) {
            for (const animationGroup of animationGroups) {
                if (animationGroup.from < beginFrame) {
                    beginFrame = animationGroup.from;
                }
                if (animationGroup.to > endFrame) {
                    endFrame = animationGroup.to;
                }
            }
        }
        const mergedAnimationGroup = new AnimationGroup(animationGroups[0].name + "_merged", animationGroups[0]._scene, weight);
        for (const animationGroup of animationGroups) {
            if (normalize) {
                animationGroup.normalize(beginFrame, endFrame);
            }
            for (const targetedAnimation of animationGroup.targetedAnimations) {
                mergedAnimationGroup.addTargetedAnimation(targetedAnimation.animation, targetedAnimation.target);
            }
            if (disposeSource) {
                animationGroup.dispose();
            }
        }
        return mergedAnimationGroup;
    }
    /**
     * Gets the scene the animation group belongs to
     * @returns The scene the animation group belongs to
     */
    getScene() {
        return this._scene;
    }
    /**
     * Instantiates a new Animation Group.
     * This helps managing several animations at once.
     * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/groupAnimations
     * @param name Defines the name of the group
     * @param scene Defines the scene the group belongs to
     * @param weight Defines the weight to use for animations in the group (-1.0 by default, meaning "no weight")
     * @param playOrder Defines the order of play of the animation group (default is 0)
     */
    constructor(
    /** The name of the animation group */
    name, scene = null, weight = -1, playOrder = 0) {
        this.name = name;
        this._targetedAnimations = new Array();
        this._animatables = new Array();
        this._from = Number.MAX_VALUE;
        this._to = -Number.MAX_VALUE;
        this._speedRatio = 1;
        this._loopAnimation = false;
        this._isAdditive = false;
        this._weight = -1;
        this._playOrder = 0;
        this._enableBlending = null;
        this._blendingSpeed = null;
        this._numActiveAnimatables = 0;
        this._shouldStart = true;
        /** @internal */
        this._parentContainer = null;
        /**
         * This observable will notify when one animation have ended
         */
        this.onAnimationEndObservable = new Observable();
        /**
         * Observer raised when one animation loops
         */
        this.onAnimationLoopObservable = new Observable();
        /**
         * Observer raised when all animations have looped
         */
        this.onAnimationGroupLoopObservable = new Observable();
        /**
         * This observable will notify when all animations have ended.
         */
        this.onAnimationGroupEndObservable = new Observable();
        /**
         * This observable will notify when all animations have paused.
         */
        this.onAnimationGroupPauseObservable = new Observable();
        /**
         * This observable will notify when all animations are playing.
         */
        this.onAnimationGroupPlayObservable = new Observable();
        /**
         * Gets or sets an object used to store user defined information for the node
         */
        this.metadata = null;
        this._mask = null;
        this._animationLoopFlags = [];
        this._scene = scene || EngineStore.LastCreatedScene;
        this._weight = weight;
        this._playOrder = playOrder;
        this.uniqueId = this._scene.getUniqueId();
        this._scene.addAnimationGroup(this);
    }
    /**
     * Add an animation (with its target) in the group
     * @param animation defines the animation we want to add
     * @param target defines the target of the animation
     * @returns the TargetedAnimation object
     */
    addTargetedAnimation(animation, target) {
        const targetedAnimation = new TargetedAnimation(this);
        targetedAnimation.animation = animation;
        targetedAnimation.target = target;
        const keys = animation.getKeys();
        if (this._from > keys[0].frame) {
            this._from = keys[0].frame;
        }
        if (this._to < keys[keys.length - 1].frame) {
            this._to = keys[keys.length - 1].frame;
        }
        if (this._enableBlending !== null) {
            animation.enableBlending = this._enableBlending;
        }
        if (this._blendingSpeed !== null) {
            animation.blendingSpeed = this._blendingSpeed;
        }
        this._targetedAnimations.push(targetedAnimation);
        this._shouldStart = true;
        return targetedAnimation;
    }
    /**
     * Remove an animation from the group
     * @param animation defines the animation we want to remove
     */
    removeTargetedAnimation(animation) {
        for (let index = this._targetedAnimations.length - 1; index > -1; index--) {
            const targetedAnimation = this._targetedAnimations[index];
            if (targetedAnimation.animation === animation) {
                this._targetedAnimations.splice(index, 1);
            }
        }
    }
    /**
     * This function will normalize every animation in the group to make sure they all go from beginFrame to endFrame
     * It can add constant keys at begin or end
     * @param beginFrame defines the new begin frame for all animations or the smallest begin frame of all animations if null (defaults to null)
     * @param endFrame defines the new end frame for all animations or the largest end frame of all animations if null (defaults to null)
     * @returns the animation group
     */
    normalize(beginFrame = null, endFrame = null) {
        if (beginFrame == null) {
            beginFrame = this._from;
        }
        if (endFrame == null) {
            endFrame = this._to;
        }
        for (let index = 0; index < this._targetedAnimations.length; index++) {
            const targetedAnimation = this._targetedAnimations[index];
            const keys = targetedAnimation.animation.getKeys();
            const startKey = keys[0];
            const endKey = keys[keys.length - 1];
            if (startKey.frame > beginFrame) {
                const newKey = {
                    frame: beginFrame,
                    value: startKey.value,
                    inTangent: startKey.inTangent,
                    outTangent: startKey.outTangent,
                    interpolation: startKey.interpolation,
                };
                keys.splice(0, 0, newKey);
            }
            if (endKey.frame < endFrame) {
                const newKey = {
                    frame: endFrame,
                    value: endKey.value,
                    inTangent: endKey.inTangent,
                    outTangent: endKey.outTangent,
                    interpolation: endKey.interpolation,
                };
                keys.push(newKey);
            }
        }
        this._from = beginFrame;
        this._to = endFrame;
        return this;
    }
    _processLoop(animatable, targetedAnimation, index) {
        animatable.onAnimationLoop = () => {
            this.onAnimationLoopObservable.notifyObservers(targetedAnimation);
            if (this._animationLoopFlags[index]) {
                return;
            }
            this._animationLoopFlags[index] = true;
            this._animationLoopCount++;
            if (this._animationLoopCount === this._numActiveAnimatables) {
                this.onAnimationGroupLoopObservable.notifyObservers(this);
                this._animationLoopCount = 0;
                this._animationLoopFlags.length = 0;
            }
        };
    }
    /**
     * Start all animations on given targets
     * @param loop defines if animations must loop
     * @param speedRatio defines the ratio to apply to animation speed (1 by default)
     * @param from defines the from key (optional)
     * @param to defines the to key (optional)
     * @param isAdditive defines the additive state for the resulting animatables (optional)
     * @returns the current animation group
     */
    start(loop = false, speedRatio = 1, from, to, isAdditive) {
        if (this._isStarted || this._targetedAnimations.length === 0) {
            return this;
        }
        this._loopAnimation = loop;
        this._shouldStart = false;
        this._animationLoopCount = 0;
        this._animationLoopFlags.length = 0;
        for (let index = 0; index < this._targetedAnimations.length; index++) {
            const targetedAnimation = this._targetedAnimations[index];
            const animatable = this._scene.beginDirectAnimation(targetedAnimation.target, [targetedAnimation.animation], from !== undefined ? from : this._from, to !== undefined ? to : this._to, loop, speedRatio, undefined, undefined, isAdditive !== undefined ? isAdditive : this._isAdditive);
            animatable.weight = this._weight;
            animatable.playOrder = this._playOrder;
            animatable.onAnimationEnd = () => {
                this.onAnimationEndObservable.notifyObservers(targetedAnimation);
                this._checkAnimationGroupEnded(animatable);
            };
            this._processLoop(animatable, targetedAnimation, index);
            this._animatables.push(animatable);
        }
        this.syncWithMask();
        this._scene.sortActiveAnimatables();
        this._speedRatio = speedRatio;
        this._isStarted = true;
        this._isPaused = false;
        this.onAnimationGroupPlayObservable.notifyObservers(this);
        return this;
    }
    /**
     * Pause all animations
     * @returns the animation group
     */
    pause() {
        if (!this._isStarted) {
            return this;
        }
        this._isPaused = true;
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.pause();
        }
        this.onAnimationGroupPauseObservable.notifyObservers(this);
        return this;
    }
    /**
     * Play all animations to initial state
     * This function will start() the animations if they were not started or will restart() them if they were paused
     * @param loop defines if animations must loop
     * @returns the animation group
     */
    play(loop) {
        // only if there are animatable available
        if (this.isStarted && this._animatables.length && !this._shouldStart) {
            if (loop !== undefined) {
                this.loopAnimation = loop;
            }
            this.restart();
        }
        else {
            this.stop();
            this.start(loop, this._speedRatio);
        }
        return this;
    }
    /**
     * Reset all animations to initial state
     * @returns the animation group
     */
    reset() {
        if (!this._isStarted) {
            this.play();
            this.goToFrame(0);
            this.stop(true);
            return this;
        }
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.reset();
        }
        return this;
    }
    /**
     * Restart animations from after pausing it
     * @returns the animation group
     */
    restart() {
        if (!this._isStarted) {
            return this;
        }
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.restart();
        }
        this.syncWithMask();
        this._isPaused = false;
        this.onAnimationGroupPlayObservable.notifyObservers(this);
        return this;
    }
    /**
     * Stop all animations
     * @param skipOnAnimationEnd defines if the system should not raise onAnimationEnd. Default is false
     * @returns the animation group
     */
    stop(skipOnAnimationEnd = false) {
        if (!this._isStarted) {
            return this;
        }
        const list = this._animatables.slice();
        for (let index = 0; index < list.length; index++) {
            list[index].stop(undefined, undefined, true, skipOnAnimationEnd);
        }
        // We will take care of removing all stopped animatables
        let curIndex = 0;
        for (let index = 0; index < this._scene._activeAnimatables.length; index++) {
            const animatable = this._scene._activeAnimatables[index];
            if (animatable._runtimeAnimations.length > 0) {
                this._scene._activeAnimatables[curIndex++] = animatable;
            }
            else if (skipOnAnimationEnd) {
                // We normally rely on the onAnimationEnd callback (assigned in the start function) to be notified when an animatable
                // ends and should be removed from the active animatables array. However, if the animatable is stopped with the skipOnAnimationEnd
                // flag set to true, then we need to explicitly remove it from the active animatables array.
                this._checkAnimationGroupEnded(animatable, skipOnAnimationEnd);
            }
        }
        this._scene._activeAnimatables.length = curIndex;
        this._isStarted = false;
        return this;
    }
    /**
     * Set animation weight for all animatables
     *
     * @since 6.12.4
     *  You can pass the weight to the AnimationGroup constructor, or use the weight property to set it after the group has been created,
     *  making it easier to define the overall animation weight than calling setWeightForAllAnimatables() after the animation group has been started
     * @param weight defines the weight to use
     * @returns the animationGroup
     * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-weights
     */
    setWeightForAllAnimatables(weight) {
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.weight = weight;
        }
        return this;
    }
    /**
     * Synchronize and normalize all animatables with a source animatable
     * @param root defines the root animatable to synchronize with (null to stop synchronizing)
     * @returns the animationGroup
     * @see https://doc.babylonjs.com/features/featuresDeepDive/animation/advanced_animations#animation-weights
     */
    syncAllAnimationsWith(root) {
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.syncWith(root);
        }
        return this;
    }
    /**
     * Goes to a specific frame in this animation group. Note that the animation group must be in playing or paused status
     * @param frame the frame number to go to
     * @param useWeight defines whether the animation weight should be applied to the image to be jumped to (false by default)
     * @returns the animationGroup
     */
    goToFrame(frame, useWeight = false) {
        if (!this._isStarted) {
            return this;
        }
        for (let index = 0; index < this._animatables.length; index++) {
            const animatable = this._animatables[index];
            animatable.goToFrame(frame, useWeight);
        }
        return this;
    }
    /**
     * Helper to get the current frame. This will return 0 if the AnimationGroup is not running, and it might return wrong results if multiple animations are running in different frames.
     * @returns current animation frame.
     */
    getCurrentFrame() {
        return this.animatables[0]?.masterFrame || 0;
    }
    /**
     * Dispose all associated resources
     */
    dispose() {
        if (this.isStarted) {
            this.stop();
        }
        this._targetedAnimations.length = 0;
        this._animatables.length = 0;
        // Remove from scene
        const index = this._scene.animationGroups.indexOf(this);
        if (index > -1) {
            this._scene.animationGroups.splice(index, 1);
        }
        if (this._parentContainer) {
            const index = this._parentContainer.animationGroups.indexOf(this);
            if (index > -1) {
                this._parentContainer.animationGroups.splice(index, 1);
            }
            this._parentContainer = null;
        }
        this.onAnimationEndObservable.clear();
        this.onAnimationGroupEndObservable.clear();
        this.onAnimationGroupPauseObservable.clear();
        this.onAnimationGroupPlayObservable.clear();
        this.onAnimationLoopObservable.clear();
        this.onAnimationGroupLoopObservable.clear();
    }
    _checkAnimationGroupEnded(animatable, skipOnAnimationEnd = false) {
        // animatable should be taken out of the array
        const idx = this._animatables.indexOf(animatable);
        if (idx > -1) {
            this._animatables.splice(idx, 1);
        }
        // all animatables were removed? animation group ended!
        if (this._animatables.length === this._targetedAnimations.length - this._numActiveAnimatables) {
            this._isStarted = false;
            if (!skipOnAnimationEnd) {
                this.onAnimationGroupEndObservable.notifyObservers(this);
            }
            this._animatables.length = 0;
        }
    }
    /**
     * Clone the current animation group and returns a copy
     * @param newName defines the name of the new group
     * @param targetConverter defines an optional function used to convert current animation targets to new ones
     * @param cloneAnimations defines if the animations should be cloned or referenced
     * @returns the new animation group
     */
    clone(newName, targetConverter, cloneAnimations = false) {
        const newGroup = new AnimationGroup(newName || this.name, this._scene, this._weight, this._playOrder);
        newGroup._from = this.from;
        newGroup._to = this.to;
        newGroup._speedRatio = this.speedRatio;
        newGroup._loopAnimation = this.loopAnimation;
        newGroup._isAdditive = this.isAdditive;
        newGroup._enableBlending = this.enableBlending;
        newGroup._blendingSpeed = this.blendingSpeed;
        newGroup.metadata = this.metadata;
        newGroup.mask = this.mask;
        for (const targetAnimation of this._targetedAnimations) {
            newGroup.addTargetedAnimation(cloneAnimations ? targetAnimation.animation.clone() : targetAnimation.animation, targetConverter ? targetConverter(targetAnimation.target) : targetAnimation.target);
        }
        return newGroup;
    }
    /**
     * Serializes the animationGroup to an object
     * @returns Serialized object
     */
    serialize() {
        const serializationObject = {};
        serializationObject.name = this.name;
        serializationObject.from = this.from;
        serializationObject.to = this.to;
        serializationObject.speedRatio = this.speedRatio;
        serializationObject.loopAnimation = this.loopAnimation;
        serializationObject.isAdditive = this.isAdditive;
        serializationObject.weight = this.weight;
        serializationObject.playOrder = this.playOrder;
        serializationObject.enableBlending = this.enableBlending;
        serializationObject.blendingSpeed = this.blendingSpeed;
        serializationObject.targetedAnimations = [];
        for (let targetedAnimationIndex = 0; targetedAnimationIndex < this.targetedAnimations.length; targetedAnimationIndex++) {
            const targetedAnimation = this.targetedAnimations[targetedAnimationIndex];
            serializationObject.targetedAnimations[targetedAnimationIndex] = targetedAnimation.serialize();
        }
        if (Tags && Tags.HasTags(this)) {
            serializationObject.tags = Tags.GetTags(this);
        }
        // Metadata
        if (this.metadata) {
            serializationObject.metadata = this.metadata;
        }
        return serializationObject;
    }
    // Statics
    /**
     * Returns a new AnimationGroup object parsed from the source provided.
     * @param parsedAnimationGroup defines the source
     * @param scene defines the scene that will receive the animationGroup
     * @param nodeMap a map of node.id to node in this scene, to accelerate node lookup
     * @returns a new AnimationGroup
     */
    static Parse(parsedAnimationGroup, scene, nodeMap) {
        const animationGroup = new AnimationGroup(parsedAnimationGroup.name, scene, parsedAnimationGroup.weight, parsedAnimationGroup.playOrder);
        for (let i = 0; i < parsedAnimationGroup.targetedAnimations.length; i++) {
            const targetedAnimation = parsedAnimationGroup.targetedAnimations[i];
            const animation = Animation.Parse(targetedAnimation.animation);
            const id = targetedAnimation.targetId;
            if (targetedAnimation.animation.property === "influence") {
                // morph target animation
                const morphTarget = scene.getMorphTargetById(id);
                if (morphTarget) {
                    animationGroup.addTargetedAnimation(animation, morphTarget);
                }
            }
            else {
                const targetNode = nodeMap ? nodeMap.get(id) : scene.getNodeById(id);
                if (targetNode != null) {
                    animationGroup.addTargetedAnimation(animation, targetNode);
                }
            }
        }
        if (Tags) {
            Tags.AddTagsTo(animationGroup, parsedAnimationGroup.tags);
        }
        if (parsedAnimationGroup.from !== null && parsedAnimationGroup.to !== null) {
            animationGroup.normalize(parsedAnimationGroup.from, parsedAnimationGroup.to);
        }
        if (parsedAnimationGroup.speedRatio !== undefined) {
            animationGroup._speedRatio = parsedAnimationGroup.speedRatio;
        }
        if (parsedAnimationGroup.loopAnimation !== undefined) {
            animationGroup._loopAnimation = parsedAnimationGroup.loopAnimation;
        }
        if (parsedAnimationGroup.isAdditive !== undefined) {
            animationGroup._isAdditive = parsedAnimationGroup.isAdditive;
        }
        if (parsedAnimationGroup.weight !== undefined) {
            animationGroup._weight = parsedAnimationGroup.weight;
        }
        if (parsedAnimationGroup.playOrder !== undefined) {
            animationGroup._playOrder = parsedAnimationGroup.playOrder;
        }
        if (parsedAnimationGroup.enableBlending !== undefined) {
            animationGroup._enableBlending = parsedAnimationGroup.enableBlending;
        }
        if (parsedAnimationGroup.blendingSpeed !== undefined) {
            animationGroup._blendingSpeed = parsedAnimationGroup.blendingSpeed;
        }
        if (parsedAnimationGroup.metadata !== undefined) {
            animationGroup.metadata = parsedAnimationGroup.metadata;
        }
        return animationGroup;
    }
    /** @internal */
    static MakeAnimationAdditive(sourceAnimationGroup, referenceFrameOrOptions, range, cloneOriginal = false, clonedName) {
        let options;
        if (typeof referenceFrameOrOptions === "object") {
            options = referenceFrameOrOptions;
        }
        else {
            options = {
                referenceFrame: referenceFrameOrOptions,
                range: range,
                cloneOriginalAnimationGroup: cloneOriginal,
                clonedAnimationName: clonedName,
            };
        }
        let animationGroup = sourceAnimationGroup;
        if (options.cloneOriginalAnimationGroup) {
            animationGroup = sourceAnimationGroup.clone(options.clonedAnimationGroupName || animationGroup.name);
        }
        const targetedAnimations = animationGroup.targetedAnimations;
        for (let index = 0; index < targetedAnimations.length; index++) {
            const targetedAnimation = targetedAnimations[index];
            targetedAnimation.animation = Animation.MakeAnimationAdditive(targetedAnimation.animation, options);
        }
        animationGroup.isAdditive = true;
        if (options.clipKeys) {
            // We need to recalculate the from/to frames for the animation group because some keys may have been removed
            let from = Number.MAX_VALUE;
            let to = -Number.MAX_VALUE;
            const targetedAnimations = animationGroup.targetedAnimations;
            for (let index = 0; index < targetedAnimations.length; index++) {
                const targetedAnimation = targetedAnimations[index];
                const animation = targetedAnimation.animation;
                const keys = animation.getKeys();
                if (from > keys[0].frame) {
                    from = keys[0].frame;
                }
                if (to < keys[keys.length - 1].frame) {
                    to = keys[keys.length - 1].frame;
                }
            }
            animationGroup._from = from;
            animationGroup._to = to;
        }
        return animationGroup;
    }
    /**
     * Creates a new animation, keeping only the keys that are inside a given key range
     * @param sourceAnimationGroup defines the animation group on which to operate
     * @param fromKey defines the lower bound of the range
     * @param toKey defines the upper bound of the range
     * @param name defines the name of the new animation group. If not provided, use the same name as animationGroup
     * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
     * @returns a new animation group stripped from all the keys outside the given range
     */
    static ClipKeys(sourceAnimationGroup, fromKey, toKey, name, dontCloneAnimations) {
        const animationGroup = sourceAnimationGroup.clone(name || sourceAnimationGroup.name);
        return AnimationGroup.ClipKeysInPlace(animationGroup, fromKey, toKey, dontCloneAnimations);
    }
    /**
     * Updates an existing animation, keeping only the keys that are inside a given key range
     * @param animationGroup defines the animation group on which to operate
     * @param fromKey defines the lower bound of the range
     * @param toKey defines the upper bound of the range
     * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
     * @returns the animationGroup stripped from all the keys outside the given range
     */
    static ClipKeysInPlace(animationGroup, fromKey, toKey, dontCloneAnimations) {
        return AnimationGroup.ClipInPlace(animationGroup, fromKey, toKey, dontCloneAnimations, false);
    }
    /**
     * Creates a new animation, keeping only the frames that are inside a given frame range
     * @param sourceAnimationGroup defines the animation group on which to operate
     * @param fromFrame defines the lower bound of the range
     * @param toFrame defines the upper bound of the range
     * @param name defines the name of the new animation group. If not provided, use the same name as animationGroup
     * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the frames. Default is false, so animations will be cloned
     * @returns a new animation group stripped from all the frames outside the given range
     */
    static ClipFrames(sourceAnimationGroup, fromFrame, toFrame, name, dontCloneAnimations) {
        const animationGroup = sourceAnimationGroup.clone(name || sourceAnimationGroup.name);
        return AnimationGroup.ClipFramesInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations);
    }
    /**
     * Updates an existing animation, keeping only the frames that are inside a given frame range
     * @param animationGroup defines the animation group on which to operate
     * @param fromFrame defines the lower bound of the range
     * @param toFrame defines the upper bound of the range
     * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the frames. Default is false, so animations will be cloned
     * @returns the animationGroup stripped from all the frames outside the given range
     */
    static ClipFramesInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations) {
        return AnimationGroup.ClipInPlace(animationGroup, fromFrame, toFrame, dontCloneAnimations, true);
    }
    /**
     * Updates an existing animation, keeping only the keys that are inside a given key or frame range
     * @param animationGroup defines the animation group on which to operate
     * @param start defines the lower bound of the range
     * @param end defines the upper bound of the range
     * @param dontCloneAnimations defines whether or not the animations should be cloned before clipping the keys. Default is false, so animations will be cloned
     * @param useFrame defines if the range is defined by frame numbers or key indices (default is false which means use key indices)
     * @returns the animationGroup stripped from all the keys outside the given range
     */
    static ClipInPlace(animationGroup, start, end, dontCloneAnimations, useFrame = false) {
        let from = Number.MAX_VALUE;
        let to = -Number.MAX_VALUE;
        const targetedAnimations = animationGroup.targetedAnimations;
        for (let index = 0; index < targetedAnimations.length; index++) {
            const targetedAnimation = targetedAnimations[index];
            const animation = dontCloneAnimations ? targetedAnimation.animation : targetedAnimation.animation.clone();
            if (useFrame) {
                // Make sure we have keys corresponding to the bounds of the frame range
                animation.createKeyForFrame(start);
                animation.createKeyForFrame(end);
            }
            const keys = animation.getKeys();
            const newKeys = [];
            let startFrame = Number.MAX_VALUE;
            for (let k = 0; k < keys.length; k++) {
                const key = keys[k];
                if ((!useFrame && k >= start && k <= end) || (useFrame && key.frame >= start && key.frame <= end)) {
                    const newKey = {
                        frame: key.frame,
                        value: key.value.clone ? key.value.clone() : key.value,
                        inTangent: key.inTangent,
                        outTangent: key.outTangent,
                        interpolation: key.interpolation,
                        lockedTangent: key.lockedTangent,
                    };
                    if (startFrame === Number.MAX_VALUE) {
                        startFrame = newKey.frame;
                    }
                    newKey.frame -= startFrame;
                    newKeys.push(newKey);
                }
            }
            if (newKeys.length === 0) {
                targetedAnimations.splice(index, 1);
                index--;
                continue;
            }
            if (from > newKeys[0].frame) {
                from = newKeys[0].frame;
            }
            if (to < newKeys[newKeys.length - 1].frame) {
                to = newKeys[newKeys.length - 1].frame;
            }
            animation.setKeys(newKeys, true);
            targetedAnimation.animation = animation; // in case the animation has been cloned
        }
        animationGroup._from = from;
        animationGroup._to = to;
        return animationGroup;
    }
    /**
     * Returns the string "AnimationGroup"
     * @returns "AnimationGroup"
     */
    getClassName() {
        return "AnimationGroup";
    }
    /**
     * Creates a detailed string about the object
     * @param fullDetails defines if the output string will support multiple levels of logging within scene loading
     * @returns a string representing the object
     */
    toString(fullDetails) {
        let ret = "Name: " + this.name;
        ret += ", type: " + this.getClassName();
        if (fullDetails) {
            ret += ", from: " + this._from;
            ret += ", to: " + this._to;
            ret += ", isStarted: " + this._isStarted;
            ret += ", speedRatio: " + this._speedRatio;
            ret += ", targetedAnimations length: " + this._targetedAnimations.length;
            ret += ", animatables length: " + this._animatables;
        }
        return ret;
    }
}

export { AnimationGroup, TargetedAnimation };
//# sourceMappingURL=animationGroup-Ddd9egDm.esm.js.map