AnimationUtils.js 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  1. import { Quaternion } from '../math/Quaternion.js';
  2. import { AdditiveAnimationBlendMode } from '../constants.js';
  3. const AnimationUtils = {
  4. // same as Array.prototype.slice, but also works on typed arrays
  5. arraySlice: function ( array, from, to ) {
  6. if ( AnimationUtils.isTypedArray( array ) ) {
  7. // in ios9 array.subarray(from, undefined) will return empty array
  8. // but array.subarray(from) or array.subarray(from, len) is correct
  9. return new array.constructor( array.subarray( from, to !== undefined ? to : array.length ) );
  10. }
  11. return array.slice( from, to );
  12. },
  13. // converts an array to a specific type
  14. convertArray: function ( array, type, forceClone ) {
  15. if ( ! array || // let 'undefined' and 'null' pass
  16. ! forceClone && array.constructor === type ) return array;
  17. if ( typeof type.BYTES_PER_ELEMENT === 'number' ) {
  18. return new type( array ); // create typed array
  19. }
  20. return Array.prototype.slice.call( array ); // create Array
  21. },
  22. isTypedArray: function ( object ) {
  23. return ArrayBuffer.isView( object ) &&
  24. ! ( object instanceof DataView );
  25. },
  26. // returns an array by which times and values can be sorted
  27. getKeyframeOrder: function ( times ) {
  28. function compareTime( i, j ) {
  29. return times[ i ] - times[ j ];
  30. }
  31. const n = times.length;
  32. const result = new Array( n );
  33. for ( let i = 0; i !== n; ++ i ) result[ i ] = i;
  34. result.sort( compareTime );
  35. return result;
  36. },
  37. // uses the array previously returned by 'getKeyframeOrder' to sort data
  38. sortedArray: function ( values, stride, order ) {
  39. const nValues = values.length;
  40. const result = new values.constructor( nValues );
  41. for ( let i = 0, dstOffset = 0; dstOffset !== nValues; ++ i ) {
  42. const srcOffset = order[ i ] * stride;
  43. for ( let j = 0; j !== stride; ++ j ) {
  44. result[ dstOffset ++ ] = values[ srcOffset + j ];
  45. }
  46. }
  47. return result;
  48. },
  49. // function for parsing AOS keyframe formats
  50. flattenJSON: function ( jsonKeys, times, values, valuePropertyName ) {
  51. let i = 1, key = jsonKeys[ 0 ];
  52. while ( key !== undefined && key[ valuePropertyName ] === undefined ) {
  53. key = jsonKeys[ i ++ ];
  54. }
  55. if ( key === undefined ) return; // no data
  56. let value = key[ valuePropertyName ];
  57. if ( value === undefined ) return; // no data
  58. if ( Array.isArray( value ) ) {
  59. do {
  60. value = key[ valuePropertyName ];
  61. if ( value !== undefined ) {
  62. times.push( key.time );
  63. values.push.apply( values, value ); // push all elements
  64. }
  65. key = jsonKeys[ i ++ ];
  66. } while ( key !== undefined );
  67. } else if ( value.toArray !== undefined ) {
  68. // ...assume THREE.Math-ish
  69. do {
  70. value = key[ valuePropertyName ];
  71. if ( value !== undefined ) {
  72. times.push( key.time );
  73. value.toArray( values, values.length );
  74. }
  75. key = jsonKeys[ i ++ ];
  76. } while ( key !== undefined );
  77. } else {
  78. // otherwise push as-is
  79. do {
  80. value = key[ valuePropertyName ];
  81. if ( value !== undefined ) {
  82. times.push( key.time );
  83. values.push( value );
  84. }
  85. key = jsonKeys[ i ++ ];
  86. } while ( key !== undefined );
  87. }
  88. },
  89. subclip: function ( sourceClip, name, startFrame, endFrame, fps ) {
  90. fps = fps || 30;
  91. const clip = sourceClip.clone();
  92. clip.name = name;
  93. const tracks = [];
  94. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  95. const track = clip.tracks[ i ];
  96. const valueSize = track.getValueSize();
  97. const times = [];
  98. const values = [];
  99. for ( let j = 0; j < track.times.length; ++ j ) {
  100. const frame = track.times[ j ] * fps;
  101. if ( frame < startFrame || frame >= endFrame ) continue;
  102. times.push( track.times[ j ] );
  103. for ( let k = 0; k < valueSize; ++ k ) {
  104. values.push( track.values[ j * valueSize + k ] );
  105. }
  106. }
  107. if ( times.length === 0 ) continue;
  108. track.times = AnimationUtils.convertArray( times, track.times.constructor );
  109. track.values = AnimationUtils.convertArray( values, track.values.constructor );
  110. tracks.push( track );
  111. }
  112. clip.tracks = tracks;
  113. // find minimum .times value across all tracks in the trimmed clip
  114. let minStartTime = Infinity;
  115. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  116. if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
  117. minStartTime = clip.tracks[ i ].times[ 0 ];
  118. }
  119. }
  120. // shift all tracks such that clip begins at t=0
  121. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  122. clip.tracks[ i ].shift( - 1 * minStartTime );
  123. }
  124. clip.resetDuration();
  125. return clip;
  126. },
  127. makeClipAdditive: function ( targetClip, referenceFrame, referenceClip, fps ) {
  128. if ( referenceFrame === undefined ) referenceFrame = 0;
  129. if ( referenceClip === undefined ) referenceClip = targetClip;
  130. if ( fps === undefined || fps <= 0 ) fps = 30;
  131. const numTracks = referenceClip.tracks.length;
  132. const referenceTime = referenceFrame / fps;
  133. // Make each track's values relative to the values at the reference frame
  134. for ( let i = 0; i < numTracks; ++ i ) {
  135. const referenceTrack = referenceClip.tracks[ i ];
  136. const referenceTrackType = referenceTrack.ValueTypeName;
  137. // Skip this track if it's non-numeric
  138. if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
  139. // Find the track in the target clip whose name and type matches the reference track
  140. const targetTrack = targetClip.tracks.find( function ( track ) {
  141. return track.name === referenceTrack.name
  142. && track.ValueTypeName === referenceTrackType;
  143. } );
  144. if ( targetTrack === undefined ) continue;
  145. let referenceOffset = 0;
  146. const referenceValueSize = referenceTrack.getValueSize();
  147. if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
  148. referenceOffset = referenceValueSize / 3;
  149. }
  150. let targetOffset = 0;
  151. const targetValueSize = targetTrack.getValueSize();
  152. if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
  153. targetOffset = targetValueSize / 3;
  154. }
  155. const lastIndex = referenceTrack.times.length - 1;
  156. let referenceValue;
  157. // Find the value to subtract out of the track
  158. if ( referenceTime <= referenceTrack.times[ 0 ] ) {
  159. // Reference frame is earlier than the first keyframe, so just use the first keyframe
  160. const startIndex = referenceOffset;
  161. const endIndex = referenceValueSize - referenceOffset;
  162. referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
  163. } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
  164. // Reference frame is after the last keyframe, so just use the last keyframe
  165. const startIndex = lastIndex * referenceValueSize + referenceOffset;
  166. const endIndex = startIndex + referenceValueSize - referenceOffset;
  167. referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
  168. } else {
  169. // Interpolate to the reference value
  170. const interpolant = referenceTrack.createInterpolant();
  171. const startIndex = referenceOffset;
  172. const endIndex = referenceValueSize - referenceOffset;
  173. interpolant.evaluate( referenceTime );
  174. referenceValue = AnimationUtils.arraySlice( interpolant.resultBuffer, startIndex, endIndex );
  175. }
  176. // Conjugate the quaternion
  177. if ( referenceTrackType === 'quaternion' ) {
  178. const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
  179. referenceQuat.toArray( referenceValue );
  180. }
  181. // Subtract the reference value from all of the track values
  182. const numTimes = targetTrack.times.length;
  183. for ( let j = 0; j < numTimes; ++ j ) {
  184. const valueStart = j * targetValueSize + targetOffset;
  185. if ( referenceTrackType === 'quaternion' ) {
  186. // Multiply the conjugate for quaternion track types
  187. Quaternion.multiplyQuaternionsFlat(
  188. targetTrack.values,
  189. valueStart,
  190. referenceValue,
  191. 0,
  192. targetTrack.values,
  193. valueStart
  194. );
  195. } else {
  196. const valueEnd = targetValueSize - targetOffset * 2;
  197. // Subtract each value for all other numeric track types
  198. for ( let k = 0; k < valueEnd; ++ k ) {
  199. targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
  200. }
  201. }
  202. }
  203. }
  204. targetClip.blendMode = AdditiveAnimationBlendMode;
  205. return targetClip;
  206. }
  207. };
  208. export { AnimationUtils };