1 /*
2  * Room3D.java 23 jan. 09
3  *
4  * Sweet Home 3D, Copyright (c) 2007-2009 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 package com.eteks.sweethome3d.j3d;
21 
22 import java.awt.geom.Area;
23 import java.awt.geom.Point2D;
24 import java.awt.geom.Rectangle2D;
25 import java.util.ArrayList;
26 import java.util.Arrays;
27 import java.util.Collections;
28 import java.util.Comparator;
29 import java.util.List;
30 
31 import javax.media.j3d.Appearance;
32 import javax.media.j3d.BranchGroup;
33 import javax.media.j3d.Geometry;
34 import javax.media.j3d.Group;
35 import javax.media.j3d.Node;
36 import javax.media.j3d.RenderingAttributes;
37 import javax.media.j3d.Shape3D;
38 import javax.media.j3d.Texture;
39 import javax.media.j3d.TransparencyAttributes;
40 import javax.vecmath.Point3f;
41 import javax.vecmath.TexCoord2f;
42 
43 import com.eteks.sweethome3d.model.Home;
44 import com.eteks.sweethome3d.model.HomeEnvironment;
45 import com.eteks.sweethome3d.model.HomeFurnitureGroup;
46 import com.eteks.sweethome3d.model.HomePieceOfFurniture;
47 import com.eteks.sweethome3d.model.HomeTexture;
48 import com.eteks.sweethome3d.model.Level;
49 import com.eteks.sweethome3d.model.Room;
50 import com.eteks.sweethome3d.model.Wall;
51 import com.sun.j3d.utils.geometry.GeometryInfo;
52 import com.sun.j3d.utils.geometry.NormalGenerator;
53 
54 /**
55  * Root of room branch.
56  */
57 public class Room3D extends Object3DBranch {
58   private static final int FLOOR_PART  = 0;
59   private static final int CEILING_PART = 1;
60 
61   private final Home home;
62 
63   /**
64    * Creates the 3D room matching the given home <code>room</code>.
65    */
Room3D(Room room, Home home)66   public Room3D(Room room, Home home) {
67     this(room, home, false, false);
68   }
69 
70   /**
71    * Creates the 3D room matching the given home <code>room</code>.
72    */
Room3D(Room room, Home home, boolean ignoreCeilingPart, boolean waitTextureLoadingEnd)73   public Room3D(Room room, Home home,
74                 boolean ignoreCeilingPart,
75                 boolean waitTextureLoadingEnd) {
76     this(room, home, ignoreCeilingPart, true, waitTextureLoadingEnd);
77   }
78 
79   /**
80    * Creates the 3D room matching the given home <code>room</code>.
81    */
Room3D(Room room, Home home, boolean ignoreCeilingPart, boolean ignoreDrawingMode, boolean waitTextureLoadingEnd)82   public Room3D(Room room, Home home,
83                 boolean ignoreCeilingPart,
84                 boolean ignoreDrawingMode,
85                 boolean waitTextureLoadingEnd) {
86     setUserData(room);
87     this.home = home;
88 
89     // Allow room branch to be removed from its parent
90     setCapability(BranchGroup.ALLOW_DETACH);
91     // Allow to read branch shape children
92     setCapability(BranchGroup.ALLOW_CHILDREN_READ);
93     setCapability(BranchGroup.ALLOW_PICKABLE_WRITE);
94 
95     // Add room floor and cellar empty shapes to branch
96     for (int i = 0; i < 2; i++) {
97       Group roomPartGroup = new Group();
98       roomPartGroup.setCapability(Group.ALLOW_CHILDREN_READ);
99       roomPartGroup.addChild(createRoomPartShape(false));
100       if (!ignoreDrawingMode) {
101         roomPartGroup.addChild(createRoomPartShape(true));
102       }
103       addChild(roomPartGroup);
104     }
105     // Set room shape geometry and appearance
106     updateRoomGeometry();
107     updateRoomAppearance(waitTextureLoadingEnd);
108 
109     if (ignoreCeilingPart) {
110       removeChild(CEILING_PART);
111     } else {
112       // Avoid making ceiling pickable because they can't be viewed from top
113       getChild(CEILING_PART).setPickable(false);
114     }
115   }
116 
117   /**
118    * Returns a new room part shape with no geometry
119    * and a default appearance with a white material.
120    */
createRoomPartShape(boolean outline)121   private Node createRoomPartShape(boolean outline) {
122     Shape3D roomShape = new Shape3D();
123     // Allow room shape to change its geometry
124     roomShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE);
125     roomShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ);
126     roomShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ);
127 
128     Appearance roomAppearance = new Appearance();
129     roomShape.setAppearance(roomAppearance);
130     roomAppearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ);
131     TransparencyAttributes transparencyAttributes = new TransparencyAttributes();
132     transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE);
133     transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE);
134     roomAppearance.setTransparencyAttributes(transparencyAttributes);
135     roomAppearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ);
136     RenderingAttributes renderingAttributes = new RenderingAttributes();
137     renderingAttributes.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE);
138     roomAppearance.setRenderingAttributes(renderingAttributes);
139 
140     if (outline) {
141       roomAppearance.setColoringAttributes(Object3DBranch.OUTLINE_COLORING_ATTRIBUTES);
142       roomAppearance.setPolygonAttributes(Object3DBranch.OUTLINE_POLYGON_ATTRIBUTES);
143       roomAppearance.setLineAttributes(Object3DBranch.OUTLINE_LINE_ATTRIBUTES);
144     } else {
145       roomAppearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE);
146       roomAppearance.setMaterial(DEFAULT_MATERIAL);
147       roomAppearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE);
148       roomAppearance.setCapability(Appearance.ALLOW_TEXTURE_READ);
149       roomAppearance.setCapability(Appearance.ALLOW_TEXTURE_ATTRIBUTES_WRITE);
150     }
151 
152     return roomShape;
153   }
154 
155   @Override
update()156   public void update() {
157     updateRoomGeometry();
158     updateRoomAppearance(false);
159   }
160 
161   /**
162    * Sets the 3D geometry of this room shapes that matches its 2D geometry.
163    */
updateRoomGeometry()164   private void updateRoomGeometry() {
165     updateRoomPartGeometry(FLOOR_PART, ((Room)getUserData()).getFloorTexture());
166     updateRoomPartGeometry(CEILING_PART, ((Room)getUserData()).getCeilingTexture());
167     Room room = (Room)getUserData();
168     setPickable(this.home.getEnvironment().getWallsAlpha() == 0
169         || room.getLevel() == null
170         || room.getLevel().getElevation() <= 0);
171   }
172 
updateRoomPartGeometry(int roomPart, HomeTexture texture)173   private void updateRoomPartGeometry(int roomPart, HomeTexture texture) {
174     Group roomPartGroup = (Group)getChild(roomPart);
175     Shape3D roomFilledShape = (Shape3D)roomPartGroup.getChild(0);
176     Shape3D roomOutlineShape = roomPartGroup.numChildren() > 1
177         ? (Shape3D)roomPartGroup.getChild(1)
178         : null;
179     int currentGeometriesCount = roomFilledShape.numGeometries();
180     Room room = (Room)getUserData();
181     if (room.getLevel() == null || room.getLevel().isViewableAndVisible()) {
182       for (Geometry roomGeometry : createRoomGeometries(roomPart, texture)) {
183         roomFilledShape.addGeometry(roomGeometry);
184         if (roomOutlineShape != null) {
185           roomOutlineShape.addGeometry(roomGeometry);
186         }
187       }
188     }
189     for (int i = currentGeometriesCount - 1; i >= 0; i--) {
190       roomFilledShape.removeGeometry(i);
191       if (roomOutlineShape != null) {
192         roomOutlineShape.removeGeometry(i);
193       }
194     }
195   }
196 
197   /**
198    * Returns room geometry computed from its points.
199    */
createRoomGeometries(int roomPart, HomeTexture texture)200   private Geometry [] createRoomGeometries(int roomPart, HomeTexture texture) {
201     Room room = (Room)getUserData();
202     float [][] points = room.getPoints();
203     if ((roomPart == FLOOR_PART && room.isFloorVisible()
204          || roomPart == CEILING_PART && room.isCeilingVisible())
205         && points.length > 2) {
206       Level roomLevel = room.getLevel();
207       List<Level> levels = this.home.getLevels();
208       boolean lastLevel = isLastLevel(roomLevel, levels);
209       float floorBottomElevation;
210       float roomElevation;
211       if (roomLevel != null) {
212         roomElevation = roomLevel.getElevation();
213         floorBottomElevation = roomElevation - roomLevel.getFloorThickness();
214       } else {
215         roomElevation = 0;
216         floorBottomElevation = 0;
217       }
218 
219       float firstLevelElevation;
220       if (levels.size() == 0) {
221         firstLevelElevation = 0;
222       } else {
223         firstLevelElevation = levels.get(0).getElevation();
224       }
225       boolean floorBottomVisible = roomPart == FLOOR_PART
226           && roomLevel != null
227           && roomElevation != firstLevelElevation;
228 
229       // Find rooms at the same elevation
230       // and room ceilings at same elevation as the floor bottom
231       final List<Room> roomsAtSameElevation = new ArrayList<Room>();
232       List<Room> ceilingsAtSameFloorBottomElevation = new ArrayList<Room>();
233       for (Room homeRoom : this.home.getRooms()) {
234         Level homeRoomLevel = homeRoom.getLevel();
235         if (homeRoomLevel == null || homeRoomLevel.isViewableAndVisible()) {
236           if (room == homeRoom // Store also the room itself to know its order among rooms at same elevation
237               || roomLevel == homeRoomLevel
238                   && (roomPart == FLOOR_PART && homeRoom.isFloorVisible()
239                       || roomPart == CEILING_PART && homeRoom.isCeilingVisible())
240               || roomLevel != null
241                   && homeRoomLevel != null
242                   && (roomPart == FLOOR_PART
243                           && homeRoom.isFloorVisible()
244                           && Math.abs(roomElevation - homeRoomLevel.getElevation()) < 1E-4
245                       || roomPart == CEILING_PART
246                           && homeRoom.isCeilingVisible()
247                           && !lastLevel
248                           && !isLastLevel(homeRoomLevel, levels)
249                           && Math.abs(roomElevation + roomLevel.getHeight() - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1E-4)) {
250             roomsAtSameElevation.add(homeRoom);
251           } else if (floorBottomVisible
252                       && homeRoomLevel != null
253                       && homeRoom.isCeilingVisible()
254                       && !isLastLevel(homeRoomLevel, levels)
255                       && Math.abs(floorBottomElevation - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1E-4) {
256             ceilingsAtSameFloorBottomElevation.add(homeRoom);
257           }
258         }
259       }
260       if (roomLevel != null) {
261         // Update order to ensure that rooms are sorted first in level order
262         Collections.sort(roomsAtSameElevation, new Comparator<Room>() {
263             public int compare(Room room1, Room room2) {
264               int comparison = Float.compare(room1.getLevel().getElevation(), room2.getLevel().getElevation());
265               if (comparison != 0) {
266                 return comparison;
267               } else {
268                 return room1.getLevel().getElevationIndex() - room2.getLevel().getElevationIndex();
269               }
270             }
271           });
272       }
273 
274       List<HomePieceOfFurniture> visibleStaircases;
275       if (roomLevel == null
276           || roomPart == CEILING_PART
277               && lastLevel) {
278         visibleStaircases = Collections.emptyList();
279       } else {
280         visibleStaircases = getVisibleStaircases(this.home.getFurniture(), roomPart, roomLevel,
281             roomLevel.getElevation() == firstLevelElevation);
282       }
283 
284       // Check ceiling points of the last level are at the same elevation
285       boolean sameElevation = true;
286       if (roomPart == CEILING_PART
287           && (roomLevel == null || lastLevel)) {
288         float firstPointElevation = getRoomHeightAt(points [0][0], points [0][1]);
289         for (int i = 1; i < points.length && sameElevation; i++) {
290           sameElevation = getRoomHeightAt(points [i][0], points [i][1]) == firstPointElevation;
291         }
292       }
293 
294       // Retrieve room points
295       List<float [][]> roomPoints;
296       List<float [][]> roomHoles;
297       List<float [][]> roomPointsWithoutHoles;
298       Area roomVisibleArea;
299       // If room isn't singular retrieve all the points of its different polygons
300       if (!room.isSingular()
301           || sameElevation
302               && (roomsAtSameElevation.get(roomsAtSameElevation.size() - 1) != room
303                   || visibleStaircases.size() > 0)) {
304         roomVisibleArea = new Area(getShape(points));
305         if (roomsAtSameElevation.contains(room)) {
306           // Remove other rooms surface that may overlap the current room
307           for (int i = roomsAtSameElevation.size() - 1; i > 0 && roomsAtSameElevation.get(i) != room; i--) {
308             Room otherRoom = roomsAtSameElevation.get(i);
309             roomVisibleArea.subtract(new Area(getShape(otherRoom.getPoints())));
310           }
311         }
312         removeStaircasesFromArea(visibleStaircases, roomVisibleArea);
313         roomPoints = new ArrayList<float[][]>();
314         roomHoles = new ArrayList<float[][]>();
315         roomPointsWithoutHoles = getAreaPoints(roomVisibleArea, roomPoints, roomHoles, 1, roomPart == CEILING_PART);
316       } else {
317         boolean clockwise = room.isClockwise();
318         if (clockwise && roomPart == FLOOR_PART
319             || !clockwise && roomPart == CEILING_PART) {
320           // Reverse points order
321           points = getReversedArray(points);
322         }
323         roomPointsWithoutHoles =
324         roomPoints = Arrays.asList(new float [][][] {points});
325         roomHoles = Collections.emptyList();
326         roomVisibleArea = null;
327       }
328 
329       List<Geometry> geometries = new ArrayList<Geometry> (3);
330       final float subpartSize = this.home.getEnvironment().getSubpartSizeUnderLight();
331 
332       if (!roomPointsWithoutHoles.isEmpty()) {
333         List<float []> roomPointElevations = new ArrayList<float[]>();
334         boolean roomAtSameElevation = true;
335         for (int i = 0; i < roomPointsWithoutHoles.size(); i++) {
336           float [][] roomPartPoints = roomPointsWithoutHoles.get(i);
337           float [] roomPartPointElevations = new float [roomPartPoints.length];
338           for (int j = 0; j < roomPartPoints.length; j++) {
339             roomPartPointElevations [j] = roomPart == FLOOR_PART
340                 ? roomElevation
341                 : getRoomHeightAt(roomPartPoints [j][0], roomPartPoints [j][1]);
342             if (roomAtSameElevation && j > 0) {
343               roomAtSameElevation = roomPartPointElevations [j] == roomPartPointElevations [j - 1];
344             }
345           }
346           roomPointElevations.add(roomPartPointElevations);
347         }
348 
349         // Compute room geometry
350         if (roomAtSameElevation && subpartSize > 0) {
351           for (int i = 0; i < roomPointsWithoutHoles.size(); i++) {
352             float [][] roomPartPoints = roomPointsWithoutHoles.get(i);
353             // Subdivide area in smaller squares to ensure a smoother effect with point lights
354             float xMin = Float.MAX_VALUE;
355             float xMax = Float.MIN_VALUE;
356             float zMin = Float.MAX_VALUE;
357             float zMax = Float.MIN_VALUE;
358             for (float [] point : roomPartPoints) {
359               xMin = Math.min(xMin, point [0]);
360               xMax = Math.max(xMax, point [0]);
361               zMin = Math.min(zMin, point [1]);
362               zMax = Math.max(zMax, point [1]);
363             }
364 
365             Area roomPartArea = new Area(getShape(roomPartPoints));
366             for (float xSquare = xMin; xSquare < xMax; xSquare += subpartSize) {
367               for (float zSquare = zMin; zSquare < zMax; zSquare += subpartSize) {
368                 Area roomPartSquare = new Area(new Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize));
369                 roomPartSquare.intersect(roomPartArea);
370                 if (!roomPartSquare.isEmpty()) {
371                   List<float [][]> geometryPartPointsWithoutHoles =
372                       getAreaPoints(roomPartSquare, 1, roomPart == CEILING_PART);
373                   if (!geometryPartPointsWithoutHoles.isEmpty()) {
374                     geometries.add(computeRoomPartGeometry(geometryPartPointsWithoutHoles,
375                         null, roomLevel, roomPointElevations.get(i) [0], floorBottomElevation,
376                         roomPart == FLOOR_PART, false, texture));
377                   }
378                 }
379               }
380             }
381           }
382         } else {
383           geometries.add(computeRoomPartGeometry(roomPointsWithoutHoles, roomPointElevations, roomLevel,
384               roomElevation, floorBottomElevation, roomPart == FLOOR_PART, false, texture));
385         }
386 
387         // Compute border geometry
388         if (roomLevel != null
389             && roomPart == FLOOR_PART
390             && roomLevel.getElevation() != firstLevelElevation) {
391           geometries.add(computeRoomBorderGeometry(roomPoints, roomHoles, roomLevel, roomElevation, texture));
392         }
393       }
394 
395       // Retrieve points of the room floor bottom
396       if (floorBottomVisible) {
397         List<float [][]> floorBottomPointsWithoutHoles;
398         if (roomVisibleArea != null
399             || ceilingsAtSameFloorBottomElevation.size() > 0) {
400           Area floorBottomVisibleArea = roomVisibleArea != null ? roomVisibleArea : new Area(getShape(points));
401           // Remove other rooms surface that may overlap the floor bottom
402           for (Room otherRoom : ceilingsAtSameFloorBottomElevation) {
403            floorBottomVisibleArea.subtract(new Area(getShape(otherRoom.getPoints())));
404           }
405           floorBottomPointsWithoutHoles = getAreaPoints(floorBottomVisibleArea, 1, true);
406         } else {
407           floorBottomPointsWithoutHoles = Arrays.asList(new float [][][] {getReversedArray(points)});
408         }
409 
410         if (!floorBottomPointsWithoutHoles.isEmpty()) {
411           // Compute floor bottom geometry
412           if (subpartSize > 0) {
413             for (int i = 0 ; i < floorBottomPointsWithoutHoles.size(); i++) {
414               float [][] floorBottomPartPoints = floorBottomPointsWithoutHoles.get(i);
415               float xMin = Float.MAX_VALUE;
416               float xMax = Float.MIN_VALUE;
417               float zMin = Float.MAX_VALUE;
418               float zMax = Float.MIN_VALUE;
419               for (float [] point : floorBottomPartPoints) {
420                 xMin = Math.min(xMin, point [0]);
421                 xMax = Math.max(xMax, point [0]);
422                 zMin = Math.min(zMin, point [1]);
423                 zMax = Math.max(zMax, point [1]);
424               }
425 
426               Area floorBottomPartArea = new Area(getShape(floorBottomPartPoints));
427               for (float xSquare = xMin; xSquare < xMax; xSquare += subpartSize) {
428                 for (float zSquare = zMin; zSquare < zMax; zSquare += subpartSize) {
429                   Area floorBottomPartSquare = new Area(new Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize));
430                   floorBottomPartSquare.intersect(floorBottomPartArea);
431                   if (!floorBottomPartSquare.isEmpty()) {
432                     List<float [][]> geometryPartPointsWithoutHoles = getAreaPoints(floorBottomPartSquare, 1, true);
433                     if (!geometryPartPointsWithoutHoles.isEmpty()) {
434                       geometries.add(computeRoomPartGeometry(geometryPartPointsWithoutHoles,
435                           null, roomLevel, roomElevation, floorBottomElevation,
436                           true, true, texture));
437                     }
438                   }
439                 }
440               }
441             }
442           } else {
443             geometries.add(computeRoomPartGeometry(floorBottomPointsWithoutHoles, null, roomLevel,
444                 roomElevation, floorBottomElevation, true, true, texture));
445           }
446         }
447       }
448 
449       return geometries.toArray(new Geometry [geometries.size()]);
450     } else {
451       return new Geometry [0];
452     }
453   }
454 
455   /**
456    * Returns the room part geometry matching the given points.
457    */
computeRoomPartGeometry(List<float [][]> geometryPoints, List<float []> roomPointElevations, Level roomLevel, float roomPartElevation, float floorBottomElevation, boolean floorPart, boolean floorBottomPart, HomeTexture texture)458   private Geometry computeRoomPartGeometry(List<float [][]> geometryPoints,
459                                            List<float []> roomPointElevations,
460                                            Level roomLevel,
461                                            float roomPartElevation, float floorBottomElevation,
462                                            boolean floorPart, boolean floorBottomPart,
463                                            HomeTexture texture) {
464     int [] stripCounts = new int [geometryPoints.size()];
465     int vertexCount = 0;
466     for (int i = 0; i < geometryPoints.size(); i++) {
467       float [][] areaPoints = geometryPoints.get(i);
468       stripCounts [i] = areaPoints.length;
469       vertexCount += stripCounts [i];
470     }
471     Point3f [] coords = new Point3f [vertexCount];
472     int i = 0;
473     for (int j = 0; j < geometryPoints.size(); j++) {
474       float [][] areaPoints = geometryPoints.get(j);
475       float [] roomPartPointElevations = roomPointElevations != null
476           ? roomPointElevations.get(j)
477           : null;
478       for (int k = 0; k < areaPoints.length; k++) {
479         float y = floorBottomPart
480             ? floorBottomElevation
481             : (roomPartPointElevations != null
482                   ? roomPartPointElevations [k]
483                   : roomPartElevation);
484         coords [i++] = new Point3f(areaPoints [k][0], y, areaPoints [k][1]);
485       }
486     }
487     GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY);
488     geometryInfo.setCoordinates(coords);
489     geometryInfo.setStripCounts(stripCounts);
490 
491     if (texture != null) {
492       TexCoord2f [] textureCoords = new TexCoord2f [vertexCount];
493       i = 0;
494       // Compute room texture coordinates
495       for (float [][] areaPoints : geometryPoints) {
496         for (int k = 0; k < areaPoints.length; k++) {
497           textureCoords [i++] = new TexCoord2f(areaPoints [k][0],
498               floorPart
499                   ? -areaPoints [k][1]
500                   : areaPoints [k][1]);
501         }
502       }
503       geometryInfo.setTextureCoordinateParams(1, 2);
504       geometryInfo.setTextureCoordinates(0, textureCoords);
505     }
506 
507     // Generate normals
508     new NormalGenerator().generateNormals(geometryInfo);
509     return geometryInfo.getIndexedGeometryArray();
510   }
511 
512   /**
513    * Returns the room border geometry matching the given points.
514    */
computeRoomBorderGeometry(List<float [][]> geometryRooms, List<float [][]> geometryHoles, Level roomLevel, float roomElevation, HomeTexture texture)515   private Geometry computeRoomBorderGeometry(List<float [][]> geometryRooms,
516                                              List<float [][]> geometryHoles,
517                                              Level roomLevel, float roomElevation,
518                                              HomeTexture texture) {
519     int vertexCount = 0;
520     for (float [][] geometryPoints : geometryRooms) {
521       vertexCount += geometryPoints.length;
522     }
523     for (float [][] geometryHole : geometryHoles) {
524       vertexCount += geometryHole.length;
525     }
526     vertexCount = vertexCount * 4;
527 
528     int i = 0;
529     Point3f [] coords = new Point3f [vertexCount];
530     float floorBottomElevation = roomElevation - roomLevel.getFloorThickness();
531     // Compute room borders coordinates
532     for (float [][] geometryPoints : geometryRooms) {
533       for (int j = 0; j < geometryPoints.length; j++) {
534         coords [i++] = new Point3f(geometryPoints [j][0], roomElevation, geometryPoints [j][1]);
535         coords [i++] = new Point3f(geometryPoints [j][0], floorBottomElevation, geometryPoints [j][1]);
536         int nextPoint = j < geometryPoints.length - 1
537             ? j + 1
538             : 0;
539         coords [i++] = new Point3f(geometryPoints [nextPoint][0], floorBottomElevation, geometryPoints [nextPoint][1]);
540         coords [i++] = new Point3f(geometryPoints [nextPoint][0], roomElevation, geometryPoints [nextPoint][1]);
541       }
542     }
543     // Compute holes borders coordinates
544     for (float [][] geometryHole : geometryHoles) {
545       for (int j = 0; j < geometryHole.length; j++) {
546         coords [i++] = new Point3f(geometryHole [j][0], roomElevation, geometryHole [j][1]);
547         int nextPoint = j < geometryHole.length - 1
548             ? j + 1
549             : 0;
550         coords [i++] = new Point3f(geometryHole [nextPoint][0], roomElevation, geometryHole [nextPoint][1]);
551         coords [i++] = new Point3f(geometryHole [nextPoint][0], floorBottomElevation, geometryHole [nextPoint][1]);
552         coords [i++] = new Point3f(geometryHole [j][0], floorBottomElevation, geometryHole [j][1]);
553       }
554     }
555 
556     GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.QUAD_ARRAY);
557     geometryInfo.setCoordinates(coords);
558 
559     if (texture != null) {
560       TexCoord2f [] textureCoords = new TexCoord2f [vertexCount];
561       i = 0;
562       // Compute room border texture coordinates
563       for (float [][] geometryPoints : geometryRooms) {
564         for (int j = 0; j < geometryPoints.length; j++) {
565           textureCoords [i++] = new TexCoord2f(0, roomLevel.getFloorThickness());
566           textureCoords [i++] = new TexCoord2f(0, 0);
567           int nextPoint = j < geometryPoints.length - 1
568               ? j + 1
569               : 0;
570           float textureCoord = (float)(Point2D.distance(geometryPoints [j][0], geometryPoints [j][1],
571               geometryPoints [nextPoint][0], geometryPoints [nextPoint][1]));
572           textureCoords [i++] = new TexCoord2f(textureCoord, 0);
573           textureCoords [i++] = new TexCoord2f(textureCoord, roomLevel.getFloorThickness());
574         }
575       }
576       // Compute holes borders texture coordinates
577       for (float [][] geometryHole : geometryHoles) {
578         for (int j = 0; j < geometryHole.length; j++) {
579           textureCoords [i++] = new TexCoord2f(0, 0);
580           int nextPoint = j < geometryHole.length - 1
581               ? j + 1
582               : 0;
583           float textureCoord = (float)(Point2D.distance(geometryHole [j][0], geometryHole [j][1],
584               geometryHole [nextPoint][0], geometryHole [nextPoint][1]));
585           textureCoords [i++] = new TexCoord2f(textureCoord, 0);
586           textureCoords [i++] = new TexCoord2f(textureCoord, roomLevel.getFloorThickness());
587           textureCoords [i++] = new TexCoord2f(0, roomLevel.getFloorThickness());
588         }
589       }
590       geometryInfo.setTextureCoordinateParams(1, 2);
591       geometryInfo.setTextureCoordinates(0, textureCoords);
592     }
593 
594     // Generate normals
595     new NormalGenerator(Math.PI / 8).generateNormals(geometryInfo);
596     return geometryInfo.getIndexedGeometryArray();
597   }
598 
599   private void removeStaircasesFromArea(List<HomePieceOfFurniture> visibleStaircases, Area area) {
600     // Remove from room area all the staircases that intersect it
601     ModelManager modelManager = ModelManager.getInstance();
602     for (HomePieceOfFurniture staircase : visibleStaircases) {
603       area.subtract(modelManager.getAreaOnFloor(staircase));
604     }
605   }
606 
607   /**
608    * Returns the visible staircases among the given <code>furniture</code>.
609    */
610   private List<HomePieceOfFurniture> getVisibleStaircases(List<HomePieceOfFurniture> furniture,
611                                                           int roomPart, Level roomLevel,
612                                                           boolean firstLevel) {
613     List<HomePieceOfFurniture> visibleStaircases = new ArrayList<HomePieceOfFurniture>(furniture.size());
614     for (HomePieceOfFurniture piece : furniture) {
615       if (piece.isVisible()
616           && (piece.getLevel() == null
617               || piece.getLevel().isViewableAndVisible())) {
618         if (piece instanceof HomeFurnitureGroup) {
619           visibleStaircases.addAll(getVisibleStaircases(((HomeFurnitureGroup)piece).getFurniture(), roomPart, roomLevel, firstLevel));
620         } else if (piece.getStaircaseCutOutShape() != null
621             && !"false".equalsIgnoreCase(piece.getStaircaseCutOutShape())
622             && ((roomPart == FLOOR_PART
623                     && piece.getGroundElevation() < roomLevel.getElevation()
624                     && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() - (firstLevel ? 0 : roomLevel.getFloorThickness())
625                 || roomPart == CEILING_PART
626                     && piece.getGroundElevation() < roomLevel.getElevation() + roomLevel.getHeight()
627                     && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() + roomLevel.getHeight()))) {
628           visibleStaircases.add(piece);
629         }
630       }
631     }
632     return visibleStaircases;
633   }
634 
635   /**
636    * Returns an array that cites <code>points</code> in reverse order.
637    */
638   private float [][] getReversedArray(float [][] points) {
639     points = points.clone();
640     List<float []> pointList = Arrays.asList(points);
641     Collections.reverse(pointList);
642     return pointList.toArray(points);
643   }
644 
645   /**
646    * Returns the room height at the given point.
647    */
648   private float getRoomHeightAt(float x, float y) {
649     double smallestDistance = Float.POSITIVE_INFINITY;
650     Room room = (Room)getUserData();
651     Level roomLevel = room.getLevel();
652     float roomElevation = roomLevel != null
653         ? roomLevel.getElevation()
654         : 0;
655     float roomHeight = roomElevation +
656         (roomLevel == null ? this.home.getWallHeight() : roomLevel.getHeight());
657     List<Level> levels = this.home.getLevels();
658     if (roomLevel == null || isLastLevel(roomLevel, levels)) {
659       // Search the closest wall point to x, y at last level
660       Wall closestWall = null;
661       float [][] closestWallPoints = null;
662       int closestIndex = -1;
663       for (Wall wall : this.home.getWalls()) {
664         if ((wall.getLevel() == null || wall.getLevel().isViewable())
665             && wall.isAtLevel(roomLevel)) {
666           float [][] points = wall.getPoints();
667           for (int i = 0; i < points.length; i++) {
668             double distanceToWallPoint = Point2D.distanceSq(points [i][0], points [i][1], x, y);
669             if (distanceToWallPoint < smallestDistance) {
670               closestWall = wall;
671               closestWallPoints = points;
672               closestIndex = i;
673               smallestDistance = distanceToWallPoint;
674             }
675           }
676         }
677       }
678 
679       if (closestWall != null) {
680         roomHeight = closestWall.getLevel() == null ? 0 : closestWall.getLevel().getElevation();
681         Float wallHeightAtStart = closestWall.getHeight();
682         if (closestIndex == 0 || closestIndex == closestWallPoints.length - 1) { // Wall start
683           roomHeight += wallHeightAtStart != null
684               ? wallHeightAtStart
685               : this.home.getWallHeight();
686         } else { // Wall end
687           if (closestWall.isTrapezoidal()) {
688             Float arcExtent = closestWall.getArcExtent();
689             if (arcExtent == null
690                 || arcExtent.floatValue() == 0
691                 || closestIndex == closestWallPoints.length / 2
692                 || closestIndex == closestWallPoints.length / 2 - 1) {
693               roomHeight += closestWall.getHeightAtEnd();
694             } else {
695               // Compute the angle between start point and the current point of the wall
696               // to get the relative height at that point
697               float xArcCircleCenter = closestWall.getXArcCircleCenter();
698               float yArcCircleCenter = closestWall.getYArcCircleCenter();
699               float xClosestPoint = closestWallPoints [closestIndex][0];
700               float yClosestPoint = closestWallPoints [closestIndex][1];
701               double centerToClosestPointDistance = Point2D.distance(xArcCircleCenter, yArcCircleCenter, xClosestPoint, yClosestPoint);
702               float xStart = closestWall.getXStart();
703               float yStart = closestWall.getYStart();
704               double centerToStartPointDistance = Point2D.distance(xArcCircleCenter, yArcCircleCenter, xStart, yStart);
705               double scalarProduct = (xClosestPoint - xArcCircleCenter) * (xStart - xArcCircleCenter)
706                   + (yClosestPoint - yArcCircleCenter) * (yStart - yArcCircleCenter);
707               scalarProduct /= (centerToClosestPointDistance * centerToStartPointDistance);
708               double arcExtentToClosestWallPoint = Math.acos(scalarProduct) * Math.signum(arcExtent);
709               roomHeight += (float)(wallHeightAtStart
710                   + (closestWall.getHeightAtEnd() - wallHeightAtStart) * arcExtentToClosestWallPoint / arcExtent);
711             }
712           } else {
713             roomHeight += (wallHeightAtStart != null ? wallHeightAtStart : this.home.getWallHeight());
714           }
715         }
716       }
717     }
718     return roomHeight;
719   }
720 
721   /**
722    * Returns <code>true</code> if the given level is the last level in home.
723    */
724   private boolean isLastLevel(Level level, List<Level> levels) {
725     return levels.indexOf(level) == levels.size() - 1;
726   }
727 
728   /**
729    * Sets room appearance with its color, texture.
730    */
731   private void updateRoomAppearance(boolean waitTextureLoadingEnd) {
732     Room room = (Room)getUserData();
733     Group roomFloorGroup = (Group)getChild(FLOOR_PART);
734     boolean ignoreFloorTransparency = room.getLevel() == null || room.getLevel().getElevation() <= 0;
735     updateFilledRoomPartAppearance(((Shape3D)roomFloorGroup.getChild(0)).getAppearance(),
736         room.getFloorTexture(), waitTextureLoadingEnd, room.getFloorColor(), room.getFloorShininess(),
737         room.isFloorVisible(), ignoreFloorTransparency, roomFloorGroup.numChildren() == 1);
738     if (roomFloorGroup.numChildren() > 1) {
739       updateOutlineRoomPartAppearance(((Shape3D)roomFloorGroup.getChild(1)).getAppearance(), room.isFloorVisible());
740     }
741 
742     Group roomCeilingGroup = (Group)getChild(CEILING_PART);
743     // Ignore ceiling transparency for rooms without level for backward compatibility
744     boolean ignoreCeillingTransparency = room.getLevel() == null;
745     updateFilledRoomPartAppearance(((Shape3D)roomCeilingGroup.getChild(0)).getAppearance(),
746         room.getCeilingTexture(), waitTextureLoadingEnd, room.getCeilingColor(), room.getCeilingShininess(),
747         room.isCeilingVisible(), ignoreCeillingTransparency, roomCeilingGroup.numChildren() == 1);
748     if (roomCeilingGroup.numChildren() > 1) {
749       updateOutlineRoomPartAppearance(((Shape3D)roomCeilingGroup.getChild(1)).getAppearance(), room.isCeilingVisible());
750     }
751   }
752 
753   /**
754    * Sets filled room part appearance with its color, texture and visibility.
755    */
756   private void updateFilledRoomPartAppearance(final Appearance roomPartAppearance,
757                                              final HomeTexture roomPartTexture,
758                                              boolean waitTextureLoadingEnd,
759                                              Integer roomPartColor,
760                                              float shininess,
761                                              boolean visible,
762                                              boolean ignoreTransparency,
763                                              boolean ignoreDrawingMode) {
764     if (roomPartTexture == null) {
765       roomPartAppearance.setMaterial(getMaterial(roomPartColor, roomPartColor, shininess));
766       roomPartAppearance.setTexture(null);
767     } else {
768       // Update material and texture of room part
769       roomPartAppearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, shininess));
770       roomPartAppearance.setTextureAttributes(getTextureAttributes(roomPartTexture, true));
771       final TextureManager textureManager = TextureManager.getInstance();
772       textureManager.loadTexture(roomPartTexture.getImage(), waitTextureLoadingEnd,
773           new TextureManager.TextureObserver() {
774               public void textureUpdated(Texture texture) {
775                 texture = getHomeTextureClone(texture, home);
776                 if (roomPartAppearance.getTexture() != texture) {
777                   roomPartAppearance.setTexture(texture);
778                 }
779               }
780             });
781     }
782     if (!ignoreTransparency) {
783       // Update room part transparency
784       float upperRoomsAlpha = this.home.getEnvironment().getWallsAlpha();
785       TransparencyAttributes transparencyAttributes = roomPartAppearance.getTransparencyAttributes();
786       transparencyAttributes.setTransparency(upperRoomsAlpha);
787       // If alpha is equal to zero, turn off transparency to get better results
788       transparencyAttributes.setTransparencyMode(upperRoomsAlpha == 0
789           ? TransparencyAttributes.NONE
790           : TransparencyAttributes.NICEST);
791     }
792     // Update room part visibility
793     RenderingAttributes renderingAttributes = roomPartAppearance.getRenderingAttributes();
794     HomeEnvironment.DrawingMode drawingMode = this.home.getEnvironment().getDrawingMode();
795     renderingAttributes.setVisible(visible
796         && (ignoreDrawingMode
797             || drawingMode == null
798             || drawingMode == HomeEnvironment.DrawingMode.FILL
799             || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE));
800   }
801 
802   /**
803    * Sets outline wall side visibility.
804    */
805   private void updateOutlineRoomPartAppearance(final Appearance roomPartAppearance,
806                                                boolean visible) {
807     // Update room part visibility
808     RenderingAttributes renderingAttributes = roomPartAppearance.getRenderingAttributes();
809     HomeEnvironment.DrawingMode drawingMode = this.home.getEnvironment().getDrawingMode();
810     renderingAttributes.setVisible(visible
811         && (drawingMode == HomeEnvironment.DrawingMode.OUTLINE
812             || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE));
813   }
814 }