GNU Classpath (0.20) | |
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 = a.getAngleStart() * (Math.PI / 180); 778: double extent = a.getAngleExtent() * (Math.PI / 180); 779: 780: if (extent < 0) 781: { 782: extent = -extent; 783: start = 2 * Math.PI - extent + start; 784: } 785: this.start = start; 786: this.extent = extent; 787: 788: type = a.type; 789: if (w < 0 || h < 0) 790: limit = -1; 791: else if (extent == 0) 792: limit = type; 793: else if (extent <= Math.PI / 2.0) 794: limit = type + 1; 795: else if (extent <= Math.PI) 796: limit = type + 2; 797: else if (extent <= 3.0 * (Math.PI / 2.0)) 798: limit = type + 3; 799: else 800: limit = type + 4; 801: } 802: 803: /** 804: * Construct a new iterator over an ellipse. 805: * 806: * @param e the ellipse 807: * @param xform the transform 808: */ 809: public ArcIterator(Ellipse2D e, AffineTransform xform) 810: { 811: this.xform = xform; 812: x = e.getX(); 813: y = e.getY(); 814: w = e.getWidth(); 815: h = e.getHeight(); 816: start = 0; 817: extent = 2 * Math.PI; 818: type = CHORD; 819: limit = (w < 0 || h < 0) ? -1 : 5; 820: } 821: 822: /** 823: * Return the winding rule. 824: * 825: * @return {@link PathIterator#WIND_NON_ZERO} 826: */ 827: public int getWindingRule() 828: { 829: return WIND_NON_ZERO; 830: } 831: 832: /** 833: * Test if the iteration is complete. 834: * 835: * @return true if more segments exist 836: */ 837: public boolean isDone() 838: { 839: return current > limit; 840: } 841: 842: /** 843: * Advance the iterator. 844: */ 845: public void next() 846: { 847: current++; 848: } 849: 850: /** 851: * Put the current segment into the array, and return the segment type. 852: * 853: * @param coords an array of 6 elements 854: * @return the segment type 855: * @throws NullPointerException if coords is null 856: * @throws ArrayIndexOutOfBoundsException if coords is too small 857: */ 858: public int currentSegment(float[] coords) 859: { 860: double[] double_coords = new double[6]; 861: int code = currentSegment(double_coords); 862: for (int i = 0; i < 6; ++i) 863: coords[i] = (float) double_coords[i]; 864: return code; 865: } 866: 867: /** 868: * Put the current segment into the array, and return the segment type. 869: * 870: * @param coords an array of 6 elements 871: * @return the segment type 872: * @throws NullPointerException if coords is null 873: * @throws ArrayIndexOutOfBoundsException if coords is too small 874: */ 875: public int currentSegment(double[] coords) 876: { 877: double rx = w / 2; 878: double ry = h / 2; 879: double xmid = x + rx; 880: double ymid = y + ry; 881: 882: if (current > limit) 883: throw new NoSuchElementException("arc iterator out of bounds"); 884: 885: if (current == 0) 886: { 887: coords[0] = xmid + rx * Math.cos(start); 888: coords[1] = ymid - ry * Math.sin(start); 889: if (xform != null) 890: xform.transform(coords, 0, coords, 0, 1); 891: return SEG_MOVETO; 892: } 893: 894: if (type != OPEN && current == limit) 895: return SEG_CLOSE; 896: 897: if ((current == limit - 1) && (type == PIE)) 898: { 899: coords[0] = xmid; 900: coords[1] = ymid; 901: if (xform != null) 902: xform.transform(coords, 0, coords, 0, 1); 903: return SEG_LINETO; 904: } 905: 906: // note that this produces a cubic approximation of the arc segment, 907: // not a true ellipsoid. there's no ellipsoid path segment code, 908: // unfortunately. the cubic approximation looks about right, though. 909: double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0); 910: double quad = (Math.PI / 2.0); 911: 912: double curr_begin = start + (current - 1) * quad; 913: double curr_extent = Math.min((start + extent) - curr_begin, quad); 914: double portion_of_a_quadrant = curr_extent / quad; 915: 916: double x0 = xmid + rx * Math.cos(curr_begin); 917: double y0 = ymid - ry * Math.sin(curr_begin); 918: 919: double x1 = xmid + rx * Math.cos(curr_begin + curr_extent); 920: double y1 = ymid - ry * Math.sin(curr_begin + curr_extent); 921: 922: AffineTransform trans = new AffineTransform(); 923: double[] cvec = new double[2]; 924: double len = kappa * portion_of_a_quadrant; 925: double angle = curr_begin; 926: 927: // in a hypothetical "first quadrant" setting, our first control 928: // vector would be sticking up, from [1,0] to [1,kappa]. 929: // 930: // let us recall however that in java2d, y coords are upside down 931: // from what one would consider "normal" first quadrant rules, so we 932: // will *subtract* the y value of this control vector from our first 933: // point. 934: cvec[0] = 0; 935: cvec[1] = len; 936: trans.scale(rx, ry); 937: trans.rotate(angle); 938: trans.transform(cvec, 0, cvec, 0, 1); 939: coords[0] = x0 + cvec[0]; 940: coords[1] = y0 - cvec[1]; 941: 942: // control vector #2 would, ideally, be sticking out and to the 943: // right, in a first quadrant arc segment. again, subtraction of y. 944: cvec[0] = 0; 945: cvec[1] = -len; 946: trans.rotate(curr_extent); 947: trans.transform(cvec, 0, cvec, 0, 1); 948: coords[2] = x1 + cvec[0]; 949: coords[3] = y1 - cvec[1]; 950: 951: // end point 952: coords[4] = x1; 953: coords[5] = y1; 954: 955: if (xform != null) 956: xform.transform(coords, 0, coords, 0, 3); 957: 958: return SEG_CUBICTO; 959: } 960: } // class ArcIterator 961: 962: /** 963: * This class implements an arc in double precision. 964: * 965: * @author Eric Blake (ebb9@email.byu.edu) 966: * @since 1.2 967: */ 968: public static class Double extends Arc2D 969: { 970: /** The x coordinate of the box bounding the ellipse of this arc. */ 971: public double x; 972: 973: /** The y coordinate of the box bounding the ellipse of this arc. */ 974: public double y; 975: 976: /** The width of the box bounding the ellipse of this arc. */ 977: public double width; 978: 979: /** The height of the box bounding the ellipse of this arc. */ 980: public double height; 981: 982: /** The start angle of this arc, in degrees. */ 983: public double start; 984: 985: /** The extent angle of this arc, in degrees. */ 986: public double extent; 987: 988: /** 989: * Create a new, open arc at (0,0) with 0 extent. 990: */ 991: public Double() 992: { 993: super(OPEN); 994: } 995: 996: /** 997: * Create a new arc of the given type at (0,0) with 0 extent. 998: * 999: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1000: * @throws IllegalArgumentException if type is invalid 1001: */ 1002: public Double(int type) 1003: { 1004: super(type); 1005: } 1006: 1007: /** 1008: * Create a new arc with the given dimensions. 1009: * 1010: * @param x the x coordinate 1011: * @param y the y coordinate 1012: * @param w the width 1013: * @param h the height 1014: * @param start the start angle, in degrees 1015: * @param extent the extent, in degrees 1016: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1017: * @throws IllegalArgumentException if type is invalid 1018: */ 1019: public Double(double x, double y, double w, double h, double start, 1020: double extent, int type) 1021: { 1022: super(type); 1023: this.x = x; 1024: this.y = y; 1025: width = w; 1026: height = h; 1027: this.start = start; 1028: this.extent = extent; 1029: } 1030: 1031: /** 1032: * Create a new arc with the given dimensions. 1033: * 1034: * @param r the bounding box 1035: * @param start the start angle, in degrees 1036: * @param extent the extent, in degrees 1037: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1038: * @throws IllegalArgumentException if type is invalid 1039: * @throws NullPointerException if r is null 1040: */ 1041: public Double(Rectangle2D r, double start, double extent, int type) 1042: { 1043: super(type); 1044: x = r.getX(); 1045: y = r.getY(); 1046: width = r.getWidth(); 1047: height = r.getHeight(); 1048: this.start = start; 1049: this.extent = extent; 1050: } 1051: 1052: /** 1053: * Return the x coordinate of the bounding box. 1054: * 1055: * @return the value of x 1056: */ 1057: public double getX() 1058: { 1059: return x; 1060: } 1061: 1062: /** 1063: * Return the y coordinate of the bounding box. 1064: * 1065: * @return the value of y 1066: */ 1067: public double getY() 1068: { 1069: return y; 1070: } 1071: 1072: /** 1073: * Return the width of the bounding box. 1074: * 1075: * @return the value of width 1076: */ 1077: public double getWidth() 1078: { 1079: return width; 1080: } 1081: 1082: /** 1083: * Return the height of the bounding box. 1084: * 1085: * @return the value of height 1086: */ 1087: public double getHeight() 1088: { 1089: return height; 1090: } 1091: 1092: /** 1093: * Return the start angle of the arc, in degrees. 1094: * 1095: * @return the value of start 1096: */ 1097: public double getAngleStart() 1098: { 1099: return start; 1100: } 1101: 1102: /** 1103: * Return the extent of the arc, in degrees. 1104: * 1105: * @return the value of extent 1106: */ 1107: public double getAngleExtent() 1108: { 1109: return extent; 1110: } 1111: 1112: /** 1113: * Tests if the arc contains points. 1114: * 1115: * @return true if the arc has no interior 1116: */ 1117: public boolean isEmpty() 1118: { 1119: return width <= 0 || height <= 0; 1120: } 1121: 1122: /** 1123: * Sets the arc to the given dimensions. 1124: * 1125: * @param x the x coordinate 1126: * @param y the y coordinate 1127: * @param w the width 1128: * @param h the height 1129: * @param start the start angle, in degrees 1130: * @param extent the extent, in degrees 1131: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1132: * @throws IllegalArgumentException if type is invalid 1133: */ 1134: public void setArc(double x, double y, double w, double h, double start, 1135: double extent, int type) 1136: { 1137: this.x = x; 1138: this.y = y; 1139: width = w; 1140: height = h; 1141: this.start = start; 1142: this.extent = extent; 1143: setArcType(type); 1144: } 1145: 1146: /** 1147: * Sets the start angle of the arc. 1148: * 1149: * @param start the new start angle 1150: */ 1151: public void setAngleStart(double start) 1152: { 1153: this.start = start; 1154: } 1155: 1156: /** 1157: * Sets the extent angle of the arc. 1158: * 1159: * @param extent the new extent angle 1160: */ 1161: public void setAngleExtent(double extent) 1162: { 1163: this.extent = extent; 1164: } 1165: 1166: /** 1167: * Creates a tight bounding box given dimensions that more precise than 1168: * the bounding box of the ellipse. 1169: * 1170: * @param x the x coordinate 1171: * @param y the y coordinate 1172: * @param w the width 1173: * @param h the height 1174: */ 1175: protected Rectangle2D makeBounds(double x, double y, double w, double h) 1176: { 1177: return new Rectangle2D.Double(x, y, w, h); 1178: } 1179: } // class Double 1180: 1181: /** 1182: * This class implements an arc in float precision. 1183: * 1184: * @author Eric Blake (ebb9@email.byu.edu) 1185: * @since 1.2 1186: */ 1187: public static class Float extends Arc2D 1188: { 1189: /** The x coordinate of the box bounding the ellipse of this arc. */ 1190: public float x; 1191: 1192: /** The y coordinate of the box bounding the ellipse of this arc. */ 1193: public float y; 1194: 1195: /** The width of the box bounding the ellipse of this arc. */ 1196: public float width; 1197: 1198: /** The height of the box bounding the ellipse of this arc. */ 1199: public float height; 1200: 1201: /** The start angle of this arc, in degrees. */ 1202: public float start; 1203: 1204: /** The extent angle of this arc, in degrees. */ 1205: public float extent; 1206: 1207: /** 1208: * Create a new, open arc at (0,0) with 0 extent. 1209: */ 1210: public Float() 1211: { 1212: super(OPEN); 1213: } 1214: 1215: /** 1216: * Create a new arc of the given type at (0,0) with 0 extent. 1217: * 1218: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1219: * @throws IllegalArgumentException if type is invalid 1220: */ 1221: public Float(int type) 1222: { 1223: super(type); 1224: } 1225: 1226: /** 1227: * Create a new arc with the given dimensions. 1228: * 1229: * @param x the x coordinate 1230: * @param y the y coordinate 1231: * @param w the width 1232: * @param h the height 1233: * @param start the start angle, in degrees 1234: * @param extent the extent, in degrees 1235: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1236: * @throws IllegalArgumentException if type is invalid 1237: */ 1238: public Float(float x, float y, float w, float h, float start, 1239: float extent, int type) 1240: { 1241: super(type); 1242: this.x = x; 1243: this.y = y; 1244: width = w; 1245: height = h; 1246: this.start = start; 1247: this.extent = extent; 1248: } 1249: 1250: /** 1251: * Create a new arc with the given dimensions. 1252: * 1253: * @param r the bounding box 1254: * @param start the start angle, in degrees 1255: * @param extent the extent, in degrees 1256: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1257: * @throws IllegalArgumentException if type is invalid 1258: * @throws NullPointerException if r is null 1259: */ 1260: public Float(Rectangle2D r, float start, float extent, int type) 1261: { 1262: super(type); 1263: x = (float) r.getX(); 1264: y = (float) r.getY(); 1265: width = (float) r.getWidth(); 1266: height = (float) r.getHeight(); 1267: this.start = start; 1268: this.extent = (float) extent; 1269: } 1270: 1271: /** 1272: * Return the x coordinate of the bounding box. 1273: * 1274: * @return the value of x 1275: */ 1276: public double getX() 1277: { 1278: return x; 1279: } 1280: 1281: /** 1282: * Return the y coordinate of the bounding box. 1283: * 1284: * @return the value of y 1285: */ 1286: public double getY() 1287: { 1288: return y; 1289: } 1290: 1291: /** 1292: * Return the width of the bounding box. 1293: * 1294: * @return the value of width 1295: */ 1296: public double getWidth() 1297: { 1298: return width; 1299: } 1300: 1301: /** 1302: * Return the height of the bounding box. 1303: * 1304: * @return the value of height 1305: */ 1306: public double getHeight() 1307: { 1308: return height; 1309: } 1310: 1311: /** 1312: * Return the start angle of the arc, in degrees. 1313: * 1314: * @return the value of start 1315: */ 1316: public double getAngleStart() 1317: { 1318: return start; 1319: } 1320: 1321: /** 1322: * Return the extent of the arc, in degrees. 1323: * 1324: * @return the value of extent 1325: */ 1326: public double getAngleExtent() 1327: { 1328: return extent; 1329: } 1330: 1331: /** 1332: * Tests if the arc contains points. 1333: * 1334: * @return true if the arc has no interior 1335: */ 1336: public boolean isEmpty() 1337: { 1338: return width <= 0 || height <= 0; 1339: } 1340: 1341: /** 1342: * Sets the arc to the given dimensions. 1343: * 1344: * @param x the x coordinate 1345: * @param y the y coordinate 1346: * @param w the width 1347: * @param h the height 1348: * @param start the start angle, in degrees 1349: * @param extent the extent, in degrees 1350: * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} 1351: * @throws IllegalArgumentException if type is invalid 1352: */ 1353: public void setArc(double x, double y, double w, double h, double start, 1354: double extent, int type) 1355: { 1356: this.x = (float) x; 1357: this.y = (float) y; 1358: width = (float) w; 1359: height = (float) h; 1360: this.start = (float) start; 1361: this.extent = (float) extent; 1362: setArcType(type); 1363: } 1364: 1365: /** 1366: * Sets the start angle of the arc. 1367: * 1368: * @param start the new start angle 1369: */ 1370: public void setAngleStart(double start) 1371: { 1372: this.start = (float) start; 1373: } 1374: 1375: /** 1376: * Sets the extent angle of the arc. 1377: * 1378: * @param extent the new extent angle 1379: */ 1380: public void setAngleExtent(double extent) 1381: { 1382: this.extent = (float) extent; 1383: } 1384: 1385: /** 1386: * Creates a tight bounding box given dimensions that more precise than 1387: * the bounding box of the ellipse. 1388: * 1389: * @param x the x coordinate 1390: * @param y the y coordinate 1391: * @param w the width 1392: * @param h the height 1393: */ 1394: protected Rectangle2D makeBounds(double x, double y, double w, double h) 1395: { 1396: return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h); 1397: } 1398: } // class Float 1399: } // class Arc2D
GNU Classpath (0.20) |