Source for java.awt.geom.Arc2D

   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