KeyframeTrack.js 9.2 KB

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