├── ColladaLoader.js ├── README.md ├── create_material.js └── parsed_model.js /ColladaLoader.js: -------------------------------------------------------------------------------- 1 | import THREE from 'three'; 2 | 3 | /** 4 | * @author Tim Knip / http://www.floorplanner.com/ / tim at floorplanner.com 5 | * @author Tony Parisi / http://www.tonyparisi.com/ 6 | */ 7 | 8 | THREE.ColladaLoader = function () { 9 | 10 | var COLLADA = null; 11 | var scene = null; 12 | var visualScene; 13 | var kinematicsModel; 14 | 15 | var readyCallbackFunc = null; 16 | 17 | var sources = {}; 18 | var images = {}; 19 | var animations = {}; 20 | var controllers = {}; 21 | var geometries = {}; 22 | var materials = {}; 23 | var effects = {}; 24 | var cameras = {}; 25 | var lights = {}; 26 | 27 | var animData; 28 | var kinematics; 29 | var visualScenes; 30 | var kinematicsModels; 31 | var baseUrl; 32 | var morphs; 33 | var skins; 34 | 35 | var flip_uv = true; 36 | var preferredShading = THREE.SmoothShading; 37 | 38 | var options = { 39 | // Force Geometry to always be centered at the local origin of the 40 | // containing Mesh. 41 | centerGeometry: false, 42 | 43 | // Axis conversion is done for geometries, animations, and controllers. 44 | // If we ever pull cameras or lights out of the COLLADA file, they'll 45 | // need extra work. 46 | convertUpAxis: false, 47 | 48 | subdivideFaces: true, 49 | 50 | upAxis: 'Y', 51 | 52 | // For reflective or refractive materials we'll use this cubemap 53 | defaultEnvMap: null 54 | 55 | }; 56 | 57 | var colladaUnit = 1.0; 58 | var colladaUp = 'Y'; 59 | var upConversion = null; 60 | 61 | function load ( url, readyCallback, progressCallback, failCallback ) { 62 | 63 | var length = 0; 64 | 65 | if ( document.implementation && document.implementation.createDocument ) { 66 | 67 | var request = new XMLHttpRequest(); 68 | 69 | request.onreadystatechange = function() { 70 | 71 | if ( request.readyState === 4 ) { 72 | 73 | if ( request.status === 0 || request.status === 200 ) { 74 | 75 | 76 | if ( request.responseXML ) { 77 | 78 | readyCallbackFunc = readyCallback; 79 | parse( request.responseXML, undefined, url ); 80 | 81 | } else if ( request.responseText ) { 82 | 83 | readyCallbackFunc = readyCallback; 84 | var xmlParser = new DOMParser(); 85 | var responseXML = xmlParser.parseFromString( request.responseText, "application/xml" ); 86 | parse( responseXML, undefined, url ); 87 | 88 | } else { 89 | 90 | if ( faillCallback ) { 91 | 92 | failCallback(); 93 | 94 | } else { 95 | 96 | console.error( "ColladaLoader: Empty or non-existing file (" + url + ")" ); 97 | 98 | } 99 | 100 | } 101 | 102 | } 103 | 104 | } else if ( request.readyState === 3 ) { 105 | 106 | if ( progressCallback ) { 107 | 108 | if ( length === 0 ) { 109 | 110 | length = request.getResponseHeader( "Content-Length" ); 111 | 112 | } 113 | 114 | progressCallback( { total: length, loaded: request.responseText.length } ); 115 | 116 | } 117 | 118 | } 119 | 120 | } 121 | 122 | request.open( "GET", url, true ); 123 | request.send( null ); 124 | 125 | } else { 126 | 127 | alert( "Don't know how to parse XML!" ); 128 | 129 | } 130 | 131 | } 132 | 133 | function parse( doc, callBack, url ) { 134 | 135 | COLLADA = doc; 136 | callBack = callBack || readyCallbackFunc; 137 | 138 | if ( url !== undefined ) { 139 | 140 | var parts = url.split( '/' ); 141 | parts.pop(); 142 | baseUrl = ( parts.length < 1 ? '.' : parts.join( '/' ) ) + '/'; 143 | 144 | } 145 | 146 | parseAsset(); 147 | setUpConversion(); 148 | images = parseLib( "library_images image", _Image, "image" ); 149 | materials = parseLib( "library_materials material", Material, "material" ); 150 | effects = parseLib( "library_effects effect", Effect, "effect" ); 151 | geometries = parseLib( "library_geometries geometry", Geometry, "geometry" ); 152 | cameras = parseLib( "library_cameras camera", Camera, "camera" ); 153 | lights = parseLib( "library_lights light", Light, "light" ); 154 | controllers = parseLib( "library_controllers controller", Controller, "controller" ); 155 | animations = parseLib( "library_animations animation", Animation, "animation" ); 156 | visualScenes = parseLib( "library_visual_scenes visual_scene", VisualScene, "visual_scene" ); 157 | kinematicsModels = parseLib( "library_kinematics_models kinematics_model", KinematicsModel, "kinematics_model" ); 158 | 159 | morphs = []; 160 | skins = []; 161 | 162 | visualScene = parseScene(); 163 | scene = new THREE.Group(); 164 | 165 | for ( var i = 0; i < visualScene.nodes.length; i ++ ) { 166 | 167 | scene.add( createSceneGraph( visualScene.nodes[ i ] ) ); 168 | 169 | } 170 | 171 | // unit conversion 172 | scene.scale.multiplyScalar( colladaUnit ); 173 | 174 | createAnimations(); 175 | 176 | kinematicsModel = parseKinematicsModel(); 177 | createKinematics(); 178 | 179 | var result = { 180 | 181 | scene: scene, 182 | morphs: morphs, 183 | skins: skins, 184 | animations: animData, 185 | kinematics: kinematics, 186 | dae: { 187 | images: images, 188 | materials: materials, 189 | cameras: cameras, 190 | lights: lights, 191 | effects: effects, 192 | geometries: geometries, 193 | controllers: controllers, 194 | animations: animations, 195 | visualScenes: visualScenes, 196 | visualScene: visualScene, 197 | scene: visualScene, 198 | kinematicsModels: kinematicsModels, 199 | kinematicsModel: kinematicsModel 200 | } 201 | 202 | }; 203 | 204 | if ( callBack ) { 205 | 206 | callBack( result ); 207 | 208 | } 209 | 210 | return result; 211 | 212 | } 213 | 214 | function setPreferredShading ( shading ) { 215 | 216 | preferredShading = shading; 217 | 218 | } 219 | 220 | function parseAsset () { 221 | 222 | var elements = COLLADA.querySelectorAll('asset'); 223 | 224 | var element = elements[0]; 225 | 226 | if ( element && element.childNodes ) { 227 | 228 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 229 | 230 | var child = element.childNodes[ i ]; 231 | 232 | switch ( child.nodeName ) { 233 | 234 | case 'unit': 235 | 236 | var meter = child.getAttribute( 'meter' ); 237 | 238 | if ( meter ) { 239 | 240 | colladaUnit = parseFloat( meter ); 241 | 242 | } 243 | 244 | break; 245 | 246 | case 'up_axis': 247 | 248 | colladaUp = child.textContent.charAt(0); 249 | break; 250 | 251 | } 252 | 253 | } 254 | 255 | } 256 | 257 | } 258 | 259 | function parseLib ( q, classSpec, prefix ) { 260 | 261 | var elements = COLLADA.querySelectorAll(q); 262 | 263 | var lib = {}; 264 | 265 | var i = 0; 266 | 267 | var elementsLength = elements.length; 268 | 269 | for ( var j = 0; j < elementsLength; j ++ ) { 270 | 271 | var element = elements[j]; 272 | var daeElement = ( new classSpec() ).parse( element ); 273 | 274 | if ( !daeElement.id || daeElement.id.length === 0 ) daeElement.id = prefix + ( i ++ ); 275 | lib[ daeElement.id ] = daeElement; 276 | 277 | } 278 | 279 | return lib; 280 | 281 | } 282 | 283 | function parseScene() { 284 | 285 | var sceneElement = COLLADA.querySelectorAll('scene instance_visual_scene')[0]; 286 | 287 | if ( sceneElement ) { 288 | 289 | var url = sceneElement.getAttribute( 'url' ).replace( /^#/, '' ); 290 | return visualScenes[ url.length > 0 ? url : 'visual_scene0' ]; 291 | 292 | } else { 293 | 294 | return null; 295 | 296 | } 297 | 298 | } 299 | 300 | function parseKinematicsModel() { 301 | 302 | var kinematicsModelElement = COLLADA.querySelectorAll('instance_kinematics_model')[0]; 303 | 304 | if ( kinematicsModelElement ) { 305 | 306 | var url = kinematicsModelElement.getAttribute( 'url' ).replace(/^#/, ''); 307 | return kinematicsModels[ url.length > 0 ? url : 'kinematics_model0' ]; 308 | 309 | } else { 310 | 311 | return null; 312 | 313 | } 314 | 315 | } 316 | 317 | function createAnimations() { 318 | 319 | animData = []; 320 | 321 | // fill in the keys 322 | recurseHierarchy( scene ); 323 | 324 | } 325 | 326 | function recurseHierarchy( node ) { 327 | 328 | var n = visualScene.getChildById( node.colladaId, true ), 329 | newData = null; 330 | 331 | if ( n && n.keys ) { 332 | 333 | newData = { 334 | fps: 60, 335 | hierarchy: [ { 336 | node: n, 337 | keys: n.keys, 338 | sids: n.sids 339 | } ], 340 | node: node, 341 | name: 'animation_' + node.name, 342 | length: 0 343 | }; 344 | 345 | animData.push(newData); 346 | 347 | for ( var i = 0, il = n.keys.length; i < il; i ++ ) { 348 | 349 | newData.length = Math.max( newData.length, n.keys[i].time ); 350 | 351 | } 352 | 353 | } else { 354 | 355 | newData = { 356 | hierarchy: [ { 357 | keys: [], 358 | sids: [] 359 | } ] 360 | } 361 | 362 | } 363 | 364 | for ( var i = 0, il = node.children.length; i < il; i ++ ) { 365 | 366 | var d = recurseHierarchy( node.children[i] ); 367 | 368 | for ( var j = 0, jl = d.hierarchy.length; j < jl; j ++ ) { 369 | 370 | newData.hierarchy.push( { 371 | keys: [], 372 | sids: [] 373 | } ); 374 | 375 | } 376 | 377 | } 378 | 379 | return newData; 380 | 381 | } 382 | 383 | function calcAnimationBounds () { 384 | 385 | var start = 1000000; 386 | var end = -start; 387 | var frames = 0; 388 | var ID; 389 | for ( var id in animations ) { 390 | 391 | var animation = animations[ id ]; 392 | ID = ID || animation.id; 393 | for ( var i = 0; i < animation.sampler.length; i ++ ) { 394 | 395 | var sampler = animation.sampler[ i ]; 396 | 397 | sampler.create(); 398 | 399 | start = Math.min( start, sampler.startTime ); 400 | end = Math.max( end, sampler.endTime ); 401 | frames = Math.max( frames, sampler.input.length ); 402 | 403 | } 404 | 405 | } 406 | 407 | return { start:start, end:end, frames:frames,ID:ID }; 408 | 409 | } 410 | 411 | function createMorph ( geometry, ctrl ) { 412 | 413 | var morphCtrl = ctrl instanceof InstanceController ? controllers[ ctrl.url ] : ctrl; 414 | 415 | if ( !morphCtrl || !morphCtrl.morph ) { 416 | 417 | console.log("could not find morph controller!"); 418 | return; 419 | 420 | } 421 | 422 | var morph = morphCtrl.morph; 423 | 424 | for ( var i = 0; i < morph.targets.length; i ++ ) { 425 | 426 | var target_id = morph.targets[ i ]; 427 | var daeGeometry = geometries[ target_id ]; 428 | 429 | if ( !daeGeometry.mesh || 430 | !daeGeometry.mesh.primitives || 431 | !daeGeometry.mesh.primitives.length ) { 432 | continue; 433 | } 434 | 435 | var target = daeGeometry.mesh.primitives[ 0 ].geometry; 436 | 437 | if ( target.vertices.length === geometry.vertices.length ) { 438 | 439 | geometry.morphTargets.push( { name: "target_1", vertices: target.vertices } ); 440 | 441 | } 442 | 443 | } 444 | 445 | geometry.morphTargets.push( { name: "target_Z", vertices: geometry.vertices } ); 446 | 447 | }; 448 | 449 | function createSkin ( geometry, ctrl, applyBindShape ) { 450 | 451 | var skinCtrl = controllers[ ctrl.url ]; 452 | 453 | if ( !skinCtrl || !skinCtrl.skin ) { 454 | 455 | console.log( "could not find skin controller!" ); 456 | return; 457 | 458 | } 459 | 460 | if ( !ctrl.skeleton || !ctrl.skeleton.length ) { 461 | 462 | console.log( "could not find the skeleton for the skin!" ); 463 | return; 464 | 465 | } 466 | 467 | var skin = skinCtrl.skin; 468 | var skeleton = visualScene.getChildById( ctrl.skeleton[ 0 ] ); 469 | var hierarchy = []; 470 | 471 | applyBindShape = applyBindShape !== undefined ? applyBindShape : true; 472 | 473 | var bones = []; 474 | geometry.skinWeights = []; 475 | geometry.skinIndices = []; 476 | 477 | //createBones( geometry.bones, skin, hierarchy, skeleton, null, -1 ); 478 | //createWeights( skin, geometry.bones, geometry.skinIndices, geometry.skinWeights ); 479 | 480 | /* 481 | geometry.animation = { 482 | name: 'take_001', 483 | fps: 30, 484 | length: 2, 485 | JIT: true, 486 | hierarchy: hierarchy 487 | }; 488 | */ 489 | 490 | if ( applyBindShape ) { 491 | 492 | for ( var i = 0; i < geometry.vertices.length; i ++ ) { 493 | 494 | geometry.vertices[ i ].applyMatrix4( skin.bindShapeMatrix ); 495 | 496 | } 497 | 498 | } 499 | 500 | } 501 | 502 | function setupSkeleton ( node, bones, frame, parent ) { 503 | 504 | node.world = node.world || new THREE.Matrix4(); 505 | node.localworld = node.localworld || new THREE.Matrix4(); 506 | node.world.copy( node.matrix ); 507 | node.localworld.copy( node.matrix ); 508 | 509 | if ( node.channels && node.channels.length ) { 510 | 511 | var channel = node.channels[ 0 ]; 512 | var m = channel.sampler.output[ frame ]; 513 | 514 | if ( m instanceof THREE.Matrix4 ) { 515 | 516 | node.world.copy( m ); 517 | node.localworld.copy(m); 518 | if (frame === 0) 519 | node.matrix.copy(m); 520 | } 521 | 522 | } 523 | 524 | if ( parent ) { 525 | 526 | node.world.multiplyMatrices( parent, node.world ); 527 | 528 | } 529 | 530 | bones.push( node ); 531 | 532 | for ( var i = 0; i < node.nodes.length; i ++ ) { 533 | 534 | setupSkeleton( node.nodes[ i ], bones, frame, node.world ); 535 | 536 | } 537 | 538 | } 539 | 540 | function setupSkinningMatrices ( bones, skin ) { 541 | 542 | // FIXME: this is dumb... 543 | 544 | for ( var i = 0; i < bones.length; i ++ ) { 545 | 546 | var bone = bones[ i ]; 547 | var found = -1; 548 | 549 | if ( bone.type != 'JOINT' ) continue; 550 | 551 | for ( var j = 0; j < skin.joints.length; j ++ ) { 552 | 553 | if ( bone.sid === skin.joints[ j ] ) { 554 | 555 | found = j; 556 | break; 557 | 558 | } 559 | 560 | } 561 | 562 | if ( found >= 0 ) { 563 | 564 | var inv = skin.invBindMatrices[ found ]; 565 | 566 | bone.invBindMatrix = inv; 567 | bone.skinningMatrix = new THREE.Matrix4(); 568 | bone.skinningMatrix.multiplyMatrices(bone.world, inv); // (IBMi * JMi) 569 | bone.animatrix = new THREE.Matrix4(); 570 | 571 | bone.animatrix.copy(bone.localworld); 572 | bone.weights = []; 573 | 574 | for ( var j = 0; j < skin.weights.length; j ++ ) { 575 | 576 | for (var k = 0; k < skin.weights[ j ].length; k ++ ) { 577 | 578 | var w = skin.weights[ j ][ k ]; 579 | 580 | if ( w.joint === found ) { 581 | 582 | bone.weights.push( w ); 583 | 584 | } 585 | 586 | } 587 | 588 | } 589 | 590 | } else { 591 | 592 | console.warn( "ColladaLoader: Could not find joint '" + bone.sid + "'." ); 593 | 594 | bone.skinningMatrix = new THREE.Matrix4(); 595 | bone.weights = []; 596 | 597 | } 598 | } 599 | 600 | } 601 | 602 | //Walk the Collada tree and flatten the bones into a list, extract the position, quat and scale from the matrix 603 | function flattenSkeleton(skeleton) { 604 | 605 | var list = []; 606 | var walk = function(parentid, node, list) { 607 | 608 | var bone = {}; 609 | bone.name = node.sid; 610 | bone.parent = parentid; 611 | bone.matrix = node.matrix; 612 | var data = [ new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3() ]; 613 | bone.matrix.decompose(data[0], data[1], data[2]); 614 | 615 | bone.pos = [ data[0].x,data[0].y,data[0].z ]; 616 | 617 | bone.scl = [ data[2].x,data[2].y,data[2].z ]; 618 | bone.rotq = [ data[1].x,data[1].y,data[1].z,data[1].w ]; 619 | list.push(bone); 620 | 621 | for (var i in node.nodes) { 622 | 623 | walk(node.sid, node.nodes[i], list); 624 | 625 | } 626 | 627 | }; 628 | 629 | walk(-1, skeleton, list); 630 | return list; 631 | 632 | } 633 | 634 | //Move the vertices into the pose that is proper for the start of the animation 635 | function skinToBindPose(geometry,skeleton,skinController) { 636 | 637 | var bones = []; 638 | setupSkeleton( skeleton, bones, -1 ); 639 | setupSkinningMatrices( bones, skinController.skin ); 640 | v = new THREE.Vector3(); 641 | var skinned = []; 642 | 643 | for (var i = 0; i < geometry.vertices.length; i ++) { 644 | 645 | skinned.push(new THREE.Vector3()); 646 | 647 | } 648 | 649 | for ( i = 0; i < bones.length; i ++ ) { 650 | 651 | if ( bones[ i ].type != 'JOINT' ) continue; 652 | 653 | for ( j = 0; j < bones[ i ].weights.length; j ++ ) { 654 | 655 | w = bones[ i ].weights[ j ]; 656 | vidx = w.index; 657 | weight = w.weight; 658 | 659 | o = geometry.vertices[vidx]; 660 | s = skinned[vidx]; 661 | 662 | v.x = o.x; 663 | v.y = o.y; 664 | v.z = o.z; 665 | 666 | v.applyMatrix4( bones[i].skinningMatrix ); 667 | 668 | s.x += (v.x * weight); 669 | s.y += (v.y * weight); 670 | s.z += (v.z * weight); 671 | } 672 | 673 | } 674 | 675 | for (var i = 0; i < geometry.vertices.length; i ++) { 676 | 677 | geometry.vertices[i] = skinned[i]; 678 | 679 | } 680 | 681 | } 682 | 683 | function applySkin ( geometry, instanceCtrl, frame ) { 684 | 685 | var skinController = controllers[ instanceCtrl.url ]; 686 | 687 | frame = frame !== undefined ? frame : 40; 688 | 689 | if ( !skinController || !skinController.skin ) { 690 | 691 | console.log( 'ColladaLoader: Could not find skin controller.' ); 692 | return; 693 | 694 | } 695 | 696 | if ( !instanceCtrl.skeleton || !instanceCtrl.skeleton.length ) { 697 | 698 | console.log( 'ColladaLoader: Could not find the skeleton for the skin. ' ); 699 | return; 700 | 701 | } 702 | 703 | var animationBounds = calcAnimationBounds(); 704 | var skeleton = visualScene.getChildById( instanceCtrl.skeleton[0], true ) || visualScene.getChildBySid( instanceCtrl.skeleton[0], true ); 705 | 706 | //flatten the skeleton into a list of bones 707 | var bonelist = flattenSkeleton(skeleton); 708 | var joints = skinController.skin.joints; 709 | 710 | //sort that list so that the order reflects the order in the joint list 711 | var sortedbones = []; 712 | for (var i = 0; i < joints.length; i ++) { 713 | 714 | for (var j = 0; j < bonelist.length; j ++) { 715 | 716 | if (bonelist[j].name === joints[i]) { 717 | 718 | sortedbones[i] = bonelist[j]; 719 | 720 | } 721 | 722 | } 723 | 724 | } 725 | 726 | //hook up the parents by index instead of name 727 | for (var i = 0; i < sortedbones.length; i ++) { 728 | 729 | for (var j = 0; j < sortedbones.length; j ++) { 730 | 731 | if (sortedbones[i].parent === sortedbones[j].name) { 732 | 733 | sortedbones[i].parent = j; 734 | 735 | } 736 | 737 | } 738 | 739 | } 740 | 741 | 742 | var i, j, w, vidx, weight; 743 | var v = new THREE.Vector3(), o, s; 744 | 745 | // move vertices to bind shape 746 | for ( i = 0; i < geometry.vertices.length; i ++ ) { 747 | geometry.vertices[i].applyMatrix4( skinController.skin.bindShapeMatrix ); 748 | } 749 | 750 | var skinIndices = []; 751 | var skinWeights = []; 752 | var weights = skinController.skin.weights; 753 | 754 | // hook up the skin weights 755 | // TODO - this might be a good place to choose greatest 4 weights 756 | for ( var i =0; i < weights.length; i ++ ) { 757 | 758 | var indicies = new THREE.Vector4(weights[i][0] ? weights[i][0].joint : 0,weights[i][1] ? weights[i][1].joint : 0,weights[i][2] ? weights[i][2].joint : 0,weights[i][3] ? weights[i][3].joint : 0); 759 | var weight = new THREE.Vector4(weights[i][0] ? weights[i][0].weight : 0,weights[i][1] ? weights[i][1].weight : 0,weights[i][2] ? weights[i][2].weight : 0,weights[i][3] ? weights[i][3].weight : 0); 760 | 761 | skinIndices.push(indicies); 762 | skinWeights.push(weight); 763 | 764 | } 765 | 766 | geometry.skinIndices = skinIndices; 767 | geometry.skinWeights = skinWeights; 768 | geometry.bones = sortedbones; 769 | // process animation, or simply pose the rig if no animation 770 | 771 | //create an animation for the animated bones 772 | //NOTE: this has no effect when using morphtargets 773 | var animationdata = { "name":animationBounds.ID,"fps":30,"length":animationBounds.frames / 30,"hierarchy":[] }; 774 | 775 | for (var j = 0; j < sortedbones.length; j ++) { 776 | 777 | animationdata.hierarchy.push({ parent:sortedbones[j].parent, name:sortedbones[j].name, keys:[] }); 778 | 779 | } 780 | 781 | console.log( 'ColladaLoader:', animationBounds.ID + ' has ' + sortedbones.length + ' bones.' ); 782 | 783 | 784 | 785 | skinToBindPose(geometry, skeleton, skinController); 786 | 787 | 788 | for ( frame = 0; frame < animationBounds.frames; frame ++ ) { 789 | 790 | var bones = []; 791 | var skinned = []; 792 | // process the frame and setup the rig with a fresh 793 | // transform, possibly from the bone's animation channel(s) 794 | 795 | setupSkeleton( skeleton, bones, frame ); 796 | setupSkinningMatrices( bones, skinController.skin ); 797 | 798 | for (var i = 0; i < bones.length; i ++) { 799 | 800 | for (var j = 0; j < animationdata.hierarchy.length; j ++) { 801 | 802 | if (animationdata.hierarchy[j].name === bones[i].sid) { 803 | 804 | var key = {}; 805 | key.time = (frame / 30); 806 | key.matrix = bones[i].animatrix; 807 | 808 | if (frame === 0) 809 | bones[i].matrix = key.matrix; 810 | 811 | var data = [ new THREE.Vector3(),new THREE.Quaternion(),new THREE.Vector3() ]; 812 | key.matrix.decompose(data[0], data[1], data[2]); 813 | 814 | key.pos = [ data[0].x,data[0].y,data[0].z ]; 815 | 816 | key.scl = [ data[2].x,data[2].y,data[2].z ]; 817 | key.rot = data[1]; 818 | 819 | animationdata.hierarchy[j].keys.push(key); 820 | 821 | } 822 | 823 | } 824 | 825 | } 826 | 827 | geometry.animation = animationdata; 828 | 829 | } 830 | 831 | }; 832 | 833 | function createKinematics() { 834 | 835 | if ( kinematicsModel && kinematicsModel.joints.length === 0 ) { 836 | kinematics = undefined; 837 | return; 838 | } 839 | 840 | var jointMap = {}; 841 | 842 | var _addToMap = function( jointIndex, parentVisualElement ) { 843 | 844 | var parentVisualElementId = parentVisualElement.getAttribute( 'id' ); 845 | var colladaNode = visualScene.getChildById( parentVisualElementId, true ); 846 | var joint = kinematicsModel.joints[ jointIndex ]; 847 | 848 | scene.traverse(function( node ) { 849 | 850 | if ( node.colladaId == parentVisualElementId ) { 851 | 852 | jointMap[ jointIndex ] = { 853 | node: node, 854 | transforms: colladaNode.transforms, 855 | joint: joint, 856 | position: joint.zeroPosition 857 | }; 858 | 859 | } 860 | 861 | }); 862 | 863 | }; 864 | 865 | kinematics = { 866 | 867 | joints: kinematicsModel && kinematicsModel.joints, 868 | 869 | getJointValue: function( jointIndex ) { 870 | 871 | var jointData = jointMap[ jointIndex ]; 872 | 873 | if ( jointData ) { 874 | 875 | return jointData.position; 876 | 877 | } else { 878 | 879 | console.log( 'getJointValue: joint ' + jointIndex + ' doesn\'t exist' ); 880 | 881 | } 882 | 883 | }, 884 | 885 | setJointValue: function( jointIndex, value ) { 886 | 887 | var jointData = jointMap[ jointIndex ]; 888 | 889 | if ( jointData ) { 890 | 891 | var joint = jointData.joint; 892 | 893 | if ( value > joint.limits.max || value < joint.limits.min ) { 894 | 895 | console.log( 'setJointValue: joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ')' ); 896 | 897 | } else if ( joint.static ) { 898 | 899 | console.log( 'setJointValue: joint ' + jointIndex + ' is static' ); 900 | 901 | } else { 902 | 903 | var threejsNode = jointData.node; 904 | var axis = joint.axis; 905 | var transforms = jointData.transforms; 906 | 907 | var matrix = new THREE.Matrix4(); 908 | 909 | for (i = 0; i < transforms.length; i ++ ) { 910 | 911 | var transform = transforms[ i ]; 912 | 913 | // kinda ghetto joint detection 914 | if ( transform.sid && transform.sid.indexOf( 'joint' + jointIndex ) !== -1 ) { 915 | 916 | // apply actual joint value here 917 | switch ( joint.type ) { 918 | 919 | case 'revolute': 920 | 921 | matrix.multiply( m1.makeRotationAxis( axis, THREE.Math.degToRad(value) ) ); 922 | break; 923 | 924 | case 'prismatic': 925 | 926 | matrix.multiply( m1.makeTranslation(axis.x * value, axis.y * value, axis.z * value ) ); 927 | break; 928 | 929 | default: 930 | 931 | console.warn( 'setJointValue: unknown joint type: ' + joint.type ); 932 | break; 933 | 934 | } 935 | 936 | } else { 937 | 938 | var m1 = new THREE.Matrix4(); 939 | 940 | switch ( transform.type ) { 941 | 942 | case 'matrix': 943 | 944 | matrix.multiply( transform.obj ); 945 | 946 | break; 947 | 948 | case 'translate': 949 | 950 | matrix.multiply( m1.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) ); 951 | 952 | break; 953 | 954 | case 'rotate': 955 | 956 | matrix.multiply( m1.makeRotationAxis( transform.obj, transform.angle ) ); 957 | 958 | break; 959 | 960 | } 961 | } 962 | } 963 | 964 | // apply the matrix to the threejs node 965 | var elementsFloat32Arr = matrix.elements; 966 | var elements = Array.prototype.slice.call( elementsFloat32Arr ); 967 | 968 | var elementsRowMajor = [ 969 | elements[ 0 ], 970 | elements[ 4 ], 971 | elements[ 8 ], 972 | elements[ 12 ], 973 | elements[ 1 ], 974 | elements[ 5 ], 975 | elements[ 9 ], 976 | elements[ 13 ], 977 | elements[ 2 ], 978 | elements[ 6 ], 979 | elements[ 10 ], 980 | elements[ 14 ], 981 | elements[ 3 ], 982 | elements[ 7 ], 983 | elements[ 11 ], 984 | elements[ 15 ] 985 | ]; 986 | 987 | threejsNode.matrix.set.apply( threejsNode.matrix, elementsRowMajor ); 988 | threejsNode.matrix.decompose( threejsNode.position, threejsNode.quaternion, threejsNode.scale ); 989 | } 990 | 991 | } else { 992 | 993 | console.log( 'setJointValue: joint ' + jointIndex + ' doesn\'t exist' ); 994 | 995 | } 996 | 997 | } 998 | 999 | }; 1000 | 1001 | var element = COLLADA.querySelector('scene instance_kinematics_scene'); 1002 | 1003 | if ( element ) { 1004 | 1005 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1006 | 1007 | var child = element.childNodes[ i ]; 1008 | 1009 | if ( child.nodeType != 1 ) continue; 1010 | 1011 | switch ( child.nodeName ) { 1012 | 1013 | case 'bind_joint_axis': 1014 | 1015 | var visualTarget = child.getAttribute( 'target' ).split( '/' ).pop(); 1016 | var axis = child.querySelector('axis param').textContent; 1017 | var jointIndex = parseInt( axis.split( 'joint' ).pop().split( '.' )[0] ); 1018 | var visualTargetElement = COLLADA.querySelector( '[sid="' + visualTarget + '"]' ); 1019 | 1020 | if ( visualTargetElement ) { 1021 | var parentVisualElement = visualTargetElement.parentElement; 1022 | _addToMap(jointIndex, parentVisualElement); 1023 | } 1024 | 1025 | break; 1026 | 1027 | default: 1028 | 1029 | break; 1030 | 1031 | } 1032 | 1033 | } 1034 | } 1035 | 1036 | }; 1037 | 1038 | function createSceneGraph ( node, parent ) { 1039 | 1040 | var obj = new THREE.Object3D(); 1041 | var skinned = false; 1042 | var skinController; 1043 | var morphController; 1044 | var i, j; 1045 | 1046 | // FIXME: controllers 1047 | 1048 | for ( i = 0; i < node.controllers.length; i ++ ) { 1049 | 1050 | var controller = controllers[ node.controllers[ i ].url ]; 1051 | 1052 | switch ( controller.type ) { 1053 | 1054 | case 'skin': 1055 | 1056 | if ( geometries[ controller.skin.source ] ) { 1057 | 1058 | var inst_geom = new InstanceGeometry(); 1059 | 1060 | inst_geom.url = controller.skin.source; 1061 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1062 | 1063 | node.geometries.push( inst_geom ); 1064 | skinned = true; 1065 | skinController = node.controllers[ i ]; 1066 | 1067 | } else if ( controllers[ controller.skin.source ] ) { 1068 | 1069 | // urgh: controller can be chained 1070 | // handle the most basic case... 1071 | 1072 | var second = controllers[ controller.skin.source ]; 1073 | morphController = second; 1074 | // skinController = node.controllers[i]; 1075 | 1076 | if ( second.morph && geometries[ second.morph.source ] ) { 1077 | 1078 | var inst_geom = new InstanceGeometry(); 1079 | 1080 | inst_geom.url = second.morph.source; 1081 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1082 | 1083 | node.geometries.push( inst_geom ); 1084 | 1085 | } 1086 | 1087 | } 1088 | 1089 | break; 1090 | 1091 | case 'morph': 1092 | 1093 | if ( geometries[ controller.morph.source ] ) { 1094 | 1095 | var inst_geom = new InstanceGeometry(); 1096 | 1097 | inst_geom.url = controller.morph.source; 1098 | inst_geom.instance_material = node.controllers[ i ].instance_material; 1099 | 1100 | node.geometries.push( inst_geom ); 1101 | morphController = node.controllers[ i ]; 1102 | 1103 | } 1104 | 1105 | console.log( 'ColladaLoader: Morph-controller partially supported.' ); 1106 | 1107 | default: 1108 | break; 1109 | 1110 | } 1111 | 1112 | } 1113 | 1114 | // geometries 1115 | 1116 | var double_sided_materials = {}; 1117 | 1118 | for ( i = 0; i < node.geometries.length; i ++ ) { 1119 | 1120 | var instance_geometry = node.geometries[i]; 1121 | var instance_materials = instance_geometry.instance_material; 1122 | var geometry = geometries[ instance_geometry.url ]; 1123 | var used_materials = {}; 1124 | var used_materials_array = []; 1125 | var num_materials = 0; 1126 | var first_material; 1127 | 1128 | if ( geometry ) { 1129 | 1130 | if ( !geometry.mesh || !geometry.mesh.primitives ) 1131 | continue; 1132 | 1133 | if ( obj.name.length === 0 ) { 1134 | 1135 | obj.name = geometry.id; 1136 | 1137 | } 1138 | 1139 | // collect used fx for this geometry-instance 1140 | 1141 | if ( instance_materials ) { 1142 | 1143 | for ( j = 0; j < instance_materials.length; j ++ ) { 1144 | 1145 | var instance_material = instance_materials[ j ]; 1146 | var mat = materials[ instance_material.target ]; 1147 | var effect_id = mat.instance_effect.url; 1148 | var shader = effects[ effect_id ].shader; 1149 | var material3js = shader.material; 1150 | 1151 | if ( geometry.doubleSided ) { 1152 | 1153 | if ( !( instance_material.symbol in double_sided_materials ) ) { 1154 | 1155 | var _copied_material = material3js.clone(); 1156 | _copied_material.side = THREE.DoubleSide; 1157 | double_sided_materials[ instance_material.symbol ] = _copied_material; 1158 | 1159 | } 1160 | 1161 | material3js = double_sided_materials[ instance_material.symbol ]; 1162 | 1163 | } 1164 | 1165 | material3js.opacity = !material3js.opacity ? 1 : material3js.opacity; 1166 | used_materials[ instance_material.symbol ] = num_materials; 1167 | used_materials_array.push( material3js ); 1168 | first_material = material3js; 1169 | first_material.name = mat.name === null || mat.name === '' ? mat.id : mat.name; 1170 | num_materials ++; 1171 | 1172 | } 1173 | 1174 | } 1175 | 1176 | var mesh; 1177 | var material = first_material || new THREE.MeshLambertMaterial( { color: 0xdddddd, side: geometry.doubleSided ? THREE.DoubleSide : THREE.FrontSide } ); 1178 | var geom = geometry.mesh.geometry3js; 1179 | 1180 | if ( num_materials > 1 ) { 1181 | 1182 | material = new THREE.MeshFaceMaterial( used_materials_array ); 1183 | 1184 | for ( j = 0; j < geom.faces.length; j ++ ) { 1185 | 1186 | var face = geom.faces[ j ]; 1187 | face.materialIndex = used_materials[ face.daeMaterial ] 1188 | 1189 | } 1190 | 1191 | } 1192 | 1193 | if ( skinController !== undefined ) { 1194 | 1195 | 1196 | applySkin( geom, skinController ); 1197 | 1198 | if ( geom.morphTargets.length > 0 ) { 1199 | 1200 | material.morphTargets = true; 1201 | material.skinning = false; 1202 | 1203 | } else { 1204 | 1205 | material.morphTargets = false; 1206 | material.skinning = true; 1207 | 1208 | } 1209 | 1210 | 1211 | mesh = new THREE.SkinnedMesh( geom, material, false ); 1212 | 1213 | 1214 | //mesh.skeleton = skinController.skeleton; 1215 | //mesh.skinController = controllers[ skinController.url ]; 1216 | //mesh.skinInstanceController = skinController; 1217 | mesh.name = 'skin_' + skins.length; 1218 | 1219 | 1220 | 1221 | //mesh.animationHandle.setKey(0); 1222 | skins.push( mesh ); 1223 | 1224 | } else if ( morphController !== undefined ) { 1225 | 1226 | createMorph( geom, morphController ); 1227 | 1228 | material.morphTargets = true; 1229 | 1230 | mesh = new THREE.Mesh( geom, material ); 1231 | mesh.name = 'morph_' + morphs.length; 1232 | 1233 | morphs.push( mesh ); 1234 | 1235 | } else { 1236 | 1237 | if ( geom.isLineStrip === true ) { 1238 | 1239 | mesh = new THREE.Line( geom ); 1240 | 1241 | } else { 1242 | 1243 | mesh = new THREE.Mesh( geom, material ); 1244 | 1245 | } 1246 | 1247 | } 1248 | 1249 | obj.add(mesh); 1250 | 1251 | } 1252 | 1253 | } 1254 | 1255 | for ( i = 0; i < node.cameras.length; i ++ ) { 1256 | 1257 | var instance_camera = node.cameras[i]; 1258 | var cparams = cameras[instance_camera.url]; 1259 | 1260 | var cam = new THREE.PerspectiveCamera(cparams.yfov, parseFloat(cparams.aspect_ratio), 1261 | parseFloat(cparams.znear), parseFloat(cparams.zfar)); 1262 | 1263 | obj.add(cam); 1264 | } 1265 | 1266 | for ( i = 0; i < node.lights.length; i ++ ) { 1267 | 1268 | var light = null; 1269 | var instance_light = node.lights[i]; 1270 | var lparams = lights[instance_light.url]; 1271 | 1272 | if ( lparams && lparams.technique ) { 1273 | 1274 | var color = lparams.color.getHex(); 1275 | var intensity = lparams.intensity; 1276 | var distance = lparams.distance; 1277 | var angle = lparams.falloff_angle; 1278 | var exponent; // Intentionally undefined, don't know what this is yet 1279 | 1280 | switch ( lparams.technique ) { 1281 | 1282 | case 'directional': 1283 | 1284 | light = new THREE.DirectionalLight( color, intensity, distance ); 1285 | light.position.set(0, 0, 1); 1286 | break; 1287 | 1288 | case 'point': 1289 | 1290 | light = new THREE.PointLight( color, intensity, distance ); 1291 | break; 1292 | 1293 | case 'spot': 1294 | 1295 | light = new THREE.SpotLight( color, intensity, distance, angle, exponent ); 1296 | light.position.set(0, 0, 1); 1297 | break; 1298 | 1299 | case 'ambient': 1300 | 1301 | light = new THREE.AmbientLight( color ); 1302 | break; 1303 | 1304 | } 1305 | 1306 | } 1307 | 1308 | if (light) { 1309 | obj.add(light); 1310 | } 1311 | } 1312 | 1313 | obj.name = node.name || node.id || ""; 1314 | obj.colladaId = node.id || ""; 1315 | obj.layer = node.layer || ""; 1316 | obj.matrix = node.matrix; 1317 | obj.matrix.decompose( obj.position, obj.quaternion, obj.scale ); 1318 | 1319 | if ( options.centerGeometry && obj.geometry ) { 1320 | 1321 | var delta = obj.geometry.center(); 1322 | delta.multiply( obj.scale ); 1323 | delta.applyQuaternion( obj.quaternion ); 1324 | 1325 | obj.position.sub( delta ); 1326 | 1327 | } 1328 | 1329 | for ( i = 0; i < node.nodes.length; i ++ ) { 1330 | 1331 | obj.add( createSceneGraph( node.nodes[i], node ) ); 1332 | 1333 | } 1334 | 1335 | return obj; 1336 | 1337 | }; 1338 | 1339 | function getJointId( skin, id ) { 1340 | 1341 | for ( var i = 0; i < skin.joints.length; i ++ ) { 1342 | 1343 | if ( skin.joints[ i ] === id ) { 1344 | 1345 | return i; 1346 | 1347 | } 1348 | 1349 | } 1350 | 1351 | }; 1352 | 1353 | function getLibraryNode( id ) { 1354 | 1355 | var nodes = COLLADA.querySelectorAll('library_nodes node'); 1356 | 1357 | for ( var i = 0; i < nodes.length; i++ ) { 1358 | 1359 | var attObj = nodes[i].attributes.getNamedItem('id'); 1360 | 1361 | if ( attObj && attObj.value === id ) { 1362 | 1363 | return nodes[i]; 1364 | 1365 | } 1366 | 1367 | } 1368 | 1369 | return undefined; 1370 | 1371 | }; 1372 | 1373 | function getChannelsForNode ( node ) { 1374 | 1375 | var channels = []; 1376 | var startTime = 1000000; 1377 | var endTime = -1000000; 1378 | 1379 | for ( var id in animations ) { 1380 | 1381 | var animation = animations[id]; 1382 | 1383 | for ( var i = 0; i < animation.channel.length; i ++ ) { 1384 | 1385 | var channel = animation.channel[i]; 1386 | var sampler = animation.sampler[i]; 1387 | var id = channel.target.split('/')[0]; 1388 | 1389 | if ( id == node.id ) { 1390 | 1391 | sampler.create(); 1392 | channel.sampler = sampler; 1393 | startTime = Math.min(startTime, sampler.startTime); 1394 | endTime = Math.max(endTime, sampler.endTime); 1395 | channels.push(channel); 1396 | 1397 | } 1398 | 1399 | } 1400 | 1401 | } 1402 | 1403 | if ( channels.length ) { 1404 | 1405 | node.startTime = startTime; 1406 | node.endTime = endTime; 1407 | 1408 | } 1409 | 1410 | return channels; 1411 | 1412 | }; 1413 | 1414 | function calcFrameDuration( node ) { 1415 | 1416 | var minT = 10000000; 1417 | 1418 | for ( var i = 0; i < node.channels.length; i ++ ) { 1419 | 1420 | var sampler = node.channels[i].sampler; 1421 | 1422 | for ( var j = 0; j < sampler.input.length - 1; j ++ ) { 1423 | 1424 | var t0 = sampler.input[ j ]; 1425 | var t1 = sampler.input[ j + 1 ]; 1426 | minT = Math.min( minT, t1 - t0 ); 1427 | 1428 | } 1429 | } 1430 | 1431 | return minT; 1432 | 1433 | }; 1434 | 1435 | function calcMatrixAt( node, t ) { 1436 | 1437 | var animated = {}; 1438 | 1439 | var i, j; 1440 | 1441 | for ( i = 0; i < node.channels.length; i ++ ) { 1442 | 1443 | var channel = node.channels[ i ]; 1444 | animated[ channel.sid ] = channel; 1445 | 1446 | } 1447 | 1448 | var matrix = new THREE.Matrix4(); 1449 | 1450 | for ( i = 0; i < node.transforms.length; i ++ ) { 1451 | 1452 | var transform = node.transforms[ i ]; 1453 | var channel = animated[ transform.sid ]; 1454 | 1455 | if ( channel !== undefined ) { 1456 | 1457 | var sampler = channel.sampler; 1458 | var value; 1459 | 1460 | for ( j = 0; j < sampler.input.length - 1; j ++ ) { 1461 | 1462 | if ( sampler.input[ j + 1 ] > t ) { 1463 | 1464 | value = sampler.output[ j ]; 1465 | //console.log(value.flatten) 1466 | break; 1467 | 1468 | } 1469 | 1470 | } 1471 | 1472 | if ( value !== undefined ) { 1473 | 1474 | if ( value instanceof THREE.Matrix4 ) { 1475 | 1476 | matrix.multiplyMatrices( matrix, value ); 1477 | 1478 | } else { 1479 | 1480 | // FIXME: handle other types 1481 | 1482 | matrix.multiplyMatrices( matrix, transform.matrix ); 1483 | 1484 | } 1485 | 1486 | } else { 1487 | 1488 | matrix.multiplyMatrices( matrix, transform.matrix ); 1489 | 1490 | } 1491 | 1492 | } else { 1493 | 1494 | matrix.multiplyMatrices( matrix, transform.matrix ); 1495 | 1496 | } 1497 | 1498 | } 1499 | 1500 | return matrix; 1501 | 1502 | }; 1503 | 1504 | function bakeAnimations ( node ) { 1505 | 1506 | if ( node.channels && node.channels.length ) { 1507 | 1508 | var keys = [], 1509 | sids = []; 1510 | 1511 | for ( var i = 0, il = node.channels.length; i < il; i ++ ) { 1512 | 1513 | var channel = node.channels[i], 1514 | fullSid = channel.fullSid, 1515 | sampler = channel.sampler, 1516 | input = sampler.input, 1517 | transform = node.getTransformBySid( channel.sid ), 1518 | member; 1519 | 1520 | if ( channel.arrIndices ) { 1521 | 1522 | member = []; 1523 | 1524 | for ( var j = 0, jl = channel.arrIndices.length; j < jl; j ++ ) { 1525 | 1526 | member[ j ] = getConvertedIndex( channel.arrIndices[ j ] ); 1527 | 1528 | } 1529 | 1530 | } else { 1531 | 1532 | member = getConvertedMember( channel.member ); 1533 | 1534 | } 1535 | 1536 | if ( transform ) { 1537 | 1538 | if ( sids.indexOf( fullSid ) === -1 ) { 1539 | 1540 | sids.push( fullSid ); 1541 | 1542 | } 1543 | 1544 | for ( var j = 0, jl = input.length; j < jl; j ++ ) { 1545 | 1546 | var time = input[j], 1547 | data = sampler.getData( transform.type, j, member ), 1548 | key = findKey( keys, time ); 1549 | 1550 | if ( !key ) { 1551 | 1552 | key = new Key( time ); 1553 | var timeNdx = findTimeNdx( keys, time ); 1554 | keys.splice( timeNdx === -1 ? keys.length : timeNdx, 0, key ); 1555 | 1556 | } 1557 | 1558 | key.addTarget( fullSid, transform, member, data ); 1559 | 1560 | } 1561 | 1562 | } else { 1563 | 1564 | console.log( 'Could not find transform "' + channel.sid + '" in node ' + node.id ); 1565 | 1566 | } 1567 | 1568 | } 1569 | 1570 | // post process 1571 | for ( var i = 0; i < sids.length; i ++ ) { 1572 | 1573 | var sid = sids[ i ]; 1574 | 1575 | for ( var j = 0; j < keys.length; j ++ ) { 1576 | 1577 | var key = keys[ j ]; 1578 | 1579 | if ( !key.hasTarget( sid ) ) { 1580 | 1581 | interpolateKeys( keys, key, j, sid ); 1582 | 1583 | } 1584 | 1585 | } 1586 | 1587 | } 1588 | 1589 | node.keys = keys; 1590 | node.sids = sids; 1591 | 1592 | } 1593 | 1594 | }; 1595 | 1596 | function findKey ( keys, time) { 1597 | 1598 | var retVal = null; 1599 | 1600 | for ( var i = 0, il = keys.length; i < il && retVal === null; i ++ ) { 1601 | 1602 | var key = keys[i]; 1603 | 1604 | if ( key.time === time ) { 1605 | 1606 | retVal = key; 1607 | 1608 | } else if ( key.time > time ) { 1609 | 1610 | break; 1611 | 1612 | } 1613 | 1614 | } 1615 | 1616 | return retVal; 1617 | 1618 | }; 1619 | 1620 | function findTimeNdx ( keys, time) { 1621 | 1622 | var ndx = -1; 1623 | 1624 | for ( var i = 0, il = keys.length; i < il && ndx === -1; i ++ ) { 1625 | 1626 | var key = keys[i]; 1627 | 1628 | if ( key.time >= time ) { 1629 | 1630 | ndx = i; 1631 | 1632 | } 1633 | 1634 | } 1635 | 1636 | return ndx; 1637 | 1638 | }; 1639 | 1640 | function interpolateKeys ( keys, key, ndx, fullSid ) { 1641 | 1642 | var prevKey = getPrevKeyWith( keys, fullSid, ndx ? ndx - 1 : 0 ), 1643 | nextKey = getNextKeyWith( keys, fullSid, ndx + 1 ); 1644 | 1645 | if ( prevKey && nextKey ) { 1646 | 1647 | var scale = (key.time - prevKey.time) / (nextKey.time - prevKey.time), 1648 | prevTarget = prevKey.getTarget( fullSid ), 1649 | nextData = nextKey.getTarget( fullSid ).data, 1650 | prevData = prevTarget.data, 1651 | data; 1652 | 1653 | if ( prevTarget.type === 'matrix' ) { 1654 | 1655 | data = prevData; 1656 | 1657 | } else if ( prevData.length ) { 1658 | 1659 | data = []; 1660 | 1661 | for ( var i = 0; i < prevData.length; ++ i ) { 1662 | 1663 | data[ i ] = prevData[ i ] + ( nextData[ i ] - prevData[ i ] ) * scale; 1664 | 1665 | } 1666 | 1667 | } else { 1668 | 1669 | data = prevData + ( nextData - prevData ) * scale; 1670 | 1671 | } 1672 | 1673 | key.addTarget( fullSid, prevTarget.transform, prevTarget.member, data ); 1674 | 1675 | } 1676 | 1677 | }; 1678 | 1679 | // Get next key with given sid 1680 | 1681 | function getNextKeyWith( keys, fullSid, ndx ) { 1682 | 1683 | for ( ; ndx < keys.length; ndx ++ ) { 1684 | 1685 | var key = keys[ ndx ]; 1686 | 1687 | if ( key.hasTarget( fullSid ) ) { 1688 | 1689 | return key; 1690 | 1691 | } 1692 | 1693 | } 1694 | 1695 | return null; 1696 | 1697 | }; 1698 | 1699 | // Get previous key with given sid 1700 | 1701 | function getPrevKeyWith( keys, fullSid, ndx ) { 1702 | 1703 | ndx = ndx >= 0 ? ndx : ndx + keys.length; 1704 | 1705 | for ( ; ndx >= 0; ndx -- ) { 1706 | 1707 | var key = keys[ ndx ]; 1708 | 1709 | if ( key.hasTarget( fullSid ) ) { 1710 | 1711 | return key; 1712 | 1713 | } 1714 | 1715 | } 1716 | 1717 | return null; 1718 | 1719 | }; 1720 | 1721 | function _Image() { 1722 | 1723 | this.id = ""; 1724 | this.init_from = ""; 1725 | 1726 | }; 1727 | 1728 | _Image.prototype.parse = function(element) { 1729 | 1730 | this.id = element.getAttribute('id'); 1731 | 1732 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1733 | 1734 | var child = element.childNodes[ i ]; 1735 | 1736 | if ( child.nodeName === 'init_from' ) { 1737 | 1738 | this.init_from = child.textContent; 1739 | 1740 | } 1741 | 1742 | } 1743 | 1744 | return this; 1745 | 1746 | }; 1747 | 1748 | function Controller() { 1749 | 1750 | this.id = ""; 1751 | this.name = ""; 1752 | this.type = ""; 1753 | this.skin = null; 1754 | this.morph = null; 1755 | 1756 | }; 1757 | 1758 | Controller.prototype.parse = function( element ) { 1759 | 1760 | this.id = element.getAttribute('id'); 1761 | this.name = element.getAttribute('name'); 1762 | this.type = "none"; 1763 | 1764 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1765 | 1766 | var child = element.childNodes[ i ]; 1767 | 1768 | switch ( child.nodeName ) { 1769 | 1770 | case 'skin': 1771 | 1772 | this.skin = (new Skin()).parse(child); 1773 | this.type = child.nodeName; 1774 | break; 1775 | 1776 | case 'morph': 1777 | 1778 | this.morph = (new Morph()).parse(child); 1779 | this.type = child.nodeName; 1780 | break; 1781 | 1782 | default: 1783 | break; 1784 | 1785 | } 1786 | } 1787 | 1788 | return this; 1789 | 1790 | }; 1791 | 1792 | function Morph() { 1793 | 1794 | this.method = null; 1795 | this.source = null; 1796 | this.targets = null; 1797 | this.weights = null; 1798 | 1799 | }; 1800 | 1801 | Morph.prototype.parse = function( element ) { 1802 | 1803 | var sources = {}; 1804 | var inputs = []; 1805 | var i; 1806 | 1807 | this.method = element.getAttribute( 'method' ); 1808 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 1809 | 1810 | for ( i = 0; i < element.childNodes.length; i ++ ) { 1811 | 1812 | var child = element.childNodes[ i ]; 1813 | if ( child.nodeType != 1 ) continue; 1814 | 1815 | switch ( child.nodeName ) { 1816 | 1817 | case 'source': 1818 | 1819 | var source = ( new Source() ).parse( child ); 1820 | sources[ source.id ] = source; 1821 | break; 1822 | 1823 | case 'targets': 1824 | 1825 | inputs = this.parseInputs( child ); 1826 | break; 1827 | 1828 | default: 1829 | 1830 | console.log( child.nodeName ); 1831 | break; 1832 | 1833 | } 1834 | 1835 | } 1836 | 1837 | for ( i = 0; i < inputs.length; i ++ ) { 1838 | 1839 | var input = inputs[ i ]; 1840 | var source = sources[ input.source ]; 1841 | 1842 | switch ( input.semantic ) { 1843 | 1844 | case 'MORPH_TARGET': 1845 | 1846 | this.targets = source.read(); 1847 | break; 1848 | 1849 | case 'MORPH_WEIGHT': 1850 | 1851 | this.weights = source.read(); 1852 | break; 1853 | 1854 | default: 1855 | break; 1856 | 1857 | } 1858 | } 1859 | 1860 | return this; 1861 | 1862 | }; 1863 | 1864 | Morph.prototype.parseInputs = function(element) { 1865 | 1866 | var inputs = []; 1867 | 1868 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1869 | 1870 | var child = element.childNodes[i]; 1871 | if ( child.nodeType != 1) continue; 1872 | 1873 | switch ( child.nodeName ) { 1874 | 1875 | case 'input': 1876 | 1877 | inputs.push( (new Input()).parse(child) ); 1878 | break; 1879 | 1880 | default: 1881 | break; 1882 | } 1883 | } 1884 | 1885 | return inputs; 1886 | 1887 | }; 1888 | 1889 | function Skin() { 1890 | 1891 | this.source = ""; 1892 | this.bindShapeMatrix = null; 1893 | this.invBindMatrices = []; 1894 | this.joints = []; 1895 | this.weights = []; 1896 | 1897 | }; 1898 | 1899 | Skin.prototype.parse = function( element ) { 1900 | 1901 | var sources = {}; 1902 | var joints, weights; 1903 | 1904 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 1905 | this.invBindMatrices = []; 1906 | this.joints = []; 1907 | this.weights = []; 1908 | 1909 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1910 | 1911 | var child = element.childNodes[i]; 1912 | if ( child.nodeType != 1 ) continue; 1913 | 1914 | switch ( child.nodeName ) { 1915 | 1916 | case 'bind_shape_matrix': 1917 | 1918 | var f = _floats(child.textContent); 1919 | this.bindShapeMatrix = getConvertedMat4( f ); 1920 | break; 1921 | 1922 | case 'source': 1923 | 1924 | var src = new Source().parse(child); 1925 | sources[ src.id ] = src; 1926 | break; 1927 | 1928 | case 'joints': 1929 | 1930 | joints = child; 1931 | break; 1932 | 1933 | case 'vertex_weights': 1934 | 1935 | weights = child; 1936 | break; 1937 | 1938 | default: 1939 | 1940 | console.log( child.nodeName ); 1941 | break; 1942 | 1943 | } 1944 | } 1945 | 1946 | this.parseJoints( joints, sources ); 1947 | this.parseWeights( weights, sources ); 1948 | 1949 | return this; 1950 | 1951 | }; 1952 | 1953 | Skin.prototype.parseJoints = function ( element, sources ) { 1954 | 1955 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1956 | 1957 | var child = element.childNodes[ i ]; 1958 | if ( child.nodeType != 1 ) continue; 1959 | 1960 | switch ( child.nodeName ) { 1961 | 1962 | case 'input': 1963 | 1964 | var input = ( new Input() ).parse( child ); 1965 | var source = sources[ input.source ]; 1966 | 1967 | if ( input.semantic === 'JOINT' ) { 1968 | 1969 | this.joints = source.read(); 1970 | 1971 | } else if ( input.semantic === 'INV_BIND_MATRIX' ) { 1972 | 1973 | this.invBindMatrices = source.read(); 1974 | 1975 | } 1976 | 1977 | break; 1978 | 1979 | default: 1980 | break; 1981 | } 1982 | 1983 | } 1984 | 1985 | }; 1986 | 1987 | Skin.prototype.parseWeights = function ( element, sources ) { 1988 | 1989 | var v, vcount, inputs = []; 1990 | 1991 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 1992 | 1993 | var child = element.childNodes[ i ]; 1994 | if ( child.nodeType != 1 ) continue; 1995 | 1996 | switch ( child.nodeName ) { 1997 | 1998 | case 'input': 1999 | 2000 | inputs.push( ( new Input() ).parse( child ) ); 2001 | break; 2002 | 2003 | case 'v': 2004 | 2005 | v = _ints( child.textContent ); 2006 | break; 2007 | 2008 | case 'vcount': 2009 | 2010 | vcount = _ints( child.textContent ); 2011 | break; 2012 | 2013 | default: 2014 | break; 2015 | 2016 | } 2017 | 2018 | } 2019 | 2020 | var index = 0; 2021 | 2022 | for ( var i = 0; i < vcount.length; i ++ ) { 2023 | 2024 | var numBones = vcount[i]; 2025 | var vertex_weights = []; 2026 | 2027 | for ( var j = 0; j < numBones; j ++ ) { 2028 | 2029 | var influence = {}; 2030 | 2031 | for ( var k = 0; k < inputs.length; k ++ ) { 2032 | 2033 | var input = inputs[ k ]; 2034 | var value = v[ index + input.offset ]; 2035 | 2036 | switch ( input.semantic ) { 2037 | 2038 | case 'JOINT': 2039 | 2040 | influence.joint = value;//this.joints[value]; 2041 | break; 2042 | 2043 | case 'WEIGHT': 2044 | 2045 | influence.weight = sources[ input.source ].data[ value ]; 2046 | break; 2047 | 2048 | default: 2049 | break; 2050 | 2051 | } 2052 | 2053 | } 2054 | 2055 | vertex_weights.push( influence ); 2056 | index += inputs.length; 2057 | } 2058 | 2059 | for ( var j = 0; j < vertex_weights.length; j ++ ) { 2060 | 2061 | vertex_weights[ j ].index = i; 2062 | 2063 | } 2064 | 2065 | this.weights.push( vertex_weights ); 2066 | 2067 | } 2068 | 2069 | }; 2070 | 2071 | function VisualScene () { 2072 | 2073 | this.id = ""; 2074 | this.name = ""; 2075 | this.nodes = []; 2076 | this.scene = new THREE.Group(); 2077 | 2078 | }; 2079 | 2080 | VisualScene.prototype.getChildById = function( id, recursive ) { 2081 | 2082 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2083 | 2084 | var node = this.nodes[ i ].getChildById( id, recursive ); 2085 | 2086 | if ( node ) { 2087 | 2088 | return node; 2089 | 2090 | } 2091 | 2092 | } 2093 | 2094 | return null; 2095 | 2096 | }; 2097 | 2098 | VisualScene.prototype.getChildBySid = function( sid, recursive ) { 2099 | 2100 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2101 | 2102 | var node = this.nodes[ i ].getChildBySid( sid, recursive ); 2103 | 2104 | if ( node ) { 2105 | 2106 | return node; 2107 | 2108 | } 2109 | 2110 | } 2111 | 2112 | return null; 2113 | 2114 | }; 2115 | 2116 | VisualScene.prototype.parse = function( element ) { 2117 | 2118 | this.id = element.getAttribute( 'id' ); 2119 | this.name = element.getAttribute( 'name' ); 2120 | this.nodes = []; 2121 | 2122 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2123 | 2124 | var child = element.childNodes[ i ]; 2125 | if ( child.nodeType != 1 ) continue; 2126 | 2127 | switch ( child.nodeName ) { 2128 | 2129 | case 'node': 2130 | 2131 | this.nodes.push( ( new Node() ).parse( child ) ); 2132 | break; 2133 | 2134 | default: 2135 | break; 2136 | 2137 | } 2138 | 2139 | } 2140 | 2141 | return this; 2142 | 2143 | }; 2144 | 2145 | function Node() { 2146 | 2147 | this.id = ""; 2148 | this.name = ""; 2149 | this.sid = ""; 2150 | this.nodes = []; 2151 | this.controllers = []; 2152 | this.transforms = []; 2153 | this.geometries = []; 2154 | this.channels = []; 2155 | this.matrix = new THREE.Matrix4(); 2156 | 2157 | }; 2158 | 2159 | Node.prototype.getChannelForTransform = function( transformSid ) { 2160 | 2161 | for ( var i = 0; i < this.channels.length; i ++ ) { 2162 | 2163 | var channel = this.channels[i]; 2164 | var parts = channel.target.split('/'); 2165 | var id = parts.shift(); 2166 | var sid = parts.shift(); 2167 | var dotSyntax = (sid.indexOf(".") >= 0); 2168 | var arrSyntax = (sid.indexOf("(") >= 0); 2169 | var arrIndices; 2170 | var member; 2171 | 2172 | if ( dotSyntax ) { 2173 | 2174 | parts = sid.split("."); 2175 | sid = parts.shift(); 2176 | member = parts.shift(); 2177 | 2178 | } else if ( arrSyntax ) { 2179 | 2180 | arrIndices = sid.split("("); 2181 | sid = arrIndices.shift(); 2182 | 2183 | for ( var j = 0; j < arrIndices.length; j ++ ) { 2184 | 2185 | arrIndices[ j ] = parseInt( arrIndices[ j ].replace( /\)/, '' ) ); 2186 | 2187 | } 2188 | 2189 | } 2190 | 2191 | if ( sid === transformSid ) { 2192 | 2193 | channel.info = { sid: sid, dotSyntax: dotSyntax, arrSyntax: arrSyntax, arrIndices: arrIndices }; 2194 | return channel; 2195 | 2196 | } 2197 | 2198 | } 2199 | 2200 | return null; 2201 | 2202 | }; 2203 | 2204 | Node.prototype.getChildById = function ( id, recursive ) { 2205 | 2206 | if ( this.id === id ) { 2207 | 2208 | return this; 2209 | 2210 | } 2211 | 2212 | if ( recursive ) { 2213 | 2214 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2215 | 2216 | var n = this.nodes[ i ].getChildById( id, recursive ); 2217 | 2218 | if ( n ) { 2219 | 2220 | return n; 2221 | 2222 | } 2223 | 2224 | } 2225 | 2226 | } 2227 | 2228 | return null; 2229 | 2230 | }; 2231 | 2232 | Node.prototype.getChildBySid = function ( sid, recursive ) { 2233 | 2234 | if ( this.sid === sid ) { 2235 | 2236 | return this; 2237 | 2238 | } 2239 | 2240 | if ( recursive ) { 2241 | 2242 | for ( var i = 0; i < this.nodes.length; i ++ ) { 2243 | 2244 | var n = this.nodes[ i ].getChildBySid( sid, recursive ); 2245 | 2246 | if ( n ) { 2247 | 2248 | return n; 2249 | 2250 | } 2251 | 2252 | } 2253 | } 2254 | 2255 | return null; 2256 | 2257 | }; 2258 | 2259 | Node.prototype.getTransformBySid = function ( sid ) { 2260 | 2261 | for ( var i = 0; i < this.transforms.length; i ++ ) { 2262 | 2263 | if ( this.transforms[ i ].sid === sid ) return this.transforms[ i ]; 2264 | 2265 | } 2266 | 2267 | return null; 2268 | 2269 | }; 2270 | 2271 | Node.prototype.parse = function( element ) { 2272 | 2273 | var url; 2274 | 2275 | this.id = element.getAttribute('id'); 2276 | this.sid = element.getAttribute('sid'); 2277 | this.name = element.getAttribute('name'); 2278 | this.type = element.getAttribute('type'); 2279 | this.layer = element.getAttribute('layer'); 2280 | 2281 | this.type = this.type === 'JOINT' ? this.type : 'NODE'; 2282 | 2283 | this.nodes = []; 2284 | this.transforms = []; 2285 | this.geometries = []; 2286 | this.cameras = []; 2287 | this.lights = []; 2288 | this.controllers = []; 2289 | this.matrix = new THREE.Matrix4(); 2290 | 2291 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2292 | 2293 | var child = element.childNodes[ i ]; 2294 | if ( child.nodeType != 1 ) continue; 2295 | 2296 | switch ( child.nodeName ) { 2297 | 2298 | case 'node': 2299 | 2300 | this.nodes.push( ( new Node() ).parse( child ) ); 2301 | break; 2302 | 2303 | case 'instance_camera': 2304 | 2305 | this.cameras.push( ( new InstanceCamera() ).parse( child ) ); 2306 | break; 2307 | 2308 | case 'instance_controller': 2309 | 2310 | this.controllers.push( ( new InstanceController() ).parse( child ) ); 2311 | break; 2312 | 2313 | case 'instance_geometry': 2314 | 2315 | this.geometries.push( ( new InstanceGeometry() ).parse( child ) ); 2316 | break; 2317 | 2318 | case 'instance_light': 2319 | 2320 | this.lights.push( ( new InstanceLight() ).parse( child ) ); 2321 | break; 2322 | 2323 | case 'instance_node': 2324 | 2325 | url = child.getAttribute( 'url' ).replace( /^#/, '' ); 2326 | var iNode = getLibraryNode( url ); 2327 | 2328 | if ( iNode ) { 2329 | 2330 | this.nodes.push( ( new Node() ).parse( iNode )) ; 2331 | 2332 | } 2333 | 2334 | break; 2335 | 2336 | case 'rotate': 2337 | case 'translate': 2338 | case 'scale': 2339 | case 'matrix': 2340 | case 'lookat': 2341 | case 'skew': 2342 | 2343 | this.transforms.push( ( new Transform() ).parse( child ) ); 2344 | break; 2345 | 2346 | case 'extra': 2347 | break; 2348 | 2349 | default: 2350 | 2351 | console.log( child.nodeName ); 2352 | break; 2353 | 2354 | } 2355 | 2356 | } 2357 | 2358 | this.channels = getChannelsForNode( this ); 2359 | bakeAnimations( this ); 2360 | 2361 | this.updateMatrix(); 2362 | 2363 | return this; 2364 | 2365 | }; 2366 | 2367 | Node.prototype.updateMatrix = function () { 2368 | 2369 | this.matrix.identity(); 2370 | 2371 | for ( var i = 0; i < this.transforms.length; i ++ ) { 2372 | 2373 | this.transforms[ i ].apply( this.matrix ); 2374 | 2375 | } 2376 | 2377 | }; 2378 | 2379 | function Transform () { 2380 | 2381 | this.sid = ""; 2382 | this.type = ""; 2383 | this.data = []; 2384 | this.obj = null; 2385 | 2386 | }; 2387 | 2388 | Transform.prototype.parse = function ( element ) { 2389 | 2390 | this.sid = element.getAttribute( 'sid' ); 2391 | this.type = element.nodeName; 2392 | this.data = _floats( element.textContent ); 2393 | this.convert(); 2394 | 2395 | return this; 2396 | 2397 | }; 2398 | 2399 | Transform.prototype.convert = function () { 2400 | 2401 | switch ( this.type ) { 2402 | 2403 | case 'matrix': 2404 | 2405 | this.obj = getConvertedMat4( this.data ); 2406 | break; 2407 | 2408 | case 'rotate': 2409 | 2410 | this.angle = THREE.Math.degToRad( this.data[3] ); 2411 | 2412 | case 'translate': 2413 | 2414 | fixCoords( this.data, -1 ); 2415 | this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); 2416 | break; 2417 | 2418 | case 'scale': 2419 | 2420 | fixCoords( this.data, 1 ); 2421 | this.obj = new THREE.Vector3( this.data[ 0 ], this.data[ 1 ], this.data[ 2 ] ); 2422 | break; 2423 | 2424 | default: 2425 | console.log( 'Can not convert Transform of type ' + this.type ); 2426 | break; 2427 | 2428 | } 2429 | 2430 | }; 2431 | 2432 | Transform.prototype.apply = function () { 2433 | 2434 | var m1 = new THREE.Matrix4(); 2435 | 2436 | return function ( matrix ) { 2437 | 2438 | switch ( this.type ) { 2439 | 2440 | case 'matrix': 2441 | 2442 | matrix.multiply( this.obj ); 2443 | 2444 | break; 2445 | 2446 | case 'translate': 2447 | 2448 | matrix.multiply( m1.makeTranslation( this.obj.x, this.obj.y, this.obj.z ) ); 2449 | 2450 | break; 2451 | 2452 | case 'rotate': 2453 | 2454 | matrix.multiply( m1.makeRotationAxis( this.obj, this.angle ) ); 2455 | 2456 | break; 2457 | 2458 | case 'scale': 2459 | 2460 | matrix.scale( this.obj ); 2461 | 2462 | break; 2463 | 2464 | } 2465 | 2466 | }; 2467 | 2468 | }(); 2469 | 2470 | Transform.prototype.update = function ( data, member ) { 2471 | 2472 | var members = [ 'X', 'Y', 'Z', 'ANGLE' ]; 2473 | 2474 | switch ( this.type ) { 2475 | 2476 | case 'matrix': 2477 | 2478 | if ( ! member ) { 2479 | 2480 | this.obj.copy( data ); 2481 | 2482 | } else if ( member.length === 1 ) { 2483 | 2484 | switch ( member[ 0 ] ) { 2485 | 2486 | case 0: 2487 | 2488 | this.obj.n11 = data[ 0 ]; 2489 | this.obj.n21 = data[ 1 ]; 2490 | this.obj.n31 = data[ 2 ]; 2491 | this.obj.n41 = data[ 3 ]; 2492 | 2493 | break; 2494 | 2495 | case 1: 2496 | 2497 | this.obj.n12 = data[ 0 ]; 2498 | this.obj.n22 = data[ 1 ]; 2499 | this.obj.n32 = data[ 2 ]; 2500 | this.obj.n42 = data[ 3 ]; 2501 | 2502 | break; 2503 | 2504 | case 2: 2505 | 2506 | this.obj.n13 = data[ 0 ]; 2507 | this.obj.n23 = data[ 1 ]; 2508 | this.obj.n33 = data[ 2 ]; 2509 | this.obj.n43 = data[ 3 ]; 2510 | 2511 | break; 2512 | 2513 | case 3: 2514 | 2515 | this.obj.n14 = data[ 0 ]; 2516 | this.obj.n24 = data[ 1 ]; 2517 | this.obj.n34 = data[ 2 ]; 2518 | this.obj.n44 = data[ 3 ]; 2519 | 2520 | break; 2521 | 2522 | } 2523 | 2524 | } else if ( member.length === 2 ) { 2525 | 2526 | var propName = 'n' + ( member[ 0 ] + 1 ) + ( member[ 1 ] + 1 ); 2527 | this.obj[ propName ] = data; 2528 | 2529 | } else { 2530 | 2531 | console.log('Incorrect addressing of matrix in transform.'); 2532 | 2533 | } 2534 | 2535 | break; 2536 | 2537 | case 'translate': 2538 | case 'scale': 2539 | 2540 | if ( Object.prototype.toString.call( member ) === '[object Array]' ) { 2541 | 2542 | member = members[ member[ 0 ] ]; 2543 | 2544 | } 2545 | 2546 | switch ( member ) { 2547 | 2548 | case 'X': 2549 | 2550 | this.obj.x = data; 2551 | break; 2552 | 2553 | case 'Y': 2554 | 2555 | this.obj.y = data; 2556 | break; 2557 | 2558 | case 'Z': 2559 | 2560 | this.obj.z = data; 2561 | break; 2562 | 2563 | default: 2564 | 2565 | this.obj.x = data[ 0 ]; 2566 | this.obj.y = data[ 1 ]; 2567 | this.obj.z = data[ 2 ]; 2568 | break; 2569 | 2570 | } 2571 | 2572 | break; 2573 | 2574 | case 'rotate': 2575 | 2576 | if ( Object.prototype.toString.call( member ) === '[object Array]' ) { 2577 | 2578 | member = members[ member[ 0 ] ]; 2579 | 2580 | } 2581 | 2582 | switch ( member ) { 2583 | 2584 | case 'X': 2585 | 2586 | this.obj.x = data; 2587 | break; 2588 | 2589 | case 'Y': 2590 | 2591 | this.obj.y = data; 2592 | break; 2593 | 2594 | case 'Z': 2595 | 2596 | this.obj.z = data; 2597 | break; 2598 | 2599 | case 'ANGLE': 2600 | 2601 | this.angle = THREE.Math.degToRad( data ); 2602 | break; 2603 | 2604 | default: 2605 | 2606 | this.obj.x = data[ 0 ]; 2607 | this.obj.y = data[ 1 ]; 2608 | this.obj.z = data[ 2 ]; 2609 | this.angle = THREE.Math.degToRad( data[ 3 ] ); 2610 | break; 2611 | 2612 | } 2613 | break; 2614 | 2615 | } 2616 | 2617 | }; 2618 | 2619 | function InstanceController() { 2620 | 2621 | this.url = ""; 2622 | this.skeleton = []; 2623 | this.instance_material = []; 2624 | 2625 | }; 2626 | 2627 | InstanceController.prototype.parse = function ( element ) { 2628 | 2629 | this.url = element.getAttribute('url').replace(/^#/, ''); 2630 | this.skeleton = []; 2631 | this.instance_material = []; 2632 | 2633 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2634 | 2635 | var child = element.childNodes[ i ]; 2636 | if ( child.nodeType !== 1 ) continue; 2637 | 2638 | switch ( child.nodeName ) { 2639 | 2640 | case 'skeleton': 2641 | 2642 | this.skeleton.push( child.textContent.replace(/^#/, '') ); 2643 | break; 2644 | 2645 | case 'bind_material': 2646 | 2647 | var instances = child.querySelectorAll('instance_material'); 2648 | 2649 | for ( var j = 0; j < instances.length; j ++ ) { 2650 | 2651 | var instance = instances[j]; 2652 | this.instance_material.push( (new InstanceMaterial()).parse(instance) ); 2653 | 2654 | } 2655 | 2656 | 2657 | break; 2658 | 2659 | case 'extra': 2660 | break; 2661 | 2662 | default: 2663 | break; 2664 | 2665 | } 2666 | } 2667 | 2668 | return this; 2669 | 2670 | }; 2671 | 2672 | function InstanceMaterial () { 2673 | 2674 | this.symbol = ""; 2675 | this.target = ""; 2676 | 2677 | }; 2678 | 2679 | InstanceMaterial.prototype.parse = function ( element ) { 2680 | 2681 | this.symbol = element.getAttribute('symbol'); 2682 | this.target = element.getAttribute('target').replace(/^#/, ''); 2683 | return this; 2684 | 2685 | }; 2686 | 2687 | function InstanceGeometry() { 2688 | 2689 | this.url = ""; 2690 | this.instance_material = []; 2691 | 2692 | }; 2693 | 2694 | InstanceGeometry.prototype.parse = function ( element ) { 2695 | 2696 | this.url = element.getAttribute('url').replace(/^#/, ''); 2697 | this.instance_material = []; 2698 | 2699 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2700 | 2701 | var child = element.childNodes[i]; 2702 | if ( child.nodeType != 1 ) continue; 2703 | 2704 | if ( child.nodeName === 'bind_material' ) { 2705 | 2706 | var instances = child.querySelectorAll('instance_material'); 2707 | 2708 | for ( var j = 0; j < instances.length; j ++ ) { 2709 | 2710 | var instance = instances[j]; 2711 | this.instance_material.push( (new InstanceMaterial()).parse(instance) ); 2712 | 2713 | } 2714 | 2715 | break; 2716 | 2717 | } 2718 | 2719 | } 2720 | 2721 | return this; 2722 | 2723 | }; 2724 | 2725 | function Geometry() { 2726 | 2727 | this.id = ""; 2728 | this.mesh = null; 2729 | 2730 | }; 2731 | 2732 | Geometry.prototype.parse = function ( element ) { 2733 | 2734 | this.id = element.getAttribute('id'); 2735 | 2736 | extractDoubleSided( this, element ); 2737 | 2738 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2739 | 2740 | var child = element.childNodes[i]; 2741 | 2742 | switch ( child.nodeName ) { 2743 | 2744 | case 'mesh': 2745 | 2746 | this.mesh = (new Mesh(this)).parse(child); 2747 | break; 2748 | 2749 | case 'extra': 2750 | 2751 | // console.log( child ); 2752 | break; 2753 | 2754 | default: 2755 | break; 2756 | } 2757 | } 2758 | 2759 | return this; 2760 | 2761 | }; 2762 | 2763 | function Mesh( geometry ) { 2764 | 2765 | this.geometry = geometry.id; 2766 | this.primitives = []; 2767 | this.vertices = null; 2768 | this.geometry3js = null; 2769 | 2770 | }; 2771 | 2772 | Mesh.prototype.parse = function ( element ) { 2773 | 2774 | this.primitives = []; 2775 | 2776 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 2777 | 2778 | var child = element.childNodes[ i ]; 2779 | 2780 | switch ( child.nodeName ) { 2781 | 2782 | case 'source': 2783 | 2784 | _source( child ); 2785 | break; 2786 | 2787 | case 'vertices': 2788 | 2789 | this.vertices = ( new Vertices() ).parse( child ); 2790 | break; 2791 | 2792 | case 'linestrips': 2793 | 2794 | this.primitives.push( ( new LineStrips().parse( child ) ) ); 2795 | break; 2796 | 2797 | case 'triangles': 2798 | 2799 | this.primitives.push( ( new Triangles().parse( child ) ) ); 2800 | break; 2801 | 2802 | case 'polygons': 2803 | 2804 | this.primitives.push( ( new Polygons().parse( child ) ) ); 2805 | break; 2806 | 2807 | case 'polylist': 2808 | 2809 | this.primitives.push( ( new Polylist().parse( child ) ) ); 2810 | break; 2811 | 2812 | default: 2813 | break; 2814 | 2815 | } 2816 | 2817 | } 2818 | 2819 | this.geometry3js = new THREE.Geometry(); 2820 | 2821 | if ( this.vertices === null ) { 2822 | 2823 | // TODO (mrdoob): Study case when this is null (carrier.dae) 2824 | 2825 | return this; 2826 | 2827 | } 2828 | 2829 | var vertexData = sources[ this.vertices.input['POSITION'].source ].data; 2830 | 2831 | for ( var i = 0; i < vertexData.length; i += 3 ) { 2832 | 2833 | this.geometry3js.vertices.push( getConvertedVec3( vertexData, i ).clone() ); 2834 | 2835 | } 2836 | 2837 | for ( var i = 0; i < this.primitives.length; i ++ ) { 2838 | 2839 | var primitive = this.primitives[ i ]; 2840 | primitive.setVertices( this.vertices ); 2841 | this.handlePrimitive( primitive, this.geometry3js ); 2842 | 2843 | } 2844 | 2845 | if ( this.geometry3js.calcNormals ) { 2846 | 2847 | this.geometry3js.computeVertexNormals(); 2848 | delete this.geometry3js.calcNormals; 2849 | 2850 | } 2851 | 2852 | return this; 2853 | 2854 | }; 2855 | 2856 | Mesh.prototype.handlePrimitive = function ( primitive, geom ) { 2857 | 2858 | if ( primitive instanceof LineStrips ) { 2859 | 2860 | // TODO: Handle indices. Maybe easier with BufferGeometry? 2861 | 2862 | geom.isLineStrip = true; 2863 | return; 2864 | 2865 | } 2866 | 2867 | var j, k, pList = primitive.p, inputs = primitive.inputs; 2868 | var input, index, idx32; 2869 | var source, numParams; 2870 | var vcIndex = 0, vcount = 3, maxOffset = 0; 2871 | var texture_sets = []; 2872 | 2873 | for ( j = 0; j < inputs.length; j ++ ) { 2874 | 2875 | input = inputs[ j ]; 2876 | 2877 | var offset = input.offset + 1; 2878 | maxOffset = (maxOffset < offset) ? offset : maxOffset; 2879 | 2880 | switch ( input.semantic ) { 2881 | 2882 | case 'TEXCOORD': 2883 | texture_sets.push( input.set ); 2884 | break; 2885 | 2886 | } 2887 | 2888 | } 2889 | 2890 | for ( var pCount = 0; pCount < pList.length; ++ pCount ) { 2891 | 2892 | var p = pList[ pCount ], i = 0; 2893 | 2894 | while ( i < p.length ) { 2895 | 2896 | var vs = []; 2897 | var ns = []; 2898 | var ts = null; 2899 | var cs = []; 2900 | 2901 | if ( primitive.vcount ) { 2902 | 2903 | vcount = primitive.vcount.length ? primitive.vcount[ vcIndex ++ ] : primitive.vcount; 2904 | 2905 | } else { 2906 | 2907 | vcount = p.length / maxOffset; 2908 | 2909 | } 2910 | 2911 | 2912 | for ( j = 0; j < vcount; j ++ ) { 2913 | 2914 | for ( k = 0; k < inputs.length; k ++ ) { 2915 | 2916 | input = inputs[ k ]; 2917 | source = sources[ input.source ]; 2918 | 2919 | index = p[ i + ( j * maxOffset ) + input.offset ]; 2920 | numParams = source.accessor.params.length; 2921 | idx32 = index * numParams; 2922 | 2923 | switch ( input.semantic ) { 2924 | 2925 | case 'VERTEX': 2926 | 2927 | vs.push( index ); 2928 | 2929 | break; 2930 | 2931 | case 'NORMAL': 2932 | 2933 | ns.push( getConvertedVec3( source.data, idx32 ) ); 2934 | 2935 | break; 2936 | 2937 | case 'TEXCOORD': 2938 | 2939 | ts = ts || { }; 2940 | if ( ts[ input.set ] === undefined ) ts[ input.set ] = []; 2941 | // invert the V 2942 | ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], source.data[ idx32 + 1 ] ) ); 2943 | 2944 | break; 2945 | 2946 | case 'COLOR': 2947 | 2948 | cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); 2949 | 2950 | break; 2951 | 2952 | default: 2953 | 2954 | break; 2955 | 2956 | } 2957 | 2958 | } 2959 | 2960 | } 2961 | 2962 | if ( ns.length === 0 ) { 2963 | 2964 | // check the vertices inputs 2965 | input = this.vertices.input.NORMAL; 2966 | 2967 | if ( input ) { 2968 | 2969 | source = sources[ input.source ]; 2970 | numParams = source.accessor.params.length; 2971 | 2972 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 2973 | 2974 | ns.push( getConvertedVec3( source.data, vs[ ndx ] * numParams ) ); 2975 | 2976 | } 2977 | 2978 | } else { 2979 | 2980 | geom.calcNormals = true; 2981 | 2982 | } 2983 | 2984 | } 2985 | 2986 | if ( !ts ) { 2987 | 2988 | ts = { }; 2989 | // check the vertices inputs 2990 | input = this.vertices.input.TEXCOORD; 2991 | 2992 | if ( input ) { 2993 | 2994 | texture_sets.push( input.set ); 2995 | source = sources[ input.source ]; 2996 | numParams = source.accessor.params.length; 2997 | 2998 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 2999 | 3000 | idx32 = vs[ ndx ] * numParams; 3001 | if ( ts[ input.set ] === undefined ) ts[ input.set ] = [ ]; 3002 | // invert the V 3003 | ts[ input.set ].push( new THREE.Vector2( source.data[ idx32 ], 1.0 - source.data[ idx32 + 1 ] ) ); 3004 | 3005 | } 3006 | 3007 | } 3008 | 3009 | } 3010 | 3011 | if ( cs.length === 0 ) { 3012 | 3013 | // check the vertices inputs 3014 | input = this.vertices.input.COLOR; 3015 | 3016 | if ( input ) { 3017 | 3018 | source = sources[ input.source ]; 3019 | numParams = source.accessor.params.length; 3020 | 3021 | for ( var ndx = 0, len = vs.length; ndx < len; ndx ++ ) { 3022 | 3023 | idx32 = vs[ ndx ] * numParams; 3024 | cs.push( new THREE.Color().setRGB( source.data[ idx32 ], source.data[ idx32 + 1 ], source.data[ idx32 + 2 ] ) ); 3025 | 3026 | } 3027 | 3028 | } 3029 | 3030 | } 3031 | 3032 | var face = null, faces = [], uv, uvArr; 3033 | 3034 | if ( vcount === 3 ) { 3035 | 3036 | faces.push( new THREE.Face3( vs[0], vs[1], vs[2], ns, cs.length ? cs : new THREE.Color() ) ); 3037 | 3038 | } else if ( vcount === 4 ) { 3039 | 3040 | faces.push( new THREE.Face3( vs[0], vs[1], vs[3], [ ns[0].clone(), ns[1].clone(), ns[3].clone() ], cs.length ? [ cs[0], cs[1], cs[3] ] : new THREE.Color() ) ); 3041 | 3042 | faces.push( new THREE.Face3( vs[1], vs[2], vs[3], [ ns[1].clone(), ns[2].clone(), ns[3].clone() ], cs.length ? [ cs[1], cs[2], cs[3] ] : new THREE.Color() ) ); 3043 | 3044 | } else if ( vcount > 4 && options.subdivideFaces ) { 3045 | 3046 | var clr = cs.length ? cs : new THREE.Color(), 3047 | vec1, vec2, vec3, v1, v2, norm; 3048 | 3049 | // subdivide into multiple Face3s 3050 | 3051 | for ( k = 1; k < vcount - 1; ) { 3052 | 3053 | faces.push( new THREE.Face3( vs[0], vs[k], vs[k + 1], [ ns[0].clone(), ns[k ++].clone(), ns[k].clone() ], clr ) ); 3054 | 3055 | } 3056 | 3057 | } 3058 | 3059 | if ( faces.length ) { 3060 | 3061 | for ( var ndx = 0, len = faces.length; ndx < len; ndx ++ ) { 3062 | 3063 | face = faces[ndx]; 3064 | face.daeMaterial = primitive.material; 3065 | geom.faces.push( face ); 3066 | 3067 | for ( k = 0; k < texture_sets.length; k ++ ) { 3068 | 3069 | uv = ts[ texture_sets[k] ]; 3070 | 3071 | if ( vcount > 4 ) { 3072 | 3073 | // Grab the right UVs for the vertices in this face 3074 | uvArr = [ uv[0], uv[ndx + 1], uv[ndx + 2] ]; 3075 | 3076 | } else if ( vcount === 4 ) { 3077 | 3078 | if ( ndx === 0 ) { 3079 | 3080 | uvArr = [ uv[0], uv[1], uv[3] ]; 3081 | 3082 | } else { 3083 | 3084 | uvArr = [ uv[1].clone(), uv[2], uv[3].clone() ]; 3085 | 3086 | } 3087 | 3088 | } else { 3089 | 3090 | uvArr = [ uv[0], uv[1], uv[2] ]; 3091 | 3092 | } 3093 | 3094 | if ( geom.faceVertexUvs[k] === undefined ) { 3095 | 3096 | geom.faceVertexUvs[k] = []; 3097 | 3098 | } 3099 | 3100 | geom.faceVertexUvs[k].push( uvArr ); 3101 | 3102 | } 3103 | 3104 | } 3105 | 3106 | } else { 3107 | 3108 | console.log( 'dropped face with vcount ' + vcount + ' for geometry with id: ' + geom.id ); 3109 | 3110 | } 3111 | 3112 | i += maxOffset * vcount; 3113 | 3114 | } 3115 | 3116 | } 3117 | 3118 | }; 3119 | 3120 | function Polygons () { 3121 | 3122 | this.material = ""; 3123 | this.count = 0; 3124 | this.inputs = []; 3125 | this.vcount = null; 3126 | this.p = []; 3127 | this.geometry = new THREE.Geometry(); 3128 | 3129 | }; 3130 | 3131 | Polygons.prototype.setVertices = function ( vertices ) { 3132 | 3133 | for ( var i = 0; i < this.inputs.length; i ++ ) { 3134 | 3135 | if ( this.inputs[ i ].source === vertices.id ) { 3136 | 3137 | this.inputs[ i ].source = vertices.input[ 'POSITION' ].source; 3138 | 3139 | } 3140 | 3141 | } 3142 | 3143 | }; 3144 | 3145 | Polygons.prototype.parse = function ( element ) { 3146 | 3147 | this.material = element.getAttribute( 'material' ); 3148 | this.count = _attr_as_int( element, 'count', 0 ); 3149 | 3150 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3151 | 3152 | var child = element.childNodes[ i ]; 3153 | 3154 | switch ( child.nodeName ) { 3155 | 3156 | case 'input': 3157 | 3158 | this.inputs.push( ( new Input() ).parse( element.childNodes[ i ] ) ); 3159 | break; 3160 | 3161 | case 'vcount': 3162 | 3163 | this.vcount = _ints( child.textContent ); 3164 | break; 3165 | 3166 | case 'p': 3167 | 3168 | this.p.push( _ints( child.textContent ) ); 3169 | break; 3170 | 3171 | case 'ph': 3172 | 3173 | console.warn( 'polygon holes not yet supported!' ); 3174 | break; 3175 | 3176 | default: 3177 | break; 3178 | 3179 | } 3180 | 3181 | } 3182 | 3183 | return this; 3184 | 3185 | }; 3186 | 3187 | function Polylist () { 3188 | 3189 | Polygons.call( this ); 3190 | 3191 | this.vcount = []; 3192 | 3193 | }; 3194 | 3195 | Polylist.prototype = Object.create( Polygons.prototype ); 3196 | Polylist.prototype.constructor = Polylist; 3197 | 3198 | function LineStrips() { 3199 | 3200 | Polygons.call( this ); 3201 | 3202 | this.vcount = 1; 3203 | 3204 | }; 3205 | 3206 | LineStrips.prototype = Object.create( Polygons.prototype ); 3207 | LineStrips.prototype.constructor = LineStrips; 3208 | 3209 | function Triangles () { 3210 | 3211 | Polygons.call( this ); 3212 | 3213 | this.vcount = 3; 3214 | 3215 | }; 3216 | 3217 | Triangles.prototype = Object.create( Polygons.prototype ); 3218 | Triangles.prototype.constructor = Triangles; 3219 | 3220 | function Accessor() { 3221 | 3222 | this.source = ""; 3223 | this.count = 0; 3224 | this.stride = 0; 3225 | this.params = []; 3226 | 3227 | }; 3228 | 3229 | Accessor.prototype.parse = function ( element ) { 3230 | 3231 | this.params = []; 3232 | this.source = element.getAttribute( 'source' ); 3233 | this.count = _attr_as_int( element, 'count', 0 ); 3234 | this.stride = _attr_as_int( element, 'stride', 0 ); 3235 | 3236 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3237 | 3238 | var child = element.childNodes[ i ]; 3239 | 3240 | if ( child.nodeName === 'param' ) { 3241 | 3242 | var param = {}; 3243 | param[ 'name' ] = child.getAttribute( 'name' ); 3244 | param[ 'type' ] = child.getAttribute( 'type' ); 3245 | this.params.push( param ); 3246 | 3247 | } 3248 | 3249 | } 3250 | 3251 | return this; 3252 | 3253 | }; 3254 | 3255 | function Vertices() { 3256 | 3257 | this.input = {}; 3258 | 3259 | }; 3260 | 3261 | Vertices.prototype.parse = function ( element ) { 3262 | 3263 | this.id = element.getAttribute('id'); 3264 | 3265 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3266 | 3267 | if ( element.childNodes[i].nodeName === 'input' ) { 3268 | 3269 | var input = ( new Input() ).parse( element.childNodes[ i ] ); 3270 | this.input[ input.semantic ] = input; 3271 | 3272 | } 3273 | 3274 | } 3275 | 3276 | return this; 3277 | 3278 | }; 3279 | 3280 | function Input () { 3281 | 3282 | this.semantic = ""; 3283 | this.offset = 0; 3284 | this.source = ""; 3285 | this.set = 0; 3286 | 3287 | }; 3288 | 3289 | Input.prototype.parse = function ( element ) { 3290 | 3291 | this.semantic = element.getAttribute('semantic'); 3292 | this.source = element.getAttribute('source').replace(/^#/, ''); 3293 | this.set = _attr_as_int(element, 'set', -1); 3294 | this.offset = _attr_as_int(element, 'offset', 0); 3295 | 3296 | if ( this.semantic === 'TEXCOORD' && this.set < 0 ) { 3297 | 3298 | this.set = 0; 3299 | 3300 | } 3301 | 3302 | return this; 3303 | 3304 | }; 3305 | 3306 | function Source ( id ) { 3307 | 3308 | this.id = id; 3309 | this.type = null; 3310 | 3311 | }; 3312 | 3313 | Source.prototype.parse = function ( element ) { 3314 | 3315 | this.id = element.getAttribute( 'id' ); 3316 | 3317 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3318 | 3319 | var child = element.childNodes[i]; 3320 | 3321 | switch ( child.nodeName ) { 3322 | 3323 | case 'bool_array': 3324 | 3325 | this.data = _bools( child.textContent ); 3326 | this.type = child.nodeName; 3327 | break; 3328 | 3329 | case 'float_array': 3330 | 3331 | this.data = _floats( child.textContent ); 3332 | this.type = child.nodeName; 3333 | break; 3334 | 3335 | case 'int_array': 3336 | 3337 | this.data = _ints( child.textContent ); 3338 | this.type = child.nodeName; 3339 | break; 3340 | 3341 | case 'IDREF_array': 3342 | case 'Name_array': 3343 | 3344 | this.data = _strings( child.textContent ); 3345 | this.type = child.nodeName; 3346 | break; 3347 | 3348 | case 'technique_common': 3349 | 3350 | for ( var j = 0; j < child.childNodes.length; j ++ ) { 3351 | 3352 | if ( child.childNodes[ j ].nodeName === 'accessor' ) { 3353 | 3354 | this.accessor = ( new Accessor() ).parse( child.childNodes[ j ] ); 3355 | break; 3356 | 3357 | } 3358 | } 3359 | break; 3360 | 3361 | default: 3362 | // console.log(child.nodeName); 3363 | break; 3364 | 3365 | } 3366 | 3367 | } 3368 | 3369 | return this; 3370 | 3371 | }; 3372 | 3373 | Source.prototype.read = function () { 3374 | 3375 | var result = []; 3376 | 3377 | //for (var i = 0; i < this.accessor.params.length; i++) { 3378 | 3379 | var param = this.accessor.params[ 0 ]; 3380 | 3381 | //console.log(param.name + " " + param.type); 3382 | 3383 | switch ( param.type ) { 3384 | 3385 | case 'IDREF': 3386 | case 'Name': case 'name': 3387 | case 'float': 3388 | 3389 | return this.data; 3390 | 3391 | case 'float4x4': 3392 | 3393 | for ( var j = 0; j < this.data.length; j += 16 ) { 3394 | 3395 | var s = this.data.slice( j, j + 16 ); 3396 | var m = getConvertedMat4( s ); 3397 | result.push( m ); 3398 | } 3399 | 3400 | break; 3401 | 3402 | default: 3403 | 3404 | console.log( 'ColladaLoader: Source: Read dont know how to read ' + param.type + '.' ); 3405 | break; 3406 | 3407 | } 3408 | 3409 | //} 3410 | 3411 | return result; 3412 | 3413 | }; 3414 | 3415 | function Material () { 3416 | 3417 | this.id = ""; 3418 | this.name = ""; 3419 | this.instance_effect = null; 3420 | 3421 | }; 3422 | 3423 | Material.prototype.parse = function ( element ) { 3424 | 3425 | this.id = element.getAttribute( 'id' ); 3426 | this.name = element.getAttribute( 'name' ); 3427 | 3428 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3429 | 3430 | if ( element.childNodes[ i ].nodeName === 'instance_effect' ) { 3431 | 3432 | this.instance_effect = ( new InstanceEffect() ).parse( element.childNodes[ i ] ); 3433 | break; 3434 | 3435 | } 3436 | 3437 | } 3438 | 3439 | return this; 3440 | 3441 | }; 3442 | 3443 | function ColorOrTexture () { 3444 | 3445 | this.color = new THREE.Color(); 3446 | this.color.setRGB( Math.random(), Math.random(), Math.random() ); 3447 | this.color.a = 1.0; 3448 | 3449 | this.texture = null; 3450 | this.texcoord = null; 3451 | this.texOpts = null; 3452 | 3453 | }; 3454 | 3455 | ColorOrTexture.prototype.isColor = function () { 3456 | 3457 | return ( this.texture === null ); 3458 | 3459 | }; 3460 | 3461 | ColorOrTexture.prototype.isTexture = function () { 3462 | 3463 | return ( this.texture != null ); 3464 | 3465 | }; 3466 | 3467 | ColorOrTexture.prototype.parse = function ( element ) { 3468 | 3469 | if (element.nodeName === 'transparent') { 3470 | 3471 | this.opaque = element.getAttribute('opaque'); 3472 | 3473 | } 3474 | 3475 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3476 | 3477 | var child = element.childNodes[ i ]; 3478 | if ( child.nodeType != 1 ) continue; 3479 | 3480 | switch ( child.nodeName ) { 3481 | 3482 | case 'color': 3483 | 3484 | var rgba = _floats( child.textContent ); 3485 | this.color = new THREE.Color(); 3486 | this.color.setRGB( rgba[0], rgba[1], rgba[2] ); 3487 | this.color.a = rgba[3]; 3488 | break; 3489 | 3490 | case 'texture': 3491 | 3492 | this.texture = child.getAttribute('texture'); 3493 | this.texcoord = child.getAttribute('texcoord'); 3494 | // Defaults from: 3495 | // https://collada.org/mediawiki/index.php/Maya_texture_placement_MAYA_extension 3496 | this.texOpts = { 3497 | offsetU: 0, 3498 | offsetV: 0, 3499 | repeatU: 1, 3500 | repeatV: 1, 3501 | wrapU: 1, 3502 | wrapV: 1 3503 | }; 3504 | this.parseTexture( child ); 3505 | break; 3506 | 3507 | default: 3508 | break; 3509 | 3510 | } 3511 | 3512 | } 3513 | 3514 | return this; 3515 | 3516 | }; 3517 | 3518 | ColorOrTexture.prototype.parseTexture = function ( element ) { 3519 | 3520 | if ( ! element.childNodes ) return this; 3521 | 3522 | // This should be supported by Maya, 3dsMax, and MotionBuilder 3523 | 3524 | if ( element.childNodes[1] && element.childNodes[1].nodeName === 'extra' ) { 3525 | 3526 | element = element.childNodes[1]; 3527 | 3528 | if ( element.childNodes[1] && element.childNodes[1].nodeName === 'technique' ) { 3529 | 3530 | element = element.childNodes[1]; 3531 | 3532 | } 3533 | 3534 | } 3535 | 3536 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3537 | 3538 | var child = element.childNodes[ i ]; 3539 | 3540 | switch ( child.nodeName ) { 3541 | 3542 | case 'offsetU': 3543 | case 'offsetV': 3544 | case 'repeatU': 3545 | case 'repeatV': 3546 | 3547 | this.texOpts[ child.nodeName ] = parseFloat( child.textContent ); 3548 | 3549 | break; 3550 | 3551 | case 'wrapU': 3552 | case 'wrapV': 3553 | 3554 | // some dae have a value of true which becomes NaN via parseInt 3555 | 3556 | if ( child.textContent.toUpperCase() === 'TRUE' ) { 3557 | 3558 | this.texOpts[ child.nodeName ] = 1; 3559 | 3560 | } else { 3561 | 3562 | this.texOpts[ child.nodeName ] = parseInt( child.textContent ); 3563 | 3564 | } 3565 | break; 3566 | 3567 | default: 3568 | 3569 | this.texOpts[ child.nodeName ] = child.textContent; 3570 | 3571 | break; 3572 | 3573 | } 3574 | 3575 | } 3576 | 3577 | return this; 3578 | 3579 | }; 3580 | 3581 | function Shader ( type, effect ) { 3582 | 3583 | this.type = type; 3584 | this.effect = effect; 3585 | this.material = null; 3586 | 3587 | }; 3588 | 3589 | Shader.prototype.parse = function ( element ) { 3590 | 3591 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3592 | 3593 | var child = element.childNodes[ i ]; 3594 | if ( child.nodeType != 1 ) continue; 3595 | 3596 | switch ( child.nodeName ) { 3597 | 3598 | case 'emission': 3599 | case 'diffuse': 3600 | case 'specular': 3601 | case 'transparent': 3602 | 3603 | this[ child.nodeName ] = ( new ColorOrTexture() ).parse( child ); 3604 | break; 3605 | 3606 | case 'bump': 3607 | 3608 | // If 'bumptype' is 'heightfield', create a 'bump' property 3609 | // Else if 'bumptype' is 'normalmap', create a 'normal' property 3610 | // (Default to 'bump') 3611 | var bumpType = child.getAttribute( 'bumptype' ); 3612 | if ( bumpType ) { 3613 | if ( bumpType.toLowerCase() === "heightfield" ) { 3614 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3615 | } else if ( bumpType.toLowerCase() === "normalmap" ) { 3616 | this[ 'normal' ] = ( new ColorOrTexture() ).parse( child ); 3617 | } else { 3618 | console.error( "Shader.prototype.parse: Invalid value for attribute 'bumptype' (" + bumpType + ") - valid bumptypes are 'HEIGHTFIELD' and 'NORMALMAP' - defaulting to 'HEIGHTFIELD'" ); 3619 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3620 | } 3621 | } else { 3622 | console.warn( "Shader.prototype.parse: Attribute 'bumptype' missing from bump node - defaulting to 'HEIGHTFIELD'" ); 3623 | this[ 'bump' ] = ( new ColorOrTexture() ).parse( child ); 3624 | } 3625 | 3626 | break; 3627 | 3628 | case 'shininess': 3629 | case 'reflectivity': 3630 | case 'index_of_refraction': 3631 | case 'transparency': 3632 | 3633 | var f = child.querySelectorAll('float'); 3634 | 3635 | if ( f.length > 0 ) 3636 | this[ child.nodeName ] = parseFloat( f[ 0 ].textContent ); 3637 | 3638 | break; 3639 | 3640 | default: 3641 | break; 3642 | 3643 | } 3644 | 3645 | } 3646 | 3647 | this.create(); 3648 | return this; 3649 | 3650 | }; 3651 | 3652 | Shader.prototype.create = function() { 3653 | 3654 | var props = {}; 3655 | 3656 | var transparent = false; 3657 | 3658 | if (this['transparency'] !== undefined && this['transparent'] !== undefined) { 3659 | // convert transparent color RBG to average value 3660 | var transparentColor = this['transparent']; 3661 | var transparencyLevel = (this.transparent.color.r + this.transparent.color.g + this.transparent.color.b) / 3 * this.transparency; 3662 | 3663 | if (transparencyLevel > 0) { 3664 | transparent = true; 3665 | props[ 'transparent' ] = true; 3666 | props[ 'opacity' ] = 1 - transparencyLevel; 3667 | 3668 | } 3669 | 3670 | } 3671 | 3672 | var keys = { 3673 | 'diffuse':'map', 3674 | 'ambient':'lightMap', 3675 | 'specular':'specularMap', 3676 | 'emission':'emissionMap', 3677 | 'bump':'bumpMap', 3678 | 'normal':'normalMap' 3679 | }; 3680 | 3681 | for ( var prop in this ) { 3682 | 3683 | switch ( prop ) { 3684 | 3685 | case 'ambient': 3686 | case 'emission': 3687 | case 'diffuse': 3688 | case 'specular': 3689 | case 'bump': 3690 | case 'normal': 3691 | 3692 | var cot = this[ prop ]; 3693 | 3694 | if ( cot instanceof ColorOrTexture ) { 3695 | 3696 | if ( cot.isTexture() ) { 3697 | 3698 | var samplerId = cot.texture; 3699 | var surfaceId = this.effect.sampler[samplerId]; 3700 | 3701 | if ( surfaceId !== undefined && surfaceId.source !== undefined ) { 3702 | 3703 | var surface = this.effect.surface[surfaceId.source]; 3704 | 3705 | if ( surface !== undefined ) { 3706 | 3707 | var image = images[ surface.init_from ]; 3708 | 3709 | if ( image ) { 3710 | 3711 | var url = baseUrl + image.init_from; 3712 | 3713 | var texture; 3714 | var loader = THREE.Loader.Handlers.get( url ); 3715 | 3716 | if ( loader !== null ) { 3717 | 3718 | texture = loader.load( url ); 3719 | 3720 | } else { 3721 | 3722 | texture = new THREE.Texture(); 3723 | 3724 | loadTextureImage( texture, url ); 3725 | 3726 | } 3727 | 3728 | texture.wrapS = cot.texOpts.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 3729 | texture.wrapT = cot.texOpts.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping; 3730 | texture.offset.x = cot.texOpts.offsetU; 3731 | texture.offset.y = cot.texOpts.offsetV; 3732 | texture.repeat.x = cot.texOpts.repeatU; 3733 | texture.repeat.y = cot.texOpts.repeatV; 3734 | props[keys[prop]] = texture; 3735 | 3736 | // Texture with baked lighting? 3737 | if (prop === 'emission') props['emissive'] = 0xffffff; 3738 | 3739 | } 3740 | 3741 | } 3742 | 3743 | } 3744 | 3745 | } else if ( prop === 'diffuse' || !transparent ) { 3746 | 3747 | if ( prop === 'emission' ) { 3748 | 3749 | props[ 'emissive' ] = cot.color.getHex(); 3750 | 3751 | } else { 3752 | 3753 | props[ prop ] = cot.color.getHex(); 3754 | 3755 | } 3756 | 3757 | } 3758 | 3759 | } 3760 | 3761 | break; 3762 | 3763 | case 'shininess': 3764 | 3765 | props[ prop ] = this[ prop ]; 3766 | break; 3767 | 3768 | case 'reflectivity': 3769 | 3770 | props[ prop ] = this[ prop ]; 3771 | if ( props[ prop ] > 0.0 ) props['envMap'] = options.defaultEnvMap; 3772 | props['combine'] = THREE.MixOperation; //mix regular shading with reflective component 3773 | break; 3774 | 3775 | case 'index_of_refraction': 3776 | 3777 | props[ 'refractionRatio' ] = this[ prop ]; //TODO: "index_of_refraction" becomes "refractionRatio" in shader, but I'm not sure if the two are actually comparable 3778 | if ( this[ prop ] !== 1.0 ) props['envMap'] = options.defaultEnvMap; 3779 | break; 3780 | 3781 | case 'transparency': 3782 | // gets figured out up top 3783 | break; 3784 | 3785 | default: 3786 | break; 3787 | 3788 | } 3789 | 3790 | } 3791 | 3792 | props[ 'shading' ] = preferredShading; 3793 | props[ 'side' ] = this.effect.doubleSided ? THREE.DoubleSide : THREE.FrontSide; 3794 | 3795 | switch ( this.type ) { 3796 | 3797 | case 'constant': 3798 | 3799 | if (props.emissive != undefined) props.color = props.emissive; 3800 | this.material = new THREE.MeshBasicMaterial( props ); 3801 | break; 3802 | 3803 | case 'phong': 3804 | case 'blinn': 3805 | 3806 | if (props.diffuse != undefined) props.color = props.diffuse; 3807 | this.material = new THREE.MeshPhongMaterial( props ); 3808 | break; 3809 | 3810 | case 'lambert': 3811 | default: 3812 | 3813 | if (props.diffuse != undefined) props.color = props.diffuse; 3814 | this.material = new THREE.MeshLambertMaterial( props ); 3815 | break; 3816 | 3817 | } 3818 | 3819 | return this.material; 3820 | 3821 | }; 3822 | 3823 | function Surface ( effect ) { 3824 | 3825 | this.effect = effect; 3826 | this.init_from = null; 3827 | this.format = null; 3828 | 3829 | }; 3830 | 3831 | Surface.prototype.parse = function ( element ) { 3832 | 3833 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3834 | 3835 | var child = element.childNodes[ i ]; 3836 | if ( child.nodeType != 1 ) continue; 3837 | 3838 | switch ( child.nodeName ) { 3839 | 3840 | case 'init_from': 3841 | 3842 | this.init_from = child.textContent; 3843 | break; 3844 | 3845 | case 'format': 3846 | 3847 | this.format = child.textContent; 3848 | break; 3849 | 3850 | default: 3851 | 3852 | console.log( "unhandled Surface prop: " + child.nodeName ); 3853 | break; 3854 | 3855 | } 3856 | 3857 | } 3858 | 3859 | return this; 3860 | 3861 | }; 3862 | 3863 | function Sampler2D ( effect ) { 3864 | 3865 | this.effect = effect; 3866 | this.source = null; 3867 | this.wrap_s = null; 3868 | this.wrap_t = null; 3869 | this.minfilter = null; 3870 | this.magfilter = null; 3871 | this.mipfilter = null; 3872 | 3873 | }; 3874 | 3875 | Sampler2D.prototype.parse = function ( element ) { 3876 | 3877 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3878 | 3879 | var child = element.childNodes[ i ]; 3880 | if ( child.nodeType != 1 ) continue; 3881 | 3882 | switch ( child.nodeName ) { 3883 | 3884 | case 'source': 3885 | 3886 | this.source = child.textContent; 3887 | break; 3888 | 3889 | case 'minfilter': 3890 | 3891 | this.minfilter = child.textContent; 3892 | break; 3893 | 3894 | case 'magfilter': 3895 | 3896 | this.magfilter = child.textContent; 3897 | break; 3898 | 3899 | case 'mipfilter': 3900 | 3901 | this.mipfilter = child.textContent; 3902 | break; 3903 | 3904 | case 'wrap_s': 3905 | 3906 | this.wrap_s = child.textContent; 3907 | break; 3908 | 3909 | case 'wrap_t': 3910 | 3911 | this.wrap_t = child.textContent; 3912 | break; 3913 | 3914 | default: 3915 | 3916 | console.log( "unhandled Sampler2D prop: " + child.nodeName ); 3917 | break; 3918 | 3919 | } 3920 | 3921 | } 3922 | 3923 | return this; 3924 | 3925 | }; 3926 | 3927 | function Effect () { 3928 | 3929 | this.id = ""; 3930 | this.name = ""; 3931 | this.shader = null; 3932 | this.surface = {}; 3933 | this.sampler = {}; 3934 | 3935 | }; 3936 | 3937 | Effect.prototype.create = function () { 3938 | 3939 | if ( this.shader === null ) { 3940 | 3941 | return null; 3942 | 3943 | } 3944 | 3945 | }; 3946 | 3947 | Effect.prototype.parse = function ( element ) { 3948 | 3949 | this.id = element.getAttribute( 'id' ); 3950 | this.name = element.getAttribute( 'name' ); 3951 | 3952 | extractDoubleSided( this, element ); 3953 | 3954 | this.shader = null; 3955 | 3956 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3957 | 3958 | var child = element.childNodes[ i ]; 3959 | if ( child.nodeType != 1 ) continue; 3960 | 3961 | switch ( child.nodeName ) { 3962 | 3963 | case 'profile_COMMON': 3964 | 3965 | this.parseTechnique( this.parseProfileCOMMON( child ) ); 3966 | break; 3967 | 3968 | default: 3969 | break; 3970 | 3971 | } 3972 | 3973 | } 3974 | 3975 | return this; 3976 | 3977 | }; 3978 | 3979 | Effect.prototype.parseNewparam = function ( element ) { 3980 | 3981 | var sid = element.getAttribute( 'sid' ); 3982 | 3983 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 3984 | 3985 | var child = element.childNodes[ i ]; 3986 | if ( child.nodeType != 1 ) continue; 3987 | 3988 | switch ( child.nodeName ) { 3989 | 3990 | case 'surface': 3991 | 3992 | this.surface[sid] = ( new Surface( this ) ).parse( child ); 3993 | break; 3994 | 3995 | case 'sampler2D': 3996 | 3997 | this.sampler[sid] = ( new Sampler2D( this ) ).parse( child ); 3998 | break; 3999 | 4000 | case 'extra': 4001 | 4002 | break; 4003 | 4004 | default: 4005 | 4006 | console.log( child.nodeName ); 4007 | break; 4008 | 4009 | } 4010 | 4011 | } 4012 | 4013 | }; 4014 | 4015 | Effect.prototype.parseProfileCOMMON = function ( element ) { 4016 | 4017 | var technique; 4018 | 4019 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4020 | 4021 | var child = element.childNodes[ i ]; 4022 | 4023 | if ( child.nodeType != 1 ) continue; 4024 | 4025 | switch ( child.nodeName ) { 4026 | 4027 | case 'profile_COMMON': 4028 | 4029 | this.parseProfileCOMMON( child ); 4030 | break; 4031 | 4032 | case 'technique': 4033 | 4034 | technique = child; 4035 | break; 4036 | 4037 | case 'newparam': 4038 | 4039 | this.parseNewparam( child ); 4040 | break; 4041 | 4042 | case 'image': 4043 | 4044 | var _image = ( new _Image() ).parse( child ); 4045 | images[ _image.id ] = _image; 4046 | break; 4047 | 4048 | case 'extra': 4049 | break; 4050 | 4051 | default: 4052 | 4053 | console.log( child.nodeName ); 4054 | break; 4055 | 4056 | } 4057 | 4058 | } 4059 | 4060 | return technique; 4061 | 4062 | }; 4063 | 4064 | Effect.prototype.parseTechnique = function ( element ) { 4065 | 4066 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4067 | 4068 | var child = element.childNodes[i]; 4069 | if ( child.nodeType != 1 ) continue; 4070 | 4071 | switch ( child.nodeName ) { 4072 | 4073 | case 'constant': 4074 | case 'lambert': 4075 | case 'blinn': 4076 | case 'phong': 4077 | 4078 | this.shader = ( new Shader( child.nodeName, this ) ).parse( child ); 4079 | break; 4080 | case 'extra': 4081 | this.parseExtra(child); 4082 | break; 4083 | default: 4084 | break; 4085 | 4086 | } 4087 | 4088 | } 4089 | 4090 | }; 4091 | 4092 | Effect.prototype.parseExtra = function ( element ) { 4093 | 4094 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4095 | 4096 | var child = element.childNodes[i]; 4097 | if ( child.nodeType != 1 ) continue; 4098 | 4099 | switch ( child.nodeName ) { 4100 | 4101 | case 'technique': 4102 | this.parseExtraTechnique( child ); 4103 | break; 4104 | default: 4105 | break; 4106 | 4107 | } 4108 | 4109 | } 4110 | 4111 | }; 4112 | 4113 | Effect.prototype.parseExtraTechnique = function ( element ) { 4114 | 4115 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4116 | 4117 | var child = element.childNodes[i]; 4118 | if ( child.nodeType != 1 ) continue; 4119 | 4120 | switch ( child.nodeName ) { 4121 | 4122 | case 'bump': 4123 | this.shader.parse( element ); 4124 | break; 4125 | default: 4126 | break; 4127 | 4128 | } 4129 | 4130 | } 4131 | 4132 | }; 4133 | 4134 | function InstanceEffect () { 4135 | 4136 | this.url = ""; 4137 | 4138 | }; 4139 | 4140 | InstanceEffect.prototype.parse = function ( element ) { 4141 | 4142 | this.url = element.getAttribute( 'url' ).replace( /^#/, '' ); 4143 | return this; 4144 | 4145 | }; 4146 | 4147 | function Animation() { 4148 | 4149 | this.id = ""; 4150 | this.name = ""; 4151 | this.source = {}; 4152 | this.sampler = []; 4153 | this.channel = []; 4154 | 4155 | }; 4156 | 4157 | Animation.prototype.parse = function ( element ) { 4158 | 4159 | this.id = element.getAttribute( 'id' ); 4160 | this.name = element.getAttribute( 'name' ); 4161 | this.source = {}; 4162 | 4163 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4164 | 4165 | var child = element.childNodes[ i ]; 4166 | 4167 | if ( child.nodeType != 1 ) continue; 4168 | 4169 | switch ( child.nodeName ) { 4170 | 4171 | case 'animation': 4172 | 4173 | var anim = ( new Animation() ).parse( child ); 4174 | 4175 | for ( var src in anim.source ) { 4176 | 4177 | this.source[ src ] = anim.source[ src ]; 4178 | 4179 | } 4180 | 4181 | for ( var j = 0; j < anim.channel.length; j ++ ) { 4182 | 4183 | this.channel.push( anim.channel[ j ] ); 4184 | this.sampler.push( anim.sampler[ j ] ); 4185 | 4186 | } 4187 | 4188 | break; 4189 | 4190 | case 'source': 4191 | 4192 | var src = ( new Source() ).parse( child ); 4193 | this.source[ src.id ] = src; 4194 | break; 4195 | 4196 | case 'sampler': 4197 | 4198 | this.sampler.push( ( new Sampler( this ) ).parse( child ) ); 4199 | break; 4200 | 4201 | case 'channel': 4202 | 4203 | this.channel.push( ( new Channel( this ) ).parse( child ) ); 4204 | break; 4205 | 4206 | default: 4207 | break; 4208 | 4209 | } 4210 | 4211 | } 4212 | 4213 | return this; 4214 | 4215 | }; 4216 | 4217 | function Channel( animation ) { 4218 | 4219 | this.animation = animation; 4220 | this.source = ""; 4221 | this.target = ""; 4222 | this.fullSid = null; 4223 | this.sid = null; 4224 | this.dotSyntax = null; 4225 | this.arrSyntax = null; 4226 | this.arrIndices = null; 4227 | this.member = null; 4228 | 4229 | }; 4230 | 4231 | Channel.prototype.parse = function ( element ) { 4232 | 4233 | this.source = element.getAttribute( 'source' ).replace( /^#/, '' ); 4234 | this.target = element.getAttribute( 'target' ); 4235 | 4236 | var parts = this.target.split( '/' ); 4237 | 4238 | var id = parts.shift(); 4239 | var sid = parts.shift(); 4240 | 4241 | var dotSyntax = ( sid.indexOf(".") >= 0 ); 4242 | var arrSyntax = ( sid.indexOf("(") >= 0 ); 4243 | 4244 | if ( dotSyntax ) { 4245 | 4246 | parts = sid.split("."); 4247 | this.sid = parts.shift(); 4248 | this.member = parts.shift(); 4249 | 4250 | } else if ( arrSyntax ) { 4251 | 4252 | var arrIndices = sid.split("("); 4253 | this.sid = arrIndices.shift(); 4254 | 4255 | for (var j = 0; j < arrIndices.length; j ++ ) { 4256 | 4257 | arrIndices[j] = parseInt( arrIndices[j].replace(/\)/, '') ); 4258 | 4259 | } 4260 | 4261 | this.arrIndices = arrIndices; 4262 | 4263 | } else { 4264 | 4265 | this.sid = sid; 4266 | 4267 | } 4268 | 4269 | this.fullSid = sid; 4270 | this.dotSyntax = dotSyntax; 4271 | this.arrSyntax = arrSyntax; 4272 | 4273 | return this; 4274 | 4275 | }; 4276 | 4277 | function Sampler ( animation ) { 4278 | 4279 | this.id = ""; 4280 | this.animation = animation; 4281 | this.inputs = []; 4282 | this.input = null; 4283 | this.output = null; 4284 | this.strideOut = null; 4285 | this.interpolation = null; 4286 | this.startTime = null; 4287 | this.endTime = null; 4288 | this.duration = 0; 4289 | 4290 | }; 4291 | 4292 | Sampler.prototype.parse = function ( element ) { 4293 | 4294 | this.id = element.getAttribute( 'id' ); 4295 | this.inputs = []; 4296 | 4297 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4298 | 4299 | var child = element.childNodes[ i ]; 4300 | if ( child.nodeType != 1 ) continue; 4301 | 4302 | switch ( child.nodeName ) { 4303 | 4304 | case 'input': 4305 | 4306 | this.inputs.push( (new Input()).parse( child ) ); 4307 | break; 4308 | 4309 | default: 4310 | break; 4311 | 4312 | } 4313 | 4314 | } 4315 | 4316 | return this; 4317 | 4318 | }; 4319 | 4320 | Sampler.prototype.create = function () { 4321 | 4322 | for ( var i = 0; i < this.inputs.length; i ++ ) { 4323 | 4324 | var input = this.inputs[ i ]; 4325 | var source = this.animation.source[ input.source ]; 4326 | 4327 | switch ( input.semantic ) { 4328 | 4329 | case 'INPUT': 4330 | 4331 | this.input = source.read(); 4332 | break; 4333 | 4334 | case 'OUTPUT': 4335 | 4336 | this.output = source.read(); 4337 | this.strideOut = source.accessor.stride; 4338 | break; 4339 | 4340 | case 'INTERPOLATION': 4341 | 4342 | this.interpolation = source.read(); 4343 | break; 4344 | 4345 | case 'IN_TANGENT': 4346 | 4347 | break; 4348 | 4349 | case 'OUT_TANGENT': 4350 | 4351 | break; 4352 | 4353 | default: 4354 | 4355 | console.log(input.semantic); 4356 | break; 4357 | 4358 | } 4359 | 4360 | } 4361 | 4362 | this.startTime = 0; 4363 | this.endTime = 0; 4364 | this.duration = 0; 4365 | 4366 | if ( this.input.length ) { 4367 | 4368 | this.startTime = 100000000; 4369 | this.endTime = -100000000; 4370 | 4371 | for ( var i = 0; i < this.input.length; i ++ ) { 4372 | 4373 | this.startTime = Math.min( this.startTime, this.input[ i ] ); 4374 | this.endTime = Math.max( this.endTime, this.input[ i ] ); 4375 | 4376 | } 4377 | 4378 | this.duration = this.endTime - this.startTime; 4379 | 4380 | } 4381 | 4382 | }; 4383 | 4384 | Sampler.prototype.getData = function ( type, ndx, member ) { 4385 | 4386 | var data; 4387 | 4388 | if ( type === 'matrix' && this.strideOut === 16 ) { 4389 | 4390 | data = this.output[ ndx ]; 4391 | 4392 | } else if ( this.strideOut > 1 ) { 4393 | 4394 | data = []; 4395 | ndx *= this.strideOut; 4396 | 4397 | for ( var i = 0; i < this.strideOut; ++ i ) { 4398 | 4399 | data[ i ] = this.output[ ndx + i ]; 4400 | 4401 | } 4402 | 4403 | if ( this.strideOut === 3 ) { 4404 | 4405 | switch ( type ) { 4406 | 4407 | case 'rotate': 4408 | case 'translate': 4409 | 4410 | fixCoords( data, -1 ); 4411 | break; 4412 | 4413 | case 'scale': 4414 | 4415 | fixCoords( data, 1 ); 4416 | break; 4417 | 4418 | } 4419 | 4420 | } else if ( this.strideOut === 4 && type === 'matrix' ) { 4421 | 4422 | fixCoords( data, -1 ); 4423 | 4424 | } 4425 | 4426 | } else { 4427 | 4428 | data = this.output[ ndx ]; 4429 | 4430 | if ( member && type === 'translate' ) { 4431 | data = getConvertedTranslation( member, data ); 4432 | } 4433 | 4434 | } 4435 | 4436 | return data; 4437 | 4438 | }; 4439 | 4440 | function Key ( time ) { 4441 | 4442 | this.targets = []; 4443 | this.time = time; 4444 | 4445 | }; 4446 | 4447 | Key.prototype.addTarget = function ( fullSid, transform, member, data ) { 4448 | 4449 | this.targets.push( { 4450 | sid: fullSid, 4451 | member: member, 4452 | transform: transform, 4453 | data: data 4454 | } ); 4455 | 4456 | }; 4457 | 4458 | Key.prototype.apply = function ( opt_sid ) { 4459 | 4460 | for ( var i = 0; i < this.targets.length; ++ i ) { 4461 | 4462 | var target = this.targets[ i ]; 4463 | 4464 | if ( !opt_sid || target.sid === opt_sid ) { 4465 | 4466 | target.transform.update( target.data, target.member ); 4467 | 4468 | } 4469 | 4470 | } 4471 | 4472 | }; 4473 | 4474 | Key.prototype.getTarget = function ( fullSid ) { 4475 | 4476 | for ( var i = 0; i < this.targets.length; ++ i ) { 4477 | 4478 | if ( this.targets[ i ].sid === fullSid ) { 4479 | 4480 | return this.targets[ i ]; 4481 | 4482 | } 4483 | 4484 | } 4485 | 4486 | return null; 4487 | 4488 | }; 4489 | 4490 | Key.prototype.hasTarget = function ( fullSid ) { 4491 | 4492 | for ( var i = 0; i < this.targets.length; ++ i ) { 4493 | 4494 | if ( this.targets[ i ].sid === fullSid ) { 4495 | 4496 | return true; 4497 | 4498 | } 4499 | 4500 | } 4501 | 4502 | return false; 4503 | 4504 | }; 4505 | 4506 | // TODO: Currently only doing linear interpolation. Should support full COLLADA spec. 4507 | Key.prototype.interpolate = function ( nextKey, time ) { 4508 | 4509 | for ( var i = 0, l = this.targets.length; i < l; i ++ ) { 4510 | 4511 | var target = this.targets[ i ], 4512 | nextTarget = nextKey.getTarget( target.sid ), 4513 | data; 4514 | 4515 | if ( target.transform.type !== 'matrix' && nextTarget ) { 4516 | 4517 | var scale = ( time - this.time ) / ( nextKey.time - this.time ), 4518 | nextData = nextTarget.data, 4519 | prevData = target.data; 4520 | 4521 | if ( scale < 0 ) scale = 0; 4522 | if ( scale > 1 ) scale = 1; 4523 | 4524 | if ( prevData.length ) { 4525 | 4526 | data = []; 4527 | 4528 | for ( var j = 0; j < prevData.length; ++ j ) { 4529 | 4530 | data[ j ] = prevData[ j ] + ( nextData[ j ] - prevData[ j ] ) * scale; 4531 | 4532 | } 4533 | 4534 | } else { 4535 | 4536 | data = prevData + ( nextData - prevData ) * scale; 4537 | 4538 | } 4539 | 4540 | } else { 4541 | 4542 | data = target.data; 4543 | 4544 | } 4545 | 4546 | target.transform.update( data, target.member ); 4547 | 4548 | } 4549 | 4550 | }; 4551 | 4552 | // Camera 4553 | function Camera() { 4554 | 4555 | this.id = ""; 4556 | this.name = ""; 4557 | this.technique = ""; 4558 | 4559 | }; 4560 | 4561 | Camera.prototype.parse = function ( element ) { 4562 | 4563 | this.id = element.getAttribute( 'id' ); 4564 | this.name = element.getAttribute( 'name' ); 4565 | 4566 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4567 | 4568 | var child = element.childNodes[ i ]; 4569 | if ( child.nodeType != 1 ) continue; 4570 | 4571 | switch ( child.nodeName ) { 4572 | 4573 | case 'optics': 4574 | 4575 | this.parseOptics( child ); 4576 | break; 4577 | 4578 | default: 4579 | break; 4580 | 4581 | } 4582 | 4583 | } 4584 | 4585 | return this; 4586 | 4587 | }; 4588 | 4589 | Camera.prototype.parseOptics = function ( element ) { 4590 | 4591 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4592 | 4593 | if ( element.childNodes[ i ].nodeName === 'technique_common' ) { 4594 | 4595 | var technique = element.childNodes[ i ]; 4596 | 4597 | for ( var j = 0; j < technique.childNodes.length; j ++ ) { 4598 | 4599 | this.technique = technique.childNodes[ j ].nodeName; 4600 | 4601 | if ( this.technique === 'perspective' ) { 4602 | 4603 | var perspective = technique.childNodes[ j ]; 4604 | 4605 | for ( var k = 0; k < perspective.childNodes.length; k ++ ) { 4606 | 4607 | var param = perspective.childNodes[ k ]; 4608 | 4609 | switch ( param.nodeName ) { 4610 | 4611 | case 'yfov': 4612 | this.yfov = param.textContent; 4613 | break; 4614 | case 'xfov': 4615 | this.xfov = param.textContent; 4616 | break; 4617 | case 'znear': 4618 | this.znear = param.textContent; 4619 | break; 4620 | case 'zfar': 4621 | this.zfar = param.textContent; 4622 | break; 4623 | case 'aspect_ratio': 4624 | this.aspect_ratio = param.textContent; 4625 | break; 4626 | 4627 | } 4628 | 4629 | } 4630 | 4631 | } else if ( this.technique === 'orthographic' ) { 4632 | 4633 | var orthographic = technique.childNodes[ j ]; 4634 | 4635 | for ( var k = 0; k < orthographic.childNodes.length; k ++ ) { 4636 | 4637 | var param = orthographic.childNodes[ k ]; 4638 | 4639 | switch ( param.nodeName ) { 4640 | 4641 | case 'xmag': 4642 | this.xmag = param.textContent; 4643 | break; 4644 | case 'ymag': 4645 | this.ymag = param.textContent; 4646 | break; 4647 | case 'znear': 4648 | this.znear = param.textContent; 4649 | break; 4650 | case 'zfar': 4651 | this.zfar = param.textContent; 4652 | break; 4653 | case 'aspect_ratio': 4654 | this.aspect_ratio = param.textContent; 4655 | break; 4656 | 4657 | } 4658 | 4659 | } 4660 | 4661 | } 4662 | 4663 | } 4664 | 4665 | } 4666 | 4667 | } 4668 | 4669 | return this; 4670 | 4671 | }; 4672 | 4673 | function InstanceCamera() { 4674 | 4675 | this.url = ""; 4676 | 4677 | }; 4678 | 4679 | InstanceCamera.prototype.parse = function ( element ) { 4680 | 4681 | this.url = element.getAttribute('url').replace(/^#/, ''); 4682 | 4683 | return this; 4684 | 4685 | }; 4686 | 4687 | // Light 4688 | 4689 | function Light() { 4690 | 4691 | this.id = ""; 4692 | this.name = ""; 4693 | this.technique = ""; 4694 | 4695 | }; 4696 | 4697 | Light.prototype.parse = function ( element ) { 4698 | 4699 | this.id = element.getAttribute( 'id' ); 4700 | this.name = element.getAttribute( 'name' ); 4701 | 4702 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4703 | 4704 | var child = element.childNodes[ i ]; 4705 | if ( child.nodeType != 1 ) continue; 4706 | 4707 | switch ( child.nodeName ) { 4708 | 4709 | case 'technique_common': 4710 | 4711 | this.parseCommon( child ); 4712 | break; 4713 | 4714 | case 'technique': 4715 | 4716 | this.parseTechnique( child ); 4717 | break; 4718 | 4719 | default: 4720 | break; 4721 | 4722 | } 4723 | 4724 | } 4725 | 4726 | return this; 4727 | 4728 | }; 4729 | 4730 | Light.prototype.parseCommon = function ( element ) { 4731 | 4732 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4733 | 4734 | switch ( element.childNodes[ i ].nodeName ) { 4735 | 4736 | case 'directional': 4737 | case 'point': 4738 | case 'spot': 4739 | case 'ambient': 4740 | 4741 | this.technique = element.childNodes[ i ].nodeName; 4742 | 4743 | var light = element.childNodes[ i ]; 4744 | 4745 | for ( var j = 0; j < light.childNodes.length; j ++ ) { 4746 | 4747 | var child = light.childNodes[j]; 4748 | 4749 | switch ( child.nodeName ) { 4750 | 4751 | case 'color': 4752 | 4753 | var rgba = _floats( child.textContent ); 4754 | this.color = new THREE.Color(0); 4755 | this.color.setRGB( rgba[0], rgba[1], rgba[2] ); 4756 | this.color.a = rgba[3]; 4757 | break; 4758 | 4759 | case 'falloff_angle': 4760 | 4761 | this.falloff_angle = parseFloat( child.textContent ); 4762 | break; 4763 | 4764 | case 'quadratic_attenuation': 4765 | var f = parseFloat( child.textContent ); 4766 | this.distance = f ? Math.sqrt( 1 / f ) : 0; 4767 | } 4768 | 4769 | } 4770 | 4771 | } 4772 | 4773 | } 4774 | 4775 | return this; 4776 | 4777 | }; 4778 | 4779 | Light.prototype.parseTechnique = function ( element ) { 4780 | 4781 | this.profile = element.getAttribute( 'profile' ); 4782 | 4783 | for ( var i = 0; i < element.childNodes.length; i ++ ) { 4784 | 4785 | var child = element.childNodes[ i ]; 4786 | 4787 | switch ( child.nodeName ) { 4788 | 4789 | case 'intensity': 4790 | 4791 | this.intensity = parseFloat(child.textContent); 4792 | break; 4793 | 4794 | } 4795 | 4796 | } 4797 | 4798 | return this; 4799 | 4800 | }; 4801 | 4802 | function InstanceLight() { 4803 | 4804 | this.url = ""; 4805 | 4806 | }; 4807 | 4808 | InstanceLight.prototype.parse = function ( element ) { 4809 | 4810 | this.url = element.getAttribute('url').replace(/^#/, ''); 4811 | 4812 | return this; 4813 | 4814 | }; 4815 | 4816 | function KinematicsModel( ) { 4817 | 4818 | this.id = ''; 4819 | this.name = ''; 4820 | this.joints = []; 4821 | this.links = []; 4822 | 4823 | } 4824 | 4825 | KinematicsModel.prototype.parse = function( element ) { 4826 | 4827 | this.id = element.getAttribute('id'); 4828 | this.name = element.getAttribute('name'); 4829 | this.joints = []; 4830 | this.links = []; 4831 | 4832 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4833 | 4834 | var child = element.childNodes[ i ]; 4835 | if ( child.nodeType != 1 ) continue; 4836 | 4837 | switch ( child.nodeName ) { 4838 | 4839 | case 'technique_common': 4840 | 4841 | this.parseCommon(child); 4842 | break; 4843 | 4844 | default: 4845 | break; 4846 | 4847 | } 4848 | 4849 | } 4850 | 4851 | return this; 4852 | 4853 | }; 4854 | 4855 | KinematicsModel.prototype.parseCommon = function( element ) { 4856 | 4857 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4858 | 4859 | var child = element.childNodes[ i ]; 4860 | if ( child.nodeType != 1 ) continue; 4861 | 4862 | switch ( element.childNodes[ i ].nodeName ) { 4863 | 4864 | case 'joint': 4865 | this.joints.push( (new Joint()).parse(child) ); 4866 | break; 4867 | 4868 | case 'link': 4869 | this.links.push( (new Link()).parse(child) ); 4870 | break; 4871 | 4872 | default: 4873 | break; 4874 | 4875 | } 4876 | 4877 | } 4878 | 4879 | return this; 4880 | 4881 | }; 4882 | 4883 | function Joint( ) { 4884 | 4885 | this.sid = ''; 4886 | this.name = ''; 4887 | this.axis = new THREE.Vector3(); 4888 | this.limits = { 4889 | min: 0, 4890 | max: 0 4891 | }; 4892 | this.type = ''; 4893 | this.static = false; 4894 | this.zeroPosition = 0.0; 4895 | this.middlePosition = 0.0; 4896 | 4897 | } 4898 | 4899 | Joint.prototype.parse = function( element ) { 4900 | 4901 | this.sid = element.getAttribute('sid'); 4902 | this.name = element.getAttribute('name'); 4903 | this.axis = new THREE.Vector3(); 4904 | this.limits = { 4905 | min: 0, 4906 | max: 0 4907 | }; 4908 | this.type = ''; 4909 | this.static = false; 4910 | this.zeroPosition = 0.0; 4911 | this.middlePosition = 0.0; 4912 | 4913 | var axisElement = element.querySelector('axis'); 4914 | var _axis = _floats(axisElement.textContent); 4915 | this.axis = getConvertedVec3(_axis, 0); 4916 | 4917 | var min = element.querySelector('limits min') ? parseFloat(element.querySelector('limits min').textContent) : -360; 4918 | var max = element.querySelector('limits max') ? parseFloat(element.querySelector('limits max').textContent) : 360; 4919 | 4920 | this.limits = { 4921 | min: min, 4922 | max: max 4923 | }; 4924 | 4925 | var jointTypes = [ 'prismatic', 'revolute' ]; 4926 | for (var i = 0; i < jointTypes.length; i ++ ) { 4927 | 4928 | var type = jointTypes[ i ]; 4929 | 4930 | var jointElement = element.querySelector(type); 4931 | 4932 | if ( jointElement ) { 4933 | 4934 | this.type = type; 4935 | 4936 | } 4937 | 4938 | } 4939 | 4940 | // if the min is equal to or somehow greater than the max, consider the joint static 4941 | if ( this.limits.min >= this.limits.max ) { 4942 | 4943 | this.static = true; 4944 | 4945 | } 4946 | 4947 | this.middlePosition = (this.limits.min + this.limits.max) / 2.0; 4948 | return this; 4949 | 4950 | }; 4951 | 4952 | function Link( ) { 4953 | 4954 | this.sid = ''; 4955 | this.name = ''; 4956 | this.transforms = []; 4957 | this.attachments = []; 4958 | 4959 | } 4960 | 4961 | Link.prototype.parse = function( element ) { 4962 | 4963 | this.sid = element.getAttribute('sid'); 4964 | this.name = element.getAttribute('name'); 4965 | this.transforms = []; 4966 | this.attachments = []; 4967 | 4968 | for (var i = 0; i < element.childNodes.length; i ++ ) { 4969 | 4970 | var child = element.childNodes[ i ]; 4971 | if ( child.nodeType != 1 ) continue; 4972 | 4973 | switch ( child.nodeName ) { 4974 | 4975 | case 'attachment_full': 4976 | this.attachments.push( (new Attachment()).parse(child) ); 4977 | break; 4978 | 4979 | case 'rotate': 4980 | case 'translate': 4981 | case 'matrix': 4982 | 4983 | this.transforms.push( (new Transform()).parse(child) ); 4984 | break; 4985 | 4986 | default: 4987 | 4988 | break; 4989 | 4990 | } 4991 | 4992 | } 4993 | 4994 | return this; 4995 | 4996 | }; 4997 | 4998 | function Attachment( ) { 4999 | 5000 | this.joint = ''; 5001 | this.transforms = []; 5002 | this.links = []; 5003 | 5004 | } 5005 | 5006 | Attachment.prototype.parse = function( element ) { 5007 | 5008 | this.joint = element.getAttribute('joint').split('/').pop(); 5009 | this.links = []; 5010 | 5011 | for (var i = 0; i < element.childNodes.length; i ++ ) { 5012 | 5013 | var child = element.childNodes[ i ]; 5014 | if ( child.nodeType != 1 ) continue; 5015 | 5016 | switch ( child.nodeName ) { 5017 | 5018 | case 'link': 5019 | this.links.push( (new Link()).parse(child) ); 5020 | break; 5021 | 5022 | case 'rotate': 5023 | case 'translate': 5024 | case 'matrix': 5025 | 5026 | this.transforms.push( (new Transform()).parse(child) ); 5027 | break; 5028 | 5029 | default: 5030 | 5031 | break; 5032 | 5033 | } 5034 | 5035 | } 5036 | 5037 | return this; 5038 | 5039 | }; 5040 | 5041 | function _source( element ) { 5042 | 5043 | var id = element.getAttribute( 'id' ); 5044 | 5045 | if ( sources[ id ] != undefined ) { 5046 | 5047 | return sources[ id ]; 5048 | 5049 | } 5050 | 5051 | sources[ id ] = ( new Source(id )).parse( element ); 5052 | return sources[ id ]; 5053 | 5054 | }; 5055 | 5056 | function _nsResolver( nsPrefix ) { 5057 | 5058 | if ( nsPrefix === "dae" ) { 5059 | 5060 | return "http://www.collada.org/2005/11/COLLADASchema"; 5061 | 5062 | } 5063 | 5064 | return null; 5065 | 5066 | }; 5067 | 5068 | function _bools( str ) { 5069 | 5070 | var raw = _strings( str ); 5071 | var data = []; 5072 | 5073 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5074 | 5075 | data.push( (raw[i] === 'true' || raw[i] === '1') ? true : false ); 5076 | 5077 | } 5078 | 5079 | return data; 5080 | 5081 | }; 5082 | 5083 | function _floats( str ) { 5084 | 5085 | var raw = _strings(str); 5086 | var data = []; 5087 | 5088 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5089 | 5090 | data.push( parseFloat( raw[ i ] ) ); 5091 | 5092 | } 5093 | 5094 | return data; 5095 | 5096 | }; 5097 | 5098 | function _ints( str ) { 5099 | 5100 | var raw = _strings( str ); 5101 | var data = []; 5102 | 5103 | for ( var i = 0, l = raw.length; i < l; i ++ ) { 5104 | 5105 | data.push( parseInt( raw[ i ], 10 ) ); 5106 | 5107 | } 5108 | 5109 | return data; 5110 | 5111 | }; 5112 | 5113 | function _strings( str ) { 5114 | 5115 | return ( str.length > 0 ) ? _trimString( str ).split( /\s+/ ) : []; 5116 | 5117 | }; 5118 | 5119 | function _trimString( str ) { 5120 | 5121 | return str.replace( /^\s+/, "" ).replace( /\s+$/, "" ); 5122 | 5123 | }; 5124 | 5125 | function _attr_as_float( element, name, defaultValue ) { 5126 | 5127 | if ( element.hasAttribute( name ) ) { 5128 | 5129 | return parseFloat( element.getAttribute( name ) ); 5130 | 5131 | } else { 5132 | 5133 | return defaultValue; 5134 | 5135 | } 5136 | 5137 | }; 5138 | 5139 | function _attr_as_int( element, name, defaultValue ) { 5140 | 5141 | if ( element.hasAttribute( name ) ) { 5142 | 5143 | return parseInt( element.getAttribute( name ), 10) ; 5144 | 5145 | } else { 5146 | 5147 | return defaultValue; 5148 | 5149 | } 5150 | 5151 | }; 5152 | 5153 | function _attr_as_string( element, name, defaultValue ) { 5154 | 5155 | if ( element.hasAttribute( name ) ) { 5156 | 5157 | return element.getAttribute( name ); 5158 | 5159 | } else { 5160 | 5161 | return defaultValue; 5162 | 5163 | } 5164 | 5165 | }; 5166 | 5167 | function _format_float( f, num ) { 5168 | 5169 | if ( f === undefined ) { 5170 | 5171 | var s = '0.'; 5172 | 5173 | while ( s.length < num + 2 ) { 5174 | 5175 | s += '0'; 5176 | 5177 | } 5178 | 5179 | return s; 5180 | 5181 | } 5182 | 5183 | num = num || 2; 5184 | 5185 | var parts = f.toString().split( '.' ); 5186 | parts[ 1 ] = parts.length > 1 ? parts[ 1 ].substr( 0, num ) : "0"; 5187 | 5188 | while ( parts[ 1 ].length < num ) { 5189 | 5190 | parts[ 1 ] += '0'; 5191 | 5192 | } 5193 | 5194 | return parts.join( '.' ); 5195 | 5196 | }; 5197 | 5198 | function loadTextureImage ( texture, url ) { 5199 | 5200 | var loader = new THREE.ImageLoader(); 5201 | //console.log(url); 5202 | 5203 | loader.load( url, function ( image ) { 5204 | 5205 | texture.image = image; 5206 | texture.needsUpdate = true; 5207 | 5208 | } ); 5209 | 5210 | }; 5211 | 5212 | function extractDoubleSided( obj, element ) { 5213 | 5214 | obj.doubleSided = false; 5215 | 5216 | var node = element.querySelectorAll('extra double_sided')[0]; 5217 | 5218 | if ( node ) { 5219 | 5220 | if ( node && parseInt( node.textContent, 10 ) === 1 ) { 5221 | 5222 | obj.doubleSided = true; 5223 | 5224 | } 5225 | 5226 | } 5227 | 5228 | }; 5229 | 5230 | // Up axis conversion 5231 | 5232 | function setUpConversion() { 5233 | 5234 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5235 | 5236 | upConversion = null; 5237 | 5238 | } else { 5239 | 5240 | switch ( colladaUp ) { 5241 | 5242 | case 'X': 5243 | 5244 | upConversion = options.upAxis === 'Y' ? 'XtoY' : 'XtoZ'; 5245 | break; 5246 | 5247 | case 'Y': 5248 | 5249 | upConversion = options.upAxis === 'X' ? 'YtoX' : 'YtoZ'; 5250 | break; 5251 | 5252 | case 'Z': 5253 | 5254 | upConversion = options.upAxis === 'X' ? 'ZtoX' : 'ZtoY'; 5255 | break; 5256 | 5257 | } 5258 | 5259 | } 5260 | 5261 | }; 5262 | 5263 | function fixCoords( data, sign ) { 5264 | 5265 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5266 | 5267 | return; 5268 | 5269 | } 5270 | 5271 | switch ( upConversion ) { 5272 | 5273 | case 'XtoY': 5274 | 5275 | var tmp = data[ 0 ]; 5276 | data[ 0 ] = sign * data[ 1 ]; 5277 | data[ 1 ] = tmp; 5278 | break; 5279 | 5280 | case 'XtoZ': 5281 | 5282 | var tmp = data[ 2 ]; 5283 | data[ 2 ] = data[ 1 ]; 5284 | data[ 1 ] = data[ 0 ]; 5285 | data[ 0 ] = tmp; 5286 | break; 5287 | 5288 | case 'YtoX': 5289 | 5290 | var tmp = data[ 0 ]; 5291 | data[ 0 ] = data[ 1 ]; 5292 | data[ 1 ] = sign * tmp; 5293 | break; 5294 | 5295 | case 'YtoZ': 5296 | 5297 | var tmp = data[ 1 ]; 5298 | data[ 1 ] = sign * data[ 2 ]; 5299 | data[ 2 ] = tmp; 5300 | break; 5301 | 5302 | case 'ZtoX': 5303 | 5304 | var tmp = data[ 0 ]; 5305 | data[ 0 ] = data[ 1 ]; 5306 | data[ 1 ] = data[ 2 ]; 5307 | data[ 2 ] = tmp; 5308 | break; 5309 | 5310 | case 'ZtoY': 5311 | 5312 | var tmp = data[ 1 ]; 5313 | data[ 1 ] = data[ 2 ]; 5314 | data[ 2 ] = sign * tmp; 5315 | break; 5316 | 5317 | } 5318 | 5319 | }; 5320 | 5321 | function getConvertedTranslation( axis, data ) { 5322 | 5323 | if ( options.convertUpAxis !== true || colladaUp === options.upAxis ) { 5324 | 5325 | return data; 5326 | 5327 | } 5328 | 5329 | switch ( axis ) { 5330 | case 'X': 5331 | data = upConversion === 'XtoY' ? data * -1 : data; 5332 | break; 5333 | case 'Y': 5334 | data = upConversion === 'YtoZ' || upConversion === 'YtoX' ? data * -1 : data; 5335 | break; 5336 | case 'Z': 5337 | data = upConversion === 'ZtoY' ? data * -1 : data ; 5338 | break; 5339 | default: 5340 | break; 5341 | } 5342 | 5343 | return data; 5344 | }; 5345 | 5346 | function getConvertedVec3( data, offset ) { 5347 | 5348 | var arr = [ data[ offset ], data[ offset + 1 ], data[ offset + 2 ] ]; 5349 | fixCoords( arr, -1 ); 5350 | return new THREE.Vector3( arr[ 0 ], arr[ 1 ], arr[ 2 ] ); 5351 | 5352 | }; 5353 | 5354 | function getConvertedMat4( data ) { 5355 | 5356 | if ( options.convertUpAxis ) { 5357 | 5358 | // First fix rotation and scale 5359 | 5360 | // Columns first 5361 | var arr = [ data[ 0 ], data[ 4 ], data[ 8 ] ]; 5362 | fixCoords( arr, -1 ); 5363 | data[ 0 ] = arr[ 0 ]; 5364 | data[ 4 ] = arr[ 1 ]; 5365 | data[ 8 ] = arr[ 2 ]; 5366 | arr = [ data[ 1 ], data[ 5 ], data[ 9 ] ]; 5367 | fixCoords( arr, -1 ); 5368 | data[ 1 ] = arr[ 0 ]; 5369 | data[ 5 ] = arr[ 1 ]; 5370 | data[ 9 ] = arr[ 2 ]; 5371 | arr = [ data[ 2 ], data[ 6 ], data[ 10 ] ]; 5372 | fixCoords( arr, -1 ); 5373 | data[ 2 ] = arr[ 0 ]; 5374 | data[ 6 ] = arr[ 1 ]; 5375 | data[ 10 ] = arr[ 2 ]; 5376 | // Rows second 5377 | arr = [ data[ 0 ], data[ 1 ], data[ 2 ] ]; 5378 | fixCoords( arr, -1 ); 5379 | data[ 0 ] = arr[ 0 ]; 5380 | data[ 1 ] = arr[ 1 ]; 5381 | data[ 2 ] = arr[ 2 ]; 5382 | arr = [ data[ 4 ], data[ 5 ], data[ 6 ] ]; 5383 | fixCoords( arr, -1 ); 5384 | data[ 4 ] = arr[ 0 ]; 5385 | data[ 5 ] = arr[ 1 ]; 5386 | data[ 6 ] = arr[ 2 ]; 5387 | arr = [ data[ 8 ], data[ 9 ], data[ 10 ] ]; 5388 | fixCoords( arr, -1 ); 5389 | data[ 8 ] = arr[ 0 ]; 5390 | data[ 9 ] = arr[ 1 ]; 5391 | data[ 10 ] = arr[ 2 ]; 5392 | 5393 | // Now fix translation 5394 | arr = [ data[ 3 ], data[ 7 ], data[ 11 ] ]; 5395 | fixCoords( arr, -1 ); 5396 | data[ 3 ] = arr[ 0 ]; 5397 | data[ 7 ] = arr[ 1 ]; 5398 | data[ 11 ] = arr[ 2 ]; 5399 | 5400 | } 5401 | 5402 | return new THREE.Matrix4().set( 5403 | data[0], data[1], data[2], data[3], 5404 | data[4], data[5], data[6], data[7], 5405 | data[8], data[9], data[10], data[11], 5406 | data[12], data[13], data[14], data[15] 5407 | ); 5408 | 5409 | }; 5410 | 5411 | function getConvertedIndex( index ) { 5412 | 5413 | if ( index > -1 && index < 3 ) { 5414 | 5415 | var members = [ 'X', 'Y', 'Z' ], 5416 | indices = { X: 0, Y: 1, Z: 2 }; 5417 | 5418 | index = getConvertedMember( members[ index ] ); 5419 | index = indices[ index ]; 5420 | 5421 | } 5422 | 5423 | return index; 5424 | 5425 | }; 5426 | 5427 | function getConvertedMember( member ) { 5428 | 5429 | if ( options.convertUpAxis ) { 5430 | 5431 | switch ( member ) { 5432 | 5433 | case 'X': 5434 | 5435 | switch ( upConversion ) { 5436 | 5437 | case 'XtoY': 5438 | case 'XtoZ': 5439 | case 'YtoX': 5440 | 5441 | member = 'Y'; 5442 | break; 5443 | 5444 | case 'ZtoX': 5445 | 5446 | member = 'Z'; 5447 | break; 5448 | 5449 | } 5450 | 5451 | break; 5452 | 5453 | case 'Y': 5454 | 5455 | switch ( upConversion ) { 5456 | 5457 | case 'XtoY': 5458 | case 'YtoX': 5459 | case 'ZtoX': 5460 | 5461 | member = 'X'; 5462 | break; 5463 | 5464 | case 'XtoZ': 5465 | case 'YtoZ': 5466 | case 'ZtoY': 5467 | 5468 | member = 'Z'; 5469 | break; 5470 | 5471 | } 5472 | 5473 | break; 5474 | 5475 | case 'Z': 5476 | 5477 | switch ( upConversion ) { 5478 | 5479 | case 'XtoZ': 5480 | 5481 | member = 'X'; 5482 | break; 5483 | 5484 | case 'YtoZ': 5485 | case 'ZtoX': 5486 | case 'ZtoY': 5487 | 5488 | member = 'Y'; 5489 | break; 5490 | 5491 | } 5492 | 5493 | break; 5494 | 5495 | } 5496 | 5497 | } 5498 | 5499 | return member; 5500 | 5501 | }; 5502 | 5503 | return { 5504 | 5505 | load: load, 5506 | parse: parse, 5507 | setPreferredShading: setPreferredShading, 5508 | applySkin: applySkin, 5509 | geometries : geometries, 5510 | options: options 5511 | 5512 | }; 5513 | 5514 | }; 5515 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ### Loading 3D models into React applications 2 | 3 | This code makes it easier to create React components from imported 3D-models. To be used in applications using react-three or react-three-renderer and Three.js. It consists of 3 files: 4 | 5 | - `parsed_model.js`: contains the class ParsedModel, the main code. 6 | - `create_material.js`: converts THREE.Material instances to react-three-renderer components, needs to be extended with more types of material which is fairly straight-forward. This file is only needed in projects that use react-three-renderer. 7 | - `ColladaLoader.js`: only necessary if your project needs to load Collada files. 8 | 9 | ### ParsedModel 10 | 11 | ``` 12 | let model = new ParsedModel(); 13 | model.load('path/to/model').then( 14 | function resolve(m){ 15 | console.log('loaded:', m); 16 | }, 17 | function reject(e){ 18 | console.error('error:', e); 19 | } 20 | ); 21 | 22 | ``` 23 | 24 | If your application does not need to be able to load Collada files you can uncomment the lines: 25 | 26 | ``` 27 | import ColladaLoader from './ColladaLoader'; 28 | ``` 29 | and in the constructor: 30 | ``` 31 | this._colladaLoader = new THREE.ColladaLoader(); 32 | ``` 33 | 34 | 35 | #### constructor 36 | You can pass an optional settings object to the constructor: 37 | 38 | ``` 39 | let model = new ParsedModel({ 40 | scale: 1, 41 | rotation: new THREE.Quaternion() 42 | }); 43 | ``` 44 | 45 | The settings object sets the rotation and the scale of the model itself, not the rotation and the scale of the THREE.Object3D instance that contains the model. 46 | 47 | 48 | #### load(url, [settings]) 49 | You can load both JSON and Collada files. Files with the extension `.dae` and `.collada` will be loaded using the ColladaLoader and files with a `.json` extension will be loaded with the THREE.ObjectLoader. 50 | Like in the constructor the settings object is optional and allows you to set the scale and rotation of the loaded model itself. 51 | 52 | 53 | #### loadCollada(url, [settings]) 54 | Allows you to load Collada files that have a different extension. 55 | 56 | 57 | #### loadJSON(url, [settings]) 58 | Allows you to load JSON files that have a different extension. 59 | 60 | 61 | #### parse(Object3D, [settings]) 62 | Parses an instance of Object3D (or subclasses thereof). This method is particularly handy if you want to use an external model that has already been loaded by another method. 63 | 64 | 65 | #### properties 66 | After you have loaded or parsed an external 3D model, an instance of `ParsedModel` has the following properties: 67 | 68 | - `model`: reference to the loaded 3D model 69 | - `name`: name of the loaded 3D model 70 | - `geometries`: a Map of all geometries of the loaded 3D model 71 | - `mergedGeometry`: all geometries merged into a single instance of THREE.BufferGeometry 72 | - `materialArray`: an Array containing all used materials 73 | - `material`: an instance of THREE.MeshFaceMaterial made of all used materials 74 | 75 | 76 | 77 | ### Rendering a 3D model with React 78 | Example with react-three using the merged geometry and the multi-material: 79 | ``` 80 | let geometry = this.props.parsedModel.mergedGeometry; 81 | let material = this.props.parsedModel.material; 82 | return( 83 | 90 | ); 91 | ``` 92 | 93 | Currently multi-materials are not yet supported in react-three-renderer so we need a bit more code here: 94 | 95 | ``` 96 | let meshes = []; 97 | let geometries = this.props.parsedModel.geometries; 98 | let materialsArray = this.props.parsedModel.materialsArray; 99 | let materialIndices = this.props.parsedModel.materialIndices; 100 | 101 | geometries.forEach((geometry, uuid) => { 102 | // get the right material for this geometry using the material index 103 | let material = materialsArray[materialIndices.get(uuid)]; 104 | // create a react-three-renderer material component 105 | material = createMaterial(material); 106 | 107 | meshes.push( 108 | 111 | 115 | {material} 116 | 117 | ); 118 | }); 119 | 120 | return( 121 | 122 | {meshes} 123 | 124 | ); 125 | 126 | ``` 127 | 128 | 129 | ### Examples 130 | 131 | In this [repository](https://github.com/tweedegolf/parsed_model_examples) you can find 2 working examples of how to use `ParsedModel` with react-three and react-three-renderer. As mentioned above if your application does not need to load collada's you don't have to add the file ColladaLoader.js to your project, and the file create_material.js is only needed in projects that use react-three-renderer. -------------------------------------------------------------------------------- /create_material.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | 3 | // simple method that parses a Threejs material into a component (to be extended with other types of material) 4 | 5 | export default function createMaterial(material){ 6 | let m; 7 | switch(material.type){ 8 | case 'MeshBasicMaterial': 9 | m = ( 10 | 13 | ); 14 | break; 15 | case 'MeshLambertMaterial': 16 | m = ( 17 | 28 | ); 29 | break; 30 | default: 31 | m = null; 32 | } 33 | 34 | return m; 35 | } 36 | -------------------------------------------------------------------------------- /parsed_model.js: -------------------------------------------------------------------------------- 1 | import THREE from 'three'; 2 | import ColladaLoader from './ColladaLoader'; 3 | 4 | export default class ParsedModel{ 5 | 6 | constructor(settings){ 7 | this._colladaLoader = new THREE.ColladaLoader(); 8 | this._objectLoader = new THREE.ObjectLoader(); 9 | this._parseSettings(settings || {}); 10 | } 11 | 12 | _parseSettings(settings){ 13 | this.quaternion = settings.quaternion || new THREE.Quaternion(); 14 | this.scale = settings.scale; 15 | if(typeof this.scale !== 'number'){ 16 | this.scale = 1; 17 | } 18 | } 19 | 20 | parse(model, settings){ 21 | if(typeof settings !== 'undefined'){ 22 | this._parseSettings(settings); 23 | } 24 | if(typeof model.traverse === 'undefined'){ 25 | console.error('not an instance of THREE.Object3D'); 26 | return; 27 | } 28 | this.model = model; 29 | this.name = model.name; 30 | this.geometries = new Map(); 31 | this.materialIndices = new Map(); 32 | this.materialsArray = []; 33 | 34 | // adjust the rotation of the model according to the rotation of the world 35 | this.model.quaternion.copy(this.quaternion); 36 | this.model.updateMatrix(); 37 | 38 | let index = 0; 39 | this.model.traverse((child) => { 40 | if(child instanceof THREE.Mesh){ 41 | // set initial scale of model by scaling its geometries 42 | child.geometry.scale(this.scale, this.scale, this.scale); 43 | // create an array of the use materials 44 | let uuid = child.material.uuid; 45 | this.materialIndices.set(uuid, index++); 46 | this.materialsArray.push(child.material); 47 | this.geometries.set(uuid, child.geometry); 48 | } 49 | }); 50 | //console.log('number of geometries merged', index); 51 | 52 | // create multimaterial 53 | this.material = new THREE.MeshFaceMaterial(this.materialsArray); 54 | 55 | let merged = new THREE.Geometry(); 56 | // merge the geometry and apply the matrix of the new position 57 | this.geometries.forEach((g, uuid) => { 58 | merged.merge(g, this.model.matrix, this.materialIndices.get(uuid)); 59 | }); 60 | 61 | this.mergedGeometry = new THREE.BufferGeometry().fromGeometry(merged); 62 | } 63 | 64 | load(url, settings){ 65 | if(typeof settings !== 'undefined'){ 66 | this._parseSettings(settings); 67 | } 68 | if(typeof url !== 'string'){ 69 | url = 'none'; 70 | } 71 | url = url || 'none'; 72 | let p; 73 | let type = url.substring(url.lastIndexOf('.') + 1).toLowerCase(); 74 | 75 | switch(type){ 76 | case 'dae': 77 | case 'collada': 78 | p = this.loadCollada(url); 79 | break; 80 | case 'json': 81 | p = this.loadJSON(url); 82 | break; 83 | default: 84 | p = new Promise((resolve, reject) => { 85 | reject('wrong data provided'); 86 | }); 87 | } 88 | return p; 89 | } 90 | 91 | loadCollada(url, settings){ 92 | if(typeof settings !== 'undefined'){ 93 | this._parseSettings(settings); 94 | } 95 | 96 | return new Promise((resolve, reject) => { 97 | this._colladaLoader.load( 98 | url, 99 | // success callback 100 | (data) => { 101 | this.parse(data.scene); 102 | resolve(); 103 | }, 104 | // progress callback 105 | () => {}, 106 | // error callback 107 | (error) => { 108 | reject(error); 109 | } 110 | ); 111 | }); 112 | } 113 | 114 | loadJSON(url, settings){ 115 | if(typeof settings !== 'undefined'){ 116 | this._parseSettings(settings); 117 | } 118 | 119 | return new Promise((resolve, reject) => { 120 | this._objectLoader.load( 121 | url, 122 | // success callback 123 | (data) => { 124 | this.parse(data); 125 | resolve(); 126 | }, 127 | // progress callback 128 | () => {}, 129 | // error callback 130 | (error) => { 131 | reject(error); 132 | } 133 | ); 134 | }); 135 | } 136 | } 137 | --------------------------------------------------------------------------------