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