/** * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com */ THREE.ColladaLoader = function () { var COLLADA = null; var scene = null; var daeScene; var readyCallbackFunc = null; var sources = {}; var images = {}; var animations = {}; var controllers = {}; var geometries = {}; var materials = {}; var effects = {}; var visualScenes; var baseUrl; var morphs; var skins; var flip_uv = true; var preferredShading = THREE.SmoothShading; 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; // force reloading // (should be configurable? sometimes caching is desirable) 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, undefined, url ); } } } req.open( "GET", url, true ); req.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( '/' ) + '/'; } 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" ); morphs = []; skins = []; daeScene = parseScene(); scene = new THREE.Object3D(); for ( var i = 0; i < daeScene.nodes.length; i ++ ) { scene.add( createSceneGraph( daeScene.nodes[ i ] ) ); } createAnimations(); var result = { scene: scene, morphs: morphs, skins: skins, dae: { images: images, materials: materials, effects: effects, geometries: geometries, controllers: controllers, animations: animations, visualScenes: visualScenes, scene: daeScene } }; if ( callBack ) { callBack( result ); } return result; }; function setPreferredShading ( shading ) { preferredShading = shading; }; 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 () { calcAnimationBounds(); for ( var animation_id in animations ) { createAnimation( animations[ animation_id ] ); } }; function createAnimation ( animation ) { }; function calcAnimationBounds () { var start = 1000000; var end = -start; var frames = 0; for ( var id in animations ) { var animation = animations[ 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 }; }; 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 = daeScene.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 ++ ) { skin.bindShapeMatrix.multiplyVector3( geometry.vertices[ i ].position ); } } }; function setupSkeleton ( node, bones, frame, parent ) { node.world = node.world || new THREE.Matrix4(); node.world.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 ); } } if ( parent ) { node.world.multiply( 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.multiply(bone.world, inv); // (IBMi * JMi) 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 { throw 'ColladaLoader: Could not find joint \'' + bone.sid + '\'.'; } } }; 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 = daeScene.getChildById( instanceCtrl.skeleton[0], true ) || daeScene.getChildBySid( instanceCtrl.skeleton[0], true ); 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 ++ ) { skinController.skin.bindShapeMatrix.multiplyVector3( geometry.vertices[i].position ); } // process animation, or simply pose the rig if no animation for ( frame = 0; frame < animationBounds.frames; frame ++ ) { var bones = []; var skinned = []; // zero skinned vertices for ( i = 0; i < geometry.vertices.length; i++ ) { skinned.push( new THREE.Vertex( new THREE.Vector3() ) ); } // 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 ); // skin 'm 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.position.x; v.y = o.position.y; v.z = o.position.z; bones[i].skinningMatrix.multiplyVector3(v); s.position.x += (v.x * weight); s.position.y += (v.y * weight); s.position.z += (v.z * weight); } } geometry.morphTargets.push( { name: "target_" + frame, vertices: skinned } ); } }; 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; } } // FIXME: multi-material mesh? // 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 used_materials = {}; 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 inst_material = instance_materials[j]; var effect_id = materials[inst_material.target].instance_effect.url; var shader = effects[effect_id].shader; shader.material.opacity = !shader.material.opacity ? 1 : shader.material.opacity; used_materials[inst_material.symbol] = shader.material; first_material = shader.material; num_materials ++; } } var mesh; var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading } ); var geom = geometry.mesh.geometry3js; if ( num_materials > 1 ) { material = new THREE.MeshFaceMaterial(); for ( j = 0; j < geom.faces.length; j ++ ) { var face = geom.faces[ j ]; face.materials = [ used_materials[ face.daeMaterial ] ]; } } if ( skinController !== undefined) { applySkin( geom, skinController ); material.morphTargets = true; mesh = new THREE.SkinnedMesh( geom, material ); mesh.skeleton = skinController.skeleton; mesh.skinController = controllers[ skinController.url ]; mesh.skinInstanceController = skinController; mesh.name = 'skin_' + skins.length; 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; } node.geometries.length > 1 ? obj.add( mesh ) : obj = mesh; } } obj.name = node.id || ""; node.matrix.decompose( obj.position, obj.rotation, obj.scale ); 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 ) { return COLLADA.evaluate(".//dae:library_nodes//dae:node[@id='"+id+"']", COLLADA, _nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null).iterateNext(); }; 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 ( var 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 = matrix.multiply( matrix, value ); } else { // FIXME: handle other types matrix = matrix.multiply( matrix, transform.matrix ); } } else { matrix = matrix.multiply( matrix, transform.matrix ); } } else { matrix = matrix.multiply( matrix, transform.matrix ); } } return matrix; }; function bakeAnimations ( node ) { if ( node.channels && node.channels.length ) { var frameDuration = calcFrameDuration( node ); var t, matrix; var keys = []; for ( t = node.startTime; t < node.endTime; t += frameDuration ) { matrix = calcMatrixAt( node, t ); //keys.push({time: t, mat: matrix.flatten()}) keys.push({ time: t, pos: [matrix.n14, matrix.n24, matrix.n34], rotq: [0, 0, 0, 1], scl: [1,1,1] }); } node.keys = keys; } }; 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 = new THREE.Matrix4(); this.bindShapeMatrix.set( f[0], f[1], f[2], f[3], f[4], f[5], f[6], f[7], f[8], f[9], f[10], f[11], f[12], f[13], f[14], f[15] ); 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.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.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.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; 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': console.warn( 'polygon holes not yet supported!' ); case 'polylist': this.primitives.push( ( new Polylist().parse( child ) ) ); break; default: break; } } var vertex_store = {}; function get_vertex ( v, index ) { var hash = _hash_vector3( v.position ); if ( vertex_store[ hash ] === undefined ) { vertex_store[ hash ] = { v: v, index: index }; } return vertex_store[ hash ]; } this.geometry3js = new THREE.Geometry(); var vertexData = sources[ this.vertices.input['POSITION'].source ].data; for ( i = 0, j = 0; i < vertexData.length; i += 3, j ++ ) { var v = new THREE.Vertex( new THREE.Vector3( vertexData[ i ], vertexData[ i + 1 ], vertexData[ i + 2 ] ) ); get_vertex( v, j ); this.geometry3js.vertices.push( v ); } for ( i = 0; i < this.primitives.length; i ++ ) { var primitive = this.primitives[ i ]; primitive.setVertices( this.vertices ); this.handlePrimitive( primitive, this.geometry3js, vertex_store ); } this.geometry3js.computeCentroids(); this.geometry3js.computeFaceNormals(); this.geometry3js.computeVertexNormals(); this.geometry3js.computeBoundingBox(); return this; }; Mesh.prototype.handlePrimitive = function( primitive, geom, vertex_store ) { var i = 0, j, k, p = primitive.p, inputs = primitive.inputs; var input, index, idx32; var source, numParams; var vcIndex = 0, vcount = 3; var texture_sets = []; for ( j = 0; j < inputs.length; j ++ ) { input = inputs[ j ]; switch ( input.semantic ) { case 'TEXCOORD': texture_sets.push( input.set ); break; } } while ( i < p.length ) { var vs = []; var ns = []; var ts = {}; var cs = []; if ( primitive.vcount ) { vcount = primitive.vcount[ vcIndex ++ ]; } for ( j = 0; j < vcount; 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': var hash = _hash_vector3( geom.vertices[ index ].position ); vs.push( vertex_store[ hash ].index ); break; case 'NORMAL': ns.push( new THREE.Vector3( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); break; case 'TEXCOORD': if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; ts[ input.set ].push( new THREE.UV( 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; } } } var face, uv; if ( vcount == 3 ) { face = new THREE.Face3( vs[0], vs[1], vs[2], [ ns[0], ns[1], ns[2] ], cs.length ? cs : new THREE.Color() ); } else if ( vcount == 4 ) { face = new THREE.Face4( vs[0], vs[1], vs[2], vs[3], [ ns[0], ns[1], ns[2], ns[3] ], cs.length ? cs : new THREE.Color() ); } face.daeMaterial = primitive.material; geom.faces.push( face ); for ( k = 0; k < texture_sets.length; k ++ ) { uv = ts[ texture_sets[ k ] ]; geom.faceVertexUvs[ k ].push( [ uv[0], uv[1], uv[2] ] ); } i += inputs.length * vcount; } }; function Polylist () { }; Polylist.prototype = new Triangles(); Polylist.prototype.constructor = Polylist; function Triangles( flip_uv ) { this.material = ""; this.count = 0; this.inputs = []; this.vcount = null; this.p = []; this.geometry = new THREE.Geometry(); }; 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 'vcount': this.vcount = _ints( child.textContent ); break; case 'p': this.p = _ints( child.textContent ); break; default: break; } } return this; }; 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 = new THREE.Matrix4(); m.set( s[0], s[1], s[2], s[3], s[4], s[5], s[6], s[7], s[8], s[9], s[10], s[11], s[12], s[13], s[14], s[15] ); 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( 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, 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 '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; } } this.create(); return this; }; Shader.prototype.create = function() { var props = {}; var transparent = ( this['transparency'] !== undefined && this['transparency'] < 1.0 ); for ( var prop in this ) { switch ( prop ) { case 'ambient': case 'emission': case 'diffuse': case 'specular': var cot = this[prop]; if ( cot instanceof ColorOrTexture ) { if ( cot.isTexture() ) { if ( this.effect.sampler && this.effect.surface ) { if ( this.effect.sampler.source == this.effect.surface.sid ) { var image = images[this.effect.surface.init_from]; if ( image ) { props['map'] = THREE.ImageUtils.loadTexture(baseUrl + image.init_from); props['map'].wrapS = THREE.RepeatWrapping; props['map'].wrapT = THREE.RepeatWrapping; props['map'].repeat.x = 1; props['map'].repeat.y = -1; } } } } else { if ( prop == 'diffuse' ) { props[ 'color' ] = cot.color.getHex(); } else if ( !transparent ) { props[ prop ] = cot.color.getHex(); } } } break; case 'shininess': case 'reflectivity': props[ prop ] = this[ prop ]; break; case 'transparency': if ( transparent ) { props[ 'transparent' ] = true; props[ 'opacity' ] = this[ prop ]; transparent = true; } break; default: break; } } props[ 'shading' ] = preferredShading; this.material = new THREE.MeshLambertMaterial( props ); switch ( this.type ) { case 'constant': case 'lambert': break; case 'phong': case 'blinn': default: /* if ( !transparent ) { // this.material = new THREE.MeshPhongMaterial(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 = null; this.sampler = null; }; 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' ); 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 = ( new Surface( this ) ).parse( child ); this.surface.sid = sid; break; case 'sampler2D': this.sampler = ( new Sampler2D( this ) ).parse( child ); this.sampler.sid = sid; 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 '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 'lambert': case 'blinn': case 'phong': this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); 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 '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.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 ); 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(/\)/, '') ); } } this.sid = sid; this.dotSyntax = dotSyntax; this.arrSyntax = arrSyntax; this.arrIndices = arrIndices; this.member = member; return this; }; function Sampler ( animation ) { this.id = ""; this.animation = animation; this.inputs = []; this.input = null; this.output = 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(); 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; } }; 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; 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 = []; for ( var i = 0; i < raw.length; i ++ ) { data.push( parseFloat( raw[ i ] ) ); } return data; }; function _ints ( str ) { var raw = _strings( str ); var data = []; for ( var 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 _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 _hash_vertex ( v, n, t0, t1, precision ) { precision = precision || 2; var s = v instanceof THREE.Vertex ? _hash_vector3( v.position, precision ) : _hash_vector3( v, precision ); if ( n === undefined ) { s += '_0.00,0.00,0.00'; } else { s += '_' + _hash_vector3( n, precision ); } if ( t0 === undefined ) { s += '_0.00,0.00'; } else { s += '_' + _hash_uv( t0, precision ); } if ( t1 === undefined ) { s += '_0.00,0.00'; } else { s += '_' + _hash_uv( t1, precision ); } return s; }; function _hash_uv ( uv, num ) { var s = ''; s += _format_float( uv.u, num ) + ','; s += _format_float( uv.v, num ); return s; }; function _hash_vector3 ( vec, num ) { var s = ''; s += _format_float( vec.x, num ) + ','; s += _format_float( vec.y, num ) + ','; s += _format_float( vec.z, num ); return s; }; 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, parse: parse, setPreferredShading: setPreferredShading, applySkin: applySkin, geometries : geometries }; };