📄 KeyframeTrack.js
¶
📊 Analysis Summary¶
Metric | Count |
---|---|
🔧 Functions | 13 |
🧱 Classes | 1 |
📦 Imports | 6 |
📊 Variables & Constants | 31 |
📚 Table of Contents¶
🛠️ File Location:¶
📂 src/animation/KeyframeTrack.js
📦 Imports¶
Name | Source |
---|---|
InterpolateLinear |
../constants.js |
InterpolateSmooth |
../constants.js |
InterpolateDiscrete |
../constants.js |
CubicInterpolant |
../math/interpolants/CubicInterpolant.js |
LinearInterpolant |
../math/interpolants/LinearInterpolant.js |
DiscreteInterpolant |
../math/interpolants/DiscreteInterpolant.js |
Variables & Constants¶
Name | Type | Kind | Value | Exported |
---|---|---|---|---|
trackType |
Function |
let/var | track.constructor |
✗ |
json |
any |
let/var | *not shown* |
✗ |
factoryMethod |
any |
let/var | *not shown* |
✗ |
message |
string |
let/var | 'unsupported interpolation for ' + this.ValueTypeName + ' keyframe track name... |
✗ |
times |
Float32Array<ArrayBufferLike> |
let/var | this.times |
✗ |
times |
Float32Array<ArrayBufferLike> |
let/var | this.times |
✗ |
times |
Float32Array<ArrayBufferLike> |
let/var | this.times |
✗ |
nKeys |
number |
let/var | times.length |
✗ |
from |
number |
let/var | 0 |
✗ |
to |
number |
let/var | nKeys - 1 |
✗ |
valid |
boolean |
let/var | true |
✗ |
times |
Float32Array<ArrayBufferLike> |
let/var | this.times |
✗ |
values |
Float32Array<ArrayBufferLike> |
let/var | this.values |
✗ |
nKeys |
number |
let/var | times.length |
✗ |
prevTime |
any |
let/var | null |
✗ |
currTime |
number |
let/var | times[ i ] |
✗ |
value |
number |
let/var | values[ i ] |
✗ |
smoothInterpolation |
boolean |
let/var | this.getInterpolation() === InterpolateSmooth |
✗ |
lastIndex |
number |
let/var | times.length - 1 |
✗ |
writeIndex |
number |
let/var | 1 |
✗ |
keep |
boolean |
let/var | false |
✗ |
time |
number |
let/var | times[ i ] |
✗ |
timeNext |
number |
let/var | times[ i + 1 ] |
✗ |
offset |
number |
let/var | i * stride |
✗ |
offsetP |
number |
let/var | offset - stride |
✗ |
offsetN |
number |
let/var | offset + stride |
✗ |
value |
number |
let/var | values[ offset + j ] |
✗ |
readOffset |
number |
let/var | i * stride |
✗ |
writeOffset |
number |
let/var | writeIndex * stride |
✗ |
TypedKeyframeTrack |
Function |
let/var | this.constructor |
✗ |
track |
any |
let/var | new TypedKeyframeTrack( this.name, times, values ) |
✗ |
Functions¶
KeyframeTrack.toJSON(track: KeyframeTrack): any
¶
JSDoc:
/**
* Converts the keyframe track to JSON.
*
* @static
* @param {KeyframeTrack} track - The keyframe track to serialize.
* @return {Object} The serialized keyframe track as JSON.
*/
Parameters:
track
KeyframeTrack
Returns: any
Calls:
trackType.toJSON
AnimationUtils.convertArray
track.getInterpolation
Internal Comments:
// derived classes can define a static toJSON method
// by default, we assume the data can be serialized as-is (x3)
Code
static toJSON( track ) {
const trackType = track.constructor;
let json;
// derived classes can define a static toJSON method
if ( trackType.toJSON !== this.toJSON ) {
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 )
};
const interpolation = track.getInterpolation();
if ( interpolation !== track.DefaultInterpolation ) {
json.interpolation = interpolation;
}
}
json.type = track.ValueTypeName; // mandatory
return json;
}
KeyframeTrack.InterpolantFactoryMethodDiscrete(result: TypedArray): DiscreteInterpolant
¶
JSDoc:
/**
* Factory method for creating a new discrete interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {DiscreteInterpolant} The new interpolant.
*/
Parameters:
result
TypedArray
Returns: DiscreteInterpolant
Calls:
this.getValueSize
Code
KeyframeTrack.InterpolantFactoryMethodLinear(result: TypedArray): LinearInterpolant
¶
JSDoc:
/**
* Factory method for creating a new linear interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {LinearInterpolant} The new interpolant.
*/
Parameters:
result
TypedArray
Returns: LinearInterpolant
Calls:
this.getValueSize
Code
KeyframeTrack.InterpolantFactoryMethodSmooth(result: TypedArray): CubicInterpolant
¶
JSDoc:
/**
* Factory method for creating a new smooth interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {CubicInterpolant} The new interpolant.
*/
Parameters:
result
TypedArray
Returns: CubicInterpolant
Calls:
this.getValueSize
Code
KeyframeTrack.setInterpolation(interpolation: number): KeyframeTrack
¶
JSDoc:
/**
* Defines the interpolation factor method for this keyframe track.
*
* @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
Parameters:
interpolation
number
Returns: KeyframeTrack
Calls:
this.setInterpolation
console.warn
Internal Comments:
Code
setInterpolation( interpolation ) {
let factoryMethod;
switch ( interpolation ) {
case InterpolateDiscrete:
factoryMethod = this.InterpolantFactoryMethodDiscrete;
break;
case InterpolateLinear:
factoryMethod = this.InterpolantFactoryMethodLinear;
break;
case InterpolateSmooth:
factoryMethod = this.InterpolantFactoryMethodSmooth;
break;
}
if ( factoryMethod === undefined ) {
const 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;
}
KeyframeTrack.getInterpolation(): number
¶
JSDoc:
/**
* Returns the current interpolation type.
*
* @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type.
*/
Returns: number
Code
KeyframeTrack.getValueSize(): number
¶
JSDoc:
Returns: number
KeyframeTrack.shift(timeOffset: number): KeyframeTrack
¶
JSDoc:
/**
* Moves all keyframes either forward or backward in time.
*
* @param {number} timeOffset - The offset to move the time values.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
Parameters:
timeOffset
number
Returns: KeyframeTrack
Code
KeyframeTrack.scale(timeScale: number): KeyframeTrack
¶
JSDoc:
/**
* Scale all keyframe times by a factor (useful for frame - seconds conversions).
*
* @param {number} timeScale - The time scale.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
Parameters:
timeScale
number
Returns: KeyframeTrack
Code
KeyframeTrack.trim(startTime: number, endTime: number): KeyframeTrack
¶
JSDoc:
/**
* Removes keyframes before and after animation without changing any values within the defined time range.
*
* Note: The method does not shift around keys to the start of the track time, because for interpolated
* keys this will change their values
*
* @param {number} startTime - The start time.
* @param {number} endTime - The end time.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
Parameters:
startTime
number
endTime
number
Returns: KeyframeTrack
Calls:
Math.max
this.getValueSize
times.slice
this.values.slice
Internal Comments:
Code
trim( startTime, endTime ) {
const times = this.times,
nKeys = times.length;
let 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;
}
const stride = this.getValueSize();
this.times = times.slice( from, to );
this.values = this.values.slice( from * stride, to * stride );
}
return this;
}
KeyframeTrack.validate(): boolean
¶
JSDoc:
/**
* Performs minimal validation on the keyframe track. Returns `true` if the values
* are valid.
*
* @return {boolean} Whether the keyframes are valid or not.
*/
Returns: boolean
Calls:
this.getValueSize
Math.floor
console.error
isNaN
AnimationUtils.isTypedArray
Code
validate() {
let valid = true;
const valueSize = this.getValueSize();
if ( valueSize - Math.floor( valueSize ) !== 0 ) {
console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
valid = false;
}
const times = this.times,
values = this.values,
nKeys = times.length;
if ( nKeys === 0 ) {
console.error( 'THREE.KeyframeTrack: Track is empty.', this );
valid = false;
}
let prevTime = null;
for ( let i = 0; i !== nKeys; i ++ ) {
const 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 ( let i = 0, n = values.length; i !== n; ++ i ) {
const value = values[ i ];
if ( isNaN( value ) ) {
console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
valid = false;
break;
}
}
}
}
return valid;
}
KeyframeTrack.optimize(): AnimationClip
¶
JSDoc:
/**
* Optimizes this keyframe track by removing equivalent sequential keys (which are
* common in morph target sequences).
*
* @return {AnimationClip} A reference to this animation clip.
*/
Returns: AnimationClip
Calls:
this.times.slice
this.values.slice
this.getValueSize
this.getInterpolation
times.slice
values.slice
Internal Comments:
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0) (x2)
// times or values may be shared with other tracks, so overwriting is unsafe (x2)
// remove adjacent keyframes scheduled at the same time
// remove unnecessary keyframes same as their neighbors (x2)
// in-place compaction
// flush last keyframe (compaction looks ahead)
Code
optimize() {
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
// times or values may be shared with other tracks, so overwriting is unsafe
const times = this.times.slice(),
values = this.values.slice(),
stride = this.getValueSize(),
smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
lastIndex = times.length - 1;
let writeIndex = 1;
for ( let i = 1; i < lastIndex; ++ i ) {
let keep = false;
const time = times[ i ];
const timeNext = times[ i + 1 ];
// remove adjacent keyframes scheduled at the same time
if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
if ( ! smoothInterpolation ) {
// remove unnecessary keyframes same as their neighbors
const offset = i * stride,
offsetP = offset - stride,
offsetN = offset + stride;
for ( let j = 0; j !== stride; ++ j ) {
const 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 ];
const readOffset = i * stride,
writeOffset = writeIndex * stride;
for ( let 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 ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
values[ writeOffset + j ] = values[ readOffset + j ];
}
++ writeIndex;
}
if ( writeIndex !== times.length ) {
this.times = times.slice( 0, writeIndex );
this.values = values.slice( 0, writeIndex * stride );
} else {
this.times = times;
this.values = values;
}
return this;
}
KeyframeTrack.clone(): KeyframeTrack
¶
JSDoc:
/**
* Returns a new keyframe track with copied values from this instance.
*
* @return {KeyframeTrack} A clone of this instance.
*/
Returns: KeyframeTrack
Calls:
this.times.slice
this.values.slice
Internal Comments:
Code
clone() {
const times = this.times.slice();
const values = this.values.slice();
const TypedKeyframeTrack = this.constructor;
const track = new TypedKeyframeTrack( this.name, times, values );
// Interpolant argument to constructor is not saved, so copy the factory method directly.
track.createInterpolant = this.createInterpolant;
return track;
}
Classes¶
KeyframeTrack
¶
Class Code
class KeyframeTrack {
/**
* Constructs a new keyframe track.
*
* @param {string} name - The keyframe track's name.
* @param {Array<number>} times - A list of keyframe times.
* @param {Array<number|string|boolean>} values - A list of keyframe values.
* @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} [interpolation] - The interpolation type.
*/
constructor( 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 );
/**
* The track's name can refer to morph targets or bones or
* possibly other values within an animated object. See {@link PropertyBinding#parseTrackName}
* for the forms of strings that can be parsed for property binding.
*
* @type {string}
*/
this.name = name;
/**
* The keyframe times.
*
* @type {Float32Array}
*/
this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
/**
* The keyframe values.
*
* @type {Float32Array}
*/
this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
this.setInterpolation( interpolation || this.DefaultInterpolation );
}
/**
* Converts the keyframe track to JSON.
*
* @static
* @param {KeyframeTrack} track - The keyframe track to serialize.
* @return {Object} The serialized keyframe track as JSON.
*/
static toJSON( track ) {
const trackType = track.constructor;
let json;
// derived classes can define a static toJSON method
if ( trackType.toJSON !== this.toJSON ) {
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 )
};
const interpolation = track.getInterpolation();
if ( interpolation !== track.DefaultInterpolation ) {
json.interpolation = interpolation;
}
}
json.type = track.ValueTypeName; // mandatory
return json;
}
/**
* Factory method for creating a new discrete interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {DiscreteInterpolant} The new interpolant.
*/
InterpolantFactoryMethodDiscrete( result ) {
return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
}
/**
* Factory method for creating a new linear interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {LinearInterpolant} The new interpolant.
*/
InterpolantFactoryMethodLinear( result ) {
return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
}
/**
* Factory method for creating a new smooth interpolant.
*
* @static
* @param {TypedArray} [result] - The result buffer.
* @return {CubicInterpolant} The new interpolant.
*/
InterpolantFactoryMethodSmooth( result ) {
return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
}
/**
* Defines the interpolation factor method for this keyframe track.
*
* @param {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} interpolation - The interpolation type.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
setInterpolation( interpolation ) {
let factoryMethod;
switch ( interpolation ) {
case InterpolateDiscrete:
factoryMethod = this.InterpolantFactoryMethodDiscrete;
break;
case InterpolateLinear:
factoryMethod = this.InterpolantFactoryMethodLinear;
break;
case InterpolateSmooth:
factoryMethod = this.InterpolantFactoryMethodSmooth;
break;
}
if ( factoryMethod === undefined ) {
const 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;
}
/**
* Returns the current interpolation type.
*
* @return {(InterpolateLinear|InterpolateDiscrete|InterpolateSmooth)} The interpolation type.
*/
getInterpolation() {
switch ( this.createInterpolant ) {
case this.InterpolantFactoryMethodDiscrete:
return InterpolateDiscrete;
case this.InterpolantFactoryMethodLinear:
return InterpolateLinear;
case this.InterpolantFactoryMethodSmooth:
return InterpolateSmooth;
}
}
/**
* Returns the value size.
*
* @return {number} The value size.
*/
getValueSize() {
return this.values.length / this.times.length;
}
/**
* Moves all keyframes either forward or backward in time.
*
* @param {number} timeOffset - The offset to move the time values.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
shift( timeOffset ) {
if ( timeOffset !== 0.0 ) {
const times = this.times;
for ( let 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).
*
* @param {number} timeScale - The time scale.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
scale( timeScale ) {
if ( timeScale !== 1.0 ) {
const times = this.times;
for ( let 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 defined time range.
*
* Note: The method does not shift around keys to the start of the track time, because for interpolated
* keys this will change their values
*
* @param {number} startTime - The start time.
* @param {number} endTime - The end time.
* @return {KeyframeTrack} A reference to this keyframe track.
*/
trim( startTime, endTime ) {
const times = this.times,
nKeys = times.length;
let 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;
}
const stride = this.getValueSize();
this.times = times.slice( from, to );
this.values = this.values.slice( from * stride, to * stride );
}
return this;
}
/**
* Performs minimal validation on the keyframe track. Returns `true` if the values
* are valid.
*
* @return {boolean} Whether the keyframes are valid or not.
*/
validate() {
let valid = true;
const valueSize = this.getValueSize();
if ( valueSize - Math.floor( valueSize ) !== 0 ) {
console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
valid = false;
}
const times = this.times,
values = this.values,
nKeys = times.length;
if ( nKeys === 0 ) {
console.error( 'THREE.KeyframeTrack: Track is empty.', this );
valid = false;
}
let prevTime = null;
for ( let i = 0; i !== nKeys; i ++ ) {
const 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 ( let i = 0, n = values.length; i !== n; ++ i ) {
const value = values[ i ];
if ( isNaN( value ) ) {
console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
valid = false;
break;
}
}
}
}
return valid;
}
/**
* Optimizes this keyframe track by removing equivalent sequential keys (which are
* common in morph target sequences).
*
* @return {AnimationClip} A reference to this animation clip.
*/
optimize() {
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
// times or values may be shared with other tracks, so overwriting is unsafe
const times = this.times.slice(),
values = this.values.slice(),
stride = this.getValueSize(),
smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
lastIndex = times.length - 1;
let writeIndex = 1;
for ( let i = 1; i < lastIndex; ++ i ) {
let keep = false;
const time = times[ i ];
const timeNext = times[ i + 1 ];
// remove adjacent keyframes scheduled at the same time
if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
if ( ! smoothInterpolation ) {
// remove unnecessary keyframes same as their neighbors
const offset = i * stride,
offsetP = offset - stride,
offsetN = offset + stride;
for ( let j = 0; j !== stride; ++ j ) {
const 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 ];
const readOffset = i * stride,
writeOffset = writeIndex * stride;
for ( let 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 ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
values[ writeOffset + j ] = values[ readOffset + j ];
}
++ writeIndex;
}
if ( writeIndex !== times.length ) {
this.times = times.slice( 0, writeIndex );
this.values = values.slice( 0, writeIndex * stride );
} else {
this.times = times;
this.values = values;
}
return this;
}
/**
* Returns a new keyframe track with copied values from this instance.
*
* @return {KeyframeTrack} A clone of this instance.
*/
clone() {
const times = this.times.slice();
const values = this.values.slice();
const TypedKeyframeTrack = this.constructor;
const track = new TypedKeyframeTrack( this.name, times, values );
// Interpolant argument to constructor is not saved, so copy the factory method directly.
track.createInterpolant = this.createInterpolant;
return track;
}
}
Methods¶
toJSON(track: KeyframeTrack): any
¶
Code
static toJSON( track ) {
const trackType = track.constructor;
let json;
// derived classes can define a static toJSON method
if ( trackType.toJSON !== this.toJSON ) {
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 )
};
const interpolation = track.getInterpolation();
if ( interpolation !== track.DefaultInterpolation ) {
json.interpolation = interpolation;
}
}
json.type = track.ValueTypeName; // mandatory
return json;
}
InterpolantFactoryMethodDiscrete(result: TypedArray): DiscreteInterpolant
¶
Code
InterpolantFactoryMethodLinear(result: TypedArray): LinearInterpolant
¶
Code
InterpolantFactoryMethodSmooth(result: TypedArray): CubicInterpolant
¶
Code
setInterpolation(interpolation: number): KeyframeTrack
¶
Code
setInterpolation( interpolation ) {
let factoryMethod;
switch ( interpolation ) {
case InterpolateDiscrete:
factoryMethod = this.InterpolantFactoryMethodDiscrete;
break;
case InterpolateLinear:
factoryMethod = this.InterpolantFactoryMethodLinear;
break;
case InterpolateSmooth:
factoryMethod = this.InterpolantFactoryMethodSmooth;
break;
}
if ( factoryMethod === undefined ) {
const 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(): number
¶
Code
getValueSize(): number
¶
shift(timeOffset: number): KeyframeTrack
¶
Code
scale(timeScale: number): KeyframeTrack
¶
Code
trim(startTime: number, endTime: number): KeyframeTrack
¶
Code
trim( startTime, endTime ) {
const times = this.times,
nKeys = times.length;
let 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;
}
const stride = this.getValueSize();
this.times = times.slice( from, to );
this.values = this.values.slice( from * stride, to * stride );
}
return this;
}
validate(): boolean
¶
Code
validate() {
let valid = true;
const valueSize = this.getValueSize();
if ( valueSize - Math.floor( valueSize ) !== 0 ) {
console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
valid = false;
}
const times = this.times,
values = this.values,
nKeys = times.length;
if ( nKeys === 0 ) {
console.error( 'THREE.KeyframeTrack: Track is empty.', this );
valid = false;
}
let prevTime = null;
for ( let i = 0; i !== nKeys; i ++ ) {
const 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 ( let i = 0, n = values.length; i !== n; ++ i ) {
const value = values[ i ];
if ( isNaN( value ) ) {
console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
valid = false;
break;
}
}
}
}
return valid;
}
optimize(): AnimationClip
¶
Code
optimize() {
// (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
// times or values may be shared with other tracks, so overwriting is unsafe
const times = this.times.slice(),
values = this.values.slice(),
stride = this.getValueSize(),
smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
lastIndex = times.length - 1;
let writeIndex = 1;
for ( let i = 1; i < lastIndex; ++ i ) {
let keep = false;
const time = times[ i ];
const timeNext = times[ i + 1 ];
// remove adjacent keyframes scheduled at the same time
if ( time !== timeNext && ( i !== 1 || time !== times[ 0 ] ) ) {
if ( ! smoothInterpolation ) {
// remove unnecessary keyframes same as their neighbors
const offset = i * stride,
offsetP = offset - stride,
offsetN = offset + stride;
for ( let j = 0; j !== stride; ++ j ) {
const 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 ];
const readOffset = i * stride,
writeOffset = writeIndex * stride;
for ( let 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 ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
values[ writeOffset + j ] = values[ readOffset + j ];
}
++ writeIndex;
}
if ( writeIndex !== times.length ) {
this.times = times.slice( 0, writeIndex );
this.values = values.slice( 0, writeIndex * stride );
} else {
this.times = times;
this.values = values;
}
return this;
}
clone(): KeyframeTrack
¶
Code
clone() {
const times = this.times.slice();
const values = this.values.slice();
const TypedKeyframeTrack = this.constructor;
const track = new TypedKeyframeTrack( this.name, times, values );
// Interpolant argument to constructor is not saved, so copy the factory method directly.
track.createInterpolant = this.createInterpolant;
return track;
}