|
/** |
|
* @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 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" ); |
|
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, |
|
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; |
|
|
|
} |
|
|
|
} |
|
|
|
// FIXME: multi-material mesh? |
|
// 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 ( !( material3js in double_sided_materials ) ) { |
|
|
|
var _copied_material = material3js.clone(); |
|
_copied_material.side = THREE.DoubleSide; |
|
double_sided_materials[ material3js ] = _copied_material; |
|
|
|
} |
|
|
|
material3js = double_sided_materials[ material3js ]; |
|
|
|
} |
|
|
|
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, cparams.aspect_ratio, cparams.znear, cparams.zfar); |
|
|
|
} |
|
|
|
obj.name = node.name || node.id || ""; |
|
obj.matrix = node.matrix; |
|
|
|
var props = node.matrix.decompose(); |
|
obj.position = props[ 0 ]; |
|
obj.quaternion = props[ 1 ]; |
|
obj.useQuaternion = true; |
|
obj.scale = props[ 2 ]; |
|
|
|
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.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': |
|
|
|
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 ( matrix ) { |
|
|
|
switch ( this.type ) { |
|
|
|
case 'matrix': |
|
|
|
matrix.multiply( this.obj ); |
|
|
|
break; |
|
|
|
case 'translate': |
|
|
|
matrix.translate( this.obj ); |
|
|
|
break; |
|
|
|
case 'rotate': |
|
|
|
matrix.rotateByAxis( 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.prototype = Object.create( Polygons.prototype ); |
|
|
|
function Accessor() { |
|
|
|
this.source = ""; |
|
this.count = 0; |
|
this.stride = 0; |
|
this.params = []; |
|
|
|
}; |
|
|
|
Accessor.prototype.parse = function ( element ) { |
|
|
|
this.params = []; |
|
this.source = element.getAttribute( 'source' ); |
|
this.count = _attr_as_int( element, 'count', 0 ); |
|
this.stride = _attr_as_int( element, 'stride', 0 ); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
|
|
if ( child.nodeName == 'param' ) { |
|
|
|
var param = {}; |
|
param[ 'name' ] = child.getAttribute( 'name' ); |
|
param[ 'type' ] = child.getAttribute( 'type' ); |
|
this.params.push( param ); |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Vertices() { |
|
|
|
this.input = {}; |
|
|
|
}; |
|
|
|
Vertices.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute('id'); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
if ( element.childNodes[i].nodeName == 'input' ) { |
|
|
|
var input = ( new Input() ).parse( element.childNodes[ i ] ); |
|
this.input[ input.semantic ] = input; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Input () { |
|
|
|
this.semantic = ""; |
|
this.offset = 0; |
|
this.source = ""; |
|
this.set = 0; |
|
|
|
}; |
|
|
|
Input.prototype.parse = function ( element ) { |
|
|
|
this.semantic = element.getAttribute('semantic'); |
|
this.source = element.getAttribute('source').replace(/^#/, ''); |
|
this.set = _attr_as_int(element, 'set', -1); |
|
this.offset = _attr_as_int(element, 'offset', 0); |
|
|
|
if ( this.semantic == 'TEXCOORD' && this.set < 0 ) { |
|
|
|
this.set = 0; |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Source ( id ) { |
|
|
|
this.id = id; |
|
this.type = null; |
|
|
|
}; |
|
|
|
Source.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[i]; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'bool_array': |
|
|
|
this.data = _bools( child.textContent ); |
|
this.type = child.nodeName; |
|
break; |
|
|
|
case 'float_array': |
|
|
|
this.data = _floats( child.textContent ); |
|
this.type = child.nodeName; |
|
break; |
|
|
|
case 'int_array': |
|
|
|
this.data = _ints( child.textContent ); |
|
this.type = child.nodeName; |
|
break; |
|
|
|
case 'IDREF_array': |
|
case 'Name_array': |
|
|
|
this.data = _strings( child.textContent ); |
|
this.type = child.nodeName; |
|
break; |
|
|
|
case 'technique_common': |
|
|
|
for ( var j = 0; j < child.childNodes.length; j ++ ) { |
|
|
|
if ( child.childNodes[ j ].nodeName == 'accessor' ) { |
|
|
|
this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] ); |
|
break; |
|
|
|
} |
|
} |
|
break; |
|
|
|
default: |
|
// console.log(child.nodeName); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
Source.prototype.read = function () { |
|
|
|
var result = []; |
|
|
|
//for (var i = 0; i < this.accessor.params.length; i++) { |
|
|
|
var param = this.accessor.params[ 0 ]; |
|
|
|
//console.log(param.name + " " + param.type); |
|
|
|
switch ( param.type ) { |
|
|
|
case 'IDREF': |
|
case 'Name': case 'name': |
|
case 'float': |
|
|
|
return this.data; |
|
|
|
case 'float4x4': |
|
|
|
for ( var j = 0; j < this.data.length; j += 16 ) { |
|
|
|
var s = this.data.slice( j, j + 16 ); |
|
var m = getConvertedMat4( s ); |
|
result.push( m ); |
|
} |
|
|
|
break; |
|
|
|
default: |
|
|
|
console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' ); |
|
break; |
|
|
|
} |
|
|
|
//} |
|
|
|
return result; |
|
|
|
}; |
|
|
|
function Material () { |
|
|
|
this.id = ""; |
|
this.name = ""; |
|
this.instance_effect = null; |
|
|
|
}; |
|
|
|
Material.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
this.name = element.getAttribute( 'name' ); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
if ( element.childNodes[ i ].nodeName == 'instance_effect' ) { |
|
|
|
this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] ); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function ColorOrTexture () { |
|
|
|
this.color = new THREE.Color( 0 ); |
|
this.color.setRGB( Math.random(), Math.random(), Math.random() ); |
|
this.color.a = 1.0; |
|
|
|
this.texture = null; |
|
this.texcoord = null; |
|
this.texOpts = null; |
|
|
|
}; |
|
|
|
ColorOrTexture.prototype.isColor = function () { |
|
|
|
return ( this.texture == null ); |
|
|
|
}; |
|
|
|
ColorOrTexture.prototype.isTexture = function () { |
|
|
|
return ( this.texture != null ); |
|
|
|
}; |
|
|
|
ColorOrTexture.prototype.parse = function ( element ) { |
|
|
|
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'); |
|
// Defaults from: |
|
// https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension |
|
this.texOpts = { |
|
offsetU: 0, |
|
offsetV: 0, |
|
repeatU: 1, |
|
repeatV: 1, |
|
wrapU: 1, |
|
wrapV: 1, |
|
}; |
|
this.parseTexture( child ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
ColorOrTexture.prototype.parseTexture = function ( element ) { |
|
|
|
if ( ! element.childNodes ) return this; |
|
|
|
// This should be supported by Maya, 3dsMax, and MotionBuilder |
|
|
|
if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) { |
|
|
|
element = element.childNodes[1]; |
|
|
|
if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) { |
|
|
|
element = element.childNodes[1]; |
|
|
|
} |
|
|
|
} |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'offsetU': |
|
case 'offsetV': |
|
case 'repeatU': |
|
case 'repeatV': |
|
|
|
this.texOpts[ child.nodeName ] = parseFloat( child.textContent ); |
|
break; |
|
|
|
case 'wrapU': |
|
case 'wrapV': |
|
|
|
this.texOpts[ child.nodeName ] = parseInt( child.textContent ); |
|
break; |
|
|
|
default: |
|
this.texOpts[ child.nodeName ] = child.textContent; |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Shader ( type, effect ) { |
|
|
|
this.type = type; |
|
this.effect = effect; |
|
this.material = null; |
|
|
|
}; |
|
|
|
Shader.prototype.parse = function ( element ) { |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'ambient': |
|
case 'emission': |
|
case 'diffuse': |
|
case 'specular': |
|
case 'transparent': |
|
|
|
this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child ); |
|
break; |
|
|
|
case 'shininess': |
|
case 'reflectivity': |
|
case 'index_of_refraction': |
|
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() ) { |
|
|
|
var samplerId = cot.texture; |
|
var surfaceId = this.effect.sampler[samplerId].source; |
|
|
|
if ( surfaceId ) { |
|
|
|
var surface = this.effect.surface[surfaceId]; |
|
var image = images[surface.init_from]; |
|
|
|
if (image) { |
|
|
|
var texture = THREE.ImageUtils.loadTexture(baseUrl + image.init_from); |
|
texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; |
|
texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; |
|
texture.offset.x = cot.texOpts.offsetU; |
|
texture.offset.y = cot.texOpts.offsetV; |
|
texture.repeat.x = cot.texOpts.repeatU; |
|
texture.repeat.y = cot.texOpts.repeatV; |
|
props['map'] = texture; |
|
|
|
// Texture with baked lighting? |
|
if (prop === 'emission') props['emissive'] = 0xffffff; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if ( prop === 'diffuse' || !transparent ) { |
|
|
|
if ( prop === 'emission' ) { |
|
|
|
props[ 'emissive' ] = cot.color.getHex(); |
|
|
|
} else { |
|
|
|
props[ prop ] = cot.color.getHex(); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'shininess': |
|
|
|
props[ prop ] = this[ prop ]; |
|
break; |
|
|
|
case 'reflectivity': |
|
|
|
props[ prop ] = this[ prop ]; |
|
if( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap; |
|
props['combine'] = THREE.MixOperation; //mix regular shading with reflective component |
|
break; |
|
|
|
case 'index_of_refraction': |
|
|
|
props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable |
|
if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap; |
|
break; |
|
|
|
case 'transparency': |
|
|
|
if ( transparent ) { |
|
|
|
props[ 'transparent' ] = true; |
|
props[ 'opacity' ] = this[ prop ]; |
|
transparent = true; |
|
|
|
} |
|
|
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
props[ 'shading' ] = preferredShading; |
|
props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide; |
|
|
|
switch ( this.type ) { |
|
|
|
case 'constant': |
|
|
|
if (props.emissive != undefined) props.color = props.emissive; |
|
this.material = new THREE.MeshBasicMaterial( props ); |
|
break; |
|
|
|
case 'phong': |
|
case 'blinn': |
|
|
|
if (props.diffuse != undefined) props.color = props.diffuse; |
|
this.material = new THREE.MeshPhongMaterial( props ); |
|
break; |
|
|
|
case 'lambert': |
|
default: |
|
|
|
if (props.diffuse != undefined) props.color = props.diffuse; |
|
this.material = new THREE.MeshLambertMaterial( props ); |
|
break; |
|
|
|
} |
|
|
|
return this.material; |
|
|
|
}; |
|
|
|
function Surface ( effect ) { |
|
|
|
this.effect = effect; |
|
this.init_from = null; |
|
this.format = null; |
|
|
|
}; |
|
|
|
Surface.prototype.parse = function ( element ) { |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'init_from': |
|
|
|
this.init_from = child.textContent; |
|
break; |
|
|
|
case 'format': |
|
|
|
this.format = child.textContent; |
|
break; |
|
|
|
default: |
|
|
|
console.log( "unhandled Surface prop: " + child.nodeName ); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Sampler2D ( effect ) { |
|
|
|
this.effect = effect; |
|
this.source = null; |
|
this.wrap_s = null; |
|
this.wrap_t = null; |
|
this.minfilter = null; |
|
this.magfilter = null; |
|
this.mipfilter = null; |
|
|
|
}; |
|
|
|
Sampler2D.prototype.parse = function ( element ) { |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'source': |
|
|
|
this.source = child.textContent; |
|
break; |
|
|
|
case 'minfilter': |
|
|
|
this.minfilter = child.textContent; |
|
break; |
|
|
|
case 'magfilter': |
|
|
|
this.magfilter = child.textContent; |
|
break; |
|
|
|
case 'mipfilter': |
|
|
|
this.mipfilter = child.textContent; |
|
break; |
|
|
|
case 'wrap_s': |
|
|
|
this.wrap_s = child.textContent; |
|
break; |
|
|
|
case 'wrap_t': |
|
|
|
this.wrap_t = child.textContent; |
|
break; |
|
|
|
default: |
|
|
|
console.log( "unhandled Sampler2D prop: " + child.nodeName ); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Effect () { |
|
|
|
this.id = ""; |
|
this.name = ""; |
|
this.shader = null; |
|
this.surface = {}; |
|
this.sampler = {}; |
|
|
|
}; |
|
|
|
Effect.prototype.create = function () { |
|
|
|
if ( this.shader == null ) { |
|
|
|
return null; |
|
|
|
} |
|
|
|
}; |
|
|
|
Effect.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
this.name = element.getAttribute( 'name' ); |
|
|
|
extractDoubleSided( this, element ); |
|
|
|
this.shader = null; |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'profile_COMMON': |
|
|
|
this.parseTechnique( this.parseProfileCOMMON( child ) ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
Effect.prototype.parseNewparam = function ( element ) { |
|
|
|
var sid = element.getAttribute( 'sid' ); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'surface': |
|
|
|
this.surface[sid] = ( new Surface( this ) ).parse( child ); |
|
break; |
|
|
|
case 'sampler2D': |
|
|
|
this.sampler[sid] = ( new Sampler2D( this ) ).parse( child ); |
|
break; |
|
|
|
case 'extra': |
|
|
|
break; |
|
|
|
default: |
|
|
|
console.log( child.nodeName ); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
Effect.prototype.parseProfileCOMMON = function ( element ) { |
|
|
|
var technique; |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
|
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'profile_COMMON': |
|
|
|
this.parseProfileCOMMON( child ); |
|
break; |
|
|
|
case 'technique': |
|
|
|
technique = child; |
|
break; |
|
|
|
case 'newparam': |
|
|
|
this.parseNewparam( child ); |
|
break; |
|
|
|
case 'image': |
|
|
|
var _image = ( new _Image() ).parse( child ); |
|
images[ _image.id ] = _image; |
|
break; |
|
|
|
case 'extra': |
|
break; |
|
|
|
default: |
|
|
|
console.log( child.nodeName ); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return technique; |
|
|
|
}; |
|
|
|
Effect.prototype.parseTechnique= function ( element ) { |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[i]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'constant': |
|
case 'lambert': |
|
case 'blinn': |
|
case 'phong': |
|
|
|
this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
function InstanceEffect () { |
|
|
|
this.url = ""; |
|
|
|
}; |
|
|
|
InstanceEffect.prototype.parse = function ( element ) { |
|
|
|
this.url = element.getAttribute( 'url' ).replace( /^#/, '' ); |
|
return this; |
|
|
|
}; |
|
|
|
function Animation() { |
|
|
|
this.id = ""; |
|
this.name = ""; |
|
this.source = {}; |
|
this.sampler = []; |
|
this.channel = []; |
|
|
|
}; |
|
|
|
Animation.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
this.name = element.getAttribute( 'name' ); |
|
this.source = {}; |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
|
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'animation': |
|
|
|
var anim = ( new Animation() ).parse( child ); |
|
|
|
for ( var src in anim.source ) { |
|
|
|
this.source[ src ] = anim.source[ src ]; |
|
|
|
} |
|
|
|
for ( var j = 0; j < anim.channel.length; j ++ ) { |
|
|
|
this.channel.push( anim.channel[ j ] ); |
|
this.sampler.push( anim.sampler[ j ] ); |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'source': |
|
|
|
var src = ( new Source() ).parse( child ); |
|
this.source[ src.id ] = src; |
|
break; |
|
|
|
case 'sampler': |
|
|
|
this.sampler.push( ( new Sampler( this ) ).parse( child ) ); |
|
break; |
|
|
|
case 'channel': |
|
|
|
this.channel.push( ( new Channel( this ) ).parse( child ) ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Channel( animation ) { |
|
|
|
this.animation = animation; |
|
this.source = ""; |
|
this.target = ""; |
|
this.fullSid = null; |
|
this.sid = null; |
|
this.dotSyntax = null; |
|
this.arrSyntax = null; |
|
this.arrIndices = null; |
|
this.member = null; |
|
|
|
}; |
|
|
|
Channel.prototype.parse = function ( element ) { |
|
|
|
this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); |
|
this.target = element.getAttribute( 'target' ); |
|
|
|
var parts = this.target.split( '/' ); |
|
|
|
var id = parts.shift(); |
|
var sid = parts.shift(); |
|
|
|
var dotSyntax = ( sid.indexOf(".") >= 0 ); |
|
var arrSyntax = ( sid.indexOf("(") >= 0 ); |
|
|
|
if ( dotSyntax ) { |
|
|
|
parts = sid.split("."); |
|
this.sid = parts.shift(); |
|
this.member = parts.shift(); |
|
|
|
} else if ( arrSyntax ) { |
|
|
|
var arrIndices = sid.split("("); |
|
this.sid = arrIndices.shift(); |
|
|
|
for (var j = 0; j < arrIndices.length; j ++ ) { |
|
|
|
arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') ); |
|
|
|
} |
|
|
|
this.arrIndices = arrIndices; |
|
|
|
} else { |
|
|
|
this.sid = sid; |
|
|
|
} |
|
|
|
this.fullSid = sid; |
|
this.dotSyntax = dotSyntax; |
|
this.arrSyntax = arrSyntax; |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function Sampler ( animation ) { |
|
|
|
this.id = ""; |
|
this.animation = animation; |
|
this.inputs = []; |
|
this.input = null; |
|
this.output = null; |
|
this.strideOut = null; |
|
this.interpolation = null; |
|
this.startTime = null; |
|
this.endTime = null; |
|
this.duration = 0; |
|
|
|
}; |
|
|
|
Sampler.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
this.inputs = []; |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'input': |
|
|
|
this.inputs.push( (new Input()).parse( child ) ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
Sampler.prototype.create = function () { |
|
|
|
for ( var i = 0; i < this.inputs.length; i ++ ) { |
|
|
|
var input = this.inputs[ i ]; |
|
var source = this.animation.source[ input.source ]; |
|
|
|
switch ( input.semantic ) { |
|
|
|
case 'INPUT': |
|
|
|
this.input = source.read(); |
|
break; |
|
|
|
case 'OUTPUT': |
|
|
|
this.output = source.read(); |
|
this.strideOut = source.accessor.stride; |
|
break; |
|
|
|
case 'INTERPOLATION': |
|
|
|
this.interpolation = source.read(); |
|
break; |
|
|
|
case 'IN_TANGENT': |
|
|
|
break; |
|
|
|
case 'OUT_TANGENT': |
|
|
|
break; |
|
|
|
default: |
|
|
|
console.log(input.semantic); |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
this.startTime = 0; |
|
this.endTime = 0; |
|
this.duration = 0; |
|
|
|
if ( this.input.length ) { |
|
|
|
this.startTime = 100000000; |
|
this.endTime = -100000000; |
|
|
|
for ( var i = 0; i < this.input.length; i ++ ) { |
|
|
|
this.startTime = Math.min( this.startTime, this.input[ i ] ); |
|
this.endTime = Math.max( this.endTime, this.input[ i ] ); |
|
|
|
} |
|
|
|
this.duration = this.endTime - this.startTime; |
|
|
|
} |
|
|
|
}; |
|
|
|
Sampler.prototype.getData = function ( type, ndx ) { |
|
|
|
var data; |
|
|
|
if ( type === 'matrix' && this.strideOut === 16 ) { |
|
|
|
data = this.output[ ndx ]; |
|
|
|
} else if ( this.strideOut > 1 ) { |
|
|
|
data = []; |
|
ndx *= this.strideOut; |
|
|
|
for ( var i = 0; i < this.strideOut; ++i ) { |
|
|
|
data[ i ] = this.output[ ndx + i ]; |
|
|
|
} |
|
|
|
if ( this.strideOut === 3 ) { |
|
|
|
switch ( type ) { |
|
|
|
case 'rotate': |
|
case 'translate': |
|
|
|
fixCoords( data, -1 ); |
|
break; |
|
|
|
case 'scale': |
|
|
|
fixCoords( data, 1 ); |
|
break; |
|
|
|
} |
|
|
|
} else if ( this.strideOut === 4 && type === 'matrix' ) { |
|
|
|
fixCoords( data, -1 ); |
|
|
|
} |
|
|
|
} else { |
|
|
|
data = this.output[ ndx ]; |
|
|
|
} |
|
|
|
return data; |
|
|
|
}; |
|
|
|
function Key ( time ) { |
|
|
|
this.targets = []; |
|
this.time = time; |
|
|
|
}; |
|
|
|
Key.prototype.addTarget = function ( fullSid, transform, member, data ) { |
|
|
|
this.targets.push( { |
|
sid: fullSid, |
|
member: member, |
|
transform: transform, |
|
data: data |
|
} ); |
|
|
|
}; |
|
|
|
Key.prototype.apply = function ( opt_sid ) { |
|
|
|
for ( var i = 0; i < this.targets.length; ++i ) { |
|
|
|
var target = this.targets[ i ]; |
|
|
|
if ( !opt_sid || target.sid === opt_sid ) { |
|
|
|
target.transform.update( target.data, target.member ); |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
Key.prototype.getTarget = function ( fullSid ) { |
|
|
|
for ( var i = 0; i < this.targets.length; ++i ) { |
|
|
|
if ( this.targets[ i ].sid === fullSid ) { |
|
|
|
return this.targets[ i ]; |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
}; |
|
|
|
Key.prototype.hasTarget = function ( fullSid ) { |
|
|
|
for ( var i = 0; i < this.targets.length; ++i ) { |
|
|
|
if ( this.targets[ i ].sid === fullSid ) { |
|
|
|
return true; |
|
|
|
} |
|
|
|
} |
|
|
|
return false; |
|
|
|
}; |
|
|
|
// TODO: Currently only doing linear interpolation. Should support full COLLADA spec. |
|
Key.prototype.interpolate = function ( nextKey, time ) { |
|
|
|
for ( var i = 0; i < this.targets.length; ++i ) { |
|
|
|
var target = this.targets[ i ], |
|
nextTarget = nextKey.getTarget( target.sid ), |
|
data; |
|
|
|
if ( target.transform.type !== 'matrix' && nextTarget ) { |
|
|
|
var scale = ( time - this.time ) / ( nextKey.time - this.time ), |
|
nextData = nextTarget.data, |
|
prevData = target.data; |
|
|
|
// check scale error |
|
|
|
if ( scale < 0 || scale > 1 ) { |
|
|
|
console.log( "Key.interpolate: Warning! Scale out of bounds:" + scale ); |
|
scale = scale < 0 ? 0 : 1; |
|
|
|
} |
|
|
|
if ( prevData.length ) { |
|
|
|
data = []; |
|
|
|
for ( var j = 0; j < prevData.length; ++j ) { |
|
|
|
data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; |
|
|
|
} |
|
|
|
} else { |
|
|
|
data = prevData + ( nextData - prevData ) * scale; |
|
|
|
} |
|
|
|
} else { |
|
|
|
data = target.data; |
|
|
|
} |
|
|
|
target.transform.update( data, target.member ); |
|
|
|
} |
|
|
|
}; |
|
|
|
function Camera() { |
|
|
|
this.id = ""; |
|
this.name = ""; |
|
this.technique = ""; |
|
|
|
}; |
|
|
|
Camera.prototype.parse = function ( element ) { |
|
|
|
this.id = element.getAttribute( 'id' ); |
|
this.name = element.getAttribute( 'name' ); |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
var child = element.childNodes[ i ]; |
|
if ( child.nodeType != 1 ) continue; |
|
|
|
switch ( child.nodeName ) { |
|
|
|
case 'optics': |
|
|
|
this.parseOptics( child ); |
|
break; |
|
|
|
default: |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
Camera.prototype.parseOptics = function ( element ) { |
|
|
|
for ( var i = 0; i < element.childNodes.length; i ++ ) { |
|
|
|
if ( element.childNodes[ i ].nodeName == 'technique_common' ) { |
|
|
|
var technique = element.childNodes[ i ]; |
|
|
|
for ( var j = 0; j < technique.childNodes.length; j ++ ) { |
|
|
|
this.technique = technique.childNodes[ j ].nodeName; |
|
|
|
if ( this.technique == 'perspective' ) { |
|
|
|
var perspective = technique.childNodes[ j ]; |
|
|
|
for ( var k = 0; k < perspective.childNodes.length; k ++ ) { |
|
|
|
var param = perspective.childNodes[ k ]; |
|
|
|
switch ( param.nodeName ) { |
|
|
|
case 'yfov': |
|
this.yfov = param.textContent; |
|
break; |
|
case 'xfov': |
|
this.xfov = param.textContent; |
|
break; |
|
case 'znear': |
|
this.znear = param.textContent; |
|
break; |
|
case 'zfar': |
|
this.zfar = param.textContent; |
|
break; |
|
case 'aspect_ratio': |
|
this.aspect_ratio = param.textContent; |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} else if ( this.technique == 'orthographic' ) { |
|
|
|
var orthographic = technique.childNodes[ j ]; |
|
|
|
for ( var k = 0; k < orthographic.childNodes.length; k ++ ) { |
|
|
|
var param = orthographic.childNodes[ k ]; |
|
|
|
switch ( param.nodeName ) { |
|
|
|
case 'xmag': |
|
this.xmag = param.textContent; |
|
break; |
|
case 'ymag': |
|
this.ymag = param.textContent; |
|
break; |
|
case 'znear': |
|
this.znear = param.textContent; |
|
break; |
|
case 'zfar': |
|
this.zfar = param.textContent; |
|
break; |
|
case 'aspect_ratio': |
|
this.aspect_ratio = param.textContent; |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function InstanceCamera() { |
|
|
|
this.url = ""; |
|
|
|
}; |
|
|
|
InstanceCamera.prototype.parse = function ( element ) { |
|
|
|
this.url = element.getAttribute('url').replace(/^#/, ''); |
|
|
|
return this; |
|
|
|
}; |
|
|
|
function _source( element ) { |
|
|
|
var id = element.getAttribute( 'id' ); |
|
|
|
if ( sources[ id ] != undefined ) { |
|
|
|
return sources[ id ]; |
|
|
|
} |
|
|
|
sources[ id ] = ( new Source(id )).parse( element ); |
|
return sources[ id ]; |
|
|
|
}; |
|
|
|
function _nsResolver( nsPrefix ) { |
|
|
|
if ( nsPrefix == "dae" ) { |
|
|
|
return "http://www.collada.org/2005/11/COLLADASchema"; |
|
|
|
} |
|
|
|
return null; |
|
|
|
}; |
|
|
|
function _bools( str ) { |
|
|
|
var raw = _strings( str ); |
|
var data = []; |
|
|
|
for ( var i = 0, l = raw.length; i < l; i ++ ) { |
|
|
|
data.push( (raw[i] == 'true' || raw[i] == '1') ? true : false ); |
|
|
|
} |
|
|
|
return data; |
|
|
|
}; |
|
|
|
function _floats( str ) { |
|
|
|
var raw = _strings(str); |
|
var data = []; |
|
|
|
for ( var i = 0, l = raw.length; i < l; i ++ ) { |
|
|
|
data.push( parseFloat( raw[ i ] ) ); |
|
|
|
} |
|
|
|
return data; |
|
|
|
}; |
|
|
|
function _ints( str ) { |
|
|
|
var raw = _strings( str ); |
|
var data = []; |
|
|
|
for ( var i = 0, l = raw.length; i < l; i ++ ) { |
|
|
|
data.push( parseInt( raw[ i ], 10 ) ); |
|
|
|
} |
|
|
|
return data; |
|
|
|
}; |
|
|
|
function _strings( str ) { |
|
|
|
return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : []; |
|
|
|
}; |
|
|
|
function _trimString( str ) { |
|
|
|
return str.replace( /^\s+/, "" ).replace( /\s+$/, "" ); |
|
|
|
}; |
|
|
|
function _attr_as_float( element, name, defaultValue ) { |
|
|
|
if ( element.hasAttribute( name ) ) { |
|
|
|
return parseFloat( element.getAttribute( name ) ); |
|
|
|
} else { |
|
|
|
return defaultValue; |
|
|
|
} |
|
|
|
}; |
|
|
|
function _attr_as_int( element, name, defaultValue ) { |
|
|
|
if ( element.hasAttribute( name ) ) { |
|
|
|
return parseInt( element.getAttribute( name ), 10) ; |
|
|
|
} else { |
|
|
|
return defaultValue; |
|
|
|
} |
|
|
|
}; |
|
|
|
function _attr_as_string( element, name, defaultValue ) { |
|
|
|
if ( element.hasAttribute( name ) ) { |
|
|
|
return element.getAttribute( name ); |
|
|
|
} else { |
|
|
|
return defaultValue; |
|
|
|
} |
|
|
|
}; |
|
|
|
function _format_float( f, num ) { |
|
|
|
if ( f === undefined ) { |
|
|
|
var s = '0.'; |
|
|
|
while ( s.length < num + 2 ) { |
|
|
|
s += '0'; |
|
|
|
} |
|
|
|
return s; |
|
|
|
} |
|
|
|
num = num || 2; |
|
|
|
var parts = f.toString().split( '.' ); |
|
parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0"; |
|
|
|
while( parts[ 1 ].length < num ) { |
|
|
|
parts[ 1 ] += '0'; |
|
|
|
} |
|
|
|
return parts.join( '.' ); |
|
|
|
}; |
|
|
|
function 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; |
|
|
|
}; |
|
|
|
function extractDoubleSided( obj, element ) { |
|
|
|
obj.doubleSided = false; |
|
|
|
var node = COLLADA.evaluate( './/dae:extra//dae:double_sided', element, _nsResolver, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null ); |
|
|
|
if ( node ) { |
|
|
|
node = node.iterateNext(); |
|
|
|
if ( node && parseInt( node.textContent, 10 ) === 1 ) { |
|
|
|
obj.doubleSided = true; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
// Up axis conversion |
|
|
|
function setUpConversion() { |
|
|
|
if ( !options.convertUpAxis || colladaUp === options.upAxis ) { |
|
|
|
upConversion = null; |
|
|
|
} else { |
|
|
|
switch ( colladaUp ) { |
|
|
|
case 'X': |
|
|
|
upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; |
|
break; |
|
|
|
case 'Y': |
|
|
|
upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; |
|
break; |
|
|
|
case 'Z': |
|
|
|
upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; |
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
}; |
|
|
|
function fixCoords( data, sign ) { |
|
|
|
if ( !options.convertUpAxis || colladaUp === options.upAxis ) { |
|
|
|
return; |
|
|
|
} |
|
|
|
switch ( upConversion ) { |
|
|
|
case 'XtoY': |
|
|
|
var tmp = data[ 0 ]; |
|
data[ 0 ] = sign * data[ 1 ]; |
|
data[ 1 ] = tmp; |
|
break; |
|
|
|
case 'XtoZ': |
|
|
|
var tmp = data[ 2 ]; |
|
data[ 2 ] = data[ 1 ]; |
|
data[ 1 ] = data[ 0 ]; |
|
data[ 0 ] = tmp; |
|
break; |
|
|
|
case 'YtoX': |
|
|
|
var tmp = data[ 0 ]; |
|
data[ 0 ] = data[ 1 ]; |
|
data[ 1 ] = sign * tmp; |
|
break; |
|
|
|
case 'YtoZ': |
|
|
|
var tmp = data[ 1 ]; |
|
data[ 1 ] = sign * data[ 2 ]; |
|
data[ 2 ] = tmp; |
|
break; |
|
|
|
case 'ZtoX': |
|
|
|
var tmp = data[ 0 ]; |
|
data[ 0 ] = data[ 1 ]; |
|
data[ 1 ] = data[ 2 ]; |
|
data[ 2 ] = tmp; |
|
break; |
|
|
|
case 'ZtoY': |
|
|
|
var tmp = data[ 1 ]; |
|
data[ 1 ] = data[ 2 ]; |
|
data[ 2 ] = sign * tmp; |
|
break; |
|
|
|
} |
|
|
|
}; |
|
|
|
function getConvertedVec3( data, offset ) { |
|
|
|
var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; |
|
fixCoords( arr, -1 ); |
|
return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); |
|
|
|
}; |
|
|
|
function getConvertedMat4( data ) { |
|
|
|
if ( options.convertUpAxis ) { |
|
|
|
// First fix rotation and scale |
|
|
|
// Columns first |
|
var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 0 ] = arr[ 0 ]; |
|
data[ 4 ] = arr[ 1 ]; |
|
data[ 8 ] = arr[ 2 ]; |
|
arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 1 ] = arr[ 0 ]; |
|
data[ 5 ] = arr[ 1 ]; |
|
data[ 9 ] = arr[ 2 ]; |
|
arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 2 ] = arr[ 0 ]; |
|
data[ 6 ] = arr[ 1 ]; |
|
data[ 10 ] = arr[ 2 ]; |
|
// Rows second |
|
arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 0 ] = arr[ 0 ]; |
|
data[ 1 ] = arr[ 1 ]; |
|
data[ 2 ] = arr[ 2 ]; |
|
arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 4 ] = arr[ 0 ]; |
|
data[ 5 ] = arr[ 1 ]; |
|
data[ 6 ] = arr[ 2 ]; |
|
arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 8 ] = arr[ 0 ]; |
|
data[ 9 ] = arr[ 1 ]; |
|
data[ 10 ] = arr[ 2 ]; |
|
|
|
// Now fix translation |
|
arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; |
|
fixCoords( arr, -1 ); |
|
data[ 3 ] = arr[ 0 ]; |
|
data[ 7 ] = arr[ 1 ]; |
|
data[ 11 ] = arr[ 2 ]; |
|
|
|
} |
|
|
|
return new THREE.Matrix4( |
|
data[0], data[1], data[2], data[3], |
|
data[4], data[5], data[6], data[7], |
|
data[8], data[9], data[10], data[11], |
|
data[12], data[13], data[14], data[15] |
|
); |
|
|
|
}; |
|
|
|
function getConvertedIndex( index ) { |
|
|
|
if ( index > -1 && index < 3 ) { |
|
|
|
var members = ['X', 'Y', 'Z'], |
|
indices = { X: 0, Y: 1, Z: 2 }; |
|
|
|
index = getConvertedMember( members[ index ] ); |
|
index = indices[ index ]; |
|
|
|
} |
|
|
|
return index; |
|
|
|
}; |
|
|
|
function getConvertedMember( member ) { |
|
|
|
if ( options.convertUpAxis ) { |
|
|
|
switch ( member ) { |
|
|
|
case 'X': |
|
|
|
switch ( upConversion ) { |
|
|
|
case 'XtoY': |
|
case 'XtoZ': |
|
case 'YtoX': |
|
|
|
member = 'Y'; |
|
break; |
|
|
|
case 'ZtoX': |
|
|
|
member = 'Z'; |
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'Y': |
|
|
|
switch ( upConversion ) { |
|
|
|
case 'XtoY': |
|
case 'YtoX': |
|
case 'ZtoX': |
|
|
|
member = 'X'; |
|
break; |
|
|
|
case 'XtoZ': |
|
case 'YtoZ': |
|
case 'ZtoY': |
|
|
|
member = 'Z'; |
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
case 'Z': |
|
|
|
switch ( upConversion ) { |
|
|
|
case 'XtoZ': |
|
|
|
member = 'X'; |
|
break; |
|
|
|
case 'YtoZ': |
|
case 'ZtoX': |
|
case 'ZtoY': |
|
|
|
member = 'Y'; |
|
break; |
|
|
|
} |
|
|
|
break; |
|
|
|
} |
|
|
|
} |
|
|
|
return member; |
|
|
|
}; |
|
|
|
return { |
|
|
|
load: load, |
|
parse: parse, |
|
setPreferredShading: setPreferredShading, |
|
applySkin: applySkin, |
|
geometries : geometries, |
|
options: options |
|
|
|
}; |
|
|
|
}; |