AnimationUtils.js 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356
  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 = 30 ) {
  90. const clip = sourceClip.clone();
  91. clip.name = name;
  92. const tracks = [];
  93. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  94. const track = clip.tracks[ i ];
  95. const valueSize = track.getValueSize();
  96. const times = [];
  97. const values = [];
  98. for ( let j = 0; j < track.times.length; ++ j ) {
  99. const frame = track.times[ j ] * fps;
  100. if ( frame < startFrame || frame >= endFrame ) continue;
  101. times.push( track.times[ j ] );
  102. for ( let k = 0; k < valueSize; ++ k ) {
  103. values.push( track.values[ j * valueSize + k ] );
  104. }
  105. }
  106. if ( times.length === 0 ) continue;
  107. track.times = AnimationUtils.convertArray( times, track.times.constructor );
  108. track.values = AnimationUtils.convertArray( values, track.values.constructor );
  109. tracks.push( track );
  110. }
  111. clip.tracks = tracks;
  112. // find minimum .times value across all tracks in the trimmed clip
  113. let minStartTime = Infinity;
  114. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  115. if ( minStartTime > clip.tracks[ i ].times[ 0 ] ) {
  116. minStartTime = clip.tracks[ i ].times[ 0 ];
  117. }
  118. }
  119. // shift all tracks such that clip begins at t=0
  120. for ( let i = 0; i < clip.tracks.length; ++ i ) {
  121. clip.tracks[ i ].shift( - 1 * minStartTime );
  122. }
  123. clip.resetDuration();
  124. return clip;
  125. },
  126. makeClipAdditive: function ( targetClip, referenceFrame = 0, referenceClip = targetClip, fps = 30 ) {
  127. if ( fps <= 0 ) fps = 30;
  128. const numTracks = referenceClip.tracks.length;
  129. const referenceTime = referenceFrame / fps;
  130. // Make each track's values relative to the values at the reference frame
  131. for ( let i = 0; i < numTracks; ++ i ) {
  132. const referenceTrack = referenceClip.tracks[ i ];
  133. const referenceTrackType = referenceTrack.ValueTypeName;
  134. // Skip this track if it's non-numeric
  135. if ( referenceTrackType === 'bool' || referenceTrackType === 'string' ) continue;
  136. // Find the track in the target clip whose name and type matches the reference track
  137. const targetTrack = targetClip.tracks.find( function ( track ) {
  138. return track.name === referenceTrack.name
  139. && track.ValueTypeName === referenceTrackType;
  140. } );
  141. if ( targetTrack === undefined ) continue;
  142. let referenceOffset = 0;
  143. const referenceValueSize = referenceTrack.getValueSize();
  144. if ( referenceTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
  145. referenceOffset = referenceValueSize / 3;
  146. }
  147. let targetOffset = 0;
  148. const targetValueSize = targetTrack.getValueSize();
  149. if ( targetTrack.createInterpolant.isInterpolantFactoryMethodGLTFCubicSpline ) {
  150. targetOffset = targetValueSize / 3;
  151. }
  152. const lastIndex = referenceTrack.times.length - 1;
  153. let referenceValue;
  154. // Find the value to subtract out of the track
  155. if ( referenceTime <= referenceTrack.times[ 0 ] ) {
  156. // Reference frame is earlier than the first keyframe, so just use the first keyframe
  157. const startIndex = referenceOffset;
  158. const endIndex = referenceValueSize - referenceOffset;
  159. referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
  160. } else if ( referenceTime >= referenceTrack.times[ lastIndex ] ) {
  161. // Reference frame is after the last keyframe, so just use the last keyframe
  162. const startIndex = lastIndex * referenceValueSize + referenceOffset;
  163. const endIndex = startIndex + referenceValueSize - referenceOffset;
  164. referenceValue = AnimationUtils.arraySlice( referenceTrack.values, startIndex, endIndex );
  165. } else {
  166. // Interpolate to the reference value
  167. const interpolant = referenceTrack.createInterpolant();
  168. const startIndex = referenceOffset;
  169. const endIndex = referenceValueSize - referenceOffset;
  170. interpolant.evaluate( referenceTime );
  171. referenceValue = AnimationUtils.arraySlice( interpolant.resultBuffer, startIndex, endIndex );
  172. }
  173. // Conjugate the quaternion
  174. if ( referenceTrackType === 'quaternion' ) {
  175. const referenceQuat = new Quaternion().fromArray( referenceValue ).normalize().conjugate();
  176. referenceQuat.toArray( referenceValue );
  177. }
  178. // Subtract the reference value from all of the track values
  179. const numTimes = targetTrack.times.length;
  180. for ( let j = 0; j < numTimes; ++ j ) {
  181. const valueStart = j * targetValueSize + targetOffset;
  182. if ( referenceTrackType === 'quaternion' ) {
  183. // Multiply the conjugate for quaternion track types
  184. Quaternion.multiplyQuaternionsFlat(
  185. targetTrack.values,
  186. valueStart,
  187. referenceValue,
  188. 0,
  189. targetTrack.values,
  190. valueStart
  191. );
  192. } else {
  193. const valueEnd = targetValueSize - targetOffset * 2;
  194. // Subtract each value for all other numeric track types
  195. for ( let k = 0; k < valueEnd; ++ k ) {
  196. targetTrack.values[ valueStart + k ] -= referenceValue[ k ];
  197. }
  198. }
  199. }
  200. }
  201. targetClip.blendMode = AdditiveAnimationBlendMode;
  202. return targetClip;
  203. }
  204. };
  205. export { AnimationUtils };
粤ICP备19079148号