KeyframeTrack.js 11 KB

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