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