123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267 |
- /**
- *
- * A Track that returns a keyframe interpolated value, currently linearly interpolated
- *
- * @author Ben Houston / http://clara.io/
- * @author David Sarno / http://lighthaus.us/
- */
- THREE.KeyframeTrack = function ( name, keys ) {
- if( keys === undefined || keys.length === 0 ) throw new Error( "no keys in track named " + name );
- this.name = name;
- this.keys = keys; // time in seconds, value as value
- // the index of the last result, used as a starting point for local search.
- this.lastIndex = 0;
- this.sort();
- this.validate();
- this.optimize();
- };
- THREE.KeyframeTrack.prototype = {
- constructor: THREE.KeyframeTrack,
- getAt: function( time ) {
-
- // this can not go higher than this.keys.length.
- while( ( this.lastIndex < this.keys.length ) && ( time >= this.keys[this.lastIndex].time ) ) {
- this.lastIndex ++;
- };
- // this can not go lower than 0.
- while( ( this.lastIndex > 0 ) && ( time < this.keys[this.lastIndex - 1].time ) ) {
- this.lastIndex --;
- }
- if( this.lastIndex >= this.keys.length ) {
- this.setResult( this.keys[ this.keys.length - 1 ].value );
- return this.result;
- }
- if( this.lastIndex === 0 ) {
- this.setResult( this.keys[ 0 ].value );
- return this.result;
- }
- var prevKey = this.keys[ this.lastIndex - 1 ];
- this.setResult( prevKey.value );
- // if true, means that prev/current keys are identical, thus no interpolation required.
- if( prevKey.constantToNext ) {
-
- return this.result;
- }
- // linear interpolation to start with
- var currentKey = this.keys[ this.lastIndex ];
- var alpha = ( time - prevKey.time ) / ( currentKey.time - prevKey.time );
- this.result = this.lerpValues( this.result, currentKey.value, alpha );
- return this.result;
- },
- // move all keyframes either forwards or backwards in time
- shift: function( timeOffset ) {
- if( timeOffset !== 0.0 ) {
- for( var i = 1; i < this.keys.length; i ++ ) {
- this.keys[i].time += timeOffset;
- }
- }
- return this;
- },
- // scale all keyframe times by a factor (useful for frame <-> seconds conversions)
- scale: function( timeScale ) {
- if( timeTime !== 1.0 ) {
- for( var i = 1; i < this.keys.length; i ++ ) {
- this.keys[i].time *= timeScale;
- }
- }
- return this;
- },
- // removes keyframes before and after animation without changing any values within the range [startTime, endTime].
- // IMPORTANT: We do not shift around keys to the start of the track time, because for interpolated keys this will change their values
- trim: function( startTime, endTime ) {
-
- var firstKeysToRemove = 0;
- for( var i = 1; i < this.keys.length; i ++ ) {
- if( this.keys[i] <= startTime ) {
- firstKeysToRemove ++;
- }
- }
-
- var lastKeysToRemove = 0;
- for( var i = this.keys.length - 2; i > 0; i ++ ) {
- if( this.keys[i] >= endTime ) {
- lastKeysToRemove ++;
- }
- else {
- break;
- }
- }
-
- // remove last keys first because it doesn't affect the position of the first keys (the otherway around doesn't work as easily)
- if( ( firstKeysToRemove + lastKeysToRemove ) > 0 ) {
- this.keys = this.keys.splice( firstKeysToRemove, this.keys.length - lastKeysToRemove - firstKeysToRemove );;
- }
- return this;
- },
- // sort in ascending order
- sort: function() {
- function keyComparator(key0, key1) {
- return key0.time - key1.time;
- };
- return function() {
- this.keys.sort( keyComparator );
- return this;
- }
- }(),
- // ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
- // One could eventually ensure that all key.values in a track are all of the same type (otherwise interpolation makes no sense.)
- validate: function() {
- var prevKey = null;
- if( this.keys.length === 0 ) {
- console.error( " track is empty, no keys", this );
- return;
- }
- for( var i = 0; i < this.keys.length; i ++ ) {
- var currKey = this.keys[i];
- if( ! currKey ) {
- console.error( " key is null in track", this, i );
- return;
- }
- if( ( typeof currKey.time ) !== 'number' || Number.isNaN( currKey.time ) ) {
- console.error( " key.time is not a valid number", this, i, currKey );
- return;
- }
- if( currKey.value === undefined || currKey.value === null) {
- console.error( " key.value is null in track", this, i, currKey );
- return;
- }
- if( prevKey && prevKey.time > currKey.time ) {
- console.error( " key.time is less than previous key time, out of order keys", this, i, currKey, prevKey );
- return;
- }
- prevKey = currKey;
- }
- return this;
- },
- // 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
- optimize: function() {
- var newKeys = [];
- var prevKey = this.keys[0];
- newKeys.push( prevKey );
- var equalsFunc = THREE.AnimationUtils.getEqualsFunc( prevKey.value );
- for( var i = 1; i < this.keys.length - 1; i ++ ) {
- var currKey = this.keys[i];
- var nextKey = this.keys[i+1];
- // if prevKey & currKey are the same time, remove currKey. If you want immediate adjacent keys, use an epsilon offset
- // 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.
- if( ( prevKey.time === currKey.time ) ) {
- continue;
- }
- // remove completely unnecessary keyframes that are the same as their prev and next keys
- if( this.compareValues( prevKey.value, currKey.value ) && this.compareValues( currKey.value, nextKey.value ) ) {
- continue;
- }
- // determine if interpolation is required
- prevKey.constantToNext = this.compareValues( prevKey.value, currKey.value );
- newKeys.push( currKey );
- prevKey = currKey;
- }
- newKeys.push( this.keys[ this.keys.length - 1 ] );
- this.keys = newKeys;
- return this;
- }
- };
- THREE.KeyframeTrack.GetTrackTypeForValue = function( value ) {
- switch( typeof value ) {
- case "object": {
- if( value.lerp ) {
- return THREE.VectorKeyframeTrack;
- }
- if( value.slerp ) {
- return THREE.QuaternionKeyframeTrack;
- }
- break;
- }
- case "number": {
- return THREE.NumberKeyframeTrack;
- }
- case "boolean": {
- return THREE.BooleanKeyframeTrack;
- }
- case "string": {
- return THREE.StringKeyframeTrack;
- }
- };
- throw new Error( "Unsupported value type" );
- };
|