KeyframeTrack.js 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267
  1. /**
  2. *
  3. * A Track that returns a keyframe interpolated value, currently linearly interpolated
  4. *
  5. * @author Ben Houston / http://clara.io/
  6. * @author David Sarno / http://lighthaus.us/
  7. */
  8. THREE.KeyframeTrack = function ( name, keys ) {
  9. if( keys === undefined || keys.length === 0 ) throw new Error( "no keys in track named " + name );
  10. this.name = name;
  11. this.keys = keys; // time in seconds, value as value
  12. // the index of the last result, used as a starting point for local search.
  13. this.lastIndex = 0;
  14. this.sort();
  15. this.validate();
  16. this.optimize();
  17. };
  18. THREE.KeyframeTrack.prototype = {
  19. constructor: THREE.KeyframeTrack,
  20. getAt: function( time ) {
  21. // this can not go higher than this.keys.length.
  22. while( ( this.lastIndex < this.keys.length ) && ( time >= this.keys[this.lastIndex].time ) ) {
  23. this.lastIndex ++;
  24. };
  25. // this can not go lower than 0.
  26. while( ( this.lastIndex > 0 ) && ( time < this.keys[this.lastIndex - 1].time ) ) {
  27. this.lastIndex --;
  28. }
  29. if( this.lastIndex >= this.keys.length ) {
  30. this.setResult( this.keys[ this.keys.length - 1 ].value );
  31. return this.result;
  32. }
  33. if( this.lastIndex === 0 ) {
  34. this.setResult( this.keys[ 0 ].value );
  35. return this.result;
  36. }
  37. var prevKey = this.keys[ this.lastIndex - 1 ];
  38. this.setResult( prevKey.value );
  39. // if true, means that prev/current keys are identical, thus no interpolation required.
  40. if( prevKey.constantToNext ) {
  41. return this.result;
  42. }
  43. // linear interpolation to start with
  44. var currentKey = this.keys[ this.lastIndex ];
  45. var alpha = ( time - prevKey.time ) / ( currentKey.time - prevKey.time );
  46. this.result = this.lerpValues( this.result, currentKey.value, alpha );
  47. return this.result;
  48. },
  49. // move all keyframes either forwards or backwards in time
  50. shift: function( timeOffset ) {
  51. if( timeOffset !== 0.0 ) {
  52. for( var i = 1; i < this.keys.length; i ++ ) {
  53. this.keys[i].time += timeOffset;
  54. }
  55. }
  56. return this;
  57. },
  58. // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
  59. scale: function( timeScale ) {
  60. if( timeTime !== 1.0 ) {
  61. for( var i = 1; i < this.keys.length; i ++ ) {
  62. this.keys[i].time *= timeScale;
  63. }
  64. }
  65. return this;
  66. },
  67. // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
  68. // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
  69. trim: function( startTime, endTime ) {
  70. var firstKeysToRemove = 0;
  71. for( var i = 1; i < this.keys.length; i ++ ) {
  72. if( this.keys[i] <= startTime ) {
  73. firstKeysToRemove ++;
  74. }
  75. }
  76. var lastKeysToRemove = 0;
  77. for( var i = this.keys.length - 2; i > 0; i ++ ) {
  78. if( this.keys[i] >= endTime ) {
  79. lastKeysToRemove ++;
  80. }
  81. else {
  82. break;
  83. }
  84. }
  85. // remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily)
  86. if( ( firstKeysToRemove + lastKeysToRemove ) > 0 ) {
  87. this.keys = this.keys.splice( firstKeysToRemove, this.keys.length - lastKeysToRemove - firstKeysToRemove );;
  88. }
  89. return this;
  90. },
  91. // sort in ascending order
  92. sort: function() {
  93. function keyComparator(key0, key1) {
  94. return key0.time - key1.time;
  95. };
  96. return function() {
  97. this.keys.sort( keyComparator );
  98. return this;
  99. }
  100. }(),
  101. // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
  102. // One could eventually ensure that all key.values in a track are all of the same type (otherwise interpolation makes no sense.)
  103. validate: function() {
  104. var prevKey = null;
  105. if( this.keys.length === 0 ) {
  106. console.error( " track is empty, no keys", this );
  107. return;
  108. }
  109. for( var i = 0; i < this.keys.length; i ++ ) {
  110. var currKey = this.keys[i];
  111. if( ! currKey ) {
  112. console.error( " key is null in track", this, i );
  113. return;
  114. }
  115. if( ( typeof currKey.time ) !== 'number' || Number.isNaN( currKey.time ) ) {
  116. console.error( " key.time is not a valid number", this, i, currKey );
  117. return;
  118. }
  119. if( currKey.value === undefined || currKey.value === null) {
  120. console.error( " key.value is null in track", this, i, currKey );
  121. return;
  122. }
  123. if( prevKey && prevKey.time > currKey.time ) {
  124. console.error( " key.time is less than previous key time, out of order keys", this, i, currKey, prevKey );
  125. return;
  126. }
  127. prevKey = currKey;
  128. }
  129. return this;
  130. },
  131. // currently only removes equivalent sequential keys (0,0,0,0,1,1,1,0,0,0,0,0,0,0) --> (0,0,1,1,0,0), which are common in morph target animations
  132. optimize: function() {
  133. var newKeys = [];
  134. var prevKey = this.keys[0];
  135. newKeys.push( prevKey );
  136. var equalsFunc = THREE.AnimationUtils.getEqualsFunc( prevKey.value );
  137. for( var i = 1; i < this.keys.length - 1; i ++ ) {
  138. var currKey = this.keys[i];
  139. var nextKey = this.keys[i+1];
  140. // if prevKey & currKey are the same time, remove currKey. If you want immediate adjacent keys, use an epsilon offset
  141. // it is not possible to have two keys at the same time as we sort them. The sort is not stable on keys with the same time.
  142. if( ( prevKey.time === currKey.time ) ) {
  143. continue;
  144. }
  145. // remove completely unnecessary keyframes that are the same as their prev and next keys
  146. if( this.compareValues( prevKey.value, currKey.value ) && this.compareValues( currKey.value, nextKey.value ) ) {
  147. continue;
  148. }
  149. // determine if interpolation is required
  150. prevKey.constantToNext = this.compareValues( prevKey.value, currKey.value );
  151. newKeys.push( currKey );
  152. prevKey = currKey;
  153. }
  154. newKeys.push( this.keys[ this.keys.length - 1 ] );
  155. this.keys = newKeys;
  156. return this;
  157. }
  158. };
  159. THREE.KeyframeTrack.GetTrackTypeForValue = function( value ) {
  160. switch( typeof value ) {
  161. case "object": {
  162. if( value.lerp ) {
  163. return THREE.VectorKeyframeTrack;
  164. }
  165. if( value.slerp ) {
  166. return THREE.QuaternionKeyframeTrack;
  167. }
  168. break;
  169. }
  170. case "number": {
  171. return THREE.NumberKeyframeTrack;
  172. }
  173. case "boolean": {
  174. return THREE.BooleanKeyframeTrack;
  175. }
  176. case "string": {
  177. return THREE.StringKeyframeTrack;
  178. }
  179. };
  180. throw new Error( "Unsupported value type" );
  181. };