1 /* 2 * Polyline.java 17 juin 2015 3 * 4 * Sweet Home 3D, Copyright (c) 2015 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.BasicStroke; 23 import java.awt.Shape; 24 import java.awt.geom.CubicCurve2D; 25 import java.awt.geom.GeneralPath; 26 import java.awt.geom.PathIterator; 27 import java.awt.geom.Point2D; 28 import java.awt.geom.Rectangle2D; 29 import java.io.IOException; 30 import java.io.ObjectInputStream; 31 import java.util.Arrays; 32 33 /** 34 * A polyline or a polygon in a home plan. 35 * @author Emmanuel Puybaret 36 * @since 5.0 37 */ 38 public class Polyline extends HomeObject implements Selectable, Elevatable { 39 private static final long serialVersionUID = 1L; 40 41 /** 42 * The properties of a polyline that may change. <code>PropertyChangeListener</code>s added 43 * to a polyline will be notified under a property name equal to the string value of one these properties. 44 */ 45 public enum Property {POINTS, THICKNESS, CAP_STYLE, JOIN_STYLE, DASH_STYLE, DASH_OFFSET, DASH_PATTERN, START_ARROW_STYLE, END_ARROW_STYLE, CLOSED_PATH, COLOR, LEVEL, ELEVATION, VISIBLE_IN_3D} 46 47 public enum CapStyle {BUTT, SQUARE, ROUND} 48 49 public enum JoinStyle {BEVEL, MITER, ROUND, CURVED} 50 51 public enum ArrowStyle {NONE, DELTA, OPEN, DISC} 52 53 public enum DashStyle {SOLID, DOT, DASH, DASH_DOT, DASH_DOT_DOT, CUSTOMIZED; 54 /** 55 * Returns an array describing the length of dashes and spaces between them 56 * for a 1 cm thick polyline. 57 */ getDashPattern()58 public float [] getDashPattern() { 59 switch (this) { 60 case SOLID : return new float [] {1f, 0f}; 61 case DOT : return new float [] {1f, 1f}; 62 case DASH : return new float [] {4f, 2f}; 63 case DASH_DOT : return new float [] {8f, 2f, 2f, 2f}; 64 case DASH_DOT_DOT : return new float [] {8f, 2f, 2f, 2f, 2f, 2f}; 65 default : return null; 66 } 67 } 68 } 69 70 private float [][] points; 71 private float thickness; 72 private transient CapStyle capStyle; 73 private String capStyleName; 74 private transient JoinStyle joinStyle; 75 private String joinStyleName; 76 private transient DashStyle dashStyle; 77 private String dashStyleName; 78 private float [] dashPattern; 79 private float dashOffset; 80 private transient ArrowStyle startArrowStyle; 81 private String startArrowStyleName; 82 private transient ArrowStyle endArrowStyle; 83 private String endArrowStyleName; 84 private boolean closedPath; 85 private int color; 86 private Float elevation; 87 private Level level; 88 89 private transient Shape polylinePathCache; 90 private transient Shape shapeCache; 91 92 /** 93 * Creates a polyline from the given coordinates. 94 */ Polyline(float [][] points)95 public Polyline(float [][] points) { 96 this(points, 1, CapStyle.BUTT, JoinStyle.MITER, DashStyle.SOLID, ArrowStyle.NONE, ArrowStyle.NONE, false, 0xFF000000); 97 } 98 99 /** 100 * Creates a polyline from the given coordinates. 101 * @since 6.4 102 */ Polyline(String id, float [][] points)103 public Polyline(String id, float [][] points) { 104 this(id, points, 1, CapStyle.BUTT, JoinStyle.MITER, DashStyle.SOLID, 0f, ArrowStyle.NONE, ArrowStyle.NONE, false, 0xFF000000); 105 } 106 107 /** 108 * Creates a polyline from the given coordinates. 109 */ Polyline(float [][] points, float thickness, CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, ArrowStyle startArrowStyle, ArrowStyle endArrowStyle, boolean closedPath, int color)110 public Polyline(float [][] points, float thickness, 111 CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, 112 ArrowStyle startArrowStyle, ArrowStyle endArrowStyle, 113 boolean closedPath, int color) { 114 this(points, thickness, capStyle, joinStyle, dashStyle, 0f, startArrowStyle, endArrowStyle, closedPath, color); 115 } 116 117 /** 118 * Creates a polyline from the given coordinates. 119 * @since 6.0 120 */ Polyline(float [][] points, float thickness, CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, float dashOffset, ArrowStyle startArrowStyle, ArrowStyle endArrowStyle, boolean closedPath, int color)121 public Polyline(float [][] points, float thickness, 122 CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, 123 float dashOffset, ArrowStyle startArrowStyle, 124 ArrowStyle endArrowStyle, boolean closedPath, int color) { 125 this(createId("polyline"), points, thickness, capStyle, joinStyle, dashStyle, dashOffset, 126 startArrowStyle, endArrowStyle, closedPath, color); 127 } 128 129 /** 130 * Creates a polyline from the given coordinates. 131 * @since 6.4 132 */ Polyline(String id, float [][] points, float thickness, CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, float dashOffset, ArrowStyle startArrowStyle, ArrowStyle endArrowStyle, boolean closedPath, int color)133 public Polyline(String id, float [][] points, float thickness, 134 CapStyle capStyle, JoinStyle joinStyle, DashStyle dashStyle, 135 float dashOffset, ArrowStyle startArrowStyle, 136 ArrowStyle endArrowStyle, boolean closedPath, int color) { 137 super(id); 138 this.points = deepCopy(points); 139 this.thickness = thickness; 140 this.capStyle = capStyle; 141 this.joinStyle = joinStyle; 142 this.dashStyle = dashStyle; 143 this.dashOffset = dashOffset; 144 this.startArrowStyle = startArrowStyle; 145 this.endArrowStyle = endArrowStyle; 146 this.closedPath = closedPath; 147 this.color = color; 148 } 149 150 /** 151 * Initializes polyline transient fields. 152 */ readObject(ObjectInputStream in)153 private void readObject(ObjectInputStream in) throws IOException, ClassNotFoundException { 154 this.capStyle = CapStyle.BUTT; 155 this.joinStyle = JoinStyle.MITER; 156 this.dashStyle = DashStyle.SOLID; 157 this.startArrowStyle = ArrowStyle.NONE; 158 this.endArrowStyle = ArrowStyle.NONE; 159 in.defaultReadObject(); 160 // Read styles from strings 161 try { 162 if (this.capStyleName != null) { 163 this.capStyle = CapStyle.valueOf(this.capStyleName); 164 } 165 } catch (IllegalArgumentException ex) { 166 // Ignore malformed enum constant 167 } 168 try { 169 if (this.joinStyleName != null) { 170 this.joinStyle = JoinStyle.valueOf(this.joinStyleName); 171 } 172 } catch (IllegalArgumentException ex) { 173 // Ignore malformed enum constant 174 } 175 try { 176 if (this.dashStyleName != null) { 177 this.dashStyle = DashStyle.valueOf(this.dashStyleName); 178 } 179 } catch (IllegalArgumentException ex) { 180 // Ignore malformed enum constant 181 } 182 try { 183 if (this.startArrowStyleName != null) { 184 this.startArrowStyle = ArrowStyle.valueOf(this.startArrowStyleName); 185 } 186 } catch (IllegalArgumentException ex) { 187 // Ignore malformed enum constant 188 } 189 try { 190 if (this.endArrowStyleName != null) { 191 this.endArrowStyle = ArrowStyle.valueOf(this.endArrowStyleName); 192 } 193 } catch (IllegalArgumentException ex) { 194 // Ignore malformed enum constant 195 } 196 } 197 198 /** 199 * Returns the points of the polygon matching this polyline. 200 * @return an array of the (x,y) coordinates of the polyline points. 201 */ getPoints()202 public float [][] getPoints() { 203 return deepCopy(this.points); 204 } 205 206 /** 207 * Returns the number of points of the polygon matching this polyline. 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 polyline. Once this polyline 223 * is updated, listeners added to this polyline 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 polyline. 233 */ updatePoints(float [][] points)234 private void updatePoints(float [][] points) { 235 float [][] oldPoints = this.points; 236 this.points = deepCopy(points); 237 this.polylinePathCache = null; 238 this.shapeCache = null; 239 firePropertyChange(Property.POINTS.name(), oldPoints, points); 240 } 241 242 /** 243 * Adds a point at the end of polyline points. 244 */ addPoint(float x, float y)245 public void addPoint(float x, float y) { 246 addPoint(x, y, this.points.length); 247 } 248 249 /** 250 * Adds a point at the given <code>index</code>. 251 * @throws IndexOutOfBoundsException if <code>index</code> is negative or > <code>getPointCount()</code> 252 */ addPoint(float x, float y, int index)253 public void addPoint(float x, float y, int index) { 254 if (index < 0 || index > this.points.length) { 255 throw new IndexOutOfBoundsException("Invalid index " + index); 256 } 257 258 float [][] newPoints = new float [this.points.length + 1][]; 259 System.arraycopy(this.points, 0, newPoints, 0, index); 260 newPoints [index] = new float [] {x, y}; 261 System.arraycopy(this.points, index, newPoints, index + 1, this.points.length - index); 262 263 float [][] oldPoints = this.points; 264 this.points = newPoints; 265 this.polylinePathCache = null; 266 this.shapeCache = null; 267 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 268 } 269 270 /** 271 * Sets the point at the given <code>index</code>. 272 * @throws IndexOutOfBoundsException if <code>index</code> is negative or >= <code>getPointCount()</code> 273 */ setPoint(float x, float y, int index)274 public void setPoint(float x, float y, int index) { 275 if (index < 0 || index >= this.points.length) { 276 throw new IndexOutOfBoundsException("Invalid index " + index); 277 } 278 if (this.points [index][0] != x 279 || this.points [index][1] != y) { 280 float [][] oldPoints = this.points; 281 this.points = deepCopy(this.points); 282 this.points [index][0] = x; 283 this.points [index][1] = y; 284 this.polylinePathCache = null; 285 this.shapeCache = null; 286 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 287 } 288 } 289 290 /** 291 * Removes the point at the given <code>index</code>. 292 * @throws IndexOutOfBoundsException if <code>index</code> is negative or >= <code>getPointCount()</code> 293 */ removePoint(int index)294 public void removePoint(int index) { 295 if (index < 0 || index >= this.points.length) { 296 throw new IndexOutOfBoundsException("Invalid index " + index); 297 } 298 299 float [][] newPoints = new float [this.points.length - 1][]; 300 System.arraycopy(this.points, 0, newPoints, 0, index); 301 System.arraycopy(this.points, index + 1, newPoints, index, this.points.length - index - 1); 302 303 float [][] oldPoints = this.points; 304 this.points = newPoints; 305 this.polylinePathCache = null; 306 this.shapeCache = null; 307 firePropertyChange(Property.POINTS.name(), oldPoints, deepCopy(this.points)); 308 } 309 310 /** 311 * Returns the thickness of this polyline. 312 */ getThickness()313 public float getThickness() { 314 return this.thickness; 315 } 316 317 /** 318 * Sets the thickness of this polyline. 319 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 320 */ setThickness(float thickness)321 public void setThickness(float thickness) { 322 if (thickness != this.thickness) { 323 float oldThickness = this.thickness; 324 this.thickness = thickness; 325 firePropertyChange(Property.THICKNESS.name(), oldThickness, thickness); 326 } 327 } 328 329 /** 330 * Returns the cap style of this polyline. 331 */ getCapStyle()332 public CapStyle getCapStyle() { 333 return this.capStyle; 334 } 335 336 /** 337 * Sets the cap style of this polyline. 338 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 339 */ setCapStyle(CapStyle capStyle)340 public void setCapStyle(CapStyle capStyle) { 341 if (capStyle != this.capStyle) { 342 CapStyle oldStyle = this.capStyle; 343 this.capStyle = capStyle; 344 this.capStyleName = this.capStyle.name(); 345 firePropertyChange(Property.CAP_STYLE.name(), oldStyle, capStyle); 346 } 347 } 348 349 /** 350 * Returns the join style of this polyline. 351 */ getJoinStyle()352 public JoinStyle getJoinStyle() { 353 return this.joinStyle; 354 } 355 356 /** 357 * Sets the join style of this polyline. 358 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 359 */ setJoinStyle(JoinStyle joinStyle)360 public void setJoinStyle(JoinStyle joinStyle) { 361 if (joinStyle != this.joinStyle) { 362 JoinStyle oldJoinStyle = this.joinStyle; 363 this.joinStyle = joinStyle; 364 this.joinStyleName = this.joinStyle.name(); 365 this.polylinePathCache = null; 366 this.shapeCache = null; 367 firePropertyChange(Property.JOIN_STYLE.name(), oldJoinStyle, joinStyle); 368 } 369 } 370 371 /** 372 * Returns the dash style of this polyline. If <code>DashStyle.CUSTOMIZED</code> is returned, 373 * the actual dash pattern will be returned by {@link #getDashPattern()}. 374 */ getDashStyle()375 public DashStyle getDashStyle() { 376 return this.dashStyle; 377 } 378 379 /** 380 * Sets the dash style of this polyline. 381 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 382 */ setDashStyle(DashStyle dashStyle)383 public void setDashStyle(DashStyle dashStyle) { 384 if (dashStyle != this.dashStyle) { 385 float [] oldDashPattern = getDashPattern(); 386 DashStyle oldDashStyle = this.dashStyle; 387 this.dashStyle = dashStyle; 388 this.dashStyleName = this.dashStyle.name(); 389 if (dashStyle != DashStyle.CUSTOMIZED) { 390 this.dashPattern = null; 391 } 392 firePropertyChange(Property.DASH_PATTERN.name(), oldDashPattern, getDashPattern()); 393 firePropertyChange(Property.DASH_STYLE.name(), oldDashStyle, dashStyle); 394 } 395 } 396 397 /** 398 * Returns the dash pattern of this polyline in percentage of its thickness. 399 * @since 6.0 400 */ getDashPattern()401 public float [] getDashPattern() { 402 float [] dashPattern = null; 403 if (this.dashStyle != DashStyle.CUSTOMIZED) { 404 return this.dashStyle.getDashPattern(); 405 } else if (this.dashPattern != null) { 406 dashPattern = this.dashPattern.clone(); 407 } 408 return dashPattern; 409 } 410 411 /** 412 * Sets the dash pattern of this polyline in percentage of its thickness. 413 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 414 * @since 6.0 415 */ setDashPattern(float [] dashPattern)416 public void setDashPattern(float [] dashPattern) { 417 for (DashStyle dashStyle : DashStyle.values()) { 418 if (this.dashStyle != DashStyle.CUSTOMIZED) { 419 // If the given dash pattern matches a default dash style, simply set this dash style 420 if (Arrays.equals(dashPattern, dashStyle.getDashPattern())) { 421 setDashStyle(dashStyle); 422 return; 423 } 424 } 425 } 426 if (!Arrays.equals(dashPattern, this.dashPattern)) { 427 float [] oldDashPattern = getDashPattern(); 428 this.dashPattern = dashPattern.clone(); 429 firePropertyChange(Property.DASH_PATTERN.name(), oldDashPattern, dashPattern); 430 // Always emit a DASH_STYLE change to let existing listeners know that the pattern change 431 DashStyle oldDashStyle = this.dashStyle; 432 this.dashStyle = DashStyle.CUSTOMIZED; 433 this.dashStyleName = this.dashStyle.name(); 434 firePropertyChange(Property.DASH_STYLE.name(), oldDashStyle, DashStyle.CUSTOMIZED); 435 } 436 } 437 438 /** 439 * Returns the offset from which the dash of this polyline should start. 440 * @return the offset in percentage of the dash pattern 441 * @since 6.0 442 */ getDashOffset()443 public float getDashOffset() { 444 return this.dashOffset; 445 } 446 447 /** 448 * Sets the offset from which the dash of this polyline should start. 449 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 450 * @param dashOffset the offset in percentage of the dash pattern 451 * @since 6.0 452 */ setDashOffset(float dashOffset)453 public void setDashOffset(float dashOffset) { 454 if (dashOffset != this.dashOffset) { 455 float oldDashOffset = this.dashOffset; 456 this.dashOffset = dashOffset; 457 firePropertyChange(Property.DASH_OFFSET.name(), oldDashOffset, dashOffset); 458 } 459 } 460 461 /** 462 * Returns the arrow style at the start of this polyline. 463 */ getStartArrowStyle()464 public ArrowStyle getStartArrowStyle() { 465 return this.startArrowStyle; 466 } 467 468 /** 469 * Sets the arrow style at the start of this polyline. 470 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 471 */ setStartArrowStyle(ArrowStyle startArrowStyle)472 public void setStartArrowStyle(ArrowStyle startArrowStyle) { 473 if (startArrowStyle != this.startArrowStyle) { 474 ArrowStyle oldStartArrowStyle = this.startArrowStyle; 475 this.startArrowStyle = startArrowStyle; 476 this.startArrowStyleName = this.startArrowStyle.name(); 477 firePropertyChange(Property.START_ARROW_STYLE.name(), oldStartArrowStyle, startArrowStyle); 478 } 479 } 480 481 /** 482 * Returns the arrow style at the end of this polyline. 483 */ getEndArrowStyle()484 public ArrowStyle getEndArrowStyle() { 485 return this.endArrowStyle; 486 } 487 488 /** 489 * Sets the arrow style at the end of this polyline. 490 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 491 */ setEndArrowStyle(ArrowStyle endArrowStyle)492 public void setEndArrowStyle(ArrowStyle endArrowStyle) { 493 if (endArrowStyle != this.endArrowStyle) { 494 ArrowStyle oldEndArrowStyle = this.endArrowStyle; 495 this.endArrowStyle = endArrowStyle; 496 this.endArrowStyleName = this.endArrowStyle.name(); 497 firePropertyChange(Property.END_ARROW_STYLE.name(), oldEndArrowStyle, endArrowStyle); 498 } 499 } 500 501 /** 502 * Returns <code>true</code> if the first and last points of this polyline should be joined to form a polygon. 503 */ isClosedPath()504 public boolean isClosedPath() { 505 return this.closedPath; 506 } 507 508 /** 509 * Sets whether the first and last points of this polyline should be joined. 510 * Once this polyline is updated, listeners added to this polyline will receive a change notification. 511 */ setClosedPath(boolean closedPath)512 public void setClosedPath(boolean closedPath) { 513 if (closedPath != this.closedPath) { 514 this.closedPath = closedPath; 515 firePropertyChange(Property.CLOSED_PATH.name(), !closedPath, closedPath); 516 } 517 } 518 519 /** 520 * Returns the color of this polyline. 521 */ getColor()522 public int getColor() { 523 return this.color; 524 } 525 526 /** 527 * Sets the color of this polyline. Once this polyline is updated, 528 * listeners added to this polyline will receive a change notification. 529 */ setColor(int color)530 public void setColor(int color) { 531 if (color != this.color) { 532 int oldColor = this.color; 533 this.color = color; 534 firePropertyChange(Property.COLOR.name(), oldColor, color); 535 } 536 } 537 538 /** 539 * Returns the elevation of this polyline 540 * from the ground according to the elevation of its level. 541 * @since 6.0 542 */ getGroundElevation()543 public float getGroundElevation() { 544 float elevation = getElevation(); 545 if (this.level != null) { 546 return elevation + this.level.getElevation(); 547 } else { 548 return elevation; 549 } 550 } 551 552 /** 553 * Returns the elevation of this polyline in 3D. 554 * @since 6.0 555 */ getElevation()556 public float getElevation() { 557 return this.elevation != null ? this.elevation : 0; 558 } 559 560 /** 561 * Sets the elevation of this polyline in 3D. Once this polyline is updated, 562 * listeners added to this polyline will receive a change notification. 563 * @since 6.0 564 */ setElevation(float elevation)565 public void setElevation(float elevation) { 566 if (this.elevation != null && elevation != this.elevation) { 567 float oldElevation = this.elevation; 568 this.elevation = elevation; 569 firePropertyChange(Property.ELEVATION.name(), oldElevation, elevation); 570 } 571 } 572 573 /** 574 * Returns <code>true</code> if this polyline should be displayed in 3D. 575 * @since 6.0 576 */ isVisibleIn3D()577 public boolean isVisibleIn3D() { 578 return this.elevation != null; 579 } 580 581 /** 582 * Sets whether this polyline should be displayed in 3D and fires a <code>PropertyChangeEvent</code>. 583 * @since 6.0 584 */ setVisibleIn3D(boolean visibleIn3D)585 public void setVisibleIn3D(boolean visibleIn3D) { 586 if (visibleIn3D ^ (this.elevation != null)) { 587 this.elevation = visibleIn3D ? Float.valueOf(0) : null; 588 firePropertyChange(Property.VISIBLE_IN_3D.name(), !visibleIn3D, visibleIn3D); 589 } 590 } 591 592 /** 593 * Returns the level which this polyline belongs to. 594 */ getLevel()595 public Level getLevel() { 596 return this.level; 597 } 598 599 /** 600 * Sets the level of this polyline. Once this polyline is updated, 601 * listeners added to this polyline will receive a change notification. 602 */ setLevel(Level level)603 public void setLevel(Level level) { 604 if (level != this.level) { 605 Level oldLevel = this.level; 606 this.level = level; 607 firePropertyChange(Property.LEVEL.name(), oldLevel, level); 608 } 609 } 610 611 /** 612 * Returns <code>true</code> if this polyline is at the given <code>level</code> 613 * or at a level with the same elevation and a smaller elevation index. 614 */ isAtLevel(Level level)615 public boolean isAtLevel(Level level) { 616 return this.level == level 617 || this.level != null && level != null 618 && this.level.getElevation() == level.getElevation() 619 && this.level.getElevationIndex() < level.getElevationIndex(); 620 621 } 622 623 /** 624 * Returns an approximate length of this polyline. 625 */ getLength()626 public float getLength() { 627 float [] firstPoint = new float [2]; 628 float [] previousPoint = new float [2]; 629 float [] point = new float [2]; 630 float length = 0; 631 for (PathIterator it = getPolylinePath().getPathIterator(null, 0.1); !it.isDone(); it.next()) { 632 switch (it.currentSegment(point)) { 633 case PathIterator.SEG_CLOSE : 634 length += Point2D.distance(firstPoint [0], firstPoint [1], previousPoint [0], previousPoint [1]); 635 break; 636 case PathIterator.SEG_MOVETO : 637 System.arraycopy(point, 0, firstPoint, 0, 2); 638 System.arraycopy(point, 0, previousPoint, 0, 2); 639 break; 640 case PathIterator.SEG_LINETO : 641 length += Point2D.distance(previousPoint [0], previousPoint [1], point [0], point [1]); 642 System.arraycopy(point, 0, previousPoint, 0, 2); 643 break; 644 } 645 } 646 return length; 647 } 648 649 /** 650 * Returns <code>true</code> if this polyline intersects 651 * with the horizontal rectangle which opposite corners are at points 652 * (<code>x0</code>, <code>y0</code>) and (<code>x1</code>, <code>y1</code>). 653 */ intersectsRectangle(float x0, float y0, float x1, float y1)654 public boolean intersectsRectangle(float x0, float y0, float x1, float y1) { 655 Rectangle2D rectangle = new Rectangle2D.Float(x0, y0, 0, 0); 656 rectangle.add(x1, y1); 657 return getShape().intersects(rectangle); 658 } 659 660 /** 661 * Returns <code>true</code> if this polyline contains 662 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>. 663 */ containsPoint(float x, float y, float margin)664 public boolean containsPoint(float x, float y, float margin) { 665 return containsShapeAtWithMargin(getShape(), x, y, margin); 666 } 667 668 /** 669 * Returns the index of the point of this polyline equal to 670 * the point at (<code>x</code>, <code>y</code>) with a given <code>margin</code>. 671 * @return the index of the first found point or -1. 672 */ getPointIndexAt(float x, float y, float margin)673 public int getPointIndexAt(float x, float y, float margin) { 674 for (int i = 0; i < this.points.length; i++) { 675 if (Math.abs(x - this.points [i][0]) <= margin && Math.abs(y - this.points [i][1]) <= margin) { 676 return i; 677 } 678 } 679 return -1; 680 } 681 682 /** 683 * Returns <code>true</code> if <code>shape</code> contains 684 * the point at (<code>x</code>, <code>y</code>) 685 * with a given <code>margin</code>. 686 */ containsShapeAtWithMargin(Shape shape, float x, float y, float margin)687 private boolean containsShapeAtWithMargin(Shape shape, float x, float y, float margin) { 688 if (margin == 0) { 689 return shape.contains(x, y); 690 } else { 691 return shape.intersects(x - margin, y - margin, 2 * margin, 2 * margin); 692 } 693 } 694 695 /** 696 * Returns the path matching this polyline. 697 */ getPolylinePath()698 private Shape getPolylinePath() { 699 if (this.polylinePathCache == null) { 700 GeneralPath polylinePath = new GeneralPath(); 701 if (this.joinStyle == JoinStyle.CURVED) { 702 for (int i = 0, n = this.closedPath ? this.points.length : this.points.length - 1; i < n; i++) { 703 CubicCurve2D.Float curve2D = new CubicCurve2D.Float(); 704 float [] previousPoint = this.points [i == 0 ? this.points.length - 1 : i - 1]; 705 float [] point = this.points [i]; 706 float [] nextPoint = this.points [i == this.points.length - 1 ? 0 : i + 1]; 707 float [] vectorToBisectorPoint = new float [] {nextPoint [0] - previousPoint [0], nextPoint [1] - previousPoint [1]}; 708 float [] nextNextPoint = this.points [(i + 2) % this.points.length]; 709 float [] vectorToBisectorNextPoint = new float [] {point[0] - nextNextPoint [0], point[1] - nextNextPoint [1]}; 710 curve2D.setCurve(point[0], point[1], 711 point [0] + (i != 0 || this.closedPath ? vectorToBisectorPoint [0] / 3.625f : 0), 712 point [1] + (i != 0 || this.closedPath ? vectorToBisectorPoint [1] / 3.625f : 0), 713 nextPoint [0] + (i != this.points.length - 2 || this.closedPath ? vectorToBisectorNextPoint [0] / 3.625f : 0), 714 nextPoint [1] + (i != this.points.length - 2 || this.closedPath ? vectorToBisectorNextPoint [1] / 3.625f : 0), 715 nextPoint [0], nextPoint [1]); 716 polylinePath.append(curve2D, true); 717 } 718 } else { 719 polylinePath.moveTo(this.points [0][0], this.points [0][1]); 720 for (int i = 1; i < this.points.length; i++) { 721 polylinePath.lineTo(this.points [i][0], this.points [i][1]); 722 } 723 if (this.closedPath) { 724 polylinePath.closePath(); 725 } 726 } 727 // Cache polylineShape 728 this.polylinePathCache = polylinePath; 729 } 730 return this.polylinePathCache; 731 } 732 733 /** 734 * Returns the shape matching this polyline. 735 */ getShape()736 private Shape getShape() { 737 if (this.shapeCache == null) { 738 this.shapeCache = new BasicStroke(this.thickness).createStrokedShape(getPolylinePath()); 739 } 740 return this.shapeCache; 741 } 742 743 /** 744 * Moves this polyline of (<code>dx</code>, <code>dy</code>) units. 745 */ move(float dx, float dy)746 public void move(float dx, float dy) { 747 if (dx != 0 || dy != 0) { 748 float [][] points = getPoints(); 749 for (int i = 0; i < points.length; i++) { 750 points [i][0] += dx; 751 points [i][1] += dy; 752 } 753 updatePoints(points); 754 } 755 } 756 757 /** 758 * Returns a clone of this polyline. 759 */ 760 @Override clone()761 public Polyline clone() { 762 Polyline clone = (Polyline)super.clone(); 763 clone.level = null; 764 return clone; 765 } 766 } 767