Sfoglia il codice sorgente

memeoization of lerp. add optimize, trim, validate, sort to KeyframeTrack. Create separate AnimationClipCreator class to get demo code out of ThreeJS src/ directory.

Ben Houston 10 anni fa
parent
commit
1be1d0511c

+ 175 - 0
examples/js/AnimationClipCreator.js

@@ -0,0 +1,175 @@
+/**
+ *
+ * Creator of typical test AnimationClips / KeyframeTracks
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.AnimationClipCreator = function() {
+};
+
+THREE.AnimationClipCreator.CreateMorphAnimation = function( morphTargets, duration ) {
+
+	var tracks = [];
+	var frameStep = duration / morphTargets.length;
+
+	for( var i = 0; i < morphTargets.length; i ++ ) {
+
+		var keys = [];
+
+		if( ( i - 1 ) >= 0 ) {
+
+			keys.push( { time: ( i - 1 ) * frameStep, value: 0 } );
+
+		}
+
+		keys.push( { time: i * frameStep, value: 1 } );
+
+		if( ( i + 1 ) <= morphTargets.length ) {
+
+			keys.push( { time: ( i + 1 ) * frameStep, value: 0 } );
+
+		}
+
+		var morphName = morphTargets[i].name;
+		var trackName = '.morphTargetInfluences[' + morphName + ']';
+		var track = new THREE.KeyframeTrack( trackName, keys );
+
+		tracks.push( track );
+	}
+
+	var clip = new THREE.AnimationClip( 'morphAnimation', duration, tracks );
+	//console.log( 'morphAnimationClip', clip );
+
+	return clip;
+};
+
+THREE.AnimationClipCreator.CreateRotationAnimation = function( period, axis ) {
+
+	var keys = [];
+	keys.push( { time: 0, value: 0 } );
+	keys.push( { time: period, value: 360 } );
+
+	axis = axis || 'x';
+	var trackName = '.rotation[' + axis + ']';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'rotate.x', 10, [ track ] );
+	//console.log( 'rotateClip', clip );
+
+	return clip;
+};
+
+THREE.AnimationClipCreator.CreateScaleAxisAnimation = function( period, axis ) {
+
+	var keys = [];
+	keys.push( { time: 0, value: 0 } );
+	keys.push( { time: period, value: 360 } );
+
+	axis = axis || 'x';
+	var trackName = '.scale[' + axis + ']';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'scale.x', 10, [ track ] );
+	//console.log( 'scaleClip', clip );
+
+	return clip;
+};
+
+THREE.AnimationClipCreator.CreateShakeAnimation = function( duration, shakeScale ) {
+
+	var keys = [];
+
+	for( var i = 0; i < duration * 10; i ++ ) {
+
+		keys.push( { 
+			time: ( i / 10.0 ),
+			value: new THREE.Vector3( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).multiply( shakeScale )
+		} );
+
+	}
+
+	var trackName = '.position';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'shake' + duration, duration, [ track ] );
+	//console.log( 'shakeClip', clip );
+
+	return clip;
+};
+
+
+THREE.AnimationClipCreator.CreatePulsationAnimation = function( duration, pulseScale ) {
+
+	var keys = [];
+
+	for( var i = 0; i < duration * 10; i ++ ) {
+
+		var scaleFactor = Math.random() * pulseScale;
+		keys.push( {
+			time: ( i / 10.0 ),
+			value: new THREE.Vector3( scaleFactor, scaleFactor, scaleFactor )
+		} );
+
+	}
+
+	var trackName = '.scale';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'scale' + duration, duration, [ track ] );
+	//console.log( 'scaleClip', clip );
+
+	return clip;
+};
+
+
+THREE.AnimationClipCreator.CreateVisibilityAnimation = function( duration ) {
+
+	var keys = [];
+	keys.push( {
+		time: 0,
+		value: true
+	} );
+	keys.push( {
+		time: duration - 1,
+		value: false
+	} );
+	keys.push( {
+		time: duration,
+		value: true
+	} );
+
+	var trackName = '.visible';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'visible' + duration, duration, [ track ] );
+	//console.log( 'scaleClip', clip );
+
+	return clip;
+};
+
+
+THREE.AnimationClipCreator.CreateMaterialColorAnimation = function( duration, colors, loop ) {
+
+	var timeStep = duration / colors.length;
+	var keys = [];
+	for( var i = 0; i <= colors.length; i ++ ) {
+		keys.push( { time: i * timeStep, value: colors[ i % colors.length ] } );
+	}
+
+	var trackName = '.material[0].color';
+
+	var track = new THREE.KeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'colorDiffuse', 10, [ track ] );
+	//console.log( 'diffuseClip', clip );
+
+	return clip;
+};
+

+ 7 - 7
examples/webgl_animation_track_clip_mixer.html

@@ -46,8 +46,8 @@
 
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
-
 		<script src="js/libs/dat.gui.min.js"></script>
+		<script src="js/AnimationClipCreator.js"></script>
 
 		<script>
 
@@ -257,21 +257,21 @@
 
 				mixer = new THREE.AnimationMixer( mesh );
 
-				var clip1 = THREE.AnimationClip.CreateShakeAnimation( 10, new THREE.Vector3( 10, 10, 10 ) );
+				var clip1 = THREE.AnimationClipCreator.CreateShakeAnimation( 10, new THREE.Vector3( 10, 10, 10 ) );
 				//mixer.addAction( new THREE.AnimationAction( clip1, 0, 1, 1, true ) );
-				var clip2 = THREE.AnimationClip.CreatePulsationAnimation( 10, 100 );
+				var clip2 = THREE.AnimationClipCreator.CreatePulsationAnimation( 10, 100 );
 				//mixer.addAction( new THREE.AnimationAction( clip2, 0, 1, 1, true ) );
-				var clip3 = THREE.AnimationClip.CreateRotationAnimation( 100, 'y' );
+				var clip3 = THREE.AnimationClipCreator.CreateRotationAnimation( 100, 'y' );
 				//mixer.addAction( new THREE.AnimationAction( clip3, 0, 1, 1, true ) );
-				var clip4 = THREE.AnimationClip.CreateScaleAxisAnimation( 10, 'x' );
+				var clip4 = THREE.AnimationClipCreator.CreateScaleAxisAnimation( 10, 'x' );
 				//mixer.addAction( new THREE.AnimationAction( clip4, 0, 1, 1, true ) );
-				var clip5 = THREE.AnimationClip.CreateMaterialColorAnimation( 10, [ new THREE.Color( 0xffffff ), new THREE.Color( 0xff0000 ), new THREE.Color( 0xff00ff ) ] );
+				var clip5 = THREE.AnimationClipCreator.CreateMaterialColorAnimation( 10, [ new THREE.Color( 0xffffff ), new THREE.Color( 0xff0000 ), new THREE.Color( 0xff00ff ) ] );
 				//mixer.addAction( new THREE.AnimationAction( clip5, 0, 1, 1, true ) );
 
 				//var clip6 = THREE.AnimationClip.CreateVisibilityAnimation( 10 );
 				//mixer.addAction( new THREE.AnimationAction( clip6, 0, 1, 1, true ) );
 				
-				var clip7 = THREE.AnimationClip.CreateMorphAnimation( mesh.geometry.morphTargets, 3 );
+				var clip7 = THREE.AnimationClipCreator.CreateMorphAnimation( mesh.geometry.morphTargets, 3 );
 				mixer.addAction( new THREE.AnimationAction( clip7, 0, 1, 1, true ) );
 			}
 

+ 80 - 333
src/animation/AnimationClip.js

@@ -1,8 +1,6 @@
 /**
  *
  * Reusable set of Tracks that represent an animation.
- *
- * TODO: MUST add support for importing AnimationClips from JSONLoader data files.
  * 
  * @author Ben Houston / http://clara.io/
  * @author David Sarno / http://lighthaus.us/
@@ -14,6 +12,11 @@ THREE.AnimationClip = function ( name, duration, tracks ) {
 	this.tracks = tracks;
 	this.duration = duration || 1;
 
+	// TODO: maybe only do these on demand, as doing them here could potentially slow down loading
+	// but leaving these here during development as this ensures a lot of testing of these functions
+	this.trim();
+	this.optimize();
+	
 };
 
 THREE.AnimationClip.prototype = {
@@ -38,41 +41,28 @@ THREE.AnimationClip.prototype = {
 		return results;
 	},
 
-	clean: function() {
-		// sort all keys by time
-		// move any keys before 0 to zero
-
-		// remove minus times
+	trim: function() {
 
-		if ( data.hierarchy[ h ].keys[ k ].time < 0 ) {
+		for( var trackIndex in this.tracks ) {
 
-			 data.hierarchy[ h ].keys[ k ].time = 0;
+			this.tracks[ trackIndex ].trim( this.duration );
 
 		}
 
-		// remove second key if there is more than one key at the same time
-		// remove all keys that are on the same time
-
-		for ( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) {
+	},
 
-			if ( data.hierarchy[ h ].keys[ k ].time === data.hierarchy[ h ].keys[ k - 1 ].time ) {
+	optimize: function() {
 
-				data.hierarchy[ h ].keys.splice( k, 1 );
-				k --;
+		for( var trackIndex in this.tracks ) {	
 
-			}
+			this.tracks[ trackIndex ].optimize();
 
 		}
 
+	}
 
-		// set index
-
-		for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+};
 
-			data.hierarchy[ h ].keys[ k ].index = k;
-
-		}
-	}
 
 /*
 	"animation" : {
@@ -90,360 +80,117 @@ THREE.AnimationClip.prototype = {
                         "scl" :[1,1,1]
                     },
 */
-	importFromData: function( data ) {
-
-		var convertTrack = function( trackName, dataKeys, dataKeyToValueFunc ) {
-
-			var keys = [];
-
-			for( var k = 0; k < dataKeys.length; k ++ ) {
-
-				var dataKey = dataKeys[k];
-				keys.push( { time: dataKey.time, value: dataKeyToValueFunc( dataKey ) } );
-		
-			}
-
-			return new THREE.KeyframeTrack( trackName, keys );
 
-		};
+THREE.AnimationClip.FromJSONLoaderAnimation = function( jsonLoader ) {
 
-		var clipName = data.name;
-		var duration = data.length;
-		var fps = data.fps;
-
-		var tracks = [];
-
-		var dataTracks = data.hierarchy;
-
-		for ( var h = 0; h < dataTracks.length; h ++ ) {
-
-			var boneName = '.bone[' + h + ']';
-			var dataKeys = dataTracks[ h ].keys;
-
-			// skip empty tracks
-			if( ! dataKeys || dataKeys.length == 0 ) {
-				continue;
-			}
-
-			// process morph targets in a way exactly compatible with AnimationHandler.init( data )
-			if( dataKeys[0].morphTargets ) {
-
-				// figure out all morph targets used in this track
-				var morphTargetNames = {};
-				for( var k = 0; k < dataKeys.length; k ++ ) {
-
-					if( dataKeys[k].morphTargets ) {
-						for( var m = 0; m < dataKeys[k].morphTargets.length; m ++ ) {
-
-							morphTagetNames[ dataKeys[k].morphTargets[m] ] = -1;
-						}
-					}
-
-				}
-
-				// create a track for each morph target with all zero morphTargetInfluences except for the keys in which the morphTarget is named.
-				for( var morphTargetName in morphTargetNames ) {
-
-					var keys = [];
-
-					for( var m = 0; m < dataKeys[k].morphTargets.length; m ++ ) {
+	var animation = jsonLoader['animation'];
+	if( ! animation ) {
+		console.error( "  no animation in JSONLoader data" );
+		return null;
+	}
 
-						var dataKey = dataKeys[k];
+	var convertTrack = function( trackName, animationKeys, animationKeyToValueFunc ) {
 
-						keys.push( {
-								time: dataKey.time,
-								value: (( dataKey.morphTarget === morphTargetName ) ? 1 : 0 )
-							});
-					
-					}
+		var keys = [];
 
-					tracks.push( new THREE.KeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
+		for( var k = 0; k < animationKeys.length; k ++ ) {
 
-				}
+			var animationKey = animationKeys[k];
+			keys.push( { time: animationKey.time, value: animationKeyToValueFunc( animationKey ) } );
+	
+		}
 
-			}
-			
-			// track contains positions...
-			if( dataKeys[0].pos ) {
+		return new THREE.KeyframeTrack( trackName, keys );
 
-				tracks.push( convertTracks( boneName + '.position', dataKeys, function( dataValue ) {
-						return new THREE.Vector3().fromArray( dataKey.pos )
-					} );
+	};
 
-			}
-			
-			// track contains quaternions...
-			if( dataKeys[0].rot ) {
+	var clipName = animation.name;
+	var duration = animation.length;
+	var fps = animation.fps;
 
-				tracks.push( convertTracks( boneName + '.quaternion', dataKeys, function( dataValue ) {
-						return new THREE.Quaternion().fromArray( dataKey.rot )
-					} );
+	var tracks = [];
 
-			}
+	var animationTracks = animation.hierarchy;
 
-			// track contains quaternions...
-			if( dataKeys[0].scl ) {
+	for ( var h = 0; h < animationTracks.length; h ++ ) {
 
-				tracks.push( convertTracks( boneName + '.quaternion', dataKeys, function( dataValue ) {
-						return new THREE.Vector3().fromArray( dataKey.scl )
-					} );
+		var boneName = '.bone[' + h + ']';
+		var animationKeys = animationTracks[ h ].keys;
 
-			}
+		// skip empty tracks
+		if( ! animationKeys || animationKeys.length == 0 ) {
+			continue;
 		}
 
-		var clip = new THREE.AnimationClip( clipName, duration, tracks );
-		console.log( 'clipFromHierarchy', clip );
-
-		return clip;
-
-	}
+		// process morph targets in a way exactly compatible with AnimationHandler.init( animation )
+		if( animationKeys[0].morphTargets ) {
 
-		// loop through all keys
+			// figure out all morph targets used in this track
+			var morphTargetNames = {};
+			for( var k = 0; k < animationKeys.length; k ++ ) {
 
-		for ( var h = 0; h < data.hierarchy.length; h ++ ) {
-
-			for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
-
-				// create quaternions
-
-				if ( data.hierarchy[ h ].keys[ k ].rot !== undefined &&
-				  ! ( data.hierarchy[ h ].keys[ k ].rot instanceof THREE.Quaternion ) ) {
-
-					var quat = data.hierarchy[ h ].keys[ k ].rot;
-					data.hierarchy[ h ].keys[ k ].rot = new THREE.Quaternion().fromArray( quat );
+				if( animationKeys[k].morphTargets ) {
+					for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
 
+						morphTagetNames[ animationKeys[k].morphTargets[m] ] = -1;
+					}
 				}
 
 			}
 
-			// prepare morph target keys
-
-			if ( data.hierarchy[ h ].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
-
-				// get all used
-
-				var usedMorphTargets = {};
-
-				for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
+			// create a track for each morph target with all zero morphTargetInfluences except for the keys in which the morphTarget is named.
+			for( var morphTargetName in morphTargetNames ) {
 
-					for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
+				var keys = [];
 
-						var morphTargetName = data.hierarchy[ h ].keys[ k ].morphTargets[ m ];
-						usedMorphTargets[ morphTargetName ] = - 1;
+				for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
 
-					}
+					var animationKey = animationKeys[k];
 
+					keys.push( {
+							time: animationKey.time,
+							value: (( animationKey.morphTarget === morphTargetName ) ? 1 : 0 )
+						});
+				
 				}
 
-				data.hierarchy[ h ].usedMorphTargets = usedMorphTargets;
-
-
-				// set all used on all frames
-
-				for ( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
-
-					var influences = {};
-
-					for ( var morphTargetName in usedMorphTargets ) {
-
-						for ( var m = 0; m < data.hierarchy[ h ].keys[ k ].morphTargets.length; m ++ ) {
-
-							if ( data.hierarchy[ h ].keys[ k ].morphTargets[ m ] === morphTargetName ) {
-
-								influences[ morphTargetName ] = data.hierarchy[ h ].keys[ k ].morphTargetsInfluences[ m ];
-								break;
-
-							}
-
-						}
-
-						if ( m === data.hierarchy[ h ].keys[ k ].morphTargets.length ) {
-
-							influences[ morphTargetName ] = 0;
-
-						}
-
-					}
-
-					data.hierarchy[ h ].keys[ k ].morphTargetsInfluences = influences;
-
-				}
+				tracks.push( new THREE.KeyframeTrack( '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
 
 			}
 
 		}
+		
+		// track contains positions...
+		if( animationKeys[0].pos ) {
 
-		data.initialized = true;
-
-		return data;
-
-	}*/
-};
-
-
-// TODO: Fix this for loops.
-// TODO: Test this
-THREE.AnimationClip.CreateMorphAnimation = function( morphTargets, duration ) {
-
-	var tracks = [];
-	var frameStep = duration / morphTargets.length;
-
-	for( var i = 0; i < morphTargets.length; i ++ ) {
-
-		var keys = [];
-
-		if( ( i - 1 ) >= 0 ) {
-
-			keys.push( { time: ( i - 1 ) * frameStep, value: 0 } );
+			tracks.push( convertTracks( boneName + '.position', animationKeys, function( dataValue ) {
+					return new THREE.Vector3().fromArray( animationKey.pos )
+				} );
 
 		}
+		
+		// track contains quaternions...
+		if( animationKeys[0].rot ) {
 
-		keys.push( { time: i * frameStep, value: 1 } );
-
-		if( ( i + 1 ) <= morphTargets.length ) {
-
-			keys.push( { time: ( i + 1 ) * frameStep, value: 0 } );
+			tracks.push( convertTracks( boneName + '.quaternion', animationKeys, function( dataValue ) {
+					return new THREE.Quaternion().fromArray( animationKey.rot )
+				} );
 
 		}
 
-		var morphName = morphTargets[i].name;
-		var trackName = '.morphTargetInfluences[' + morphName + ']';
-		var track = new THREE.KeyframeTrack( trackName, keys );
-
-		tracks.push( track );
-	}
-
-	var clip = new THREE.AnimationClip( 'morphAnimation', duration, tracks );
-	//console.log( 'morphAnimationClip', clip );
-
-	return clip;
-};
-
-THREE.AnimationClip.CreateRotationAnimation = function( period, axis ) {
-
-	var keys = [];
-	keys.push( { time: 0, value: 0 } );
-	keys.push( { time: period, value: 360 } );
-
-	axis = axis || 'x';
-	var trackName = '.rotation[' + axis + ']';
-
-	var track = new THREE.KeyframeTrack( trackName, keys );
-
-	var clip = new THREE.AnimationClip( 'rotate.x', 10, [ track ] );
-	//console.log( 'rotateClip', clip );
-
-	return clip;
-};
-
-THREE.AnimationClip.CreateScaleAxisAnimation = function( period, axis ) {
-
-	var keys = [];
-	keys.push( { time: 0, value: 0 } );
-	keys.push( { time: period, value: 360 } );
-
-	axis = axis || 'x';
-	var trackName = '.scale[' + axis + ']';
-
-	var track = new THREE.KeyframeTrack( trackName, keys );
-
-	var clip = new THREE.AnimationClip( 'scale.x', 10, [ track ] );
-	//console.log( 'scaleClip', clip );
-
-	return clip;
-};
-
-THREE.AnimationClip.CreateShakeAnimation = function( duration, shakeScale ) {
-
-	var keys = [];
-
-	for( var i = 0; i < duration * 10; i ++ ) {
-
-		keys.push( { 
-			time: ( i / 10.0 ),
-			value: new THREE.Vector3( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).multiply( shakeScale )
-		} );
-
-	}
-
-	var trackName = '.position';
-
-	var track = new THREE.KeyframeTrack( trackName, keys );
+		// track contains quaternions...
+		if( animationKeys[0].scl ) {
 
-	var clip = new THREE.AnimationClip( 'shake' + duration, duration, [ track ] );
-	//console.log( 'shakeClip', clip );
-
-	return clip;
-};
-
-
-THREE.AnimationClip.CreatePulsationAnimation = function( duration, pulseScale ) {
-
-	var keys = [];
-
-	for( var i = 0; i < duration * 10; i ++ ) {
-
-		var scaleFactor = Math.random() * pulseScale;
-		keys.push( {
-			time: ( i / 10.0 ),
-			value: new THREE.Vector3( scaleFactor, scaleFactor, scaleFactor )
-		} );
+			tracks.push( convertTracks( boneName + '.quaternion', animationKeys, function( dataValue ) {
+					return new THREE.Vector3().fromArray( animationKey.scl )
+				} );
 
+		}
 	}
 
-	var trackName = '.scale';
-
-	var track = new THREE.KeyframeTrack( trackName, keys );
-
-	var clip = new THREE.AnimationClip( 'scale' + duration, duration, [ track ] );
-	//console.log( 'scaleClip', clip );
+	var clip = new THREE.AnimationClip( clipName, duration, tracks );
+	console.log( 'clipFromJSONLoaderAnimation', clip );
 
 	return clip;
-};
-
-
-THREE.AnimationClip.CreateVisibilityAnimation = function( duration ) {
-
-	var keys = [];
-	keys.push( {
-		time: 0,
-		value: true
-	} );
-	keys.push( {
-		time: duration - 1,
-		value: false
-	} );
-	keys.push( {
-		time: duration,
-		value: true
-	} );
-
-	var trackName = '.visible';
 
-	var track = new THREE.KeyframeTrack( trackName, keys );
-
-	var clip = new THREE.AnimationClip( 'visible' + duration, duration, [ track ] );
-	//console.log( 'scaleClip', clip );
-
-	return clip;
 };
-
-
-THREE.AnimationClip.CreateMaterialColorAnimation = function( duration, colors, loop ) {
-
-	var timeStep = duration / colors.length;
-	var keys = [];
-	for( var i = 0; i <= colors.length; i ++ ) {
-		keys.push( { time: i * timeStep, value: colors[ i % colors.length ] } );
-	}
-
-	var trackName = '.material[0].color';
-
-	var track = new THREE.KeyframeTrack( trackName, keys );
-
-	var clip = new THREE.AnimationClip( 'colorDiffuse', 10, [ track ] );
-	//console.log( 'diffuseClip', clip );
-
-	return clip;
-};
-

+ 48 - 12
src/animation/AnimationUtils.js

@@ -5,43 +5,79 @@
 
  THREE.AnimationUtils = {
 
- 	// TODO/OPTIMIZATION: do the switch statement once per user of this and cache the resulting function and call it directly.
+ 	equalsFunc: function( exemplarValue ) {
+
+		if( exemplarValue.equals ) {
+			return function( a, b ) {
+				return a.equals( b );
+			}
+		}
+
+		return function( a, b ) {
+			return ( a === b );	
+		};
+
+	},
+
+
+ 	lerp: function( a, b, alpha, interTrack ) {
+
+		var lerpFunc = THREE.AnimationUtils.getLerpFunc( a, interTrack );
+
+		return lerpFunc( a, b, alpha );
+
+	},
+
  	// TODO/OPTIMIZATION: Accumulator should be writable and it will get rid of the *.clone() calls that are likely slow.
- 	lerp: function( accumulator, b, alpha, interTrack ) {
+	getLerpFunc: function( exemplarValue, interTrack ) {
 
-		var typeName = typeof accumulator;
+		var typeName = typeof exemplarValue;
 		switch( typeName ) {
 		 	case "object": {
 
-				if( accumulator.lerp ) {
+				if( exemplarValue.lerp ) {
 
-					return accumulator.clone().lerp( b, alpha );
+					return function( a, b, alpha ) {
+						return a.clone().lerp( b, alpha );
+					}
 
 				}
-				if( accumulator.slerp ) {
+				if( exemplarValue.slerp ) {
 
-					return accumulator.clone().slerp( b, alpha );
+					return function( a, b, alpha ) {
+						return a.clone().slerp( b, alpha );
+					}
 
 				}
 				break;
 			}
 		 	case "number": {
-				return accumulator * ( 1 - alpha ) + b * alpha;
+				return function( a, b, alpha ) {
+					return a * ( 1 - alpha ) + b * alpha;
+				}
 		 	}	
 		 	case "boolean": {
 		 		if( interTrack ) {
-		 			return ( alpha < 0.5 ) ? accumulator : b;
+					return function( a, b, alpha ) {
+			 			return ( alpha < 0.5 ) ? a : b;
+			 		}
 		 		}
 		 		else {
-		 			return accumulator;
+					return function( a, b, alpha ) {
+			 			return a;
+			 		}
 		 		}
 		 	}
 		 	case "string": {
 		 		if( interTrack ) {
-		 			return ( alpha < 0.5 ) ? accumulator : b;
+					return function( a, b, alpha ) {
+			 			return ( alpha < 0.5 ) ? a : b;
+			 		}
 		 		}
 		 		else {
-			 		return accumulator;		 		
+					return function( a, b, alpha ) {
+				 		return a;		 		
+				 	}
 			 	}
 		 	}
 		};

+ 138 - 5
src/animation/KeyframeTrack.js

@@ -13,9 +13,9 @@ THREE.KeyframeTrack = function ( name, keys ) {
 	this.name = name;
 	this.keys = keys || [];	// time in seconds, value as value
 
-	// TODO: sort keys via their times
-	//this.keys.sort( function( a, b ) { return a.time < b.time; } );
-
+	this.sort();
+	this.validate();
+	this.optimize();
 };
 
 THREE.KeyframeTrack.prototype = {
@@ -52,7 +52,7 @@ THREE.KeyframeTrack.prototype = {
 				// linear interpolation to start with
 				var alpha = ( time - this.keys[ i - 1 ].time ) / ( this.keys[ i ].time - this.keys[ i - 1 ].time );
 
-				var interpolatedValue = THREE.AnimationUtils.lerp( this.keys[ i - 1 ].value, this.keys[ i ].value, alpha );
+				var interpolatedValue = this.lerp( this.keys[ i - 1 ].value, this.keys[ i ].value, alpha );
 
 				/*console.log( '   interpolated: ', {
 					value: interpolatedValue, 
@@ -70,6 +70,139 @@ THREE.KeyframeTrack.prototype = {
 
 		throw new Error( "should never get here." );
 
+	},
+
+	// memoization of the lerp function for speed.
+	// NOTE: Do not optimize as a prototype initialization closure, as value0 will be different on a per class basis.
+	lerp: function( value0, value1, alpha ) {
+
+		this.lerp = THREE.AnimationUtils.getLerpFunc( value0, false );
+
+		return this.lerp( value0, value1, alpha );
+
+	},
+
+	// sort in ascending order
+	sort: function() {
+
+		var keyComparator = function(key0, key1) {
+			return key0.time - key1.time;
+		};
+
+		return function() {
+
+			this.keys.sort( keyComparator );
+		}
+
+	}();
+
+	// ensure we do not get a GarbageInGarbageOut situation, make sure tracks are at least minimally viable
+	// TODO: 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' || currKey.time == NaN ) {
+				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;
+
+		}
+
+	},
+
+	// 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
+	// TODO: linear based interpolation optimization with an error threshold.
+	optimize: function() {
+
+		var newKeys = [];
+		var prevKey = this.keys[0];
+		newKeys.push( prevKey );
+
+		var equalsFunc = THREE.AnimationUtils.equalsFunc( 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 ) ) {
+				console.log(  'removing key at the same time', currKey );
+				continue;
+			}
+
+			// remove completely unnecessary keyframes that are the same as their prev and next keys
+			if( equalsFunc( prevKey.value, currKey.value ) && equals( currKey.value, nextKey.value ) ) {
+				console.log(  'removing key identical to prev and next', currKey );
+				continue;
+			}
+
+			// TODO:add here a check for linear interpolation optimization.
+
+			newKeys.push( currKey );
+			prevKey = currKey;
+		}
+
+		console.log( '  track optimization removed keys:', ( this.keys.length - newKeys.length ), this.name );
+		this.keys = newKeys;
+
+	},
+
+	// removes keyframes before and after animation without changing any values within the range [0,duration].
+	// 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( duration ) {
+		
+		var firstKeysToRemove = 0;
+		for( var i = 1; i < this.keys.length; i ++ ) {
+			if( this.keys[i] <= 0 ) {
+				firstKeysToRemove ++;
+			}
+		}
+
+		var lastKeysToRemove = 0;
+		for( var i = this.keys.length - 2; i > 0; i ++ ) {
+			if( this.keys[i] >= duration ) {
+				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)
+		// TODO: Figure out if there is an array subarray function... might be faster
+		var keysWithoutLastKeys = this.keys.splice( this.keys.length - lastKeysToRemove, lastKeysToRemove );
+		var keysWithoutFirstKeys = keysWithoutLastKeys.splice( 0, firstKeysToRemove );
+		this.keys = keysWithoutFirstKeys;
+
+
 	}
 
-};
+};

+ 15 - 9
src/animation/PropertyBinding.js

@@ -40,22 +40,28 @@ THREE.PropertyBinding.prototype = {
 
 	accumulate: function( value, weight ) {
 		
-		if( this.cumulativeWeight === 0 ) {
+		var lerp = THREE.AnimationUtils.getLerpFunc( this.cumulativeValue, true );
 
-			this.cumulativeValue = value;
-			this.cumulativeWeight = weight;
+		this.accumulate = function( value, weight ) {
 
-		}
-		else {
+			if( this.cumulativeWeight === 0 ) {
+
+				this.cumulativeValue = value;
+				this.cumulativeWeight = weight;
+
+			}
+			else {
 
-			var lerpAlpha = weight / ( this.cumulativeWeight + weight );
-			this.cumulativeValue = THREE.AnimationUtils.lerp( this.cumulativeValue, value, lerpAlpha );
-			this.cumulativeWeight += weight;
+				var lerpAlpha = weight / ( this.cumulativeWeight + weight );
+				this.cumulativeValue = lerp( this.cumulativeValue, value, lerpAlpha );
+				this.cumulativeWeight += weight;
 
+			}
 		}
 
-	},
+		this.accumulate( value, weight );
 
+	},
 
 	apply: function() {