Browse Source

collada support

timk 14 years ago
parent
commit
0a11f631b1
3 changed files with 1620 additions and 0 deletions
  1. 64 0
      examples/models/monster.dae
  2. 246 0
      examples/webgl_collada.html
  3. 1310 0
      src/extras/collada/dae.js

File diff suppressed because it is too large
+ 64 - 0
examples/models/monster.dae


+ 246 - 0
examples/webgl_collada.html

@@ -0,0 +1,246 @@
+<!DOCTYPE HTML>
+<html lang="en">
+	<head>
+		<title>three.js webgl - collada</title>
+		<meta charset="utf-8">
+		<style type="text/css">
+			body {
+				font-family: Monospace;
+				background-color: #ffffff;
+				margin: 0px;
+				overflow: hidden;
+			}
+			#log { color:#fff; position:absolute; top:50px; text-align:left; display:block; z-index:100; pointer-events:none; }
+		</style>
+	</head>
+	<body>
+		<pre id="log"></pre>
+
+		<script type="text/javascript" src="../build/Three.js"></script>
+
+		<script type="text/javascript" src="js/Detector.js"></script>
+		<script type="text/javascript" src="js/RequestAnimationFrame.js"></script>
+		<script type="text/javascript" src="js/Stats.js"></script>
+		<script type="text/javascript" src="../src/extras/collada/dae.js"></script>
+		
+		<script type="text/javascript">
+
+			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
+
+			var container, stats;
+
+			var camera, scene, renderer, objects;
+			var particleLight, pointLight;
+			var dae;
+			
+			DAE.load('./models/monster.dae', colladaReady);
+			
+			function colladaReady(collada) {
+				dae = collada.scene;
+				dae.scale.x = dae.scale.y = dae.scale.z = 0.005;
+				dae.updateMatrix();
+
+				init();
+				animate();
+			}
+			
+			function init() {
+					
+				container = document.createElement('div');
+				document.body.appendChild(container);
+
+				camera = new THREE.Camera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
+				camera.position.x = 2;
+				camera.position.y = 2;
+				camera.position.z = 3;
+
+				scene = new THREE.Scene();
+
+				// Grid
+
+				var line_material = new THREE.LineBasicMaterial( { color: 0x0, opacity: 0.2 } ),
+					geometry = new THREE.Geometry(),
+					floor = -0.04, step = 1, size = 14;
+
+				for ( var i = 0; i <= size / step * 2; i ++ ) {
+
+					geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( - size, floor, i * step - size ) ) );
+					geometry.vertices.push( new THREE.Vertex( new THREE.Vector3(   size, floor, i * step - size ) ) );
+
+					geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( i * step - size, floor, -size ) ) );
+					geometry.vertices.push( new THREE.Vertex( new THREE.Vector3( i * step - size, floor,  size ) ) );
+
+				}
+
+				var line = new THREE.Line( geometry, line_material, THREE.LinePieces );
+				scene.addObject( line );
+
+				// Materials
+
+				var generatedTexture = new THREE.Texture( generateTexture() );
+				generatedTexture.needsUpdate = true;
+
+				var materials = [];
+				materials.push( new THREE.MeshLambertMaterial( { map: generatedTexture } ) );
+				materials.push( new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading } ) );
+				materials.push( new THREE.MeshPhongMaterial( { ambient: 0x030303, color: 0xdddddd, specular: 0x009900, shininess: 30, shading: THREE.FlatShading } ) );
+				materials.push( new THREE.MeshNormalMaterial( ) );
+				materials.push( new THREE.MeshBasicMaterial( { color: 0x665500, blending: THREE.AdditiveBlending } ) );
+				//materials.push( new THREE.MeshBasicMaterial( { color: 0xff0000, blending: THREE.SubtractiveBlending } ) );
+
+				materials.push( new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.SmoothShading } ) );
+				materials.push( new THREE.MeshPhongMaterial( { ambient: 0x030303, color: 0xdddddd, specular: 0x009900, shininess: 30, shading: THREE.SmoothShading } ) );
+				materials.push( new THREE.MeshNormalMaterial( { shading: THREE.SmoothShading } ) );
+				materials.push( new THREE.MeshBasicMaterial( { color: 0xffaa00, wireframe: true } ) );
+
+				materials.push( new THREE.MeshDepthMaterial() );
+				materials.push( new THREE.MeshBasicMaterial( { map: generatedTexture } ) );
+
+				// Spheres geometry
+
+				var geometry_smooth = new THREE.SphereGeometry( 70, 32, 16 );
+				var geometry_flat = new THREE.SphereGeometry( 70, 32, 16 );
+				var geometry_pieces = new THREE.SphereGeometry( 70, 32, 16 ); // Extra geometry to be broken down for MeshFaceMaterial
+
+				for ( var i = 0, l = geometry_pieces.faces.length; i < l; i ++ ) {
+
+					var face = geometry_pieces.faces[ i ];
+					if ( Math.random() > 0.7 ) face.materials = [ materials[ Math.floor( Math.random() * materials.length )  ] ];
+
+				}
+
+				materials.push( new THREE.MeshFaceMaterial() );
+
+				objects = [];
+
+				var sphere, geometry, material;
+
+				for ( var i = 0, l = materials.length; i < l; i ++ ) {
+
+					material = materials[ i ];
+
+					geometry = material instanceof THREE.MeshFaceMaterial ? geometry_pieces :
+							   ( material.shading == THREE.FlatShading ? geometry_flat : geometry_smooth );
+
+					sphere = new THREE.Mesh( geometry, material );
+
+					sphere.position.x = ( i % 4 ) * 200 - 400;
+					sphere.position.z = Math.floor( i / 4 ) * 200 - 200;
+
+					sphere.rotation.x = Math.random() * 200 - 100;
+					sphere.rotation.y = Math.random() * 200 - 100;
+					sphere.rotation.z = Math.random() * 200 - 100;
+
+					objects.push( sphere );
+
+					//scene.addObject( sphere );
+
+				}
+				
+				//dae.rotation.x = -Math.PI/2;
+				scene.addObject(dae);
+				//var wall = dae_geometries['wall-geometry'][0];
+		
+				//var dae = new THREE.Mesh( wall, materials[3] );
+				//dae.scale.x = dae.scale.y = dae.scale.z = 100.0;				
+				//scene.addObject(dae);
+
+				particleLight = new THREE.Mesh( new THREE.SphereGeometry( 4, 8, 8 ), new THREE.MeshBasicMaterial( { color: 0xffffff } ) );
+				scene.addObject( particleLight );
+
+				// Lights
+
+				scene.addLight( new THREE.AmbientLight( 0x202020 ) );
+
+				var directionalLight = new THREE.DirectionalLight(/*Math.random() * 0xffffff*/0xcccccc);
+				directionalLight.position.x = Math.random() - 0.5;
+				directionalLight.position.y = Math.random() - 0.5;
+				directionalLight.position.z = Math.random() - 0.5;
+				directionalLight.position.normalize();
+				scene.addLight( directionalLight );
+
+				pointLight = new THREE.PointLight( 0xdddddd, 0.6 );
+				scene.addLight( pointLight );
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				container.appendChild( renderer.domElement );
+
+				stats = new Stats();
+				stats.domElement.style.position = 'absolute';
+				stats.domElement.style.top = '0px';
+				container.appendChild( stats.domElement );
+
+			}
+
+			function generateTexture() {
+
+				var canvas = document.createElement( 'canvas' );
+				canvas.width = 256;
+				canvas.height = 256;
+
+				var context = canvas.getContext( '2d' );
+				var image = context.getImageData( 0, 0, 256, 256 );
+
+				var x = 0, y = 0;
+
+				for ( var i = 0, j = 0, l = image.data.length; i < l; i += 4, j ++ ) {
+
+					x = j % 256;
+					y = x == 0 ? y + 1 : y;
+
+					image.data[ i + 2 ] = Math.floor( x ^ y );
+					image.data[ i + 3 ] = 255;
+
+				}
+
+				context.putImageData( image, 0, 0 );
+
+				return canvas;
+
+			}
+
+			//
+
+			function animate() {
+
+				requestAnimationFrame( animate );
+
+				render();
+				stats.update();
+
+			}
+
+			function render() {
+
+				var timer = new Date().getTime() * 0.0005;
+
+				camera.position.x = Math.cos( timer ) * 10;
+				camera.position.y = 20;
+				camera.position.z = Math.sin( timer ) * 10;
+
+				for ( var i = 0, l = objects.length; i < l; i++ ) {
+
+					var object = objects[ i ];
+
+				//	object.rotation.x += 0.01;
+			//		object.rotation.y += 0.005;
+
+				}
+
+				particleLight.position.x = Math.sin( timer * 4 ) * 300;
+				particleLight.position.y = 800;//Math.cos( timer * 5 ) * 400;
+				particleLight.position.z = Math.cos( timer * 4 ) * 300;
+
+				pointLight.position.x = particleLight.position.x;
+				pointLight.position.y = particleLight.position.y;
+				pointLight.position.z = particleLight.position.z;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1310 - 0
src/extras/collada/dae.js

@@ -0,0 +1,1310 @@
+var DAE = (function() {
+	var COLLADA = null;
+	var scene = null;
+	var daeScene;
+ 	var sources = {};
+	var images = {};
+	var animations = {};
+	var controllers = {};
+	var geometries = {};
+	var materials = {};
+	var effects = {};
+	var visualScenes;
+	var readyCallbackFunc = null;
+	
+	function load(url, readyCallback) {
+		if (document.implementation && document.implementation.createDocument) {
+			var namespaceURL = "http://www.collada.org/2005/11/COLLADASchema";
+			var rootTagName = "COLLADA";
+
+			var xmldoc = document.implementation.createDocument(namespaceURL, rootTagName, null);
+			var me = this;
+			url += "?rnd=" + Math.random();
+
+			var req = new XMLHttpRequest();
+
+			if(req.overrideMimeType) {
+				// need this? yes... if extension is other then *.xml :-S
+				req.overrideMimeType("text/xml");
+			}
+
+			req.onreadystatechange = function() {
+				if(req.readyState == 4) {
+					if(req.status == 0 || req.status == 200) {
+						
+						readyCallbackFunc = readyCallback;
+						parse(req.responseXML);
+					}
+				}
+			}
+			req.open("GET", url, true); 
+			req.send(null);
+		} else {
+			alert("Don't know how to parse XML!");
+		}
+	}
+	
+	function parse(doc, callBack) {
+		COLLADA = doc;
+		callBack = callBack || readyCallbackFunc;
+		
+		images = parseLib("//dae:library_images/dae:image", _Image, "image");
+		materials = parseLib("//dae:library_materials/dae:material", Material, "material");
+		effects = parseLib("//dae:library_effects/dae:effect", Effect, "effect");
+		geometries = parseLib("//dae:library_geometries/dae:geometry", Geometry, "geometry");
+		controllers = parseLib("//dae:library_controllers/dae:controller", Controller, "controller");
+		animations = parseLib("//dae:library_animations/dae:animation", Animation, "animation");
+		visualScenes = parseLib(".//dae:library_visual_scenes/dae:visual_scene", VisualScene, "visual_scene");
+		
+		daeScene = parseScene();
+		scene = new THREE.Object3D();
+		for (var i = 0; i < daeScene.nodes.length; i++) {
+			scene.addChild(createSceneGraph(daeScene.nodes[i]));
+		}
+		
+		createAnimations();
+
+		var result = {
+			scene: scene, 
+			dae: {
+				images: images,
+				materials: materials,
+				effects: effects,
+				geometries: geometries,
+				controllers: controllers,
+				animations: animations,
+				visualScenes: visualScenes,
+				scene: daeScene
+			}
+		};
+		
+		if (callBack) {
+			callBack(result);
+		}
+		return result;
+	}
+	
+	function parseLib(q, classSpec, prefix) {
+		var elements = COLLADA.evaluate(q, 
+			COLLADA, 
+			_nsResolver, 
+			XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+			null);
+		var lib = {};
+		var element = elements.iterateNext();
+		var i = 0;
+		while (element) {
+			var daeElement = (new classSpec()).parse(element);
+			if (daeElement.id.length == 0) daeElement.id = prefix + (i++); 
+			lib[daeElement.id] = daeElement;
+			element = elements.iterateNext();
+		}
+		return lib;
+	}
+	
+	function parseScene() {
+		var sceneElement = COLLADA.evaluate(".//dae:scene/dae:instance_visual_scene", 
+			COLLADA, 
+			_nsResolver, 
+			XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+			null).iterateNext();
+		
+		
+		if (sceneElement) {
+			var url = sceneElement.getAttribute('url').replace(/^#/, '');
+			return visualScenes[url];
+		} else {
+			return null;
+		}
+	}
+	
+	function createAnimations() {
+		for (animation_id in animations) {
+			createAnimation(animations[animation_id]);
+		}
+	}
+	
+	function createAnimation(animation) {
+		
+		for (var i = 0; i < animation.channel.length; i++) {
+			var channel = animation.channel[i];
+			var sampler = animation.sampler[i];
+			
+			var parts = channel.target.split("/");
+			var id = parts.shift();
+			var node = daeScene.getChildById(id, true);
+			var object = scene.getChildByName(id, true);
+			
+			if (!node || !object) {
+				console.error("Dae::createAnimation : could not find node with id=" + id);
+				continue;
+			}
+			
+			if (parts.length > 1) {
+				console.log("Dae::createAnimation : can only handle simple paths [" + channel.target + "]");
+				continue;
+			}
+			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(/\)/, ''));
+				}
+			}
+			var transform = node.getTransformBySid(sid);
+			
+			sampler.create();
+			switch (transform.type) {
+				case 'matrix':
+					//console.log(sampler.output[0]);
+					break;
+				default:
+					break;
+			}
+		}
+	}
+	
+	function createSceneGraph(node) {
+		var obj = new THREE.Object3D();
+		var skinned = false;
+		var i;
+		
+		obj.name = node.id || "";
+		obj.matrixAutoUpdate = false;
+		obj.matrix = node.matrix;
+
+		// 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.geometry]) {
+						var inst_geom = new InstanceGeometry();
+						inst_geom.url = controller.skin.geometry;
+						inst_geom.instance_material = node.controllers[i].instance_material;
+						node.geometries.push(inst_geom);
+						skinned = true;
+					}
+					break;
+				case 'morph': // unsupported
+					console.log("DAE: morph-controller not yet supported.")
+				default:
+					break;
+			}
+		}
+		
+		// geometries
+		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 shaders = {};
+			
+			// collect materials for this geometry instance
+			if (instance_materials) {
+				for (j = 0; j < instance_materials.length; j++) {
+					var inst_material = instance_materials[j];
+					var target = inst_material.target;
+					var symbol = inst_material.symbol;
+					var effect_id = materials[target].instance_effect.url;
+					var effect = effects[effect_id];
+					var shader = effect.shader;
+					shaders[symbol] = shader;
+				}
+			}
+
+			if (geometry) {
+				if (!geometry.mesh || !geometry.mesh.primitives)
+					continue;
+					
+				if (obj.name.length == 0) {
+					obj.name = geometry.id;
+				}
+				
+				var primitives = geometry.mesh.primitives;
+				for (j = 0; j < primitives.length; j++) {
+					var symbol = primitives[j].material;
+					var shader = shaders[symbol];
+					var ambient = shader.ambient ? shader.ambient.color : 0x505050;
+					var diffuse = shader.diffuse ? shader.diffuse.color : 0xff0000;
+					var specular = shader.specular ? shader.specular.color : null;
+					var transparency = shader.transparency;
+					var use_transparency = (transparency !== undefined && transparency < 1.0);
+					var opacity = use_transparency ? transparency : 1.0;
+				
+					var mat = new THREE.MeshLambertMaterial( { 
+						color: diffuse.hex, 
+						opacity: opacity, 
+						transparent: use_transparency,
+						shading: THREE.SmoothShading } );
+					
+					if (shader.type != 'lambert' && shader.shininess && specular && ambient) {
+						mat = new THREE.MeshPhongMaterial( { 
+							ambient: ambient.hex, 
+							color: diffuse.hex, 
+							specular: specular.hex, 
+							shininess: shader.shininess, 
+							shading: THREE.SmoothShading,
+							opacity: opacity,
+							transparent: use_transparency } )
+					}
+					if (Math.abs(primitives[j].geometry.boundingBox.z[0]) < 0.01) {
+						obj.matrix.n34 += Math.random() * 0.001;
+					}
+					if (skinned) {
+						obj.addChild(new THREE.SkinnedMesh( primitives[j].geometry, mat ));
+					} else {
+						obj.addChild(new THREE.Mesh( primitives[j].geometry, mat ));
+					}
+				}
+			}
+		}
+		
+		for (i = 0; i < node.nodes.length; i++) {
+			obj.addChild(createSceneGraph(node.nodes[i]));
+		}
+		
+		return obj;
+	}
+	
+	function getLibraryNode(id) {
+		return COLLADA.evaluate(".//dae:library_nodes//dae:node[@id='"+id+"']", 
+			COLLADA, 
+			_nsResolver, 
+			XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+			null).iterateNext();
+	}
+	
+	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;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	function Skin() {
+		this.geometry = "";
+		this.bindShapeMatrix = null;
+		this.invBindMatrices = [];
+		this.joints = [];
+		this.weights = [];
+	}
+	Skin.prototype.parse = function(element) {
+		var sources = {};
+		var joints, weights;
+		this.geometry = 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':
+					this.bindShapeMatrix = new THREE.Matrix4(_floats(child.textContent));
+					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 = this.joints[value];
+							break;
+						case 'WEIGHT':
+							influence.weight = sources[input.source].data[value];
+							break;
+						default:
+							break;
+					}
+				}
+				vertex_weights.push(influence);
+				index += inputs.length;
+			}
+			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.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.matrix = new THREE.Matrix4();
+	}
+	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.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.type = this.type == 'JOINT' ? this.type : 'NODE';
+		this.nodes = [];
+		this.transforms = [];
+		this.geometries = [];
+		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':
+					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':
+					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.updateMatrix();
+		return this;
+	}
+	Node.prototype.updateMatrix = function() {
+		this.matrix.identity();
+		for (var i = 0; i < this.transforms.length; i++) {
+			this.matrix.multiply(this.matrix, this.transforms[i].matrix);
+		}
+	}
+	function Transform() {
+		this.sid = "";
+		this.type = "";
+		this.data = [];
+		this.matrix = new THREE.Matrix4();
+	}
+	Transform.prototype.parse = function(element) {
+		this.sid = element.getAttribute('sid');
+		this.type = element.nodeName;
+		this.data = _floats(element.textContent);
+		this.updateMatrix();
+		return this;
+	}
+	Transform.prototype.updateMatrix = function() {
+		var angle = 0;
+		
+		this.matrix.identity();
+		
+		switch (this.type) {
+			case 'matrix':
+				this.matrix.set(
+					this.data[0], this.data[1], this.data[2], this.data[3],
+					this.data[4], this.data[5], this.data[6], this.data[7],
+					this.data[8], this.data[9], this.data[10], this.data[11],
+					this.data[12], this.data[13], this.data[14], this.data[15]
+					);
+				break;
+			case 'translate':
+				this.matrix.setTranslation(this.data[0], this.data[1], this.data[2]);
+				break;
+			case 'rotate':
+				angle = this.data[3] * (Math.PI / 180.0);
+				this.matrix.setRotationAxis(new THREE.Vector3(this.data[0], this.data[1], this.data[2]), angle);
+				break;
+			case 'scale':
+				this.matrix.setScale(this.data[0], this.data[1], this.data[2]);
+				break;
+			default:
+				break;
+		}
+		return this.matrix;
+	}
+
+	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 = COLLADA.evaluate(".//dae:instance_material", 
+						child, 
+						_nsResolver, 
+						XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+						null);
+					if (instances) {
+						var instance = instances.iterateNext();
+						while (instance) {
+							this.instance_material.push((new InstanceMaterial()).parse(instance));
+							instance = instances.iterateNext();	
+						}
+					}
+					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 = COLLADA.evaluate(".//dae:instance_material", 
+					child, 
+					_nsResolver, 
+					XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+					null);
+				if (instances) {
+					var instance = instances.iterateNext();
+					while (instance) {
+						this.instance_material.push((new InstanceMaterial()).parse(instance));
+						instance = instances.iterateNext();	
+					}
+				}
+				break;
+			}
+		}
+		return this;
+	}
+	
+	function Geometry() {
+		this.id = "";
+		this.mesh = null;
+	}
+	Geometry.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 'mesh':
+					this.mesh = (new Mesh(this)).parse(child);
+					break;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	
+	function Mesh(geometry) {
+		this.geometry = geometry.id;
+		this.primitives = [];
+		this.vertices = null;
+	}
+	Mesh.prototype.parse = function(element) {
+		this.primitives = [];
+		var i;
+		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;
+				default:
+					break;
+			}
+		}
+		for (i = 0; i < this.primitives.length; i++) {
+			primitive = this.primitives[i];
+			primitive.setVertices(this.vertices);
+			primitive.create();
+		}
+		return this;
+	}
+	
+	function Triangles() {
+		this.material = "";
+		this.count = 0;
+		this.inputs = [];
+		this.p = [];
+		this.aabb = new AABB();
+		this.geometry = new THREE.Geometry();
+	}
+	Triangles.prototype.create = function() {
+		var i = 0, j, k, p = this.p, inputs = this.inputs, input, index;
+		var v, n, t;
+		var texture_sets = [];
+		
+		for (j = 0; j < inputs.length; j++) {
+			input = inputs[j];
+			if (input.semantic == 'TEXCOORD') {
+				texture_sets.push(input.set);
+			}
+		}
+		
+		this.geometry = new THREE.Geometry();
+
+		while (i < p.length) {
+			vs = [];
+			ns = [];
+			ts = {};
+			for (j = 0; j < 3; j++) {
+				for (k = 0; k < inputs.length; k++) {
+					input = inputs[k];
+					source = sources[input.source];
+					index = p[i + (j*inputs.length) + input.offset];
+					numParams = source.accessor.params.length;
+					idx32 = index * numParams;
+					switch (input.semantic) {
+						case 'VERTEX':
+							v = new THREE.Vertex(new THREE.Vector3(source.data[idx32+0], source.data[idx32+1], source.data[idx32+2]));
+							v.daeId = vs.length;
+							vs.push(v);
+							break;
+						case 'NORMAL':
+							n = new THREE.Vector3(source.data[idx32+0], source.data[idx32+1], source.data[idx32+2]);
+							n.daeId = ns.length;
+							ns.push(n);
+							break;
+						case 'TEXCOORD':
+							if (ts[input.set] == undefined) ts[input.set] = [];
+							t = new THREE.UV(source.data[idx32+0], source.data[idx32+1]);
+							t.daeId = ts[input.set].length;
+							ts[input.set].push(t);
+							break;
+						default:
+							break;
+					}
+				}
+			}
+			var has_v = (vs.length == 3);
+			var has_t = (texture_sets.length > 0 && ts[texture_sets[0]].length == 3);
+			if (has_v) {
+				var c = this.geometry.vertices.length;
+				this.geometry.vertices.push(vs[0], vs[1], vs[2]);
+				this.geometry.faces.push( new THREE.Face3(c+0, c+1, c+2, ns ) );
+				if (has_t) {
+					for (k = 0; k < texture_sets.length; k++) {
+						this.geometry.faceVertexUvs[ k ].push( ts[texture_sets[k]] );
+					}
+				}
+			}
+			i += 3 * inputs.length;
+		}
+		this.geometry.material = this.material;
+		this.geometry.computeCentroids();
+		this.geometry.computeFaceNormals();
+		this.geometry.computeVertexNormals();
+		this.geometry.computeBoundingBox();
+	}
+	Triangles.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;
+			}
+		}
+	}
+	Triangles.prototype.parse = function(element) {
+		this.inputs = [];
+		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 'p':
+					this.p = _ints(child.textContent);
+					break;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	Triangles.prototype.calcNormals = function(v, n) {
+		var i;
+		var v0 = [0, 0, 0];
+		var v1 = [0, 0, 0];
+		var v2 = [0, 0, 0];
+		var n0 = [0, 0, 0];
+		for (i = 0; i < v.length; i += 9) {
+			v0[0] = v[i+0]; v0[1] = v[i+1]; v0[2] = v[i+2];
+			v1[0] = v[i+3]; v1[1] = v[i+4]; v1[2] = v[i+5];
+			v2[0] = v[i+6]; v2[1] = v[i+7]; v2[2] = v[i+8];
+			// sub
+			v1[0] = v1[0] - v0[0];
+			v1[1] = v1[1] - v0[1];
+			v1[2] = v1[2] - v0[2];
+			v2[0] = v2[0] - v0[0];
+			v2[1] = v2[1] - v0[1];
+			v2[2] = v2[2] - v0[2];
+			// cross
+			n0[0] = v1[1] * v2[2] - v1[2] * v2[1];
+			n0[1] = v1[2] * v2[0] - v1[0] * v2[2];
+			n0[2] = v1[0] * v2[1] - v1[1] * v2[0];
+			// normalize
+			length = 1.0 / Math.sqrt(n0[0]*n0[0]+n0[1]*n0[1]+n0[2]*n0[2]);
+			if (length == 0) length = 1.0;
+			n[i+0] = n[i+3] = n[i+6] = n0[0] * length;
+			n[i+1] = n[i+4] = n[i+7] = n0[1] * length;
+			n[i+2] = n[i+5] = n[i+8] = n0[2] * length;
+		}
+	}
+	
+	function AABB() {
+		this.minx = 0;	this.miny = 0;	this.minz = 0;
+		this.maxx = 0;	this.maxy = 0;	this.maxz = 0;
+	}
+	AABB.prototype.reset = function() {
+		this.minx = this.miny = this.minz = Number.MAX_VALUE;
+		this.maxx = this.maxy = this.maxz = Number.MIN_VALUE;
+		return this;
+	}
+	AABB.prototype.setTriangles = function(positions) {
+		this.reset();
+		for (var i = 0; i < positions.length; i += 3) {
+			this.minx = Math.min(this.minx, positions[i+0]); 
+			this.miny = Math.min(this.miny, positions[i+1]);
+			this.minz = Math.min(this.minz, positions[i+2]);
+			this.maxx = Math.max(this.maxx, positions[i+0]);
+			this.maxy = Math.max(this.maxy, positions[i+1]);
+			this.maxz = Math.max(this.maxz, positions[i+2]);
+		}
+		return this;
+	}
+	AABB.prototype.centerX = function() { return this.minx + (this.sizeX() / 2); }
+	AABB.prototype.centerY = function() { return this.miny + (this.sizeY() / 2); }
+	AABB.prototype.centerZ = function() { return this.minz + (this.sizeZ() / 2); }
+	AABB.prototype.sizeX = function() { return this.maxx - this.minx; }
+	AABB.prototype.sizeY = function() { return this.maxy - this.miny; }
+	AABB.prototype.sizeZ = function() { return this.maxz - this.minz; }
+	
+	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') {
+				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 'Name':
+				case 'float':
+					return this.data;
+				case 'float4x4':
+					for (var j = 0; j < this.data.length; j += 16) {
+						result.push(new THREE.Matrix4(this.data.slice(j, j+16)));
+					}
+					break;
+				default:
+					console.log('Dae::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(0);
+		this.color.setRGB(Math.random(), Math.random(), Math.random());
+		this.color.a = 1.0;
+		this.texture = null;
+		this.texcoord = null;
+	}
+	ColorOrTexture.prototype.isColor = function() {
+		return (this.texture == null);
+	}
+	ColorOrTexture.prototype.isTexture = function() {
+		return (this.texture != null);
+	}
+	ColorOrTexture.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 '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 'texture':
+					//this.texture = child.getAttribute('texture');
+					//this.texcoord = child.getAttribute('texcoord');
+					break;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	
+	function Shader(type) {
+		this.type = type;
+	}
+	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 'shininess':
+				case 'reflectivity':
+				case 'transparency':
+					var f = evaluateXPath(child, ".//dae:float");
+					if (f.length > 0)
+						this[child.nodeName] = parseFloat(f[0].textContent);
+					break;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	
+	function Effect() {
+		this.id = "";
+		this.name = "";
+		this.shader = null;
+	}
+	Effect.prototype.parse = function(element) {
+		this.id = element.getAttribute('id');
+		this.name = element.getAttribute('name');
+		var technique = COLLADA.evaluate(".//dae:technique", 
+			element, 
+			_nsResolver, 
+			XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+			null).iterateNext();
+		if (!technique) return null;
+		for (var i = 0; i < technique.childNodes.length; i++) {
+			var child = technique.childNodes[i];
+			if (child.nodeType != 1) continue;
+			switch (child.nodeName) {
+				case 'lambert':
+				case 'blinn':
+				case 'phong':
+					this.shader = (new Shader(child.nodeName)).parse(child);
+					break;
+				default:
+					break;
+			}
+		}
+		return this;
+	}
+	
+	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 '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 = "";
+	}
+	Channel.prototype.parse = function(element) {
+		this.source = element.getAttribute('source').replace(/^#/, '');
+		this.target = element.getAttribute('target');
+		return this;
+	}	
+	function Sampler(animation) {
+		this.id = "";
+		this.animation = animation;
+		this.inputs = [];
+		this.input;
+		this.output;
+		this.interpolation;
+	}
+	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();
+					break;
+				case 'INTERPOLATION':
+					this.interpolation = source.read();
+					break;
+				default:
+					console.log(input.semantic);
+					break;
+			}
+		}
+	}
+	
+	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 = [];
+		var i;
+		for (i = 0; i < raw.length; i++) {
+			data.push((raw[i] == 'true' || raw[i] == '1') ? true : false);
+		}
+		return data;
+	}
+	
+	function _floats ( str ) {
+		var raw = _strings(str);
+		var data = [];
+		var i;
+		for (i = 0; i < raw.length; i++) {
+			data.push( parseFloat(raw[i]) );
+		}
+		return data;
+	}
+
+	function _ints ( str ) {
+		var raw = _strings(str);
+		var data = [];
+		var i;
+		for (i = 0; i < raw.length; i++) {
+			data.push( parseInt(raw[i], 10) );
+		}
+		return data;
+	}
+	
+	function _strings ( str ) {
+		return _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 evaluateXPath(node, query) {
+		var instances = COLLADA.evaluate(query, 
+			node, 
+			_nsResolver, 
+			XPathResult.ORDERED_NODE_ITERATOR_TYPE, 
+			null);
+		var inst = instances.iterateNext();
+		var result = [];
+		while (inst) {
+			result.push(inst);
+			inst = instances.iterateNext();
+		} 
+		return result;
+	}
+	return {
+		load: load,
+		geometries : geometries
+	};
+})();
+

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