Skip to content

⬅️ Back to Table of Contents

📄 AnimationAction.js

📊 Analysis Summary

Metric Count
🔧 Functions 30
🧱 Classes 1
📦 Imports 8
📊 Variables & Constants 39

📚 Table of Contents

🛠️ File Location:

📂 src/animation/AnimationAction.js

📦 Imports

Name Source
WrapAroundEnding ../constants.js
ZeroCurvatureEnding ../constants.js
ZeroSlopeEnding ../constants.js
LoopPingPong ../constants.js
LoopOnce ../constants.js
LoopRepeat ../constants.js
NormalAnimationBlendMode ../constants.js
AdditiveAnimationBlendMode ../constants.js

Variables & Constants

Name Type Kind Value Exported
tracks any let/var clip.tracks
nTracks any let/var tracks.length
interpolants any[] let/var new Array( nTracks )
interpolantSettings { endingStart: number; endingEnd: num... let/var { endingStart: ZeroCurvatureEnding, endingEnd: ZeroCurvatureEnding }
fadeInDuration any let/var this._clip.duration
fadeOutDuration any let/var fadeOutAction._clip.duration
startEndRatio number let/var fadeOutDuration / fadeInDuration
endStartRatio number let/var fadeInDuration / fadeOutDuration
weightInterpolant any let/var this._weightInterpolant
mixer AnimationMixer let/var this._mixer
now any let/var mixer.time
timeScale number let/var this.timeScale
interpolant any let/var this._timeScaleInterpolant
times any let/var interpolant.parameterPositions
values any let/var interpolant.sampleValues
timeScaleInterpolant any let/var this._timeScaleInterpolant
startTime number let/var this._startTime
timeRunning number let/var ( time - startTime ) * timeDirection
interpolants any[] let/var this._interpolants
propertyMixers any[] let/var this._propertyBindings
weight number let/var 0
interpolant any let/var this._weightInterpolant
interpolantValue any let/var interpolant.evaluate( time )[ 0 ]
timeScale number let/var 0
interpolant any let/var this._timeScaleInterpolant
interpolantValue any let/var interpolant.evaluate( time )[ 0 ]
duration any let/var this._clip.duration
loop number let/var this.loop
time any let/var this.time + deltaTime
loopCount number let/var this._loopCount
pingPong boolean let/var ( loop === LoopPingPong )
pending number let/var this.repetitions - loopCount
atStart boolean let/var deltaTime < 0
settings { endingStart: number; endingEnd: num... let/var this._interpolantSettings
mixer AnimationMixer let/var this._mixer
now any let/var mixer.time
interpolant any let/var this._weightInterpolant
times any let/var interpolant.parameterPositions
values any let/var interpolant.sampleValues

Functions

AnimationAction.play(): AnimationAction

JSDoc:

/**
     * Starts the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */

Returns: AnimationAction

Calls:

  • this._mixer._activateAction
Code
play() {

        this._mixer._activateAction( this );

        return this;

    }

AnimationAction.stop(): AnimationAction

JSDoc:

/**
     * Stops the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */

Returns: AnimationAction

Calls:

  • this._mixer._deactivateAction
  • this.reset
Code
stop() {

        this._mixer._deactivateAction( this );

        return this.reset();

    }

AnimationAction.reset(): AnimationAction

JSDoc:

/**
     * Resets the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */

Returns: AnimationAction

Calls:

  • this.stopFading().stopWarping
Code
reset() {

        this.paused = false;
        this.enabled = true;

        this.time = 0; // restart clip
        this._loopCount = - 1;// forget previous loops
        this._startTime = null;// forget scheduling

        return this.stopFading().stopWarping();

    }

AnimationAction.isRunning(): boolean

JSDoc:

/**
     * Returns `true` if the animation is running.
     *
     * @return {boolean} Whether the animation is running or not.
     */

Returns: boolean

Calls:

  • this._mixer._isActiveAction
Code
isRunning() {

        return this.enabled && ! this.paused && this.timeScale !== 0 &&
            this._startTime === null && this._mixer._isActiveAction( this );

    }

AnimationAction.isScheduled(): boolean

JSDoc:

/**
     * Returns `true` when {@link AnimationAction#play} has been called.
     *
     * @return {boolean} Whether the animation is scheduled or not.
     */

Returns: boolean

Calls:

  • this._mixer._isActiveAction
Code
isScheduled() {

        return this._mixer._isActiveAction( this );

    }

AnimationAction.startAt(time: number): AnimationAction

JSDoc:

/**
     * Defines the time when the animation should start.
     *
     * @param {number} time - The start time in seconds.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • time number

Returns: AnimationAction

Code
startAt( time ) {

        this._startTime = time;

        return this;

    }

AnimationAction.setLoop(mode: number, repetitions: number): AnimationAction

JSDoc:

/**
     * Configures the loop settings for this action.
     *
     * @param {(LoopRepeat|LoopOnce|LoopPingPong)} mode - The loop mode.
     * @param {number} repetitions - The number of repetitions.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • mode number
  • repetitions number

Returns: AnimationAction

Code
setLoop( mode, repetitions ) {

        this.loop = mode;
        this.repetitions = repetitions;

        return this;

    }

AnimationAction.setEffectiveWeight(weight: number): AnimationAction

JSDoc:

/**
     * Sets the effective weight of this action.
     *
     * An action has no effect and thus an effective weight of zero when the
     * action is disabled.
     *
     * @param {number} weight - The weight to set.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • weight number

Returns: AnimationAction

Calls:

  • this.stopFading

Internal Comments:

// note: same logic as when updated at runtime (x4)

Code
setEffectiveWeight( weight ) {

        this.weight = weight;

        // note: same logic as when updated at runtime
        this._effectiveWeight = this.enabled ? weight : 0;

        return this.stopFading();

    }

AnimationAction.getEffectiveWeight(): number

JSDoc:

/**
     * Returns the effective weight of this action.
     *
     * @return {number} The effective weight.
     */

Returns: number

Code
getEffectiveWeight() {

        return this._effectiveWeight;

    }

AnimationAction.fadeIn(duration: number): AnimationAction

JSDoc:

/**
     * Fades the animation in by increasing its weight gradually from `0` to `1`,
     * within the passed time interval.
     *
     * @param {number} duration - The duration of the fade.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • duration number

Returns: AnimationAction

Calls:

  • this._scheduleFading
Code
fadeIn( duration ) {

        return this._scheduleFading( duration, 0, 1 );

    }

AnimationAction.fadeOut(duration: number): AnimationAction

JSDoc:

/**
     * Fades the animation out by decreasing its weight gradually from `1` to `0`,
     * within the passed time interval.
     *
     * @param {number} duration - The duration of the fade.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • duration number

Returns: AnimationAction

Calls:

  • this._scheduleFading
Code
fadeOut( duration ) {

        return this._scheduleFading( duration, 1, 0 );

    }

AnimationAction.crossFadeFrom(fadeOutAction: AnimationAction, duration: number, warp: boolean): AnimationAction

JSDoc:

/**
     * Causes this action to fade in and the given action to fade out,
     * within the passed time interval.
     *
     * @param {AnimationAction} fadeOutAction - The animation action to fade out.
     * @param {number} duration - The duration of the fade.
     * @param {boolean} [warp=false] - Whether warping should be used or not.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • fadeOutAction AnimationAction
  • duration number
  • warp boolean

Returns: AnimationAction

Calls:

  • fadeOutAction.fadeOut
  • this.fadeIn
  • fadeOutAction.warp
  • this.warp
Code
crossFadeFrom( fadeOutAction, duration, warp = false ) {

        fadeOutAction.fadeOut( duration );
        this.fadeIn( duration );

        if ( warp === true ) {

            const fadeInDuration = this._clip.duration,
                fadeOutDuration = fadeOutAction._clip.duration,

                startEndRatio = fadeOutDuration / fadeInDuration,
                endStartRatio = fadeInDuration / fadeOutDuration;

            fadeOutAction.warp( 1.0, startEndRatio, duration );
            this.warp( endStartRatio, 1.0, duration );

        }

        return this;

    }

AnimationAction.crossFadeTo(fadeInAction: AnimationAction, duration: number, warp: boolean): AnimationAction

JSDoc:

/**
     * Causes this action to fade out and the given action to fade in,
     * within the passed time interval.
     *
     * @param {AnimationAction} fadeInAction - The animation action to fade in.
     * @param {number} duration - The duration of the fade.
     * @param {boolean} [warp=false] - Whether warping should be used or not.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • fadeInAction AnimationAction
  • duration number
  • warp boolean

Returns: AnimationAction

Calls:

  • fadeInAction.crossFadeFrom
Code
crossFadeTo( fadeInAction, duration, warp = false ) {

        return fadeInAction.crossFadeFrom( this, duration, warp );

    }

AnimationAction.stopFading(): AnimationAction

JSDoc:

/**
     * Stops any fading which is applied to this action.
     *
     * @return {AnimationAction} A reference to this animation action.
     */

Returns: AnimationAction

Calls:

  • this._mixer._takeBackControlInterpolant
Code
stopFading() {

        const weightInterpolant = this._weightInterpolant;

        if ( weightInterpolant !== null ) {

            this._weightInterpolant = null;
            this._mixer._takeBackControlInterpolant( weightInterpolant );

        }

        return this;

    }

AnimationAction.setEffectiveTimeScale(timeScale: number): AnimationAction

JSDoc:

/**
     * Sets the effective time scale of this action.
     *
     * An action has no effect and thus an effective time scale of zero when the
     * action is paused.
     *
     * @param {number} timeScale - The time scale to set.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • timeScale number

Returns: AnimationAction

Calls:

  • this.stopWarping
Code
setEffectiveTimeScale( timeScale ) {

        this.timeScale = timeScale;
        this._effectiveTimeScale = this.paused ? 0 : timeScale;

        return this.stopWarping();

    }

AnimationAction.getEffectiveTimeScale(): number

JSDoc:

/**
     * Returns the effective time scale of this action.
     *
     * @return {number} The effective time scale.
     */

Returns: number

Code
getEffectiveTimeScale() {

        return this._effectiveTimeScale;

    }

AnimationAction.setDuration(duration: number): AnimationAction

JSDoc:

/**
     * Sets the duration for a single loop of this action.
     *
     * @param {number} duration - The duration to set.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • duration number

Returns: AnimationAction

Calls:

  • this.stopWarping
Code
setDuration( duration ) {

        this.timeScale = this._clip.duration / duration;

        return this.stopWarping();

    }

AnimationAction.syncWith(action: AnimationAction): AnimationAction

JSDoc:

/**
     * Synchronizes this action with the passed other action.
     *
     * @param {AnimationAction} action - The action to sync with.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • action AnimationAction

Returns: AnimationAction

Calls:

  • this.stopWarping
Code
syncWith( action ) {

        this.time = action.time;
        this.timeScale = action.timeScale;

        return this.stopWarping();

    }

AnimationAction.halt(duration: number): AnimationAction

JSDoc:

/**
     * Decelerates this animation's speed to `0` within the passed time interval.
     *
     * @param {number} duration - The duration.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • duration number

Returns: AnimationAction

Calls:

  • this.warp
Code
halt( duration ) {

        return this.warp( this._effectiveTimeScale, 0, duration );

    }

AnimationAction.warp(startTimeScale: number, endTimeScale: number, duration: number): AnimationAction

JSDoc:

/**
     * Changes the playback speed, within the passed time interval, by modifying
     * {@link AnimationAction#timeScale} gradually from `startTimeScale` to
     * `endTimeScale`.
     *
     * @param {number} startTimeScale - The start time scale.
     * @param {number} endTimeScale - The end time scale.
     * @param {number} duration - The duration.
     * @return {AnimationAction} A reference to this animation action.
     */

Parameters:

  • startTimeScale number
  • endTimeScale number
  • duration number

Returns: AnimationAction

Calls:

  • mixer._lendControlInterpolant
Code
warp( startTimeScale, endTimeScale, duration ) {

        const mixer = this._mixer,
            now = mixer.time,
            timeScale = this.timeScale;

        let interpolant = this._timeScaleInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._timeScaleInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        times[ 1 ] = now + duration;

        values[ 0 ] = startTimeScale / timeScale;
        values[ 1 ] = endTimeScale / timeScale;

        return this;

    }

AnimationAction.stopWarping(): AnimationAction

JSDoc:

/**
     * Stops any scheduled warping which is applied to this action.
     *
     * @return {AnimationAction} A reference to this animation action.
     */

Returns: AnimationAction

Calls:

  • this._mixer._takeBackControlInterpolant
Code
stopWarping() {

        const timeScaleInterpolant = this._timeScaleInterpolant;

        if ( timeScaleInterpolant !== null ) {

            this._timeScaleInterpolant = null;
            this._mixer._takeBackControlInterpolant( timeScaleInterpolant );

        }

        return this;

    }

AnimationAction.getMixer(): AnimationMixer

JSDoc:

/**
     * Returns the animation mixer of this animation action.
     *
     * @return {AnimationMixer} The animation mixer.
     */

Returns: AnimationMixer

Code
getMixer() {

        return this._mixer;

    }

AnimationAction.getClip(): AnimationClip

JSDoc:

/**
     * Returns the animation clip of this animation action.
     *
     * @return {AnimationClip} The animation clip.
     */

Returns: AnimationClip

Code
getClip() {

        return this._clip;

    }

AnimationAction.getRoot(): Object3D

JSDoc:

/**
     * Returns the root object of this animation action.
     *
     * @return {Object3D} The root object.
     */

Returns: Object3D

Code
getRoot() {

        return this._localRoot || this._mixer._root;

    }

AnimationAction._update(time: any, deltaTime: any, timeDirection: any, accuIndex: any): void

Parameters:

  • time any
  • deltaTime any
  • timeDirection any
  • accuIndex any

Returns: void

Calls:

  • this._updateWeight
  • this._updateTimeScale
  • this._updateTime
  • interpolants[ j ].evaluate
  • propertyMixers[ j ].accumulateAdditive
  • propertyMixers[ j ].accumulate

Internal Comments:

// called by the mixer
// call ._updateWeight() to update ._effectiveWeight (x4)
// check for scheduled start of action (x2)
// apply time scale and advance time (x3)
// note: _updateTime may disable the action resulting in (x2)
// an effective weight of 0 (x2)

Code
_update( time, deltaTime, timeDirection, accuIndex ) {

        // called by the mixer

        if ( ! this.enabled ) {

            // call ._updateWeight() to update ._effectiveWeight

            this._updateWeight( time );
            return;

        }

        const startTime = this._startTime;

        if ( startTime !== null ) {

            // check for scheduled start of action

            const timeRunning = ( time - startTime ) * timeDirection;
            if ( timeRunning < 0 || timeDirection === 0 ) {

                deltaTime = 0;

            } else {


                this._startTime = null; // unschedule
                deltaTime = timeDirection * timeRunning;

            }

        }

        // apply time scale and advance time

        deltaTime *= this._updateTimeScale( time );
        const clipTime = this._updateTime( deltaTime );

        // note: _updateTime may disable the action resulting in
        // an effective weight of 0

        const weight = this._updateWeight( time );

        if ( weight > 0 ) {

            const interpolants = this._interpolants;
            const propertyMixers = this._propertyBindings;

            switch ( this.blendMode ) {

                case AdditiveAnimationBlendMode:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulateAdditive( weight );

                    }

                    break;

                case NormalAnimationBlendMode:
                default:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulate( accuIndex, weight );

                    }

            }

        }

    }

AnimationAction._updateWeight(time: any): number

Parameters:

  • time any

Returns: number

Calls:

  • interpolant.evaluate
  • this.stopFading

Internal Comments:

// faded out, disable (x4)

Code
_updateWeight( time ) {

        let weight = 0;

        if ( this.enabled ) {

            weight = this.weight;
            const interpolant = this._weightInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                weight *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopFading();

                    if ( interpolantValue === 0 ) {

                        // faded out, disable
                        this.enabled = false;

                    }

                }

            }

        }

        this._effectiveWeight = weight;
        return weight;

    }

AnimationAction._updateTimeScale(time: any): number

Parameters:

  • time any

Returns: number

Calls:

  • interpolant.evaluate
  • this.stopWarping

Internal Comments:

// motion has halted, pause (x4)
// warp done - apply final time scale (x4)

Code
_updateTimeScale( time ) {

        let timeScale = 0;

        if ( ! this.paused ) {

            timeScale = this.timeScale;

            const interpolant = this._timeScaleInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                timeScale *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopWarping();

                    if ( timeScale === 0 ) {

                        // motion has halted, pause
                        this.paused = true;

                    } else {

                        // warp done - apply final time scale
                        this.timeScale = timeScale;

                    }

                }

            }

        }

        this._effectiveTimeScale = timeScale;
        return timeScale;

    }

AnimationAction._updateTime(deltaTime: any): any

Parameters:

  • deltaTime any

Returns: any

Calls:

  • this._setEndings
  • this._mixer.dispatchEvent
  • Math.floor
  • Math.abs

Internal Comments:

// just started (x5)
// when looping in reverse direction, the initial (x4)
// transition through zero counts as a repetition, (x4)
// so leave loopCount at -1 (x4)
// wrap around (x2)
// have to stop (switch state, clamp time, fire event)
// keep running
// entering the last round (x2)
// invert time for the "pong round"

Code
_updateTime( deltaTime ) {

        const duration = this._clip.duration;
        const loop = this.loop;

        let time = this.time + deltaTime;
        let loopCount = this._loopCount;

        const pingPong = ( loop === LoopPingPong );

        if ( deltaTime === 0 ) {

            if ( loopCount === - 1 ) return time;

            return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;

        }

        if ( loop === LoopOnce ) {

            if ( loopCount === - 1 ) {

                // just started

                this._loopCount = 0;
                this._setEndings( true, true, false );

            }

            handle_stop: {

                if ( time >= duration ) {

                    time = duration;

                } else if ( time < 0 ) {

                    time = 0;

                } else {

                    this.time = time;

                    break handle_stop;

                }

                if ( this.clampWhenFinished ) this.paused = true;
                else this.enabled = false;

                this.time = time;

                this._mixer.dispatchEvent( {
                    type: 'finished', action: this,
                    direction: deltaTime < 0 ? - 1 : 1
                } );

            }

        } else { // repetitive Repeat or PingPong

            if ( loopCount === - 1 ) {

                // just started

                if ( deltaTime >= 0 ) {

                    loopCount = 0;

                    this._setEndings( true, this.repetitions === 0, pingPong );

                } else {

                    // when looping in reverse direction, the initial
                    // transition through zero counts as a repetition,
                    // so leave loopCount at -1

                    this._setEndings( this.repetitions === 0, true, pingPong );

                }

            }

            if ( time >= duration || time < 0 ) {

                // wrap around

                const loopDelta = Math.floor( time / duration ); // signed
                time -= duration * loopDelta;

                loopCount += Math.abs( loopDelta );

                const pending = this.repetitions - loopCount;

                if ( pending <= 0 ) {

                    // have to stop (switch state, clamp time, fire event)

                    if ( this.clampWhenFinished ) this.paused = true;
                    else this.enabled = false;

                    time = deltaTime > 0 ? duration : 0;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'finished', action: this,
                        direction: deltaTime > 0 ? 1 : - 1
                    } );

                } else {

                    // keep running

                    if ( pending === 1 ) {

                        // entering the last round

                        const atStart = deltaTime < 0;
                        this._setEndings( atStart, ! atStart, pingPong );

                    } else {

                        this._setEndings( false, false, pingPong );

                    }

                    this._loopCount = loopCount;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'loop', action: this, loopDelta: loopDelta
                    } );

                }

            } else {

                this.time = time;

            }

            if ( pingPong && ( loopCount & 1 ) === 1 ) {

                // invert time for the "pong round"

                return duration - time;

            }

        }

        return time;

    }

AnimationAction._setEndings(atStart: any, atEnd: any, pingPong: any): void

Parameters:

  • atStart any
  • atEnd any
  • pingPong any

Returns: void

Internal Comments:

// assuming for LoopOnce atStart == atEnd == true

Code
_setEndings( atStart, atEnd, pingPong ) {

        const settings = this._interpolantSettings;

        if ( pingPong ) {

            settings.endingStart = ZeroSlopeEnding;
            settings.endingEnd = ZeroSlopeEnding;

        } else {

            // assuming for LoopOnce atStart == atEnd == true

            if ( atStart ) {

                settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingStart = WrapAroundEnding;

            }

            if ( atEnd ) {

                settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingEnd   = WrapAroundEnding;

            }

        }

    }

AnimationAction._scheduleFading(duration: any, weightNow: any, weightThen: any): this

Parameters:

  • duration any
  • weightNow any
  • weightThen any

Returns: this

Calls:

  • mixer._lendControlInterpolant
Code
_scheduleFading( duration, weightNow, weightThen ) {

        const mixer = this._mixer, now = mixer.time;
        let interpolant = this._weightInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._weightInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        values[ 0 ] = weightNow;
        times[ 1 ] = now + duration;
        values[ 1 ] = weightThen;

        return this;

    }

Classes

AnimationAction

Class Code
class AnimationAction {

    /**
     * Constructs a new animation action.
     *
     * @param {AnimationMixer} mixer - The mixer that is controlled by this action.
     * @param {AnimationClip} clip - The animation clip that holds the actual keyframes.
     * @param {?Object3D} [localRoot=null] - The root object on which this action is performed.
     * @param {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)} [blendMode] - The blend mode.
     */
    constructor( mixer, clip, localRoot = null, blendMode = clip.blendMode ) {

        this._mixer = mixer;
        this._clip = clip;
        this._localRoot = localRoot;

        /**
         * Defines how the animation is blended/combined when two or more animations
         * are simultaneously played.
         *
         * @type {(NormalAnimationBlendMode|AdditiveAnimationBlendMode)}
         */
        this.blendMode = blendMode;

        const tracks = clip.tracks,
            nTracks = tracks.length,
            interpolants = new Array( nTracks );

        const interpolantSettings = {
            endingStart: ZeroCurvatureEnding,
            endingEnd: ZeroCurvatureEnding
        };

        for ( let i = 0; i !== nTracks; ++ i ) {

            const interpolant = tracks[ i ].createInterpolant( null );
            interpolants[ i ] = interpolant;
            interpolant.settings = interpolantSettings;

        }

        this._interpolantSettings = interpolantSettings;

        this._interpolants = interpolants; // bound by the mixer

        // inside: PropertyMixer (managed by the mixer)
        this._propertyBindings = new Array( nTracks );

        this._cacheIndex = null; // for the memory manager
        this._byClipCacheIndex = null; // for the memory manager

        this._timeScaleInterpolant = null;
        this._weightInterpolant = null;

        /**
         * The loop mode, set via {@link AnimationAction#setLoop}.
         *
         * @type {(LoopRepeat|LoopOnce|LoopPingPong)}
         * @default LoopRepeat
         */
        this.loop = LoopRepeat;
        this._loopCount = - 1;

        // global mixer time when the action is to be started
        // it's set back to 'null' upon start of the action
        this._startTime = null;

        /**
         * The local time of this action (in seconds, starting with `0`).
         *
         * The value gets clamped or wrapped to `[0,clip.duration]` (according to the
         * loop state).
         *
         * @type {number}
         * @default Infinity
         */
        this.time = 0;

        /**
         * Scaling factor for the {@link AnimationAction#time}. A value of `0` causes the
         * animation to pause. Negative values cause the animation to play backwards.
         *
         * @type {number}
         * @default 1
         */
        this.timeScale = 1;
        this._effectiveTimeScale = 1;

        /**
         * The degree of influence of this action (in the interval `[0, 1]`). Values
         * between `0` (no impact) and `1` (full impact) can be used to blend between
         * several actions.
         *
         * @type {number}
         * @default 1
         */
        this.weight = 1;
        this._effectiveWeight = 1;

        /**
         * The number of repetitions of the performed clip over the course of this action.
         * Can be set via {@link AnimationAction#setLoop}.
         *
         * Setting this number has no effect if {@link AnimationAction#loop} is set to
         * `THREE:LoopOnce`.
         *
         * @type {number}
         * @default Infinity
         */
        this.repetitions = Infinity;

        /**
         * If set to `true`, the playback of the action is paused.
         *
         * @type {boolean}
         * @default false
         */
        this.paused = false;

        /**
         * If set to `false`, the action is disabled so it has no impact.
         *
         * When the action is re-enabled, the animation continues from its current
         * time (setting `enabled` to `false` doesn't reset the action).
         *
         * @type {boolean}
         * @default true
         */
        this.enabled = true;

        /**
         * If set to true the animation will automatically be paused on its last frame.
         *
         * If set to false, {@link AnimationAction#enabled} will automatically be switched
         * to `false` when the last loop of the action has finished, so that this action has
         * no further impact.
         *
         * Note: This member has no impact if the action is interrupted (it
         * has only an effect if its last loop has really finished).
         *
         * @type {boolean}
         * @default false
         */
        this.clampWhenFinished = false;

        /**
         * Enables smooth interpolation without separate clips for start, loop and end.
         *
         * @type {boolean}
         * @default true
         */
        this.zeroSlopeAtStart = true;

        /**
         * Enables smooth interpolation without separate clips for start, loop and end.
         *
         * @type {boolean}
         * @default true
         */
        this.zeroSlopeAtEnd = true;

    }

    /**
     * Starts the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */
    play() {

        this._mixer._activateAction( this );

        return this;

    }

    /**
     * Stops the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */
    stop() {

        this._mixer._deactivateAction( this );

        return this.reset();

    }

    /**
     * Resets the playback of the animation.
     *
     * @return {AnimationAction} A reference to this animation action.
     */
    reset() {

        this.paused = false;
        this.enabled = true;

        this.time = 0; // restart clip
        this._loopCount = - 1;// forget previous loops
        this._startTime = null;// forget scheduling

        return this.stopFading().stopWarping();

    }

    /**
     * Returns `true` if the animation is running.
     *
     * @return {boolean} Whether the animation is running or not.
     */
    isRunning() {

        return this.enabled && ! this.paused && this.timeScale !== 0 &&
            this._startTime === null && this._mixer._isActiveAction( this );

    }

    /**
     * Returns `true` when {@link AnimationAction#play} has been called.
     *
     * @return {boolean} Whether the animation is scheduled or not.
     */
    isScheduled() {

        return this._mixer._isActiveAction( this );

    }

    /**
     * Defines the time when the animation should start.
     *
     * @param {number} time - The start time in seconds.
     * @return {AnimationAction} A reference to this animation action.
     */
    startAt( time ) {

        this._startTime = time;

        return this;

    }

    /**
     * Configures the loop settings for this action.
     *
     * @param {(LoopRepeat|LoopOnce|LoopPingPong)} mode - The loop mode.
     * @param {number} repetitions - The number of repetitions.
     * @return {AnimationAction} A reference to this animation action.
     */
    setLoop( mode, repetitions ) {

        this.loop = mode;
        this.repetitions = repetitions;

        return this;

    }

    /**
     * Sets the effective weight of this action.
     *
     * An action has no effect and thus an effective weight of zero when the
     * action is disabled.
     *
     * @param {number} weight - The weight to set.
     * @return {AnimationAction} A reference to this animation action.
     */
    setEffectiveWeight( weight ) {

        this.weight = weight;

        // note: same logic as when updated at runtime
        this._effectiveWeight = this.enabled ? weight : 0;

        return this.stopFading();

    }

    /**
     * Returns the effective weight of this action.
     *
     * @return {number} The effective weight.
     */
    getEffectiveWeight() {

        return this._effectiveWeight;

    }

    /**
     * Fades the animation in by increasing its weight gradually from `0` to `1`,
     * within the passed time interval.
     *
     * @param {number} duration - The duration of the fade.
     * @return {AnimationAction} A reference to this animation action.
     */
    fadeIn( duration ) {

        return this._scheduleFading( duration, 0, 1 );

    }

    /**
     * Fades the animation out by decreasing its weight gradually from `1` to `0`,
     * within the passed time interval.
     *
     * @param {number} duration - The duration of the fade.
     * @return {AnimationAction} A reference to this animation action.
     */
    fadeOut( duration ) {

        return this._scheduleFading( duration, 1, 0 );

    }

    /**
     * Causes this action to fade in and the given action to fade out,
     * within the passed time interval.
     *
     * @param {AnimationAction} fadeOutAction - The animation action to fade out.
     * @param {number} duration - The duration of the fade.
     * @param {boolean} [warp=false] - Whether warping should be used or not.
     * @return {AnimationAction} A reference to this animation action.
     */
    crossFadeFrom( fadeOutAction, duration, warp = false ) {

        fadeOutAction.fadeOut( duration );
        this.fadeIn( duration );

        if ( warp === true ) {

            const fadeInDuration = this._clip.duration,
                fadeOutDuration = fadeOutAction._clip.duration,

                startEndRatio = fadeOutDuration / fadeInDuration,
                endStartRatio = fadeInDuration / fadeOutDuration;

            fadeOutAction.warp( 1.0, startEndRatio, duration );
            this.warp( endStartRatio, 1.0, duration );

        }

        return this;

    }

    /**
     * Causes this action to fade out and the given action to fade in,
     * within the passed time interval.
     *
     * @param {AnimationAction} fadeInAction - The animation action to fade in.
     * @param {number} duration - The duration of the fade.
     * @param {boolean} [warp=false] - Whether warping should be used or not.
     * @return {AnimationAction} A reference to this animation action.
     */
    crossFadeTo( fadeInAction, duration, warp = false ) {

        return fadeInAction.crossFadeFrom( this, duration, warp );

    }

    /**
     * Stops any fading which is applied to this action.
     *
     * @return {AnimationAction} A reference to this animation action.
     */
    stopFading() {

        const weightInterpolant = this._weightInterpolant;

        if ( weightInterpolant !== null ) {

            this._weightInterpolant = null;
            this._mixer._takeBackControlInterpolant( weightInterpolant );

        }

        return this;

    }

    /**
     * Sets the effective time scale of this action.
     *
     * An action has no effect and thus an effective time scale of zero when the
     * action is paused.
     *
     * @param {number} timeScale - The time scale to set.
     * @return {AnimationAction} A reference to this animation action.
     */
    setEffectiveTimeScale( timeScale ) {

        this.timeScale = timeScale;
        this._effectiveTimeScale = this.paused ? 0 : timeScale;

        return this.stopWarping();

    }

    /**
     * Returns the effective time scale of this action.
     *
     * @return {number} The effective time scale.
     */
    getEffectiveTimeScale() {

        return this._effectiveTimeScale;

    }

    /**
     * Sets the duration for a single loop of this action.
     *
     * @param {number} duration - The duration to set.
     * @return {AnimationAction} A reference to this animation action.
     */
    setDuration( duration ) {

        this.timeScale = this._clip.duration / duration;

        return this.stopWarping();

    }

    /**
     * Synchronizes this action with the passed other action.
     *
     * @param {AnimationAction} action - The action to sync with.
     * @return {AnimationAction} A reference to this animation action.
     */
    syncWith( action ) {

        this.time = action.time;
        this.timeScale = action.timeScale;

        return this.stopWarping();

    }

    /**
     * Decelerates this animation's speed to `0` within the passed time interval.
     *
     * @param {number} duration - The duration.
     * @return {AnimationAction} A reference to this animation action.
     */
    halt( duration ) {

        return this.warp( this._effectiveTimeScale, 0, duration );

    }

    /**
     * Changes the playback speed, within the passed time interval, by modifying
     * {@link AnimationAction#timeScale} gradually from `startTimeScale` to
     * `endTimeScale`.
     *
     * @param {number} startTimeScale - The start time scale.
     * @param {number} endTimeScale - The end time scale.
     * @param {number} duration - The duration.
     * @return {AnimationAction} A reference to this animation action.
     */
    warp( startTimeScale, endTimeScale, duration ) {

        const mixer = this._mixer,
            now = mixer.time,
            timeScale = this.timeScale;

        let interpolant = this._timeScaleInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._timeScaleInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        times[ 1 ] = now + duration;

        values[ 0 ] = startTimeScale / timeScale;
        values[ 1 ] = endTimeScale / timeScale;

        return this;

    }

    /**
     * Stops any scheduled warping which is applied to this action.
     *
     * @return {AnimationAction} A reference to this animation action.
     */
    stopWarping() {

        const timeScaleInterpolant = this._timeScaleInterpolant;

        if ( timeScaleInterpolant !== null ) {

            this._timeScaleInterpolant = null;
            this._mixer._takeBackControlInterpolant( timeScaleInterpolant );

        }

        return this;

    }

    /**
     * Returns the animation mixer of this animation action.
     *
     * @return {AnimationMixer} The animation mixer.
     */
    getMixer() {

        return this._mixer;

    }

    /**
     * Returns the animation clip of this animation action.
     *
     * @return {AnimationClip} The animation clip.
     */
    getClip() {

        return this._clip;

    }

    /**
     * Returns the root object of this animation action.
     *
     * @return {Object3D} The root object.
     */
    getRoot() {

        return this._localRoot || this._mixer._root;

    }

    // Interna

    _update( time, deltaTime, timeDirection, accuIndex ) {

        // called by the mixer

        if ( ! this.enabled ) {

            // call ._updateWeight() to update ._effectiveWeight

            this._updateWeight( time );
            return;

        }

        const startTime = this._startTime;

        if ( startTime !== null ) {

            // check for scheduled start of action

            const timeRunning = ( time - startTime ) * timeDirection;
            if ( timeRunning < 0 || timeDirection === 0 ) {

                deltaTime = 0;

            } else {


                this._startTime = null; // unschedule
                deltaTime = timeDirection * timeRunning;

            }

        }

        // apply time scale and advance time

        deltaTime *= this._updateTimeScale( time );
        const clipTime = this._updateTime( deltaTime );

        // note: _updateTime may disable the action resulting in
        // an effective weight of 0

        const weight = this._updateWeight( time );

        if ( weight > 0 ) {

            const interpolants = this._interpolants;
            const propertyMixers = this._propertyBindings;

            switch ( this.blendMode ) {

                case AdditiveAnimationBlendMode:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulateAdditive( weight );

                    }

                    break;

                case NormalAnimationBlendMode:
                default:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulate( accuIndex, weight );

                    }

            }

        }

    }

    _updateWeight( time ) {

        let weight = 0;

        if ( this.enabled ) {

            weight = this.weight;
            const interpolant = this._weightInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                weight *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopFading();

                    if ( interpolantValue === 0 ) {

                        // faded out, disable
                        this.enabled = false;

                    }

                }

            }

        }

        this._effectiveWeight = weight;
        return weight;

    }

    _updateTimeScale( time ) {

        let timeScale = 0;

        if ( ! this.paused ) {

            timeScale = this.timeScale;

            const interpolant = this._timeScaleInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                timeScale *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopWarping();

                    if ( timeScale === 0 ) {

                        // motion has halted, pause
                        this.paused = true;

                    } else {

                        // warp done - apply final time scale
                        this.timeScale = timeScale;

                    }

                }

            }

        }

        this._effectiveTimeScale = timeScale;
        return timeScale;

    }

    _updateTime( deltaTime ) {

        const duration = this._clip.duration;
        const loop = this.loop;

        let time = this.time + deltaTime;
        let loopCount = this._loopCount;

        const pingPong = ( loop === LoopPingPong );

        if ( deltaTime === 0 ) {

            if ( loopCount === - 1 ) return time;

            return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;

        }

        if ( loop === LoopOnce ) {

            if ( loopCount === - 1 ) {

                // just started

                this._loopCount = 0;
                this._setEndings( true, true, false );

            }

            handle_stop: {

                if ( time >= duration ) {

                    time = duration;

                } else if ( time < 0 ) {

                    time = 0;

                } else {

                    this.time = time;

                    break handle_stop;

                }

                if ( this.clampWhenFinished ) this.paused = true;
                else this.enabled = false;

                this.time = time;

                this._mixer.dispatchEvent( {
                    type: 'finished', action: this,
                    direction: deltaTime < 0 ? - 1 : 1
                } );

            }

        } else { // repetitive Repeat or PingPong

            if ( loopCount === - 1 ) {

                // just started

                if ( deltaTime >= 0 ) {

                    loopCount = 0;

                    this._setEndings( true, this.repetitions === 0, pingPong );

                } else {

                    // when looping in reverse direction, the initial
                    // transition through zero counts as a repetition,
                    // so leave loopCount at -1

                    this._setEndings( this.repetitions === 0, true, pingPong );

                }

            }

            if ( time >= duration || time < 0 ) {

                // wrap around

                const loopDelta = Math.floor( time / duration ); // signed
                time -= duration * loopDelta;

                loopCount += Math.abs( loopDelta );

                const pending = this.repetitions - loopCount;

                if ( pending <= 0 ) {

                    // have to stop (switch state, clamp time, fire event)

                    if ( this.clampWhenFinished ) this.paused = true;
                    else this.enabled = false;

                    time = deltaTime > 0 ? duration : 0;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'finished', action: this,
                        direction: deltaTime > 0 ? 1 : - 1
                    } );

                } else {

                    // keep running

                    if ( pending === 1 ) {

                        // entering the last round

                        const atStart = deltaTime < 0;
                        this._setEndings( atStart, ! atStart, pingPong );

                    } else {

                        this._setEndings( false, false, pingPong );

                    }

                    this._loopCount = loopCount;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'loop', action: this, loopDelta: loopDelta
                    } );

                }

            } else {

                this.time = time;

            }

            if ( pingPong && ( loopCount & 1 ) === 1 ) {

                // invert time for the "pong round"

                return duration - time;

            }

        }

        return time;

    }

    _setEndings( atStart, atEnd, pingPong ) {

        const settings = this._interpolantSettings;

        if ( pingPong ) {

            settings.endingStart = ZeroSlopeEnding;
            settings.endingEnd = ZeroSlopeEnding;

        } else {

            // assuming for LoopOnce atStart == atEnd == true

            if ( atStart ) {

                settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingStart = WrapAroundEnding;

            }

            if ( atEnd ) {

                settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingEnd   = WrapAroundEnding;

            }

        }

    }

    _scheduleFading( duration, weightNow, weightThen ) {

        const mixer = this._mixer, now = mixer.time;
        let interpolant = this._weightInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._weightInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        values[ 0 ] = weightNow;
        times[ 1 ] = now + duration;
        values[ 1 ] = weightThen;

        return this;

    }

}

Methods

play(): AnimationAction
Code
play() {

        this._mixer._activateAction( this );

        return this;

    }
stop(): AnimationAction
Code
stop() {

        this._mixer._deactivateAction( this );

        return this.reset();

    }
reset(): AnimationAction
Code
reset() {

        this.paused = false;
        this.enabled = true;

        this.time = 0; // restart clip
        this._loopCount = - 1;// forget previous loops
        this._startTime = null;// forget scheduling

        return this.stopFading().stopWarping();

    }
isRunning(): boolean
Code
isRunning() {

        return this.enabled && ! this.paused && this.timeScale !== 0 &&
            this._startTime === null && this._mixer._isActiveAction( this );

    }
isScheduled(): boolean
Code
isScheduled() {

        return this._mixer._isActiveAction( this );

    }
startAt(time: number): AnimationAction
Code
startAt( time ) {

        this._startTime = time;

        return this;

    }
setLoop(mode: number, repetitions: number): AnimationAction
Code
setLoop( mode, repetitions ) {

        this.loop = mode;
        this.repetitions = repetitions;

        return this;

    }
setEffectiveWeight(weight: number): AnimationAction
Code
setEffectiveWeight( weight ) {

        this.weight = weight;

        // note: same logic as when updated at runtime
        this._effectiveWeight = this.enabled ? weight : 0;

        return this.stopFading();

    }
getEffectiveWeight(): number
Code
getEffectiveWeight() {

        return this._effectiveWeight;

    }
fadeIn(duration: number): AnimationAction
Code
fadeIn( duration ) {

        return this._scheduleFading( duration, 0, 1 );

    }
fadeOut(duration: number): AnimationAction
Code
fadeOut( duration ) {

        return this._scheduleFading( duration, 1, 0 );

    }
crossFadeFrom(fadeOutAction: AnimationAction, duration: number, warp: boolean): AnimationAction
Code
crossFadeFrom( fadeOutAction, duration, warp = false ) {

        fadeOutAction.fadeOut( duration );
        this.fadeIn( duration );

        if ( warp === true ) {

            const fadeInDuration = this._clip.duration,
                fadeOutDuration = fadeOutAction._clip.duration,

                startEndRatio = fadeOutDuration / fadeInDuration,
                endStartRatio = fadeInDuration / fadeOutDuration;

            fadeOutAction.warp( 1.0, startEndRatio, duration );
            this.warp( endStartRatio, 1.0, duration );

        }

        return this;

    }
crossFadeTo(fadeInAction: AnimationAction, duration: number, warp: boolean): AnimationAction
Code
crossFadeTo( fadeInAction, duration, warp = false ) {

        return fadeInAction.crossFadeFrom( this, duration, warp );

    }
stopFading(): AnimationAction
Code
stopFading() {

        const weightInterpolant = this._weightInterpolant;

        if ( weightInterpolant !== null ) {

            this._weightInterpolant = null;
            this._mixer._takeBackControlInterpolant( weightInterpolant );

        }

        return this;

    }
setEffectiveTimeScale(timeScale: number): AnimationAction
Code
setEffectiveTimeScale( timeScale ) {

        this.timeScale = timeScale;
        this._effectiveTimeScale = this.paused ? 0 : timeScale;

        return this.stopWarping();

    }
getEffectiveTimeScale(): number
Code
getEffectiveTimeScale() {

        return this._effectiveTimeScale;

    }
setDuration(duration: number): AnimationAction
Code
setDuration( duration ) {

        this.timeScale = this._clip.duration / duration;

        return this.stopWarping();

    }
syncWith(action: AnimationAction): AnimationAction
Code
syncWith( action ) {

        this.time = action.time;
        this.timeScale = action.timeScale;

        return this.stopWarping();

    }
halt(duration: number): AnimationAction
Code
halt( duration ) {

        return this.warp( this._effectiveTimeScale, 0, duration );

    }
warp(startTimeScale: number, endTimeScale: number, duration: number): AnimationAction
Code
warp( startTimeScale, endTimeScale, duration ) {

        const mixer = this._mixer,
            now = mixer.time,
            timeScale = this.timeScale;

        let interpolant = this._timeScaleInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._timeScaleInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        times[ 1 ] = now + duration;

        values[ 0 ] = startTimeScale / timeScale;
        values[ 1 ] = endTimeScale / timeScale;

        return this;

    }
stopWarping(): AnimationAction
Code
stopWarping() {

        const timeScaleInterpolant = this._timeScaleInterpolant;

        if ( timeScaleInterpolant !== null ) {

            this._timeScaleInterpolant = null;
            this._mixer._takeBackControlInterpolant( timeScaleInterpolant );

        }

        return this;

    }
getMixer(): AnimationMixer
Code
getMixer() {

        return this._mixer;

    }
getClip(): AnimationClip
Code
getClip() {

        return this._clip;

    }
getRoot(): Object3D
Code
getRoot() {

        return this._localRoot || this._mixer._root;

    }
_update(time: any, deltaTime: any, timeDirection: any, accuIndex: any): void
Code
_update( time, deltaTime, timeDirection, accuIndex ) {

        // called by the mixer

        if ( ! this.enabled ) {

            // call ._updateWeight() to update ._effectiveWeight

            this._updateWeight( time );
            return;

        }

        const startTime = this._startTime;

        if ( startTime !== null ) {

            // check for scheduled start of action

            const timeRunning = ( time - startTime ) * timeDirection;
            if ( timeRunning < 0 || timeDirection === 0 ) {

                deltaTime = 0;

            } else {


                this._startTime = null; // unschedule
                deltaTime = timeDirection * timeRunning;

            }

        }

        // apply time scale and advance time

        deltaTime *= this._updateTimeScale( time );
        const clipTime = this._updateTime( deltaTime );

        // note: _updateTime may disable the action resulting in
        // an effective weight of 0

        const weight = this._updateWeight( time );

        if ( weight > 0 ) {

            const interpolants = this._interpolants;
            const propertyMixers = this._propertyBindings;

            switch ( this.blendMode ) {

                case AdditiveAnimationBlendMode:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulateAdditive( weight );

                    }

                    break;

                case NormalAnimationBlendMode:
                default:

                    for ( let j = 0, m = interpolants.length; j !== m; ++ j ) {

                        interpolants[ j ].evaluate( clipTime );
                        propertyMixers[ j ].accumulate( accuIndex, weight );

                    }

            }

        }

    }
_updateWeight(time: any): number
Code
_updateWeight( time ) {

        let weight = 0;

        if ( this.enabled ) {

            weight = this.weight;
            const interpolant = this._weightInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                weight *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopFading();

                    if ( interpolantValue === 0 ) {

                        // faded out, disable
                        this.enabled = false;

                    }

                }

            }

        }

        this._effectiveWeight = weight;
        return weight;

    }
_updateTimeScale(time: any): number
Code
_updateTimeScale( time ) {

        let timeScale = 0;

        if ( ! this.paused ) {

            timeScale = this.timeScale;

            const interpolant = this._timeScaleInterpolant;

            if ( interpolant !== null ) {

                const interpolantValue = interpolant.evaluate( time )[ 0 ];

                timeScale *= interpolantValue;

                if ( time > interpolant.parameterPositions[ 1 ] ) {

                    this.stopWarping();

                    if ( timeScale === 0 ) {

                        // motion has halted, pause
                        this.paused = true;

                    } else {

                        // warp done - apply final time scale
                        this.timeScale = timeScale;

                    }

                }

            }

        }

        this._effectiveTimeScale = timeScale;
        return timeScale;

    }
_updateTime(deltaTime: any): any
Code
_updateTime( deltaTime ) {

        const duration = this._clip.duration;
        const loop = this.loop;

        let time = this.time + deltaTime;
        let loopCount = this._loopCount;

        const pingPong = ( loop === LoopPingPong );

        if ( deltaTime === 0 ) {

            if ( loopCount === - 1 ) return time;

            return ( pingPong && ( loopCount & 1 ) === 1 ) ? duration - time : time;

        }

        if ( loop === LoopOnce ) {

            if ( loopCount === - 1 ) {

                // just started

                this._loopCount = 0;
                this._setEndings( true, true, false );

            }

            handle_stop: {

                if ( time >= duration ) {

                    time = duration;

                } else if ( time < 0 ) {

                    time = 0;

                } else {

                    this.time = time;

                    break handle_stop;

                }

                if ( this.clampWhenFinished ) this.paused = true;
                else this.enabled = false;

                this.time = time;

                this._mixer.dispatchEvent( {
                    type: 'finished', action: this,
                    direction: deltaTime < 0 ? - 1 : 1
                } );

            }

        } else { // repetitive Repeat or PingPong

            if ( loopCount === - 1 ) {

                // just started

                if ( deltaTime >= 0 ) {

                    loopCount = 0;

                    this._setEndings( true, this.repetitions === 0, pingPong );

                } else {

                    // when looping in reverse direction, the initial
                    // transition through zero counts as a repetition,
                    // so leave loopCount at -1

                    this._setEndings( this.repetitions === 0, true, pingPong );

                }

            }

            if ( time >= duration || time < 0 ) {

                // wrap around

                const loopDelta = Math.floor( time / duration ); // signed
                time -= duration * loopDelta;

                loopCount += Math.abs( loopDelta );

                const pending = this.repetitions - loopCount;

                if ( pending <= 0 ) {

                    // have to stop (switch state, clamp time, fire event)

                    if ( this.clampWhenFinished ) this.paused = true;
                    else this.enabled = false;

                    time = deltaTime > 0 ? duration : 0;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'finished', action: this,
                        direction: deltaTime > 0 ? 1 : - 1
                    } );

                } else {

                    // keep running

                    if ( pending === 1 ) {

                        // entering the last round

                        const atStart = deltaTime < 0;
                        this._setEndings( atStart, ! atStart, pingPong );

                    } else {

                        this._setEndings( false, false, pingPong );

                    }

                    this._loopCount = loopCount;

                    this.time = time;

                    this._mixer.dispatchEvent( {
                        type: 'loop', action: this, loopDelta: loopDelta
                    } );

                }

            } else {

                this.time = time;

            }

            if ( pingPong && ( loopCount & 1 ) === 1 ) {

                // invert time for the "pong round"

                return duration - time;

            }

        }

        return time;

    }
_setEndings(atStart: any, atEnd: any, pingPong: any): void
Code
_setEndings( atStart, atEnd, pingPong ) {

        const settings = this._interpolantSettings;

        if ( pingPong ) {

            settings.endingStart = ZeroSlopeEnding;
            settings.endingEnd = ZeroSlopeEnding;

        } else {

            // assuming for LoopOnce atStart == atEnd == true

            if ( atStart ) {

                settings.endingStart = this.zeroSlopeAtStart ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingStart = WrapAroundEnding;

            }

            if ( atEnd ) {

                settings.endingEnd = this.zeroSlopeAtEnd ? ZeroSlopeEnding : ZeroCurvatureEnding;

            } else {

                settings.endingEnd   = WrapAroundEnding;

            }

        }

    }
_scheduleFading(duration: any, weightNow: any, weightThen: any): this
Code
_scheduleFading( duration, weightNow, weightThen ) {

        const mixer = this._mixer, now = mixer.time;
        let interpolant = this._weightInterpolant;

        if ( interpolant === null ) {

            interpolant = mixer._lendControlInterpolant();
            this._weightInterpolant = interpolant;

        }

        const times = interpolant.parameterPositions,
            values = interpolant.sampleValues;

        times[ 0 ] = now;
        values[ 0 ] = weightNow;
        times[ 1 ] = now + duration;
        values[ 1 ] = weightThen;

        return this;

    }