Browse Source

Merge remote-tracking branch 'origin/sceneAnimations'

jackcaron 10 years ago
parent
commit
bb72081136

+ 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.NumberKeyframeTrack( 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.NumberKeyframeTrack( 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.NumberKeyframeTrack( 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.VectorKeyframeTrack( 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.VectorKeyframeTrack( 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.BooleanKeyframeTrack( 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.ColorKeyframeTrack( trackName, keys );
+
+	var clip = new THREE.AnimationClip( 'colorDiffuse', 10, [ track ] );
+	//console.log( 'diffuseClip', clip );
+
+	return clip;
+};
+

+ 26 - 145
examples/js/BlendCharacter.js

@@ -20,12 +20,14 @@ THREE.BlendCharacter = function () {
 
 
 			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
 			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
 
 
+			scope.mixer = new THREE.AnimationMixer( scope );
+
 			// Create the animations
 			// Create the animations
 
 
 			for ( var i = 0; i < geometry.animations.length; ++ i ) {
 			for ( var i = 0; i < geometry.animations.length; ++ i ) {
 
 
 				var animName = geometry.animations[ i ].name;
 				var animName = geometry.animations[ i ].name;
-				scope.animations[ animName ] = new THREE.Animation( scope, geometry.animations[ i ] );
+				scope.animations[ animName ] = THREE.AnimationClip.FromJSONLoaderAnimation( geometry.animations[ i ], geometry.bones );
 
 
 			}
 			}
 
 
@@ -38,191 +40,70 @@ THREE.BlendCharacter = function () {
 
 
 	this.update = function( dt ) {
 	this.update = function( dt ) {
 
 
-		for ( var i = this.weightSchedule.length - 1; i >= 0; -- i ) {
-
-			var data = this.weightSchedule[ i ];
-			data.timeElapsed += dt;
-
-			// If the transition is complete, remove it from the schedule
-
-			if ( data.timeElapsed > data.duration ) {
-
-				data.anim.weight = data.endWeight;
-				this.weightSchedule.splice( i, 1 );
-
-				// If we've faded out completely, stop the animation
-
-				if ( data.anim.weight == 0 ) {
-
-					data.anim.stop( 0 );
-
-				}
-
-			} else {
-
-				// interpolate the weight for the current time
-
-				data.anim.weight = data.startWeight + ( data.endWeight - data.startWeight ) * data.timeElapsed / data.duration;
-
-			}
-
-		}
-
-		this.updateWarps( dt );
-
-	};
-
-	this.updateWarps = function( dt ) {
-
-		// Warping modifies the time scale over time to make 2 animations of different
-		// lengths match. This is useful for smoothing out transitions that get out of
-		// phase such as between a walk and run cycle
-
-		for ( var i = this.warpSchedule.length - 1; i >= 0; -- i ) {
-
-			var data = this.warpSchedule[ i ];
-			data.timeElapsed += dt;
-
-			if ( data.timeElapsed > data.duration ) {
-
-				data.to.weight = 1;
-				data.to.timeScale = 1;
-				data.from.weight = 0;
-				data.from.timeScale = 1;
-				data.from.stop( 0 );
-
-				this.warpSchedule.splice( i, 1 );
-
-			} else {
-
-				var alpha = data.timeElapsed / data.duration;
-
-				var fromLength = data.from.data.length;
-				var toLength = data.to.data.length;
-
-				var fromToRatio = fromLength / toLength;
-				var toFromRatio = toLength / fromLength;
-
-				// scale from each time proportionally to the other animation
-
-				data.from.timeScale = ( 1 - alpha ) + fromToRatio * alpha;
-				data.to.timeScale = alpha + toFromRatio * ( 1 - alpha );
-
-				data.from.weight = 1 - alpha;
-				data.to.weight = alpha;
-
-			}
-
-		}
+		this.mixer.update( dt );
 
 
 	};
 	};
 
 
 	this.play = function( animName, weight ) {
 	this.play = function( animName, weight ) {
 
 
-		this.animations[ animName ].play( 0, weight );
+		this.mixer.removeAllActions();
+		
+		this.mixer.play( new THREE.AnimationAction( this.animations[ animName ], 0, 1, 1, true ) );
 
 
 	};
 	};
 
 
 	this.crossfade = function( fromAnimName, toAnimName, duration ) {
 	this.crossfade = function( fromAnimName, toAnimName, duration ) {
 
 
-		var fromAnim = this.animations[ fromAnimName ];
-		var toAnim = this.animations[ toAnimName ];
+		this.mixer.removeAllActions();
+ 
+		var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ], 0, 1, 1, true );
+		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ], 0, 1, 1, true );
 
 
-		fromAnim.play( 0, 1 );
-		toAnim.play( 0, 0 );
+		this.mixer.play( fromAction );
+		this.mixer.play( toAction );
 
 
-		this.weightSchedule.push( {
-
-			anim: fromAnim,
-			startWeight: 1,
-			endWeight: 0,
-			timeElapsed: 0,
-			duration: duration
-
-		} );
-
-		this.weightSchedule.push( {
-
-			anim: toAnim,
-			startWeight: 0,
-			endWeight: 1,
-			timeElapsed: 0,
-			duration: duration
-
-		} );
+		this.mixer.crossFade( fromAction, toAction, duration, false );
 
 
 	};
 	};
 
 
 	this.warp = function( fromAnimName, toAnimName, duration ) {
 	this.warp = function( fromAnimName, toAnimName, duration ) {
 
 
-		var fromAnim = this.animations[ fromAnimName ];
-		var toAnim = this.animations[ toAnimName ];
-
-		fromAnim.play( 0, 1 );
-		toAnim.play( 0, 0 );
+		this.mixer.removeAllActions();
 
 
-		this.warpSchedule.push( {
+		var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ], 0, 1, 1, true );
+		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ], 0, 1, 1, true );
 
 
-			from: fromAnim,
-			to: toAnim,
-			timeElapsed: 0,
-			duration: duration
+		this.mixer.play( fromAction );
+		this.mixer.play( toAction );
 
 
-		} );
+		this.mixer.crossFade( fromAction, toAction, duration, true );
 
 
 	};
 	};
 
 
 	this.applyWeight = function( animName, weight ) {
 	this.applyWeight = function( animName, weight ) {
 
 
-		this.animations[ animName ].weight = weight;
+		if( this.mixer[ animName ] ) {
+			this.mixer[ animName ].weight = weight;
+		}
 
 
 	};
 	};
 
 
 	this.pauseAll = function() {
 	this.pauseAll = function() {
 
 
-		for ( var a in this.animations ) {
-
-			if ( this.animations[ a ].isPlaying ) {
-
-				this.animations[ a ].stop();
-
-			}
-
-		}
+		this.mixer.timeScale = 0;
 
 
 	};
 	};
 
 
 	this.unPauseAll = function() {
 	this.unPauseAll = function() {
 
 
-		for ( var a in this.animations ) {
-
-			if ( this.animations[ a ].isPlaying && this.animations[ a ].isPaused ) {
-
-				this.animations[ a ].pause();
-
-			}
-
-		}
+		this.mixer.timeScale = 1;
 
 
 	};
 	};
 
 
 
 
 	this.stopAll = function() {
 	this.stopAll = function() {
 
 
-		for ( a in this.animations ) {
-
-			if ( this.animations[ a ].isPlaying ) {
-
-				this.animations[ a ].stop( 0 );
-
-			}
-
-			this.animations[ a ].weight = 0;
-
-		}
-
-		this.weightSchedule.length = 0;
-		this.warpSchedule.length = 0;
+		this.mixer.removeAllActions();
 
 
 	};
 	};
 
 

+ 15 - 6
examples/js/BlendCharacterGui.js

@@ -2,7 +2,7 @@
  * @author Michael Guerrero / http://realitymeltdown.com
  * @author Michael Guerrero / http://realitymeltdown.com
  */
  */
 
 
-function BlendCharacterGui( animations ) {
+function BlendCharacterGui( blendMesh ) {
 
 
 	var controls = {
 	var controls = {
 
 
@@ -18,7 +18,7 @@ function BlendCharacterGui( animations ) {
 
 
 	};
 	};
 
 
-	var animations = animations;
+	var blendMesh = blendMesh;
 
 
 	this.showModel = function() {
 	this.showModel = function() {
 
 
@@ -38,11 +38,20 @@ function BlendCharacterGui( animations ) {
 
 
 	};
 	};
 
 
-	this.update = function() {
+	this.update = function( time ) {
 
 
-		controls[ 'idle' ] = animations[ 'idle' ].weight;
-		controls[ 'walk' ] = animations[ 'walk' ].weight;
-		controls[ 'run' ] = animations[ 'run' ].weight;
+		var getWeight = function( actionName ) {
+			for( var i = 0; i < blendMesh.mixer.actions.length; i ++ ) {
+				var action = blendMesh.mixer.actions[i];
+				if( action.clip.name === actionName ) {
+					return action.getWeightAt( time );	
+				}
+			}
+			return 0;
+		}
+		controls[ 'idle' ] = getWeight( 'idle' );
+		controls[ 'walk' ] = getWeight( 'walk' );
+		controls[ 'run' ] = getWeight( 'run' );
 
 
 	};
 	};
 
 

+ 3 - 3
examples/js/MD2Character.js

@@ -52,7 +52,7 @@ THREE.MD2Character = function () {
 			scope.root.add( mesh );
 			scope.root.add( mesh );
 
 
 			scope.meshBody = mesh;
 			scope.meshBody = mesh;
-			scope.activeAnimation = geo.firstAnimation;
+			scope.activeAnimationClipName = mesh.firstAnimationClip.name;
 
 
 			checkLoadingComplete();
 			checkLoadingComplete();
 
 
@@ -133,7 +133,7 @@ THREE.MD2Character = function () {
 			activeWeapon.visible = true;
 			activeWeapon.visible = true;
 			this.meshWeapon = activeWeapon;
 			this.meshWeapon = activeWeapon;
 
 
-			activeWeapon.playAnimation( this.activeAnimation, this.animationFPS );
+			activeWeapon.playAnimation( this.activeAnimationClipName, this.animationFPS );
 
 
 			this.meshWeapon.baseDuration = this.meshWeapon.duration;
 			this.meshWeapon.baseDuration = this.meshWeapon.duration;
 
 
@@ -219,7 +219,7 @@ THREE.MD2Character = function () {
 
 
 		mesh.parseAnimations();
 		mesh.parseAnimations();
 
 
-		mesh.playAnimation( geometry.firstAnimation, scope.animationFPS );
+		mesh.playAnimation( mesh.firstAnimationClip.name, scope.animationFPS );
 		mesh.baseDuration = mesh.duration;
 		mesh.baseDuration = mesh.duration;
 
 
 		return mesh;
 		return mesh;

+ 7 - 5
examples/js/UCSCharacter.js

@@ -15,6 +15,8 @@ THREE.UCSCharacter = function() {
 	this.materials = [];
 	this.materials = [];
 	this.morphs = [];
 	this.morphs = [];
 
 
+	this.mixer = new THREE.AnimationMixer( this.root );
+
 	this.onLoadComplete = function () {};
 	this.onLoadComplete = function () {};
 	
 	
 	this.loadCounter = 0;
 	this.loadCounter = 0;
@@ -41,10 +43,9 @@ THREE.UCSCharacter = function() {
 
 
 			geometry.computeBoundingBox();
 			geometry.computeBoundingBox();
 			geometry.computeVertexNormals();
 			geometry.computeVertexNormals();
-
-			//THREE.AnimationHandler.add( geometry.animation );
-
+			
 			mesh = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial() );
 			mesh = new THREE.SkinnedMesh( geometry, new THREE.MeshFaceMaterial() );
+			mesh.name = config.character;
 			scope.root.add( mesh );
 			scope.root.add( mesh );
 			
 			
 			var bb = geometry.boundingBox;
 			var bb = geometry.boundingBox;
@@ -54,8 +55,9 @@ THREE.UCSCharacter = function() {
 			mesh.castShadow = true;
 			mesh.castShadow = true;
 			mesh.receiveShadow = true;
 			mesh.receiveShadow = true;
 
 
-			animation = new THREE.Animation( mesh, geometry.animation );
-			animation.play();
+			var clipBones = THREE.AnimationClip.FromJSONLoaderAnimation( geometry.animation, geometry.bones, mesh.uuid );
+
+			scope.mixer.addAction( new THREE.AnimationAction( clipBones, 0, 1, 1, true ) );
 			
 			
 			scope.setSkin( 0 );
 			scope.setSkin( 0 );
 			
 			

File diff suppressed because it is too large
+ 69 - 0
examples/models/json/sceneNodeAnimation.json


File diff suppressed because it is too large
+ 2686 - 0
examples/models/json/three-gears-node-animation5.json


+ 217 - 0
examples/webgl_animation_nodes.html

@@ -0,0 +1,217 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - blendShapes + animation tracks [boxes]</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #000;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+
+				background-color: #fff;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+			}
+
+			a {
+				color: #0af;
+			}
+
+			#stats { position: absolute; top:0; left: 0 }
+			#stats #fps { background: transparent !important }
+			#stats #fps #fpsText { color: #777 !important }
+			#stats #fps #fpsGraph { display: none }
+		</style>
+	</head>
+
+	<body>
+
+		<div id="container"></div>
+
+		<div id="info">
+		<a href="http://threejs.org" target="_blank">three.js</a> webgl - clip system
+		- knight by <a href="http://vimeo.com/36113323">apendua</a>
+		</div>
+
+		<script src="../build/three.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.min.js"></script>
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script>
+
+			var SCREEN_WIDTH = window.innerWidth;
+			var SCREEN_HEIGHT = window.innerHeight;
+			var FLOOR = -250;
+
+			var container,stats;
+
+			var camera, scene, sceneAnimationClip;
+			var renderer;
+
+			var mesh, helper;
+
+			var mixer;
+		
+			var mouseX = 0, mouseY = 0;
+
+			var windowHalfX = window.innerWidth / 2;
+			var windowHalfY = window.innerHeight / 2;
+
+			var clock = new THREE.Clock();
+
+			document.addEventListener( 'mousemove', onDocumentMouseMove, false );
+
+			init();
+			animate();
+
+			function init() {
+
+				container = document.getElementById( 'container' );
+
+				camera = new THREE.PerspectiveCamera( 30, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 10000 );
+				camera.position.z = 150;
+
+				scene = new THREE.Scene();
+
+				scene.fog = new THREE.Fog( 0x000000, 2000, 10000 );
+
+				//scene.add( camera );
+
+				// GROUND
+
+				var geometry = new THREE.PlaneBufferGeometry( 16000, 16000 );
+				var material = new THREE.MeshPhongMaterial( { emissive: 0x000000 } );
+
+				var ground = new THREE.Mesh( geometry, material );
+				ground.position.set( 0, FLOOR, 0 );
+				ground.rotation.x = -Math.PI/2;
+				/*scene.add( ground );*/
+
+				ground.receiveShadow = true;
+
+
+				// RENDERER
+
+				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setClearColor( scene.fog.color );
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
+				renderer.domElement.style.position = "relative";
+
+				container.appendChild( renderer.domElement );
+
+				renderer.gammaInput = true;
+				renderer.gammaOutput = true;
+
+				renderer.shadowMap.enabled = true;
+
+
+				// STATS
+
+				stats = new Stats();
+				container.appendChild( stats.domElement );
+
+				//
+
+				var loader = new THREE.ObjectLoader();
+				loader.load( "models/json/three-gears-node-animation5.json", function ( loadedScene ) {
+
+					console.log( loadedScene );
+					sceneAnimationClip = loadedScene.animationClips[0];
+					scene = loadedScene;
+					scene.add( camera );
+				
+					mixer = new THREE.AnimationMixer( scene );
+			
+					mixer.addAction( new THREE.AnimationAction( sceneAnimationClip, 0, 1, 1, true ) );
+
+				} );
+
+				// GUI
+
+				initGUI();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				windowHalfX = window.innerWidth / 2;
+				windowHalfY = window.innerHeight / 2;
+
+				camera.aspect = window.innerWidth / window.innerHeight;
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+			}
+
+			function initGUI() {
+
+				var API = {
+					'show model'    : true,
+					'show skeleton' : false
+				};
+
+				var gui = new dat.GUI();
+
+				gui.add( API, 'show model' ).onChange( function() { mesh.visible = API[ 'show model' ]; } );
+
+				gui.add( API, 'show skeleton' ).onChange( function() { helper.visible = API[ 'show skeleton' ]; } );
+
+			}
+
+			function onDocumentMouseMove( event ) {
+
+				mouseX = ( event.clientX - windowHalfX );
+				mouseY = ( event.clientY - windowHalfY );
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var delta = 0.75 * clock.getDelta();
+
+				camera.position.x += ( mouseX - camera.position.x ) * .05;
+				camera.position.y = THREE.Math.clamp( camera.position.y + ( - mouseY - camera.position.y ) * .05, 0, 1000 );
+
+				camera.lookAt( scene.position );
+
+				if( mixer ) {
+					//console.log( "updating mixer by " + delta );
+					mixer.update( delta );
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 11 - 5
examples/webgl_animation_skinning_blending.html

@@ -116,6 +116,7 @@
 				var data = event.detail;
 				var data = event.detail;
 
 
 				blendMesh.stopAll();
 				blendMesh.stopAll();
+				blendMesh.unPauseAll();
 
 
 				// the blend mesh will combine 1 or more animations
 				// the blend mesh will combine 1 or more animations
 				for ( var i = 0; i < data.anims.length; ++i ) {
 				for ( var i = 0; i < data.anims.length; ++i ) {
@@ -155,7 +156,14 @@
 				var data = event.detail;
 				var data = event.detail;
 				for ( var i = 0; i < data.anims.length; ++i ) {
 				for ( var i = 0; i < data.anims.length; ++i ) {
 
 
-					blendMesh.applyWeight(data.anims[i], data.weights[i]);
+					for( var j = 0; j < blendMesh.mixer.actions.length; j ++ ) {
+						var action = blendMesh.mixer.actions[j];
+						if( action.clip.name === data.anims[i] ) {
+							if( action.getWeightAt( blendMesh.mixer.time ) !== data.weights[i] ) {
+								action.weight = data.weights[i];
+							}
+						}
+					}
 
 
 				}
 				}
 
 
@@ -218,7 +226,7 @@
 				blendMesh.animations[ 'walk' ].weight = 1 / 3;
 				blendMesh.animations[ 'walk' ].weight = 1 / 3;
 				blendMesh.animations[ 'run' ].weight = 1 / 3;
 				blendMesh.animations[ 'run' ].weight = 1 / 3;
 
 
-				gui = new BlendCharacterGui(blendMesh.animations);
+				gui = new BlendCharacterGui(blendMesh);
 
 
 				// Create the debug visualization
 				// Create the debug visualization
 
 
@@ -245,9 +253,7 @@
 
 
 				blendMesh.update( stepSize );
 				blendMesh.update( stepSize );
 				helper.update();
 				helper.update();
-				gui.update();
-
-				THREE.AnimationHandler.update( stepSize );
+				gui.update( blendMesh.mixer.time );
 
 
 				renderer.render( scene, camera );
 				renderer.render( scene, camera );
 				stats.update();
 				stats.update();

+ 13 - 54
examples/webgl_animation_skinning_morph.html

@@ -38,7 +38,7 @@
 		<div id="container"></div>
 		<div id="container"></div>
 
 
 		<div id="info">
 		<div id="info">
-		<a href="http://threejs.org" target="_blank">three.js</a> webgl - skinning + morphing
+		<a href="http://threejs.org" target="_blank">three.js</a> webgl - clip system
 		- knight by <a href="http://vimeo.com/36113323">apendua</a>
 		- knight by <a href="http://vimeo.com/36113323">apendua</a>
 		</div>
 		</div>
 
 
@@ -46,7 +46,6 @@
 
 
 		<script src="js/Detector.js"></script>
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 		<script src="js/libs/stats.min.js"></script>
-
 		<script src="js/libs/dat.gui.min.js"></script>
 		<script src="js/libs/dat.gui.min.js"></script>
 
 
 		<script>
 		<script>
@@ -62,6 +61,8 @@
 
 
 			var mesh, helper;
 			var mesh, helper;
 
 
+			var mixer;
+		
 			var mouseX = 0, mouseY = 0;
 			var mouseX = 0, mouseY = 0;
 
 
 			var windowHalfX = window.innerWidth / 2;
 			var windowHalfX = window.innerWidth / 2;
@@ -183,26 +184,9 @@
 
 
 			}
 			}
 
 
-			function ensureLoop( animation ) {
-
-				for ( var i = 0; i < animation.hierarchy.length; i ++ ) {
-
-					var bone = animation.hierarchy[ i ];
-
-					var first = bone.keys[ 0 ];
-					var last = bone.keys[ bone.keys.length - 1 ];
-
-					last.pos = first.pos;
-					last.rot = first.rot;
-					last.scl = first.scl;
-
-				}
-
-			}
-
 			function createScene( geometry, materials, x, y, z, s ) {
 			function createScene( geometry, materials, x, y, z, s ) {
 
 
-				ensureLoop( geometry.animation );
+				//ensureLoop( geometry.animation );
 
 
 				geometry.computeBoundingBox();
 				geometry.computeBoundingBox();
 				var bb = geometry.boundingBox;
 				var bb = geometry.boundingBox;
@@ -215,14 +199,6 @@
 						path + 'posz' + format, path + 'negz' + format
 						path + 'posz' + format, path + 'negz' + format
 					];
 					];
 
 
-
-				//var envMap = THREE.ImageUtils.loadTextureCube( urls );
-
-				//var map = THREE.ImageUtils.loadTexture( "textures/UV_Grid_Sm.jpg" );
-
-				//var bumpMap = THREE.ImageUtils.generateDataTexture( 1, 1, new THREE.Color() );
-				//var bumpMap = THREE.ImageUtils.loadTexture( "textures/water.jpg" );
-
 				for ( var i = 0; i < materials.length; i ++ ) {
 				for ( var i = 0; i < materials.length; i ++ ) {
 
 
 					var m = materials[ i ];
 					var m = materials[ i ];
@@ -256,9 +232,13 @@
 				helper.visible = false;
 				helper.visible = false;
 				scene.add( helper );
 				scene.add( helper );
 
 
-				var animation = new THREE.Animation( mesh, geometry.animation );
-				animation.play();
+				mixer = new THREE.AnimationMixer( mesh );
+		
+				var clipMorpher = THREE.AnimationClip.CreateMorphAnimation( mesh.geometry.morphTargets, 3 );
+				mixer.addAction( new THREE.AnimationAction( clipMorpher, 0, 1, 1, true ) );
 
 
+				var clipBones = THREE.AnimationClip.FromJSONLoaderAnimation( geometry.animation, geometry.bones );
+				mixer.addAction( new THREE.AnimationAction( clipBones, 0, 1, 1, true ) );
 			}
 			}
 
 
 			function initGUI() {
 			function initGUI() {
@@ -303,30 +283,9 @@
 
 
 				camera.lookAt( scene.position );
 				camera.lookAt( scene.position );
 
 
-				// update skinning
-
-				THREE.AnimationHandler.update( delta );
-
-				if ( helper !== undefined ) helper.update();
-
-				// update morphs
-
-				if ( mesh ) {
-
-					var time = Date.now() * 0.001;
-
-					// mouth
-
-					mesh.morphTargetInfluences[ 1 ] = ( 1 + Math.sin( 4 * time ) ) / 2;
-
-					// frown ?
-
-					mesh.morphTargetInfluences[ 2 ] = ( 1 + Math.sin( 2 * time ) ) / 2;
-
-					// eyes
-
-					mesh.morphTargetInfluences[ 3 ] = ( 1 + Math.cos( 4 * time ) ) / 2;
-
+				if( mixer ) {
+					mixer.update( delta );
+					helper.update();
 				}
 				}
 
 
 				renderer.render( scene, camera );
 				renderer.render( scene, camera );

+ 8 - 6
examples/webgl_loader_md2.html

@@ -302,19 +302,21 @@
 
 
 				var folder = gui.addFolder( "Animations" );
 				var folder = gui.addFolder( "Animations" );
 
 
-				var generateCallback = function( animationName ) {
+				var generateCallback = function( animationClip ) {
 
 
-					return function () { character.setAnimation( animationName ); };
+					return function () { character.setAnimation( animationClip.name ); };
 
 
 				};
 				};
 
 
 				var i = 0, guiItems = [];
 				var i = 0, guiItems = [];
-				var animations = character.meshBody.geometry.animations;
+				var animationClips = character.meshBody.animationClips;
 
 
-				for ( var a in animations ) {
+				for ( var i = 0; i < animationClips.length; i ++ ) {
 
 
-					playbackConfig[ a ] = generateCallback( a );
-					guiItems[ i ] = folder.add( playbackConfig, a, a );
+					var animationClip = animationClips[i];
+
+					playbackConfig[ animationClip.name ] = generateCallback( animationClip );
+					guiItems[ i ] = folder.add( playbackConfig, animationClip.name, animationClip.name );
 
 
 					i ++;
 					i ++;
 
 

+ 8 - 6
examples/webgl_morphtargets_horse.html

@@ -16,14 +16,14 @@
 	<body>
 	<body>
 
 
 		<script src="../build/three.min.js"></script>
 		<script src="../build/three.min.js"></script>
-
 		<script src="js/libs/stats.min.js"></script>
 		<script src="js/libs/stats.min.js"></script>
+		<script src="js/AnimationClipCreator.js"></script>
 
 
 		<script>
 		<script>
 
 
 			var container, stats;
 			var container, stats;
 			var camera, scene, projector, renderer;
 			var camera, scene, projector, renderer;
-			var mesh, animation;
+			var mesh, mixer;
 
 
 			init();
 			init();
 			animate();
 			animate();
@@ -66,8 +66,10 @@
 					mesh.scale.set( 1.5, 1.5, 1.5 );
 					mesh.scale.set( 1.5, 1.5, 1.5 );
 					scene.add( mesh );
 					scene.add( mesh );
 
 
-					animation = new THREE.MorphAnimation( mesh );
-					animation.play();
+					mixer = new THREE.AnimationMixer( mesh );
+
+					var clipMorphTargets = THREE.AnimationClipCreator.CreateMorphAnimation( geometry.morphTargets, 1.0 );
+					mixer.addAction( new THREE.AnimationAction( clipMorphTargets, 0, 1, 1, true ) );
 
 
 				} );
 				} );
 
 
@@ -126,11 +128,11 @@
 
 
 				camera.lookAt( camera.target );
 				camera.lookAt( camera.target );
 
 
-				if ( animation ) {
+				if ( mixer ) {
 
 
 					var time = Date.now();
 					var time = Date.now();
 
 
-					animation.update( time - prevTime );
+					mixer.update( ( time - prevTime ) * 0.001 );
 
 
 					prevTime = time;
 					prevTime = time;
 
 

+ 1 - 1
examples/webgl_morphtargets_human.html

@@ -224,7 +224,7 @@
 
 
 				// update skinning
 				// update skinning
 
 
-				THREE.AnimationHandler.update( delta );
+				character.mixer.update( delta );
 
 
 				renderer.render( scene, camera );
 				renderer.render( scene, camera );
 
 

+ 15 - 5
examples/webgl_shading_physical.html

@@ -38,10 +38,11 @@
 		<div id="info">
 		<div id="info">
 			<a href="http://threejs.org" target="_blank">three.js</a> - webgl physically based shading testbed
 			<a href="http://threejs.org" target="_blank">three.js</a> - webgl physically based shading testbed
 		</div>
 		</div>
-
+-
 		<script src="../build/three.min.js"></script>
 		<script src="../build/three.min.js"></script>
 
 
 		<script src="js/controls/TrackballControls.js"></script>
 		<script src="js/controls/TrackballControls.js"></script>
+		<script src="js/AnimationClipCreator.js"></script>
 
 
 		<script src="js/Detector.js"></script>
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 		<script src="js/libs/stats.min.js"></script>
@@ -73,7 +74,7 @@
 
 
 			var sunLight, pointLight, ambientLight;
 			var sunLight, pointLight, ambientLight;
 
 
-			var morph;
+			var morph, mixer;
 
 
 			var parameters, tweenDirection, tweenDay, tweenNight;
 			var parameters, tweenDirection, tweenDay, tweenNight;
 
 
@@ -273,11 +274,16 @@
 
 
 					morph = new THREE.MorphAnimMesh( geometry, morphMaterial );
 					morph = new THREE.MorphAnimMesh( geometry, morphMaterial );
 
 
+					mixer = new THREE.AnimationMixer( morph );
+
+					var clipMorphTargets = THREE.AnimationClipCreator.CreateMorphAnimation( geometry.morphTargets, 8.0 );
+					mixer.addAction( new THREE.AnimationAction( clipMorphTargets, 0, 1, 1, true ) );
+
 					var s = 200;
 					var s = 200;
 					morph.scale.set( s, s, s );
 					morph.scale.set( s, s, s );
 
 
-					morph.duration = 8000;
-					morph.mirroredLoop = true;
+					//morph.duration = 8000;
+					//morph.mirroredLoop = true;
 
 
 					morph.castShadow = true;
 					morph.castShadow = true;
 					morph.receiveShadow = true;
 					morph.receiveShadow = true;
@@ -521,7 +527,11 @@
 				TWEEN.update();
 				TWEEN.update();
 				controls.update();
 				controls.update();
 
 
-				if ( morph ) morph.updateAnimation( delta );
+				if ( mixer ) {
+
+					mixer.update( delta * 0.001 );
+
+				}
 
 
 				scene.fog.color.setHSL( 0.63, 0.05, parameters.control );
 				scene.fog.color.setHSL( 0.63, 0.05, parameters.control );
 				renderer.setClearColor( scene.fog.color );
 				renderer.setClearColor( scene.fog.color );

+ 132 - 0
src/animation/AnimationAction.js

@@ -0,0 +1,132 @@
+/**
+ *
+ * A clip that has been explicitly scheduled.
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.AnimationAction = function ( clip, startTime, timeScale, weight, loop ) {
+
+	this.clip = clip;
+	this.startTime = startTime || 0;
+	this.timeScale = timeScale || 1;
+	this.weight = weight || 1;
+	this.loop = loop || clip.loop || false;
+	this.loopCount = 0;
+	this.enabled = true;	// allow for easy disabling of the action.
+
+	this.clipTime = 0;
+
+	this.propertyBindingIndices = [];
+};
+
+THREE.AnimationAction.prototype = {
+
+	constructor: THREE.AnimationAction,
+
+	updateTime: function( clipDeltaTime ) {
+
+		var newClipTime = this.clipTime + clipDeltaTime;
+		var duration = this.clip.duration;
+
+		if( newClipTime <= 0 ) {
+
+			if( this.loop ) {
+
+				newClipTime -= Math.floor( newClipTime / duration ) * duration;
+		   		this.clipTime = newClipTime % duration;
+
+		   		this.loopCount --;
+
+	   			this.mixer.dispatchEvent( { type: 'loop', action: this, direction: -1 } );
+
+			}
+			else {
+
+		   		if( this.clipTime > 0 ) {
+
+		   			this.mixer.dispatchEvent( { type: 'finished', action: this, direction: -1 } );
+
+		   		}
+
+				this.clipTime = Math.min( newClipTime, Math.max( duration, 0 ) );
+
+			}
+
+		}
+		else if( newClipTime >= duration ) {
+
+			if( this.loop ) {
+	
+				this.clipTime = newClipTime % duration;
+
+		   		this.loopCount ++;
+	
+	 			this.mixer.dispatchEvent( { type: 'loop', action: this, direction: +1 } );
+
+			}
+			else {
+
+		   		if( this.clipTime < duration ) {
+
+		   			this.mixer.dispatchEvent( { type: 'finished', action: this, direction: +1 } );
+
+		   		}
+
+				this.clipTime = Math.min( newClipTime, Math.max( duration, 0 ) );
+
+		   	}
+
+	   	}
+	   	else {
+
+	   		this.clipTime = newClipTime;
+	   		
+	   	}
+	
+	   	return this.clipTime;
+
+	},
+
+	init: function( time ) {
+
+		this.clipTime = time - this.startTime;
+
+	},
+
+	update: function( clipDeltaTime ) {
+
+		this.updateTime( clipDeltaTime );
+
+		var clipResults = this.clip.getAt( this.clipTime );
+
+		return clipResults;
+		
+	},
+
+	getTimeScaleAt: function( time ) {
+
+		if( this.timeScale.getAt ) {
+			// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
+			return this.timeScale.getAt( time );
+
+		}
+
+		return this.timeScale;
+
+	},
+
+	getWeightAt: function( time ) {
+
+		if( this.weight.getAt ) {
+			// pass in time, not clip time, allows for fadein/fadeout across multiple loops of the clip
+			return this.weight.getAt( time );
+
+		}
+
+		return this.weight;
+
+	}
+
+};

+ 304 - 0
src/animation/AnimationClip.js

@@ -0,0 +1,304 @@
+/**
+ *
+ * Reusable set of Tracks that represent an animation.
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.AnimationClip = function ( name, duration, tracks ) {
+
+	this.name = name;
+	this.tracks = tracks;
+	this.duration = duration;
+
+	// this means it should figure out its duration by scanning the tracks
+	if( this.duration < 0 ) {
+		for( var i = 0; i < this.tracks.length; i ++ ) {
+			var track = this.tracks[i];
+			this.duration = Math.max( track.keys[ track.keys.length - 1 ].time );
+		}
+	}
+
+	// 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();
+
+	this.results = [];
+	
+};
+
+THREE.AnimationClip.prototype = {
+
+	constructor: THREE.AnimationClip,
+
+	getAt: function( clipTime ) {
+
+		clipTime = Math.max( 0, Math.min( clipTime, this.duration ) );
+
+		for( var i = 0; i < this.tracks.length; i ++ ) {
+
+			var track = this.tracks[ i ];
+
+			this.results[ i ] = track.getAt( clipTime );
+
+		}
+
+		return this.results;
+	},
+
+	trim: function() {
+
+		for( var i = 0; i < this.tracks.length; i ++ ) {
+
+			this.tracks[ i ].trim( 0, this.duration );
+
+		}
+
+		return this;
+
+	},
+
+	optimize: function() {
+
+		for( var i = 0; i < this.tracks.length; i ++ ) {
+
+			this.tracks[ i ].optimize();
+
+		}
+
+		return this;
+
+	}
+
+};
+
+
+THREE.AnimationClip.CreateMorphAnimationFromNames = function( morphTargetNames, duration ) {
+
+	var tracks = [];
+	var frameStep = duration / morphTargetNames.length;
+
+	for( var i = 0; i < morphTargetNames.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 ) <= morphTargetNames.length ) {
+
+			keys.push( { time: ( i + 1 ) * frameStep, value: 0 } );
+
+		}
+
+		if( ( i - 1 ) < 0 ) {
+			
+			keys.push( { time: ( morphTargetNames.length - 1 ) * frameStep, value: 0 } );
+			keys.push( { time: morphTargetNames.length * frameStep, value: 1 } );
+
+		}
+
+		var morphName = morphTargetNames[i];
+		var trackName = '.morphTargetInfluences[' + morphName + ']';
+		var track = new THREE.NumberKeyframeTrack( trackName, keys );
+
+		tracks.push( track );
+	}
+
+	var clip = new THREE.AnimationClip( 'morphAnimation', duration, tracks );
+
+	return clip;
+};
+
+THREE.AnimationClip.CreateMorphAnimation = function( morphTargets, duration ) {
+
+	var morphTargetNames = [];
+
+	for( var i = 0; i < morphTargets.length; i ++ ) {
+
+		morphTargetNames.push( morphTargets[i].name );
+
+	}
+
+	return THREE.AnimationClip.CreateMorphAnimationFromNames( morphTargetNames, duration );
+
+};
+
+
+THREE.AnimationClip.FromImplicitMorphTargetAnimations = function( morphTargets, fps ) {
+	
+	var animations = {};
+	var animationsArray = [];
+
+	var pattern = /([a-z]+)_?(\d+)/;
+
+	for ( var i = 0, il = morphTargets.length; i < il; i ++ ) {
+
+		var morphTarget = morphTargets[ i ];
+		var parts = morphTarget.name.match( pattern );
+
+		if ( parts && parts.length > 1 ) {
+
+			var animationName = parts[ 1 ];
+
+			var animation = animations[ animationName ];
+			if ( ! animation ) {
+				animations[ animationName ] = animation = { name: animationName, morphTargetNames: [] };
+				animationsArray.push( animation );
+			}
+
+			animation.morphTargetNames.push( morphTarget.name );
+		}
+
+	}
+
+	var clips = [];
+
+	for( var i = 0; i < animationsArray.length; i ++ ) {
+
+		var animation = animationsArray[i];
+
+		var clip = new THREE.AnimationClip.CreateMorphAnimationFromNames( animation.morphTargetNames, animation.morphTargetNames.length * fps );
+		clip.name = animation.name;
+
+		clips.push( clip );
+	}
+
+	return clips;
+
+};
+
+THREE.AnimationClip.FromJSONLoaderAnimation = function( animation, bones, nodeName ) {
+
+	if( ! animation ) {
+		console.error( "  no animation in JSONLoader data" );
+		return null;
+	}
+
+	var convertTrack = function( trackName, animationKeys, propertyName, trackType, animationKeyToValueFunc ) {
+
+		var keys = [];
+
+		for( var k = 0; k < animationKeys.length; k ++ ) {
+
+			var animationKey = animationKeys[k];
+
+			if( animationKey[propertyName] !== undefined ) {
+
+				keys.push( { time: animationKey.time, value: animationKeyToValueFunc( animationKey ) } );
+			}
+	
+		}
+
+		// only return track if there are actually keys.
+		if( keys.length > 0 ) {
+		
+			return new trackType( trackName, keys );
+
+		}
+
+		return null;
+
+	};
+
+	var clipName = animation.name;
+	var duration = animation.length;
+	var fps = animation.fps;
+
+	var tracks = [];
+
+	var animationTracks = animation.hierarchy;
+
+	for ( var h = 0; h < animationTracks.length; h ++ ) {
+
+		var animationKeys = animationTracks[ h ].keys;
+
+		// skip empty tracks
+		if( ! animationKeys || animationKeys.length == 0 ) {
+			continue;
+		}
+
+		// process morph targets in a way exactly compatible with AnimationHandler.init( animation )
+		if( animationKeys[0].morphTargets ) {
+
+			// figure out all morph targets used in this track
+			var morphTargetNames = {};
+			for( var k = 0; k < animationKeys.length; k ++ ) {
+
+				if( animationKeys[k].morphTargets ) {
+					for( var m = 0; m < animationKeys[k].morphTargets.length; m ++ ) {
+
+						morphTargetNames[ animationKeys[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 < animationKeys[k].morphTargets.length; m ++ ) {
+
+					var animationKey = animationKeys[k];
+
+					keys.push( {
+							time: animationKey.time,
+							value: (( animationKey.morphTarget === morphTargetName ) ? 1 : 0 )
+						});
+				
+				}
+
+				tracks.push( new THREE.NumberKeyframeTrack( nodeName + '.morphTargetInfluence[' + morphTargetName + ']', keys ) );
+
+			}
+
+			duration = morphTargetNames.length * ( fps || 1.0 );
+
+		}
+		else {
+
+			var boneName = nodeName + '.bones[' + bones[ h ].name + ']';
+		
+			// track contains positions...
+			var positionTrack = convertTrack( boneName + '.position', animationKeys, 'pos', THREE.VectorKeyframeTrack, function( animationKey ) {
+					return new THREE.Vector3().fromArray( animationKey.pos )
+				} );
+
+			if( positionTrack ) tracks.push( positionTrack );
+			
+			// track contains quaternions...
+			var quaternionTrack = convertTrack( boneName + '.quaternion', animationKeys, 'rot', THREE.QuaternionKeyframeTrack, function( animationKey ) {
+					if( animationKey.rot.slerp ) {
+						return animationKey.rot.clone();
+					}
+					else {
+						return new THREE.Quaternion().fromArray( animationKey.rot );
+					}
+				} );
+
+			if( quaternionTrack ) tracks.push( quaternionTrack );
+
+			// track contains quaternions...
+			var scaleTrack = convertTrack( boneName + '.scale', animationKeys, 'scl', THREE.VectorKeyframeTrack, function( animationKey ) {
+					return new THREE.Vector3().fromArray( animationKey.scl )
+				} );
+
+			if( scaleTrack ) tracks.push( scaleTrack );
+
+		}
+	}
+
+	var clip = new THREE.AnimationClip( clipName, duration, tracks );
+
+	return clip;
+
+};

+ 257 - 0
src/animation/AnimationMixer.js

@@ -0,0 +1,257 @@
+/**
+ *
+ * Mixes together the AnimationClips scheduled by AnimationActions and applies them to the root and subtree
+ *
+ *
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.AnimationMixer = function( root ) {
+
+	this.root = root;
+	this.time = 0;
+	this.timeScale = 1.0;
+	this.actions = [];
+	this.propertyBindings = [];
+
+};
+
+THREE.AnimationMixer.prototype = {
+
+	constructor: THREE.AnimationMixer,
+
+	addAction: function( action ) {
+
+		this.actions.push( action );
+		action.mixer = this;
+
+		var tracks = action.clip.tracks;
+
+		for( var i = 0; i < tracks.length; i ++ ) {
+
+			var track = tracks[ i ];
+
+			var j = this.getPropertyBindingIndex( track.name )
+
+			var propertyBinding;
+
+			if( j < 0 ) {
+			
+				propertyBinding = new THREE.PropertyBinding( this.root, track.name );
+				this.propertyBindings.push( propertyBinding );
+			
+			}
+			else {
+				propertyBinding = this.propertyBindings[ j ];
+			}
+			
+			// track usages of shared property bindings, because if we leave too many around, the mixer can get slow
+			propertyBinding.referenceCount += 1;
+
+		}
+
+		this.updatePropertyBindingIndices();
+
+	},
+
+	getPropertyBindingIndex: function( trackName ) {
+		
+		for( var k = 0; k < this.propertyBindings.length; k ++ ) {
+			if( this.propertyBindings[k].trackName === trackName ) {
+				return k;
+			}
+		}	
+
+		return -1;
+
+	},
+
+	updatePropertyBindingIndices: function() {
+
+		for( var i = 0; i < this.actions.length; i++ ) {
+
+			var action = this.actions[i];
+
+			var propertyBindingIndices = [];
+
+			for( var j = 0; j < action.clip.tracks.length; j ++ ) {
+
+				var trackName = action.clip.tracks[j].name;
+				propertyBindingIndices.push( this.getPropertyBindingIndex( trackName ) );
+			
+			}
+
+			action.propertyBindingIndices = propertyBindingIndices;
+		}
+
+	},
+
+	removeAllActions: function() {
+
+		for( var i = 0; i < this.actions.length; i ++ ) {
+
+			this.actions[i].mixer = null;
+			
+		}
+
+		// unbind all property bindings
+		for( var i = 0; i < this.propertyBindings.length; i ++ ) {
+
+			this.propertyBindings[i].unbind();
+
+		}
+
+		this.actions = [];
+		this.propertyBindings = [];
+
+		return this;
+
+	},
+
+	removeAction: function( action ) {
+
+		var index = this.actions.indexOf( action );
+
+		if ( index !== - 1 ) {
+
+			this.actions.splice( index, 1 );
+			action.mixer = null;
+
+		}
+
+		// remove unused property bindings because if we leave them around the mixer can get slow
+		var tracks = action.clip.tracks;
+
+		for( var i = 0; i < tracks.length; i ++ ) {
+		
+			var track = tracks[ i ];
+			var propertyBindingIndex = this.getPropertyBindingIndex( track.name );
+			var propertyBinding = this.propertyBindings[ propertyBindingIndex ];
+
+			propertyBinding.referenceCount -= 1;
+
+			if( propertyBinding.referenceCount <= 0 ) {
+
+				propertyBinding.unbind();
+
+				this.propertyBindings.splice( this.propertyBindings.indexOf( propertyBinding ), 1 );
+
+			}
+		}
+
+		this.updatePropertyBindingIndices();
+
+		return this;
+
+	},
+
+	play: function( action, optionalFadeInDuration ) {
+
+		action.startTime = this.time;
+		this.addAction( action );
+
+		return this;
+
+	},
+
+	fadeOut: function( action, duration ) {
+
+		var keys = [];
+
+		keys.push( { time: this.time, value: 1 } );
+		keys.push( { time: this.time + duration, value: 0 } );
+		
+		action.weight = new THREE.NumberKeyframeTrack( "weight", keys );
+
+		return this;
+
+	},
+
+	fadeIn: function( action, duration ) {
+		
+		var keys = [];
+		
+		keys.push( { time: this.time, value: 0 } );
+		keys.push( { time: this.time + duration, value: 1 } );
+		
+		action.weight = new THREE.NumberKeyframeTrack( "weight", keys );
+
+		return this;
+
+	},
+
+	warp: function( action, startTimeScale, endTimeScale, duration ) {
+
+		var keys = [];
+		
+		keys.push( { time: this.time, value: startTimeScale } );
+		keys.push( { time: this.time + duration, value: endTimeScale } );
+		
+		action.timeScale = new THREE.NumberKeyframeTrack( "timeScale", keys );
+
+		return this;
+
+	},
+
+	crossFade: function( fadeOutAction, fadeInAction, duration, warp ) {
+
+		this.fadeOut( fadeOutAction, duration );
+		this.fadeIn( fadeInAction, duration );
+
+		if( warp ) {
+	
+			var startEndRatio = fadeOutAction.clip.duration / fadeInAction.clip.duration;
+			var endStartRatio = 1.0 / startEndRatio;
+
+			this.warp( fadeOutAction, 1.0, startEndRatio, duration );
+			this.warp( fadeInAction, endStartRatio, 1.0, duration );
+
+		}
+
+		return this;
+		
+	},
+
+	update: function( deltaTime ) {
+
+		var mixerDeltaTime = deltaTime * this.timeScale;
+		this.time += mixerDeltaTime;
+
+		for( var i = 0; i < this.actions.length; i ++ ) {
+
+			var action = this.actions[i];
+
+			var weight = action.getWeightAt( this.time );
+
+			var actionTimeScale = action.getTimeScaleAt( this.time );
+			var actionDeltaTime = mixerDeltaTime * actionTimeScale;
+		
+			var actionResults = action.update( actionDeltaTime );
+
+			if( action.weight <= 0 || ! action.enabled ) continue;
+
+			for( var j = 0; j < actionResults.length; j ++ ) {
+
+				var name = action.clip.tracks[j].name;
+
+				this.propertyBindings[ action.propertyBindingIndices[ j ] ].accumulate( actionResults[j], weight );
+
+			}
+
+		}
+	
+		// apply to nodes
+		for ( var i = 0; i < this.propertyBindings.length; i ++ ) {
+
+			this.propertyBindings[ i ].apply();
+
+		}
+
+		return this;
+		
+	}
+
+};
+
+THREE.EventDispatcher.prototype.apply( THREE.AnimationMixer.prototype );

+ 116 - 0
src/animation/AnimationUtils.js

@@ -0,0 +1,116 @@
+/**
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+ THREE.AnimationUtils = {
+
+ 	getEqualsFunc: function( exemplarValue ) {
+
+		if( exemplarValue.equals ) {
+			return function equals_object( a, b ) {
+				return a.equals( b );
+			}
+		}
+
+		return function equals_primitive( a, b ) {
+			return ( a === b );	
+		};
+
+	},
+
+ 	clone: function( exemplarValue ) {
+
+ 		var typeName = typeof exemplarValue;
+		if( typeName === "object" ) {
+			if( exemplarValue.clone ) {
+				return exemplarValue.clone();
+			}
+			console.error( "can not figure out how to copy exemplarValue", exemplarValue );
+		}
+
+		return exemplarValue;
+
+	},
+
+ 	lerp: function( a, b, alpha, interTrack ) {
+
+		var lerpFunc = THREE.AnimationUtils.getLerpFunc( a, interTrack );
+
+		return lerpFunc( a, b, alpha );
+
+	},
+
+	lerp_object: function( a, b, alpha ) {
+		return a.lerp( b, alpha );
+	},
+	
+	slerp_object: function( a, b, alpha ) {
+		return a.slerp( b, alpha );
+	},
+
+	lerp_number: function( a, b, alpha ) {
+		return a * ( 1 - alpha ) + b * alpha;
+	},
+
+	lerp_boolean: function( a, b, alpha ) {
+		return ( alpha < 0.5 ) ? a : b;
+	},
+
+	lerp_boolean_immediate: function( a, b, alpha ) {
+		return a;
+	},
+	
+	lerp_string: function( a, b, alpha ) {
+		return ( alpha < 0.5 ) ? a : b;
+	},
+	
+	lerp_string_immediate: function( a, b, alpha ) {
+ 		return a;		 		
+ 	},
+
+	// NOTE: this is an accumulator function that modifies the first argument (e.g. a).  This is to minimize memory alocations.
+	getLerpFunc: function( exemplarValue, interTrack ) {
+
+		if( exemplarValue === undefined || exemplarValue === null ) throw new Error( "examplarValue is null" );
+
+		var typeName = typeof exemplarValue;
+		switch( typeName ) {
+		 	case "object": {
+
+				if( exemplarValue.lerp ) {
+
+					return THREE.AnimationUtils.lerp_object;
+
+				}
+				if( exemplarValue.slerp ) {
+
+					return THREE.AnimationUtils.slerp_object;
+
+				}
+				break;
+			}
+		 	case "number": {
+				return THREE.AnimationUtils.lerp_number;
+		 	}	
+		 	case "boolean": {
+		 		if( interTrack ) {
+					return THREE.AnimationUtils.lerp_boolean;
+		 		}
+		 		else {
+					return THREE.AnimationUtils.lerp_boolean_immediate;
+		 		}
+		 	}
+		 	case "string": {
+		 		if( interTrack ) {
+					return THREE.AnimationUtils.lerp_string;
+		 		}
+		 		else {
+					return THREE.AnimationUtils.lerp_string_immediate;
+			 	}
+		 	}
+		};
+
+	}
+	
+};

+ 270 - 0
src/animation/KeyframeTrack.js

@@ -0,0 +1,270 @@
+/**
+ *
+ * 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 = 0; 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( timeScale !== 1.0 ) {
+
+			for( var i = 0; 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" );
+};

+ 393 - 0
src/animation/PropertyBinding.js

@@ -0,0 +1,393 @@
+/**
+ *
+ * A track bound to a real value in the scene graph.
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.PropertyBinding = function ( rootNode, trackName ) {
+
+	this.rootNode = rootNode;
+	this.trackName = trackName;
+	this.referenceCount = 0;
+	this.originalValue = null; // the value of the property before it was controlled by this binding
+
+	var parseResults = THREE.PropertyBinding.parseTrackName( trackName );
+
+	this.directoryName = parseResults.directoryName;
+	this.nodeName = parseResults.nodeName;
+	this.objectName = parseResults.objectName;
+	this.objectIndex = parseResults.objectIndex;
+	this.propertyName = parseResults.propertyName;
+	this.propertyIndex = parseResults.propertyIndex;
+
+	this.node = THREE.PropertyBinding.findNode( rootNode, this.nodeName ) || rootNode;
+	
+	this.cumulativeValue = null;
+	this.cumulativeWeight = 0;
+};
+
+THREE.PropertyBinding.prototype = {
+
+	constructor: THREE.PropertyBinding,
+
+	reset: function() {
+
+		this.cumulativeValue = null;
+		this.cumulativeWeight = 0;
+
+	},
+
+	accumulate: function( value, weight ) {
+		
+		if( ! this.isBound ) this.bind();
+
+		if( this.cumulativeWeight === 0 ) {
+
+			if( weight > 0 ) {
+
+				if( this.cumulativeValue === null ) {
+					this.cumulativeValue = THREE.AnimationUtils.clone( value );
+				}
+				this.cumulativeWeight = weight;
+
+			}
+
+		}
+		else {
+
+			var lerpAlpha = weight / ( this.cumulativeWeight + weight );
+			this.cumulativeValue = this.lerpValue( this.cumulativeValue, value, lerpAlpha );
+			this.cumulativeWeight += weight;
+
+		}
+
+	},
+
+	unbind: function() {
+
+		if( ! this.isBound ) return;
+
+		this.setValue( this.originalValue );
+
+		this.setValue = null;
+		this.getValue = null;
+		this.lerpValue = null;
+		this.equalsValue = null;
+		this.triggerDirty = null;	
+		this.isBound = false;
+
+	},
+
+	// bind to the real property in the scene graph, remember original value, memorize various accessors for speed/inefficiency
+	bind: function() {
+
+		if( this.isBound ) return;
+
+		var targetObject = this.node;
+
+ 		// ensure there is a value node
+		if( ! targetObject ) {
+			console.error( "  trying to update node for track: " + this.trackName + " but it wasn't found." );
+			return;
+		}
+
+		if( this.objectName ) {
+			// special case were we need to reach deeper into the hierarchy to get the face materials....
+			if( this.objectName === "materials" ) {
+				if( ! targetObject.material ) {
+					console.error( '  can not bind to material as node does not have a material', this );
+					return;				
+				}
+				if( ! targetObject.material.materials ) {
+					console.error( '  can not bind to material.materials as node.material does not have a materials array', this );
+					return;				
+				}
+				targetObject = targetObject.material.materials;
+			}
+			else if( this.objectName === "bones" ) {
+				if( ! targetObject.skeleton ) {
+					console.error( '  can not bind to bones as node does not have a skeleton', this );
+					return;
+				}
+				// potential future optimization: skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
+				
+				targetObject = targetObject.skeleton.bones;
+
+				// support resolving morphTarget names into indices.
+				for( var i = 0; i < targetObject.length; i ++ ) {
+					if( targetObject[i].name === this.objectIndex ) {
+						this.objectIndex = i;
+						break;
+					}
+				}
+			}
+			else {
+
+				if( targetObject[ this.objectName ] === undefined ) {
+					console.error( '  can not bind to objectName of node, undefined', this );			
+					return;
+				}
+				targetObject = targetObject[ this.objectName ];
+			}
+			
+			if( this.objectIndex !== undefined ) {
+				if( targetObject[ this.objectIndex ] === undefined ) {
+					console.error( "  trying to bind to objectIndex of objectName, but is undefined:", this, targetObject );
+					return;				
+				}
+
+				targetObject = targetObject[ this.objectIndex ];
+			}
+
+		}
+
+ 		// special case mappings
+ 		var nodeProperty = targetObject[ this.propertyName ];
+		if( ! nodeProperty ) {
+			console.error( "  trying to update property for track: " + this.nodeName + '.' + this.propertyName + " but it wasn't found.", targetObject );				
+			return;
+		}
+
+		// access a sub element of the property array (only primitives are supported right now)
+		if( this.propertyIndex !== undefined ) {
+
+			if( this.propertyName === "morphTargetInfluences" ) {
+				// potential optimization, skip this if propertyIndex is already an integer, and convert the integer string to a true integer.
+				
+				// support resolving morphTarget names into indices.
+				if( ! targetObject.geometry ) {
+					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry', this );				
+				}
+				if( ! targetObject.geometry.morphTargets ) {
+					console.error( '  can not bind to morphTargetInfluences becasuse node does not have a geometry.morphTargets', this );				
+				}
+				
+				for( var i = 0; i < this.node.geometry.morphTargets.length; i ++ ) {
+					if( targetObject.geometry.morphTargets[i].name === this.propertyIndex ) {
+						this.propertyIndex = i;
+						break;
+					}
+				}
+			}
+
+			this.setValue = function setValue_propertyIndexed( value ) {
+				if( ! this.equalsValue( nodeProperty[ this.propertyIndex ], value ) ) {
+					nodeProperty[ this.propertyIndex ] = value;
+					return true;
+				}
+				return false;
+			};
+
+			this.getValue = function getValue_propertyIndexed() {
+				return nodeProperty[ this.propertyIndex ];
+			};
+
+		}
+		// must use copy for Object3D.Euler/Quaternion		
+		else if( nodeProperty.copy ) {
+			
+			this.setValue = function setValue_propertyObject( value ) {
+				if( ! this.equalsValue( nodeProperty, value ) ) {
+					nodeProperty.copy( value );
+					return true;
+				}
+				return false;
+			}
+
+			this.getValue = function getValue_propertyObject() {
+				return nodeProperty;
+			};
+
+		}
+		// otherwise just set the property directly on the node (do not use nodeProperty as it may not be a reference object)
+		else {
+
+			this.setValue = function setValue_property( value ) {
+				if( ! this.equalsValue( targetObject[ this.propertyName ], value ) ) {
+					targetObject[ this.propertyName ] = value;	
+					return true;
+				}
+				return false;
+			}
+
+			this.getValue = function getValue_property() {
+				return targetObject[ this.propertyName ];
+			};
+
+		}
+
+		// trigger node dirty			
+		if( targetObject.needsUpdate !== undefined ) { // material
+			
+			this.triggerDirty = function triggerDirty_needsUpdate() {
+				this.node.needsUpdate = true;
+			}
+
+		}			
+		else if( targetObject.matrixWorldNeedsUpdate !== undefined ) { // node transform
+			
+			this.triggerDirty = function triggerDirty_matrixWorldNeedsUpdate() {
+				targetObject.matrixWorldNeedsUpdate = true;
+			}
+
+		}
+
+		this.originalValue = this.getValue();
+
+		this.equalsValue = THREE.AnimationUtils.getEqualsFunc( this.originalValue );
+		this.lerpValue = THREE.AnimationUtils.getLerpFunc( this.originalValue, true );
+
+		this.isBound = true;
+
+	},
+
+	apply: function() {
+
+		// for speed capture the setter pattern as a closure (sort of a memoization pattern: https://en.wikipedia.org/wiki/Memoization)
+		if( ! this.isBound ) this.bind();
+
+		// early exit if there is nothing to apply.
+		if( this.cumulativeWeight > 0 ) {
+		
+			// blend with original value
+			if( this.cumulativeWeight < 1 ) {
+
+				var remainingWeight = 1 - this.cumulativeWeight;
+				var lerpAlpha = remainingWeight / ( this.cumulativeWeight + remainingWeight );
+				this.cumulativeValue = this.lerpValue( this.cumulativeValue, this.originalValue, lerpAlpha );
+
+			}
+
+			var valueChanged = this.setValue( this.cumulativeValue );
+
+			if( valueChanged && this.triggerDirty ) {
+				this.triggerDirty();
+			}
+
+			// reset accumulator
+			this.cumulativeValue = null;
+			this.cumulativeWeight = 0;
+
+		}
+	}
+
+};
+
+
+THREE.PropertyBinding.parseTrackName = function( trackName ) {
+
+	// matches strings in the form of:
+	//    nodeName.property
+	//    nodeName.property[accessor]
+	//    nodeName.material.property[accessor]
+	//    uuid.property[accessor]
+	//    uuid.objectName[objectIndex].propertyName[propertyIndex]
+	//    parentName/nodeName.property
+	//    parentName/parentName/nodeName.property[index]
+	//	  .bone[Armature.DEF_cog].position
+	// created and tested via https://regex101.com/#javascript
+
+	var re = /^(([\w]+\/)*)([\w-\d]+)?(\.([\w]+)(\[([\w\d\[\]\_. ]+)\])?)?(\.([\w.]+)(\[([\w\d\[\]\_. ]+)\])?)$/; 
+	var matches = re.exec(trackName);
+
+	if( ! matches ) {
+		throw new Error( "cannot parse trackName at all: " + trackName );
+	}
+
+    if (matches.index === re.lastIndex) {
+        re.lastIndex++;
+    }
+
+	var results = {
+		directoryName: matches[1],
+		nodeName: matches[3], 	// allowed to be null, specified root node.
+		objectName: matches[5],
+		objectIndex: matches[7],
+		propertyName: matches[9],
+		propertyIndex: matches[11]	// allowed to be null, specifies that the whole property is set.
+	};
+
+	if( results.propertyName === null || results.propertyName.length === 0 ) {
+		throw new Error( "can not parse propertyName from trackName: " + trackName );
+	}
+
+	return results;
+
+};
+
+THREE.PropertyBinding.findNode = function( root, nodeName ) {
+
+	if( ! nodeName || nodeName === "" || nodeName === "root" || nodeName === "." || nodeName === -1 || nodeName === root.name || nodeName === root.uuid ) {
+
+		return root;
+
+	}
+
+	// search into skeleton bones.
+	if( root.skeleton ) {
+
+		var searchSkeleton = function( skeleton ) {
+
+			for( var i = 0; i < skeleton.bones.length; i ++ ) {
+
+				var bone = skeleton.bones[i];
+
+				if( bone.name === nodeName ) {
+
+					return bone;
+
+				}
+			}
+
+			return null;
+
+		};
+
+		var bone = searchSkeleton( root.skeleton );
+
+		if( bone ) {
+
+			return bone;
+
+		}
+	}
+
+	// search into node subtree.
+	if( root.children ) {
+
+		var searchNodeSubtree = function( children ) {
+
+			for( var i = 0; i < children.length; i ++ ) {
+
+				var childNode = children[i];
+
+				if( childNode.name === nodeName || childNode.uuid === nodeName ) {
+
+					return childNode;
+
+				}
+
+				var result = searchNodeSubtree( childNode.children );
+
+				if( result ) return result;
+
+			}
+
+			return null;	
+
+		};
+
+		var subTreeNode = searchNodeSubtree( root.children );
+
+		if( subTreeNode ) {
+
+			return subTreeNode;
+
+		}
+
+	}
+
+	return null;
+}

+ 64 - 0
src/animation/tracks/BooleanKeyframeTrack.js

@@ -0,0 +1,64 @@
+/**
+ *
+ * A Track that interpolates Boolean
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.BooleanKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value;
+
+};
+
+THREE.BooleanKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.BooleanKeyframeTrack.prototype.constructor = THREE.BooleanKeyframeTrack;
+
+THREE.BooleanKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result = value;
+
+};
+
+// 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.
+THREE.BooleanKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return ( alpha < 1.0 ) ? value0 : value1;
+
+};
+
+THREE.BooleanKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return ( value0 === value1 );
+
+};
+
+THREE.BooleanKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value
+		} );
+	}
+
+	return new THREE.BooleanKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.BooleanKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	return new THREE.BooleanKeyframeTrack( name, jsonKeys );
+
+};
+ 

+ 75 - 0
src/animation/tracks/ColorKeyframeTrack.js

@@ -0,0 +1,75 @@
+/**
+ *
+ * A Track that interpolates Color
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.ColorKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value.clone();
+
+};
+
+THREE.ColorKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.ColorKeyframeTrack.prototype.constructor = THREE.ColorKeyframeTrack;
+
+THREE.ColorKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result.copy( value );
+
+};
+
+// 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.
+THREE.ColorKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return value0.lerp( value1, alpha );
+
+};
+
+THREE.ColorKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return value0.equals( value1 );
+
+};
+
+THREE.ColorKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value.clone()
+		} );
+	}
+
+	return new THREE.ColorKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.ColorKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	var keys = [];
+
+	for( var i = 0; i < jsonKeys.length; i ++ ) {
+		var jsonKey = jsonKeys[i];
+		var key = {
+			value: new THREE.Color().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		};
+		keys.push( key );
+	}
+
+	return new THREE.ColorKeyframeTrack( name, keys );
+
+};
+ 

+ 64 - 0
src/animation/tracks/NumberKeyframeTrack.js

@@ -0,0 +1,64 @@
+/**
+ *
+ * A Track that interpolates Numbers
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.NumberKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value;
+
+};
+
+THREE.NumberKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.NumberKeyframeTrack.prototype.constructor = THREE.NumberKeyframeTrack;
+
+THREE.NumberKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result = value;
+
+};
+
+// 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.
+THREE.NumberKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return value0 * ( 1 - alpha ) + value1 * alpha;
+
+};
+
+THREE.NumberKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return ( value0 === value1 );
+
+};
+
+THREE.NumberKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value
+		} );
+	}
+
+	return new THREE.NumberKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.NumberKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	return new THREE.NumberKeyframeTrack( name, jsonKeys );
+
+};
+ 

+ 87 - 0
src/animation/tracks/QuaternionKeyframeTrack.js

@@ -0,0 +1,87 @@
+/**
+ *
+ * A Track that interpolates Quaternion
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.QuaternionKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value.clone();
+
+};
+ 
+THREE.QuaternionKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.QuaternionKeyframeTrack.prototype.constructor = THREE.QuaternionKeyframeTrack;
+
+THREE.QuaternionKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result.copy( value );
+
+};
+
+// 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.
+THREE.QuaternionKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return value0.slerp( value1, alpha );
+
+};
+
+THREE.QuaternionKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return value0.equals( value1 );
+
+};
+
+THREE.QuaternionKeyframeTrack.prototype.multiply = function( quat ) {
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+
+		this.keys[i].value.multiply( quat );
+		
+	}
+
+	return this;
+
+};
+
+THREE.QuaternionKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value.clone()
+		} );
+	}
+
+	return new THREE.QuaternionKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.QuaternionKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	var keys = [];
+
+	for( var i = 0; i < jsonKeys.length; i ++ ) {
+		var jsonKey = jsonKeys[i];
+		var key = {
+			value: new THREE.Quaternion().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		};
+		keys.push( key );
+	}
+
+	return new THREE.QuaternionKeyframeTrack( name, keys );
+
+};
+ 

+ 64 - 0
src/animation/tracks/StringKeyframeTrack.js

@@ -0,0 +1,64 @@
+/**
+ *
+ * A Track that interpolates Strings
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.StringKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value;
+
+};
+
+THREE.StringKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.StringKeyframeTrack.prototype.constructor = THREE.StringKeyframeTrack;
+
+THREE.StringKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result = value;
+
+};
+
+// 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.
+THREE.StringKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return ( alpha < 1.0 ) ? value0 : value1;
+
+};
+
+THREE.StringKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return ( value0 === value1 );
+
+};
+
+THREE.StringKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value
+		} );
+	}
+
+	return new THREE.StringKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.StringKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	return new THREE.StringKeyframeTrack( name, jsonKeys );
+
+};
+ 

+ 78 - 0
src/animation/tracks/VectorKeyframeTrack.js

@@ -0,0 +1,78 @@
+/**
+ *
+ * A Track that interpolates Vectors
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.VectorKeyframeTrack = function ( name, keys ) {
+
+	THREE.KeyframeTrack.call( this, name, keys );
+
+	// local cache of value type to avoid allocations during runtime.
+	this.result = this.keys[0].value.clone();
+
+};
+
+THREE.VectorKeyframeTrack.prototype = Object.create( THREE.KeyframeTrack.prototype );
+
+THREE.VectorKeyframeTrack.prototype.constructor = THREE.VectorKeyframeTrack;
+
+THREE.VectorKeyframeTrack.prototype.setResult = function( value ) {
+
+	this.result.copy( value );
+
+};
+
+// 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.
+THREE.VectorKeyframeTrack.prototype.lerpValues = function( value0, value1, alpha ) {
+
+	return value0.lerp( value1, alpha );
+
+};
+
+THREE.VectorKeyframeTrack.prototype.compareValues = function( value0, value1 ) {
+
+	return value0.equals( value1 );
+
+};
+
+THREE.VectorKeyframeTrack.prototype.clone = function() {
+
+	var clonedKeys = [];
+
+	for( var i = 0; i < this.keys.length; i ++ ) {
+		
+		var key = this.keys[i];
+		clonedKeys.push( {
+			time: key.time,
+			value: key.value.clone()
+		} );
+	}
+
+	return new THREE.VectorKeyframeTrack( this.name, clonedKeys );
+
+};
+
+THREE.VectorKeyframeTrack.parse = function( name, jsonKeys ) {
+
+	var elementCount = jsonKeys[0].value.length;
+	var valueType = THREE[ 'Vector' + elementCount ];
+
+	var keys = [];
+
+	for( var i = 0; i < jsonKeys.length; i ++ ) {
+		var jsonKey = jsonKeys[i];
+		var key = {
+			value: new valueType().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		};
+		keys.push( key );
+	}
+
+	return new THREE.VectorKeyframeTrack( name, keys );
+
+};
+ 

+ 42 - 3
src/loaders/ObjectLoader.js

@@ -57,7 +57,16 @@ THREE.ObjectLoader.prototype = {
 
 
 		var textures  = this.parseTextures( json.textures, images );
 		var textures  = this.parseTextures( json.textures, images );
 		var materials = this.parseMaterials( json.materials, textures );
 		var materials = this.parseMaterials( json.materials, textures );
-		var object = this.parseObject( json.object, geometries, materials );
+
+		var tracks = [];
+
+		var object = this.parseObject( json.object, geometries, materials, tracks );
+
+		if( tracks.length > 0 ) {
+
+			object.animationClips = [ new THREE.AnimationClip( "default", -1, tracks ) ];
+
+		}
 
 
 		if ( json.images === undefined || json.images.length === 0 ) {
 		if ( json.images === undefined || json.images.length === 0 ) {
 
 
@@ -465,7 +474,7 @@ THREE.ObjectLoader.prototype = {
 
 
 		var matrix = new THREE.Matrix4();
 		var matrix = new THREE.Matrix4();
 
 
-		return function ( data, geometries, materials ) {
+		return function ( data, geometries, materials, tracks ) {
 
 
 			var object;
 			var object;
 
 
@@ -612,10 +621,40 @@ THREE.ObjectLoader.prototype = {
 
 
 				for ( var child in data.children ) {
 				for ( var child in data.children ) {
 
 
-					object.add( this.parseObject( data.children[ child ], geometries, materials ) );
+					object.add( this.parseObject( data.children[ child ], geometries, materials, tracks ) );
+
+				}
+
+			}
+
+			if( data.animations && data.animations.tracks ) {
+
+				var dataTracks = data.animations.tracks;
+
+				var fpsToSeconds = ( data.animations.fps !== undefined ) ? ( 1.0 / data.animations.fps ) : 1.0;
+
+				if( dataTracks.position ) {
+
+					tracks.push( THREE.VectorKeyframeTrack.parse( object.uuid + '.position', dataTracks.position ).scale( fpsToSeconds ) );
 
 
 				}
 				}
 
 
+				if( dataTracks.quaternion ) {
+
+					var trackQuaternion = THREE.QuaternionKeyframeTrack.parse( object.uuid + '.quaternion', dataTracks.quaternion ).scale( fpsToSeconds );
+				
+					//trackQuaternion.multiply( trackQuaternion.keys[0].value.clone().inverse() );
+					//trackQuaternion.multiply( object.quaternion );
+					
+					tracks.push( trackQuaternion );
+
+				}
+
+				if( dataTracks.scale ) {
+
+					tracks.push( THREE.VectorKeyframeTrack.parse( object.uuid + '.scale', dataTracks.scale ).scale( fpsToSeconds ) );
+
+				}
 			}
 			}
 
 
 			return object;
 			return object;

+ 23 - 139
src/objects/MorphAnimMesh.js

@@ -10,105 +10,53 @@ THREE.MorphAnimMesh = function ( geometry, material ) {
 
 
 	// API
 	// API
 
 
-	this.duration = 1000; // milliseconds
-	this.mirroredLoop = false;
-	this.time = 0;
-
-	// internals
-
-	this.lastKeyframe = 0;
-	this.currentKeyframe = 0;
-
-	this.direction = 1;
-	this.directionBackwards = false;
-
-	this.setFrameRange( 0, geometry.morphTargets.length - 1 );
+	this.mixer = new THREE.AnimationMixer( this );
 
 
 };
 };
 
 
 THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
 THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
 THREE.MorphAnimMesh.prototype.constructor = THREE.MorphAnimMesh;
 THREE.MorphAnimMesh.prototype.constructor = THREE.MorphAnimMesh;
 
 
-THREE.MorphAnimMesh.prototype.setFrameRange = function ( start, end ) {
-
-	this.startKeyframe = start;
-	this.endKeyframe = end;
-
-	this.length = this.endKeyframe - this.startKeyframe + 1;
-
-};
-
 THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
 THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
 
 
-	this.direction = 1;
-	this.directionBackwards = false;
+	this.mixer.timeScale = 1.0;
 
 
 };
 };
 
 
 THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
 THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
 
 
-	this.direction = - 1;
-	this.directionBackwards = true;
+	this.mixer.timeScale = -1.0;
 
 
 };
 };
 
 
 THREE.MorphAnimMesh.prototype.parseAnimations = function () {
 THREE.MorphAnimMesh.prototype.parseAnimations = function () {
 
 
-	var geometry = this.geometry;
-
-	if ( ! geometry.animations ) geometry.animations = {};
-
-	var firstAnimation, animations = geometry.animations;
-
-	var pattern = /([a-z]+)_?(\d+)/;
-
-	for ( var i = 0, il = geometry.morphTargets.length; i < il; i ++ ) {
-
-		var morph = geometry.morphTargets[ i ];
-		var parts = morph.name.match( pattern );
-
-		if ( parts && parts.length > 1 ) {
-
-			var label = parts[ 1 ];
-
-			if ( ! animations[ label ] ) animations[ label ] = { start: Infinity, end: - Infinity };
-
-			var animation = animations[ label ];
-
-			if ( i < animation.start ) animation.start = i;
-			if ( i > animation.end ) animation.end = i;
-
-			if ( ! firstAnimation ) firstAnimation = label;
-
-		}
-
-	}
-
-	geometry.firstAnimation = firstAnimation;
-
-};
-
-THREE.MorphAnimMesh.prototype.setAnimationLabel = function ( label, start, end ) {
-
-	if ( ! this.geometry.animations ) this.geometry.animations = {};
-
-	this.geometry.animations[ label ] = { start: start, end: end };
+	this.animationClips = THREE.AnimationClip.FromImplicitMorphTargetAnimations( this.geometry.morphTargets, 20 );
+	this.firstAnimationClip = this.animationClips[0];
 
 
 };
 };
 
 
 THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
 THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
 
 
-	var animation = this.geometry.animations[ label ];
+	this.mixer.removeAllActions();
 
 
-	if ( animation ) {
+	var clip = null;
+	for( var i = 0; i < this.animationClips.length; i ++ ) {
+		if( this.animationClips[ i ].name === label ) {
+			clip = this.animationClips[ i ];
+			break;
+		}
+	}
+	
+	if ( clip ) {
 
 
-		this.setFrameRange( animation.start, animation.end );
-		this.duration = 1000 * ( ( animation.end - animation.start ) / fps );
-		this.time = 0;
+		var action = new THREE.AnimationAction( clip, 0, 1, 1, true );
+		action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
+		this.mixer.addAction( action );
 
 
 	} else {
 	} else {
 
 
-		console.warn( 'THREE.MorphAnimMesh: animation[' + label + '] undefined in .playAnimation()' );
+		throw new Error( 'THREE.MorphAnimMesh: animationClips[' + label + '] undefined in .playAnimation()' );
 
 
 	}
 	}
 
 
@@ -116,65 +64,7 @@ THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
 
 
 THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
 THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
 
 
-	var frameTime = this.duration / this.length;
-
-	this.time += this.direction * delta;
-
-	if ( this.mirroredLoop ) {
-
-		if ( this.time > this.duration || this.time < 0 ) {
-
-			this.direction *= - 1;
-
-			if ( this.time > this.duration ) {
-
-				this.time = this.duration;
-				this.directionBackwards = true;
-
-			}
-
-			if ( this.time < 0 ) {
-
-				this.time = 0;
-				this.directionBackwards = false;
-
-			}
-
-		}
-
-	} else {
-
-		this.time = this.time % this.duration;
-
-		if ( this.time < 0 ) this.time += this.duration;
-
-	}
-
-	var keyframe = this.startKeyframe + THREE.Math.clamp( Math.floor( this.time / frameTime ), 0, this.length - 1 );
-
-	var influences = this.morphTargetInfluences;
-
-	if ( keyframe !== this.currentKeyframe ) {
-
-		influences[ this.lastKeyframe ] = 0;
-		influences[ this.currentKeyframe ] = 1;
-		influences[ keyframe ] = 0;
-
-		this.lastKeyframe = this.currentKeyframe;
-		this.currentKeyframe = keyframe;
-
-	}
-
-	var mix = ( this.time % frameTime ) / frameTime;
-
-	if ( this.directionBackwards ) {
-
-		mix = 1 - mix;
-
-	}
-
-	influences[ this.currentKeyframe ] = mix;
-	influences[ this.lastKeyframe ] = 1 - mix;
+	this.mixer.update( delta );
 
 
 };
 };
 
 
@@ -197,15 +87,9 @@ THREE.MorphAnimMesh.prototype.copy = function ( source ) {
 
 
 	THREE.Object3D.prototype.copy.call( this, source );
 	THREE.Object3D.prototype.copy.call( this, source );
 
 
-	this.duration = source.duration;
-	this.mirroredLoop = source.mirroredLoop;
-	this.time = source.time;
-
-	this.lastKeyframe = source.lastKeyframe;
-	this.currentKeyframe = source.currentKeyframe;
-
-	this.direction = source.direction;
-	this.directionBackwards = source.directionBackwards;
+	this.mixer = new THREE.AnimationMixer( this );
+	this.animationClips = source.animationClips;
+	this.firstAnimationClips = source.firstAnimationClips;
 
 
 	return this;
 	return this;
 
 

+ 11 - 0
utils/build/includes/common.json

@@ -35,6 +35,17 @@
 	"src/core/DirectGeometry.js",
 	"src/core/DirectGeometry.js",
 	"src/core/BufferGeometry.js",
 	"src/core/BufferGeometry.js",
 	"src/core/InstancedBufferGeometry.js",
 	"src/core/InstancedBufferGeometry.js",
+	"src/animation/AnimationAction.js",
+	"src/animation/AnimationClip.js",
+	"src/animation/AnimationMixer.js",
+	"src/animation/AnimationUtils.js",
+	"src/animation/KeyframeTrack.js",
+	"src/animation/PropertyBinding.js",
+	"src/animation/tracks/VectorKeyframeTrack.js",
+	"src/animation/tracks/QuaternionKeyframeTrack.js",
+	"src/animation/tracks/StringKeyframeTrack.js",
+	"src/animation/tracks/BooleanKeyframeTrack.js",
+	"src/animation/tracks/NumberKeyframeTrack.js",
 	"src/cameras/Camera.js",
 	"src/cameras/Camera.js",
 	"src/cameras/CubeCamera.js",
 	"src/cameras/CubeCamera.js",
 	"src/cameras/OrthographicCamera.js",
 	"src/cameras/OrthographicCamera.js",

+ 2 - 3
utils/npm/build.js

@@ -87,9 +87,9 @@ var buildModule = function ( name, version ) {
 var cmdExe, args;
 var cmdExe, args;
 if (process.platform === 'win32' || process.platform === 'win64') {
 if (process.platform === 'win32' || process.platform === 'win64') {
 	cmdExe = "cmd.exe";
 	cmdExe = "cmd.exe";
-	args = [ "/c", "build_all.bat" ];
+	args = [ "/c", "build.bat" ];
 } else {
 } else {
-	cmdExe = './build_all.sh';
+	cmdExe = './build.sh';
 	args = [];
 	args = [];
 }
 }
 var opts = { "cwd": "../build" };
 var opts = { "cwd": "../build" };
@@ -106,5 +106,4 @@ buildAll.stderr.on('data', function (data) {
 buildAll.on( 'exit', function ( exitCode ) {
 buildAll.on( 'exit', function ( exitCode ) {
 	console.log( "exitCode: " + exitCode );
 	console.log( "exitCode: " + exitCode );
 	buildModule( "three" );
 	buildModule( "three" );
-	buildModule( "three-math" );
 });
 });

Some files were not shown because too many files changed in this diff