2
0
Эх сурвалжийг харах

Merge branch 'dev' into feature/obj+mtl

Daniel Hritzkiv 9 жил өмнө
parent
commit
ebca8f9fe9
100 өөрчлөгдсөн 6646 нэмэгдсэн , 909 устгасан
  1. 5 1
      build/three.js
  2. 19 19
      build/three.min.js
  3. 21 46
      docs/api/materials/ShaderMaterial.html
  4. 1 1
      editor/index.html
  5. 1 1
      editor/js/Viewport.js
  6. 7 5
      examples/canvas_morphtargets_horse.html
  7. 139 0
      examples/js/AnimationClipCreator.js
  8. 27 146
      examples/js/BlendCharacter.js
  9. 15 6
      examples/js/BlendCharacterGui.js
  10. 52 33
      examples/js/MD2Character.js
  11. 1 3
      examples/js/MarchingCubes.js
  12. 70 0
      examples/js/MorphAnimMesh.js
  13. 0 0
      examples/js/MorphAnimation.js
  14. 5 5
      examples/js/UCSCharacter.js
  15. 1 1
      examples/js/controls/OrthographicTrackballControls.js
  16. 54 13
      examples/js/controls/TransformControls.js
  17. 0 0
      examples/js/libs/jszip.min.js
  18. 6 1
      examples/js/loaders/MD2Loader.js
  19. 1 7
      examples/js/loaders/MTLLoader.js
  20. 0 0
      examples/js/loaders/collada/Animation.js
  21. 0 0
      examples/js/loaders/collada/AnimationHandler.js
  22. 0 0
      examples/js/loaders/collada/KeyFrameAnimation.js
  23. 3 0
      examples/misc_animation_keys.html
  24. 51 25
      examples/misc_controls_transform.html
  25. 121 0
      examples/models/json/blend-animation.json
  26. 2696 0
      examples/models/json/scene-animation.json
  27. 196 0
      examples/webgl_animation_blend.html
  28. 194 0
      examples/webgl_animation_scene.html
  29. 14 9
      examples/webgl_animation_skinning_blending.html
  30. 13 54
      examples/webgl_animation_skinning_morph.html
  31. 14 14
      examples/webgl_lights_hemisphere.html
  32. 3 0
      examples/webgl_loader_collada.html
  33. 4 0
      examples/webgl_loader_collada_keyframe.html
  34. 3 0
      examples/webgl_loader_collada_skinning.html
  35. 21 20
      examples/webgl_loader_json_blender.html
  36. 9 5
      examples/webgl_loader_md2.html
  37. 14 15
      examples/webgl_loader_scene.html
  38. 3 0
      examples/webgl_loader_sea3d.html
  39. 3 0
      examples/webgl_loader_sea3d_skinning.html
  40. 20 17
      examples/webgl_morphnormals.html
  41. 8 6
      examples/webgl_morphtargets_horse.html
  42. 1 1
      examples/webgl_morphtargets_human.html
  43. 20 11
      examples/webgl_shading_physical.html
  44. 24 22
      examples/webgl_shadowmap.html
  45. 16 15
      examples/webgl_shadowmap_performance.html
  46. 5 4
      examples/webgl_skinning_simple.html
  47. 19 17
      examples/webgl_terrain_dynamic.html
  48. 6 1
      src/Three.js
  49. 166 0
      src/animation/AnimationAction.js
  50. 312 0
      src/animation/AnimationClip.js
  51. 241 0
      src/animation/AnimationMixer.js
  52. 116 0
      src/animation/AnimationUtils.js
  53. 274 0
      src/animation/KeyframeTrack.js
  54. 393 0
      src/animation/PropertyBinding.js
  55. 64 0
      src/animation/tracks/BooleanKeyframeTrack.js
  56. 74 0
      src/animation/tracks/ColorKeyframeTrack.js
  57. 64 0
      src/animation/tracks/NumberKeyframeTrack.js
  58. 86 0
      src/animation/tracks/QuaternionKeyframeTrack.js
  59. 64 0
      src/animation/tracks/StringKeyframeTrack.js
  60. 77 0
      src/animation/tracks/VectorKeyframeTrack.js
  61. 1 1
      src/core/Geometry.js
  62. 1 1
      src/core/Raycaster.js
  63. 1 1
      src/extras/geometries/LatheGeometry.js
  64. 1 1
      src/extras/geometries/WireframeGeometry.js
  65. 1 1
      src/extras/helpers/ArrowHelper.js
  66. 2 1
      src/extras/objects/ImmediateRenderObject.js
  67. 51 0
      src/loaders/AnimationLoader.js
  68. 38 6
      src/loaders/JSONLoader.js
  69. 23 0
      src/loaders/ObjectLoader.js
  70. 1 1
      src/math/Box2.js
  71. 1 1
      src/math/Box3.js
  72. 1 1
      src/math/Euler.js
  73. 1 1
      src/math/Frustum.js
  74. 1 1
      src/math/Line3.js
  75. 1 1
      src/math/Matrix3.js
  76. 1 1
      src/math/Matrix4.js
  77. 1 1
      src/math/Plane.js
  78. 1 1
      src/math/Quaternion.js
  79. 1 1
      src/math/Ray.js
  80. 1 1
      src/math/Sphere.js
  81. 1 1
      src/math/Triangle.js
  82. 0 212
      src/objects/MorphAnimMesh.js
  83. 20 75
      src/renderers/WebGLRenderer.js
  84. 18 0
      src/renderers/shaders/ShaderChunk/hemilight_fragment.glsl
  85. 0 54
      src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl
  86. 28 0
      src/renderers/shaders/ShaderChunk/normal_phong_fragment.glsl
  87. 2 0
      src/renderers/shaders/ShaderLib.js
  88. 1 1
      src/renderers/webgl/WebGLPrograms.js
  89. 1 1
      src/renderers/webgl/WebGLShadowMap.js
  90. 16 3
      test/unit/cameras/Camera.js
  91. 41 0
      test/unit/cameras/OrthographicCamera.js
  92. 50 0
      test/unit/cameras/PerspectiveCamera.js
  93. 117 0
      test/unit/core/BufferAttribute.js
  94. 319 0
      test/unit/core/BufferGeometry.js
  95. 6 2
      test/unit/unittests_three.html
  96. 13 1
      utils/build/includes/common.json
  97. 0 4
      utils/build/includes/extras.json
  98. 34 4
      utils/exporters/blender/addons/io_three/__init__.py
  99. 9 2
      utils/exporters/blender/addons/io_three/constants.py
  100. 3 2
      utils/exporters/blender/addons/io_three/exporter/__init__.py

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 5 - 1
build/three.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 19 - 19
build/three.min.js


+ 21 - 46
docs/api/materials/ShaderMaterial.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="page.js"></script>
@@ -16,9 +16,13 @@
 		<ul>
 			<li>implement an effect not included with any of the built-in [page:Material materials]</li>
 			<li>combine many objects into a single [page:Geometry] or [page:BufferGeometry] in order to improve performance</li>
-			<li>associate custom data with individual vertices ("custom attributes")</li>
 		</ul>
-		Note that a ShaderMaterial will only be rendered properly by [page:WebGLRenderer], since the GLSL code in the vertexShader and fragmentShader properties must be compiled and run on the GPU using WebGL.
+		There are the following notes to bear in mind when using a *ShaderMaterial*:
+
+		<ul>
+			<li>A *ShaderMaterial* will only be rendered properly by [page:WebGLRenderer], since the GLSL code in the *vertexShader* and *fragmentShader* properties must be compiled and run on the GPU using WebGL.</li>
+			<li>As of THREE r72, directly assigning attributes in a *ShaderMaterial* is no longer supported. A [page:BufferGeometry] instance (instead of a [page:Geometry] instance) must be used instead, using [page:BufferAttribute] instances to define custom attributes.</li>
+		</ul>
 		</div>
 
 		<h3>Example</h3>
@@ -67,66 +71,51 @@
 		</p>
 
 		<h3>Custom attributes and uniforms</h3>
-		Custom attributes and uniforms must be declared both in your GLSL shader code (within *vertexShader* and/or *fragmentShader*), <emph>and</emph> in the *attributes* and *uniforms* properties of your ShaderMaterial. The declaration in the material is necessary to ensure [page:WebGLRenderer] passes your attribute/uniform data in a buffer to the GPU when the shader is run. Note that *varying*s only need to be declared within the shader code (not within the material).
+		<p>Both custom attributes and uniforms must be declared in your GLSL shader code (within *vertexShader* and/or *fragmentShader*). Custom uniforms must be defined in <emph>both</emph> the *uniforms* property of your *ShaderMaterial*, whereas any custom attributes must be defined via [page:BufferAttribute] instances. Note that *varying*s only need to be declared within the shader code (not within the material).
 		</p>
-		<p>
-		To declare a custom attribute, use the *attributes* property of the material:
-		<code>
-		attributes: {
-			vertexOpacity: { type: 'f', value: [] }
-		}
-		</code>
-		Each attribute must have a *type* property and a *value* property.
+
+		<p>To declare a custom attribute, please reference the [page:BufferGeometry] page for an overview, and the [page:BufferAttribute] page for a detailed look at the *BufferAttribute* API.</p>
+		<p>When creating your attributes, each typed array that you create to hold your attribute's data must be a multiple of your data type's size. For example, if your attribute is a [page:Vector3 THREE.Vector3] type, and you have 3000 vertices in your [page:BufferGeometry], your typed array value must be created with a length of 3000 * 3, or 9000 (one value per-component). A table of each data type's size is shown below for reference:</p>
+
 		<table>
-			<caption><a id="attribute-types">Attribute types</a></caption>
+			<caption><a id="attribute-sizes">Attribute sizes</a></caption>
 			<thead>
 				<tr>
-					<th>Attribute *type* string</th>
 					<th>GLSL type</th>
 					<th>JavaScript type</th>
+					<th>Size</th>
 				</tr>
 			</thead>
 			<tbody>
 				<tr>
-					<td><code>'f'</code></td>
 					<td>float</td>
 					<td>[page:Number]</td>
+					<td>1</td>
 				</tr>
 				<tr>
-					<td><code>'v2'</code></td>
 					<td>vec2</td>
 					<td>[page:Vector2 THREE.Vector2]</td>
+					<td>2</td>
 				</tr>
 				<tr>
-					<td><code>'v3'</code></td>
 					<td>vec3</td>
 					<td>[page:Vector3 THREE.Vector3]</td>
+					<td>3</td>
 				</tr>
 				<tr>
-					<td><code>'c'</code></td>
 					<td>vec3</td>
 					<td>[page:Color THREE.Color]</td>
+					<td>3</td>
 				</tr>
 				<tr>
-					<td><code>'v4'</code></td>
 					<td>vec4</td>
 					<td>[page:Vector4 THREE.Vector4]</td>
+					<td>4</td>
 				</tr>
 			</tbody>
 		</table>
-		The way attribute data is stored depends on whether you're using [page:BufferGeometry] or [page:Geometry]:
-		<ul>
-			<li>When using [page:Geometry], attribute data is stored directly on the <emph>material</emph>, using the attribute's *value* array; each element of *value* should correspond to one vertex. To update an attribute, set the *needsUpdate* flag to true on the material attribute:
-			<code>
-			material.attributes.vertexOpacity.needsUpdate = true;
-			</code>
-			</li>
-			<li>When using [page:BufferGeometry], attribute data is stored within a [page:BufferAttribute] on the geometry itself, and the *value* within the material is ignored. To update an attribute, set the *needsUpdate* flag to true on the [page:BufferAttribute] of the geometry:
-			<code>
-			geometry.attributes.vertexOpacity.needsUpdate = true;
-			</code>
-			See [page:BufferGeometry] for details.</li>
-		</ul>
+
+		Note that attribute buffers are <emph>not</emph> refreshed automatically when their values change. To update custom attributes, set the *needsUpdate* flag to true on the [page:BufferAttribute] of the geometry (see [page:BufferGeometry] for further details).
 		</p>
 
 		<p>
@@ -232,20 +221,6 @@
 		where *type* is a <a href="#uniform-types">uniform type string</a>, and *value* is the value of the uniform. Names must match the name of the uniform, as defined in the GLSL code. Note that uniforms are refreshed on every frame, so updating the value of the uniform will immediately update the value available to the GLSL code.
 		</div>
 
-		<h3>[property:Object attributes]</h3>
-		<div>
-		<p>
-		Object specifying the custom attributes to be passed to the shader code; keys are attribute names, values are definitions of the form
-		<code>
-		{ type: 'f', value: [1.0, 0.5, 2.0, ...] }
-		</code>
-		where *type* is an <a href="#attribute-types">attribute type string</a>, and *value* is an array containing an attribute value for each vertex in the geometry (or *undefined* if using [page:BufferGeometry]). Names must match the name of the attribute, as defined in the GLSL code.
-		</p>
-		<p>
-		Note that attribute buffers are <emph>not</emph> refreshed automatically when their values change; if using [page:Geometry], set <code>needsUpdate = true</code> on the attribute definition. If using [page:BufferGeometry], set <code>needsUpdate = true</code> on the [page:BufferAttribute].
-		</p>
-		</div>
-
 		<h3>[property:Object defines]</h3>
 		<div>
 		Defines custom constants using *#define* directives within the GLSL code for both the vertex shader and the fragment shader; each key/value pair yields another directive:

+ 1 - 1
editor/index.html

@@ -67,7 +67,7 @@
 		<script src="js/libs/ternjs/doc_comment.js"></script>
 		<script src="js/libs/tern-threejs/threejs.js"></script>
 
-		<script src="js/libs/jszip.min.js"></script>
+		<script src="../examples/js/libs/jszip.min.js"></script>
 		<script src="js/libs/sortable.min.js"></script>
 		<script src="js/libs/signals.min.js"></script>
 		<script src="js/libs/ui.js"></script>

+ 1 - 1
editor/js/Viewport.js

@@ -283,7 +283,7 @@ var Viewport = function ( editor ) {
 
 	signals.snapChanged.add( function ( dist ) {
 
-		transformControls.setSnap( dist );
+		transformControls.setTranslationSnap( dist );
 
 	} );
 

+ 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;
 
 	}

+ 1 - 3
examples/js/MarchingCubes.js

@@ -7,9 +7,7 @@
 
 THREE.MarchingCubes = function ( resolution, material, enableUvs, enableColors ) {
 
-	THREE.ImmediateRenderObject.call( this );
-
-	this.material = material;
+	THREE.ImmediateRenderObject.call( this, material );
 
 	this.enableUvs = enableUvs !== undefined ? enableUvs : false;
 	this.enableColors = enableColors !== undefined ? enableColors : false;

+ 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 );
 			

+ 1 - 1
examples/js/controls/OrthographicTrackballControls.js

@@ -245,7 +245,7 @@ THREE.OrthographicTrackballControls = function ( object, domElement ) {
 
 			if ( Math.abs( factor - 1.0 ) > EPS && factor > 0.0 ) {
 
-				_this.object.zoom *= factor;
+				_this.object.zoom /= factor;
 
 				if ( _this.staticMoving ) {
 

+ 54 - 13
examples/js/controls/TransformControls.js

@@ -617,7 +617,8 @@
 
 		this.object = undefined;
 		this.visible = false;
-		this.snap = null;
+		this.translationSnap = null;
+		this.rotationSnap = null;
 		this.space = "world";
 		this.size = 1;
 		this.axis = null;
@@ -750,9 +751,15 @@
 
 		};
 
-		this.setSnap = function ( snap ) {
+		this.setTranslationSnap = function ( translationSnap ) {
 
-			scope.snap = snap;
+			scope.translationSnap = translationSnap;
+
+		};
+
+		this.setRotationSnap = function ( rotationSnap ) {
+
+			scope.rotationSnap = rotationSnap;
 
 		};
 
@@ -930,11 +937,23 @@
 
 				}
 
-				if ( scope.snap !== null ) {
+				if ( scope.translationSnap !== null ) {
 
-					if ( scope.axis.search( "X" ) !== - 1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.snap ) * scope.snap;
-					if ( scope.axis.search( "Y" ) !== - 1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.snap ) * scope.snap;
-					if ( scope.axis.search( "Z" ) !== - 1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.snap ) * scope.snap;
+					if ( scope.space === "local" ) {
+
+						scope.object.position.applyMatrix4( tempMatrix.getInverse( worldRotationMatrix ) );
+
+					}
+
+					if ( scope.axis.search( "X" ) !== - 1 ) scope.object.position.x = Math.round( scope.object.position.x / scope.translationSnap ) * scope.translationSnap;
+					if ( scope.axis.search( "Y" ) !== - 1 ) scope.object.position.y = Math.round( scope.object.position.y / scope.translationSnap ) * scope.translationSnap;
+					if ( scope.axis.search( "Z" ) !== - 1 ) scope.object.position.z = Math.round( scope.object.position.z / scope.translationSnap ) * scope.translationSnap;
+
+					if ( scope.space === "local" ) {
+
+						scope.object.position.applyMatrix4( worldRotationMatrix );
+
+					}
 
 				}
 
@@ -1013,9 +1032,20 @@
 					offsetRotation.set( Math.atan2( tempVector.z, tempVector.y ), Math.atan2( tempVector.x, tempVector.z ), Math.atan2( tempVector.y, tempVector.x ) );
 
 					quaternionXYZ.setFromRotationMatrix( oldRotationMatrix );
-					quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
-					quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
-					quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+
+					if ( scope.rotationSnap !== null ) {
+
+						quaternionX.setFromAxisAngle( unitX, Math.round( ( rotation.x - offsetRotation.x ) / scope.rotationSnap ) * scope.rotationSnap );
+						quaternionY.setFromAxisAngle( unitY, Math.round( ( rotation.y - offsetRotation.y ) / scope.rotationSnap ) * scope.rotationSnap );
+						quaternionZ.setFromAxisAngle( unitZ, Math.round( ( rotation.z - offsetRotation.z ) / scope.rotationSnap ) * scope.rotationSnap );
+
+					} else {
+
+						quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
+						quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
+						quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+
+					}
 
 					if ( scope.axis === "X" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionX );
 					if ( scope.axis === "Y" ) quaternionXYZ.multiplyQuaternions( quaternionXYZ, quaternionY );
@@ -1030,9 +1060,20 @@
 
 					tempQuaternion.setFromRotationMatrix( tempMatrix.getInverse( parentRotationMatrix ) );
 
-					quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
-					quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
-					quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+					if ( scope.rotationSnap !== null ) {
+
+						quaternionX.setFromAxisAngle( unitX, Math.round( ( rotation.x - offsetRotation.x ) / scope.rotationSnap ) * scope.rotationSnap );
+						quaternionY.setFromAxisAngle( unitY, Math.round( ( rotation.y - offsetRotation.y ) / scope.rotationSnap ) * scope.rotationSnap );
+						quaternionZ.setFromAxisAngle( unitZ, Math.round( ( rotation.z - offsetRotation.z ) / scope.rotationSnap ) * scope.rotationSnap );
+
+					} else {
+
+						quaternionX.setFromAxisAngle( unitX, rotation.x - offsetRotation.x );
+						quaternionY.setFromAxisAngle( unitY, rotation.y - offsetRotation.y );
+						quaternionZ.setFromAxisAngle( unitZ, rotation.z - offsetRotation.z );
+
+					}
+
 					quaternionXYZ.setFromRotationMatrix( worldRotationMatrix );
 
 					if ( scope.axis === "X" ) tempQuaternion.multiplyQuaternions( tempQuaternion, quaternionX );

+ 0 - 0
editor/js/libs/jszip.min.js → examples/js/libs/jszip.min.js


+ 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;

+ 1 - 7
examples/js/loaders/MTLLoader.js

@@ -319,7 +319,7 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 					// Diffuse color (color under white light) using RGB values
 
-					params[ 'diffuse' ] = new THREE.Color().fromArray( value );
+					params[ 'color' ] = new THREE.Color().fromArray( value );
 
 					break;
 
@@ -390,12 +390,6 @@ THREE.MTLLoader.MaterialCreator.prototype = {
 
 		}
 
-		if ( params[ 'diffuse' ] ) {
-
-			params[ 'color' ] = params[ 'diffuse' ];
-
-		}
-
 		this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
 		return this.materials[ materialName ];
 

+ 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>
 

+ 51 - 25
examples/misc_controls_transform.html

@@ -26,7 +26,7 @@
 
 		<div id="info">
 		"W" translate | "E" rotate | "R" scale | "+" increase size | "-" decrease size<br />
-		Press "Q" to toggle world/local space
+		Press "Q" to toggle world/local space, keep "Ctrl" down to snap to grid
 		</div>
 
 		<script src="../build/three.min.js"></script>
@@ -79,30 +79,56 @@
 				window.addEventListener( 'resize', onWindowResize, false );
 
 				window.addEventListener( 'keydown', function ( event ) {
-		            //console.log(event.which);
-		            switch ( event.keyCode ) {
-		              case 81: // Q
-		                control.setSpace( control.space == "local" ? "world" : "local" );
-		                break;
-		              case 87: // W
-		                control.setMode( "translate" );
-		                break;
-		              case 69: // E
-		                control.setMode( "rotate" );
-		                break;
-		              case 82: // R
-		                control.setMode( "scale" );
-		                break;
-					case 187:
-					case 107: // +,=,num+
-						control.setSize( control.size + 0.1 );
-						break;
-					case 189:
-					case 109: // -,_,num-
-						control.setSize( Math.max(control.size - 0.1, 0.1 ) );
-						break;
-		            }
-        		});
+
+					switch ( event.keyCode ) {
+
+						case 81: // Q
+							control.setSpace( control.space === "local" ? "world" : "local" );
+							break;
+
+						case 17: // Ctrl
+							control.setTranslationSnap( 100 );
+							control.setRotationSnap( THREE.Math.degToRad( 15 ) );
+							break;
+
+						case 87: // W
+							control.setMode( "translate" );
+							break;
+
+						case 69: // E
+							control.setMode( "rotate" );
+							break;
+
+						case 82: // R
+							control.setMode( "scale" );
+							break;
+
+						case 187:
+						case 107: // +, =, num+
+							control.setSize( control.size + 0.1 );
+							break;
+
+						case 189:
+						case 109: // -, _, num-
+							control.setSize( Math.max( control.size - 0.1, 0.1 ) );
+							break;
+
+					}
+
+				});
+
+				window.addEventListener( 'keyup', function ( event ) {
+
+					switch ( event.keyCode ) {
+
+						case 17: // Ctrl
+							control.setTranslationSnap( null );
+							control.setRotationSnap( null );
+							break;
+
+					}
+
+				});
 
 			}
 

+ 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": []
+}

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 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;
 

+ 6 - 1
src/Three.js

@@ -2,7 +2,7 @@
  * @author mrdoob / http://mrdoob.com/
  */
 
-var THREE = { REVISION: '72' };
+var THREE = { REVISION: '73dev' };
 
 //
 
@@ -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/geometries/WireframeGeometry.js

@@ -81,7 +81,7 @@ THREE.WireframeGeometry = function ( geometry ) {
 
 			if ( drawcalls.length === 0 ) {
 
-				geometry.addDrawCall( 0, indices.length );
+				geometry.addGroup( 0, indices.length );
 
 			}
 

+ 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
  *

+ 2 - 1
src/extras/objects/ImmediateRenderObject.js

@@ -2,10 +2,11 @@
  * @author alteredq / http://alteredqualia.com/
  */
 
-THREE.ImmediateRenderObject = function () {
+THREE.ImmediateRenderObject = function ( material ) {
 
 	THREE.Object3D.call( this );
 
+	this.material = material;
 	this.render = function ( renderCallback ) {};
 
 };

+ 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;
-
-};

+ 20 - 75
src/renderers/WebGLRenderer.js

@@ -36,11 +36,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 	var transparentObjects = [];
 	var transparentObjectsLastIndex = -1;
 
-	var opaqueImmediateObjects = [];
-	var opaqueImmediateObjectsLastIndex = -1;
-	var transparentImmediateObjects = [];
-	var transparentImmediateObjectsLastIndex = -1;
-
 	var morphInfluences = new Float32Array( 8 );
 
 
@@ -803,7 +798,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		}
 
-		if ( group === undefined ) {
+		if ( group === null ) {
 
 			var count;
 
@@ -1101,9 +1096,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 		opaqueObjectsLastIndex = -1;
 		transparentObjectsLastIndex = -1;
 
-		opaqueImmediateObjectsLastIndex = -1;
-		transparentImmediateObjectsLastIndex = -1;
-
 		sprites.length = 0;
 		lensFlares.length = 0;
 
@@ -1112,9 +1104,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 		opaqueObjects.length = opaqueObjectsLastIndex + 1;
 		transparentObjects.length = transparentObjectsLastIndex + 1;
 
-		opaqueImmediateObjects.length = opaqueImmediateObjectsLastIndex + 1;
-		transparentImmediateObjects.length = transparentImmediateObjectsLastIndex + 1;
-
 		if ( _this.sortObjects === true ) {
 
 			opaqueObjects.sort( painterSortStable );
@@ -1150,22 +1139,16 @@ THREE.WebGLRenderer = function ( parameters ) {
 			renderObjects( opaqueObjects, camera, lights, fog, overrideMaterial );
 			renderObjects( transparentObjects, camera, lights, fog, overrideMaterial );
 
-			renderObjectsImmediate( opaqueImmediateObjects, camera, lights, fog, overrideMaterial );
-			renderObjectsImmediate( transparentImmediateObjects, camera, lights, fog, overrideMaterial );
-
 		} else {
 
 			// opaque pass (front-to-back order)
 
 			state.setBlending( THREE.NoBlending );
-
 			renderObjects( opaqueObjects, camera, lights, fog );
-			renderObjectsImmediate( opaqueImmediateObjects, camera, lights, fog );
 
 			// transparent pass (back-to-front order)
 
 			renderObjects( transparentObjects, camera, lights, fog );
-			renderObjectsImmediate( transparentImmediateObjects, camera, lights, fog );
 
 		}
 
@@ -1192,40 +1175,6 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 	};
 
-	function pushImmediateRenderItem( object ) {
-
-		var array, index;
-
-		// allocate the next position in the appropriate array
-
-		if ( object.material.transparent ) {
-
-			array = transparentImmediateObjects;
-			index = ++ transparentImmediateObjectsLastIndex;
-
-		} else {
-
-			array = opaqueImmediateObjects;
-			index = ++ opaqueImmediateObjectsLastIndex;
-
-		}
-
-		// recycle existing position or grow the array
-
-		if ( index < array.length ) {
-
-			array[ index ] = object;
-
-		} else {
-
-			// assert( index === array.length );
-			array.push( object );
-
-		}
-
-
-	}
-
 	function pushRenderItem( object, geometry, material, z, group ) {
 
 		var array, index;
@@ -1293,7 +1242,14 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 		} else if ( object instanceof THREE.ImmediateRenderObject ) {
 
-			pushImmediateRenderItem( object );
+			if ( _this.sortObjects === true ) {
+
+				_vector3.setFromMatrixPosition( object.matrixWorld );
+				_vector3.applyProjection( _projScreenMatrix );
+
+			}
+
+			pushRenderItem( object, null, object.material, _vector3.z, null );
 
 		} else if ( object instanceof THREE.Mesh || object instanceof THREE.Line || object instanceof THREE.Points ) {
 
@@ -1338,7 +1294,7 @@ THREE.WebGLRenderer = function ( parameters ) {
 
 					} else {
 
-						pushRenderItem( object, geometry, material, _vector3.z );
+						pushRenderItem( object, geometry, material, _vector3.z, null );
 
 					}
 
@@ -1372,36 +1328,25 @@ THREE.WebGLRenderer = function ( parameters ) {
 			object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
 			object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
 
-			_this.renderBufferDirect( camera, lights, fog, geometry, material, object, group );
-
-		}
-
-	}
+			if ( object instanceof THREE.ImmediateRenderObject ) {
 
-	function renderObjectsImmediate( renderList, camera, lights, fog, overrideMaterial ) {
+					setMaterial( material );
 
-		var material = overrideMaterial;
+					var program = setProgram( camera, lights, fog, material, object );
 
-		for ( var i = 0, l = renderList.length; i < l; i ++ ) {
+					_currentGeometryProgram = '';
 
-			var object = renderList[ i ];
+					object.render( function ( object ) {
 
-			object.modelViewMatrix.multiplyMatrices( camera.matrixWorldInverse, object.matrixWorld );
-			object.normalMatrix.getNormalMatrix( object.modelViewMatrix );
+						_this.renderBufferImmediate( object, program, material );
 
-			if ( overrideMaterial === undefined ) material = object.material;
+					} );
 
-			setMaterial( material );
-
-			var program = setProgram( camera, lights, fog, material, object );
-
-			_currentGeometryProgram = '';
-
-			object.render( function ( object ) {
+			} else {
 
-				_this.renderBufferImmediate( object, program, material );
+				_this.renderBufferDirect( camera, lights, fog, geometry, material, object, group );
 
-			} );
+			}
 
 		}
 

+ 18 - 0
src/renderers/shaders/ShaderChunk/hemilight_fragment.glsl

@@ -0,0 +1,18 @@
+#if MAX_HEMI_LIGHTS > 0
+
+	for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {
+
+		vec3 lightDir = hemisphereLightDirection[ i ];
+
+		float dotProduct = dot( normal, lightDir );
+
+		float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;
+
+		vec3 lightColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );
+
+		totalAmbientLight += lightColor;
+
+	}
+
+#endif
+

+ 0 - 54
src/renderers/shaders/ShaderChunk/lights_phong_fragment.glsl

@@ -1,31 +1,3 @@
-#ifndef FLAT_SHADED
-
-	vec3 normal = normalize( vNormal );
-
-	#ifdef DOUBLE_SIDED
-
-		normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );
-
-	#endif
-
-#else
-
-	vec3 fdx = dFdx( vViewPosition );
-	vec3 fdy = dFdy( vViewPosition );
-	vec3 normal = normalize( cross( fdx, fdy ) );
-
-#endif
-
-#ifdef USE_NORMALMAP
-
-	normal = perturbNormal2Arb( -vViewPosition, normal );
-
-#elif defined( USE_BUMPMAP )
-
-	normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );
-
-#endif
-
 vec3 viewDir = normalize( vViewPosition );
 
 vec3 totalDiffuseLight = vec3( 0.0 );
@@ -126,32 +98,6 @@ vec3 totalSpecularLight = vec3( 0.0 );
 
 #endif
 
-#if MAX_HEMI_LIGHTS > 0
-
-	for( int i = 0; i < MAX_HEMI_LIGHTS; i ++ ) {
-
-		vec3 lightDir = hemisphereLightDirection[ i ];
-
-		// diffuse
-
-		float dotProduct = dot( normal, lightDir );
-
-		float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;
-
-		vec3 lightColor = mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );
-
-		totalDiffuseLight += lightColor;
-
-		// specular (sky term only)
-
-		vec3 brdf = BRDF_BlinnPhong( specular, shininess, normal, lightDir, viewDir );
-
-		totalSpecularLight += brdf * specularStrength * lightColor * max( dotProduct, 0.0 );
-
-	}
-
-#endif
-
 #ifdef METAL
 
 	outgoingLight += diffuseColor.rgb * ( totalDiffuseLight + totalAmbientLight ) * specular + totalSpecularLight + totalEmissiveLight;

+ 28 - 0
src/renderers/shaders/ShaderChunk/normal_phong_fragment.glsl

@@ -0,0 +1,28 @@
+#ifndef FLAT_SHADED
+
+	vec3 normal = normalize( vNormal );
+
+	#ifdef DOUBLE_SIDED
+
+		normal = normal * ( -1.0 + 2.0 * float( gl_FrontFacing ) );
+
+	#endif
+
+#else
+
+	vec3 fdx = dFdx( vViewPosition );
+	vec3 fdy = dFdy( vViewPosition );
+	vec3 normal = normalize( cross( fdx, fdy ) );
+
+#endif
+
+#ifdef USE_NORMALMAP
+
+	normal = perturbNormal2Arb( -vViewPosition, normal );
+
+#elif defined( USE_BUMPMAP )
+
+	normal = perturbNormalArb( -vViewPosition, normal, dHdxy_fwd() );
+
+#endif
+

+ 2 - 0
src/renderers/shaders/ShaderLib.js

@@ -366,7 +366,9 @@ THREE.ShaderLib = {
 				THREE.ShaderChunk[ "alphamap_fragment" ],
 				THREE.ShaderChunk[ "alphatest_fragment" ],
 				THREE.ShaderChunk[ "specularmap_fragment" ],
+				THREE.ShaderChunk[ "normal_phong_fragment" ],
 				THREE.ShaderChunk[ "lightmap_fragment" ],
+				THREE.ShaderChunk[ "hemilight_fragment" ],
 				THREE.ShaderChunk[ "aomap_fragment" ],
 				THREE.ShaderChunk[ "emissivemap_fragment" ],
 

+ 1 - 1
src/renderers/webgl/WebGLPrograms.js

@@ -160,7 +160,7 @@ THREE.WebGLPrograms = function ( renderer, capabilities ) {
 			flatShading: material.shading === THREE.FlatShading,
 
 			sizeAttenuation: material.sizeAttenuation,
-			logarithmicDepthBuffer: renderer.logarithmicDepthBuffer,
+			logarithmicDepthBuffer: capabilities.logarithmicDepthBuffer,
 
 			skinning: material.skinning,
 			maxBones: maxBones,

+ 1 - 1
src/renderers/webgl/WebGLShadowMap.js

@@ -224,7 +224,7 @@ THREE.WebGLShadowMap = function ( _renderer, _lights, _objects ) {
 
 				} else {
 
-					_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, getDepthMaterial( object, material ), object );
+					_renderer.renderBufferDirect( shadowCamera, _lights, null, geometry, getDepthMaterial( object, material ), object, null );
 
 				}
 

+ 16 - 3
test/unit/cameras/Camera.js

@@ -5,8 +5,21 @@
 module( "Camera" );
 
 test( "lookAt", function() {
-	var obj = new THREE.Camera();
-	obj.lookAt(new THREE.Vector3(0, 1, -1));
+	var cam = new THREE.Camera();
+	cam.lookAt(new THREE.Vector3(0, 1, -1));
 
-	ok( obj.rotation.x * (180 / Math.PI) === 45 , "x is equal" );
+	ok( cam.rotation.x * (180 / Math.PI) === 45 , "x is equal" );
+});
+
+test( "clone", function() {
+	var cam = new THREE.Camera();
+
+	// fill the matrices with any nonsense values just to see if they get copied
+	cam.matrixWorldInverse.set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+	cam.projectionMatrix.set( 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 );
+
+	var clonedCam = cam.clone();
+
+	ok( cam.matrixWorldInverse.equals(clonedCam.matrixWorldInverse) , "matrixWorldInverse is equal" );
+	ok( cam.projectionMatrix.equals(clonedCam.projectionMatrix) , "projectionMatrix is equal" );
 });

+ 41 - 0
test/unit/cameras/OrthographicCamera.js

@@ -0,0 +1,41 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+
+module( "OrthographicCamera" );
+
+test( "updateProjectionMatrix", function() {
+	var left = -1, right = 1, top = 1, bottom = -1, near = 1, far = 3;
+	cam = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
+
+	// updateProjectionMatrix is called in contructor
+	var pMatrix = cam.projectionMatrix.elements;
+
+	// orthographic projection is given my the 4x4 Matrix
+	// 2/r-l		0			 0		-(l+r/r-l)
+	//   0		2/t-b		 0		-(t+b/t-b)
+	//   0			0		-2/f-n	-(f+n/f-n)
+	//   0			0			 0				1
+
+	ok( pMatrix[0] === 2 / ( right - left ), "m[0,0] === 2 / (r - l)" );
+	ok( pMatrix[5] === 2 / ( top - bottom ), "m[1,1] === 2 / (t - b)" );
+	ok( pMatrix[10] === -2 / ( far - near ), "m[2,2] === -2 / (f - n)" );
+	ok( pMatrix[12] === - ( ( right + left ) / ( right - left ) ), "m[3,0] === -(r+l/r-l)" );
+	ok( pMatrix[13] === - ( ( top + bottom ) / ( top - bottom ) ), "m[3,1] === -(t+b/b-t)" );
+	ok( pMatrix[14] === - ( ( far + near ) / ( far - near ) ), "m[3,2] === -(f+n/f-n)" );
+});
+
+test( "clone", function() {
+	var left = -1.5, right = 1.5, top = 1, bottom = -1, near = 0.1, far = 42;
+	var cam = new THREE.OrthographicCamera(left, right, top, bottom, near, far);
+
+	var clonedCam = cam.clone();
+
+	ok( cam.left === clonedCam.left , "left is equal" );
+	ok( cam.right === clonedCam.right , "right is equal" );
+	ok( cam.top === clonedCam.top , "top is equal" );
+	ok( cam.bottom === clonedCam.bottom , "bottom is equal" );
+	ok( cam.near === clonedCam.near , "near is equal" );
+	ok( cam.far === clonedCam.far , "far is equal" );
+	ok( cam.zoom === clonedCam.zoom , "zoom is equal" );
+});

+ 50 - 0
test/unit/cameras/PerspectiveCamera.js

@@ -0,0 +1,50 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+
+module( "PerspectiveCamera" );
+
+test( "updateProjectionMatrix", function() {
+
+	var cam = new THREE.PerspectiveCamera( 75, 16 / 9, 0.1, 300.0 );
+
+	// updateProjectionMatrix is called in contructor
+	var m = cam.projectionMatrix;
+
+	// perspective projection is given my the 4x4 Matrix
+	// 2n/r-l		0			l+r/r-l				 0
+	//   0		2n/t-b	t+b/t-b				 0
+	//   0			0		-(f+n/f-n)	-(2fn/f-n)
+	//   0			0				-1					 0
+
+	// this matrix was calculated by hand via glMatrix.perspective(75, 16 / 9, 0.1, 300.0, pMatrix)
+	// to get a reference matrix from plain WebGL
+	var reference = new THREE.Matrix4().set(
+		0.7330642938613892, 0, 0, 0,
+		0, 1.3032253980636597, 0, 0,
+		0, 0, -1.000666856765747, -0.2000666856765747,
+		0, 0, -1, 0
+	);
+
+	ok( reference.equals(m) );
+});
+
+test( "clone", function() {
+	var near = 1,
+			far = 3,
+			bottom = -1,
+			top = 1,
+			aspect = 16 / 9,
+			fov = 90;
+
+	var cam = new THREE.PerspectiveCamera( fov, aspect, near, far );
+
+	var clonedCam = cam.clone();
+
+	ok( cam.fov === clonedCam.fov , "fov is equal" );
+	ok( cam.aspect === clonedCam.aspect , "aspect is equal" );
+	ok( cam.near === clonedCam.near , "near is equal" );
+	ok( cam.far === clonedCam.far , "far is equal" );
+	ok( cam.zoom === clonedCam.zoom , "zoom is equal" );
+	ok( cam.projectionMatrix.equals(clonedCam.projectionMatrix) , "projectionMatrix is equal" );
+});

+ 117 - 0
test/unit/core/BufferAttribute.js

@@ -0,0 +1,117 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+
+module( "BufferAttribute" );
+
+test( "count", function() {
+	ok(
+		new THREE.BufferAttribute( new Float32Array( [1, 2, 3, 4, 5, 6] ), 3 ).count === 2,
+		'count is equal to the number of chunks'
+	);
+});
+
+test( "copy", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array( [1, 2, 3, 4, 5, 6] ), 3 );
+	attr.setDynamic( true );
+	attr.needsUpdate = true;
+
+	var attrCopy = new THREE.BufferAttribute().copy( attr );
+
+	ok( attr.count === attrCopy.count, 'count is equal' );
+	ok( attr.itemSize === attrCopy.itemSize, 'itemSize is equal' );
+	ok( attr.dynamic === attrCopy.dynamic, 'dynamic is equal' );
+	ok( attr.array.length === attrCopy.array.length, 'array length is equal' );
+	ok( attr.version === 1 && attrCopy.version === 0, 'version is not copied which is good' );
+});
+
+test( "copyAt", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array( [1, 2, 3, 4, 5, 6, 7, 8, 9] ), 3 );
+	var attr2 = new THREE.BufferAttribute( new Float32Array(9), 3 );
+
+	attr2.copyAt( 1, attr, 2 );
+	attr2.copyAt( 0, attr, 1 );
+	attr2.copyAt( 2, attr, 0 );
+
+	var i = attr.array;
+	var i2 = attr2.array; // should be [4, 5, 6, 7, 8, 9, 1, 2, 3]
+
+	ok( i2[0] === i[3] && i2[1] === i[4] && i2[2] === i[5], 'chunck copied to correct place' );
+	ok( i2[3] === i[6] && i2[4] === i[7] && i2[5] === i[8], 'chunck copied to correct place' );
+	ok( i2[6] === i[0] && i2[7] === i[1] && i2[8] === i[2], 'chunck copied to correct place' );
+});
+
+test( "copyColorsArray", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array(6), 3 );
+
+	attr.copyColorsArray( [
+		new THREE.Color( 0, 0.5, 1 ),
+		new THREE.Color( 0.25, 1, 0 )
+	]);
+
+	var i = attr.array;
+	ok( i[0] === 0 && i[1] === 0.5 && i[2] === 1, 'first color was copied correctly' );
+	ok( i[3] === 0.25 && i[4] === 1 && i[5] === 0, 'second color was copied correctly' );
+});
+
+test( "copyIndicesArray", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array(6), 3 );
+
+	attr.copyIndicesArray( [
+		{a: 1, b: 2, c: 3},
+		{a: 4, b: 5, c: 6}
+	]);
+
+	var i = attr.array;
+	ok( i[0] === 1 && i[1] === 2 && i[2] === 3, 'first indices were copied correctly' );
+	ok( i[3] === 4 && i[4] === 5 && i[5] === 6, 'second indices were copied correctly' );
+});
+
+test( "copyVector2sArray", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array(4), 2 );
+
+	attr.copyVector2sArray( [
+		new THREE.Vector2(1, 2),
+		new THREE.Vector2(4, 5)
+	]);
+
+	var i = attr.array;
+	ok( i[0] === 1 && i[1] === 2, 'first vector was copied correctly' );
+	ok( i[2] === 4 && i[3] === 5, 'second vector was copied correctly' );
+});
+
+test( "copyVector3sArray", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array(6), 2 );
+
+	attr.copyVector3sArray( [
+		new THREE.Vector3(1, 2, 3),
+		new THREE.Vector3(10, 20, 30)
+	]);
+
+	var i = attr.array;
+	ok( i[0] === 1 && i[1] === 2 && i[2] === 3, 'first vector was copied correctly' );
+	ok( i[3] === 10 && i[4] === 20 && i[5] === 30, 'second vector was copied correctly' );
+});
+
+test( "copyVector4sArray", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array(8), 2 );
+
+	attr.copyVector4sArray( [
+		new THREE.Vector4(1, 2, 3, 4),
+		new THREE.Vector4(10, 20, 30, 40)
+	]);
+
+	var i = attr.array;
+	ok( i[0] === 1 && i[1] === 2 && i[2] === 3 && i[3] === 4, 'first vector was copied correctly' );
+	ok( i[4] === 10 && i[5] === 20 && i[6] === 30 && i[7] === 40, 'second vector was copied correctly' );
+});
+
+test( "clone", function() {
+	var attr = new THREE.BufferAttribute( new Float32Array([1, 2, 3, 4, 0.12, -12]), 2 );
+	var attrCopy = attr.clone();
+
+	ok( attr.array.length === attrCopy.array.length, 'attribute was cloned' );
+	for ( var i = 0; i < attr.array.length; i++ ) {
+		ok( attr.array[i] === attrCopy.array[i], 'array item is equal' );
+	}
+});

+ 319 - 0
test/unit/core/BufferGeometry.js

@@ -0,0 +1,319 @@
+/**
+ * @author simonThiele / https://github.com/simonThiele
+ */
+
+module( "BufferGeometry" );
+
+var DegToRad = Math.PI / 180;
+
+test( "add / delete Attribute", function() {
+	var geometry = new THREE.BufferGeometry();
+	var attributeName = "position";
+
+	ok ( geometry.attributes[attributeName] === undefined , 'no attribute defined' );
+
+	geometry.addAttribute( attributeName, new THREE.BufferAttribute( new Float32Array( [1, 2, 3], 1 ) ) );
+
+	ok ( geometry.attributes[attributeName] !== undefined , 'attribute is defined' );
+
+	geometry.removeAttribute( attributeName );
+
+	ok ( geometry.attributes[attributeName] === undefined , 'no attribute defined' );
+});
+
+test( "applyMatrix", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array(6), 3 ) );
+
+	var matrix = new THREE.Matrix4().set(
+		1, 0, 0, 1.5,
+		0, 1, 0, -2,
+		0, 0, 1, 3,
+		0, 0, 0, 1
+	);
+	geometry.applyMatrix(matrix);
+
+	var position = geometry.attributes.position.array;
+	var m = matrix.elements;
+	ok( position[0] === m[12] && position[1] === m[13] && position[2] === m[14], "position was extracted from matrix" );
+	ok( position[3] === m[12] && position[4] === m[13] && position[5] === m[14], "position was extracted from matrix twice" );
+	ok( geometry.attributes.position.version === 1, "version was increased during update" );
+});
+
+test( "rotateX/Y/Z", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array([1, 2, 3, 4, 5, 6]), 3 ) );
+
+	var pos = geometry.attributes.position.array;
+
+	geometry.rotateX( 180 * DegToRad );
+
+	// object was rotated around x so all items should be flipped but the x ones
+	ok( pos[0] === 1 && pos[1] === -2 && pos[2] === -3 &&
+			pos[3] === 4 && pos[4] === -5 && pos[5] === -6, "vertices were rotated around x by 180 degrees" );
+
+
+	geometry.rotateY( 180 * DegToRad );
+
+	// vertices were rotated around y so all items should be flipped again but the y ones
+	ok( pos[0] === -1 && pos[1] === -2 && pos[2] === 3 &&
+			pos[3] === -4 && pos[4] === -5 && pos[5] === 6, "vertices were rotated around y by 180 degrees" );
+
+
+	geometry.rotateZ( 180 * DegToRad );
+
+	// vertices were rotated around z so all items should be flipped again but the z ones
+	ok( pos[0] === 1 && pos[1] === 2 && pos[2] === 3 &&
+			pos[3] === 4 && pos[4] === 5 && pos[5] === 6, "vertices were rotated around z by 180 degrees" );
+});
+
+
+test( "translate", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array([1, 2, 3, 4, 5, 6]), 3 ) );
+
+	var pos = geometry.attributes.position.array;
+
+	geometry.translate( 10, 20, 30 );
+
+	ok( pos[0] === 11 && pos[1] === 22 && pos[2] === 33 &&
+			pos[3] === 14 && pos[4] === 25 && pos[5] === 36, "vertices were translated" );
+});
+
+test( "scale", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array([-1, -1, -1, 2, 2, 2]), 3 ) );
+
+	var pos = geometry.attributes.position.array;
+
+	geometry.scale( 1, 2, 3 );
+
+	ok( pos[0] === -1 && pos[1] === -2 && pos[2] === -3 &&
+			pos[3] === 2 && pos[4] === 4 && pos[5] === 6, "vertices were scaled" );
+});
+
+test( "center", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array([
+		-1, -1, -1,
+		1, 1, 1,
+		4, 4, 4
+	]), 3 ) );
+
+	geometry.center();
+
+	var pos = geometry.attributes.position.array;
+	var bb = geometry.boundingBox;
+
+	// the boundingBox should go from (-1, -1, -1) to (4, 4, 4) so it has a size of (5, 5, 5)
+	// after centering it the vertices should be placed between (-2.5, -2.5, -2.5) and (2.5, 2.5, 2.5)
+	ok( pos[0] === -2.5 && pos[1] === -2.5 && pos[2] === -2.5 &&
+			pos[3] === -0.5 && pos[4] === -0.5 && pos[5] === -0.5 &&
+			pos[6] === 2.5 && pos[7] === 2.5 && pos[8] === 2.5, "vertices were replaced by boundingBox dimensions" );
+});
+
+test( "setFromObject", function() {
+	var lineGeo = new THREE.Geometry();
+	lineGeo.vertices.push(
+		new THREE.Vector3( -10, 0, 0 ),
+		new THREE.Vector3( 0, 10, 0 ),
+		new THREE.Vector3( 10, 0, 0 )
+	);
+
+	lineGeo.colors.push(
+		new THREE.Color(1, 0, 0 ),
+		new THREE.Color(0, 1, 0 ),
+		new THREE.Color(0, 0, 1 )
+	);
+
+	var line = new THREE.Line( lineGeo, null );
+	var geometry = new THREE.BufferGeometry().setFromObject( line );
+
+	var pos = geometry.attributes.position.array;
+	var col = geometry.attributes.color.array;
+	var v = lineGeo.vertices;
+	var c = lineGeo.colors;
+
+	ok(
+		 // position exists
+			pos !== undefined &&
+
+			// vertex arrays have the same size
+			v.length * 3 === pos.length &&
+
+			// there are three complete vertices (each vertex contains three values)
+			geometry.attributes.position.count === 3 &&
+
+			// check if both arrays contains the same data
+			pos[0] === v[0].x && pos[1] === v[0].y && pos[2] === v[0].z &&
+			pos[3] === v[1].x && pos[4] === v[1].y && pos[5] === v[1].z &&
+			pos[6] === v[2].x && pos[7] === v[2].y && pos[8] === v[2].z
+			, "positions are equal" );
+
+	ok(
+		 // color exists
+			col !== undefined &&
+
+			// color arrays have the same size
+			c.length * 3 === col.length &&
+
+			// there are three complete colors (each color contains three values)
+			geometry.attributes.color.count === 3 &&
+
+			// check if both arrays contains the same data
+			col[0] === c[0].r && col[1] === c[0].g && col[2] === c[0].b &&
+			col[3] === c[1].r && col[4] === c[1].g && col[5] === c[1].b &&
+			col[6] === c[2].r && col[7] === c[2].g && col[8] === c[2].b
+			, "colors are equal" );
+});
+
+test( "computeBoundingBox", function() {
+	var bb = getBBForVertices( [-1, -2, -3, 13, -2, -3.5, -1, -20, 0, -4, 5, 6] );
+
+	ok( bb.min.x === -4 && bb.min.y === -20 && bb.min.z === -3.5, "min values are set correctly" );
+	ok( bb.max.x === 13 && bb.max.y === 5 && bb.max.z === 6, "max values are set correctly" );
+
+
+	bb = getBBForVertices( [] );
+
+	ok( bb.min.x === 0 && bb.min.y === 0 && bb.min.z === 0, "since there are no values given, the bb has size = 0" );
+	ok( bb.max.x === 0 && bb.max.y === 0 && bb.max.z === 0, "since there are no values given, the bb has size = 0" );
+
+
+	bb = getBBForVertices( [-1, -1, -1] );
+
+	ok( bb.min.x === bb.max.x && bb.min.y === bb.max.y && bb.min.z === bb.max.z, "since there is only one vertex, max and min are equal" );
+	ok( bb.min.x === -1 && bb.min.y === -1 && bb.min.z === -1, "since there is only one vertex, min and max are this vertex" );
+
+	bb = getBBForVertices( [-1] );
+});
+
+test( "computeBoundingSphere", function() {
+	var bs = getBSForVertices( [-10, 0, 0, 10, 0, 0] );
+
+	ok( bs.radius === (10 + 10) / 2, "radius is equal to deltaMinMax / 2" )
+	ok( bs.center.x === 0 && bs.center.y === 0 && bs.center.y === 0, "bounding sphere is at ( 0, 0, 0 )" )
+
+
+	var bs = getBSForVertices( [-5, 11, -3, 5, -11, 3] );
+	var radius = new THREE.Vector3(5, 11, 3).length();
+
+	ok( bs.radius === radius, "radius is equal to directionLength" )
+	ok( bs.center.x === 0 && bs.center.y === 0 && bs.center.y === 0, "bounding sphere is at ( 0, 0, 0 )" )
+});
+
+function getBBForVertices(vertices) {
+	var geometry = new THREE.BufferGeometry();
+
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array(vertices), 3 ) );
+	geometry.computeBoundingBox();
+
+	return geometry.boundingBox;
+}
+
+function getBSForVertices(vertices) {
+	var geometry = new THREE.BufferGeometry();
+
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array(vertices), 3 ) );
+	geometry.computeBoundingSphere();
+
+	return geometry.boundingSphere;
+}
+
+test( "computeVertexNormals", function() {
+	// get normals for a counter clockwise created triangle
+	var normals = getNormalsForVertices([-1, 0, 0, 1, 0, 0, 0, 1, 0]);
+
+	ok( normals[0] === 0 && normals[1] === 0 && normals[2] === 1,
+		"first normal is pointing to screen since the the triangle was created counter clockwise" );
+
+	ok( normals[3] === 0 && normals[4] === 0 && normals[5] === 1,
+		"second normal is pointing to screen since the the triangle was created counter clockwise" );
+
+	ok( normals[6] === 0 && normals[7] === 0 && normals[8] === 1,
+		"third normal is pointing to screen since the the triangle was created counter clockwise" );
+
+
+	// get normals for a clockwise created triangle
+	var normals = getNormalsForVertices([1, 0, 0, -1, 0, 0, 0, 1, 0]);
+
+	ok( normals[0] === 0 && normals[1] === 0 && normals[2] === -1,
+		"first normal is pointing to screen since the the triangle was created clockwise" );
+
+	ok( normals[3] === 0 && normals[4] === 0 && normals[5] === -1,
+		"second normal is pointing to screen since the the triangle was created clockwise" );
+
+	ok( normals[6] === 0 && normals[7] === 0 && normals[8] === -1,
+		"third normal is pointing to screen since the the triangle was created clockwise" );
+
+
+	var normals = getNormalsForVertices([0, 0, 1, 0, 0, -1, 1, 1, 0]);
+
+	// the triangle is rotated by 45 degrees to the right so the normals of the three vertices
+	// should point to (1, -1, 0).normalized(). The simplest solution is to check against a normalized
+	// vector (1, -1, 0) but you will get calculation errors because of floating calculations so another
+	// valid technique is to create a vector which stands in 90 degrees to the normals and calculate the
+	// dot product which is the cos of the angle between them. This should be < floating calculation error
+	// which can be taken from Number.EPSILON
+	var direction = new THREE.Vector3(1, 1, 0).normalize(); // a vector which should have 90 degrees difference to normals
+	var difference = direction.dot( new THREE.Vector3( normals[0], normals[1], normals[2] ) );
+	ok( difference < Number.EPSILON, "normal is equal to reference vector");
+
+
+	// get normals for a line should be NAN because you need min a triangle to calculate normals
+	var normals = getNormalsForVertices([1, 0, 0, -1, 0, 0]);
+	for (var i = 0; i < normals.length; i++) {
+		ok ( !normals[i], "normals can't be calculated which is good");
+	}
+});
+
+function getNormalsForVertices(vertices) {
+	var geometry = new THREE.BufferGeometry();
+
+	geometry.addAttribute( "position", new THREE.BufferAttribute( new Float32Array(vertices), 3 ) );
+
+	geometry.computeVertexNormals();
+
+	ok( geometry.attributes.normal !== undefined, "normal attribute was created" );
+
+	return geometry.attributes.normal.array;
+}
+
+test( "merge", function() {
+	var geometry1 = new THREE.BufferGeometry();
+	geometry1.addAttribute( "attrName", new THREE.BufferAttribute( new Float32Array([1, 2, 3, 0, 0, 0]), 3 ) );
+
+	var geometry2 = new THREE.BufferGeometry();
+	geometry2.addAttribute( "attrName", new THREE.BufferAttribute( new Float32Array([4, 5, 6]), 3 ) );
+
+	var attr = geometry1.attributes.attrName.array;
+
+	geometry1.merge(geometry2, 1);
+
+	// merged array should be 1, 2, 3, 4, 5, 6
+	for (var i = 0; i < attr.length; i++) {
+	  ok( attr[i] === i + 1, "");
+	}
+
+	geometry1.merge(geometry2);
+	ok( attr[0] === 4 && attr[1] === 5 && attr[2] === 6, "copied the 3 attributes without offset" );
+});
+
+test( "copy", function() {
+	var geometry = new THREE.BufferGeometry();
+	geometry.addAttribute( "attrName", new THREE.BufferAttribute( new Float32Array([1, 2, 3, 4, 5, 6]), 3 ) );
+	geometry.addAttribute( "attrName2", new THREE.BufferAttribute( new Float32Array([0, 1, 3, 5, 6]), 1 ) );
+
+	var copy = new THREE.BufferGeometry().copy(geometry);
+
+	ok( copy !== geometry && geometry.id !== copy.id, "new object was created" );
+
+	Object.keys(geometry.attributes).forEach(function(key) {
+		var attribute = geometry.attributes[key];
+		ok( attribute !== undefined, "all attributes where copied");
+
+		for (var i = 0; i < attribute.array.length; i++) {
+			ok( attribute.array[i] === copy.attributes[key].array[i], "values of the attribute are equal" );
+		}
+	});
+});

+ 6 - 2
test/unit/unittests_three.html

@@ -18,9 +18,13 @@
 
   <!-- add class-based unit tests below -->
 
-  <script src="core/Object3D.js"></script>
+  <script src="cameras/Camera.js"></script>
+  <script src="cameras/OrthographicCamera.js"></script>
+  <script src="cameras/PerspectiveCamera.js"></script>
 
-  <script src="Cameras/Camera.js"></script>
+  <script src="core/BufferAttribute.js"></script>
+  <script src="core/BufferGeometry.js"></script>
+  <script src="core/Object3D.js"></script>
 
   <script src="math/Constants.js"></script>
   <script src="math/Box2.js"></script>

+ 13 - 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",
@@ -114,6 +124,7 @@
 	"src/renderers/shaders/ShaderChunk/envmap_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/fog_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/fog_pars_fragment.glsl",
+	"src/renderers/shaders/ShaderChunk/hemilight_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/lightmap_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/lightmap_pars_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/lights_lambert_pars_vertex.glsl",
@@ -134,6 +145,7 @@
 	"src/renderers/shaders/ShaderChunk/morphnormal_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/morphtarget_pars_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/morphtarget_vertex.glsl",
+	"src/renderers/shaders/ShaderChunk/normal_phong_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/normalmap_pars_fragment.glsl",
 	"src/renderers/shaders/ShaderChunk/project_vertex.glsl",
 	"src/renderers/shaders/ShaderChunk/shadowmap_fragment.glsl",

+ 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/__init__.py

@@ -8,9 +8,10 @@ from . import scene, geometry, api, base_classes
 def _error_handler(func):
     
     def inner(filepath, options, *args, **kwargs):
-        level = options.get(constants.LOGGING, constants.DEBUG)
+        level = options.get(constants.LOGGING, constants.DISABLED)
         version = options.get('addon_version')
-        logger.init('io_three.export.log', level=level)
+        if level != constants.DISABLED:
+            logger.init('io_three.export.log', level=level)
         if version is not None:
             logger.debug("Addon Version %s", version)
         api.init()

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно