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