UNPKG

threex

Version:

Game Extensions for three.js http://www.threejsgames.com/extensions/

2,747 lines (1,608 loc) 83.1 kB
/** * @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 cameras = {}; var lights = {}; var animData; var visualScenes; var baseUrl; var morphs; var skins; var flip_uv = true; var preferredShading = THREE.SmoothShading; var options = { // Force Geometry to always be centered at the local origin of the // containing Mesh. centerGeometry: false, // Axis conversion is done for geometries, animations, and controllers. // If we ever pull cameras or lights out of the COLLADA file, they'll // need extra work. convertUpAxis: false, subdivideFaces: true, upAxis: 'Y', // For reflective or refractive materials we'll use this cubemap defaultEnvMap: null }; var colladaUnit = 1.0; var colladaUp = 'Y'; var upConversion = null; function load ( url, readyCallback, progressCallback ) { var length = 0; if ( document.implementation && document.implementation.createDocument ) { var request = new XMLHttpRequest(); request.onreadystatechange = function() { if( request.readyState == 4 ) { if( request.status == 0 || request.status == 200 ) { if ( request.responseXML ) { readyCallbackFunc = readyCallback; parse( request.responseXML, undefined, url ); } else if ( request.responseText ) { readyCallbackFunc = readyCallback; var xmlParser = new DOMParser(); var responseXML = xmlParser.parseFromString( request.responseText, "application/xml" ); parse( responseXML, undefined, url ); } else { console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" ); } } } else if ( request.readyState == 3 ) { if ( progressCallback ) { if ( length == 0 ) { length = request.getResponseHeader( "Content-Length" ); } progressCallback( { total: length, loaded: request.responseText.length } ); } } } request.open( "GET", url, true ); request.send( null ); } else { alert( "Don't know how to parse XML!" ); } }; function parse( doc, callBack, url ) { COLLADA = doc; callBack = callBack || readyCallbackFunc; if ( url !== undefined ) { var parts = url.split( '/' ); parts.pop(); baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; } parseAsset(); setUpConversion(); images = parseLib( "//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" ); cameras = parseLib( ".//dae:library_cameras/dae:camera", Camera, "camera" ); lights = parseLib( ".//dae:library_lights/dae:light", Light, "light" ); 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 ] ) ); } // unit conversion scene.scale.multiplyScalar( colladaUnit ); createAnimations(); var result = { scene: scene, morphs: morphs, skins: skins, animations: animData, dae: { images: images, materials: materials, cameras: cameras, lights: lights, effects: effects, geometries: geometries, controllers: controllers, animations: animations, visualScenes: visualScenes, scene: daeScene } }; if ( callBack ) { callBack( result ); } return result; }; function setPreferredShading ( shading ) { preferredShading = shading; }; function parseAsset () { var elements = COLLADA.evaluate( '//dae:asset', COLLADA, _nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ); var element = elements.iterateNext(); if ( element && element.childNodes ) { for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; switch ( child.nodeName ) { case 'unit': var meter = child.getAttribute( 'meter' ); if ( meter ) { colladaUnit = parseFloat( meter ); } break; case 'up_axis': colladaUp = child.textContent.charAt(0); break; } } } }; function parseLib ( q, classSpec, prefix ) { var elements = COLLADA.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 || 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.length > 0 ? url : 'visual_scene0' ]; } else { return null; } }; function createAnimations() { animData = []; // fill in the keys recurseHierarchy( scene ); }; function recurseHierarchy( node ) { var n = daeScene.getChildById( node.name, true ), newData = null; if ( n && n.keys ) { newData = { fps: 60, hierarchy: [ { node: n, keys: n.keys, sids: n.sids } ], node: node, name: 'animation_' + node.name, length: 0 }; animData.push(newData); for ( var i = 0, il = n.keys.length; i < il; i++ ) { newData.length = Math.max( newData.length, n.keys[i].time ); } } else { newData = { hierarchy: [ { keys: [], sids: [] } ] } } for ( var i = 0, il = node.children.length; i < il; i++ ) { var d = recurseHierarchy( node.children[i] ); for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { newData.hierarchy.push( { keys: [], sids: [] } ); } } return newData; }; function calcAnimationBounds () { var start = 1000000; var end = -start; var frames = 0; 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 ++ ) { geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix ); } } }; 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.multiplyMatrices( parent, node.world ); } bones.push( node ); for ( var i = 0; i < node.nodes.length; i ++ ) { setupSkeleton( node.nodes[ i ], bones, frame, node.world ); } }; function setupSkinningMatrices ( bones, skin ) { // FIXME: this is dumb... for ( var i = 0; i < bones.length; i ++ ) { var bone = bones[ i ]; var found = -1; if ( bone.type != 'JOINT' ) continue; for ( var j = 0; j < skin.joints.length; j ++ ) { if ( bone.sid == skin.joints[ j ] ) { found = j; break; } } if ( found >= 0 ) { var inv = skin.invBindMatrices[ found ]; bone.invBindMatrix = inv; bone.skinningMatrix = new THREE.Matrix4(); bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi) bone.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 ++ ) { geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix ); } // 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.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.x; v.y = o.y; v.z = o.z; v.applyMatrix4( bones[i].skinningMatrix ); s.x += (v.x * weight); s.y += (v.y * weight); s.z += (v.z * weight); } } 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; } } // geometries var double_sided_materials = {}; for ( i = 0; i < node.geometries.length; i ++ ) { var instance_geometry = node.geometries[i]; var instance_materials = instance_geometry.instance_material; var geometry = geometries[ instance_geometry.url ]; var used_materials = {}; var used_materials_array = []; var num_materials = 0; var first_material; if ( geometry ) { if ( !geometry.mesh || !geometry.mesh.primitives ) continue; if ( obj.name.length == 0 ) { obj.name = geometry.id; } // collect used fx for this geometry-instance if ( instance_materials ) { for ( j = 0; j < instance_materials.length; j ++ ) { var instance_material = instance_materials[ j ]; var mat = materials[ instance_material.target ]; var effect_id = mat.instance_effect.url; var shader = effects[ effect_id ].shader; var material3js = shader.material; if ( geometry.doubleSided ) { if ( !( instance_material.symbol in double_sided_materials ) ) { var _copied_material = material3js.clone(); _copied_material.side = THREE.DoubleSide; double_sided_materials[ instance_material.symbol ] = _copied_material; } material3js = double_sided_materials[ instance_material.symbol ]; } material3js.opacity = !material3js.opacity ? 1 : material3js.opacity; used_materials[ instance_material.symbol ] = num_materials; used_materials_array.push( material3js ); first_material = material3js; first_material.name = mat.name == null || mat.name === '' ? mat.id : mat.name; num_materials ++; } } var mesh; var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, shading: THREE.FlatShading, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } ); var geom = geometry.mesh.geometry3js; if ( num_materials > 1 ) { material = new THREE.MeshFaceMaterial( used_materials_array ); for ( j = 0; j < geom.faces.length; j ++ ) { var face = geom.faces[ j ]; face.materialIndex = used_materials[ face.daeMaterial ] } } if ( skinController !== undefined ) { applySkin( geom, skinController ); material.morphTargets = true; mesh = new THREE.SkinnedMesh( geom, material, false ); mesh.skeleton = skinController.skeleton; mesh.skinController = controllers[ skinController.url ]; mesh.skinInstanceController = skinController; mesh.name = 'skin_' + skins.length; 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; } } for ( i = 0; i < node.cameras.length; i ++ ) { var instance_camera = node.cameras[i]; var cparams = cameras[instance_camera.url]; obj = new THREE.PerspectiveCamera(cparams.fov, parseFloat(cparams.aspect_ratio), parseFloat(cparams.znear), parseFloat(cparams.zfar)); } for ( i = 0; i < node.lights.length; i ++ ) { var instance_light = node.lights[i]; var lparams = lights[instance_light.url]; if ( lparams && lparams.technique ) { var color = lparams.color.getHex(); var intensity = lparams.intensity; var distance = 0; var angle = lparams.falloff_angle; var exponent; // Intentionally undefined, don't know what this is yet switch ( lparams.technique ) { case 'directional': obj = new THREE.DirectionalLight( color, intensity, distance ); break; case 'point': obj = new THREE.PointLight( color, intensity, distance ); break; case 'spot': obj = new THREE.SpotLight( color, intensity, distance, angle, exponent ); break; case 'ambient': obj = new THREE.AmbientLight( color ); break; } } } obj.name = node.name || node.id || ""; obj.matrix = node.matrix; obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); if ( options.centerGeometry && obj.geometry ) { var delta = THREE.GeometryUtils.center( obj.geometry ); delta.multiply( obj.scale ); delta.applyQuaternion( obj.quaternion ); obj.position.sub( delta ); } for ( i = 0; i < node.nodes.length; i ++ ) { obj.add( createSceneGraph( node.nodes[i], node ) ); } return obj; }; function getJointId( skin, id ) { for ( var i = 0; i < skin.joints.length; i ++ ) { if ( skin.joints[ i ] == id ) { return i; } } }; function getLibraryNode( id ) { 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 ( j = 0; j < sampler.input.length - 1; j ++ ) { if ( sampler.input[ j + 1 ] > t ) { value = sampler.output[ j ]; //console.log(value.flatten) break; } } if ( value !== undefined ) { if ( value instanceof THREE.Matrix4 ) { matrix.multiplyMatrices( matrix, value ); } else { // FIXME: handle other types matrix.multiplyMatrices( matrix, transform.matrix ); } } else { matrix.multiplyMatrices( matrix, transform.matrix ); } } else { matrix.multiplyMatrices( matrix, transform.matrix ); } } return matrix; }; function bakeAnimations ( node ) { if ( node.channels && node.channels.length ) { var keys = [], sids = []; for ( var i = 0, il = node.channels.length; i < il; i++ ) { var channel = node.channels[i], fullSid = channel.fullSid, sampler = channel.sampler, input = sampler.input, transform = node.getTransformBySid( channel.sid ), member; if ( channel.arrIndices ) { member = []; for ( var j = 0, jl = channel.arrIndices.length; j < jl; j++ ) { member[ j ] = getConvertedIndex( channel.arrIndices[ j ] ); } } else { member = getConvertedMember( channel.member ); } if ( transform ) { if ( sids.indexOf( fullSid ) === -1 ) { sids.push( fullSid ); } for ( var j = 0, jl = input.length; j < jl; j++ ) { var time = input[j], data = sampler.getData( transform.type, j ), key = findKey( keys, time ); if ( !key ) { key = new Key( time ); var timeNdx = findTimeNdx( keys, time ); keys.splice( timeNdx == -1 ? keys.length : timeNdx, 0, key ); } key.addTarget( fullSid, transform, member, data ); } } else { console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); } } // post process for ( var i = 0; i < sids.length; i++ ) { var sid = sids[ i ]; for ( var j = 0; j < keys.length; j++ ) { var key = keys[ j ]; if ( !key.hasTarget( sid ) ) { interpolateKeys( keys, key, j, sid ); } } } node.keys = keys; node.sids = sids; } }; function findKey ( keys, time) { var retVal = null; for ( var i = 0, il = keys.length; i < il && retVal == null; i++ ) { var key = keys[i]; if ( key.time === time ) { retVal = key; } else if ( key.time > time ) { break; } } return retVal; }; function findTimeNdx ( keys, time) { var ndx = -1; for ( var i = 0, il = keys.length; i < il && ndx == -1; i++ ) { var key = keys[i]; if ( key.time >= time ) { ndx = i; } } return ndx; }; function interpolateKeys ( keys, key, ndx, fullSid ) { var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx-1 : 0 ), nextKey = getNextKeyWith( keys, fullSid, ndx+1 ); if ( prevKey && nextKey ) { var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), prevTarget = prevKey.getTarget( fullSid ), nextData = nextKey.getTarget( fullSid ).data, prevData = prevTarget.data, data; if ( prevTarget.type === 'matrix' ) { data = prevData; } else if ( prevData.length ) { data = []; for ( var i = 0; i < prevData.length; ++i ) { data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; } } else { data = prevData + ( nextData - prevData ) * scale; } key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); } }; // Get next key with given sid function getNextKeyWith( keys, fullSid, ndx ) { for ( ; ndx < keys.length; ndx++ ) { var key = keys[ ndx ]; if ( key.hasTarget( fullSid ) ) { return key; } } return null; }; // Get previous key with given sid function getPrevKeyWith( keys, fullSid, ndx ) { ndx = ndx >= 0 ? ndx : ndx + keys.length; for ( ; ndx >= 0; ndx-- ) { var key = keys[ ndx ]; if ( key.hasTarget( fullSid ) ) { return key; } } return null; }; function _Image() { this.id = ""; this.init_from = ""; }; _Image.prototype.parse = function(element) { this.id = element.getAttribute('id'); for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeName == 'init_from' ) { this.init_from = child.textContent; } } return this; }; function Controller() { this.id = ""; this.name = ""; this.type = ""; this.skin = null; this.morph = null; }; Controller.prototype.parse = function( element ) { this.id = element.getAttribute('id'); this.name = element.getAttribute('name'); this.type = "none"; for ( var i = 0; i < element.childNodes.length; i++ ) { var child = element.childNodes[ i ]; switch ( child.nodeName ) { case 'skin': this.skin = (new Skin()).parse(child); this.type = child.nodeName; break; case 'morph': this.morph = (new Morph()).parse(child); this.type = child.nodeName; break; default: break; } } return this; }; function Morph() { this.method = null; this.source = null; this.targets = null; this.weights = null; }; Morph.prototype.parse = function( element ) { var sources = {}; var inputs = []; var i; this.method = element.getAttribute( 'method' ); this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); for ( i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'source': var source = ( new Source() ).parse( child ); sources[ source.id ] = source; break; case 'targets': inputs = this.parseInputs( child ); break; default: console.log( child.nodeName ); break; } } for ( i = 0; i < inputs.length; i ++ ) { var input = inputs[ i ]; var source = sources[ input.source ]; switch ( input.semantic ) { case 'MORPH_TARGET': this.targets = source.read(); break; case 'MORPH_WEIGHT': this.weights = source.read(); break; default: break; } } return this; }; Morph.prototype.parseInputs = function(element) { var inputs = []; for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[i]; if ( child.nodeType != 1) continue; switch ( child.nodeName ) { case 'input': inputs.push( (new Input()).parse(child) ); break; default: break; } } return inputs; }; function Skin() { this.source = ""; this.bindShapeMatrix = null; this.invBindMatrices = []; this.joints = []; this.weights = []; }; Skin.prototype.parse = function( element ) { var sources = {}; var joints, weights; this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); this.invBindMatrices = []; this.joints = []; this.weights = []; for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[i]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'bind_shape_matrix': var f = _floats(child.textContent); this.bindShapeMatrix = getConvertedMat4( f ); break; case 'source': var src = new Source().parse(child); sources[ src.id ] = src; break; case 'joints': joints = child; break; case 'vertex_weights': weights = child; break; default: console.log( child.nodeName ); break; } } this.parseJoints( joints, sources ); this.parseWeights( weights, sources ); return this; }; Skin.prototype.parseJoints = function ( element, sources ) { for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'input': var input = ( new Input() ).parse( child ); var source = sources[ input.source ]; if ( input.semantic == 'JOINT' ) { this.joints = source.read(); } else if ( input.semantic == 'INV_BIND_MATRIX' ) { this.invBindMatrices = source.read(); } break; default: break; } } }; Skin.prototype.parseWeights = function ( element, sources ) { var v, vcount, inputs = []; for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'input': inputs.push( ( new Input() ).parse( child ) ); break; case 'v': v = _ints( child.textContent ); break; case 'vcount': vcount = _ints( child.textContent ); break; default: break; } } var index = 0; for ( var i = 0; i < vcount.length; i ++ ) { var numBones = vcount[i]; var vertex_weights = []; for ( var j = 0; j < numBones; j++ ) { var influence = {}; for ( var k = 0; k < inputs.length; k ++ ) { var input = inputs[ k ]; var value = v[ index + input.offset ]; switch ( input.semantic ) { case 'JOINT': influence.joint = value;//this.joints[value]; break; case 'WEIGHT': influence.weight = sources[ input.source ].data[ value ]; break; default: break; } } vertex_weights.push( influence ); index += inputs.length; } for ( var j = 0; j < vertex_weights.length; j ++ ) { vertex_weights[ j ].index = i; } this.weights.push( vertex_weights ); } }; function VisualScene () { this.id = ""; this.name = ""; this.nodes = []; this.scene = new THREE.Object3D(); }; VisualScene.prototype.getChildById = function( id, recursive ) { for ( var i = 0; i < this.nodes.length; i ++ ) { var node = this.nodes[ i ].getChildById( id, recursive ); if ( node ) { return node; } } return null; }; VisualScene.prototype.getChildBySid = function( sid, recursive ) { for ( var i = 0; i < this.nodes.length; i ++ ) { var node = this.nodes[ i ].getChildBySid( sid, recursive ); if ( node ) { return node; } } return null; }; VisualScene.prototype.parse = function( element ) { this.id = element.getAttribute( 'id' ); this.name = element.getAttribute( 'name' ); this.nodes = []; for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'node': this.nodes.push( ( new Node() ).parse( child ) ); break; default: break; } } return this; }; function Node() { this.id = ""; this.name = ""; this.sid = ""; this.nodes = []; this.controllers = []; this.transforms = []; this.geometries = []; this.channels = []; this.matrix = new THREE.Matrix4(); }; Node.prototype.getChannelForTransform = function( transformSid ) { for ( var i = 0; i < this.channels.length; i ++ ) { var channel = this.channels[i]; var parts = channel.target.split('/'); var id = parts.shift(); var sid = parts.shift(); var dotSyntax = (sid.indexOf(".") >= 0); var arrSyntax = (sid.indexOf("(") >= 0); var arrIndices; var member; if ( dotSyntax ) { parts = sid.split("."); sid = parts.shift(); member = parts.shift(); } else if ( arrSyntax ) { arrIndices = sid.split("("); sid = arrIndices.shift(); for ( var j = 0; j < arrIndices.length; j ++ ) { arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) ); } } if ( sid == transformSid ) { channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices }; return channel; } } return null; }; Node.prototype.getChildById = function ( id, recursive ) { if ( this.id == id ) { return this; } if ( recursive ) { for ( var i = 0; i < this.nodes.length; i ++ ) { var n = this.nodes[ i ].getChildById( id, recursive ); if ( n ) { return n; } } } return null; }; Node.prototype.getChildBySid = function ( sid, recursive ) { if ( this.sid == sid ) { return this; } if ( recursive ) { for ( var i = 0; i < this.nodes.length; i ++ ) { var n = this.nodes[ i ].getChildBySid( sid, recursive ); if ( n ) { return n; } } } return null; }; Node.prototype.getTransformBySid = function ( sid ) { for ( var i = 0; i < this.transforms.length; i ++ ) { if ( this.transforms[ i ].sid == sid ) return this.transforms[ i ]; } return null; }; Node.prototype.parse = function( element ) { var url; this.id = element.getAttribute('id'); this.sid = element.getAttribute('sid'); this.name = element.getAttribute('name'); this.type = element.getAttribute('type'); this.type = this.type == 'JOINT' ? this.type : 'NODE'; this.nodes = []; this.transforms = []; this.geometries = []; this.cameras = []; this.lights = []; this.controllers = []; this.matrix = new THREE.Matrix4(); for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType != 1 ) continue; switch ( child.nodeName ) { case 'node': this.nodes.push( ( new Node() ).parse( child ) ); break; case 'instance_camera': this.cameras.push( ( new InstanceCamera() ).parse( child ) ); break; case 'instance_controller': this.controllers.push( ( new InstanceController() ).parse( child ) ); break; case 'instance_geometry': this.geometries.push( ( new InstanceGeometry() ).parse( child ) ); break; case 'instance_light': this.lights.push( ( new InstanceLight() ).parse( child ) ); break; case 'instance_node': url = child.getAttribute( 'url' ).replace( /^#/, '' ); var iNode = getLibraryNode( url ); if ( iNode ) { this.nodes.push( ( new Node() ).parse( iNode )) ; } break; case 'rotate': case 'translate': case 'scale': case 'matrix': case 'lookat': case 'skew': this.transforms.push( ( new Transform() ).parse( child ) ); break; case 'extra': break; default: console.log( child.nodeName ); break; } } this.channels = getChannelsForNode( this ); bakeAnimations( this ); this.updateMatrix(); return this; }; Node.prototype.updateMatrix = function () { this.matrix.identity(); for ( var i = 0; i < this.transforms.length; i ++ ) { this.transforms[ i ].apply( this.matrix ); } }; function Transform () { this.sid = ""; this.type = ""; this.data = []; this.obj = null; }; Transform.prototype.parse = function ( element ) { this.sid = element.getAttribute( 'sid' ); this.type = element.nodeName; this.data = _floats( element.textContent ); this.convert(); return this; }; Transform.prototype.convert = function () { switch ( this.type ) { case 'matrix': this.obj = getConvertedMat4( this.data ); break; case 'rotate': this.angle = THREE.Math.degToRad( this.data[3] ); case 'translate': fixCoords( this.data, -1 ); this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); break; case 'scale': fixCoords( this.data, 1 ); this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); break; default: console.log( 'Can not convert Transform of type ' + this.type ); break; } }; Transform.prototype.apply = function () { var m1 = new THREE.Matrix4(); return function ( matrix ) { switch ( this.type ) { case 'matrix': matrix.multiply( this.obj ); break; case 'translate': matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) ); break; case 'rotate': matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) ); break; case 'scale': matrix.scale( this.obj ); break; } }; }(); Transform.prototype.update = function ( data, member ) { var members = [ 'X', 'Y', 'Z', 'ANGLE' ]; switch ( this.type ) { case 'matrix': if ( ! member ) { this.obj.copy( data ); } else if ( member.length === 1 ) { switch ( member[ 0 ] ) { case 0: this.obj.n11 = data[ 0 ]; this.obj.n21 = data[ 1 ]; this.obj.n31 = data[ 2 ]; this.obj.n41 = data[ 3 ]; break; case 1: this.obj.n12 = data[ 0 ]; this.obj.n22 = data[ 1 ]; this.obj.n32 = data[ 2 ]; this.obj.n42 = data[ 3 ]; break; case 2: this.obj.n13 = data[ 0 ]; this.obj.n23 = data[ 1 ]; this.obj.n33 = data[ 2 ]; this.obj.n43 = data[ 3 ]; break; case 3: this.obj.n14 = data[ 0 ]; this.obj.n24 = data[ 1 ]; this.obj.n34 = data[ 2 ]; this.obj.n44 = data[ 3 ]; break; } } else if ( member.length === 2 ) { var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 ); this.obj[ propName ] = data; } else { console.log('Incorrect addressing of matrix in transform.'); } break; case 'translate': case 'scale': if ( Object.prototype.toString.call( member ) === '[object Array]' ) { member = members[ member[ 0 ] ]; } switch ( member ) { case 'X': this.obj.x = data; break; case 'Y': this.obj.y = data; break; case 'Z': this.obj.z = data; break; default: this.obj.x = data[ 0 ]; this.obj.y = data[ 1 ]; this.obj.z = data[ 2 ]; break; } break; case 'rotate': if ( Object.prototype.toString.call( member ) === '[object Array]' ) { member = members[ member[ 0 ] ]; } switch ( member ) { case 'X': this.obj.x = data; break; case 'Y': this.obj.y = data; break; case 'Z': this.obj.z = data; break; case 'ANGLE': this.angle = THREE.Math.degToRad( data ); break; default: this.obj.x = data[ 0 ]; this.obj.y = data[ 1 ]; this.obj.z = data[ 2 ]; this.angle = THREE.Math.degToRad( data[ 3 ] ); break; } break; } }; function InstanceController() { this.url = ""; this.skeleton = []; this.instance_material = []; }; InstanceController.prototype.parse = function ( element ) { this.url = element.getAttribute('url').replace(/^#/, ''); this.skeleton = []; this.instance_material = []; for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; if ( child.nodeType !== 1 ) continue; switch ( child.nodeName ) { case 'skeleton': this.skeleton.push( child.textContent.replace(/^#/, '') ); break; case 'bind_material': var instances = 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'); extractDoubleSided( this, element ); for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[i]; switch ( child.nodeName ) { case 'mesh': this.mesh = (new Mesh(this)).parse(child); break; case 'extra': // console.log( child ); break; default: break; } } return this; }; function Mesh( geometry ) { this.geometry = geometry.id; this.primitives = []; this.vertices = null; this.geometry3js = null; }; Mesh.prototype.parse = function( element ) { this.primitives = []; var i, j; for ( i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; switch ( child.nodeName ) { case 'source': _source( child ); break; case 'vertices': this.vertices = ( new Vertices() ).parse( child ); break; case 'triangles': this.primitives.push( ( new Triangles().parse( child ) ) ); break; case 'polygons': this.primitives.push( ( new Polygons().parse( child ) ) ); break; case 'polylist': this.primitives.push( ( new Polylist().parse( child ) ) ); break; default: break; } } this.geometry3js = new THREE.Geometry(); var vertexData = sources[ this.vertices.input['POSITION'].source ].data; for ( i = 0; i < vertexData.length; i += 3 ) { this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() ); } for ( i = 0; i < this.primitives.length; i ++ ) { var primitive = this.primitives[ i ]; primitive.setVertices( this.vertices ); this.handlePrimitive( primitive, this.geometry3js ); } this.geometry3js.computeCentroids(); this.geometry3js.computeFaceNormals(); if ( this.geometry3js.calcNormals ) { this.geometry3js.computeVertexNormals(); delete this.geometry3js.calcNormals; } this.geometry3js.computeBoundingBox(); return this; }; Mesh.prototype.handlePrimitive = function( primitive, geom ) { var j, k, pList = primitive.p, inputs = primitive.inputs; var input, index, idx32; var source, numParams; var vcIndex = 0, vcount = 3, maxOffset = 0; var texture_sets = []; for ( j = 0; j < inputs.length; j ++ ) { input = inputs[ j ]; var offset = input.offset + 1; maxOffset = (maxOffset < offset)? offset : maxOffset; switch ( input.semantic ) { case 'TEXCOORD': texture_sets.push( input.set ); break; } } for ( var pCount = 0; pCount < pList.length; ++pCount ) { var p = pList[ pCount ], i = 0; while ( i < p.length ) { var vs = []; var ns = []; var ts = null; var cs = []; if ( primitive.vcount ) { vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount; } else { vcount = p.length / maxOffset; } for ( j = 0; j < vcount; j ++ ) { for ( k = 0; k < inputs.length; k ++ ) { input = inputs[ k ]; source = sources[ input.source ]; index = p[ i + ( j * maxOffset ) + input.offset ]; numParams = source.accessor.params.length; idx32 = index * numParams; switch ( input.semantic ) { case 'VERTEX': vs.push( index ); break; case 'NORMAL': ns.push( getConvertedVec3( source.data, idx32 ) ); break; case 'TEXCOORD': ts = ts || { }; if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; // invert the V ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) ); break; case 'COLOR': cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); break; default: break; } } } if ( ns.length == 0 ) { // check the vertices inputs input = this.vertices.input.NORMAL; if ( input ) { source = sources[ input.source ]; numParams = source.accessor.params.length; for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) ); } } else { geom.calcNormals = true; } } if ( !ts ) { ts = { }; // check the vertices inputs input = this.vertices.input.TEXCOORD; if ( input ) { texture_sets.push( input.set ); source = sources[ input.source ]; numParams = source.accessor.params.length; for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { idx32 = vs[ ndx ] * numParams; if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ]; // invert the V ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) ); } } } if ( cs.length == 0 ) { // check the vertices inputs input = this.vertices.input.COLOR; if ( input ) { source = sources[ input.source ]; numParams = source.accessor.params.length; for ( var ndx = 0, len = vs.length; ndx < len; ndx++ ) { idx32 = vs[ ndx ] * numParams; cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); } } } var face = null, faces = [], uv, uvArr; if ( vcount === 3 ) { faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) ); } else if ( vcount === 4 ) { faces.push( new THREE.Face4( vs[0], vs[1], vs[2], vs[3], ns, cs.length ? cs : new THREE.Color() ) ); } else if ( vcount > 4 && options.subdivideFaces ) { var clr = cs.length ? cs : new THREE.Color(), vec1, vec2, vec3, v1, v2, norm; // subdivide into multiple Face3s for ( k = 1; k < vcount - 1; ) { // FIXME: normals don't seem to be quite right faces.push( new THREE.Face3( vs[0], vs[k], vs[k+1], [ ns[0], ns[k++], ns[k] ], clr ) ); } } if ( faces.length ) { for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) { face = faces[ndx]; face.daeMaterial = primitive.material; geom.faces.push( face ); for ( k = 0; k < texture_sets.length; k++ ) { uv = ts[ texture_sets[k] ]; if ( vcount > 4 ) { // Grab the right UVs for the vertices in this face uvArr = [ uv[0], uv[ndx+1], uv[ndx+2] ]; } else if ( vcount === 4 ) { uvArr = [ uv[0], uv[1], uv[2], uv[3] ]; } else { uvArr = [ uv[0], uv[1], uv[2] ]; } if ( !geom.faceVertexUvs[k] ) { geom.faceVertexUvs[k] = []; } geom.faceVertexUvs[k].push( uvArr ); } } } else { console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id ); } i += maxOffset * vcount; } } }; function Polygons () { this.material = ""; this.count = 0; this.inputs = []; this.vcount = null; this.p = []; this.geometry = new THREE.Geometry(); }; Polygons.prototype.setVertices = function ( vertices ) { for ( var i = 0; i < this.inputs.length; i ++ ) { if ( this.inputs[ i ].source == vertices.id ) { this.inputs[ i ].source = vertices.input[ 'POSITION' ].source; } } }; Polygons.prototype.parse = function ( element ) { this.material = element.getAttribute( 'material' ); this.count = _attr_as_int( element, 'count', 0 ); for ( var i = 0; i < element.childNodes.length; i ++ ) { var child = element.childNodes[ i ]; switch ( child.nodeName ) { case 'input': this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) ); break; case 'vcount': this.vcount = _ints( child.textContent ); break; case 'p': this.p.push( _ints( child.textContent ) ); break; case 'ph': console.warn( 'polygon holes not yet supported!' ); break; default: break; } } return this; }; function Polylist () { Polygons.call( this ); this.vcount = []; }; Polylist.prototype = Object.create( Polygons.prototype ); function Triangles () { Polygons.call( this ); this.vcount = 3; }; Triangles.p