1 /* 2 Copyright (c) 2008 Seneca College 3 Licenced under the MIT License (http://www.c3dl.org/index.php/mit-license/) 4 */ 5 6 /** 7 @private 8 9 class ColladaLoader is used by the ModelManager to load .DAE (COLLADA) files. 10 */ 11 c3dl.ColladaLoader = function() 12 { 13 var XHR_STATE_COMPLETED = 4; 14 15 var xmlhttp = null; 16 this.done = false; 17 this.name = ""; 18 this.rootNode = new c3dl.SceneNode(); 19 20 /** 21 @private 22 Opens the DAE file, reads the vertex, normal and uv data and stores 23 all the data in 'expanded' form into members. 24 25 @param {String} relativePath 26 @param {c3dl.SceneNode} rootNode 27 */ 28 this.load = function(relativePath, rootNode) 29 { 30 // 31 this.rootNode = rootNode; 32 33 xmlhttp = new XMLHttpRequest(); 34 35 // 36 xmlhttp.parent = this; 37 38 // call the parse function when XMLHttpRequest is ready. 39 xmlhttp.callbackFunc = this.parse; 40 xmlhttp.open("GET", relativePath, true); 41 xmlhttp.overrideMimeType('text/xml'); 42 43 // this may throw an exception if the file isn't found, so 44 // catch the exception and give the user a helpful warning message. 45 try 46 { 47 // send the request 48 xmlhttp.send(null); 49 } 50 catch(err) 51 { 52 c3dl.debug.logWarning("Could not find file '" + relativePath +"'. Check the path."); 53 } 54 55 /** 56 @private 57 */ 58 xmlhttp.onreadystatechange = function() 59 { 60 // when the state has changed to being finished, 61 if(xmlhttp.readyState == XHR_STATE_COMPLETED) 62 { 63 // this line may not be totally nnecessary. 64 if(xmlhttp.responseXML) 65 { 66 xmlhttp.responseXML.colladaPath = relativePath; 67 68 // we can now parse by calling the callback which 69 // was set the the parse function. 70 this.callbackFunc(xmlhttp.responseXML); 71 } 72 } 73 } 74 } 75 76 77 /** 78 @private 79 80 Parse a node from the DAE file. The part of the DAE file 81 we are interested with is the scenegraph which is a hierarchical 82 structure of nodes. Nodes therefore can contain other nodes, thus 83 we use a recursive function to parse each one. 84 85 // clarify nodes in sg nodes in _DOM_ 86 @param {xmlDocument} xmlObject The DAE DOM. 87 @param {} node The node element to parse. the DOM 88 @param {c3dl.SceneNode} sgNode The SceneGraph node, not part of the XML DOM. 89 */ 90 this.parseNodeRecursive = function(xmlObject, node, sgNode) 91 { 92 // set this node's transform 93 var translateTag = c3dl.ColladaLoader.getChildNodesByNodeName(node, "translate"); 94 95 // node may not have one, so check first 96 if(translateTag) 97 { 98 // string representation of data between <translate> tags. 99 var floatValues = c3dl.ColladaLoader.stringsToFloats(translateTag[0].childNodes[0].nodeValue, ' '); 100 sgNode.translate(floatValues); 101 } 102 103 // rotations 104 var rotationTags = c3dl.ColladaLoader.getChildNodesByNodeName(node, "rotate"); 105 106 if(rotationTags) 107 { 108 // example 109 // <rotate sid="rotateZ">0 0 1 15</rotate> 110 // <rotate sid="rotateY">0 1 0 0</rotate> 111 // <rotate sid="rotateX">1 0 0 0</rotate> 112 for(var i=0; i < rotationTags.length; i++) 113 { 114 var floatValues = c3dl.ColladaLoader.stringsToFloats(rotationTags[i].childNodes[0].nodeValue, ' '); 115 116 var vec = [floatValues[0], floatValues[1],floatValues[2]]; 117 var angle = c3dl.degreesToRadians(floatValues[3]); 118 119 sgNode.rotateOnAxis(vec, angle); 120 } 121 } 122 123 // <scale> tag 124 var scaleTag = c3dl.ColladaLoader.getChildNodesByNodeName(node, "scale"); 125 if(scaleTag) 126 { 127 var floatValues = c3dl.ColladaLoader.stringsToFloats(scaleTag[0].childNodes[0].nodeValue, ' '); 128 sgNode.scale(floatValues); 129 } 130 131 // <matrix> tag specifies the matrix of the node instead of 132 // a scale, translate and rotate. 133 // 134 // The set of 16 numbers can have values with leading or trailing spaces, so we have to 135 // 136 var matrixTag = c3dl.ColladaLoader.getChildNodesByNodeName(node, "matrix"); 137 if(matrixTag) 138 { 139 var floatValues = c3dl.ColladaLoader.stringsToFloats(matrixTag[0].childNodes[0].nodeValue, ' '); 140 sgNode.setTransform(c3dl.transposeMatrix(floatValues)); 141 } 142 143 144 // At this point, this node's child nodes have been parsed or it 145 // did not contain subnodes. 146 147 // get the geometryNodes of this node. 148 var geometries = c3dl.ColladaLoader.getChildNodesByNodeName(node,"instance_geometry"); 149 150 // base case: we found a geometry leaf node, now instantiate it. 151 if(geometries) 152 { 153 // a node may contain many geometryNodes, so we have to create each one. 154 for (var currGeo = 0; currGeo < geometries.length; currGeo++) 155 { 156 // the url references a <geometry> element within the <library_geometry> 157 var url = geometries[currGeo].getAttribute("url").split('#')[1]; 158 159 // A separate function exists to actually create the geometry which can 160 // be pretty messy going through the DOM and constructing the geometry. 161 sgNode.addChild(this.instantiateGeometry(xmlObject, url, geometries[currGeo])); 162 } 163 } 164 165 // 166 // <instance_node> appears after <instance_geometry> 167 // 168 var instance_nodes = c3dl.ColladaLoader.getChildNodesByNodeName(node, "instance_node"); 169 if(instance_nodes) 170 { 171 // a <node> can contain 0..N <instance_node> so iterate over each one 172 for(var currNode = 0; currNode < instance_nodes.length; currNode++) 173 { 174 // remove the '#' from the url 175 var url = instance_nodes[currNode].getAttribute("url").split('#')[1]; 176 sgNode.addChild(this.instantiateNode(xmlObject, url)); 177 } 178 } 179 180 181 // get the DIRECT child nodes of this node. We can't use 182 // getElementsByTagName since its recursive and will 183 // return this node's grandchildren, we don't want that. 184 var nodes = c3dl.ColladaLoader.getChildNodesByNodeName(node, "node"); 185 186 // recursive case: the node has one or many nodes, therefore 187 // we have to parse all of its nodes. 188 if(nodes) 189 { 190 // for each of subnodes, call this function. 191 for(var i=0; i < nodes.length; i++) 192 { 193 var scenenode = new c3dl.SceneNode(); 194 scenenode.setName(nodes[i].getAttribute("name")); 195 sgNode.addChild(scenenode); 196 197 // call this function for each of the nodes found. 198 this.parseNodeRecursive(xmlObject,nodes[i],scenenode); 199 } 200 } 201 } 202 203 /** 204 @private 205 206 Get the child element of a parent element in the scenario when a schema can 207 have only one of set of children. For example, the technique element in the 208 common profile can have either constant, lambert, phong or blinn child elements 209 which are mutually exclusive. 210 211 @returns {object Element} 212 213 @param {Array} choiceTagNames Array of strings which contain the tags of 214 choices an element can have. 215 */ 216 this.getChoice = function(parentTag, choiceTagNames) 217 { 218 var choice = null; 219 var i = 0; 220 221 // use an iterator just in case the file does not conform to the spec 222 // and we can just out of the loop 223 while( choice == null && i < choiceTagNames.length) 224 { 225 choice = parentTag.getElementsByTagName(choiceTagNames[i])[0]; 226 i++; 227 } 228 229 return choice; 230 } 231 232 /** 233 @private 234 Parse is responsible for traversing different parts of the xmlObject 235 and constructing the geometries from the data provided. 236 237 This function is not called directly, it is called once the xml is 238 finished downloading. 239 240 @param {xmlDocument} xmlObject 241 */ 242 this.parse = function(xmlObject) 243 { 244 // since the xmlhttp object is calling this function, any reference 245 // to 'this' refers to the xmlhttp object, but we want to access the members in 246 // the object that owns this function, the DAELoader instance. To 247 // do this, simply make a reference to the this.parent which was set 248 // earlier, now we can access the members we need. 249 var loader = this.parent; 250 251 // get the root element of the xml document, to keep the naming short. 252 var root = xmlObject.documentElement; 253 254 // load the images listed in the dae file under the 255 // library_images into the texture manager. 256 var library_images = root.getElementsByTagName("library_images"); 257 258 // <collada> may have 0 or many <library_images> 259 for(var libraryImagesIter = 0; libraryImagesIter < library_images.length; libraryImagesIter++) 260 { 261 // one <library_images> has <images>. cardinality isn't mentioned in the spec. 262 var imageElements = library_images[libraryImagesIter].getElementsByTagName("image"); 263 264 // 265 for(var imageElementIter = 0; imageElementIter < imageElements.length; imageElementIter++) 266 { 267 // an <image> has exactly one <init_from> which have the uri or the 268 // texture. 269 // <image id="file2" name="file2" depth="1"> 270 // <init_from>./duckCM.tga</init_from> 271 // </image> 272 // 273 var init_from = imageElements[imageElementIter].getElementsByTagName("init_from")[0]; 274 } 275 } 276 277 // we start at the scene tag. A document instance 278 // can contain zero or 1 <scene> tags, therefore we 279 // can address the 0th element. If the 280 // scene tag is not present, this is an error, as 281 // we are concerned with some sort of visual scene. 282 // TODO: handle no scene elements. 283 var sceneElement = root.getElementsByTagName("scene")[0]; 284 285 // A scene element can contain ZERO OR ONE of each of the following 286 // elements: 287 // <instance_visual_scene> 288 // <instance_physics_scene> 289 // again, we are concerned only with visual stuff right now 290 // so if the element <instance_visual_scene> is not present, 291 // its another error. the <instance_visual_scene> has an attribute that acts as 292 // a foreign key into an element in the library_visual_scenes 293 // tag. 294 // TODO: handle no <instance_visual_scene> element. This can occur if the 295 // user tries to load a 1.3 Collada instance. 296 var instanceVisualSceneElem = sceneElement.getElementsByTagName("instance_visual_scene")[0]; 297 298 // get the url attribute of the <instance_visual_scene> 299 // this will tell us which scene to load. (we will be loading one scene from the 300 // library of scenes). 301 // this has a # in front of it so we have to remove it. 302 var visualSceneToLoad = instanceVisualSceneElem.getAttribute("url").split('#')[1]; 303 304 // a collada document has ZERO OR MANY <library_visual_scenes> 305 // which in turn have ONE OR MANY <visual_scene>'s. 306 // a <visual_scene> is the base of a scenegraph, which is what we 307 // want. Note that even if <library_visual_scenes> has 308 // more than one <visual_scene>'s, only one will be loaded. 309 // The one to load is the the value found earlier in the 310 // <instance_visual_scene>'s url attribute. 311 // so first, go the the collada's/root's <library_visual_scenes> 312 // TODO: can't access the first node since there may be many library_visual_scenes, 313 // we have to iterate over the libraries until we find the visualScene we want. 314 var libraryVisualScenes = root.getElementsByTagName("library_visual_scenes")[0]; 315 316 // added 'List' to avoid confusion with 'visualScene', which is the 317 // scene we will load. 318 // This is the list of visual scenes. Only one of which we will actually load. 319 var visualSceneList = libraryVisualScenes.getElementsByTagName("visual_scene"); 320 321 // Find the <visual_scene> element which we want, buy 322 // using the 'foreign key' we got from the <instance_visual_scene>. 323 var visualScene = null; 324 325 // go over all the visual scenes trying to identify the one we want. 326 for(var i = 0; i < visualSceneList.length; i++) 327 { 328 if( visualSceneList[i].getAttribute("id") == visualSceneToLoad) 329 { 330 visualScene = visualSceneList[i]; 331 } 332 } 333 334 // we are at the start of the visual_scene tag, this may have many 335 // nodes 336 337 // we now should have the visual_scene to load. 338 // A visual scene has ONE OR MANY <node> elements which compose the scenegraph. 339 var nodes = c3dl.ColladaLoader.getChildNodesByNodeName(visualScene,"node"); 340 341 // there is a change nodes is null if the dae file was edited manually 342 // and the nodes were removed. 343 if(nodes) 344 { 345 // parse each of the 'root' nodes. 346 for(var currNode = 0; currNode < nodes.length; currNode++) 347 { 348 var scenenode = new c3dl.SceneNode(); 349 350 // 351 loader.rootNode.addChild(scenenode); 352 scenenode.setName(nodes[currNode].getAttribute("name")); 353 354 loader.parseNodeRecursive(xmlObject, nodes[currNode], scenenode); 355 } 356 } 357 358 // TODO: comment 359 c3dl.ColladaQueue.popFront(); 360 delete xmlObject; 361 delete xmlhttp; 362 } 363 364 /** 365 */ 366 367 368 /** 369 @private 370 371 @param {XMLDocument} xmlObject 372 @param {String} target 373 */ 374 this.instantiateMaterial = function(xmlObject, target) 375 { 376 var tempTexture = null; 377 378 // we now have the material ID which we can look up in the library materials. 379 var material = this.findElementInLibrary(xmlObject, "library_materials", "material", target); 380 var tempName = target; 381 382 // a <material> has exactly 1 <instance_effect>, so just get the first. 383 // 384 //<library_materials> 385 // <material id="shine" name="shine"> 386 // <instance_effect url="#shine-fx"/> 387 // </material> 388 // <material id="matte" name="matte"> 389 // <instance_effect url="#matte-fx"/> 390 // </material> 391 //</library_materials> 392 var instanceEffect = material.getElementsByTagName("instance_effect")[0]; 393 var instanceEffectURL = instanceEffect.getAttribute("url").split('#')[1]; 394 395 // go to the <library_effects> since we have the <instance_effect> 396 // and it points to an entry in the library. 397 // 398 // 399 var effect = this.findElementInLibrary(xmlObject, "library_effects", "effect", instanceEffectURL); 400 401 // An effect has 1..N profiles. Each profile is designed for a specific platform, 402 // usage scenario, etc. 403 // 404 // profile types include: 405 // profile_COMMON - used for basic interchange between DCCs. 406 // profile_CG - opengl and NVIDIA's Cg shading language. 407 // profile_GLSL - opengl & glslang 408 // profile_GLES - opengl 1.0 and 1.1 409 // 410 // Collada book states support for GLES 2.0 and HLSL are in development, 411 // has this already been released? 412 // 413 // Right now we will focus on the fallback, profile_COMMON since it seems to be 414 // the safest profile and will likely be available. This should be understood 415 // by every application. We use it now as a default, later other profiles can 416 // be supported, namely GLSL/GLES. 417 var profile_COMMON = effect.getElementsByTagName("profile_COMMON")[0]; 418 419 // In each technique in the profile_COMMON profile is one of the shader 420 // algorithms: blinn, phong, constant or lambert. These shading algorithms 421 // are used in DCC tools which do not have adopted a shading language, therefore 422 // are using fixed functionality. Blinn shading algorithm is used by most DCC 423 // tools and therefore most common. 424 var technique = profile_COMMON.getElementsByTagName("technique")[0]; 425 426 // get the texture 427 var newparam = profile_COMMON.getElementsByTagName("newparam")[0]; 428 429 if( newparam) 430 { 431 // go to the surface type 432 var surface = newparam.getElementsByTagName("surface")[0]; 433 434 // get the value between the <init_from> tags 435 // the plane would have: 436 // <init_from>file1</init_from> 437 // it also has a format, but we ignore this for now. 438 var init_from = surface.getElementsByTagName("init_from")[0]; 439 440 // got the file id. 441 var fileID = init_from.childNodes[0].nodeValue; 442 443 // file1 is an id of an image in the <library_images> library 444 var texture = this.findElementInLibrary(xmlObject, "library_images", "image", fileID); 445 446 // finally, get the image name 447 //<image id="file2" name="file2" depth="1"> 448 // <init_from>./duckCM.tga</init_from> 449 //</image> 450 var textureName = texture.getElementsByTagName("init_from")[0].childNodes[0].nodeValue; 451 452 var resolvedTexture; 453 454 // if the texture is an abosolute path, use it. 455 if(c3dl.isPathAbsolute(textureName)) 456 { 457 resolvedTexture = textureName; 458 } 459 // otherwise, we need to place the path of dae file before the texture. 460 else 461 { 462 resolvedTexture = c3dl.getPathWithoutFilename(xmlObject.colladaPath) + textureName; 463 } 464 tempTexture = resolvedTexture; 465 } 466 467 // get the shading algorithm used. 468 // as of right now, we aren't concerned with the algorithm itself, (we use our 469 // own custom shading algorithm) but what we want to extract are the properties 470 // of the shading method which include diffuse, ambient, specular, etc. components. 471 var shadingAlgorithm = this.getChoice(technique, ["blinn", "constant", "phong", "lambert"]); 472 473 var mat = new c3dl.Material(); 474 //mat.texture = tempTexture; 475 mat.setName(tempName); 476 mat.setAmbient(this.getColor(shadingAlgorithm, "ambient")); 477 mat.setDiffuse(this.getColor(shadingAlgorithm, "diffuse")); 478 mat.setEmission(this.getColor(shadingAlgorithm, "emission")); 479 mat.setSpecular(this.getColor(shadingAlgorithm, "specular")); 480 mat.setShininess(this.getColor(shadingAlgorithm, "shininess")); 481 482 return [mat, tempTexture]; 483 } 484 485 /** 486 @private 487 488 The root COLLADA can contain ZERO OR MANY library_geometries node. 489 However in our case, since we are importing model data, should 490 have at least one library_geometries. If absent this will cause 491 an error. 492 493 The library geometries node contains 1 or Many <geometry> 494 nodes. 495 If library_geometries describes a car, 496 there may be several <geometries> which described the chairs, 497 seats, body, etc. 498 499 @param {XMLDocument} xmlObject 500 @param url 501 @param instanceGeometryElement 502 */ 503 this.instantiateGeometry = function(xmlObject, url, instanceGeometryElement) 504 { 505 var root = xmlObject.documentElement; 506 var libraryGeometries = root.getElementsByTagName("library_geometries"); 507 508 // once not null, we can stop searching. 509 var geoToCreate = null; 510 var geometry = new c3dl.Geometry(); 511 512 // the url provided points to a geometry in a geometry library. We'll need to 513 // go through the libraries and find which library it is in. 514 515 // TODO: add breakout when found 516 for (var currLib = 0; currLib < libraryGeometries.length; currLib++) 517 { 518 var geometries = libraryGeometries[currLib].getElementsByTagName("geometry"); 519 // for each geometry 520 for(var currGeo =0; currGeo < geometries.length; currGeo++) 521 { 522 if( geometries[currGeo].getAttribute("id") == url) 523 { 524 // found it 525 geoToCreate = geometries[currGeo]; 526 } 527 } 528 } 529 530 var verticesArray = null; 531 var vertexStride; 532 533 var normalsArray = null; 534 var normalsStride; 535 536 var texCoordsArray = null; 537 var texCoordsStride; 538 539 var faces = null; 540 var rawFaces; 541 542 // the library geometry will have a <mesh> which will have one or many <triangles>. 543 // we have to go over each <triangle> element and construct it. 544 545 // <geometry> contains all the data which describes a geometric object. 546 // <geometry> contains <mesh>, which in turn contains collation elements. 547 // these collation elements describe parts of the mesh differently either 548 // using polygons or lines. 549 // 550 // a geometry contains only one mesh node, so just get the first index. 551 // there are other kinds of meshes a geometry can have, but for now 552 // we aren't supporting them (convex_mesh, brep, spline, ...) 553 var mesh = geoToCreate.getElementsByTagName("mesh")[0]; 554 555 // we'll need to iterate over all the collation elements. 556 var collations = []; 557 558 // A mesh is composed of a set of collation elements. 559 // Types of collation elements are 560 // <lines>, <linestrips>, 561 // <polygons>, <polylist>, 562 // <triangles>, <tristrips>, <trifans> 563 // 564 // polygons can be a set of 3 or more vertices, therefore, we'll need to divide the polygons 565 // into triangles since GLES does not support quads or polygon rendering. 566 // 567 // The library does have means to render lines, but reading this primitive has not been added yet. 568 // 569 for(var i = 0; i < mesh.childNodes.length; i++) 570 { 571 if( mesh.childNodes[i].nodeName == "triangles" || 572 mesh.childNodes[i].nodeName == "polygons" || 573 mesh.childNodes[i].nodeName == "polylist") 574 { 575 collations.push(mesh.childNodes[i]); 576 } 577 } 578 579 // the collation elements have many primitives. 580 // <p> element represents a primitive. 581 // 582 // currColl = current collation 583 for(var currColl = 0; currColl < collations.length; currColl++) 584 { 585 // Depending on the type of collation element, the data will be layed out slighly differently. 586 // 587 // <triangles> and <polylist> are similar in that they both only have one <p> tag. 588 // polylist has an additional <vcount> child element which has a list of integers 589 // which states the number of vertices per polygon (which can vary). 590 // 591 // triangles are always composed of 3 vertices so this element does not require <vcount> 592 // 593 if( collations[currColl].nodeName =="triangles" || collations[currColl].nodeName =="polylist") 594 { 595 var p = this.getFirstChildByNodeName(collations[currColl],"p"); 596 rawFaces = this.mergeChildData(p.childNodes).split(" "); 597 } 598 599 // <polygon>s are broken up like this: 600 // <p> 0 0 1 1 2 2 3 3 </p> 601 // <p> ... </p> 602 // each <p> element describes one polygon which can vary in length from others 603 // 604 else if( collations[currColl].nodeName == "polygons") 605 { 606 var p_tags = collations[currColl].getElementsByTagName("p"); 607 rawFaces = []; 608 for(var i = 0; i < p_tags.length; i++) 609 { 610 // need to get rid of the spaces 611 var p_line = p_tags[i].childNodes[0].nodeValue.split(" "); 612 for(var j=0; j < p_line.length;j++) 613 { 614 rawFaces.push(parseInt(p_line[j])); 615 } 616 } 617 } 618 // If this message is ever seen, that means I have to write the case for it. 619 else 620 { 621 c3dl.debug.logError(collations[currColl].nodeName + " collation element is not yet supported"); 622 } 623 624 // At this point, we finished getting the faces for one collation element, the integers which will index 625 // into data steams. Now we move onto getting the input data streams. 626 627 // 628 // get the inputs which contain the verts, normals, uvs. 629 // 630 // A collation element first contains the <input> tags, then <vcount> (in the case of 631 // <polylist>) and then <p> tags. The <input> tags point to the raw data streams which 632 // 633 // The <input> tags associate a semantic to a data stream. This explains how the raw data 634 // stream will be used for this particular context. <input> has a source attribute which 635 // points to a source element which contains the raw data. 636 // 637 var inputs = collations[currColl].getElementsByTagName("input"); 638 639 collationElement = new c3dl.PrimitiveSet(); 640 641 // The order of the <input> tags can vary, so we can't rely on how they are arranged 642 // in most documents. 643 for (var i = 0; i < inputs.length; i++) 644 { 645 /**************/ 646 /* VERTICES */ 647 /**************/ 648 if(inputs[i].getAttribute("semantic") == "VERTEX") 649 { 650 this.vertexOffset = inputs[i].getAttribute("offset"); 651 // need to remove the leading # from the value 652 this.vertexSource = inputs[i].getAttribute("source").split('#')[1]; 653 654 var vertices = c3dl.ColladaLoader.getNodeWithAttribute(xmlObject,"vertices", "id", this.vertexSource); 655 656 // get the child 657 var input = vertices.getElementsByTagName("input")[0]; 658 659 // get the <input>s source 660 var posSource = input.getAttribute("source").split('#')[1]; 661 662 // the raw data in a long list of floats, we have to group this so the face indices 663 // can index into it. 664 var data = this.getData(xmlObject,"source", "id", posSource); 665 vertexStride = parseInt(data.stride); 666 667 verticesArray = this.groupScalarsIntoArray(data.values, 3,vertexStride); 668 } 669 670 /**************/ 671 /* NORMAL */ 672 /**************/ 673 else if(inputs[i].getAttribute("semantic") == "NORMAL") 674 { 675 this.normalOffset = inputs[i].getAttribute("offset"); 676 // need to remove the leading # from the value 677 this.normalSource = inputs[i].getAttribute("source").split('#')[1]; 678 var data = this.getData(xmlObject, "source", "id", this.normalSource); 679 normalsStride = parseInt(data.stride); 680 // length * stride instead of literal? 681 normalsArray = this.groupScalarsIntoArray(data.values, 3,normalsStride); 682 } 683 684 /**************/ 685 /* TEXCOORD */ 686 /**************/ 687 else if(inputs[i].getAttribute("semantic") == "TEXCOORD") 688 { 689 this.texCoordOffset = inputs[i].getAttribute("offset"); 690 // need to remove the leading # from the value 691 692 var uvSource = inputs[i].getAttribute("source").split('#')[1]; 693 var data = this.getData(xmlObject,"source", "id", uvSource); 694 texCoordsStride = parseInt(data.stride); 695 696 // OpenGL ES seems to expect the bottom left corner of the 697 // texture in the top left, so we need to flip the texture coordinates 698 // start at 1, for V. 699 for (var currUV = 1; currUV < data.values.length; currUV+=texCoordsStride) 700 { 701 data.values[currUV] = 1 - data.values[currUV]; 702 } 703 704 // we only want 2 values stored, but we may have to stride either 2 or 705 // 3 depending if a zero was added 706 // 1.0 1.0 0.0 707 // ^ don't need this. 708 texCoordsArray = this.groupScalarsIntoArray(data.values, 2,texCoordsStride); 709 } 710 } 711 712 713 // We now have the faces for a collation element and its input data streams, 714 // however if the collation element is a <polylist> or <polygon>, we'll need to 715 // re-arrange some of the faces since OpenGLES does not support quads. 716 // The next two conditionals handle these two cases. 717 718 719 720 // <polylist> contains a list of polygons, each which can contains a different number 721 // of vertices (one poly has 3 another has 5). Since we can't render polygons or even 722 // quads in GLES, the polygons need to be broken down into triangles. Note, it is assumed 723 // only simple convex polygons are used. (no intersections are present and no polygons are 724 // concave). If there are intersections or the polygons are concave, they will be represented 725 // incorrectly. 726 if(collations[currColl].nodeName == "polylist") 727 { 728 rawFaces = this.splitPolylist(collations[currColl], inputs.length, rawFaces); 729 } 730 731 // before we group the individual values in the faces array into 732 // arrays so we can easily address values in the arrays for vertices, 733 // textures, etc, we have to convert the quads into triangles since 734 // OpenGLES does not support the QUADS primitive mode. 735 else if( collations[currColl].nodeName == "polygons") 736 { 737 // example looks like this: 738 // 0,0,1,1,2,2,3,3,4,4,.... 739 // here is a quad and collada states its winding 740 // order is counter clockwise, so our goal 741 // to convert it to: 742 // 0,0, 1,1, 3,3 3,3 1,1 2,2 743 // which is 2 triangles. 744 745 // first thing is to seperate the individual numbers into 'parts' 746 // 0,0 is one part, 2,2 is another part. If there are more 747 // inputs, we have to account for that and our parts will be 748 // larger 749 var partSize = inputs.length; 750 751 // make a new list which uses triangles and which will overrite the quads list. 752 var trianglesList = []; 753 754 // knowing the partSize, we can use it as a stride to create a new face list 755 756 // count is an attribute of polygons which lists how many primitives the 757 // polygon has. we can use this data to find out how many times we have to 758 // change the parts. 759 for (var currPrim = 0; currPrim < collations[currColl].getAttribute("count"); currPrim++) 760 { 761 var partsArray = []; 762 763 // make an array of array so we can easily index parts 764 // use four since a polygon primitive is defined as having 4 points 765 for (var currPart = 0; currPart < 4; currPart++) 766 { 767 var part = []; 768 for(currScalar = 0; currScalar < inputs.length; currScalar++) 769 { 770 part.push(rawFaces[(currPrim * inputs.length * 4) + (currPart * partSize) + currScalar]); 771 } 772 partsArray.push(part); 773 } 774 775 // need to push on the RAW values, don't push on arrays. 776 // TODO: write a function for this. 777 for (var s=0; s < partsArray[0].length; s++){trianglesList.push(partsArray[0][s]);} 778 for (var s=0; s < partsArray[1].length; s++){trianglesList.push(partsArray[1][s]);} 779 for (var s=0; s < partsArray[3].length; s++){trianglesList.push(partsArray[3][s]);} 780 for (var s=0; s < partsArray[3].length; s++){trianglesList.push(partsArray[3][s]);} 781 for (var s=0; s < partsArray[1].length; s++){trianglesList.push(partsArray[1][s]);} 782 for (var s=0; s < partsArray[2].length; s++){trianglesList.push(partsArray[2][s]);} 783 } 784 // now we can overrite what rawFaces had in it 785 rawFaces = trianglesList; 786 }// if polygons 787 788 // we don't need a case for triangles since 789 790 // now that we know how many inputs there were, we can group the faces. 791 faces = this.groupScalarsIntoArray(rawFaces,inputs.length, inputs.length); 792 793 // each primitive collation element can have a material name. this name matches to the 794 // <instance_material>'s symbol attribute value. 795 collationElement.tempMaterial = collations[currColl].getAttribute("material"); 796 collationElement.init( this.expandFaces(faces,verticesArray,this.vertexOffset, vertexStride), 797 this.expandFaces(faces,normalsArray,this.normalOffset, normalsStride), 798 this.expandFaces(faces,texCoordsArray,this.texCoordOffset,2)); 799 800 geometry.addPrimitiveSet(collationElement); 801 } // end iterating over collations 802 803 804 805 // get the texture for the geometry 806 // go back to the instance_geometry 807 808 // <instance_geometry> has either 0 or 1 <bind_material>, so just get the first if it exsits. 809 var bind_material = instanceGeometryElement.getElementsByTagName("bind_material")[0]; 810 if( bind_material) 811 { 812 // <bind_material> has exactly 1 <technique_common>, so only get the first. 813 var technique_common = bind_material.getElementsByTagName("technique_common")[0]; 814 815 // technique_common within instance geometry may have many materials. 816 var instance_materials = technique_common.getElementsByTagName("instance_material"); 817 818 // iterate over all the <instance_material>'s <technique_common> has. 819 for(var im = 0;im < instance_materials.length;im++) 820 { 821 // target is the target material to instantiate 822 var target = instance_materials[im].getAttribute("target").split('#')[1]; 823 824 // symbol links to the primitive collation's material attribute. 825 var symbol = instance_materials[im].getAttribute("symbol"); 826 827 // we now have the material ID which we can look up in the library materials. 828 var material = this.findElementInLibrary(xmlObject, "library_materials", "material", target); 829 830 // 831 var matAndTex = this.instantiateMaterial(xmlObject, target); 832 var instanceMaterial = matAndTex[0]; 833 var tex = matAndTex[1]; 834 835 var GeoCollations = geometry.getPrimitiveSets(); 836 837 // The material was instantiated and now we have to iterate over the collations 838 // and find the collation which has a material which matches the material name. 839 for(var ic = 0; ic < GeoCollations.length; ic++) 840 { 841 if( GeoCollations[ic].tempMaterial == symbol) 842 { 843 GeoCollations[ic].setMaterial(instanceMaterial); 844 GeoCollations[ic].setTexture(tex); 845 } 846 } 847 }// instance_materials loop 848 }// bind_material conditional 849 850 return geometry; 851 } 852 853 /** 854 @private 855 @param {XMLDocument} xmlObject 856 @param {String} url 857 858 @returns {c3dl.SceneNode} 859 */ 860 this.instantiateNode = function(xmlObject, url) 861 { 862 var root = xmlObject.documentElement; 863 var libraryNodes = root.getElementsByTagName("library_nodes"); 864 865 // domNode 866 var nodeToCreate = null; 867 868 // <collada> may have many <library_node>. We'll need to go through each to find 869 // the node with the name we are looking for. 870 for (var currLib = 0; currLib < libraryNodes.length /*&& nodeToCreate == null*/; currLib++) 871 { 872 // get all the nodes in this library. 873 var nodes = libraryNodes[currLib].getElementsByTagName("node"); 874 875 // find the node in the list. 876 for(var currNode = 0; currNode < nodes.length /*&& nodeToCreate == null*/; currNode++) 877 { 878 if( nodes[currNode].getAttribute("id") == url) 879 { 880 // found it 881 nodeToCreate = nodes[currNode]; 882 } 883 } 884 } 885 886 var inode = new c3dl.SceneNode(); 887 inode.setName(nodeToCreate.getAttribute("name")); 888 889 this.parseNodeRecursive(xmlObject, nodeToCreate, inode); 890 891 return inode; 892 } 893 894 /** 895 @private 896 This function takes a list of scalar values and 897 groups them into elements inside an array. This 898 must be done since in the DAE file, the values are 899 stored one after another in a linear format. Since 900 the triangles/faces use indices to access these scalars, 901 we have to group them together so indexing will work 902 properly. 903 904 @param {Array} rawScalarValues 905 @param {int} numComponentsPerElement 906 @param {int} stride 907 908 @returns {Array} 909 */ 910 this.groupScalarsIntoArray = function(rawScalarValues,numComponentsPerElement, stride ) 911 { 912 // start off with an empty list 913 var listOfArrays = []; 914 915 // 916 for (var i = 0; i < rawScalarValues.length; i+= stride ) 917 { 918 var element = []; 919 920 // 921 for (var j = i; j < i+numComponentsPerElement; j++) 922 { 923 element.push(rawScalarValues[j]); 924 } 925 926 listOfArrays.push(element); 927 } 928 929 return listOfArrays; 930 } 931 932 933 /** 934 @private 935 936 @param {} collation 937 @param {int} numInputs 938 @param {Array} rawFaces 939 */ 940 this.splitPolylist = function(collation, numInputs, rawFaces) 941 { 942 // rawFaces = this.splitPolylist(collations[currColl], inputs.length); 943 944 // <vcount> has the count of vertices for each polygon. 945 // <vcount> 4 4 4 3 4 5 3 4 .. </vcount> 946 var vcountNode = this.getFirstChildByNodeName(collation,"vcount"); 947 var vcountList = this.mergeChildData(vcountNode.childNodes).split(" "); 948 949 // this counter keeps track of where we are in the vcount list 950 // <vcount>4 4 3 4 4 4 5 ... 4 4 3 4</vcount> 951 var vcountIndex = 0; 952 953 // 954 var primOffset = 0; 955 956 // since we can only support triangles, we have to make a triangle list 957 // and convert any quads to triangles. 958 var trianglesList = []; 959 960 // 961 var partSize = numInputs; 962 963 // iterate from the 0 to vcount, this is the number of primitives 964 // there are in this mesh. Primitives in a polylist may have 965 // more than 4 vertices, we will solve this by making the 966 // triangle fans. 967 // Vertices, according to the spec, will be counter clockwise. Because 968 // of this, we should be able to make fans without problems. 969 for (var currPrim = 0; currPrim < collation.getAttribute("count"); currPrim++, vcountIndex++) 970 { 971 var partsArray = []; 972 973 // the current number in the vcount list may have different values depending, may have 3, 4 or more 974 // so we have to iterate for the amount of values in the primitive. 975 for (var currPart = 0; currPart < vcountList[vcountIndex]; currPart++) 976 { 977 var part = []; 978 979 for(currScalar = 0; currScalar < numInputs; currScalar++) 980 { 981 // first part (primOffset * inputs.length) indexes into the primitive 982 // part. Second part will index into the specific part we want. 983 part.push(rawFaces[ (primOffset * numInputs) + (currPart * numInputs) + currScalar]); 984 } 985 partsArray.push(part); 986 } 987 // set the new value for the primitive offset 988 // since the number of vertices vary in a list of primitives, 989 // we just keep track how many we have to skip here. 990 // 4 3 4 3 3 991 // we can't just calculate how many values we have to skip, we acutally 992 // have to keep the total count. 993 primOffset += parseInt(vcountList[vcountIndex]); 994 995 // the way we will create a triangle fan is by creating a pivot 996 // vertex and keep track of the last vertex added. 997 // everytime a new vertex is added we will create a triangle from 998 // the pivot, the last vertex and the new one. 999 1000 // start this as index 1. the first time this main loop runs, 1001 // last will actually be the middle vertex, from then on, it 1002 // will act as the last one added since before the iteration ends 1003 // we set last to be the fanIndex 1004 var last = 1; 1005 var firstTriangle = true; 1006 // create a triangle fan 1007 for (var fanIndex = 0; fanIndex < vcountList[vcountIndex]-1; ) 1008 { 1009 // s iterator is for scalar, we are dealing with single values. 1010 // push on the first 1011 for (var s=0; s < partsArray[0].length; s++) 1012 { 1013 // first 1014 trianglesList.push(partsArray[0][s]); 1015 } 1016 fanIndex++; 1017 1018 // last 1019 for (var s=0; s < partsArray[0].length; s++) 1020 { 1021 trianglesList.push(partsArray[last][s]); 1022 } 1023 1024 // if this is the first iteration, increase the fanIndex. 1025 if( firstTriangle) 1026 { 1027 fanIndex++; 1028 firstTriangle = false; 1029 } 1030 1031 for (var s=0; s < partsArray[0].length; s++) 1032 { 1033 trianglesList.push(partsArray[fanIndex][s]); 1034 } 1035 1036 last = fanIndex; 1037 } 1038 } 1039 // overwrite rawFaces with the triangle faces, as if quads never existed. 1040 //rawFaces = trianglesList; 1041 return trianglesList; 1042 } 1043 1044 1045 /** 1046 @private 1047 @param {XMLDocument} xmlObject 1048 @param {String} libraryName 1049 @param {String} elementName 1050 @param {String} elementAttributeId 1051 1052 @returns 1053 */ 1054 this.findElementInLibrary = function(xmlObject, libraryName, elementName, elementAttributeId) 1055 { 1056 // collada may have many listings of the same library. 1057 var libraries = xmlObject.getElementsByTagName(libraryName); 1058 1059 // for all the libraries in the <collada> element 1060 for(libraryIter = 0;libraryIter < libraries.length; libraryIter++) 1061 { 1062 var elements = libraries[libraryIter].getElementsByTagName(elementName); 1063 1064 // for all the elements in each library. 1065 for(elementIter = 0; elementIter < elements.length; elementIter++) 1066 { 1067 // found it 1068 if(elementAttributeId == elements[elementIter].getAttribute("id")) 1069 { 1070 return elements[elementIter]; 1071 } 1072 } 1073 } 1074 // return null if it could not be found. 1075 return null; 1076 } 1077 1078 1079 /** 1080 @private 1081 @param {} xmlObject 1082 @param {String} nodeName 1083 @param {String} attributeKey 1084 @param {String} attributeValue 1085 1086 @return 1087 */ 1088 this.getData = function(xmlObject, nodeName, attributeKey, attributeValue) 1089 { 1090 var data = new Object(); 1091 1092 // find node that has 'src' value as id 1093 var nsrc = c3dl.ColladaLoader.getNodeWithAttribute(xmlObject,"source", "id", attributeValue); 1094 // go to the node's technique common 1095 var tech_common = nsrc.getElementsByTagName("technique_common")[0]; 1096 1097 // go to its accessor 1098 var accessor = tech_common.getElementsByTagName("accessor")[0]; 1099 data.stride = accessor.getAttribute("stride"); 1100 1101 // get the source 1102 var accessorSrc = accessor.getAttribute("source").split("#")[1]; 1103 1104 // 1105 var float_array = c3dl.ColladaLoader.getNodeWithAttribute(xmlObject,"float_array", "id", accessorSrc); 1106 1107 //values in the DAE file are seperated with a space 1108 // don't use nodeValue since it will be broken up in 4096 chunks 1109 data.values = this.mergeChildData(float_array.childNodes).split(" "); 1110 return data; 1111 } 1112 1113 /** 1114 @private 1115 @param {Array} faces A 2D array which has indices. Each index points 1116 to a set of vertex, normal or texture coordinates. 1117 1118 <pre> 1119 // faces can look like this: 1120 [ 1121 [0,0,0] , 1122 [1,0,1] , 1123 [2,0,2] 1124 ] 1125 // first value in each element is vertex index. 1126 // second value is normal index. 1127 // third value is uv index. 1128 </pre> 1129 1130 @param {Array} array The array to expand. For a cube, the array of vertices 1131 can look like: 1132 1133 <pre> 1134 [1.00000,1.00000,-1.00000] , [1.00000,-1.00000,-1.00000] , [-1.00000,-1.00000,-1.00000] 1135 </pre> 1136 1137 1138 @param offset Refers to the component we are interested in within an array 1139 in the faces array. Since the faces array has indices for verts, normals and texcoords, 1140 using a different index gets us a index which is a 0 based index into a list of 1141 coordinates. 1142 1143 <pre> 1144 here is a faces array. If we used offset=2, we would get the following indices: 1145 [0,0,0] , [1,0,1] , [2,0,2] 1146 ^ ^ ^ 1147 </pre> 1148 1149 @param {int} numComponentsToExpand 1150 */ 1151 this.expandFaces = function(faces, array, offset, numComponentsToExpand) 1152 { 1153 // this is a single dimensional array which hold expanded values. 1154 var expandedArray = []; 1155 1156 // in the nested loop, we divide the instructions into parts to 1157 // make it easier to read, but don't allocate these varialbes everytime 1158 // the loop is executed, so declare them here. This speeds up the parser 1159 // quite a bit. 1160 var face; 1161 var coordIndex; 1162 var coord; 1163 var floatValue; 1164 1165 // for all the faces 1166 // [0,0,0] , [1,0,1] , [2,0,2] 1167 // \ / \ / \ / 1168 // \/ \/ \/ 1169 // currFace=0 currFace=1 ... 1170 for(var currFace = 0; currFace < faces.length; currFace++) 1171 { 1172 // have to be scalar, no arrays 1173 1174 // each element in the faces array is another array. That second array 1175 // hold components. We iterate 1176 for (var currComp = 0; currComp < numComponentsToExpand; currComp++) 1177 { 1178 // go to a particular face: 1179 // [3, 1, 4] 1180 face = faces[currFace]; 1181 1182 1183 // we might be dealing with verts, norms or textures, each has a different offset. 1184 // then go to a particular value inside denoted by the offset. 1185 // [3, 1, 4] 1186 // ^ 1187 // offset = 2 for tex 1188 // 4 is an index which refers to the 4th set of tex coords. 1189 // something like: 1190 // ["1.0", "0.0"] 1191 coordIndex = face[offset]; 1192 1193 // coord is a vertex coord, normal or uv coord retrieved from the 1194 // 'array' array. 1195 // 1196 // ["29.4787", "0", "50.5349"] -> array[0] 1197 // ["29.4787", "0", "50.5349"] -> array[1] 1198 // ... 1199 // this is done for each component, for textures, we have 2 components. 1200 // for normals, we have 3. 1201 if(coordIndex) 1202 { 1203 coord = array[coordIndex][currComp]; 1204 } 1205 1206 // we have the value, but its a string so we have to convert it to a number. 1207 floatVal = parseFloat(coord); 1208 1209 // insert that value into the 1d array. 1210 expandedArray.push(floatVal); 1211 } 1212 } 1213 return expandedArray; 1214 } 1215 1216 /** 1217 @private 1218 Loading is done once all the nodes in the .DAE file have been created and placed 1219 into the ModelManager. 1220 1221 @returns {bool} true if loading is done, false otherwise. 1222 */ 1223 this.doneLoading = function() 1224 { 1225 return this.done; 1226 } 1227 1228 /** 1229 @private 1230 1231 get data from blinn, phong, lambert node 1232 1233 @param {} 1234 @param {String} str 1235 */ 1236 this.getColor = function(node, str) 1237 { 1238 // ambient, diffuse, specular, etc. 1239 var component = node != null ? node.getElementsByTagName(str)[0] : null; 1240 1241 // if the component has a reference parameter, ignore it for now. 1242 // that component will not be used in the calculations. 1243 var returnValue = [0,0,0]; 1244 1245 // if the component is present (ambient, diffuse, specular) 1246 if(component) 1247 { 1248 var value = this.getChoice(component,["color", "float", "texture"]); 1249 1250 if(value.nodeName == "color" ) 1251 { 1252 returnValue = []; 1253 for (var currNode = 0; currNode < value.childNodes.length; currNode++) 1254 { 1255 returnValue += value.childNodes[currNode].nodeValue; 1256 } 1257 returnValue = returnValue.split(" "); 1258 returnValue = [parseFloat(returnValue[0]),parseFloat(returnValue[1]),parseFloat(returnValue[2])]; 1259 returnValue = returnValue.slice(0,3); 1260 } 1261 // 1262 else if(value.nodeName == "float" ) 1263 { 1264 returnValue = parseFloat(value.childNodes[0].nodeValue); 1265 } 1266 // 1267 else if(value.nodeName == "texture") 1268 { 1269 returnValue = [1,1,1]; 1270 } 1271 } 1272 1273 return returnValue; 1274 } 1275 1276 1277 1278 /** 1279 @private 1280 When reading data in between tags, 1281 1282 There tends to be a lot of data between tags such as <float_array> 1283 <float_array id="Teapot-mesh-positions-array" count="1590">29.4787 0 50.5349 ..... 34.093 </float_array> 1284 1285 We can't just read the node value contents because it is broken up into 4096 byte chunks. So 1286 we use this function to merge all the chunks together. 1287 1288 @param {String} childNodes 1289 1290 @returns 1291 */ 1292 this.mergeChildData = function(childNodes) 1293 { 1294 var values = []; 1295 for (var currNode = 0; currNode < childNodes.length; currNode++) 1296 { 1297 values += childNodes[currNode].nodeValue; 1298 } 1299 1300 // there are some 3d authoring tools which place a trailing space after a long set of data. 1301 // for example: 1302 // 1303 // <float_array id="Teapot-mesh-positions-array" count="1590">29.4787 0 50.5349 ..... 34.093 </float_array> 1304 // 1305 // the trailing space causes problems as when the string is split, an undefined value 1306 // is placed at the end of the array. So we will get rid of any trailing spaces here. 1307 // 1308 return values.replace(/\s+$/,''); 1309 } 1310 1311 1312 1313 /** 1314 @private 1315 Get the first child of type 'nodeName' of a element 'searchNode'. 1316 1317 @param {Object Element} searchNode - the element to search. 1318 @param {String} nodeName - the node to search for. 1319 1320 @returns 1321 */ 1322 this.getFirstChildByNodeName = function(searchNode, nodeName) 1323 { 1324 for (var i=0; i < searchNode.childNodes.length; i++ ) 1325 { 1326 // found it! 1327 if( searchNode.childNodes[ i ].nodeName == nodeName ) 1328 { 1329 return searchNode.childNodes[ i ]; 1330 } 1331 } 1332 return null; 1333 } 1334 1335 } 1336 1337 1338 /** 1339 @private 1340 1341 static method of collada loader. 1342 1343 @param {} xmlObject 1344 @param {String} nodeName 1345 @param {String} attributeKey 1346 @param {String} attributeValue 1347 1348 @returns 1349 */ 1350 c3dl.ColladaLoader.getNodeWithAttribute = function(xmlObject, nodeName, attributeKey, attributeValue) 1351 { 1352 var nodeFound; 1353 1354 // go to the root of the XML 1355 var root = xmlObject.documentElement; 1356 1357 // get all the tags names 'nodeName' 1358 var elements = root.getElementsByTagName(nodeName); 1359 1360 // we might have a few tags, we need to find the one with the id specified 1361 for (var i = 0; i < elements.length; i++) 1362 { 1363 if( elements[i].getAttribute(attributeKey) == attributeValue) 1364 { 1365 nodeFound = elements[i]; 1366 } 1367 } 1368 return nodeFound; 1369 } 1370 1371 1372 /** 1373 @private 1374 Get the child nodes of searchNode which have the name 'nodeName'. 1375 This funciton is not recursive. It only returns the <b>direct</b> children. 1376 1377 @param {String} searchNode - the node to search. 1378 @param {String} nodeName - the nodes to search for. 1379 1380 @returns {Array} array of node elements. 1381 */ 1382 c3dl.ColladaLoader.getChildNodesByNodeName = function(searchNode, nodeName) 1383 { 1384 var children = []; 1385 var foundOne = false; 1386 1387 if(searchNode.childNodes.length > 0) 1388 { 1389 for (var i = 0; i < searchNode.childNodes.length; i++) 1390 { 1391 if(searchNode.childNodes[i].nodeName == nodeName) 1392 { 1393 children.push( searchNode.childNodes[i]); 1394 foundOne = true; 1395 } 1396 } 1397 } 1398 1399 // somehow need to specify if non were found, so 1400 // send back a null if that's the case. 1401 if(foundOne == false) 1402 { 1403 children = null; 1404 } 1405 1406 return children; 1407 } 1408 1409 1410 1411 /** 1412 @private 1413 static method of c3dl.ColladaLoader 1414 1415 Turn a series of strings seperated by 'delimiter' into float values. 1416 used when we need to convert <translate>0.0 0.0 0.0</translate> into float 1417 values. 1418 1419 @param {String} numbers A string which contains numbers such as "1.0 0.0 1.0 0.0 0.0 1.0 ..." note the spaces. 1420 @param {String} delimeter A string which is being used to separate the numbers. 1421 */ 1422 c3dl.ColladaLoader.stringsToFloats = function(numbers, delimeter) 1423 { 1424 var floatValues = []; 1425 1426 // Get rid of leading and trailing spaces so they don't interfere with out split(). 1427 1428 // remove leading whitespace 1429 var trimmedNumbers = numbers.replace(/^\s+/, ''); 1430 1431 // remove trailing whitespace 1432 trimmedNumbers = trimmedNumbers.replace(/\s+$/, ''); 1433 1434 // remove superfluous whitespace between numbers 1435 // find one or more instances of whitespace and replace with a single space. 1436 trimmedNumbers = trimmedNumbers.replace(/\s+/g,' ' ); 1437 1438 // split each number into an element of an array, but they are still 1439 // strings, so convert them to float. 1440 var strValues = trimmedNumbers.split(delimeter); 1441 1442 for (var i = 0; i < strValues.length; i++) 1443 { 1444 floatValues.push(parseFloat(strValues[i])); 1445 } 1446 1447 return floatValues; 1448 } 1449