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 }