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