001 /* Arc2D.java -- represents an arc in 2-D space 002 Copyright (C) 2002, 2003, 2004 Free Software Foundation 003 004 This file is part of GNU Classpath. 005 006 GNU Classpath is free software; you can redistribute it and/or modify 007 it under the terms of the GNU General Public License as published by 008 the Free Software Foundation; either version 2, or (at your option) 009 any later version. 010 011 GNU Classpath is distributed in the hope that it will be useful, but 012 WITHOUT ANY WARRANTY; without even the implied warranty of 013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014 General Public License for more details. 015 016 You should have received a copy of the GNU General Public License 017 along with GNU Classpath; see the file COPYING. If not, write to the 018 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 019 02110-1301 USA. 020 021 Linking this library statically or dynamically with other modules is 022 making a combined work based on this library. Thus, the terms and 023 conditions of the GNU General Public License cover the whole 024 combination. 025 026 As a special exception, the copyright holders of this library give you 027 permission to link this library with independent modules to produce an 028 executable, regardless of the license terms of these independent 029 modules, and to copy and distribute the resulting executable under 030 terms of your choice, provided that you also meet, for each linked 031 independent module, the terms and conditions of the license of that 032 module. An independent module is a module which is not derived from 033 or based on this library. If you modify this library, you may extend 034 this exception to your version of the library, but you are not 035 obligated to do so. If you do not wish to do so, delete this 036 exception statement from your version. */ 037 038 package java.awt.geom; 039 040 import java.util.NoSuchElementException; 041 042 043 /** 044 * This class represents all arcs (segments of an ellipse in 2-D space). The 045 * arcs are defined by starting angle and extent (arc length) in degrees, as 046 * opposed to radians (like the rest of Java), and can be open, chorded, or 047 * wedge shaped. The angles are skewed according to the ellipse, so that 45 048 * degrees always points to the upper right corner (positive x, negative y) 049 * of the bounding rectangle. A positive extent draws a counterclockwise arc, 050 * and while the angle can be any value, the path iterator only traverses the 051 * first 360 degrees. Storage is up to the subclasses. 052 * 053 * @author Eric Blake (ebb9@email.byu.edu) 054 * @author Sven de Marothy (sven@physto.se) 055 * @since 1.2 056 */ 057 public abstract class Arc2D extends RectangularShape 058 { 059 /** 060 * An open arc, with no segment connecting the endpoints. This type of 061 * arc still contains the same points as a chorded version. 062 */ 063 public static final int OPEN = 0; 064 065 /** 066 * A closed arc with a single segment connecting the endpoints (a chord). 067 */ 068 public static final int CHORD = 1; 069 070 /** 071 * A closed arc with two segments, one from each endpoint, meeting at the 072 * center of the ellipse. 073 */ 074 public static final int PIE = 2; 075 076 /** The closure type of this arc. This is package-private to avoid an 077 * accessor method. */ 078 int type; 079 080 /** 081 * Create a new arc, with the specified closure type. 082 * 083 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}. 084 * @throws IllegalArgumentException if type is invalid 085 */ 086 protected Arc2D(int type) 087 { 088 if (type < OPEN || type > PIE) 089 throw new IllegalArgumentException(); 090 this.type = type; 091 } 092 093 /** 094 * Get the starting angle of the arc in degrees. 095 * 096 * @return the starting angle 097 * @see #setAngleStart(double) 098 */ 099 public abstract double getAngleStart(); 100 101 /** 102 * Get the extent angle of the arc in degrees. 103 * 104 * @return the extent angle 105 * @see #setAngleExtent(double) 106 */ 107 public abstract double getAngleExtent(); 108 109 /** 110 * Return the closure type of the arc. 111 * 112 * @return the closure type 113 * @see #OPEN 114 * @see #CHORD 115 * @see #PIE 116 * @see #setArcType(int) 117 */ 118 public int getArcType() 119 { 120 return type; 121 } 122 123 /** 124 * Returns the starting point of the arc. 125 * 126 * @return the start point 127 */ 128 public Point2D getStartPoint() 129 { 130 double angle = Math.toRadians(getAngleStart()); 131 double rx = getWidth() / 2; 132 double ry = getHeight() / 2; 133 double x = getX() + rx + rx * Math.cos(angle); 134 double y = getY() + ry - ry * Math.sin(angle); 135 return new Point2D.Double(x, y); 136 } 137 138 /** 139 * Returns the ending point of the arc. 140 * 141 * @return the end point 142 */ 143 public Point2D getEndPoint() 144 { 145 double angle = Math.toRadians(getAngleStart() + getAngleExtent()); 146 double rx = getWidth() / 2; 147 double ry = getHeight() / 2; 148 double x = getX() + rx + rx * Math.cos(angle); 149 double y = getY() + ry - ry * Math.sin(angle); 150 return new Point2D.Double(x, y); 151 } 152 153 /** 154 * Set the parameters of the arc. The angles are in degrees, and a positive 155 * extent sweeps counterclockwise (from the positive x-axis to the negative 156 * y-axis). 157 * 158 * @param x the new x coordinate of the upper left of the bounding box 159 * @param y the new y coordinate of the upper left of the bounding box 160 * @param w the new width of the bounding box 161 * @param h the new height of the bounding box 162 * @param start the start angle, in degrees 163 * @param extent the arc extent, in degrees 164 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 165 * @throws IllegalArgumentException if type is invalid 166 */ 167 public abstract void setArc(double x, double y, double w, double h, 168 double start, double extent, int type); 169 170 /** 171 * Set the parameters of the arc. The angles are in degrees, and a positive 172 * extent sweeps counterclockwise (from the positive x-axis to the negative 173 * y-axis). 174 * 175 * @param p the upper left point of the bounding box 176 * @param d the dimensions of the bounding box 177 * @param start the start angle, in degrees 178 * @param extent the arc extent, in degrees 179 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 180 * @throws IllegalArgumentException if type is invalid 181 * @throws NullPointerException if p or d is null 182 */ 183 public void setArc(Point2D p, Dimension2D d, double start, double extent, 184 int type) 185 { 186 setArc(p.getX(), p.getY(), d.getWidth(), d.getHeight(), start, extent, type); 187 } 188 189 /** 190 * Set the parameters of the arc. The angles are in degrees, and a positive 191 * extent sweeps counterclockwise (from the positive x-axis to the negative 192 * y-axis). 193 * 194 * @param r the new bounding box 195 * @param start the start angle, in degrees 196 * @param extent the arc extent, in degrees 197 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 198 * @throws IllegalArgumentException if type is invalid 199 * @throws NullPointerException if r is null 200 */ 201 public void setArc(Rectangle2D r, double start, double extent, int type) 202 { 203 setArc(r.getX(), r.getY(), r.getWidth(), r.getHeight(), start, extent, type); 204 } 205 206 /** 207 * Set the parameters of the arc from the given one. 208 * 209 * @param a the arc to copy 210 * @throws NullPointerException if a is null 211 */ 212 public void setArc(Arc2D a) 213 { 214 setArc(a.getX(), a.getY(), a.getWidth(), a.getHeight(), a.getAngleStart(), 215 a.getAngleExtent(), a.getArcType()); 216 } 217 218 /** 219 * Set the parameters of the arc. The angles are in degrees, and a positive 220 * extent sweeps counterclockwise (from the positive x-axis to the negative 221 * y-axis). This controls the center point and radius, so the arc will be 222 * circular. 223 * 224 * @param x the x coordinate of the center of the circle 225 * @param y the y coordinate of the center of the circle 226 * @param r the radius of the circle 227 * @param start the start angle, in degrees 228 * @param extent the arc extent, in degrees 229 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 230 * @throws IllegalArgumentException if type is invalid 231 */ 232 public void setArcByCenter(double x, double y, double r, double start, 233 double extent, int type) 234 { 235 setArc(x - r, y - r, r + r, r + r, start, extent, type); 236 } 237 238 /** 239 * Sets the parameters of the arc by finding the tangents of two lines, and 240 * using the specified radius. The arc will be circular, will begin on the 241 * tangent point of the line extending from p1 to p2, and will end on the 242 * tangent point of the line extending from p2 to p3. 243 * 244 * XXX What happens if the points are colinear, or the radius negative? 245 * 246 * @param p1 the first point 247 * @param p2 the tangent line intersection point 248 * @param p3 the third point 249 * @param r the radius of the arc 250 * @throws NullPointerException if any point is null 251 */ 252 public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3, double r) 253 { 254 if ((p2.getX() - p1.getX()) * (p3.getY() - p1.getY()) 255 - (p3.getX() - p1.getX()) * (p2.getY() - p1.getY()) > 0) 256 { 257 Point2D p = p3; 258 p3 = p1; 259 p1 = p; 260 } 261 262 // normalized tangent vectors 263 double dx1 = (p1.getX() - p2.getX()) / p1.distance(p2); 264 double dy1 = (p1.getY() - p2.getY()) / p1.distance(p2); 265 double dx2 = (p2.getX() - p3.getX()) / p3.distance(p2); 266 double dy2 = (p2.getY() - p3.getY()) / p3.distance(p2); 267 double theta1 = Math.atan2(dx1, dy1); 268 double theta2 = Math.atan2(dx2, dy2); 269 270 double dx = r * Math.cos(theta2) - r * Math.cos(theta1); 271 double dy = -r * Math.sin(theta2) + r * Math.sin(theta1); 272 273 if (theta1 < 0) 274 theta1 += 2 * Math.PI; 275 if (theta2 < 0) 276 theta2 += 2 * Math.PI; 277 if (theta2 < theta1) 278 theta2 += 2 * Math.PI; 279 280 // Vectors of the lines, not normalized, note we change 281 // the direction of line 2. 282 dx1 = p1.getX() - p2.getX(); 283 dy1 = p1.getY() - p2.getY(); 284 dx2 = p3.getX() - p2.getX(); 285 dy2 = p3.getY() - p2.getY(); 286 287 // Calculate the tangent point to the second line 288 double t2 = -(dx1 * dy - dy1 * dx) / (dx2 * dy1 - dx1 * dy2); 289 double x2 = t2 * (p3.getX() - p2.getX()) + p2.getX(); 290 double y2 = t2 * (p3.getY() - p2.getY()) + p2.getY(); 291 292 // calculate the center point 293 double x = x2 - r * Math.cos(theta2); 294 double y = y2 + r * Math.sin(theta2); 295 296 setArc(x - r, y - r, 2 * r, 2 * r, Math.toDegrees(theta1), 297 Math.toDegrees(theta2 - theta1), getArcType()); 298 } 299 300 /** 301 * Set the start, in degrees. 302 * 303 * @param start the new start angle 304 * @see #getAngleStart() 305 */ 306 public abstract void setAngleStart(double start); 307 308 /** 309 * Set the extent, in degrees. 310 * 311 * @param extent the new extent angle 312 * @see #getAngleExtent() 313 */ 314 public abstract void setAngleExtent(double extent); 315 316 /** 317 * Sets the starting angle to the angle of the given point relative to 318 * the center of the arc. The extent remains constant; in other words, 319 * this rotates the arc. 320 * 321 * @param p the new start point 322 * @throws NullPointerException if p is null 323 * @see #getStartPoint() 324 * @see #getAngleStart() 325 */ 326 public void setAngleStart(Point2D p) 327 { 328 // Normalize. 329 double x = p.getX() - (getX() + getWidth() / 2); 330 double y = p.getY() - (getY() + getHeight() / 2); 331 setAngleStart(Math.toDegrees(Math.atan2(-y, x))); 332 } 333 334 /** 335 * Sets the starting and extent angles to those of the given points 336 * relative to the center of the arc. The arc will be non-empty, and will 337 * extend counterclockwise. 338 * 339 * @param x1 the first x coordinate 340 * @param y1 the first y coordinate 341 * @param x2 the second x coordinate 342 * @param y2 the second y coordinate 343 * @see #setAngleStart(Point2D) 344 */ 345 public void setAngles(double x1, double y1, double x2, double y2) 346 { 347 // Normalize the points. 348 double mx = getX(); 349 double my = getY(); 350 double mw = getWidth(); 351 double mh = getHeight(); 352 x1 = x1 - (mx + mw / 2); 353 y1 = y1 - (my + mh / 2); 354 x2 = x2 - (mx + mw / 2); 355 y2 = y2 - (my + mh / 2); 356 double start = Math.toDegrees(Math.atan2(-y1, x1)); 357 double extent = Math.toDegrees(Math.atan2(-y2, x2)) - start; 358 if (extent < 0) 359 extent += 360; 360 setAngleStart(start); 361 setAngleExtent(extent); 362 } 363 364 /** 365 * Sets the starting and extent angles to those of the given points 366 * relative to the center of the arc. The arc will be non-empty, and will 367 * extend counterclockwise. 368 * 369 * @param p1 the first point 370 * @param p2 the second point 371 * @throws NullPointerException if either point is null 372 * @see #setAngleStart(Point2D) 373 */ 374 public void setAngles(Point2D p1, Point2D p2) 375 { 376 setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY()); 377 } 378 379 /** 380 * Set the closure type of this arc. 381 * 382 * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 383 * @throws IllegalArgumentException if type is invalid 384 * @see #getArcType() 385 */ 386 public void setArcType(int type) 387 { 388 if (type < OPEN || type > PIE) 389 throw new IllegalArgumentException(); 390 this.type = type; 391 } 392 393 /** 394 * Sets the location and bounds of the ellipse of which this arc is a part. 395 * 396 * @param x the new x coordinate 397 * @param y the new y coordinate 398 * @param w the new width 399 * @param h the new height 400 * @see #getFrame() 401 */ 402 public void setFrame(double x, double y, double w, double h) 403 { 404 setArc(x, y, w, h, getAngleStart(), getAngleExtent(), type); 405 } 406 407 /** 408 * Gets the bounds of the arc. This is much tighter than 409 * <code>getBounds</code>, as it takes into consideration the start and 410 * end angles, and the center point of a pie wedge, rather than just the 411 * overall ellipse. 412 * 413 * @return the bounds of the arc 414 * @see #getBounds() 415 */ 416 public Rectangle2D getBounds2D() 417 { 418 double extent = getAngleExtent(); 419 if (Math.abs(extent) >= 360) 420 return makeBounds(getX(), getY(), getWidth(), getHeight()); 421 422 // Find the minimal bounding box. This determined by its extrema, 423 // which are the center, the endpoints of the arc, and any local 424 // maximum contained by the arc. 425 double rX = getWidth() / 2; 426 double rY = getHeight() / 2; 427 double centerX = getX() + rX; 428 double centerY = getY() + rY; 429 430 Point2D p1 = getStartPoint(); 431 Rectangle2D result = makeBounds(p1.getX(), p1.getY(), 0, 0); 432 result.add(getEndPoint()); 433 434 if (type == PIE) 435 result.add(centerX, centerY); 436 if (containsAngle(0)) 437 result.add(centerX + rX, centerY); 438 if (containsAngle(90)) 439 result.add(centerX, centerY - rY); 440 if (containsAngle(180)) 441 result.add(centerX - rX, centerY); 442 if (containsAngle(270)) 443 result.add(centerX, centerY + rY); 444 445 return result; 446 } 447 448 /** 449 * Construct a bounding box in a precision appropriate for the subclass. 450 * 451 * @param x the x coordinate 452 * @param y the y coordinate 453 * @param w the width 454 * @param h the height 455 * @return the rectangle for use in getBounds2D 456 */ 457 protected abstract Rectangle2D makeBounds(double x, double y, double w, 458 double h); 459 460 /** 461 * Tests if the given angle, in degrees, is included in the arc. 462 * All angles are normalized to be between 0 and 360 degrees. 463 * 464 * @param a the angle to test 465 * @return true if it is contained 466 */ 467 public boolean containsAngle(double a) 468 { 469 double start = getAngleStart(); 470 double extent = getAngleExtent(); 471 double end = start + extent; 472 473 if (extent == 0) 474 return false; 475 476 if (extent >= 360 || extent <= -360) 477 return true; 478 479 if (extent < 0) 480 { 481 end = start; 482 start += extent; 483 } 484 485 start %= 360; 486 while (start < 0) 487 start += 360; 488 489 end %= 360; 490 while (end < start) 491 end += 360; 492 493 a %= 360; 494 while (a < start) 495 a += 360; 496 497 return a >= start && a < end; // starting angle included, ending angle not 498 } 499 500 /** 501 * Determines if the arc contains the given point. If the bounding box 502 * is empty, then this will return false. 503 * 504 * The area considered 'inside' an arc of type OPEN is the same as the 505 * area inside an equivalent filled CHORD-type arc. The area considered 506 * 'inside' a CHORD-type arc is the same as the filled area. 507 * 508 * @param x the x coordinate to test 509 * @param y the y coordinate to test 510 * @return true if the point is inside the arc 511 */ 512 public boolean contains(double x, double y) 513 { 514 double w = getWidth(); 515 double h = getHeight(); 516 double extent = getAngleExtent(); 517 if (w <= 0 || h <= 0 || extent == 0) 518 return false; 519 520 double mx = getX() + w / 2; 521 double my = getY() + h / 2; 522 double dx = (x - mx) * 2 / w; 523 double dy = (y - my) * 2 / h; 524 if ((dx * dx + dy * dy) >= 1.0) 525 return false; 526 527 double angle = Math.toDegrees(Math.atan2(-dy, dx)); 528 if (getArcType() == PIE) 529 return containsAngle(angle); 530 531 double a1 = Math.toRadians(getAngleStart()); 532 double a2 = Math.toRadians(getAngleStart() + extent); 533 double x1 = mx + getWidth() * Math.cos(a1) / 2; 534 double y1 = my - getHeight() * Math.sin(a1) / 2; 535 double x2 = mx + getWidth() * Math.cos(a2) / 2; 536 double y2 = my - getHeight() * Math.sin(a2) / 2; 537 double sgn = ((x2 - x1) * (my - y1) - (mx - x1) * (y2 - y1)) * ((x2 - x1) * (y 538 - y1) - (x - x1) * (y2 - y1)); 539 540 if (Math.abs(extent) > 180) 541 { 542 if (containsAngle(angle)) 543 return true; 544 return sgn > 0; 545 } 546 else 547 { 548 if (! containsAngle(angle)) 549 return false; 550 return sgn < 0; 551 } 552 } 553 554 /** 555 * Tests if a given rectangle intersects the area of the arc. 556 * 557 * For a definition of the 'inside' area, see the contains() method. 558 * @see #contains(double, double) 559 * 560 * @param x the x coordinate of the rectangle 561 * @param y the y coordinate of the rectangle 562 * @param w the width of the rectangle 563 * @param h the height of the rectangle 564 * @return true if the two shapes share common points 565 */ 566 public boolean intersects(double x, double y, double w, double h) 567 { 568 double extent = getAngleExtent(); 569 if (extent == 0) 570 return false; 571 572 if (contains(x, y) || contains(x, y + h) || contains(x + w, y) 573 || contains(x + w, y + h)) 574 return true; 575 576 Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); 577 578 double a = getWidth() / 2.0; 579 double b = getHeight() / 2.0; 580 581 double mx = getX() + a; 582 double my = getY() + b; 583 double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); 584 double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); 585 double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); 586 double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); 587 588 if (getArcType() != CHORD) 589 { 590 // check intersections against the pie radii 591 if (rect.intersectsLine(mx, my, x1, y1)) 592 return true; 593 if (rect.intersectsLine(mx, my, x2, y2)) 594 return true; 595 } 596 else// check the chord 597 if (rect.intersectsLine(x1, y1, x2, y2)) 598 return true; 599 600 // Check the Arc segment against the four edges 601 double dx; 602 603 // Check the Arc segment against the four edges 604 double dy; 605 dy = y - my; 606 dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); 607 if (! java.lang.Double.isNaN(dx)) 608 { 609 if (mx + dx >= x && mx + dx <= x + w 610 && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) 611 return true; 612 if (mx - dx >= x && mx - dx <= x + w 613 && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) 614 return true; 615 } 616 dy = (y + h) - my; 617 dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); 618 if (! java.lang.Double.isNaN(dx)) 619 { 620 if (mx + dx >= x && mx + dx <= x + w 621 && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) 622 return true; 623 if (mx - dx >= x && mx - dx <= x + w 624 && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) 625 return true; 626 } 627 dx = x - mx; 628 dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); 629 if (! java.lang.Double.isNaN(dy)) 630 { 631 if (my + dy >= y && my + dy <= y + h 632 && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) 633 return true; 634 if (my - dy >= y && my - dy <= y + h 635 && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) 636 return true; 637 } 638 639 dx = (x + w) - mx; 640 dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); 641 if (! java.lang.Double.isNaN(dy)) 642 { 643 if (my + dy >= y && my + dy <= y + h 644 && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) 645 return true; 646 if (my - dy >= y && my - dy <= y + h 647 && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) 648 return true; 649 } 650 651 // Check whether the arc is contained within the box 652 if (rect.contains(mx, my)) 653 return true; 654 655 return false; 656 } 657 658 /** 659 * Tests if a given rectangle is contained in the area of the arc. 660 * 661 * @param x the x coordinate of the rectangle 662 * @param y the y coordinate of the rectangle 663 * @param w the width of the rectangle 664 * @param h the height of the rectangle 665 * @return true if the arc contains the rectangle 666 */ 667 public boolean contains(double x, double y, double w, double h) 668 { 669 double extent = getAngleExtent(); 670 if (extent == 0) 671 return false; 672 673 if (! (contains(x, y) && contains(x, y + h) && contains(x + w, y) 674 && contains(x + w, y + h))) 675 return false; 676 677 Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); 678 679 double a = getWidth() / 2.0; 680 double b = getHeight() / 2.0; 681 682 double mx = getX() + a; 683 double my = getY() + b; 684 double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); 685 double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); 686 double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); 687 double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); 688 if (getArcType() != CHORD) 689 { 690 // check intersections against the pie radii 691 if (rect.intersectsLine(mx, my, x1, y1)) 692 return false; 693 694 if (rect.intersectsLine(mx, my, x2, y2)) 695 return false; 696 } 697 else if (rect.intersectsLine(x1, y1, x2, y2)) 698 return false; 699 return true; 700 } 701 702 /** 703 * Tests if a given rectangle is contained in the area of the arc. 704 * 705 * @param r the rectangle 706 * @return true if the arc contains the rectangle 707 */ 708 public boolean contains(Rectangle2D r) 709 { 710 return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); 711 } 712 713 /** 714 * Returns an iterator over this arc, with an optional transformation. 715 * This iterator is threadsafe, so future modifications to the arc do not 716 * affect the iteration. 717 * 718 * @param at the transformation, or null 719 * @return a path iterator 720 */ 721 public PathIterator getPathIterator(AffineTransform at) 722 { 723 return new ArcIterator(this, at); 724 } 725 726 /** 727 * This class is used to iterate over an arc. Since ellipses are a subclass 728 * of arcs, this is used by Ellipse2D as well. 729 * 730 * @author Eric Blake (ebb9@email.byu.edu) 731 */ 732 static final class ArcIterator implements PathIterator 733 { 734 /** The current iteration. */ 735 private int current; 736 737 /** The last iteration. */ 738 private final int limit; 739 740 /** The optional transformation. */ 741 private final AffineTransform xform; 742 743 /** The x coordinate of the bounding box. */ 744 private final double x; 745 746 /** The y coordinate of the bounding box. */ 747 private final double y; 748 749 /** The width of the bounding box. */ 750 private final double w; 751 752 /** The height of the bounding box. */ 753 private final double h; 754 755 /** The start angle, in radians (not degrees). */ 756 private final double start; 757 758 /** The extent angle, in radians (not degrees). */ 759 private final double extent; 760 761 /** The arc closure type. */ 762 private final int type; 763 764 /** 765 * Construct a new iterator over an arc. 766 * 767 * @param a the arc 768 * @param xform the transform 769 */ 770 public ArcIterator(Arc2D a, AffineTransform xform) 771 { 772 this.xform = xform; 773 x = a.getX(); 774 y = a.getY(); 775 w = a.getWidth(); 776 h = a.getHeight(); 777 double start = Math.toRadians(a.getAngleStart()); 778 double extent = Math.toRadians(a.getAngleExtent()); 779 780 this.start = start; 781 this.extent = extent; 782 783 type = a.type; 784 if (w < 0 || h < 0) 785 limit = -1; 786 else if (extent == 0) 787 limit = type; 788 else if (Math.abs(extent) <= Math.PI / 2.0) 789 limit = type + 1; 790 else if (Math.abs(extent) <= Math.PI) 791 limit = type + 2; 792 else if (Math.abs(extent) <= 3.0 * (Math.PI / 2.0)) 793 limit = type + 3; 794 else 795 limit = type + 4; 796 } 797 798 /** 799 * Construct a new iterator over an ellipse. 800 * 801 * @param e the ellipse 802 * @param xform the transform 803 */ 804 public ArcIterator(Ellipse2D e, AffineTransform xform) 805 { 806 this.xform = xform; 807 x = e.getX(); 808 y = e.getY(); 809 w = e.getWidth(); 810 h = e.getHeight(); 811 start = 0; 812 extent = 2 * Math.PI; 813 type = CHORD; 814 limit = (w < 0 || h < 0) ? -1 : 5; 815 } 816 817 /** 818 * Return the winding rule. 819 * 820 * @return {@link PathIterator#WIND_NON_ZERO} 821 */ 822 public int getWindingRule() 823 { 824 return WIND_NON_ZERO; 825 } 826 827 /** 828 * Test if the iteration is complete. 829 * 830 * @return true if more segments exist 831 */ 832 public boolean isDone() 833 { 834 return current > limit; 835 } 836 837 /** 838 * Advance the iterator. 839 */ 840 public void next() 841 { 842 current++; 843 } 844 845 /** 846 * Put the current segment into the array, and return the segment type. 847 * 848 * @param coords an array of 6 elements 849 * @return the segment type 850 * @throws NullPointerException if coords is null 851 * @throws ArrayIndexOutOfBoundsException if coords is too small 852 */ 853 public int currentSegment(float[] coords) 854 { 855 double[] double_coords = new double[6]; 856 int code = currentSegment(double_coords); 857 for (int i = 0; i < 6; ++i) 858 coords[i] = (float) double_coords[i]; 859 return code; 860 } 861 862 /** 863 * Put the current segment into the array, and return the segment type. 864 * 865 * @param coords an array of 6 elements 866 * @return the segment type 867 * @throws NullPointerException if coords is null 868 * @throws ArrayIndexOutOfBoundsException if coords is too small 869 */ 870 public int currentSegment(double[] coords) 871 { 872 double rx = w / 2; 873 double ry = h / 2; 874 double xmid = x + rx; 875 double ymid = y + ry; 876 877 if (current > limit) 878 throw new NoSuchElementException("arc iterator out of bounds"); 879 880 if (current == 0) 881 { 882 coords[0] = xmid + rx * Math.cos(start); 883 coords[1] = ymid - ry * Math.sin(start); 884 if (xform != null) 885 xform.transform(coords, 0, coords, 0, 1); 886 return SEG_MOVETO; 887 } 888 889 if (type != OPEN && current == limit) 890 return SEG_CLOSE; 891 892 if ((current == limit - 1) && (type == PIE)) 893 { 894 coords[0] = xmid; 895 coords[1] = ymid; 896 if (xform != null) 897 xform.transform(coords, 0, coords, 0, 1); 898 return SEG_LINETO; 899 } 900 901 // note that this produces a cubic approximation of the arc segment, 902 // not a true ellipsoid. there's no ellipsoid path segment code, 903 // unfortunately. the cubic approximation looks about right, though. 904 double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0); 905 double quad = (Math.PI / 2.0); 906 907 double curr_begin; 908 double curr_extent; 909 if (extent > 0) 910 { 911 curr_begin = start + (current - 1) * quad; 912 curr_extent = Math.min((start + extent) - curr_begin, quad); 913 } 914 else 915 { 916 curr_begin = start - (current - 1) * quad; 917 curr_extent = Math.max((start + extent) - curr_begin, -quad); 918 } 919 920 double portion_of_a_quadrant = Math.abs(curr_extent / quad); 921 922 double x0 = xmid + rx * Math.cos(curr_begin); 923 double y0 = ymid - ry * Math.sin(curr_begin); 924 925 double x1 = xmid + rx * Math.cos(curr_begin + curr_extent); 926 double y1 = ymid - ry * Math.sin(curr_begin + curr_extent); 927 928 AffineTransform trans = new AffineTransform(); 929 double[] cvec = new double[2]; 930 double len = kappa * portion_of_a_quadrant; 931 double angle = curr_begin; 932 933 // in a hypothetical "first quadrant" setting, our first control 934 // vector would be sticking up, from [1,0] to [1,kappa]. 935 // 936 // let us recall however that in java2d, y coords are upside down 937 // from what one would consider "normal" first quadrant rules, so we 938 // will *subtract* the y value of this control vector from our first 939 // point. 940 cvec[0] = 0; 941 if (extent > 0) 942 cvec[1] = len; 943 else 944 cvec[1] = -len; 945 946 trans.scale(rx, ry); 947 trans.rotate(angle); 948 trans.transform(cvec, 0, cvec, 0, 1); 949 coords[0] = x0 + cvec[0]; 950 coords[1] = y0 - cvec[1]; 951 952 // control vector #2 would, ideally, be sticking out and to the 953 // right, in a first quadrant arc segment. again, subtraction of y. 954 cvec[0] = 0; 955 if (extent > 0) 956 cvec[1] = -len; 957 else 958 cvec[1] = len; 959 960 trans.rotate(curr_extent); 961 trans.transform(cvec, 0, cvec, 0, 1); 962 coords[2] = x1 + cvec[0]; 963 coords[3] = y1 - cvec[1]; 964 965 // end point 966 coords[4] = x1; 967 coords[5] = y1; 968 969 if (xform != null) 970 xform.transform(coords, 0, coords, 0, 3); 971 972 return SEG_CUBICTO; 973 } 974 } // class ArcIterator 975 976 /** 977 * This class implements an arc in double precision. 978 * 979 * @author Eric Blake (ebb9@email.byu.edu) 980 * @since 1.2 981 */ 982 public static class Double extends Arc2D 983 { 984 /** The x coordinate of the box bounding the ellipse of this arc. */ 985 public double x; 986 987 /** The y coordinate of the box bounding the ellipse of this arc. */ 988 public double y; 989 990 /** The width of the box bounding the ellipse of this arc. */ 991 public double width; 992 993 /** The height of the box bounding the ellipse of this arc. */ 994 public double height; 995 996 /** The start angle of this arc, in degrees. */ 997 public double start; 998 999 /** The extent angle of this arc, in degrees. */ 1000 public double extent; 1001 1002 /** 1003 * Create a new, open arc at (0,0) with 0 extent. 1004 */ 1005 public Double() 1006 { 1007 super(OPEN); 1008 } 1009 1010 /** 1011 * Create a new arc of the given type at (0,0) with 0 extent. 1012 * 1013 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1014 * @throws IllegalArgumentException if type is invalid 1015 */ 1016 public Double(int type) 1017 { 1018 super(type); 1019 } 1020 1021 /** 1022 * Create a new arc with the given dimensions. 1023 * 1024 * @param x the x coordinate 1025 * @param y the y coordinate 1026 * @param w the width 1027 * @param h the height 1028 * @param start the start angle, in degrees 1029 * @param extent the extent, in degrees 1030 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1031 * @throws IllegalArgumentException if type is invalid 1032 */ 1033 public Double(double x, double y, double w, double h, double start, 1034 double extent, int type) 1035 { 1036 super(type); 1037 this.x = x; 1038 this.y = y; 1039 width = w; 1040 height = h; 1041 this.start = start; 1042 this.extent = extent; 1043 } 1044 1045 /** 1046 * Create a new arc with the given dimensions. 1047 * 1048 * @param r the bounding box 1049 * @param start the start angle, in degrees 1050 * @param extent the extent, in degrees 1051 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1052 * @throws IllegalArgumentException if type is invalid 1053 * @throws NullPointerException if r is null 1054 */ 1055 public Double(Rectangle2D r, double start, double extent, int type) 1056 { 1057 super(type); 1058 x = r.getX(); 1059 y = r.getY(); 1060 width = r.getWidth(); 1061 height = r.getHeight(); 1062 this.start = start; 1063 this.extent = extent; 1064 } 1065 1066 /** 1067 * Return the x coordinate of the bounding box. 1068 * 1069 * @return the value of x 1070 */ 1071 public double getX() 1072 { 1073 return x; 1074 } 1075 1076 /** 1077 * Return the y coordinate of the bounding box. 1078 * 1079 * @return the value of y 1080 */ 1081 public double getY() 1082 { 1083 return y; 1084 } 1085 1086 /** 1087 * Return the width of the bounding box. 1088 * 1089 * @return the value of width 1090 */ 1091 public double getWidth() 1092 { 1093 return width; 1094 } 1095 1096 /** 1097 * Return the height of the bounding box. 1098 * 1099 * @return the value of height 1100 */ 1101 public double getHeight() 1102 { 1103 return height; 1104 } 1105 1106 /** 1107 * Return the start angle of the arc, in degrees. 1108 * 1109 * @return the value of start 1110 */ 1111 public double getAngleStart() 1112 { 1113 return start; 1114 } 1115 1116 /** 1117 * Return the extent of the arc, in degrees. 1118 * 1119 * @return the value of extent 1120 */ 1121 public double getAngleExtent() 1122 { 1123 return extent; 1124 } 1125 1126 /** 1127 * Tests if the arc contains points. 1128 * 1129 * @return true if the arc has no interior 1130 */ 1131 public boolean isEmpty() 1132 { 1133 return width <= 0 || height <= 0; 1134 } 1135 1136 /** 1137 * Sets the arc to the given dimensions. 1138 * 1139 * @param x the x coordinate 1140 * @param y the y coordinate 1141 * @param w the width 1142 * @param h the height 1143 * @param start the start angle, in degrees 1144 * @param extent the extent, in degrees 1145 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1146 * @throws IllegalArgumentException if type is invalid 1147 */ 1148 public void setArc(double x, double y, double w, double h, double start, 1149 double extent, int type) 1150 { 1151 this.x = x; 1152 this.y = y; 1153 width = w; 1154 height = h; 1155 this.start = start; 1156 this.extent = extent; 1157 setArcType(type); 1158 } 1159 1160 /** 1161 * Sets the start angle of the arc. 1162 * 1163 * @param start the new start angle 1164 */ 1165 public void setAngleStart(double start) 1166 { 1167 this.start = start; 1168 } 1169 1170 /** 1171 * Sets the extent angle of the arc. 1172 * 1173 * @param extent the new extent angle 1174 */ 1175 public void setAngleExtent(double extent) 1176 { 1177 this.extent = extent; 1178 } 1179 1180 /** 1181 * Creates a tight bounding box given dimensions that more precise than 1182 * the bounding box of the ellipse. 1183 * 1184 * @param x the x coordinate 1185 * @param y the y coordinate 1186 * @param w the width 1187 * @param h the height 1188 */ 1189 protected Rectangle2D makeBounds(double x, double y, double w, double h) 1190 { 1191 return new Rectangle2D.Double(x, y, w, h); 1192 } 1193 } // class Double 1194 1195 /** 1196 * This class implements an arc in float precision. 1197 * 1198 * @author Eric Blake (ebb9@email.byu.edu) 1199 * @since 1.2 1200 */ 1201 public static class Float extends Arc2D 1202 { 1203 /** The x coordinate of the box bounding the ellipse of this arc. */ 1204 public float x; 1205 1206 /** The y coordinate of the box bounding the ellipse of this arc. */ 1207 public float y; 1208 1209 /** The width of the box bounding the ellipse of this arc. */ 1210 public float width; 1211 1212 /** The height of the box bounding the ellipse of this arc. */ 1213 public float height; 1214 1215 /** The start angle of this arc, in degrees. */ 1216 public float start; 1217 1218 /** The extent angle of this arc, in degrees. */ 1219 public float extent; 1220 1221 /** 1222 * Create a new, open arc at (0,0) with 0 extent. 1223 */ 1224 public Float() 1225 { 1226 super(OPEN); 1227 } 1228 1229 /** 1230 * Create a new arc of the given type at (0,0) with 0 extent. 1231 * 1232 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1233 * @throws IllegalArgumentException if type is invalid 1234 */ 1235 public Float(int type) 1236 { 1237 super(type); 1238 } 1239 1240 /** 1241 * Create a new arc with the given dimensions. 1242 * 1243 * @param x the x coordinate 1244 * @param y the y coordinate 1245 * @param w the width 1246 * @param h the height 1247 * @param start the start angle, in degrees 1248 * @param extent the extent, in degrees 1249 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1250 * @throws IllegalArgumentException if type is invalid 1251 */ 1252 public Float(float x, float y, float w, float h, float start, 1253 float extent, int type) 1254 { 1255 super(type); 1256 this.x = x; 1257 this.y = y; 1258 width = w; 1259 height = h; 1260 this.start = start; 1261 this.extent = extent; 1262 } 1263 1264 /** 1265 * Create a new arc with the given dimensions. 1266 * 1267 * @param r the bounding box 1268 * @param start the start angle, in degrees 1269 * @param extent the extent, in degrees 1270 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1271 * @throws IllegalArgumentException if type is invalid 1272 * @throws NullPointerException if r is null 1273 */ 1274 public Float(Rectangle2D r, float start, float extent, int type) 1275 { 1276 super(type); 1277 x = (float) r.getX(); 1278 y = (float) r.getY(); 1279 width = (float) r.getWidth(); 1280 height = (float) r.getHeight(); 1281 this.start = start; 1282 this.extent = extent; 1283 } 1284 1285 /** 1286 * Return the x coordinate of the bounding box. 1287 * 1288 * @return the value of x 1289 */ 1290 public double getX() 1291 { 1292 return x; 1293 } 1294 1295 /** 1296 * Return the y coordinate of the bounding box. 1297 * 1298 * @return the value of y 1299 */ 1300 public double getY() 1301 { 1302 return y; 1303 } 1304 1305 /** 1306 * Return the width of the bounding box. 1307 * 1308 * @return the value of width 1309 */ 1310 public double getWidth() 1311 { 1312 return width; 1313 } 1314 1315 /** 1316 * Return the height of the bounding box. 1317 * 1318 * @return the value of height 1319 */ 1320 public double getHeight() 1321 { 1322 return height; 1323 } 1324 1325 /** 1326 * Return the start angle of the arc, in degrees. 1327 * 1328 * @return the value of start 1329 */ 1330 public double getAngleStart() 1331 { 1332 return start; 1333 } 1334 1335 /** 1336 * Return the extent of the arc, in degrees. 1337 * 1338 * @return the value of extent 1339 */ 1340 public double getAngleExtent() 1341 { 1342 return extent; 1343 } 1344 1345 /** 1346 * Tests if the arc contains points. 1347 * 1348 * @return true if the arc has no interior 1349 */ 1350 public boolean isEmpty() 1351 { 1352 return width <= 0 || height <= 0; 1353 } 1354 1355 /** 1356 * Sets the arc to the given dimensions. 1357 * 1358 * @param x the x coordinate 1359 * @param y the y coordinate 1360 * @param w the width 1361 * @param h the height 1362 * @param start the start angle, in degrees 1363 * @param extent the extent, in degrees 1364 * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1365 * @throws IllegalArgumentException if type is invalid 1366 */ 1367 public void setArc(double x, double y, double w, double h, double start, 1368 double extent, int type) 1369 { 1370 this.x = (float) x; 1371 this.y = (float) y; 1372 width = (float) w; 1373 height = (float) h; 1374 this.start = (float) start; 1375 this.extent = (float) extent; 1376 setArcType(type); 1377 } 1378 1379 /** 1380 * Sets the start angle of the arc. 1381 * 1382 * @param start the new start angle 1383 */ 1384 public void setAngleStart(double start) 1385 { 1386 this.start = (float) start; 1387 } 1388 1389 /** 1390 * Sets the extent angle of the arc. 1391 * 1392 * @param extent the new extent angle 1393 */ 1394 public void setAngleExtent(double extent) 1395 { 1396 this.extent = (float) extent; 1397 } 1398 1399 /** 1400 * Creates a tight bounding box given dimensions that more precise than 1401 * the bounding box of the ellipse. 1402 * 1403 * @param x the x coordinate 1404 * @param y the y coordinate 1405 * @param w the width 1406 * @param h the height 1407 */ 1408 protected Rectangle2D makeBounds(double x, double y, double w, double h) 1409 { 1410 return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h); 1411 } 1412 } // class Float 1413 } // class Arc2D