Browse Source

Merge pull request #6934 from bhouston/modernAnimationSystem

Modern Mixer-based Animation System
Mr.doob 10 years ago
parent
commit
c07a5b8ac2
80 changed files with 6119 additions and 671 deletions
  1. 7 5
      examples/canvas_morphtargets_horse.html
  2. 139 0
      examples/js/AnimationClipCreator.js
  3. 27 146
      examples/js/BlendCharacter.js
  4. 15 6
      examples/js/BlendCharacterGui.js
  5. 52 33
      examples/js/MD2Character.js
  6. 70 0
      examples/js/MorphAnimMesh.js
  7. 0 0
      examples/js/MorphAnimation.js
  8. 5 5
      examples/js/UCSCharacter.js
  9. 6 1
      examples/js/loaders/MD2Loader.js
  10. 0 0
      examples/js/loaders/collada/Animation.js
  11. 0 0
      examples/js/loaders/collada/AnimationHandler.js
  12. 0 0
      examples/js/loaders/collada/KeyFrameAnimation.js
  13. 3 0
      examples/misc_animation_keys.html
  14. 121 0
      examples/models/json/blend-animation.json
  15. 2696 0
      examples/models/json/scene-animation.json
  16. 196 0
      examples/webgl_animation_blend.html
  17. 194 0
      examples/webgl_animation_scene.html
  18. 14 9
      examples/webgl_animation_skinning_blending.html
  19. 13 54
      examples/webgl_animation_skinning_morph.html
  20. 14 14
      examples/webgl_lights_hemisphere.html
  21. 3 0
      examples/webgl_loader_collada.html
  22. 4 0
      examples/webgl_loader_collada_keyframe.html
  23. 3 0
      examples/webgl_loader_collada_skinning.html
  24. 21 20
      examples/webgl_loader_json_blender.html
  25. 9 5
      examples/webgl_loader_md2.html
  26. 14 15
      examples/webgl_loader_scene.html
  27. 3 0
      examples/webgl_loader_sea3d.html
  28. 3 0
      examples/webgl_loader_sea3d_skinning.html
  29. 20 17
      examples/webgl_morphnormals.html
  30. 8 6
      examples/webgl_morphtargets_horse.html
  31. 1 1
      examples/webgl_morphtargets_human.html
  32. 20 11
      examples/webgl_shading_physical.html
  33. 24 22
      examples/webgl_shadowmap.html
  34. 16 15
      examples/webgl_shadowmap_performance.html
  35. 5 4
      examples/webgl_skinning_simple.html
  36. 19 17
      examples/webgl_terrain_dynamic.html
  37. 5 0
      src/Three.js
  38. 166 0
      src/animation/AnimationAction.js
  39. 312 0
      src/animation/AnimationClip.js
  40. 241 0
      src/animation/AnimationMixer.js
  41. 116 0
      src/animation/AnimationUtils.js
  42. 274 0
      src/animation/KeyframeTrack.js
  43. 393 0
      src/animation/PropertyBinding.js
  44. 64 0
      src/animation/tracks/BooleanKeyframeTrack.js
  45. 74 0
      src/animation/tracks/ColorKeyframeTrack.js
  46. 64 0
      src/animation/tracks/NumberKeyframeTrack.js
  47. 86 0
      src/animation/tracks/QuaternionKeyframeTrack.js
  48. 64 0
      src/animation/tracks/StringKeyframeTrack.js
  49. 77 0
      src/animation/tracks/VectorKeyframeTrack.js
  50. 1 1
      src/core/Geometry.js
  51. 1 1
      src/core/Raycaster.js
  52. 1 1
      src/extras/geometries/LatheGeometry.js
  53. 1 1
      src/extras/helpers/ArrowHelper.js
  54. 51 0
      src/loaders/AnimationLoader.js
  55. 38 6
      src/loaders/JSONLoader.js
  56. 23 0
      src/loaders/ObjectLoader.js
  57. 1 1
      src/math/Box2.js
  58. 1 1
      src/math/Box3.js
  59. 1 1
      src/math/Euler.js
  60. 1 1
      src/math/Frustum.js
  61. 1 1
      src/math/Line3.js
  62. 1 1
      src/math/Matrix3.js
  63. 1 1
      src/math/Matrix4.js
  64. 1 1
      src/math/Plane.js
  65. 1 1
      src/math/Quaternion.js
  66. 1 1
      src/math/Ray.js
  67. 1 1
      src/math/Sphere.js
  68. 1 1
      src/math/Triangle.js
  69. 0 212
      src/objects/MorphAnimMesh.js
  70. 11 1
      utils/build/includes/common.json
  71. 0 4
      utils/build/includes/extras.json
  72. 34 4
      utils/exporters/blender/addons/io_three/__init__.py
  73. 9 2
      utils/exporters/blender/addons/io_three/constants.py
  74. 3 2
      utils/exporters/blender/addons/io_three/exporter/api/material.py
  75. 63 1
      utils/exporters/blender/addons/io_three/exporter/api/mesh.py
  76. 147 5
      utils/exporters/blender/addons/io_three/exporter/api/object.py
  77. 15 5
      utils/exporters/blender/addons/io_three/exporter/geometry.py
  78. 14 2
      utils/exporters/blender/addons/io_three/exporter/object.py
  79. 12 2
      utils/exporters/blender/addons/io_three/exporter/scene.py
  80. 2 3
      utils/npm/build.js

+ 7 - 5
examples/canvas_morphtargets_horse.html

@@ -26,7 +26,7 @@
 
 			var container, stats;
 			var camera, scene, projector, renderer;
-			var mesh, animation;
+			var mesh, mixer;
 
 			init();
 			animate();
@@ -69,8 +69,10 @@
 					mesh.scale.set( 1.5, 1.5, 1.5 );
 					scene.add( mesh );
 
-					animation = new THREE.MorphAnimation( mesh );
-					animation.play();
+					mixer = new THREE.AnimationMixer( mesh );
+
+					var clip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'gallop', geometry.morphTargets, 30 );
+					mixer.addAction( new THREE.AnimationAction( clip ).warpToDuration( 1.5 ) );
 
 				} );
 
@@ -129,11 +131,11 @@
 
 				camera.lookAt( camera.target );
 
-				if ( animation ) {
+				if ( mixer ) {
 
 					var time = Date.now();
 
-					animation.update( time - prevTime );
+					mixer.update( ( time - prevTime ) * 0.001 );
 
 					prevTime = time;
 

+ 139 - 0
examples/js/AnimationClipCreator.js

@@ -0,0 +1,139 @@
+/**
+ *
+ * Creator of typical test AnimationClips / KeyframeTracks
+ * 
+ * @author Ben Houston / http://clara.io/
+ * @author David Sarno / http://lighthaus.us/
+ */
+
+THREE.AnimationClipCreator = function() {
+};
+
+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;
+};
+

+ 27 - 146
examples/js/BlendCharacter.js

@@ -20,12 +20,13 @@ THREE.BlendCharacter = function () {
 
 			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
 
-			// Create the animations
+			scope.mixer = new THREE.AnimationMixer( scope );
 
+			// Create the animations		
 			for ( var i = 0; i < geometry.animations.length; ++ i ) {
 
 				var animName = geometry.animations[ i ].name;
-				scope.animations[ animName ] = new THREE.Animation( scope, geometry.animations[ i ] );
+				scope.animations[ animName ] = geometry.animations[ i ];
 
 			}
 
@@ -38,191 +39,71 @@ THREE.BlendCharacter = function () {
 
 	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.animations[ animName ].play( 0, weight );
+		this.mixer.removeAllActions();
+		
+		this.mixer.play( new THREE.AnimationAction( this.animations[ animName ] ) );
 
 	};
 
 	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 ] );
+		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
 
-		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 ) {
 
-		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 ] );
+		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
 
-			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.animations[ animName ].weight = weight;
+		var action = this.mixer.findActionByName( animName );
+		if( action ) {
+			action.weight = weight;
+		}
 
 	};
 
 	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() {
 
-		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() {
 
-		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
  */
 
-function BlendCharacterGui( animations ) {
+function BlendCharacterGui( blendMesh ) {
 
 	var controls = {
 
@@ -18,7 +18,7 @@ function BlendCharacterGui( animations ) {
 
 	};
 
-	var animations = animations;
+	var blendMesh = blendMesh;
 
 	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' );
 
 	};
 

+ 52 - 33
examples/js/MD2Character.js

@@ -21,6 +21,8 @@ THREE.MD2Character = function () {
 
 	this.activeAnimation = null;
 
+	this.mixer = null;
+
 	this.onLoadComplete = function () {};
 
 	this.loadCounter = 0;
@@ -52,7 +54,11 @@ THREE.MD2Character = function () {
 			scope.root.add( mesh );
 
 			scope.meshBody = mesh;
-			scope.activeAnimation = geo.firstAnimation;
+
+			scope.meshBody.clipOffset = 0;
+			scope.activeAnimationClipName = mesh.geometry.animations[0].name;
+
+			scope.mixer = new THREE.AnimationMixer( mesh );
 
 			checkLoadingComplete();
 
@@ -91,8 +97,12 @@ THREE.MD2Character = function () {
 
 	this.setPlaybackRate = function ( rate ) {
 
-		if ( this.meshBody ) this.meshBody.duration = this.meshBody.baseDuration / rate;
-		if ( this.meshWeapon ) this.meshWeapon.duration = this.meshWeapon.baseDuration / rate;
+		if( rate !== 0 ) {
+			this.mixer.timeScale = 1 / rate;
+		}
+		else {
+			this.mixer.timeScale = 0;
+		}
 
 	};
 
@@ -133,51 +143,67 @@ THREE.MD2Character = function () {
 			activeWeapon.visible = true;
 			this.meshWeapon = activeWeapon;
 
-			activeWeapon.playAnimation( this.activeAnimation, this.animationFPS );
-
-			this.meshWeapon.baseDuration = this.meshWeapon.duration;
-
-			this.meshWeapon.time = this.meshBody.time;
-			this.meshWeapon.duration = this.meshBody.duration;
+			scope.syncWeaponAnimation();
 
 		}
 
 	};
 
-	this.setAnimation = function ( animationName ) {
+	this.setAnimation = function ( clipName ) {
 
 		if ( this.meshBody ) {
 
-			this.meshBody.playAnimation( animationName, this.animationFPS );
-			this.meshBody.baseDuration = this.meshBody.duration;
+			if( this.meshBody.activeAction ) {
+				scope.mixer.removeAction( this.meshBody.activeAction );
+				this.meshBody.activeAction = null;
+			}
 
-		}
+			var clip = THREE.AnimationClip.findByName( this.meshBody.geometry.animations, clipName );
+			if( clip ) {
 
-		if ( this.meshWeapon ) {
+				var action = new THREE.AnimationAction( clip, this.mixer.time ).setLocalRoot( this.meshBody );
+				scope.mixer.addAction( action );
 
-			this.meshWeapon.playAnimation( animationName, this.animationFPS );
-			this.meshWeapon.baseDuration = this.meshWeapon.duration;
-			this.meshWeapon.time = this.meshBody.time;
+				this.meshBody.activeAction = action;
+
+			}
 
 		}
 
-		this.activeAnimation = animationName;
+		scope.activeClipName = clipName;
+
+		scope.syncWeaponAnimation();
 
 	};
 
-	this.update = function ( delta ) {
+	this.syncWeaponAnimation = function() {
 
-		if ( this.meshBody ) {
+		var clipName = scope.activeClipName;
 
-			this.meshBody.updateAnimation( 1000 * delta );
+		if ( scope.meshWeapon ) {
 
-		}
+			if( this.meshWeapon.activeAction ) {
+				scope.mixer.removeAction( this.meshWeapon.activeAction );
+				this.meshWeapon.activeAction = null;
+			}
 
-		if ( this.meshWeapon ) {
+			var clip = THREE.AnimationClip.findByName( this.meshWeapon.geometry.animations, clipName );
+			if( clip ) {
 
-			this.meshWeapon.updateAnimation( 1000 * delta );
+				var action = new THREE.AnimationAction( clip ).syncWith( this.meshBody.activeAction ).setLocalRoot( this.meshWeapon );
+				scope.mixer.addAction( action );
+
+				this.meshWeapon.activeAction = action;
+
+			}
 
 		}
+			
+	}
+
+	this.update = function ( delta ) {
+
+		if( this.mixer ) this.mixer.update( delta );
 
 	};
 
@@ -204,7 +230,7 @@ THREE.MD2Character = function () {
 
 		//
 
-		var mesh = new THREE.MorphAnimMesh( geometry, materialTexture );
+		var mesh = new THREE.Mesh( geometry, materialTexture );
 		mesh.rotation.y = - Math.PI / 2;
 
 		mesh.castShadow = true;
@@ -214,14 +240,7 @@ THREE.MD2Character = function () {
 
 		mesh.materialTexture = materialTexture;
 		mesh.materialWireframe = materialWireframe;
-
-		//
-
-		mesh.parseAnimations();
-
-		mesh.playAnimation( geometry.firstAnimation, scope.animationFPS );
-		mesh.baseDuration = mesh.duration;
-
+	
 		return mesh;
 
 	}

+ 70 - 0
examples/js/MorphAnimMesh.js

@@ -0,0 +1,70 @@
+/**
+ * @author alteredq / http://alteredqualia.com/
+ */
+
+THREE.MorphAnimMesh = function ( geometry, material ) {
+
+	THREE.Mesh.call( this, geometry, material );
+
+	this.type = 'MorphAnimMesh';
+
+	this.mixer = new THREE.AnimationMixer( this );
+	this.activeAction = null;
+};
+
+THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
+THREE.MorphAnimMesh.prototype.constructor = THREE.MorphAnimMesh;
+
+THREE.MorphAnimMesh.prototype.setDirectionForward = function () {
+
+	this.mixer.timeScale = 1.0;
+
+};
+
+THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
+
+	this.mixer.timeScale = -1.0;
+
+};
+
+THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
+
+	if( this.activeAction ) {
+
+		this.mixer.removeAction( this.activeAction );
+		this.activeAction = null;
+		
+	}
+
+	var clip = THREE.AnimationClip.findByName( this.geometry.animations, label );
+
+	if ( clip ) {
+
+		var action = new THREE.AnimationAction( clip );
+		action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
+		this.mixer.addAction( action );
+		this.activeAction = action;
+
+	} else {
+
+		throw new Error( 'THREE.MorphAnimMesh: animations[' + label + '] undefined in .playAnimation()' );
+
+	}
+
+};
+
+THREE.MorphAnimMesh.prototype.updateAnimation = function ( delta ) {
+
+	this.mixer.update( delta );
+
+};
+
+THREE.MorphAnimMesh.prototype.copy = function ( source ) {
+
+	THREE.Mesh.prototype.copy.call( this, source );
+
+	this.mixer = new THREE.AnimationMixer( this );
+
+	return this;
+
+};

+ 0 - 0
src/extras/animation/MorphAnimation.js → examples/js/MorphAnimation.js


+ 5 - 5
examples/js/UCSCharacter.js

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

+ 6 - 1
examples/js/loaders/MD2Loader.js

@@ -224,7 +224,10 @@ THREE.MD2Loader.prototype = {
 
 				for ( var j = 0; j < 16; j ++ ) {
 
-					string[ j ] = data.getUint8( offset + j, true );
+					var character = data.getUint8( offset + j, true );
+					if( character === 0 ) break;
+					
+					string[ j ] = character;
 
 				}
 
@@ -302,6 +305,8 @@ THREE.MD2Loader.prototype = {
 
 			}
 
+			geometry.animations = THREE.AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 )
+
 			console.timeEnd( 'MD2Loader' );
 
 			return geometry;

+ 0 - 0
src/extras/animation/Animation.js → examples/js/loaders/collada/Animation.js


+ 0 - 0
src/extras/animation/AnimationHandler.js → examples/js/loaders/collada/AnimationHandler.js


+ 0 - 0
src/extras/animation/KeyFrameAnimation.js → examples/js/loaders/collada/KeyFrameAnimation.js


+ 3 - 0
examples/misc_animation_keys.html

@@ -37,6 +37,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 

+ 121 - 0
examples/models/json/blend-animation.json

@@ -0,0 +1,121 @@
+{
+    "metadata": {
+        "generator": "io_three",
+        "type": "Object",
+        "version": 4.3
+    },
+    "textures": [],
+    "geometries": [{
+        "uuid": "9C17D067-6185-36C2-898A-43F0618F9682",
+        "type": "Geometry",
+        "data": {
+            "skinWeights": [],
+            "skinIndices": [],
+            "faces": [42,0,2,3,0,0,1,2,0,1,2,42,3,1,0,0,2,3,0,2,3,0,42,4,5,7,0,3,0,1,4,5,6,42,7,6,4,0,1,2,3,6,7,4,42,0,1,9,0,3,0,4,0,3,8,42,9,8,0,0,4,5,3,8,9,0,42,8,9,13,0,5,4,6,9,8,10,42,13,12,8,0,6,7,5,10,11,9,42,12,13,17,0,7,6,8,11,10,12,42,17,16,12,0,8,9,7,12,13,11,42,16,17,21,0,9,8,10,13,12,14,42,21,20,16,0,10,11,9,14,15,13,42,20,21,5,0,11,10,1,15,14,5,42,5,4,20,0,1,2,11,5,4,15,42,1,3,10,0,3,0,4,3,2,16,42,10,9,1,0,4,5,3,16,8,3,42,9,10,14,0,5,4,6,8,16,17,42,14,13,9,0,6,7,5,17,10,8,42,13,14,18,0,7,6,8,10,17,18,42,18,17,13,0,8,9,7,18,12,10,42,17,18,22,0,9,8,10,12,18,19,42,22,21,17,0,10,11,9,19,14,12,42,21,22,7,0,11,10,1,14,19,6,42,7,5,21,0,1,2,11,6,5,14,42,3,2,11,0,3,0,4,2,1,20,42,11,10,3,0,4,5,3,20,16,2,42,10,11,15,0,5,4,6,16,20,21,42,15,14,10,0,6,7,5,21,17,16,42,14,15,19,0,7,6,8,17,21,22,42,19,18,14,0,8,9,7,22,18,17,42,33,34,35,0,9,8,10,23,24,25,42,35,32,33,0,10,11,9,25,26,23,42,22,23,6,0,11,10,1,19,27,7,42,6,7,22,0,1,2,11,7,6,19,42,2,0,8,0,3,0,4,1,0,9,42,8,11,2,0,4,5,3,9,20,1,42,11,8,12,0,5,4,6,20,9,11,42,12,15,11,0,6,7,5,11,21,20,42,15,12,16,0,7,6,8,21,11,13,42,16,19,15,0,8,9,7,13,22,21,42,19,16,20,0,9,8,10,22,13,15,42,20,23,19,0,10,11,9,15,27,22,42,23,20,4,0,11,10,1,27,15,4,42,4,6,23,0,1,2,11,4,7,27,42,25,24,22,0,9,11,10,28,29,19,42,22,18,25,0,10,8,9,19,18,28,42,26,25,18,0,8,9,9,30,28,18,42,18,19,26,0,9,8,8,18,22,30,42,27,26,19,0,10,8,9,31,30,22,42,19,23,27,0,9,11,10,22,27,31,42,24,27,23,0,11,10,10,29,31,27,42,23,22,24,0,10,11,11,27,19,29,42,29,28,24,0,9,11,11,28,29,29,42,24,25,29,0,11,9,9,29,28,28,42,30,29,25,0,8,9,9,30,28,28,42,25,26,30,0,9,8,8,28,30,30,42,31,30,26,0,10,8,8,31,30,30,42,26,27,31,0,8,10,10,30,31,31,42,28,31,27,0,11,10,10,29,31,31,42,27,24,28,0,10,11,11,31,29,29,42,33,32,28,0,9,11,11,23,26,29,42,28,29,33,0,11,9,9,29,28,23,42,34,33,29,0,8,9,9,24,23,28,42,29,30,34,0,9,8,8,28,30,24,42,35,34,30,0,10,8,8,25,24,30,42,30,31,35,0,8,10,10,30,31,25,42,32,35,31,0,11,10,10,26,25,31,42,31,28,32,0,10,11,11,31,29,26],
+            "morphTargets": [{
+                "name": "T",
+                "vertices": [[-4,0.620216,-4],[4,0.620216,-4],[-4,0.620216,4],[4,0.620216,4],[-2.34599,-60.3799,-2.34599],[2.34599,-60.3799,-2.34599],[-2.34599,-60.3799,2.34599],[2.34599,-60.3799,2.34599],[-3.05437,-11.951,-3.05437],[3.05437,-11.951,-3.05437],[3.05437,-11.951,3.05437],[-3.05437,-11.951,3.05437],[-3.05437,-26.2605,-3.05437],[3.05437,-26.2605,-3.05437],[3.05437,-26.2605,3.05437],[-3.05437,-26.2605,3.05437],[-2.91343,-43.3116,-2.91343],[2.91343,-43.3116,-2.91343],[2.91343,-43.3116,2.91343],[-2.91343,-43.3116,2.91343],[-2.91343,-47.6772,-2.91343],[2.91343,-47.6772,-2.91343],[2.91343,-47.6772,2.91343],[-2.91343,-47.6772,2.91343],[2.91343,-47.6772,9.18208],[2.91343,-43.3116,9.18208],[-2.91343,-43.3116,9.18208],[-2.91343,-47.6772,9.18208],[2.91343,-47.6772,14.6098],[2.91343,-43.3116,14.6098],[-2.91343,-43.3116,14.6098],[-2.91343,-47.6772,14.6098],[2.91343,-47.6772,18.4439],[2.91343,-43.3116,18.4439],[-2.91343,-43.3116,18.4439],[-2.91343,-47.6772,18.4439]]
+            }],
+            "uvs": [[1,0,1,1,0,1,0,0,1,0.2,0,0.2,1,0.4,0,0.4,1,0.6,0,0.6,1,0.8,0,0.8]],
+            "influencesPerVertex": 2,
+            "metadata": {
+                "morphTargets": 1,
+                "normals": 32,
+                "version": 3,
+                "vertices": 36,
+                "faces": 68,
+                "materials": 1,
+                "generator": "io_three",
+                "uvs": 1,
+                "bones": 0
+            },
+            "normals": [-0.586627,0.558306,-0.586627,-0.586627,0.558306,0.586627,0.586627,0.558306,0.586627,0.586627,0.558306,-0.586627,-0.571917,-0.588,-0.571917,0.571917,-0.588,-0.571917,0.571917,-0.588,0.571917,-0.571917,-0.588,0.571917,0.706839,-0.02591,-0.706839,-0.706839,-0.02591,-0.706839,0.707083,-0.005799,-0.707083,-0.707083,-0.005799,-0.707083,0.707083,-0.00586,-0.707083,-0.707083,-0.00586,-0.707083,0.707022,-0.014252,-0.707022,-0.707022,-0.014252,-0.707022,0.706839,-0.02591,0.706839,0.707083,-0.005799,0.707083,0.905728,0.296365,0.302957,0.901486,-0.313913,0.29783,-0.706839,-0.02591,0.706839,-0.707083,-0.005799,0.707083,-0.905728,0.296365,0.302957,0.577349,0.577349,0.577349,-0.577349,0.577349,0.577349,-0.577349,-0.577349,0.577349,0.577349,-0.577349,0.577349,-0.901486,-0.313913,0.29783,0.707083,0.707083,0,0.707083,-0.707083,0,-0.707083,0.707083,0,-0.707083,-0.707083,0],
+            "bones": [],
+            "animations": {
+                "morphTargets[T]": [{
+                    "time": 0,
+                    "value": 0
+                },{
+                    "time": 10,
+                    "value": 0
+                },{
+                    "time": 50,
+                    "value": 0.5
+                },{
+                    "time": 95,
+                    "value": 1
+                },{
+                    "time": 110,
+                    "value": 1
+                },{
+                    "time": 140,
+                    "value": 0
+                },{
+                    "time": 150,
+                    "value": 0
+                }]
+            },
+            "name": "test-objGeometry",
+            "vertices": [-4,0.620216,-4,4,0.620216,-4,-4,0.620216,4,4,0.620216,4,-2.34599,-89.0885,-2.34599,2.34599,-89.0885,-2.34599,-2.34599,-89.0885,2.34599,2.34599,-89.0885,2.34599,-3.05437,-25.4524,-3.05437,3.05437,-25.4524,-3.05437,3.05437,-25.4524,3.05437,-3.05437,-25.4524,3.05437,-3.05437,-39.7619,-3.05437,3.05437,-39.7619,-3.05437,3.05437,-39.7619,3.05437,-3.05437,-39.7619,3.05437,-2.91343,-56.813,-2.91343,2.91343,-56.813,-2.91343,2.91343,-56.813,2.91343,-2.91343,-56.813,2.91343,-2.91343,-61.1786,-2.91343,2.91343,-61.1786,-2.91343,2.91343,-61.1786,2.91343,-2.91343,-61.1786,2.91343,2.91343,-61.1786,12.081,2.91343,-56.813,12.081,-2.91343,-56.813,12.081,-2.91343,-61.1786,12.081,2.91343,-61.1786,23.2496,2.91343,-56.813,23.2496,-2.91343,-56.813,23.2496,-2.91343,-61.1786,23.2496,2.91343,-61.1786,35.7722,2.91343,-56.813,35.7722,-2.91343,-56.813,35.7722,-2.91343,-61.1786,35.7722]
+        },
+        "materials": [{
+            "opacity": 1,
+            "colorAmbient": [0.8,0.8,0.8],
+            "doubleSided": true,
+            "depthWrite": true,
+            "depthTest": true,
+            "colorDiffuse": [0.8,0.8,0.8],
+            "DbgName": "01-default",
+            "specularCoef": 50,
+            "colorSpecular": [0.44902,0.44902,0.44902],
+            "shading": "phong",
+            "DbgIndex": 0,
+            "DbgColor": 15658734,
+            "visible": true,
+            "blending": "NormalBlending",
+            "wireframe": false,
+            "colorEmissive": [0,0,0],
+            "transparent": false
+        }]
+    }],
+    "materials": [{
+        "ambient": 13421772,
+        "depthWrite": true,
+        "depthTest": true,
+        "specular": 7500402,
+        "name": "01-default",
+        "emissive": 0,
+        "shininess": 50,
+        "color": 13421772,
+        "uuid": "8DC6308D-11A2-3251-91BC-D52FB2C734BD",
+        "type": "MeshPhongMaterial",
+        "blending": "NormalBlending",
+        "vertexColors": false
+    }],
+    "object": {
+        "uuid": "13D0028E-5D9E-44AF-BB17-B385656A35C6",
+        "type": "Scene",
+        "children": [{
+            "type": "Object",
+            "name": "lighthaus-test-morph01",
+            "uuid": "C3F5BA0D-BEB5-3118-9CF8-738E95FF4EF3",
+            "matrix": [-1,0,0,0,0,0,1,0,0,1,0,0,0,0,0,1],
+            "visible": true,
+            "animations": {},
+            "children": [{
+                "type": "Mesh",
+                "name": "test-obj",
+                "uuid": "1202E101-A129-3E65-A14B-8546898B2323",
+                "matrix": [1,0,0,0,0,-0,-1,0,0,1,-0,0,0,-0,0.563976,1],
+                "visible": true,
+                "material": "8DC6308D-11A2-3251-91BC-D52FB2C734BD",
+                "castShadow": true,
+                "receiveShadow": true,
+                "geometry": "9C17D067-6185-36C2-898A-43F0618F9682",
+                "animations": {}
+            }]
+        }],
+        "matrix": [1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]
+    },
+    "images": []
+}

File diff suppressed because it is too large
+ 2696 - 0
examples/models/json/scene-animation.json


+ 196 - 0
examples/webgl_animation_blend.html

@@ -0,0 +1,196 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - scene animation</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 - scene animation - <a href="https://clara.io/view/b06c3d56-b301-4f03-9295-482c61642d82">Tree Blend</a> courtesy of David Sarno</div>
+
+		<script src="../build/three.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.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( 0xffffff, 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/blend-animation.json", function ( loadedScene ) {
+
+					scene = loadedScene;
+					console.log( scene );
+					scene.add( camera );
+					scene.fog = new THREE.Fog( 0xffffff, 2000, 10000 );
+		
+				
+					var blendObject = scene.getObjectByName( 'tree-morph' );
+					var clip = blendObject.geometry.animations[0];
+					mixer = new THREE.AnimationMixer( blendObject );
+					mixer.addAction( new THREE.AnimationAction( clip ) );
+
+				} );
+
+				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 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.time );
+					mixer.update( delta );
+				}
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+
+	</body>
+</html>

+ 194 - 0
examples/webgl_animation_scene.html

@@ -0,0 +1,194 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - scene animation</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 - scene animation - <a href="https://clara.io/view/96106133-2e99-40cf-8abd-64defd153e61">Three Gears Scene</a> courtesy of David Sarno</div>
+
+		<script src="../build/three.min.js"></script>
+
+		<script src="js/Detector.js"></script>
+		<script src="js/libs/stats.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( 0xffffff, 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/scene-animation.json", function ( loadedScene ) {
+
+					sceneAnimationClip = loadedScene.animations[0];
+					scene = loadedScene;
+					scene.add( camera );
+					scene.fog = new THREE.Fog( 0xffffff, 2000, 10000 );
+		
+					mixer = new THREE.AnimationMixer( scene );
+			
+					mixer.addAction( new THREE.AnimationAction( sceneAnimationClip ) );
+
+				} );
+
+				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 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>

+ 14 - 9
examples/webgl_animation_skinning_blending.html

@@ -116,6 +116,7 @@
 				var data = event.detail;
 
 				blendMesh.stopAll();
+				blendMesh.unPauseAll();
 
 				// the blend mesh will combine 1 or more animations
 				for ( var i = 0; i < data.anims.length; ++i ) {
@@ -155,7 +156,14 @@
 				var data = event.detail;
 				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];
+							}
+						}
+					}
 
 				}
 
@@ -213,12 +221,11 @@
 				controls.update();
 
 				// Set default weights
+				blendMesh.applyWeight( 'idle', 1 / 3 );
+				blendMesh.applyWeight( 'walk', 1 / 3 );
+				blendMesh.applyWeight( 'run', 1 / 3 );
 
-				blendMesh.animations[ 'idle' ].weight = 1 / 3;
-				blendMesh.animations[ 'walk' ].weight = 1 / 3;
-				blendMesh.animations[ 'run' ].weight = 1 / 3;
-
-				gui = new BlendCharacterGui(blendMesh.animations);
+				gui = new BlendCharacterGui(blendMesh);
 
 				// Create the debug visualization
 
@@ -245,9 +252,7 @@
 
 				blendMesh.update( stepSize );
 				helper.update();
-				gui.update();
-
-				THREE.AnimationHandler.update( stepSize );
+				gui.update( blendMesh.mixer.time );
 
 				renderer.render( scene, camera );
 				stats.update();

+ 13 - 54
examples/webgl_animation_skinning_morph.html

@@ -38,7 +38,7 @@
 		<div id="container"></div>
 
 		<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>
 		</div>
 
@@ -46,7 +46,6 @@
 
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
-
 		<script src="js/libs/dat.gui.min.js"></script>
 
 		<script>
@@ -62,6 +61,8 @@
 
 			var mesh, helper;
 
+			var mixer;
+		
 			var mouseX = 0, mouseY = 0;
 
 			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 ) {
 
-				ensureLoop( geometry.animation );
+				//ensureLoop( geometry.animation );
 
 				geometry.computeBoundingBox();
 				var bb = geometry.boundingBox;
@@ -215,14 +199,6 @@
 						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 ++ ) {
 
 					var m = materials[ i ];
@@ -256,9 +232,13 @@
 				helper.visible = false;
 				scene.add( helper );
 
-				var animation = new THREE.Animation( mesh, geometry.animation );
-				animation.play();
+	
+				var clipMorpher = THREE.AnimationClip.CreateFromMorphTargetSequence( 'facialExpressions', mesh.geometry.morphTargets, 3 );
+				var clipBones = geometry.animations[0];
 
+				mixer = new THREE.AnimationMixer( mesh );
+				mixer.addAction( new THREE.AnimationAction( clipMorpher ) );
+				mixer.addAction( new THREE.AnimationAction( clipBones ) );
 			}
 
 			function initGUI() {
@@ -303,30 +283,9 @@
 
 				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 );

+ 14 - 14
examples/webgl_lights_hemisphere.html

@@ -95,7 +95,7 @@
 			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
 
 			var camera, scene, renderer, dirLight, hemiLight;
-			var morphs = [];
+			var mixers = [];
 			var stats;
 
 			var clock = new THREE.Clock();
@@ -204,20 +204,21 @@
 					morphColorsToFaceColors( geometry );
 
 					var material = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0xffffff, shininess: 20, morphTargets: true, vertexColors: THREE.FaceColors, shading: THREE.FlatShading } );
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
-
-					meshAnim.duration = 1000;
+					var mesh = new THREE.Mesh( geometry, material );
 
 					var s = 0.35;
-					meshAnim.scale.set( s, s, s );
-					meshAnim.position.y = 15;
-					meshAnim.rotation.y = -1;
+					mesh.scale.set( s, s, s );
+					mesh.position.y = 15;
+					mesh.rotation.y = -1;
+
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
 
-					meshAnim.castShadow = true;
-					meshAnim.receiveShadow = true;
+					scene.add( mesh );
 
-					scene.add( meshAnim );
-					morphs.push( meshAnim );
+					var mixer = new THREE.AnimationMixer( mesh );
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( 5 ) );
+					mixers.push( mixer );
 
 				} );
 
@@ -307,10 +308,9 @@
 
 				//controls.update();
 
-				for ( var i = 0; i < morphs.length; i ++ ) {
+				for ( var i = 0; i < mixers.length; i ++ ) {
 
-					morph = morphs[ i ];
-					morph.updateAnimation( 1000 * delta );
+					mixers[ i ].update( delta );
 
 				}
 

+ 3 - 0
examples/webgl_loader_collada.html

@@ -33,6 +33,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 
 		<script src="js/loaders/ColladaLoader.js"></script>
 

+ 4 - 0
examples/webgl_loader_collada_keyframe.html

@@ -36,6 +36,10 @@
 
 		<script src="../build/three.min.js"></script>
 
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
+
 		<script src="js/loaders/ColladaLoader.js"></script>
 
 		<script src="js/Detector.js"></script>

+ 3 - 0
examples/webgl_loader_collada_skinning.html

@@ -38,6 +38,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 		<script src="js/loaders/ColladaLoader.js"></script>
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>

+ 21 - 20
examples/webgl_loader_json_blender.html

@@ -39,6 +39,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 
 		<script src="js/loaders/ColladaLoader.js"></script>
 
@@ -56,7 +59,7 @@
 			var dae;
 
 			var clock = new THREE.Clock();
-			var morphs = [];
+			var mixers = [];
 
 			// Collada model
 
@@ -121,28 +124,26 @@
 
 						if ( Math.abs( x ) < 2 && Math.abs( z ) < 2 ) continue;
 
-						morph = new THREE.MorphAnimMesh( geometry, faceMaterial );
-
-						// one second duration
-
-						morph.duration = 1000;
-
-						// random animation offset
-
-						morph.time = 1000 * Math.random();
+						mesh = new THREE.Mesh( geometry, faceMaterial );
 
 						var s = THREE.Math.randFloat( 0.00075, 0.001 );
-						morph.scale.set( s, s, s );
+						mesh.scale.set( s, s, s );
+
+						mesh.position.set( x, 0, z );
+						mesh.rotation.y = THREE.Math.randFloat( -0.25, 0.25 );
 
-						morph.position.set( x, 0, z );
-						morph.rotation.y = THREE.Math.randFloat( -0.25, 0.25 );
+						mesh.matrixAutoUpdate = false;
+						mesh.updateMatrix();
 
-						morph.matrixAutoUpdate = false;
-						morph.updateMatrix();
+						scene.add( mesh );
+						
+						var mixer = new THREE.AnimationMixer( mesh );
+						mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( 1 ) );
 
-						scene.add( morph );
+						// random animation offset
+						mixer.update( 1000 * Math.random() );
 
-						morphs.push( morph );
+						mixers.push( mixer );
 
 					}
 
@@ -202,13 +203,13 @@
 
 				THREE.AnimationHandler.update( delta );
 
-				if ( morphs.length ) {
+				for ( var i = 0; i < mixers.length; i ++ ) {
 
-					for ( var i = 0; i < morphs.length; i ++ )
-						morphs[ i ].updateAnimation( 1000 * delta );
+					mixers[ i ].update( delta );
 
 				}
 
+
 				render();
 				stats.update();
 

+ 9 - 5
examples/webgl_loader_md2.html

@@ -207,6 +207,8 @@
 					setupWeaponsGUI( character );
 					setupGUIAnimations( character );
 
+					character.setAnimation( character.meshBody.geometry.animations[0].name )
+
 				};
 
 				character.loadParts( config );
@@ -302,19 +304,21 @@
 
 				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 animations = character.meshBody.geometry.animations;
 
-				for ( var a in animations ) {
+				for ( var i = 0; i < animations.length; i ++ ) {
+
+					var clip = animations[i];
 
-					playbackConfig[ a ] = generateCallback( a );
-					guiItems[ i ] = folder.add( playbackConfig, a, a );
+					playbackConfig[ clip.name ] = generateCallback( clip );
+					guiItems[ i ] = folder.add( playbackConfig, clip.name, clip.name );
 
 					i ++;
 

+ 14 - 15
examples/webgl_loader_scene.html

@@ -92,6 +92,10 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/MorphAnimMesh.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 
 		<script src="js/loaders/DDSLoader.js"></script>
 
@@ -125,11 +129,12 @@
 
 			var mouseX = 0, mouseY = 0;
 
+			var mixers = [];
+
 			var windowHalfX = window.innerWidth / 2;
 			var windowHalfY = window.innerHeight / 2;
 
 			var rotatingObjects = [];
-			var morphAnimatedObjects = [];
 
 			var clock = new THREE.Clock();
 
@@ -204,18 +209,13 @@
 
 						}
 
-						if ( object instanceof THREE.MorphAnimMesh ) {
-
-							morphAnimatedObjects.push( object );
-
-						}
-
-						if ( object instanceof THREE.SkinnedMesh ) {
+						if ( object instanceof THREE.Mesh ) {
 
-							if ( object.geometry.animation ) {
+							if( object.geometry && object.geometry.animations && object.geometry.animations.length > 0 ) {
 
-								var animation = new THREE.Animation( object, object.geometry.animation );
-								animation.play();
+								var mixer = new THREE.AnimationMixer( object );
+								mixer.addAction( new THREE.AnimationAction( object.geometry.animations[0] ) );
+								mixers.push( mixer );
 
 							}
 
@@ -361,11 +361,10 @@
 
 				}
 
-				for ( var i = 0; i < morphAnimatedObjects.length; i ++ ) {
-
-					var object = morphAnimatedObjects[ i ];
+				
+				for ( var i = 0; i < mixers.length; i ++ ) {
 
-					object.updateAnimation( 1000 * delta );
+					mixers[ i ].update( delta );
 
 				}
 

+ 3 - 0
examples/webgl_loader_sea3d.html

@@ -32,6 +32,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 
 		<script src="js/controls/OrbitControls.js"></script>
 

+ 3 - 0
examples/webgl_loader_sea3d_skinning.html

@@ -34,6 +34,9 @@
 		</div>
 
 		<script src="../build/three.min.js"></script>
+		<script src="js/loaders/collada/Animation.js"></script>
+		<script src="js/loaders/collada/AnimationHandler.js"></script>
+		<script src="js/loaders/collada/KeyFrameAnimation.js"></script>
 
 		<script src="js/controls/OrbitControls.js"></script>
 

+ 20 - 17
examples/webgl_morphnormals.html

@@ -36,8 +36,8 @@
 
 			var container, stats;
 			var camera, scene1, scene2, renderer;
+			var mixers = [];
 
-			var morphs = [];
 			var clock = new THREE.Clock();
 
 			init();
@@ -87,21 +87,24 @@
 
 				//
 
+				
 				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/flamingo.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
 
 					var material = new THREE.MeshPhongMaterial( { color: 0xffffff, morphTargets: true, vertexColors: THREE.FaceColors, shading: THREE.FlatShading } );
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
+					var mesh = new THREE.Mesh( geometry, material );
 
-					meshAnim.duration = 5000;
+					mesh.scale.set( 1.5, 1.5, 1.5 );
+					mesh.position.y = 150;
 
-					meshAnim.scale.set( 1.5, 1.5, 1.5 );
-					meshAnim.position.y = 150;
+					scene1.add( mesh );
 
-					scene1.add( meshAnim );
-					morphs.push( meshAnim );
+					var mixer = new THREE.AnimationMixer( mesh );
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[ 0 ] ).warpToDuration( 5 ) );
+
+					mixers.push( mixer );
 
 				} );
 
@@ -112,15 +115,17 @@
 					geometry.computeMorphNormals();
 
 					var material = new THREE.MeshPhongMaterial( { color: 0xffffff, specular: 0xffffff, shininess: 20, morphTargets: true, morphNormals: true, vertexColors: THREE.FaceColors, shading: THREE.SmoothShading } );
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
+					var mesh = new THREE.Mesh( geometry, material );
+					
+					mesh.scale.set( 1.5, 1.5, 1.5 );
+					mesh.position.y = 150;
 
-					meshAnim.duration = 5000;
+					scene2.add( mesh );
 
-					meshAnim.scale.set( 1.5, 1.5, 1.5 );
-					meshAnim.position.y = 150;
+					var mixer = new THREE.AnimationMixer( mesh );
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[ 0 ] ).warpToDuration( 5 ) );
 
-					scene2.add( meshAnim );
-					morphs.push( meshAnim );
+					mixers.push( mixer );
 
 				} );
 
@@ -205,10 +210,8 @@
 
 				var delta = clock.getDelta();
 
-				for ( var i = 0; i < morphs.length; i ++ ) {
-
-					morph = morphs[ i ];
-					morph.updateAnimation( 1000 * delta );
+   				for ( var i = 0; i < mixers.length; i ++ ) {
+					mixers[ i ].update( delta );
 
 				}
 

+ 8 - 6
examples/webgl_morphtargets_horse.html

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

+ 1 - 1
examples/webgl_morphtargets_human.html

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

+ 20 - 11
examples/webgl_shading_physical.html

@@ -38,10 +38,11 @@
 		<div id="info">
 			<a href="http://threejs.org" target="_blank">three.js</a> - webgl physically based shading testbed
 		</div>
-
+-
 		<script src="../build/three.min.js"></script>
 
 		<script src="js/controls/TrackballControls.js"></script>
+		<script src="js/AnimationClipCreator.js"></script>
 
 		<script src="js/Detector.js"></script>
 		<script src="js/libs/stats.min.js"></script>
@@ -73,7 +74,7 @@
 
 			var sunLight, pointLight, ambientLight;
 
-			var morph;
+			var mixer;
 
 			var parameters, tweenDirection, tweenDay, tweenNight;
 
@@ -271,18 +272,22 @@
 
 					var morphMaterial = new THREE.MeshPhongMaterial( { color: 0x000000, specular: 0xff9900, shininess: 50, morphTargets: true, side: THREE.DoubleSide, shading: THREE.FlatShading } );
 
-					morph = new THREE.MorphAnimMesh( geometry, morphMaterial );
+					mesh = new THREE.Mesh( geometry, morphMaterial );
+
+					mixer = new THREE.AnimationMixer( mesh );
+
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( 10 ) );
 
 					var s = 200;
-					morph.scale.set( s, s, s );
+					mesh.scale.set( s, s, s );
 
-					morph.duration = 8000;
-					morph.mirroredLoop = true;
+					//morph.duration = 8000;
+					//morph.mirroredLoop = true;
 
-					morph.castShadow = true;
-					morph.receiveShadow = true;
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
 
-					scene.add( morph );
+					scene.add( mesh );
 
 				} );
 
@@ -516,12 +521,16 @@
 
 				// update
 
-				var delta = 1000 * clock.getDelta();
+				var delta = clock.getDelta();
 
 				TWEEN.update();
 				controls.update();
 
-				if ( morph ) morph.updateAnimation( delta );
+				if ( mixer ) {
+
+					mixer.update( delta );
+
+				}
 
 				scene.fog.color.setHSL( 0.63, 0.05, parameters.control );
 				renderer.setClearColor( scene.fog.color );

+ 24 - 22
examples/webgl_shadowmap.html

@@ -62,7 +62,7 @@
 
 			var sceneHUD, cameraOrtho, hudMesh;
 
-			var morph, morphs = [];
+			var morphs = [];
 
 			var light;
 
@@ -315,21 +315,23 @@
 
 					}
 
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.speed = speed;
 
-					meshAnim.speed = speed;
-					meshAnim.duration = duration;
-					meshAnim.time = 600 * Math.random();
+					var mixer = new THREE.AnimationMixer( mesh );
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( duration ) );
+					mixer.update( 600 * Math.random() );
+					mesh.mixer = mixer;
 
-					meshAnim.position.set( x, y, z );
-					meshAnim.rotation.y = Math.PI/2;
+					mesh.position.set( x, y, z );
+					mesh.rotation.y = Math.PI/2;
 
-					meshAnim.castShadow = true;
-					meshAnim.receiveShadow = true;
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
+				
+					scene.add( mesh );
 
-					scene.add( meshAnim );
-
-					morphs.push( meshAnim );
+					morphs.push( mesh );
 
 				}
 
@@ -355,34 +357,34 @@
 
 					morphColorsToFaceColors( geometry );
 
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, 300, true );
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, 450, true );
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, 600, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, 300, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, 450, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, 600, true );
 
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, -300, true );
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, -450, true );
-					addMorph( geometry, 550, 1000, 100 - Math.random() * 1000, FLOOR, -600, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, -300, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, -450, true );
+					addMorph( geometry, 550, 1, 100 - Math.random() * 1000, FLOOR, -600, true );
 
 				} );
 
 				loader.load( "models/animated/flamingo.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 500, 1000, 500 - Math.random() * 500, FLOOR + 350, 40 );
+					addMorph( geometry, 500, 1, 500 - Math.random() * 500, FLOOR + 350, 40 );
 
 				} );
 
 				loader.load( "models/animated/stork.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 350, 1000, 500 - Math.random() * 500, FLOOR + 350, 340 );
+					addMorph( geometry, 350, 1, 500 - Math.random() * 500, FLOOR + 350, 340 );
 
 				} );
 
 				loader.load( "models/animated/parrot.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 450, 500, 500 - Math.random() * 500, FLOOR + 300, 700 );
+					addMorph( geometry, 450, 0.5, 500 - Math.random() * 500, FLOOR + 300, 700 );
 
 				} );
 
@@ -405,7 +407,7 @@
 
 					morph = morphs[ i ];
 
-					morph.updateAnimation( 1000 * delta );
+					morph.mixer.update( delta );
 
 					morph.position.x += morph.speed * delta;
 

+ 16 - 15
examples/webgl_shadowmap_performance.html

@@ -57,7 +57,7 @@
 
 			var NEAR = 5, FAR = 3000;
 
-			var morph, morphs = [];
+			var morph, morphs = [], mixer;
 
 			var light;
 
@@ -237,6 +237,8 @@
 
 				scene.add( mesh );
 
+				mixer = new THREE.AnimationMixer( scene );
+					
 				// MORPHS
 
 				function addMorph( geometry, speed, duration, x, y, z, fudgeColor ) {
@@ -249,21 +251,20 @@
 
 					}
 
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.speed = speed;
 
-					meshAnim.speed = speed;
-					meshAnim.duration = duration;
-					meshAnim.time = 600 * Math.random();
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[0], Math.random() ).warpToDuration( duration ).setLocalRoot( mesh ) );
+				
+					mesh.position.set( x, y, z );
+					mesh.rotation.y = Math.PI/2;
 
-					meshAnim.position.set( x, y, z );
-					meshAnim.rotation.y = Math.PI/2;
+					mesh.castShadow = true;
+					mesh.receiveShadow = true;
 
-					meshAnim.castShadow = true;
-					meshAnim.receiveShadow = true;
+					scene.add( mesh );
 
-					scene.add( meshAnim );
-
-					morphs.push( meshAnim );
+					morphs.push( mesh );
 
 				}
 
@@ -291,7 +292,7 @@
 
 					for ( var i = - 600; i < 601; i += 2 ) {
 
-						addMorph( geometry, 550, 1000, 100 - Math.random() * 3000, FLOOR, i, true );
+						addMorph( geometry, 550, 1, 100 - Math.random() * 3000, FLOOR, i, true );
 
 					}
 
@@ -365,12 +366,12 @@
 
 				var delta = clock.getDelta();
 
+				if( mixer ) mixer.update( delta );
+
 				for ( var i = 0; i < morphs.length; i ++ ) {
 
 					morph = morphs[ i ];
 
-					morph.updateAnimation( 1000 * delta );
-
 					morph.position.x += morph.speed * delta;
 
 					if ( morph.position.x  > 2000 )  {

+ 5 - 4
examples/webgl_skinning_simple.html

@@ -23,7 +23,7 @@
 
 			var container, stats, controls;
 			var camera, scene, renderer, loader, clock, light;
-			var skinnedMesh, animation, groundMaterial, planeGeometry;
+			var skinnedMesh, animation, groundMaterial, planeGeometry, mixer;
 
 			init();
 			animate();
@@ -74,8 +74,9 @@
 					skinnedMesh = new THREE.SkinnedMesh(geometry, new THREE.MeshFaceMaterial(materials));
 					skinnedMesh.scale.set( 1, 1, 1 );
 					scene.add( skinnedMesh );
-					animation = new THREE.Animation( skinnedMesh, skinnedMesh.geometry.animations[ 0 ] );
-					animation.play();
+
+					mixer = new THREE.AnimationMixer( skinnedMesh );
+					mixer.addAction( new THREE.AnimationAction( skinnedMesh.geometry.animations[0] ) );					
 
 				});
 
@@ -85,7 +86,7 @@
 
 				requestAnimationFrame( animate );
 
-				THREE.AnimationHandler.update( clock.getDelta() );
+				if( mixer ) mixer.update( clock.getDelta() );
 
 				controls.update();
 

+ 19 - 17
examples/webgl_terrain_dynamic.html

@@ -498,21 +498,23 @@
 
 					var material = new THREE.MeshLambertMaterial( { color: 0xffaa55, morphTargets: true, vertexColors: THREE.FaceColors } );
 
-					var meshAnim = new THREE.MorphAnimMesh( geometry, material );
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.speed = speed;
 
-					meshAnim.speed = speed;
-					meshAnim.duration = duration;
-					meshAnim.time = 600 * Math.random();
+					var mixer = new THREE.AnimationMixer( mesh );
+					mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).warpToDuration( duration ) );
+					mixer.update( 600 * Math.random() );
+					mesh.mixer = mixer;
 
-					meshAnim.position.set( x, y, z );
-					meshAnim.rotation.y = Math.PI/2;
+					mesh.position.set( x, y, z );
+					mesh.rotation.y = Math.PI/2;
 
-					meshAnim.castShadow = true;
-					meshAnim.receiveShadow = false;
+					mesh.castShadow = true;
+					mesh.receiveShadow = false;
 
-					scene.add( meshAnim );
+					scene.add( mesh );
 
-					morphs.push( meshAnim );
+					morphs.push( mesh );
 
 				}
 
@@ -539,24 +541,24 @@
 				loader.load( "models/animated/parrot.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 250, 500, startX -500, 500, 700 );
-					addMorph( geometry, 250, 500, startX - Math.random() * 500, 500, -200 );
-					addMorph( geometry, 250, 500, startX - Math.random() * 500, 500, 200 );
-					addMorph( geometry, 250, 500, startX - Math.random() * 500, 500, 1000 );
+					addMorph( geometry, 250, 0.5, startX -500, 500, 700 );
+					addMorph( geometry, 250, 0.5, startX - Math.random() * 500, 500, -200 );
+					addMorph( geometry, 250, 0.5, startX - Math.random() * 500, 500, 200 );
+					addMorph( geometry, 250, 0.5, startX - Math.random() * 500, 500, 1000 );
 
 				} );
 
 				loader.load( "models/animated/flamingo.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 500, 1000, startX - Math.random() * 500, 350, 40 );
+					addMorph( geometry, 500, 1, startX - Math.random() * 500, 350, 40 );
 
 				} );
 
 				loader.load( "models/animated/stork.js", function( geometry ) {
 
 					morphColorsToFaceColors( geometry );
-					addMorph( geometry, 350, 1000, startX - Math.random() * 500, 350, 340 );
+					addMorph( geometry, 350, 1, startX - Math.random() * 500, 350, 340 );
 
 				} );
 
@@ -688,7 +690,7 @@
 
 						morph = morphs[ i ];
 
-						morph.updateAnimation( 1000 * delta );
+						morph.mixer.update( delta );
 
 						morph.position.x += morph.speed * delta;
 

+ 5 - 0
src/Three.js

@@ -264,6 +264,11 @@ THREE.RGB_PVRTC_2BPPV1_Format = 2101;
 THREE.RGBA_PVRTC_4BPPV1_Format = 2102;
 THREE.RGBA_PVRTC_2BPPV1_Format = 2103;
 
+// Loop styles for AnimationAction
+
+THREE.LoopOnce = 2200;
+THREE.LoopRepeat = 2201;
+THREE.LoopPingPong = 2202;
 
 // DEPRECATED
 

+ 166 - 0
src/animation/AnimationAction.js

@@ -0,0 +1,166 @@
+/**
+ *
+ * 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 ) {
+
+	if( clip === undefined ) throw new Error( 'clip is null' );
+	this.clip = clip;
+	this.localRoot = null;
+	this.startTime = startTime || 0;
+	this.timeScale = timeScale || 1;
+	this.weight = weight || 1;
+	this.loop = loop || THREE.LoopRepeat;
+	this.loopCount = 0;
+	this.enabled = true;	// allow for easy disabling of the action.
+
+	this.actionTime = - this.startTime;
+	this.clipTime = 0;
+
+	this.propertyBindings = [];
+};
+
+/*
+THREE.LoopOnce = 2200;
+THREE.LoopRepeat = 2201;
+THREE.LoopPingPing = 2202;
+*/
+
+THREE.AnimationAction.prototype = {
+
+	constructor: THREE.AnimationAction,
+
+	setLocalRoot: function( localRoot ) {
+
+		this.localRoot = localRoot;
+
+		return this;
+		
+	},
+
+	updateTime: function( clipDeltaTime ) {
+
+		var previousClipTime = this.clipTime;
+   		var previousLoopCount = this.loopCount;
+   		var previousActionTime = this.actionTime;
+
+		var duration = this.clip.duration;
+	
+		this.actionTime = this.actionTime + clipDeltaTime;
+	
+		if( this.loop === THREE.LoopOnce ) {
+
+			this.loopCount = 0;
+			this.clipTime = Math.min( Math.max( this.actionTime, 0 ), duration );
+	
+			// if time is changed since last time, see if we have hit a start/end limit
+			if( this.clipTime !== previousClipTime ) {
+
+				if( this.clipTime === duration ) {
+
+					this.mixer.dispatchEvent( { type: 'finished', action: this, direction: 1 } );
+
+				}
+				else if( this.clipTime === 0 ) {
+
+					this.mixer.dispatchEvent( { type: 'finished', action: this, direction: -1 } );
+
+				}
+
+			}
+
+		
+			return this.clipTime;
+
+		}
+		
+		this.loopCount = Math.floor( this.actionTime / duration );
+	
+		var newClipTime = this.actionTime - this.loopCount * duration;
+		newClipTime = newClipTime % duration;
+	
+		// if we are ping pong looping, ensure that we go backwards when appropriate
+		if( this.loop == THREE.LoopPingPong ) {
+
+			if( Math.abs( this.loopCount % 2 ) === 1 ) {
+
+				newClipTime = duration - newClipTime;
+
+			}
+
+		}
+
+		this.clipTime = newClipTime;
+
+		if( this.loopCount !== previousLoopCount ) {
+
+   			this.mixer.dispatchEvent( { type: 'loop', action: this, loopDelta: ( this.loopCount - this.loopCount ) } );
+
+   		}
+	
+	   	return this.clipTime;
+
+	},
+
+	syncWith: function( action ) {
+
+		this.actionTime = action.actionTime;
+		this.timeScale = action.timeScale;
+
+		return this;
+	},
+
+	warpToDuration: function( duration ) {
+
+		this.timeScale = this.clip.duration / duration;
+
+		return this;
+	},
+
+	init: function( time ) {
+
+		this.clipTime = time - this.startTime;
+
+		return this;
+
+	},
+
+	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;
+
+	}
+
+};

+ 312 - 0
src/animation/AnimationClip.js

@@ -0,0 +1,312 @@
+/**
+ *
+ * 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 !== undefined ) ? duration : -1;
+
+	// 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.CreateFromMorphTargetSequence = function( name, morphTargetSequence, fps ) {
+
+
+	var numMorphTargets = morphTargetSequence.length;
+	var tracks = [];
+
+	for( var i = 0; i < numMorphTargets; i ++ ) {
+
+		var keys = [];
+
+		keys.push( { time: ( i + numMorphTargets - 1 ) % numMorphTargets, value: 0 } );
+		keys.push( { time: i, value: 1 } );
+		keys.push( { time: ( i + 1 ) % numMorphTargets, value: 0 } );
+
+		keys.sort( THREE.KeyframeTrack.keyComparer );
+
+		// if there is a key at the first frame, duplicate it as the last frame as well for perfect loop.
+		if( keys[0].time === 0 ) {
+			keys.push( {
+				time: numMorphTargets,
+				value: keys[0].value
+			});
+		}
+
+		tracks.push( new THREE.NumberKeyframeTrack( '.morphTargetInfluences[' + morphTargetSequence[i].name + ']', keys ).scale( 1.0 / fps ) );
+	}
+
+	return new THREE.AnimationClip( name, -1, tracks );
+
+};
+
+THREE.AnimationClip.findByName = function( clipArray, name ) {
+
+	for( var i = 0; i < clipArray.length; i ++ ) {
+
+		if( clipArray[i].name === name ) {
+
+			return clipArray[i];
+
+		}
+	}
+
+	return null;
+
+};
+
+THREE.AnimationClip.CreateClipsFromMorphTargetSequences = function( morphTargets, fps ) {
+	
+	var animationToMorphTargets = {};
+
+	// tested with https://regex101.com/ on trick sequences such flamingo_flyA_003, flamingo_run1_003, crdeath0059
+	var pattern = /^([\w-]*?)([\d]+)$/;
+
+	// sort morph target names into animation groups based patterns like Walk_001, Walk_002, Run_001, Run_002
+	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 name = parts[ 1 ];
+
+			var animationMorphTargets = animationToMorphTargets[ name ];
+			if( ! animationMorphTargets ) {
+				animationToMorphTargets[ name ] = animationMorphTargets = [];
+			}
+
+			animationMorphTargets.push( morphTarget );
+
+		}
+
+	}
+
+	var clips = [];
+
+	for( var name in animationToMorphTargets ) {
+
+		clips.push( THREE.AnimationClip.CreateFromMorphTargetSequence( name, animationToMorphTargets[ name ], fps ) );
+	}
+
+	return clips;
+
+};
+
+// parse the standard JSON format for clips
+THREE.AnimationClip.parse = function( json ) {
+
+	var tracks = [];
+
+	for( var i = 0; i < json.tracks.length; i ++ ) {
+
+		tracks.push( THREE.KeyframeTrack.parse( json.tracks[i] ).scale( 1.0 / json.fps ) );
+
+	}
+
+	return new THREE.AnimationClip( json.name, json.duration, tracks );
+
+};
+
+
+// parse the animation.hierarchy format
+THREE.AnimationClip.parseAnimation = 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 tracks = [];
+
+	var clipName = animation.name || 'default';
+	var duration = animation.length || -1; // automatic length determination in AnimationClip.
+	var fps = animation.fps || 30;
+
+	var hierarchyTracks = animation.hierarchy || [];
+
+	for ( var h = 0; h < hierarchyTracks.length; h ++ ) {
+
+		var animationKeys = hierarchyTracks[ 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 );
+
+		}
+	}
+
+	if( tracks.length === 0 ) {
+
+		return null;
+
+	}
+
+	var clip = new THREE.AnimationClip( clipName, duration, tracks );
+
+	return clip;
+
+};

+ 241 - 0
src/animation/AnimationMixer.js

@@ -0,0 +1,241 @@
+/**
+ *
+ * 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.propertyBindingMap = {};
+
+};
+
+THREE.AnimationMixer.prototype = {
+
+	constructor: THREE.AnimationMixer,
+
+	addAction: function( action ) {
+
+		// TODO: check for duplicate action names?  Or provide each action with a UUID?
+
+		this.actions.push( action );
+		action.init( this.time );
+		action.mixer = this;
+
+		var tracks = action.clip.tracks;
+
+		var root = action.localRoot || this.root;
+
+		for( var i = 0; i < tracks.length; i ++ ) {
+
+			var track = tracks[ i ];
+
+			var propertyBindingKey = root.uuid + '-' + track.name;			
+			var propertyBinding = this.propertyBindingMap[ propertyBindingKey ];
+
+			if( propertyBinding === undefined ) {
+			
+				propertyBinding = new THREE.PropertyBinding( root, track.name );
+				this.propertyBindingMap[ propertyBindingKey ] = propertyBinding;
+			
+			}
+
+			// push in the same order as the tracks.
+			action.propertyBindings.push( propertyBinding );
+			
+			// track usages of shared property bindings, because if we leave too many around, the mixer can get slow
+			propertyBinding.referenceCount += 1;
+
+		}
+
+	},
+
+	removeAllActions: function() {
+
+		for( var i = 0; i < this.actions.length; i ++ ) {
+
+			this.actions[i].mixer = null;
+			
+		}
+
+		// unbind all property bindings
+		for( var properyBindingKey in this.propertyBindingMap ) {
+
+			this.propertyBindingMap[ properyBindingKey ].unbind();
+
+		}
+
+		this.actions = [];
+		this.propertyBindingMap = {};
+
+		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 root = action.localRoot || this.root;
+		var tracks = action.clip.tracks;
+
+		for( var i = 0; i < tracks.length; i ++ ) {
+		
+			var track = tracks[ i ];
+
+			var propertyBindingKey = root.uuid + '-' + track.name;			
+			var propertyBinding = this.propertyBindingMap[ propertyBindingKey ];
+	
+			propertyBinding.referenceCount -= 1;
+
+			if( propertyBinding.referenceCount <= 0 ) {
+
+				propertyBinding.unbind();
+
+				delete this.propertyBindingMap[ propertyBindingKey ];
+
+			}
+		}
+
+		return this;
+
+	},
+
+	// can be optimized if needed
+	findActionByName: function( name ) {
+
+		for( var i = 0; i < this.actions.length; i ++ ) {
+
+			if( this.actions[i].name === name ) return this.actions[i];
+
+		}
+
+		return null;
+
+	},
+
+	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;
+
+				action.propertyBindings[ j ].accumulate( actionResults[j], weight );
+
+			}
+
+		}
+	
+		// apply to nodes
+		for( var propertyBindingKey in this.propertyBindingMap ) {
+
+			this.propertyBindingMap[ propertyBindingKey ].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;
+			 	}
+		 	}
+		};
+
+	}
+	
+};

+ 274 - 0
src/animation/KeyframeTrack.js

@@ -0,0 +1,274 @@
+/**
+ *
+ * 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( name === undefined ) throw new Error( "track name is undefined" );
+	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.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;
+
+	},
+
+	/* NOTE: This is commented out because we really shouldn't have to handle unsorted key lists
+	         Tracks with out of order keys should be considered to be invalid.  - bhouston
+	sort: function() {
+
+		this.keys.sort( THREE.KeyframeTrack.keyComparer );
+
+		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.keyComparer = function keyComparator(key0, key1) {
+	return key0.time - key1.time;
+};
+
+THREE.KeyframeTrack.parse = function( json ) {
+
+	if( json.type === undefined ) throw new Error( "track type undefined, can not parse" );
+
+	var trackType = THREE.KeyframeTrack.GetTrackTypeForTypeName( json.type );
+
+	return trackType.parse( json );
+
+};
+
+THREE.KeyframeTrack.GetTrackTypeForTypeName = function( typeName ) {
+	switch( typeName.toLowerCase() ) {
+	 	case "vector":
+	 	case "vector2":
+	 	case "vector3":
+	 	case "vector4":
+			return THREE.VectorKeyframeTrack;
+
+	 	case "quaternion":
+			return THREE.QuaternionKeyframeTrack;
+
+	 	case "integer":
+	 	case "scalar":
+	 	case "double":
+	 	case "float":
+	 	case "number":
+			return THREE.NumberKeyframeTrack;
+
+	 	case "bool":
+	 	case "boolean":
+			return THREE.BooleanKeyframeTrack;
+
+	 	case "string":
+	 		return THREE.StringKeyframeTrack;
+	};
+
+	throw new Error( "Unsupported typeName: " + typeName );
+};

+ 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( json ) {
+
+	return new THREE.BooleanKeyframeTrack( json.name, json.keys );
+
+};
+ 

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

@@ -0,0 +1,74 @@
+/**
+ *
+ * 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( json ) {
+
+	var keys = [];
+
+	for( var i = 0; i < json.keys.length; i ++ ) {
+		var jsonKey = json.keys[i];
+		keys.push( {
+			value: new THREE.Color().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		} );
+	}
+
+	return new THREE.ColorKeyframeTrack( json.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( json ) {
+
+	return new THREE.NumberKeyframeTrack( json.name, json.keys );
+
+};
+ 

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

@@ -0,0 +1,86 @@
+/**
+ *
+ * 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( json ) {
+
+	var keys = [];
+
+	for( var i = 0; i < json.keys.length; i ++ ) {
+		var jsonKey = json.keys[i];
+		keys.push( {
+			value: new THREE.Quaternion().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		} );
+	}
+
+	return new THREE.QuaternionKeyframeTrack( json.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( json ) {
+
+	return new THREE.StringKeyframeTrack( json.name, json.keys );
+
+};
+ 

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

@@ -0,0 +1,77 @@
+/**
+ *
+ * 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( json ) {
+
+	var elementCount = json.keys[0].value.length;
+	var valueType = THREE[ 'Vector' + elementCount ];
+
+	var keys = [];
+
+	for( var i = 0; i < json.keys.length; i ++ ) {
+		var jsonKey = json.keys[i];
+		keys.push( {
+			value: new valueType().fromArray( jsonKey.value ),
+			time: jsonKey.time
+		} );
+	}
+
+	return new THREE.VectorKeyframeTrack( json.name, keys );
+
+};
+ 

+ 1 - 1
src/core/Geometry.js

@@ -4,7 +4,7 @@
  * @author alteredq / http://alteredqualia.com/
  * @author mikael emtinger / http://gomo.se/
  * @author zz85 / http://www.lab4games.net/zz85/blog
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Geometry = function () {

+ 1 - 1
src/core/Raycaster.js

@@ -1,6 +1,6 @@
 /**
  * @author mrdoob / http://mrdoob.com/
- * @author bhouston / http://exocortex.com/
+ * @author bhouston / http://clara.io/
  * @author stephomi / http://stephaneginier.com/
  */
 

+ 1 - 1
src/extras/geometries/LatheGeometry.js

@@ -1,7 +1,7 @@
 /**
  * @author astrodud / http://astrodud.isgreat.org/
  * @author zz85 / https://github.com/zz85
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 // points - to create a closed torus, one must use a set of points 

+ 1 - 1
src/extras/helpers/ArrowHelper.js

@@ -1,7 +1,7 @@
 /**
  * @author WestLangley / http://github.com/WestLangley
  * @author zz85 / http://github.com/zz85
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  *
  * Creates an arrow for visualizing directions
  *

+ 51 - 0
src/loaders/AnimationLoader.js

@@ -0,0 +1,51 @@
+/**
+ * @author bhouston / http://clara.io/
+ */
+
+THREE.AnimationLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
+
+};
+
+THREE.AnimationLoader.prototype = {
+
+	constructor: THREE.AnimationLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new THREE.XHRLoader( scope.manager );
+		loader.setCrossOrigin( this.crossOrigin );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( JSON.parse( text ) ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+
+	},
+
+	parse: function ( json, onLoad ) {
+
+		var animations = [];
+
+		for( var i = 0; i < json.length; i ++ ) {
+
+			var clip = THREE.AnimationClip.parse( json[i] );
+
+			animations.push( clip );
+
+		}
+
+		onLoad( animations );
+
+	}
+
+};

+ 38 - 6
src/loaders/JSONLoader.js

@@ -97,6 +97,7 @@ THREE.JSONLoader.prototype = {
 
 		parseSkin();
 		parseMorphing( scale );
+		parseAnimations();
 
 		geometry.computeFaceNormals();
 		geometry.computeBoundingSphere();
@@ -444,12 +445,6 @@ THREE.JSONLoader.prototype = {
 
 			}
 
-
-			// could change this to json.animations[0] or remove completely
-
-			geometry.animation = json.animation;
-			geometry.animations = json.animations;
-
 		};
 
 		function parseMorphing( scale ) {
@@ -506,6 +501,43 @@ THREE.JSONLoader.prototype = {
 				}
 
 			}
+		}
+
+		function parseAnimations() {
+
+			var outputAnimations = [];
+
+			// parse old style Bone/Hierarchy animations
+			var animations = [];
+			if( json.animation !== undefined ) {
+				animations.push( json.animation );
+			}
+			if( json.animations !== undefined ) {
+				if( json.animations.length ) {
+					animations = animations.concat( json.animations );
+				}
+				else {
+					animations.push( json.animations );
+				}
+			}
+
+			for( var i = 0; i < animations.length; i ++ ) {
+
+				var clip = THREE.AnimationClip.parseAnimation( animations[i], geometry.bones );
+				if( clip ) outputAnimations.push( clip );
+
+			}
+
+			// parse implicit morph animations
+			if( geometry.morphTargets ) {
+
+				// TODO: Figure out what an appropraite FPS is for morph target animations -- defaulting to 10, but really it is completely arbitrary.
+				var morphAnimationClips = THREE.AnimationClip.CreateClipsFromMorphTargetSequences( geometry.morphTargets, 10 );
+				outputAnimations = outputAnimations.concat( morphAnimationClips );
+
+			}
+
+			if( outputAnimations.length > 0 ) geometry.animations = outputAnimations;
 
 		};
 

+ 23 - 0
src/loaders/ObjectLoader.js

@@ -57,8 +57,15 @@ THREE.ObjectLoader.prototype = {
 
 		var textures  = this.parseTextures( json.textures, images );
 		var materials = this.parseMaterials( json.materials, textures );
+
 		var object = this.parseObject( json.object, geometries, materials );
 
+		if( json.animations ) {
+
+			object.animations = this.parseAnimations( json.animations );
+
+		}
+
 		if ( json.images === undefined || json.images.length === 0 ) {
 
 			if ( onLoad !== undefined ) onLoad( object );
@@ -316,6 +323,22 @@ THREE.ObjectLoader.prototype = {
 
 	},
 
+	parseAnimations: function ( json ) {
+
+		var animations = [];
+
+		for( var i = 0; i < json.length; i ++ ) {
+
+			var clip = THREE.AnimationClip.parse( json[i] );
+
+			animations.push( clip );
+
+		}
+
+		return animations;
+
+	},
+
 	parseImages: function ( json, onLoad ) {
 
 		var scope = this;

+ 1 - 1
src/math/Box2.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Box2 = function ( min, max ) {

+ 1 - 1
src/math/Box3.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  * @author WestLangley / http://github.com/WestLangley
  */
 

+ 1 - 1
src/math/Euler.js

@@ -1,7 +1,7 @@
 /**
  * @author mrdoob / http://mrdoob.com/
  * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Euler = function ( x, y, z, order ) {

+ 1 - 1
src/math/Frustum.js

@@ -1,7 +1,7 @@
 /**
  * @author mrdoob / http://mrdoob.com/
  * @author alteredq / http://alteredqualia.com/
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Frustum = function ( p0, p1, p2, p3, p4, p5 ) {

+ 1 - 1
src/math/Line3.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Line3 = function ( start, end ) {

+ 1 - 1
src/math/Matrix3.js

@@ -1,7 +1,7 @@
 /**
  * @author alteredq / http://alteredqualia.com/
  * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Matrix3 = function () {

+ 1 - 1
src/math/Matrix4.js

@@ -7,7 +7,7 @@
  * @author alteredq / http://alteredqualia.com/
  * @author mikael emtinger / http://gomo.se/
  * @author timknip / http://www.floorplanner.com/
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  * @author WestLangley / http://github.com/WestLangley
  */
 

+ 1 - 1
src/math/Plane.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Plane = function ( normal, constant ) {

+ 1 - 1
src/math/Quaternion.js

@@ -2,7 +2,7 @@
  * @author mikael emtinger / http://gomo.se/
  * @author alteredq / http://alteredqualia.com/
  * @author WestLangley / http://github.com/WestLangley
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Quaternion = function ( x, y, z, w ) {

+ 1 - 1
src/math/Ray.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  */
 
 THREE.Ray = function ( origin, direction ) {

+ 1 - 1
src/math/Sphere.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  * @author mrdoob / http://mrdoob.com/
  */
 

+ 1 - 1
src/math/Triangle.js

@@ -1,5 +1,5 @@
 /**
- * @author bhouston / http://exocortex.com
+ * @author bhouston / http://clara.io
  * @author mrdoob / http://mrdoob.com/
  */
 

+ 0 - 212
src/objects/MorphAnimMesh.js

@@ -1,212 +0,0 @@
-/**
- * @author alteredq / http://alteredqualia.com/
- */
-
-THREE.MorphAnimMesh = function ( geometry, material ) {
-
-	THREE.Mesh.call( this, geometry, material );
-
-	this.type = 'MorphAnimMesh';
-
-	// 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 );
-
-};
-
-THREE.MorphAnimMesh.prototype = Object.create( THREE.Mesh.prototype );
-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 () {
-
-	this.direction = 1;
-	this.directionBackwards = false;
-
-};
-
-THREE.MorphAnimMesh.prototype.setDirectionBackward = function () {
-
-	this.direction = - 1;
-	this.directionBackwards = true;
-
-};
-
-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 };
-
-};
-
-THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
-
-	var animation = this.geometry.animations[ label ];
-
-	if ( animation ) {
-
-		this.setFrameRange( animation.start, animation.end );
-		this.duration = 1000 * ( ( animation.end - animation.start ) / fps );
-		this.time = 0;
-
-	} else {
-
-		console.warn( 'THREE.MorphAnimMesh: animation[' + label + '] undefined in .playAnimation()' );
-
-	}
-
-};
-
-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;
-
-};
-
-THREE.MorphAnimMesh.prototype.interpolateTargets = function ( a, b, t ) {
-
-	var influences = this.morphTargetInfluences;
-
-	for ( var i = 0, l = influences.length; i < l; i ++ ) {
-
-		influences[ i ] = 0;
-
-	}
-
-	if ( a > - 1 ) influences[ a ] = 1 - t;
-	if ( b > - 1 ) influences[ b ] = t;
-
-};
-
-THREE.MorphAnimMesh.prototype.copy = function ( source ) {
-
-	THREE.Mesh.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;
-
-	return this;
-
-};

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

@@ -33,6 +33,17 @@
 	"src/core/DirectGeometry.js",
 	"src/core/BufferGeometry.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/CubeCamera.js",
 	"src/cameras/OrthographicCamera.js",
@@ -82,7 +93,6 @@
 	"src/objects/Bone.js",
 	"src/objects/Skeleton.js",
 	"src/objects/SkinnedMesh.js",
-	"src/objects/MorphAnimMesh.js",
 	"src/objects/LOD.js",
 	"src/objects/Sprite.js",
 	"src/objects/LensFlare.js",

+ 0 - 4
utils/build/includes/extras.json

@@ -21,10 +21,6 @@
 	"src/extras/curves/SplineCurve3.js",
 	"src/extras/curves/CatmullRomCurve3.js",
 	"src/extras/curves/ClosedSplineCurve3.js",
-	"src/extras/animation/AnimationHandler.js",
-	"src/extras/animation/Animation.js",
-	"src/extras/animation/KeyFrameAnimation.js",
-	"src/extras/animation/MorphAnimation.js",
 	"src/extras/geometries/BoxGeometry.js",
 	"src/extras/geometries/CircleGeometry.js",
 	"src/extras/geometries/CircleBufferGeometry.js",

+ 34 - 4
utils/exporters/blender/addons/io_three/__init__.py

@@ -38,9 +38,9 @@ logging.basicConfig(
 
 bl_info = {
     'name': "Three.js Format",
-    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw",
-    'version': (1, 4, 0),
-    'blender': (2, 73, 0),
+    'author': "repsac, mrdoob, yomotsu, mpk, jpweeks, rkusa, tschw, jackcaron, bhouston",
+    'version': (1, 5, 0),
+    'blender': (2, 74, 0),
     'location': "File > Export",
     'description': "Export Three.js formatted JSON files.",
     'warning': "Importer not included.",
@@ -322,7 +322,7 @@ def restore_export_settings(properties, settings):
         constants.INDEX_TYPE,
         constants.EXPORT_OPTIONS[constants.INDEX_TYPE])
     ## }
-
+   
     ## Materials {
     properties.option_materials = settings.get(
         constants.MATERIALS,
@@ -414,10 +414,18 @@ def restore_export_settings(properties, settings):
         constants.MORPH_TARGETS,
         constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
 
+    properties.option_blend_shape = settings.get(
+        constants.BLEND_SHAPES,
+        constants.EXPORT_OPTIONS[constants.BLEND_SHAPES])
+
     properties.option_animation_skeletal = settings.get(
         constants.ANIMATION,
         constants.EXPORT_OPTIONS[constants.ANIMATION])
 
+    properties.option_keyframes = settings.get(
+        constants.KEYFRAMES,
+        constants.EXPORT_OPTIONS[constants.KEYFRAMES])
+
     properties.option_frame_step = settings.get(
         constants.FRAME_STEP,
         constants.EXPORT_OPTIONS[constants.FRAME_STEP])
@@ -470,7 +478,9 @@ def set_settings(properties):
         constants.HIERARCHY: properties.option_hierarchy,
 
         constants.MORPH_TARGETS: properties.option_animation_morph,
+        constants.BLEND_SHAPES: properties.option_blend_shape,
         constants.ANIMATION: properties.option_animation_skeletal,
+        constants.KEYFRAMES: properties.option_keyframes,
         constants.FRAME_STEP: properties.option_frame_step,
         constants.FRAME_INDEX_AS_TIME: properties.option_frame_index_as_time,
         constants.INFLUENCES_PER_VERTEX: properties.option_influences
@@ -684,12 +694,22 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         description="Export animation (morphs)",
         default=constants.EXPORT_OPTIONS[constants.MORPH_TARGETS])
 
+    option_blend_shape = BoolProperty(
+        name="Blend Shape animation",
+        description="Export Blend Shapes",
+        default=constants.EXPORT_OPTIONS[constants.BLEND_SHAPES])
+
     option_animation_skeletal = EnumProperty(
         name="",
         description="Export animation (skeletal)",
         items=animation_options(),
         default=constants.OFF)
 
+    option_keyframes = BoolProperty(
+        name="Keyframe animation",
+        description="Export animation (keyframes)",
+        default=constants.EXPORT_OPTIONS[constants.KEYFRAMES])
+
     option_frame_index_as_time = BoolProperty(
         name="Frame index as time",
         description="Use (original) frame index as frame time",
@@ -804,6 +824,7 @@ class ExportThree(bpy.types.Operator, ExportHelper):
 
         row = layout.row()
         row.prop(self.properties, 'option_index_type')
+
         ## }
 
         layout.separator()
@@ -831,12 +852,21 @@ class ExportThree(bpy.types.Operator, ExportHelper):
         row = layout.row()
         row.prop(self.properties, 'option_animation_morph')
 
+        row = layout.row()
+        row.prop(self.properties, 'option_blend_shape')
+
         row = layout.row()
         row.label(text="Skeletal animations:")
 
         row = layout.row()
         row.prop(self.properties, 'option_animation_skeletal')
 
+        row = layout.row()
+        row.label(text="Keyframe animations:")
+
+        row = layout.row()
+        row.prop(self.properties, 'option_keyframes')
+
         layout.row()
         row = layout.row()
         row.prop(self.properties, 'option_influences')

+ 9 - 2
utils/exporters/blender/addons/io_three/constants.py

@@ -51,7 +51,6 @@ NUMERIC = {
     'LinearMipMapNearestFilter': 1007,
     'LinearMipMapLinearFilter': 1008
 }
-
 JSON = 'json'
 EXTENSION = '.%s' % JSON
 INDENT = 'indent'
@@ -79,7 +78,11 @@ MAPS = 'maps'
 FRAME_STEP = 'frameStep'
 FRAME_INDEX_AS_TIME = 'frameIndexAsTime'
 ANIMATION = 'animations'
+CLIPS="clips"
+KEYFRAMES = 'tracks'
 MORPH_TARGETS = 'morphTargets'
+MORPH_TARGETS_ANIM = 'morphTargetsAnimation'
+BLEND_SHAPES = 'blendShapes'
 POSE = 'pose'
 REST = 'rest'
 SKIN_INDICES = 'skinIndices'
@@ -141,9 +144,11 @@ EXPORT_OPTIONS = {
     COMPRESSION: None,
     MAPS: False,
     ANIMATION: OFF,
+    KEYFRAMES: False,
     BONES: False,
     SKINNING: False,
     MORPH_TARGETS: False,
+    BLEND_SHAPES: False,
     CAMERAS: False,
     LIGHTS: False,
     HIERARCHY: False,
@@ -160,7 +165,7 @@ EXPORT_OPTIONS = {
 }
 
 
-FORMAT_VERSION = 4.3
+FORMAT_VERSION = 4.4
 VERSION = 'version'
 THREE = 'io_three'
 GENERATOR = 'generator'
@@ -219,6 +224,8 @@ NORMAL = 'normal'
 ITEM_SIZE = 'itemSize'
 ARRAY = 'array'
 
+FLOAT_32 = 'Float32Array'
+
 VISIBLE = 'visible'
 CAST_SHADOW = 'castShadow'
 RECEIVE_SHADOW = 'receiveShadow'

+ 3 - 2
utils/exporters/blender/addons/io_three/exporter/api/material.py

@@ -19,12 +19,13 @@ def _material(func):
 
         """
 
+        material = None
         if isinstance(name, types.Material):
             material = name
-        else:
+        elif name:
             material = data.materials[name]
 
-        return func(material, *args, **kwargs)
+        return func(material, *args, **kwargs) if material else None
 
     return inner
 

+ 63 - 1
utils/exporters/blender/addons/io_three/exporter/api/mesh.py

@@ -68,7 +68,6 @@ def skeletal_animation(mesh, options):
 
     return animations
 
-
 @_mesh
 def bones(mesh, options):
     """
@@ -431,6 +430,69 @@ def morph_targets(mesh, options):
 
     return manifest
 
+@_mesh
+def blend_shapes(mesh, options):
+    """
+
+    :param mesh:
+    :param options:
+
+    """
+    logger.debug("mesh.blend_shapes(%s, %s)", mesh, options)
+    manifest = []
+    if mesh.shape_keys:
+        logger.info("mesh.blend_shapes -- there's shape keys")
+        key_blocks = mesh.shape_keys.key_blocks
+        for key in key_blocks.keys()[1:]:     # skip "Basis"
+            logger.info("mesh.blend_shapes -- key %s", key)
+            morph = []
+            for d in key_blocks[key].data:
+                co = d.co
+                morph.append([co.x, co.y, co.z])
+            manifest.append({
+                constants.NAME: key,
+                constants.VERTICES: morph
+            })
+    else:
+        logger.debug("No valid blend_shapes detected")
+    return manifest
+
+@_mesh
+def animated_blend_shapes(mesh, name, options):
+    """
+
+    :param mesh:
+    :param options:
+
+    """
+    logger.debug("mesh.animated_blend_shapes(%s, %s)", mesh, options)
+    tracks = []
+    shp = mesh.shape_keys
+    animCurves = shp.animation_data
+    if animCurves:
+        animCurves = animCurves.action.fcurves
+
+    for key in shp.key_blocks.keys()[1:]:    # skip "Basis"
+        key_name = name+".morphTargetInfluences["+key+"]"
+        found_animation = False
+        data_path = 'key_blocks["'+key+'"].value'
+        values = []
+        if animCurves:
+            for fcurve in animCurves:
+                if fcurve.data_path == data_path:
+                    for xx in fcurve.keyframe_points:
+                        values.append({ "time": xx.co.x, "value": xx.co.y })
+                    found_animation = True
+                    break # no need to continue
+
+        if found_animation:
+            tracks.append({
+                constants.NAME: key_name,
+                constants.TYPE: "number",
+                constants.KEYS: values
+            });
+
+    return tracks
 
 @_mesh
 def materials(mesh, options):

+ 147 - 5
utils/exporters/blender/addons/io_three/exporter/api/object.py

@@ -91,8 +91,8 @@ def cast_shadow(obj):
         if obj.data.type in (SPOT, SUN):
             ret = obj.data.shadow_method != NO_SHADOW
         else:
-            logger.info("%s is a lamp but this lamp type does not "
-                        "have supported shadows in ThreeJS", obj.name)
+            logger.info('%s is a lamp but this lamp type does not '\
+                'have supported shadows in ThreeJS', obj.name)
             ret = None
         return ret
     elif obj.type == MESH:
@@ -130,6 +130,106 @@ def material(obj):
     except IndexError:
         pass
 
+def extract_time(fcurves, start_index):
+    time = []
+    for xx in fcurves[start_index].keyframe_points:
+        time.append(xx.co.x)
+    return time
+
+def merge_sorted_lists(l1, l2):
+  sorted_list = []
+  l1 = l1[:]
+  l2 = l2[:]
+  while (l1 and l2):
+    h1 = l1[0]
+    h2 = l2[0]
+    if h1 == h2:
+      sorted_list.append(h1)
+      l1.pop(0)
+      l2.pop(0)
+    elif h1 < h2:
+      l1.pop(0)
+      sorted_list.append(h1)
+    else:
+      l2.pop(0)
+      sorted_list.append(h2)
+  # Add the remaining of the lists
+  sorted_list.extend(l1 if l1 else l2)
+  return sorted_list
+
+def appendVec3(track, time, vec3):
+    track.append({ "time": time, "value": [ vec3.x, vec3.y, vec3.z ] })
+
+def appendQuat(track, time, quat):
+    track.append({ "time": time, "value": [ quat.x, quat.y, quat.z, quat.w ] })
+
+# trackable transform fields ( <output field>, <nb fcurve> )
+TRACKABLE_FIELDS = {
+    "location": ( ".position", 3, "vector3" ),
+    "scale": ( ".scale", 3, "vector3" ),
+    "rotation_euler": ( ".rotation", 3, "vector3" ),
+    "rotation_quaternion": ( ".quaternion", 4, "quaternion" )
+}
+EXPORTED_TRACKABLE_FIELDS = [ "location", "scale", "rotation_quaternion" ]
+
+@_object
+def animated_xform(obj, options):
+    fcurves = obj.animation_data
+    if not fcurves:
+        return []
+    fcurves = fcurves.action.fcurves
+
+    objName = obj.name
+
+    tracks = []
+    i = 0
+    nb_curves = len(fcurves)
+
+    # extract unique frames
+    times = None
+    while i < nb_curves:
+        field_info = TRACKABLE_FIELDS.get(fcurves[i].data_path)
+        if field_info:
+            newTimes = extract_time(fcurves, i)
+            times = merge_sorted_lists(times, newTimes) if times else newTimes  # merge list
+            i += field_info[1]
+        else:
+            i += 1
+
+    # init tracks
+    track_loc = []
+    for fld in EXPORTED_TRACKABLE_FIELDS:
+        field_info = TRACKABLE_FIELDS[fld]
+        track = []
+        track_loc.append(track)
+        tracks.append({
+            constants.NAME: objName+field_info[0],
+            constants.TYPE: field_info[2],
+            constants.KEYS: track
+        })
+
+    # track arrays
+    track_sca = track_loc[1]
+    track_qua = track_loc[2]
+    track_loc = track_loc[0]
+    use_inverted = options.get(constants.HIERARCHY, False) and obj.parent
+
+    # for each frame
+    inverted_fallback = mathutils.Matrix() if use_inverted else None
+    convert_matrix = AXIS_CONVERSION    # matrix to convert the exported matrix
+    original_frame = context.scene.frame_current
+    for time in times:
+        context.scene.frame_set(time, 0.0)
+        if use_inverted:  # need to use the inverted, parent matrix might have chance
+            convert_matrix = obj.parent.matrix_world.inverted(inverted_fallback)
+        wm = convert_matrix * obj.matrix_world
+        appendVec3(track_loc, time, wm.to_translation())
+        appendVec3(track_sca, time, wm.to_scale()      )
+        appendQuat(track_qua, time, wm.to_quaternion() )
+    context.scene.frame_set(original_frame, 0.0)  # restore to original frame
+
+    # TODO: remove duplicated key frames
+    return tracks
 
 @_object
 def mesh(obj, options):
@@ -215,7 +315,6 @@ def nodes(valid_types, options):
         if _valid_node(obj, valid_types, options):
             yield obj.name
 
-
 @_object
 def position(obj, options):
     """
@@ -243,10 +342,8 @@ def receive_shadow(obj):
         else:
             return False
 
-
 AXIS_CONVERSION = axis_conversion(to_forward='Z', to_up='Y').to_4x4()
 
-
 @_object
 def matrix(obj, options):
     """
@@ -344,6 +441,8 @@ def extract_mesh(obj, options, recalculate=False):
     opt_buffer = opt_buffer == constants.BUFFER_GEOMETRY
     prop_buffer = mesh_node.THREE_geometry_type == constants.BUFFER_GEOMETRY
 
+    bpy.context.scene.objects.active = obj
+
     # if doing buffer geometry it is imperative to triangulate the mesh
     if opt_buffer or prop_buffer:
         original_mesh = obj.data
@@ -352,6 +451,8 @@ def extract_mesh(obj, options, recalculate=False):
                      original_mesh.name,
                      mesh_node.name)
 
+        hidden_state = obj.hide
+        obj.hide = False
         bpy.ops.object.mode_set(mode='OBJECT')
         obj.select = True
         bpy.context.scene.objects.active = obj
@@ -361,6 +462,7 @@ def extract_mesh(obj, options, recalculate=False):
                                       modifier='Triangulate')
         obj.data = original_mesh
         obj.select = False
+        obj.hide = hidden_state
 
     # recalculate the normals to face outwards, this is usually
     # best after applying a modifiers, especialy for something
@@ -382,6 +484,43 @@ def extract_mesh(obj, options, recalculate=False):
         xrot = mathutils.Matrix.Rotation(-math.pi/2, 4, 'X')
         mesh_node.transform(xrot * obj.matrix_world)
 
+    # blend shapes
+    if options.get(constants.BLEND_SHAPES) and not options.get(constants.MORPH_TARGETS):
+        original_mesh = obj.data
+        if original_mesh.shape_keys:
+            logger.info('Using blend shapes')
+            obj.data = mesh_node  # swap to be able to add the shape keys
+            shp = original_mesh.shape_keys
+
+            animCurves = shp.animation_data
+            if animCurves:
+                animCurves = animCurves.action.fcurves
+
+            src_kbs = shp.key_blocks
+            for key in src_kbs.keys():
+                logger.info("-- Parsing key %s", key)
+                obj.shape_key_add(name=key, from_mix=False)
+                src_kb = src_kbs[key].data
+                if key == 'Basis':
+                    dst_kb = mesh_node.vertices
+                else:
+                    dst_kb = mesh_node.shape_keys.key_blocks[key].data
+                for idx in range(len(src_kb)):
+                    dst_kb[idx].co = src_kb[idx].co
+
+                if animCurves:
+                    data_path = 'key_blocks["'+key+'"].value'
+                    for fcurve in animCurves:
+                        if fcurve.data_path == data_path:
+                            dst_kb = mesh_node.shape_keys.key_blocks[key]
+                            for xx in fcurve.keyframe_points:
+                                dst_kb.value = xx.co.y
+                                dst_kb.keyframe_insert("value",frame=xx.co.x)
+                            pass
+                            break  # no need to continue to loop
+                    pass
+            obj.data = original_mesh
+
     # now generate a unique name
     index = 0
     while True:
@@ -553,3 +692,6 @@ def _valid_node(obj, valid_types, options):
 
     # if we get this far assume that the mesh is valid
     return True
+
+
+

+ 15 - 5
utils/exporters/blender/addons/io_three/exporter/geometry.py

@@ -45,7 +45,7 @@ class Geometry(base_classes.BaseNode):
             ext = constants.PACK
 
         key = ''
-        for key in (constants.MORPH_TARGETS, constants.ANIMATION):
+        for key in (constants.MORPH_TARGETS, constants.ANIMATION, constants.CLIPS):
             if key in self.keys():
                 break
         else:
@@ -152,7 +152,7 @@ class Geometry(base_classes.BaseNode):
             texture_registration = self.register_textures()
             if texture_registration:
                 logger.info("%s has registered textures", self.node)
-                dirname = os.path.dirname(self.scene.filepath)
+                dirname = os.path.dirname(os.path.abspath(self.scene.filepath))
                 full_path = os.path.join(dirname, texture_folder)
                 io.copy_registered_textures(
                     full_path, texture_registration)
@@ -206,7 +206,7 @@ class Geometry(base_classes.BaseNode):
         """
         logger.debug("Geometry().write_animation(%s)", filepath)
 
-        for key in (constants.MORPH_TARGETS, constants.ANIMATION):
+        for key in (constants.MORPH_TARGETS, constants.ANIMATION, constants.CLIPS):
             try:
                 data = self[key]
                 break
@@ -245,7 +245,7 @@ class Geometry(base_classes.BaseNode):
                       constants.INDEX]
 
         data = {}
-        anim_components = [constants.MORPH_TARGETS, constants.ANIMATION]
+        anim_components = [constants.MORPH_TARGETS, constants.ANIMATION, constants.MORPH_TARGETS_ANIM, constants.CLIPS]
         if self.options.get(constants.EMBED_ANIMATION):
             components.extend(anim_components)
         else:
@@ -560,7 +560,17 @@ class Geometry(base_classes.BaseNode):
             self[constants.SKIN_WEIGHTS] = api.mesh.skin_weights(
                 self.node, bone_map, influences) or []
 
-        if self.options.get(constants.MORPH_TARGETS):
+        if self.options.get(constants.BLEND_SHAPES):
+            logger.info("Parsing %s", constants.BLEND_SHAPES)
+            mt = api.mesh.blend_shapes(self.node, self.options) or []
+            self[constants.MORPH_TARGETS] = mt
+            if len(mt) > 0 and self._scene:  # there's blend shapes, let check for animation
+                #self[constants.CLIPS] = api.mesh.animated_blend_shapes(self.node, self.options) or []
+                tracks = api.mesh.animated_blend_shapes(self.node, self[constants.NAME], self.options) or []
+                merge = self._scene[constants.ANIMATION][0][constants.KEYFRAMES]
+                for track in tracks:
+                    merge.append(track)
+        elif self.options.get(constants.MORPH_TARGETS):
             logger.info("Parsing %s", constants.MORPH_TARGETS)
             self[constants.MORPH_TARGETS] = api.mesh.morph_targets(
                 self.node, self.options) or []

+ 14 - 2
utils/exporters/blender/addons/io_three/exporter/object.py

@@ -46,8 +46,10 @@ class Object(base_classes.BaseNode):
         self[constants.COLOR] = api.light.color(self.data)
         self[constants.INTENSITY] = api.light.intensity(self.data)
 
-        if self[constants.TYPE] != constants.DIRECTIONAL_LIGHT:
-            self[constants.DISTANCE] = api.light.distance(self.data)
+        # Commented out because Blender's distance is not a cutoff value.
+        #if self[constants.TYPE] != constants.DIRECTIONAL_LIGHT:
+        #    self[constants.DISTANCE] = api.light.distance(self.data)
+        self[constants.DISTANCE] = 0;
 
         if self[constants.TYPE] == constants.SPOT_LIGHT:
             self[constants.ANGLE] = api.light.angle(self.data)
@@ -119,6 +121,16 @@ class Object(base_classes.BaseNode):
         elif self[constants.TYPE] in lights:
             self._init_light()
 
+        no_anim = (None, False, constants.OFF)
+        if self.options.get(constants.KEYFRAMES) not in no_anim:
+            logger.info("Export Transform Animation for %s", self.node)
+            if self._scene:
+                # only when exporting scene
+                tracks = api.object.animated_xform(self.node, self.options)
+                merge = self._scene[constants.ANIMATION][0][constants.KEYFRAMES]
+                for track in tracks:
+                    merge.append(track)
+
         if self.options.get(constants.HIERARCHY, False):
             for child in api.object.children(self.node, self.scene.valid_types):
                 if not self.get(constants.CHILDREN):

+ 12 - 2
utils/exporters/blender/addons/io_three/exporter/scene.py

@@ -10,7 +10,7 @@ from . import (
     io,
     api
 )
-
+from bpy import context
 
 class Scene(base_classes.BaseScene):
     """Class that handles the contruction of a Three scene"""
@@ -22,13 +22,23 @@ class Scene(base_classes.BaseScene):
             constants.GEOMETRIES: [],
             constants.MATERIALS: [],
             constants.IMAGES: [],
-            constants.TEXTURES: []
+            constants.TEXTURES: [],
+            constants.ANIMATION: []
         }
         base_classes.BaseScene.__init__(self, filepath, options or {})
 
         source_file = api.scene_name()
         if source_file:
             self[constants.METADATA][constants.SOURCE_FILE] = source_file
+        self.__init_animation()
+
+    def __init_animation(self):
+        self[constants.ANIMATION].append({
+            constants.NAME: "default",
+            constants.FPS : context.scene.render.fps,
+            constants.KEYFRAMES: []
+        });
+        pass
 
     @property
     def valid_types(self):

+ 2 - 3
utils/npm/build.js

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

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