KeyframeTrack.js 9.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468
  1. import {
  2. InterpolateLinear,
  3. InterpolateSmooth,
  4. InterpolateDiscrete
  5. } from '../constants.js';
  6. import { CubicInterpolant } from '../math/interpolants/CubicInterpolant.js';
  7. import { LinearInterpolant } from '../math/interpolants/LinearInterpolant.js';
  8. import { DiscreteInterpolant } from '../math/interpolants/DiscreteInterpolant.js';
  9. import { AnimationUtils } from './AnimationUtils.js';
  10. class KeyframeTrack {
  11. constructor( name, times, values, interpolation ) {
  12. if ( name === undefined ) throw new Error( 'THREE.KeyframeTrack: track name is undefined' );
  13. if ( times === undefined || times.length === 0 ) throw new Error( 'THREE.KeyframeTrack: no keyframes in track named ' + name );
  14. this.name = name;
  15. this.times = AnimationUtils.convertArray( times, this.TimeBufferType );
  16. this.values = AnimationUtils.convertArray( values, this.ValueBufferType );
  17. this.setInterpolation( interpolation || this.DefaultInterpolation );
  18. }
  19. // Serialization (in static context, because of constructor invocation
  20. // and automatic invocation of .toJSON):
  21. static toJSON( track ) {
  22. const trackType = track.constructor;
  23. let json;
  24. // derived classes can define a static toJSON method
  25. if ( trackType.toJSON !== undefined ) {
  26. json = trackType.toJSON( track );
  27. } else {
  28. // by default, we assume the data can be serialized as-is
  29. json = {
  30. 'name': track.name,
  31. 'times': AnimationUtils.convertArray( track.times, Array ),
  32. 'values': AnimationUtils.convertArray( track.values, Array )
  33. };
  34. const interpolation = track.getInterpolation();
  35. if ( interpolation !== track.DefaultInterpolation ) {
  36. json.interpolation = interpolation;
  37. }
  38. }
  39. json.type = track.ValueTypeName; // mandatory
  40. return json;
  41. }
  42. InterpolantFactoryMethodDiscrete( result ) {
  43. return new DiscreteInterpolant( this.times, this.values, this.getValueSize(), result );
  44. }
  45. InterpolantFactoryMethodLinear( result ) {
  46. return new LinearInterpolant( this.times, this.values, this.getValueSize(), result );
  47. }
  48. InterpolantFactoryMethodSmooth( result ) {
  49. return new CubicInterpolant( this.times, this.values, this.getValueSize(), result );
  50. }
  51. setInterpolation( interpolation ) {
  52. let factoryMethod;
  53. switch ( interpolation ) {
  54. case InterpolateDiscrete:
  55. factoryMethod = this.InterpolantFactoryMethodDiscrete;
  56. break;
  57. case InterpolateLinear:
  58. factoryMethod = this.InterpolantFactoryMethodLinear;
  59. break;
  60. case InterpolateSmooth:
  61. factoryMethod = this.InterpolantFactoryMethodSmooth;
  62. break;
  63. }
  64. if ( factoryMethod === undefined ) {
  65. const message = "unsupported interpolation for " +
  66. this.ValueTypeName + " keyframe track named " + this.name;
  67. if ( this.createInterpolant === undefined ) {
  68. // fall back to default, unless the default itself is messed up
  69. if ( interpolation !== this.DefaultInterpolation ) {
  70. this.setInterpolation( this.DefaultInterpolation );
  71. } else {
  72. throw new Error( message ); // fatal, in this case
  73. }
  74. }
  75. console.warn( 'THREE.KeyframeTrack:', message );
  76. return this;
  77. }
  78. this.createInterpolant = factoryMethod;
  79. return this;
  80. }
  81. getInterpolation() {
  82. switch ( this.createInterpolant ) {
  83. case this.InterpolantFactoryMethodDiscrete:
  84. return InterpolateDiscrete;
  85. case this.InterpolantFactoryMethodLinear:
  86. return InterpolateLinear;
  87. case this.InterpolantFactoryMethodSmooth:
  88. return InterpolateSmooth;
  89. }
  90. }
  91. getValueSize() {
  92. return this.values.length / this.times.length;
  93. }
  94. // move all keyframes either forwards or backwards in time
  95. shift( timeOffset ) {
  96. if ( timeOffset !== 0.0 ) {
  97. const times = this.times;
  98. for ( let i = 0, n = times.length; i !== n; ++ i ) {
  99. times[ i ] += timeOffset;
  100. }
  101. }
  102. return this;
  103. }
  104. // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
  105. scale( timeScale ) {
  106. if ( timeScale !== 1.0 ) {
  107. const times = this.times;
  108. for ( let i = 0, n = times.length; i !== n; ++ i ) {
  109. times[ i ] *= timeScale;
  110. }
  111. }
  112. return this;
  113. }
  114. // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
  115. // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
  116. trim( startTime, endTime ) {
  117. const times = this.times,
  118. nKeys = times.length;
  119. let from = 0,
  120. to = nKeys - 1;
  121. while ( from !== nKeys && times[ from ] < startTime ) {
  122. ++ from;
  123. }
  124. while ( to !== - 1 && times[ to ] > endTime ) {
  125. -- to;
  126. }
  127. ++ to; // inclusive -> exclusive bound
  128. if ( from !== 0 || to !== nKeys ) {
  129. // empty tracks are forbidden, so keep at least one keyframe
  130. if ( from >= to ) {
  131. to = Math.max( to, 1 );
  132. from = to - 1;
  133. }
  134. const stride = this.getValueSize();
  135. this.times = AnimationUtils.arraySlice( times, from, to );
  136. this.values = AnimationUtils.arraySlice( this.values, from * stride, to * stride );
  137. }
  138. return this;
  139. }
  140. // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
  141. validate() {
  142. let valid = true;
  143. const valueSize = this.getValueSize();
  144. if ( valueSize - Math.floor( valueSize ) !== 0 ) {
  145. console.error( 'THREE.KeyframeTrack: Invalid value size in track.', this );
  146. valid = false;
  147. }
  148. const times = this.times,
  149. values = this.values,
  150. nKeys = times.length;
  151. if ( nKeys === 0 ) {
  152. console.error( 'THREE.KeyframeTrack: Track is empty.', this );
  153. valid = false;
  154. }
  155. let prevTime = null;
  156. for ( let i = 0; i !== nKeys; i ++ ) {
  157. const currTime = times[ i ];
  158. if ( typeof currTime === 'number' && isNaN( currTime ) ) {
  159. console.error( 'THREE.KeyframeTrack: Time is not a valid number.', this, i, currTime );
  160. valid = false;
  161. break;
  162. }
  163. if ( prevTime !== null && prevTime > currTime ) {
  164. console.error( 'THREE.KeyframeTrack: Out of order keys.', this, i, currTime, prevTime );
  165. valid = false;
  166. break;
  167. }
  168. prevTime = currTime;
  169. }
  170. if ( values !== undefined ) {
  171. if ( AnimationUtils.isTypedArray( values ) ) {
  172. for ( let i = 0, n = values.length; i !== n; ++ i ) {
  173. const value = values[ i ];
  174. if ( isNaN( value ) ) {
  175. console.error( 'THREE.KeyframeTrack: Value is not a valid number.', this, i, value );
  176. valid = false;
  177. break;
  178. }
  179. }
  180. }
  181. }
  182. return valid;
  183. }
  184. // removes equivalent sequential keys as common in morph target sequences
  185. // (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0)
  186. optimize() {
  187. // times or values may be shared with other tracks, so overwriting is unsafe
  188. const times = AnimationUtils.arraySlice( this.times ),
  189. values = AnimationUtils.arraySlice( this.values ),
  190. stride = this.getValueSize(),
  191. smoothInterpolation = this.getInterpolation() === InterpolateSmooth,
  192. lastIndex = times.length - 1;
  193. let writeIndex = 1;
  194. for ( let i = 1; i < lastIndex; ++ i ) {
  195. let keep = false;
  196. const time = times[ i ];
  197. const timeNext = times[ i + 1 ];
  198. // remove adjacent keyframes scheduled at the same time
  199. if ( time !== timeNext && ( i !== 1 || time !== time[ 0 ] ) ) {
  200. if ( ! smoothInterpolation ) {
  201. // remove unnecessary keyframes same as their neighbors
  202. const offset = i * stride,
  203. offsetP = offset - stride,
  204. offsetN = offset + stride;
  205. for ( let j = 0; j !== stride; ++ j ) {
  206. const value = values[ offset + j ];
  207. if ( value !== values[ offsetP + j ] ||
  208. value !== values[ offsetN + j ] ) {
  209. keep = true;
  210. break;
  211. }
  212. }
  213. } else {
  214. keep = true;
  215. }
  216. }
  217. // in-place compaction
  218. if ( keep ) {
  219. if ( i !== writeIndex ) {
  220. times[ writeIndex ] = times[ i ];
  221. const readOffset = i * stride,
  222. writeOffset = writeIndex * stride;
  223. for ( let j = 0; j !== stride; ++ j ) {
  224. values[ writeOffset + j ] = values[ readOffset + j ];
  225. }
  226. }
  227. ++ writeIndex;
  228. }
  229. }
  230. // flush last keyframe (compaction looks ahead)
  231. if ( lastIndex > 0 ) {
  232. times[ writeIndex ] = times[ lastIndex ];
  233. for ( let readOffset = lastIndex * stride, writeOffset = writeIndex * stride, j = 0; j !== stride; ++ j ) {
  234. values[ writeOffset + j ] = values[ readOffset + j ];
  235. }
  236. ++ writeIndex;
  237. }
  238. if ( writeIndex !== times.length ) {
  239. this.times = AnimationUtils.arraySlice( times, 0, writeIndex );
  240. this.values = AnimationUtils.arraySlice( values, 0, writeIndex * stride );
  241. } else {
  242. this.times = times;
  243. this.values = values;
  244. }
  245. return this;
  246. }
  247. clone() {
  248. const times = AnimationUtils.arraySlice( this.times, 0 );
  249. const values = AnimationUtils.arraySlice( this.values, 0 );
  250. const TypedKeyframeTrack = this.constructor;
  251. const track = new TypedKeyframeTrack( this.name, times, values );
  252. // Interpolant argument to constructor is not saved, so copy the factory method directly.
  253. track.createInterpolant = this.createInterpolant;
  254. return track;
  255. }
  256. }
  257. Object.assign( KeyframeTrack.prototype, {
  258. TimeBufferType: Float32Array,
  259. ValueBufferType: Float32Array,
  260. DefaultInterpolation: InterpolateLinear,
  261. } );
  262. export { KeyframeTrack };