Browse Source

Added proper up-axis conversion for COLLADA models. Added support for key-framed animation.

Erik Kitson 13 years ago
parent
commit
01ac1e3d17

+ 1 - 1
examples/webgl_collada.html

@@ -31,13 +31,13 @@
 			var dae, skin;
 
 			var loader = new THREE.ColladaLoader();
+			loader.options.convertUpAxis = true;
 			loader.load( './models/monster.dae', function colladaReady( collada ) {
 
 				dae = collada.scene;
 				skin = collada.skins[ 0 ];
 
 				dae.scale.x = dae.scale.y = dae.scale.z = 0.002;
-				dae.rotation.x = -Math.PI/2;
 				dae.updateMatrix();
 
 				init();

+ 152 - 150
src/extras/animation/Animation.js

@@ -10,7 +10,7 @@ THREE.Animation = function( root, data, interpolationType, JITCompile ) {
 	this.data = THREE.AnimationHandler.get( data );
 	this.hierarchy = THREE.AnimationHandler.parse( root );
 	this.currentTime = 0;
-	this.timeScale = 1;
+	this.timeScale = 0.001;
 	this.isPlaying = false;
 	this.isPaused = true;
 	this.loop = true;
@@ -20,6 +20,37 @@ THREE.Animation = function( root, data, interpolationType, JITCompile ) {
 	this.points = [];
 	this.target = new THREE.Vector3();
 
+	// initialize to first keyframes
+
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
+
+		var keys = this.data.hierarchy[h].keys,
+			sids = this.data.hierarchy[h].sids,
+			obj = this.hierarchy[h];
+
+		if ( keys.length ) {
+
+			for ( var s = 0; s < sids.length; s++ ) {
+
+				var sid = sids[ s ],
+					next = this.getNextKeyWith( sid, h, 0 );
+
+				if ( next ) {
+
+					next.apply( sid );
+
+				}
+
+			}
+
+			obj.matrixAutoUpdate = false;
+			this.data.hierarchy[h].node.updateMatrix();
+			obj.matrixWorldNeedsUpdate = true;
+
+		}
+
+	}
+
 };
 
 // Play
@@ -31,16 +62,21 @@ THREE.Animation.prototype.play = function( loop, startTimeMS ) {
 		this.isPlaying = true;
 		this.loop = loop !== undefined ? loop : true;
 		this.currentTime = startTimeMS !== undefined ? startTimeMS : 0;
+		this.startTimeMs = startTimeMS;
+		this.startTime = 10000000;
+		this.endTime = -this.startTime;
 
 
 		// reset key cache
 
 		var h, hl = this.hierarchy.length,
-			object;
+			object,
+			node;
 
 		for ( h = 0; h < hl; h++ ) {
 
 			object = this.hierarchy[ h ];
+			node = this.data.hierarchy[ h ];
 
 			if ( this.interpolationType !== THREE.AnimationHandler.CATMULLROM_FORWARD ) {
 
@@ -48,27 +84,26 @@ THREE.Animation.prototype.play = function( loop, startTimeMS ) {
 
 			}
 
-			object.matrixAutoUpdate = true;
-
-			if ( object.animationCache === undefined ) {
+			if ( node.animationCache === undefined ) {
 
-				object.animationCache = {};
-				object.animationCache.prevKey = { pos: 0, rot: 0, scl: 0 };
-				object.animationCache.nextKey = { pos: 0, rot: 0, scl: 0 };
-				object.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
+				node.animationCache = {};
+				node.animationCache.prevKey = null;
+				node.animationCache.nextKey = null;
+				node.animationCache.originalMatrix = object instanceof THREE.Bone ? object.skinMatrix : object.matrix;
 
 			}
 
-			var prevKey = object.animationCache.prevKey;
-			var nextKey = object.animationCache.nextKey;
+			var keys = this.data.hierarchy[h].keys;
+
+			if (keys.length) {
 
-			prevKey.pos = this.data.hierarchy[ h ].keys[ 0 ];
-			prevKey.rot = this.data.hierarchy[ h ].keys[ 0 ];
-			prevKey.scl = this.data.hierarchy[ h ].keys[ 0 ];
+				node.animationCache.prevKey = keys[ 0 ];
+				node.animationCache.nextKey = keys[ 1 ];
 
-			nextKey.pos = this.getNextKeyWith( "pos", h, 1 );
-			nextKey.rot = this.getNextKeyWith( "rot", h, 1 );
-			nextKey.scl = this.getNextKeyWith( "scl", h, 1 );
+				this.startTime = Math.min( keys[0].time, this.startTime );
+				this.endTime = Math.max( keys[keys.length - 1].time, this.endTime );
+
+			}
 
 		}
 
@@ -116,20 +151,25 @@ THREE.Animation.prototype.stop = function() {
 
 	for ( var h = 0; h < this.hierarchy.length; h++ ) {
 
-		if ( this.hierarchy[ h ].animationCache !== undefined ) {
+		var obj = this.hierarchy[ h ];
+
+		if ( obj.animationCache !== undefined ) {
 
-			if( this.hierarchy[ h ] instanceof THREE.Bone ) {
+			var original = obj.animationCache.originalMatrix;
 
-				this.hierarchy[ h ].skinMatrix = this.hierarchy[ h ].animationCache.originalMatrix;
+			if( obj instanceof THREE.Bone ) {
+
+				original.copy( obj.skinMatrix );
+				obj.skinMatrix = original;
 
 			} else {
 
-				this.hierarchy[ h ].matrix = this.hierarchy[ h ].animationCache.originalMatrix;
+				original.copy( obj.matrix );
+				obj.matrix = original;
 
 			}
 
-
-			delete this.hierarchy[ h ].animationCache;
+			delete obj.animationCache;
 
 		}
 
@@ -149,18 +189,13 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) {
 
 	// vars
 
-	var types = [ "pos", "rot", "scl" ];
-	var type;
-	var scale;
-	var vector;
-	var prevXYZ, nextXYZ;
 	var prevKey, nextKey;
 	var object;
-	var animationCache;
+	var node;
 	var frame;
 	var JIThierarchy = this.data.JIT.hierarchy;
 	var currentTime, unloopedCurrentTime;
-	var currentPoint, forwardPoint, angle;
+	var looped;
 
 
 	// update
@@ -169,188 +204,155 @@ THREE.Animation.prototype.update = function( deltaTimeMS ) {
 
 	unloopedCurrentTime = this.currentTime;
 	currentTime         = this.currentTime = this.currentTime % this.data.length;
-	frame               = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
 
+	// if looped around, the current time should be based on the startTime
+	if ( currentTime < this.startTimeMs ) {
 
-	// update
-
-	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
-
-		object = this.hierarchy[ h ];
-		animationCache = object.animationCache;
-
-		// use JIT?
-
-		if ( this.JITCompile && JIThierarchy[ h ][ frame ] !== undefined ) {
-
-			if( object instanceof THREE.Bone ) {
-
-				object.skinMatrix = JIThierarchy[ h ][ frame ];
+		currentTime = this.currentTime = this.startTimeMs + currentTime;
 
-				object.matrixAutoUpdate = false;
-				object.matrixWorldNeedsUpdate = false;
-
-			} else {
+	}
 
-				object.matrix = JIThierarchy[ h ][ frame ];
+	frame               = parseInt( Math.min( currentTime * this.data.fps, this.data.length * this.data.fps ), 10 );
+	looped 				= currentTime < unloopedCurrentTime;
 
-				object.matrixAutoUpdate = false;
-				object.matrixWorldNeedsUpdate = true;
+	if ( looped && !this.loop ) {
 
-			}
+		// Set the animation to the last keyframes and stop
+		for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
 
-		// use interpolation
+			var keys = this.data.hierarchy[h].keys,
+				sids = this.data.hierarchy[h].sids,
+				end = keys.length-1,
+				obj = this.hierarchy[h];
 
-		} else {
+			if ( keys.length ) {
 
-			// make sure so original matrix and not JIT matrix is set
+				for ( var s = 0; s < sids.length; s++ ) {
 
-			if ( this.JITCompile ) {
+					var sid = sids[ s ],
+						prev = this.getPrevKeyWith( sid, h, end );
 
-				if( object instanceof THREE.Bone ) {
+					if ( prev ) {
 
-					object.skinMatrix = object.animationCache.originalMatrix;
+						prev.apply( sid );
 
-				} else {
-
-					object.matrix = object.animationCache.originalMatrix;
+					}
 
 				}
 
-			}
-
-
-			// loop through pos/rot/scl
-
-			for ( var t = 0; t < 3; t++ ) {
-
-				// get keys
+				this.data.hierarchy[h].node.updateMatrix();
+				obj.matrixWorldNeedsUpdate = true;
 
-				type    = types[ t ];
-				prevKey = animationCache.prevKey[ type ];
-				nextKey = animationCache.nextKey[ type ];
-
-				// switch keys?
+			}
 
-				if ( nextKey.time <= unloopedCurrentTime ) {
+		}
 
-					// did we loop?
+		this.stop();
+		return;
 
-					if ( currentTime < unloopedCurrentTime ) {
+	}
 
-						if ( this.loop ) {
+	// check pre-infinity
+	if ( currentTime < this.startTime ) {
 
-							prevKey = this.data.hierarchy[ h ].keys[ 0 ];
-							nextKey = this.getNextKeyWith( type, h, 1 );
+		return;
 
-							while( nextKey.time < currentTime ) {
+	}
 
-								prevKey = nextKey;
-								nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+	// update
 
-							}
+	for ( var h = 0, hl = this.hierarchy.length; h < hl; h++ ) {
 
-						} else {
+		object = this.hierarchy[ h ];
+		node = this.data.hierarchy[ h ];
 
-							this.stop();
-							return;
+		var keys = node.keys,
+			animationCache = node.animationCache;
 
-						}
+		// use JIT?
 
-					} else {
+		if ( this.JITCompile && JIThierarchy[ h ][ frame ] !== undefined ) {
 
-						do {
+			if( object instanceof THREE.Bone ) {
 
-							prevKey = nextKey;
-							nextKey = this.getNextKeyWith( type, h, nextKey.index + 1 );
+				object.skinMatrix = JIThierarchy[ h ][ frame ];
+				object.matrixWorldNeedsUpdate = false;
 
-						} while( nextKey.time < currentTime )
+			} else {
 
-					}
+				object.matrix = JIThierarchy[ h ][ frame ];
+				object.matrixWorldNeedsUpdate = true;
 
-					animationCache.prevKey[ type ] = prevKey;
-					animationCache.nextKey[ type ] = nextKey;
+			}
 
-				}
+		// use interpolation
 
+		} else if ( keys.length ) {
 
-				object.matrixAutoUpdate = true;
-				object.matrixWorldNeedsUpdate = true;
+			// make sure so original matrix and not JIT matrix is set
 
-				scale = ( currentTime - prevKey.time ) / ( nextKey.time - prevKey.time );
-				prevXYZ = prevKey[ type ];
-				nextXYZ = nextKey[ type ];
+			if ( this.JITCompile && animationCache ) {
 
+				if( object instanceof THREE.Bone ) {
 
-				// check scale error
+					object.skinMatrix = animationCache.originalMatrix;
 
-				if ( scale < 0 || scale > 1 ) {
+				} else {
 
-					console.log( "THREE.Animation.update: Warning! Scale out of bounds:" + scale + " on bone " + h );
-					scale = scale < 0 ? 0 : 1;
+					object.matrix = animationCache.originalMatrix;
 
 				}
 
-				// interpolate
+			}
 
-				if ( type === "pos" ) {
+			prevKey = animationCache.prevKey;
+			nextKey = animationCache.nextKey;
 
-					vector = object.position;
+			if ( prevKey && nextKey ) {
 
-					if( this.interpolationType === THREE.AnimationHandler.LINEAR ) {
+				// switch keys?
 
-						vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
-						vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
-						vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+				if ( nextKey.time <= unloopedCurrentTime ) {
+
+					// did we loop?
 
-					} else if ( this.interpolationType === THREE.AnimationHandler.CATMULLROM ||
-							    this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+					if ( looped && this.loop ) {
 
-						this.points[ 0 ] = this.getPrevKeyWith( "pos", h, prevKey.index - 1 )[ "pos" ];
-						this.points[ 1 ] = prevXYZ;
-						this.points[ 2 ] = nextXYZ;
-						this.points[ 3 ] = this.getNextKeyWith( "pos", h, nextKey.index + 1 )[ "pos" ];
+						prevKey = keys[ 0 ];
+						nextKey = keys[ 1 ];
 
-						scale = scale * 0.33 + 0.33;
+						while ( nextKey.time < currentTime ) {
 
-						currentPoint = this.interpolateCatmullRom( this.points, scale );
+							prevKey = nextKey;
+							nextKey = keys[ prevKey.index + 1 ];
 
-						vector.x = currentPoint[ 0 ];
-						vector.y = currentPoint[ 1 ];
-						vector.z = currentPoint[ 2 ];
+						}
 
-						if( this.interpolationType === THREE.AnimationHandler.CATMULLROM_FORWARD ) {
+					} else if ( !looped ) {
 
-							forwardPoint = this.interpolateCatmullRom( this.points, scale * 1.01 );
+						var lastIndex = keys.length - 1;
 
-							this.target.set( forwardPoint[ 0 ], forwardPoint[ 1 ], forwardPoint[ 2 ] );
-							this.target.subSelf( vector );
-							this.target.y = 0;
-							this.target.normalize();
+						while ( nextKey.time < currentTime && nextKey.index !== lastIndex ) {
 
-							angle = Math.atan2( this.target.x, this.target.z );
-							object.rotation.set( 0, angle, 0 );
+							prevKey = nextKey;
+							nextKey = keys[ prevKey.index + 1 ];
 
 						}
 
 					}
 
-				} else if ( type === "rot" ) {
-
-					THREE.Quaternion.slerp( prevXYZ, nextXYZ, object.quaternion, scale );
-
-				} else if( type === "scl" ) {
-
-					vector = object.scale;
-
-					vector.x = prevXYZ[ 0 ] + ( nextXYZ[ 0 ] - prevXYZ[ 0 ] ) * scale;
-					vector.y = prevXYZ[ 1 ] + ( nextXYZ[ 1 ] - prevXYZ[ 1 ] ) * scale;
-					vector.z = prevXYZ[ 2 ] + ( nextXYZ[ 2 ] - prevXYZ[ 2 ] ) * scale;
+					animationCache.prevKey = prevKey;
+					animationCache.nextKey = nextKey;
 
 				}
 
+				prevKey.interpolate( nextKey, currentTime );
+
 			}
 
+			this.data.hierarchy[h].node.updateMatrix();
+			object.matrixWorldNeedsUpdate = true;
+
 		}
 
 	}
@@ -429,7 +431,7 @@ THREE.Animation.prototype.interpolate = function( p0, p1, p2, p3, t, t2, t3 ) {
 
 // Get next key with
 
-THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) {
+THREE.Animation.prototype.getNextKeyWith = function( sid, h, key ) {
 
 	var keys = this.data.hierarchy[ h ].keys;
 
@@ -446,7 +448,7 @@ THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) {
 
 	for ( ; key < keys.length; key++ ) {
 
-		if ( keys[ key ][ type ] !== undefined ) {
+		if ( keys[ key ].hasTarget( sid ) ) {
 
 			return keys[ key ];
 
@@ -454,13 +456,13 @@ THREE.Animation.prototype.getNextKeyWith = function( type, h, key ) {
 
 	}
 
-	return this.data.hierarchy[ h ].keys[ 0 ];
+	return keys[ 0 ];
 
 };
 
 // Get previous key with
 
-THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) {
+THREE.Animation.prototype.getPrevKeyWith = function( sid, h, key ) {
 
 	var keys = this.data.hierarchy[ h ].keys;
 
@@ -478,7 +480,7 @@ THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) {
 
 	for ( ; key >= 0; key-- ) {
 
-		if ( keys[ key ][ type ] !== undefined ) {
+		if ( keys[ key ].hasTarget( sid ) ) {
 
 			return keys[ key ];
 
@@ -486,6 +488,6 @@ THREE.Animation.prototype.getPrevKeyWith = function( type, h, key ) {
 
 	}
 
-	return this.data.hierarchy[ h ].keys[ keys.length - 1 ];
+	return keys[ keys.length - 1 ];
 
 };

+ 2 - 2
src/extras/animation/AnimationHandler.js

@@ -149,7 +149,7 @@ THREE.AnimationHandler = (function() {
 
 			// prepare morph target keys
 
-			if( data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
+			if( data.hierarchy[h].keys.length && data.hierarchy[ h ].keys[ 0 ].morphTargets !== undefined ) {
 
 				// get all used
 
@@ -219,7 +219,7 @@ THREE.AnimationHandler = (function() {
 
 			// set index
 
-			for( var k = 1; k < data.hierarchy[ h ].keys.length; k ++ ) {
+			for( var k = 0; k < data.hierarchy[ h ].keys.length; k ++ ) {
 
 				data.hierarchy[ h ].keys[ k ].index = k;
 

+ 831 - 65
src/extras/loaders/ColladaLoader.js

@@ -18,6 +18,7 @@ THREE.ColladaLoader = function () {
 	var materials = {};
 	var effects = {};
 
+	var animData;
 	var visualScenes;
 	var baseUrl;
 	var morphs;
@@ -26,6 +27,24 @@ THREE.ColladaLoader = function () {
 	var flip_uv = true;
 	var preferredShading = THREE.SmoothShading;
 
+	var options = {
+		// Axis conversion is done for geometries, animations, and controllers.
+		// If we ever pull cameras or lights out of the COLLADA file, they'll
+		// need extra work.
+		convertUpAxis: false,
+
+		subdivideFaces: true,
+
+		upAxis: 'Y'
+	};
+
+	// TODO: support unit conversion as well
+	var colladaUnit = 1.0;
+	var colladaUp = 'Y';
+	var upConversion = null;
+
+	var TO_RADIANS = Math.PI / 180;
+
 	function load ( url, readyCallback ) {
 
 		if ( document.implementation && document.implementation.createDocument ) {
@@ -84,6 +103,8 @@ THREE.ColladaLoader = function () {
 
 		}
 
+		parseAsset();
+		setUpConversion();
 		images = parseLib( "//dae:library_images/dae:image", _Image, "image" );
 		materials = parseLib( "//dae:library_materials/dae:material", Material, "material") ;
 		effects = parseLib( "//dae:library_effects/dae:effect", Effect, "effect" );
@@ -111,6 +132,7 @@ THREE.ColladaLoader = function () {
 			scene: scene,
 			morphs: morphs,
 			skins: skins,
+			animations: animData,
 			dae: {
 				images: images,
 				materials: materials,
@@ -140,6 +162,49 @@ THREE.ColladaLoader = function () {
 
 	};
 
+	function parseAsset () {
+
+		var elements = COLLADA.evaluate("//dae:asset",
+										COLLADA,
+										_nsResolver,
+										XPathResult.ORDERED_NODE_ITERATOR_TYPE,
+										null) ;
+
+		var element = elements.iterateNext();
+
+		if ( element ) {
+
+			for ( var i = 0; i < element.children.length; i ++ ) {
+
+				var child = element.children[ i ];
+
+				switch ( child.nodeName ) {
+
+					case 'unit':
+
+						var meter = child.getAttribute( 'meter' );
+
+						if ( meter ) {
+
+							colladaUnit = parseFloat( meter );
+
+						}
+
+						break;
+
+					case 'up_axis':
+
+						colladaUp = child.textContent.charAt(0);
+						break;
+
+				}
+
+			}
+
+		}
+
+	};
+
 	function parseLib ( q, classSpec, prefix ) {
 
 		var elements = COLLADA.evaluate(q,
@@ -188,17 +253,67 @@ THREE.ColladaLoader = function () {
 
 	function createAnimations () {
 
-		calcAnimationBounds();
+		animData = [];
+
+		// fill in the keys
+		recurseHierarchy( scene );
+
+	};
+
+	function recurseHierarchy ( node ) {
+
+		var n = daeScene.getChildById( node.name, true ),
+			newData = null;
+
+		if ( n && n.keys ) {
+
+			newData = {
+				fps: 60,
+				hierarchy: [ {
+					node: n,
+					keys: n.keys,
+					sids: n.sids
+				} ],
+				node: node,
+				name: 'animation_' + node.name,
+				length: 0
+			};
 
-		for ( var animation_id in animations ) {
+			animData.push(newData);
 
-			createAnimation( animations[ animation_id ] );
+			for ( var i = 0, il = n.keys.length; i < il; i++ ) {
+
+				newData.length = Math.max( newData.length, n.keys[i].time );
+
+			}
+
+		} else  {
+
+			newData = {
+				hierarchy: [ {
+					keys: [],
+					sids: []
+				} ]
+			}
 
 		}
 
-	};
+		for ( var i = 0, il = node.children.length; i < il; i++ ) {
+
+			var d = recurseHierarchy( node.children[i] );
+
+			for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) {
+
+				newData.hierarchy.push( {
+					keys: [],
+					sids: []
+				} );
+
+			}
+
+		}
 
-	function createAnimation ( animation ) {
+		return newData;
 
 	};
 
@@ -853,31 +968,199 @@ THREE.ColladaLoader = function () {
 
 		if ( node.channels && node.channels.length ) {
 
-			var frameDuration = calcFrameDuration( node );
-			var t, matrix;
-			var keys = [];
+			var keys = [],
+				sids = [];
 
-			for ( t = node.startTime; t < node.endTime; t += frameDuration ) {
+			for ( var i = 0, il = node.channels.length; i < il; i++ ) {
 
-				matrix = calcMatrixAt( node, t );
+				var channel = node.channels[i],
+					fullSid = channel.fullSid,
+					member = getConvertedMember( channel.member ),
+					sampler = channel.sampler,
+					input = sampler.input,
+					transform = node.getTransformBySid( channel.sid );
 
-				//keys.push({time: t, mat: matrix.flatten()})
+				if ( transform ) {
 
-				keys.push({
-					time: t,
-					pos: [matrix.n14, matrix.n24, matrix.n34],
-					rotq: [0, 0, 0, 1],
-					scl: [1,1,1]
-				});
+					if ( sids.indexOf( fullSid ) === -1 ) {
+
+						sids.push( fullSid );
+
+					}
+
+					for ( var j = 0, jl = input.length; j < jl; j++ ) {
+
+						var time = input[j],
+							data = sampler.getData( transform.type, j ),
+							key = findKey( keys, time );
+
+						if ( !key ) {
+
+							key = new Key( time );
+							var timeNdx = findTimeNdx( keys, time );
+							keys.splice( timeNdx == -1 ? keys.length : timeNdx, 0, key );
+
+						}
+
+						key.addTarget( fullSid, transform, member, data );
+
+					}
+
+				} else {
+
+					console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id );
+
+				}
+
+			}
+
+			// post process
+			for ( var i = 0; i < sids.length; i++ ) {
+
+				var sid = sids[ i ];
+
+				for ( var j = 0; j < keys.length; j++ ) {
+
+					var key = keys[ j ];
+
+					if ( !key.hasTarget( sid ) ) {
+
+						interpolateKeys( keys, key, j, sid );
+
+					}
+
+				}
 
 			}
 
 			node.keys = keys;
+			node.sids = sids;
 
 		}
 
 	};
 
+	function findKey ( keys, time) {
+
+		var retVal = null;
+
+		for ( var i = 0, il = keys.length; i < il && retVal == null; i++ ) {
+
+			var key = keys[i];
+
+			if ( key.time === time ) {
+
+				retVal = key;
+
+			} else if ( key.time > time ) {
+
+				break;
+
+			}
+
+		}
+
+		return retVal;
+
+	};
+
+	function findTimeNdx ( keys, time) {
+
+		var ndx = -1;
+
+		for ( var i = 0, il = keys.length; i < il && ndx == -1; i++ ) {
+
+			var key = keys[i];
+
+			if ( key.time >= time ) {
+
+				ndx = i;
+
+			}
+
+		}
+
+		return ndx;
+
+	};
+
+	function interpolateKeys ( keys, key, ndx, fullSid ) {
+
+		var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx-1 : 0 ),
+			nextKey = getNextKeyWith( keys, fullSid, ndx+1 );
+
+		if ( prevKey && nextKey ) {
+
+			var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time),
+				prevTarget = prevKey.getTarget( fullSid ),
+				nextData = nextKey.getTarget( fullSid ).data,
+				prevData = prevTarget.data,
+				data;
+
+			if ( prevData.length ) {
+
+				data = [];
+
+				for ( var i = 0; i < prevData.length; ++i ) {
+
+					data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale;
+
+				}
+
+			} else {
+
+				data = prevData + ( nextData - prevData ) * scale;
+
+			}
+
+			key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data );
+
+		}
+
+	};
+
+	// Get next key with given sid
+
+	function getNextKeyWith( keys, fullSid, ndx ) {
+
+		for ( ; ndx < keys.length; ndx++ ) {
+
+			var key = keys[ ndx ];
+
+			if ( key.hasTarget( fullSid ) ) {
+
+				return key;
+
+			}
+
+		}
+
+		return null;
+
+	};
+
+	// Get previous key with given sid
+
+	function getPrevKeyWith( keys, fullSid, ndx ) {
+
+		ndx = ndx >= 0 ? ndx : ndx + keys.length;
+
+		for ( ; ndx >= 0; ndx-- ) {
+
+			var key = keys[ ndx ];
+
+			if ( key.hasTarget( fullSid ) ) {
+
+				return key;
+
+			}
+
+		}
+
+		return null;
+
+	};
+
 	function _Image() {
 
 		this.id = "";
@@ -1076,13 +1359,7 @@ THREE.ColladaLoader = function () {
 				case 'bind_shape_matrix':
 
 					var f = _floats(child.textContent);
-					this.bindShapeMatrix = new THREE.Matrix4();
-					this.bindShapeMatrix.set(
-						f[0], f[1], f[2], f[3],
-						f[4], f[5], f[6], f[7],
-						f[8], f[9], f[10], f[11],
-						f[12], f[13], f[14], f[15]
-						);
+					this.bindShapeMatrix = getConvertedMat4( f );
 					break;
 
 				case 'source':
@@ -1531,7 +1808,7 @@ THREE.ColladaLoader = function () {
 
 		for ( var i = 0; i < this.transforms.length; i ++ ) {
 
-			this.matrix.multiply( this.matrix, this.transforms[ i ].matrix );
+			this.transforms[ i ].apply( this.matrix );
 
 		}
 
@@ -1542,7 +1819,7 @@ THREE.ColladaLoader = function () {
 		this.sid = "";
 		this.type = "";
 		this.data = [];
-		this.matrix = new THREE.Matrix4();
+		this.obj = null;
 
 	};
 
@@ -1551,53 +1828,149 @@ THREE.ColladaLoader = function () {
 		this.sid = element.getAttribute( 'sid' );
 		this.type = element.nodeName;
 		this.data = _floats( element.textContent );
-
-		this.updateMatrix();
+		this.convert();
 
 		return this;
 
 	};
 
-	Transform.prototype.updateMatrix = function () {
+	Transform.prototype.convert = function () {
 
-		var angle = 0;
+		switch ( this.type ) {
 
-		this.matrix.identity();
+			case 'matrix':
+
+				this.obj = getConvertedMat4( this.data );
+				break;
+
+			case 'rotate':
+
+				this.angle = this.data[3] * TO_RADIANS;
+
+			case 'translate':
+
+				fixCoords( this.data, -1 );
+				this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] );
+				break;
+
+			case 'scale':
+
+				fixCoords( this.data, 1 );
+				this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] );
+				break;
+
+			default:
+				console.log( 'Can not convert Transform of type ' + this.type );
+				break;
+
+		}
+
+	};
+
+	Transform.prototype.apply = function ( matrix ) {
 
 		switch ( this.type ) {
 
 			case 'matrix':
 
-				this.matrix.set(
-					this.data[0], this.data[1], this.data[2], this.data[3],
-					this.data[4], this.data[5], this.data[6], this.data[7],
-					this.data[8], this.data[9], this.data[10], this.data[11],
-					this.data[12], this.data[13], this.data[14], this.data[15]
-					);
+				matrix.multiplySelf( this.obj );
 				break;
 
 			case 'translate':
 
-				this.matrix.setTranslation(this.data[0], this.data[1], this.data[2]);
+				matrix.translate( this.obj );
 				break;
 
 			case 'rotate':
 
-				angle = this.data[3] * (Math.PI / 180.0);
-				this.matrix.setRotationAxis(new THREE.Vector3(this.data[0], this.data[1], this.data[2]), angle);
+				matrix.rotateByAxis( this.obj, this.angle );
 				break;
 
 			case 'scale':
 
-				this.matrix.setScale(this.data[0], this.data[1], this.data[2]);
+				matrix.scale( this.obj );
 				break;
 
-			default:
+		}
+
+	};
+
+	Transform.prototype.update = function ( data, member ) {
+
+		switch ( this.type ) {
+
+			case 'matrix':
+
+				console.log( 'Currently not handling matrix transform updates' );
 				break;
 
-		}
+			case 'translate':
+			case 'scale':
+
+				switch ( member ) {
+
+					case 'X':
+
+						this.obj.x = data;
+						break;
+
+					case 'Y':
+
+						this.obj.y = data;
+						break;
+
+					case 'Z':
+
+						this.obj.z = data;
+						break;
+
+					default:
+
+						this.obj.x = data[ 0 ];
+						this.obj.y = data[ 1 ];
+						this.obj.z = data[ 2 ];
+						break;
+
+				}
+
+				break;
+
+			case 'rotate':
+
+				switch ( member ) {
+
+					case 'X':
+
+						this.obj.x = data;
+						break;
+
+					case 'Y':
+
+						this.obj.y = data;
+						break;
 
-		return this.matrix;
+					case 'Z':
+
+						this.obj.z = data;
+						break;
+
+					case 'ANGLE':
+
+						this.angle = data * TO_RADIANS;
+						break;
+
+					default:
+
+						this.obj.x = data[ 0 ];
+						this.obj.y = data[ 1 ];
+						this.obj.z = data[ 2 ];
+						this.angle = data[ 3 ] * TO_RADIANS;
+						break;
+
+				}
+				break;
+
+		}
 
 	};
 
@@ -1820,7 +2193,7 @@ THREE.ColladaLoader = function () {
 
 		for ( i = 0; i < vertexData.length; i += 3 ) {
 
-			var v = new THREE.Vertex( new THREE.Vector3( vertexData[ i ], vertexData[ i + 1 ], vertexData[ i + 2 ] ) );
+			var v = new THREE.Vertex( getConvertedVec3( vertexData, i ) );
 			this.geometry3js.vertices.push( v );
 
 		}
@@ -1898,7 +2271,7 @@ THREE.ColladaLoader = function () {
 
 						case 'NORMAL':
 
-							ns.push( new THREE.Vector3( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) );
+							ns.push( getConvertedVec3( source.data, idx32 ) );
 
 							break;
 
@@ -1936,7 +2309,7 @@ THREE.ColladaLoader = function () {
 
 				faces.push( new THREE.Face4( vs[0], vs[1], vs[2], vs[3], [ ns[0], ns[1], ns[2], ns[3] ], cs.length ? cs : new THREE.Color() ) );
 
-			} else if ( vcount > 4 ) {
+			} else if ( vcount > 4 && options.subdivideFaces ) {
 
 				var clr = cs.length ? cs : new THREE.Color(),
 					vec1, vec2, vec3, v1, v2, norm;
@@ -2248,13 +2621,7 @@ THREE.ColladaLoader = function () {
 					for ( var j = 0; j < this.data.length; j += 16 ) {
 
 						var s = this.data.slice( j, j + 16 );
-						var m = new THREE.Matrix4();
-						m.set(
-							s[0], s[1], s[2], s[3],
-							s[4], s[5], s[6], s[7],
-							s[8], s[9], s[10], s[11],
-							s[12], s[13], s[14], s[15]
-							);
+						var m = getConvertedMat4( s );
 						result.push( m );
 					}
 
@@ -2911,6 +3278,7 @@ THREE.ColladaLoader = function () {
 		this.animation = animation;
 		this.source = "";
 		this.target = "";
+		this.fullSid = null;
 		this.sid = null;
 		this.dotSyntax = null;
 		this.arrSyntax = null;
@@ -2932,19 +3300,16 @@ THREE.ColladaLoader = function () {
 		var dotSyntax = ( sid.indexOf(".") >= 0 );
 		var arrSyntax = ( sid.indexOf("(") >= 0 );
 
-		var arrIndices;
-		var member;
-
 		if ( dotSyntax ) {
 
 			parts = sid.split(".");
-			sid = parts.shift();
-			member = parts.shift();
+			this.sid = parts.shift();
+			this.member = parts.shift();
 
 		} else if ( arrSyntax ) {
 
-			arrIndices = sid.split("(");
-			sid = arrIndices.shift();
+			var arrIndices = sid.split("(");
+			this.sid = arrIndices.shift();
 
 			for (var j = 0; j < arrIndices.length; j ++ ) {
 
@@ -2952,13 +3317,17 @@ THREE.ColladaLoader = function () {
 
 			}
 
+			this.arrIndices = arrIndices;
+
+		} else {
+
+			this.sid = sid;
+
 		}
 
-		this.sid = sid;
+		this.fullSid = sid;
 		this.dotSyntax = dotSyntax;
 		this.arrSyntax = arrSyntax;
-		this.arrIndices = arrIndices;
-		this.member = member;
 
 		return this;
 
@@ -2971,6 +3340,7 @@ THREE.ColladaLoader = function () {
 		this.inputs = [];
 		this.input = null;
 		this.output = null;
+		this.strideOut = null;
 		this.interpolation = null;
 		this.startTime = null;
 		this.endTime = null;
@@ -3023,6 +3393,7 @@ THREE.ColladaLoader = function () {
 				case 'OUTPUT':
 
 					this.output = source.read();
+					this.strideOut = source.accessor.stride;
 					break;
 
 				case 'INTERPOLATION':
@@ -3069,6 +3440,168 @@ THREE.ColladaLoader = function () {
 
 	};
 
+	Sampler.prototype.getData = function ( type, ndx ) {
+
+		var data;
+
+		if ( this.strideOut > 1 ) {
+
+			data = [];
+			ndx *= this.strideOut;
+
+			for ( var i = 0; i < this.strideOut; ++i ) {
+
+				data[ i ] = this.output[ ndx + i ];
+
+			}
+
+			if ( this.strideOut === 3 ) {
+
+				switch ( type ) {
+
+					case 'rotate':
+					case 'translate':
+
+						fixCoords( data, -1 );
+						break;
+
+					case 'scale':
+
+						fixCoords( data, 1 );
+						break;
+
+				}
+
+			}
+
+		} else {
+
+			data = this.output[ ndx ];
+
+		}
+
+		return data;
+
+	};
+
+	function Key ( time ) {
+
+		this.targets = [];
+		this.time = time;
+
+	};
+
+	Key.prototype.addTarget = function ( fullSid, transform, member, data ) {
+
+		this.targets.push( {
+			sid: fullSid,
+			member: member,
+			transform: transform,
+			data: data
+		} );
+
+	};
+
+	Key.prototype.apply = function ( opt_sid ) {
+
+		for ( var i = 0; i < this.targets.length; ++i ) {
+
+			var target = this.targets[ i ];
+
+			if ( !opt_sid || target.sid === opt_sid ) {
+
+				target.transform.update( target.data, target.member );
+
+			}
+
+		}
+
+	};
+
+	Key.prototype.getTarget = function ( fullSid ) {
+
+		for ( var i = 0; i < this.targets.length; ++i ) {
+
+			if ( this.targets[ i ].sid === fullSid ) {
+
+				return this.targets[ i ];
+
+			}
+
+		}
+
+		return null;
+
+	};
+
+	Key.prototype.hasTarget = function ( fullSid ) {
+
+		for ( var i = 0; i < this.targets.length; ++i ) {
+
+			if ( this.targets[ i ].sid === fullSid ) {
+
+				return true;
+
+			}
+
+		}
+
+		return false;
+
+	};
+
+	// TODO: Currently only doing linear interpolation. Should support full COLLADA spec.
+	Key.prototype.interpolate = function ( nextKey, time ) {
+
+		for ( var i = 0; i < this.targets.length; ++i ) {
+
+			var target = this.targets[ i ],
+				nextTarget = nextKey.getTarget( target.sid ),
+				data;
+
+			if ( nextTarget ) {
+
+				var scale = ( time - this.time ) / ( nextKey.time - this.time ),
+					nextData = nextTarget.data,
+					prevData = target.data;
+
+				// check scale error
+
+				if ( scale < 0 || scale > 1 ) {
+
+					console.log( "Key.interpolate: Warning! Scale out of bounds:" + scale );
+					scale = scale < 0 ? 0 : 1;
+
+				}
+
+				if ( prevData.length ) {
+
+					data = [];
+
+					for ( var j = 0; j < prevData.length; ++j ) {
+
+						data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale;
+
+					}
+
+				} else {
+
+					data = prevData + ( nextData - prevData ) * scale;
+
+				}
+
+			} else {
+
+				data = target.data;
+
+			}
+
+			target.transform.update( data, target.member );
+
+		}
+
+	};
+
 	function _source ( element ) {
 
 		var id = element.getAttribute( 'id' );
@@ -3248,13 +3781,246 @@ THREE.ColladaLoader = function () {
 
 	};
 
+	// Up axis conversion
+
+	function setUpConversion () {
+
+		if ( !options.convertUpAxis || colladaUp === options.upAxis ) {
+
+			upConversion = null;
+
+		} else {
+
+			switch ( colladaUp ) {
+
+				case 'X':
+
+					upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ';
+					break;
+
+				case 'Y':
+
+					upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ';
+					break;
+
+				case 'Z':
+
+					upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY';
+					break;
+
+			}
+
+		}
+
+	};
+
+	function fixCoords ( data, sign ) {
+
+		if ( !options.convertUpAxis || colladaUp === options.upAxis ) {
+
+			return;
+
+		}
+
+		switch ( upConversion ) {
+
+			case 'XtoY':
+
+				var tmp = data[ 0 ];
+				data[ 0 ] = sign * data[ 1 ];
+				data[ 1 ] = tmp;
+				break;
+
+			case 'XtoZ':
+
+				var tmp = data[ 2 ];
+				data[ 2 ] = data[ 1 ];
+				data[ 1 ] = data[ 0 ];
+				data[ 0 ] = tmp;
+				break;
+
+			case 'YtoX':
+
+				var tmp = data[ 0 ];
+				data[ 0 ] = data[ 1 ];
+				data[ 1 ] = sign * tmp;
+				break;
+
+			case 'YtoZ':
+
+				var tmp = data[ 1 ];
+				data[ 1 ] = sign * data[ 2 ];
+				data[ 2 ] = tmp;
+				break;
+
+			case 'ZtoX':
+
+				var tmp = data[ 0 ];
+				data[ 0 ] = data[ 1 ];
+				data[ 1 ] = data[ 2 ];
+				data[ 2 ] = tmp;
+				break;
+
+			case 'ZtoY':
+
+				var tmp = data[ 1 ];
+				data[ 1 ] = data[ 2 ];
+				data[ 2 ] = sign * tmp;
+				break;
+
+		}
+
+	};
+
+	function getConvertedVec3 ( data, offset ) {
+
+		var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ];
+		fixCoords( arr, -1 );
+		return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] );
+
+	};
+
+	function getConvertedMat4 ( data ) {
+
+		if ( options.convertUpAxis ) {
+
+			// First fix rotation and scale
+
+			// Columns first
+			var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ];
+			fixCoords( arr, -1 );
+			data[ 0 ] = arr[ 0 ];
+			data[ 4 ] = arr[ 1 ];
+			data[ 8 ] = arr[ 2 ];
+			arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ];
+			fixCoords( arr, -1 );
+			data[ 1 ] = arr[ 0 ];
+			data[ 5 ] = arr[ 1 ];
+			data[ 9 ] = arr[ 2 ];
+			arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ];
+			fixCoords( arr, -1 );
+			data[ 2 ] = arr[ 0 ];
+			data[ 6 ] = arr[ 1 ];
+			data[ 10 ] = arr[ 2 ];
+			// Rows second
+			arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ];
+			fixCoords( arr, -1 );
+			data[ 0 ] = arr[ 0 ];
+			data[ 1 ] = arr[ 1 ];
+			data[ 2 ] = arr[ 2 ];
+			arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ];
+			fixCoords( arr, -1 );
+			data[ 4 ] = arr[ 0 ];
+			data[ 5 ] = arr[ 1 ];
+			data[ 6 ] = arr[ 2 ];
+			arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ];
+			fixCoords( arr, -1 );
+			data[ 8 ] = arr[ 0 ];
+			data[ 9 ] = arr[ 1 ];
+			data[ 10 ] = arr[ 2 ];
+
+			// Now fix translation
+			arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ];
+			fixCoords( arr, -1 );
+			data[ 3 ] = arr[ 0 ];
+			data[ 7 ] = arr[ 1 ];
+			data[ 11 ] = arr[ 2 ];
+
+		}
+
+		return new THREE.Matrix4(
+			data[0], data[1], data[2], data[3],
+			data[4], data[5], data[6], data[7],
+			data[8], data[9], data[10], data[11],
+			data[12], data[13], data[14], data[15]
+			);
+
+	};
+
+	function getConvertedMember ( member ) {
+
+		if ( options.convertUpAxis ) {
+
+			switch ( member ) {
+
+				case 'X':
+
+					switch ( upConversion ) {
+
+						case 'XtoY':
+						case 'XtoZ':
+						case 'YtoX':
+
+							member = 'Y';
+							break;
+
+						case 'ZtoX':
+
+							member = 'Z';
+							break;
+
+					}
+
+					break;
+
+				case 'Y':
+
+					switch ( upConversion ) {
+
+						case 'XtoY':
+						case 'YtoX':
+						case 'ZtoX':
+
+							member = 'X';
+							break;
+
+						case 'XtoZ':
+						case 'YtoZ':
+						case 'ZtoY':
+
+							member = 'Z';
+							break;
+
+					}
+
+					break;
+
+				case 'Z':
+
+					switch ( upConversion ) {
+
+						case 'XtoZ':
+
+							member = 'X';
+							break;
+
+						case 'YtoZ':
+						case 'ZtoX':
+						case 'ZtoY':
+
+							member = 'Y';
+							break;
+
+					}
+
+					break;
+
+			}
+
+		}
+
+		return member;
+
+	};
+
 	return {
 
 		load: load,
 		parse: parse,
 		setPreferredShading: setPreferredShading,
 		applySkin: applySkin,
-		geometries : geometries
+		geometries : geometries,
+		options: options
 
 	};