📄 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
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
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
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
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
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
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
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:
Code
AnimationAction.getEffectiveWeight(): number
¶
JSDoc:
Returns: number
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
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
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
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
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
AnimationAction.getEffectiveTimeScale(): number
¶
JSDoc:
/**
* Returns the effective time scale of this action.
*
* @return {number} The effective time scale.
*/
Returns: number
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
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
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
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
AnimationAction.getMixer(): AnimationMixer
¶
JSDoc:
/**
* Returns the animation mixer of this animation action.
*
* @return {AnimationMixer} The animation mixer.
*/
Returns: AnimationMixer
AnimationAction.getClip(): AnimationClip
¶
JSDoc:
/**
* Returns the animation clip of this animation action.
*
* @return {AnimationClip} The animation clip.
*/
Returns: AnimationClip
AnimationAction.getRoot(): Object3D
¶
JSDoc:
Returns: Object3D
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:
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:
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:
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
¶
stop(): AnimationAction
¶
reset(): AnimationAction
¶
Code
isRunning(): boolean
¶
Code
isScheduled(): boolean
¶
startAt(time: number): AnimationAction
¶
setLoop(mode: number, repetitions: number): AnimationAction
¶
Code
setEffectiveWeight(weight: number): AnimationAction
¶
Code
getEffectiveWeight(): number
¶
fadeIn(duration: number): AnimationAction
¶
fadeOut(duration: number): AnimationAction
¶
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
stopFading(): AnimationAction
¶
Code
setEffectiveTimeScale(timeScale: number): AnimationAction
¶
Code
getEffectiveTimeScale(): number
¶
setDuration(duration: number): AnimationAction
¶
Code
syncWith(action: AnimationAction): AnimationAction
¶
Code
halt(duration: number): AnimationAction
¶
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
getMixer(): AnimationMixer
¶
getClip(): AnimationClip
¶
getRoot(): Object3D
¶
_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;
}