1 /* 2 * Room.java 18 nov. 2008 3 * 4 * Sweet Home 3D, Copyright (c) 2008 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.Area; 24 import java.awt.geom.GeneralPath; 25 import java.awt.geom.PathIterator; 26 import java.awt.geom.Rectangle2D; 27 import java.util.ArrayList; 28 import java.util.Arrays; 29 import java.util.List; 30 31 /** 32 * A room or a polygon in a home plan. 33 * @author Emmanuel Puybaret 34 */ 35 public class Room extends HomeObject implements Selectable, Elevatable { 36 /** 37 * The properties of a room that may change. <code>PropertyChangeListener</code>s added 38 * to a room will be notified under a property name equal to the string value of one these properties. 39 */ 40 public enum Property {NAME, NAME_X_OFFSET, NAME_Y_OFFSET, NAME_STYLE, NAME_ANGLE, 41 POINTS, AREA_VISIBLE, AREA_X_OFFSET, AREA_Y_OFFSET, AREA_STYLE, AREA_ANGLE, 42 FLOOR_COLOR, FLOOR_TEXTURE, FLOOR_VISIBLE, FLOOR_SHININESS, 43 CEILING_COLOR, CEILING_TEXTURE, CEILING_VISIBLE, CEILING_SHININESS, LEVEL} 44 45 private static final long serialVersionUID = 1L; 46 47 private static final double TWICE_PI = 2 * Math.PI; 48 49 private String name; 50 private float nameXOffset; 51 private float nameYOffset; 52 private TextStyle nameStyle; 53 private float nameAngle; 54 private float [][] points; 55 private boolean areaVisible; 56 private float areaXOffset; 57 private float areaYOffset; 58 private TextStyle areaStyle; 59 private float areaAngle; 60 private boolean floorVisible; 61 private Integer floorColor; 62 private HomeTexture floorTexture; 63 private float floorShininess; 64 private boolean ceilingVisible; 65 private Integer ceilingColor; 66 private HomeTexture ceilingTexture; 67 private float ceilingShininess; 68 private Level level; 69 70 private transient Shape shapeCache; 71 private transient Float areaCache; 72 73 /** 74 * Creates a room from its name and the given coordinates. 75 */ Room(float [][] points)76 public Room(float [][] points) { 77 this(createId("room"), points); 78 } 79 80 /** 81 * Creates a room from its name and the given coordinates. 82 */ Room(String id, float [][] points)83 public Room(String id, float [][] points) { 84 super(id); 85 if (points.length <= 1) { 86 throw new IllegalStateException("Room points must containt at least two points"); 87 } 88 this.points = deepCopy(points); 89 this.areaVisible = true; 90 this.nameYOffset = -40f; 91 this.floorVisible = true; 92 this.ceilingVisible = true; 93 } 94 95 /** 96 * Returns the name of this room. 97 */ getName()98 public String getName() { 99 return this.name; 100 } 101 102 /** 103 * Sets the name of this room. Once this room is updated, 104 * listeners added to this room will receive a change notification. 105 */ setName(String name)106 public void setName(String name) { 107 if (name != this.name 108 && (name == null || !name.equals(this.name))) { 109 String oldName = this.name; 110 this.name = name; 111 firePropertyChange(Property.NAME.name(), oldName, name); 112 } 113 } 114 115 /** 116 * Returns the distance along x axis applied to room center abscissa 117 * to display room name. 118 */ getNameXOffset()119 public float getNameXOffset() { 120 return this.nameXOffset; 121 } 122 123 /** 124 * Sets the distance along x axis applied to room center abscissa to display room name. 125 * Once this room is updated, listeners added to this room will receive a change notification. 126 */ setNameXOffset(float nameXOffset)127 public void setNameXOffset(float nameXOffset) { 128 if (nameXOffset != this.nameXOffset) { 129 float oldNameXOffset = this.nameXOffset; 130 this.nameXOffset = nameXOffset; 131 firePropertyChange(Property.NAME_X_OFFSET.name(), oldNameXOffset, nameXOffset); 132 } 133 } 134 135 /** 136 * Returns the distance along y axis applied to room center ordinate 137 * to display room name. 138 */ getNameYOffset()139 public float getNameYOffset() { 140 return this.nameYOffset; 141 } 142 143 /** 144 * Sets the distance along y axis applied to room center ordinate to display room name. 145 * Once this room is updated, listeners added to this room will receive a change notification. 146 */ setNameYOffset(float nameYOffset)147 public void setNameYOffset(float nameYOffset) { 148 if (nameYOffset != this.nameYOffset) { 149 float oldNameYOffset = this.nameYOffset; 150 this.nameYOffset = nameYOffset; 151 firePropertyChange(Property.NAME_Y_OFFSET.name(), oldNameYOffset, nameYOffset); 152 } 153 } 154 155 /** 156 * Returns the text style used to display room name. 157 */ getNameStyle()158 public TextStyle getNameStyle() { 159 return this.nameStyle; 160 } 161 162 /** 163 * Sets the text style used to display room name. 164 * Once this room is updated, listeners added to this room will receive a change notification. 165 */ setNameStyle(TextStyle nameStyle)166 public void setNameStyle(TextStyle nameStyle) { 167 if (nameStyle != this.nameStyle) { 168 TextStyle oldNameStyle = this.nameStyle; 169 this.nameStyle = nameStyle; 170 firePropertyChange(Property.NAME_STYLE.name(), oldNameStyle, nameStyle); 171 } 172 } 173 174 /** 175 * Returns the angle in radians used to display the room name. 176 * @since 3.6 177 */ getNameAngle()178 public float getNameAngle() { 179 return this.nameAngle; 180 } 181 182 /** 183 * Sets the angle in radians used to display the room name. Once this piece is updated, 184 * listeners added to this piece will receive a change notification. 185 * @since 3.6 186 */ setNameAngle(float nameAngle)187 public void setNameAngle(float nameAngle) { 188 // Ensure angle is always positive and between 0 and 2 PI 189 nameAngle = (float)((nameAngle % TWICE_PI + TWICE_PI) % TWICE_PI); 190 if (nameAngle != this.nameAngle) { 191 float oldNameAngle = this.nameAngle; 192 this.nameAngle = nameAngle; 193 firePropertyChange(Property.NAME_ANGLE.name(), oldNameAngle, nameAngle); 194 } 195 } 196 197 /** 198 * Returns the points of the polygon matching this room. 199 * @return an array of the (x,y) coordinates of the room points. 200 */ getPoints()201 public float [][] getPoints() { 202 return deepCopy(this.points); 203 } 204 205 /** 206 * Returns the number of points of the polygon matching this room. 207 * @since 2.0 208 */ getPointCount()209 public int getPointCount() { 210 return this.points.length; 211 } 212 deepCopy(float [][] points)213 private float [][] deepCopy(float [][] points) { 214 float [][] pointsCopy = new float [points.length][]; 215 for (int i = 0; i < points.length; i++) { 216 pointsCopy [i] = points [i].clone(); 217 } 218 return pointsCopy; 219 } 220 221 /** 222 * Sets the points of the polygon matching this room. Once this room 223 * is updated, listeners added to this room will receive a change notification. 224 */ setPoints(float [][] points)225 public void setPoints(float [][] points) { 226 if (!Arrays.deepEquals(this.points, points)) { 227 updatePoints(points); 228 } 229 } 230 231 /** 232 * Update the points of the polygon matching this room. 233 */ updatePoints(float [][] points)234 private void updatePoints(float [][] points) { 235 float [][] oldPoints = this.points; 236 this.points = deepCopy(points); 237 this.shapeCache = null; 238 this.areaCache = null; 239 firePropertyChange(Property.POINTS.name(), oldPoints, points); 240 } 241 242 /** 243 * Adds a point at the end of room points. 244 * @since 2.0 245 */ addPoint(float x, float y)246 public void addPoint(float x, float y) { 247 addPoint(x, y, this.points.length); 248 } 249 250 /** 251 * Adds a point at the given <code>index</code>. 252 * @throws IndexOutOfBoundsException if <code>index</code> is negative or > <code>getPointCount()</code> 253 * @since 2.0 254 */ addPoint(float x, float y, int index)255 public void addPoint(float x, float y, int index) { 256 if (index < 0 || index > this.points.length) { 257 throw new IndexOutOfBoundsException("Invalid index " + index); 258 } 259 260 float [][] newPoints = new float [this.points.length + 1][]; 261 System.arraycopy(this.points, 0, newPoints, 0, index); 262 newPoints [index] = new float [] {x, y}; 263 System.arraycopy(this.points, index, newPoints, index + 1, this.points.length - index); 264 265 float [][] oldPoints = this.points; 266 this.points = newPoints; 267 this.shapeCache = null; 268 this.areaCache = null; 269 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 270 } 271 272 /** 273 * Sets the point at the given <code>index</code>. 274 * @throws IndexOutOfBoundsException if <code>index</code> is negative or >= <code>getPointCount()</code> 275 * @since 2.0 276 */ setPoint(float x, float y, int index)277 public void setPoint(float x, float y, int index) { 278 if (index < 0 || index >= this.points.length) { 279 throw new IndexOutOfBoundsException("Invalid index " + index); 280 } 281 if (this.points [index][0] != x 282 || this.points [index][1] != y) { 283 float [][] oldPoints = this.points; 284 this.points = deepCopy(this.points); 285 this.points [index][0] = x; 286 this.points [index][1] = y; 287 this.shapeCache = null; 288 this.areaCache = null; 289 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 290 } 291 } 292 293 /** 294 * Removes the point at the given <code>index</code>. 295 * @throws IndexOutOfBoundsException if <code>index</code> is negative or >= <code>getPointCount()</code> 296 * @since 2.0 297 */ removePoint(int index)298 public void removePoint(int index) { 299 if (index < 0 || index >= this.points.length) { 300 throw new IndexOutOfBoundsException("Invalid index " + index); 301 } else if (this.points.length <= 1) { 302 throw new IllegalStateException("Room points must containt at least one point"); 303 } 304 305 float [][] newPoints = new float [this.points.length - 1][]; 306 System.arraycopy(this.points, 0, newPoints, 0, index); 307 System.arraycopy(this.points, index + 1, newPoints, index, this.points.length - index - 1); 308 309 float [][] oldPoints = this.points; 310 this.points = newPoints; 311 this.shapeCache = null; 312 this.areaCache = null; 313 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 314 } 315 316 /** 317 * Returns whether the area of this room is visible or not. 318 */ isAreaVisible()319 public boolean isAreaVisible() { 320 return this.areaVisible; 321 } 322 323 /** 324 * Sets whether the area of this room is visible or not. Once this room 325 * is updated, listeners added to this room will receive a change notification. 326 */ setAreaVisible(boolean areaVisible)327 public void setAreaVisible(boolean areaVisible) { 328 if (areaVisible != this.areaVisible) { 329 this.areaVisible = areaVisible; 330 firePropertyChange(Property.AREA_VISIBLE.name(), !areaVisible, areaVisible); 331 } 332 } 333 334 /** 335 * Returns the distance along x axis applied to room center abscissa 336 * to display room area. 337 */ getAreaXOffset()338 public float getAreaXOffset() { 339 return this.areaXOffset; 340 } 341 342 /** 343 * Sets the distance along x axis applied to room center abscissa to display room area. 344 * Once this room is updated, listeners added to this room will receive a change notification. 345 */ setAreaXOffset(float areaXOffset)346 public void setAreaXOffset(float areaXOffset) { 347 if (areaXOffset != this.areaXOffset) { 348 float oldAreaXOffset = this.areaXOffset; 349 this.areaXOffset = areaXOffset; 350 firePropertyChange(Property.AREA_X_OFFSET.name(), oldAreaXOffset, areaXOffset); 351 } 352 } 353 354 /** 355 * Returns the distance along y axis applied to room center ordinate 356 * to display room area. 357 */ getAreaYOffset()358 public float getAreaYOffset() { 359 return this.areaYOffset; 360 } 361 362 /** 363 * Sets the distance along y axis applied to room center ordinate to display room area. 364 * Once this room is updated, listeners added to this room will receive a change notification. 365 */ setAreaYOffset(float areaYOffset)366 public void setAreaYOffset(float areaYOffset) { 367 if (areaYOffset != this.areaYOffset) { 368 float oldAreaYOffset = this.areaYOffset; 369 this.areaYOffset = areaYOffset; 370 firePropertyChange(Property.AREA_Y_OFFSET.name(), oldAreaYOffset, areaYOffset); 371 } 372 } 373 374 /** 375 * Returns the text style used to display room area. 376 */ getAreaStyle()377 public TextStyle getAreaStyle() { 378 return this.areaStyle; 379 } 380 381 /** 382 * Sets the text style used to display room area. 383 * Once this room is updated, listeners added to this room will receive a change notification. 384 */ setAreaStyle(TextStyle areaStyle)385 public void setAreaStyle(TextStyle areaStyle) { 386 if (areaStyle != this.areaStyle) { 387 TextStyle oldAreaStyle = this.areaStyle; 388 this.areaStyle = areaStyle; 389 firePropertyChange(Property.AREA_STYLE.name(), oldAreaStyle, areaStyle); 390 } 391 } 392 393 /** 394 * Returns the angle in radians used to display the room area. 395 * @since 3.6 396 */ getAreaAngle()397 public float getAreaAngle() { 398 return this.areaAngle; 399 } 400 401 /** 402 * Sets the angle in radians used to display the room area. Once this piece is updated, 403 * listeners added to this piece will receive a change notification. 404 * @since 3.6 405 */ setAreaAngle(float areaAngle)406 public void setAreaAngle(float areaAngle) { 407 // Ensure angle is always positive and between 0 and 2 PI 408 areaAngle = (float)((areaAngle % TWICE_PI + TWICE_PI) % TWICE_PI); 409 if (areaAngle != this.areaAngle) { 410 float oldAreaAngle = this.areaAngle; 411 this.areaAngle = areaAngle; 412 firePropertyChange(Property.AREA_ANGLE.name(), oldAreaAngle, areaAngle); 413 } 414 } 415 416 /** 417 * Returns the abscissa of the center point of this room. 418 */ getXCenter()419 public float getXCenter() { 420 float xMin = this.points [0][0]; 421 float xMax = this.points [0][0]; 422 for (int i = 1; i < this.points.length; i++) { 423 xMin = Math.min(xMin, this.points [i][0]); 424 xMax = Math.max(xMax, this.points [i][0]); 425 } 426 return (xMin + xMax) / 2; 427 } 428 429 /** 430 * Returns the ordinate of the center point of this room. 431 */ getYCenter()432 public float getYCenter() { 433 float yMin = this.points [0][1]; 434 float yMax = this.points [0][1]; 435 for (int i = 1; i < this.points.length; i++) { 436 yMin = Math.min(yMin, this.points [i][1]); 437 yMax = Math.max(yMax, this.points [i][1]); 438 } 439 return (yMin + yMax) / 2; 440 } 441 442 /** 443 * Returns the floor color of this room. 444 */ getFloorColor()445 public Integer getFloorColor() { 446 return this.floorColor; 447 } 448 449 /** 450 * Sets the floor color of this room. Once this room is updated, 451 * listeners added to this room will receive a change notification. 452 */ setFloorColor(Integer floorColor)453 public void setFloorColor(Integer floorColor) { 454 if (floorColor != this.floorColor 455 && (floorColor == null || !floorColor.equals(this.floorColor))) { 456 Integer oldFloorColor = this.floorColor; 457 this.floorColor = floorColor; 458 firePropertyChange(Property.FLOOR_COLOR.name(), oldFloorColor, floorColor); 459 } 460 } 461 462 /** 463 * Returns the floor texture of this room. 464 */ getFloorTexture()465 public HomeTexture getFloorTexture() { 466 return this.floorTexture; 467 } 468 469 /** 470 * Sets the floor texture of this room. Once this room is updated, 471 * listeners added to this room will receive a change notification. 472 */ setFloorTexture(HomeTexture floorTexture)473 public void setFloorTexture(HomeTexture floorTexture) { 474 if (floorTexture != this.floorTexture 475 && (floorTexture == null || !floorTexture.equals(this.floorTexture))) { 476 HomeTexture oldFloorTexture = this.floorTexture; 477 this.floorTexture = floorTexture; 478 firePropertyChange(Property.FLOOR_TEXTURE.name(), oldFloorTexture, floorTexture); 479 } 480 } 481 482 /** 483 * Returns whether the floor of this room is visible or not. 484 */ isFloorVisible()485 public boolean isFloorVisible() { 486 return this.floorVisible; 487 } 488 489 /** 490 * Sets whether the floor of this room is visible or not. Once this room 491 * is updated, listeners added to this room will receive a change notification. 492 */ setFloorVisible(boolean floorVisible)493 public void setFloorVisible(boolean floorVisible) { 494 if (floorVisible != this.floorVisible) { 495 this.floorVisible = floorVisible; 496 firePropertyChange(Property.FLOOR_VISIBLE.name(), !floorVisible, floorVisible); 497 } 498 } 499 500 /** 501 * Returns the floor shininess of this room. 502 * @return a value between 0 (matt) and 1 (very shiny) 503 * @since 3.0 504 */ getFloorShininess()505 public float getFloorShininess() { 506 return this.floorShininess; 507 } 508 509 /** 510 * Sets the floor shininess of this room. Once this room is updated, 511 * listeners added to this room will receive a change notification. 512 * @since 3.0 513 */ setFloorShininess(float floorShininess)514 public void setFloorShininess(float floorShininess) { 515 if (floorShininess != this.floorShininess) { 516 float oldFloorShininess = this.floorShininess; 517 this.floorShininess = floorShininess; 518 firePropertyChange(Property.FLOOR_SHININESS.name(), oldFloorShininess, floorShininess); 519 } 520 } 521 522 /** 523 * Returns the ceiling color color of this room. 524 */ getCeilingColor()525 public Integer getCeilingColor() { 526 return this.ceilingColor; 527 } 528 529 /** 530 * Sets the ceiling color of this room. Once this room is updated, 531 * listeners added to this room will receive a change notification. 532 */ setCeilingColor(Integer ceilingColor)533 public void setCeilingColor(Integer ceilingColor) { 534 if (ceilingColor != this.ceilingColor 535 && (ceilingColor == null || !ceilingColor.equals(this.ceilingColor))) { 536 Integer oldCeilingColor = this.ceilingColor; 537 this.ceilingColor = ceilingColor; 538 firePropertyChange(Property.CEILING_COLOR.name(), oldCeilingColor, ceilingColor); 539 } 540 } 541 542 /** 543 * Returns the ceiling texture of this room. 544 */ getCeilingTexture()545 public HomeTexture getCeilingTexture() { 546 return this.ceilingTexture; 547 } 548 549 /** 550 * Sets the ceiling texture of this room. Once this room is updated, 551 * listeners added to this room will receive a change notification. 552 */ setCeilingTexture(HomeTexture ceilingTexture)553 public void setCeilingTexture(HomeTexture ceilingTexture) { 554 if (ceilingTexture != this.ceilingTexture 555 && (ceilingTexture == null || !ceilingTexture.equals(this.ceilingTexture))) { 556 HomeTexture oldCeilingTexture = this.ceilingTexture; 557 this.ceilingTexture = ceilingTexture; 558 firePropertyChange(Property.CEILING_TEXTURE.name(), oldCeilingTexture, ceilingTexture); 559 } 560 } 561 562 /** 563 * Returns whether the ceiling of this room is visible or not. 564 */ isCeilingVisible()565 public boolean isCeilingVisible() { 566 return this.ceilingVisible; 567 } 568 569 /** 570 * Sets whether the ceiling of this room is visible or not. Once this room 571 * is updated, listeners added to this room will receive a change notification. 572 */ setCeilingVisible(boolean ceilingVisible)573 public void setCeilingVisible(boolean ceilingVisible) { 574 if (ceilingVisible != this.ceilingVisible) { 575 this.ceilingVisible = ceilingVisible; 576 firePropertyChange(Property.CEILING_VISIBLE.name(), !ceilingVisible, ceilingVisible); 577 } 578 } 579 580 /** 581 * Returns the ceiling shininess of this room. 582 * @return a value between 0 (matt) and 1 (very shiny) 583 * @since 3.0 584 */ getCeilingShininess()585 public float getCeilingShininess() { 586 return this.ceilingShininess; 587 } 588 589 /** 590 * Sets the ceiling shininess of this room. Once this room is updated, 591 * listeners added to this room will receive a change notification. 592 * @since 3.0 593 */ setCeilingShininess(float ceilingShininess)594 public void setCeilingShininess(float ceilingShininess) { 595 if (ceilingShininess != this.ceilingShininess) { 596 float oldCeilingShininess = this.ceilingShininess; 597 this.ceilingShininess = ceilingShininess; 598 firePropertyChange(Property.CEILING_SHININESS.name(), oldCeilingShininess, ceilingShininess); 599 } 600 } 601 602 /** 603 * Returns the level which this room belongs to. 604 * @since 3.4 605 */ getLevel()606 public Level getLevel() { 607 return this.level; 608 } 609 610 /** 611 * Sets the level of this room. Once this room is updated, 612 * listeners added to this room will receive a change notification. 613 * @since 3.4 614 */ setLevel(Level level)615 public void setLevel(Level level) { 616 if (level != this.level) { 617 Level oldLevel = this.level; 618 this.level = level; 619 firePropertyChange(Property.LEVEL.name(), oldLevel, level); 620 } 621 } 622 623 /** 624 * Returns <code>true</code> if this room is at the given <code>level</code> 625 * or at a level with the same elevation and a smaller elevation index. 626 * @since 3.4 627 */ isAtLevel(Level level)628 public boolean isAtLevel(Level level) { 629 return this.level == level 630 || this.level != null && level != null 631 && this.level.getElevation() == level.getElevation() 632 && this.level.getElevationIndex() < level.getElevationIndex(); 633 } 634 635 /** 636 * Returns the area of this room. 637 */ getArea()638 public float getArea() { 639 if (this.areaCache == null) { 640 Area roomArea = new Area(getShape()); 641 if (roomArea.isSingular()) { 642 this.areaCache = Math.abs(getSignedArea(getPoints())); 643 } else { 644 // Add the surface of the different polygons of this room 645 float area = 0; 646 List<float []> currentPathPoints = new ArrayList<float[]>(); 647 for (PathIterator it = roomArea.getPathIterator(null); !it.isDone(); ) { 648 float [] roomPoint = new float[2]; 649 switch (it.currentSegment(roomPoint)) { 650 case PathIterator.SEG_MOVETO : 651 currentPathPoints.add(roomPoint); 652 break; 653 case PathIterator.SEG_LINETO : 654 currentPathPoints.add(roomPoint); 655 break; 656 case PathIterator.SEG_CLOSE : 657 float [][] pathPoints = 658 currentPathPoints.toArray(new float [currentPathPoints.size()][]); 659 area += getSignedArea(pathPoints); 660 currentPathPoints.clear(); 661 break; 662 } 663 it.next(); 664 } 665 this.areaCache = area; 666 } 667 } 668 return this.areaCache; 669 } 670 getSignedArea(float areaPoints [][])671 private float getSignedArea(float areaPoints [][]) { 672 // From "Area of a General Polygon" algorithm described in 673 // http://www.davidchandler.com/AreaOfAGeneralPolygon.pdf 674 double area = 0; // Compute in double to avoid precision loss with complex areas 675 for (int i = 1; i < areaPoints.length; i++) { 676 area += (double)areaPoints [i][0] * areaPoints [i - 1][1]; 677 area -= (double)areaPoints [i][1] * areaPoints [i - 1][0]; 678 } 679 area += (double)areaPoints [0][0] * areaPoints [areaPoints.length - 1][1]; 680 area -= (double)areaPoints [0][1] * areaPoints [areaPoints.length - 1][0]; 681 return (float)area / 2; 682 } 683 684 /** 685 * Returns <code>true</code> if the points of this room are in clockwise order. 686 */ isClockwise()687 public boolean isClockwise() { 688 return getSignedArea(getPoints()) < 0; 689 } 690 691 /** 692 * Returns <code>true</code> if this room is comprised of only one polygon. 693 */ isSingular()694 public boolean isSingular() { 695 return new Area(getShape()).isSingular(); 696 } 697 698 /** 699 * Returns <code>true</code> if this room intersects 700 * with the horizontal rectangle which opposite corners are at points 701 * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>). 702 */ intersectsRectangle(float x0, float y0, float x1, float y1)703 public boolean intersectsRectangle(float x0, float y0, float x1, float y1) { 704 Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0); 705 rectangle.add(x1, y1); 706 return getShape().intersects(rectangle); 707 } 708 709 /** 710 * Returns <code>true</code> if this room contains 711 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>. 712 */ containsPoint(float x, float y, float margin)713 public boolean containsPoint(float x, float y, float margin) { 714 return containsShapeAtWithMargin(getShape(), x, y, margin); 715 } 716 717 /** 718 * Returns the index of the point of this room equal to 719 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>. 720 * @return the index of the first found point or -1. 721 */ getPointIndexAt(float x, float y, float margin)722 public int getPointIndexAt(float x, float y, float margin) { 723 for (int i = 0; i < this.points.length; i++) { 724 if (Math.abs(x - this.points [i][0]) <= margin && Math.abs(y - this.points [i][1]) <= margin) { 725 return i; 726 } 727 } 728 return -1; 729 } 730 731 /** 732 * Returns <code>true</code> if the center point at which is displayed the name 733 * of this room is equal to the point at (<code>x</code>, <code>y</code>) 734 * with a given <code>margin</code>. 735 */ isNameCenterPointAt(float x, float y, float margin)736 public boolean isNameCenterPointAt(float x, float y, float margin) { 737 return Math.abs(x - getXCenter() - getNameXOffset()) <= margin 738 && Math.abs(y - getYCenter() - getNameYOffset()) <= margin; 739 } 740 741 /** 742 * Returns <code>true</code> if the center point at which is displayed the area 743 * of this room is equal to the point at (<code>x</code>, <code>y</code>) 744 * with a given <code>margin</code>. 745 */ isAreaCenterPointAt(float x, float y, float margin)746 public boolean isAreaCenterPointAt(float x, float y, float margin) { 747 return Math.abs(x - getXCenter() - getAreaXOffset()) <= margin 748 && Math.abs(y - getYCenter() - getAreaYOffset()) <= margin; 749 } 750 751 /** 752 * Returns <code>true</code> if <code>shape</code> contains 753 * the point at (<code>x</code>, <code>y</code>) 754 * with a given <code>margin</code>. 755 */ containsShapeAtWithMargin(Shape shape, float x, float y, float margin)756 private boolean containsShapeAtWithMargin(Shape shape, float x, float y, float margin) { 757 if (margin == 0) { 758 return shape.contains(x, y); 759 } else { 760 return shape.intersects(x - margin, y - margin, 2 * margin, 2 * margin); 761 } 762 } 763 764 /** 765 * Returns the shape matching this room. 766 */ getShape()767 private Shape getShape() { 768 if (this.shapeCache == null) { 769 GeneralPath roomShape = new GeneralPath(); 770 roomShape.moveTo(this.points [0][0], this.points [0][1]); 771 for (int i = 1; i < this.points.length; i++) { 772 roomShape.lineTo(this.points [i][0], this.points [i][1]); 773 } 774 roomShape.closePath(); 775 // Cache roomShape 776 this.shapeCache = roomShape; 777 } 778 return this.shapeCache; 779 } 780 781 /** 782 * Moves this room of (<code>dx</code>, <code>dy</code>) units. 783 */ move(float dx, float dy)784 public void move(float dx, float dy) { 785 if (dx != 0 || dy != 0) { 786 float [][] points = getPoints(); 787 for (int i = 0; i < points.length; i++) { 788 points [i][0] += dx; 789 points [i][1] += dy; 790 } 791 updatePoints(points); 792 } 793 } 794 795 /** 796 * Returns a clone of this room. 797 */ 798 @Override clone()799 public Room clone() { 800 Room clone = (Room)super.clone(); 801 clone.level = null; 802 return clone; 803 } 804 } 805