AnimationUtils.js 7.6 KB

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