OSDN Git Service

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