/* * Wall3D.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.Shape; import java.awt.geom.Area; import java.awt.geom.Line2D; import java.awt.geom.PathIterator; import java.awt.geom.Point2D; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Comparator; import java.util.List; import javax.media.j3d.Appearance; import javax.media.j3d.BranchGroup; import javax.media.j3d.Geometry; import javax.media.j3d.Group; 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.HomeEnvironment; 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.Wall; import com.sun.j3d.utils.geometry.GeometryInfo; import com.sun.j3d.utils.geometry.NormalGenerator; /** * Root of wall branch. */ public class Wall3D extends Object3DBranch { private static final TextureAttributes MODULATE_TEXTURE_ATTRIBUTES = new TextureAttributes(); private static final float LEVEL_ELEVATION_SHIFT = 0.1f; static { MODULATE_TEXTURE_ATTRIBUTES.setTextureMode(TextureAttributes.MODULATE); } private static final int WALL_LEFT_SIDE = 0; private static final int WALL_RIGHT_SIDE = 1; private final Home home; /** * Creates the 3D wall matching the given home <code>wall</code>. */ public Wall3D(Wall wall, Home home) { this(wall, home, false, false); } /** * Creates the 3D wall matching the given home <code>wall</code>. */ public Wall3D(Wall wall, Home home, boolean ignoreDrawingMode, boolean waitTextureLoadingEnd) { setUserData(wall); this.home = home; // Allow wall branch to be removed from its parent setCapability(BranchGroup.ALLOW_DETACH); // Allow to read branch shape children setCapability(BranchGroup.ALLOW_CHILDREN_READ); // Add wall bottom, main and top shapes to branch for left and right side for (int i = 0; i < 6; i++) { Group wallSideGroup = new Group(); wallSideGroup.setCapability(Group.ALLOW_CHILDREN_READ); wallSideGroup.addChild(createWallPartShape(false)); if (!ignoreDrawingMode) { // Add wall left and right empty outline shapes to branch wallSideGroup.addChild(createWallPartShape(true)); } addChild(wallSideGroup); } // Set wall shape geometry and appearance updateWallGeometry(); updateWallAppearance(waitTextureLoadingEnd); } /** * Returns a new wall part shape with no geometry * and a default appearance with a white material. */ private Node createWallPartShape(boolean outline) { Shape3D wallShape = new Shape3D(); // Allow wall shape to change its geometry wallShape.setCapability(Shape3D.ALLOW_GEOMETRY_WRITE); wallShape.setCapability(Shape3D.ALLOW_GEOMETRY_READ); wallShape.setCapability(Shape3D.ALLOW_APPEARANCE_READ); Appearance wallAppearance = new Appearance(); wallShape.setAppearance(wallAppearance); wallAppearance.setCapability(Appearance.ALLOW_TRANSPARENCY_ATTRIBUTES_READ); TransparencyAttributes transparencyAttributes = new TransparencyAttributes(); transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_VALUE_WRITE); transparencyAttributes.setCapability(TransparencyAttributes.ALLOW_MODE_WRITE); wallAppearance.setTransparencyAttributes(transparencyAttributes); wallAppearance.setCapability(Appearance.ALLOW_RENDERING_ATTRIBUTES_READ); RenderingAttributes renderingAttributes = new RenderingAttributes(); renderingAttributes.setCapability(RenderingAttributes.ALLOW_VISIBLE_WRITE); wallAppearance.setRenderingAttributes(renderingAttributes); if (outline) { wallAppearance.setColoringAttributes(Object3DBranch.OUTLINE_COLORING_ATTRIBUTES); wallAppearance.setPolygonAttributes(Object3DBranch.OUTLINE_POLYGON_ATTRIBUTES); wallAppearance.setLineAttributes(Object3DBranch.OUTLINE_LINE_ATTRIBUTES); } else { wallAppearance.setCapability(Appearance.ALLOW_MATERIAL_WRITE); wallAppearance.setMaterial(DEFAULT_MATERIAL); wallAppearance.setCapability(Appearance.ALLOW_TEXTURE_WRITE); wallAppearance.setCapability(Appearance.ALLOW_TEXTURE_READ); // Mix texture and wall color wallAppearance.setTextureAttributes(MODULATE_TEXTURE_ATTRIBUTES); } return wallShape; } @Override public void update() { updateWallGeometry(); updateWallAppearance(false); } /** * Sets the 3D geometry of this wall shapes that matches its 2D geometry. */ private void updateWallGeometry() { updateWallSideGeometry(WALL_LEFT_SIDE, ((Wall)getUserData()).getLeftSideTexture()); updateWallSideGeometry(WALL_RIGHT_SIDE, ((Wall)getUserData()).getRightSideTexture()); } private void updateWallSideGeometry(int wallSide, HomeTexture texture) { Group [] wallSideGroups = {(Group)getChild(wallSide * 3), // Bottom group (Group)getChild(wallSide * 3 + 1), // Main group (Group)getChild(wallSide * 3 + 2)}; // Top group Shape3D [] wallFilledShapes = new Shape3D [wallSideGroups.length]; Shape3D [] wallOutlineShapes = new Shape3D [wallSideGroups.length]; int [] currentGeometriesCounts = new int [3]; for (int i = 0; i < wallSideGroups.length; i++) { wallFilledShapes [i] = (Shape3D)wallSideGroups [i].getChild(0); wallOutlineShapes [i] = wallSideGroups [i].numChildren() > 1 ? (Shape3D)wallSideGroups [i].getChild(1) : null; currentGeometriesCounts [i] = wallFilledShapes [i].numGeometries(); } Wall wall = (Wall)getUserData(); if (wall.getLevel() == null || wall.getLevel().isVisible()) { List [] wallGeometries = {new ArrayList<Geometry>(), new ArrayList<Geometry>(), new ArrayList<Geometry>()}; createWallGeometries(wallGeometries [0], wallGeometries [1], wallGeometries [2], wallSide, texture); for (int i = 0; i < wallSideGroups.length; i++) { for (Geometry wallGeometry : (List<Geometry>)wallGeometries [i]) { if (wallGeometry != null) { wallFilledShapes [i].addGeometry(wallGeometry); if (wallOutlineShapes [i] != null) { wallOutlineShapes [i].addGeometry(wallGeometry); } } } } } for (int i = 0; i < wallSideGroups.length; i++) { for (int j = currentGeometriesCounts [i] - 1; j >= 0; j--) { wallFilledShapes [i].removeGeometry(j); if (wallOutlineShapes [i] != null) { wallOutlineShapes [i].removeGeometry(j); } } } } /** * Creates <code>wall</code> geometries computed with windows or doors * that intersect wall. */ private void createWallGeometries(List<Geometry> wallBottomGeometries, List<Geometry> wallGeometries, List<Geometry> wallTopGeometries, int wallSide, HomeTexture texture) { float [][] wallSidePoints = getWallSidePoints(wallSide); float [] textureReferencePoint = wallSide == WALL_LEFT_SIDE ? wallSidePoints [0] : wallSidePoints [wallSidePoints.length - 1]; Shape wallShape = getShape(wallSidePoints); Area wallArea = new Area(wallShape); float wallElevation = getWallElevation(); float wallHeightAtStart = getWallHeightAtStart(); float wallHeightAtEnd = getWallHeightAtEnd(); float maxWallHeight = Math.max(wallHeightAtStart, wallHeightAtEnd); // Compute wall angles and top line factors Wall wall = (Wall)getUserData(); double wallYawAngle = Math.atan2(wall.getYEnd() - wall.getYStart(), wall.getXEnd() - wall.getXStart()); double cosWallYawAngle = Math.cos(wallYawAngle); double sinWallYawAngle = Math.sin(wallYawAngle); double wallXStartWithZeroYaw = cosWallYawAngle * wall.getXStart() + sinWallYawAngle * wall.getYStart(); double wallXEndWithZeroYaw = cosWallYawAngle * wall.getXEnd() + sinWallYawAngle * wall.getYEnd(); boolean roundWall = wall.getArcExtent() != null && wall.getArcExtent() != 0; double topLineAlpha; double topLineBeta; if (wallHeightAtStart == wallHeightAtEnd) { topLineAlpha = 0; topLineBeta = wallHeightAtStart; } else { topLineAlpha = (wallHeightAtEnd - wallHeightAtStart) / (wallXEndWithZeroYaw - wallXStartWithZeroYaw); topLineBeta = wallHeightAtStart - topLineAlpha * wallXStartWithZeroYaw; } // Search which doors or windows intersect with this wall side List<DoorOrWindowArea> windowIntersections = new ArrayList<DoorOrWindowArea>(); for (HomePieceOfFurniture piece : getVisibleDoorsAndWindows(this.home.getFurniture())) { float pieceElevation = piece.getGroundElevation(); if (pieceElevation + piece.getHeight() > wallElevation && pieceElevation < maxWallHeight) { Shape pieceShape = getShape(piece.getPoints()); Area pieceArea = new Area(pieceShape); Area intersectionArea = new Area(wallShape); intersectionArea.intersect(pieceArea); if (!intersectionArea.isEmpty()) { windowIntersections.add(new DoorOrWindowArea(intersectionArea, Arrays.asList(new HomePieceOfFurniture [] {piece}))); // Remove from wall area the piece shape wallArea.subtract(pieceArea); } } } // Refine intersections in case some doors or windows are superimposed if (windowIntersections.size() > 1) { // Search superimposed windows for (int windowIndex = 0; windowIndex < windowIntersections.size(); windowIndex++) { DoorOrWindowArea windowIntersection = windowIntersections.get(windowIndex); List<DoorOrWindowArea> otherWindowIntersections = new ArrayList<DoorOrWindowArea>(); int otherWindowIndex = 0; for (DoorOrWindowArea otherWindowIntersection : windowIntersections) { if (windowIntersection.getArea().isEmpty()) { break; } else if (otherWindowIndex > windowIndex) { // Avoid search twice the intersection between two items Area windowsIntersectionArea = new Area(otherWindowIntersection.getArea()); windowsIntersectionArea.intersect(windowIntersection.getArea()); if (!windowsIntersectionArea.isEmpty()) { // Remove intersection from wall area otherWindowIntersection.getArea().subtract(windowsIntersectionArea); windowIntersection.getArea().subtract(windowsIntersectionArea); // Create a new area for the intersection List<HomePieceOfFurniture> doorsOrWindows = new ArrayList<HomePieceOfFurniture>(windowIntersection.getDoorsOrWindows()); doorsOrWindows.addAll(otherWindowIntersection.getDoorsOrWindows()); otherWindowIntersections.add(new DoorOrWindowArea(windowsIntersectionArea, doorsOrWindows)); } } otherWindowIndex++; } windowIntersections.addAll(otherWindowIntersections); } } List<float[]> wallPoints = new ArrayList<float[]>(4); // Generate geometry for each wall part that doesn't contain a window float [] previousWallPoint = null; for (PathIterator it = wallArea.getPathIterator(null); !it.isDone(); ) { float [] wallPoint = new float[2]; if (it.currentSegment(wallPoint) == PathIterator.SEG_CLOSE) { if (wallPoints.size() > 2) { // Remove last point if it's equal to first point if (Arrays.equals(wallPoints.get(0), wallPoints.get(wallPoints.size() - 1))) { wallPoints.remove(wallPoints.size() - 1); } if (wallPoints.size() > 2) { float [][] wallPartPoints = wallPoints.toArray(new float[wallPoints.size()][]); // Compute geometry for vertical part wallGeometries.add(createWallVerticalPartGeometry(wall, wallPartPoints, wallElevation, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, texture, textureReferencePoint, wallSide)); // Compute geometry for bottom part wallBottomGeometries.add(createWallHorizontalPartGeometry(wallPartPoints, wallElevation, true, roundWall)); // Compute geometry for top part wallTopGeometries.add(createWallTopPartGeometry(wallPartPoints, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, roundWall)); } } wallPoints.clear(); previousWallPoint = null; } else if (previousWallPoint == null || !Arrays.equals(wallPoint, previousWallPoint)) { wallPoints.add(wallPoint); previousWallPoint = wallPoint; } it.next(); } // Generate geometry for each wall part above and below a window Level level = wall.getLevel(); previousWallPoint = null; for (DoorOrWindowArea windowIntersection : windowIntersections) { if (!windowIntersection.getArea().isEmpty()) { for (PathIterator it = windowIntersection.getArea().getPathIterator(null); !it.isDone(); ) { float [] wallPoint = new float[2]; if (it.currentSegment(wallPoint) == PathIterator.SEG_CLOSE) { // Remove last point if it's equal to first point if (Arrays.equals(wallPoints.get(0), wallPoints.get(wallPoints.size() - 1))) { wallPoints.remove(wallPoints.size() - 1); } if (wallPoints.size() > 2) { float [][] wallPartPoints = wallPoints.toArray(new float[wallPoints.size()][]); List<HomePieceOfFurniture> doorsOrWindows = windowIntersection.getDoorsOrWindows(); if (doorsOrWindows.size() > 1) { // Sort superimposed doors and windows by elevation and height Collections.sort(doorsOrWindows, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { float piece1Elevation = piece1.getGroundElevation(); float piece2Elevation = piece2.getGroundElevation(); if (piece1Elevation < piece2Elevation) { return -1; } else if (piece1Elevation > piece2Elevation) { return 1; } else { return 0; } } }); } HomePieceOfFurniture lowestDoorOrWindow = doorsOrWindows.get(0); float lowestDoorOrWindowElevation = lowestDoorOrWindow.getGroundElevation(); // Generate geometry for wall part below window if (lowestDoorOrWindowElevation > wallElevation) { if (level != null && level.getElevation() != wallElevation && lowestDoorOrWindow.getElevation() < LEVEL_ELEVATION_SHIFT) { // Give more chance to an overlapping room floor to be displayed lowestDoorOrWindowElevation -= LEVEL_ELEVATION_SHIFT; } wallGeometries.add(createWallVerticalPartGeometry(wall, wallPartPoints, wallElevation, cosWallYawAngle, sinWallYawAngle, 0, lowestDoorOrWindowElevation, texture, textureReferencePoint, wallSide)); wallBottomGeometries.add(createWallHorizontalPartGeometry(wallPartPoints, wallElevation, true, roundWall)); wallGeometries.add(createWallHorizontalPartGeometry(wallPartPoints, lowestDoorOrWindowElevation, false, roundWall)); } // Generate geometry for wall parts between superimposed windows for (int i = 0; i < doorsOrWindows.size() - 1; ) { HomePieceOfFurniture lowerDoorOrWindow = doorsOrWindows.get(i); float lowerDoorOrWindowElevation = lowerDoorOrWindow.getGroundElevation(); HomePieceOfFurniture higherDoorOrWindow = doorsOrWindows.get(++i); float higherDoorOrWindowElevation = higherDoorOrWindow.getGroundElevation(); // Ignore higher windows smaller than lower window while (lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight() >= higherDoorOrWindowElevation + higherDoorOrWindow.getHeight() && ++i < doorsOrWindows.size()) { higherDoorOrWindow = doorsOrWindows.get(i); } if (i < doorsOrWindows.size() && lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight() < higherDoorOrWindowElevation) { wallGeometries.add(createWallVerticalPartGeometry(wall, wallPartPoints, lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight(), cosWallYawAngle, sinWallYawAngle, 0, higherDoorOrWindowElevation, texture, textureReferencePoint, wallSide)); wallGeometries.add(createWallHorizontalPartGeometry(wallPartPoints, lowerDoorOrWindowElevation + lowerDoorOrWindow.getHeight(), true, roundWall)); wallGeometries.add(createWallHorizontalPartGeometry(wallPartPoints, higherDoorOrWindowElevation, false, roundWall)); } } HomePieceOfFurniture highestDoorOrWindow = doorsOrWindows.get(doorsOrWindows.size() - 1); float highestDoorOrWindowElevation = highestDoorOrWindow.getGroundElevation(); for (int i = doorsOrWindows.size() - 2; i >= 0; i--) { HomePieceOfFurniture doorOrWindow = doorsOrWindows.get(i); if (doorOrWindow.getGroundElevation() + doorOrWindow.getHeight() > highestDoorOrWindowElevation + highestDoorOrWindow.getHeight()) { highestDoorOrWindow = doorOrWindow; } } float doorOrWindowTop = highestDoorOrWindowElevation + highestDoorOrWindow.getHeight(); // Compute the minimum vertical position of wallPartPoints double minTopY = maxWallHeight; for (int i = 0; i < wallPartPoints.length; i++) { double xTopPointWithZeroYaw = cosWallYawAngle * wallPartPoints[i][0] + sinWallYawAngle * wallPartPoints[i][1]; minTopY = Math.min(minTopY, topLineAlpha * xTopPointWithZeroYaw + topLineBeta); } // Generate geometry for wall part above window if (doorOrWindowTop < minTopY) { wallGeometries.add(createWallVerticalPartGeometry(wall, wallPartPoints, doorOrWindowTop, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, texture, textureReferencePoint, wallSide)); wallGeometries.add(createWallHorizontalPartGeometry( wallPartPoints, doorOrWindowTop, true, roundWall)); wallTopGeometries.add(createWallTopPartGeometry(wallPartPoints, cosWallYawAngle, sinWallYawAngle, topLineAlpha, topLineBeta, roundWall)); } } wallPoints.clear(); previousWallPoint = null; } else if (previousWallPoint == null || !Arrays.equals(wallPoint, previousWallPoint)) { wallPoints.add(wallPoint); previousWallPoint = wallPoint; } it.next(); } } } } /** * Returns all the visible doors and windows in the given <code>furniture</code>. */ private List<HomePieceOfFurniture> getVisibleDoorsAndWindows(List<HomePieceOfFurniture> furniture) { List<HomePieceOfFurniture> visibleDoorsAndWindows = new ArrayList<HomePieceOfFurniture>(furniture.size()); for (HomePieceOfFurniture piece : furniture) { if (piece.isVisible()) { if (piece instanceof HomeFurnitureGroup) { visibleDoorsAndWindows.addAll(getVisibleDoorsAndWindows(((HomeFurnitureGroup)piece).getFurniture())); } else if (piece.isDoorOrWindow()) { visibleDoorsAndWindows.add(piece); } } } return visibleDoorsAndWindows; } /** * Returns the points of one of the side of this wall. */ private float [][] getWallSidePoints(int wallSide) { Wall wall = (Wall)getUserData(); float [][] wallPoints = wall.getPoints(); if (wallSide == WALL_LEFT_SIDE) { for (int i = wallPoints.length / 2; i < wallPoints.length; i++) { wallPoints [i][0] = (wallPoints [i][0] + wallPoints [wallPoints.length - i - 1][0]) / 2; wallPoints [i][1] = (wallPoints [i][1] + wallPoints [wallPoints.length - i - 1][1]) / 2; } } else { // WALL_RIGHT_SIDE for (int i = 0, n = wallPoints.length / 2; i < n; i++) { wallPoints [i][0] = (wallPoints [i][0] + wallPoints [wallPoints.length - i - 1][0]) / 2; wallPoints [i][1] = (wallPoints [i][1] + wallPoints [wallPoints.length - i - 1][1]) / 2; } } return wallPoints; } /** * Returns the vertical rectangles that join each point of <code>points</code> * and spread from <code>yMin</code> to a top line (y = ax + b) described by <code>topLineAlpha</code> * and <code>topLineBeta</code> factors in a vertical plan that is rotated around * vertical axis matching <code>cosWallYawAngle</code> and <code>sinWallYawAngle</code>. */ private Geometry createWallVerticalPartGeometry(Wall wall, float [][] points, float yMin, double cosWallYawAngle, double sinWallYawAngle, double topLineAlpha, double topLineBeta, HomeTexture texture, float [] textureReferencePoint, int wallSide) { final float subpartSize = this.home.getEnvironment().getSubpartSizeUnderLight(); if (wall.getArcExtent() == null && subpartSize > 0) { // Subdivide points in smaller parts to ensure a smoother effect with point lights List<float []> pointsList = new ArrayList<float[]>(points.length * 2); pointsList.add(points [0]); for (int i = 1; i < points.length; i++) { double distance = Point2D.distance(points [i - 1][0], points [i - 1][1], points [i][0], points [i][1]) - subpartSize / 2; double angle = Math.atan2(points [i][1] - points [i - 1][1], points [i][0] - points [i - 1][0]); double cosAngle = Math.cos(angle); double sinAngle = Math.sin(angle); for (double d = 0; d < distance; d += subpartSize) { pointsList.add(new float [] {(float)(points [i - 1][0] + d * cosAngle), (float)(points [i - 1][1] + d * sinAngle)}); } pointsList.add(points [i]); } points = pointsList.toArray(new float [pointsList.size()][]); } // Compute wall coordinates Point3f [] bottom = new Point3f [points.length]; Point3f [] top = new Point3f [points.length]; double [] distanceSqToWallMiddle = new double [points.length]; Float [] pointUCoordinates = new Float [points.length]; float xStart = wall.getXStart(); float yStart = wall.getYStart(); float xEnd = wall.getXEnd(); float yEnd = wall.getYEnd(); Float arcExtent = wall.getArcExtent(); float [] arcCircleCenter = null; float arcCircleRadius = 0; float referencePointAngle = 0; if (arcExtent != null && arcExtent != 0) { arcCircleCenter = new float [] {wall.getXArcCircleCenter(), wall.getYArcCircleCenter()}; arcCircleRadius = (float)Point2D.distance(arcCircleCenter [0], arcCircleCenter [1], xStart, yStart); referencePointAngle = (float)Math.atan2(textureReferencePoint [1] - arcCircleCenter [1], textureReferencePoint [0] - arcCircleCenter [0]); } for (int i = 0; i < points.length; i++) { bottom [i] = new Point3f(points [i][0], yMin, points [i][1]); if (arcCircleCenter == null) { distanceSqToWallMiddle [i] = Line2D.ptLineDistSq(xStart, yStart, xEnd, yEnd, bottom [i].x, bottom [i].z); } else { distanceSqToWallMiddle [i] = arcCircleRadius - Point2D.distance(arcCircleCenter [0], arcCircleCenter [1], bottom [i].x, bottom [i].z); distanceSqToWallMiddle [i] *= distanceSqToWallMiddle [i]; } // Compute vertical top point double xTopPointWithZeroYaw = cosWallYawAngle * points [i][0] + sinWallYawAngle * points [i][1]; float topY = (float)(topLineAlpha * xTopPointWithZeroYaw + topLineBeta); top [i] = new Point3f(points [i][0], topY, points [i][1]); } // Search which rectangles should be ignored int rectanglesCount = 0; boolean [] usedRectangle = new boolean [points.length]; for (int i = 0; i < points.length - 1; i++) { usedRectangle [i] = distanceSqToWallMiddle [i] > 0.001f || distanceSqToWallMiddle [i + 1] > 0.001f; if (usedRectangle [i]) { rectanglesCount++; } } usedRectangle [usedRectangle.length - 1] = distanceSqToWallMiddle [0] > 0.001f || distanceSqToWallMiddle [points.length - 1] > 0.001f; if (usedRectangle [usedRectangle.length - 1]) { rectanglesCount++; } if (rectanglesCount == 0) { return null; } List<Point3f> coords = new ArrayList<Point3f> (rectanglesCount * 4); for (int index = 0; index < points.length; index++) { if (usedRectangle [index]) { float y = yMin; Point3f point1 = bottom [index]; int nextIndex = (index + 1) % points.length; Point3f point2 = bottom [nextIndex]; if (subpartSize > 0) { for (float yMax = Math.min(top [index].y, top [nextIndex].y) - subpartSize / 2; y < yMax; y += subpartSize) { coords.add(point1); coords.add(point2); point1 = new Point3f(bottom [index].x, y, bottom [index].z); point2 = new Point3f(bottom [nextIndex].x, y, bottom [nextIndex].z); coords.add(point2); coords.add(point1); } } coords.add(point1); coords.add(point2); coords.add(top [nextIndex]); coords.add(top [index]); } } GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.QUAD_ARRAY); geometryInfo.setCoordinates(coords.toArray(new Point3f [coords.size()])); // Compute wall texture coordinates if (texture != null) { float halfThicknessSq = (wall.getThickness() * wall.getThickness()) / 4; TexCoord2f [] textureCoords = new TexCoord2f [coords.size()]; float yMinTextureCoords = yMin / texture.getHeight(); TexCoord2f firstTextureCoords = new TexCoord2f(0, yMinTextureCoords); int j = 0; // Tolerate more error with round walls since arc points are approximative float epsilon = arcCircleCenter == null ? 0.0001f : halfThicknessSq / 4; for (int index = 0; index < points.length; index++) { if (usedRectangle [index]) { int nextIndex = (index + 1) % points.length; TexCoord2f textureCoords1; TexCoord2f textureCoords2; if (Math.abs(distanceSqToWallMiddle [index] - halfThicknessSq) < epsilon && Math.abs(distanceSqToWallMiddle [nextIndex] - halfThicknessSq) < epsilon) { // Compute texture coordinates of wall part parallel to wall middle // according to textureReferencePoint float firstHorizontalTextureCoords; float secondHorizontalTextureCoords; if (arcCircleCenter == null) { firstHorizontalTextureCoords = (float)Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], points [index][0], points [index][1]) / texture.getWidth(); secondHorizontalTextureCoords = (float)Point2D.distance(textureReferencePoint[0], textureReferencePoint[1], points [nextIndex][0], points [nextIndex][1]) / texture.getWidth(); } else { if (pointUCoordinates [index] == null) { float pointAngle = (float)Math.atan2(points [index][1] - arcCircleCenter [1], points [index][0] - arcCircleCenter [0]); pointAngle = adjustAngleOnReferencePointAngle(pointAngle, referencePointAngle, arcExtent); pointUCoordinates [index] = (pointAngle - referencePointAngle) * arcCircleRadius / texture.getWidth(); } if (pointUCoordinates [nextIndex] == null) { float pointAngle = (float)Math.atan2(points [nextIndex][1] - arcCircleCenter [1], points [nextIndex][0] - arcCircleCenter [0]); pointAngle = adjustAngleOnReferencePointAngle(pointAngle, referencePointAngle, arcExtent); pointUCoordinates [nextIndex] = (pointAngle - referencePointAngle) * arcCircleRadius / texture.getWidth(); } firstHorizontalTextureCoords = pointUCoordinates [index]; secondHorizontalTextureCoords = pointUCoordinates [nextIndex]; } if (wallSide == WALL_LEFT_SIDE && texture.isLeftToRightOriented()) { firstHorizontalTextureCoords = -firstHorizontalTextureCoords; secondHorizontalTextureCoords = -secondHorizontalTextureCoords; } textureCoords1 = new TexCoord2f(firstHorizontalTextureCoords, yMinTextureCoords); textureCoords2 = new TexCoord2f(secondHorizontalTextureCoords, yMinTextureCoords); } else { textureCoords1 = firstTextureCoords; float horizontalTextureCoords = (float)Point2D.distance(points [index][0], points [index][1], points [nextIndex][0], points [nextIndex][1]) / texture.getWidth(); textureCoords2 = new TexCoord2f(horizontalTextureCoords, yMinTextureCoords); } if (subpartSize > 0) { float y = yMin; for (float yMax = Math.min(top [index].y, top [nextIndex].y) - subpartSize / 2; y < yMax; y += subpartSize) { textureCoords [j++] = textureCoords1; textureCoords [j++] = textureCoords2; float yTextureCoords = y / texture.getHeight(); textureCoords1 = new TexCoord2f(textureCoords1.x, yTextureCoords); textureCoords2 = new TexCoord2f(textureCoords2.x, yTextureCoords); textureCoords [j++] = textureCoords2; textureCoords [j++] = textureCoords1; } } textureCoords [j++] = textureCoords1; textureCoords [j++] = textureCoords2; textureCoords [j++] = new TexCoord2f(textureCoords2.x, top [nextIndex].y / texture.getHeight()); textureCoords [j++] = new TexCoord2f(textureCoords1.x, top [index].y / texture.getHeight()); } } geometryInfo.setTextureCoordinateParams(1, 2); geometryInfo.setTextureCoordinates(0, textureCoords); } // Generate normals NormalGenerator normalGenerator = new NormalGenerator(); if (arcCircleCenter == null) { normalGenerator.setCreaseAngle(0); } normalGenerator.generateNormals(geometryInfo); return geometryInfo.getIndexedGeometryArray(); } /** * Returns <code>pointAngle</code> plus or minus 2 PI to ensure <code>pointAngle</code> value * will be greater or lower than <code>referencePointAngle</code> depending on <code>arcExtent</code> direction. */ private float adjustAngleOnReferencePointAngle(float pointAngle, float referencePointAngle, float arcExtent) { if (arcExtent > 0) { if ((referencePointAngle > 0 && (pointAngle < 0 || referencePointAngle > pointAngle)) || (referencePointAngle < 0 && pointAngle < 0 && referencePointAngle > pointAngle)) { pointAngle += 2 * (float)Math.PI; } } else { if ((referencePointAngle < 0 && (pointAngle > 0 || referencePointAngle < pointAngle)) || (referencePointAngle > 0 && pointAngle > 0 && referencePointAngle < pointAngle)) { pointAngle -= 2 * (float)Math.PI; } } return pointAngle; } /** * Returns the geometry of an horizontal part of a wall at <code>y</code>. */ private Geometry createWallHorizontalPartGeometry(float [][] points, float y, boolean reverseOrder, boolean roundWall) { Point3f [] coords = new Point3f [points.length]; for (int i = 0; i < points.length; i++) { coords [i] = new Point3f(points [i][0], y, points [i][1]); } GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); geometryInfo.setCoordinates (coords); geometryInfo.setStripCounts(new int [] {coords.length}); if (reverseOrder) { geometryInfo.reverse(); } // Generate normals NormalGenerator normalGenerator = new NormalGenerator(); if (roundWall) { normalGenerator.setCreaseAngle(0); } normalGenerator.generateNormals(geometryInfo); return geometryInfo.getIndexedGeometryArray (); } /** * Returns the geometry of the top part of a wall. */ private Geometry createWallTopPartGeometry(float [][] points, double cosWallYawAngle, double sinWallYawAngle, double topLineAlpha, double topLineBeta, boolean roundWall) { Point3f [] coords = new Point3f [points.length]; for (int i = 0; i < points.length; i++) { double xTopPointWithZeroYaw = cosWallYawAngle * points [i][0] + sinWallYawAngle * points [i][1]; float topY = (float)(topLineAlpha * xTopPointWithZeroYaw + topLineBeta); coords [i] = new Point3f(points [i][0], topY, points [i][1]); } GeometryInfo geometryInfo = new GeometryInfo(GeometryInfo.POLYGON_ARRAY); geometryInfo.setCoordinates (coords); geometryInfo.setStripCounts(new int [] {coords.length}); // Generate normals NormalGenerator normalGenerator = new NormalGenerator(); if (roundWall) { normalGenerator.setCreaseAngle(0); } normalGenerator.generateNormals(geometryInfo); return geometryInfo.getIndexedGeometryArray (); } /** * Returns the elevation of the wall managed by this 3D object. */ private float getWallElevation() { Wall wall = (Wall)getUserData(); Level level = wall.getLevel(); if (level == null) { return 0; } else { float floorThicknessBottomWall = getFloorThicknessBottomWall(); if (floorThicknessBottomWall > 0) { // Shift a little wall elevation at upper floors to avoid their bottom part overlaps a room ceiling floorThicknessBottomWall -= LEVEL_ELEVATION_SHIFT; } return level.getElevation() - floorThicknessBottomWall; } } /** * Returns the floor thickness at the bottom of the wall managed by this 3D object. */ private float getFloorThicknessBottomWall() { Wall wall = (Wall)getUserData(); Level level = wall.getLevel(); if (level == null) { return 0; } else { List<Level> levels = this.home.getLevels(); if (!levels.isEmpty() && levels.get(0).getElevation() == level.getElevation()) { // Ignore floor thickness at first level return 0; } else { return level.getFloorThickness(); } } } /** * Returns the height at the start of the wall managed by this 3D object. */ private float getWallHeightAtStart() { Float wallHeight = ((Wall)getUserData()).getHeight(); float wallHeightAtStart; if (wallHeight != null) { wallHeightAtStart = wallHeight + getWallElevation() + getFloorThicknessBottomWall(); } else { // If wall height isn't set, use home wall height wallHeightAtStart = this.home.getWallHeight() + getWallElevation() + getFloorThicknessBottomWall(); } return wallHeightAtStart + getHeightElevationShift(); } private float getHeightElevationShift() { Level level = ((Wall)getUserData()).getLevel(); if (level != null) { List<Level> levels = this.home.getLevels(); // Don't shift last level if (levels.get(levels.size() - 1) != level) { return LEVEL_ELEVATION_SHIFT; } } return 0; } /** * Returns the height at the end of the wall managed by this 3D object. */ private float getWallHeightAtEnd() { Wall wall = (Wall)getUserData(); if (wall.isTrapezoidal()) { return wall.getHeightAtEnd() + getWallElevation() + getFloorThicknessBottomWall() + getHeightElevationShift(); } else { // If the wall isn't trapezoidal, use same height as at wall start return getWallHeightAtStart(); } } /** * Sets wall appearance with its color, texture and transparency. */ private void updateWallAppearance(boolean waitTextureLoadingEnd) { Wall wall = (Wall)getUserData(); Integer wallsTopColor = wall.getTopColor(); Group [] wallLeftSideGroups = {(Group)getChild(0), // Bottom group (Group)getChild(1), // Main group (Group)getChild(2)}; // Top group Group [] wallRightSideGroups = {(Group)getChild(3), // Bottom group (Group)getChild(4), // Main group (Group)getChild(5)}; // Top group for (int i = 0; i < wallLeftSideGroups.length; i++) { if (i % 3 != 2 || wallsTopColor == null) { updateFilledWallSideAppearance(((Shape3D)wallLeftSideGroups [i].getChild(0)).getAppearance(), wall.getLeftSideTexture(), waitTextureLoadingEnd, wall.getLeftSideColor(), wall.getLeftSideShininess()); updateFilledWallSideAppearance(((Shape3D)wallRightSideGroups [i].getChild(0)).getAppearance(), wall.getRightSideTexture(), waitTextureLoadingEnd, wall.getRightSideColor(), wall.getRightSideShininess()); } else { // Fill walls top with the color set in home environment updateFilledWallSideAppearance(((Shape3D)wallLeftSideGroups [i].getChild(0)).getAppearance(), null, waitTextureLoadingEnd, wallsTopColor, 0); updateFilledWallSideAppearance(((Shape3D)wallRightSideGroups [i].getChild(0)).getAppearance(), null, waitTextureLoadingEnd, wallsTopColor, 0); } if (wallLeftSideGroups [i].numChildren() > 1) { updateOutlineWallSideAppearance(((Shape3D)wallLeftSideGroups [i].getChild(1)).getAppearance()); updateOutlineWallSideAppearance(((Shape3D)wallRightSideGroups [i].getChild(1)).getAppearance()); } } } /** * Sets filled wall side appearance with its color, texture, transparency and visibility. */ private void updateFilledWallSideAppearance(final Appearance wallSideAppearance, final HomeTexture wallSideTexture, boolean waitTextureLoadingEnd, Integer wallSideColor, float shininess) { if (wallSideTexture == null) { wallSideAppearance.setMaterial(getMaterial(wallSideColor, wallSideColor, shininess)); wallSideAppearance.setTexture(null); } else { // Update material and texture of wall side wallSideAppearance.setMaterial(getMaterial(DEFAULT_COLOR, DEFAULT_AMBIENT_COLOR, shininess)); final TextureManager textureManager = TextureManager.getInstance(); textureManager.loadTexture(wallSideTexture.getImage(), waitTextureLoadingEnd, new TextureManager.TextureObserver() { public void textureUpdated(Texture texture) { wallSideAppearance.setTexture(texture); } }); } // Update wall side transparency float wallsAlpha = this.home.getEnvironment().getWallsAlpha(); TransparencyAttributes transparencyAttributes = wallSideAppearance.getTransparencyAttributes(); transparencyAttributes.setTransparency(wallsAlpha); // If walls alpha is equal to zero, turn off transparency to get better results transparencyAttributes.setTransparencyMode(wallsAlpha == 0 ? TransparencyAttributes.NONE : TransparencyAttributes.NICEST); // Update wall side visibility RenderingAttributes renderingAttributes = wallSideAppearance.getRenderingAttributes(); HomeEnvironment.DrawingMode drawingMode = this.home.getEnvironment().getDrawingMode(); renderingAttributes.setVisible(drawingMode == null || drawingMode == HomeEnvironment.DrawingMode.FILL || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE); } /** * Sets outline wall side visibility. */ private void updateOutlineWallSideAppearance(final Appearance wallSideAppearance) { // Update wall side visibility RenderingAttributes renderingAttributes = wallSideAppearance.getRenderingAttributes(); HomeEnvironment.DrawingMode drawingMode = this.home.getEnvironment().getDrawingMode(); renderingAttributes.setVisible(drawingMode == HomeEnvironment.DrawingMode.OUTLINE || drawingMode == HomeEnvironment.DrawingMode.FILL_AND_OUTLINE); } /** * An area used to compute holes in walls. */ private static class DoorOrWindowArea { private final Area area; private final List<HomePieceOfFurniture> doorsOrWindows; public DoorOrWindowArea(Area area, List<HomePieceOfFurniture> doorsOrWindows) { this.area = area; this.doorsOrWindows = doorsOrWindows; } public Area getArea() { return this.area; } public List<HomePieceOfFurniture> getDoorsOrWindows() { return this.doorsOrWindows; } } }