2
0

KeyframeTrack.js 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245
  1. /**
  2. *
  3. * A Track that returns a keyframe interpolated value.
  4. *
  5. * TODO: Add cubic in addition to linear interpolation.
  6. *
  7. * @author Ben Houston / http://clara.io/
  8. * @author David Sarno / http://lighthaus.us/
  9. */
  10. THREE.KeyframeTrack = function ( name, keys ) {
  11. if( keys === undefined || keys.length === 0 ) throw new Error( "no keys in track named " + name );
  12. this.name = name;
  13. this.keys = keys; // time in seconds, value as value
  14. // local cache of value type to avoid allocations during runtime.
  15. this.result = THREE.AnimationUtils.clone( this.keys[0].value );
  16. //console.log( 'constructor result', this.result )
  17. this.lastIndex = 0;
  18. this.sort();
  19. this.validate();
  20. this.optimize();
  21. };
  22. THREE.KeyframeTrack.prototype = {
  23. constructor: THREE.KeyframeTrack,
  24. getAt: function( time ) {
  25. //if( /morph/i.test( this.name ) ) {
  26. // console.log( 'KeyframeTrack[' + this.name + '].getAt( ' + time + ' )' );
  27. //}
  28. // this can not go higher than this.keys.length.
  29. while( ( this.lastIndex < this.keys.length ) && ( time >= this.keys[this.lastIndex].time ) ) {
  30. this.lastIndex ++;
  31. };
  32. // this can not go lower than 0.
  33. while( ( this.lastIndex > 0 ) && ( time < this.keys[this.lastIndex - 1].time ) ) {
  34. this.lastIndex --;
  35. }
  36. //if( /morph/i.test( this.name ) ) {
  37. // console.log( "lastIndex: ", this.lastIndex );
  38. //}
  39. if( this.lastIndex >= this.keys.length ) {
  40. this.setResult( this.keys[ this.keys.length - 1 ].value );
  41. return this.result;
  42. }
  43. if( this.lastIndex === 0 ) {
  44. this.setResult( this.keys[ 0 ].value );
  45. return this.result;
  46. }
  47. var prevKey = this.keys[ this.lastIndex - 1 ];
  48. this.setResult( prevKey.value );
  49. if( prevKey.constantToNext ) {
  50. return this.result;
  51. }
  52. // linear interpolation to start with
  53. var currentKey = this.keys[ this.lastIndex ];
  54. var alpha = ( time - prevKey.time ) / ( currentKey.time - prevKey.time );
  55. this.result = this.lerp( this.result, currentKey.value, alpha );
  56. //console.log( 'lerp result', this.result )
  57. /*if( /morph/i.test( this.name ) ) {
  58. console.log( ' interpolated: ', {
  59. index: this.lastIndex,
  60. value: this.result,
  61. alpha: alpha,
  62. time0: this.keys[ this.lastIndex - 1 ].time,
  63. time1: this.keys[ this.lastIndex ].time,
  64. value0: this.keys[ this.lastIndex - 1 ].value,
  65. value1: this.keys[ this.lastIndex ].value
  66. } );
  67. }*/
  68. return this.result;
  69. },
  70. setResult: function( value ) {
  71. if( this.result.copy ) {
  72. this.result.copy( value );
  73. }
  74. else {
  75. this.result = value;
  76. }
  77. },
  78. // memoization of the lerp function for speed.
  79. // NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis.
  80. lerp: function( value0, value1, alpha ) {
  81. this.lerp = THREE.AnimationUtils.getLerpFunc( value0, false );
  82. return this.lerp( value0, value1, alpha );
  83. },
  84. // sort in ascending order
  85. sort: function() {
  86. var keyComparator = function(key0, key1) {
  87. return key0.time - key1.time;
  88. };
  89. return function() {
  90. this.keys.sort( keyComparator );
  91. }
  92. }(),
  93. // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
  94. // TODO: ensure that all key.values in a track are all of the same type (otherwise interpolation makes no sense.)
  95. validate: function() {
  96. var prevKey = null;
  97. if( this.keys.length === 0 ) {
  98. console.error( " track is empty, no keys", this );
  99. return;
  100. }
  101. for( var i = 0; i < this.keys.length; i ++ ) {
  102. var currKey = this.keys[i];
  103. if( ! currKey ) {
  104. console.error( " key is null in track", this, i );
  105. return;
  106. }
  107. if( ( typeof currKey.time ) !== 'number' || Number.isNaN( currKey.time ) ) {
  108. console.error( " key.time is not a valid number", this, i, currKey );
  109. return;
  110. }
  111. if( currKey.value === undefined || currKey.value === null) {
  112. console.error( " key.value is null in track", this, i, currKey );
  113. return;
  114. }
  115. if( prevKey && prevKey.time > currKey.time ) {
  116. console.error( " key.time is less than previous key time, out of order keys", this, i, currKey, prevKey );
  117. return;
  118. }
  119. prevKey = currKey;
  120. }
  121. },
  122. // 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
  123. // TODO: linear based interpolation optimization with an error threshold.
  124. optimize: function() {
  125. var newKeys = [];
  126. var prevKey = this.keys[0];
  127. newKeys.push( prevKey );
  128. var equalsFunc = THREE.AnimationUtils.getEqualsFunc( prevKey.value );
  129. for( var i = 1; i < this.keys.length - 1; i ++ ) {
  130. var currKey = this.keys[i];
  131. var nextKey = this.keys[i+1];
  132. //console.log( prevKey, currKey, nextKey );
  133. // if prevKey & currKey are the same time, remove currKey. If you want immediate adjacent keys, use an epsilon offset
  134. // 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.
  135. if( ( prevKey.time === currKey.time ) ) {
  136. //console.log( 'removing key at the same time', currKey );
  137. continue;
  138. }
  139. // remove completely unnecessary keyframes that are the same as their prev and next keys
  140. if( equalsFunc( prevKey.value, currKey.value ) && equalsFunc( currKey.value, nextKey.value ) ) {
  141. //console.log( 'removing key identical to prev and next', currKey );
  142. continue;
  143. }
  144. // TODO:add here a check for linear interpolation optimization.
  145. // determine if interpolation is required
  146. prevKey.constantToNext = equalsFunc( prevKey.value, currKey.value );
  147. newKeys.push( currKey );
  148. prevKey = currKey;
  149. }
  150. newKeys.push( this.keys[ this.keys.length - 1 ] );
  151. //if( ( this.keys.length - newKeys.length ) > 0 ) {
  152. //console.log( ' optimizing removed keys:', ( this.keys.length - newKeys.length ), this.name );
  153. //}
  154. this.keys = newKeys;
  155. },
  156. // removes keyframes before and after animation without changing any values within the range [0,duration].
  157. // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
  158. trim: function( duration ) {
  159. var firstKeysToRemove = 0;
  160. for( var i = 1; i < this.keys.length; i ++ ) {
  161. if( this.keys[i] <= 0 ) {
  162. firstKeysToRemove ++;
  163. }
  164. }
  165. var lastKeysToRemove = 0;
  166. for( var i = this.keys.length - 2; i > 0; i ++ ) {
  167. if( this.keys[i] >= duration ) {
  168. lastKeysToRemove ++;
  169. }
  170. else {
  171. break;
  172. }
  173. }
  174. // remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily)
  175. if( ( firstKeysToRemove + lastKeysToRemove ) > 0 ) {
  176. //console.log( ' triming removed keys: first and last', firstKeysToRemove, lastKeysToRemove, this.keys );
  177. this.keys = this.keys.splice( firstKeysToRemove, this.keys.length - lastKeysToRemove - firstKeysToRemove );;
  178. //console.log( ' result', this.keys );
  179. }
  180. }
  181. };