text: /**
  * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com
  * @author Tony Parisi / http://www.tonyparisi.com/
  */
  
  THREE.ColladaLoader = function () {
  
  	var COLLADA = null;
  	var scene = null;
  	var visualScene;
  	var rootObject;
  	var kinematicsModel;
  
  	var readyCallbackFunc = null;
  
  	var sources = {};
  	var images = {};
  	var animations = {};
  	var controllers = {};
  	var geometries = {};
  	var materials = {};
  	var effects = {};
  	var cameras = {};
  	var lights = {};
  
  	var animData;
  	var kinematics;
  	var visualScenes;
  	var kinematicsModel;
  	var baseUrl;
  	var morphs;
  	var skins;
  
  	var flip_uv = true;
  	var preferredShading = THREE.SmoothShading;
  
  	var options = {
  		// Force Geometry to always be centered at the local origin of the
  		// containing Mesh.
  		centerGeometry: false,
  
  		// 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',
  
  		// For reflective or refractive materials we'll use this cubemap
  		defaultEnvMap: null
  
  	};
  
  	var colladaUnit = 1.0;
  	var colladaUp = 'Y';
  	var upConversion = null;
  
  	function load ( url, readyCallback, progressCallback ) {
  
  		var length = 0;
  
  		if ( document.implementation && document.implementation.createDocument ) {
  
  			var request = new XMLHttpRequest();
  
  			request.onreadystatechange = function() {
  
  				if( request.readyState == 4 ) {
  
  					if( request.status == 0 || request.status == 200 ) {
  
  
  						if ( request.responseXML ) {
  
  							readyCallbackFunc = readyCallback;
  							parse( request.responseXML, undefined, url );
  
  						} else if ( request.responseText ) {
  
  							readyCallbackFunc = readyCallback;
  							var xmlParser = new DOMParser();
  							var responseXML = xmlParser.parseFromString( request.responseText, "application/xml" );
  							parse( responseXML, undefined, url );
  
  						} else {
  
  							console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" );
  
  						}
  
  					}
  
  				} else if ( request.readyState == 3 ) {
  
  					if ( progressCallback ) {
  
  						if ( length == 0 ) {
  
  							length = request.getResponseHeader( "Content-Length" );
  
  						}
  
  						progressCallback( { total: length, loaded: request.responseText.length } );
  
  					}
  
  				}
  
  			}
  
  			request.open( "GET", url, true );
  			request.send( null );
  
  		} else {
  
  			alert( "Don't know how to parse XML!" );
  
  		}
  
  	}
  
  	function parse( doc, callBack, url ) {
  
  		COLLADA = doc;
  		callBack = callBack || readyCallbackFunc;
  
  		if ( url !== undefined ) {
  
  			var parts = url.split( '/' );
  			parts.pop();
  			baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/';
  
  		}
  
  		parseAsset();
  		setUpConversion();
  		images = parseLib( "library_images image", _Image, "image" );
  		materials = parseLib( "library_materials material", Material, "material" );
  		effects = parseLib( "library_effects effect", Effect, "effect" );
  		geometries = parseLib( "library_geometries geometry", Geometry, "geometry" );
  		cameras = parseLib( "library_cameras camera", Camera, "camera" );
  		lights = parseLib( "library_lights light", Light, "light" );
  		controllers = parseLib( "library_controllers controller", Controller, "controller" );
  		animations = parseLib( "library_animations animation", Animation, "animation" );
  		visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" );
  		kinematicsModels = parseLib( "library_kinematics_models kinematics_model", KinematicsModel, "kinematics_model" );
  
  		morphs = [];
  		skins = [];
  
  		visualScene = parseScene();
  		scene = new THREE.Scene();
  		rootObject = new THREE.Object3D();
  
  		for ( var i = 0; i < visualScene.nodes.length; i ++ ) {
  
  			rootObject.add( createSceneGraph( visualScene.nodes[ i ] ) );
  
  		}
      scene.add( rootObject );
  
  		// unit conversion
  		scene.scale.multiplyScalar( colladaUnit );
  
  		createAnimations();
  
  		kinematicsModel = parseKinematicsModel();
  		createKinematics();
  
  		var result = {
  
  			scene: scene,
  			morphs: morphs,
  			skins: skins,
  			animations: animData,
  			kinematics: kinematics,
  			rootObject: rootObject,
  			dae: {
  				images: images,
  				materials: materials,
  				cameras: cameras,
  				lights: lights,
  				effects: effects,
  				geometries: geometries,
  				controllers: controllers,
  				animations: animations,
  				visualScenes: visualScenes,
  				visualScene: visualScene,
  				scene: visualScene,
  				kinematicsModels: kinematicsModels,
  				kinematicsModel: kinematicsModel
  			}
  
  		};
  
  		if ( callBack ) {
  
  			callBack( result );
  
  		}
  
  		return result;
  
  	}
  
  	function setPreferredShading ( shading ) {
  
  		preferredShading = shading;
  
  	}
  
  	function parseAsset () {
  
  		var elements = COLLADA.querySelectorAll('asset');
  
  		var element = elements[0];
  
  		if ( element && element.childNodes ) {
  
  			for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  				var child = element.childNodes[ 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.querySelectorAll(q);
  
  		var lib = {};
  
  		var i = 0;
  
  		var elementsLength = elements.length;
  
  		for ( var j = 0; j < elementsLength; j ++ ) {
  
  			var element = elements[j];
  			var daeElement = ( new classSpec() ).parse( element );
  
  			if ( !daeElement.id || daeElement.id.length == 0 ) daeElement.id = prefix + ( i ++ );
  			lib[ daeElement.id ] = daeElement;
  
  		}
  
  		return lib;
  
  	}
  
  	function parseScene() {
  
  		var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0];
  
  		if ( sceneElement ) {
  
  			var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' );
  			return visualScenes[ url.length > 0 ? url : 'visual_scene0' ];
  
  		} else {
  
  			return null;
  
  		}
  
  	}
  
  	function parseKinematicsModel() {
  
  		var kinematicsModelElement = COLLADA.querySelectorAll('instance_kinematics_model')[0];
  
  		if ( kinematicsModelElement ) {
  
  			var url = kinematicsModelElement.getAttribute( 'url' ).replace(/^#/, '');
  			return kinematicsModels[ url.length > 0 ? url : 'kinematics_model0' ];
  
  		} else {
  
  			return null;
  
  		}
  
  	}
  
  	function createAnimations() {
  
  		animData = [];
  
  		// fill in the keys
  		recurseHierarchy( scene );
  
  	}
  
  	function recurseHierarchy( node ) {
  
  		var n = visualScene.getChildById( node.id, 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
  			};
  
  			animData.push(newData);
  
  			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: []
  				} );
  
  			}
  
  		}
  
  		return newData;
  
  	}
  
  	function calcAnimationBounds () {
  
  		var start = 1000000;
  		var end = -start;
  		var frames = 0;
  		var ID;
  		for ( var id in animations ) {
  
  			var animation = animations[ id ];
  			ID = ID || animation.id;
  			for ( var i = 0; i < animation.sampler.length; i ++ ) {
  
  				var sampler = animation.sampler[ i ];
  
  				sampler.create();
  
  				start = Math.min( start, sampler.startTime );
  				end = Math.max( end, sampler.endTime );
  				frames = Math.max( frames, sampler.input.length );
  
  			}
  
  		}
  
  		return { start:start, end:end, frames:frames,ID:ID };
  
  	}
  
  	function createMorph ( geometry, ctrl ) {
  
  		var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl;
  
  		if ( !morphCtrl || !morphCtrl.morph ) {
  
  			console.log("could not find morph controller!");
  			return;
  
  		}
  
  		var morph = morphCtrl.morph;
  
  		for ( var i = 0; i < morph.targets.length; i ++ ) {
  
  			var target_id = morph.targets[ i ];
  			var daeGeometry = geometries[ target_id ];
  
  			if ( !daeGeometry.mesh ||
  				 !daeGeometry.mesh.primitives ||
  				 !daeGeometry.mesh.primitives.length ) {
  				 continue;
  			}
  
  			var target = daeGeometry.mesh.primitives[ 0 ].geometry;
  
  			if ( target.vertices.length === geometry.vertices.length ) {
  
  				geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } );
  
  			}
  
  		}
  
  		geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } );
  
  	};
  
  	function createSkin ( geometry, ctrl, applyBindShape ) {
  
  		var skinCtrl = controllers[ ctrl.url ];
  
  		if ( !skinCtrl || !skinCtrl.skin ) {
  
  			console.log( "could not find skin controller!" );
  			return;
  
  		}
  
  		if ( !ctrl.skeleton || !ctrl.skeleton.length ) {
  
  			console.log( "could not find the skeleton for the skin!" );
  			return;
  
  		}
  
  		var skin = skinCtrl.skin;
  		var skeleton = visualScene.getChildById( ctrl.skeleton[ 0 ] );
  		var hierarchy = [];
  
  		applyBindShape = applyBindShape !== undefined ? applyBindShape : true;
  
  		var bones = [];
  		geometry.skinWeights = [];
  		geometry.skinIndices = [];
  
  		//createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 );
  		//createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights );
  
  		/*
  		geometry.animation = {
  			name: 'take_001',
  			fps: 30,
  			length: 2,
  			JIT: true,
  			hierarchy: hierarchy
  		};
  		*/
  
  		if ( applyBindShape ) {
  
  			for ( var i = 0; i < geometry.vertices.length; i ++ ) {
  
  				geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix );
  
  			}
  
  		}
  
  	}
  
  	function setupSkeleton ( node, bones, frame, parent ) {
  
  		node.world = node.world || new THREE.Matrix4();
  		node.localworld = node.localworld || new THREE.Matrix4();
  		node.world.copy( node.matrix );
  		node.localworld.copy( node.matrix );
  
  		if ( node.channels && node.channels.length ) {
  
  			var channel = node.channels[ 0 ];
  			var m = channel.sampler.output[ frame ];
  
  			if ( m instanceof THREE.Matrix4 ) {
  
  				node.world.copy( m );
  				node.localworld.copy(m);
  				if(frame == 0)
  					node.matrix.copy(m);
  			}
  
  		}
  
  		if ( parent ) {
  
  			node.world.multiplyMatrices( parent, node.world );
  
  		}
  
  		bones.push( node );
  
  		for ( var i = 0; i < node.nodes.length; i ++ ) {
  
  			setupSkeleton( node.nodes[ i ], bones, frame, node.world );
  
  		}
  
  	}
  
  	function setupSkinningMatrices ( bones, skin ) {
  
  		// FIXME: this is dumb...
  
  		for ( var i = 0; i < bones.length; i ++ ) {
  
  			var bone = bones[ i ];
  			var found = -1;
  
  			if ( bone.type != 'JOINT' ) continue;
  
  			for ( var j = 0; j < skin.joints.length; j ++ ) {
  
  				if ( bone.sid == skin.joints[ j ] ) {
  
  					found = j;
  					break;
  
  				}
  
  			}
  
  			if ( found >= 0 ) {
  
  				var inv = skin.invBindMatrices[ found ];
  
  				bone.invBindMatrix = inv;
  				bone.skinningMatrix = new THREE.Matrix4();
  				bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi)
  				bone.animatrix = new THREE.Matrix4();
  
  				bone.animatrix.copy(bone.localworld);
  				bone.weights = [];
  
  				for ( var j = 0; j < skin.weights.length; j ++ ) {
  
  					for (var k = 0; k < skin.weights[ j ].length; k ++ ) {
  
  						var w = skin.weights[ j ][ k ];
  
  						if ( w.joint == found ) {
  
  							bone.weights.push( w );
  
  						}
  
  					}
  
  				}
  
  			} else {
  
  				console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." );
  
  				bone.skinningMatrix = new THREE.Matrix4();
  				bone.weights = [];
  
  			}
  		}
  
  	}
  
  	//Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix
  	function flattenSkeleton(skeleton) {
  
  		var list = [];
  		var walk = function(parentid, node, list) {
  
  			var bone = {};
  			bone.name = node.sid;
  			bone.parent = parentid;
  			bone.matrix = node.matrix;
  			var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()];
  			bone.matrix.decompose(data[0],data[1],data[2]);
  
  			bone.pos = [data[0].x,data[0].y,data[0].z];
  
  			bone.scl = [data[2].x,data[2].y,data[2].z];
  			bone.rotq = [data[1].x,data[1].y,data[1].z,data[1].w];
  			list.push(bone);
  
  			for(var i in node.nodes) {
  
  				walk(node.sid,node.nodes[i],list);
  
  			}
  
  		};
  
  		walk(-1,skeleton,list);
  		return list;
  
  	}
  
  	//Move the vertices into the pose that is proper for the start of the animation
  	function skinToBindPose(geometry,skeleton,skinController) {
  
  		var bones = [];
  		setupSkeleton( skeleton, bones, -1 );
  		setupSkinningMatrices( bones, skinController.skin );
  		v = new THREE.Vector3();
  		var skinned = [];
  
  		for(var i =0; i < geometry.vertices.length; i++) {
  
  			skinned.push(new THREE.Vector3());
  
  		}
  
  		for ( i = 0; i < bones.length; i ++ ) {
  
  			if ( bones[ i ].type != 'JOINT' ) continue;
  
  			for ( j = 0; j < bones[ i ].weights.length; j ++ ) {
  
  				w = bones[ i ].weights[ j ];
  				vidx = w.index;
  				weight = w.weight;
  
  				o = geometry.vertices[vidx];
  				s = skinned[vidx];
  
  				v.x = o.x;
  				v.y = o.y;
  				v.z = o.z;
  
  				v.applyMatrix4( bones[i].skinningMatrix );
  
  				s.x += (v.x * weight);
  				s.y += (v.y * weight);
  				s.z += (v.z * weight);
  			}
  
  		}
  
  		for(var i =0; i < geometry.vertices.length; i++) {
  
  			geometry.vertices[i] = skinned[i];
  
  		}
  
  	}
  
  	function applySkin ( geometry, instanceCtrl, frame ) {
  
  		var skinController = controllers[ instanceCtrl.url ];
  
  		frame = frame !== undefined ? frame : 40;
  
  		if ( !skinController || !skinController.skin ) {
  
  			console.log( 'ColladaLoader: Could not find skin controller.' );
  			return;
  
  		}
  
  		if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) {
  
  			console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' );
  			return;
  
  		}
  
  		var animationBounds = calcAnimationBounds();
  		var skeleton = visualScene.getChildById( instanceCtrl.skeleton[0], true ) ||
  					   visualScene.getChildBySid( instanceCtrl.skeleton[0], true );
  
  		//flatten the skeleton into a list of bones
  		var bonelist = flattenSkeleton(skeleton);
  		var joints = skinController.skin.joints;
  
  		//sort that list so that the order reflects the order in the joint list
  		var sortedbones = [];
  		for(var i = 0; i < joints.length; i++) {
  
  			for(var j =0; j < bonelist.length; j++) {
  
  				if(bonelist[j].name == joints[i]) {
  
  					sortedbones[i] = bonelist[j];
  
  				}
  
  			}
  
  		}
  
  		//hook up the parents by index instead of name
  		for(var i = 0; i < sortedbones.length; i++) {
  
  			for(var j =0; j < sortedbones.length; j++) {
  
  				if(sortedbones[i].parent == sortedbones[j].name) {
  
  					sortedbones[i].parent = j;
  
  				}
  
  			}
  
  		}
  
  
  		var i, j, w, vidx, weight;
  		var v = new THREE.Vector3(), o, s;
  
  		// move vertices to bind shape
  		for ( i = 0; i < geometry.vertices.length; i ++ ) {
  					geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix );
  		}
  
  		var skinIndices = [];
  		var skinWeights = [];
  		var weights = skinController.skin.weights;
  
  		//hook up the skin weights
  		// TODO -  this might be a good place to choose greatest 4 weights
  		for(var i =0; i < weights.length; i++) {
  
  			var indicies = new THREE.Vector4(weights[i][0]?weights[i][0].joint:0,weights[i][1]?weights[i][1].joint:0,weights[i][2]?weights[i][2].joint:0,weights[i][3]?weights[i][3].joint:0);
  			var weight = new THREE.Vector4(weights[i][0]?weights[i][0].weight:0,weights[i][1]?weights[i][1].weight:0,weights[i][2]?weights[i][2].weight:0,weights[i][3]?weights[i][3].weight:0);
  
  			skinIndices.push(indicies);
  			skinWeights.push(weight);
  
  		}
  
  		geometry.skinIndices = skinIndices;
  		geometry.skinWeights = skinWeights;
  		geometry.bones = sortedbones;
  		// process animation, or simply pose the rig if no animation
  
  		//create an animation for the animated bones
  		//NOTE: this has no effect when using morphtargets
  		var animationdata = {"name":animationBounds.ID,"fps":30,"length":animationBounds.frames/30,"hierarchy":[]};
  
  		for(var j =0; j < sortedbones.length; j++) {
  
  			animationdata.hierarchy.push({parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[]});
  
  		}
  
  		console.log( 'ColladaLoader:', animationBounds.ID + ' has ' + sortedbones.length + ' bones.' );
  
  
  
  		skinToBindPose(geometry,skeleton,skinController);
  
  
  		for ( frame = 0; frame < animationBounds.frames; frame ++ ) {
  
  			var bones = [];
  			var skinned = [];
  			// process the frame and setup the rig with a fresh
  			// transform, possibly from the bone's animation channel(s)
  
  			setupSkeleton( skeleton, bones, frame );
  			setupSkinningMatrices( bones, skinController.skin );
  
  			for(var i = 0; i < bones.length; i ++) {
  
  				for(var j = 0; j < animationdata.hierarchy.length; j ++) {
  
  					if(animationdata.hierarchy[j].name == bones[i].sid) {
  
  						var key = {};
  						key.time = (frame/30);
  						key.matrix = bones[i].animatrix;
  
  						if(frame == 0)
  							bones[i].matrix = key.matrix;
  
  						var data = [new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3()];
  						key.matrix.decompose(data[0],data[1],data[2]);
  
  						key.pos = [data[0].x,data[0].y,data[0].z];
  
  						key.scl = [data[2].x,data[2].y,data[2].z];
  						key.rot = data[1];
  
  						animationdata.hierarchy[j].keys.push(key);
  
  					}
  
  				}
  
  			}
  
  			geometry.animation = animationdata;
  
  		}
  
  	};
  
  	function createKinematics() {
  
  		if ( kinematicsModel && kinematicsModel.joints.length === 0 ) {
  			kinematics = undefined;
  			return;
  		}
  
  		var jointMap = {};
  
  		var _addToMap = function( jointIndex, parentVisualElement ) {
  
  			var parentVisualElementId = parentVisualElement.getAttribute( 'id' );
  			var colladaNode = visualScene.getChildById( parentVisualElementId, true );
  			var joint = kinematicsModel.joints[ jointIndex ];
  
  			scene.traverse(function( node ) {
  
  				if ( node.id == parentVisualElementId ) {
  
  					jointMap[ jointIndex ] = {
  						node: node,
  						transforms: colladaNode.transforms,
  						joint: joint,
  						position: joint.zeroPosition
  					};
  
  				}
  
  			});
  
  		};
  
  		kinematics = {
  
  			joints: kinematicsModel && kinematicsModel.joints,
  
  			getJointValue: function( jointIndex ) {
  
  				var jointData = jointMap[ jointIndex ];
  
  				if ( jointData ) {
  
  					return jointData.position;
  
  				} else {
  
  					console.log( 'getJointValue: joint ' + jointIndex + ' doesn\'t exist' );
  
  				}
  
  			},
  
  			setJointValue: function( jointIndex, value ) {
  
  				var jointData = jointMap[ jointIndex ];
  
  				if ( jointData ) {
  
  					var joint = jointData.joint;
  
  					if ( value > joint.limits.max || value < joint.limits.min ) {
  
  						console.log( 'setJointValue: joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ')' );
  
  					} else if ( joint.static ) {
  
  						console.log( 'setJointValue: joint ' + jointIndex + ' is static' );
  
  					} else {
  
  						var threejsNode = jointData.node;
  						var axis = joint.axis;
  						var transforms = jointData.transforms;
  
  						var matrix = new THREE.Matrix4();
  
  						for (i = 0; i < transforms.length; i++ ) {
  
  							var transform = transforms[ i ];
  
  							// kinda ghetto joint detection
  							if ( transform.sid && transform.sid.indexOf( 'joint' + jointIndex ) !== -1 ) {
  
  								// apply actual joint value here
  								switch ( joint.type ) {
  
  									case 'revolute':
  
  										matrix.multiply( m1.makeRotationAxis( axis, THREE.Math.degToRad(value) ) );
  										break;
  
  									case 'prismatic':
  
  										matrix.multiply( m1.makeTranslation(axis.x * value, axis.y * value, axis.z * value ) );
  										break;
  
  									default:
  
  										console.warn( 'setJointValue: unknown joint type: ' + joint.type );
  										break;
  
  								}
  
  							} else {
  
  								var m1 = new THREE.Matrix4();
  
  								switch ( transform.type ) {
  
  									case 'matrix':
  
  										matrix.multiply( transform.obj );
  
  										break;
  
  									case 'translate':
  
  										matrix.multiply( m1.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );
  
  										break;
  
  									case 'rotate':
  
  										matrix.multiply( m1.makeRotationAxis( transform.obj, transform.angle ) );
  
  										break;
  
  								}
  							}
  						}
  
  						// apply the matrix to the threejs node
  						var elementsFloat32Arr = matrix.elements;
  						var elements = Array.prototype.slice.call( elementsFloat32Arr );
  
  						var elementsRowMajor = [
  							elements[ 0 ],
  							elements[ 4 ],
  							elements[ 8 ],
  							elements[ 12 ],
  							elements[ 1 ],
  							elements[ 5 ],
  							elements[ 9 ],
  							elements[ 13 ],
  							elements[ 2 ],
  							elements[ 6 ],
  							elements[ 10 ],
  							elements[ 14 ],
  							elements[ 3 ],
  							elements[ 7 ],
  							elements[ 11 ],
  							elements[ 15 ]
  						];
  
  						threejsNode.matrix.set.apply( threejsNode.matrix, elementsRowMajor );
  						threejsNode.matrix.decompose( threejsNode.position, threejsNode.quaternion, threejsNode.scale );
  					}
  
  				} else {
  
  					console.log( 'setJointValue: joint ' + jointIndex + ' doesn\'t exist' );
  
  				}
  
  			}
  
  		};
  
  		var element = COLLADA.querySelector('scene instance_kinematics_scene');
  
  		if ( element ) {
  
  			for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  				var child = element.childNodes[ i ];
  
  				if ( child.nodeType != 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'bind_joint_axis':
  
  						var visualTarget = child.getAttribute( 'target' ).split( '/' ).pop();
  						var axis = child.querySelector('axis param').textContent;
  						var jointIndex = parseInt( axis.split( 'joint' ).pop().split( '.' )[0] );
  						var visualTargetElement = COLLADA.querySelector( '[sid="' + visualTarget + '"]' );
  
  						if ( visualTargetElement ) {
  							var parentVisualElement = visualTargetElement.parentElement;
  							_addToMap(jointIndex, parentVisualElement);
  						}
  
  						break;
  
  					default:
  
  						break;
  
  				}
  
  			}
  		}
  
  	};
  
  	function createSceneGraph ( node, parent ) {
  
  		var obj = new THREE.Object3D();
  		var skinned = false;
  		var skinController;
  		var morphController;
  		var i, j;
  
  		// FIXME: controllers
  
  		for ( i = 0; i < node.controllers.length; i ++ ) {
  
  			var controller = controllers[ node.controllers[ i ].url ];
  
  			switch ( controller.type ) {
  
  				case 'skin':
  
  					if ( geometries[ controller.skin.source ] ) {
  
  						var inst_geom = new InstanceGeometry();
  
  						inst_geom.url = controller.skin.source;
  						inst_geom.instance_material = node.controllers[ i ].instance_material;
  
  						node.geometries.push( inst_geom );
  						skinned = true;
  						skinController = node.controllers[ i ];
  
  					} else if ( controllers[ controller.skin.source ] ) {
  
  						// urgh: controller can be chained
  						// handle the most basic case...
  
  						var second = controllers[ controller.skin.source ];
  						morphController = second;
  					//	skinController = node.controllers[i];
  
  						if ( second.morph && geometries[ second.morph.source ] ) {
  
  							var inst_geom = new InstanceGeometry();
  
  							inst_geom.url = second.morph.source;
  							inst_geom.instance_material = node.controllers[ i ].instance_material;
  
  							node.geometries.push( inst_geom );
  
  						}
  
  					}
  
  					break;
  
  				case 'morph':
  
  					if ( geometries[ controller.morph.source ] ) {
  
  						var inst_geom = new InstanceGeometry();
  
  						inst_geom.url = controller.morph.source;
  						inst_geom.instance_material = node.controllers[ i ].instance_material;
  
  						node.geometries.push( inst_geom );
  						morphController = node.controllers[ i ];
  
  					}
  
  					console.log( 'ColladaLoader: Morph-controller partially supported.' );
  
  				default:
  					break;
  
  			}
  
  		}
  
  		// geometries
  
  		var double_sided_materials = {};
  
  		for ( i = 0; i < node.geometries.length; i ++ ) {
  
  			var instance_geometry = node.geometries[i];
  			var instance_materials = instance_geometry.instance_material;
  			var geometry = geometries[ instance_geometry.url ];
  			var used_materials = {};
  			var used_materials_array = [];
  			var num_materials = 0;
  			var first_material;
  
  			if ( geometry ) {
  
  				if ( !geometry.mesh || !geometry.mesh.primitives )
  					continue;
  
  				if ( obj.name.length == 0 ) {
  
  					obj.name = geometry.id;
  
  				}
  
  				// collect used fx for this geometry-instance
  
  				if ( instance_materials ) {
  
  					for ( j = 0; j < instance_materials.length; j ++ ) {
  
  						var instance_material = instance_materials[ j ];
  						var mat = materials[ instance_material.target ];
  						var effect_id = mat.instance_effect.url;
  						var shader = effects[ effect_id ].shader;
  						var material3js = shader.material;
  
  						if ( geometry.doubleSided ) {
  
  							if ( !( instance_material.symbol in double_sided_materials ) ) {
  
  								var _copied_material = material3js.clone();
  								_copied_material.side = THREE.DoubleSide;
  								double_sided_materials[ instance_material.symbol ] = _copied_material;
  
  							}
  
  							material3js = double_sided_materials[ instance_material.symbol ];
  
  						}
  
  						material3js.opacity = !material3js.opacity ? 1 : material3js.opacity;
  						used_materials[ instance_material.symbol ] = num_materials;
  						used_materials_array.push( material3js );
  						first_material = material3js;
  						first_material.name = mat.name == null || mat.name === '' ? mat.id : mat.name;
  						num_materials ++;
  
  					}
  
  				}
  
  				var mesh;
  				var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } );
  				var geom = geometry.mesh.geometry3js;
  
  				if ( num_materials > 1 ) {
  
  					material = new THREE.MeshFaceMaterial( used_materials_array );
  
  					for ( j = 0; j < geom.faces.length; j ++ ) {
  
  						var face = geom.faces[ j ];
  						face.materialIndex = used_materials[ face.daeMaterial ]
  
  					}
  
  				}
  
  				if ( skinController !== undefined ) {
  
  
  					applySkin( geom, skinController );
  
  					if ( geom.morphTargets.length > 0 ) {
  
  						material.morphTargets = true;
  						material.skinning = false;
  
  					} else {
  
  						material.morphTargets = false;
  						material.skinning = true;
  
  					}
  
  
  					mesh = new THREE.SkinnedMesh( geom, material, false );
  
  
  					//mesh.skeleton = skinController.skeleton;
  					//mesh.skinController = controllers[ skinController.url ];
  					//mesh.skinInstanceController = skinController;
  					mesh.name = 'skin_' + skins.length;
  
  
  
  					//mesh.animationHandle.setKey(0);
  					skins.push( mesh );
  
  				} else if ( morphController !== undefined ) {
  
  					createMorph( geom, morphController );
  
  					material.morphTargets = true;
  
  					mesh = new THREE.Mesh( geom, material );
  					mesh.name = 'morph_' + morphs.length;
  
  					morphs.push( mesh );
  
  				} else {
  
  					mesh = new THREE.Mesh( geom, material );
  					// mesh.geom.name = geometry.id;
  
  				}
  
  				// N.B.: TP says this is not a great default behavior. It's a nice
  				// optimization to flatten the hierarchy but this should be done
  				// only if requested by the user via a flag. For now I undid it
  				// and fixed the character animation example that uses it
  				// node.geometries.length > 1 ? obj.add( mesh ) : obj = mesh;
  				obj.add(mesh);
  
  			}
  
  		}
  
  		for ( i = 0; i < node.cameras.length; i ++ ) {
  
  			var instance_camera = node.cameras[i];
  			var cparams = cameras[instance_camera.url];
  
  			var cam = new THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio),
  					parseFloat(cparams.znear), parseFloat(cparams.zfar));
  
  			obj.add(cam);
  		}
  
  		for ( i = 0; i < node.lights.length; i ++ ) {
  
  			var light = null;
  			var instance_light = node.lights[i];
  			var lparams = lights[instance_light.url];
  
  			if ( lparams && lparams.technique ) {
  
  				var color = lparams.color.getHex();
  				var intensity = lparams.intensity;
  				var distance = lparams.distance;
  				var angle = lparams.falloff_angle;
  				var exponent; // Intentionally undefined, don't know what this is yet
  
  				switch ( lparams.technique ) {
  
  					case 'directional':
  
  						light = new THREE.DirectionalLight( color, intensity, distance );
  						light.position.set(0, 0, 1);
  						break;
  
  					case 'point':
  
  						light = new THREE.PointLight( color, intensity, distance );
  						break;
  
  					case 'spot':
  
  						light = new THREE.SpotLight( color, intensity, distance, angle, exponent );
  						light.position.set(0, 0, 1);
  						break;
  
  					case 'ambient':
  
  						light = new THREE.AmbientLight( color );
  						break;
  
  				}
  
  			}
  
  			if (light) {
  				obj.add(light);
  			}
  		}
  
  		obj.name = node.name || node.id || "";
  		obj.id = node.id || "";
  		obj.layer = node.layer || "";
  		obj.matrix = node.matrix;
  		obj.matrix.decompose( obj.position, obj.quaternion, obj.scale );
  
  		if ( options.centerGeometry && obj.geometry ) {
  
  			var delta = obj.geometry.center();
  			delta.multiply( obj.scale );
  			delta.applyQuaternion( obj.quaternion );
  
  			obj.position.sub( delta );
  
  		}
  
  		for ( i = 0; i < node.nodes.length; i ++ ) {
  
  			obj.add( createSceneGraph( node.nodes[i], node ) );
  
  		}
  
  		return obj;
  
  	};
  
  	function getJointId( skin, id ) {
  
  		for ( var i = 0; i < skin.joints.length; i ++ ) {
  
  			if ( skin.joints[ i ] == id ) {
  
  				return i;
  
  			}
  
  		}
  
  	};
  
  	function getLibraryNode( id ) {
  
          var nodes = COLLADA.querySelectorAll('library_nodes node');
  
          for ( var i = 0; i < nodes.length; i++ ) {
  
              var attObj = nodes[i].attributes.getNamedItem('id');
              if ( attObj && attObj.value === id ) {
                  return nodes[i];
          }
          }
  
          return undefined;
  
  	};
  
  	function getChannelsForNode (node ) {
  
  		var channels = [];
  		var startTime = 1000000;
  		var endTime = -1000000;
  
  		for ( var id in animations ) {
  
  			var animation = animations[id];
  
  			for ( var i = 0; i < animation.channel.length; i ++ ) {
  
  				var channel = animation.channel[i];
  				var sampler = animation.sampler[i];
  				var id = channel.target.split('/')[0];
  
  				if ( id == node.id ) {
  
  					sampler.create();
  					channel.sampler = sampler;
  					startTime = Math.min(startTime, sampler.startTime);
  					endTime = Math.max(endTime, sampler.endTime);
  					channels.push(channel);
  
  				}
  
  			}
  
  		}
  
  		if ( channels.length ) {
  
  			node.startTime = startTime;
  			node.endTime = endTime;
  
  		}
  
  		return channels;
  
  	};
  
  	function calcFrameDuration( node ) {
  
  		var minT = 10000000;
  
  		for ( var i = 0; i < node.channels.length; i ++ ) {
  
  			var sampler = node.channels[i].sampler;
  
  			for ( var j = 0; j < sampler.input.length - 1; j ++ ) {
  
  				var t0 = sampler.input[ j ];
  				var t1 = sampler.input[ j + 1 ];
  				minT = Math.min( minT, t1 - t0 );
  
  			}
  		}
  
  		return minT;
  
  	};
  
  	function calcMatrixAt( node, t ) {
  
  		var animated = {};
  
  		var i, j;
  
  		for ( i = 0; i < node.channels.length; i ++ ) {
  
  			var channel = node.channels[ i ];
  			animated[ channel.sid ] = channel;
  
  		}
  
  		var matrix = new THREE.Matrix4();
  
  		for ( i = 0; i < node.transforms.length; i ++ ) {
  
  			var transform = node.transforms[ i ];
  			var channel = animated[ transform.sid ];
  
  			if ( channel !== undefined ) {
  
  				var sampler = channel.sampler;
  				var value;
  
  				for ( j = 0; j < sampler.input.length - 1; j ++ ) {
  
  					if ( sampler.input[ j + 1 ] > t ) {
  
  						value = sampler.output[ j ];
  						//console.log(value.flatten)
  						break;
  
  					}
  
  				}
  
  				if ( value !== undefined ) {
  
  					if ( value instanceof THREE.Matrix4 ) {
  
  						matrix.multiplyMatrices( matrix, value );
  
  					} else {
  
  						// FIXME: handle other types
  
  						matrix.multiplyMatrices( matrix, transform.matrix );
  
  					}
  
  				} else {
  
  					matrix.multiplyMatrices( matrix, transform.matrix );
  
  				}
  
  			} else {
  
  				matrix.multiplyMatrices( matrix, transform.matrix );
  
  			}
  
  		}
  
  		return matrix;
  
  	};
  
  	function bakeAnimations ( node ) {
  
  		if ( node.channels && node.channels.length ) {
  
  			var keys = [],
  				sids = [];
  
  			for ( var i = 0, il = node.channels.length; i < il; i++ ) {
  
  				var channel = node.channels[i],
  					fullSid = channel.fullSid,
  					sampler = channel.sampler,
  					input = sampler.input,
  					transform = node.getTransformBySid( channel.sid ),
  					member;
  
  				if ( channel.arrIndices ) {
  
  					member = [];
  
  					for ( var j = 0, jl = channel.arrIndices.length; j < jl; j++ ) {
  
  						member[ j ] = getConvertedIndex( channel.arrIndices[ j ] );
  
  					}
  
  				} else {
  
  					member = getConvertedMember( channel.member );
  
  				}
  
  				if ( transform ) {
  
  					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, member ),
  							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 ( prevTarget.type === 'matrix' ) {
  
  				data = prevData;
  
  			} else 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 = "";
  		this.init_from = "";
  
  	};
  
  	_Image.prototype.parse = function(element) {
  
  		this.id = element.getAttribute('id');
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			if ( child.nodeName == 'init_from' ) {
  
  				this.init_from = child.textContent;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Controller() {
  
  		this.id = "";
  		this.name = "";
  		this.type = "";
  		this.skin = null;
  		this.morph = null;
  
  	};
  
  	Controller.prototype.parse = function( element ) {
  
  		this.id = element.getAttribute('id');
  		this.name = element.getAttribute('name');
  		this.type = "none";
  
  		for ( var i = 0; i < element.childNodes.length; i++ ) {
  
  			var child = element.childNodes[ i ];
  
  			switch ( child.nodeName ) {
  
  				case 'skin':
  
  					this.skin = (new Skin()).parse(child);
  					this.type = child.nodeName;
  					break;
  
  				case 'morph':
  
  					this.morph = (new Morph()).parse(child);
  					this.type = child.nodeName;
  					break;
  
  				default:
  					break;
  
  			}
  		}
  
  		return this;
  
  	};
  
  	function Morph() {
  
  		this.method = null;
  		this.source = null;
  		this.targets = null;
  		this.weights = null;
  
  	};
  
  	Morph.prototype.parse = function( element ) {
  
  		var sources = {};
  		var inputs = [];
  		var i;
  
  		this.method = element.getAttribute( 'method' );
  		this.source = element.getAttribute( 'source' ).replace( /^#/, '' );
  
  		for ( i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'source':
  
  					var source = ( new Source() ).parse( child );
  					sources[ source.id ] = source;
  					break;
  
  				case 'targets':
  
  					inputs = this.parseInputs( child );
  					break;
  
  				default:
  
  					console.log( child.nodeName );
  					break;
  
  			}
  
  		}
  
  		for ( i = 0; i < inputs.length; i ++ ) {
  
  			var input = inputs[ i ];
  			var source = sources[ input.source ];
  
  			switch ( input.semantic ) {
  
  				case 'MORPH_TARGET':
  
  					this.targets = source.read();
  					break;
  
  				case 'MORPH_WEIGHT':
  
  					this.weights = source.read();
  					break;
  
  				default:
  					break;
  
  			}
  		}
  
  		return this;
  
  	};
  
  	Morph.prototype.parseInputs = function(element) {
  
  		var inputs = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'input':
  
  					inputs.push( (new Input()).parse(child) );
  					break;
  
  				default:
  					break;
  			}
  		}
  
  		return inputs;
  
  	};
  
  	function Skin() {
  
  		this.source = "";
  		this.bindShapeMatrix = null;
  		this.invBindMatrices = [];
  		this.joints = [];
  		this.weights = [];
  
  	};
  
  	Skin.prototype.parse = function( element ) {
  
  		var sources = {};
  		var joints, weights;
  
  		this.source = element.getAttribute( 'source' ).replace( /^#/, '' );
  		this.invBindMatrices = [];
  		this.joints = [];
  		this.weights = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'bind_shape_matrix':
  
  					var f = _floats(child.textContent);
  					this.bindShapeMatrix = getConvertedMat4( f );
  					break;
  
  				case 'source':
  
  					var src = new Source().parse(child);
  					sources[ src.id ] = src;
  					break;
  
  				case 'joints':
  
  					joints = child;
  					break;
  
  				case 'vertex_weights':
  
  					weights = child;
  					break;
  
  				default:
  
  					console.log( child.nodeName );
  					break;
  
  			}
  		}
  
  		this.parseJoints( joints, sources );
  		this.parseWeights( weights, sources );
  
  		return this;
  
  	};
  
  	Skin.prototype.parseJoints = function ( element, sources ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'input':
  
  					var input = ( new Input() ).parse( child );
  					var source = sources[ input.source ];
  
  					if ( input.semantic == 'JOINT' ) {
  
  						this.joints = source.read();
  
  					} else if ( input.semantic == 'INV_BIND_MATRIX' ) {
  
  						this.invBindMatrices = source.read();
  
  					}
  
  					break;
  
  				default:
  					break;
  			}
  
  		}
  
  	};
  
  	Skin.prototype.parseWeights = function ( element, sources ) {
  
  		var v, vcount, inputs = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'input':
  
  					inputs.push( ( new Input() ).parse( child ) );
  					break;
  
  				case 'v':
  
  					v = _ints( child.textContent );
  					break;
  
  				case 'vcount':
  
  					vcount = _ints( child.textContent );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		var index = 0;
  
  		for ( var i = 0; i < vcount.length; i ++ ) {
  
  			var numBones = vcount[i];
  			var vertex_weights = [];
  
  			for ( var j = 0; j < numBones; j++ ) {
  
  				var influence = {};
  
  				for ( var k = 0; k < inputs.length; k ++ ) {
  
  					var input = inputs[ k ];
  					var value = v[ index + input.offset ];
  
  					switch ( input.semantic ) {
  
  						case 'JOINT':
  
  							influence.joint = value;//this.joints[value];
  							break;
  
  						case 'WEIGHT':
  
  							influence.weight = sources[ input.source ].data[ value ];
  							break;
  
  						default:
  							break;
  
  					}
  
  				}
  
  				vertex_weights.push( influence );
  				index += inputs.length;
  			}
  
  			for ( var j = 0; j < vertex_weights.length; j ++ ) {
  
  				vertex_weights[ j ].index = i;
  
  			}
  
  			this.weights.push( vertex_weights );
  
  		}
  
  	};
  
  	function VisualScene () {
  
  		this.id = "";
  		this.name = "";
  		this.nodes = [];
  		this.scene = new THREE.Object3D();
  
  	};
  
  	VisualScene.prototype.getChildById = function( id, recursive ) {
  
  		for ( var i = 0; i < this.nodes.length; i ++ ) {
  
  			var node = this.nodes[ i ].getChildById( id, recursive );
  
  			if ( node ) {
  
  				return node;
  
  			}
  
  		}
  
  		return null;
  
  	};
  
  	VisualScene.prototype.getChildBySid = function( sid, recursive ) {
  
  		for ( var i = 0; i < this.nodes.length; i ++ ) {
  
  			var node = this.nodes[ i ].getChildBySid( sid, recursive );
  
  			if ( node ) {
  
  				return node;
  
  			}
  
  		}
  
  		return null;
  
  	};
  
  	VisualScene.prototype.parse = function( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  		this.nodes = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'node':
  
  					this.nodes.push( ( new Node() ).parse( child ) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Node() {
  
  		this.id = "";
  		this.name = "";
  		this.sid = "";
  		this.nodes = [];
  		this.controllers = [];
  		this.transforms = [];
  		this.geometries = [];
  		this.channels = [];
  		this.matrix = new THREE.Matrix4();
  
  	};
  
  	Node.prototype.getChannelForTransform = function( transformSid ) {
  
  		for ( var i = 0; i < this.channels.length; i ++ ) {
  
  			var channel = this.channels[i];
  			var parts = channel.target.split('/');
  			var id = parts.shift();
  			var sid = parts.shift();
  			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();
  
  			} else if ( arrSyntax ) {
  
  				arrIndices = sid.split("(");
  				sid = arrIndices.shift();
  
  				for ( var j = 0; j < arrIndices.length; j ++ ) {
  
  					arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) );
  
  				}
  
  			}
  
  			if ( sid == transformSid ) {
  
  				channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices };
  				return channel;
  
  			}
  
  		}
  
  		return null;
  
  	};
  
  	Node.prototype.getChildById = function ( id, recursive ) {
  
  		if ( this.id == id ) {
  
  			return this;
  
  		}
  
  		if ( recursive ) {
  
  			for ( var i = 0; i < this.nodes.length; i ++ ) {
  
  				var n = this.nodes[ i ].getChildById( id, recursive );
  
  				if ( n ) {
  
  					return n;
  
  				}
  
  			}
  
  		}
  
  		return null;
  
  	};
  
  	Node.prototype.getChildBySid = function ( sid, recursive ) {
  
  		if ( this.sid == sid ) {
  
  			return this;
  
  		}
  
  		if ( recursive ) {
  
  			for ( var i = 0; i < this.nodes.length; i ++ ) {
  
  				var n = this.nodes[ i ].getChildBySid( sid, recursive );
  
  				if ( n ) {
  
  					return n;
  
  				}
  
  			}
  		}
  
  		return null;
  
  	};
  
  	Node.prototype.getTransformBySid = function ( sid ) {
  
  		for ( var i = 0; i < this.transforms.length; i ++ ) {
  
  			if ( this.transforms[ i ].sid == sid ) return this.transforms[ i ];
  
  		}
  
  		return null;
  
  	};
  
  	Node.prototype.parse = function( element ) {
  
  		var url;
  
  		this.id = element.getAttribute('id');
  		this.sid = element.getAttribute('sid');
  		this.name = element.getAttribute('name');
  		this.type = element.getAttribute('type');
  		this.layer = element.getAttribute('layer');
  
  		this.type = this.type == 'JOINT' ? this.type : 'NODE';
  
  		this.nodes = [];
  		this.transforms = [];
  		this.geometries = [];
  		this.cameras = [];
  		this.lights = [];
  		this.controllers = [];
  		this.matrix = new THREE.Matrix4();
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'node':
  
  					this.nodes.push( ( new Node() ).parse( child ) );
  					break;
  
  				case 'instance_camera':
  
  					this.cameras.push( ( new InstanceCamera() ).parse( child ) );
  					break;
  
  				case 'instance_controller':
  
  					this.controllers.push( ( new InstanceController() ).parse( child ) );
  					break;
  
  				case 'instance_geometry':
  
  					this.geometries.push( ( new InstanceGeometry() ).parse( child ) );
  					break;
  
  				case 'instance_light':
  
  					this.lights.push( ( new InstanceLight() ).parse( child ) );
  					break;
  
  				case 'instance_node':
  
  					url = child.getAttribute( 'url' ).replace( /^#/, '' );
  					var iNode = getLibraryNode( url );
  
  					if ( iNode ) {
  
  						this.nodes.push( ( new Node() ).parse( iNode )) ;
  
  					}
  
  					break;
  
  				case 'rotate':
  				case 'translate':
  				case 'scale':
  				case 'matrix':
  				case 'lookat':
  				case 'skew':
  
  					this.transforms.push( ( new Transform() ).parse( child ) );
  					break;
  
  				case 'extra':
  					break;
  
  				default:
  
  					console.log( child.nodeName );
  					break;
  
  			}
  
  		}
  
  		this.channels = getChannelsForNode( this );
  		bakeAnimations( this );
  
  		this.updateMatrix();
  
  		return this;
  
  	};
  
  	Node.prototype.updateMatrix = function () {
  
  		this.matrix.identity();
  
  		for ( var i = 0; i < this.transforms.length; i ++ ) {
  
  			this.transforms[ i ].apply( this.matrix );
  
  		}
  
  	};
  
  	function Transform () {
  
  		this.sid = "";
  		this.type = "";
  		this.data = [];
  		this.obj = null;
  
  	};
  
  	Transform.prototype.parse = function ( element ) {
  
  		this.sid = element.getAttribute( 'sid' );
  		this.type = element.nodeName;
  		this.data = _floats( element.textContent );
  		this.convert();
  
  		return this;
  
  	};
  
  	Transform.prototype.convert = function () {
  
  		switch ( this.type ) {
  
  			case 'matrix':
  
  				this.obj = getConvertedMat4( this.data );
  				break;
  
  			case 'rotate':
  
  				this.angle = THREE.Math.degToRad( this.data[3] );
  
  			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 () {
  
  		var m1 = new THREE.Matrix4();
  
  		return function ( matrix ) {
  
  			switch ( this.type ) {
  
  				case 'matrix':
  
  					matrix.multiply( this.obj );
  
  					break;
  
  				case 'translate':
  
  					matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) );
  
  					break;
  
  				case 'rotate':
  
  					matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) );
  
  					break;
  
  				case 'scale':
  
  					matrix.scale( this.obj );
  
  					break;
  
  			}
  
  		};
  
  	}();
  
  	Transform.prototype.update = function ( data, member ) {
  
  		var members = [ 'X', 'Y', 'Z', 'ANGLE' ];
  
  		switch ( this.type ) {
  
  			case 'matrix':
  
  				if ( ! member ) {
  
  					this.obj.copy( data );
  
  				} else if ( member.length === 1 ) {
  
  					switch ( member[ 0 ] ) {
  
  						case 0:
  
  							this.obj.n11 = data[ 0 ];
  							this.obj.n21 = data[ 1 ];
  							this.obj.n31 = data[ 2 ];
  							this.obj.n41 = data[ 3 ];
  
  							break;
  
  						case 1:
  
  							this.obj.n12 = data[ 0 ];
  							this.obj.n22 = data[ 1 ];
  							this.obj.n32 = data[ 2 ];
  							this.obj.n42 = data[ 3 ];
  
  							break;
  
  						case 2:
  
  							this.obj.n13 = data[ 0 ];
  							this.obj.n23 = data[ 1 ];
  							this.obj.n33 = data[ 2 ];
  							this.obj.n43 = data[ 3 ];
  
  							break;
  
  						case 3:
  
  							this.obj.n14 = data[ 0 ];
  							this.obj.n24 = data[ 1 ];
  							this.obj.n34 = data[ 2 ];
  							this.obj.n44 = data[ 3 ];
  
  							break;
  
  					}
  
  				} else if ( member.length === 2 ) {
  
  					var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 );
  					this.obj[ propName ] = data;
  
  				} else {
  
  					console.log('Incorrect addressing of matrix in transform.');
  
  				}
  
  				break;
  
  			case 'translate':
  			case 'scale':
  
  				if ( Object.prototype.toString.call( member ) === '[object Array]' ) {
  
  					member = members[ member[ 0 ] ];
  
  				}
  
  				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':
  
  				if ( Object.prototype.toString.call( member ) === '[object Array]' ) {
  
  					member = members[ member[ 0 ] ];
  
  				}
  
  				switch ( member ) {
  
  					case 'X':
  
  						this.obj.x = data;
  						break;
  
  					case 'Y':
  
  						this.obj.y = data;
  						break;
  
  					case 'Z':
  
  						this.obj.z = data;
  						break;
  
  					case 'ANGLE':
  
  						this.angle = THREE.Math.degToRad( data );
  						break;
  
  					default:
  
  						this.obj.x = data[ 0 ];
  						this.obj.y = data[ 1 ];
  						this.obj.z = data[ 2 ];
  						this.angle = THREE.Math.degToRad( data[ 3 ] );
  						break;
  
  				}
  				break;
  
  		}
  
  	};
  
  	function InstanceController() {
  
  		this.url = "";
  		this.skeleton = [];
  		this.instance_material = [];
  
  	};
  
  	InstanceController.prototype.parse = function ( element ) {
  
  		this.url = element.getAttribute('url').replace(/^#/, '');
  		this.skeleton = [];
  		this.instance_material = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType !== 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'skeleton':
  
  					this.skeleton.push( child.textContent.replace(/^#/, '') );
  					break;
  
  				case 'bind_material':
  
  					var instances = child.querySelectorAll('instance_material');
  
  					for ( var j = 0; j < instances.length; j ++ ){
  
  						var instance = instances[j];
  						this.instance_material.push( (new InstanceMaterial()).parse(instance) );
  
  					}
  
  
  					break;
  
  				case 'extra':
  					break;
  
  				default:
  					break;
  
  			}
  		}
  
  		return this;
  
  	};
  
  	function InstanceMaterial () {
  
  		this.symbol = "";
  		this.target = "";
  
  	};
  
  	InstanceMaterial.prototype.parse = function ( element ) {
  
  		this.symbol = element.getAttribute('symbol');
  		this.target = element.getAttribute('target').replace(/^#/, '');
  		return this;
  
  	};
  
  	function InstanceGeometry() {
  
  		this.url = "";
  		this.instance_material = [];
  
  	};
  
  	InstanceGeometry.prototype.parse = function ( element ) {
  
  		this.url = element.getAttribute('url').replace(/^#/, '');
  		this.instance_material = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1 ) continue;
  
  			if ( child.nodeName == 'bind_material' ) {
  
  				var instances = child.querySelectorAll('instance_material');
  
  				for ( var j = 0; j < instances.length; j ++ ) {
  
  					var instance = instances[j];
  					this.instance_material.push( (new InstanceMaterial()).parse(instance) );
  
  				}
  
  				break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Geometry() {
  
  		this.id = "";
  		this.mesh = null;
  
  	};
  
  	Geometry.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute('id');
  
  		extractDoubleSided( this, element );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  
  			switch ( child.nodeName ) {
  
  				case 'mesh':
  
  					this.mesh = (new Mesh(this)).parse(child);
  					break;
  
  				case 'extra':
  
  					// console.log( child );
  					break;
  
  				default:
  					break;
  			}
  		}
  
  		return this;
  
  	};
  
  	function Mesh( geometry ) {
  
  		this.geometry = geometry.id;
  		this.primitives = [];
  		this.vertices = null;
  		this.geometry3js = null;
  
  	};
  
  	Mesh.prototype.parse = function( element ) {
  
  		this.primitives = [];
  
  		var i, j;
  
  		for ( i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			switch ( child.nodeName ) {
  
  				case 'source':
  
  					_source( child );
  					break;
  
  				case 'vertices':
  
  					this.vertices = ( new Vertices() ).parse( child );
  					break;
  
  				case 'triangles':
  
  					this.primitives.push( ( new Triangles().parse( child ) ) );
  					break;
  
  				case 'polygons':
  
  					this.primitives.push( ( new Polygons().parse( child ) ) );
  					break;
  
  				case 'polylist':
  
  					this.primitives.push( ( new Polylist().parse( child ) ) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		this.geometry3js = new THREE.Geometry();
  
  		var vertexData = sources[ this.vertices.input['POSITION'].source ].data;
  
  		for ( i = 0; i < vertexData.length; i += 3 ) {
  
  			this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() );
  
  		}
  
  		for ( i = 0; i < this.primitives.length; i ++ ) {
  
  			var primitive = this.primitives[ i ];
  			primitive.setVertices( this.vertices );
  			this.handlePrimitive( primitive, this.geometry3js );
  
  		}
  
  		this.geometry3js.computeFaceNormals();
  
  		if ( this.geometry3js.calcNormals ) {
  
  			this.geometry3js.computeVertexNormals();
  			delete this.geometry3js.calcNormals;
  
  		}
  
  		// this.geometry3js.computeBoundingBox();
  
  		return this;
  
  	};
  
  	Mesh.prototype.handlePrimitive = function( primitive, geom ) {
  
  		var j, k, pList = primitive.p, inputs = primitive.inputs;
  		var input, index, idx32;
  		var source, numParams;
  		var vcIndex = 0, vcount = 3, maxOffset = 0;
  		var texture_sets = [];
  
  		for ( j = 0; j < inputs.length; j ++ ) {
  
  			input = inputs[ j ];
  
  			var offset = input.offset + 1;
  			maxOffset = (maxOffset < offset)? offset : maxOffset;
  
  			switch ( input.semantic ) {
  
  				case 'TEXCOORD':
  					texture_sets.push( input.set );
  					break;
  
  			}
  
  		}
  
  		for ( var pCount = 0; pCount < pList.length; ++pCount ) {
  
  			var p = pList[ pCount ], i = 0;
  
  			while ( i < p.length ) {
  
  				var vs = [];
  				var ns = [];
  				var ts = null;
  				var cs = [];
  
  				if ( primitive.vcount ) {
  
  					vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount;
  
  				} else {
  
  					vcount = p.length / maxOffset;
  
  				}
  
  
  				for ( j = 0; j < vcount; j ++ ) {
  
  					for ( k = 0; k < inputs.length; k ++ ) {
  
  						input = inputs[ k ];
  						source = sources[ input.source ];
  
  						index = p[ i + ( j * maxOffset ) + input.offset ];
  						numParams = source.accessor.params.length;
  						idx32 = index * numParams;
  
  						switch ( input.semantic ) {
  
  							case 'VERTEX':
  
  								vs.push( index );
  
  								break;
  
  							case 'NORMAL':
  
  								ns.push( getConvertedVec3( source.data, idx32 ) );
  
  								break;
  
  							case 'TEXCOORD':
  
  								ts = ts || { };
  								if ( ts[ input.set ] === undefined ) ts[ input.set ] = [];
  								// invert the V
  								ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) );
  
  								break;
  
  							case 'COLOR':
  
  								cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) );
  
  								break;
  
  							default:
  
  								break;
  
  						}
  
  					}
  
  				}
  
  				if ( ns.length == 0 ) {
  
  					// check the vertices inputs
  					input = this.vertices.input.NORMAL;
  
  					if ( input ) {
  
  						source = sources[ input.source ];
  						numParams = source.accessor.params.length;
  
  						for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) {
  
  							ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) );
  
  						}
  
  					} else {
  
  						geom.calcNormals = true;
  
  					}
  
  				}
  
  				if ( !ts ) {
  
  					ts = { };
  					// check the vertices inputs
  					input = this.vertices.input.TEXCOORD;
  
  					if ( input ) {
  
  						texture_sets.push( input.set );
  						source = sources[ input.source ];
  						numParams = source.accessor.params.length;
  
  						for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) {
  
  							idx32 = vs[ ndx ] * numParams;
  							if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ];
  							// invert the V
  							ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) );
  
  						}
  
  					}
  
  				}
  
  				if ( cs.length == 0 ) {
  
  					// check the vertices inputs
  					input = this.vertices.input.COLOR;
  
  					if ( input ) {
  
  						source = sources[ input.source ];
  						numParams = source.accessor.params.length;
  
  						for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) {
  
  							idx32 = vs[ ndx ] * numParams;
  							cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) );
  
  						}
  
  					}
  
  				}
  
  				var face = null, faces = [], uv, uvArr;
  
  				if ( vcount === 3 ) {
  
  					faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) );
  
  				} else if ( vcount === 4 ) {
  
  					faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ns[0], ns[1], ns[3]], cs.length ? [cs[0], cs[1], cs[3]] : new THREE.Color() ) );
  
  					faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ns[1], ns[2], ns[3]], cs.length ? [cs[1], cs[2], cs[3]] : new THREE.Color() ) );
  
  				} else if ( vcount > 4 && options.subdivideFaces ) {
  
  					var clr = cs.length ? cs : new THREE.Color(),
  						vec1, vec2, vec3, v1, v2, norm;
  
  					// subdivide into multiple Face3s
  
  					for ( k = 1; k < vcount - 1; ) {
  
  						// FIXME: normals don't seem to be quite right
  
  						faces.push( new THREE.Face3( vs[0], vs[k], vs[k+1], [ ns[0], ns[k++], ns[k] ],  clr ) );
  
  					}
  
  				}
  
  				if ( faces.length ) {
  
  					for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) {
  
  						face = faces[ndx];
  						face.daeMaterial = primitive.material;
  						geom.faces.push( face );
  
  						for ( k = 0; k < texture_sets.length; k++ ) {
  
  							uv = ts[ texture_sets[k] ];
  
  							if ( vcount > 4 ) {
  
  								// Grab the right UVs for the vertices in this face
  								uvArr = [ uv[0], uv[ndx+1], uv[ndx+2] ];
  
  							} else if ( vcount === 4 ) {
  
  								if ( ndx === 0 ) {
  
  									uvArr = [ uv[0], uv[1], uv[3] ];
  
  								} else {
  
  									uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ];
  
  								}
  
  							} else {
  
  								uvArr = [ uv[0], uv[1], uv[2] ];
  
  							}
  
  							if ( geom.faceVertexUvs[k] === undefined ) {
  
  								geom.faceVertexUvs[k] = [];
  
  							}
  
  							geom.faceVertexUvs[k].push( uvArr );
  
  						}
  
  					}
  
  				} else {
  
  					console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id );
  
  				}
  
  				i += maxOffset * vcount;
  
  			}
  		}
  
  	};
  
  	function Polygons () {
  
  		this.material = "";
  		this.count = 0;
  		this.inputs = [];
  		this.vcount = null;
  		this.p = [];
  		this.geometry = new THREE.Geometry();
  
  	};
  
  	Polygons.prototype.setVertices = function ( vertices ) {
  
  		for ( var i = 0; i < this.inputs.length; i ++ ) {
  
  			if ( this.inputs[ i ].source == vertices.id ) {
  
  				this.inputs[ i ].source = vertices.input[ 'POSITION' ].source;
  
  			}
  
  		}
  
  	};
  
  	Polygons.prototype.parse = function ( element ) {
  
  		this.material = element.getAttribute( 'material' );
  		this.count = _attr_as_int( element, 'count', 0 );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			switch ( child.nodeName ) {
  
  				case 'input':
  
  					this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) );
  					break;
  
  				case 'vcount':
  
  					this.vcount = _ints( child.textContent );
  					break;
  
  				case 'p':
  
  					this.p.push( _ints( child.textContent ) );
  					break;
  
  				case 'ph':
  
  					console.warn( 'polygon holes not yet supported!' );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Polylist () {
  
  		Polygons.call( this );
  
  		this.vcount = [];
  
  	};
  
  	Polylist.prototype = Object.create( Polygons.prototype );
  
  	function Triangles () {
  
  		Polygons.call( this );
  
  		this.vcount = 3;
  
  	};
  
  	Triangles.prototype = Object.create( Polygons.prototype );
  
  	function Accessor() {
  
  		this.source = "";
  		this.count = 0;
  		this.stride = 0;
  		this.params = [];
  
  	};
  
  	Accessor.prototype.parse = function ( element ) {
  
  		this.params = [];
  		this.source = element.getAttribute( 'source' );
  		this.count = _attr_as_int( element, 'count', 0 );
  		this.stride = _attr_as_int( element, 'stride', 0 );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			if ( child.nodeName == 'param' ) {
  
  				var param = {};
  				param[ 'name' ] = child.getAttribute( 'name' );
  				param[ 'type' ] = child.getAttribute( 'type' );
  				this.params.push( param );
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Vertices() {
  
  		this.input = {};
  
  	};
  
  	Vertices.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute('id');
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			if ( element.childNodes[i].nodeName == 'input' ) {
  
  				var input = ( new Input() ).parse( element.childNodes[ i ] );
  				this.input[ input.semantic ] = input;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Input () {
  
  		this.semantic = "";
  		this.offset = 0;
  		this.source = "";
  		this.set = 0;
  
  	};
  
  	Input.prototype.parse = function ( element ) {
  
  		this.semantic = element.getAttribute('semantic');
  		this.source = element.getAttribute('source').replace(/^#/, '');
  		this.set = _attr_as_int(element, 'set', -1);
  		this.offset = _attr_as_int(element, 'offset', 0);
  
  		if ( this.semantic == 'TEXCOORD' && this.set < 0 ) {
  
  			this.set = 0;
  
  		}
  
  		return this;
  
  	};
  
  	function Source ( id ) {
  
  		this.id = id;
  		this.type = null;
  
  	};
  
  	Source.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  
  			switch ( child.nodeName ) {
  
  				case 'bool_array':
  
  					this.data = _bools( child.textContent );
  					this.type = child.nodeName;
  					break;
  
  				case 'float_array':
  
  					this.data = _floats( child.textContent );
  					this.type = child.nodeName;
  					break;
  
  				case 'int_array':
  
  					this.data = _ints( child.textContent );
  					this.type = child.nodeName;
  					break;
  
  				case 'IDREF_array':
  				case 'Name_array':
  
  					this.data = _strings( child.textContent );
  					this.type = child.nodeName;
  					break;
  
  				case 'technique_common':
  
  					for ( var j = 0; j < child.childNodes.length; j ++ ) {
  
  						if ( child.childNodes[ j ].nodeName == 'accessor' ) {
  
  							this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] );
  							break;
  
  						}
  					}
  					break;
  
  				default:
  					// console.log(child.nodeName);
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Source.prototype.read = function () {
  
  		var result = [];
  
  		//for (var i = 0; i < this.accessor.params.length; i++) {
  
  			var param = this.accessor.params[ 0 ];
  
  			//console.log(param.name + " " + param.type);
  
  			switch ( param.type ) {
  
  				case 'IDREF':
  				case 'Name': case 'name':
  				case 'float':
  
  					return this.data;
  
  				case 'float4x4':
  
  					for ( var j = 0; j < this.data.length; j += 16 ) {
  
  						var s = this.data.slice( j, j + 16 );
  						var m = getConvertedMat4( s );
  						result.push( m );
  					}
  
  					break;
  
  				default:
  
  					console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' );
  					break;
  
  			}
  
  		//}
  
  		return result;
  
  	};
  
  	function Material () {
  
  		this.id = "";
  		this.name = "";
  		this.instance_effect = null;
  
  	};
  
  	Material.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			if ( element.childNodes[ i ].nodeName == 'instance_effect' ) {
  
  				this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] );
  				break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function ColorOrTexture () {
  
  		this.color = new THREE.Color();
  		this.color.setRGB( Math.random(), Math.random(), Math.random() );
  		this.color.a = 1.0;
  
  		this.texture = null;
  		this.texcoord = null;
  		this.texOpts = null;
  
  	};
  
  	ColorOrTexture.prototype.isColor = function () {
  
  		return ( this.texture == null );
  
  	};
  
  	ColorOrTexture.prototype.isTexture = function () {
  
  		return ( this.texture != null );
  
  	};
  
  	ColorOrTexture.prototype.parse = function ( element ) {
  
  		if (element.nodeName == 'transparent') {
  
  			this.opaque = element.getAttribute('opaque');
  
  		}
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'color':
  
  					var rgba = _floats( child.textContent );
  					this.color = new THREE.Color();
  					this.color.setRGB( rgba[0], rgba[1], rgba[2] );
  					this.color.a = rgba[3];
  					break;
  
  				case 'texture':
  
  					this.texture = child.getAttribute('texture');
  					this.texcoord = child.getAttribute('texcoord');
  					// Defaults from:
  					// https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension
  					this.texOpts = {
  						offsetU: 0,
  						offsetV: 0,
  						repeatU: 1,
  						repeatV: 1,
  						wrapU: 1,
  						wrapV: 1
  					};
  					this.parseTexture( child );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	ColorOrTexture.prototype.parseTexture = function ( element ) {
  
  		if ( ! element.childNodes ) return this;
  
  		// This should be supported by Maya, 3dsMax, and MotionBuilder
  
  		if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) {
  
  			element = element.childNodes[1];
  
  			if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) {
  
  				element = element.childNodes[1];
  
  			}
  
  		}
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			switch ( child.nodeName ) {
  
  				case 'offsetU':
  				case 'offsetV':
  				case 'repeatU':
  				case 'repeatV':
  
  					this.texOpts[ child.nodeName ] = parseFloat( child.textContent );
  
  					break;
  
  				case 'wrapU':
  				case 'wrapV':
  
  					// some dae have a value of true which becomes NaN via parseInt
  
  					if ( child.textContent.toUpperCase() === 'TRUE' ) {
  
  						this.texOpts[ child.nodeName ] = 1;
  
  					} else {
  
  						this.texOpts[ child.nodeName ] = parseInt( child.textContent );
  
  					}
  					break;
  
  				default:
  
  					this.texOpts[ child.nodeName ] = child.textContent;
  
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Shader ( type, effect ) {
  
  		this.type = type;
  		this.effect = effect;
  		this.material = null;
  
  	};
  
  	Shader.prototype.parse = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'ambient':
  				case 'emission':
  				case 'diffuse':
  				case 'specular':
  				case 'transparent':
  
  					this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child );
  					break;
  
  				case 'bump':
  
  					// If 'bumptype' is 'heightfield', create a 'bump' property
  					// Else if 'bumptype' is 'normalmap', create a 'normal' property
  					// (Default to 'bump')
  					var bumpType = child.getAttribute( 'bumptype' );
  					if ( bumpType ) {
  						if ( bumpType.toLowerCase() === "heightfield" ) {
  							this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
  						} else if ( bumpType.toLowerCase() === "normalmap" ) {
  							this[ 'normal' ] = ( new ColorOrTexture() ).parse( child );
  						} else {
  							console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType +
  								           ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" );
  							this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
  						}
  					} else {
  						console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" );
  						this[ 'bump' ] = ( new ColorOrTexture() ).parse( child );
  					}
  
  					break;
  
  				case 'shininess':
  				case 'reflectivity':
  				case 'index_of_refraction':
  				case 'transparency':
  
  					var f = child.querySelectorAll('float');
  
  					if ( f.length > 0 )
  						this[ child.nodeName ] = parseFloat( f[ 0 ].textContent );
  
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		this.create();
  		return this;
  
  	};
  
  	Shader.prototype.create = function() {
  
  		var props = {};
  
  		var transparent = false;
  
  		if (this['transparency'] !== undefined && this['transparent'] !== undefined) {
  			// convert transparent color RBG to average value
  			var transparentColor = this['transparent'];
  			var transparencyLevel = (this.transparent.color.r + this.transparent.color.g + this.transparent.color.b) / 3 * this.transparency;
  
  			if (transparencyLevel > 0) {
  				transparent = true;
  				props[ 'transparent' ] = true;
  				props[ 'opacity' ] = 1 - transparencyLevel;
  
  			}
  
  		}
  
  		var keys = {
  			'diffuse':'map',
  			'ambient':'lightMap' ,
  			'specular':'specularMap',
  			'emission':'emissionMap',
  			'bump':'bumpMap',
  			'normal':'normalMap'
  			};
  
  		for ( var prop in this ) {
  
  			switch ( prop ) {
  
  				case 'ambient':
  				case 'emission':
  				case 'diffuse':
  				case 'specular':
  				case 'bump':
  				case 'normal':
  
  					var cot = this[ prop ];
  
  					if ( cot instanceof ColorOrTexture ) {
  
  						if ( cot.isTexture() ) {
  
  							var samplerId = cot.texture;
  							var surfaceId = this.effect.sampler[samplerId];
  
  							if ( surfaceId !== undefined && surfaceId.source !== undefined ) {
  
  								var surface = this.effect.surface[surfaceId.source];
  								var image = images[surface.init_from];
  
  								if (image) {
  
  									var url = baseUrl + image.init_from;
  
  									var texture;
  									var loader = THREE.Loader.Handlers.get( url );
  
  									if ( loader !== null ) {
  
  										texture = loader.load( url );
  
  									} else {
  
  										texture = new THREE.Texture();
  										loader = new THREE.ImageLoader();
  										loader.load( url, function ( image ) {
  
  											texture.image = image;
  											texture.needsUpdate = true;
  
  										} );
  
  									}
  
  									texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
  									texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
  									texture.offset.x = cot.texOpts.offsetU;
  									texture.offset.y = cot.texOpts.offsetV;
  									texture.repeat.x = cot.texOpts.repeatU;
  									texture.repeat.y = cot.texOpts.repeatV;
  									props[keys[prop]] = texture;
  
  									// Texture with baked lighting?
  									if (prop === 'emission') props['emissive'] = 0xffffff;
  
  								}
  
  							}
  
  						} else if ( prop === 'diffuse' || !transparent ) {
  
  							if ( prop === 'emission' ) {
  
  								props[ 'emissive' ] = cot.color.getHex();
  
  							} else {
  
  								props[ prop ] = cot.color.getHex();
  
  							}
  
  						}
  
  					}
  
  					break;
  
  				case 'shininess':
  
  					props[ prop ] = this[ prop ];
  					break;
  
  				case 'reflectivity':
  
  					props[ prop ] = this[ prop ];
  					if( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap;
  					props['combine'] = THREE.MixOperation;	//mix regular shading with reflective component
  					break;
  
  				case 'index_of_refraction':
  
  					props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable
  					if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap;
  					break;
  
  				case 'transparency':
  					// gets figured out up top
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		props[ 'shading' ] = preferredShading;
  		props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide;
  
  		switch ( this.type ) {
  
  			case 'constant':
  
  				if (props.emissive != undefined) props.color = props.emissive;
  				this.material = new THREE.MeshBasicMaterial( props );
  				break;
  
  			case 'phong':
  			case 'blinn':
  
  				if (props.diffuse != undefined) props.color = props.diffuse;
  				this.material = new THREE.MeshPhongMaterial( props );
  				break;
  
  			case 'lambert':
  			default:
  
  				if (props.diffuse != undefined) props.color = props.diffuse;
  				this.material = new THREE.MeshLambertMaterial( props );
  				break;
  
  		}
  
  		return this.material;
  
  	};
  
  	function Surface ( effect ) {
  
  		this.effect = effect;
  		this.init_from = null;
  		this.format = null;
  
  	};
  
  	Surface.prototype.parse = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'init_from':
  
  					this.init_from = child.textContent;
  					break;
  
  				case 'format':
  
  					this.format = child.textContent;
  					break;
  
  				default:
  
  					console.log( "unhandled Surface prop: " + child.nodeName );
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Sampler2D ( effect ) {
  
  		this.effect = effect;
  		this.source = null;
  		this.wrap_s = null;
  		this.wrap_t = null;
  		this.minfilter = null;
  		this.magfilter = null;
  		this.mipfilter = null;
  
  	};
  
  	Sampler2D.prototype.parse = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'source':
  
  					this.source = child.textContent;
  					break;
  
  				case 'minfilter':
  
  					this.minfilter = child.textContent;
  					break;
  
  				case 'magfilter':
  
  					this.magfilter = child.textContent;
  					break;
  
  				case 'mipfilter':
  
  					this.mipfilter = child.textContent;
  					break;
  
  				case 'wrap_s':
  
  					this.wrap_s = child.textContent;
  					break;
  
  				case 'wrap_t':
  
  					this.wrap_t = child.textContent;
  					break;
  
  				default:
  
  					console.log( "unhandled Sampler2D prop: " + child.nodeName );
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Effect () {
  
  		this.id = "";
  		this.name = "";
  		this.shader = null;
  		this.surface = {};
  		this.sampler = {};
  
  	};
  
  	Effect.prototype.create = function () {
  
  		if ( this.shader == null ) {
  
  			return null;
  
  		}
  
  	};
  
  	Effect.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  
  		extractDoubleSided( this, element );
  
  		this.shader = null;
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'profile_COMMON':
  
  					this.parseTechnique( this.parseProfileCOMMON( child ) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Effect.prototype.parseNewparam = function ( element ) {
  
  		var sid = element.getAttribute( 'sid' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'surface':
  
  					this.surface[sid] = ( new Surface( this ) ).parse( child );
  					break;
  
  				case 'sampler2D':
  
  					this.sampler[sid] = ( new Sampler2D( this ) ).parse( child );
  					break;
  
  				case 'extra':
  
  					break;
  
  				default:
  
  					console.log( child.nodeName );
  					break;
  
  			}
  
  		}
  
  	};
  
  	Effect.prototype.parseProfileCOMMON = function ( element ) {
  
  		var technique;
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'profile_COMMON':
  
  					this.parseProfileCOMMON( child );
  					break;
  
  				case 'technique':
  
  					technique = child;
  					break;
  
  				case 'newparam':
  
  					this.parseNewparam( child );
  					break;
  
  				case 'image':
  
  					var _image = ( new _Image() ).parse( child );
  					images[ _image.id ] = _image;
  					break;
  
  				case 'extra':
  					break;
  
  				default:
  
  					console.log( child.nodeName );
  					break;
  
  			}
  
  		}
  
  		return technique;
  
  	};
  
  	Effect.prototype.parseTechnique= function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'constant':
  				case 'lambert':
  				case 'blinn':
  				case 'phong':
  
  					this.shader = ( new Shader( child.nodeName, this ) ).parse( child );
  					break;
  				case 'extra':
  					this.parseExtra(child);
  					break;
  				default:
  					break;
  
  			}
  
  		}
  
  	};
  
  	Effect.prototype.parseExtra = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'technique':
  					this.parseExtraTechnique( child );
  					break;
  				default:
  					break;
  
  			}
  
  		}
  
  	};
  
  	Effect.prototype.parseExtraTechnique= function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[i];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'bump':
  					this.shader.parse( element );
  					break;
  				default:
  					break;
  
  			}
  
  		}
  
  	};
  
  	function InstanceEffect () {
  
  		this.url = "";
  
  	};
  
  	InstanceEffect.prototype.parse = function ( element ) {
  
  		this.url = element.getAttribute( 'url' ).replace( /^#/, '' );
  		return this;
  
  	};
  
  	function Animation() {
  
  		this.id = "";
  		this.name = "";
  		this.source = {};
  		this.sampler = [];
  		this.channel = [];
  
  	};
  
  	Animation.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  		this.source = {};
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'animation':
  
  					var anim = ( new Animation() ).parse( child );
  
  					for ( var src in anim.source ) {
  
  						this.source[ src ] = anim.source[ src ];
  
  					}
  
  					for ( var j = 0; j < anim.channel.length; j ++ ) {
  
  						this.channel.push( anim.channel[ j ] );
  						this.sampler.push( anim.sampler[ j ] );
  
  					}
  
  					break;
  
  				case 'source':
  
  					var src = ( new Source() ).parse( child );
  					this.source[ src.id ] = src;
  					break;
  
  				case 'sampler':
  
  					this.sampler.push( ( new Sampler( this ) ).parse( child ) );
  					break;
  
  				case 'channel':
  
  					this.channel.push( ( new Channel( this ) ).parse( child ) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Channel( animation ) {
  
  		this.animation = animation;
  		this.source = "";
  		this.target = "";
  		this.fullSid = null;
  		this.sid = null;
  		this.dotSyntax = null;
  		this.arrSyntax = null;
  		this.arrIndices = null;
  		this.member = null;
  
  	};
  
  	Channel.prototype.parse = function ( element ) {
  
  		this.source = element.getAttribute( 'source' ).replace( /^#/, '' );
  		this.target = element.getAttribute( 'target' );
  
  		var parts = this.target.split( '/' );
  
  		var id = parts.shift();
  		var sid = parts.shift();
  
  		var dotSyntax = ( sid.indexOf(".") >= 0 );
  		var arrSyntax = ( sid.indexOf("(") >= 0 );
  
  		if ( dotSyntax ) {
  
  			parts = sid.split(".");
  			this.sid = parts.shift();
  			this.member = parts.shift();
  
  		} else if ( arrSyntax ) {
  
  			var arrIndices = sid.split("(");
  			this.sid = arrIndices.shift();
  
  			for (var j = 0; j < arrIndices.length; j ++ ) {
  
  				arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') );
  
  			}
  
  			this.arrIndices = arrIndices;
  
  		} else {
  
  			this.sid = sid;
  
  		}
  
  		this.fullSid = sid;
  		this.dotSyntax = dotSyntax;
  		this.arrSyntax = arrSyntax;
  
  		return this;
  
  	};
  
  	function Sampler ( animation ) {
  
  		this.id = "";
  		this.animation = animation;
  		this.inputs = [];
  		this.input = null;
  		this.output = null;
  		this.strideOut = null;
  		this.interpolation = null;
  		this.startTime = null;
  		this.endTime = null;
  		this.duration = 0;
  
  	};
  
  	Sampler.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.inputs = [];
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'input':
  
  					this.inputs.push( (new Input()).parse( child ) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Sampler.prototype.create = function () {
  
  		for ( var i = 0; i < this.inputs.length; i ++ ) {
  
  			var input = this.inputs[ i ];
  			var source = this.animation.source[ input.source ];
  
  			switch ( input.semantic ) {
  
  				case 'INPUT':
  
  					this.input = source.read();
  					break;
  
  				case 'OUTPUT':
  
  					this.output = source.read();
  					this.strideOut = source.accessor.stride;
  					break;
  
  				case 'INTERPOLATION':
  
  					this.interpolation = source.read();
  					break;
  
  				case 'IN_TANGENT':
  
  					break;
  
  				case 'OUT_TANGENT':
  
  					break;
  
  				default:
  
  					console.log(input.semantic);
  					break;
  
  			}
  
  		}
  
  		this.startTime = 0;
  		this.endTime = 0;
  		this.duration = 0;
  
  		if ( this.input.length ) {
  
  			this.startTime = 100000000;
  			this.endTime = -100000000;
  
  			for ( var i = 0; i < this.input.length; i ++ ) {
  
  				this.startTime = Math.min( this.startTime, this.input[ i ] );
  				this.endTime = Math.max( this.endTime, this.input[ i ] );
  
  			}
  
  			this.duration = this.endTime - this.startTime;
  
  		}
  
  	};
  
  	Sampler.prototype.getData = function ( type, ndx, member ) {
  
  		var data;
  
  		if ( type === 'matrix' && this.strideOut === 16 ) {
  
  			data = this.output[ ndx ];
  
  		} else 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 if ( this.strideOut === 4 && type === 'matrix' ) {
  
  				fixCoords( data, -1 );
  
  			}
  
  		} else {
  
  			data = this.output[ ndx ];
  
  			if ( member && type == 'translate' ) {
  				data = getConvertedTranslation( member, data );
  			}
  
  		}
  
  		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, l = this.targets.length; i < l; i ++ ) {
  
  			var target = this.targets[ i ],
  				nextTarget = nextKey.getTarget( target.sid ),
  				data;
  
  			if ( target.transform.type !== 'matrix' && nextTarget ) {
  
  				var scale = ( time - this.time ) / ( nextKey.time - this.time ),
  					nextData = nextTarget.data,
  					prevData = target.data;
  
  				if ( scale < 0 ) scale = 0;
  				if ( scale > 1 ) scale = 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 );
  
  		}
  
  	};
  
  	// Camera
  	function Camera() {
  
  		this.id = "";
  		this.name = "";
  		this.technique = "";
  
  	};
  
  	Camera.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'optics':
  
  					this.parseOptics( child );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Camera.prototype.parseOptics = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			if ( element.childNodes[ i ].nodeName == 'technique_common' ) {
  
  				var technique = element.childNodes[ i ];
  
  				for ( var j = 0; j < technique.childNodes.length; j ++ ) {
  
  					this.technique = technique.childNodes[ j ].nodeName;
  
  					if ( this.technique == 'perspective' ) {
  
  						var perspective = technique.childNodes[ j ];
  
  						for ( var k = 0; k < perspective.childNodes.length; k ++ ) {
  
  							var param = perspective.childNodes[ k ];
  
  							switch ( param.nodeName ) {
  
  								case 'yfov':
  									this.yfov = param.textContent;
  									break;
  								case 'xfov':
  									this.xfov = param.textContent;
  									break;
  								case 'znear':
  									this.znear = param.textContent;
  									break;
  								case 'zfar':
  									this.zfar = param.textContent;
  									break;
  								case 'aspect_ratio':
  									this.aspect_ratio = param.textContent;
  									break;
  
  							}
  
  						}
  
  					} else if ( this.technique == 'orthographic' ) {
  
  						var orthographic = technique.childNodes[ j ];
  
  						for ( var k = 0; k < orthographic.childNodes.length; k ++ ) {
  
  							var param = orthographic.childNodes[ k ];
  
  							switch ( param.nodeName ) {
  
  								case 'xmag':
  									this.xmag = param.textContent;
  									break;
  								case 'ymag':
  									this.ymag = param.textContent;
  									break;
  								case 'znear':
  									this.znear = param.textContent;
  									break;
  								case 'zfar':
  									this.zfar = param.textContent;
  									break;
  								case 'aspect_ratio':
  									this.aspect_ratio = param.textContent;
  									break;
  
  							}
  
  						}
  
  					}
  
  				}
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function InstanceCamera() {
  
  		this.url = "";
  
  	};
  
  	InstanceCamera.prototype.parse = function ( element ) {
  
  		this.url = element.getAttribute('url').replace(/^#/, '');
  
  		return this;
  
  	};
  
  	// Light
  
  	function Light() {
  
  		this.id = "";
  		this.name = "";
  		this.technique = "";
  
  	};
  
  	Light.prototype.parse = function ( element ) {
  
  		this.id = element.getAttribute( 'id' );
  		this.name = element.getAttribute( 'name' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'technique_common':
  
  					this.parseCommon( child );
  					break;
  
  				case 'technique':
  
  					this.parseTechnique( child );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Light.prototype.parseCommon = function ( element ) {
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			switch ( element.childNodes[ i ].nodeName ) {
  
  				case 'directional':
  				case 'point':
  				case 'spot':
  				case 'ambient':
  
  					this.technique = element.childNodes[ i ].nodeName;
  
  					var light = element.childNodes[ i ];
  
  					for ( var j = 0; j < light.childNodes.length; j ++ ) {
  
  						var child = light.childNodes[j];
  
  						switch ( child.nodeName ) {
  
  							case 'color':
  
  								var rgba = _floats( child.textContent );
  								this.color = new THREE.Color(0);
  								this.color.setRGB( rgba[0], rgba[1], rgba[2] );
  								this.color.a = rgba[3];
  								break;
  
  							case 'falloff_angle':
  
  								this.falloff_angle = parseFloat( child.textContent );
  								break;
  
  							case 'quadratic_attenuation':
  								var f = parseFloat( child.textContent );
  								this.distance = f ? Math.sqrt( 1/f ) : 0;
  						}
  
  					}
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	Light.prototype.parseTechnique = function ( element ) {
  
  		this.profile = element.getAttribute( 'profile' );
  
  		for ( var i = 0; i < element.childNodes.length; i ++ ) {
  
  			var child = element.childNodes[ i ];
  
  			switch ( child.nodeName ) {
  
  				case 'intensity':
  
  					this.intensity = parseFloat(child.textContent);
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function InstanceLight() {
  
  		this.url = "";
  
  	};
  
  	InstanceLight.prototype.parse = function ( element ) {
  
  		this.url = element.getAttribute('url').replace(/^#/, '');
  
  		return this;
  
  	};
  
  	function KinematicsModel( ) {
  
  		this.id = '';
  		this.name = '';
  		this.joints = [];
  		this.links = [];
  
  	}
  
  	KinematicsModel.prototype.parse = function( element ) {
  
  		this.id = element.getAttribute('id');
  		this.name = element.getAttribute('name');
  		this.joints = [];
  		this.links = [];
  
  		for (var i = 0; i < element.childNodes.length; i++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'technique_common':
  
  					this.parseCommon(child);
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	KinematicsModel.prototype.parseCommon = function( element ) {
  
  		for (var i = 0; i < element.childNodes.length; i++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( element.childNodes[ i ].nodeName ) {
  
  				case 'joint':
  					this.joints.push( (new Joint()).parse(child) );
  					break;
  
  				case 'link':
  					this.links.push( (new Link()).parse(child) );
  					break;
  
  				default:
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Joint( ) {
  
  		this.sid = '';
  		this.name = '';
  		this.axis = new THREE.Vector3();
  		this.limits = {
  			min: 0,
  			max: 0
  		};
  		this.type = '';
  		this.static = false;
  		this.zeroPosition = 0.0;
  		this.middlePosition = 0.0;
  
  	}
  
  	Joint.prototype.parse = function( element ) {
  
  		this.sid = element.getAttribute('sid');
  		this.name = element.getAttribute('name');
  		this.axis = new THREE.Vector3();
  		this.limits = {
  			min: 0,
  			max: 0
  		};
  		this.type = '';
  		this.static = false;
  		this.zeroPosition = 0.0;
  		this.middlePosition = 0.0;
  
  		var axisElement = element.querySelector('axis');
  		var _axis = _floats(axisElement.textContent);
  		this.axis = getConvertedVec3(_axis, 0);
  
  		var min = element.querySelector('limits min') ? parseFloat(element.querySelector('limits min').textContent) : -360;
  		var max = element.querySelector('limits max') ? parseFloat(element.querySelector('limits max').textContent) : 360;
  
  		this.limits = {
  			min: min,
  			max: max
  		};
  
  		var jointTypes = ['prismatic', 'revolute'];
  		for (var i = 0; i < jointTypes.length; i++ ) {
  
  			var type = jointTypes[ i ];
  
  			var jointElement = element.querySelector(type);
  
  			if ( jointElement ) {
  
  				this.type = type;
  
  			}
  
  		}
  
  		// if the min is equal to or somehow greater than the max, consider the joint static
  		if ( this.limits.min >= this.limits.max ) {
  
  			this.static = true;
  
  		}
  
  		this.middlePosition = (this.limits.min + this.limits.max) / 2.0;
  		return this;
  
  	};
  
  	function Link( ) {
  
  		this.sid = '';
  		this.name = '';
  		this.transforms = [];
  		this.attachments = [];
  
  	}
  
  	Link.prototype.parse = function( element ) {
  
  		this.sid = element.getAttribute('sid');
  		this.name = element.getAttribute('name');
  		this.transforms = [];
  		this.attachments = [];
  
  		for (var i = 0; i < element.childNodes.length; i++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'attachment_full':
  					this.attachments.push( (new Attachment()).parse(child) );
  					break;
  
  				case 'rotate':
  				case 'translate':
  				case 'matrix':
  
  					this.transforms.push( (new Transform()).parse(child) );
  					break;
  
  				default:
  
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function Attachment( ) {
  
  		this.joint = '';
  		this.transforms = [];
  		this.links = [];
  
  	}
  
  	Attachment.prototype.parse = function( element ) {
  
  		this.joint = element.getAttribute('joint').split('/').pop();
  		this.links = [];
  
  		for (var i = 0; i < element.childNodes.length; i++ ) {
  
  			var child = element.childNodes[ i ];
  			if ( child.nodeType != 1 ) continue;
  
  			switch ( child.nodeName ) {
  
  				case 'link':
  					this.links.push( (new Link()).parse(child) );
  					break;
  
  				case 'rotate':
  				case 'translate':
  				case 'matrix':
  
  					this.transforms.push( (new Transform()).parse(child) );
  					break;
  
  				default:
  
  					break;
  
  			}
  
  		}
  
  		return this;
  
  	};
  
  	function _source( element ) {
  
  		var id = element.getAttribute( 'id' );
  
  		if ( sources[ id ] != undefined ) {
  
  			return sources[ id ];
  
  		}
  
  		sources[ id ] = ( new Source(id )).parse( element );
  		return sources[ id ];
  
  	};
  
  	function _nsResolver( nsPrefix ) {
  
  		if ( nsPrefix == "dae" ) {
  
  			return "http://www.collada.org/2005/11/COLLADASchema";
  
  		}
  
  		return null;
  
  	};
  
  	function _bools( str ) {
  
  		var raw = _strings( str );
  		var data = [];
  
  		for ( var i = 0, l = raw.length; i < l; i ++ ) {
  
  			data.push( (raw[i] == 'true' || raw[i] == '1') ? true : false );
  
  		}
  
  		return data;
  
  	};
  
  	function _floats( str ) {
  
  		var raw = _strings(str);
  		var data = [];
  
  		for ( var i = 0, l = raw.length; i < l; i ++ ) {
  
  			data.push( parseFloat( raw[ i ] ) );
  
  		}
  
  		return data;
  
  	};
  
  	function _ints( str ) {
  
  		var raw = _strings( str );
  		var data = [];
  
  		for ( var i = 0, l = raw.length; i < l; i ++ ) {
  
  			data.push( parseInt( raw[ i ], 10 ) );
  
  		}
  
  		return data;
  
  	};
  
  	function _strings( str ) {
  
  		return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : [];
  
  	};
  
  	function _trimString( str ) {
  
  		return str.replace( /^\s+/, "" ).replace( /\s+$/, "" );
  
  	};
  
  	function _attr_as_float( element, name, defaultValue ) {
  
  		if ( element.hasAttribute( name ) ) {
  
  			return parseFloat( element.getAttribute( name ) );
  
  		} else {
  
  			return defaultValue;
  
  		}
  
  	};
  
  	function _attr_as_int( element, name, defaultValue ) {
  
  		if ( element.hasAttribute( name ) ) {
  
  			return parseInt( element.getAttribute( name ), 10) ;
  
  		} else {
  
  			return defaultValue;
  
  		}
  
  	};
  
  	function _attr_as_string( element, name, defaultValue ) {
  
  		if ( element.hasAttribute( name ) ) {
  
  			return element.getAttribute( name );
  
  		} else {
  
  			return defaultValue;
  
  		}
  
  	};
  
  	function _format_float( f, num ) {
  
  		if ( f === undefined ) {
  
  			var s = '0.';
  
  			while ( s.length < num + 2 ) {
  
  				s += '0';
  
  			}
  
  			return s;
  
  		}
  
  		num = num || 2;
  
  		var parts = f.toString().split( '.' );
  		parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0";
  
  		while( parts[ 1 ].length < num ) {
  
  			parts[ 1 ] += '0';
  
  		}
  
  		return parts.join( '.' );
  
  	};
  
  	function extractDoubleSided( obj, element ) {
  
  		obj.doubleSided = false;
  
  		var node = element.querySelectorAll('extra double_sided')[0];
  
  		if ( node ) {
  
  			if ( node && parseInt( node.textContent, 10 ) === 1 ) {
  
  				obj.doubleSided = true;
  
  			}
  
  		}
  
  	};
  
  	// Up axis conversion
  
  	function setUpConversion() {
  
  		if ( options.convertUpAxis !== true || 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 !== true || 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 getConvertedTranslation( axis, data ) {
  
  		if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) {
  
  			return data;
  
  		}
  
  		switch ( axis ) {
  			case 'X':
  				data = upConversion == 'XtoY' ? data * -1 : data;
  				break;
  			case 'Y':
  				data = upConversion == 'YtoZ' || upConversion == 'YtoX' ? data * -1 : data;
  				break;
  			case 'Z':
  				data = upConversion == 'ZtoY' ? data * -1 : data ;
  				break;
  			default:
  				break;
  		}
  
  		return data;
  	};
  
  	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().set(
  			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 getConvertedIndex( index ) {
  
  		if ( index > -1 && index < 3 ) {
  
  			var members = ['X', 'Y', 'Z'],
  				indices = { X: 0, Y: 1, Z: 2 };
  
  			index = getConvertedMember( members[ index ] );
  			index = indices[ index ];
  
  		}
  
  		return index;
  
  	};
  
  	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,
  		options: options
  
  	};
  
  };
  