KeyframeTrack.js 9.2 KB

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