initial commit
This commit is contained in:
672
lib/animation/AnimationAction.js
Normal file
672
lib/animation/AnimationAction.js
Normal file
@ -0,0 +1,672 @@
|
||||
import { WrapAroundEnding, ZeroCurvatureEnding, ZeroSlopeEnding, LoopPingPong, LoopOnce, LoopRepeat } from '../constants.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* Action provided by AnimationMixer for scheduling clip playback on specific
|
||||
* objects.
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*
|
||||
*/
|
||||
|
||||
function AnimationAction( mixer, clip, localRoot ) {
|
||||
|
||||
this._mixer = mixer;
|
||||
this._clip = clip;
|
||||
this._localRoot = localRoot || null;
|
||||
|
||||
var tracks = clip.tracks,
|
||||
nTracks = tracks.length,
|
||||
interpolants = new Array( nTracks );
|
||||
|
||||
var interpolantSettings = {
|
||||
endingStart: ZeroCurvatureEnding,
|
||||
endingEnd: ZeroCurvatureEnding
|
||||
};
|
||||
|
||||
for ( var i = 0; i !== nTracks; ++ i ) {
|
||||
|
||||
var 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;
|
||||
|
||||
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;
|
||||
|
||||
// scaled local time of the action
|
||||
// gets clamped or wrapped to 0..clip.duration according to loop
|
||||
this.time = 0;
|
||||
|
||||
this.timeScale = 1;
|
||||
this._effectiveTimeScale = 1;
|
||||
|
||||
this.weight = 1;
|
||||
this._effectiveWeight = 1;
|
||||
|
||||
this.repetitions = Infinity; // no. of repetitions when looping
|
||||
|
||||
this.paused = false; // true -> zero effective time scale
|
||||
this.enabled = true; // false -> zero effective weight
|
||||
|
||||
this.clampWhenFinished = false; // keep feeding the last frame?
|
||||
|
||||
this.zeroSlopeAtStart = true; // for smooth interpolation w/o separate
|
||||
this.zeroSlopeAtEnd = true; // clips for start, loop and end
|
||||
|
||||
}
|
||||
|
||||
Object.assign( AnimationAction.prototype, {
|
||||
|
||||
// State & Scheduling
|
||||
|
||||
play: function () {
|
||||
|
||||
this._mixer._activateAction( this );
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
stop: function () {
|
||||
|
||||
this._mixer._deactivateAction( this );
|
||||
|
||||
return this.reset();
|
||||
|
||||
},
|
||||
|
||||
reset: function () {
|
||||
|
||||
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: function () {
|
||||
|
||||
return this.enabled && ! this.paused && this.timeScale !== 0 &&
|
||||
this._startTime === null && this._mixer._isActiveAction( this );
|
||||
|
||||
},
|
||||
|
||||
// return true when play has been called
|
||||
isScheduled: function () {
|
||||
|
||||
return this._mixer._isActiveAction( this );
|
||||
|
||||
},
|
||||
|
||||
startAt: function ( time ) {
|
||||
|
||||
this._startTime = time;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
setLoop: function ( mode, repetitions ) {
|
||||
|
||||
this.loop = mode;
|
||||
this.repetitions = repetitions;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// Weight
|
||||
|
||||
// set the weight stopping any scheduled fading
|
||||
// although .enabled = false yields an effective weight of zero, this
|
||||
// method does *not* change .enabled, because it would be confusing
|
||||
setEffectiveWeight: function ( weight ) {
|
||||
|
||||
this.weight = weight;
|
||||
|
||||
// note: same logic as when updated at runtime
|
||||
this._effectiveWeight = this.enabled ? weight : 0;
|
||||
|
||||
return this.stopFading();
|
||||
|
||||
},
|
||||
|
||||
// return the weight considering fading and .enabled
|
||||
getEffectiveWeight: function () {
|
||||
|
||||
return this._effectiveWeight;
|
||||
|
||||
},
|
||||
|
||||
fadeIn: function ( duration ) {
|
||||
|
||||
return this._scheduleFading( duration, 0, 1 );
|
||||
|
||||
},
|
||||
|
||||
fadeOut: function ( duration ) {
|
||||
|
||||
return this._scheduleFading( duration, 1, 0 );
|
||||
|
||||
},
|
||||
|
||||
crossFadeFrom: function ( fadeOutAction, duration, warp ) {
|
||||
|
||||
fadeOutAction.fadeOut( duration );
|
||||
this.fadeIn( duration );
|
||||
|
||||
if ( warp ) {
|
||||
|
||||
var 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: function ( fadeInAction, duration, warp ) {
|
||||
|
||||
return fadeInAction.crossFadeFrom( this, duration, warp );
|
||||
|
||||
},
|
||||
|
||||
stopFading: function () {
|
||||
|
||||
var weightInterpolant = this._weightInterpolant;
|
||||
|
||||
if ( weightInterpolant !== null ) {
|
||||
|
||||
this._weightInterpolant = null;
|
||||
this._mixer._takeBackControlInterpolant( weightInterpolant );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// Time Scale Control
|
||||
|
||||
// set the time scale stopping any scheduled warping
|
||||
// although .paused = true yields an effective time scale of zero, this
|
||||
// method does *not* change .paused, because it would be confusing
|
||||
setEffectiveTimeScale: function ( timeScale ) {
|
||||
|
||||
this.timeScale = timeScale;
|
||||
this._effectiveTimeScale = this.paused ? 0 : timeScale;
|
||||
|
||||
return this.stopWarping();
|
||||
|
||||
},
|
||||
|
||||
// return the time scale considering warping and .paused
|
||||
getEffectiveTimeScale: function () {
|
||||
|
||||
return this._effectiveTimeScale;
|
||||
|
||||
},
|
||||
|
||||
setDuration: function ( duration ) {
|
||||
|
||||
this.timeScale = this._clip.duration / duration;
|
||||
|
||||
return this.stopWarping();
|
||||
|
||||
},
|
||||
|
||||
syncWith: function ( action ) {
|
||||
|
||||
this.time = action.time;
|
||||
this.timeScale = action.timeScale;
|
||||
|
||||
return this.stopWarping();
|
||||
|
||||
},
|
||||
|
||||
halt: function ( duration ) {
|
||||
|
||||
return this.warp( this._effectiveTimeScale, 0, duration );
|
||||
|
||||
},
|
||||
|
||||
warp: function ( startTimeScale, endTimeScale, duration ) {
|
||||
|
||||
var mixer = this._mixer, now = mixer.time,
|
||||
interpolant = this._timeScaleInterpolant,
|
||||
|
||||
timeScale = this.timeScale;
|
||||
|
||||
if ( interpolant === null ) {
|
||||
|
||||
interpolant = mixer._lendControlInterpolant();
|
||||
this._timeScaleInterpolant = interpolant;
|
||||
|
||||
}
|
||||
|
||||
var times = interpolant.parameterPositions,
|
||||
values = interpolant.sampleValues;
|
||||
|
||||
times[ 0 ] = now;
|
||||
times[ 1 ] = now + duration;
|
||||
|
||||
values[ 0 ] = startTimeScale / timeScale;
|
||||
values[ 1 ] = endTimeScale / timeScale;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
stopWarping: function () {
|
||||
|
||||
var timeScaleInterpolant = this._timeScaleInterpolant;
|
||||
|
||||
if ( timeScaleInterpolant !== null ) {
|
||||
|
||||
this._timeScaleInterpolant = null;
|
||||
this._mixer._takeBackControlInterpolant( timeScaleInterpolant );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// Object Accessors
|
||||
|
||||
getMixer: function () {
|
||||
|
||||
return this._mixer;
|
||||
|
||||
},
|
||||
|
||||
getClip: function () {
|
||||
|
||||
return this._clip;
|
||||
|
||||
},
|
||||
|
||||
getRoot: function () {
|
||||
|
||||
return this._localRoot || this._mixer._root;
|
||||
|
||||
},
|
||||
|
||||
// Interna
|
||||
|
||||
_update: function ( time, deltaTime, timeDirection, accuIndex ) {
|
||||
|
||||
// called by the mixer
|
||||
|
||||
if ( ! this.enabled ) {
|
||||
|
||||
// call ._updateWeight() to update ._effectiveWeight
|
||||
|
||||
this._updateWeight( time );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
var startTime = this._startTime;
|
||||
|
||||
if ( startTime !== null ) {
|
||||
|
||||
// check for scheduled start of action
|
||||
|
||||
var timeRunning = ( time - startTime ) * timeDirection;
|
||||
if ( timeRunning < 0 || timeDirection === 0 ) {
|
||||
|
||||
return; // yet to come / don't decide when delta = 0
|
||||
|
||||
}
|
||||
|
||||
// start
|
||||
|
||||
this._startTime = null; // unschedule
|
||||
deltaTime = timeDirection * timeRunning;
|
||||
|
||||
}
|
||||
|
||||
// apply time scale and advance time
|
||||
|
||||
deltaTime *= this._updateTimeScale( time );
|
||||
var clipTime = this._updateTime( deltaTime );
|
||||
|
||||
// note: _updateTime may disable the action resulting in
|
||||
// an effective weight of 0
|
||||
|
||||
var weight = this._updateWeight( time );
|
||||
|
||||
if ( weight > 0 ) {
|
||||
|
||||
var interpolants = this._interpolants;
|
||||
var propertyMixers = this._propertyBindings;
|
||||
|
||||
for ( var j = 0, m = interpolants.length; j !== m; ++ j ) {
|
||||
|
||||
interpolants[ j ].evaluate( clipTime );
|
||||
propertyMixers[ j ].accumulate( accuIndex, weight );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_updateWeight: function ( time ) {
|
||||
|
||||
var weight = 0;
|
||||
|
||||
if ( this.enabled ) {
|
||||
|
||||
weight = this.weight;
|
||||
var interpolant = this._weightInterpolant;
|
||||
|
||||
if ( interpolant !== null ) {
|
||||
|
||||
var 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: function ( time ) {
|
||||
|
||||
var timeScale = 0;
|
||||
|
||||
if ( ! this.paused ) {
|
||||
|
||||
timeScale = this.timeScale;
|
||||
|
||||
var interpolant = this._timeScaleInterpolant;
|
||||
|
||||
if ( interpolant !== null ) {
|
||||
|
||||
var 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: function ( deltaTime ) {
|
||||
|
||||
var time = this.time + deltaTime;
|
||||
var duration = this._clip.duration;
|
||||
var loop = this.loop;
|
||||
var loopCount = this._loopCount;
|
||||
|
||||
var 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 break handle_stop;
|
||||
|
||||
if ( this.clampWhenFinished ) this.paused = true;
|
||||
else this.enabled = false;
|
||||
|
||||
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
|
||||
|
||||
var loopDelta = Math.floor( time / duration ); // signed
|
||||
time -= duration * loopDelta;
|
||||
|
||||
loopCount += Math.abs( loopDelta );
|
||||
|
||||
var 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._mixer.dispatchEvent( {
|
||||
type: 'finished', action: this,
|
||||
direction: deltaTime > 0 ? 1 : - 1
|
||||
} );
|
||||
|
||||
} else {
|
||||
|
||||
// keep running
|
||||
|
||||
if ( pending === 1 ) {
|
||||
|
||||
// entering the last round
|
||||
|
||||
var atStart = deltaTime < 0;
|
||||
this._setEndings( atStart, ! atStart, pingPong );
|
||||
|
||||
} else {
|
||||
|
||||
this._setEndings( false, false, pingPong );
|
||||
|
||||
}
|
||||
|
||||
this._loopCount = loopCount;
|
||||
|
||||
this._mixer.dispatchEvent( {
|
||||
type: 'loop', action: this, loopDelta: loopDelta
|
||||
} );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( pingPong && ( loopCount & 1 ) === 1 ) {
|
||||
|
||||
// invert time for the "pong round"
|
||||
|
||||
this.time = time;
|
||||
return duration - time;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this.time = time;
|
||||
return time;
|
||||
|
||||
},
|
||||
|
||||
_setEndings: function ( atStart, atEnd, pingPong ) {
|
||||
|
||||
var 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: function ( duration, weightNow, weightThen ) {
|
||||
|
||||
var mixer = this._mixer, now = mixer.time,
|
||||
interpolant = this._weightInterpolant;
|
||||
|
||||
if ( interpolant === null ) {
|
||||
|
||||
interpolant = mixer._lendControlInterpolant();
|
||||
this._weightInterpolant = interpolant;
|
||||
|
||||
}
|
||||
|
||||
var times = interpolant.parameterPositions,
|
||||
values = interpolant.sampleValues;
|
||||
|
||||
times[ 0 ] = now; values[ 0 ] = weightNow;
|
||||
times[ 1 ] = now + duration; values[ 1 ] = weightThen;
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { AnimationAction };
|
453
lib/animation/AnimationClip.js
Normal file
453
lib/animation/AnimationClip.js
Normal file
@ -0,0 +1,453 @@
|
||||
import { AnimationUtils } from './AnimationUtils.js';
|
||||
import { KeyframeTrack } from './KeyframeTrack.js';
|
||||
import { BooleanKeyframeTrack } from './tracks/BooleanKeyframeTrack.js';
|
||||
import { ColorKeyframeTrack } from './tracks/ColorKeyframeTrack.js';
|
||||
import { NumberKeyframeTrack } from './tracks/NumberKeyframeTrack.js';
|
||||
import { QuaternionKeyframeTrack } from './tracks/QuaternionKeyframeTrack.js';
|
||||
import { StringKeyframeTrack } from './tracks/StringKeyframeTrack.js';
|
||||
import { VectorKeyframeTrack } from './tracks/VectorKeyframeTrack.js';
|
||||
import { _Math } from '../math/Math.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* Reusable set of Tracks that represent an animation.
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
*/
|
||||
|
||||
function AnimationClip( name, duration, tracks ) {
|
||||
|
||||
this.name = name;
|
||||
this.tracks = tracks;
|
||||
this.duration = ( duration !== undefined ) ? duration : - 1;
|
||||
|
||||
this.uuid = _Math.generateUUID();
|
||||
|
||||
// this means it should figure out its duration by scanning the tracks
|
||||
if ( this.duration < 0 ) {
|
||||
|
||||
this.resetDuration();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
function getTrackTypeForValueTypeName( typeName ) {
|
||||
|
||||
switch ( typeName.toLowerCase() ) {
|
||||
|
||||
case 'scalar':
|
||||
case 'double':
|
||||
case 'float':
|
||||
case 'number':
|
||||
case 'integer':
|
||||
|
||||
return NumberKeyframeTrack;
|
||||
|
||||
case 'vector':
|
||||
case 'vector2':
|
||||
case 'vector3':
|
||||
case 'vector4':
|
||||
|
||||
return VectorKeyframeTrack;
|
||||
|
||||
case 'color':
|
||||
|
||||
return ColorKeyframeTrack;
|
||||
|
||||
case 'quaternion':
|
||||
|
||||
return QuaternionKeyframeTrack;
|
||||
|
||||
case 'bool':
|
||||
case 'boolean':
|
||||
|
||||
return BooleanKeyframeTrack;
|
||||
|
||||
case 'string':
|
||||
|
||||
return StringKeyframeTrack;
|
||||
|
||||
}
|
||||
|
||||
throw new Error( 'THREE.KeyframeTrack: Unsupported typeName: ' + typeName );
|
||||
|
||||
}
|
||||
|
||||
function parseKeyframeTrack( json ) {
|
||||
|
||||
if ( json.type === undefined ) {
|
||||
|
||||
throw new Error( 'THREE.KeyframeTrack: track type undefined, can not parse' );
|
||||
|
||||
}
|
||||
|
||||
var trackType = getTrackTypeForValueTypeName( json.type );
|
||||
|
||||
if ( json.times === undefined ) {
|
||||
|
||||
var times = [], values = [];
|
||||
|
||||
AnimationUtils.flattenJSON( json.keys, times, values, 'value' );
|
||||
|
||||
json.times = times;
|
||||
json.values = values;
|
||||
|
||||
}
|
||||
|
||||
// derived classes can define a static parse method
|
||||
if ( trackType.parse !== undefined ) {
|
||||
|
||||
return trackType.parse( json );
|
||||
|
||||
} else {
|
||||
|
||||
// by default, we assume a constructor compatible with the base
|
||||
return new trackType( json.name, json.times, json.values, json.interpolation );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Object.assign( AnimationClip, {
|
||||
|
||||
parse: function ( json ) {
|
||||
|
||||
var tracks = [],
|
||||
jsonTracks = json.tracks,
|
||||
frameTime = 1.0 / ( json.fps || 1.0 );
|
||||
|
||||
for ( var i = 0, n = jsonTracks.length; i !== n; ++ i ) {
|
||||
|
||||
tracks.push( parseKeyframeTrack( jsonTracks[ i ] ).scale( frameTime ) );
|
||||
|
||||
}
|
||||
|
||||
return new AnimationClip( json.name, json.duration, tracks );
|
||||
|
||||
},
|
||||
|
||||
toJSON: function ( clip ) {
|
||||
|
||||
var tracks = [],
|
||||
clipTracks = clip.tracks;
|
||||
|
||||
var json = {
|
||||
|
||||
'name': clip.name,
|
||||
'duration': clip.duration,
|
||||
'tracks': tracks,
|
||||
'uuid': clip.uuid
|
||||
|
||||
};
|
||||
|
||||
for ( var i = 0, n = clipTracks.length; i !== n; ++ i ) {
|
||||
|
||||
tracks.push( KeyframeTrack.toJSON( clipTracks[ i ] ) );
|
||||
|
||||
}
|
||||
|
||||
return json;
|
||||
|
||||
},
|
||||
|
||||
CreateFromMorphTargetSequence: function ( name, morphTargetSequence, fps, noLoop ) {
|
||||
|
||||
var numMorphTargets = morphTargetSequence.length;
|
||||
var tracks = [];
|
||||
|
||||
for ( var i = 0; i < numMorphTargets; i ++ ) {
|
||||
|
||||
var times = [];
|
||||
var values = [];
|
||||
|
||||
times.push(
|
||||
( i + numMorphTargets - 1 ) % numMorphTargets,
|
||||
i,
|
||||
( i + 1 ) % numMorphTargets );
|
||||
|
||||
values.push( 0, 1, 0 );
|
||||
|
||||
var order = AnimationUtils.getKeyframeOrder( times );
|
||||
times = AnimationUtils.sortedArray( times, 1, order );
|
||||
values = AnimationUtils.sortedArray( values, 1, order );
|
||||
|
||||
// if there is a key at the first frame, duplicate it as the
|
||||
// last frame as well for perfect loop.
|
||||
if ( ! noLoop && times[ 0 ] === 0 ) {
|
||||
|
||||
times.push( numMorphTargets );
|
||||
values.push( values[ 0 ] );
|
||||
|
||||
}
|
||||
|
||||
tracks.push(
|
||||
new NumberKeyframeTrack(
|
||||
'.morphTargetInfluences[' + morphTargetSequence[ i ].name + ']',
|
||||
times, values
|
||||
).scale( 1.0 / fps ) );
|
||||
|
||||
}
|
||||
|
||||
return new AnimationClip( name, - 1, tracks );
|
||||
|
||||
},
|
||||
|
||||
findByName: function ( objectOrClipArray, name ) {
|
||||
|
||||
var clipArray = objectOrClipArray;
|
||||
|
||||
if ( ! Array.isArray( objectOrClipArray ) ) {
|
||||
|
||||
var o = objectOrClipArray;
|
||||
clipArray = o.geometry && o.geometry.animations || o.animations;
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 0; i < clipArray.length; i ++ ) {
|
||||
|
||||
if ( clipArray[ i ].name === name ) {
|
||||
|
||||
return clipArray[ i ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
},
|
||||
|
||||
CreateClipsFromMorphTargetSequences: function ( morphTargets, fps, noLoop ) {
|
||||
|
||||
var animationToMorphTargets = {};
|
||||
|
||||
// tested with https://regex101.com/ on trick sequences
|
||||
// such flamingo_flyA_003, flamingo_run1_003, crdeath0059
|
||||
var pattern = /^([\w-]*?)([\d]+)$/;
|
||||
|
||||
// sort morph target names into animation groups based
|
||||
// patterns like Walk_001, Walk_002, Run_001, Run_002
|
||||
for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {
|
||||
|
||||
var morphTarget = morphTargets[ i ];
|
||||
var parts = morphTarget.name.match( pattern );
|
||||
|
||||
if ( parts && parts.length > 1 ) {
|
||||
|
||||
var name = parts[ 1 ];
|
||||
|
||||
var animationMorphTargets = animationToMorphTargets[ name ];
|
||||
if ( ! animationMorphTargets ) {
|
||||
|
||||
animationToMorphTargets[ name ] = animationMorphTargets = [];
|
||||
|
||||
}
|
||||
|
||||
animationMorphTargets.push( morphTarget );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var clips = [];
|
||||
|
||||
for ( var name in animationToMorphTargets ) {
|
||||
|
||||
clips.push( AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps, noLoop ) );
|
||||
|
||||
}
|
||||
|
||||
return clips;
|
||||
|
||||
},
|
||||
|
||||
// parse the animation.hierarchy format
|
||||
parseAnimation: function ( animation, bones ) {
|
||||
|
||||
if ( ! animation ) {
|
||||
|
||||
console.error( 'THREE.AnimationClip: No animation in JSONLoader data.' );
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
var addNonemptyTrack = function ( trackType, trackName, animationKeys, propertyName, destTracks ) {
|
||||
|
||||
// only return track if there are actually keys.
|
||||
if ( animationKeys.length !== 0 ) {
|
||||
|
||||
var times = [];
|
||||
var values = [];
|
||||
|
||||
AnimationUtils.flattenJSON( animationKeys, times, values, propertyName );
|
||||
|
||||
// empty keys are filtered out, so check again
|
||||
if ( times.length !== 0 ) {
|
||||
|
||||
destTracks.push( new trackType( trackName, times, values ) );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
var tracks = [];
|
||||
|
||||
var clipName = animation.name || 'default';
|
||||
// automatic length determination in AnimationClip.
|
||||
var duration = animation.length || - 1;
|
||||
var fps = animation.fps || 30;
|
||||
|
||||
var hierarchyTracks = animation.hierarchy || [];
|
||||
|
||||
for ( var h = 0; h < hierarchyTracks.length; h ++ ) {
|
||||
|
||||
var animationKeys = hierarchyTracks[ h ].keys;
|
||||
|
||||
// skip empty tracks
|
||||
if ( ! animationKeys || animationKeys.length === 0 ) continue;
|
||||
|
||||
// process morph targets
|
||||
if ( animationKeys[ 0 ].morphTargets ) {
|
||||
|
||||
// figure out all morph targets used in this track
|
||||
var morphTargetNames = {};
|
||||
|
||||
for ( var k = 0; k < animationKeys.length; k ++ ) {
|
||||
|
||||
if ( animationKeys[ k ].morphTargets ) {
|
||||
|
||||
for ( var m = 0; m < animationKeys[ k ].morphTargets.length; m ++ ) {
|
||||
|
||||
morphTargetNames[ animationKeys[ k ].morphTargets[ m ] ] = - 1;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// create a track for each morph target with all zero
|
||||
// morphTargetInfluences except for the keys in which
|
||||
// the morphTarget is named.
|
||||
for ( var morphTargetName in morphTargetNames ) {
|
||||
|
||||
var times = [];
|
||||
var values = [];
|
||||
|
||||
for ( var m = 0; m !== animationKeys[ k ].morphTargets.length; ++ m ) {
|
||||
|
||||
var animationKey = animationKeys[ k ];
|
||||
|
||||
times.push( animationKey.time );
|
||||
values.push( ( animationKey.morphTarget === morphTargetName ) ? 1 : 0 );
|
||||
|
||||
}
|
||||
|
||||
tracks.push( new NumberKeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', times, values ) );
|
||||
|
||||
}
|
||||
|
||||
duration = morphTargetNames.length * ( fps || 1.0 );
|
||||
|
||||
} else {
|
||||
|
||||
// ...assume skeletal animation
|
||||
|
||||
var boneName = '.bones[' + bones[ h ].name + ']';
|
||||
|
||||
addNonemptyTrack(
|
||||
VectorKeyframeTrack, boneName + '.position',
|
||||
animationKeys, 'pos', tracks );
|
||||
|
||||
addNonemptyTrack(
|
||||
QuaternionKeyframeTrack, boneName + '.quaternion',
|
||||
animationKeys, 'rot', tracks );
|
||||
|
||||
addNonemptyTrack(
|
||||
VectorKeyframeTrack, boneName + '.scale',
|
||||
animationKeys, 'scl', tracks );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( tracks.length === 0 ) {
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
var clip = new AnimationClip( clipName, duration, tracks );
|
||||
|
||||
return clip;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
Object.assign( AnimationClip.prototype, {
|
||||
|
||||
resetDuration: function () {
|
||||
|
||||
var tracks = this.tracks, duration = 0;
|
||||
|
||||
for ( var i = 0, n = tracks.length; i !== n; ++ i ) {
|
||||
|
||||
var track = this.tracks[ i ];
|
||||
|
||||
duration = Math.max( duration, track.times[ track.times.length - 1 ] );
|
||||
|
||||
}
|
||||
|
||||
this.duration = duration;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
trim: function () {
|
||||
|
||||
for ( var i = 0; i < this.tracks.length; i ++ ) {
|
||||
|
||||
this.tracks[ i ].trim( 0, this.duration );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
validate: function () {
|
||||
|
||||
var valid = true;
|
||||
|
||||
for ( var i = 0; i < this.tracks.length; i ++ ) {
|
||||
|
||||
valid = valid && this.tracks[ i ].validate();
|
||||
|
||||
}
|
||||
|
||||
return valid;
|
||||
|
||||
},
|
||||
|
||||
optimize: function () {
|
||||
|
||||
for ( var i = 0; i < this.tracks.length; i ++ ) {
|
||||
|
||||
this.tracks[ i ].optimize();
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { AnimationClip };
|
761
lib/animation/AnimationMixer.js
Normal file
761
lib/animation/AnimationMixer.js
Normal file
@ -0,0 +1,761 @@
|
||||
import { AnimationAction } from './AnimationAction.js';
|
||||
import { EventDispatcher } from '../core/EventDispatcher.js';
|
||||
import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
|
||||
import { PropertyBinding } from './PropertyBinding.js';
|
||||
import { PropertyMixer } from './PropertyMixer.js';
|
||||
import { AnimationClip } from './AnimationClip.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* Player for AnimationClips.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function AnimationMixer( root ) {
|
||||
|
||||
this._root = root;
|
||||
this._initMemoryManager();
|
||||
this._accuIndex = 0;
|
||||
|
||||
this.time = 0;
|
||||
|
||||
this.timeScale = 1.0;
|
||||
|
||||
}
|
||||
|
||||
AnimationMixer.prototype = Object.assign( Object.create( EventDispatcher.prototype ), {
|
||||
|
||||
constructor: AnimationMixer,
|
||||
|
||||
_bindAction: function ( action, prototypeAction ) {
|
||||
|
||||
var root = action._localRoot || this._root,
|
||||
tracks = action._clip.tracks,
|
||||
nTracks = tracks.length,
|
||||
bindings = action._propertyBindings,
|
||||
interpolants = action._interpolants,
|
||||
rootUuid = root.uuid,
|
||||
bindingsByRoot = this._bindingsByRootAndName,
|
||||
bindingsByName = bindingsByRoot[ rootUuid ];
|
||||
|
||||
if ( bindingsByName === undefined ) {
|
||||
|
||||
bindingsByName = {};
|
||||
bindingsByRoot[ rootUuid ] = bindingsByName;
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 0; i !== nTracks; ++ i ) {
|
||||
|
||||
var track = tracks[ i ],
|
||||
trackName = track.name,
|
||||
binding = bindingsByName[ trackName ];
|
||||
|
||||
if ( binding !== undefined ) {
|
||||
|
||||
bindings[ i ] = binding;
|
||||
|
||||
} else {
|
||||
|
||||
binding = bindings[ i ];
|
||||
|
||||
if ( binding !== undefined ) {
|
||||
|
||||
// existing binding, make sure the cache knows
|
||||
|
||||
if ( binding._cacheIndex === null ) {
|
||||
|
||||
++ binding.referenceCount;
|
||||
this._addInactiveBinding( binding, rootUuid, trackName );
|
||||
|
||||
}
|
||||
|
||||
continue;
|
||||
|
||||
}
|
||||
|
||||
var path = prototypeAction && prototypeAction.
|
||||
_propertyBindings[ i ].binding.parsedPath;
|
||||
|
||||
binding = new PropertyMixer(
|
||||
PropertyBinding.create( root, trackName, path ),
|
||||
track.ValueTypeName, track.getValueSize() );
|
||||
|
||||
++ binding.referenceCount;
|
||||
this._addInactiveBinding( binding, rootUuid, trackName );
|
||||
|
||||
bindings[ i ] = binding;
|
||||
|
||||
}
|
||||
|
||||
interpolants[ i ].resultBuffer = binding.buffer;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_activateAction: function ( action ) {
|
||||
|
||||
if ( ! this._isActiveAction( action ) ) {
|
||||
|
||||
if ( action._cacheIndex === null ) {
|
||||
|
||||
// this action has been forgotten by the cache, but the user
|
||||
// appears to be still using it -> rebind
|
||||
|
||||
var rootUuid = ( action._localRoot || this._root ).uuid,
|
||||
clipUuid = action._clip.uuid,
|
||||
actionsForClip = this._actionsByClip[ clipUuid ];
|
||||
|
||||
this._bindAction( action,
|
||||
actionsForClip && actionsForClip.knownActions[ 0 ] );
|
||||
|
||||
this._addInactiveAction( action, clipUuid, rootUuid );
|
||||
|
||||
}
|
||||
|
||||
var bindings = action._propertyBindings;
|
||||
|
||||
// increment reference counts / sort out state
|
||||
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
var binding = bindings[ i ];
|
||||
|
||||
if ( binding.useCount ++ === 0 ) {
|
||||
|
||||
this._lendBinding( binding );
|
||||
binding.saveOriginalState();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._lendAction( action );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_deactivateAction: function ( action ) {
|
||||
|
||||
if ( this._isActiveAction( action ) ) {
|
||||
|
||||
var bindings = action._propertyBindings;
|
||||
|
||||
// decrement reference counts / sort out state
|
||||
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
var binding = bindings[ i ];
|
||||
|
||||
if ( -- binding.useCount === 0 ) {
|
||||
|
||||
binding.restoreOriginalState();
|
||||
this._takeBackBinding( binding );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
this._takeBackAction( action );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// Memory manager
|
||||
|
||||
_initMemoryManager: function () {
|
||||
|
||||
this._actions = []; // 'nActiveActions' followed by inactive ones
|
||||
this._nActiveActions = 0;
|
||||
|
||||
this._actionsByClip = {};
|
||||
// inside:
|
||||
// {
|
||||
// knownActions: Array< AnimationAction > - used as prototypes
|
||||
// actionByRoot: AnimationAction - lookup
|
||||
// }
|
||||
|
||||
|
||||
this._bindings = []; // 'nActiveBindings' followed by inactive ones
|
||||
this._nActiveBindings = 0;
|
||||
|
||||
this._bindingsByRootAndName = {}; // inside: Map< name, PropertyMixer >
|
||||
|
||||
|
||||
this._controlInterpolants = []; // same game as above
|
||||
this._nActiveControlInterpolants = 0;
|
||||
|
||||
var scope = this;
|
||||
|
||||
this.stats = {
|
||||
|
||||
actions: {
|
||||
get total() {
|
||||
|
||||
return scope._actions.length;
|
||||
|
||||
},
|
||||
get inUse() {
|
||||
|
||||
return scope._nActiveActions;
|
||||
|
||||
}
|
||||
},
|
||||
bindings: {
|
||||
get total() {
|
||||
|
||||
return scope._bindings.length;
|
||||
|
||||
},
|
||||
get inUse() {
|
||||
|
||||
return scope._nActiveBindings;
|
||||
|
||||
}
|
||||
},
|
||||
controlInterpolants: {
|
||||
get total() {
|
||||
|
||||
return scope._controlInterpolants.length;
|
||||
|
||||
},
|
||||
get inUse() {
|
||||
|
||||
return scope._nActiveControlInterpolants;
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
},
|
||||
|
||||
// Memory management for AnimationAction objects
|
||||
|
||||
_isActiveAction: function ( action ) {
|
||||
|
||||
var index = action._cacheIndex;
|
||||
return index !== null && index < this._nActiveActions;
|
||||
|
||||
},
|
||||
|
||||
_addInactiveAction: function ( action, clipUuid, rootUuid ) {
|
||||
|
||||
var actions = this._actions,
|
||||
actionsByClip = this._actionsByClip,
|
||||
actionsForClip = actionsByClip[ clipUuid ];
|
||||
|
||||
if ( actionsForClip === undefined ) {
|
||||
|
||||
actionsForClip = {
|
||||
|
||||
knownActions: [ action ],
|
||||
actionByRoot: {}
|
||||
|
||||
};
|
||||
|
||||
action._byClipCacheIndex = 0;
|
||||
|
||||
actionsByClip[ clipUuid ] = actionsForClip;
|
||||
|
||||
} else {
|
||||
|
||||
var knownActions = actionsForClip.knownActions;
|
||||
|
||||
action._byClipCacheIndex = knownActions.length;
|
||||
knownActions.push( action );
|
||||
|
||||
}
|
||||
|
||||
action._cacheIndex = actions.length;
|
||||
actions.push( action );
|
||||
|
||||
actionsForClip.actionByRoot[ rootUuid ] = action;
|
||||
|
||||
},
|
||||
|
||||
_removeInactiveAction: function ( action ) {
|
||||
|
||||
var actions = this._actions,
|
||||
lastInactiveAction = actions[ actions.length - 1 ],
|
||||
cacheIndex = action._cacheIndex;
|
||||
|
||||
lastInactiveAction._cacheIndex = cacheIndex;
|
||||
actions[ cacheIndex ] = lastInactiveAction;
|
||||
actions.pop();
|
||||
|
||||
action._cacheIndex = null;
|
||||
|
||||
|
||||
var clipUuid = action._clip.uuid,
|
||||
actionsByClip = this._actionsByClip,
|
||||
actionsForClip = actionsByClip[ clipUuid ],
|
||||
knownActionsForClip = actionsForClip.knownActions,
|
||||
|
||||
lastKnownAction =
|
||||
knownActionsForClip[ knownActionsForClip.length - 1 ],
|
||||
|
||||
byClipCacheIndex = action._byClipCacheIndex;
|
||||
|
||||
lastKnownAction._byClipCacheIndex = byClipCacheIndex;
|
||||
knownActionsForClip[ byClipCacheIndex ] = lastKnownAction;
|
||||
knownActionsForClip.pop();
|
||||
|
||||
action._byClipCacheIndex = null;
|
||||
|
||||
|
||||
var actionByRoot = actionsForClip.actionByRoot,
|
||||
rootUuid = ( action._localRoot || this._root ).uuid;
|
||||
|
||||
delete actionByRoot[ rootUuid ];
|
||||
|
||||
if ( knownActionsForClip.length === 0 ) {
|
||||
|
||||
delete actionsByClip[ clipUuid ];
|
||||
|
||||
}
|
||||
|
||||
this._removeInactiveBindingsForAction( action );
|
||||
|
||||
},
|
||||
|
||||
_removeInactiveBindingsForAction: function ( action ) {
|
||||
|
||||
var bindings = action._propertyBindings;
|
||||
for ( var i = 0, n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
var binding = bindings[ i ];
|
||||
|
||||
if ( -- binding.referenceCount === 0 ) {
|
||||
|
||||
this._removeInactiveBinding( binding );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_lendAction: function ( action ) {
|
||||
|
||||
// [ active actions | inactive actions ]
|
||||
// [ active actions >| inactive actions ]
|
||||
// s a
|
||||
// <-swap->
|
||||
// a s
|
||||
|
||||
var actions = this._actions,
|
||||
prevIndex = action._cacheIndex,
|
||||
|
||||
lastActiveIndex = this._nActiveActions ++,
|
||||
|
||||
firstInactiveAction = actions[ lastActiveIndex ];
|
||||
|
||||
action._cacheIndex = lastActiveIndex;
|
||||
actions[ lastActiveIndex ] = action;
|
||||
|
||||
firstInactiveAction._cacheIndex = prevIndex;
|
||||
actions[ prevIndex ] = firstInactiveAction;
|
||||
|
||||
},
|
||||
|
||||
_takeBackAction: function ( action ) {
|
||||
|
||||
// [ active actions | inactive actions ]
|
||||
// [ active actions |< inactive actions ]
|
||||
// a s
|
||||
// <-swap->
|
||||
// s a
|
||||
|
||||
var actions = this._actions,
|
||||
prevIndex = action._cacheIndex,
|
||||
|
||||
firstInactiveIndex = -- this._nActiveActions,
|
||||
|
||||
lastActiveAction = actions[ firstInactiveIndex ];
|
||||
|
||||
action._cacheIndex = firstInactiveIndex;
|
||||
actions[ firstInactiveIndex ] = action;
|
||||
|
||||
lastActiveAction._cacheIndex = prevIndex;
|
||||
actions[ prevIndex ] = lastActiveAction;
|
||||
|
||||
},
|
||||
|
||||
// Memory management for PropertyMixer objects
|
||||
|
||||
_addInactiveBinding: function ( binding, rootUuid, trackName ) {
|
||||
|
||||
var bindingsByRoot = this._bindingsByRootAndName,
|
||||
bindingByName = bindingsByRoot[ rootUuid ],
|
||||
|
||||
bindings = this._bindings;
|
||||
|
||||
if ( bindingByName === undefined ) {
|
||||
|
||||
bindingByName = {};
|
||||
bindingsByRoot[ rootUuid ] = bindingByName;
|
||||
|
||||
}
|
||||
|
||||
bindingByName[ trackName ] = binding;
|
||||
|
||||
binding._cacheIndex = bindings.length;
|
||||
bindings.push( binding );
|
||||
|
||||
},
|
||||
|
||||
_removeInactiveBinding: function ( binding ) {
|
||||
|
||||
var bindings = this._bindings,
|
||||
propBinding = binding.binding,
|
||||
rootUuid = propBinding.rootNode.uuid,
|
||||
trackName = propBinding.path,
|
||||
bindingsByRoot = this._bindingsByRootAndName,
|
||||
bindingByName = bindingsByRoot[ rootUuid ],
|
||||
|
||||
lastInactiveBinding = bindings[ bindings.length - 1 ],
|
||||
cacheIndex = binding._cacheIndex;
|
||||
|
||||
lastInactiveBinding._cacheIndex = cacheIndex;
|
||||
bindings[ cacheIndex ] = lastInactiveBinding;
|
||||
bindings.pop();
|
||||
|
||||
delete bindingByName[ trackName ];
|
||||
|
||||
remove_empty_map: {
|
||||
|
||||
for ( var _ in bindingByName ) break remove_empty_map; // eslint-disable-line no-unused-vars
|
||||
|
||||
delete bindingsByRoot[ rootUuid ];
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_lendBinding: function ( binding ) {
|
||||
|
||||
var bindings = this._bindings,
|
||||
prevIndex = binding._cacheIndex,
|
||||
|
||||
lastActiveIndex = this._nActiveBindings ++,
|
||||
|
||||
firstInactiveBinding = bindings[ lastActiveIndex ];
|
||||
|
||||
binding._cacheIndex = lastActiveIndex;
|
||||
bindings[ lastActiveIndex ] = binding;
|
||||
|
||||
firstInactiveBinding._cacheIndex = prevIndex;
|
||||
bindings[ prevIndex ] = firstInactiveBinding;
|
||||
|
||||
},
|
||||
|
||||
_takeBackBinding: function ( binding ) {
|
||||
|
||||
var bindings = this._bindings,
|
||||
prevIndex = binding._cacheIndex,
|
||||
|
||||
firstInactiveIndex = -- this._nActiveBindings,
|
||||
|
||||
lastActiveBinding = bindings[ firstInactiveIndex ];
|
||||
|
||||
binding._cacheIndex = firstInactiveIndex;
|
||||
bindings[ firstInactiveIndex ] = binding;
|
||||
|
||||
lastActiveBinding._cacheIndex = prevIndex;
|
||||
bindings[ prevIndex ] = lastActiveBinding;
|
||||
|
||||
},
|
||||
|
||||
|
||||
// Memory management of Interpolants for weight and time scale
|
||||
|
||||
_lendControlInterpolant: function () {
|
||||
|
||||
var interpolants = this._controlInterpolants,
|
||||
lastActiveIndex = this._nActiveControlInterpolants ++,
|
||||
interpolant = interpolants[ lastActiveIndex ];
|
||||
|
||||
if ( interpolant === undefined ) {
|
||||
|
||||
interpolant = new LinearInterpolant(
|
||||
new Float32Array( 2 ), new Float32Array( 2 ),
|
||||
1, this._controlInterpolantsResultBuffer );
|
||||
|
||||
interpolant.__cacheIndex = lastActiveIndex;
|
||||
interpolants[ lastActiveIndex ] = interpolant;
|
||||
|
||||
}
|
||||
|
||||
return interpolant;
|
||||
|
||||
},
|
||||
|
||||
_takeBackControlInterpolant: function ( interpolant ) {
|
||||
|
||||
var interpolants = this._controlInterpolants,
|
||||
prevIndex = interpolant.__cacheIndex,
|
||||
|
||||
firstInactiveIndex = -- this._nActiveControlInterpolants,
|
||||
|
||||
lastActiveInterpolant = interpolants[ firstInactiveIndex ];
|
||||
|
||||
interpolant.__cacheIndex = firstInactiveIndex;
|
||||
interpolants[ firstInactiveIndex ] = interpolant;
|
||||
|
||||
lastActiveInterpolant.__cacheIndex = prevIndex;
|
||||
interpolants[ prevIndex ] = lastActiveInterpolant;
|
||||
|
||||
},
|
||||
|
||||
_controlInterpolantsResultBuffer: new Float32Array( 1 ),
|
||||
|
||||
// return an action for a clip optionally using a custom root target
|
||||
// object (this method allocates a lot of dynamic memory in case a
|
||||
// previously unknown clip/root combination is specified)
|
||||
clipAction: function ( clip, optionalRoot ) {
|
||||
|
||||
var root = optionalRoot || this._root,
|
||||
rootUuid = root.uuid,
|
||||
|
||||
clipObject = typeof clip === 'string' ?
|
||||
AnimationClip.findByName( root, clip ) : clip,
|
||||
|
||||
clipUuid = clipObject !== null ? clipObject.uuid : clip,
|
||||
|
||||
actionsForClip = this._actionsByClip[ clipUuid ],
|
||||
prototypeAction = null;
|
||||
|
||||
if ( actionsForClip !== undefined ) {
|
||||
|
||||
var existingAction =
|
||||
actionsForClip.actionByRoot[ rootUuid ];
|
||||
|
||||
if ( existingAction !== undefined ) {
|
||||
|
||||
return existingAction;
|
||||
|
||||
}
|
||||
|
||||
// we know the clip, so we don't have to parse all
|
||||
// the bindings again but can just copy
|
||||
prototypeAction = actionsForClip.knownActions[ 0 ];
|
||||
|
||||
// also, take the clip from the prototype action
|
||||
if ( clipObject === null )
|
||||
clipObject = prototypeAction._clip;
|
||||
|
||||
}
|
||||
|
||||
// clip must be known when specified via string
|
||||
if ( clipObject === null ) return null;
|
||||
|
||||
// allocate all resources required to run it
|
||||
var newAction = new AnimationAction( this, clipObject, optionalRoot );
|
||||
|
||||
this._bindAction( newAction, prototypeAction );
|
||||
|
||||
// and make the action known to the memory manager
|
||||
this._addInactiveAction( newAction, clipUuid, rootUuid );
|
||||
|
||||
return newAction;
|
||||
|
||||
},
|
||||
|
||||
// get an existing action
|
||||
existingAction: function ( clip, optionalRoot ) {
|
||||
|
||||
var root = optionalRoot || this._root,
|
||||
rootUuid = root.uuid,
|
||||
|
||||
clipObject = typeof clip === 'string' ?
|
||||
AnimationClip.findByName( root, clip ) : clip,
|
||||
|
||||
clipUuid = clipObject ? clipObject.uuid : clip,
|
||||
|
||||
actionsForClip = this._actionsByClip[ clipUuid ];
|
||||
|
||||
if ( actionsForClip !== undefined ) {
|
||||
|
||||
return actionsForClip.actionByRoot[ rootUuid ] || null;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
},
|
||||
|
||||
// deactivates all previously scheduled actions
|
||||
stopAllAction: function () {
|
||||
|
||||
var actions = this._actions,
|
||||
nActions = this._nActiveActions,
|
||||
bindings = this._bindings,
|
||||
nBindings = this._nActiveBindings;
|
||||
|
||||
this._nActiveActions = 0;
|
||||
this._nActiveBindings = 0;
|
||||
|
||||
for ( var i = 0; i !== nActions; ++ i ) {
|
||||
|
||||
actions[ i ].reset();
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 0; i !== nBindings; ++ i ) {
|
||||
|
||||
bindings[ i ].useCount = 0;
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// advance the time and update apply the animation
|
||||
update: function ( deltaTime ) {
|
||||
|
||||
deltaTime *= this.timeScale;
|
||||
|
||||
var actions = this._actions,
|
||||
nActions = this._nActiveActions,
|
||||
|
||||
time = this.time += deltaTime,
|
||||
timeDirection = Math.sign( deltaTime ),
|
||||
|
||||
accuIndex = this._accuIndex ^= 1;
|
||||
|
||||
// run active actions
|
||||
|
||||
for ( var i = 0; i !== nActions; ++ i ) {
|
||||
|
||||
var action = actions[ i ];
|
||||
|
||||
action._update( time, deltaTime, timeDirection, accuIndex );
|
||||
|
||||
}
|
||||
|
||||
// update scene graph
|
||||
|
||||
var bindings = this._bindings,
|
||||
nBindings = this._nActiveBindings;
|
||||
|
||||
for ( var i = 0; i !== nBindings; ++ i ) {
|
||||
|
||||
bindings[ i ].apply( accuIndex );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// return this mixer's root target object
|
||||
getRoot: function () {
|
||||
|
||||
return this._root;
|
||||
|
||||
},
|
||||
|
||||
// free all resources specific to a particular clip
|
||||
uncacheClip: function ( clip ) {
|
||||
|
||||
var actions = this._actions,
|
||||
clipUuid = clip.uuid,
|
||||
actionsByClip = this._actionsByClip,
|
||||
actionsForClip = actionsByClip[ clipUuid ];
|
||||
|
||||
if ( actionsForClip !== undefined ) {
|
||||
|
||||
// note: just calling _removeInactiveAction would mess up the
|
||||
// iteration state and also require updating the state we can
|
||||
// just throw away
|
||||
|
||||
var actionsToRemove = actionsForClip.knownActions;
|
||||
|
||||
for ( var i = 0, n = actionsToRemove.length; i !== n; ++ i ) {
|
||||
|
||||
var action = actionsToRemove[ i ];
|
||||
|
||||
this._deactivateAction( action );
|
||||
|
||||
var cacheIndex = action._cacheIndex,
|
||||
lastInactiveAction = actions[ actions.length - 1 ];
|
||||
|
||||
action._cacheIndex = null;
|
||||
action._byClipCacheIndex = null;
|
||||
|
||||
lastInactiveAction._cacheIndex = cacheIndex;
|
||||
actions[ cacheIndex ] = lastInactiveAction;
|
||||
actions.pop();
|
||||
|
||||
this._removeInactiveBindingsForAction( action );
|
||||
|
||||
}
|
||||
|
||||
delete actionsByClip[ clipUuid ];
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// free all resources specific to a particular root target object
|
||||
uncacheRoot: function ( root ) {
|
||||
|
||||
var rootUuid = root.uuid,
|
||||
actionsByClip = this._actionsByClip;
|
||||
|
||||
for ( var clipUuid in actionsByClip ) {
|
||||
|
||||
var actionByRoot = actionsByClip[ clipUuid ].actionByRoot,
|
||||
action = actionByRoot[ rootUuid ];
|
||||
|
||||
if ( action !== undefined ) {
|
||||
|
||||
this._deactivateAction( action );
|
||||
this._removeInactiveAction( action );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
var bindingsByRoot = this._bindingsByRootAndName,
|
||||
bindingByName = bindingsByRoot[ rootUuid ];
|
||||
|
||||
if ( bindingByName !== undefined ) {
|
||||
|
||||
for ( var trackName in bindingByName ) {
|
||||
|
||||
var binding = bindingByName[ trackName ];
|
||||
binding.restoreOriginalState();
|
||||
this._removeInactiveBinding( binding );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// remove a targeted clip from the cache
|
||||
uncacheAction: function ( clip, optionalRoot ) {
|
||||
|
||||
var action = this.existingAction( clip, optionalRoot );
|
||||
|
||||
if ( action !== null ) {
|
||||
|
||||
this._deactivateAction( action );
|
||||
this._removeInactiveAction( action );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { AnimationMixer };
|
381
lib/animation/AnimationObjectGroup.js
Normal file
381
lib/animation/AnimationObjectGroup.js
Normal file
@ -0,0 +1,381 @@
|
||||
import { PropertyBinding } from './PropertyBinding.js';
|
||||
import { _Math } from '../math/Math.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A group of objects that receives a shared animation state.
|
||||
*
|
||||
* Usage:
|
||||
*
|
||||
* - Add objects you would otherwise pass as 'root' to the
|
||||
* constructor or the .clipAction method of AnimationMixer.
|
||||
*
|
||||
* - Instead pass this object as 'root'.
|
||||
*
|
||||
* - You can also add and remove objects later when the mixer
|
||||
* is running.
|
||||
*
|
||||
* Note:
|
||||
*
|
||||
* Objects of this class appear as one object to the mixer,
|
||||
* so cache control of the individual objects must be done
|
||||
* on the group.
|
||||
*
|
||||
* Limitation:
|
||||
*
|
||||
* - The animated properties must be compatible among the
|
||||
* all objects in the group.
|
||||
*
|
||||
* - A single property can either be controlled through a
|
||||
* target group or directly, but not both.
|
||||
*
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function AnimationObjectGroup() {
|
||||
|
||||
this.uuid = _Math.generateUUID();
|
||||
|
||||
// cached objects followed by the active ones
|
||||
this._objects = Array.prototype.slice.call( arguments );
|
||||
|
||||
this.nCachedObjects_ = 0; // threshold
|
||||
// note: read by PropertyBinding.Composite
|
||||
|
||||
var indices = {};
|
||||
this._indicesByUUID = indices; // for bookkeeping
|
||||
|
||||
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
|
||||
|
||||
indices[ arguments[ i ].uuid ] = i;
|
||||
|
||||
}
|
||||
|
||||
this._paths = []; // inside: string
|
||||
this._parsedPaths = []; // inside: { we don't care, here }
|
||||
this._bindings = []; // inside: Array< PropertyBinding >
|
||||
this._bindingsIndicesByPath = {}; // inside: indices in these arrays
|
||||
|
||||
var scope = this;
|
||||
|
||||
this.stats = {
|
||||
|
||||
objects: {
|
||||
get total() {
|
||||
|
||||
return scope._objects.length;
|
||||
|
||||
},
|
||||
get inUse() {
|
||||
|
||||
return this.total - scope.nCachedObjects_;
|
||||
|
||||
}
|
||||
},
|
||||
get bindingsPerObject() {
|
||||
|
||||
return scope._bindings.length;
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
Object.assign( AnimationObjectGroup.prototype, {
|
||||
|
||||
isAnimationObjectGroup: true,
|
||||
|
||||
add: function () {
|
||||
|
||||
var objects = this._objects,
|
||||
nObjects = objects.length,
|
||||
nCachedObjects = this.nCachedObjects_,
|
||||
indicesByUUID = this._indicesByUUID,
|
||||
paths = this._paths,
|
||||
parsedPaths = this._parsedPaths,
|
||||
bindings = this._bindings,
|
||||
nBindings = bindings.length,
|
||||
knownObject = undefined;
|
||||
|
||||
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
|
||||
|
||||
var object = arguments[ i ],
|
||||
uuid = object.uuid,
|
||||
index = indicesByUUID[ uuid ];
|
||||
|
||||
if ( index === undefined ) {
|
||||
|
||||
// unknown object -> add it to the ACTIVE region
|
||||
|
||||
index = nObjects ++;
|
||||
indicesByUUID[ uuid ] = index;
|
||||
objects.push( object );
|
||||
|
||||
// accounting is done, now do the same for all bindings
|
||||
|
||||
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
|
||||
|
||||
bindings[ j ].push( new PropertyBinding( object, paths[ j ], parsedPaths[ j ] ) );
|
||||
|
||||
}
|
||||
|
||||
} else if ( index < nCachedObjects ) {
|
||||
|
||||
knownObject = objects[ index ];
|
||||
|
||||
// move existing object to the ACTIVE region
|
||||
|
||||
var firstActiveIndex = -- nCachedObjects,
|
||||
lastCachedObject = objects[ firstActiveIndex ];
|
||||
|
||||
indicesByUUID[ lastCachedObject.uuid ] = index;
|
||||
objects[ index ] = lastCachedObject;
|
||||
|
||||
indicesByUUID[ uuid ] = firstActiveIndex;
|
||||
objects[ firstActiveIndex ] = object;
|
||||
|
||||
// accounting is done, now do the same for all bindings
|
||||
|
||||
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
|
||||
|
||||
var bindingsForPath = bindings[ j ],
|
||||
lastCached = bindingsForPath[ firstActiveIndex ],
|
||||
binding = bindingsForPath[ index ];
|
||||
|
||||
bindingsForPath[ index ] = lastCached;
|
||||
|
||||
if ( binding === undefined ) {
|
||||
|
||||
// since we do not bother to create new bindings
|
||||
// for objects that are cached, the binding may
|
||||
// or may not exist
|
||||
|
||||
binding = new PropertyBinding( object, paths[ j ], parsedPaths[ j ] );
|
||||
|
||||
}
|
||||
|
||||
bindingsForPath[ firstActiveIndex ] = binding;
|
||||
|
||||
}
|
||||
|
||||
} else if ( objects[ index ] !== knownObject ) {
|
||||
|
||||
console.error( 'THREE.AnimationObjectGroup: Different objects with the same UUID ' +
|
||||
'detected. Clean the caches or recreate your infrastructure when reloading scenes.' );
|
||||
|
||||
} // else the object is already where we want it to be
|
||||
|
||||
} // for arguments
|
||||
|
||||
this.nCachedObjects_ = nCachedObjects;
|
||||
|
||||
},
|
||||
|
||||
remove: function () {
|
||||
|
||||
var objects = this._objects,
|
||||
nCachedObjects = this.nCachedObjects_,
|
||||
indicesByUUID = this._indicesByUUID,
|
||||
bindings = this._bindings,
|
||||
nBindings = bindings.length;
|
||||
|
||||
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
|
||||
|
||||
var object = arguments[ i ],
|
||||
uuid = object.uuid,
|
||||
index = indicesByUUID[ uuid ];
|
||||
|
||||
if ( index !== undefined && index >= nCachedObjects ) {
|
||||
|
||||
// move existing object into the CACHED region
|
||||
|
||||
var lastCachedIndex = nCachedObjects ++,
|
||||
firstActiveObject = objects[ lastCachedIndex ];
|
||||
|
||||
indicesByUUID[ firstActiveObject.uuid ] = index;
|
||||
objects[ index ] = firstActiveObject;
|
||||
|
||||
indicesByUUID[ uuid ] = lastCachedIndex;
|
||||
objects[ lastCachedIndex ] = object;
|
||||
|
||||
// accounting is done, now do the same for all bindings
|
||||
|
||||
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
|
||||
|
||||
var bindingsForPath = bindings[ j ],
|
||||
firstActive = bindingsForPath[ lastCachedIndex ],
|
||||
binding = bindingsForPath[ index ];
|
||||
|
||||
bindingsForPath[ index ] = firstActive;
|
||||
bindingsForPath[ lastCachedIndex ] = binding;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} // for arguments
|
||||
|
||||
this.nCachedObjects_ = nCachedObjects;
|
||||
|
||||
},
|
||||
|
||||
// remove & forget
|
||||
uncache: function () {
|
||||
|
||||
var objects = this._objects,
|
||||
nObjects = objects.length,
|
||||
nCachedObjects = this.nCachedObjects_,
|
||||
indicesByUUID = this._indicesByUUID,
|
||||
bindings = this._bindings,
|
||||
nBindings = bindings.length;
|
||||
|
||||
for ( var i = 0, n = arguments.length; i !== n; ++ i ) {
|
||||
|
||||
var object = arguments[ i ],
|
||||
uuid = object.uuid,
|
||||
index = indicesByUUID[ uuid ];
|
||||
|
||||
if ( index !== undefined ) {
|
||||
|
||||
delete indicesByUUID[ uuid ];
|
||||
|
||||
if ( index < nCachedObjects ) {
|
||||
|
||||
// object is cached, shrink the CACHED region
|
||||
|
||||
var firstActiveIndex = -- nCachedObjects,
|
||||
lastCachedObject = objects[ firstActiveIndex ],
|
||||
lastIndex = -- nObjects,
|
||||
lastObject = objects[ lastIndex ];
|
||||
|
||||
// last cached object takes this object's place
|
||||
indicesByUUID[ lastCachedObject.uuid ] = index;
|
||||
objects[ index ] = lastCachedObject;
|
||||
|
||||
// last object goes to the activated slot and pop
|
||||
indicesByUUID[ lastObject.uuid ] = firstActiveIndex;
|
||||
objects[ firstActiveIndex ] = lastObject;
|
||||
objects.pop();
|
||||
|
||||
// accounting is done, now do the same for all bindings
|
||||
|
||||
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
|
||||
|
||||
var bindingsForPath = bindings[ j ],
|
||||
lastCached = bindingsForPath[ firstActiveIndex ],
|
||||
last = bindingsForPath[ lastIndex ];
|
||||
|
||||
bindingsForPath[ index ] = lastCached;
|
||||
bindingsForPath[ firstActiveIndex ] = last;
|
||||
bindingsForPath.pop();
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// object is active, just swap with the last and pop
|
||||
|
||||
var lastIndex = -- nObjects,
|
||||
lastObject = objects[ lastIndex ];
|
||||
|
||||
indicesByUUID[ lastObject.uuid ] = index;
|
||||
objects[ index ] = lastObject;
|
||||
objects.pop();
|
||||
|
||||
// accounting is done, now do the same for all bindings
|
||||
|
||||
for ( var j = 0, m = nBindings; j !== m; ++ j ) {
|
||||
|
||||
var bindingsForPath = bindings[ j ];
|
||||
|
||||
bindingsForPath[ index ] = bindingsForPath[ lastIndex ];
|
||||
bindingsForPath.pop();
|
||||
|
||||
}
|
||||
|
||||
} // cached or active
|
||||
|
||||
} // if object is known
|
||||
|
||||
} // for arguments
|
||||
|
||||
this.nCachedObjects_ = nCachedObjects;
|
||||
|
||||
},
|
||||
|
||||
// Internal interface used by befriended PropertyBinding.Composite:
|
||||
|
||||
subscribe_: function ( path, parsedPath ) {
|
||||
|
||||
// returns an array of bindings for the given path that is changed
|
||||
// according to the contained objects in the group
|
||||
|
||||
var indicesByPath = this._bindingsIndicesByPath,
|
||||
index = indicesByPath[ path ],
|
||||
bindings = this._bindings;
|
||||
|
||||
if ( index !== undefined ) return bindings[ index ];
|
||||
|
||||
var paths = this._paths,
|
||||
parsedPaths = this._parsedPaths,
|
||||
objects = this._objects,
|
||||
nObjects = objects.length,
|
||||
nCachedObjects = this.nCachedObjects_,
|
||||
bindingsForPath = new Array( nObjects );
|
||||
|
||||
index = bindings.length;
|
||||
|
||||
indicesByPath[ path ] = index;
|
||||
|
||||
paths.push( path );
|
||||
parsedPaths.push( parsedPath );
|
||||
bindings.push( bindingsForPath );
|
||||
|
||||
for ( var i = nCachedObjects, n = objects.length; i !== n; ++ i ) {
|
||||
|
||||
var object = objects[ i ];
|
||||
bindingsForPath[ i ] = new PropertyBinding( object, path, parsedPath );
|
||||
|
||||
}
|
||||
|
||||
return bindingsForPath;
|
||||
|
||||
},
|
||||
|
||||
unsubscribe_: function ( path ) {
|
||||
|
||||
// tells the group to forget about a property path and no longer
|
||||
// update the array previously obtained with 'subscribe_'
|
||||
|
||||
var indicesByPath = this._bindingsIndicesByPath,
|
||||
index = indicesByPath[ path ];
|
||||
|
||||
if ( index !== undefined ) {
|
||||
|
||||
var paths = this._paths,
|
||||
parsedPaths = this._parsedPaths,
|
||||
bindings = this._bindings,
|
||||
lastBindingsIndex = bindings.length - 1,
|
||||
lastBindings = bindings[ lastBindingsIndex ],
|
||||
lastBindingsPath = path[ lastBindingsIndex ];
|
||||
|
||||
indicesByPath[ lastBindingsPath ] = index;
|
||||
|
||||
bindings[ index ] = lastBindings;
|
||||
bindings.pop();
|
||||
|
||||
parsedPaths[ index ] = parsedPaths[ lastBindingsIndex ];
|
||||
parsedPaths.pop();
|
||||
|
||||
paths[ index ] = paths[ lastBindingsIndex ];
|
||||
paths.pop();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { AnimationObjectGroup };
|
166
lib/animation/AnimationUtils.js
Normal file
166
lib/animation/AnimationUtils.js
Normal file
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* @author tschw
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
*/
|
||||
|
||||
var AnimationUtils = {
|
||||
|
||||
// same as Array.prototype.slice, but also works on typed arrays
|
||||
arraySlice: function ( array, from, to ) {
|
||||
|
||||
if ( AnimationUtils.isTypedArray( array ) ) {
|
||||
|
||||
// in ios9 array.subarray(from, undefined) will return empty array
|
||||
// but array.subarray(from) or array.subarray(from, len) is correct
|
||||
return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
|
||||
|
||||
}
|
||||
|
||||
return array.slice( from, to );
|
||||
|
||||
},
|
||||
|
||||
// converts an array to a specific type
|
||||
convertArray: function ( array, type, forceClone ) {
|
||||
|
||||
if ( ! array || // let 'undefined' and 'null' pass
|
||||
! forceClone && array.constructor === type ) return array;
|
||||
|
||||
if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
|
||||
|
||||
return new type( array ); // create typed array
|
||||
|
||||
}
|
||||
|
||||
return Array.prototype.slice.call( array ); // create Array
|
||||
|
||||
},
|
||||
|
||||
isTypedArray: function ( object ) {
|
||||
|
||||
return ArrayBuffer.isView( object ) &&
|
||||
! ( object instanceof DataView );
|
||||
|
||||
},
|
||||
|
||||
// returns an array by which times and values can be sorted
|
||||
getKeyframeOrder: function ( times ) {
|
||||
|
||||
function compareTime( i, j ) {
|
||||
|
||||
return times[ i ] - times[ j ];
|
||||
|
||||
}
|
||||
|
||||
var n = times.length;
|
||||
var result = new Array( n );
|
||||
for ( var i = 0; i !== n; ++ i ) result[ i ] = i;
|
||||
|
||||
result.sort( compareTime );
|
||||
|
||||
return result;
|
||||
|
||||
},
|
||||
|
||||
// uses the array previously returned by 'getKeyframeOrder' to sort data
|
||||
sortedArray: function ( values, stride, order ) {
|
||||
|
||||
var nValues = values.length;
|
||||
var result = new values.constructor( nValues );
|
||||
|
||||
for ( var i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
|
||||
|
||||
var srcOffset = order[ i ] * stride;
|
||||
|
||||
for ( var j = 0; j !== stride; ++ j ) {
|
||||
|
||||
result[ dstOffset ++ ] = values[ srcOffset + j ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return result;
|
||||
|
||||
},
|
||||
|
||||
// function for parsing AOS keyframe formats
|
||||
flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
|
||||
|
||||
var i = 1, key = jsonKeys[ 0 ];
|
||||
|
||||
while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
|
||||
|
||||
key = jsonKeys[ i ++ ];
|
||||
|
||||
}
|
||||
|
||||
if ( key === undefined ) return; // no data
|
||||
|
||||
var value = key[ valuePropertyName ];
|
||||
if ( value === undefined ) return; // no data
|
||||
|
||||
if ( Array.isArray( value ) ) {
|
||||
|
||||
do {
|
||||
|
||||
value = key[ valuePropertyName ];
|
||||
|
||||
if ( value !== undefined ) {
|
||||
|
||||
times.push( key.time );
|
||||
values.push.apply( values, value ); // push all elements
|
||||
|
||||
}
|
||||
|
||||
key = jsonKeys[ i ++ ];
|
||||
|
||||
} while ( key !== undefined );
|
||||
|
||||
} else if ( value.toArray !== undefined ) {
|
||||
|
||||
// ...assume THREE.Math-ish
|
||||
|
||||
do {
|
||||
|
||||
value = key[ valuePropertyName ];
|
||||
|
||||
if ( value !== undefined ) {
|
||||
|
||||
times.push( key.time );
|
||||
value.toArray( values, values.length );
|
||||
|
||||
}
|
||||
|
||||
key = jsonKeys[ i ++ ];
|
||||
|
||||
} while ( key !== undefined );
|
||||
|
||||
} else {
|
||||
|
||||
// otherwise push as-is
|
||||
|
||||
do {
|
||||
|
||||
value = key[ valuePropertyName ];
|
||||
|
||||
if ( value !== undefined ) {
|
||||
|
||||
times.push( key.time );
|
||||
values.push( value );
|
||||
|
||||
}
|
||||
|
||||
key = jsonKeys[ i ++ ];
|
||||
|
||||
} while ( key !== undefined );
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
|
||||
export { AnimationUtils };
|
454
lib/animation/KeyframeTrack.js
Normal file
454
lib/animation/KeyframeTrack.js
Normal file
@ -0,0 +1,454 @@
|
||||
import {
|
||||
InterpolateLinear,
|
||||
InterpolateSmooth,
|
||||
InterpolateDiscrete
|
||||
} from '../constants.js';
|
||||
import { CubicInterpolant } from '../math/interpolants/CubicInterpolant.js';
|
||||
import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
|
||||
import { DiscreteInterpolant } from '../math/interpolants/DiscreteInterpolant.js';
|
||||
import { AnimationUtils } from './AnimationUtils.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A timed sequence of keyframes for a specific property.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function KeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
|
||||
if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
|
||||
|
||||
this.name = name;
|
||||
|
||||
this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
|
||||
this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
|
||||
|
||||
this.setInterpolation( interpolation || this.DefaultInterpolation );
|
||||
|
||||
}
|
||||
|
||||
// Static methods
|
||||
|
||||
Object.assign( KeyframeTrack, {
|
||||
|
||||
// Serialization (in static context, because of constructor invocation
|
||||
// and automatic invocation of .toJSON):
|
||||
|
||||
toJSON: function ( track ) {
|
||||
|
||||
var trackType = track.constructor;
|
||||
|
||||
var json;
|
||||
|
||||
// derived classes can define a static toJSON method
|
||||
if ( trackType.toJSON !== undefined ) {
|
||||
|
||||
json = trackType.toJSON( track );
|
||||
|
||||
} else {
|
||||
|
||||
// by default, we assume the data can be serialized as-is
|
||||
json = {
|
||||
|
||||
'name': track.name,
|
||||
'times': AnimationUtils.convertArray( track.times, Array ),
|
||||
'values': AnimationUtils.convertArray( track.values, Array )
|
||||
|
||||
};
|
||||
|
||||
var interpolation = track.getInterpolation();
|
||||
|
||||
if ( interpolation !== track.DefaultInterpolation ) {
|
||||
|
||||
json.interpolation = interpolation;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
json.type = track.ValueTypeName; // mandatory
|
||||
|
||||
return json;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
Object.assign( KeyframeTrack.prototype, {
|
||||
|
||||
constructor: KeyframeTrack,
|
||||
|
||||
TimeBufferType: Float32Array,
|
||||
|
||||
ValueBufferType: Float32Array,
|
||||
|
||||
DefaultInterpolation: InterpolateLinear,
|
||||
|
||||
InterpolantFactoryMethodDiscrete: function ( result ) {
|
||||
|
||||
return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
|
||||
|
||||
},
|
||||
|
||||
InterpolantFactoryMethodLinear: function ( result ) {
|
||||
|
||||
return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
|
||||
|
||||
},
|
||||
|
||||
InterpolantFactoryMethodSmooth: function ( result ) {
|
||||
|
||||
return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
|
||||
|
||||
},
|
||||
|
||||
setInterpolation: function ( interpolation ) {
|
||||
|
||||
var factoryMethod;
|
||||
|
||||
switch ( interpolation ) {
|
||||
|
||||
case InterpolateDiscrete:
|
||||
|
||||
factoryMethod = this.InterpolantFactoryMethodDiscrete;
|
||||
|
||||
break;
|
||||
|
||||
case InterpolateLinear:
|
||||
|
||||
factoryMethod = this.InterpolantFactoryMethodLinear;
|
||||
|
||||
break;
|
||||
|
||||
case InterpolateSmooth:
|
||||
|
||||
factoryMethod = this.InterpolantFactoryMethodSmooth;
|
||||
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( factoryMethod === undefined ) {
|
||||
|
||||
var message = "unsupported interpolation for " +
|
||||
this.ValueTypeName + " keyframe track named " + this.name;
|
||||
|
||||
if ( this.createInterpolant === undefined ) {
|
||||
|
||||
// fall back to default, unless the default itself is messed up
|
||||
if ( interpolation !== this.DefaultInterpolation ) {
|
||||
|
||||
this.setInterpolation( this.DefaultInterpolation );
|
||||
|
||||
} else {
|
||||
|
||||
throw new Error( message ); // fatal, in this case
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
console.warn( 'THREE.KeyframeTrack:', message );
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
this.createInterpolant = factoryMethod;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
getInterpolation: function () {
|
||||
|
||||
switch ( this.createInterpolant ) {
|
||||
|
||||
case this.InterpolantFactoryMethodDiscrete:
|
||||
|
||||
return InterpolateDiscrete;
|
||||
|
||||
case this.InterpolantFactoryMethodLinear:
|
||||
|
||||
return InterpolateLinear;
|
||||
|
||||
case this.InterpolantFactoryMethodSmooth:
|
||||
|
||||
return InterpolateSmooth;
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
getValueSize: function () {
|
||||
|
||||
return this.values.length / this.times.length;
|
||||
|
||||
},
|
||||
|
||||
// move all keyframes either forwards or backwards in time
|
||||
shift: function ( timeOffset ) {
|
||||
|
||||
if ( timeOffset !== 0.0 ) {
|
||||
|
||||
var times = this.times;
|
||||
|
||||
for ( var i = 0, n = times.length; i !== n; ++ i ) {
|
||||
|
||||
times[ i ] += timeOffset;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// scale all keyframe times by a factor (useful for frame <-> seconds conversions)
|
||||
scale: function ( timeScale ) {
|
||||
|
||||
if ( timeScale !== 1.0 ) {
|
||||
|
||||
var times = this.times;
|
||||
|
||||
for ( var i = 0, n = times.length; i !== n; ++ i ) {
|
||||
|
||||
times[ i ] *= timeScale;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// removes keyframes before and after animation without changing any values within the range [startTime, endTime].
|
||||
// IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
|
||||
trim: function ( startTime, endTime ) {
|
||||
|
||||
var times = this.times,
|
||||
nKeys = times.length,
|
||||
from = 0,
|
||||
to = nKeys - 1;
|
||||
|
||||
while ( from !== nKeys && times[ from ] < startTime ) {
|
||||
|
||||
++ from;
|
||||
|
||||
}
|
||||
|
||||
while ( to !== - 1 && times[ to ] > endTime ) {
|
||||
|
||||
-- to;
|
||||
|
||||
}
|
||||
|
||||
++ to; // inclusive -> exclusive bound
|
||||
|
||||
if ( from !== 0 || to !== nKeys ) {
|
||||
|
||||
// empty tracks are forbidden, so keep at least one keyframe
|
||||
if ( from >= to ) to = Math.max( to, 1 ), from = to - 1;
|
||||
|
||||
var stride = this.getValueSize();
|
||||
this.times = AnimationUtils.arraySlice( times, from, to );
|
||||
this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
// ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
|
||||
validate: function () {
|
||||
|
||||
var valid = true;
|
||||
|
||||
var valueSize = this.getValueSize();
|
||||
if ( valueSize - Math.floor( valueSize ) !== 0 ) {
|
||||
|
||||
console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
|
||||
valid = false;
|
||||
|
||||
}
|
||||
|
||||
var times = this.times,
|
||||
values = this.values,
|
||||
|
||||
nKeys = times.length;
|
||||
|
||||
if ( nKeys === 0 ) {
|
||||
|
||||
console.error( 'THREE.KeyframeTrack: Track is empty.', this );
|
||||
valid = false;
|
||||
|
||||
}
|
||||
|
||||
var prevTime = null;
|
||||
|
||||
for ( var i = 0; i !== nKeys; i ++ ) {
|
||||
|
||||
var currTime = times[ i ];
|
||||
|
||||
if ( typeof currTime === 'number' && isNaN( currTime ) ) {
|
||||
|
||||
console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
|
||||
valid = false;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
if ( prevTime !== null && prevTime > currTime ) {
|
||||
|
||||
console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
|
||||
valid = false;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
prevTime = currTime;
|
||||
|
||||
}
|
||||
|
||||
if ( values !== undefined ) {
|
||||
|
||||
if ( AnimationUtils.isTypedArray( values ) ) {
|
||||
|
||||
for ( var i = 0, n = values.length; i !== n; ++ i ) {
|
||||
|
||||
var value = values[ i ];
|
||||
|
||||
if ( isNaN( value ) ) {
|
||||
|
||||
console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
|
||||
valid = false;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return valid;
|
||||
|
||||
},
|
||||
|
||||
// removes equivalent sequential keys as common in morph target sequences
|
||||
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
|
||||
optimize: function () {
|
||||
|
||||
var times = this.times,
|
||||
values = this.values,
|
||||
stride = this.getValueSize(),
|
||||
|
||||
smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
|
||||
|
||||
writeIndex = 1,
|
||||
lastIndex = times.length - 1;
|
||||
|
||||
for ( var i = 1; i < lastIndex; ++ i ) {
|
||||
|
||||
var keep = false;
|
||||
|
||||
var time = times[ i ];
|
||||
var timeNext = times[ i + 1 ];
|
||||
|
||||
// remove adjacent keyframes scheduled at the same time
|
||||
|
||||
if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {
|
||||
|
||||
if ( ! smoothInterpolation ) {
|
||||
|
||||
// remove unnecessary keyframes same as their neighbors
|
||||
|
||||
var offset = i * stride,
|
||||
offsetP = offset - stride,
|
||||
offsetN = offset + stride;
|
||||
|
||||
for ( var j = 0; j !== stride; ++ j ) {
|
||||
|
||||
var value = values[ offset + j ];
|
||||
|
||||
if ( value !== values[ offsetP + j ] ||
|
||||
value !== values[ offsetN + j ] ) {
|
||||
|
||||
keep = true;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
keep = true;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// in-place compaction
|
||||
|
||||
if ( keep ) {
|
||||
|
||||
if ( i !== writeIndex ) {
|
||||
|
||||
times[ writeIndex ] = times[ i ];
|
||||
|
||||
var readOffset = i * stride,
|
||||
writeOffset = writeIndex * stride;
|
||||
|
||||
for ( var j = 0; j !== stride; ++ j ) {
|
||||
|
||||
values[ writeOffset + j ] = values[ readOffset + j ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
++ writeIndex;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// flush last keyframe (compaction looks ahead)
|
||||
|
||||
if ( lastIndex > 0 ) {
|
||||
|
||||
times[ writeIndex ] = times[ lastIndex ];
|
||||
|
||||
for ( var readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
|
||||
|
||||
values[ writeOffset + j ] = values[ readOffset + j ];
|
||||
|
||||
}
|
||||
|
||||
++ writeIndex;
|
||||
|
||||
}
|
||||
|
||||
if ( writeIndex !== times.length ) {
|
||||
|
||||
this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
|
||||
this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
|
||||
|
||||
}
|
||||
|
||||
return this;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
export { KeyframeTrack };
|
726
lib/animation/PropertyBinding.js
Normal file
726
lib/animation/PropertyBinding.js
Normal file
@ -0,0 +1,726 @@
|
||||
/**
|
||||
*
|
||||
* A reference to a real property in the scene graph.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
// Characters [].:/ are reserved for track binding syntax.
|
||||
var RESERVED_CHARS_RE = '\\[\\]\\.:\\/';
|
||||
|
||||
function Composite( targetGroup, path, optionalParsedPath ) {
|
||||
|
||||
var parsedPath = optionalParsedPath || PropertyBinding.parseTrackName( path );
|
||||
|
||||
this._targetGroup = targetGroup;
|
||||
this._bindings = targetGroup.subscribe_( path, parsedPath );
|
||||
|
||||
}
|
||||
|
||||
Object.assign( Composite.prototype, {
|
||||
|
||||
getValue: function ( array, offset ) {
|
||||
|
||||
this.bind(); // bind all binding
|
||||
|
||||
var firstValidIndex = this._targetGroup.nCachedObjects_,
|
||||
binding = this._bindings[ firstValidIndex ];
|
||||
|
||||
// and only call .getValue on the first
|
||||
if ( binding !== undefined ) binding.getValue( array, offset );
|
||||
|
||||
},
|
||||
|
||||
setValue: function ( array, offset ) {
|
||||
|
||||
var bindings = this._bindings;
|
||||
|
||||
for ( var i = this._targetGroup.nCachedObjects_,
|
||||
n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
bindings[ i ].setValue( array, offset );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
bind: function () {
|
||||
|
||||
var bindings = this._bindings;
|
||||
|
||||
for ( var i = this._targetGroup.nCachedObjects_,
|
||||
n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
bindings[ i ].bind();
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
unbind: function () {
|
||||
|
||||
var bindings = this._bindings;
|
||||
|
||||
for ( var i = this._targetGroup.nCachedObjects_,
|
||||
n = bindings.length; i !== n; ++ i ) {
|
||||
|
||||
bindings[ i ].unbind();
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
function PropertyBinding( rootNode, path, parsedPath ) {
|
||||
|
||||
this.path = path;
|
||||
this.parsedPath = parsedPath || PropertyBinding.parseTrackName( path );
|
||||
|
||||
this.node = PropertyBinding.findNode( rootNode, this.parsedPath.nodeName ) || rootNode;
|
||||
|
||||
this.rootNode = rootNode;
|
||||
|
||||
}
|
||||
|
||||
Object.assign( PropertyBinding, {
|
||||
|
||||
Composite: Composite,
|
||||
|
||||
create: function ( root, path, parsedPath ) {
|
||||
|
||||
if ( ! ( root && root.isAnimationObjectGroup ) ) {
|
||||
|
||||
return new PropertyBinding( root, path, parsedPath );
|
||||
|
||||
} else {
|
||||
|
||||
return new PropertyBinding.Composite( root, path, parsedPath );
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* Replaces spaces with underscores and removes unsupported characters from
|
||||
* node names, to ensure compatibility with parseTrackName().
|
||||
*
|
||||
* @param {string} name Node name to be sanitized.
|
||||
* @return {string}
|
||||
*/
|
||||
sanitizeNodeName: ( function () {
|
||||
|
||||
var reservedRe = new RegExp( '[' + RESERVED_CHARS_RE + ']', 'g' );
|
||||
|
||||
return function sanitizeNodeName( name ) {
|
||||
|
||||
return name.replace( /\s/g, '_' ).replace( reservedRe, '' );
|
||||
|
||||
};
|
||||
|
||||
}() ),
|
||||
|
||||
parseTrackName: function () {
|
||||
|
||||
// Attempts to allow node names from any language. ES5's `\w` regexp matches
|
||||
// only latin characters, and the unicode \p{L} is not yet supported. So
|
||||
// instead, we exclude reserved characters and match everything else.
|
||||
var wordChar = '[^' + RESERVED_CHARS_RE + ']';
|
||||
var wordCharOrDot = '[^' + RESERVED_CHARS_RE.replace( '\\.', '' ) + ']';
|
||||
|
||||
// Parent directories, delimited by '/' or ':'. Currently unused, but must
|
||||
// be matched to parse the rest of the track name.
|
||||
var directoryRe = /((?:WC+[\/:])*)/.source.replace( 'WC', wordChar );
|
||||
|
||||
// Target node. May contain word characters (a-zA-Z0-9_) and '.' or '-'.
|
||||
var nodeRe = /(WCOD+)?/.source.replace( 'WCOD', wordCharOrDot );
|
||||
|
||||
// Object on target node, and accessor. May not contain reserved
|
||||
// characters. Accessor may contain any character except closing bracket.
|
||||
var objectRe = /(?:\.(WC+)(?:\[(.+)\])?)?/.source.replace( 'WC', wordChar );
|
||||
|
||||
// Property and accessor. May not contain reserved characters. Accessor may
|
||||
// contain any non-bracket characters.
|
||||
var propertyRe = /\.(WC+)(?:\[(.+)\])?/.source.replace( 'WC', wordChar );
|
||||
|
||||
var trackRe = new RegExp( ''
|
||||
+ '^'
|
||||
+ directoryRe
|
||||
+ nodeRe
|
||||
+ objectRe
|
||||
+ propertyRe
|
||||
+ '$'
|
||||
);
|
||||
|
||||
var supportedObjectNames = [ 'material', 'materials', 'bones' ];
|
||||
|
||||
return function parseTrackName( trackName ) {
|
||||
|
||||
var matches = trackRe.exec( trackName );
|
||||
|
||||
if ( ! matches ) {
|
||||
|
||||
throw new Error( 'PropertyBinding: Cannot parse trackName: ' + trackName );
|
||||
|
||||
}
|
||||
|
||||
var results = {
|
||||
// directoryName: matches[ 1 ], // (tschw) currently unused
|
||||
nodeName: matches[ 2 ],
|
||||
objectName: matches[ 3 ],
|
||||
objectIndex: matches[ 4 ],
|
||||
propertyName: matches[ 5 ], // required
|
||||
propertyIndex: matches[ 6 ]
|
||||
};
|
||||
|
||||
var lastDot = results.nodeName && results.nodeName.lastIndexOf( '.' );
|
||||
|
||||
if ( lastDot !== undefined && lastDot !== - 1 ) {
|
||||
|
||||
var objectName = results.nodeName.substring( lastDot + 1 );
|
||||
|
||||
// Object names must be checked against a whitelist. Otherwise, there
|
||||
// is no way to parse 'foo.bar.baz': 'baz' must be a property, but
|
||||
// 'bar' could be the objectName, or part of a nodeName (which can
|
||||
// include '.' characters).
|
||||
if ( supportedObjectNames.indexOf( objectName ) !== - 1 ) {
|
||||
|
||||
results.nodeName = results.nodeName.substring( 0, lastDot );
|
||||
results.objectName = objectName;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ( results.propertyName === null || results.propertyName.length === 0 ) {
|
||||
|
||||
throw new Error( 'PropertyBinding: can not parse propertyName from trackName: ' + trackName );
|
||||
|
||||
}
|
||||
|
||||
return results;
|
||||
|
||||
};
|
||||
|
||||
}(),
|
||||
|
||||
findNode: function ( root, nodeName ) {
|
||||
|
||||
if ( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === - 1 || nodeName === root.name || nodeName === root.uuid ) {
|
||||
|
||||
return root;
|
||||
|
||||
}
|
||||
|
||||
// search into skeleton bones.
|
||||
if ( root.skeleton ) {
|
||||
|
||||
var bone = root.skeleton.getBoneByName( nodeName );
|
||||
|
||||
if ( bone !== undefined ) {
|
||||
|
||||
return bone;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// search into node subtree.
|
||||
if ( root.children ) {
|
||||
|
||||
var searchNodeSubtree = function ( children ) {
|
||||
|
||||
for ( var i = 0; i < children.length; i ++ ) {
|
||||
|
||||
var childNode = children[ i ];
|
||||
|
||||
if ( childNode.name === nodeName || childNode.uuid === nodeName ) {
|
||||
|
||||
return childNode;
|
||||
|
||||
}
|
||||
|
||||
var result = searchNodeSubtree( childNode.children );
|
||||
|
||||
if ( result ) return result;
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
};
|
||||
|
||||
var subTreeNode = searchNodeSubtree( root.children );
|
||||
|
||||
if ( subTreeNode ) {
|
||||
|
||||
return subTreeNode;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return null;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
Object.assign( PropertyBinding.prototype, { // prototype, continued
|
||||
|
||||
// these are used to "bind" a nonexistent property
|
||||
_getValue_unavailable: function () {},
|
||||
_setValue_unavailable: function () {},
|
||||
|
||||
BindingType: {
|
||||
Direct: 0,
|
||||
EntireArray: 1,
|
||||
ArrayElement: 2,
|
||||
HasFromToArray: 3
|
||||
},
|
||||
|
||||
Versioning: {
|
||||
None: 0,
|
||||
NeedsUpdate: 1,
|
||||
MatrixWorldNeedsUpdate: 2
|
||||
},
|
||||
|
||||
GetterByBindingType: [
|
||||
|
||||
function getValue_direct( buffer, offset ) {
|
||||
|
||||
buffer[ offset ] = this.node[ this.propertyName ];
|
||||
|
||||
},
|
||||
|
||||
function getValue_array( buffer, offset ) {
|
||||
|
||||
var source = this.resolvedProperty;
|
||||
|
||||
for ( var i = 0, n = source.length; i !== n; ++ i ) {
|
||||
|
||||
buffer[ offset ++ ] = source[ i ];
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
function getValue_arrayElement( buffer, offset ) {
|
||||
|
||||
buffer[ offset ] = this.resolvedProperty[ this.propertyIndex ];
|
||||
|
||||
},
|
||||
|
||||
function getValue_toArray( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty.toArray( buffer, offset );
|
||||
|
||||
}
|
||||
|
||||
],
|
||||
|
||||
SetterByBindingTypeAndVersioning: [
|
||||
|
||||
[
|
||||
// Direct
|
||||
|
||||
function setValue_direct( buffer, offset ) {
|
||||
|
||||
this.targetObject[ this.propertyName ] = buffer[ offset ];
|
||||
|
||||
},
|
||||
|
||||
function setValue_direct_setNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.targetObject[ this.propertyName ] = buffer[ offset ];
|
||||
this.targetObject.needsUpdate = true;
|
||||
|
||||
},
|
||||
|
||||
function setValue_direct_setMatrixWorldNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.targetObject[ this.propertyName ] = buffer[ offset ];
|
||||
this.targetObject.matrixWorldNeedsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
], [
|
||||
|
||||
// EntireArray
|
||||
|
||||
function setValue_array( buffer, offset ) {
|
||||
|
||||
var dest = this.resolvedProperty;
|
||||
|
||||
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
|
||||
|
||||
dest[ i ] = buffer[ offset ++ ];
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
function setValue_array_setNeedsUpdate( buffer, offset ) {
|
||||
|
||||
var dest = this.resolvedProperty;
|
||||
|
||||
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
|
||||
|
||||
dest[ i ] = buffer[ offset ++ ];
|
||||
|
||||
}
|
||||
|
||||
this.targetObject.needsUpdate = true;
|
||||
|
||||
},
|
||||
|
||||
function setValue_array_setMatrixWorldNeedsUpdate( buffer, offset ) {
|
||||
|
||||
var dest = this.resolvedProperty;
|
||||
|
||||
for ( var i = 0, n = dest.length; i !== n; ++ i ) {
|
||||
|
||||
dest[ i ] = buffer[ offset ++ ];
|
||||
|
||||
}
|
||||
|
||||
this.targetObject.matrixWorldNeedsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
], [
|
||||
|
||||
// ArrayElement
|
||||
|
||||
function setValue_arrayElement( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
|
||||
|
||||
},
|
||||
|
||||
function setValue_arrayElement_setNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
|
||||
this.targetObject.needsUpdate = true;
|
||||
|
||||
},
|
||||
|
||||
function setValue_arrayElement_setMatrixWorldNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty[ this.propertyIndex ] = buffer[ offset ];
|
||||
this.targetObject.matrixWorldNeedsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
], [
|
||||
|
||||
// HasToFromArray
|
||||
|
||||
function setValue_fromArray( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty.fromArray( buffer, offset );
|
||||
|
||||
},
|
||||
|
||||
function setValue_fromArray_setNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty.fromArray( buffer, offset );
|
||||
this.targetObject.needsUpdate = true;
|
||||
|
||||
},
|
||||
|
||||
function setValue_fromArray_setMatrixWorldNeedsUpdate( buffer, offset ) {
|
||||
|
||||
this.resolvedProperty.fromArray( buffer, offset );
|
||||
this.targetObject.matrixWorldNeedsUpdate = true;
|
||||
|
||||
}
|
||||
|
||||
]
|
||||
|
||||
],
|
||||
|
||||
getValue: function getValue_unbound( targetArray, offset ) {
|
||||
|
||||
this.bind();
|
||||
this.getValue( targetArray, offset );
|
||||
|
||||
// Note: This class uses a State pattern on a per-method basis:
|
||||
// 'bind' sets 'this.getValue' / 'setValue' and shadows the
|
||||
// prototype version of these methods with one that represents
|
||||
// the bound state. When the property is not found, the methods
|
||||
// become no-ops.
|
||||
|
||||
},
|
||||
|
||||
setValue: function getValue_unbound( sourceArray, offset ) {
|
||||
|
||||
this.bind();
|
||||
this.setValue( sourceArray, offset );
|
||||
|
||||
},
|
||||
|
||||
// create getter / setter pair for a property in the scene graph
|
||||
bind: function () {
|
||||
|
||||
var targetObject = this.node,
|
||||
parsedPath = this.parsedPath,
|
||||
|
||||
objectName = parsedPath.objectName,
|
||||
propertyName = parsedPath.propertyName,
|
||||
propertyIndex = parsedPath.propertyIndex;
|
||||
|
||||
if ( ! targetObject ) {
|
||||
|
||||
targetObject = PropertyBinding.findNode( this.rootNode, parsedPath.nodeName ) || this.rootNode;
|
||||
|
||||
this.node = targetObject;
|
||||
|
||||
}
|
||||
|
||||
// set fail state so we can just 'return' on error
|
||||
this.getValue = this._getValue_unavailable;
|
||||
this.setValue = this._setValue_unavailable;
|
||||
|
||||
// ensure there is a value node
|
||||
if ( ! targetObject ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Trying to update node for track: ' + this.path + ' but it wasn\'t found.' );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( objectName ) {
|
||||
|
||||
var objectIndex = parsedPath.objectIndex;
|
||||
|
||||
// special cases were we need to reach deeper into the hierarchy to get the face materials....
|
||||
switch ( objectName ) {
|
||||
|
||||
case 'materials':
|
||||
|
||||
if ( ! targetObject.material ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to material as node does not have a material.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( ! targetObject.material.materials ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to material.materials as node.material does not have a materials array.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
targetObject = targetObject.material.materials;
|
||||
|
||||
break;
|
||||
|
||||
case 'bones':
|
||||
|
||||
if ( ! targetObject.skeleton ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to bones as node does not have a skeleton.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// potential future optimization: skip this if propertyIndex is already an integer
|
||||
// and convert the integer string to a true integer.
|
||||
|
||||
targetObject = targetObject.skeleton.bones;
|
||||
|
||||
// support resolving morphTarget names into indices.
|
||||
for ( var i = 0; i < targetObject.length; i ++ ) {
|
||||
|
||||
if ( targetObject[ i ].name === objectIndex ) {
|
||||
|
||||
objectIndex = i;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
break;
|
||||
|
||||
default:
|
||||
|
||||
if ( targetObject[ objectName ] === undefined ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to objectName of node undefined.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
targetObject = targetObject[ objectName ];
|
||||
|
||||
}
|
||||
|
||||
|
||||
if ( objectIndex !== undefined ) {
|
||||
|
||||
if ( targetObject[ objectIndex ] === undefined ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Trying to bind to objectIndex of objectName, but is undefined.', this, targetObject );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
targetObject = targetObject[ objectIndex ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// resolve property
|
||||
var nodeProperty = targetObject[ propertyName ];
|
||||
|
||||
if ( nodeProperty === undefined ) {
|
||||
|
||||
var nodeName = parsedPath.nodeName;
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Trying to update property for track: ' + nodeName +
|
||||
'.' + propertyName + ' but it wasn\'t found.', targetObject );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
// determine versioning scheme
|
||||
var versioning = this.Versioning.None;
|
||||
|
||||
this.targetObject = targetObject;
|
||||
|
||||
if ( targetObject.needsUpdate !== undefined ) { // material
|
||||
|
||||
versioning = this.Versioning.NeedsUpdate;
|
||||
|
||||
} else if ( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
|
||||
|
||||
versioning = this.Versioning.MatrixWorldNeedsUpdate;
|
||||
|
||||
}
|
||||
|
||||
// determine how the property gets bound
|
||||
var bindingType = this.BindingType.Direct;
|
||||
|
||||
if ( propertyIndex !== undefined ) {
|
||||
|
||||
// access a sub element of the property array (only primitives are supported right now)
|
||||
|
||||
if ( propertyName === "morphTargetInfluences" ) {
|
||||
|
||||
// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
|
||||
|
||||
// support resolving morphTarget names into indices.
|
||||
if ( ! targetObject.geometry ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
if ( targetObject.geometry.isBufferGeometry ) {
|
||||
|
||||
if ( ! targetObject.geometry.morphAttributes ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphAttributes.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 0; i < this.node.geometry.morphAttributes.position.length; i ++ ) {
|
||||
|
||||
if ( targetObject.geometry.morphAttributes.position[ i ].name === propertyIndex ) {
|
||||
|
||||
propertyIndex = i;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
} else {
|
||||
|
||||
if ( ! targetObject.geometry.morphTargets ) {
|
||||
|
||||
console.error( 'THREE.PropertyBinding: Can not bind to morphTargetInfluences because node does not have a geometry.morphTargets.', this );
|
||||
return;
|
||||
|
||||
}
|
||||
|
||||
for ( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
|
||||
|
||||
if ( targetObject.geometry.morphTargets[ i ].name === propertyIndex ) {
|
||||
|
||||
propertyIndex = i;
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bindingType = this.BindingType.ArrayElement;
|
||||
|
||||
this.resolvedProperty = nodeProperty;
|
||||
this.propertyIndex = propertyIndex;
|
||||
|
||||
} else if ( nodeProperty.fromArray !== undefined && nodeProperty.toArray !== undefined ) {
|
||||
|
||||
// must use copy for Object3D.Euler/Quaternion
|
||||
|
||||
bindingType = this.BindingType.HasFromToArray;
|
||||
|
||||
this.resolvedProperty = nodeProperty;
|
||||
|
||||
} else if ( Array.isArray( nodeProperty ) ) {
|
||||
|
||||
bindingType = this.BindingType.EntireArray;
|
||||
|
||||
this.resolvedProperty = nodeProperty;
|
||||
|
||||
} else {
|
||||
|
||||
this.propertyName = propertyName;
|
||||
|
||||
}
|
||||
|
||||
// select getter / setter
|
||||
this.getValue = this.GetterByBindingType[ bindingType ];
|
||||
this.setValue = this.SetterByBindingTypeAndVersioning[ bindingType ][ versioning ];
|
||||
|
||||
},
|
||||
|
||||
unbind: function () {
|
||||
|
||||
this.node = null;
|
||||
|
||||
// back to the prototype version of getValue / setValue
|
||||
// note: avoiding to mutate the shape of 'this' via 'delete'
|
||||
this.getValue = this._getValue_unbound;
|
||||
this.setValue = this._setValue_unbound;
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
//!\ DECLARE ALIAS AFTER assign prototype !
|
||||
Object.assign( PropertyBinding.prototype, {
|
||||
|
||||
// initial state of these methods that calls 'bind'
|
||||
_getValue_unbound: PropertyBinding.prototype.getValue,
|
||||
_setValue_unbound: PropertyBinding.prototype.setValue,
|
||||
|
||||
} );
|
||||
|
||||
export { PropertyBinding };
|
209
lib/animation/PropertyMixer.js
Normal file
209
lib/animation/PropertyMixer.js
Normal file
@ -0,0 +1,209 @@
|
||||
import { Quaternion } from '../math/Quaternion.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* Buffered scene graph property that allows weighted accumulation.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function PropertyMixer( binding, typeName, valueSize ) {
|
||||
|
||||
this.binding = binding;
|
||||
this.valueSize = valueSize;
|
||||
|
||||
var bufferType = Float64Array,
|
||||
mixFunction;
|
||||
|
||||
switch ( typeName ) {
|
||||
|
||||
case 'quaternion':
|
||||
mixFunction = this._slerp;
|
||||
break;
|
||||
|
||||
case 'string':
|
||||
case 'bool':
|
||||
bufferType = Array;
|
||||
mixFunction = this._select;
|
||||
break;
|
||||
|
||||
default:
|
||||
mixFunction = this._lerp;
|
||||
|
||||
}
|
||||
|
||||
this.buffer = new bufferType( valueSize * 4 );
|
||||
// layout: [ incoming | accu0 | accu1 | orig ]
|
||||
//
|
||||
// interpolators can use .buffer as their .result
|
||||
// the data then goes to 'incoming'
|
||||
//
|
||||
// 'accu0' and 'accu1' are used frame-interleaved for
|
||||
// the cumulative result and are compared to detect
|
||||
// changes
|
||||
//
|
||||
// 'orig' stores the original state of the property
|
||||
|
||||
this._mixBufferRegion = mixFunction;
|
||||
|
||||
this.cumulativeWeight = 0;
|
||||
|
||||
this.useCount = 0;
|
||||
this.referenceCount = 0;
|
||||
|
||||
}
|
||||
|
||||
Object.assign( PropertyMixer.prototype, {
|
||||
|
||||
// accumulate data in the 'incoming' region into 'accu<i>'
|
||||
accumulate: function ( accuIndex, weight ) {
|
||||
|
||||
// note: happily accumulating nothing when weight = 0, the caller knows
|
||||
// the weight and shouldn't have made the call in the first place
|
||||
|
||||
var buffer = this.buffer,
|
||||
stride = this.valueSize,
|
||||
offset = accuIndex * stride + stride,
|
||||
|
||||
currentWeight = this.cumulativeWeight;
|
||||
|
||||
if ( currentWeight === 0 ) {
|
||||
|
||||
// accuN := incoming * weight
|
||||
|
||||
for ( var i = 0; i !== stride; ++ i ) {
|
||||
|
||||
buffer[ offset + i ] = buffer[ i ];
|
||||
|
||||
}
|
||||
|
||||
currentWeight = weight;
|
||||
|
||||
} else {
|
||||
|
||||
// accuN := accuN + incoming * weight
|
||||
|
||||
currentWeight += weight;
|
||||
var mix = weight / currentWeight;
|
||||
this._mixBufferRegion( buffer, offset, 0, mix, stride );
|
||||
|
||||
}
|
||||
|
||||
this.cumulativeWeight = currentWeight;
|
||||
|
||||
},
|
||||
|
||||
// apply the state of 'accu<i>' to the binding when accus differ
|
||||
apply: function ( accuIndex ) {
|
||||
|
||||
var stride = this.valueSize,
|
||||
buffer = this.buffer,
|
||||
offset = accuIndex * stride + stride,
|
||||
|
||||
weight = this.cumulativeWeight,
|
||||
|
||||
binding = this.binding;
|
||||
|
||||
this.cumulativeWeight = 0;
|
||||
|
||||
if ( weight < 1 ) {
|
||||
|
||||
// accuN := accuN + original * ( 1 - cumulativeWeight )
|
||||
|
||||
var originalValueOffset = stride * 3;
|
||||
|
||||
this._mixBufferRegion(
|
||||
buffer, offset, originalValueOffset, 1 - weight, stride );
|
||||
|
||||
}
|
||||
|
||||
for ( var i = stride, e = stride + stride; i !== e; ++ i ) {
|
||||
|
||||
if ( buffer[ i ] !== buffer[ i + stride ] ) {
|
||||
|
||||
// value has changed -> update scene graph
|
||||
|
||||
binding.setValue( buffer, offset );
|
||||
break;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
// remember the state of the bound property and copy it to both accus
|
||||
saveOriginalState: function () {
|
||||
|
||||
var binding = this.binding;
|
||||
|
||||
var buffer = this.buffer,
|
||||
stride = this.valueSize,
|
||||
|
||||
originalValueOffset = stride * 3;
|
||||
|
||||
binding.getValue( buffer, originalValueOffset );
|
||||
|
||||
// accu[0..1] := orig -- initially detect changes against the original
|
||||
for ( var i = stride, e = originalValueOffset; i !== e; ++ i ) {
|
||||
|
||||
buffer[ i ] = buffer[ originalValueOffset + ( i % stride ) ];
|
||||
|
||||
}
|
||||
|
||||
this.cumulativeWeight = 0;
|
||||
|
||||
},
|
||||
|
||||
// apply the state previously taken via 'saveOriginalState' to the binding
|
||||
restoreOriginalState: function () {
|
||||
|
||||
var originalValueOffset = this.valueSize * 3;
|
||||
this.binding.setValue( this.buffer, originalValueOffset );
|
||||
|
||||
},
|
||||
|
||||
|
||||
// mix functions
|
||||
|
||||
_select: function ( buffer, dstOffset, srcOffset, t, stride ) {
|
||||
|
||||
if ( t >= 0.5 ) {
|
||||
|
||||
for ( var i = 0; i !== stride; ++ i ) {
|
||||
|
||||
buffer[ dstOffset + i ] = buffer[ srcOffset + i ];
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
},
|
||||
|
||||
_slerp: function ( buffer, dstOffset, srcOffset, t ) {
|
||||
|
||||
Quaternion.slerpFlat( buffer, dstOffset, buffer, dstOffset, buffer, srcOffset, t );
|
||||
|
||||
},
|
||||
|
||||
_lerp: function ( buffer, dstOffset, srcOffset, t, stride ) {
|
||||
|
||||
var s = 1 - t;
|
||||
|
||||
for ( var i = 0; i !== stride; ++ i ) {
|
||||
|
||||
var j = dstOffset + i;
|
||||
|
||||
buffer[ j ] = buffer[ j ] * s + buffer[ srcOffset + i ] * t;
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
} );
|
||||
|
||||
|
||||
export { PropertyMixer };
|
38
lib/animation/tracks/BooleanKeyframeTrack.js
Normal file
38
lib/animation/tracks/BooleanKeyframeTrack.js
Normal file
@ -0,0 +1,38 @@
|
||||
import { InterpolateDiscrete } from '../../constants.js';
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track of Boolean keyframe values.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function BooleanKeyframeTrack( name, times, values ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values );
|
||||
|
||||
}
|
||||
|
||||
BooleanKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: BooleanKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'bool',
|
||||
ValueBufferType: Array,
|
||||
|
||||
DefaultInterpolation: InterpolateDiscrete,
|
||||
|
||||
InterpolantFactoryMethodLinear: undefined,
|
||||
InterpolantFactoryMethodSmooth: undefined
|
||||
|
||||
// Note: Actually this track could have a optimized / compressed
|
||||
// representation of a single value and a custom interpolant that
|
||||
// computes "firstValue ^ isOdd( index )".
|
||||
|
||||
} );
|
||||
|
||||
export { BooleanKeyframeTrack };
|
34
lib/animation/tracks/ColorKeyframeTrack.js
Normal file
34
lib/animation/tracks/ColorKeyframeTrack.js
Normal file
@ -0,0 +1,34 @@
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track of keyframe values that represent color.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function ColorKeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values, interpolation );
|
||||
|
||||
}
|
||||
|
||||
ColorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: ColorKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'color'
|
||||
|
||||
// ValueBufferType is inherited
|
||||
|
||||
// DefaultInterpolation is inherited
|
||||
|
||||
// Note: Very basic implementation and nothing special yet.
|
||||
// However, this is the place for color space parameterization.
|
||||
|
||||
} );
|
||||
|
||||
export { ColorKeyframeTrack };
|
30
lib/animation/tracks/NumberKeyframeTrack.js
Normal file
30
lib/animation/tracks/NumberKeyframeTrack.js
Normal file
@ -0,0 +1,30 @@
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track of numeric keyframe values.
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function NumberKeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values, interpolation );
|
||||
|
||||
}
|
||||
|
||||
NumberKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: NumberKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'number'
|
||||
|
||||
// ValueBufferType is inherited
|
||||
|
||||
// DefaultInterpolation is inherited
|
||||
|
||||
} );
|
||||
|
||||
export { NumberKeyframeTrack };
|
40
lib/animation/tracks/QuaternionKeyframeTrack.js
Normal file
40
lib/animation/tracks/QuaternionKeyframeTrack.js
Normal file
@ -0,0 +1,40 @@
|
||||
import { InterpolateLinear } from '../../constants.js';
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
import { QuaternionLinearInterpolant } from '../../math/interpolants/QuaternionLinearInterpolant.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track of quaternion keyframe values.
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function QuaternionKeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values, interpolation );
|
||||
|
||||
}
|
||||
|
||||
QuaternionKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: QuaternionKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'quaternion',
|
||||
|
||||
// ValueBufferType is inherited
|
||||
|
||||
DefaultInterpolation: InterpolateLinear,
|
||||
|
||||
InterpolantFactoryMethodLinear: function ( result ) {
|
||||
|
||||
return new QuaternionLinearInterpolant( this.times, this.values, this.getValueSize(), result );
|
||||
|
||||
},
|
||||
|
||||
InterpolantFactoryMethodSmooth: undefined // not yet implemented
|
||||
|
||||
} );
|
||||
|
||||
export { QuaternionKeyframeTrack };
|
35
lib/animation/tracks/StringKeyframeTrack.js
Normal file
35
lib/animation/tracks/StringKeyframeTrack.js
Normal file
@ -0,0 +1,35 @@
|
||||
import { InterpolateDiscrete } from '../../constants.js';
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track that interpolates Strings
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function StringKeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values, interpolation );
|
||||
|
||||
}
|
||||
|
||||
StringKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: StringKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'string',
|
||||
ValueBufferType: Array,
|
||||
|
||||
DefaultInterpolation: InterpolateDiscrete,
|
||||
|
||||
InterpolantFactoryMethodLinear: undefined,
|
||||
|
||||
InterpolantFactoryMethodSmooth: undefined
|
||||
|
||||
} );
|
||||
|
||||
export { StringKeyframeTrack };
|
31
lib/animation/tracks/VectorKeyframeTrack.js
Normal file
31
lib/animation/tracks/VectorKeyframeTrack.js
Normal file
@ -0,0 +1,31 @@
|
||||
import { KeyframeTrack } from '../KeyframeTrack.js';
|
||||
|
||||
/**
|
||||
*
|
||||
* A Track of vectored keyframe values.
|
||||
*
|
||||
*
|
||||
* @author Ben Houston / http://clara.io/
|
||||
* @author David Sarno / http://lighthaus.us/
|
||||
* @author tschw
|
||||
*/
|
||||
|
||||
function VectorKeyframeTrack( name, times, values, interpolation ) {
|
||||
|
||||
KeyframeTrack.call( this, name, times, values, interpolation );
|
||||
|
||||
}
|
||||
|
||||
VectorKeyframeTrack.prototype = Object.assign( Object.create( KeyframeTrack.prototype ), {
|
||||
|
||||
constructor: VectorKeyframeTrack,
|
||||
|
||||
ValueTypeName: 'vector'
|
||||
|
||||
// ValueBufferType is inherited
|
||||
|
||||
// DefaultInterpolation is inherited
|
||||
|
||||
} );
|
||||
|
||||
export { VectorKeyframeTrack };
|
Reference in New Issue
Block a user