1 /*
  2  * Room3D.js
  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 scene3d.js
 22 //          Object3DBranch.js
 23 //          TextureManager.js
 24 
 25 
 26 /**
 27  * Creates the 3D room matching the given home <code>room</code>.
 28  * @param {Room} room
 29  * @param {Home} home
 30  * @param {UserPreferences} [preferences]
 31  * @param {boolean} ignoreCeilingPart
 32  * @param {boolean} waitTextureLoadingEnd
 33  * @constructor
 34  * @extends Object3DBranch
 35  * @author Emmanuel Puybaret
 36  */
 37 function Room3D(room, home, preferences, ignoreCeilingPart, waitTextureLoadingEnd) {
 38   if (waitTextureLoadingEnd === undefined) {
 39     // 4 parameters
 40     waitModelAndTextureLoadingEnd = ignoreCeilingPart;
 41     ignoreCeilingPart = preferences;
 42     preferences = null;
 43   }
 44   Object3DBranch.call(this, room, home, preferences);
 45   if (ignoreCeilingPart === undefined) {
 46     ignoreCeilingPart = false;
 47     waitTextureLoadingEnd = false;
 48   }
 49   
 50   this.addChild(this.createRoomPartShape());
 51   this.addChild(this.createRoomPartShape());
 52   
 53   // Add selection node
 54   var roomSelectionShape = new Shape3D();
 55   roomSelectionShape.setAppearance(this.getSelectionAppearance());
 56   roomSelectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
 57   roomSelectionShape.setPickable(false);
 58   this.addChild(roomSelectionShape);
 59 
 60   this.updateRoomGeometry();
 61   this.updateRoomAppearance(waitTextureLoadingEnd);
 62   if (ignoreCeilingPart) {
 63     this.removeChild(Room3D.CEILING_PART);
 64   }
 65 }
 66 Room3D.prototype = Object.create(Object3DBranch.prototype);
 67 Room3D.prototype.constructor = Room3D;
 68 
 69 Room3D.FLOOR_PART  = 0;
 70 Room3D.CEILING_PART = 1;
 71 
 72 /**
 73  * Returns a new room part shape with no geometry
 74  * and a default appearance with a white material.
 75  * @return {Node3D}
 76  * @private
 77  */
 78 Room3D.prototype.createRoomPartShape = function() {
 79   var roomShape = new Shape3D();
 80   roomShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
 81   var roomAppearance = new Appearance3D();
 82   roomShape.setAppearance(roomAppearance);
 83   this.updateAppearanceMaterial(roomAppearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, 0);
 84   return roomShape;
 85 }
 86 
 87 Room3D.prototype.update = function() {
 88   this.updateRoomGeometry();
 89   this.updateRoomAppearance(false);
 90 }
 91 
 92 /**
 93  * Sets the 3D geometry of this room shapes that matches its 2D geometry.
 94  * @private
 95  */
 96 Room3D.prototype.updateRoomGeometry = function() {
 97   this.updateRoomPartGeometry(Room3D.FLOOR_PART, this.getUserData().getFloorTexture());
 98   this.updateRoomPartGeometry(Room3D.CEILING_PART, this.getUserData().getCeilingTexture());
 99   var room = this.getUserData();
100   this.setPickable(this.getHome().getEnvironment().getWallsAlpha() == 0
101       || room.getLevel() == null 
102       || room.getLevel().getElevation() <= 0);
103 }
104 
105 Room3D.prototype.updateRoomPartGeometry = function(roomPart, texture) {
106   var roomShape = this.getChild(roomPart);
107   var currentGeometriesCount = roomShape.getGeometries().length;
108   var room = this.getUserData();
109   if (room.getLevel() == null || room.getLevel().isViewableAndVisible()) {
110     var geometries = this.createRoomGeometries(roomPart, texture);
111     for (var i = 0; i < geometries.length; i++) {
112       roomShape.addGeometry(geometries[i]);
113     }
114   }
115   for (var i = currentGeometriesCount - 1; i >= 0; i--) {
116     roomShape.removeGeometry(i);
117   }
118   
119   var roomSelectionShape = this.getChild(2);
120   roomSelectionShape.addGeometry(this.createRoomSelectionGeometry());
121   if (roomSelectionShape.getGeometries().length > 1) {
122     roomSelectionShape.removeGeometry(0);
123   }
124 }
125 
126 /**
127  * Returns room geometry computed from its points.
128  * @param {number} roomPart
129  * @param {HomeTexture} texture
130  * @return {Array}
131  * @private
132  */
133 Room3D.prototype.createRoomGeometries = function(roomPart, texture) {
134   var room = this.getUserData();
135   var points = room.getPoints();
136   if ((roomPart === Room3D.FLOOR_PART && room.isFloorVisible() 
137         || roomPart === Room3D.CEILING_PART && room.isCeilingVisible()) 
138       && points.length > 2) {
139     var roomLevel = room.getLevel();
140     var levels = this.getHome().getLevels();
141     var lastLevel = this.isLastLevel(roomLevel, levels);
142     var floorBottomElevation;
143     var roomElevation;
144     if (roomLevel != null) {
145       roomElevation = roomLevel.getElevation();
146       floorBottomElevation = roomElevation - roomLevel.getFloorThickness();
147     } else {
148       roomElevation = 0;
149       floorBottomElevation = 0;
150     }
151     var firstLevelElevation;
152     if (levels.length === 0) {
153       firstLevelElevation = 0;
154     } else {
155       firstLevelElevation = levels[0].getElevation();
156     }
157     var floorBottomVisible = roomPart === Room3D.FLOOR_PART 
158         && roomLevel !== null 
159         && roomElevation !== firstLevelElevation;
160     
161     var roomsAtSameElevation = [];
162     var ceilingsAtSameFloorBottomElevation = [];
163     var rooms = this.getHome().getRooms();
164     for (var i = 0; i < rooms.length; i++) {
165       var homeRoom = rooms[i];
166       var homeRoomLevel = homeRoom.getLevel();
167       if (homeRoomLevel === null || homeRoomLevel.isViewableAndVisible()) {
168         if (room === homeRoom 
169             || roomLevel === homeRoomLevel 
170                 && (roomPart === Room3D.FLOOR_PART && homeRoom.isFloorVisible() 
171                     || roomPart === Room3D.CEILING_PART && homeRoom.isCeilingVisible()) 
172             || roomLevel != null 
173                 && homeRoomLevel != null 
174                 && (roomPart === Room3D.FLOOR_PART 
175                         && homeRoom.isFloorVisible() 
176                         && Math.abs(roomElevation - homeRoomLevel.getElevation()) < 1.0E-4 
177                     || roomPart === Room3D.CEILING_PART 
178                         && homeRoom.isCeilingVisible() 
179                         && !lastLevel 
180                         && !this.isLastLevel(homeRoomLevel, levels) 
181                         && Math.abs(roomElevation + roomLevel.getHeight() - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1.0E-4)) {
182           roomsAtSameElevation.push(homeRoom);
183         } else if (floorBottomVisible 
184                     && homeRoomLevel != null 
185                     && homeRoom.isCeilingVisible() 
186                     && !this.isLastLevel(homeRoomLevel, levels) 
187                     && Math.abs(floorBottomElevation - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1.0E-4) {
188           ceilingsAtSameFloorBottomElevation.push(homeRoom);
189         }
190       }
191     }
192     if (roomLevel != null) {
193       roomsAtSameElevation.sort(function (room1, room2) {
194           var comparison = (room1.getLevel().getElevation() - room2.getLevel().getElevation());
195           if (comparison !== 0) {
196             return comparison;
197           } else {
198             return room1.getLevel().getElevationIndex() - room2.getLevel().getElevationIndex();
199           }
200         });
201     }
202     
203     var visibleStaircases;
204     if (roomLevel === null 
205         || roomPart === Room3D.CEILING_PART 
206             && lastLevel) {
207       visibleStaircases = [];
208     } else {
209       visibleStaircases = this.getVisibleStaircases(this.getHome().getFurniture(), roomPart, roomLevel, 
210           roomLevel.getElevation() === firstLevelElevation);
211     }
212     
213     var sameElevation = true;
214     if (roomPart === Room3D.CEILING_PART && (roomLevel === null || lastLevel)) {
215       var firstPointElevation = this.getRoomHeightAt(points[0][0], points[0][1]);
216       for (var i = 1; i < points.length && sameElevation; i++) {
217         sameElevation = this.getRoomHeightAt(points[i][0], points[i][1]) === firstPointElevation;
218       }
219     }
220     
221     var roomPoints;
222     var roomHoles;
223     var roomPointsWithoutHoles;
224     var roomVisibleArea;
225     if (!room.isSingular() 
226         || sameElevation 
227             && (roomsAtSameElevation[roomsAtSameElevation.length - 1] !== room 
228                 || visibleStaircases.length > 0)) {
229       roomVisibleArea = new java.awt.geom.Area(this.getShape(points));
230       if ((roomsAtSameElevation.indexOf(room) >= 0)) {
231         for (var i = roomsAtSameElevation.length - 1; i > 0 && roomsAtSameElevation[i] !== room; i--) {
232           var otherRoom = roomsAtSameElevation[i];
233           roomVisibleArea.subtract(new java.awt.geom.Area(this.getShape(otherRoom.getPoints())));
234         }
235       }
236       this.removeStaircasesFromArea(visibleStaircases, roomVisibleArea);
237       roomPoints = [];
238       roomHoles = [];
239       roomPointsWithoutHoles = this.getAreaPoints(roomVisibleArea, roomPoints, roomHoles, 1, roomPart === Room3D.CEILING_PART);
240     } else {
241       var clockwise = room.isClockwise();
242       if (clockwise && roomPart === Room3D.FLOOR_PART 
243           || !clockwise && roomPart === Room3D.CEILING_PART) {
244         points = this.getReversedArray(points);
245       }
246       roomPointsWithoutHoles = 
247       roomPoints = [points];
248       roomHoles = [];
249       roomVisibleArea = null;
250     }
251     
252     var geometries = [];
253     var subpartSize = this.getHome().getEnvironment().getSubpartSizeUnderLight();
254     
255     if (roomPointsWithoutHoles.length !== 0) {
256       var roomPointElevations = [];
257       var roomAtSameElevation = true;
258       for (var i = 0; i < roomPointsWithoutHoles.length; i++) {
259         var roomPartPoints = roomPointsWithoutHoles[i];
260         var roomPartPointElevations = new Array(roomPartPoints.length);
261         for (var j = 0; j < roomPartPoints.length; j++) {
262           roomPartPointElevations[j] = roomPart === Room3D.FLOOR_PART 
263               ? roomElevation 
264               : this.getRoomHeightAt(roomPartPoints[j][0], roomPartPoints[j][1]);
265           if (roomAtSameElevation && j > 0) {
266             roomAtSameElevation = roomPartPointElevations[j] === roomPartPointElevations[j - 1];
267           }
268         }
269         roomPointElevations.push(roomPartPointElevations);
270       }
271       
272       if (roomAtSameElevation && subpartSize > 0) {
273         for (var j = 0; j < roomPointsWithoutHoles.length; j++) {
274           var roomPartPoints = roomPointsWithoutHoles[j];
275           var xMin = Number.MAX_VALUE;
276           var xMax = Number.MIN_VALUE;
277           var zMin = Number.MAX_VALUE;
278           var zMax = Number.MIN_VALUE;
279           for (var i = 0; i < roomPartPoints.length; i++) {
280             var point = roomPartPoints[i];
281             xMin = Math.min(xMin, point[0]);
282             xMax = Math.max(xMax, point[0]);
283             zMin = Math.min(zMin, point[1]);
284             zMax = Math.max(zMax, point[1]);
285           }
286           
287           var roomPartArea = new java.awt.geom.Area(this.getShape(roomPartPoints));
288           for (var xSquare = xMin; xSquare < xMax; xSquare += subpartSize) {
289             for (var zSquare = zMin; zSquare < zMax; zSquare += subpartSize) {
290               var roomPartSquare = new java.awt.geom.Area(new java.awt.geom.Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize));
291               roomPartSquare.intersect(roomPartArea);
292               if (!roomPartSquare.isEmpty()) {
293                 var geometryPartPointsWithoutHoles = this.getAreaPoints(roomPartSquare, 1, roomPart === Room3D.CEILING_PART);
294                 if (!(geometryPartPointsWithoutHoles.length == 0)) {
295                   geometries.push(this.computeRoomPartGeometry(geometryPartPointsWithoutHoles, 
296                       null, roomLevel, roomPointElevations[i][0], floorBottomElevation, 
297                       roomPart === Room3D.FLOOR_PART, false, texture));
298                 }
299               }
300             }
301           }
302         }
303       } else {
304         geometries.push(this.computeRoomPartGeometry(roomPointsWithoutHoles, roomPointElevations, roomLevel, 
305             roomElevation, floorBottomElevation, roomPart === Room3D.FLOOR_PART, false, texture));
306       }
307       if (roomLevel != null 
308           && roomPart === Room3D.FLOOR_PART 
309           && roomLevel.getElevation() !== firstLevelElevation) {
310         geometries.push(this.computeRoomBorderGeometry(roomPoints, roomHoles, roomLevel, roomElevation, texture));
311       }
312     }
313     
314     if (floorBottomVisible) {
315       var floorBottomPointsWithoutHoles;
316       if (roomVisibleArea != null 
317           || ceilingsAtSameFloorBottomElevation.length > 0) {
318         var floorBottomVisibleArea = roomVisibleArea != null ? roomVisibleArea : new java.awt.geom.Area(this.getShape(points));
319         for (var i = 0; i < ceilingsAtSameFloorBottomElevation.length; i++) {
320           var otherRoom = ceilingsAtSameFloorBottomElevation[i];
321           floorBottomVisibleArea.subtract(new java.awt.geom.Area(this.getShape(otherRoom.getPoints())));
322         }
323         floorBottomPointsWithoutHoles = this.getAreaPoints(floorBottomVisibleArea, 1, true);
324       } else {
325         floorBottomPointsWithoutHoles = [this.getReversedArray(points)];
326       }
327       
328       if (floorBottomPointsWithoutHoles.length !== 0) {
329         if (subpartSize > 0) {
330           for (var i = 0; i < floorBottomPointsWithoutHoles.length; i++) {
331             var floorBottomPartPoints = floorBottomPointsWithoutHoles[i];
332             var xMin = Number.MAX_VALUE;
333             var xMax = Number.MIN_VALUE;
334             var zMin = Number.MAX_VALUE;
335             var zMax = Number.MIN_VALUE;
336             for (var j = 0; j < floorBottomPartPoints.length; j++) {
337               var point = floorBottomPartPoints[j];
338               xMin = Math.min(xMin, point[0]);
339               xMax = Math.max(xMax, point[0]);
340               zMin = Math.min(zMin, point[1]);
341               zMax = Math.max(zMax, point[1]);
342             }
343             
344             var floorBottomPartArea = new java.awt.geom.Area(this.getShape(floorBottomPartPoints));
345             for (var xSquare = xMin; xSquare < xMax; xSquare += subpartSize) {
346               for (var zSquare = zMin; zSquare < zMax; zSquare += subpartSize) {
347                 var floorBottomPartSquare = new java.awt.geom.Area(new java.awt.geom.Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize));
348                 floorBottomPartSquare.intersect(floorBottomPartArea);
349                 if (!floorBottomPartSquare.isEmpty()) {
350                   var geometryPartPointsWithoutHoles = this.getAreaPoints(floorBottomPartSquare, 1, true);
351                   if (geometryPartPointsWithoutHoles.length !== 0) {
352                     geometries.push(this.computeRoomPartGeometry(geometryPartPointsWithoutHoles, 
353                         null, roomLevel, roomElevation, floorBottomElevation, 
354                         true, true, texture));
355                   }
356                 }
357               }
358             }
359           }
360         } else {
361           geometries.push(this.computeRoomPartGeometry(floorBottomPointsWithoutHoles, null, roomLevel, 
362               roomElevation, floorBottomElevation, true, true, texture));
363         }
364       }
365     }
366     
367     return geometries.slice(0);
368   } else {
369     return [];
370   }
371 }
372 
373 /**
374  * Returns the room part geometry matching the given points.
375  * @param {Array} geometryPoints
376  * @param {Array} roomPointElevations
377  * @param {Level} roomLevel
378  * @param {number} roomPartElevation
379  * @param {number} floorBottomElevation
380  * @param {boolean} floorPart
381  * @param {boolean} floorBottomPart
382  * @param {HomeTexture} texture
383  * @return {IndexedGeometryArray3D}
384  * @private
385  */
386 Room3D.prototype.computeRoomPartGeometry = function(geometryPoints, roomPointElevations, 
387                                                     roomLevel, roomPartElevation, floorBottomElevation, 
388                                                     floorPart, floorBottomPart, texture) {
389   var stripCounts = new Array(geometryPoints.length);
390   var vertexCount = 0;
391   for (var i = 0; i < geometryPoints.length; i++) {
392     var areaPoints = geometryPoints[i];
393     stripCounts[i] = areaPoints.length;
394     vertexCount += stripCounts[i];
395   }
396   var coords = new Array(vertexCount);
397   var i = 0;
398   for (var j = 0; j < geometryPoints.length; j++) {
399     var areaPoints = geometryPoints[j];
400     var roomPartPointElevations = roomPointElevations != null 
401         ? roomPointElevations[j] 
402         : null;
403     for (var k = 0; k < areaPoints.length; k++) {
404       var y = floorBottomPart 
405           ? floorBottomElevation 
406           : (roomPartPointElevations != null 
407                 ? roomPartPointElevations[k] 
408                 : roomPartElevation);
409       coords[i++] = vec3.fromValues(areaPoints[k][0], y, areaPoints[k][1]);
410     }
411   }
412   
413   var geometryInfo = new GeometryInfo3D(GeometryInfo3D.POLYGON_ARRAY);
414   geometryInfo.setCoordinates(coords);
415   geometryInfo.setStripCounts(stripCounts);
416   
417   if (texture != null) {
418     var textureCoords = new Array(vertexCount);
419     i = 0;
420     for (var j = 0; j < geometryPoints.length; j++) {
421       var areaPoints = geometryPoints[j];
422       for (var k = 0; k < areaPoints.length; k++) {
423         textureCoords[i++] = vec2.fromValues(areaPoints[k][0], 
424             floorPart 
425                 ? -areaPoints[k][1] 
426                 : areaPoints[k][1]);
427       }
428     }
429     geometryInfo.setTextureCoordinates(textureCoords);
430   }
431   geometryInfo.setGeneratedNormals(true);
432   return geometryInfo.getIndexedGeometryArray();
433 }
434 
435 /**
436  * Returns the room border geometry matching the given points.
437  * @param {Array} geometryRooms
438  * @param {Array} geometryHoles
439  * @param {Level} roomLevel
440  * @param {number} roomElevation
441  * @param {HomeTexture} texture
442  * @return {IndexedGeometryArray3D}
443  * @private
444  */
445 Room3D.prototype.computeRoomBorderGeometry = function(geometryRooms, geometryHoles, 
446                                                       roomLevel, roomElevation, texture) {
447   var vertexCount = 0;
448   for (var i = 0; i < geometryRooms.length; i++) {
449     vertexCount += geometryRooms[i].length;
450   }
451   for (var i = 0; i < geometryHoles.length; i++) {
452     vertexCount += geometryHoles[i].length;
453   }
454   vertexCount = vertexCount * 4;
455   
456   var i = 0;
457   var coords = new Array(vertexCount);
458   var floorBottomElevation = roomElevation - roomLevel.getFloorThickness();
459   for (var index = 0; index < geometryRooms.length; index++) {
460     var geometryPoints = geometryRooms[index];
461     for (var j = 0; j < geometryPoints.length; j++) {
462       coords[i++] = vec3.fromValues(geometryPoints[j][0], roomElevation, geometryPoints[j][1]);
463       coords[i++] = vec3.fromValues(geometryPoints[j][0], floorBottomElevation, geometryPoints[j][1]);
464       var nextPoint = j < geometryPoints.length - 1 
465           ? j + 1 
466           : 0;
467       coords[i++] = vec3.fromValues(geometryPoints[nextPoint][0], floorBottomElevation, geometryPoints[nextPoint][1]);
468       coords[i++] = vec3.fromValues(geometryPoints[nextPoint][0], roomElevation, geometryPoints[nextPoint][1]);
469     }
470   }
471   for (var index = 0; index < geometryHoles.length; index++) {
472     var geometryHole = geometryHoles[index];
473     for (var j = 0; j < geometryHole.length; j++) {
474       coords[i++] = vec3.fromValues(geometryHole[j][0], roomElevation, geometryHole[j][1]);
475       var nextPoint = j < geometryHole.length - 1 
476           ? j + 1 
477           : 0;
478       coords[i++] = vec3.fromValues(geometryHole[nextPoint][0], roomElevation, geometryHole[nextPoint][1]);
479       coords[i++] = vec3.fromValues(geometryHole[nextPoint][0], floorBottomElevation, geometryHole[nextPoint][1]);
480       coords[i++] = vec3.fromValues(geometryHole[j][0], floorBottomElevation, geometryHole[j][1]);
481     }
482   }
483   
484   var geometryInfo = new GeometryInfo3D(GeometryInfo3D.QUAD_ARRAY);
485   geometryInfo.setCoordinates(coords);
486   
487   if (texture != null) {
488     var textureCoords = new Array(vertexCount);
489     i = 0;
490     if (texture.isFittingArea()) {
491       for (var index = 0; index < geometryRooms.length; index++) {
492         var geometryPoints = geometryRooms[index];
493         for (var j = 0; j < geometryPoints.length; j++) {
494           textureCoords [i++] =
495           textureCoords [i++] = vec2.fromValues(geometryPoints [j][0], -geometryPoints [j][1]);
496           var nextPoint = j < geometryPoints.length - 1
497               ? j + 1
498               : 0;
499           textureCoords [i++] =
500           textureCoords [i++] = vec2.fromValues(geometryPoints [nextPoint][0], -geometryPoints [nextPoint][1]);
501         }
502       }
503       for (var index = 0; index < geometryHoles.length; index++) {
504         var geometryHole = geometryHoles[index];
505         for (var j = 0; j < geometryHole.length; j++) {
506           textureCoords [i] = vec2.fromValues(geometryHole [j][0], -geometryHole [j][1]);
507           var nextPoint = j < geometryHole.length - 1
508               ? j + 1
509               : 0;
510           textureCoords [i + 1] = vec2.fromValues(geometryHole [nextPoint][0], -geometryHole [nextPoint][1]);
511           textureCoords [i + 2] = textureCoords [i + 1];
512           textureCoords [i + 3] = textureCoords [i];
513           i += 4;
514         }
515       }
516     } else {
517       for (var index = 0; index < geometryRooms.length; index++) {
518         var geometryPoints = geometryRooms[index];
519         for (var j = 0; j < geometryPoints.length; j++) {
520           textureCoords[i++] = vec2.fromValues(0, roomLevel.getFloorThickness());
521           textureCoords[i++] = vec2.fromValues(0, 0);
522           var nextPoint = j < geometryPoints.length - 1 
523               ? j + 1 
524               : 0;
525           var textureCoord = java.awt.geom.Point2D.distance(geometryPoints[j][0], geometryPoints[j][1], geometryPoints[nextPoint][0], geometryPoints[nextPoint][1]);
526           textureCoords[i++] = vec2.fromValues(textureCoord, 0);
527           textureCoords[i++] = vec2.fromValues(textureCoord, roomLevel.getFloorThickness());
528         }
529       }
530       for (var index = 0; index < geometryHoles.length; index++) {
531         var geometryHole = geometryHoles[index];
532         for (var j = 0; j < geometryHole.length; j++) {
533           textureCoords[i++] = vec2.fromValues(0, 0);
534           var nextPoint = j < geometryHole.length - 1 
535               ? j + 1 
536               : 0;
537           var textureCoord = java.awt.geom.Point2D.distance(geometryHole[j][0], geometryHole[j][1], geometryHole[nextPoint][0], geometryHole[nextPoint][1]);
538           textureCoords[i++] = vec2.fromValues(textureCoord, 0);
539           textureCoords[i++] = vec2.fromValues(textureCoord, roomLevel.getFloorThickness());
540           textureCoords[i++] = vec2.fromValues(0, roomLevel.getFloorThickness());
541         }
542       }
543     }
544     geometryInfo.setTextureCoordinates(textureCoords);
545   }
546   
547   geometryInfo.setCreaseAngle(Math.PI / 8);
548   geometryInfo.setGeneratedNormals(true);
549   return geometryInfo.getIndexedGeometryArray();
550 }
551 
552 Room3D.prototype.removeStaircasesFromArea = function (visibleStaircases, area) {
553   var modelManager = ModelManager.getInstance();
554   for (var i = 0; i < visibleStaircases.length; i++) {
555     var staircase = visibleStaircases[i];
556     area.subtract(modelManager.getAreaOnFloor(staircase));
557   }
558 }
559 
560 /**
561  * Returns the visible staircases among the given <code>furniture</code>.
562  * @param {Array} furniture
563  * @param {number} roomPart
564  * @param {Level} roomLevel
565  * @param {boolean} firstLevel
566  * @return {Array}
567  * @private
568  */
569 Room3D.prototype.getVisibleStaircases = function(furniture, roomPart, roomLevel, firstLevel) {
570   var visibleStaircases = [];
571   for (var i = 0; i < furniture.length; i++) {
572     var piece = furniture[i];
573     if (piece.isVisible() 
574         && (piece.getLevel() === null 
575             || piece.getLevel().isViewableAndVisible())) {
576       if (piece instanceof HomeFurnitureGroup) {
577         visibleStaircases.push.apply(visibleStaircases, this.getVisibleStaircases(piece.getFurniture(), roomPart, roomLevel, firstLevel));
578       } else if (piece.getStaircaseCutOutShape() != null 
579           && "false" != piece.getStaircaseCutOutShape().toLowerCase() 
580           && ((roomPart === Room3D.FLOOR_PART 
581                   && piece.getGroundElevation() < roomLevel.getElevation() 
582                   && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() - (firstLevel ? 0 : roomLevel.getFloorThickness()) 
583               || roomPart === Room3D.CEILING_PART 
584                   && piece.getGroundElevation() < roomLevel.getElevation() + roomLevel.getHeight() 
585                   && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() + roomLevel.getHeight()))) {
586         visibleStaircases.push(piece);
587       }
588     }
589   }
590   return visibleStaircases;
591 }
592 
593 /**
594  * Returns an array that cites <code>points</code> in reverse order.
595  * @param {Array} points
596  * @return {Array}
597  * @private
598  */
599 Room3D.prototype.getReversedArray = function (points) {
600   points = points.slice(0);
601   return points.reverse();
602 }
603 
604 /**
605  * Returns the room height at the given point.
606  * @param {number} x
607  * @param {number} y
608  * @return {number}
609  * @private
610  */
611 Room3D.prototype.getRoomHeightAt = function (x, y) {
612   var smallestDistance = Infinity;
613   var room = this.getUserData();
614   var home = this.getHome();
615   var roomLevel = room.getLevel();
616   var roomElevation = roomLevel !== null 
617       ? roomLevel.getElevation() 
618       : 0;
619   var roomHeight = roomElevation + 
620       (roomLevel == null ? home.getWallHeight() : roomLevel.getHeight());
621   var levels = home.getLevels();
622   if (roomLevel == null || this.isLastLevel(roomLevel, levels)) {
623     var walls = home.getWalls();
624     if (room.isCeilingFlat()) {
625       // Search the highest wall at last level
626       var roomHeightSet = false;
627       for (var index = 0; index < walls.length; index++) {
628         var wall = walls[index];
629         if ((wall.getLevel() === null || wall.getLevel().isViewable())
630             && wall.isAtLevel(roomLevel)) {
631           if (wall.getHeight() !== null) {
632             var wallHeight = wall.getHeight();
633             if (wall.getLevel() !== null) {
634               wallHeight += wall.getLevel().getElevation();
635             }
636             if (roomHeightSet) {
637               roomHeight = Math.max(roomHeight, wallHeight);
638             } else {
639               roomHeight = wallHeight;
640               roomHeightSet = true;
641             }
642           }
643           if (wall.getHeightAtEnd() !== null) {
644             var wallHeightAtEnd = wall.getHeightAtEnd();
645             if (wall.getLevel() !== null) {
646               wallHeightAtEnd += wall.getLevel().getElevation();
647             }
648             if (roomHeightSet) {
649               roomHeight = Math.max(roomHeight, wallHeightAtEnd);
650             } else {
651               roomHeight = wallHeightAtEnd;
652               roomHeightSet = true;
653             }
654           }
655         }
656       }
657     } else {
658       var closestWall = null;
659       var closestWallPoints = null;
660       var closestIndex = -1;
661       for (var index = 0; index < walls.length; index++) {
662         var wall = walls[index];
663         if ((wall.getLevel() === null || wall.getLevel().isViewable()) 
664             && wall.isAtLevel(roomLevel)) {
665           var points = wall.getPoints();
666           for (var i = 0; i < points.length; i++) {
667             var distanceToWallPoint = java.awt.geom.Point2D.distanceSq(points[i][0], points[i][1], x, y);
668             if (distanceToWallPoint < smallestDistance) {
669               closestWall = wall;
670               closestWallPoints = points;
671               closestIndex = i;
672               smallestDistance = distanceToWallPoint;
673             }
674           }
675         }
676       }
677       
678       if (closestWall != null) {
679         roomHeight = closestWall.getLevel() == null ? 0 : closestWall.getLevel().getElevation();
680         var wallHeightAtStart = closestWall.getHeight();
681         if (closestIndex === 0 || closestIndex === closestWallPoints.length - 1) {
682           roomHeight += wallHeightAtStart != null 
683               ? wallHeightAtStart 
684               : home.getWallHeight();
685         } else {
686           if (closestWall.isTrapezoidal()) {
687             var arcExtent = closestWall.getArcExtent();
688             if (arcExtent == null 
689                 || arcExtent === 0 
690                 || closestIndex === Math.floor(closestWallPoints.length / 2) 
691                 || closestIndex === Math.floor(closestWallPoints.length / 2) - 1) {
692               roomHeight += closestWall.getHeightAtEnd();
693             } else {
694               var xArcCircleCenter = closestWall.getXArcCircleCenter();
695               var yArcCircleCenter = closestWall.getYArcCircleCenter();
696               var xClosestPoint = closestWallPoints[closestIndex][0];
697               var yClosestPoint = closestWallPoints[closestIndex][1];
698               var centerToClosestPointDistance = java.awt.geom.Point2D.distance(xArcCircleCenter, yArcCircleCenter, xClosestPoint, yClosestPoint);
699               var xStart = closestWall.getXStart();
700               var yStart = closestWall.getYStart();
701               var centerToStartPointDistance = java.awt.geom.Point2D.distance(xArcCircleCenter, yArcCircleCenter, xStart, yStart);
702               var scalarProduct = (xClosestPoint - xArcCircleCenter) * (xStart - xArcCircleCenter) 
703                   + (yClosestPoint - yArcCircleCenter) * (yStart - yArcCircleCenter);
704               scalarProduct /= (centerToClosestPointDistance * centerToStartPointDistance);
705               var arcExtentToClosestWallPoint = Math.acos(scalarProduct) * (arcExtent > 0 ? 1 : (arcExtent < 0 ? -1 : 0));;
706               roomHeight += wallHeightAtStart 
707                   + (closestWall.getHeightAtEnd() - wallHeightAtStart) * arcExtentToClosestWallPoint / arcExtent;
708             }
709           } else {
710             roomHeight += (wallHeightAtStart != null ? wallHeightAtStart : home.getWallHeight());
711           }
712         }
713       }
714     }
715   }
716   return roomHeight;
717 }
718 
719 /**
720  * Returns <code>true</code> if the given level is the last level in home.
721  * @param {Level} level
722  * @param {Array} levels
723  * @return {boolean}
724  * @private
725  */
726 Room3D.prototype.isLastLevel = function(level, levels) {
727   return levels.indexOf(level) === levels.length - 1;
728 }
729 
730 /**
731  * Returns the selection geometry of this room.
732  * @return {IndexedGeometryArray3D}
733  * @private
734  */
735 Room3D.prototype.createRoomSelectionGeometry = function() {
736   var room = this.getUserData();
737   var roomLevel = room.getLevel();
738   var levels = this.getHome().getLevels();
739   var floorBottomElevation;
740   var roomElevation;
741   if (roomLevel != null) {
742     roomElevation = roomLevel.getElevation();
743     floorBottomElevation = roomElevation - roomLevel.getFloorThickness();
744   } else {
745     roomElevation = 0;
746     floorBottomElevation = 0;
747   }
748   var firstLevelElevation;
749   if (levels.length == 0) {
750     firstLevelElevation = 0;
751   } else {
752     firstLevelElevation = levels [0].getElevation();
753   }
754   var floorVisible = room.isFloorVisible();
755   var floorBottomVisible = floorVisible
756       && roomLevel != null
757       && roomElevation != firstLevelElevation;
758 
759   var roomPoints = room.getPoints();
760   var ceilingVisible = room.isCeilingVisible();
761   if (!floorVisible && !ceilingVisible) {
762     // If floor and ceiling not visible, draw at least floor contour for feedback
763     floorVisible = true;
764   }
765   var selectionCoordinates = new Array(roomPoints.length * ((floorVisible ? (floorBottomVisible ? 2 : 1) : 0)
766                                        + (ceilingVisible ? 1 : 0)));
767   var indices = new Array ((floorVisible ? (floorBottomVisible ? roomPoints.length * 6 : roomPoints.length * 2) : 0)
768                            + (ceilingVisible ? (roomPoints.length * 2) : 0));
769   var j = 0, k = 0;
770   if (floorVisible) {
771     // Contour at room elevation
772     for (var i = 0; i < roomPoints.length; i++, j++) {
773       selectionCoordinates [j] = vec3.fromValues(roomPoints [i][0], roomElevation, roomPoints [i][1]);
774       indices [k++] = j;
775       if (i > 0) {
776         indices [k++] = j;
777       }
778     }
779     indices [k++] = 0;
780 
781     if (floorBottomVisible) {
782       // Contour at floor bottom
783       for (var i = 0; i < roomPoints.length; i++, j++) {
784         selectionCoordinates [j] = vec3.fromValues(roomPoints [i][0], floorBottomElevation, roomPoints [i][1]);
785         indices [k++] = j;
786         if (i > 0) {
787           indices [k++] = j;
788         }
789       }
790       indices [k++] = roomPoints.length;
791 
792       for (var i = 0; i < roomPoints.length; i++) {
793         indices [k++] = i;
794         indices [k++] = i + roomPoints.length;
795       }
796     }
797   }
798 
799   if (ceilingVisible) {
800     // Contour at room ceiling
801     for (var i = 0; i < roomPoints.length; i++, j++) {
802       selectionCoordinates [j] = vec3.fromValues(roomPoints [i][0], this.getRoomHeightAt(roomPoints [i][0], roomPoints [i][1]), roomPoints [i][1]);
803       indices [k++] = j;
804       if (i > 0) {
805         indices [k++] = j;
806       }
807     }
808     indices [k++] = selectionCoordinates.length - roomPoints.length;
809   }
810 
811   return new IndexedLineArray3D(selectionCoordinates, indices);
812 }
813 
814 /**
815  * Sets room appearance with its color, texture.
816  * @param {boolean} waitTextureLoadingEnd
817  * @private
818  */
819 Room3D.prototype.updateRoomAppearance = function(waitTextureLoadingEnd) {
820   var room = this.getUserData();
821   var ignoreFloorTransparency = room.getLevel() == null || room.getLevel().getElevation() <= 0;
822   this.updateRoomPartAppearance(this.getChild(Room3D.FLOOR_PART).getAppearance(), 
823       room.getFloorTexture(), waitTextureLoadingEnd, room.getFloorColor(), room.getFloorShininess(), room.isFloorVisible(), ignoreFloorTransparency, true);
824   var numChildren = this.getChildren().length;
825   if (numChildren > 2) {
826     var ignoreCeillingTransparency = room.getLevel() == null;
827     this.updateRoomPartAppearance(this.getChild(Room3D.CEILING_PART).getAppearance(), 
828         room.getCeilingTexture(), waitTextureLoadingEnd, room.getCeilingColor(), room.getCeilingShininess(), room.isCeilingVisible(), ignoreCeillingTransparency, false);
829   }
830   var selectionShapeAppearance = this.getChild(numChildren > 2 ? 2 : 1).getAppearance();
831   selectionShapeAppearance.setVisible(this.getUserPreferences() != null
832       && this.getUserPreferences().isEditingIn3DViewEnabled()
833       && this.getHome().isItemSelected(room));      
834 }
835 
836 /**
837  * Sets room part appearance with its color, texture and visibility.
838  * @param {Appearance3D} roomPartAppearance
839  * @param {HomeTexture} roomPartTexture
840  * @param {boolean} waitTextureLoadingEnd
841  * @param {number} roomPartColor
842  * @param {number} shininess
843  * @param {boolean} visible
844  * @param {boolean} ignoreTransparency
845  * @param {boolean} floor
846  * @private
847  */
848 Room3D.prototype.updateRoomPartAppearance = function(roomPartAppearance, roomPartTexture, waitTextureLoadingEnd, roomPartColor, shininess, visible, ignoreTransparency, floor) {
849   if (roomPartTexture == null) {
850     this.updateAppearanceMaterial(roomPartAppearance, roomPartColor, roomPartColor, shininess);
851     roomPartAppearance.setTextureImage(null);
852   } else {
853     this.updateAppearanceMaterial(roomPartAppearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, shininess);
854     var room = this.getUserData();
855     if (roomPartTexture.isFittingArea()) {
856       this.updateTextureTransformFittingArea(roomPartAppearance, roomPartTexture, room.getPoints(), floor);
857     } else {
858       this.updateTextureTransform(roomPartAppearance, roomPartTexture, true);
859     }
860     TextureManager.getInstance().loadTexture(roomPartTexture.getImage(), waitTextureLoadingEnd, {
861         textureUpdated : function(texture) {
862           roomPartAppearance.setTextureImage(texture);
863         },
864         textureError : function(error) {
865           return this.textureUpdated(TextureManager.getInstance().getErrorImage());
866         }
867       });
868   }
869   var upperRoomsAlpha = this.getHome().getEnvironment().getWallsAlpha();
870   if (ignoreTransparency || upperRoomsAlpha === 0) {
871     roomPartAppearance.setTransparency(0);
872   } else {
873     roomPartAppearance.setTransparency(upperRoomsAlpha);
874   }
875   roomPartAppearance.setVisible(visible);
876 }