001    /* Arc2D.java -- represents an arc in 2-D space
002       Copyright (C) 2002, 2003, 2004 Free Software Foundation
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    package java.awt.geom;
039    
040    import 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     */
057    public 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