AnimationUtils.js 7.8 KB

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