|
@@ -1,343 +1,339 @@
|
|
|
import { Quaternion } from '../math/Quaternion.js';
|
|
|
import { AdditiveAnimationBlendMode } from '../constants.js';
|
|
|
|
|
|
-const AnimationUtils = {
|
|
|
+// same as Array.prototype.slice, but also works on typed arrays
|
|
|
+function arraySlice( array, from, to ) {
|
|
|
|
|
|
- // same as Array.prototype.slice, but also works on typed arrays
|
|
|
- arraySlice: function ( array, from, to ) {
|
|
|
+ if ( isTypedArray( array ) ) {
|
|
|
|
|
|
- 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 ) );
|
|
|
|
|
|
- // 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 );
|
|
|
+ return array.slice( from, to );
|
|
|
|
|
|
- },
|
|
|
+}
|
|
|
|
|
|
- // converts an array to a specific type
|
|
|
- convertArray: function ( array, type, forceClone ) {
|
|
|
+// converts an array to a specific type
|
|
|
+function convertArray( array, type, forceClone ) {
|
|
|
|
|
|
- if ( ! array || // let 'undefined' and 'null' pass
|
|
|
- ! forceClone && array.constructor === type ) return array;
|
|
|
+ if ( ! array || // let 'undefined' and 'null' pass
|
|
|
+ ! forceClone && array.constructor === type ) return array;
|
|
|
|
|
|
- if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
|
|
|
+ if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
|
|
|
|
|
|
- return new type( array ); // create typed array
|
|
|
+ return new type( array ); // create typed array
|
|
|
|
|
|
- }
|
|
|
-
|
|
|
- return Array.prototype.slice.call( array ); // create Array
|
|
|
+ }
|
|
|
|
|
|
- },
|
|
|
+ return Array.prototype.slice.call( array ); // create Array
|
|
|
|
|
|
- isTypedArray: function ( object ) {
|
|
|
+}
|
|
|
|
|
|
- return ArrayBuffer.isView( object ) &&
|
|
|
- ! ( object instanceof DataView );
|
|
|
+function isTypedArray( 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 ) {
|
|
|
+// returns an array by which times and values can be sorted
|
|
|
+function getKeyframeOrder( times ) {
|
|
|
|
|
|
- return times[ i ] - times[ j ];
|
|
|
+ function compareTime( i, j ) {
|
|
|
|
|
|
- }
|
|
|
+ return times[ i ] - times[ j ];
|
|
|
|
|
|
- const n = times.length;
|
|
|
- const result = new Array( n );
|
|
|
- for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
|
|
|
+ }
|
|
|
|
|
|
- result.sort( compareTime );
|
|
|
+ const n = times.length;
|
|
|
+ const result = new Array( n );
|
|
|
+ for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
|
|
|
|
|
|
- return result;
|
|
|
+ result.sort( compareTime );
|
|
|
|
|
|
- },
|
|
|
+ return result;
|
|
|
|
|
|
- // uses the array previously returned by 'getKeyframeOrder' to sort data
|
|
|
- sortedArray: function ( values, stride, order ) {
|
|
|
+}
|
|
|
|
|
|
- const nValues = values.length;
|
|
|
- const result = new values.constructor( nValues );
|
|
|
+// uses the array previously returned by 'getKeyframeOrder' to sort data
|
|
|
+function sortedArray( values, stride, order ) {
|
|
|
|
|
|
- for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
|
|
|
+ const nValues = values.length;
|
|
|
+ const result = new values.constructor( nValues );
|
|
|
|
|
|
- const srcOffset = order[ i ] * stride;
|
|
|
+ for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
|
|
|
|
|
|
- for ( let j = 0; j !== stride; ++ j ) {
|
|
|
+ const srcOffset = order[ i ] * stride;
|
|
|
|
|
|
- result[ dstOffset ++ ] = values[ srcOffset + j ];
|
|
|
+ for ( let j = 0; j !== stride; ++ j ) {
|
|
|
|
|
|
- }
|
|
|
+ result[ dstOffset ++ ] = values[ srcOffset + j ];
|
|
|
|
|
|
}
|
|
|
|
|
|
- return result;
|
|
|
+ }
|
|
|
|
|
|
- },
|
|
|
+ return result;
|
|
|
|
|
|
- // function for parsing AOS keyframe formats
|
|
|
- flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
|
|
|
+}
|
|
|
|
|
|
- let i = 1, key = jsonKeys[ 0 ];
|
|
|
+// function for parsing AOS keyframe formats
|
|
|
+function flattenJSON( jsonKeys, times, values, valuePropertyName ) {
|
|
|
|
|
|
- while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
|
|
|
+ let i = 1, key = jsonKeys[ 0 ];
|
|
|
|
|
|
- key = jsonKeys[ i ++ ];
|
|
|
+ while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
|
|
|
|
|
|
- }
|
|
|
+ key = jsonKeys[ i ++ ];
|
|
|
|
|
|
- if ( key === undefined ) return; // no data
|
|
|
+ }
|
|
|
|
|
|
- let value = key[ valuePropertyName ];
|
|
|
- if ( value === undefined ) return; // no data
|
|
|
+ if ( key === undefined ) return; // no data
|
|
|
|
|
|
- if ( Array.isArray( value ) ) {
|
|
|
+ let value = key[ valuePropertyName ];
|
|
|
+ if ( value === undefined ) return; // no data
|
|
|
|
|
|
- do {
|
|
|
+ if ( Array.isArray( value ) ) {
|
|
|
|
|
|
- value = key[ valuePropertyName ];
|
|
|
+ do {
|
|
|
|
|
|
- if ( value !== undefined ) {
|
|
|
+ value = key[ valuePropertyName ];
|
|
|
|
|
|
- times.push( key.time );
|
|
|
- values.push.apply( values, value ); // push all elements
|
|
|
+ if ( value !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
+ times.push( key.time );
|
|
|
+ values.push.apply( values, value ); // push all elements
|
|
|
|
|
|
- key = jsonKeys[ i ++ ];
|
|
|
+ }
|
|
|
|
|
|
- } while ( key !== undefined );
|
|
|
+ key = jsonKeys[ i ++ ];
|
|
|
|
|
|
- } else if ( value.toArray !== undefined ) {
|
|
|
+ } while ( key !== undefined );
|
|
|
|
|
|
- // ...assume THREE.Math-ish
|
|
|
+ } else if ( value.toArray !== undefined ) {
|
|
|
|
|
|
- do {
|
|
|
+ // ...assume THREE.Math-ish
|
|
|
|
|
|
- value = key[ valuePropertyName ];
|
|
|
+ do {
|
|
|
|
|
|
- if ( value !== undefined ) {
|
|
|
+ value = key[ valuePropertyName ];
|
|
|
|
|
|
- times.push( key.time );
|
|
|
- value.toArray( values, values.length );
|
|
|
+ if ( value !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
+ times.push( key.time );
|
|
|
+ value.toArray( values, values.length );
|
|
|
|
|
|
- key = jsonKeys[ i ++ ];
|
|
|
+ }
|
|
|
|
|
|
- } while ( key !== undefined );
|
|
|
+ key = jsonKeys[ i ++ ];
|
|
|
|
|
|
- } else {
|
|
|
+ } while ( key !== undefined );
|
|
|
|
|
|
- // otherwise push as-is
|
|
|
+ } else {
|
|
|
|
|
|
- do {
|
|
|
+ // otherwise push as-is
|
|
|
|
|
|
- value = key[ valuePropertyName ];
|
|
|
+ do {
|
|
|
|
|
|
- if ( value !== undefined ) {
|
|
|
+ value = key[ valuePropertyName ];
|
|
|
|
|
|
- times.push( key.time );
|
|
|
- values.push( value );
|
|
|
+ if ( value !== undefined ) {
|
|
|
|
|
|
- }
|
|
|
+ times.push( key.time );
|
|
|
+ values.push( value );
|
|
|
|
|
|
- key = jsonKeys[ i ++ ];
|
|
|
+ }
|
|
|
|
|
|
- } while ( key !== undefined );
|
|
|
+ key = jsonKeys[ i ++ ];
|
|
|
|
|
|
- }
|
|
|
+ } while ( key !== undefined );
|
|
|
|
|
|
- },
|
|
|
+ }
|
|
|
|
|
|
- subclip: function ( sourceClip, name, startFrame, endFrame, fps = 30 ) {
|
|
|
+}
|
|
|
|
|
|
- const clip = sourceClip.clone();
|
|
|
+function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) {
|
|
|
|
|
|
- clip.name = name;
|
|
|
+ const clip = sourceClip.clone();
|
|
|
|
|
|
- const tracks = [];
|
|
|
+ clip.name = name;
|
|
|
|
|
|
- for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
+ const tracks = [];
|
|
|
|
|
|
- const track = clip.tracks[ i ];
|
|
|
- const valueSize = track.getValueSize();
|
|
|
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
|
|
|
- const times = [];
|
|
|
- const values = [];
|
|
|
+ const track = clip.tracks[ i ];
|
|
|
+ const valueSize = track.getValueSize();
|
|
|
|
|
|
- for ( let j = 0; j < track.times.length; ++ j ) {
|
|
|
+ const times = [];
|
|
|
+ const values = [];
|
|
|
|
|
|
- const frame = track.times[ j ] * fps;
|
|
|
+ for ( let j = 0; j < track.times.length; ++ j ) {
|
|
|
|
|
|
- if ( frame < startFrame || frame >= endFrame ) continue;
|
|
|
+ const frame = track.times[ j ] * fps;
|
|
|
|
|
|
- times.push( track.times[ j ] );
|
|
|
+ if ( frame < startFrame || frame >= endFrame ) continue;
|
|
|
|
|
|
- for ( let k = 0; k < valueSize; ++ k ) {
|
|
|
+ times.push( track.times[ j ] );
|
|
|
|
|
|
- values.push( track.values[ j * valueSize + k ] );
|
|
|
+ for ( let k = 0; k < valueSize; ++ k ) {
|
|
|
|
|
|
- }
|
|
|
+ values.push( track.values[ j * valueSize + k ] );
|
|
|
|
|
|
}
|
|
|
|
|
|
- if ( times.length === 0 ) continue;
|
|
|
+ }
|
|
|
|
|
|
- track.times = AnimationUtils.convertArray( times, track.times.constructor );
|
|
|
- track.values = AnimationUtils.convertArray( values, track.values.constructor );
|
|
|
+ if ( times.length === 0 ) continue;
|
|
|
|
|
|
- tracks.push( track );
|
|
|
+ track.times = convertArray( times, track.times.constructor );
|
|
|
+ track.values = convertArray( values, track.values.constructor );
|
|
|
|
|
|
- }
|
|
|
+ tracks.push( track );
|
|
|
|
|
|
- clip.tracks = tracks;
|
|
|
+ }
|
|
|
|
|
|
- // find minimum .times value across all tracks in the trimmed clip
|
|
|
+ clip.tracks = tracks;
|
|
|
|
|
|
- let minStartTime = Infinity;
|
|
|
+ // find minimum .times value across all tracks in the trimmed clip
|
|
|
|
|
|
- for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
+ let minStartTime = Infinity;
|
|
|
|
|
|
- if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
|
|
|
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
|
|
|
- minStartTime = clip.tracks[ i ].times[ 0 ];
|
|
|
+ if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
|
|
|
|
|
|
- }
|
|
|
+ minStartTime = clip.tracks[ i ].times[ 0 ];
|
|
|
|
|
|
}
|
|
|
|
|
|
- // shift all tracks such that clip begins at t=0
|
|
|
+ }
|
|
|
|
|
|
- for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
+ // shift all tracks such that clip begins at t=0
|
|
|
|
|
|
- clip.tracks[ i ].shift( - 1 * minStartTime );
|
|
|
+ for ( let i = 0; i < clip.tracks.length; ++ i ) {
|
|
|
|
|
|
- }
|
|
|
+ clip.tracks[ i ].shift( - 1 * minStartTime );
|
|
|
|
|
|
- clip.resetDuration();
|
|
|
+ }
|
|
|
|
|
|
- return clip;
|
|
|
+ clip.resetDuration();
|
|
|
|
|
|
- },
|
|
|
+ return clip;
|
|
|
|
|
|
- makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
|
|
|
+}
|
|
|
|
|
|
- if ( fps <= 0 ) fps = 30;
|
|
|
+function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
|
|
|
|
|
|
- const numTracks = referenceClip.tracks.length;
|
|
|
- const referenceTime = referenceFrame / fps;
|
|
|
+ if ( fps <= 0 ) fps = 30;
|
|
|
|
|
|
- // Make each track's values relative to the values at the reference frame
|
|
|
- for ( let i = 0; i < numTracks; ++ i ) {
|
|
|
+ const numTracks = referenceClip.tracks.length;
|
|
|
+ const referenceTime = referenceFrame / fps;
|
|
|
|
|
|
- const referenceTrack = referenceClip.tracks[ i ];
|
|
|
- const referenceTrackType = referenceTrack.ValueTypeName;
|
|
|
+ // Make each track's values relative to the values at the reference frame
|
|
|
+ for ( let i = 0; i < numTracks; ++ i ) {
|
|
|
|
|
|
- // Skip this track if it's non-numeric
|
|
|
- if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
|
|
|
+ const referenceTrack = referenceClip.tracks[ i ];
|
|
|
+ const referenceTrackType = referenceTrack.ValueTypeName;
|
|
|
|
|
|
- // Find the track in the target clip whose name and type matches the reference track
|
|
|
- const targetTrack = targetClip.tracks.find( function ( track ) {
|
|
|
+ // Skip this track if it's non-numeric
|
|
|
+ if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
|
|
|
|
|
|
- return track.name === referenceTrack.name
|
|
|
- && track.ValueTypeName === referenceTrackType;
|
|
|
+ // Find the track in the target clip whose name and type matches the reference track
|
|
|
+ const targetTrack = targetClip.tracks.find( function ( track ) {
|
|
|
|
|
|
- } );
|
|
|
+ return track.name === referenceTrack.name
|
|
|
+ && track.ValueTypeName === referenceTrackType;
|
|
|
|
|
|
- if ( targetTrack === undefined ) continue;
|
|
|
+ } );
|
|
|
|
|
|
- let referenceOffset = 0;
|
|
|
- const referenceValueSize = referenceTrack.getValueSize();
|
|
|
+ if ( targetTrack === undefined ) continue;
|
|
|
|
|
|
- if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
|
|
+ let referenceOffset = 0;
|
|
|
+ const referenceValueSize = referenceTrack.getValueSize();
|
|
|
|
|
|
- referenceOffset = referenceValueSize / 3;
|
|
|
+ if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
|
|
|
|
|
- }
|
|
|
+ referenceOffset = referenceValueSize / 3;
|
|
|
|
|
|
- let targetOffset = 0;
|
|
|
- const targetValueSize = targetTrack.getValueSize();
|
|
|
+ }
|
|
|
|
|
|
- if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
|
|
+ let targetOffset = 0;
|
|
|
+ const targetValueSize = targetTrack.getValueSize();
|
|
|
|
|
|
- targetOffset = targetValueSize / 3;
|
|
|
+ if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
|
|
|
|
|
|
- }
|
|
|
+ targetOffset = targetValueSize / 3;
|
|
|
|
|
|
- const lastIndex = referenceTrack.times.length - 1;
|
|
|
- let referenceValue;
|
|
|
+ }
|
|
|
|
|
|
- // Find the value to subtract out of the track
|
|
|
- if ( referenceTime <= referenceTrack.times[ 0 ] ) {
|
|
|
+ const lastIndex = referenceTrack.times.length - 1;
|
|
|
+ let referenceValue;
|
|
|
|
|
|
- // Reference frame is earlier than the first keyframe, so just use the first keyframe
|
|
|
- const startIndex = referenceOffset;
|
|
|
- const endIndex = referenceValueSize - referenceOffset;
|
|
|
- referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
|
|
|
+ // Find the value to subtract out of the track
|
|
|
+ if ( referenceTime <= referenceTrack.times[ 0 ] ) {
|
|
|
|
|
|
- } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
|
|
|
+ // Reference frame is earlier than the first keyframe, so just use the first keyframe
|
|
|
+ const startIndex = referenceOffset;
|
|
|
+ const endIndex = referenceValueSize - referenceOffset;
|
|
|
+ referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex );
|
|
|
|
|
|
- // Reference frame is after the last keyframe, so just use the last keyframe
|
|
|
- const startIndex = lastIndex * referenceValueSize + referenceOffset;
|
|
|
- const endIndex = startIndex + referenceValueSize - referenceOffset;
|
|
|
- referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
|
|
|
+ } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
|
|
|
|
|
|
- } else {
|
|
|
+ // Reference frame is after the last keyframe, so just use the last keyframe
|
|
|
+ const startIndex = lastIndex * referenceValueSize + referenceOffset;
|
|
|
+ const endIndex = startIndex + referenceValueSize - referenceOffset;
|
|
|
+ referenceValue = arraySlice( referenceTrack.values, startIndex, endIndex );
|
|
|
|
|
|
- // Interpolate to the reference value
|
|
|
- const interpolant = referenceTrack.createInterpolant();
|
|
|
- const startIndex = referenceOffset;
|
|
|
- const endIndex = referenceValueSize - referenceOffset;
|
|
|
- interpolant.evaluate( referenceTime );
|
|
|
- referenceValue = AnimationUtils.arraySlice( interpolant.resultBuffer, startIndex, endIndex );
|
|
|
+ } else {
|
|
|
|
|
|
- }
|
|
|
+ // Interpolate to the reference value
|
|
|
+ const interpolant = referenceTrack.createInterpolant();
|
|
|
+ const startIndex = referenceOffset;
|
|
|
+ const endIndex = referenceValueSize - referenceOffset;
|
|
|
+ interpolant.evaluate( referenceTime );
|
|
|
+ referenceValue = arraySlice( interpolant.resultBuffer, startIndex, endIndex );
|
|
|
|
|
|
- // Conjugate the quaternion
|
|
|
- if ( referenceTrackType === 'quaternion' ) {
|
|
|
+ }
|
|
|
|
|
|
- const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
|
|
|
- referenceQuat.toArray( referenceValue );
|
|
|
+ // Conjugate the quaternion
|
|
|
+ if ( referenceTrackType === 'quaternion' ) {
|
|
|
|
|
|
- }
|
|
|
+ const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
|
|
|
+ referenceQuat.toArray( referenceValue );
|
|
|
|
|
|
- // Subtract the reference value from all of the track values
|
|
|
+ }
|
|
|
|
|
|
- const numTimes = targetTrack.times.length;
|
|
|
- for ( let j = 0; j < numTimes; ++ j ) {
|
|
|
+ // Subtract the reference value from all of the track values
|
|
|
|
|
|
- const valueStart = j * targetValueSize + targetOffset;
|
|
|
+ const numTimes = targetTrack.times.length;
|
|
|
+ for ( let j = 0; j < numTimes; ++ j ) {
|
|
|
|
|
|
- if ( referenceTrackType === 'quaternion' ) {
|
|
|
+ const valueStart = j * targetValueSize + targetOffset;
|
|
|
|
|
|
- // Multiply the conjugate for quaternion track types
|
|
|
- Quaternion.multiplyQuaternionsFlat(
|
|
|
- targetTrack.values,
|
|
|
- valueStart,
|
|
|
- referenceValue,
|
|
|
- 0,
|
|
|
- targetTrack.values,
|
|
|
- valueStart
|
|
|
- );
|
|
|
+ if ( referenceTrackType === 'quaternion' ) {
|
|
|
|
|
|
- } else {
|
|
|
+ // Multiply the conjugate for quaternion track types
|
|
|
+ Quaternion.multiplyQuaternionsFlat(
|
|
|
+ targetTrack.values,
|
|
|
+ valueStart,
|
|
|
+ referenceValue,
|
|
|
+ 0,
|
|
|
+ targetTrack.values,
|
|
|
+ valueStart
|
|
|
+ );
|
|
|
|
|
|
- const valueEnd = targetValueSize - targetOffset * 2;
|
|
|
+ } else {
|
|
|
|
|
|
- // Subtract each value for all other numeric track types
|
|
|
- for ( let k = 0; k < valueEnd; ++ k ) {
|
|
|
+ const valueEnd = targetValueSize - targetOffset * 2;
|
|
|
|
|
|
- targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
|
|
|
+ // Subtract each value for all other numeric track types
|
|
|
+ for ( let k = 0; k < valueEnd; ++ k ) {
|
|
|
|
|
|
- }
|
|
|
+ targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
|
|
|
|
|
|
}
|
|
|
|
|
@@ -345,12 +341,21 @@ const AnimationUtils = {
|
|
|
|
|
|
}
|
|
|
|
|
|
- targetClip.blendMode = AdditiveAnimationBlendMode;
|
|
|
+ }
|
|
|
+
|
|
|
+ targetClip.blendMode = AdditiveAnimationBlendMode;
|
|
|
|
|
|
- return targetClip;
|
|
|
+ return targetClip;
|
|
|
|
|
|
- }
|
|
|
+}
|
|
|
|
|
|
+export {
|
|
|
+ arraySlice,
|
|
|
+ convertArray,
|
|
|
+ isTypedArray,
|
|
|
+ getKeyframeOrder,
|
|
|
+ sortedArray,
|
|
|
+ flattenJSON,
|
|
|
+ subclip,
|
|
|
+ makeClipAdditive
|
|
|
};
|
|
|
-
|
|
|
-export { AnimationUtils };
|