1 /* 2 * HomePieceOfFurniture.java 15 mai 2006 3 * 4 * Sweet Home 3D, Copyright (c) 2006 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.model; 21 22 import java.awt.Shape; 23 import java.awt.geom.AffineTransform; 24 import java.awt.geom.GeneralPath; 25 import java.awt.geom.PathIterator; 26 import java.awt.geom.Point2D; 27 import java.awt.geom.Rectangle2D; 28 import java.io.IOException; 29 import java.io.ObjectInputStream; 30 import java.math.BigDecimal; 31 import java.math.RoundingMode; 32 import java.text.Collator; 33 import java.util.Arrays; 34 import java.util.Comparator; 35 import java.util.HashMap; 36 import java.util.Map; 37 38 /** 39 * A piece of furniture in {@linkplain Home home}. 40 * @author Emmanuel Puybaret 41 */ 42 public class HomePieceOfFurniture extends HomeObject implements PieceOfFurniture, Selectable, Elevatable { 43 private static final long serialVersionUID = 1L; 44 45 private static final double TWICE_PI = 2 * Math.PI; 46 private static final double STRAIGHT_WALL_ANGLE_MARGIN = Math.toRadians(1); 47 private static final double ROUND_WALL_ANGLE_MARGIN = Math.toRadians(10); 48 49 /** 50 * The properties of a piece of furniture that may change. <code>PropertyChangeListener</code>s added 51 * to a piece of furniture will be notified under a property name equal to the string value of one these properties. 52 */ 53 public enum Property {CATALOG_ID, NAME, NAME_VISIBLE, NAME_X_OFFSET, NAME_Y_OFFSET, NAME_STYLE, NAME_ANGLE, 54 DESCRIPTION, INFORMATION, PRICE, VALUE_ADDED_TAX_PERCENTAGE, CURRENCY, ICON, PLAN_ICON, MODEL, 55 WIDTH, WIDTH_IN_PLAN, DEPTH, DEPTH_IN_PLAN, HEIGHT, HEIGHT_IN_PLAN, 56 COLOR, TEXTURE, MODEL_MATERIALS, MODEL_TRANSFORMATIONS, 57 STAIRCASE_CUT_OUT_SHAPE, CREATOR, SHININESS, VISIBLE, 58 X, Y, ELEVATION, ANGLE, PITCH, ROLL, MODEL_ROTATION, MODEL_MIRRORED, BACK_FACE_SHOWN, MOVABLE, LEVEL}; 59 60 /** 61 * The properties on which home furniture may be sorted. 62 */ 63 public enum SortableProperty {CATALOG_ID, NAME, WIDTH, DEPTH, HEIGHT, MOVABLE, 64 DOOR_OR_WINDOW, COLOR, TEXTURE, VISIBLE, X, Y, ELEVATION, ANGLE, MODEL_SIZE, CREATOR, 65 PRICE, VALUE_ADDED_TAX, VALUE_ADDED_TAX_PERCENTAGE, PRICE_VALUE_ADDED_TAX_INCLUDED, LEVEL}; 66 private static final Map<SortableProperty, Comparator<HomePieceOfFurniture>> SORTABLE_PROPERTY_COMPARATORS; 67 68 static { 69 final Collator collator = Collator.getInstance(); 70 // Init piece property comparators 71 SORTABLE_PROPERTY_COMPARATORS = new HashMap<SortableProperty, Comparator<HomePieceOfFurniture>>(); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.CATALOG_ID, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { if (piece1.catalogId == piece2.catalogId) { return 0; } else if (piece1.catalogId == null) { return -1; } else if (piece2.catalogId == null) { return 1; } else { return collator.compare(piece1.catalogId, piece2.catalogId); } } })72 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.CATALOG_ID, new Comparator<HomePieceOfFurniture>() { 73 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 74 if (piece1.catalogId == piece2.catalogId) { 75 return 0; 76 } else if (piece1.catalogId == null) { 77 return -1; 78 } else if (piece2.catalogId == null) { 79 return 1; 80 } else { 81 return collator.compare(piece1.catalogId, piece2.catalogId); 82 } 83 } 84 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.NAME, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { if (piece1.name == piece2.name) { return 0; } else if (piece1.name == null) { return -1; } else if (piece2.name == null) { return 1; } else { return collator.compare(piece1.name, piece2.name); } } })85 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.NAME, new Comparator<HomePieceOfFurniture>() { 86 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 87 if (piece1.name == piece2.name) { 88 return 0; 89 } else if (piece1.name == null) { 90 return -1; 91 } else if (piece2.name == null) { 92 return 1; 93 } else { 94 return collator.compare(piece1.name, piece2.name); 95 } 96 } 97 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.WIDTH, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.width, piece2.width); } })98 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.WIDTH, new Comparator<HomePieceOfFurniture>() { 99 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 100 return HomePieceOfFurniture.compare(piece1.width, piece2.width); 101 } 102 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.HEIGHT, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.height, piece2.height); } })103 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.HEIGHT, new Comparator<HomePieceOfFurniture>() { 104 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 105 return HomePieceOfFurniture.compare(piece1.height, piece2.height); 106 } 107 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.DEPTH, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.depth, piece2.depth); } })108 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.DEPTH, new Comparator<HomePieceOfFurniture>() { 109 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 110 return HomePieceOfFurniture.compare(piece1.depth, piece2.depth); 111 } 112 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.MOVABLE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.movable, piece2.movable); } })113 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.MOVABLE, new Comparator<HomePieceOfFurniture>() { 114 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 115 return HomePieceOfFurniture.compare(piece1.movable, piece2.movable); 116 } 117 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.DOOR_OR_WINDOW, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.doorOrWindow, piece2.doorOrWindow); } })118 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.DOOR_OR_WINDOW, new Comparator<HomePieceOfFurniture>() { 119 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 120 return HomePieceOfFurniture.compare(piece1.doorOrWindow, piece2.doorOrWindow); 121 } 122 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.COLOR, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { if (piece1.color == piece2.color) { return 0; } else if (piece1.color == null) { return -1; } else if (piece2.color == null) { return 1; } else { return piece1.color - piece2.color; } } })123 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.COLOR, new Comparator<HomePieceOfFurniture>() { 124 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 125 if (piece1.color == piece2.color) { 126 return 0; 127 } else if (piece1.color == null) { 128 return -1; 129 } else if (piece2.color == null) { 130 return 1; 131 } else { 132 return piece1.color - piece2.color; 133 } 134 } 135 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.TEXTURE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { if (piece1.texture == piece2.texture) { return 0; } else if (piece1.texture == null) { return -1; } else if (piece2.texture == null) { return 1; } else { return collator.compare(piece1.texture.getName(), piece2.texture.getName()); } } })136 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.TEXTURE, new Comparator<HomePieceOfFurniture>() { 137 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 138 if (piece1.texture == piece2.texture) { 139 return 0; 140 } else if (piece1.texture == null) { 141 return -1; 142 } else if (piece2.texture == null) { 143 return 1; 144 } else { 145 return collator.compare(piece1.texture.getName(), piece2.texture.getName()); 146 } 147 } 148 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VISIBLE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.visible, piece2.visible); } })149 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VISIBLE, new Comparator<HomePieceOfFurniture>() { 150 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 151 return HomePieceOfFurniture.compare(piece1.visible, piece2.visible); 152 } 153 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.X, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.x, piece2.x); } })154 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.X, new Comparator<HomePieceOfFurniture>() { 155 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 156 return HomePieceOfFurniture.compare(piece1.x, piece2.x); 157 } 158 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.Y, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.y, piece2.y); } })159 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.Y, new Comparator<HomePieceOfFurniture>() { 160 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 161 return HomePieceOfFurniture.compare(piece1.y, piece2.y); 162 } 163 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.ELEVATION, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.elevation, piece2.elevation); } })164 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.ELEVATION, new Comparator<HomePieceOfFurniture>() { 165 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 166 return HomePieceOfFurniture.compare(piece1.elevation, piece2.elevation); 167 } 168 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.ANGLE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.angle, piece2.angle); } })169 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.ANGLE, new Comparator<HomePieceOfFurniture>() { 170 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 171 return HomePieceOfFurniture.compare(piece1.angle, piece2.angle); 172 } 173 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.MODEL_SIZE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { Long piece1ModelSize = HomePieceOfFurniture.getComparableModelSize(piece1); Long piece2ModelSize = HomePieceOfFurniture.getComparableModelSize(piece2); if (piece1ModelSize == piece2ModelSize) { return 0; } else if (piece1ModelSize == null) { return -1; } else if (piece2ModelSize == null) { return 1; } else { return piece1ModelSize < piece2ModelSize ? -1 : (piece1ModelSize.longValue() == piece2ModelSize.longValue() ? 0 : 1); } } })174 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.MODEL_SIZE, new Comparator<HomePieceOfFurniture>() { 175 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 176 Long piece1ModelSize = HomePieceOfFurniture.getComparableModelSize(piece1); 177 Long piece2ModelSize = HomePieceOfFurniture.getComparableModelSize(piece2); 178 if (piece1ModelSize == piece2ModelSize) { 179 return 0; 180 } else if (piece1ModelSize == null) { 181 return -1; 182 } else if (piece2ModelSize == null) { 183 return 1; 184 } else { 185 return piece1ModelSize < piece2ModelSize 186 ? -1 187 : (piece1ModelSize.longValue() == piece2ModelSize.longValue() ? 0 : 1); 188 } 189 } 190 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.CREATOR, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { if (piece1.creator == piece2.creator) { return 0; } else if (piece1.creator == null) { return -1; } else if (piece2.creator == null) { return 1; } else { return collator.compare(piece1.creator, piece2.creator); } } })191 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.CREATOR, new Comparator<HomePieceOfFurniture>() { 192 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 193 if (piece1.creator == piece2.creator) { 194 return 0; 195 } else if (piece1.creator == null) { 196 return -1; 197 } else if (piece2.creator == null) { 198 return 1; 199 } else { 200 return collator.compare(piece1.creator, piece2.creator); 201 } 202 } 203 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.LEVEL, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.getLevel(), piece2.getLevel()); } })204 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.LEVEL, new Comparator<HomePieceOfFurniture>() { 205 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 206 return HomePieceOfFurniture.compare(piece1.getLevel(), piece2.getLevel()); 207 } 208 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.PRICE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.price, piece2.price); } })209 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.PRICE, new Comparator<HomePieceOfFurniture>() { 210 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 211 return HomePieceOfFurniture.compare(piece1.price, piece2.price); 212 } 213 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VALUE_ADDED_TAX_PERCENTAGE, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.valueAddedTaxPercentage, piece2.valueAddedTaxPercentage); } })214 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VALUE_ADDED_TAX_PERCENTAGE, new Comparator<HomePieceOfFurniture>() { 215 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 216 return HomePieceOfFurniture.compare(piece1.valueAddedTaxPercentage, piece2.valueAddedTaxPercentage); 217 } 218 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VALUE_ADDED_TAX, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.getValueAddedTax(), piece2.getValueAddedTax()); } })219 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.VALUE_ADDED_TAX, new Comparator<HomePieceOfFurniture>() { 220 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 221 return HomePieceOfFurniture.compare(piece1.getValueAddedTax(), piece2.getValueAddedTax()); 222 } 223 }); SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.PRICE_VALUE_ADDED_TAX_INCLUDED, new Comparator<HomePieceOfFurniture>() { public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { return HomePieceOfFurniture.compare(piece1.getPriceValueAddedTaxIncluded(), piece2.getPriceValueAddedTaxIncluded()); } })224 SORTABLE_PROPERTY_COMPARATORS.put(SortableProperty.PRICE_VALUE_ADDED_TAX_INCLUDED, new Comparator<HomePieceOfFurniture>() { 225 public int compare(HomePieceOfFurniture piece1, HomePieceOfFurniture piece2) { 226 return HomePieceOfFurniture.compare(piece1.getPriceValueAddedTaxIncluded(), piece2.getPriceValueAddedTaxIncluded()); 227 } 228 }); 229 } 230 compare(float value1, float value2)231 private static int compare(float value1, float value2) { 232 return Float.compare(value1, value2); 233 } 234 compare(boolean value1, boolean value2)235 private static int compare(boolean value1, boolean value2) { 236 return value1 == value2 237 ? 0 238 : (value1 ? -1 : 1); 239 } 240 compare(BigDecimal value1, BigDecimal value2)241 private static int compare(BigDecimal value1, BigDecimal value2) { 242 if (value1 == value2) { 243 return 0; 244 } else if (value1 == null) { 245 return -1; 246 } else if (value2 == null) { 247 return 1; 248 } else { 249 return value1.compareTo(value2); 250 } 251 } 252 compare(Level level1, Level level2)253 private static int compare(Level level1, Level level2) { 254 if (level1 == level2) { 255 return 0; 256 } else if (level1 == null) { 257 return -1; 258 } else if (level2 == null) { 259 return 1; 260 } else { 261 return Float.compare(level1.getElevation(), level2.getElevation()); 262 } 263 } 264 getComparableModelSize(HomePieceOfFurniture piece)265 private static Long getComparableModelSize(HomePieceOfFurniture piece) { 266 if (piece instanceof HomeFurnitureGroup) { 267 Long biggestModelSize = null; 268 for (HomePieceOfFurniture childPiece : ((HomeFurnitureGroup)piece).getFurniture()) { 269 Long modelSize = getComparableModelSize(childPiece); 270 if (modelSize != null 271 && (biggestModelSize == null 272 || biggestModelSize.longValue() < modelSize.longValue())) { 273 biggestModelSize = modelSize; 274 } 275 } 276 return biggestModelSize; 277 } else { 278 return piece.modelSize; 279 } 280 } 281 282 private String catalogId; 283 private String name; 284 private boolean nameVisible; 285 private float nameXOffset; 286 private float nameYOffset; 287 private TextStyle nameStyle; 288 private float nameAngle; 289 private String description; 290 private String information; 291 private Content icon; 292 private Content planIcon; 293 private Content model; 294 private Long modelSize; 295 private float width; 296 private float widthInPlan; 297 private float depth; 298 private float depthInPlan; 299 private float height; 300 private float heightInPlan; 301 private float elevation; 302 private float dropOnTopElevation; 303 private boolean movable; 304 private boolean doorOrWindow; 305 private HomeMaterial [] modelMaterials; 306 private Integer color; 307 private HomeTexture texture; 308 private Float shininess; 309 private float [][] modelRotation; 310 private boolean modelCenteredAtOrigin; 311 private Transformation [] modelTransformations; 312 private String staircaseCutOutShape; 313 private String creator; 314 private boolean backFaceShown; 315 private boolean resizable; 316 private boolean deformable; 317 private boolean texturable; 318 private boolean horizontallyRotatable; 319 private BigDecimal price; 320 private BigDecimal valueAddedTaxPercentage; 321 private String currency; 322 private boolean visible; 323 private float x; 324 private float y; 325 private float angle; 326 private float pitch; 327 private float roll; 328 private boolean modelMirrored; 329 private Level level; 330 331 private transient Shape shapeCache; 332 333 334 /** 335 * Creates a home piece of furniture from an existing piece. 336 * @param piece the piece from which data are copied 337 */ HomePieceOfFurniture(PieceOfFurniture piece)338 public HomePieceOfFurniture(PieceOfFurniture piece) { 339 this(createId("pieceOfFurniture"), piece); 340 } 341 342 /** 343 * Creates a home piece of furniture from an existing piece. 344 * @param id the ID of the piece 345 * @param piece the piece from which data are copied 346 * @since 6.4 347 */ HomePieceOfFurniture(String id, PieceOfFurniture piece)348 public HomePieceOfFurniture(String id, PieceOfFurniture piece) { 349 super(id); 350 this.name = piece.getName(); 351 this.description = piece.getDescription(); 352 this.information = piece.getInformation(); 353 this.icon = piece.getIcon(); 354 this.planIcon = piece.getPlanIcon(); 355 this.model = piece.getModel(); 356 this.modelSize = piece.getModelSize(); 357 this.width = piece.getWidth(); 358 this.depth = piece.getDepth(); 359 this.height = piece.getHeight(); 360 this.elevation = piece.getElevation(); 361 this.dropOnTopElevation = piece.getDropOnTopElevation(); 362 this.movable = piece.isMovable(); 363 this.doorOrWindow = piece.isDoorOrWindow(); 364 this.color = piece.getColor(); 365 this.modelRotation = piece.getModelRotation(); 366 this.staircaseCutOutShape = piece.getStaircaseCutOutShape(); 367 this.creator = piece.getCreator(); 368 this.backFaceShown = piece.isBackFaceShown(); 369 this.resizable = piece.isResizable(); 370 this.deformable = piece.isDeformable(); 371 this.texturable = piece.isTexturable(); 372 this.horizontallyRotatable = piece.isHorizontallyRotatable(); 373 this.price = piece.getPrice(); 374 this.valueAddedTaxPercentage = piece.getValueAddedTaxPercentage(); 375 this.currency = piece.getCurrency(); 376 if (piece instanceof HomePieceOfFurniture) { 377 HomePieceOfFurniture homePiece = (HomePieceOfFurniture)piece; 378 this.catalogId = homePiece.getCatalogId(); 379 this.nameVisible = homePiece.isNameVisible(); 380 this.nameXOffset = homePiece.getNameXOffset(); 381 this.nameYOffset = homePiece.getNameYOffset(); 382 this.nameAngle = homePiece.getNameAngle(); 383 this.nameStyle = homePiece.getNameStyle(); 384 this.visible = homePiece.isVisible(); 385 this.widthInPlan = homePiece.getWidthInPlan(); 386 this.depthInPlan = homePiece.getDepthInPlan(); 387 this.heightInPlan = homePiece.getHeightInPlan(); 388 this.modelCenteredAtOrigin = homePiece.isModelCenteredAtOrigin(); 389 this.modelTransformations = homePiece.getModelTransformations(); 390 this.angle = homePiece.getAngle(); 391 this.pitch = homePiece.getPitch(); 392 this.roll = homePiece.getRoll(); 393 this.x = homePiece.getX(); 394 this.y = homePiece.getY(); 395 this.modelMirrored = homePiece.isModelMirrored(); 396 this.texture = homePiece.getTexture(); 397 this.shininess = homePiece.getShininess(); 398 this.modelMaterials = homePiece.getModelMaterials(); 399 } else { 400 if (piece instanceof CatalogPieceOfFurniture) { 401 this.catalogId = ((CatalogPieceOfFurniture)piece).getId(); 402 } 403 this.visible = true; 404 this.widthInPlan = this.width; 405 this.depthInPlan = this.depth; 406 this.heightInPlan = this.height; 407 this.modelCenteredAtOrigin = true; // false by default for version < 5.5 408 this.x = this.width / 2; 409 this.y = this.depth / 2; 410 } 411 } 412 413 /** 414 * Initializes new piece fields to their default values 415 * and reads piece from <code>in</code> stream with default reading method. 416 */ readObject(ObjectInputStream in)417 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 418 this.dropOnTopElevation = 1f; 419 this.modelRotation = IDENTITY_ROTATION; 420 this.resizable = true; 421 this.deformable = true; 422 this.texturable = true; 423 this.horizontallyRotatable = true; 424 this.widthInPlan = 425 this.depthInPlan = 426 this.heightInPlan = Float.NEGATIVE_INFINITY; 427 in.defaultReadObject(); 428 429 // Ensure angle is always positive and between 0 and 2 PI 430 this.angle = (float)((this.angle % TWICE_PI + TWICE_PI) % TWICE_PI); 431 // Update new fields used to store dimensions of a piece in plan after pitch or roll is applied to it 432 if (this.widthInPlan == Float.NEGATIVE_INFINITY 433 || this.depthInPlan == Float.NEGATIVE_INFINITY 434 || this.heightInPlan == Float.NEGATIVE_INFINITY) { 435 this.widthInPlan = this.width; 436 this.depthInPlan = this.depth; 437 this.heightInPlan = this.height; 438 this.pitch = 0; 439 this.roll = 0; 440 } 441 if (!this.modelCenteredAtOrigin) { 442 // Keep default value to false only if model rotation matrix isn't identity 443 this.modelCenteredAtOrigin = Arrays.deepEquals(IDENTITY_ROTATION, this.modelRotation); 444 } 445 } 446 447 /** 448 * Returns the catalog ID of this piece of furniture or <code>null</code> if it doesn't exist. 449 */ getCatalogId()450 public String getCatalogId() { 451 return this.catalogId; 452 } 453 454 /** 455 * Sets the catalog ID of this piece of furniture. Once this piece is updated, 456 * listeners added to this piece will receive a change notification. 457 * @since 6.5 458 */ setCatalogId(String catalogId)459 public void setCatalogId(String catalogId) { 460 if (catalogId != this.catalogId 461 && (catalogId == null || !catalogId.equals(this.catalogId))) { 462 String oldCatalogId = this.catalogId; 463 this.catalogId = catalogId; 464 firePropertyChange(Property.CATALOG_ID.name(), oldCatalogId, catalogId); 465 } 466 } 467 468 /** 469 * Returns the name of this piece of furniture. 470 */ getName()471 public String getName() { 472 return this.name; 473 } 474 475 /** 476 * Sets the name of this piece of furniture. Once this piece is updated, 477 * listeners added to this piece will receive a change notification. 478 */ setName(String name)479 public void setName(String name) { 480 if (name != this.name 481 && (name == null || !name.equals(this.name))) { 482 String oldName = this.name; 483 this.name = name; 484 firePropertyChange(Property.NAME.name(), oldName, name); 485 } 486 } 487 488 /** 489 * Returns whether the name of this piece should be drawn or not. 490 */ isNameVisible()491 public boolean isNameVisible() { 492 return this.nameVisible; 493 } 494 495 /** 496 * Sets whether the name of this piece is visible or not. Once this piece of furniture 497 * is updated, listeners added to this piece will receive a change notification. 498 */ setNameVisible(boolean nameVisible)499 public void setNameVisible(boolean nameVisible) { 500 if (nameVisible != this.nameVisible) { 501 this.nameVisible = nameVisible; 502 firePropertyChange(Property.NAME_VISIBLE.name(), !nameVisible, nameVisible); 503 } 504 } 505 506 /** 507 * Returns the distance along x axis applied to piece abscissa to display piece name. 508 */ getNameXOffset()509 public float getNameXOffset() { 510 return this.nameXOffset; 511 } 512 513 /** 514 * Sets the distance along x axis applied to piece abscissa to display piece name. 515 * Once this piece is updated, listeners added to this piece will receive a change notification. 516 */ setNameXOffset(float nameXOffset)517 public void setNameXOffset(float nameXOffset) { 518 if (nameXOffset != this.nameXOffset) { 519 float oldNameXOffset = this.nameXOffset; 520 this.nameXOffset = nameXOffset; 521 firePropertyChange(Property.NAME_X_OFFSET.name(), oldNameXOffset, nameXOffset); 522 } 523 } 524 525 /** 526 * Returns the distance along y axis applied to piece ordinate 527 * to display piece name. 528 */ getNameYOffset()529 public float getNameYOffset() { 530 return this.nameYOffset; 531 } 532 533 /** 534 * Sets the distance along y axis applied to piece ordinate to display piece name. 535 * Once this piece is updated, listeners added to this piece will receive a change notification. 536 */ setNameYOffset(float nameYOffset)537 public void setNameYOffset(float nameYOffset) { 538 if (nameYOffset != this.nameYOffset) { 539 float oldNameYOffset = this.nameYOffset; 540 this.nameYOffset = nameYOffset; 541 firePropertyChange(Property.NAME_Y_OFFSET.name(), oldNameYOffset, nameYOffset); 542 } 543 } 544 545 /** 546 * Returns the text style used to display piece name. 547 */ getNameStyle()548 public TextStyle getNameStyle() { 549 return this.nameStyle; 550 } 551 552 /** 553 * Sets the text style used to display piece name. 554 * Once this piece is updated, listeners added to this piece will receive a change notification. 555 */ setNameStyle(TextStyle nameStyle)556 public void setNameStyle(TextStyle nameStyle) { 557 if (nameStyle != this.nameStyle) { 558 TextStyle oldNameStyle = this.nameStyle; 559 this.nameStyle = nameStyle; 560 firePropertyChange(Property.NAME_STYLE.name(), oldNameStyle, nameStyle); 561 } 562 } 563 564 /** 565 * Returns the angle in radians used to display the piece name. 566 * @since 3.6 567 */ getNameAngle()568 public float getNameAngle() { 569 return this.nameAngle; 570 } 571 572 /** 573 * Sets the angle in radians used to display the piece name. Once this piece is updated, 574 * listeners added to this piece will receive a change notification. 575 * @since 3.6 576 */ setNameAngle(float nameAngle)577 public void setNameAngle(float nameAngle) { 578 // Ensure angle is always positive and between 0 and 2 PI 579 nameAngle = (float)((nameAngle % TWICE_PI + TWICE_PI) % TWICE_PI); 580 if (nameAngle != this.nameAngle) { 581 float oldNameAngle = this.nameAngle; 582 this.nameAngle = nameAngle; 583 firePropertyChange(Property.NAME_ANGLE.name(), oldNameAngle, nameAngle); 584 } 585 } 586 587 /** 588 * Returns the description of this piece of furniture. 589 * The returned value may be <code>null</code>. 590 */ getDescription()591 public String getDescription() { 592 return this.description; 593 } 594 595 /** 596 * Sets the description of this piece of furniture. Once this piece is updated, 597 * listeners added to this piece will receive a change notification. 598 */ setDescription(String description)599 public void setDescription(String description) { 600 if (description != this.description 601 && (description == null || !description.equals(this.description))) { 602 String oldDescription = this.description; 603 this.description = description; 604 firePropertyChange(Property.DESCRIPTION.name(), oldDescription, description); 605 } 606 } 607 608 /** 609 * Returns the additional information associated to this piece, or <code>null</code>. 610 * @since 4.2 611 */ getInformation()612 public String getInformation() { 613 return this.information; 614 } 615 616 /** 617 * Sets the additional information associated to this piece . Once this piece is updated, 618 * listeners added to this piece will receive a change notification. 619 * @since 6.5 620 */ setInformation(String information)621 public void setInformation(String information) { 622 if (information != this.information 623 && (information == null || !information.equals(this.information))) { 624 String oldInformation = this.information; 625 this.information = information; 626 firePropertyChange(Property.INFORMATION.name(), oldInformation, information); 627 } 628 } 629 630 /** 631 * Returns the depth of this piece of furniture. 632 */ getDepth()633 public float getDepth() { 634 return this.depth; 635 } 636 637 /** 638 * Sets the depth of this piece of furniture. Once this piece is updated, 639 * listeners added to this piece will receive a change notification. 640 * @throws IllegalStateException if this piece of furniture isn't resizable 641 */ setDepth(float depth)642 public void setDepth(float depth) { 643 if (isResizable()) { 644 if (depth != this.depth) { 645 float oldDepth = this.depth; 646 this.depth = depth; 647 this.shapeCache = null; 648 firePropertyChange(Property.DEPTH.name(), oldDepth, depth); 649 } 650 } else { 651 throw new IllegalStateException("Piece isn't resizable"); 652 } 653 } 654 655 /** 656 * Returns the depth of this piece of furniture in the horizontal plan (after pitch or roll is applied to it). 657 * @since 5.5 658 */ getDepthInPlan()659 public float getDepthInPlan() { 660 return this.depthInPlan; 661 } 662 663 /** 664 * Sets the depth of this piece of furniture in the horizontal plan (after pitch or roll is applied to it). 665 * listeners added to this piece will receive a change notification. 666 * @since 5.5 667 */ setDepthInPlan(float depthInPlan)668 public void setDepthInPlan(float depthInPlan) { 669 if (depthInPlan != this.depthInPlan) { 670 float oldDepth = this.depthInPlan; 671 this.depthInPlan = depthInPlan; 672 this.shapeCache = null; 673 firePropertyChange(Property.DEPTH_IN_PLAN.name(), oldDepth, depthInPlan); 674 } 675 } 676 677 /** 678 * Returns the height of this piece of furniture. 679 */ getHeight()680 public float getHeight() { 681 return this.height; 682 } 683 684 /** 685 * Sets the height of this piece of furniture. Once this piece is updated, 686 * listeners added to this piece will receive a change notification. 687 * @throws IllegalStateException if this piece of furniture isn't resizable 688 */ setHeight(float height)689 public void setHeight(float height) { 690 if (isResizable()) { 691 if (height != this.height) { 692 float oldHeight = this.height; 693 this.height = height; 694 firePropertyChange(Property.HEIGHT.name(), oldHeight, height); 695 } 696 } else { 697 throw new IllegalStateException("Piece isn't resizable"); 698 } 699 } 700 701 /** 702 * Returns the height of this piece of furniture from the horizontal plan (after pitch or roll is applied to it). 703 * @since 5.5 704 */ getHeightInPlan()705 public float getHeightInPlan() { 706 return this.heightInPlan; 707 } 708 709 /** 710 * Sets the height of this piece of furniture from the horizontal plan (after pitch or roll is applied to it). 711 * Once this piece is updated, listeners added to this piece will receive a change notification. 712 * @since 5.5 713 */ setHeightInPlan(float heightInPlan)714 public void setHeightInPlan(float heightInPlan) { 715 if (heightInPlan != this.heightInPlan) { 716 float oldHeight = this.heightInPlan; 717 this.heightInPlan = heightInPlan; 718 firePropertyChange(Property.HEIGHT_IN_PLAN.name(), oldHeight, heightInPlan); 719 } 720 } 721 722 /** 723 * Returns the width of this piece of furniture. 724 */ getWidth()725 public float getWidth() { 726 return this.width; 727 } 728 729 /** 730 * Sets the width of this piece of furniture. Once this piece is updated, 731 * listeners added to this piece will receive a change notification. 732 * @throws IllegalStateException if this piece of furniture isn't resizable 733 */ setWidth(float width)734 public void setWidth(float width) { 735 if (isResizable()) { 736 if (width != this.width) { 737 float oldWidth = this.width; 738 this.width = width; 739 this.shapeCache = null; 740 firePropertyChange(Property.WIDTH.name(), oldWidth, width); 741 } 742 } else { 743 throw new IllegalStateException("Piece isn't resizable"); 744 } 745 } 746 747 /** 748 * Returns the width of this piece of furniture in the horizontal plan (after pitch or roll is applied to it). 749 * @since 5.5 750 */ getWidthInPlan()751 public float getWidthInPlan() { 752 return this.widthInPlan; 753 } 754 755 /** 756 * Sets the width of this piece of furniture in the horizontal plan (after pitch or roll is applied to it). 757 * Once this piece is updated, listeners added to this piece will receive a change notification. 758 * @since 5.5 759 */ setWidthInPlan(float widthInPlan)760 public void setWidthInPlan(float widthInPlan) { 761 if (widthInPlan != this.widthInPlan) { 762 float oldWidth = this.widthInPlan; 763 this.widthInPlan = widthInPlan; 764 this.shapeCache = null; 765 firePropertyChange(Property.WIDTH_IN_PLAN.name(), oldWidth, widthInPlan); 766 } 767 } 768 769 /** 770 * Scales this piece of furniture with the given <code>scale</code>. 771 * Once this piece is updated, listeners added to this piece will receive a change notification. 772 * @since 5.5 773 */ scale(float scale)774 public void scale(float scale) { 775 setWidth(getWidth() * scale); 776 setDepth(getDepth() * scale); 777 setHeight(getHeight() * scale); 778 } 779 780 /** 781 * Returns the elevation of the bottom of this piece of furniture on its level. 782 */ getElevation()783 public float getElevation() { 784 return this.elevation; 785 } 786 787 /** 788 * Returns the elevation at which should be placed an object dropped on this piece. 789 * @return a percentage of the height of this piece. A negative value means that the piece 790 * should be ignored when an object is dropped on it. 791 * @since 4.4 792 */ getDropOnTopElevation()793 public float getDropOnTopElevation() { 794 return this.dropOnTopElevation; 795 } 796 797 /** 798 * Returns the elevation of the bottom of this piece of furniture 799 * from the ground according to the elevation of its level. 800 * @since 3.4 801 */ getGroundElevation()802 public float getGroundElevation() { 803 if (this.level != null) { 804 return this.elevation + this.level.getElevation(); 805 } else { 806 return this.elevation; 807 } 808 } 809 810 /** 811 * Sets the elevation of this piece of furniture on its level. Once this piece is updated, 812 * listeners added to this piece will receive a change notification. 813 */ setElevation(float elevation)814 public void setElevation(float elevation) { 815 if (elevation != this.elevation) { 816 float oldElevation = this.elevation; 817 this.elevation = elevation; 818 firePropertyChange(Property.ELEVATION.name(), oldElevation, elevation); 819 } 820 } 821 822 /** 823 * Returns <code>true</code> if this piece of furniture is movable. 824 */ isMovable()825 public boolean isMovable() { 826 return this.movable; 827 } 828 829 /** 830 * Sets whether this piece is movable or not. 831 * @since 3.0 832 */ setMovable(boolean movable)833 public void setMovable(boolean movable) { 834 if (movable != this.movable) { 835 this.movable = movable; 836 firePropertyChange(Property.MOVABLE.name(), !movable, movable); 837 } 838 } 839 840 /** 841 * Returns <code>true</code> if this piece of furniture is a door or a window. 842 * As this method existed before {@linkplain HomeDoorOrWindow HomeDoorOrWindow} class, 843 * you shouldn't rely on the value returned by this method to guess if a piece 844 * is an instance of <code>DoorOrWindow</code> class. 845 */ isDoorOrWindow()846 public boolean isDoorOrWindow() { 847 return this.doorOrWindow; 848 } 849 850 /** 851 * Returns the icon of this piece of furniture. 852 */ getIcon()853 public Content getIcon() { 854 return this.icon; 855 } 856 857 /** 858 * Sets the icon of this piece of furniture. Once this piece is updated, 859 * listeners added to this piece will receive a change notification. 860 * @since 6.5 861 */ setIcon(Content icon)862 public void setIcon(Content icon) { 863 if (icon != this.icon 864 && (icon == null || !icon.equals(this.icon))) { 865 Content oldIcon = this.icon; 866 this.icon = icon; 867 firePropertyChange(Property.ICON.name(), oldIcon, icon); 868 } 869 } 870 871 /** 872 * Returns the icon of this piece of furniture displayed in plan or <code>null</code>. 873 * @since 2.2 874 */ getPlanIcon()875 public Content getPlanIcon() { 876 return this.planIcon; 877 } 878 879 /** 880 * Sets the plan icon of this piece of furniture. Once this piece is updated, 881 * listeners added to this piece will receive a change notification. 882 * @since 6.5 883 */ setPlanIcon(Content planIcon)884 public void setPlanIcon(Content planIcon) { 885 if (planIcon != this.planIcon 886 && (planIcon == null || !planIcon.equals(this.planIcon))) { 887 Content oldPlanIcon = this.planIcon; 888 this.planIcon = planIcon; 889 firePropertyChange(Property.PLAN_ICON.name(), oldPlanIcon, planIcon); 890 } 891 } 892 893 /** 894 * Returns the 3D model of this piece of furniture. 895 */ getModel()896 public Content getModel() { 897 return this.model; 898 } 899 900 /** 901 * Sets the 3D model of this piece of furniture. Once this piece is updated, 902 * listeners added to this piece will receive a change notification. 903 * @since 6.5 904 */ setModel(Content model)905 public void setModel(Content model) { 906 if (model != this.model 907 && (model == null || !model.equals(this.model))) { 908 Content oldModel = this.model; 909 this.model = model; 910 firePropertyChange(Property.MODEL.name(), oldModel, model); 911 } 912 } 913 914 /** 915 * Returns the size of the 3D model of this piece of furniture. 916 * @since 5.5 917 */ getModelSize()918 public Long getModelSize() { 919 return this.modelSize; 920 } 921 922 /** 923 * Sets the size of the 3D model of this piece of furniture. 924 * This method should be called only to update a piece created with an older version. 925 * @since 5.5 926 */ setModelSize(Long modelSize)927 public void setModelSize(Long modelSize) { 928 this.modelSize = modelSize; 929 } 930 931 /** 932 * Returns the materials applied to the 3D model of this piece of furniture. 933 * @return the materials of the 3D model or <code>null</code> 934 * if the individual materials of the 3D model are not modified. 935 * @since 4.0 936 */ getModelMaterials()937 public HomeMaterial [] getModelMaterials() { 938 if (this.modelMaterials != null) { 939 return this.modelMaterials.clone(); 940 } else { 941 return null; 942 } 943 } 944 945 /** 946 * Sets the materials of the 3D model of this piece of furniture. 947 * Once this piece is updated, listeners added to this piece will receive a change notification. 948 * @param modelMaterials the materials of the 3D model or <code>null</code> if they shouldn't be changed 949 * @throws IllegalStateException if this piece of furniture isn't texturable 950 * @since 4.0 951 */ setModelMaterials(HomeMaterial [] modelMaterials)952 public void setModelMaterials(HomeMaterial [] modelMaterials) { 953 if (isTexturable()) { 954 if (!Arrays.equals(modelMaterials, this.modelMaterials)) { 955 HomeMaterial [] oldModelMaterials = this.modelMaterials; 956 this.modelMaterials = modelMaterials != null 957 ? modelMaterials.clone() 958 : null; 959 firePropertyChange(Property.MODEL_MATERIALS.name(), oldModelMaterials, modelMaterials); 960 } 961 } else { 962 throw new IllegalStateException("Piece isn't texturable"); 963 } 964 } 965 966 /** 967 * Returns the color of this piece of furniture. 968 * @return the color of the piece as RGB code or <code>null</code> if piece color is unchanged. 969 */ getColor()970 public Integer getColor() { 971 return this.color; 972 } 973 974 /** 975 * Sets the color of this piece of furniture. 976 * Once this piece is updated, listeners added to this piece will receive a change notification. 977 * @param color the color of this piece of furniture or <code>null</code> if piece color is the default one 978 * @throws IllegalStateException if this piece of furniture isn't texturable 979 */ setColor(Integer color)980 public void setColor(Integer color) { 981 if (isTexturable()) { 982 if (color != this.color 983 && (color == null || !color.equals(this.color))) { 984 Integer oldColor = this.color; 985 this.color = color; 986 firePropertyChange(Property.COLOR.name(), oldColor, color); 987 } 988 } else { 989 throw new IllegalStateException("Piece isn't texturable"); 990 } 991 } 992 993 /** 994 * Returns the texture of this piece of furniture. 995 * @return the texture of the piece or <code>null</code> if piece texture is unchanged. 996 * @since 2.3 997 */ getTexture()998 public HomeTexture getTexture() { 999 return this.texture; 1000 } 1001 1002 /** 1003 * Sets the texture of this piece of furniture. 1004 * Once this piece is updated, listeners added to this piece will receive a change notification. 1005 * @param texture the texture of this piece of furniture or <code>null</code> if piece texture is the default one 1006 * @throws IllegalStateException if this piece of furniture isn't texturable 1007 * @since 2.3 1008 */ setTexture(HomeTexture texture)1009 public void setTexture(HomeTexture texture) { 1010 if (isTexturable()) { 1011 if (texture != this.texture 1012 && (texture == null || !texture.equals(this.texture))) { 1013 HomeTexture oldTexture = this.texture; 1014 this.texture = texture; 1015 firePropertyChange(Property.TEXTURE.name(), oldTexture, texture); 1016 } 1017 } else { 1018 throw new IllegalStateException("Piece isn't texturable"); 1019 } 1020 } 1021 1022 /** 1023 * Returns the shininess of this piece of furniture. 1024 * @return a value between 0 (matt) and 1 (very shiny) or <code>null</code> if piece shininess is unchanged. 1025 * @since 3.0 1026 */ getShininess()1027 public Float getShininess() { 1028 return this.shininess; 1029 } 1030 1031 /** 1032 * Sets the shininess of this piece of furniture or <code>null</code> if piece shininess is unchanged. 1033 * Once this piece is updated, listeners added to this piece will receive a change notification. 1034 * @throws IllegalStateException if this piece of furniture isn't texturable 1035 * @since 3.0 1036 */ setShininess(Float shininess)1037 public void setShininess(Float shininess) { 1038 if (isTexturable()) { 1039 if (shininess != this.shininess 1040 && (shininess == null || !shininess.equals(this.shininess))) { 1041 Float oldShininess = this.shininess; 1042 this.shininess = shininess; 1043 firePropertyChange(Property.SHININESS.name(), oldShininess, shininess); 1044 } 1045 } else { 1046 throw new IllegalStateException("Piece isn't texturable"); 1047 } 1048 } 1049 1050 /** 1051 * Returns <code>true</code> if this piece is resizable. 1052 */ isResizable()1053 public boolean isResizable() { 1054 return this.resizable; 1055 } 1056 1057 /** 1058 * Returns <code>true</code> if this piece is deformable. 1059 * @since 3.0 1060 */ isDeformable()1061 public boolean isDeformable() { 1062 return this.deformable; 1063 } 1064 1065 /** 1066 * Returns <code>true</code> if this piece is deformable. 1067 * @since 5.5 1068 */ isWidthDepthDeformable()1069 public boolean isWidthDepthDeformable() { 1070 return isDeformable(); 1071 } 1072 1073 /** 1074 * Returns <code>false</code> if this piece should always keep the same color or texture. 1075 * @since 3.0 1076 */ isTexturable()1077 public boolean isTexturable() { 1078 return this.texturable; 1079 } 1080 1081 /** 1082 * Returns <code>false</code> if this piece should not rotate around an horizontal axis. 1083 * @since 5.5 1084 */ isHorizontallyRotatable()1085 public boolean isHorizontallyRotatable() { 1086 return this.horizontallyRotatable; 1087 } 1088 1089 /** 1090 * Returns the price of this piece of furniture or <code>null</code>. 1091 */ getPrice()1092 public BigDecimal getPrice() { 1093 return this.price; 1094 } 1095 1096 /** 1097 * Sets the price of this piece of furniture. Once this piece is updated, 1098 * listeners added to this piece will receive a change notification. 1099 * @since 4.0 1100 */ setPrice(BigDecimal price)1101 public void setPrice(BigDecimal price) { 1102 if (price != this.price 1103 && (price == null || !price.equals(this.price))) { 1104 BigDecimal oldPrice = this.price; 1105 this.price = price; 1106 firePropertyChange(Property.PRICE.name(), oldPrice, price); 1107 } 1108 } 1109 1110 /** 1111 * Returns the Value Added Tax percentage applied to the price of this piece of furniture. 1112 */ getValueAddedTaxPercentage()1113 public BigDecimal getValueAddedTaxPercentage() { 1114 return this.valueAddedTaxPercentage; 1115 } 1116 1117 /** 1118 * Sets the Value Added Tax percentage applied to prices. 1119 * @since 6.0 1120 */ setValueAddedTaxPercentage(BigDecimal valueAddedTaxPercentage)1121 public void setValueAddedTaxPercentage(BigDecimal valueAddedTaxPercentage) { 1122 if (valueAddedTaxPercentage != this.valueAddedTaxPercentage 1123 && (valueAddedTaxPercentage == null || !valueAddedTaxPercentage.equals(this.valueAddedTaxPercentage))) { 1124 BigDecimal oldValueAddedTaxPercentage = this.valueAddedTaxPercentage; 1125 this.valueAddedTaxPercentage = valueAddedTaxPercentage; 1126 firePropertyChange(Property.VALUE_ADDED_TAX_PERCENTAGE.name(), oldValueAddedTaxPercentage, valueAddedTaxPercentage); 1127 1128 } 1129 } 1130 1131 /** 1132 * Returns the Value Added Tax applied to the price of this piece of furniture. 1133 */ getValueAddedTax()1134 public BigDecimal getValueAddedTax() { 1135 if (this.price != null && this.valueAddedTaxPercentage != null) { 1136 return this.price.multiply(this.valueAddedTaxPercentage). 1137 setScale(this.price.scale(), RoundingMode.HALF_UP); 1138 } else { 1139 return null; 1140 } 1141 } 1142 1143 /** 1144 * Returns the price of this piece of furniture, Value Added Tax included. 1145 */ getPriceValueAddedTaxIncluded()1146 public BigDecimal getPriceValueAddedTaxIncluded() { 1147 if (this.price != null && this.valueAddedTaxPercentage != null) { 1148 return this.price.add(getValueAddedTax()); 1149 } else { 1150 return this.price; 1151 } 1152 } 1153 1154 /** 1155 * Returns the price currency, noted with ISO 4217 code, or <code>null</code> 1156 * if it has no price or default currency should be used. 1157 * @since 3.4 1158 */ getCurrency()1159 public String getCurrency() { 1160 return this.currency; 1161 } 1162 1163 /** 1164 * Sets the price currency, noted with ISO 4217 code. Once this piece is updated, 1165 * listeners added to this piece will receive a change notification. 1166 * @since 6.0 1167 */ setCurrency(String currency)1168 public void setCurrency(String currency) { 1169 if (currency != this.currency 1170 && (currency == null || !currency.equals(this.currency))) { 1171 String oldCurrency = this.currency; 1172 this.currency = currency; 1173 firePropertyChange(Property.CURRENCY.name(), oldCurrency, currency); 1174 } 1175 } 1176 1177 /** 1178 * Returns <code>true</code> if this piece of furniture is visible. 1179 */ isVisible()1180 public boolean isVisible() { 1181 return this.visible; 1182 } 1183 1184 /** 1185 * Sets whether this piece of furniture is visible or not. Once this piece is updated, 1186 * listeners added to this piece will receive a change notification. 1187 */ setVisible(boolean visible)1188 public void setVisible(boolean visible) { 1189 if (visible != this.visible) { 1190 this.visible = visible; 1191 firePropertyChange(Property.VISIBLE.name(), !visible, visible); 1192 } 1193 } 1194 1195 /** 1196 * Returns the abscissa of the center of this piece of furniture. 1197 */ getX()1198 public float getX() { 1199 return this.x; 1200 } 1201 1202 /** 1203 * Sets the abscissa of the center of this piece. Once this piece is updated, 1204 * listeners added to this piece will receive a change notification. 1205 */ setX(float x)1206 public void setX(float x) { 1207 if (x != this.x) { 1208 float oldX = this.x; 1209 this.x = x; 1210 this.shapeCache = null; 1211 firePropertyChange(Property.X.name(), oldX, x); 1212 } 1213 } 1214 1215 /** 1216 * Returns the ordinate of the center of this piece of furniture. 1217 */ getY()1218 public float getY() { 1219 return this.y; 1220 } 1221 1222 /** 1223 * Sets the ordinate of the center of this piece. Once this piece is updated, 1224 * listeners added to this piece will receive a change notification. 1225 */ setY(float y)1226 public void setY(float y) { 1227 if (y != this.y) { 1228 float oldY = this.y; 1229 this.y = y; 1230 this.shapeCache = null; 1231 firePropertyChange(Property.Y.name(), oldY, y); 1232 } 1233 } 1234 1235 /** 1236 * Returns the angle in radians of this piece around vertical axis. 1237 */ getAngle()1238 public float getAngle() { 1239 return this.angle; 1240 } 1241 1242 /** 1243 * Sets the angle of this piece around vertical axis. Once this piece is updated, 1244 * listeners added to this piece will receive a change notification. 1245 */ setAngle(float angle)1246 public void setAngle(float angle) { 1247 // Ensure angle is always positive and between 0 and 2 PI 1248 angle = (float)((angle % TWICE_PI + TWICE_PI) % TWICE_PI); 1249 if (angle != this.angle) { 1250 float oldAngle = this.angle; 1251 this.angle = angle; 1252 this.shapeCache = null; 1253 firePropertyChange(Property.ANGLE.name(), oldAngle, angle); 1254 } 1255 } 1256 1257 /** 1258 * Returns the pitch angle in radians of this piece of furniture. 1259 * @since 5.5 1260 */ getPitch()1261 public float getPitch() { 1262 return this.pitch; 1263 } 1264 1265 /** 1266 * Sets the pitch angle in radians of this piece and notifies listeners of this change. 1267 * Pitch axis is horizontal lateral (or transverse) axis. 1268 * @since 5.5 1269 */ setPitch(float pitch)1270 public void setPitch(float pitch) { 1271 if (isHorizontallyRotatable()) { 1272 // Ensure pitch is always positive and between 0 and 2 PI 1273 pitch = (float)((pitch % TWICE_PI + TWICE_PI) % TWICE_PI); 1274 if (pitch != this.pitch) { 1275 float oldPitch = this.pitch; 1276 this.pitch = pitch; 1277 this.shapeCache = null; 1278 firePropertyChange(Property.PITCH.name(), oldPitch, pitch); 1279 } 1280 } else { 1281 throw new IllegalStateException("Piece can't be rotated around an horizontal axis"); 1282 } 1283 } 1284 1285 /** 1286 * Returns the roll angle in radians of this piece of furniture. 1287 * @since 5.5 1288 */ getRoll()1289 public float getRoll() { 1290 return this.roll; 1291 } 1292 1293 /** 1294 * Sets the roll angle in radians of this piece and notifies listeners of this change. 1295 * Roll axis is horizontal longitudinal axis. 1296 * @since 5.5 1297 */ setRoll(float roll)1298 public void setRoll(float roll) { 1299 if (isHorizontallyRotatable()) { 1300 // Ensure roll is always positive and between 0 and 2 PI 1301 roll = (float)((roll % TWICE_PI + TWICE_PI) % TWICE_PI); 1302 if (roll != this.roll) { 1303 float oldRoll = this.roll; 1304 this.roll = roll; 1305 this.shapeCache = null; 1306 firePropertyChange(Property.ROLL.name(), oldRoll, roll); 1307 } 1308 } else { 1309 throw new IllegalStateException("Piece can't be rotated around an horizontal axis"); 1310 } 1311 } 1312 1313 /** 1314 * Returns <code>true</code> if the pitch or roll angle of this piece is different from 0. 1315 * @since 5.5 1316 */ isHorizontallyRotated()1317 public boolean isHorizontallyRotated() { 1318 return this.roll != 0 || this.pitch != 0; 1319 } 1320 1321 /** 1322 * Returns the rotation 3 by 3 matrix of this piece of furniture that ensures 1323 * its model is correctly oriented. 1324 */ getModelRotation()1325 public float [][] getModelRotation() { 1326 // Return a deep copy to avoid any misuse of piece data 1327 return CatalogPieceOfFurniture.deepClone(this.modelRotation); 1328 } 1329 1330 /** 1331 * Sets the rotation 3 by 3 matrix of this piece of furniture and notifies listeners of this change. 1332 * @since 6.5 1333 */ setModelRotation(float [][] modelRotation)1334 public void setModelRotation(float [][] modelRotation) { 1335 if (!Arrays.deepEquals(modelRotation, this.modelRotation)) { 1336 float [][] oldModelRotation = CatalogPieceOfFurniture.deepClone(this.modelRotation); 1337 this.modelRotation = CatalogPieceOfFurniture.deepClone(modelRotation); 1338 firePropertyChange(Property.MODEL_ROTATION.name(), oldModelRotation, modelRotation); 1339 } 1340 } 1341 1342 /** 1343 * Returns <code>true</code> if the model of this piece should be mirrored. 1344 */ isModelMirrored()1345 public boolean isModelMirrored() { 1346 return this.modelMirrored; 1347 } 1348 1349 /** 1350 * Sets whether the model of this piece of furniture is mirrored or not. Once this piece is updated, 1351 * listeners added to this piece will receive a change notification. 1352 * @throws IllegalStateException if this piece of furniture isn't resizable 1353 */ setModelMirrored(boolean modelMirrored)1354 public void setModelMirrored(boolean modelMirrored) { 1355 if (isResizable()) { 1356 if (modelMirrored != this.modelMirrored) { 1357 this.modelMirrored = modelMirrored; 1358 firePropertyChange(Property.MODEL_MIRRORED.name(), 1359 !modelMirrored, modelMirrored); 1360 } 1361 } else { 1362 throw new IllegalStateException("Piece isn't resizable"); 1363 } 1364 } 1365 1366 /** 1367 * Returns <code>true</code> if model center should be always centered at the origin 1368 * when model rotation isn't <code>null</code>. 1369 * @return <code>false</code> by default if version < 5.5 1370 * @since 5.5 1371 */ isModelCenteredAtOrigin()1372 public boolean isModelCenteredAtOrigin() { 1373 return this.modelCenteredAtOrigin; 1374 } 1375 1376 /** 1377 * Sets whether model center should be always centered at the origin 1378 * when model rotation isn't <code>null</code>. 1379 * This method should be called only to keep unchanged the (wrong) location 1380 * of a rotated model created with version < 5.5. 1381 * @since 5.5 1382 */ setModelCenteredAtOrigin(boolean modelCenteredAtOrigin)1383 public void setModelCenteredAtOrigin(boolean modelCenteredAtOrigin) { 1384 this.modelCenteredAtOrigin = modelCenteredAtOrigin; 1385 } 1386 1387 /** 1388 * Returns <code>true</code> if the back face of the piece of furniture 1389 * model should be displayed. 1390 */ isBackFaceShown()1391 public boolean isBackFaceShown() { 1392 return this.backFaceShown; 1393 } 1394 1395 /** 1396 * Sets whether the back face of the piece of furniture model should be displayed. 1397 * Once this piece is updated, listeners added to this piece will receive a change notification. 1398 * @since 6.5 1399 */ setBackFaceShown(boolean backFaceShown)1400 public void setBackFaceShown(boolean backFaceShown) { 1401 if (backFaceShown != this.backFaceShown) { 1402 this.backFaceShown = backFaceShown; 1403 firePropertyChange(Property.BACK_FACE_SHOWN.name(), 1404 !backFaceShown, backFaceShown); 1405 } 1406 } 1407 1408 /** 1409 * Returns the transformations applied to the 3D model of this piece of furniture. 1410 * @return the transformations of the 3D model or <code>null</code> 1411 * if the 3D model is not transformed. 1412 * @since 6.0 1413 */ getModelTransformations()1414 public Transformation [] getModelTransformations() { 1415 if (this.modelTransformations != null) { 1416 return this.modelTransformations.clone(); 1417 } else { 1418 return null; 1419 } 1420 } 1421 1422 /** 1423 * Sets the transformations applied to some parts of the 3D model of this piece of furniture. 1424 * Once this piece is updated, listeners added to this piece will receive a change notification. 1425 * @param modelTransformations the transformations of the 3D model or <code>null</code> if no transformation shouldn't be applied 1426 * @since 6.0 1427 */ setModelTransformations(Transformation [] modelTransformations)1428 public void setModelTransformations(Transformation [] modelTransformations) { 1429 if (!Arrays.equals(modelTransformations, this.modelTransformations)) { 1430 Transformation [] oldModelTransformations = this.modelTransformations; 1431 this.modelTransformations = modelTransformations != null && modelTransformations.length > 0 1432 ? modelTransformations.clone() 1433 : null; 1434 firePropertyChange(Property.MODEL_MATERIALS.name(), oldModelTransformations, modelTransformations); 1435 } 1436 } 1437 1438 /** 1439 * Returns the shape used to cut out upper levels when they intersect with the piece 1440 * like a staircase. 1441 * @since 3.4 1442 */ getStaircaseCutOutShape()1443 public String getStaircaseCutOutShape() { 1444 return this.staircaseCutOutShape; 1445 } 1446 1447 /** 1448 * Sets the shape used to cut out upper levels when they intersect with the piece 1449 * like a staircase. Once this piece is updated, listeners added to this piece 1450 * will receive a change notification. 1451 * @since 6.5 1452 */ setStaircaseCutOutShape(String staircaseCutOutShape)1453 public void setStaircaseCutOutShape(String staircaseCutOutShape) { 1454 if (staircaseCutOutShape != this.staircaseCutOutShape 1455 && (staircaseCutOutShape == null || !staircaseCutOutShape.equals(this.staircaseCutOutShape))) { 1456 String oldCutOutShape = this.staircaseCutOutShape; 1457 this.staircaseCutOutShape = staircaseCutOutShape; 1458 firePropertyChange(Property.STAIRCASE_CUT_OUT_SHAPE.name(), oldCutOutShape, staircaseCutOutShape); 1459 } 1460 } 1461 1462 /** 1463 * Returns the creator of this piece. 1464 * @since 4.2 1465 */ getCreator()1466 public String getCreator() { 1467 return this.creator; 1468 } 1469 1470 /** 1471 * Sets the creator of this piece. Once this piece is updated, listeners added to this piece 1472 * will receive a change notification. 1473 * @since 6.5 1474 */ setCreator(String creator)1475 public void setCreator(String creator) { 1476 if (creator != this.creator 1477 && (creator == null || !creator.equals(this.creator))) { 1478 String oldCreator = this.creator; 1479 this.creator = creator; 1480 firePropertyChange(Property.CREATOR.name(), oldCreator, creator); 1481 } 1482 } 1483 1484 /** 1485 * Returns the level which this piece belongs to. 1486 * @since 3.4 1487 */ getLevel()1488 public Level getLevel() { 1489 return this.level; 1490 } 1491 1492 /** 1493 * Sets the level of this piece of furniture. Once this piece is updated, 1494 * listeners added to this piece will receive a change notification. 1495 * @since 3.4 1496 */ setLevel(Level level)1497 public void setLevel(Level level) { 1498 if (level != this.level) { 1499 Level oldLevel = this.level; 1500 this.level = level; 1501 firePropertyChange(Property.LEVEL.name(), oldLevel, level); 1502 } 1503 } 1504 1505 /** 1506 * Returns <code>true</code> if this piece is at the given <code>level</code> 1507 * or at a level with the same elevation and a smaller elevation index 1508 * or if the elevation of its highest point is higher than <code>level</code> elevation. 1509 * @since 3.4 1510 */ isAtLevel(Level level)1511 public boolean isAtLevel(Level level) { 1512 if (this.level == level) { 1513 return true; 1514 } else if (this.level != null && level != null) { 1515 float pieceLevelElevation = this.level.getElevation(); 1516 float levelElevation = level.getElevation(); 1517 return pieceLevelElevation == levelElevation 1518 && this.level.getElevationIndex() < level.getElevationIndex() 1519 || pieceLevelElevation < levelElevation 1520 && isTopAtLevel(level); 1521 } else { 1522 return false; 1523 } 1524 } 1525 1526 /** 1527 * Returns <code>true</code> if the top of this piece is visible at the given level. 1528 */ isTopAtLevel(Level level)1529 private boolean isTopAtLevel(Level level) { 1530 float topElevation = this.level.getElevation() + this.elevation + this.heightInPlan; 1531 if (this.staircaseCutOutShape != null) { 1532 // Consider the top of stair cases is at the given level if their elevation is higher or equal 1533 return topElevation >= level.getElevation(); 1534 } else { 1535 return topElevation > level.getElevation(); 1536 } 1537 } 1538 1539 /** 1540 * Returns the points of each corner of a piece. 1541 * @return an array of the 4 (x,y) coordinates of the piece corners. 1542 */ getPoints()1543 public float [][] getPoints() { 1544 float [][] piecePoints = new float[4][2]; 1545 PathIterator it = getShape().getPathIterator(null); 1546 for (int i = 0; i < piecePoints.length; i++) { 1547 it.currentSegment(piecePoints [i]); 1548 it.next(); 1549 } 1550 return piecePoints; 1551 } 1552 1553 /** 1554 * Returns <code>true</code> if this piece intersects 1555 * with the horizontal rectangle which opposite corners are at points 1556 * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>). 1557 */ intersectsRectangle(float x0, float y0, float x1, float y1)1558 public boolean intersectsRectangle(float x0, float y0, 1559 float x1, float y1) { 1560 Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0); 1561 rectangle.add(x1, y1); 1562 return getShape().intersects(rectangle); 1563 } 1564 1565 /** 1566 * Returns <code>true</code> if this piece contains 1567 * the point at (<code>x</code>, <code>y</code>) 1568 * with a given <code>margin</code>. 1569 */ containsPoint(float x, float y, float margin)1570 public boolean containsPoint(float x, float y, float margin) { 1571 if (margin == 0) { 1572 return getShape().contains(x, y); 1573 } else { 1574 return getShape().intersects(x - margin, y - margin, 2 * margin, 2 * margin); 1575 } 1576 } 1577 1578 /** 1579 * Returns <code>true</code> if one of the corner of this piece is 1580 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>. 1581 */ isPointAt(float x, float y, float margin)1582 public boolean isPointAt(float x, float y, float margin) { 1583 for (float [] point : getPoints()) { 1584 if (Math.abs(x - point[0]) <= margin && Math.abs(y - point[1]) <= margin) { 1585 return true; 1586 } 1587 } 1588 return false; 1589 } 1590 1591 /** 1592 * Returns <code>true</code> if the top left point of this piece is 1593 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>, 1594 * and if that point is closer to top left point than to top right and bottom left points. 1595 */ isTopLeftPointAt(float x, float y, float margin)1596 public boolean isTopLeftPointAt(float x, float y, float margin) { 1597 float [][] points = getPoints(); 1598 double distanceSquareToTopLeftPoint = Point2D.distanceSq(x, y, points[0][0], points[0][1]); 1599 return distanceSquareToTopLeftPoint <= margin * margin 1600 && distanceSquareToTopLeftPoint < Point2D.distanceSq(x, y, points[1][0], points[1][1]) 1601 && distanceSquareToTopLeftPoint < Point2D.distanceSq(x, y, points[3][0], points[3][1]); 1602 } 1603 1604 /** 1605 * Returns <code>true</code> if the top right point of this piece is 1606 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>, 1607 * and if that point is closer to top right point than to top left and bottom right points. 1608 */ isTopRightPointAt(float x, float y, float margin)1609 public boolean isTopRightPointAt(float x, float y, float margin) { 1610 float [][] points = getPoints(); 1611 double distanceSquareToTopRightPoint = Point2D.distanceSq(x, y, points[1][0], points[1][1]); 1612 return distanceSquareToTopRightPoint <= margin * margin 1613 && distanceSquareToTopRightPoint < Point2D.distanceSq(x, y, points[0][0], points[0][1]) 1614 && distanceSquareToTopRightPoint < Point2D.distanceSq(x, y, points[2][0], points[2][1]); 1615 } 1616 1617 /** 1618 * Returns <code>true</code> if the bottom left point of this piece is 1619 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>, 1620 * and if that point is closer to bottom left point than to top left and bottom right points. 1621 */ isBottomLeftPointAt(float x, float y, float margin)1622 public boolean isBottomLeftPointAt(float x, float y, float margin) { 1623 float [][] points = getPoints(); 1624 double distanceSquareToBottomLeftPoint = Point2D.distanceSq(x, y, points[3][0], points[3][1]); 1625 return distanceSquareToBottomLeftPoint <= margin * margin 1626 && distanceSquareToBottomLeftPoint < Point2D.distanceSq(x, y, points[0][0], points[0][1]) 1627 && distanceSquareToBottomLeftPoint < Point2D.distanceSq(x, y, points[2][0], points[2][1]); 1628 } 1629 1630 /** 1631 * Returns <code>true</code> if the bottom right point of this piece is 1632 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>, 1633 * and if that point is closer to top left point than to top right and bottom left points. 1634 */ isBottomRightPointAt(float x, float y, float margin)1635 public boolean isBottomRightPointAt(float x, float y, float margin) { 1636 float [][] points = getPoints(); 1637 double distanceSquareToBottomRightPoint = Point2D.distanceSq(x, y, points[2][0], points[2][1]); 1638 return distanceSquareToBottomRightPoint <= margin * margin 1639 && distanceSquareToBottomRightPoint < Point2D.distanceSq(x, y, points[1][0], points[1][1]) 1640 && distanceSquareToBottomRightPoint < Point2D.distanceSq(x, y, points[3][0], points[3][1]); 1641 } 1642 1643 /** 1644 * Returns <code>true</code> if the center point at which is displayed the name 1645 * of this piece is equal to the point at (<code>x</code>, <code>y</code>) 1646 * with a given <code>margin</code>. 1647 */ isNameCenterPointAt(float x, float y, float margin)1648 public boolean isNameCenterPointAt(float x, float y, float margin) { 1649 return Math.abs(x - getX() - getNameXOffset()) <= margin 1650 && Math.abs(y - getY() - getNameYOffset()) <= margin; 1651 } 1652 1653 /** 1654 * Returns <code>true</code> if the front side of this piece is parallel to the given <code>wall</code> 1655 * with a margin. 1656 * @since 5.5 1657 */ isParallelToWall(Wall wall)1658 public boolean isParallelToWall(Wall wall) { 1659 if (wall.getArcExtent() == null) { 1660 float deltaY = wall.getYEnd() - wall.getYStart(); 1661 float deltaX = wall.getXEnd() - wall.getXStart(); 1662 if (deltaX == 0 && deltaY == 0) { 1663 return false; 1664 } else { 1665 // Check parallelism with line joining wall ends 1666 double wallAngle = Math.atan2(deltaY, deltaX); 1667 double pieceWallAngle = Math.abs(wallAngle - getAngle()) % Math.PI; 1668 return pieceWallAngle <= STRAIGHT_WALL_ANGLE_MARGIN || (Math.PI - pieceWallAngle) <= STRAIGHT_WALL_ANGLE_MARGIN; 1669 } 1670 } else { 1671 // Tangent angle at piece center 1672 double tangentAngle = Math.PI / 2 + Math.atan2( 1673 wall.getYArcCircleCenter() - getY(), wall.getXArcCircleCenter() - getX()); 1674 double pieceWallAngle = Math.abs(tangentAngle - getAngle()) % Math.PI; 1675 // Be more tolerant for angles along round walls 1676 return pieceWallAngle <= ROUND_WALL_ANGLE_MARGIN || (Math.PI - pieceWallAngle) <= ROUND_WALL_ANGLE_MARGIN; 1677 } 1678 } 1679 1680 /** 1681 * Returns the shape matching this piece in the horizontal plan. 1682 */ getShape()1683 private Shape getShape() { 1684 if (this.shapeCache == null) { 1685 // Create the rectangle that matches piece bounds 1686 Rectangle2D pieceRectangle = new Rectangle2D.Float( 1687 getX() - getWidthInPlan() / 2, 1688 getY() - getDepthInPlan() / 2, 1689 getWidthInPlan(), getDepthInPlan()); 1690 // Apply rotation to the rectangle 1691 AffineTransform rotation = AffineTransform.getRotateInstance(getAngle(), getX(), getY()); 1692 PathIterator it = pieceRectangle.getPathIterator(rotation); 1693 GeneralPath pieceShape = new GeneralPath(); 1694 pieceShape.append(it, false); 1695 // Cache shape 1696 this.shapeCache = pieceShape; 1697 } 1698 return this.shapeCache; 1699 } 1700 1701 /** 1702 * Moves this piece of (<code>dx</code>, <code>dy</code>) units. 1703 */ move(float dx, float dy)1704 public void move(float dx, float dy) { 1705 setX(getX() + dx); 1706 setY(getY() + dy); 1707 } 1708 1709 /** 1710 * Returns a clone of this piece. 1711 */ 1712 @Override clone()1713 public HomePieceOfFurniture clone() { 1714 HomePieceOfFurniture clone = (HomePieceOfFurniture)super.clone(); 1715 clone.level = null; 1716 return clone; 1717 } 1718 1719 /** 1720 * Returns a comparator that compares furniture on a given <code>property</code> in ascending order. 1721 */ getFurnitureComparator(SortableProperty property)1722 public static Comparator<HomePieceOfFurniture> getFurnitureComparator(SortableProperty property) { 1723 return SORTABLE_PROPERTY_COMPARATORS.get(property); 1724 } 1725 } 1726