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 = 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