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