1 /*
  2  * Wall3D.js
  3  *
  4  * Sweet Home 3D, Copyright (c) 2024 Space Mushrooms <info@sweethome3d.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 //          ModelManager.js
 24 //          TextureManager.js
 25 
 26 
 27 /**
 28  * Creates the 3D wall matching the given home <code>wall</code>.
 29  * @param {Wall} wall
 30  * @param {Home} home
 31  * @param {UserPreferences} [preferences]
 32  * @param {boolean} waitModelAndTextureLoadingEnd
 33  * @constructor
 34  * @extends Object3DBranch
 35  * @author Emmanuel Puybaret
 36  */
 37 function Wall3D(wall, home, preferences, waitModelAndTextureLoadingEnd) {
 38   if (waitModelAndTextureLoadingEnd === undefined) {
 39     // 3 parameters
 40     waitModelAndTextureLoadingEnd = preferences;
 41     preferences = null;
 42   }
 43   Object3DBranch.call(this, wall, home, preferences);
 44 
 45   for (var i = 0; i < 8; i++) {
 46     var wallSideGroup = new Group3D();
 47     wallSideGroup.addChild(this.createWallPartShape());
 48     this.addChild(wallSideGroup);
 49   }
 50   
 51   // Add selection node
 52   var wallSelectionShape = new Shape3D();
 53   wallSelectionShape.setAppearance(this.getSelectionAppearance());
 54   wallSelectionShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
 55   wallSelectionShape.setPickable(false);
 56   this.addChild(wallSelectionShape);
 57 
 58   this.updateWallGeometry(waitModelAndTextureLoadingEnd);
 59   this.updateWallAppearance(waitModelAndTextureLoadingEnd);
 60 }
 61 Wall3D.prototype = Object.create(Object3DBranch.prototype);
 62 Wall3D.prototype.constructor = Wall3D;
 63 
 64 Wall3D.LEVEL_ELEVATION_SHIFT = 0.1;
 65 Wall3D.FULL_FACE_CUT_OUT_AREA = new java.awt.geom.Area(new java.awt.geom.Rectangle2D.Float(-0.5, 0.5, 1, 1));
 66 Wall3D.WALL_LEFT_SIDE = 0;
 67 Wall3D.WALL_RIGHT_SIDE = 1;
 68 
 69 Wall3D.rotatedModelsFrontAreas = [];
 70 
 71 /**
 72  * Returns a new wall part shape with no geometry and a default appearance with
 73  * a white material.
 74  * @return {Node3D}
 75  * @private
 76  */
 77 Wall3D.prototype.createWallPartShape = function() {
 78   var wallShape = new Shape3D();
 79   wallShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
 80   var wallAppearance = new Appearance3D();
 81   wallShape.setAppearance(wallAppearance);
 82   this.updateAppearanceMaterial(wallAppearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, 0);
 83   return wallShape;
 84 }
 85 
 86 Wall3D.prototype.update = function() {
 87   this.updateWallGeometry(false);
 88   this.updateWallAppearance(false);
 89 }
 90 
 91 /**
 92  * Sets the 3D geometry of this wall shapes that matches its 2D geometry.
 93  * @param {boolean} waitDoorOrWindowModelsLoadingEnd
 94  * @private
 95  */
 96 Wall3D.prototype.updateWallGeometry = function(waitDoorOrWindowModelsLoadingEnd) {
 97   this.updateWallSideGeometry(Wall3D.WALL_LEFT_SIDE, waitDoorOrWindowModelsLoadingEnd);
 98   this.updateWallSideGeometry(Wall3D.WALL_RIGHT_SIDE, waitDoorOrWindowModelsLoadingEnd);
 99   this.setPickable(this.getHome().getEnvironment().getWallsAlpha() == 0);
100 
101   var wallSelectionShape = this.getChild(8);
102   wallSelectionShape.addGeometry(this.createWallSelectionGeometry());
103   if (wallSelectionShape.getGeometries().length > 1) {
104     wallSelectionShape.removeGeometry(0);
105   }
106 }
107 
108 Wall3D.prototype.updateWallSideGeometry = function(wallSide, waitDoorOrWindowModelsLoadingEnd) {
109   var wall = this.getUserData();
110   var wallTexture;
111   var baseboard;
112   if (wallSide === Wall3D.WALL_LEFT_SIDE) {
113     wallTexture = wall.getLeftSideTexture();
114     baseboard = wall.getLeftSideBaseboard();
115   } else {
116     wallTexture = wall.getRightSideTexture();
117     baseboard = wall.getRightSideBaseboard();
118   }
119   var wallSideGroups = [this.getChild(wallSide), 
120                         this.getChild(wallSide + 2), 
121                         this.getChild(wallSide + 4), 
122                         this.getChild(wallSide + 6)];
123   var wallFilledShapes = new Array(wallSideGroups.length);
124   var currentGeometriesCounts = new Array(wallSideGroups.length);
125   for (var i = 0; i < wallSideGroups.length; i++) {
126     wallFilledShapes[i] = wallSideGroups[i].getChild(0);
127     currentGeometriesCounts[i] = wallFilledShapes[i].getGeometries().length;
128   }
129   if (wall.getLevel() == null || wall.getLevel().isViewableAndVisible()) {
130     var wallGeometries = [[], [], [], []];
131     this.createWallGeometries(wallGeometries[0], wallGeometries[2], wallGeometries[3], wallSide, 
132         null, wallTexture, waitDoorOrWindowModelsLoadingEnd);
133     if (baseboard != null) {
134       var baseboardTexture = baseboard.getTexture();
135       if (baseboardTexture === null && baseboard.getColor() === null) {
136         baseboardTexture = wallTexture;
137       }
138       this.createWallGeometries(wallGeometries[1], wallGeometries[1], wallGeometries[1], wallSide, 
139           baseboard, baseboardTexture, waitDoorOrWindowModelsLoadingEnd);
140     }
141     for (var i = 0; i < wallSideGroups.length; i++) {
142       for (var j = 0; j < wallGeometries[i].length; j++) {
143         var wallGeometry = wallGeometries[i][j];
144         if (wallGeometry !== null) {
145           wallFilledShapes[i].addGeometry(wallGeometry);
146         }
147       }
148     }
149   }
150   for (var i = 0; i < wallSideGroups.length; i++) {
151     for (var j = currentGeometriesCounts[i] - 1; j >= 0; j--) {
152       wallFilledShapes[i].removeGeometry(j);
153     }
154   }
155 }
156 
157 /**
158 * Creates <code>wall</code> or baseboard geometries computed with windows or doors
159 * that intersect wall.
160 * @param {Array} bottomGeometries
161 * @param {Array} sideGeometries
162 * @param {Array} topGeometries
163 * @param {number} wallSide
164 * @param {Baseboard} baseboard
165 * @param {HomeTexture} texture
166 * @param {boolean} waitDoorOrWindowModelsLoadingEnd
167 * @private
168 */
169 Wall3D.prototype.createWallGeometries = function(bottomGeometries, sideGeometries, topGeometries, wallSide, 
170                                                  baseboard, texture, waitDoorOrWindowModelsLoadingEnd) {
171   var wall = this.getUserData();
172   var wallShape = this.getShape(wall.getPoints());
173   var wallSidePoints = this.getWallSidePoints(wallSide);
174   var wallSideShape = this.getShape(wallSidePoints);
175   var wallSideOrBaseboardPoints = baseboard == null 
176       ? wallSidePoints 
177       : this.getWallBaseboardPoints(wallSide);
178   var wallSideOrBaseboardShape = this.getShape(wallSideOrBaseboardPoints);
179   var wallSideOrBaseboardArea = new java.awt.geom.Area(wallSideOrBaseboardShape);
180   var textureReferencePoint = wallSide === Wall3D.WALL_LEFT_SIDE 
181       ? wallSideOrBaseboardPoints[0].slice(0) 
182       : wallSideOrBaseboardPoints[wallSideOrBaseboardPoints.length - 1].slice(0);
183   var wallElevation = this.getWallElevation(baseboard !== null);
184   var topElevationAtStart;
185   var topElevationAtEnd;
186   if (baseboard === null) {
187     topElevationAtStart = this.getWallTopElevationAtStart();
188     topElevationAtEnd = this.getWallTopElevationAtEnd();
189   } else {
190     topElevationAtStart = 
191     topElevationAtEnd = this.getBaseboardTopElevation(baseboard);
192   }
193   var maxTopElevation = Math.max(topElevationAtStart, topElevationAtEnd);
194   
195   var wallYawAngle = Math.atan2(wall.getYEnd() - wall.getYStart(), wall.getXEnd() - wall.getXStart());
196   var cosWallYawAngle = Math.cos(wallYawAngle);
197   var sinWallYawAngle = Math.sin(wallYawAngle);
198   var wallXStartWithZeroYaw = cosWallYawAngle * wall.getXStart() + sinWallYawAngle * wall.getYStart();
199   var wallXEndWithZeroYaw = cosWallYawAngle * wall.getXEnd() + sinWallYawAngle * wall.getYEnd();
200   var arcExtent = wall.getArcExtent();
201   var roundWall = arcExtent !== null && arcExtent !== 0;
202   var topLineAlpha;
203   var topLineBeta;
204   if (topElevationAtStart === topElevationAtEnd) {
205     topLineAlpha = 0;
206     topLineBeta = topElevationAtStart;
207   } else {
208     topLineAlpha = (topElevationAtEnd - topElevationAtStart) / (wallXEndWithZeroYaw - wallXStartWithZeroYaw);
209     topLineBeta = topElevationAtStart - topLineAlpha * wallXStartWithZeroYaw;
210   }
211   var windowIntersections = [];
212   var intersectingDoorOrWindows = [];
213   var visibleDoorsAndWindows = this.getVisibleDoorsAndWindows(this.getHome().getFurniture());
214   for (var i = 0; i < visibleDoorsAndWindows.length; i++) {
215     var piece = visibleDoorsAndWindows[i];
216     var pieceElevation = piece.getGroundElevation();
217     if (pieceElevation + piece.getHeight() > wallElevation 
218         && pieceElevation < maxTopElevation) {
219       var pieceArea = new java.awt.geom.Area(this.getShape(piece.getPoints()));
220       var intersectionArea = new java.awt.geom.Area(wallShape);
221       intersectionArea.intersect(pieceArea);
222       if (!intersectionArea.isEmpty()) {
223         var deeperPiece = null;
224         if (piece.isParallelToWall(wall)) {
225           if (baseboard !== null) {
226             // Increase piece depth to ensure baseboard will be cut even if the window is as thick as the wall
227             deeperPiece = piece.clone();
228             deeperPiece.setDepthInPlan(deeperPiece.getDepth() + 2 * baseboard.getThickness());
229           }
230           if (typeof HomeDoorOrWindow !== "undefined"
231               && piece instanceof HomeDoorOrWindow) {
232             if (piece.isWallCutOutOnBothSides()) {
233               if (deeperPiece === null) {
234                 deeperPiece = piece.clone();
235               }
236               // Increase piece depth to ensure the wall will be cut on both sides 
237               deeperPiece.setDepthInPlan(deeperPiece.getDepth() + 4 * wall.getThickness());
238             }
239           }
240         }
241         // Recompute intersection on wall side shape only
242         if (deeperPiece !== null) {
243           pieceArea = new java.awt.geom.Area(this.getShape(deeperPiece.getPoints()));
244           intersectionArea = new java.awt.geom.Area(wallSideOrBaseboardShape);
245           intersectionArea.intersect(pieceArea);
246         } else {
247           intersectionArea = new java.awt.geom.Area(wallSideShape);
248           intersectionArea.intersect(pieceArea);
249         }
250         if (!intersectionArea.isEmpty()
251             && (!wall.isTrapezoidal()
252                 || pieceElevation < this.getMaxElevationAtWallIntersection(intersectionArea, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta))) {
253           windowIntersections.push(new Wall3D.DoorOrWindowArea(intersectionArea, [piece]));
254           intersectingDoorOrWindows.push(piece);
255           wallSideOrBaseboardArea.subtract(pieceArea);
256         }
257       }
258     }
259   }
260   if (windowIntersections.length > 1) {
261     for (var windowIndex = 0; windowIndex < windowIntersections.length; windowIndex++) {
262       var windowIntersection = windowIntersections[windowIndex];
263       var otherWindowIntersections = [];
264       var otherWindowIndex = 0;
265       for (var i = 0; i < windowIntersections.length; i++) {
266         var otherWindowIntersection = windowIntersections[i];
267         if (windowIntersection.getArea().isEmpty()) {
268           break;
269         } else if (otherWindowIndex > windowIndex) {
270           var windowsIntersectionArea = new java.awt.geom.Area(otherWindowIntersection.getArea());
271           windowsIntersectionArea.intersect(windowIntersection.getArea());
272           if (!windowsIntersectionArea.isEmpty()) {
273             otherWindowIntersection.getArea().subtract(windowsIntersectionArea);
274             windowIntersection.getArea().subtract(windowsIntersectionArea);
275             var doorsOrWindows = (windowIntersection.getDoorsOrWindows().slice(0));
276             doorsOrWindows.push.apply(doorsOrWindows, otherWindowIntersection.getDoorsOrWindows());
277             otherWindowIntersections.push(new Wall3D.DoorOrWindowArea(windowsIntersectionArea, doorsOrWindows));
278           }
279         }
280         otherWindowIndex++;
281       }
282       windowIntersections.push.apply(windowIntersections, otherWindowIntersections);
283     }
284   }
285   var points = [];
286   var previousPoint = null;
287   for (var it = wallSideOrBaseboardArea.getPathIterator(null); !it.isDone(); it.next()) {
288     var wallPoint = [0, 0];
289     if (it.currentSegment(wallPoint) === java.awt.geom.PathIterator.SEG_CLOSE) {
290       if (points.length > 2) {
291         if (points[0][0] === points[points.length - 1][0]
292             && points[0][1] === points[points.length - 1][1]) {
293           points.splice(points.length - 1, 1);
294         }
295         if (points.length > 2) {
296           var wallPartPoints = points.slice(0);
297           sideGeometries.push(this.createVerticalPartGeometry(wall, wallPartPoints, wallElevation, 
298               cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, baseboard, texture, 
299               textureReferencePoint, wallSide));
300           bottomGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, wallElevation, true, roundWall));
301           topGeometries.push(this.createTopPartGeometry(wallPartPoints, 
302               cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, roundWall));
303         }
304       }
305       points.length = 0;
306       previousPoint = null;
307     } else if (previousPoint === null 
308                || wallPoint[0] !== previousPoint[0]
309                || wallPoint[1] !== previousPoint[1]) {
310       points.push(wallPoint);
311       previousPoint = wallPoint;
312     }
313   }
314   
315   var level = wall.getLevel();
316   previousPoint = null;
317   for (var index = 0; index < windowIntersections.length; index++) {
318     var windowIntersection = windowIntersections[index];
319     if (!windowIntersection.getArea().isEmpty()) {
320       for (var it = windowIntersection.getArea().getPathIterator(null); !it.isDone(); it.next()) {
321         var wallPoint = [0, 0];
322         if (it.currentSegment(wallPoint) === java.awt.geom.PathIterator.SEG_CLOSE) {
323           if (points[0][0] === points[points.length - 1][0]
324               && points[0][1] === points[points.length - 1][1]) {
325             points.splice(points.length - 1, 1);
326           }
327           
328           if (points.length > 2) {
329             var wallPartPoints = points.slice(0);
330             var doorsOrWindows = windowIntersection.getDoorsOrWindows();
331             if (doorsOrWindows.length > 1) {
332               doorsOrWindows.sort(function(piece1, piece2) {
333                   var piece1Elevation = piece1.getGroundElevation();
334                   var piece2Elevation = piece2.getGroundElevation();
335                   if (piece1Elevation < piece2Elevation) {
336                     return -1;
337                   } else if (piece1Elevation > piece2Elevation) {
338                     return 1;
339                   } else {
340                     return 0;
341                   }
342                 });
343             }
344             var lowestDoorOrWindow = doorsOrWindows[0];
345             var lowestDoorOrWindowElevation = lowestDoorOrWindow.getGroundElevation();
346             if (lowestDoorOrWindowElevation > wallElevation) {
347               if (level != null 
348                   && level.getElevation() !== wallElevation 
349                   && lowestDoorOrWindow.getElevation() < Wall3D.LEVEL_ELEVATION_SHIFT) {
350                 lowestDoorOrWindowElevation -= Wall3D.LEVEL_ELEVATION_SHIFT;
351               }
352               sideGeometries.push(this.createVerticalPartGeometry(wall, wallPartPoints, wallElevation, 
353                   cosWallYawAngle, sinWallYawAngle, 0, lowestDoorOrWindowElevation, baseboard, texture, 
354                   textureReferencePoint, wallSide));
355               bottomGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, wallElevation, true, roundWall));
356               sideGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, 
357                   lowestDoorOrWindowElevation, false, roundWall));
358             }
359             for (var i = 0; i < doorsOrWindows.length - 1;) {
360               var lowerDoorOrWindow = doorsOrWindows[i];
361               var lowerDoorOrWindowElevation = lowerDoorOrWindow.getGroundElevation();
362               var higherDoorOrWindow = doorsOrWindows[++i];
363               var higherDoorOrWindowElevation = higherDoorOrWindow.getGroundElevation();
364               while ((lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight() >= higherDoorOrWindowElevation + higherDoorOrWindow.getHeight() 
365                   && ++i < doorsOrWindows.length)) {
366                 higherDoorOrWindow = doorsOrWindows[i];
367               }
368               if (i < doorsOrWindows.length 
369                   && lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight() < higherDoorOrWindowElevation) {
370                 sideGeometries.push(this.createVerticalPartGeometry(wall, wallPartPoints, lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight(), 
371                     cosWallYawAngle, sinWallYawAngle, 0, higherDoorOrWindowElevation, baseboard, texture, textureReferencePoint, wallSide));
372                 sideGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, 
373                     lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight(), true, roundWall));
374                 sideGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, higherDoorOrWindowElevation, false, roundWall));
375               }
376             }
377             var highestDoorOrWindow = doorsOrWindows[doorsOrWindows.length - 1];
378             var highestDoorOrWindowElevation = highestDoorOrWindow.getGroundElevation();
379             for (var i = doorsOrWindows.length - 2; i >= 0; i--) {
380               var doorOrWindow = doorsOrWindows[i];
381               if (doorOrWindow.getGroundElevation() + doorOrWindow.getHeight() > highestDoorOrWindowElevation + highestDoorOrWindow.getHeight()) {
382                 highestDoorOrWindow = doorOrWindow;
383               }
384             }
385             var doorOrWindowTop = highestDoorOrWindowElevation + highestDoorOrWindow.getHeight();
386             var generateGeometry = true;
387             for (var i = 0; i < wallPartPoints.length; i++) {
388               var xTopPointWithZeroYaw = cosWallYawAngle * wallPartPoints[i][0] + sinWallYawAngle * wallPartPoints[i][1];
389               var topPointWithZeroYawElevation = topLineAlpha * xTopPointWithZeroYaw + topLineBeta;
390               if (doorOrWindowTop > topPointWithZeroYawElevation) {
391                 if (topLineAlpha === 0 || roundWall) {
392                   generateGeometry = false;
393                   break;
394                 }
395                 var translation = (doorOrWindowTop - topPointWithZeroYawElevation) / topLineAlpha;
396                 wallPartPoints[i][0] += (translation * cosWallYawAngle);
397                 wallPartPoints[i][1] += (translation * sinWallYawAngle);
398               }
399             }
400             if (generateGeometry) {
401               sideGeometries.push(this.createVerticalPartGeometry(wall, wallPartPoints, doorOrWindowTop, 
402                   cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, baseboard, texture, textureReferencePoint, wallSide));
403               sideGeometries.push(this.createHorizontalPartGeometry(wallPartPoints, doorOrWindowTop, true, roundWall));
404               topGeometries.push(this.createTopPartGeometry(wallPartPoints, 
405                   cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, roundWall));
406             }
407           }
408           points.length = 0;
409           previousPoint = null;
410         } else if (previousPoint == null 
411                  || wallPoint[0] !== previousPoint[0]
412                  || wallPoint[1] !== previousPoint[1]) {
413           points.push(wallPoint);
414           previousPoint = wallPoint;
415         }
416       }
417     }
418   }
419   
420   if (!roundWall && intersectingDoorOrWindows.length > 0) {
421     var epsilon = Math.PI / 720;
422     var missingModels = [];
423     for (var i = 0; i < intersectingDoorOrWindows.length; i++) {
424       var piece = intersectingDoorOrWindows[i];
425       if (typeof HomeDoorOrWindow !== "undefined"
426           && piece instanceof HomeDoorOrWindow 
427           && (PieceOfFurniture.DEFAULT_CUT_OUT_SHAPE != piece.getCutOutShape()
428               || piece.getWallWidth() !== 1
429               || piece.getWallLeft() !== 0
430               || piece.getWallHeight() !== 1
431               || piece.getWallTop() !== 0)) {
432         var angleDifference = Math.abs(wallYawAngle - piece.getAngle()) % (2 * Math.PI);
433         if (angleDifference < epsilon 
434             || angleDifference > 2 * Math.PI - epsilon 
435             || Math.abs(angleDifference - Math.PI) < epsilon) {
436           var frontOrBackSide = Math.abs(angleDifference - Math.PI) < epsilon ? 1 : -1;
437           var rotatedModelFrontArea = Wall3D.getRotatedModelFrontArea(piece);
438           if (rotatedModelFrontArea !== null 
439               && (missingModels.length === 0 || !waitDoorOrWindowModelsLoadingEnd)) {
440             this.createGeometriesSurroundingDoorOrWindow(piece, rotatedModelFrontArea, frontOrBackSide, 
441                 wall, sideGeometries, topGeometries, 
442                 wallSideOrBaseboardPoints, wallElevation, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, 
443                 texture, textureReferencePoint, wallSide);
444           } else {
445             missingModels.push(piece);
446           }
447         }
448       }
449     }
450     if (missingModels.length > 0) {
451       var modelManager = ModelManager.getInstance();
452       missingModels = missingModels.slice(0);
453       var wall3d = this;
454       for (var i = 0; i < missingModels.length; i++) {
455         var modelObserver = {
456             doorOrWindow : missingModels[i],
457             modelUpdated : function(modelRoot) {
458               var rotatedModelFrontArea = Wall3D.getRotatedModelFrontArea(this.doorOrWindow);
459               var frontArea;
460               if (rotatedModelFrontArea === null) {
461                 var rotation = new TransformGroup3D(modelManager.getRotationTransformation(this.doorOrWindow.getModelRotation()));
462                 rotation.addChild(modelRoot);
463                 frontArea = modelManager.getFrontArea(this.doorOrWindow.getCutOutShape(), rotation);
464                 Wall3D.rotatedModelsFrontAreas.push({
465                     model : this.doorOrWindow.getModel(),
466                     modelRotation : this.doorOrWindow.getModelRotation(),
467                     cutOutShape : this.doorOrWindow.getCutOutShape(),
468                     frontArea : frontArea});
469               }
470               
471               var angleDifference = Math.abs(wallYawAngle - this.doorOrWindow.getAngle()) % (2 * Math.PI);
472               var frontOrBackSide = Math.abs(angleDifference - Math.PI) < epsilon ? 1 : -1;
473               if (waitDoorOrWindowModelsLoadingEnd) {
474                 wall3d.createGeometriesSurroundingDoorOrWindow(this.doorOrWindow, frontArea, frontOrBackSide, 
475                     wall, sideGeometries, topGeometries, wallSideOrBaseboardPoints, wallElevation, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, 
476                     texture, textureReferencePoint, wallSide);
477               } else {
478                 missingModels.splice(missingModels.indexOf(this.doorOrWindow), 1);
479                 if (missingModels.length === 0 
480                     && this.baseboard == null) {
481                   setTimeout(function() {
482                       wall3d.updateWallSideGeometry(wallSide, waitDoorOrWindowModelsLoadingEnd);
483                     }, 0);
484                 }
485               }
486             },        
487             modelError : function(ex) {
488               if (Wall3D.getRotatedModelFrontArea(this.doorOrWindow) === null) {
489                 Wall3D.rotatedModelsFrontAreas.push({
490                     model : this.doorOrWindow.getModel(),
491                     modelRotation : this.doorOrWindow.getModelRotation(),
492                     cutOutShape : this.doorOrWindow.getCutOutShape(),
493                     frontArea : Wall3D.FULL_FACE_CUT_OUT_AREA});
494               }
495               if (!waitDoorOrWindowModelsLoadingEnd) {
496                 missingModels.splice(missingModels.indexOf(this.doorOrWindow), 1);
497               }
498             }
499           };
500         modelManager.loadModel(missingModels[i].getModel(), waitDoorOrWindowModelsLoadingEnd, modelObserver); 
501       }
502     }
503   }
504 }
505 
506 /**
507  * Returns the front area of the given piece if already computed. 
508  * @private
509  */
510 Wall3D.getRotatedModelFrontArea = function(piece) {
511   var rotatedModelFrontArea = null;
512   for (var j = 0; j < Wall3D.rotatedModelsFrontAreas.length; j++) {
513     if (Wall3D.rotatedModelsFrontAreas [j].model.equals(piece.getModel())
514         && Object3DBranch.areModelRotationsEqual(Wall3D.rotatedModelsFrontAreas [j].modelRotation, piece.getModelRotation())
515         && Wall3D.rotatedModelsFrontAreas [j].cutOutShape == piece.getCutOutShape()) {
516       return Wall3D.rotatedModelsFrontAreas [j].frontArea;
517     }
518   }
519   return null;
520 }
521 
522 /**
523 * Returns all the visible doors and windows in the given <code>furniture</code>.
524 * @param {Array} furniture
525 * @return {Array}
526 * @private
527 */
528 Wall3D.prototype.getVisibleDoorsAndWindows = function(furniture) {
529   var visibleDoorsAndWindows = [];
530   for (var i = 0; i < furniture.length; i++) {
531     var piece = furniture[i];
532     if (piece.isVisible() 
533         && (piece.getLevel() == null 
534             || piece.getLevel().isViewableAndVisible())) {
535       if (piece instanceof HomeFurnitureGroup) {
536         visibleDoorsAndWindows.push.apply(visibleDoorsAndWindows, this.getVisibleDoorsAndWindows(piece.getFurniture()));
537       } else if (piece.isDoorOrWindow()) {
538         visibleDoorsAndWindows.push(piece);
539       }
540     }
541   }
542   return visibleDoorsAndWindows;
543 }
544 
545 /**
546 * Returns the points of one of the side of this wall.
547 * @param {number} wallSide
548 * @return {Array}
549 * @private
550 */
551 Wall3D.prototype.getWallSidePoints = function(wallSide) {
552   var wall = this.getUserData();
553   var wallPoints = wall.getPoints();
554   
555   if (wallSide === Wall3D.WALL_LEFT_SIDE) {
556     for (var i = (wallPoints.length / 2 | 0); i < wallPoints.length; i++) {
557       wallPoints[i][0] = (wallPoints[i][0] + wallPoints[wallPoints.length - i - 1][0]) / 2;
558       wallPoints[i][1] = (wallPoints[i][1] + wallPoints[wallPoints.length - i - 1][1]) / 2;
559     }
560   } else {
561     for (var i = 0, n = (wallPoints.length / 2 | 0); i < n; i++) {
562       wallPoints[i][0] = (wallPoints[i][0] + wallPoints[wallPoints.length - i - 1][0]) / 2;
563       wallPoints[i][1] = (wallPoints[i][1] + wallPoints[wallPoints.length - i - 1][1]) / 2;
564     }
565   }
566   return wallPoints;
567 }
568 
569 /**
570 * Returns the points of one of the baseboard of this wall.
571 * @param {number} wallSide
572 * @return {Array}
573 * @private
574 */
575 Wall3D.prototype.getWallBaseboardPoints = function(wallSide) {
576   var wall = this.getUserData();
577   var wallPointsIncludingBaseboards = wall.getPoints$boolean(true);
578   var wallPoints = wall.getPoints();
579   
580   if (wallSide === Wall3D.WALL_LEFT_SIDE) {
581     for (var i = Math.floor(wallPointsIncludingBaseboards.length / 2); i < wallPointsIncludingBaseboards.length; i++) {
582       wallPointsIncludingBaseboards[i] = wallPoints[wallPoints.length - i - 1];
583     }
584   } else {
585     for (var i = 0, n = Math.floor(wallPoints.length / 2); i < n; i++) {
586       wallPointsIncludingBaseboards[i] = wallPoints[wallPoints.length - i - 1];
587     }
588   }
589   return wallPointsIncludingBaseboards;
590 }
591 
592 /**
593 * Returns the vertical rectangles that join each point of <code>points</code>
594 * and spread from <code>minElevation</code> to a top line (y = ax + b) described by <code>topLineAlpha</code>
595 * and <code>topLineBeta</code> factors in a vertical plan that is rotated around
596 * vertical axis matching <code>cosWallYawAngle</code> and <code>sinWallYawAngle</code>.
597 * @param {Wall} wall
598 * @param {Array} points
599 * @param {number} minElevation
600 * @param {number} cosWallYawAngle
601 * @param {number} sinWallYawAngle
602 * @param {number} topLineAlpha
603 * @param {number} topLineBeta
604 * @param {Baseboard} baseboard
605 * @param {HomeTexture} texture
606 * @param {Array} textureReferencePoint
607 * @param {number} wallSide
608 * @return {IndexedGeometryArray3D}
609 * @private
610 */
611 Wall3D.prototype.createVerticalPartGeometry = function(wall, points, minElevation, 
612                                                        cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, 
613                                                        baseboard, texture, textureReferencePoint, wallSide) {
614   var subpartSize = this.getHome().getEnvironment().getSubpartSizeUnderLight();
615   var arcExtent = wall.getArcExtent();
616   if ((arcExtent === null || arcExtent === 0) && subpartSize > 0) {
617     var pointsList = [];
618     pointsList.push(points[0]);
619     for (var i = 1; i < points.length; i++) {
620       var distance = java.awt.geom.Point2D.distance(points[i - 1][0], points[i - 1][1], points[i][0], points[i][1]) - subpartSize / 2;
621       var angle = Math.atan2(points[i][1] - points[i - 1][1], points[i][0] - points[i - 1][0]);
622       var cosAngle = Math.cos(angle);
623       var sinAngle = Math.sin(angle);
624       for (var d = 0; d < distance; d += subpartSize) {
625         pointsList.push([(points[i - 1][0] + d * cosAngle), (points[i - 1][1] + d * sinAngle)]);
626       }
627       pointsList.push(points[i]);
628     }
629     points = pointsList.slice(0);
630   }
631   
632   var bottom = new Array(points.length);
633   var top = new Array(points.length);
634   var pointUCoordinates = new Array(points.length);
635   var xStart = wall.getXStart();
636   var yStart = wall.getYStart();
637   var xEnd = wall.getXEnd();
638   var yEnd = wall.getYEnd();
639   var arcCircleCenter = null;
640   var arcCircleRadius = 0;
641   var referencePointAngle = 0;
642   if (arcExtent !== null && arcExtent !== 0) {
643     arcCircleCenter = [wall.getXArcCircleCenter(), wall.getYArcCircleCenter()];
644     arcCircleRadius = java.awt.geom.Point2D.distance(arcCircleCenter[0], arcCircleCenter[1], xStart, yStart);
645     referencePointAngle = Math.fround(Math.atan2(textureReferencePoint[1] - arcCircleCenter[1], textureReferencePoint[0] - arcCircleCenter[0]));
646   }
647   for (var i = 0; i < points.length; i++) {
648     bottom[i] = vec3.fromValues(points[i][0], minElevation, points[i][1]);
649     var topY = this.getWallPointElevation(points[i][0], points[i][1], cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta);
650     top[i] = vec3.fromValues(points[i][0], topY, points[i][1]);
651   }
652   var distanceSqToWallMiddle = new Array(points.length);
653   for (var i = 0; i < points.length; i++) {
654     if (arcCircleCenter === null) {
655       distanceSqToWallMiddle[i] = java.awt.geom.Line2D.ptLineDistSq(xStart, yStart, xEnd, yEnd, bottom[i][0], bottom[i][2]);
656     } else {
657       distanceSqToWallMiddle[i] = arcCircleRadius - java.awt.geom.Point2D.distance(arcCircleCenter[0], arcCircleCenter[1], bottom[i][0], bottom[i][2]);
658       distanceSqToWallMiddle[i] *= distanceSqToWallMiddle[i];
659     }
660   }
661   var rectanglesCount = points.length;
662   var usedRectangle = new Array(points.length);
663   if (baseboard === null) {
664     for (var i = 0; i < points.length - 1; i++) {
665       usedRectangle[i] = distanceSqToWallMiddle[i] > 0.001 
666           || distanceSqToWallMiddle[i + 1] > 0.001;
667       if (!usedRectangle[i]) {
668         rectanglesCount--;
669       }
670     }
671     usedRectangle[usedRectangle.length - 1] = distanceSqToWallMiddle[0] > 0.001 
672         || distanceSqToWallMiddle[points.length - 1] > 0.001;
673     if (!usedRectangle[usedRectangle.length - 1]) {
674       rectanglesCount--;
675     }
676     if (rectanglesCount === 0) {
677       return null;
678     }
679   } else {
680     for (var i = 0; i < usedRectangle.length; i++) {
681       usedRectangle [i] = true;
682     }
683   }
684   
685   var coords = [];
686   for (var index = 0; index < points.length; index++) {
687     if (usedRectangle[index]) {
688       var y = minElevation;
689       var point1 = bottom[index];
690       var nextIndex = (index + 1) % points.length;
691       var point2 = bottom[nextIndex];
692       if (subpartSize > 0) {
693         for (var yMax = Math.min(top[index][1], top[nextIndex][1]) - subpartSize / 2; y < yMax; y += subpartSize) {
694           coords.push(point1);
695           coords.push(point2);
696           point1 = vec3.fromValues(bottom[index][0], y, bottom[index][2]);
697           point2 = vec3.fromValues(bottom[nextIndex][0], y, bottom[nextIndex][2]);
698           coords.push(point2);
699           coords.push(point1);
700         }
701       }
702       coords.push(point1);
703       coords.push(point2);
704       coords.push(top[nextIndex]);
705       coords.push(top[index]);
706     }
707   }
708   
709   var geometryInfo = new GeometryInfo3D(GeometryInfo3D.QUAD_ARRAY);
710   geometryInfo.setCoordinates(coords.slice(0));
711   
712   if (texture !== null) {
713     var halfThicknessSq;
714     if (baseboard !== null) {
715       halfThicknessSq = wall.getThickness() / 2 + baseboard.getThickness();
716       halfThicknessSq *= halfThicknessSq;
717     } else {
718       halfThicknessSq = (wall.getThickness() * wall.getThickness()) / 4;
719     }
720     var textureCoords = new Array(coords.length);
721     var firstTextureCoords = vec2.fromValues(0, minElevation);
722     var j = 0;
723     var epsilon = arcCircleCenter === null 
724         ? wall.getThickness() / 10000.0 
725         : halfThicknessSq / 4;
726     for (var index = 0; index < points.length; index++) {
727       if (usedRectangle[index]) {
728         var nextIndex = (index + 1) % points.length;
729         var textureCoords1;
730         var textureCoords2;
731         if (Math.abs(distanceSqToWallMiddle[index] - halfThicknessSq) < epsilon 
732             && Math.abs(distanceSqToWallMiddle[nextIndex] - halfThicknessSq) < epsilon) {
733           var firstHorizontalTextureCoords;
734           var secondHorizontalTextureCoords;
735           if (arcCircleCenter === null) {
736             firstHorizontalTextureCoords = java.awt.geom.Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], 
737                 points[index][0], points[index][1]);
738             secondHorizontalTextureCoords = java.awt.geom.Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], 
739                 points[nextIndex][0], points[nextIndex][1]);
740           } else {
741             if (pointUCoordinates[index] === undefined) {
742               var pointAngle = Math.fround(Math.atan2(points[index][1] - arcCircleCenter[1], points[index][0] - arcCircleCenter[0]));
743               pointAngle = this.adjustAngleOnReferencePointAngle(pointAngle, referencePointAngle, arcExtent);
744               pointUCoordinates[index] = (pointAngle - referencePointAngle) * arcCircleRadius;
745             }
746             if (pointUCoordinates[nextIndex] === undefined) {
747               var pointAngle = Math.fround(Math.atan2(points[nextIndex][1] - arcCircleCenter[1], points[nextIndex][0] - arcCircleCenter[0]));
748               pointAngle = this.adjustAngleOnReferencePointAngle(pointAngle, referencePointAngle, arcExtent);
749               pointUCoordinates[nextIndex] = (pointAngle - referencePointAngle) * arcCircleRadius;
750             }
751             firstHorizontalTextureCoords = pointUCoordinates[index];
752             secondHorizontalTextureCoords = pointUCoordinates[nextIndex];
753           }
754           if (wallSide === Wall3D.WALL_LEFT_SIDE && texture.isLeftToRightOriented()) {
755             firstHorizontalTextureCoords = -firstHorizontalTextureCoords;
756             secondHorizontalTextureCoords = -secondHorizontalTextureCoords;
757           }
758           
759           textureCoords1 = vec2.fromValues(firstHorizontalTextureCoords, minElevation);
760           textureCoords2 = vec2.fromValues(secondHorizontalTextureCoords, minElevation);
761         } else {
762           textureCoords1 = firstTextureCoords;
763           var horizontalTextureCoords = java.awt.geom.Point2D.distance(points[index][0], points[index][1], points[nextIndex][0], 
764               points[nextIndex][1]);
765           textureCoords2 = vec2.fromValues(horizontalTextureCoords, minElevation);
766         }
767         
768         if (subpartSize > 0) {
769           var y = minElevation;
770           for (var yMax = Math.min(top[index][1], top[nextIndex][1]) - subpartSize / 2; y < yMax; y += subpartSize) {
771             textureCoords[j++] = textureCoords1;
772             textureCoords[j++] = textureCoords2;
773             textureCoords1 = vec2.fromValues(textureCoords1[0], y);
774             textureCoords2 = vec2.fromValues(textureCoords2[0], y);
775             textureCoords[j++] = textureCoords2;
776             textureCoords[j++] = textureCoords1;
777           }
778         }
779         textureCoords[j++] = textureCoords1;
780         textureCoords[j++] = textureCoords2;
781         textureCoords[j++] = vec2.fromValues(textureCoords2[0], top[nextIndex][1]);
782         textureCoords[j++] = vec2.fromValues(textureCoords1[0], top[index][1]);
783       }
784     }
785     geometryInfo.setTextureCoordinates(textureCoords);
786   }
787   if (arcCircleCenter === null) {
788     geometryInfo.setCreaseAngle(0);
789   }
790   geometryInfo.setGeneratedNormals(true);
791   return geometryInfo.getIndexedGeometryArray();
792 }
793 
794 /**
795  * Returns the maximum wall elevation of each point of the given intersection.
796  * @param {Area} pieceWallIntersection
797  * @param {number} cosWallYawAngle
798  * @param {number} sinWallYawAngle
799  * @param {number} topLineAlpha
800  * @param {number} topLineBeta
801  * @return {number}
802  * @private
803  */
804 Wall3D.prototype.getMaxElevationAtWallIntersection = function(pieceWallIntersection, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta) {
805   var maxElevation = -Infinity;
806   for (var it = pieceWallIntersection.getPathIterator(null); !it.isDone(); it.next()) {
807     var wallPoint = [0, 0];
808     if (it.currentSegment(wallPoint) !== java.awt.geom.PathIterator.SEG_CLOSE) {
809       maxElevation = Math.max(maxElevation, this.getWallPointElevation(wallPoint [0], wallPoint [1],
810             cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta));
811     }
812   }
813   return maxElevation;
814 }
815 
816 /**
817 * Returns the elevation of the wall at the given point.
818 * @param {number} xWallPoint
819 * @param {number} yWallPoint
820 * @param {number} cosWallYawAngle
821 * @param {number} sinWallYawAngle
822 * @param {number} topLineAlpha
823 * @param {number} topLineBeta
824 * @return {number}
825 * @private
826 */
827 Wall3D.prototype.getWallPointElevation = function(xWallPoint, yWallPoint, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta) {
828   var xTopPointWithZeroYaw = cosWallYawAngle * xWallPoint + sinWallYawAngle * yWallPoint;
829   return (topLineAlpha * xTopPointWithZeroYaw + topLineBeta);
830 }
831 
832 /**
833 * Returns <code>pointAngle</code> plus or minus 2 PI to ensure <code>pointAngle</code> value
834 * will be greater or lower than <code>referencePointAngle</code> depending on <code>arcExtent</code> direction.
835 * @param {number} pointAngle
836 * @param {number} referencePointAngle
837 * @param {number} arcExtent
838 * @return {number}
839 * @private
840 */
841 Wall3D.prototype.adjustAngleOnReferencePointAngle = function(pointAngle, referencePointAngle, arcExtent) {
842   if (arcExtent > 0) {
843     if ((referencePointAngle > 0 
844         && (pointAngle < 0
845             || pointAngle < referencePointAngle)) 
846       || (referencePointAngle < 0 
847            && pointAngle < referencePointAngle)) {
848       pointAngle += 2 * Math.PI;
849     }
850   } else {
851     if ((referencePointAngle < 0 
852           && (pointAngle > 0
853               || referencePointAngle < pointAngle)) 
854         || (referencePointAngle > 0 
855             && referencePointAngle < pointAngle)) {
856       pointAngle -= 2 * Math.PI;
857     }
858   }
859   return pointAngle;
860 }
861 
862 /**
863 * Returns the geometry of an horizontal part of a wall or a baseboard at <code>y</code>.
864 * @param {Array} points
865 * @param {number} y
866 * @param {boolean} reverseOrder
867 * @param {boolean} roundWall
868 * @return {IndexedGeometryArray3D}
869 * @private
870 */
871 Wall3D.prototype.createHorizontalPartGeometry = function(points, y, reverseOrder, roundWall) {
872   var coords = new Array(points.length);
873   for (var i = 0; i < points.length; i++) {
874     coords[i] = vec3.fromValues(points[i][0], y, points[i][1]);
875   }
876   var geometryInfo = new GeometryInfo3D(GeometryInfo3D.POLYGON_ARRAY);
877   geometryInfo.setCoordinates(reverseOrder ? coords.reverse() : coords);
878   geometryInfo.setStripCounts([coords.length]);
879   if (roundWall) {
880     geometryInfo.setCreaseAngle(0);
881   }
882   geometryInfo.setGeneratedNormals(true);
883   return geometryInfo.getIndexedGeometryArray();
884 }
885 
886 /**
887 * Returns the geometry of the top part of a wall or a baseboard.
888 * @param {Array} points
889 * @param {number} cosWallYawAngle
890 * @param {number} sinWallYawAngle
891 * @param {number} topLineAlpha
892 * @param {number} topLineBeta
893 * @param {boolean} roundWall
894 * @return {IndexedGeometryArray3D}
895 * @private
896 */
897 Wall3D.prototype.createTopPartGeometry = function(points, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, roundWall) {
898   var coords = new Array(points.length);
899   for (var i = 0; i < points.length; i++) {
900     var xTopPointWithZeroYaw = cosWallYawAngle * points[i][0] + sinWallYawAngle * points[i][1];
901     var topY = (topLineAlpha * xTopPointWithZeroYaw + topLineBeta);
902     coords[i] = vec3.fromValues(points[i][0], topY, points[i][1]);
903   }
904   var geometryInfo = new GeometryInfo3D(GeometryInfo3D.POLYGON_ARRAY);
905   geometryInfo.setCoordinates(coords);
906   geometryInfo.setStripCounts([coords.length]);
907   if (roundWall) {
908     geometryInfo.setCreaseAngle(0);
909   }
910   geometryInfo.setGeneratedNormals(true);
911   return geometryInfo.getIndexedGeometryArray();
912 }
913 
914 /**
915 * Creates the geometry surrounding the given non rectangular door or window.
916 * @param {HomeDoorOrWindow} doorOrWindow
917 * @param {Area} doorOrWindowFrontArea
918 * @param {number} frontOrBackSide
919 * @param {Wall} wall
920 * @param {Array} wallGeometries
921 * @param {Array} wallTopGeometries
922 * @param {Array} wallSidePoints
923 * @param {number} wallElevation
924 * @param {number} cosWallYawAngle
925 * @param {number} sinWallYawAngle
926 * @param {number} topLineAlpha
927 * @param {number} topLineBeta
928 * @param {HomeTexture} texture
929 * @param {Array} textureReferencePoint
930 * @param {number} wallSide
931 * @private
932 */
933 Wall3D.prototype.createGeometriesSurroundingDoorOrWindow = function(doorOrWindow, doorOrWindowFrontArea, frontOrBackSide, wall, wallGeometries, wallTopGeometries, 
934                                                                     wallSidePoints, wallElevation, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, 
935                                                                     texture, textureReferencePoint, wallSide) {
936   if (doorOrWindow.getModelTransformations() !== null) {
937     doorOrWindowFrontArea = new java.awt.geom.Area(doorOrWindowFrontArea);
938     doorOrWindowFrontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(0.5, 0.5));
939     doorOrWindowFrontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(
940         doorOrWindow.getWidth() * doorOrWindow.getWallWidth(),
941         doorOrWindow.getHeight() * doorOrWindow.getWallHeight()));
942     doorOrWindowFrontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(
943         doorOrWindow.getWallLeft() * doorOrWindow.getWidth(),
944         (1 - doorOrWindow.getWallHeight() - doorOrWindow.getWallTop()) * doorOrWindow.getHeight()));
945     doorOrWindowFrontArea.transform(java.awt.geom.AffineTransform.getScaleInstance(
946         1 / doorOrWindow.getWidth(),
947         1 / doorOrWindow.getHeight()));
948     doorOrWindowFrontArea.transform(java.awt.geom.AffineTransform.getTranslateInstance(-0.5, -0.5));
949   }
950 
951   var fullFaceArea = new java.awt.geom.Area(Wall3D.FULL_FACE_CUT_OUT_AREA);
952   fullFaceArea.subtract(doorOrWindowFrontArea);
953   if (!fullFaceArea.isEmpty()) {
954     var doorOrWindowDepth = doorOrWindow.getDepth();
955     var xPieceSide = (doorOrWindow.getX() - frontOrBackSide * doorOrWindowDepth / 2 * Math.sin(doorOrWindow.getAngle()));
956     var yPieceSide = (doorOrWindow.getY() + frontOrBackSide * doorOrWindowDepth / 2 * Math.cos(doorOrWindow.getAngle()));
957     var wallFirstPoint = wallSide === Wall3D.WALL_LEFT_SIDE 
958         ? wallSidePoints[0] 
959         : wallSidePoints[wallSidePoints.length - 1];
960     var wallSecondPoint = wallSide === Wall3D.WALL_LEFT_SIDE 
961         ? wallSidePoints[(wallSidePoints.length / 2 | 0) - 1] 
962         : wallSidePoints[(wallSidePoints.length / 2 | 0)];
963     var frontSideToWallDistance = java.awt.geom.Line2D.ptLineDist(wallFirstPoint[0], wallFirstPoint[1], wallSecondPoint[0], 
964         wallSecondPoint[1], xPieceSide, yPieceSide);
965     var position = java.awt.geom.Line2D.relativeCCW(wallFirstPoint[0], wallFirstPoint[1], 
966         wallSecondPoint[0], wallSecondPoint[1], xPieceSide, yPieceSide);
967     var depthTranslation = frontOrBackSide * (0.5 - position * frontSideToWallDistance / doorOrWindowDepth);
968     
969     var frontAreaTransform = ModelManager.getInstance().getPieceOfFurnitureNormalizedModelTransformation(doorOrWindow, null);
970     var frontAreaTranslation = mat4.create();
971     mat4.fromTranslation(frontAreaTranslation, vec3.fromValues(0, 0, depthTranslation));
972     mat4.mul(frontAreaTransform, frontAreaTransform, frontAreaTranslation);
973     
974     var invertedFrontAreaTransform = mat4.create();
975     mat4.invert(invertedFrontAreaTransform, frontAreaTransform);
976     var wallPath = new java.awt.geom.GeneralPath();
977     var wallPoint = vec3.fromValues(wallFirstPoint[0], wallElevation, wallFirstPoint[1]);
978     vec3.transformMat4(wallPoint, wallPoint, invertedFrontAreaTransform);
979     wallPath.moveTo(wallPoint[0], wallPoint[1]);
980     wallPoint = vec3.fromValues(wallSecondPoint[0], wallElevation, wallSecondPoint[1]);
981     vec3.transformMat4(wallPoint, wallPoint, invertedFrontAreaTransform);
982     wallPath.lineTo(wallPoint[0], wallPoint[1]);
983     var topWallPoint1 = vec3.fromValues(wallSecondPoint[0], this.getWallPointElevation(wallSecondPoint[0], wallSecondPoint[1], cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta), wallSecondPoint[1]);
984     vec3.transformMat4(topWallPoint1, topWallPoint1, invertedFrontAreaTransform);
985     wallPath.lineTo(topWallPoint1[0], topWallPoint1[1]);
986     var topWallPoint2 = vec3.fromValues(wallFirstPoint[0], this.getWallPointElevation(wallFirstPoint[0], wallFirstPoint[1], cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta), wallFirstPoint[1]);
987     vec3.transformMat4(topWallPoint2, topWallPoint2, invertedFrontAreaTransform);
988     wallPath.lineTo(topWallPoint2[0], topWallPoint2[1]);
989     wallPath.closePath();
990     
991     var doorOrWindowSurroundingPath = new java.awt.geom.GeneralPath();
992     doorOrWindowSurroundingPath.moveTo(-0.5, -0.5);
993     doorOrWindowSurroundingPath.lineTo(-0.5, 0.5);
994     doorOrWindowSurroundingPath.lineTo(0.5, 0.5);
995     doorOrWindowSurroundingPath.lineTo(0.5, -0.5);
996     doorOrWindowSurroundingPath.closePath();
997     
998     var doorOrWindowSurroundingArea = new java.awt.geom.Area(doorOrWindowSurroundingPath);
999     doorOrWindowSurroundingArea.intersect(new java.awt.geom.Area(wallPath));
1000     doorOrWindowSurroundingArea.subtract(doorOrWindowFrontArea);
1001     var flatness = 0.5 / (Math.max(doorOrWindow.getWidth(), doorOrWindow.getHeight()));
1002     if (!doorOrWindowSurroundingArea.isEmpty()) {
1003       var reversed = frontOrBackSide > 0 !== (wallSide === Wall3D.WALL_RIGHT_SIDE) !== doorOrWindow.isModelMirrored();
1004       var doorOrWindowSurroundingAreasPoints = this.getAreaPoints(doorOrWindowSurroundingArea, flatness, reversed);
1005       if (!(doorOrWindowSurroundingAreasPoints.length === 0)) {
1006         var stripCounts = new Array(doorOrWindowSurroundingAreasPoints.length);
1007         var vertexCount = 0;
1008         for (var i = 0; i < doorOrWindowSurroundingAreasPoints.length; i++) {
1009           var areaPoints = doorOrWindowSurroundingAreasPoints[i];
1010           stripCounts[i] = areaPoints.length + 1;
1011           vertexCount += stripCounts[i];
1012         }
1013         var halfWallThickness = wall.getThickness() / 2;
1014         var deltaXToWallMiddle = halfWallThickness * sinWallYawAngle;
1015         var deltaZToWallMiddle = -halfWallThickness * cosWallYawAngle;
1016         if (wallSide === Wall3D.WALL_LEFT_SIDE) {
1017           deltaXToWallMiddle *= -1;
1018           deltaZToWallMiddle *= -1;
1019         }
1020         var coords = new Array(vertexCount);
1021         var borderCoords = ([]);
1022         var slopingTopCoords = ([]);
1023         var textureCoords;
1024         var borderTextureCoords;
1025         if (texture != null) {
1026           textureCoords = new Array(coords.length);
1027           borderTextureCoords = [];
1028         } else {
1029           textureCoords = null;
1030           borderTextureCoords = null;
1031         }
1032         var i = 0;
1033         for (var index = 0; index < doorOrWindowSurroundingAreasPoints.length; index++) {
1034           var areaPoints = doorOrWindowSurroundingAreasPoints[index];
1035           var point = vec3.fromValues(areaPoints[0][0], areaPoints[0][1], 0);
1036           vec3.transformMat4(point, point, frontAreaTransform);
1037           var textureCoord = null;
1038           if (texture != null) {
1039             var horizontalTextureCoords = java.awt.geom.Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], point[0], point[2]);
1040             if (wallSide === Wall3D.WALL_LEFT_SIDE && texture.isLeftToRightOriented()) {
1041               horizontalTextureCoords = -horizontalTextureCoords;
1042             }
1043             textureCoord = vec2.fromValues(horizontalTextureCoords, point[1]);
1044           }
1045           var distanceToTop = java.awt.geom.Line2D.ptLineDistSq(topWallPoint1[0], topWallPoint1[1], topWallPoint2[0], topWallPoint2[1], 
1046               areaPoints[0][0], areaPoints[0][1]);
1047           
1048           for (var j = 0; j < areaPoints.length; j++, i++) {
1049             coords[i] = point;
1050             if (texture != null) {
1051               textureCoords[i] = textureCoord;
1052             }
1053             
1054             var nextPointIndex = j < areaPoints.length - 1 
1055                 ? j + 1 
1056                 : 0;
1057             var coordsList;
1058             var nextDistanceToTop = java.awt.geom.Line2D.ptLineDistSq(topWallPoint1[0], topWallPoint1[1], topWallPoint2[0], topWallPoint2[1], 
1059                 areaPoints[nextPointIndex][0], areaPoints[nextPointIndex][1]);
1060             if (distanceToTop < 1.0E-10 && nextDistanceToTop < 1.0E-10) {
1061               coordsList = slopingTopCoords;
1062             } else {
1063               coordsList = borderCoords;
1064             }
1065             
1066             var nextPoint = vec3.fromValues(areaPoints[nextPointIndex][0], areaPoints[nextPointIndex][1], 0);
1067             vec3.transformMat4(nextPoint, nextPoint, frontAreaTransform);
1068             coordsList.push(point);
1069             coordsList.push(vec3.fromValues(point[0] + deltaXToWallMiddle, point[1], point[2] + deltaZToWallMiddle));
1070             coordsList.push(vec3.fromValues(nextPoint[0] + deltaXToWallMiddle, nextPoint[1], nextPoint[2] + deltaZToWallMiddle));
1071             coordsList.push(nextPoint);
1072             
1073             var nextTextureCoord = null;
1074             if (texture != null) {
1075               var horizontalTextureCoords = java.awt.geom.Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], 
1076                   nextPoint[0], nextPoint[2]);
1077               if (wallSide === Wall3D.WALL_LEFT_SIDE && texture.isLeftToRightOriented()) {
1078                 horizontalTextureCoords = -horizontalTextureCoords;
1079               }
1080               nextTextureCoord = vec2.fromValues(horizontalTextureCoords, nextPoint[1]);
1081               if (coordsList === borderCoords) {
1082                 borderTextureCoords.push(textureCoord);
1083                 borderTextureCoords.push(textureCoord);
1084                 borderTextureCoords.push(nextTextureCoord);
1085                 borderTextureCoords.push(nextTextureCoord);
1086               }
1087             }
1088             
1089             distanceToTop = nextDistanceToTop;
1090             point = nextPoint;
1091             textureCoord = nextTextureCoord;
1092           }
1093           
1094           coords[i] = point;
1095           if (texture != null) {
1096             textureCoords[i] = textureCoord;
1097           }
1098           i++;
1099         }
1100         
1101         var geometryInfo = new GeometryInfo3D(GeometryInfo3D.POLYGON_ARRAY);
1102         geometryInfo.setStripCounts(stripCounts);
1103         geometryInfo.setCoordinates(coords);
1104         if (texture !== null) {
1105           geometryInfo.setTextureCoordinates(textureCoords);
1106         }
1107         geometryInfo.setGeneratedNormals(true);
1108         wallGeometries.push(geometryInfo.getIndexedGeometryArray());
1109         
1110         if (borderCoords.length > 0) {
1111           geometryInfo = new GeometryInfo3D(GeometryInfo3D.QUAD_ARRAY);
1112           geometryInfo.setCoordinates(borderCoords.slice(0));
1113           if (texture !== null) {
1114             geometryInfo.setTextureCoordinates(borderTextureCoords.slice(0));
1115           }
1116           geometryInfo.setCreaseAngle(Math.PI / 2);
1117           geometryInfo.setGeneratedNormals(true);
1118           wallGeometries.push(geometryInfo.getIndexedGeometryArray());
1119         }
1120         
1121         if (slopingTopCoords.length > 0) {
1122           geometryInfo = new GeometryInfo3D(GeometryInfo3D.QUAD_ARRAY);
1123           geometryInfo.setCoordinates(slopingTopCoords.slice(0));
1124           geometryInfo.setGeneratedNormals(true);
1125           wallTopGeometries.push(geometryInfo.getIndexedGeometryArray());
1126         }
1127       }
1128     }
1129   }
1130 }
1131 
1132 /**
1133 * Returns the elevation of the wall managed by this 3D object.
1134 * @param {boolean} ignoreFloorThickness
1135 * @return {number}
1136 * @private
1137 */
1138 Wall3D.prototype.getWallElevation = function(ignoreFloorThickness) {
1139   var wall = this.getUserData();
1140   var level = wall.getLevel();
1141   if (level === null) {
1142     return 0;
1143   } else if (ignoreFloorThickness) {
1144     return level.getElevation();
1145   } else {
1146     var floorThicknessBottomWall = this.getFloorThicknessBottomWall();
1147     if (floorThicknessBottomWall > 0) {
1148       floorThicknessBottomWall -= Wall3D.LEVEL_ELEVATION_SHIFT;
1149     }
1150     return level.getElevation() - floorThicknessBottomWall;
1151   }
1152 }
1153 
1154 /**
1155 * Returns the floor thickness at the bottom of the wall managed by this 3D object.
1156 * @return {number}
1157 * @private
1158 */
1159 Wall3D.prototype.getFloorThicknessBottomWall = function() {
1160   var wall = this.getUserData();
1161   var level = wall.getLevel();
1162   if (level == null) {
1163     return 0;
1164   } else {
1165     var levels = this.getHome().getLevels();
1166     if (!(levels.length === 0) && levels[0].getElevation() === level.getElevation()) {
1167       return 0;
1168     } else {
1169       return level.getFloorThickness();
1170     }
1171   }
1172 }
1173 
1174 /**
1175 * Returns the elevation of the wall top at its start.
1176 * @return {number}
1177 * @private
1178 */
1179 Wall3D.prototype.getWallTopElevationAtStart = function() {
1180   var wallHeight = this.getUserData().getHeight();
1181   var wallHeightAtStart;
1182   if (wallHeight !== null) {
1183     wallHeightAtStart = wallHeight + this.getWallElevation(false) + this.getFloorThicknessBottomWall();
1184   } else {
1185     wallHeightAtStart = this.getHome().getWallHeight() + this.getWallElevation(false) + this.getFloorThicknessBottomWall();
1186   }
1187   return wallHeightAtStart + this.getTopElevationShift();
1188 }
1189 
1190 /**
1191  * @private
1192  */
1193 Wall3D.prototype.getTopElevationShift = function() {
1194   var level = this.getUserData().getLevel();
1195   if (level !== null) {
1196     var levels = this.getHome().getLevels();
1197     if (levels[levels.length - 1] !== level) {
1198       return Wall3D.LEVEL_ELEVATION_SHIFT;
1199     }
1200   }
1201   return 0;
1202 }
1203 
1204 /**
1205  * Returns the elevation of the wall top at its end.
1206  * @return {number}
1207  * @private
1208  */
1209 Wall3D.prototype.getWallTopElevationAtEnd = function() {
1210   var wall = this.getUserData();
1211   if (wall.isTrapezoidal()) {
1212     return wall.getHeightAtEnd() + this.getWallElevation(false) + this.getFloorThicknessBottomWall() + this.getTopElevationShift();
1213   } else {
1214     return this.getWallTopElevationAtStart();
1215   }
1216 }
1217 
1218 /**
1219 * Returns the elevation of the given baseboard top.
1220 * @param {Baseboard} baseboard
1221 * @return {number}
1222 * @private
1223 */
1224 Wall3D.prototype.getBaseboardTopElevation = function(baseboard) {
1225   return baseboard.getHeight() + this.getWallElevation(true);
1226 }
1227 
1228 /**
1229  * Returns the selection geometry of this wall.
1230  * @return {IndexedGeometryArray3D}
1231  * @private
1232  */
1233 Wall3D.prototype.createWallSelectionGeometry = function() {
1234   var wall = this.getUserData();
1235   var wallElevation = this.getWallElevation(true);
1236   var leftSideBaseboard = wall.getLeftSideBaseboard();
1237   var rightSideBaseboard = wall.getRightSideBaseboard();
1238   var wallPoints = wall.getPoints();
1239   var wallPointsIncludingBaseboards = wall.getPoints(true);
1240   var selectionCoordinates = new Array(wallPoints.length * 2
1241                                        + (leftSideBaseboard != null ? 4 : 0)
1242                                        + (rightSideBaseboard != null ? 4 : 0));
1243   var indices = new Array(wallPoints.length * 4
1244                           + (leftSideBaseboard != null ? 12 : 4)
1245                           + (rightSideBaseboard != null ? 12 : 4));
1246   // Contour at bottom
1247   var j = 0, k = 0;
1248   for (var i = 0; i < wallPoints.length; i++, j++) {
1249     selectionCoordinates [j] = vec3.fromValues(wallPointsIncludingBaseboards [i][0], wallElevation, wallPointsIncludingBaseboards [i][1]);
1250     indices [k++] = j;
1251     if (i > 0) {
1252       indices [k++] = j;
1253     }
1254   }
1255   indices [k++] = 0;
1256 
1257   // Compute wall angles and top line factors to generate top contour
1258   var topElevationAtStart = this.getWallTopElevationAtStart();
1259   var topElevationAtEnd = this.getWallTopElevationAtEnd();
1260   var wallYawAngle = Math.atan2(wall.getYEnd() - wall.getYStart(), wall.getXEnd() - wall.getXStart());
1261   var cosWallYawAngle = Math.cos(wallYawAngle);
1262   var sinWallYawAngle = Math.sin(wallYawAngle);
1263   var wallXStartWithZeroYaw = cosWallYawAngle * wall.getXStart() + sinWallYawAngle * wall.getYStart();
1264   var wallXEndWithZeroYaw = cosWallYawAngle * wall.getXEnd() + sinWallYawAngle * wall.getYEnd();
1265   var topLineAlpha;
1266   var topLineBeta;
1267   if (topElevationAtStart == topElevationAtEnd) {
1268     topLineAlpha = 0;
1269     topLineBeta = topElevationAtStart;
1270   } else {
1271     topLineAlpha = (topElevationAtEnd - topElevationAtStart) / (wallXEndWithZeroYaw - wallXStartWithZeroYaw);
1272     topLineBeta = topElevationAtStart - topLineAlpha * wallXStartWithZeroYaw;
1273   }
1274 
1275   // Contour at top
1276   for (var i = 0; i < wallPoints.length; i++, j++) {
1277     var xTopPointWithZeroYaw = cosWallYawAngle * wallPoints [i][0] + sinWallYawAngle * wallPoints [i][1];
1278     var topY = topLineAlpha * xTopPointWithZeroYaw + topLineBeta;
1279     selectionCoordinates [j] = vec3.fromValues(wallPoints [i][0], topY, wallPoints [i][1]);
1280     indices [k++] = j;
1281     if (i > 0) {
1282       indices [k++] = j;
1283     }
1284   }
1285   indices [k++] = wallPoints.length;
1286 
1287   // Vertical lines at corners
1288   indices [k++] = 0;
1289   if (leftSideBaseboard != null) {
1290     var leftBaseboardHeight = this.getBaseboardTopElevation(leftSideBaseboard);
1291     selectionCoordinates [j] = vec3.fromValues(wallPointsIncludingBaseboards [0][0], Math.min(leftBaseboardHeight, topElevationAtStart), wallPointsIncludingBaseboards [0][1]);
1292     indices [k++] = j;
1293     indices [k++] = j++;
1294     selectionCoordinates [j] = vec3.fromValues(wallPoints [0][0], Math.min(leftBaseboardHeight, topElevationAtStart), wallPoints [0][1]);
1295     indices [k++] = j;
1296     indices [k++] = j++;
1297   }
1298   indices [k++] = wallPoints.length;
1299   indices [k++] = wallPoints.length / 2 - 1;
1300   if (leftSideBaseboard != null) {
1301     var leftBaseboardHeight = this.getBaseboardTopElevation(leftSideBaseboard);
1302     selectionCoordinates [j] = vec3.fromValues(wallPointsIncludingBaseboards [wallPoints.length / 2 - 1][0], Math.min(leftBaseboardHeight, topElevationAtEnd), wallPointsIncludingBaseboards [wallPoints.length / 2 - 1][1]);
1303     indices [k++] = j;
1304     indices [k++] = j++;
1305     selectionCoordinates [j] = vec3.fromValues(wallPoints [wallPoints.length / 2 - 1][0], Math.min(leftBaseboardHeight, topElevationAtEnd), wallPoints [wallPoints.length / 2 - 1][1]);
1306     indices [k++] = j;
1307     indices [k++] = j++;
1308   }
1309   indices [k++] = wallPoints.length + wallPoints.length / 2 - 1;
1310   indices [k++] = wallPoints.length - 1;
1311   if (rightSideBaseboard != null) {
1312     var rightBaseboardHeight = this.getBaseboardTopElevation(rightSideBaseboard);
1313     selectionCoordinates [j] = vec3.fromValues(wallPointsIncludingBaseboards [wallPoints.length - 1][0], Math.min(rightBaseboardHeight, topElevationAtStart), wallPointsIncludingBaseboards [wallPoints.length - 1][1]);
1314     indices [k++] = j;
1315     indices [k++] = j++;
1316     selectionCoordinates [j] = vec3.fromValues(wallPoints [wallPoints.length - 1][0], Math.min(rightBaseboardHeight, topElevationAtStart), wallPoints [wallPoints.length - 1][1]);
1317     indices [k++] = j;
1318     indices [k++] = j++;
1319   }
1320   indices [k++] = 2 * wallPoints.length - 1;
1321   indices [k++] = wallPoints.length / 2;
1322   if (rightSideBaseboard != null) {
1323     var rightBaseboardHeight = this.getBaseboardTopElevation(rightSideBaseboard);
1324     selectionCoordinates [j] = vec3.fromValues(wallPointsIncludingBaseboards [wallPoints.length / 2][0], Math.min(rightBaseboardHeight, topElevationAtEnd), wallPointsIncludingBaseboards [wallPoints.length / 2][1]);
1325     indices [k++] = j;
1326     indices [k++] = j++;
1327     selectionCoordinates [j] = vec3.fromValues(wallPoints [wallPoints.length / 2][0], Math.min(rightBaseboardHeight, topElevationAtEnd), wallPoints [wallPoints.length / 2][1]);
1328     indices [k++] = j;
1329     indices [k++] = j++;
1330   }
1331   indices [k++] = wallPoints.length + wallPoints.length / 2;
1332 
1333   return new IndexedLineArray3D(selectionCoordinates, indices);
1334 }
1335 
1336 /**
1337 * Sets wall appearance with its color, texture and transparency.
1338 * @param {boolean} waitTextureLoadingEnd
1339 * @private
1340 */
1341 Wall3D.prototype.updateWallAppearance = function(waitTextureLoadingEnd) {
1342   var wall = this.getUserData();
1343   var wallsTopColor = wall.getTopColor();
1344   var wallLeftSideGroups = [this.getChild(0), this.getChild(2), this.getChild(4), this.getChild(6)];
1345   var wallRightSideGroups = [this.getChild(1), this.getChild(3), this.getChild(5), this.getChild(7)];
1346   for (var i = 0; i < wallLeftSideGroups.length; i++) {
1347     if (i === 1) {
1348       var leftSideBaseboard = wall.getLeftSideBaseboard();
1349       if (leftSideBaseboard != null) {
1350         var texture = leftSideBaseboard.getTexture();
1351         var color = leftSideBaseboard.getColor();
1352         if (color === null && texture === null) {
1353           texture = wall.getLeftSideTexture();
1354           color = wall.getLeftSideColor();
1355         }
1356         this.updateFilledWallSideAppearance(wallLeftSideGroups[i].getChild(0).getAppearance(), 
1357             texture, waitTextureLoadingEnd, color, wall.getLeftSideShininess());
1358       }
1359       var rightSideBaseboard = wall.getRightSideBaseboard();
1360       if (rightSideBaseboard != null) {
1361         var texture = rightSideBaseboard.getTexture();
1362         var color = rightSideBaseboard.getColor();
1363         if (color == null && texture == null) {
1364           texture = wall.getRightSideTexture();
1365           color = wall.getRightSideColor();
1366         }
1367         this.updateFilledWallSideAppearance(wallRightSideGroups[i].getChild(0).getAppearance(), 
1368             texture, waitTextureLoadingEnd, color, wall.getRightSideShininess());
1369       }
1370     } else if (i !== 3 || wallsTopColor == null) {
1371       this.updateFilledWallSideAppearance(wallLeftSideGroups[i].getChild(0).getAppearance(), 
1372           wall.getLeftSideTexture(), waitTextureLoadingEnd, wall.getLeftSideColor(), wall.getLeftSideShininess());
1373       this.updateFilledWallSideAppearance(wallRightSideGroups[i].getChild(0).getAppearance(), 
1374           wall.getRightSideTexture(), waitTextureLoadingEnd, wall.getRightSideColor(), wall.getRightSideShininess());
1375     } else {
1376       this.updateFilledWallSideAppearance(wallLeftSideGroups[i].getChild(0).getAppearance(), 
1377           null, waitTextureLoadingEnd, wallsTopColor, 0);
1378       this.updateFilledWallSideAppearance(wallRightSideGroups[i].getChild(0).getAppearance(), 
1379           null, waitTextureLoadingEnd, wallsTopColor, 0);
1380     }
1381   }
1382   
1383   var selectionShapeAppearance = this.getChild(8).getAppearance();
1384   selectionShapeAppearance.setVisible(this.getUserPreferences() != null
1385       && this.getUserPreferences().isEditingIn3DViewEnabled()
1386       && this.getHome().isItemSelected(wall));
1387 }
1388 
1389 /**
1390 * Sets filled wall side appearance with its color, texture, transparency and visibility.
1391 * @param {Appearance3D} wallSideAppearance
1392 * @param {HomeTexture} wallSideTexture
1393 * @param {boolean} waitTextureLoadingEnd
1394 * @param {number} wallSideColor
1395 * @param {number} shininess
1396 * @private
1397 */
1398 Wall3D.prototype.updateFilledWallSideAppearance = function(wallSideAppearance, wallSideTexture, waitTextureLoadingEnd, 
1399                                                            wallSideColor, shininess) {
1400   if (wallSideTexture == null) {
1401     this.updateAppearanceMaterial(wallSideAppearance, wallSideColor, wallSideColor, shininess);
1402     wallSideAppearance.setTextureImage(null);
1403   } else {
1404     this.updateAppearanceMaterial(wallSideAppearance, Object3DBranch.DEFAULT_COLOR, Object3DBranch.DEFAULT_AMBIENT_COLOR, shininess);
1405     this.updateTextureTransform(wallSideAppearance, wallSideTexture, true);
1406     TextureManager.getInstance().loadTexture(wallSideTexture.getImage(), waitTextureLoadingEnd, {
1407         textureUpdated : function(texture) {
1408           wallSideAppearance.setTextureImage(texture);
1409         },
1410         textureError : function(error) {
1411           return this.textureUpdated(TextureManager.getInstance().getErrorImage());
1412         }
1413       });
1414   }
1415   var wallsAlpha = this.getHome().getEnvironment().getWallsAlpha();
1416   wallSideAppearance.setTransparency(wallsAlpha);
1417 }
1418 
1419 /**
1420 * An area used to compute holes in walls.
1421  * @constructor
1422  * @private
1423 */
1424 Wall3D.DoorOrWindowArea = function(area, doorsOrWindows) {
1425   this.area = area;
1426   this.doorsOrWindows = doorsOrWindows;
1427 }
1428 
1429 Wall3D.DoorOrWindowArea.prototype.getArea = function() {
1430   return this.area;
1431 }
1432 
1433 Wall3D.DoorOrWindowArea.prototype.getDoorsOrWindows = function() {
1434   return this.doorsOrWindows;
1435 }
1436