Frames | No Frames |
1: /* Arc2D.java -- represents an arc in 2-D space 2: Copyright (C) 2002, 2003, 2004 Free Software Foundation 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath 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, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: package java.awt.geom; 39: 40: import java.util.NoSuchElementException; 41: 42: 43: /** 44: * This class represents all arcs (segments of an ellipse in 2-D space). The 45: * arcs are defined by starting angle and extent (arc length) in degrees, as 46: * opposed to radians (like the rest of Java), and can be open, chorded, or 47: * wedge shaped. The angles are skewed according to the ellipse, so that 45 48: * degrees always points to the upper right corner (positive x, negative y) 49: * of the bounding rectangle. A positive extent draws a counterclockwise arc, 50: * and while the angle can be any value, the path iterator only traverses the 51: * first 360 degrees. Storage is up to the subclasses. 52: * 53: * @author Eric Blake (ebb9@email.byu.edu) 54: * @author Sven de Marothy (sven@physto.se) 55: * @since 1.2 56: */ 57: public abstract class Arc2D extends RectangularShape 58: { 59: /** 60: * An open arc, with no segment connecting the endpoints. This type of 61: * arc still contains the same points as a chorded version. 62: */ 63: public static final int OPEN = 0; 64: 65: /** 66: * A closed arc with a single segment connecting the endpoints (a chord). 67: */ 68: public static final int CHORD = 1; 69: 70: /** 71: * A closed arc with two segments, one from each endpoint, meeting at the 72: * center of the ellipse. 73: */ 74: public static final int PIE = 2; 75: 76: /** The closure type of this arc. This is package-private to avoid an 77: * accessor method. */ 78: int type; 79: 80: /** 81: * Create a new arc, with the specified closure type. 82: * 83: * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}. 84: * @throws IllegalArgumentException if type is invalid 85: */ 86: protected Arc2D(int type) 87: { 88: if (type < OPEN || type > PIE) 89: throw new IllegalArgumentException(); 90: this.type = type; 91: } 92: 93: /** 94: * Get the starting angle of the arc in degrees. 95: * 96: * @return the starting angle 97: * @see #setAngleStart(double) 98: */ 99: 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