| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495 |
- import { Quaternion } from '../math/Quaternion.js';
- import { AdditiveAnimationBlendMode } from '../constants.js';
- import { isTypedArray } from '../utils.js';
- /**
- * Converts an array to a specific type.
- *
- * @param {TypedArray|Array} array - The array to convert.
- * @param {TypedArray.constructor} type - The constructor of a typed array that defines the new type.
- * @return {TypedArray} The converted array.
- */
- function convertArray( array, type ) {
- if ( ! array || 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
- }
- /**
- * Returns an array by which times and values can be sorted.
- *
- * @param {Array<number>} times - The keyframe time values.
- * @return {Array<number>} The array.
- */
- function getKeyframeOrder( times ) {
- 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 );
- return result;
- }
- /**
- * Sorts the given array by the previously computed order via `getKeyframeOrder()`.
- *
- * @param {Array<number>} values - The values to sort.
- * @param {number} stride - The stride.
- * @param {Array<number>} order - The sort order.
- * @return {Array<number>} The sorted values.
- */
- function sortedArray( values, stride, order ) {
- const nValues = values.length;
- const result = new values.constructor( nValues );
- for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
- const srcOffset = order[ i ] * stride;
- for ( let j = 0; j !== stride; ++ j ) {
- result[ dstOffset ++ ] = values[ srcOffset + j ];
- }
- }
- return result;
- }
- /**
- * Used for parsing AOS keyframe formats.
- *
- * @param {Array<number>} jsonKeys - A list of JSON keyframes.
- * @param {Array<number>} times - This array will be filled with keyframe times by this function.
- * @param {Array<number>} values - This array will be filled with keyframe values by this function.
- * @param {string} valuePropertyName - The name of the property to use.
- */
- function flattenJSON( jsonKeys, times, values, valuePropertyName ) {
- let i = 1, key = jsonKeys[ 0 ];
- 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 ( Array.isArray( value ) ) {
- do {
- value = key[ valuePropertyName ];
- if ( value !== undefined ) {
- times.push( key.time );
- values.push( ...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 );
- }
- }
- /**
- * Creates a new clip, containing only the segment of the original clip between the given frames.
- *
- * @param {AnimationClip} sourceClip - The values to sort.
- * @param {string} name - The name of the clip.
- * @param {number} startFrame - The start frame.
- * @param {number} endFrame - The end frame.
- * @param {number} [fps=30] - The FPS.
- * @return {AnimationClip} The new sub clip.
- */
- function subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) {
- const clip = sourceClip.clone();
- clip.name = name;
- const tracks = [];
- for ( let i = 0; i < clip.tracks.length; ++ i ) {
- const track = clip.tracks[ i ];
- const valueSize = track.getValueSize();
- const times = [];
- const values = [];
- for ( let j = 0; j < track.times.length; ++ j ) {
- const frame = track.times[ j ] * fps;
- if ( frame < startFrame || frame >= endFrame ) continue;
- times.push( track.times[ j ] );
- for ( let k = 0; k < valueSize; ++ k ) {
- values.push( track.values[ j * valueSize + k ] );
- }
- }
- if ( times.length === 0 ) continue;
- 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
- let minStartTime = Infinity;
- for ( let i = 0; i < clip.tracks.length; ++ i ) {
- 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 ) {
- clip.tracks[ i ].shift( - 1 * minStartTime );
- }
- clip.resetDuration();
- return clip;
- }
- /**
- * Converts the keyframes of the given animation clip to an additive format.
- *
- * @param {AnimationClip} targetClip - The clip to make additive.
- * @param {number} [referenceFrame=0] - The reference frame.
- * @param {AnimationClip} [referenceClip=targetClip] - The reference clip.
- * @param {number} [fps=30] - The FPS.
- * @return {AnimationClip} The updated clip which is now additive.
- */
- function makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
- if ( fps <= 0 ) fps = 30;
- const numTracks = referenceClip.tracks.length;
- const referenceTime = referenceFrame / fps;
- // Make each track's values relative to the values at the reference frame
- for ( let i = 0; i < numTracks; ++ i ) {
- const referenceTrack = referenceClip.tracks[ i ];
- const referenceTrackType = referenceTrack.ValueTypeName;
- // Skip this track if it's non-numeric
- if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
- // 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 ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
- referenceOffset = referenceValueSize / 3;
- }
- let targetOffset = 0;
- const targetValueSize = targetTrack.getValueSize();
- 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 ] ) {
- // Reference frame is earlier than the first keyframe, so just use the first keyframe
- const startIndex = referenceOffset;
- const endIndex = referenceValueSize - referenceOffset;
- referenceValue = referenceTrack.values.slice( startIndex, endIndex );
- } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
- // Reference frame is after the last keyframe, so just use the last keyframe
- const startIndex = lastIndex * referenceValueSize + referenceOffset;
- const endIndex = startIndex + referenceValueSize - referenceOffset;
- referenceValue = referenceTrack.values.slice( startIndex, endIndex );
- } else {
- // Interpolate to the reference value
- const interpolant = referenceTrack.createInterpolant();
- const startIndex = referenceOffset;
- const endIndex = referenceValueSize - referenceOffset;
- interpolant.evaluate( referenceTime );
- referenceValue = interpolant.resultBuffer.slice( startIndex, endIndex );
- }
- // 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 ) {
- const valueStart = j * targetValueSize + targetOffset;
- if ( referenceTrackType === 'quaternion' ) {
- // Multiply the conjugate for quaternion track types
- Quaternion.multiplyQuaternionsFlat(
- targetTrack.values,
- valueStart,
- referenceValue,
- 0,
- targetTrack.values,
- valueStart
- );
- } else {
- const valueEnd = targetValueSize - targetOffset * 2;
- // Subtract each value for all other numeric track types
- for ( let k = 0; k < valueEnd; ++ k ) {
- targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
- }
- }
- }
- }
- targetClip.blendMode = AdditiveAnimationBlendMode;
- return targetClip;
- }
- /**
- * A class with various methods to assist with animations.
- *
- * @hideconstructor
- */
- class AnimationUtils {
- /**
- * Converts an array to a specific type
- *
- * @static
- * @param {TypedArray|Array} array - The array to convert.
- * @param {TypedArray.constructor} type - The constructor of a type array.
- * @return {TypedArray} The converted array
- */
- static convertArray( array, type ) {
- return convertArray( array, type );
- }
- /**
- * Returns `true` if the given object is a typed array.
- *
- * @static
- * @param {any} object - The object to check.
- * @return {boolean} Whether the given object is a typed array.
- */
- static isTypedArray( object ) {
- return isTypedArray( object );
- }
- /**
- * Returns an array by which times and values can be sorted.
- *
- * @static
- * @param {Array<number>} times - The keyframe time values.
- * @return {Array<number>} The array.
- */
- static getKeyframeOrder( times ) {
- return getKeyframeOrder( times );
- }
- /**
- * Sorts the given array by the previously computed order via `getKeyframeOrder()`.
- *
- * @static
- * @param {Array<number>} values - The values to sort.
- * @param {number} stride - The stride.
- * @param {Array<number>} order - The sort order.
- * @return {Array<number>} The sorted values.
- */
- static sortedArray( values, stride, order ) {
- return sortedArray( values, stride, order );
- }
- /**
- * Used for parsing AOS keyframe formats.
- *
- * @static
- * @param {Array<number>} jsonKeys - A list of JSON keyframes.
- * @param {Array<number>} times - This array will be filled with keyframe times by this method.
- * @param {Array<number>} values - This array will be filled with keyframe values by this method.
- * @param {string} valuePropertyName - The name of the property to use.
- */
- static flattenJSON( jsonKeys, times, values, valuePropertyName ) {
- flattenJSON( jsonKeys, times, values, valuePropertyName );
- }
- /**
- * Creates a new clip, containing only the segment of the original clip between the given frames.
- *
- * @static
- * @param {AnimationClip} sourceClip - The values to sort.
- * @param {string} name - The name of the clip.
- * @param {number} startFrame - The start frame.
- * @param {number} endFrame - The end frame.
- * @param {number} [fps=30] - The FPS.
- * @return {AnimationClip} The new sub clip.
- */
- static subclip( sourceClip, name, startFrame, endFrame, fps = 30 ) {
- return subclip( sourceClip, name, startFrame, endFrame, fps );
- }
- /**
- * Converts the keyframes of the given animation clip to an additive format.
- *
- * @static
- * @param {AnimationClip} targetClip - The clip to make additive.
- * @param {number} [referenceFrame=0] - The reference frame.
- * @param {AnimationClip} [referenceClip=targetClip] - The reference clip.
- * @param {number} [fps=30] - The FPS.
- * @return {AnimationClip} The updated clip which is now additive.
- */
- static makeClipAdditive( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
- return makeClipAdditive( targetClip, referenceFrame, referenceClip, fps );
- }
- }
- export {
- convertArray,
- isTypedArray,
- getKeyframeOrder,
- sortedArray,
- flattenJSON,
- subclip,
- makeClipAdditive,
- AnimationUtils
- };
|