text: /**
   * @author mrdoob / http://mrdoob.com/
   */
  
  
  
  
  
  THREE.ColladaLoader = function ( manager ) {
  
  	this.manager = ( manager !== undefined ) ? manager : THREE.DefaultLoadingManager;
  
  };
  
  THREE.ColladaLoader.prototype = {
  
  	constructor: THREE.ColladaLoader,
  
  	load: function ( url, onLoad, onProgress, onError ) {
  
  		var scope = this;
  
  		var loader = new THREE.FileLoader( scope.manager );
  		loader.load( url, function ( text ) {
  
  			onLoad( scope.parse( text ) );
  
  		}, onProgress, onError );
  
  	},
  
  	options: {
  
  		set convertUpAxis( value ) {
  
  			console.log( 'ColladaLoder.options.convertUpAxis: TODO' );
  
  		}
  
  	},
  
  	setCrossOrigin: function ( value ) {
  
  		this.crossOrigin = value;
  
  	},
  
  	parse: function ( text ) {
  
  		function getElementsByTagName( xml, name ) {
  
  			// Non recursive xml.getElementsByTagName() ...
  
  			var array = [];
  			var childNodes = xml.childNodes;
  
  			for ( var i = 0, l = childNodes.length; i < l; i ++ ) {
  
  				var child = childNodes[ i ];
  
  				if ( child.nodeName === name ) {
  
  					array.push( child );
  
  				}
  
  			}
  
  			return array;
  
  		}
  
  		function parseFloats( text ) {
  
  			if ( text.length === 0 ) return [];
  
  			var parts = text.trim().split( /\s+/ );
  			var array = new Array( parts.length );
  
  			for ( var i = 0, l = parts.length; i < l; i ++ ) {
  
  				array[ i ] = parseFloat( parts[ i ] );
  
  			}
  
  			return array;
  
  		}
  
  		function parseInts( text ) {
  
  			if ( text.length === 0 ) return [];
  
  			var parts = text.trim().split( /\s+/ );
  			var array = new Array( parts.length );
  
  			for ( var i = 0, l = parts.length; i < l; i ++ ) {
  
  				array[ i ] = parseInt( parts[ i ] );
  
  			}
  
  			return array;
  
  		}
  
  		function parseId( text ) {
  
  			return text.substring( 1 );
  
  		}
  
  		// asset
  
  		function parseAsset( xml ) {
  
  			return {
  				unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ),
  				upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] )
  			};
  
  		}
  
  		function parseAssetUnit( xml ) {
  
  			return xml !== undefined ? parseFloat( xml.getAttribute( 'meter' ) ) : 1;
  
  		}
  
  		function parseAssetUpAxis( xml ) {
  
  			return xml !== undefined ? xml.textContent : 'Y_UP';
  
  		}
  
  		// library
  
  		function parseLibrary( xml, libraryName, nodeName, parser ) {
  
  			var library = getElementsByTagName( xml, libraryName )[ 0 ];
  
  			if ( library !== undefined ) {
  
  				var elements = getElementsByTagName( library, nodeName );
  
  				for ( var i = 0; i < elements.length; i ++ ) {
  
  					parser( elements[ i ] );
  
  				}
  
  			}
  
  		}
  
  		function buildLibrary( data, builder ) {
  
  			for ( var name in data ) {
  
  				var object = data[ name ];
  				object.build = builder( data[ name ] );
  
  			}
  
  		}
  
  		// get
  
  		function getBuild( data, builder ) {
  
  			if ( data.build !== undefined ) return data.build;
  
  			data.build = builder( data );
  
  			return data.build;
  
  		}
  
  		// image
  
  		function parseImage( xml ) {
  
  			var data = {
  				init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent
  			};
  
  			library.images[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function buildImage( data ) {
  
  			if ( data.build !== undefined ) return data.build;
  
  			return new Image();
  
  		}
  
  		function getImage( id ) {
  
  			return getBuild( library.images[ id ], buildImage );
  
  		}
  
  		// effect
  
  		function parseEffect( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'profile_COMMON':
  						data.profile = parseEffectProfileCOMMON( child );
  						break;
  
  				}
  
  			}
  
  			library.effects[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function parseEffectProfileCOMMON( xml ) {
  
  			var data = {
  				surfaces: {},
  				samplers: {}
  			};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'newparam':
  						parseEffectNewparam( child, data );
  						break;
  
  					case 'technique':
  						data.technique = parseEffectTechnique( child );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectNewparam( xml, data ) {
  
  			var sid = xml.getAttribute( 'sid' );
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'surface':
  						data.surfaces[ sid ] = parseEffectSurface( child );
  						break;
  
  					case 'sampler2D':
  						data.samplers[ sid ] = parseEffectSampler( child );
  						break;
  
  				}
  
  			}
  
  		}
  
  		function parseEffectSurface( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'init_from':
  						data.init_from = child.textContent;
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectSampler( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'source':
  						data.source = child.textContent;
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectTechnique( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'constant':
  					case 'lambert':
  					case 'blinn':
  					case 'phong':
  						data.type = child.nodeName;
  						data.parameters = parseEffectParameters( child );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectParameters( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'emission':
  					case 'diffuse':
  					case 'specular':
  					case 'shininess':
  					case 'transparent':
  					case 'transparency':
  						data[ child.nodeName ] = parseEffectParameter( child );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectParameter( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'color':
  						data[ child.nodeName ] = parseFloats( child.textContent );
  						break;
  
  					case 'float':
  						data[ child.nodeName ] = parseFloat( child.textContent );
  						break;
  
  					case 'texture':
  						data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) };
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectParameterTexture( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'extra':
  						data = parseEffectParameterTextureExtra( child );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectParameterTextureExtra( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'technique':
  						data[ child.nodeName ] = parseEffectParameterTextureExtraTechnique( child );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseEffectParameterTextureExtraTechnique( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'repeatU':
  					case 'repeatV':
  					case 'offsetU':
  					case 'offsetV':
  						data[ child.nodeName ] = parseFloat( child.textContent );
  						break;
  
  					case 'wrapU':
  					case 'wrapV':
  						data[ child.nodeName ] = parseInt( child.textContent );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function buildEffect( data ) {
  
  			return data;
  
  		}
  
  		function getEffect( id ) {
  
  			return getBuild( library.effects[ id ], buildEffect );
  
  		}
  
  		// material
  
  		function parseMaterial( xml ) {
  
  			var data = {
  				name: xml.getAttribute( 'name' )
  			};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'instance_effect':
  						data.url = parseId( child.getAttribute( 'url' ) );
  						break;
  
  				}
  
  			}
  
  			library.materials[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function buildMaterial( data ) {
  
  			var effect = getEffect( data.url );
  			var technique = effect.profile.technique;
  
  			var material;
  
  			switch ( technique.type ) {
  
  				case 'phong':
  				case 'blinn':
  					material = new THREE.MeshPhongMaterial();
  					break;
  
  				case 'lambert':
  					material = new THREE.MeshLambertMaterial();
  					break;
  
  				default:
  					material = new THREE.MeshBasicMaterial();
  					break;
  
  			}
  
  			material.name = data.name;
  
  			function getTexture( textureObject ) {
  
  				var sampler = effect.profile.samplers[ textureObject.id ];
  
  				if ( sampler !== undefined ) {
  
  					var surface = effect.profile.surfaces[ sampler.source ];
  
  					var texture = new THREE.Texture( getImage( surface.init_from ) );
  
  					var extra = textureObject.extra;
  
  					if ( extra !== undefined && extra.technique !== undefined ) {
  
  						var technique = extra.technique;
  
  						texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
  						texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
  
  						texture.offset.set( technique.offsetU, technique.offsetV );
  						texture.repeat.set( technique.repeatU, technique.repeatV );
  
  					} else {
  
  						texture.wrapS = THREE.RepeatWrapping;
  						texture.wrapT = THREE.RepeatWrapping;
  
  					}
  
  					texture.needsUpdate = true;
  
  					return texture;
  
  				}
  
  				console.error( 'ColladaLoder: Undefined sampler', textureObject.id );
  
  				return null;
  
  			}
  
  			var parameters = technique.parameters;
  
  			for ( var key in parameters ) {
  
  				var parameter = parameters[ key ];
  
  				switch ( key ) {
  
  					case 'diffuse':
  						if ( parameter.color ) material.color.fromArray( parameter.color );
  						if ( parameter.texture ) material.map = getTexture( parameter.texture );
  						break;
  					case 'specular':
  						if ( parameter.color && material.specular )
  							material.specular.fromArray( parameter.color );
  						break;
  					case 'shininess':
  						if ( parameter.float && material.shininess )
  							material.shininess = parameter.float;
  						break;
  					case 'emission':
  						if ( parameter.color && material.emissive )
  							material.emissive.fromArray( parameter.color );
  						break;
  					case 'transparent':
  						// if ( parameter.texture ) material.alphaMap = getTexture( parameter.texture );
  						material.transparent = true;
  						break;
  					case 'transparency':
  						if ( parameter.float !== undefined ) material.opacity = parameter.float;
  						material.transparent = true;
  						break;
  
  				}
  
  			}
  
  			return material;
  
  		}
  
  		function getMaterial( id ) {
  
  			return getBuild( library.materials[ id ], buildMaterial );
  
  		}
  
  		// camera
  
  		function parseCamera( xml ) {
  
  			var data = {
  				name: xml.getAttribute( 'name' )
  			};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'optics':
  						data.optics = parseCameraOptics( child );
  						break;
  
  				}
  
  			}
  
  			library.cameras[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function parseCameraOptics( xml ) {
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				switch ( child.nodeName ) {
  
  					case 'technique_common':
  						return parseCameraTechnique( child );
  
  				}
  
  			}
  
  			return {};
  
  		}
  
  		function parseCameraTechnique( xml ) {
  
  			var data = {};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				switch ( child.nodeName ) {
  
  					case 'perspective':
  					case 'orthographic':
  
  						data.technique = child.nodeName;
  						data.parameters = parseCameraParameters( child );
  
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseCameraParameters( xml ) {
  
  			var data = {};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				switch ( child.nodeName ) {
  
  					case 'xfov':
  					case 'yfov':
  					case 'xmag':
  					case 'ymag':
  					case 'znear':
  					case 'zfar':
  					case 'aspect_ratio':
  						data[ child.nodeName ] = parseFloat( child.textContent );
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function buildCamera( data ) {
  
  			var camera;
  
  			switch ( data.optics.technique ) {
  
  				case 'perspective':
  					camera = new THREE.PerspectiveCamera(
  						data.optics.parameters.yfov,
  						data.optics.parameters.aspect_ratio,
  						data.optics.parameters.znear,
  						data.optics.parameters.zfar
  					);
  					break;
  
  				case 'orthographic':
  					camera = new THREE.OrthographicCamera( /* TODO */ );
  					break;
  
  				default:
  					camera = new THREE.PerspectiveCamera();
  					break;
  
  			}
  
  			camera.name = data.name;
  
  			return camera;
  
  		}
  
  		function getCamera( id ) {
  
  			return getBuild( library.cameras[ id ], buildCamera );
  
  		}
  
  		// light
  
  		function parseLight( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'technique_common':
  						data = parseLightTechnique( child );
  						break;
  
  				}
  
  			}
  
  			library.lights[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function parseLightTechnique( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'directional':
  					case 'point':
  					case 'spot':
  					case 'ambient':
  
  						data.technique = child.nodeName;
  						data.parameters = parseLightParameters( child );
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseLightParameters( xml ) {
  
  			var data = {};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'color':
  						var array = parseFloats( child.textContent );
  						data.color = new THREE.Color().fromArray( array );
  						break;
  
  					case 'falloff_angle':
  						data.falloffAngle = parseFloat( child.textContent );
  						break;
  
  					case 'quadratic_attenuation':
  						var f = parseFloat( child.textContent );
  						data.distance = f ? Math.sqrt( 1 / f ) : 0;
  						break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function buildLight( data ) {
  
  			var light;
  
  			switch ( data.technique ) {
  
  				case 'directional':
  					light = new THREE.DirectionalLight();
  					break;
  
  				case 'point':
  					light = new THREE.PointLight();
  					break;
  
  				case 'spot':
  					light = new THREE.SpotLight();
  					break;
  
  				case 'ambient':
  					light = new THREE.AmbientLight();
  					break;
  
  			}
  
  			if ( data.parameters.color ) light.color.copy( data.parameters.color );
  			if ( data.parameters.distance ) light.distance = data.parameters.distance;
  
  			return light;
  
  		}
  
  		function getLight( id ) {
  
  			return getBuild( library.lights[ id ], buildLight );
  
  		}
  
  		// geometry
  
  		function parseGeometry( xml ) {
  
  			var data = {
  				name: xml.getAttribute( 'name' ),
  				sources: {},
  				vertices: {},
  				primitives: []
  			};
  
  			var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ];
  
  			for ( var i = 0; i < mesh.childNodes.length; i ++ ) {
  
  				var child = mesh.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				var id = child.getAttribute( 'id' );
  
  				switch ( child.nodeName ) {
  
  					case 'source':
  						data.sources[ id ] = parseGeometrySource( child );
  						break;
  
  					case 'vertices':
  						// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
  						data.vertices = parseGeometryVertices( child );
  						break;
  
  					case 'polygons':
  						console.warn( 'ColladaLoader: Unsupported primitive type: ', child.nodeName );
  						break;
  
  					case 'lines':
  					case 'linestrips':
  					case 'polylist':
  					case 'triangles':
  						data.primitives.push( parseGeometryPrimitive( child ) );
  						break;
  
  					default:
  						console.log( child );
  
  				}
  
  			}
  
  			library.geometries[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function parseGeometrySource( xml ) {
  
  			var data = {
  				array: [],
  				stride: 3
  			};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'float_array':
  						data.array = parseFloats( child.textContent );
  						break;
  
  					case 'technique_common':
  						var accessor = getElementsByTagName( child, 'accessor' )[ 0 ];
  
  						if ( accessor !== undefined ) {
  
  							data.stride = parseInt( accessor.getAttribute( 'stride' ) );
  
  						}
  						break;
  
  					default:
  						console.log( child );
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function parseGeometryVertices( xml ) {
  
  			var data = {};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) );
  
  			}
  
  			return data;
  
  		}
  
  		function parseGeometryPrimitive( xml ) {
  
  			var primitive = {
  				type: xml.nodeName,
  				material: xml.getAttribute( 'material' ),
  				inputs: {},
  				stride: 0
  			};
  
  			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'input':
  						var id = parseId( child.getAttribute( 'source' ) );
  						var semantic = child.getAttribute( 'semantic' );
  						var offset = parseInt( child.getAttribute( 'offset' ) );
  						primitive.inputs[ semantic ] = { id: id, offset: offset };
  						primitive.stride = Math.max( primitive.stride, offset + 1 );
  						break;
  
  					case 'vcount':
  						primitive.vcount = parseInts( child.textContent );
  						break;
  
  					case 'p':
  						primitive.p = parseInts( child.textContent );
  						break;
  
  				}
  
  			}
  
  			return primitive;
  
  		}
  
  		var DEFAULT_LINEMATERIAL = new THREE.LineBasicMaterial();
  		var DEFAULT_MESHMATERIAL = new THREE.MeshPhongMaterial();
  
  		function buildGeometry( data ) {
  
  			var group = {};
  
  			var sources = data.sources;
  			var vertices = data.vertices;
  			var primitives = data.primitives;
  
  			if ( primitives.length === 0 ) return group;
  
  			for ( var p = 0; p < primitives.length; p ++ ) {
  
  				var primitive = primitives[ p ];
  				var inputs = primitive.inputs;
  
  				var geometry = new THREE.BufferGeometry();
  
  				if ( data.name ) geometry.name = data.name;
  
  				for ( var name in inputs ) {
  
  					var input = inputs[ name ];
  
  					switch ( name )	{
  
  						case 'VERTEX':
  							for ( var key in vertices ) {
  
  								geometry.addAttribute( key.toLowerCase(), buildGeometryAttribute( primitive, sources[ vertices[ key ] ], input.offset ) );
  
  							}
  							break;
  
  						case 'NORMAL':
  							geometry.addAttribute( 'normal', buildGeometryAttribute( primitive, sources[ input.id ], input.offset ) );
  							break;
  
  						case 'COLOR':
  							geometry.addAttribute( 'color', buildGeometryAttribute( primitive, sources[ input.id ], input.offset ) );
  							break;
  
  						case 'TEXCOORD':
  							geometry.addAttribute( 'uv', buildGeometryAttribute( primitive, sources[ input.id ], input.offset ) );
  							break;
  
  					}
  
  				}
  
  				var object;
  
  				switch ( primitive.type ) {
  
  					case 'lines':
  						object = new THREE.LineSegments( geometry, DEFAULT_LINEMATERIAL );
  						break;
  
  					case 'linestrips':
  						object = new THREE.Line( geometry, DEFAULT_LINEMATERIAL );
  						break;
  
  					case 'triangles':
  					case 'polylist':
  						object = new THREE.Mesh( geometry, DEFAULT_MESHMATERIAL );
  						break;
  
  				}
  
  				group[ primitive.material ] = object;
  
  			}
  
  			return group;
  
  		}
  
  		function buildGeometryAttribute( primitive, source, offset ) {
  
  			var indices = primitive.p;
  			var stride = primitive.stride;
  			var vcount = primitive.vcount;
  
  			function pushVector( i ) {
  
  				var index = indices[ i + offset ] * sourceStride;
  				var length = index + sourceStride;
  
  				for ( ; index < length; index ++ ) {
  
  					array.push( sourceArray[ index ] );
  
  				}
  
  			}
  
  			var maxcount = 0;
  
  			var sourceArray = source.array;
  			var sourceStride = source.stride;
  
  			var array = [];
  
  			if ( primitive.vcount !== undefined ) {
  
  				var index = 0;
  
  				for ( var i = 0, l = vcount.length; i < l; i ++ ) {
  
  					var count = vcount[ i ];
  
  					if ( count === 4 ) {
  
  						var a = index + stride * 0;
  						var b = index + stride * 1;
  						var c = index + stride * 2;
  						var d = index + stride * 3;
  
  						pushVector( a ); pushVector( b ); pushVector( d );
  						pushVector( b ); pushVector( c ); pushVector( d );
  
  					} else if ( count === 3 ) {
  
  						var a = index + stride * 0;
  						var b = index + stride * 1;
  						var c = index + stride * 2;
  
  						pushVector( a ); pushVector( b ); pushVector( c );
  
  					} else {
  
  						maxcount = Math.max( maxcount, count );
  
  					}
  
  					index += stride * count;
  
  				}
  
  				if ( maxcount > 0 ) {
  
  					console.log( 'ColladaLoader: Geometry has faces with more than 4 vertices.' );
  
  				}
  
  			} else {
  
  				for ( var i = 0, l = indices.length; i < l; i += stride ) {
  
  					pushVector( i );
  
  				}
  
  			}
  
  			return new THREE.Float32BufferAttribute( array, sourceStride );
  
  		}
  
  		function getGeometry( id ) {
  
  			return getBuild( library.geometries[ id ], buildGeometry );
  
  		}
  
  		// nodes
  
  		var matrix = new THREE.Matrix4();
  		var vector = new THREE.Vector3();
  
  		function parseNode( xml ) {
  
  			var data = {
  				name: xml.getAttribute( 'name' ),
  				matrix: new THREE.Matrix4(),
  				nodes: [],
  				instanceCameras: [],
  				instanceLights: [],
  				instanceGeometries: [],
  				instanceNodes: []
  			};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeType !== 1 ) continue;
  
  				switch ( child.nodeName ) {
  
  					case 'node':
  
  						if ( child.hasAttribute( 'id' ) ) {
  
  							data.nodes.push( child.getAttribute( 'id' ) );
  							parseNode( child );
  
  						}
  
  						break;
  
  					case 'instance_camera':
  						data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) );
  						break;
  
  					case 'instance_light':
  						data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) );
  						break;
  
  					case 'instance_geometry':
  						data.instanceGeometries.push( parseNodeInstanceGeometry( child ) );
  						break;
  
  					case 'instance_node':
  						data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) );
  						break;
  
  					case 'matrix':
  						var array = parseFloats( child.textContent );
  						data.matrix.multiply( matrix.fromArray( array ).transpose() ); // .transpose() when Z_UP?
  						break;
  
  					case 'translate':
  						var array = parseFloats( child.textContent );
  						vector.fromArray( array );
  						data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) );
  						break;
  
  					case 'rotate':
  						var array = parseFloats( child.textContent );
  						var angle = THREE.Math.degToRad( array[ 3 ] );
  						data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) );
  						break;
  
  					case 'scale':
  						var array = parseFloats( child.textContent );
  						data.matrix.scale( vector.fromArray( array ) );
  						break;
  
  					case 'extra':
  						break;
  
  					default:
  						console.log( child );
  
  				}
  
  			}
  
  			if ( xml.hasAttribute( 'id' ) ) {
  
  				library.nodes[ xml.getAttribute( 'id' ) ] = data;
  
  			}
  
  			return data;
  
  		}
  
  		function parseNodeInstanceGeometry( xml ) {
  
  			var data = {
  				id: parseId( xml.getAttribute( 'url' ) ),
  				materials: {}
  			};
  
  			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
  
  				var child = xml.childNodes[ i ];
  
  				if ( child.nodeName === 'bind_material' ) {
  
  					var instances = child.getElementsByTagName( 'instance_material' );
  
  					for ( var j = 0; j < instances.length; j ++ ) {
  
  						var instance = instances[ j ];
  						var symbol = instance.getAttribute( 'symbol' );
  						var target = instance.getAttribute( 'target' );
  
  						data.materials[ symbol ] = parseId( target );
  
  					}
  
  					break;
  
  				}
  
  			}
  
  			return data;
  
  		}
  
  		function buildNode( data ) {
  
  			var objects = [];
  
  			var matrix = data.matrix;
  			var nodes = data.nodes;
  			var instanceCameras = data.instanceCameras;
  			var instanceLights = data.instanceLights;
  			var instanceGeometries = data.instanceGeometries;
  			var instanceNodes = data.instanceNodes;
  
  			for ( var i = 0, l = nodes.length; i < l; i ++ ) {
  
  				objects.push( getNode( nodes[ i ] ).clone() );
  
  			}
  
  			for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) {
  
  				objects.push( getCamera( instanceCameras[ i ] ).clone() );
  
  			}
  
  			for ( var i = 0, l = instanceLights.length; i < l; i ++ ) {
  
  				objects.push( getLight( instanceLights[ i ] ).clone() );
  
  			}
  
  			for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) {
  
  				var instance = instanceGeometries[ i ];
  				var geometries = getGeometry( instance.id );
  
  				for ( var key in geometries ) {
  
  					var object = geometries[ key ].clone();
  
  					if ( instance.materials[ key ] !== undefined ) {
  
  						object.material = getMaterial( instance.materials[ key ] );
  
  					}
  
  					objects.push( object );
  
  				}
  
  			}
  
  			for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) {
  
  				objects.push( getNode( instanceNodes[ i ] ).clone() );
  
  			}
  
  			var object;
  
  			if ( nodes.length === 0 && objects.length === 1 ) {
  
  				object = objects[ 0 ];
  
  			} else {
  
  				object = new THREE.Group();
  
  				for ( var i = 0; i < objects.length; i ++ ) {
  
  					object.add( objects[ i ] );
  
  				}
  
  			}
  
  			object.name = data.name;
  			object.matrix.copy( matrix );
  			object.matrix.decompose( object.position, object.quaternion, object.scale );
  
  			return object;
  
  		}
  
  		function getNode( id ) {
  
  			return getBuild( library.nodes[ id ], buildNode );
  
  		}
  
  		// visual scenes
  
  		function parseVisualScene( xml ) {
  
  			var data = {
  				name: xml.getAttribute( 'name' ),
  				children: []
  			};
  
  			var elements = getElementsByTagName( xml, 'node' );
  
  			for ( var i = 0; i < elements.length; i ++ ) {
  
  				data.children.push( parseNode( elements[ i ] ) );
  
  			}
  
  			library.visualScenes[ xml.getAttribute( 'id' ) ] = data;
  
  		}
  
  		function buildVisualScene( data ) {
  
  			var group = new THREE.Group();
  			group.name = data.name;
  
  			var children = data.children;
  
  			for ( var i = 0; i < children.length; i ++ ) {
  
  				group.add( buildNode( children[ i ] ) );
  
  			}
  
  			return group;
  
  		}
  
  		function getVisualScene( id ) {
  
  			return getBuild( library.visualScenes[ id ], buildVisualScene );
  
  		}
  
  		// scenes
  
  		function parseScene( xml ) {
  
  			var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ];
  			return getVisualScene( parseId( instance.getAttribute( 'url' ) ) );
  
  		}
  
  		console.time( 'ColladaLoader' );
  
  		if ( text.length === 0 ) {
  
  			return { scene: new THREE.Scene() };
  
  		}
  
  		console.time( 'ColladaLoader: DOMParser' );
  
  		var xml = new DOMParser().parseFromString( text, 'application/xml' );
  
  		console.timeEnd( 'ColladaLoader: DOMParser' );
  
  		var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
  
  		// metadata
  
  		var version = collada.getAttribute( 'version' );
  		console.log( 'ColladaLoader: File version', version );
  
  		var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] );
  
  		//
  
  		var library = {
  			images: {},
  			effects: {},
  			materials: {},
  			cameras: {},
  			lights: {},
  			geometries: {},
  			nodes: {},
  			visualScenes: {}
  		};
  
  		console.time( 'ColladaLoader: Parse' );
  
  		parseLibrary( collada, 'library_images', 'image', parseImage );
  		parseLibrary( collada, 'library_effects', 'effect', parseEffect );
  		parseLibrary( collada, 'library_materials', 'material', parseMaterial );
  		parseLibrary( collada, 'library_cameras', 'camera', parseCamera );
  		parseLibrary( collada, 'library_lights', 'light', parseLight );
  		parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry );
  		parseLibrary( collada, 'library_nodes', 'node', parseNode );
  		parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene );
  
  		console.timeEnd( 'ColladaLoader: Parse' );
  
  		console.time( 'ColladaLoader: Build' );
  
  		buildLibrary( library.images, buildImage );
  		buildLibrary( library.effects, buildEffect );
  		buildLibrary( library.materials, buildMaterial );
  		buildLibrary( library.cameras, buildCamera );
  		buildLibrary( library.lights, buildLight );
  		buildLibrary( library.geometries, buildGeometry );
  		// buildLibrary( library.nodes, buildNode );
  		buildLibrary( library.visualScenes, buildVisualScene );
  
  		console.timeEnd( 'ColladaLoader: Build' );
  
  		// console.log( library );
  
  		var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] );
  
  		if ( asset.upAxis === 'Z_UP' ) {
  
  			scene.rotation.x = - Math.PI / 2;
  
  		}
  
  		scene.scale.multiplyScalar( asset.unit );
  
  		console.timeEnd( 'ColladaLoader' );
  
  		// console.log( scene );
  
  		return {
  			animations: [],
  			kinematics: { joints: [] },
  			library: library,
  			scene: scene
  		};
  
  	}
  
  };
  