OSDN Git Service

Initial revision
[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 = a.getAngleStart() * (Math.PI / 180);
778       double extent = a.getAngleExtent() * (Math.PI / 180);
779
780       if (extent < 0)
781         {
782           extent = -extent;
783           start = 2 * Math.PI - extent + start;
784         }
785       this.start = start;
786       this.extent = extent;
787
788       type = a.type;
789       if (w < 0 || h < 0)
790         limit = -1;
791       else if (extent == 0)
792         limit = type;
793       else if (extent <= Math.PI / 2.0)
794         limit = type + 1;
795       else if (extent <= Math.PI)
796         limit = type + 2;
797       else if (extent <= 3.0 * (Math.PI / 2.0))
798         limit = type + 3;
799       else
800         limit = type + 4;
801     }
802
803     /**
804      * Construct a new iterator over an ellipse.
805      *
806      * @param e the ellipse
807      * @param xform the transform
808      */
809     public ArcIterator(Ellipse2D e, AffineTransform xform)
810     {
811       this.xform = xform;
812       x = e.getX();
813       y = e.getY();
814       w = e.getWidth();
815       h = e.getHeight();
816       start = 0;
817       extent = 2 * Math.PI;
818       type = CHORD;
819       limit = (w < 0 || h < 0) ? -1 : 5;
820     }
821
822     /**
823      * Return the winding rule.
824      *
825      * @return {@link PathIterator#WIND_NON_ZERO}
826      */
827     public int getWindingRule()
828     {
829       return WIND_NON_ZERO;
830     }
831
832     /**
833      * Test if the iteration is complete.
834      *
835      * @return true if more segments exist
836      */
837     public boolean isDone()
838     {
839       return current > limit;
840     }
841
842     /**
843      * Advance the iterator.
844      */
845     public void next()
846     {
847       current++;
848     }
849
850     /**
851      * Put the current segment into the array, and return the segment type.
852      *
853      * @param coords an array of 6 elements
854      * @return the segment type
855      * @throws NullPointerException if coords is null
856      * @throws ArrayIndexOutOfBoundsException if coords is too small
857      */
858     public int currentSegment(float[] coords)
859     {
860       double[] double_coords = new double[6];
861       int code = currentSegment(double_coords);
862       for (int i = 0; i < 6; ++i)
863         coords[i] = (float) double_coords[i];
864       return code;
865     }
866
867     /**
868      * Put the current segment into the array, and return the segment type.
869      *
870      * @param coords an array of 6 elements
871      * @return the segment type
872      * @throws NullPointerException if coords is null
873      * @throws ArrayIndexOutOfBoundsException if coords is too small
874      */
875     public int currentSegment(double[] coords)
876     {
877       double rx = w / 2;
878       double ry = h / 2;
879       double xmid = x + rx;
880       double ymid = y + ry;
881
882       if (current > limit)
883         throw new NoSuchElementException("arc iterator out of bounds");
884
885       if (current == 0)
886         {
887           coords[0] = xmid + rx * Math.cos(start);
888           coords[1] = ymid - ry * Math.sin(start);
889           if (xform != null)
890             xform.transform(coords, 0, coords, 0, 1);
891           return SEG_MOVETO;
892         }
893
894       if (type != OPEN && current == limit)
895         return SEG_CLOSE;
896
897       if ((current == limit - 1) && (type == PIE))
898         {
899           coords[0] = xmid;
900           coords[1] = ymid;
901           if (xform != null)
902             xform.transform(coords, 0, coords, 0, 1);
903           return SEG_LINETO;
904         }
905
906       // note that this produces a cubic approximation of the arc segment,
907       // not a true ellipsoid. there's no ellipsoid path segment code,
908       // unfortunately. the cubic approximation looks about right, though.
909       double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0);
910       double quad = (Math.PI / 2.0);
911
912       double curr_begin = start + (current - 1) * quad;
913       double curr_extent = Math.min((start + extent) - curr_begin, quad);
914       double portion_of_a_quadrant = curr_extent / quad;
915
916       double x0 = xmid + rx * Math.cos(curr_begin);
917       double y0 = ymid - ry * Math.sin(curr_begin);
918
919       double x1 = xmid + rx * Math.cos(curr_begin + curr_extent);
920       double y1 = ymid - ry * Math.sin(curr_begin + curr_extent);
921
922       AffineTransform trans = new AffineTransform();
923       double[] cvec = new double[2];
924       double len = kappa * portion_of_a_quadrant;
925       double angle = curr_begin;
926
927       // in a hypothetical "first quadrant" setting, our first control
928       // vector would be sticking up, from [1,0] to [1,kappa].
929       //
930       // let us recall however that in java2d, y coords are upside down
931       // from what one would consider "normal" first quadrant rules, so we
932       // will *subtract* the y value of this control vector from our first
933       // point.
934       cvec[0] = 0;
935       cvec[1] = len;
936       trans.scale(rx, ry);
937       trans.rotate(angle);
938       trans.transform(cvec, 0, cvec, 0, 1);
939       coords[0] = x0 + cvec[0];
940       coords[1] = y0 - cvec[1];
941
942       // control vector #2 would, ideally, be sticking out and to the
943       // right, in a first quadrant arc segment. again, subtraction of y.
944       cvec[0] = 0;
945       cvec[1] = -len;
946       trans.rotate(curr_extent);
947       trans.transform(cvec, 0, cvec, 0, 1);
948       coords[2] = x1 + cvec[0];
949       coords[3] = y1 - cvec[1];
950
951       // end point
952       coords[4] = x1;
953       coords[5] = y1;
954
955       if (xform != null)
956         xform.transform(coords, 0, coords, 0, 3);
957
958       return SEG_CUBICTO;
959     }
960   } // class ArcIterator
961
962   /**
963    * This class implements an arc in double precision.
964    *
965    * @author Eric Blake (ebb9@email.byu.edu)
966    * @since 1.2
967    */
968   public static class Double extends Arc2D
969   {
970     /** The x coordinate of the box bounding the ellipse of this arc. */
971     public double x;
972
973     /** The y coordinate of the box bounding the ellipse of this arc. */
974     public double y;
975
976     /** The width of the box bounding the ellipse of this arc. */
977     public double width;
978
979     /** The height of the box bounding the ellipse of this arc. */
980     public double height;
981
982     /** The start angle of this arc, in degrees. */
983     public double start;
984
985     /** The extent angle of this arc, in degrees. */
986     public double extent;
987
988     /**
989      * Create a new, open arc at (0,0) with 0 extent.
990      */
991     public Double()
992     {
993       super(OPEN);
994     }
995
996     /**
997      * Create a new arc of the given type at (0,0) with 0 extent.
998      *
999      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1000      * @throws IllegalArgumentException if type is invalid
1001      */
1002     public Double(int type)
1003     {
1004       super(type);
1005     }
1006
1007     /**
1008      * Create a new arc with the given dimensions.
1009      *
1010      * @param x the x coordinate
1011      * @param y the y coordinate
1012      * @param w the width
1013      * @param h the height
1014      * @param start the start angle, in degrees
1015      * @param extent the extent, in degrees
1016      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1017      * @throws IllegalArgumentException if type is invalid
1018      */
1019     public Double(double x, double y, double w, double h, double start,
1020                   double extent, int type)
1021     {
1022       super(type);
1023       this.x = x;
1024       this.y = y;
1025       width = w;
1026       height = h;
1027       this.start = start;
1028       this.extent = extent;
1029     }
1030
1031     /**
1032      * Create a new arc with the given dimensions.
1033      *
1034      * @param r the bounding box
1035      * @param start the start angle, in degrees
1036      * @param extent the extent, in degrees
1037      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1038      * @throws IllegalArgumentException if type is invalid
1039      * @throws NullPointerException if r is null
1040      */
1041     public Double(Rectangle2D r, double start, double extent, int type)
1042     {
1043       super(type);
1044       x = r.getX();
1045       y = r.getY();
1046       width = r.getWidth();
1047       height = r.getHeight();
1048       this.start = start;
1049       this.extent = extent;
1050     }
1051
1052     /**
1053      * Return the x coordinate of the bounding box.
1054      *
1055      * @return the value of x
1056      */
1057     public double getX()
1058     {
1059       return x;
1060     }
1061
1062     /**
1063      * Return the y coordinate of the bounding box.
1064      *
1065      * @return the value of y
1066      */
1067     public double getY()
1068     {
1069       return y;
1070     }
1071
1072     /**
1073      * Return the width of the bounding box.
1074      *
1075      * @return the value of width
1076      */
1077     public double getWidth()
1078     {
1079       return width;
1080     }
1081
1082     /**
1083      * Return the height of the bounding box.
1084      *
1085      * @return the value of height
1086      */
1087     public double getHeight()
1088     {
1089       return height;
1090     }
1091
1092     /**
1093      * Return the start angle of the arc, in degrees.
1094      *
1095      * @return the value of start
1096      */
1097     public double getAngleStart()
1098     {
1099       return start;
1100     }
1101
1102     /**
1103      * Return the extent of the arc, in degrees.
1104      *
1105      * @return the value of extent
1106      */
1107     public double getAngleExtent()
1108     {
1109       return extent;
1110     }
1111
1112     /**
1113      * Tests if the arc contains points.
1114      *
1115      * @return true if the arc has no interior
1116      */
1117     public boolean isEmpty()
1118     {
1119       return width <= 0 || height <= 0;
1120     }
1121
1122     /**
1123      * Sets the arc to the given dimensions.
1124      *
1125      * @param x the x coordinate
1126      * @param y the y coordinate
1127      * @param w the width
1128      * @param h the height
1129      * @param start the start angle, in degrees
1130      * @param extent the extent, in degrees
1131      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1132      * @throws IllegalArgumentException if type is invalid
1133      */
1134     public void setArc(double x, double y, double w, double h, double start,
1135                        double extent, int type)
1136     {
1137       this.x = x;
1138       this.y = y;
1139       width = w;
1140       height = h;
1141       this.start = start;
1142       this.extent = extent;
1143       setArcType(type);
1144     }
1145
1146     /**
1147      * Sets the start angle of the arc.
1148      *
1149      * @param start the new start angle
1150      */
1151     public void setAngleStart(double start)
1152     {
1153       this.start = start;
1154     }
1155
1156     /**
1157      * Sets the extent angle of the arc.
1158      *
1159      * @param extent the new extent angle
1160      */
1161     public void setAngleExtent(double extent)
1162     {
1163       this.extent = extent;
1164     }
1165
1166     /**
1167      * Creates a tight bounding box given dimensions that more precise than
1168      * the bounding box of the ellipse.
1169      *
1170      * @param x the x coordinate
1171      * @param y the y coordinate
1172      * @param w the width
1173      * @param h the height
1174      */
1175     protected Rectangle2D makeBounds(double x, double y, double w, double h)
1176     {
1177       return new Rectangle2D.Double(x, y, w, h);
1178     }
1179   } // class Double
1180
1181   /**
1182    * This class implements an arc in float precision.
1183    *
1184    * @author Eric Blake (ebb9@email.byu.edu)
1185    * @since 1.2
1186    */
1187   public static class Float extends Arc2D
1188   {
1189     /** The x coordinate of the box bounding the ellipse of this arc. */
1190     public float x;
1191
1192     /** The y coordinate of the box bounding the ellipse of this arc. */
1193     public float y;
1194
1195     /** The width of the box bounding the ellipse of this arc. */
1196     public float width;
1197
1198     /** The height of the box bounding the ellipse of this arc. */
1199     public float height;
1200
1201     /** The start angle of this arc, in degrees. */
1202     public float start;
1203
1204     /** The extent angle of this arc, in degrees. */
1205     public float extent;
1206
1207     /**
1208      * Create a new, open arc at (0,0) with 0 extent.
1209      */
1210     public Float()
1211     {
1212       super(OPEN);
1213     }
1214
1215     /**
1216      * Create a new arc of the given type at (0,0) with 0 extent.
1217      *
1218      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1219      * @throws IllegalArgumentException if type is invalid
1220      */
1221     public Float(int type)
1222     {
1223       super(type);
1224     }
1225
1226     /**
1227      * Create a new arc with the given dimensions.
1228      *
1229      * @param x the x coordinate
1230      * @param y the y coordinate
1231      * @param w the width
1232      * @param h the height
1233      * @param start the start angle, in degrees
1234      * @param extent the extent, in degrees
1235      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1236      * @throws IllegalArgumentException if type is invalid
1237      */
1238     public Float(float x, float y, float w, float h, float start,
1239                  float extent, int type)
1240     {
1241       super(type);
1242       this.x = x;
1243       this.y = y;
1244       width = w;
1245       height = h;
1246       this.start = start;
1247       this.extent = extent;
1248     }
1249
1250     /**
1251      * Create a new arc with the given dimensions.
1252      *
1253      * @param r the bounding box
1254      * @param start the start angle, in degrees
1255      * @param extent the extent, in degrees
1256      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1257      * @throws IllegalArgumentException if type is invalid
1258      * @throws NullPointerException if r is null
1259      */
1260     public Float(Rectangle2D r, float start, float extent, int type)
1261     {
1262       super(type);
1263       x = (float) r.getX();
1264       y = (float) r.getY();
1265       width = (float) r.getWidth();
1266       height = (float) r.getHeight();
1267       this.start = start;
1268       this.extent = (float) extent;
1269     }
1270
1271     /**
1272      * Return the x coordinate of the bounding box.
1273      *
1274      * @return the value of x
1275      */
1276     public double getX()
1277     {
1278       return x;
1279     }
1280
1281     /**
1282      * Return the y coordinate of the bounding box.
1283      *
1284      * @return the value of y
1285      */
1286     public double getY()
1287     {
1288       return y;
1289     }
1290
1291     /**
1292      * Return the width of the bounding box.
1293      *
1294      * @return the value of width
1295      */
1296     public double getWidth()
1297     {
1298       return width;
1299     }
1300
1301     /**
1302      * Return the height of the bounding box.
1303      *
1304      * @return the value of height
1305      */
1306     public double getHeight()
1307     {
1308       return height;
1309     }
1310
1311     /**
1312      * Return the start angle of the arc, in degrees.
1313      *
1314      * @return the value of start
1315      */
1316     public double getAngleStart()
1317     {
1318       return start;
1319     }
1320
1321     /**
1322      * Return the extent of the arc, in degrees.
1323      *
1324      * @return the value of extent
1325      */
1326     public double getAngleExtent()
1327     {
1328       return extent;
1329     }
1330
1331     /**
1332      * Tests if the arc contains points.
1333      *
1334      * @return true if the arc has no interior
1335      */
1336     public boolean isEmpty()
1337     {
1338       return width <= 0 || height <= 0;
1339     }
1340
1341     /**
1342      * Sets the arc to the given dimensions.
1343      *
1344      * @param x the x coordinate
1345      * @param y the y coordinate
1346      * @param w the width
1347      * @param h the height
1348      * @param start the start angle, in degrees
1349      * @param extent the extent, in degrees
1350      * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE}
1351      * @throws IllegalArgumentException if type is invalid
1352      */
1353     public void setArc(double x, double y, double w, double h, double start,
1354                        double extent, int type)
1355     {
1356       this.x = (float) x;
1357       this.y = (float) y;
1358       width = (float) w;
1359       height = (float) h;
1360       this.start = (float) start;
1361       this.extent = (float) extent;
1362       setArcType(type);
1363     }
1364
1365     /**
1366      * Sets the start angle of the arc.
1367      *
1368      * @param start the new start angle
1369      */
1370     public void setAngleStart(double start)
1371     {
1372       this.start = (float) start;
1373     }
1374
1375     /**
1376      * Sets the extent angle of the arc.
1377      *
1378      * @param extent the new extent angle
1379      */
1380     public void setAngleExtent(double extent)
1381     {
1382       this.extent = (float) extent;
1383     }
1384
1385     /**
1386      * Creates a tight bounding box given dimensions that more precise than
1387      * the bounding box of the ellipse.
1388      *
1389      * @param x the x coordinate
1390      * @param y the y coordinate
1391      * @param w the width
1392      * @param h the height
1393      */
1394     protected Rectangle2D makeBounds(double x, double y, double w, double h)
1395     {
1396       return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h);
1397     }
1398   } // class Float
1399 } // class Arc2D