/* * Room3D.java 23 jan. 09 * * Sweet Home 3D, Copyright (c) 2007-2009 Emmanuel PUYBARET / eTeks <info@eteks.com> * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ package com.eteks.sweethome3d.j3d; import java.awt.geom.Area; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.awt.geom.Rectangle2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; import javax.media.j3d.Appearance; import javax.media.j3d.BranchGroup; import javax.media.j3d.Geometry; import javax.media.j3d.Node; import javax.media.j3d.RenderingAttributes; import javax.media.j3d.Shape3D; import javax.media.j3d.Texture; import javax.media.j3d.TextureAttributes; import javax.media.j3d.TransparencyAttributes; import javax.vecmath.Point3f; import javax.vecmath.TexCoord2f; import com.eteks.sweethome3d.model.Home; import com.eteks.sweethome3d.model.HomeFurnitureGroup; import com.eteks.sweethome3d.model.HomePieceOfFurniture; import com.eteks.sweethome3d.model.HomeTexture; import com.eteks.sweethome3d.model.Level; import com.eteks.sweethome3d.model.Room; import com.eteks.sweethome3d.model.Wall; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; /** * Root of room branch. */ public class Room3D extends Object3DBranch { private static final TextureAttributes MODULATE_TEXTURE_ATTRIBUTES = new TextureAttributes(); static { MODULATE_TEXTURE_ATTRIBUTES.setTextureMode(TextureAttributes.MODULATE); } private static final int FLOOR_PART = 0; private static final int CEILING_PART = 1; private final Home home; /** * Creates the 3D room matching the given home <code>room</code>. */ public Room3D(Room room, Home home) { this(room, home, false, false, false); } /** * Creates the 3D room matching the given home <code>room</code>. */ public Room3D(Room room, Home home, boolean ignoreCeilingPart, boolean ignoreInvisiblePart, boolean waitTextureLoadingEnd) { setUserData(room); this.home = home; // Allow room branch to be removed from its parent setCapability(BranchGroup.ALLOW_DETACH); // Allow to read branch shape children setCapability(BranchGroup.ALLOW_CHILDREN_READ); // Add room floor and cellar empty shapes to branch addChild(createRoomPartShape()); addChild(createRoomPartShape()); // Set room shape geometry and appearance updateRoomGeometry(); updateRoomAppearance(waitTextureLoadingEnd); if (ignoreCeilingPart || (ignoreInvisiblePart && !room.isCeilingVisible())) { removeChild(CEILING_PART); } if (ignoreInvisiblePart && !room.isFloorVisible()) { removeChild(FLOOR_PART); } } /** * Returns a new room part shape with no geometry * and a default appearance with a white material. */ private Node createRoomPartShape() { Shape3D roomShape = new Shape3D(); // Allow room shape to change its geometry roomShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); roomShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); roomShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ); Appearance roomAppearance = new Appearance(); roomShape.setAppearance(roomAppearance); roomAppearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ); TransparencyAttributes transparencyAttributes = new TransparencyAttributes(); transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE); transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE); roomAppearance.setTransparencyAttributes(transparencyAttributes); roomAppearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ); RenderingAttributes renderingAttributes = new RenderingAttributes(); renderingAttributes.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE); roomAppearance.setRenderingAttributes(renderingAttributes); roomAppearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE); roomAppearance.setMaterial(DEFAULT_MATERIAL); roomAppearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE); roomAppearance.setCapability(Appearance.ALLOW_TEXTURE_READ); // Mix texture and room color roomAppearance.setTextureAttributes(MODULATE_TEXTURE_ATTRIBUTES); return roomShape; } @Override public void update() { updateRoomGeometry(); updateRoomAppearance(false); } /** * Sets the 3D geometry of this room shapes that matches its 2D geometry. */ private void updateRoomGeometry() { updateRoomPartGeometry(FLOOR_PART, ((Room)getUserData()).getFloorTexture()); updateRoomPartGeometry(CEILING_PART, ((Room)getUserData()).getCeilingTexture()); } private void updateRoomPartGeometry(int roomPart, HomeTexture texture) { Shape3D roomShape = (Shape3D)getChild(roomPart); int currentGeometriesCount = roomShape.numGeometries(); Room room = (Room)getUserData(); if (room.getLevel() == null || room.getLevel().isVisible()) { for (Geometry roomGeometry : createRoomGeometries(roomPart, texture)) { roomShape.addGeometry(roomGeometry); } } for (int i = currentGeometriesCount - 1; i >= 0; i--) { roomShape.removeGeometry(i); } } /** * Returns room geometry computed from its points. */ private Geometry [] createRoomGeometries(int roomPart, HomeTexture texture) { Room room = (Room)getUserData(); float [][] points = room.getPoints(); if ((roomPart == FLOOR_PART && room.isFloorVisible() || roomPart == CEILING_PART && room.isCeilingVisible()) && points.length > 2) { Level roomLevel = room.getLevel(); List<Level> levels = this.home.getLevels(); boolean lastLevel = isLastLevel(roomLevel, levels); float floorBottomElevation; float roomElevation; if (roomLevel != null) { roomElevation = roomLevel.getElevation(); floorBottomElevation = roomElevation - roomLevel.getFloorThickness(); } else { roomElevation = 0; floorBottomElevation = 0; } float firstLevelElevation; if (levels.size() == 0) { firstLevelElevation = 0; } else { firstLevelElevation = levels.get(0).getElevation(); } boolean floorBottomVisible = roomPart == FLOOR_PART && roomLevel != null && roomElevation != firstLevelElevation; // Find rooms at the same elevation // and room ceilings at same elevation as the floor bottom List<Room> roomsAtSameElevation = new ArrayList<Room>(); List<Room> ceilingsAtSameFloorBottomElevation = new ArrayList<Room>(); for (Room homeRoom : this.home.getRooms()) { Level homeRoomLevel = homeRoom.getLevel(); if (room == homeRoom // Store also the room itself to know its order among rooms at same elevation || roomLevel == homeRoomLevel && (roomPart == FLOOR_PART && homeRoom.isFloorVisible() || roomPart == CEILING_PART && homeRoom.isCeilingVisible()) || roomLevel != null && homeRoomLevel != null && (roomPart == FLOOR_PART && homeRoom.isFloorVisible() && Math.abs(roomElevation - homeRoomLevel.getElevation()) < 1E-4 || roomPart == CEILING_PART && homeRoom.isCeilingVisible() && !lastLevel && !isLastLevel(homeRoomLevel, levels) && Math.abs(roomElevation + roomLevel.getHeight() - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1E-4)) { roomsAtSameElevation.add(homeRoom); } else if (floorBottomVisible && homeRoomLevel != null && homeRoom.isCeilingVisible() && !isLastLevel(homeRoomLevel, levels) && Math.abs(floorBottomElevation - (homeRoomLevel.getElevation() + homeRoomLevel.getHeight())) < 1E-4) { ceilingsAtSameFloorBottomElevation.add(homeRoom); } } List<HomePieceOfFurniture> visibleStaircases; if (roomLevel == null || roomPart == CEILING_PART && lastLevel) { visibleStaircases = Collections.emptyList(); } else { visibleStaircases = getVisibleStaircases(this.home.getFurniture(), roomPart, roomLevel, roomLevel.getElevation() == firstLevelElevation); } // Check ceiling points of the last level are at the same elevation boolean sameElevation = true; if (roomPart == CEILING_PART && (roomLevel == null || lastLevel)) { float firstPointElevation = getRoomHeightAt(points [0][0], points [0][1]); for (int i = 1; i < points.length && sameElevation; i++) { sameElevation = getRoomHeightAt(points [i][0], points [i][1]) == firstPointElevation; } } // Retrieve room points List<float [][]> roomPoints; Map<Integer, List<float [][]>> roomHoles; Area roomVisibleArea; // If room isn't singular retrieve all the points of its different polygons if (!room.isSingular() || sameElevation && (roomsAtSameElevation.get(roomsAtSameElevation.size() - 1) != room || visibleStaircases.size() > 0)) { roomVisibleArea = new Area(getShape(points)); if (roomsAtSameElevation.contains(room)) { // Remove other rooms surface that may overlap the current room for (int i = roomsAtSameElevation.size() - 1; i > 0 && roomsAtSameElevation.get(i) != room; i--) { Room otherRoom = roomsAtSameElevation.get(i); roomVisibleArea.subtract(new Area(getShape(otherRoom.getPoints()))); } } removeStaircasesFromArea(visibleStaircases, roomVisibleArea); roomPoints = new ArrayList<float[][]>(); roomHoles = new HashMap<Integer, List<float [][]>>(); getAreaPoints(roomVisibleArea, roomPart == CEILING_PART, roomPoints, roomHoles); } else { boolean clockwise = room.isClockwise(); if (clockwise && roomPart == FLOOR_PART || !clockwise && roomPart == CEILING_PART) { // Reverse points order if they are in the good order points = getReversedArray(points); } roomPoints = Arrays.asList(new float [][][] {points}); roomHoles = Collections.emptyMap(); roomVisibleArea = null; } // Retrieve points of the room floor bottom List<float [][]> floorBottomPoints; Map<Integer, List<float [][]>> floorBottomHoles; if (floorBottomVisible) { if (roomVisibleArea != null || ceilingsAtSameFloorBottomElevation.size() > 0) { Area floorBottomVisibleArea = roomVisibleArea != null ? roomVisibleArea : new Area(getShape(points)); // Remove other rooms surface that may overlap the floor bottom for (Room otherRoom : ceilingsAtSameFloorBottomElevation) { floorBottomVisibleArea.subtract(new Area(getShape(otherRoom.getPoints()))); } floorBottomPoints = new ArrayList<float[][]>(); floorBottomHoles = new HashMap<Integer, List<float [][]>>(); getAreaPoints(floorBottomVisibleArea, true, floorBottomPoints, floorBottomHoles); } else { floorBottomPoints = Arrays.asList(new float [][][] {getReversedArray(points)}); floorBottomHoles = Collections.emptyMap(); } } else { floorBottomPoints = Collections.emptyList(); floorBottomHoles = Collections.emptyMap(); } List<Geometry> geometries = new ArrayList<Geometry> (roomPoints.size() + floorBottomPoints.size()); boolean computeFloorBorder = roomLevel != null && roomPart == FLOOR_PART && roomLevel.getElevation() != firstLevelElevation; // Compute room and border geometries int i = 0; final float subpartSize = this.home.getEnvironment().getSubpartSizeUnderLight(); for ( ; i < roomPoints.size(); i++) { float [][] roomPartPoints = roomPoints.get(i); float [] roomPartPointElevations = new float [roomPartPoints.length]; boolean roomPartAtSameElevation = true; for (int j = 0; j < roomPartPoints.length; j++) { roomPartPointElevations [j] = roomPart == FLOOR_PART ? roomElevation : getRoomHeightAt(roomPartPoints [j][0], roomPartPoints [j][1]); if (roomPartAtSameElevation && j > 0) { roomPartAtSameElevation = roomPartPointElevations [j] == roomPartPointElevations [j - 1]; } } if (roomPartAtSameElevation && subpartSize > 0) { // Subdivide area in smaller squares to ensure a smoother effect with point lights float xMin = Float.MAX_VALUE; float xMax = Float.MIN_VALUE; float zMin = Float.MAX_VALUE; float zMax = Float.MIN_VALUE; for (float [] point : roomPartPoints) { xMin = Math.min(xMin, point [0]); xMax = Math.max(xMax, point [0]); zMin = Math.min(zMin, point [1]); zMax = Math.max(zMax, point [1]); } Area roomPartArea = new Area(getShape(roomPartPoints)); for (float xSquare = xMin; xSquare < xMax; xSquare += subpartSize) { for (float zSquare = zMin; zSquare < zMax; zSquare += subpartSize) { Area roomPartSquare = new Area(new Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize)); roomPartSquare.intersect(roomPartArea); List<float [][]> holes = roomHoles.get(i); if (holes != null) { for (float [][] geometryHole : holes) { roomPartSquare.subtract(new Area(getShape(geometryHole))); } } if (!roomPartSquare.isEmpty()) { List<float [][]> geometryPartPointsList = new ArrayList<float [][]>(); Map<Integer, List<float [][]>> geometryPartHolesMap = new HashMap<Integer, List<float [][]>>(); getAreaPoints(roomPartSquare, roomPart == CEILING_PART, geometryPartPointsList, geometryPartHolesMap); for (int j = 0 ; j < geometryPartPointsList.size(); j++) { geometries.add(computeRoomPartGeometry(geometryPartPointsList.get(j), geometryPartHolesMap.get(j), null, roomLevel, roomPartPointElevations [0], floorBottomElevation, roomPart == FLOOR_PART, false, texture)); } } } } } else { geometries.add(computeRoomPartGeometry(roomPartPoints, roomHoles.get(i), roomPartPointElevations, roomLevel, roomElevation, floorBottomElevation, roomPart == FLOOR_PART, false, texture)); } if (computeFloorBorder) { geometries.add(computeRoomBorderGeometry(roomPartPoints, roomHoles.get(i), roomLevel, roomElevation, texture)); } } // Compute floor bottom geometry for (i = 0 ; i < floorBottomPoints.size(); i++) { float [][] floorBottomPartPoints = floorBottomPoints.get(i); if (subpartSize > 0) { float xMin = Float.MAX_VALUE; float xMax = Float.MIN_VALUE; float zMin = Float.MAX_VALUE; float zMax = Float.MIN_VALUE; for (float [] point : floorBottomPartPoints) { xMin = Math.min(xMin, point [0]); xMax = Math.max(xMax, point [0]); zMin = Math.min(zMin, point [1]); zMax = Math.max(zMax, point [1]); } Area floorBottomPartArea = new Area(getShape(floorBottomPartPoints)); for (float xSquare = xMin; xSquare < xMax; xSquare += subpartSize) { for (float zSquare = zMin; zSquare < zMax; zSquare += subpartSize) { Area floorBottomPartSquare = new Area(new Rectangle2D.Float(xSquare, zSquare, subpartSize, subpartSize)); floorBottomPartSquare.intersect(floorBottomPartArea); List<float [][]> holes = floorBottomHoles.get(i); if (holes != null) { for (float [][] geometryHole : holes) { floorBottomPartSquare.subtract(new Area(getShape(geometryHole))); } } if (!floorBottomPartSquare.isEmpty()) { List<float [][]> geometryPartPointsList = new ArrayList<float [][]>(); Map<Integer, List<float [][]>> geometryPartHolesMap = new HashMap<Integer, List<float [][]>>(); getAreaPoints(floorBottomPartSquare, true, geometryPartPointsList, geometryPartHolesMap); for (int j = 0 ; j < geometryPartPointsList.size(); j++) { geometries.add(computeRoomPartGeometry(geometryPartPointsList.get(j), geometryPartHolesMap.get(j), null, roomLevel, roomElevation, floorBottomElevation, true, true, texture)); } } } } } else { geometries.add(computeRoomPartGeometry(floorBottomPartPoints, floorBottomHoles.get(i), null, roomLevel, roomElevation, floorBottomElevation, true, true, texture)); } } return geometries.toArray(new Geometry [geometries.size()]); } else { return new Geometry [0]; } } /** * Returns the room part geometry matching the given points. */ private Geometry computeRoomPartGeometry(float [][] geometryPoints, List<float [][]> geometryHoles, float [] roomPartPointElevations, Level roomLevel, float roomPartElevation, float floorBottomElevation, boolean floorPart, boolean floorBottomPart, HomeTexture texture) { if (geometryHoles == null) { geometryHoles = Collections.emptyList(); } int [] contourCounts = {1 + geometryHoles.size()}; int [] stripCounts = new int [contourCounts.length + geometryHoles.size()]; int i = 0; int vertexCount = geometryPoints.length; stripCounts [i++] = geometryPoints.length; for (float [][] geometryHole : geometryHoles) { vertexCount += geometryHole.length; stripCounts [i++] = geometryHole.length; } i = 0; Point3f [] coords = new Point3f [vertexCount]; // Compute room coordinates for (int j = 0; j < geometryPoints.length; j++) { float y = floorBottomPart ? floorBottomElevation : (roomPartPointElevations != null ? roomPartPointElevations [j] : roomPartElevation); coords [i++] = new Point3f(geometryPoints [j][0], y, geometryPoints [j][1]); } for (float [][] geometryHole : geometryHoles) { for (int j = 0; j < geometryHole.length; j++) { float y = floorBottomPart ? floorBottomElevation : (floorPart ? roomPartElevation : getRoomHeightAt(geometryHole [j][0], geometryHole [j][1])); coords [i++] = new Point3f(geometryHole [j][0], y, geometryHole [j][1]); } } GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); geometryInfo.setCoordinates(coords); geometryInfo.setStripCounts(stripCounts); geometryInfo.setContourCounts(contourCounts); if (texture != null) { TexCoord2f [] textureCoords = new TexCoord2f [vertexCount]; i = 0; // Compute room texture coordinates for (int j = 0; j < geometryPoints.length; j++) { textureCoords [i++] = new TexCoord2f(geometryPoints [j][0] / texture.getWidth(), floorPart ? -geometryPoints [j][1] / texture.getHeight() : geometryPoints [j][1] / texture.getHeight()); } for (float [][] geometryHole : geometryHoles) { for (int j = 0; j < geometryHole.length; j++) { textureCoords [i++] = new TexCoord2f(geometryHole [j][0] / texture.getWidth(), floorPart ? -geometryHole [j][1] / texture.getHeight() : geometryHole [j][1] / texture.getHeight()); } } geometryInfo.setTextureCoordinateParams(1, 2); geometryInfo.setTextureCoordinates(0, textureCoords); } // Generate normals new NormalGenerator().generateNormals(geometryInfo); return geometryInfo.getIndexedGeometryArray(); } /** * Returns the room border geometry matching the given points. */ private Geometry computeRoomBorderGeometry(float [][] geometryPoints, List<float [][]> geometryHoles, Level roomLevel, float roomElevation, HomeTexture texture) { if (geometryHoles == null) { geometryHoles = Collections.emptyList(); } int geometryHolesPointCount = 0; for (float [][] geometryHole : geometryHoles) { geometryHolesPointCount += geometryHole.length; } int [] contourCounts = new int [geometryPoints.length + geometryHolesPointCount]; int [] stripCounts = new int [contourCounts.length]; int vertexCount = geometryPoints.length * 4; vertexCount += geometryHolesPointCount * 4; Arrays.fill(contourCounts, 1); Arrays.fill(stripCounts, 4); int i = 0; Point3f [] coords = new Point3f [vertexCount]; float floorBottomElevation = roomElevation - roomLevel.getFloorThickness(); // Compute room borders coordinates for (int k = 0; k < geometryPoints.length; k++) { coords [i++] = new Point3f(geometryPoints [k][0], roomElevation, geometryPoints [k][1]); coords [i++] = new Point3f(geometryPoints [k][0], floorBottomElevation, geometryPoints [k][1]); int nextPoint = k < geometryPoints.length - 1 ? k + 1 : 0; coords [i++] = new Point3f(geometryPoints [nextPoint][0], floorBottomElevation, geometryPoints [nextPoint][1]); coords [i++] = new Point3f(geometryPoints [nextPoint][0], roomElevation, geometryPoints [nextPoint][1]); } // Compute holes borders coordinates for (float [][] geometryHole : geometryHoles) { for (int k = 0; k < geometryHole.length; k++) { coords [i++] = new Point3f(geometryHole [k][0], roomElevation, geometryHole [k][1]); int nextPoint = k < geometryHole.length - 1 ? k + 1 : 0; coords [i++] = new Point3f(geometryHole [nextPoint][0], roomElevation, geometryHole [nextPoint][1]); coords [i++] = new Point3f(geometryHole [nextPoint][0], floorBottomElevation, geometryHole [nextPoint][1]); coords [i++] = new Point3f(geometryHole [k][0], floorBottomElevation, geometryHole [k][1]); } } GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); geometryInfo.setCoordinates(coords); geometryInfo.setStripCounts(stripCounts); geometryInfo.setContourCounts(contourCounts); if (texture != null) { TexCoord2f [] textureCoords = new TexCoord2f [vertexCount]; i = 0; // Compute room border texture coordinates for (int j = 0; j < geometryPoints.length; j++) { textureCoords [i++] = new TexCoord2f(geometryPoints [j][0] / texture.getWidth(), -geometryPoints [j][1] / texture.getHeight()); textureCoords [i++] = new TexCoord2f(geometryPoints [j][0] / texture.getWidth(), -(geometryPoints [j][1] - roomLevel.getFloorThickness()) / texture.getHeight()); int nextPoint = j < geometryPoints.length - 1 ? j + 1 : 0; textureCoords [i++] = new TexCoord2f(geometryPoints [nextPoint][0] / texture.getWidth(), -(geometryPoints [nextPoint][1] - roomLevel.getFloorThickness()) / texture.getHeight()); textureCoords [i++] = new TexCoord2f(geometryPoints [nextPoint][0] / texture.getWidth(), -geometryPoints [nextPoint][1] / texture.getHeight()); } // Compute holes borders texture coordinates for (float [][] geometryHole : geometryHoles) { for (int j = 0; j < geometryHole.length; j++) { textureCoords [i++] = new TexCoord2f(geometryHole [j][0] / texture.getWidth(), -geometryHole [j][1] / texture.getHeight()); int nextPoint = j < geometryHole.length - 1 ? j + 1 : 0; textureCoords [i++] = new TexCoord2f(geometryHole [nextPoint][0] / texture.getWidth(), -geometryHole [nextPoint][1] / texture.getHeight()); textureCoords [i++] = new TexCoord2f(geometryHole [nextPoint][0] / texture.getWidth(), -(geometryHole [nextPoint][1] - roomLevel.getFloorThickness()) / texture.getHeight()); textureCoords [i++] = new TexCoord2f(geometryHole [j][0] / texture.getWidth(), -(geometryHole [j][1] - roomLevel.getFloorThickness()) / texture.getHeight()); } } geometryInfo.setTextureCoordinateParams(1, 2); geometryInfo.setTextureCoordinates(0, textureCoords); } // Generate normals new NormalGenerator(Math.PI / 8).generateNormals(geometryInfo); return geometryInfo.getIndexedGeometryArray(); } private void removeStaircasesFromArea(List<HomePieceOfFurniture> visibleStaircases, Area area) { // Remove from room area all the staircases that intersect it ModelManager modelManager = ModelManager.getInstance(); for (HomePieceOfFurniture staircase : visibleStaircases) { area.subtract(modelManager.getAreaOnFloor(staircase)); } } /** * Returns the visible staircases among the given <code>furniture</code>. */ private List<HomePieceOfFurniture> getVisibleStaircases(List<HomePieceOfFurniture> furniture, int roomPart, Level roomLevel, boolean firstLevel) { List<HomePieceOfFurniture> visibleStaircases = new ArrayList<HomePieceOfFurniture>(furniture.size()); for (HomePieceOfFurniture piece : furniture) { if (piece.isVisible()) { if (piece instanceof HomeFurnitureGroup) { visibleStaircases.addAll(getVisibleStaircases(((HomeFurnitureGroup)piece).getFurniture(), roomPart, roomLevel, firstLevel)); } else if (piece.getStaircaseCutOutShape() != null && !"false".equalsIgnoreCase(piece.getStaircaseCutOutShape()) && ((roomPart == FLOOR_PART && piece.getGroundElevation() < roomLevel.getElevation() && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() - (firstLevel ? 0 : roomLevel.getFloorThickness()) || roomPart == CEILING_PART && piece.getGroundElevation() < roomLevel.getElevation() + roomLevel.getHeight() && piece.getGroundElevation() + piece.getHeight() >= roomLevel.getElevation() + roomLevel.getHeight()))) { visibleStaircases.add(piece); } } } return visibleStaircases; } /** * Fills <code>areaPoints</code> and <code>areaHoles</code> from the given area. */ private void getAreaPoints(Area area, boolean reversed, List<float [][]> areaPoints, Map<Integer, List<float [][]>> areaHoles) { // Retrieve the points of the different polygons // and reverse their points order if necessary List<float []> currentPathPoints = new ArrayList<float[]>(); float [] previousRoomPoint = null; int i = 0; for (PathIterator it = area.getPathIterator(null, 1); !it.isDone(); it.next()) { float [] roomPoint = new float[2]; switch (it.currentSegment(roomPoint)) { case PathIterator.SEG_MOVETO : case PathIterator.SEG_LINETO : if (previousRoomPoint == null || roomPoint [0] != previousRoomPoint [0] || roomPoint [1] != previousRoomPoint [1]) { currentPathPoints.add(roomPoint); } previousRoomPoint = roomPoint; break; case PathIterator.SEG_CLOSE : if (currentPathPoints.get(0) [0] == previousRoomPoint [0] && currentPathPoints.get(0) [1] == previousRoomPoint [1]) { currentPathPoints.remove(currentPathPoints.size() - 1); } if (currentPathPoints.size() > 2) { float [][] pathPoints = currentPathPoints.toArray(new float [currentPathPoints.size()][]); Room subRoom = new Room(pathPoints); if (subRoom.getArea() > 0) { boolean pathPointsClockwise = subRoom.isClockwise(); if (pathPointsClockwise) { // Store counter clockwise points as holes if (!reversed) { pathPoints = getReversedArray(pathPoints); } List<float [][]> holes = areaHoles.get(i); if (holes == null) { holes = new ArrayList<float [][]>(1); areaHoles.put(i, holes); } holes.add(pathPoints); } else { if (reversed) { pathPoints = getReversedArray(pathPoints); } areaPoints.add(pathPoints); i++; } } } currentPathPoints.clear(); previousRoomPoint = null; break; } } } /** * Returns an array that cites <code>points</code> in reverse order. */ private float [][] getReversedArray(float [][] points) { points = points.clone(); List<float []> pointList = Arrays.asList(points); Collections.reverse(pointList); return pointList.toArray(points); } /** * Returns the room height at the given point. */ private float getRoomHeightAt(float x, float y) { double smallestDistance = Float.POSITIVE_INFINITY; Room room = (Room)getUserData(); Level roomLevel = room.getLevel(); float roomElevation = roomLevel != null ? roomLevel.getElevation() : 0; float roomHeight = roomElevation + (roomLevel == null ? this.home.getWallHeight() : roomLevel.getHeight()); List<Level> levels = this.home.getLevels(); if (roomLevel == null || isLastLevel(roomLevel, levels)) { // Search the closest wall point to x, y at last level Wall closestWall = null; float [][] closestWallPoints = null; int closestIndex = -1; for (Wall wall : this.home.getWalls()) { if (wall.isAtLevel(roomLevel)) { float [][] points = wall.getPoints(); for (int i = 0; i < points.length; i++) { double distanceToWallPoint = Point2D.distanceSq(points [i][0], points [i][1], x, y); if (distanceToWallPoint < smallestDistance) { closestWall = wall; closestWallPoints = points; closestIndex = i; smallestDistance = distanceToWallPoint; } } } } if (closestWall != null) { roomHeight = closestWall.getLevel() == null ? 0 : closestWall.getLevel().getElevation(); Float wallHeightAtStart = closestWall.getHeight(); if (closestIndex == 0 || closestIndex == closestWallPoints.length - 1) { // Wall start roomHeight += wallHeightAtStart != null ? wallHeightAtStart : this.home.getWallHeight(); } else { // Wall end if (closestWall.isTrapezoidal()) { Float arcExtent = closestWall.getArcExtent(); if (arcExtent == null || closestIndex == closestWallPoints.length / 2 || closestIndex == closestWallPoints.length / 2 - 1) { roomHeight += closestWall.getHeightAtEnd(); } else { // Compute the angle between start point and the current point of the wall // to get the relative height at that point float xArcCircleCenter = closestWall.getXArcCircleCenter(); float yArcCircleCenter = closestWall.getYArcCircleCenter(); float xClosestPoint = closestWallPoints [closestIndex][0]; float yClosestPoint = closestWallPoints [closestIndex][1]; double centerToClosestPointDistance = Point2D.distance(xArcCircleCenter, yArcCircleCenter, xClosestPoint, yClosestPoint); float xStart = closestWall.getXStart(); float yStart = closestWall.getYStart(); double centerToStartPointDistance = Point2D.distance(xArcCircleCenter, yArcCircleCenter, xStart, yStart); double scalarProduct = (xClosestPoint - xArcCircleCenter) * (xStart - xArcCircleCenter) + (yClosestPoint - yArcCircleCenter) * (yStart - yArcCircleCenter); scalarProduct /= (centerToClosestPointDistance * centerToStartPointDistance); double arcExtentToClosestWallPoint = Math.acos(scalarProduct) * Math.signum(arcExtent); roomHeight += (float)(wallHeightAtStart + (closestWall.getHeightAtEnd() - wallHeightAtStart) * arcExtentToClosestWallPoint / arcExtent); } } else { roomHeight += (wallHeightAtStart != null ? wallHeightAtStart : this.home.getWallHeight()); } } } } return roomHeight; } /** * Returns <code>true</code> if the given level is the last level in home. */ private boolean isLastLevel(Level level, List<Level> levels) { return levels.indexOf(level) == levels.size() - 1; } /** * Sets room appearance with its color, texture. */ private void updateRoomAppearance(boolean waitTextureLoadingEnd) { Room room = (Room)getUserData(); boolean ignoreFloorTransparency = room.getLevel() == null || room.getLevel().getElevation() <= 0; updateRoomPartAppearance(((Shape3D)getChild(FLOOR_PART)).getAppearance(), room.getFloorTexture(), waitTextureLoadingEnd, room.getFloorColor(), room.getFloorShininess(), room.isFloorVisible(), ignoreFloorTransparency); // Ignore ceiling transparency for rooms without level for backward compatibility boolean ignoreCeillingTransparency = room.getLevel() == null; updateRoomPartAppearance(((Shape3D)getChild(CEILING_PART)).getAppearance(), room.getCeilingTexture(), waitTextureLoadingEnd, room.getCeilingColor(), room.getCeilingShininess(), room.isCeilingVisible(), ignoreCeillingTransparency); } /** * Sets room part appearance with its color, texture and visibility. */ private void updateRoomPartAppearance(final Appearance roomPartAppearance, final HomeTexture roomPartTexture, boolean waitTextureLoadingEnd, Integer roomPartColor, float shininess, boolean visible, boolean ignoreTransparency) { if (roomPartTexture == null) { roomPartAppearance.setMaterial(getMaterial(roomPartColor, roomPartColor, shininess)); roomPartAppearance.setTexture(null); } else { // Update material and texture of room part roomPartAppearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, shininess)); final TextureManager textureManager = TextureManager.getInstance(); textureManager.loadTexture(roomPartTexture.getImage(), waitTextureLoadingEnd, new TextureManager.TextureObserver() { public void textureUpdated(Texture texture) { roomPartAppearance.setTexture(texture); } }); } if (!ignoreTransparency) { // Update room part transparency float upperRoomsAlpha = this.home.getEnvironment().getWallsAlpha(); TransparencyAttributes transparencyAttributes = roomPartAppearance.getTransparencyAttributes(); transparencyAttributes.setTransparency(upperRoomsAlpha); // If alpha is equal to zero, turn off transparency to get better results transparencyAttributes.setTransparencyMode(upperRoomsAlpha == 0 ? TransparencyAttributes.NONE : TransparencyAttributes.NICEST); } // Update room part visibility RenderingAttributes renderingAttributes = roomPartAppearance.getRenderingAttributes(); renderingAttributes.setVisible(visible); } }