1 /*
2  * Wall.java 3 juin 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.GeneralPath;
24 import java.awt.geom.Line2D;
25 import java.awt.geom.Point2D;
26 import java.awt.geom.Rectangle2D;
27 import java.util.ArrayList;
28 import java.util.List;
29 
30 /**
31  * A wall of a home plan.
32  * @author Emmanuel Puybaret
33  */
34 public class Wall extends HomeObject implements Selectable, Elevatable {
35   /**
36    * The properties of a wall that may change. <code>PropertyChangeListener</code>s added
37    * to a wall will be notified under a property name equal to the string value of one these properties.
38    */
39   public enum Property {X_START, Y_START, X_END, Y_END, ARC_EXTENT, WALL_AT_START, WALL_AT_END,
40                         THICKNESS, HEIGHT, HEIGHT_AT_END,
41                         LEFT_SIDE_COLOR, LEFT_SIDE_TEXTURE, LEFT_SIDE_SHININESS, LEFT_SIDE_BASEBOARD,
42                         RIGHT_SIDE_COLOR, RIGHT_SIDE_TEXTURE, RIGHT_SIDE_SHININESS, RIGHT_SIDE_BASEBOARD,
43                         PATTERN, TOP_COLOR, LEVEL}
44 
45   private static final long serialVersionUID = 1L;
46 
47   private float               xStart;
48   private float               yStart;
49   private float               xEnd;
50   private float               yEnd;
51   private Float               arcExtent;
52   private Wall                wallAtStart;
53   private Wall                wallAtEnd;
54   private float               thickness;
55   private Float               height;
56   private Float               heightAtEnd;
57   private Integer             leftSideColor;
58   private HomeTexture         leftSideTexture;
59   private float               leftSideShininess;
60   private Baseboard           leftSideBaseboard;
61   private Integer             rightSideColor;
62   private HomeTexture         rightSideTexture;
63   private float               rightSideShininess;
64   private Baseboard           rightSideBaseboard;
65   private boolean             symmetric = true;
66   private TextureImage        pattern;
67   private Integer             topColor;
68   private Level               level;
69 
70   private transient Shape      shapeCache;
71   private transient float []   arcCircleCenterCache;
72   private transient float [][] pointsCache;
73   private transient float [][] pointsIncludingBaseboardsCache;
74 
75 
76   /**
77    * Creates a wall from (<code>xStart</code>,<code>yStart</code>)
78    * to (<code>xEnd</code>, <code>yEnd</code>),
79    * with given thickness. Height, left and right colors are <code>null</code>.
80    * @deprecated specify a height with the {@linkplain #Wall(float, float, float, float, float, float) other constructor}.
81    */
Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness)82   public Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness) {
83     this(xStart, yStart, xEnd, yEnd, thickness, 0);
84   }
85 
86   /**
87    * Creates a wall from (<code>xStart</code>,<code>yStart</code>)
88    * to (<code>xEnd</code>, <code>yEnd</code>),
89    * with given thickness and height. Pattern, left and right colors are <code>null</code>.
90    */
Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness, float height)91   public Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness, float height) {
92     this(xStart, yStart, xEnd, yEnd, thickness, height, null);
93   }
94 
95  /**
96    * Creates a wall from (<code>xStart</code>,<code>yStart</code>)
97    * to (<code>xEnd</code>, <code>yEnd</code>),
98    * with given thickness and height. Pattern, left and right colors are <code>null</code>.
99    * @since 6.4
100    */
Wall(String id, float xStart, float yStart, float xEnd, float yEnd, float thickness, float height)101   public Wall(String id, float xStart, float yStart, float xEnd, float yEnd, float thickness, float height) {
102     this(id, xStart, yStart, xEnd, yEnd, thickness, height, null);
103   }
104 
105   /**
106    * Creates a wall from (<code>xStart</code>,<code>yStart</code>)
107    * to (<code>xEnd</code>, <code>yEnd</code>),
108    * with given thickness, height and pattern.
109    * Colors are <code>null</code>.
110    * @since 4.0
111    */
Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness, float height, TextureImage pattern)112   public Wall(float xStart, float yStart, float xEnd, float yEnd, float thickness, float height, TextureImage pattern) {
113     this(createId("wall"), xStart, yStart, xEnd, yEnd, thickness, height, pattern);
114   }
115 
116   /**
117    * Creates a wall from (<code>xStart</code>,<code>yStart</code>)
118    * to (<code>xEnd</code>, <code>yEnd</code>),
119    * with given thickness, height and pattern.
120    * Colors are <code>null</code>.
121    * @since 6.4
122    */
Wall(String id, float xStart, float yStart, float xEnd, float yEnd, float thickness, float height, TextureImage pattern)123   public Wall(String id, float xStart, float yStart, float xEnd, float yEnd, float thickness, float height, TextureImage pattern) {
124     super(id);
125     this.xStart = xStart;
126     this.yStart = yStart;
127     this.xEnd = xEnd;
128     this.yEnd = yEnd;
129     this.thickness = thickness;
130     this.height = height;
131     this.pattern = pattern;
132   }
133 
134   /**
135    * Returns the start point abscissa of this wall.
136    */
getXStart()137   public float getXStart() {
138     return this.xStart;
139   }
140 
141   /**
142    * Sets the start point abscissa of this wall. Once this wall is updated,
143    * listeners added to this wall will receive a change notification.
144    */
setXStart(float xStart)145   public void setXStart(float xStart) {
146     if (xStart != this.xStart) {
147       float oldXStart = this.xStart;
148       this.xStart = xStart;
149       clearPointsCache();
150       this.arcCircleCenterCache = null;
151       firePropertyChange(Property.X_START.name(), oldXStart, xStart);
152     }
153   }
154 
155   /**
156    * Returns the start point ordinate of this wall.
157    */
getYStart()158   public float getYStart() {
159     return this.yStart;
160   }
161 
162   /**
163    * Sets the start point ordinate of this wall. Once this wall is updated,
164    * listeners added to this wall will receive a change notification.
165    */
setYStart(float yStart)166   public void setYStart(float yStart) {
167     if (yStart != this.yStart) {
168       float oldYStart = this.yStart;
169       this.yStart = yStart;
170       clearPointsCache();
171       this.arcCircleCenterCache = null;
172       firePropertyChange(Property.Y_START.name(), oldYStart, yStart);
173     }
174   }
175 
176   /**
177    * Returns the end point abscissa of this wall.
178    */
getXEnd()179   public float getXEnd() {
180     return this.xEnd;
181   }
182 
183   /**
184    * Sets the end point abscissa of this wall. Once this wall is updated,
185    * listeners added to this wall will receive a change notification.
186    */
setXEnd(float xEnd)187   public void setXEnd(float xEnd) {
188     if (xEnd != this.xEnd) {
189       float oldXEnd = this.xEnd;
190       this.xEnd = xEnd;
191       clearPointsCache();
192       this.arcCircleCenterCache = null;
193       firePropertyChange(Property.X_END.name(), oldXEnd, xEnd);
194     }
195   }
196 
197   /**
198    * Returns the end point ordinate of this wall.
199    */
getYEnd()200   public float getYEnd() {
201     return this.yEnd;
202   }
203 
204   /**
205    * Sets the end point ordinate of this wall. Once this wall is updated,
206    * listeners added to this wall will receive a change notification.
207    */
setYEnd(float yEnd)208   public void setYEnd(float yEnd) {
209     if (yEnd != this.yEnd) {
210       float oldYEnd = this.yEnd;
211       this.yEnd = yEnd;
212       clearPointsCache();
213       this.arcCircleCenterCache = null;
214       firePropertyChange(Property.Y_END.name(), oldYEnd, yEnd);
215     }
216   }
217 
218   /**
219    * Returns the length of this wall.
220    * @since 2.0
221    */
getLength()222   public float getLength() {
223     if (this.arcExtent == null
224         || this.arcExtent.floatValue() == 0) {
225       return (float)Point2D.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
226     } else {
227       float [] arcCircleCenter = getArcCircleCenter();
228       float arcCircleRadius = (float)Point2D.distance(this.xStart, this.yStart,
229           arcCircleCenter [0], arcCircleCenter [1]);
230       return Math.abs(this.arcExtent) * arcCircleRadius;
231     }
232   }
233 
234   /**
235    * Returns the distance from the start point of this wall to its end point.
236    * @since 3.0
237    */
getStartPointToEndPointDistance()238   public float getStartPointToEndPointDistance() {
239     return (float)Point2D.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
240   }
241 
242   /**
243    * Sets the arc extent of a round wall.
244    * @since 3.0
245    */
setArcExtent(Float arcExtent)246   public void setArcExtent(Float arcExtent) {
247     if (arcExtent != this.arcExtent
248         && (arcExtent == null || !arcExtent.equals(this.arcExtent))) {
249       Float oldArcExtent = this.arcExtent;
250       this.arcExtent = arcExtent;
251       clearPointsCache();
252       this.arcCircleCenterCache = null;
253       firePropertyChange(Property.ARC_EXTENT.name(), oldArcExtent, arcExtent);
254     }
255   }
256 
257   /**
258    * Returns the arc extent of a round wall or <code>null</code> if this wall isn't round.
259    * @since 3.0
260    */
getArcExtent()261   public Float getArcExtent() {
262     return this.arcExtent;
263   }
264 
265   /**
266    * Returns the abscissa of the arc circle center of this wall.
267    * If the wall isn't round, the return abscissa is at the middle of the wall.
268    * @since 3.0
269    */
getXArcCircleCenter()270   public float getXArcCircleCenter() {
271     if (this.arcExtent == null) {
272       return (this.xStart + this.xEnd) / 2;
273     } else {
274       return getArcCircleCenter() [0];
275     }
276   }
277 
278   /**
279    * Returns the ordinate of the arc circle center of this wall.
280    * If the wall isn't round, the return ordinate is at the middle of the wall.
281    * @since 3.0
282    */
getYArcCircleCenter()283   public float getYArcCircleCenter() {
284     if (this.arcExtent == null) {
285       return (this.yStart + this.yEnd) / 2;
286     } else {
287       return getArcCircleCenter() [1];
288     }
289   }
290 
291   /**
292    * Returns the coordinates of the arc circle center of this wall.
293    */
getArcCircleCenter()294   private float [] getArcCircleCenter() {
295     if (this.arcCircleCenterCache == null) {
296       double startToEndPointsDistance = Point2D.distance(this.xStart, this.yStart, this.xEnd, this.yEnd);
297       double wallToStartPointArcCircleCenterAngle = Math.abs(this.arcExtent) > Math.PI
298           ? -(Math.PI + this.arcExtent) / 2
299           : (Math.PI - this.arcExtent) / 2;
300       float arcCircleCenterToWallDistance = -(float)(Math.tan(wallToStartPointArcCircleCenterAngle)
301           * startToEndPointsDistance / 2);
302       float xMiddlePoint = (this.xStart + this.xEnd) / 2;
303       float yMiddlePoint = (this.yStart + this.yEnd) / 2;
304       double angle = Math.atan2(this.xStart - this.xEnd, this.yEnd - this.yStart);
305       this.arcCircleCenterCache = new float [] {
306           (float)(xMiddlePoint + arcCircleCenterToWallDistance * Math.cos(angle)),
307           (float)(yMiddlePoint + arcCircleCenterToWallDistance * Math.sin(angle))};
308     }
309     return this.arcCircleCenterCache;
310   }
311 
312   /**
313    * Returns the wall joined to this wall at start point.
314    */
getWallAtStart()315   public Wall getWallAtStart() {
316     return this.wallAtStart;
317   }
318 
319   /**
320    * Sets the wall joined to this wall at start point. Once this wall is updated,
321    * listeners added to this wall will receive a change notification.
322    * If the start point of this wall is attached to an other wall, it will be detached
323    * from this wall, and wall listeners will receive a change notification.
324    * @param wallAtStart a wall or <code>null</code> to detach this wall
325    *          from any wall it was attached to before.
326    */
setWallAtStart(Wall wallAtStart)327   public void setWallAtStart(Wall wallAtStart) {
328     setWallAtStart(wallAtStart, true);
329   }
330 
331   /**
332    * Sets the wall joined to this wall at start point and detachs the wall at start
333    * from this wall if <code>detachJoinedWallAtStart</code> is true.
334    */
setWallAtStart(Wall wallAtStart, boolean detachJoinedWallAtStart)335   private void setWallAtStart(Wall wallAtStart, boolean detachJoinedWallAtStart) {
336     if (wallAtStart != this.wallAtStart) {
337       Wall oldWallAtStart = this.wallAtStart;
338       this.wallAtStart = wallAtStart;
339       clearPointsCache();
340       firePropertyChange(Property.WALL_AT_START.name(), oldWallAtStart, wallAtStart);
341 
342       if (detachJoinedWallAtStart) {
343         detachJoinedWall(oldWallAtStart);
344       }
345     }
346   }
347 
348   /**
349    * Returns the wall joined to this wall at end point.
350    */
getWallAtEnd()351   public Wall getWallAtEnd() {
352     return this.wallAtEnd;
353   }
354 
355 
356   /**
357    * Sets the wall joined to this wall at end point. Once this wall is updated,
358    * listeners added to this wall will receive a change notification.
359    * If the end point of this wall is attached to an other wall, it will be detached
360    * from this wall, and wall listeners will receive a change notification.
361    * @param wallAtEnd a wall or <code>null</code> to detach this wall
362    *          from any wall it was attached to before.
363    */
setWallAtEnd(Wall wallAtEnd)364   public void setWallAtEnd(Wall wallAtEnd) {
365     setWallAtEnd(wallAtEnd, true);
366   }
367 
368   /**
369    * Sets the wall joined to this wall at end point and detachs the wall at end
370    * from this wall if <code>detachJoinedWallAtEnd</code> is true.
371    */
setWallAtEnd(Wall wallAtEnd, boolean detachJoinedWallAtEnd)372   private void setWallAtEnd(Wall wallAtEnd, boolean detachJoinedWallAtEnd) {
373     if (wallAtEnd != this.wallAtEnd) {
374       Wall oldWallAtEnd = this.wallAtEnd;
375       this.wallAtEnd = wallAtEnd;
376       clearPointsCache();
377       firePropertyChange(Property.WALL_AT_END.name(), oldWallAtEnd, wallAtEnd);
378 
379       if (detachJoinedWallAtEnd) {
380         detachJoinedWall(oldWallAtEnd);
381       }
382     }
383   }
384 
385   /**
386    * Detaches <code>joinedWall</code> from this wall.
387    */
detachJoinedWall(Wall joinedWall)388   private void detachJoinedWall(Wall joinedWall) {
389     // Detach the previously attached wall
390     if (joinedWall != null) {
391       if (joinedWall.getWallAtStart() == this) {
392         joinedWall.setWallAtStart(null, false);
393       } else if (joinedWall.getWallAtEnd() == this) {
394         joinedWall.setWallAtEnd(null, false);
395       }
396     }
397   }
398 
399   /**
400    * Returns the thickness of this wall.
401    */
getThickness()402   public float getThickness() {
403     return this.thickness;
404   }
405 
406   /**
407    * Sets wall thickness. Once this wall is updated,
408    * listeners added to this wall will receive a change notification.
409    */
setThickness(float thickness)410   public void setThickness(float thickness) {
411     if (thickness != this.thickness) {
412       float oldThickness = this.thickness;
413       this.thickness = thickness;
414       clearPointsCache();
415       firePropertyChange(Property.THICKNESS.name(), oldThickness, thickness);
416     }
417   }
418 
419   /**
420    * Returns the height of this wall. If {@link #getHeightAtEnd() getHeightAtEnd}
421    * returns a value not <code>null</code>, the returned height should be
422    * considered as the height of this wall at its start point.
423    */
getHeight()424   public Float getHeight() {
425     return this.height;
426   }
427 
428   /**
429    * Sets the height of this wall. Once this wall is updated,
430    * listeners added to this wall will receive a change notification.
431    */
setHeight(Float height)432   public void setHeight(Float height) {
433     if (height != this.height
434         && (height == null || !height.equals(this.height))) {
435       Float oldHeight = this.height;
436       this.height = height;
437       firePropertyChange(Property.HEIGHT.name(), oldHeight, height);
438     }
439   }
440 
441   /**
442    * Returns the height of this wall at its end point.
443    */
getHeightAtEnd()444   public Float getHeightAtEnd() {
445     return this.heightAtEnd;
446   }
447 
448   /**
449    * Sets the height of this wall at its end point. Once this wall is updated,
450    * listeners added to this wall will receive a change notification.
451    */
setHeightAtEnd(Float heightAtEnd)452   public void setHeightAtEnd(Float heightAtEnd) {
453     if (heightAtEnd != this.heightAtEnd
454         && (heightAtEnd == null || !heightAtEnd.equals(this.heightAtEnd))) {
455       Float oldHeightAtEnd = this.heightAtEnd;
456       this.heightAtEnd = heightAtEnd;
457       firePropertyChange(Property.HEIGHT_AT_END.name(), oldHeightAtEnd, heightAtEnd);
458     }
459   }
460 
461   /**
462    * Returns <code>true</code> if the height of this wall is different
463    * at its start and end points.
464    */
isTrapezoidal()465   public boolean isTrapezoidal() {
466     return this.height != null
467         && this.heightAtEnd != null
468         && !this.height.equals(this.heightAtEnd);
469   }
470 
471   /**
472    * Returns left side color of this wall. This is the color of the left side
473    * of this wall when you go through wall from start point to end point.
474    */
getLeftSideColor()475   public Integer getLeftSideColor() {
476     return this.leftSideColor;
477   }
478 
479   /**
480    * Sets left side color of this wall. Once this wall is updated,
481    * listeners added to this wall will receive a change notification.
482    */
setLeftSideColor(Integer leftSideColor)483   public void setLeftSideColor(Integer leftSideColor) {
484     if (leftSideColor != this.leftSideColor
485         && (leftSideColor == null || !leftSideColor.equals(this.leftSideColor))) {
486       Integer oldLeftSideColor = this.leftSideColor;
487       this.leftSideColor = leftSideColor;
488       firePropertyChange(Property.LEFT_SIDE_COLOR.name(), oldLeftSideColor, leftSideColor);
489     }
490   }
491 
492   /**
493    * Returns right side color of this wall. This is the color of the right side
494    * of this wall when you go through wall from start point to end point.
495    */
getRightSideColor()496   public Integer getRightSideColor() {
497     return this.rightSideColor;
498   }
499 
500   /**
501    * Sets right side color of this wall. Once this wall is updated,
502    * listeners added to this wall will receive a change notification.
503    */
setRightSideColor(Integer rightSideColor)504   public void setRightSideColor(Integer rightSideColor) {
505     if (rightSideColor != this.rightSideColor
506         && (rightSideColor == null || !rightSideColor.equals(this.rightSideColor))) {
507       Integer oldLeftSideColor = this.rightSideColor;
508       this.rightSideColor = rightSideColor;
509       firePropertyChange(Property.RIGHT_SIDE_COLOR.name(), oldLeftSideColor, rightSideColor);
510     }
511   }
512 
513 
514   /**
515    * Returns the left side texture of this wall.
516    */
getLeftSideTexture()517   public HomeTexture getLeftSideTexture() {
518     return this.leftSideTexture;
519   }
520 
521   /**
522    * Sets the left side texture of this wall. Once this wall is updated,
523    * listeners added to this wall will receive a change notification.
524    */
setLeftSideTexture(HomeTexture leftSideTexture)525   public void setLeftSideTexture(HomeTexture leftSideTexture) {
526     if (leftSideTexture != this.leftSideTexture
527         && (leftSideTexture == null || !leftSideTexture.equals(this.leftSideTexture))) {
528       HomeTexture oldLeftSideTexture = this.leftSideTexture;
529       this.leftSideTexture = leftSideTexture;
530       firePropertyChange(Property.LEFT_SIDE_TEXTURE.name(), oldLeftSideTexture, leftSideTexture);
531     }
532   }
533 
534   /**
535    * Returns the right side texture of this wall.
536    */
getRightSideTexture()537   public HomeTexture getRightSideTexture() {
538     return this.rightSideTexture;
539   }
540 
541   /**
542    * Sets the right side texture of this wall. Once this wall is updated,
543    * listeners added to this wall will receive a change notification.
544    */
setRightSideTexture(HomeTexture rightSideTexture)545   public void setRightSideTexture(HomeTexture rightSideTexture) {
546     if (rightSideTexture != this.rightSideTexture
547         && (rightSideTexture == null || !rightSideTexture.equals(this.rightSideTexture))) {
548       HomeTexture oldLeftSideTexture = this.rightSideTexture;
549       this.rightSideTexture = rightSideTexture;
550       firePropertyChange(Property.RIGHT_SIDE_TEXTURE.name(), oldLeftSideTexture, rightSideTexture);
551     }
552   }
553 
554   /**
555    * Returns the left side shininess of this wall.
556    * @return a value between 0 (matt) and 1 (very shiny)
557    * @since 3.0
558    */
getLeftSideShininess()559   public float getLeftSideShininess() {
560     return this.leftSideShininess;
561   }
562 
563   /**
564    * Sets the left side shininess of this wall. Once this wall is updated,
565    * listeners added to this wall will receive a change notification.
566    * @since 3.0
567    */
setLeftSideShininess(float leftSideShininess)568   public void setLeftSideShininess(float leftSideShininess) {
569     if (leftSideShininess != this.leftSideShininess) {
570       float oldLeftSideShininess = this.leftSideShininess;
571       this.leftSideShininess = leftSideShininess;
572       firePropertyChange(Property.LEFT_SIDE_SHININESS.name(), oldLeftSideShininess, leftSideShininess);
573     }
574   }
575 
576   /**
577    * Returns the right side shininess of this wall.
578    * @return a value between 0 (matt) and 1 (very shiny)
579    * @since 3.0
580    */
getRightSideShininess()581   public float getRightSideShininess() {
582     return this.rightSideShininess;
583   }
584 
585   /**
586    * Sets the right side shininess of this wall. Once this wall is updated,
587    * listeners added to this wall will receive a change notification.
588    * @since 3.0
589    */
setRightSideShininess(float rightSideShininess)590   public void setRightSideShininess(float rightSideShininess) {
591     if (rightSideShininess != this.rightSideShininess) {
592       float oldRightSideShininess = this.rightSideShininess;
593       this.rightSideShininess = rightSideShininess;
594       firePropertyChange(Property.RIGHT_SIDE_SHININESS.name(), oldRightSideShininess, rightSideShininess);
595     }
596   }
597 
598   /**
599    * Returns the left side baseboard of this wall.
600    * @since 5.0
601    */
getLeftSideBaseboard()602   public Baseboard getLeftSideBaseboard() {
603     return this.leftSideBaseboard;
604   }
605 
606   /**
607    * Sets the left side baseboard of this wall. Once this wall is updated,
608    * listeners added to this wall will receive a change notification.
609    * @since 5.0
610    */
setLeftSideBaseboard(Baseboard leftSideBaseboard)611   public void setLeftSideBaseboard(Baseboard leftSideBaseboard) {
612     if (leftSideBaseboard != this.leftSideBaseboard
613         && (leftSideBaseboard == null || !leftSideBaseboard.equals(this.leftSideBaseboard))) {
614       Baseboard oldLeftSideBaseboard = this.leftSideBaseboard;
615       this.leftSideBaseboard = leftSideBaseboard;
616       clearPointsCache();
617       firePropertyChange(Property.LEFT_SIDE_BASEBOARD.name(), oldLeftSideBaseboard, leftSideBaseboard);
618     }
619   }
620 
621   /**
622    * Returns the right side baseboard of this wall.
623    * @since 5.0
624    */
getRightSideBaseboard()625   public Baseboard getRightSideBaseboard() {
626     return this.rightSideBaseboard;
627   }
628 
629   /**
630    * Sets the right side baseboard of this wall. Once this wall is updated,
631    * listeners added to this wall will receive a change notification.
632    * @since 5.0
633    */
setRightSideBaseboard(Baseboard rightSideBaseboard)634   public void setRightSideBaseboard(Baseboard rightSideBaseboard) {
635     if (rightSideBaseboard != this.rightSideBaseboard
636         && (rightSideBaseboard == null || !rightSideBaseboard.equals(this.rightSideBaseboard))) {
637       Baseboard oldRightSideBaseboard = this.rightSideBaseboard;
638       this.rightSideBaseboard = rightSideBaseboard;
639       clearPointsCache();
640       firePropertyChange(Property.RIGHT_SIDE_BASEBOARD.name(), oldRightSideBaseboard, rightSideBaseboard);
641     }
642   }
643 
644   /**
645    * Returns the pattern of this wall in the plan.
646    * @since 3.3
647    */
getPattern()648   public TextureImage getPattern() {
649     return this.pattern;
650   }
651 
652   /**
653    * Sets the pattern of this wall in the plan, and notifies
654    * listeners of this change.
655    * @since 3.3
656    */
setPattern(TextureImage pattern)657   public void setPattern(TextureImage pattern) {
658     if (this.pattern != pattern) {
659       TextureImage oldPattern = this.pattern;
660       this.pattern = pattern;
661       firePropertyChange(Property.PATTERN.name(), oldPattern, pattern);
662     }
663   }
664 
665   /**
666    * Returns the color of the top of this wall in the 3D view.
667    * @since 4.0
668    */
getTopColor()669   public Integer getTopColor() {
670     return this.topColor;
671   }
672 
673   /**
674    * Sets the color of the top of this wall in the 3D view, and notifies
675    * listeners of this change.
676    * @since 4.0
677    */
setTopColor(Integer topColor)678   public void setTopColor(Integer topColor) {
679     if (this.topColor != topColor
680         && (topColor == null || !topColor.equals(this.topColor))) {
681       Integer oldTopColor = this.topColor;
682       this.topColor = topColor;
683       firePropertyChange(Property.TOP_COLOR.name(), oldTopColor, topColor);
684     }
685   }
686 
687   /**
688    * Returns the level which this wall belongs to.
689    * @since 3.4
690    */
getLevel()691   public Level getLevel() {
692     return this.level;
693   }
694 
695   /**
696    * Sets the level of this wall. Once this wall is updated,
697    * listeners added to this wall will receive a change notification.
698    * @since 3.4
699    */
setLevel(Level level)700   public void setLevel(Level level) {
701     if (level != this.level) {
702       Level oldLevel = this.level;
703       this.level = level;
704       firePropertyChange(Property.LEVEL.name(), oldLevel, level);
705     }
706   }
707 
708   /**
709    * Returns <code>true</code> if this wall is at the given <code>level</code>
710    * or at a level with the same elevation and a smaller elevation index
711    * or if the elevation of its highest point is higher than <code>level</code> elevation.
712    * @since 3.4
713    */
isAtLevel(Level level)714   public boolean isAtLevel(Level level) {
715     if (this.level == level) {
716       return true;
717     } else if (this.level != null && level != null) {
718       float wallLevelElevation = this.level.getElevation();
719       float levelElevation = level.getElevation();
720       return wallLevelElevation == levelElevation
721              && this.level.getElevationIndex() < level.getElevationIndex()
722           || wallLevelElevation < levelElevation
723              && wallLevelElevation + getWallMaximumHeight() > levelElevation;
724     } else {
725       return false;
726     }
727   }
728 
729   /**
730    * Returns the maximum height of the given wall.
731    */
getWallMaximumHeight()732   private float getWallMaximumHeight() {
733     if (this.height == null) {
734       // Shouldn't happen
735       return 0;
736     } else if (isTrapezoidal()) {
737       return Math.max(this.height, this.heightAtEnd);
738     } else {
739       return this.height;
740     }
741   }
742 
743   /**
744    * Clears the points cache of this wall and of the walls attached to it.
745    */
clearPointsCache()746   private void clearPointsCache() {
747     this.shapeCache = null;
748     this.pointsCache = null;
749     this.pointsIncludingBaseboardsCache = null;
750     if (this.wallAtStart != null ) {
751       this.wallAtStart.pointsCache = null;
752       this.wallAtStart.pointsIncludingBaseboardsCache = null;
753     }
754     if (this.wallAtEnd != null) {
755       this.wallAtEnd.pointsCache = null;
756       this.wallAtEnd.pointsIncludingBaseboardsCache = null;
757     }
758   }
759 
760   /**
761    * Returns the points of each corner of a wall not including its baseboards.
762    * @return an array of the (x,y) coordinates of the wall corners.
763    *    For a straight wall, the points at index 0 and 3 indicates the start of the wall,
764    *    while the points at index 1 and 2 indicates the end of the wall.
765    */
getPoints()766   public float [][] getPoints() {
767     return getPoints(false);
768   }
769 
770   /**
771    * Returns the points of each corner of a wall possibly including its baseboards.
772    * @since 5.0
773    */
getPoints(boolean includeBaseboards)774   public float [][] getPoints(boolean includeBaseboards) {
775     if (includeBaseboards
776         && (this.leftSideBaseboard != null
777             || this.rightSideBaseboard != null)) {
778       if (this.pointsIncludingBaseboardsCache == null) {
779         this.pointsIncludingBaseboardsCache = getShapePoints(true);
780       }
781       return clonePoints(this.pointsIncludingBaseboardsCache);
782     } else {
783       if (this.pointsCache == null) {
784         this.pointsCache = getShapePoints(false);
785       }
786       return clonePoints(this.pointsCache);
787     }
788   }
789 
790   /**
791    * Return a clone of the given <code>points</code> array.
792    */
clonePoints(float [][] points)793   private float [][] clonePoints(float [][] points) {
794     float [][] clonedPoints = new float [points.length][];
795     for (int i = 0; i < points.length; i++) {
796       clonedPoints [i] = points [i].clone();
797     }
798     return clonedPoints;
799   }
800 
801   /**
802    * Returns the points of the wall possibly including baseboards thickness.
803    */
getShapePoints(boolean includeBaseboards)804   private float [][] getShapePoints(boolean includeBaseboards) {
805     final float epsilon = 0.01f;
806     float [][] wallPoints = getUnjoinedShapePoints(includeBaseboards);
807     int leftSideStartPointIndex = 0;
808     int rightSideStartPointIndex = wallPoints.length - 1;
809     int leftSideEndPointIndex = wallPoints.length / 2 - 1;
810     int rightSideEndPointIndex = wallPoints.length / 2;
811     float limit = 2 * this.thickness;
812     // If wall is joined to a wall at its start,
813     // compute the intersection between their outlines
814     if (this.wallAtStart != null) {
815       float [][] wallAtStartPoints = this.wallAtStart.getUnjoinedShapePoints(includeBaseboards);
816       int wallAtStartLeftSideStartPointIndex = 0;
817       int wallAtStartRightSideStartPointIndex = wallAtStartPoints.length - 1;
818       int wallAtStartLeftSideEndPointIndex = wallAtStartPoints.length / 2 - 1;
819       int wallAtStartRightSideEndPointIndex = wallAtStartPoints.length / 2;
820       boolean wallAtStartJoinedAtEnd = this.wallAtStart.getWallAtEnd() == this
821           // Check the coordinates when walls are joined to each other at both ends
822           && (this.wallAtStart.getWallAtStart() != this
823               || (this.wallAtStart.xEnd == this.xStart
824                   && this.wallAtStart.yEnd == this.yStart));
825       boolean wallAtStartJoinedAtStart = this.wallAtStart.getWallAtStart() == this
826           // Check the coordinates when walls are joined to each other at both ends
827           && (this.wallAtStart.getWallAtEnd() != this
828               || (this.wallAtStart.xStart == this.xStart
829                   && this.wallAtStart.yStart == this.yStart));
830       float [][] wallAtStartPointsCache = includeBaseboards
831           ? this.wallAtStart.pointsIncludingBaseboardsCache
832           : this.wallAtStart.pointsCache;
833       if (wallAtStartJoinedAtEnd) {
834         computeIntersection(wallPoints [leftSideStartPointIndex], wallPoints [leftSideStartPointIndex + 1],
835             wallAtStartPoints [wallAtStartLeftSideEndPointIndex], wallAtStartPoints [wallAtStartLeftSideEndPointIndex - 1], limit);
836         computeIntersection(wallPoints [rightSideStartPointIndex], wallPoints [rightSideStartPointIndex - 1],
837             wallAtStartPoints [wallAtStartRightSideEndPointIndex], wallAtStartPoints [wallAtStartRightSideEndPointIndex + 1], limit);
838 
839         // If the computed start point of this wall and the computed end point of the wall at start
840         // are equal to within epsilon, share the exact same point to avoid computing errors on areas
841         if (wallAtStartPointsCache != null) {
842           if (Math.abs(wallPoints [leftSideStartPointIndex][0] - wallAtStartPointsCache [wallAtStartLeftSideEndPointIndex][0]) < epsilon
843               && Math.abs(wallPoints [leftSideStartPointIndex][1] - wallAtStartPointsCache [wallAtStartLeftSideEndPointIndex][1]) < epsilon) {
844             wallPoints [leftSideStartPointIndex] = wallAtStartPointsCache [wallAtStartLeftSideEndPointIndex];
845           }
846           if (Math.abs(wallPoints [rightSideStartPointIndex][0] - wallAtStartPointsCache [wallAtStartRightSideEndPointIndex][0]) < epsilon
847               && Math.abs(wallPoints [rightSideStartPointIndex][1] - wallAtStartPointsCache [wallAtStartRightSideEndPointIndex][1]) < epsilon) {
848             wallPoints [rightSideStartPointIndex] = wallAtStartPointsCache [wallAtStartRightSideEndPointIndex];
849           }
850         }
851       } else if (wallAtStartJoinedAtStart) {
852         computeIntersection(wallPoints [leftSideStartPointIndex], wallPoints [leftSideStartPointIndex + 1],
853             wallAtStartPoints [wallAtStartRightSideStartPointIndex], wallAtStartPoints [wallAtStartRightSideStartPointIndex - 1], limit);
854         computeIntersection(wallPoints [rightSideStartPointIndex], wallPoints [rightSideStartPointIndex - 1],
855             wallAtStartPoints [wallAtStartLeftSideStartPointIndex], wallAtStartPoints [wallAtStartLeftSideStartPointIndex + 1], limit);
856 
857         // If the computed start point of this wall and the computed start point of the wall at start
858         // are equal to within epsilon, share the exact same point to avoid computing errors on areas
859         if (wallAtStartPointsCache != null) {
860           if (Math.abs(wallPoints [leftSideStartPointIndex][0] - wallAtStartPointsCache [wallAtStartRightSideStartPointIndex][0]) < epsilon
861               && Math.abs(wallPoints [leftSideStartPointIndex][1] - wallAtStartPointsCache [wallAtStartRightSideStartPointIndex][1]) < epsilon) {
862             wallPoints [leftSideStartPointIndex] = wallAtStartPointsCache [wallAtStartRightSideStartPointIndex];
863           }
864           if (wallAtStartPointsCache != null
865               && Math.abs(wallPoints [rightSideStartPointIndex][0] - wallAtStartPointsCache [wallAtStartLeftSideStartPointIndex][0]) < epsilon
866               && Math.abs(wallPoints [rightSideStartPointIndex][1] - wallAtStartPointsCache [wallAtStartLeftSideStartPointIndex][1]) < epsilon) {
867             wallPoints [rightSideStartPointIndex] = wallAtStartPointsCache [wallAtStartLeftSideStartPointIndex];
868           }
869         }
870       }
871     }
872 
873     // If wall is joined to a wall at its end,
874     // compute the intersection between their outlines
875     if (this.wallAtEnd != null) {
876       float [][] wallAtEndPoints = this.wallAtEnd.getUnjoinedShapePoints(includeBaseboards);
877       int wallAtEndLeftSideStartPointIndex = 0;
878       int wallAtEndRightSideStartPointIndex = wallAtEndPoints.length - 1;
879       int wallAtEndLeftSideEndPointIndex = wallAtEndPoints.length / 2 - 1;
880       int wallAtEndRightSideEndPointIndex = wallAtEndPoints.length / 2;
881       boolean wallAtEndJoinedAtStart = this.wallAtEnd.getWallAtStart() == this
882           // Check the coordinates when walls are joined to each other at both ends
883           && (this.wallAtEnd.getWallAtEnd() != this
884               || (this.wallAtEnd.xStart == this.xEnd
885                   && this.wallAtEnd.yStart == this.yEnd));
886       boolean wallAtEndJoinedAtEnd = this.wallAtEnd.getWallAtEnd() == this
887           // Check the coordinates when walls are joined to each other at both ends
888           && (this.wallAtEnd.getWallAtStart() != this
889               || (this.wallAtEnd.xEnd == this.xEnd
890                   && this.wallAtEnd.yEnd == this.yEnd));
891       float [][] wallAtEndPointsCache = includeBaseboards
892           ? this.wallAtEnd.pointsIncludingBaseboardsCache
893           : this.wallAtEnd.pointsCache;
894       if (wallAtEndJoinedAtStart) {
895         computeIntersection(wallPoints [leftSideEndPointIndex], wallPoints [leftSideEndPointIndex - 1],
896             wallAtEndPoints [wallAtEndLeftSideStartPointIndex], wallAtEndPoints [wallAtEndLeftSideStartPointIndex + 1], limit);
897         computeIntersection(wallPoints [rightSideEndPointIndex], wallPoints [rightSideEndPointIndex + 1],
898             wallAtEndPoints [wallAtEndRightSideStartPointIndex], wallAtEndPoints [wallAtEndRightSideStartPointIndex - 1], limit);
899 
900         // If the computed end point of this wall and the computed start point of the wall at end
901         // are equal to within epsilon, share the exact same point to avoid computing errors on areas
902         if (wallAtEndPointsCache != null) {
903           if (Math.abs(wallPoints [leftSideEndPointIndex][0] - wallAtEndPointsCache [wallAtEndLeftSideStartPointIndex][0]) < epsilon
904               && Math.abs(wallPoints [leftSideEndPointIndex][1] - wallAtEndPointsCache [wallAtEndLeftSideStartPointIndex][1]) < epsilon) {
905             wallPoints [leftSideEndPointIndex] = wallAtEndPointsCache [wallAtEndLeftSideStartPointIndex];
906           }
907           if (Math.abs(wallPoints [rightSideEndPointIndex][0] - wallAtEndPointsCache [wallAtEndRightSideStartPointIndex][0]) < epsilon
908               && Math.abs(wallPoints [rightSideEndPointIndex][1] - wallAtEndPointsCache [wallAtEndRightSideStartPointIndex][1]) < epsilon) {
909             wallPoints [rightSideEndPointIndex] = wallAtEndPointsCache [wallAtEndRightSideStartPointIndex];
910           }
911         }
912       } else if (wallAtEndJoinedAtEnd) {
913         computeIntersection(wallPoints [leftSideEndPointIndex], wallPoints [leftSideEndPointIndex - 1],
914             wallAtEndPoints [wallAtEndRightSideEndPointIndex], wallAtEndPoints [wallAtEndRightSideEndPointIndex + 1], limit);
915         computeIntersection(wallPoints [rightSideEndPointIndex], wallPoints [rightSideEndPointIndex + 1],
916             wallAtEndPoints [wallAtEndLeftSideEndPointIndex], wallAtEndPoints [wallAtEndLeftSideEndPointIndex - 1], limit);
917 
918         // If the computed end point of this wall and the computed start point of the wall at end
919         // are equal to within epsilon, share the exact same point to avoid computing errors on areas
920         if (wallAtEndPointsCache != null) {
921           if (Math.abs(wallPoints [leftSideEndPointIndex][0] - wallAtEndPointsCache [wallAtEndRightSideEndPointIndex][0]) < epsilon
922               && Math.abs(wallPoints [leftSideEndPointIndex][1] - wallAtEndPointsCache [wallAtEndRightSideEndPointIndex][1]) < epsilon) {
923             wallPoints [leftSideEndPointIndex] = wallAtEndPointsCache [wallAtEndRightSideEndPointIndex];
924           }
925           if (Math.abs(wallPoints [rightSideEndPointIndex][0] - wallAtEndPointsCache [wallAtEndLeftSideEndPointIndex][0]) < epsilon
926               && Math.abs(wallPoints [rightSideEndPointIndex][1] - wallAtEndPointsCache [wallAtEndLeftSideEndPointIndex][1]) < epsilon) {
927             wallPoints [rightSideEndPointIndex] = wallAtEndPointsCache [wallAtEndLeftSideEndPointIndex];
928           }
929         }
930       }
931     }
932     return wallPoints;
933   }
934 
935   /**
936    * Computes the rectangle or the circle arc of a wall according to its thickness
937    * and possibly the thickness of its baseboards.
938    */
getUnjoinedShapePoints(boolean includeBaseboards)939   private float [][] getUnjoinedShapePoints(boolean includeBaseboards) {
940     if (this.arcExtent != null
941         && this.arcExtent.floatValue() != 0
942         && Point2D.distanceSq(this.xStart, this.yStart, this.xEnd, this.yEnd) > 1E-10) {
943       float [] arcCircleCenter = getArcCircleCenter();
944       float startAngle = (float)Math.atan2(arcCircleCenter [1] - this.yStart, arcCircleCenter [0] - this.xStart);
945       startAngle += 2 * (float)Math.atan2(this.yStart - this.yEnd, this.xEnd - this.xStart);
946       float arcCircleRadius = (float)Point2D.distance(arcCircleCenter [0], arcCircleCenter [1], this.xStart, this.yStart);
947       float exteriorArcRadius = arcCircleRadius + this.thickness / 2;
948       float interiorArcRadius = Math.max(0, arcCircleRadius - this.thickness / 2);
949       float exteriorArcLength = exteriorArcRadius * Math.abs(this.arcExtent);
950       float angleDelta = this.arcExtent / (float)Math.sqrt(exteriorArcLength);
951       int angleStepCount = (int)(this.arcExtent / angleDelta);
952       if (includeBaseboards) {
953         if (angleDelta > 0) {
954           if (this.leftSideBaseboard != null) {
955             exteriorArcRadius += this.leftSideBaseboard.getThickness();
956           }
957           if (this.rightSideBaseboard != null) {
958             interiorArcRadius -= this.rightSideBaseboard.getThickness();
959           }
960         } else {
961           if (this.leftSideBaseboard != null) {
962             interiorArcRadius -= this.leftSideBaseboard.getThickness();
963           }
964           if (this.rightSideBaseboard != null) {
965             exteriorArcRadius += this.rightSideBaseboard.getThickness();
966           }
967         }
968       }
969       List<float[]> wallPoints = new ArrayList<float[]>((angleStepCount + 2) * 2);
970       if (this.symmetric) {
971         if (Math.abs(this.arcExtent - angleStepCount * angleDelta) > 1E-6) {
972           angleDelta = this.arcExtent / ++angleStepCount;
973         }
974         for (int i = 0; i <= angleStepCount; i++) {
975           computeRoundWallShapePoint(wallPoints, startAngle + this.arcExtent - i * angleDelta, i, angleDelta,
976               arcCircleCenter, exteriorArcRadius, interiorArcRadius);
977         }
978       } else {
979         // Don't change the way walls were computed in version 3.0 to ensure they exactly look the same
980         // (as symmetric has no API to modify its value, this case may happen  only for unserialized walls)
981         int i = 0;
982         for (float angle = this.arcExtent; angleDelta > 0 ? angle >= angleDelta * 0.1f : angle <= -angleDelta * 0.1f; angle -= angleDelta, i++) {
983           computeRoundWallShapePoint(wallPoints, startAngle + angle, i, angleDelta,
984               arcCircleCenter, exteriorArcRadius, interiorArcRadius);
985         }
986         computeRoundWallShapePoint(wallPoints, startAngle, i, angleDelta,
987             arcCircleCenter, exteriorArcRadius, interiorArcRadius);
988       }
989       return wallPoints.toArray(new float [wallPoints.size()][]);
990     } else {
991       double angle = Math.atan2(this.yEnd - this.yStart,
992                                 this.xEnd - this.xStart);
993       float sin = (float)Math.sin(angle);
994       float cos = (float)Math.cos(angle);
995       float leftSideTickness = this.thickness / 2;
996       if (includeBaseboards && this.leftSideBaseboard != null) {
997         leftSideTickness += this.leftSideBaseboard.getThickness();
998       }
999       float leftSideDx = sin * leftSideTickness;
1000       float leftSideDy = cos * leftSideTickness;
1001       float rightSideTickness = this.thickness / 2;
1002       if (includeBaseboards && this.rightSideBaseboard != null) {
1003         rightSideTickness += this.rightSideBaseboard.getThickness();
1004       }
1005       float rightSideDx = sin * rightSideTickness;
1006       float rightSideDy = cos * rightSideTickness;
1007       return new float [][] {
1008           {this.xStart + leftSideDx, this.yStart - leftSideDy},
1009           {this.xEnd   + leftSideDx, this.yEnd   - leftSideDy},
1010           {this.xEnd   - rightSideDx, this.yEnd   + rightSideDy},
1011           {this.xStart - rightSideDx, this.yStart + rightSideDy}};
1012     }
1013   }
1014 
1015   /**
1016    * Computes the exterior and interior arc points of a round wall at the given <code>index</code>.
1017    */
computeRoundWallShapePoint(List<float []> wallPoints, float angle, int index, float angleDelta, float [] arcCircleCenter, float exteriorArcRadius, float interiorArcRadius)1018   private void computeRoundWallShapePoint(List<float []> wallPoints, float angle, int index, float angleDelta,
1019                                           float [] arcCircleCenter, float exteriorArcRadius, float interiorArcRadius) {
1020     double cos = Math.cos(angle);
1021     double sin = Math.sin(angle);
1022     float [] interiorArcPoint = new float [] {(float)(arcCircleCenter [0] + interiorArcRadius * cos),
1023                                               (float)(arcCircleCenter [1] - interiorArcRadius * sin)};
1024     float [] exteriorArcPoint = new float [] {(float)(arcCircleCenter [0] + exteriorArcRadius * cos),
1025                                               (float)(arcCircleCenter [1] - exteriorArcRadius * sin)};
1026     if (angleDelta > 0) {
1027       wallPoints.add(index, interiorArcPoint);
1028       wallPoints.add(wallPoints.size() - 1 - index, exteriorArcPoint);
1029     } else {
1030       wallPoints.add(index, exteriorArcPoint);
1031       wallPoints.add(wallPoints.size() - 1 - index, interiorArcPoint);
1032     }
1033   }
1034 
1035   /**
1036    * Compute the intersection between the line that joins <code>point1</code> to <code>point2</code>
1037    * and the line that joins <code>point3</code> and <code>point4</code>, and stores the result
1038    * in <code>point1</code>.
1039    */
computeIntersection(float [] point1, float [] point2, float [] point3, float [] point4, float limit)1040   private void computeIntersection(float [] point1, float [] point2,
1041                                    float [] point3, float [] point4, float limit) {
1042     float alpha1 = (point2 [1] - point1 [1]) / (point2 [0] - point1 [0]);
1043     float alpha2 = (point4 [1] - point3 [1]) / (point4 [0] - point3 [0]);
1044     // If the two lines are not parallel
1045     if (alpha1 != alpha2) {
1046       float x = point1 [0];
1047       float y = point1 [1];
1048 
1049       // If first line is vertical
1050       if (Math.abs(alpha1) > 4000)  {
1051         if (Math.abs(alpha2) < 4000) {
1052           x = point1 [0];
1053           float beta2  = point4 [1] - alpha2 * point4 [0];
1054           y = alpha2 * x + beta2;
1055         }
1056       // If second line is vertical
1057       } else if (Math.abs(alpha2) > 4000) {
1058         if (Math.abs(alpha1) < 4000) {
1059           x = point3 [0];
1060           float beta1  = point2 [1] - alpha1 * point2 [0];
1061           y = alpha1 * x + beta1;
1062         }
1063       } else {
1064         boolean sameSignum = Math.signum(alpha1) == Math.signum(alpha2);
1065         if (Math.abs(alpha1 - alpha2) > 1E-5
1066             && (!sameSignum || (Math.abs(alpha1) > Math.abs(alpha2)   ? alpha1 / alpha2   : alpha2 / alpha1) > 1.004)) {
1067           float beta1 = point2 [1] - alpha1 * point2 [0];
1068           float beta2 = point4 [1] - alpha2 * point4 [0];
1069           x = (beta2 - beta1) / (alpha1 - alpha2);
1070           y = alpha1 * x + beta1;
1071         }
1072       }
1073 
1074       if (Point2D.distanceSq(x, y, point1 [0], point1 [1]) < limit * limit) {
1075         point1 [0] = x;
1076         point1 [1] = y;
1077       }
1078     }
1079   }
1080 
1081   /**
1082    * Returns <code>true</code> if this wall intersects
1083    * with the horizontal rectangle which opposite corners are at points
1084    * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>).
1085    */
intersectsRectangle(float x0, float y0, float x1, float y1)1086   public boolean intersectsRectangle(float x0, float y0, float x1, float y1) {
1087     Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0);
1088     rectangle.add(x1, y1);
1089     return getShape(false).intersects(rectangle);
1090   }
1091 
1092   /**
1093    * Returns <code>true</code> if this wall contains the point at (<code>x</code>, <code>y</code>)
1094    * not including its baseboards, with a given <code>margin</code>.
1095    */
containsPoint(float x, float y, float margin)1096   public boolean containsPoint(float x, float y, float margin) {
1097     return containsPoint(x, y, false, margin);
1098   }
1099 
1100   /**
1101    * Returns <code>true</code> if this wall contains the point at (<code>x</code>, <code>y</code>)
1102    * possibly including its baseboards, with a given <code>margin</code>.
1103    * @since 5.0
1104    */
containsPoint(float x, float y, boolean includeBaseboards, float margin)1105   public boolean containsPoint(float x, float y, boolean includeBaseboards, float margin) {
1106     return containsShapeAtWithMargin(getShape(includeBaseboards), x, y, margin);
1107   }
1108 
1109   /**
1110    * Returns <code>true</code> if the middle point of this wall is the point at (<code>x</code>, <code>y</code>)
1111    * with a given <code>margin</code>.
1112    */
isMiddlePointAt(float x, float y, float margin)1113   public boolean isMiddlePointAt(float x, float y, float margin) {
1114     float [][] wallPoints = getPoints();
1115     int leftSideMiddlePointIndex = wallPoints.length / 4;
1116     int rightSideMiddlePointIndex = wallPoints.length - 1 - leftSideMiddlePointIndex;
1117     Line2D middleLine = wallPoints.length % 4 == 0
1118         ? new Line2D.Float((wallPoints [leftSideMiddlePointIndex - 1][0] + wallPoints [leftSideMiddlePointIndex][0]) / 2,
1119             (wallPoints [leftSideMiddlePointIndex - 1][1] + wallPoints [leftSideMiddlePointIndex][1]) / 2,
1120             (wallPoints [rightSideMiddlePointIndex][0] + wallPoints [rightSideMiddlePointIndex + 1][0]) / 2,
1121             (wallPoints [rightSideMiddlePointIndex][1] + wallPoints [rightSideMiddlePointIndex + 1][1]) / 2)
1122         : new Line2D.Float(wallPoints [leftSideMiddlePointIndex][0], wallPoints [leftSideMiddlePointIndex][1],
1123             wallPoints [rightSideMiddlePointIndex][0], wallPoints [rightSideMiddlePointIndex][1]);
1124     return containsShapeAtWithMargin(middleLine, x, y, margin);
1125   }
1126 
1127   /**
1128    * Returns <code>true</code> if this wall start line contains
1129    * the point at (<code>x</code>, <code>y</code>)
1130    * with a given <code>margin</code> around the wall start line.
1131    */
containsWallStartAt(float x, float y, float margin)1132   public boolean containsWallStartAt(float x, float y, float margin) {
1133     float [][] wallPoints = getPoints();
1134     Line2D startLine = new Line2D.Float(wallPoints [0][0], wallPoints [0][1],
1135         wallPoints [wallPoints.length - 1][0], wallPoints [wallPoints.length - 1][1]);
1136     return containsShapeAtWithMargin(startLine, x, y, margin);
1137   }
1138 
1139   /**
1140    * Returns <code>true</code> if this wall end line contains
1141    * the point at (<code>x</code>, <code>y</code>)
1142    * with a given <code>margin</code> around the wall end line.
1143    */
containsWallEndAt(float x, float y, float margin)1144   public boolean containsWallEndAt(float x, float y, float margin) {
1145     float [][] wallPoints = getPoints();
1146     Line2D endLine = new Line2D.Float(wallPoints [wallPoints.length / 2 - 1][0], wallPoints [wallPoints.length / 2 - 1][1],
1147         wallPoints [wallPoints.length / 2][0], wallPoints [wallPoints.length / 2][1]);
1148     return containsShapeAtWithMargin(endLine, x, y, margin);
1149   }
1150 
1151   /**
1152    * Returns <code>true</code> if <code>shape</code> contains
1153    * the point at (<code>x</code>, <code>y</code>)
1154    * with a given <code>margin</code>.
1155    */
containsShapeAtWithMargin(Shape shape, float x, float y, float margin)1156   private boolean containsShapeAtWithMargin(Shape shape, float x, float y, float margin) {
1157     if (margin == 0) {
1158       return shape.contains(x, y);
1159     } else {
1160       return shape.intersects(x - margin, y - margin, 2 * margin, 2 * margin);
1161     }
1162   }
1163 
1164   /**
1165    * Returns the shape matching this wall.
1166    */
getShape(boolean includeBaseboards)1167   private Shape getShape(boolean includeBaseboards) {
1168     if (this.shapeCache == null) {
1169       float [][] wallPoints = getPoints(includeBaseboards);
1170       GeneralPath wallPath = new GeneralPath();
1171       wallPath.moveTo(wallPoints [0][0], wallPoints [0][1]);
1172       for (int i = 1; i < wallPoints.length; i++) {
1173         wallPath.lineTo(wallPoints [i][0], wallPoints [i][1]);
1174       }
1175       wallPath.closePath();
1176       this.shapeCache = wallPath;
1177     }
1178     return this.shapeCache;
1179   }
1180 
1181   /**
1182    * Moves this wall of (<code>dx</code>, <code>dy</code>) units.
1183    */
move(float dx, float dy)1184   public void move(float dx, float dy) {
1185     setXStart(getXStart() + dx);
1186     setYStart(getYStart() + dy);
1187     setXEnd(getXEnd() + dx);
1188     setYEnd(getYEnd() + dy);
1189   }
1190 
1191   /**
1192    * Returns a duplicate of the <code>walls</code> list. All existing walls
1193    * are copied and their wall at start and end point are set with copied
1194    * walls only if they belong to the returned list.
1195    * The id of duplicated walls are regenerated.
1196    * @since 6.4
1197    */
duplicate(List<Wall> walls)1198   public static List<Wall> duplicate(List<Wall> walls) {
1199     ArrayList<Wall> wallsCopy = new ArrayList<Wall>(walls.size());
1200     // Duplicate walls
1201     for (Wall wall : walls) {
1202       wallsCopy.add((Wall)wall.duplicate());
1203     }
1204     updateBoundWalls(wallsCopy, walls);
1205     return wallsCopy;
1206   }
1207 
1208   /**
1209    * Returns a clone of the <code>walls</code> list. All existing walls
1210    * are copied and their wall at start and end point are set with copied
1211    * walls only if they belong to the returned list.
1212    */
clone(List<Wall> walls)1213   public static List<Wall> clone(List<Wall> walls) {
1214     ArrayList<Wall> wallsCopy = new ArrayList<Wall>(walls.size());
1215     // Clone walls
1216     for (Wall wall : walls) {
1217       wallsCopy.add(wall.clone());
1218     }
1219     updateBoundWalls(wallsCopy, walls);
1220     return wallsCopy;
1221   }
1222 
updateBoundWalls(ArrayList<Wall> wallsCopy, List<Wall> walls)1223   private static void updateBoundWalls(ArrayList<Wall> wallsCopy, List<Wall> walls) {
1224     // Update walls at start and end point in wallsCopy
1225     for (int i = 0; i < walls.size(); i++) {
1226       Wall wall = walls.get(i);
1227       int wallAtStartIndex = walls.indexOf(wall.getWallAtStart());
1228       if (wallAtStartIndex != -1) {
1229         wallsCopy.get(i).setWallAtStart(wallsCopy.get(wallAtStartIndex));
1230       }
1231       int wallAtEndIndex = walls.indexOf(wall.getWallAtEnd());
1232       if (wallAtEndIndex != -1) {
1233         wallsCopy.get(i).setWallAtEnd(wallsCopy.get(wallAtEndIndex));
1234       }
1235     }
1236   }
1237 
1238   /**
1239    * Returns a clone of this wall expected
1240    * its wall at start and wall at end aren't copied.
1241    */
1242   @Override
clone()1243   public Wall clone() {
1244     Wall clone = (Wall)super.clone();
1245     clone.wallAtStart = null;
1246     clone.wallAtEnd = null;
1247     clone.level = null;
1248     clone.shapeCache = null;
1249     clone.pointsCache = null;
1250     clone.pointsIncludingBaseboardsCache = null;
1251     return clone;
1252   }
1253 }
1254