1 /*
  2  * DAELoader.js 2017
  3  *
  4  * Sweet Home 3D, Copyright (c) 2017 Emmanuel PUYBARET / eTeks <info@eteks.com>
  5  *
  6  * This program is free software; you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation; either version 2 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * This program is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with this program; if not, write to the Free Software
 18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
 19  */
 20 
 21 // Requires core.js
 22 //          gl-matrix-min.js
 23 //          jszip.min.js
 24 //          scene3d.js
 25 //          Triangulator.js
 26 //          URLContent.js
 27 //          ModelLoader.js
 28 //          jsXmlSaxParser.min.js
 29 
 30 /**
 31  * Creates a loader for DAE Collada 1.4.1 format as specified by
 32  * <a href="http://www.khronos.org/files/collada_spec_1_4.pdf">http://www.khronos.org/files/collada_spec_1_4.pdf</a>.
 33  * All texture coordinates are considered to belong to the same set (for example UVSET0).<br>
 34  * @constructor
 35  * @extends ModelLoader
 36  * @author Emmanuel Puybaret
 37  */
 38 function DAELoader() {
 39   ModelLoader.call(this, "dae");
 40 }
 41 DAELoader.prototype = Object.create(ModelLoader.prototype);
 42 DAELoader.prototype.constructor = DAELoader;
 43 
 44 /**
 45  * Parses the given DAE content and calls onmodelloaded asynchronously or 
 46  * returns the scene it describes if onmodelloaded is null.
 47  * @protected
 48  */
 49 DAELoader.prototype.parseEntryScene = function(daeContent, daeEntryName, zip, modelContext, onmodelloaded, onprogression) {
 50   var sceneRoot = new Group3D();
 51   var handler = new DAEHandler(sceneRoot, daeEntryName);
 52   var saxParser = new SAXParser(handler, handler, handler, handler, handler);
 53   onprogression(ModelLoader.PARSING_MODEL, daeEntryName, 0);
 54   try {
 55     saxParser.parseString(daeContent);
 56   } catch (ex) {
 57     sceneRoot.removeAllChildren();
 58   }
 59   if (onmodelloaded === null) {
 60     onprogression(ModelLoader.PARSING_MODEL, daeEntryName, 1);
 61     return sceneRoot;
 62   } else {
 63     setTimeout(
 64         function() {
 65           onprogression(ModelLoader.PARSING_MODEL, daeEntryName, 1);
 66           onmodelloaded(sceneRoot);
 67         }, 0);
 68   }
 69 }
 70 
 71 /**
 72  * SAX handler for DAE Collada stream.
 73  * @constructor
 74  * @private
 75  */
 76 function DAEHandler(sceneRoot, daeEntryName) {
 77   DefaultHandler.call(this);
 78   this.sceneRoot = sceneRoot;
 79   this.daeEntryName = daeEntryName;
 80   
 81   this.parentGroups = []; // Group3D
 82   this.parentElements = []; // string
 83   this.buffer = "";
 84   this.postProcessingBinders = []; // function
 85 
 86   this.textures = {}; // string
 87   this.effectAppearances = {}; // Appearance3D
 88   this.materialEffects = {}; // string
 89   this.materialNames = {};  // string
 90   this.surface2DIds = {}; // string
 91   this.sampler2DIds = {}; // string
 92   this.geometries = {}; // IndexedGeometryArray3D []
 93   this.sources = {}; // number []
 94   this.positions = {}; // number []
 95   this.normals = {}; // number []
 96   this.textureCoordinates = {}; // number []
 97   this.floatArrays = {}; // number []
 98   this.sourceAccessorStrides = [];
 99   this.appearanceGeometries = {}; // IndexedGeometryArray3D
100   this.facesAndLinesPrimitives = []; // number [][]
101   this.polygonsPrimitives = []; // number [][]
102   this.polygonsHoles = []; // number [][][]
103   this.nodes = {}; // TransformGroup3D
104   this.controllersSkinMeshes = {}; // string
105   this.instantiatedNodes = {}; // SharedGroup3D
106   this.visualScenes = {}; // TransformGroup3D
107   this.visualScene = null;
108   this.floats = null; // number []
109   this.geometryVertices = []; // number []
110   this.geometryNormals = []; // number []
111   this.geometryTextureCoordinates = []; // number []
112   this.vcount = []; // number []
113   this.transparentColor = []; // number []
114   this.transparency = null;
115 
116   this.inRootAsset = false;
117   this.reverseTransparency = false;
118   this.imageId = null;
119   this.materialId = null;
120   this.effectId = null;
121   this.newParamSid = null;
122   this.inSurface2D = false;
123   this.inPhongBlinnOrLambert = false;
124   this.techniqueProfile = null;
125   this.inConstant = false;
126   this.geometryId = null;
127   this.meshSourceId = null;
128   this.verticesId = null;
129   this.floatArrayId = null;
130   this.controllerId = null;
131   this.geometryAppearance = null;
132   this.geometryVertexOffset = 0;
133   this.geometryNormalOffset = 0;
134   this.geometryTextureCoordinatesOffset = 0;
135   this.axis = null;
136   this.meterScale = 1.;
137   this.floatValue = 0.;
138   this.opaque = null;
139   this.inputCount = 0;
140 }
141 DAEHandler.prototype = Object.create(DefaultHandler.prototype);
142 DAEHandler.prototype.constructor = DAEHandler;
143 
144 DAEHandler.prototype.startElement = function(uri, localName, name, attributes) {
145   this.buffer = "";
146   var parent = this.parentElements.length === 0 
147       ? null 
148       : this.parentElements [this.parentElements.length - 1];
149   
150   if (parent === null && !("COLLADA" == name)) {
151     throw new SAXException("Expected COLLADA element");
152   } else if ("COLLADA" == name) {
153     var version = attributes.getValue("version");
154     if (version.indexOf("1.4") !== 0) {
155       throw new SAXException("Version " + version + " not supported");
156     }
157   } else if ("COLLADA" == parent && "asset" == name) {
158     this.inRootAsset = true;
159   } else if ("asset" == parent && "unit" == name) {
160     var value = attributes.getValue("meter");
161     if (value !== null) {
162       this.meterScale = parseFloat(value);
163     }
164   } else if ("image" == name) {
165     this.imageId = attributes.getValue("id");
166   } else if ("material" == name) {
167     this.materialId = attributes.getValue("id");
168     this.materialNames [this.materialId] = attributes.getValue("name");
169   } else if ("material" == parent && "instance_effect" == name) {
170     var effectInstanceUrl = attributes.getValue("url"); 
171     if (effectInstanceUrl.indexOf("#") === 0) {
172       var effectInstanceAnchor = effectInstanceUrl.substring(1);
173       this.materialEffects [this.materialId] = effectInstanceAnchor;
174     }
175   } else if ("effect" == name) {
176     this.effectId = attributes.getValue("id");
177     this.effectAppearances [this.effectId] = new Appearance3D();
178   } else if (this.effectId !== null) { 
179     if ("profile_COMMON" == parent && "newparam" == name) {
180       this.newParamSid = attributes.getValue("sid");
181     } else if ("newparam" == parent && "surface" == name 
182                && "2D" == attributes.getValue("type")) {
183       this.inSurface2D = true;
184     } else if ("extra" == parent && "technique" == name) {
185       this.techniqueProfile = attributes.getValue("profile");
186     } else if ("phong" == name || "blinn" == name) {
187       this.inPhongBlinnOrLambert = true;
188     } else if ("lambert" == name) {
189       this.inPhongBlinnOrLambert = true;
190       this.effectAppearances [this.effectId].setSpecularColor(vec3.fromValues(0, 0, 0));
191       this.effectAppearances [this.effectId].setShininess(1);
192     } else if ("constant" == name) {
193       this.inConstant = true;
194     } else if (this.inConstant || this.inPhongBlinnOrLambert 
195                && ("transparent" == name)) {
196       this.opaque = attributes.getValue("opaque");
197       if (this.opaque === null) {
198         this.opaque = "A_ONE";
199       }
200     } else if ("texture" == name && "diffuse" == parent) {
201       var textureId = this.surface2DIds [this.sampler2DIds [attributes.getValue("texture")]];
202       var appearance = this.effectAppearances [this.effectId];
203       var handler = this;
204       this.postProcessingBinders.push(function() {
205           // Resolve texture at the end of the document
206           appearance.imageEntryName = handler.textures [textureId];
207         });
208     }
209   } else if ("geometry" == name) {
210     this.geometryId = attributes.getValue("id");
211     this.geometries [this.geometryId] = [];
212   } else if (this.geometryId !== null) { 
213     if ("mesh" == parent && "source" == name) {
214       this.meshSourceId = attributes.getValue("id");
215     } else if ("mesh" == parent && "vertices" == name) {
216       this.verticesId = attributes.getValue("id");
217     } else if (this.meshSourceId !== null) {
218       if ("float_array" == name) {
219         this.floatArrayId = attributes.getValue("id");
220       } else if ("technique_common" == parent && "accessor" == name) {
221         var floatArrayAnchor = attributes.getValue("source").substring(1);
222         var stride = attributes.getValue("stride");
223         stride = stride !== null ? parseInt(stride) : 1;
224         this.sourceAccessorStrides.push(
225             {floatArray : this.floatArrays [floatArrayAnchor], stride : stride});
226       } 
227     } else if (this.verticesId !== null && "input" == name) {
228       var sourceAnchor = attributes.getValue("source").substring(1);
229       var source = this.sources [sourceAnchor];
230       if (source !== undefined) {
231         if ("POSITION" == attributes.getValue("semantic")) {
232           this.positions [this.verticesId] = new Array(source.length / 3);
233           for (var i = 0, k = 0; k < source.length; i++, k += 3) {
234             this.positions [this.verticesId][i] = vec3.fromValues(source [k], source [k + 1], source [k + 2]);
235           }
236         } else if ("NORMAL" == attributes.getValue("semantic")) {
237           this.normals [this.verticesId] = new Array(source.length / 3);
238           for (var i = 0, k = 0; k < source.length; i++, k += 3) {
239             this.normals [this.verticesId][i] = vec3.fromValues(source [k], source [k + 1], source [k + 2]);
240           }
241         } else if ("TEXCOORD" == attributes.getValue("semantic")) {
242           this.textureCoordinates [this.verticesId] = new Array(source.length / 2);
243           for (var i = 0, k = 0; k < source.length; i++, k += 2) {
244             this.textureCoordinates [this.verticesId][i] = vec2.fromValues(source [k], source [k + 1]);
245           }
246         }
247       }
248     } else if (this.verticesId === null && "input" == name) {
249       var sourceAnchor = attributes.getValue("source").substring(1);
250       var offset = parseInt(attributes.getValue("offset"));
251       if (this.inputCount < offset + 1) {
252         this.inputCount = offset + 1;
253       }
254       if ("VERTEX" == attributes.getValue("semantic")) {
255         this.geometryVertices = this.positions [sourceAnchor];
256         if (this.geometryVertices === undefined) {
257           this.geometryVertices = null;
258         }
259         this.geometryVertexOffset = offset;
260         if (this.geometryNormals === null) {
261           this.geometryNormals = this.normals [sourceAnchor];
262           this.geometryNormalOffset = offset;
263         }
264         if (this.geometryTextureCoordinates === null) {
265           this.geometryTextureCoordinates = this.textureCoordinates [sourceAnchor];
266           this.geometryTextureCoordinatesOffset = offset;
267         }
268       } else if ("NORMAL" == attributes.getValue("semantic")) {
269         var source = this.sources [sourceAnchor];
270         if (source !== undefined) {
271           this.geometryNormals = new Array(source.length / 3);
272           for (var i = 0, k = 0; k < source.length; i++, k += 3) {
273             this.geometryNormals [i] = vec3.fromValues(source [k], source [k + 1], source [k + 2]);
274           }
275           this.geometryNormalOffset = offset;
276         }
277       } else if ("TEXCOORD" == attributes.getValue("semantic")) {
278         var source = this.sources [sourceAnchor];
279         if (source !== undefined) {
280           this.geometryTextureCoordinates = new Array(source.length / 2);
281           for (var i = 0, k = 0; k < source.length; i++, k += 2) {
282             this.geometryTextureCoordinates [i] = vec2.fromValues(source [k], source [k + 1]);
283           }
284           this.geometryTextureCoordinatesOffset = offset;
285         }
286       }
287     } else if ("triangles" == name
288                || "trifans" == name
289                || "tristrips" == name
290                || "polylist" == name
291                || "polygons" == name
292                || "lines" == name
293                || "linestrips" == name) {
294       this.geometryAppearance = attributes.getValue("material");
295       this.inputCount = 0;
296       this.facesAndLinesPrimitives = [];
297       this.polygonsPrimitives = [];
298       this.polygonsHoles = [];
299       this.vcount = null;
300     } 
301   } else if ("controller" == name) {
302     this.controllerId = attributes.getValue("id");
303   } else if ("skin" == name) {
304     var skinSource = attributes.getValue("source");
305     if (skinSource.indexOf("#") === 0) {
306       var skinSourceAnchor = skinSource.substring(1);
307       this.controllersSkinMeshes [this.controllerId] = skinSourceAnchor;
308     }
309   } else if ("visual_scene" == name) {
310     var visualSceneGroup = new TransformGroup3D();
311     this.parentGroups.push(visualSceneGroup);
312     this.visualScenes [attributes.getValue("id")] = visualSceneGroup;
313   } else if ("node" == name) {
314     var nodeGroup = new TransformGroup3D();
315     if (this.parentGroups.length > 0) {
316       // Add node to parent node only for children nodes
317       this.parentGroups [this.parentGroups.length - 1].addChild(nodeGroup);
318     }
319     this.parentGroups.push(nodeGroup);
320     this.nodes [attributes.getValue("id")] = nodeGroup;
321     var nodeName = attributes.getValue("name");
322     if (nodeName !== null) {
323       nodeGroup.setName(nodeName);
324     }
325   } else if ("node" == parent 
326              && ("instance_geometry" == name
327                  || "instance_controller" == name)) {
328     var instanceUrl = attributes.getValue("url");
329     if (instanceUrl.indexOf("#") === 0) {
330       var inInstanceController = "instance_controller" == name;
331       var instanceAnchor = instanceUrl.substring(1);
332       var nodeName = attributes.getValue("name");
333       var parentGroup = new Group3D();
334       this.parentGroups [this.parentGroups.length - 1].addChild(parentGroup);
335       this.parentGroups.push(parentGroup);
336       var handler = this;
337       this.postProcessingBinders.push(function() {
338           var nameSuffix = 0;
339           // Resolve URL at the end of the document
340           var geometries = inInstanceController
341               ? handler.geometries [handler.controllersSkinMeshes [instanceAnchor]]
342               : handler.geometries [instanceAnchor];
343           for (var i = 0; i < geometries.length; i++) {
344             var shape = new Shape3D(geometries [i]);
345             parentGroup.addChild(shape);
346             // Give a name to shape 
347             if (nodeName !== null) {
348               if (nameSuffix === 0) {
349                 shape.setName(nodeName);
350               } else {
351                 shape.setName(nodeName + "_" + nameSuffix);
352               }
353               nameSuffix++;
354             }
355           }
356         });
357     }
358   } else if ("instance_node" == name) {
359     var nodeInstanceUrl = attributes.getValue("url");
360     if (nodeInstanceUrl.indexOf("#") === 0) {
361       var nodeInstanceAnchor = nodeInstanceUrl.substring(1);
362       var parentTransformGroup = this.parentGroups [this.parentGroups.length - 1];
363       var handler = this;
364       this.postProcessingBinders.push(function() {
365           // Resolve URL at the end of the document
366           var sharedGroup = handler.instantiatedNodes [nodeInstanceAnchor];
367           if (sharedGroup === undefined) {
368             sharedGroup = new SharedGroup3D();
369             sharedGroup.addChild(handler.nodes [nodeInstanceAnchor]);
370             handler.instantiatedNodes [nodeInstanceAnchor] = sharedGroup;
371           }
372           parentTransformGroup.addChild(new Link3D(sharedGroup));
373         });
374     }
375   } else if ("instance_material" == name && this.parentGroups.length > 0) {
376     var materialInstanceTarget = attributes.getValue("target");
377     if (materialInstanceTarget.indexOf("#") == 0) {
378       var materialInstanceAnchor = materialInstanceTarget.substring(1);
379       var materialInstanceSymbol = attributes.getValue("symbol");
380       var group = this.parentGroups [this.parentGroups.length - 1];
381       var handler = this;
382       this.postProcessingBinders.push(function() {
383           var appearance = handler.effectAppearances [handler.materialEffects [materialInstanceAnchor]];
384           var updateShapeAppearance = function(node, appearance, forceUpdate) {
385               if (node instanceof Group3D) {
386                 var updated = false;
387                 var children = node.getChildren();
388                 for (var i = 0; i < children.length; i++) {
389                   updated |= updateShapeAppearance(children[i], appearance, forceUpdate);
390                 }
391                 return updated;
392               } else if (node instanceof Link3D) {
393                 return updateShapeAppearance(node.getSharedGroup(), appearance, forceUpdate);
394               } else if (node instanceof Shape3D) {
395                 var geometries = forceUpdate
396                     ? node.getGeometries()
397                     : handler.appearanceGeometries [materialInstanceSymbol];
398                 if (geometries !== undefined) {
399                   for (var i = 0; i < geometries.length; i++) {
400                     if (geometries [i] === node.getGeometries() [0]) {
401                       node.setAppearance(appearance);
402                       return true;
403                     }
404                   }
405                 }
406               }
407               return false;
408             };
409             
410           if (!updateShapeAppearance(group, appearance, false)) {
411             updateShapeAppearance(group, appearance, true);
412           }
413           appearance.setName(handler.materialNames [materialInstanceAnchor]);
414         });
415     }
416   } else if ("instance_visual_scene" == name) {
417     var visualSceneInstanceUrl = attributes.getValue("url");
418     if (visualSceneInstanceUrl.indexOf("#") === 0) {
419       var visualSceneInstanceAnchor = visualSceneInstanceUrl.substring(1);
420       var handler = this;
421       this.postProcessingBinders.push(function() {
422           // Resolve URL at the end of the document
423           handler.visualScene = handler.visualScenes [visualSceneInstanceAnchor];
424         });
425     }
426   }
427   this.parentElements.push(name);
428 }
429   
430 DAEHandler.prototype.characters = function(ch, start, length) {
431   this.buffer += ch.substring(start, start + length);
432 }
433   
434 DAEHandler.prototype.endElement = function(uri, localName, name) {
435   this.parentElements.pop();
436   var parent = this.parentElements.length === 0 
437       ? null 
438       : this.parentElements [this.parentElements.length - 1];
439   
440   if ("color" == name
441       || "float_array" == name 
442       || "matrix" == name
443       || "rotate" == name
444       || "scale" == name
445       || "translate" == name) {
446     var floatValues = this.getCharacters().split(/\s/);
447     this.floats = new Array(floatValues.length);
448     var floatCount = 0;
449     for (var i = 0; i < floatValues.length; i++) {
450       if (floatValues [i].length > 0) {
451         var floatValue = parseFloat(floatValues [i]);
452         if (isNaN(floatValue)) {
453           // This may happen with some bad DAE files
454           floatValue = 0.;
455         }
456         this.floats [floatCount++] = floatValue;
457       }
458     }
459     if (floatCount !== floatValues.length) {
460       this.floats.splice(floatCount, this.floats.length - floatCount);        
461     }
462     if (this.floatArrayId !== null) {
463       this.floatArrays [this.floatArrayId] = this.floats;
464       this.floatArrayId = null;
465     }
466   } else if ("float" == name) {
467     this.floatValue = parseFloat(this.getCharacters());
468   }
469 
470   if (this.inRootAsset) {
471     this.handleRootAssetElementsEnd(name);
472   } else if ("image" == name) {
473     this.imageId = null;
474   } else if (this.imageId !== null) {
475     this.handleImageElementsEnd(name);
476   } else if ("material" == name) {
477     this.materialId = null;
478   } else if ("effect" == name) {
479     this.effectId = null;
480   } else if (this.effectId !== null) { 
481     this.handleEffectElementsEnd(name, parent);
482   } else if ("geometry" == name) {
483     this.geometryId = null;
484   } else if (this.geometryId !== null) {
485     this.handleGeometryElementsEnd(name, parent);
486   } else if ("controller" == name) {
487     this.controllerId = null;
488   } else if ("visual_scene" == name
489           || "node" == name
490           || "node" == parent && "instance_geometry" == name) {
491     this.parentGroups.pop();
492   } else if ("matrix" == name) {
493     var matrix = mat4.fromValues(
494         this.floats [0], this.floats [4], this.floats [8],  this.floats [12], 
495         this.floats [1], this.floats [5], this.floats [9],  this.floats [13],
496         this.floats [2], this.floats [6], this.floats [10], this.floats [14],
497         this.floats [3], this.floats [7], this.floats [11], this.floats [15]);
498     this.mulTransformGroup(matrix); 
499   } else if ("node" == parent && "rotate" == name) {
500     var rotation = mat4.create(); 
501     mat4.fromRotation(rotation, this.floats [3] * Math.PI / 180., 
502         vec3.fromValues(this.floats [0], this.floats [1], this.floats [2]));
503     this.mulTransformGroup(rotation);
504   } else if ("scale" == name) {
505     var scale = mat4.create();
506     mat4.scale(scale, scale, vec3.fromValues(this.floats [0], this.floats [1], this.floats [2]));
507     this.mulTransformGroup(scale);
508   } else if ("node" == parent && "translate" == name) {
509     var translation = mat4.create();
510     mat4.translate(translation, translation, vec3.fromValues(this.floats [0], this.floats [1], this.floats [2]));
511     this.mulTransformGroup(translation);
512   }
513 }
514 
515 /**
516  * Returns the trimmed string of last element value. 
517  * @private
518  */
519 DAEHandler.prototype.getCharacters = function() {
520   return this.buffer.trim();
521 }
522   
523 /**
524  * Handles the end tag of elements children of root "asset".
525  * @private
526  */
527 DAEHandler.prototype.handleRootAssetElementsEnd = function(name) {
528   if ("asset" == name) {
529     this.inRootAsset = false;
530   } else if ("up_axis" == name) {
531     this.axis = this.getCharacters(); 
532   } else if ("subject" == name) {
533     this.scene.setName(this.getCharacters());
534   } else if ("authoring_tool" == name) {
535     var tool = this.getCharacters();
536     // Try to detect if DAE file was created by Google SketchUp version < 7.1 
537     if (tool.indexOf("Google SketchUp") === 0) {
538       var sketchUpVersion = tool.substring("Google SketchUp".length).trim();
539       if (sketchUpVersion.length > 0) {
540         var dotIndex = sketchUpVersion.indexOf('.');
541         var majorVersionString = dotIndex === -1 
542             ? sketchUpVersion : sketchUpVersion.substring(0, dotIndex);
543         var majorVersion = parseInt(majorVersionString);
544         if (majorVersion < 7
545             || (majorVersion == 7
546                 && (dotIndex >= sketchUpVersion.length - 1 // No subversion
547                     || sketchUpVersion [dotIndex + 1] < '1'))) {
548           // From http://www.collada.org/public_forum/viewtopic.php?f=12&t=1667
549           // let's reverse transparency   
550           this.reverseTransparency = true;
551         }
552       }
553     }
554   }
555 }
556 
557 /**
558  * Handles the end tag of elements children of "image".
559  * @private
560  */
561 DAEHandler.prototype.handleImageElementsEnd = function(name) {
562   if ("init_from" == name) {
563     var imageName = this.getCharacters();
564     if (imageName.indexOf("./") === 0) {
565       // Remove leading dot
566       imageName = imageName.substring(2);
567     }
568     var lastSlash = this.daeEntryName.lastIndexOf("/");
569     if (lastSlash >= 0) {
570       // Build imageName path simplifying .. relative paths if necessary
571       var daeEntryNameParts = this.daeEntryName.split("/");
572       var imageNameParts = imageName.split("/");
573       daeEntryNameParts.splice(daeEntryNameParts.length - 1, 1);
574       while (imageNameParts [0] == ".." || imageNameParts [0] == ".") {
575         if (imageNameParts [0] == "..") {
576           daeEntryNameParts.splice(daeEntryNameParts.length - 1, 1);
577         }
578         imageNameParts.splice(0, 1);
579       }
580       imageName = "";
581       for (var i = 0; i < daeEntryNameParts.length; i++) {
582         imageName += daeEntryNameParts [i] + "/";
583       }
584       for (var i = 0; i < imageNameParts.length; i++) {
585         imageName += imageNameParts [i] + "/";
586       }
587       imageName = imageName.substring(0, imageName.length - 1);
588     }
589     this.textures [this.imageId] = imageName;
590   } else if ("data" == name) {
591     throw new SAXException("<data> not supported");
592   }
593 }
594 
595 /**
596  * Handles the end tag of elements children of "effect".
597  * @private
598  */
599 DAEHandler.prototype.handleEffectElementsEnd = function(name, parent) {
600   if ("profile_COMMON" == parent && "newparam" == name) {
601     this.newParamSid = null;
602   } else if ("newparam" == parent && "surface" == name) {
603     this.inSurface2D = false;
604   } else if (this.newParamSid !== null) {
605     if (this.inSurface2D && "init_from" == name) {
606       this.surface2DIds [this.newParamSid] = this.getCharacters();
607     } else if ("sampler2D" == parent && "source" == name) {
608       this.sampler2DIds [this.newParamSid] = this.getCharacters();
609     }
610   } else if ("extra" == parent && "technique" == name) {
611     this.techniqueProfile = null;
612   } else if ("phong" == name || "blinn" == name 
613              || "lambert" == name || "constant" == name) {
614     var transparencyValue;
615     if (this.transparentColor !== null) {
616       if ("RGB_ZERO" == this.opaque) {
617         transparencyValue = this.transparentColor [0] * 0.212671
618             + this.transparentColor [1] * 0.715160
619             + this.transparentColor [2] * 0.072169;
620         if (this.transparency !== null) {
621           transparencyValue *= this.transparency;
622         }
623       } else { // A_ONE
624         if (this.transparency !== null) {
625           transparencyValue = 1 - this.transparentColor [3] * this.transparency;
626         } else {
627           transparencyValue = 1 - this.transparentColor [3];
628         }
629         if (this.reverseTransparency) {
630           transparencyValue = 1 - transparencyValue;
631         }
632       }
633     } else {
634       transparencyValue = 0;
635     }
636     var appearance = this.effectAppearances [this.effectId];
637     if (transparencyValue > 0) {
638       appearance.setTransparency(transparencyValue); // 0 means opaque 
639     }
640     // Set default color if it doesn't exist yet
641     var defaultColor = this.transparentColor != null
642         ? vec3.fromValues(this.transparentColor [0], this.transparentColor [1], this.transparentColor [2])
643         : vec3.fromValues(0., 0., 0.);
644     if (!appearance.getAmbientColor()
645         && !appearance.getDiffuseColor()
646         && !appearance.getSpecularColor()) {
647       if (!appearance.getAmbientColor()) {
648         appearance.setAmbientColor(defaultColor);
649       }
650       if (!appearance.getDiffuseColor()) {
651         appearance.setDiffuseColor(defaultColor);
652       }
653       if (!appearance.getSpecularColor()) {
654         appearance.setSpecularColor(defaultColor);
655       }
656       if (!appearance.getShininess()) {
657         appearance.setShininess(1);
658       }
659       if ("constant" == name) {
660         // Set illumination to 0 for coloring attributes effect
661         this.effectAppearances [this.effectId].setIllumination(0);
662       }
663     }
664     this.transparentColor = null;
665     this.transparency = null;
666     
667     this.inPhongBlinnOrLambert = false;
668     this.inConstant = false;
669   } else if (this.inConstant || this.inPhongBlinnOrLambert) {
670     // Set appearance attributes
671     if ("color" == name) {
672       if ("emission" == parent) {
673         if (this.inPhongBlinnOrLambert) {
674           this.effectAppearances [this.effectId].setEmissiveColor(vec3.fromValues(
675               this.floats [0], this.floats [1], this.floats [2]));
676         } else { // inConstant
677           this.effectAppearances [this.effectId].setDiffuseColor(vec3.fromValues(
678               this.floats [0], this.floats [1], this.floats [2])); 
679           // Set illumination to 0 for coloring attributes effect
680           this.effectAppearances [this.effectId].setIllumination(0);
681         }
682       } else if ("ambient" == parent) {
683         this.effectAppearances [this.effectId].setAmbientColor(vec3.fromValues(
684             this.floats [0], this.floats [1], this.floats [2]));
685       } else if ("diffuse" == parent) {
686         this.effectAppearances [this.effectId].setDiffuseColor(vec3.fromValues(
687             this.floats [0], this.floats [1], this.floats [2])); // this.floats [3]
688       } else if ("specular" == parent) {
689         this.effectAppearances [this.effectId].setSpecularColor(vec3.fromValues(
690             this.floats [0], this.floats [1], this.floats [2]));
691       } else if ("transparent" == parent) {
692         this.transparentColor = this.floats;
693       }
694     } else if ("float" == name) {
695       if ("shininess" == parent) {
696         this.effectAppearances [this.effectId].setShininess(this.floatValue);
697       } else if ("transparency" == parent) {
698         this.transparency = this.floatValue;
699       }
700     }
701   } else if ("double_sided" == name 
702              && "1" == this.getCharacters()
703              && ("GOOGLEEARTH" == this.techniqueProfile
704                  || "MAX3D" == this.techniqueProfile
705                  || "MAYA" == this.techniqueProfile)) {
706     this.effectAppearances [this.effectId].setCullFace(Appearance3D.CULL_NONE);
707   }
708 }
709 
710 /**
711  * Handles the end tag of elements children of "geometry".
712  * @private
713  */
714 DAEHandler.prototype.handleGeometryElementsEnd = function(name, parent) {
715   if ("mesh" == parent && "source" == name) {
716     if (this.floats !== null) {
717       this.sources [this.meshSourceId] = this.floats;
718       this.floats = null;
719     }
720     this.meshSourceId = null;
721   } else if ("mesh" == parent && "vertices" == name) {
722     this.verticesId = null;
723   } else if ("p" == name
724              || "h" == name
725              || "vcount" == name) {
726     // Get integers
727     var intValues = this.getCharacters().split(/\s/);
728     var integers = new Array(intValues.length);
729     var intCount = 0;
730     for (var i = 0; i < intValues.length; i++) {
731       if (intValues [i].length > 0) {
732         integers [intCount++] = parseInt(intValues [i]);
733       }
734     }
735     if (intCount !== intValues.length) {
736       integers.splice(intCount, integers.length - intCount);        
737     }
738     
739     if ("ph" != parent && "p" == name) {
740       this.facesAndLinesPrimitives.push(integers);
741     } else if ("vcount" == name) { 
742       this.vcount = integers;
743     } else if ("ph" == parent) {
744       if ("p" == name) {
745         this.polygonsPrimitives.push(integers);
746       } else if ("h" == name) {
747         if (this.polygonsPrimitives.length > this.polygonsHoles.length) {
748           this.polygonsHoles.push([]);
749         }
750         this.polygonsHoles [this.polygonsPrimitives.length - 1].push(integers);
751       } 
752     }
753   } else if (("triangles" == name
754               || "trifans" == name
755               || "tristrips" == name
756               || "polylist" == name
757               || "polygons" == name
758               || "lines" == name
759               || "linestrips" == name)
760              // Ignore geometries with missing vertices
761              && this.geometryVertices !== null) {
762     var geometry;
763     if ("lines" == name
764         || "linestrips" == name) {
765       geometry = this.getLinesGeometry(name);
766     } else {
767       geometry = this.getFacesGeometry(name);
768     }
769     if (geometry !== null) {
770       this.geometries [this.geometryId].push(geometry);
771       if (this.geometryAppearance !== null) {
772         var geometries = this.appearanceGeometries [this.geometryAppearance];
773         if (geometries === undefined) {
774           geometries = [];
775           this.appearanceGeometries [this.geometryAppearance] = geometries;
776         }
777         geometries.push(geometry);
778       }
779     }
780     this.geometryAppearance = null;
781     this.geometryVertices = null;
782     this.geometryNormals = null;
783     this.geometryTextureCoordinates = null;
784     this.facesAndLinesPrimitives = [];
785     this.polygonsPrimitives = [];
786     this.polygonsHoles = [];
787     this.vcount = null;
788   }
789 }
790 
791 /**
792  * Returns the triangles or polygons geometry matching the read values.
793  * @private
794  */
795 DAEHandler.prototype.getFacesGeometry = function(name) {
796   var primitiveType;
797   if ("triangles" == name) {
798     primitiveType = GeometryInfo3D.TRIANGLE_ARRAY;
799   } else if ("trifans" == name) {
800     primitiveType = GeometryInfo3D.TRIANGLE_FAN_ARRAY;
801   } else if ("tristrips" == name) {
802     primitiveType = GeometryInfo3D.TRIANGLE_STRIP_ARRAY;
803   } else {
804     primitiveType = GeometryInfo3D.POLYGON_ARRAY;
805   }
806   
807   var geometryInfo = new GeometryInfo3D(primitiveType);
808   geometryInfo.setCoordinates(this.geometryVertices);
809   geometryInfo.setCoordinateIndices(this.getIndices(this.geometryVertexOffset));
810   if (this.geometryNormals) {
811     geometryInfo.setNormals(this.geometryNormals);
812     geometryInfo.setNormalIndices(this.getIndices(this.geometryNormalOffset));
813   }
814   if (this.geometryTextureCoordinates) {
815     var stride;
816     for (var i = 0; i < this.sourceAccessorStrides.length; i++) {
817       var sourceAccessorStride = this.sourceAccessorStrides [i];
818       if (sourceAccessorStride.floatArray === this.geometryTextureCoordinates) {
819         stride = sourceAccessorStride.stride;
820         break;
821       }
822     }
823     // Support only UV texture coordinates
824     var textureCoordinates;
825     if (stride > 2) {
826       textureCoordinates = new Array(this.geometryTextureCoordinates.length / stride * 2);
827       for (var i = 0, j = 0; j < this.geometryTextureCoordinates.length; j += stride) {
828         textureCoordinates [i++] = this.geometryTextureCoordinates [j];
829         textureCoordinates [i++] = this.geometryTextureCoordinates [j + 1];
830       }
831     } else {
832       textureCoordinates = this.geometryTextureCoordinates;
833     }
834     geometryInfo.setTextureCoordinates(textureCoordinates);
835     geometryInfo.setTextureCoordinateIndices(this.getIndices(this.geometryTextureCoordinatesOffset));
836   }
837 
838   
839   if ("tristrips" == name
840       || "trifans" == name) {
841     var stripCounts = new Array(this.facesAndLinesPrimitives.length);
842     for (var i = 0; i < stripCounts.length; i++) {
843       stripCounts [i] = this.facesAndLinesPrimitives [i].length / this.inputCount;
844     }
845     geometryInfo.setStripCounts(stripCounts);
846   } else if ("polylist" == name) {
847     geometryInfo.setStripCounts(this.vcount);
848   } else if ("polygons" == name) {
849     var polygonHolesCount = 0;
850     for (var i = 0; i < this.polygonsHoles.length; i++) {
851       polygonHolesCount += this.polygonsHoles [i].length;
852     }
853     var stripCounts = new Array(this.facesAndLinesPrimitives.length + this.polygonsPrimitives.length 
854                                 + polygonHolesCount);
855     var contourCounts = new Array(this.facesAndLinesPrimitives.length + this.polygonsPrimitives.length);
856     var stripIndex = 0;
857     var countourIndex = 0;
858     for (var i = 0; i < this.facesAndLinesPrimitives.length; i++) {
859       stripCounts [stripIndex++] = this.facesAndLinesPrimitives [i].length / this.inputCount;
860       contourCounts [countourIndex++] = 1; // One polygon 
861     }
862     for (var i = 0; i < this.polygonsPrimitives.length; i++) {
863       stripCounts [stripIndex++] = this.polygonsPrimitives [i].length / this.inputCount;
864       var polygonHoles = this.polygonsHoles [i];
865       for (var j = 0; j < polygonHoles.length; j++) {
866         stripCounts [stripIndex++] = polygonHoles [j].length / this.inputCount;
867       }
868       contourCounts [countourIndex++] = 1 + polygonHoles.length; // One polygon + its holes count
869     }
870     geometryInfo.setStripCounts(stripCounts);
871     geometryInfo.setContourCounts(contourCounts);
872   }
873 
874   if (!this.geometryNormals) {
875     geometryInfo.setCreaseAngle(Math.PI / 2);
876     geometryInfo.setGeneratedNormals(true);
877   }
878   return geometryInfo.getIndexedGeometryArray();
879 }
880 
881 /**
882  * Returns the lines geometry matching the read values.
883  * @private
884  */
885 DAEHandler.prototype.getLinesGeometry = function(name) {
886   var coordinatesIndices = this.getIndices(this.geometryVertexOffset);
887   if (coordinatesIndices.length !== 0) {
888     var textureCoordinatesIndices = this.geometryTextureCoordinates
889         ? this.getIndices(this.geometryTextureCoordinatesOffset)
890         : [];
891     if ("linestrips" == name) {
892       var noStripCoordinatesIndices = [];
893       var noStripTextureCoordinatesIndices = [];
894       for (var i = 0, index = 0; i < this.facesAndLinesPrimitives.length; i++) {
895         var stripCount = this.facesAndLinesPrimitives [i].length / this.inputCount;
896         for (var k = 0; k < stripCount - 1; k++) {
897           noStripCoordinatesIndices.push(coordinatesIndices [index + k]);
898           noStripCoordinatesIndices.push(coordinatesIndices [index + k + 1]);
899           if (textureCoordinateIndices.length > 0) {
900             noStripTextureCoordinatesIndices.push(textureCoordinateIndices [index + k]);
901             noStripTextureCoordinatesIndices.push(textureCoordinateIndices [index + k + 1]);
902           }
903         }
904         index += stripCount;
905       }
906       coordinatesIndices = noStripCoordinatesIndices;
907       textureCoordinatesIndices = noStripTextureCoordinatesIndices;
908     }
909     return new IndexedLineArray3D(this.geometryVertices, coordinatesIndices,
910         this.geometryTextureCoordinates, textureCoordinatesIndices);
911   } else {
912     // Ignore lines with an empty index set
913     return null;
914   }
915 }
916 
917 /**
918  * Returns the indices at the given <code>indexOffset</code>.
919  * @private
920  */
921 DAEHandler.prototype.getIndices = function(indexOffset) {
922   if (this.facesAndLinesPrimitives.length === 1 && this.polygonsPrimitives.length === 1 && this.inputCount === 1) {
923     return facesAndLinesPrimitives [0];
924   } else {
925     var indexCount = this.getIndexCount(this.facesAndLinesPrimitives);
926     indexCount += this.getIndexCount(this.polygonsPrimitives);
927     for (var i = 0; i < this.polygonsHoles.length; i++) {
928       indexCount += this.getIndexCount(this.polygonsHoles [i]);
929     }
930     
931     var indices = new Array(indexCount / this.inputCount);
932     var i = 0;
933     for (var j = 0; j < this.facesAndLinesPrimitives.length; j++) {
934       var primitives = this.facesAndLinesPrimitives [j];
935       for (var k = indexOffset; k < primitives.length; k += this.inputCount) {
936         indices [i++] = primitives [k];
937       }
938     }
939     for (var j = 0; j < this.polygonsPrimitives.length; j++) {
940       var polygonPrimitives = this.polygonsPrimitives [j];
941       for (var k = indexOffset; k < polygonPrimitives.length; k += this.inputCount) {
942         indices [i++] = polygonPrimitives [k];
943       }
944       var polygonHoles = this.polygonsHoles [j];
945       for (var k = 0; k < polygonHoles.length; k++) {
946         var polygonHole = polygonHoles [k];
947         for (var l = indexOffset; l < polygonHole.length; l += this.inputCount) {
948           indices [i++] = polygonHole [l];
949         }
950       }
951     }
952     return indices;
953   }
954 }
955   
956 /**
957  * Returns the total count of indices among the given <code>faceIndices</code>. 
958  * @private
959  */
960 DAEHandler.prototype.getIndexCount = function(faceIndices) {
961   var indexCount = 0;
962   for (var i = 0; i < faceIndices.length; i++) {
963     indexCount += faceIndices [i].length;
964   }
965   return indexCount;
966 }
967   
968 /**
969  * Multiplies the transform at top of the transform groups stack by the 
970  * given <code>transformMultiplier</code>.
971  * @private
972  */
973 DAEHandler.prototype.mulTransformGroup = function(transformMultiplier) {
974   var transformGroup = this.parentGroups [this.parentGroups.length - 1];
975   var transform = mat4.create();
976   transformGroup.getTransform(transform);
977   mat4.mul(transform, transform, transformMultiplier);
978   transformGroup.setTransform(transform);
979 }
980 
981 DAEHandler.prototype.endDocument = function() {
982   for (var i = 0; i < this.postProcessingBinders.length; i++) {
983     this.postProcessingBinders [i] ();
984   }
985 
986   if (this.visualScene !== null) {
987     var rootTransform = mat4.create();
988     this.visualScene.getTransform(rootTransform);
989 
990     var bounds = new BoundingBox3D(
991         vec3.fromValues(Infinity, Infinity, Infinity),
992         vec3.fromValues(-Infinity, -Infinity, -Infinity));
993     this.computeBounds(this.visualScene, bounds, mat4.create());
994     
995     // Translate model to its center
996     var lower = vec3.create();
997     bounds.getLower(lower);
998     if (lower [0] !== Infinity) {
999       var upper = vec3.create();
1000       bounds.getUpper(upper);
1001       var translation = mat4.create();
1002       mat4.translate(translation, translation, 
1003           vec3.fromValues(-lower [0] - (upper [0] - lower [0]) / 2, 
1004               -lower [1] - (upper [1] - lower [1]) / 2, 
1005               -lower [2] - (upper [2] - lower [2]) / 2));      
1006       mat4.mul(translation, translation, rootTransform);
1007       rootTransform = translation;
1008     }
1009 
1010     // Scale model to cm
1011     var scaleTransform = mat4.create();
1012     mat4.scale(scaleTransform, scaleTransform, 
1013         vec3.fromValues(this.meterScale * 100, this.meterScale * 100, this.meterScale * 100));
1014     mat4.mul(scaleTransform, scaleTransform, rootTransform);
1015 
1016     // Set orientation to Y_UP
1017     var axisTransform = mat4.create();
1018     if ("Z_UP" == this.axis) {
1019       mat4.fromXRotation(axisTransform, -Math.PI / 2);
1020     } else if ("X_UP" == this.axis) {
1021       mat4.fromZRotation(axisTransform, Math.PI / 2);
1022     }
1023     mat4.mul(axisTransform, axisTransform, scaleTransform);
1024 
1025     this.visualScene.setTransform(axisTransform);
1026 
1027     this.sceneRoot.addChild(this.visualScene);
1028   }
1029 }
1030 
1031 /**
1032  * Combines the given <code>bounds</code> with the bounds of the given <code>node</code>
1033  * and its children.
1034  */
1035 DAEHandler.prototype.computeBounds = function(node, bounds, parentTransformations) {
1036   if (node instanceof Group3D) {
1037     if (node instanceof TransformGroup3D) {
1038       parentTransformations = mat4.clone(parentTransformations);
1039       var transform = mat4.create();
1040       node.getTransform(transform);
1041       mat4.mul(parentTransformations, parentTransformations, transform);
1042     }
1043     // Compute the bounds of all the node children
1044     var children = node.getChildren();
1045     for (var i = 0; i < children.length; i++) {
1046       this.computeBounds(children [i], bounds, parentTransformations);
1047     }
1048   } else if (node instanceof Link3D) {
1049     this.computeBounds(node.getSharedGroup(), bounds, parentTransformations);
1050   } else if (node instanceof Shape3D) {
1051     var shapeBounds = node.getBounds();
1052     shapeBounds.transform(parentTransformations);
1053     bounds.combine(shapeBounds);
1054   }
1055 }
1056 
1057