| /* Arc2D.java -- represents an arc in 2-D space |
| Copyright (C) 2002, 2003, 2004 Free Software Foundation |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath is distributed in the hope that it will be useful, but |
| WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
| General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| package java.awt.geom; |
| |
| import java.util.NoSuchElementException; |
| |
| |
| /** |
| * This class represents all arcs (segments of an ellipse in 2-D space). The |
| * arcs are defined by starting angle and extent (arc length) in degrees, as |
| * opposed to radians (like the rest of Java), and can be open, chorded, or |
| * wedge shaped. The angles are skewed according to the ellipse, so that 45 |
| * degrees always points to the upper right corner (positive x, negative y) |
| * of the bounding rectangle. A positive extent draws a counterclockwise arc, |
| * and while the angle can be any value, the path iterator only traverses the |
| * first 360 degrees. Storage is up to the subclasses. |
| * |
| * @author Eric Blake (ebb9@email.byu.edu) |
| * @author Sven de Marothy (sven@physto.se) |
| * @since 1.2 |
| */ |
| public abstract class Arc2D extends RectangularShape |
| { |
| /** |
| * An open arc, with no segment connecting the endpoints. This type of |
| * arc still contains the same points as a chorded version. |
| */ |
| public static final int OPEN = 0; |
| |
| /** |
| * A closed arc with a single segment connecting the endpoints (a chord). |
| */ |
| public static final int CHORD = 1; |
| |
| /** |
| * A closed arc with two segments, one from each endpoint, meeting at the |
| * center of the ellipse. |
| */ |
| public static final int PIE = 2; |
| |
| /** The closure type of this arc. This is package-private to avoid an |
| * accessor method. */ |
| int type; |
| |
| /** |
| * Create a new arc, with the specified closure type. |
| * |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE}. |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| protected Arc2D(int type) |
| { |
| if (type < OPEN || type > PIE) |
| throw new IllegalArgumentException(); |
| this.type = type; |
| } |
| |
| /** |
| * Get the starting angle of the arc in degrees. |
| * |
| * @return the starting angle |
| * @see #setAngleStart(double) |
| */ |
| public abstract double getAngleStart(); |
| |
| /** |
| * Get the extent angle of the arc in degrees. |
| * |
| * @return the extent angle |
| * @see #setAngleExtent(double) |
| */ |
| public abstract double getAngleExtent(); |
| |
| /** |
| * Return the closure type of the arc. |
| * |
| * @return the closure type |
| * @see #OPEN |
| * @see #CHORD |
| * @see #PIE |
| * @see #setArcType(int) |
| */ |
| public int getArcType() |
| { |
| return type; |
| } |
| |
| /** |
| * Returns the starting point of the arc. |
| * |
| * @return the start point |
| */ |
| public Point2D getStartPoint() |
| { |
| double angle = Math.toRadians(getAngleStart()); |
| double rx = getWidth() / 2; |
| double ry = getHeight() / 2; |
| double x = getX() + rx + rx * Math.cos(angle); |
| double y = getY() + ry - ry * Math.sin(angle); |
| return new Point2D.Double(x, y); |
| } |
| |
| /** |
| * Returns the ending point of the arc. |
| * |
| * @return the end point |
| */ |
| public Point2D getEndPoint() |
| { |
| double angle = Math.toRadians(getAngleStart() + getAngleExtent()); |
| double rx = getWidth() / 2; |
| double ry = getHeight() / 2; |
| double x = getX() + rx + rx * Math.cos(angle); |
| double y = getY() + ry - ry * Math.sin(angle); |
| return new Point2D.Double(x, y); |
| } |
| |
| /** |
| * Set the parameters of the arc. The angles are in degrees, and a positive |
| * extent sweeps counterclockwise (from the positive x-axis to the negative |
| * y-axis). |
| * |
| * @param x the new x coordinate of the upper left of the bounding box |
| * @param y the new y coordinate of the upper left of the bounding box |
| * @param w the new width of the bounding box |
| * @param h the new height of the bounding box |
| * @param start the start angle, in degrees |
| * @param extent the arc extent, in degrees |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public abstract void setArc(double x, double y, double w, double h, |
| double start, double extent, int type); |
| |
| /** |
| * Set the parameters of the arc. The angles are in degrees, and a positive |
| * extent sweeps counterclockwise (from the positive x-axis to the negative |
| * y-axis). |
| * |
| * @param p the upper left point of the bounding box |
| * @param d the dimensions of the bounding box |
| * @param start the start angle, in degrees |
| * @param extent the arc extent, in degrees |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| * @throws NullPointerException if p or d is null |
| */ |
| public void setArc(Point2D p, Dimension2D d, double start, double extent, |
| int type) |
| { |
| setArc(p.getX(), p.getY(), d.getWidth(), d.getHeight(), start, extent, type); |
| } |
| |
| /** |
| * Set the parameters of the arc. The angles are in degrees, and a positive |
| * extent sweeps counterclockwise (from the positive x-axis to the negative |
| * y-axis). |
| * |
| * @param r the new bounding box |
| * @param start the start angle, in degrees |
| * @param extent the arc extent, in degrees |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| * @throws NullPointerException if r is null |
| */ |
| public void setArc(Rectangle2D r, double start, double extent, int type) |
| { |
| setArc(r.getX(), r.getY(), r.getWidth(), r.getHeight(), start, extent, type); |
| } |
| |
| /** |
| * Set the parameters of the arc from the given one. |
| * |
| * @param a the arc to copy |
| * @throws NullPointerException if a is null |
| */ |
| public void setArc(Arc2D a) |
| { |
| setArc(a.getX(), a.getY(), a.getWidth(), a.getHeight(), a.getAngleStart(), |
| a.getAngleExtent(), a.getArcType()); |
| } |
| |
| /** |
| * Set the parameters of the arc. The angles are in degrees, and a positive |
| * extent sweeps counterclockwise (from the positive x-axis to the negative |
| * y-axis). This controls the center point and radius, so the arc will be |
| * circular. |
| * |
| * @param x the x coordinate of the center of the circle |
| * @param y the y coordinate of the center of the circle |
| * @param r the radius of the circle |
| * @param start the start angle, in degrees |
| * @param extent the arc extent, in degrees |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public void setArcByCenter(double x, double y, double r, double start, |
| double extent, int type) |
| { |
| setArc(x - r, y - r, r + r, r + r, start, extent, type); |
| } |
| |
| /** |
| * Sets the parameters of the arc by finding the tangents of two lines, and |
| * using the specified radius. The arc will be circular, will begin on the |
| * tangent point of the line extending from p1 to p2, and will end on the |
| * tangent point of the line extending from p2 to p3. |
| * |
| * XXX What happens if the points are colinear, or the radius negative? |
| * |
| * @param p1 the first point |
| * @param p2 the tangent line intersection point |
| * @param p3 the third point |
| * @param r the radius of the arc |
| * @throws NullPointerException if any point is null |
| */ |
| public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3, double r) |
| { |
| if ((p2.getX() - p1.getX()) * (p3.getY() - p1.getY()) |
| - (p3.getX() - p1.getX()) * (p2.getY() - p1.getY()) > 0) |
| { |
| Point2D p = p3; |
| p3 = p1; |
| p1 = p; |
| } |
| |
| // normalized tangent vectors |
| double dx1 = (p1.getX() - p2.getX()) / p1.distance(p2); |
| double dy1 = (p1.getY() - p2.getY()) / p1.distance(p2); |
| double dx2 = (p2.getX() - p3.getX()) / p3.distance(p2); |
| double dy2 = (p2.getY() - p3.getY()) / p3.distance(p2); |
| double theta1 = Math.atan2(dx1, dy1); |
| double theta2 = Math.atan2(dx2, dy2); |
| |
| double dx = r * Math.cos(theta2) - r * Math.cos(theta1); |
| double dy = -r * Math.sin(theta2) + r * Math.sin(theta1); |
| |
| if (theta1 < 0) |
| theta1 += 2 * Math.PI; |
| if (theta2 < 0) |
| theta2 += 2 * Math.PI; |
| if (theta2 < theta1) |
| theta2 += 2 * Math.PI; |
| |
| // Vectors of the lines, not normalized, note we change |
| // the direction of line 2. |
| dx1 = p1.getX() - p2.getX(); |
| dy1 = p1.getY() - p2.getY(); |
| dx2 = p3.getX() - p2.getX(); |
| dy2 = p3.getY() - p2.getY(); |
| |
| // Calculate the tangent point to the second line |
| double t2 = -(dx1 * dy - dy1 * dx) / (dx2 * dy1 - dx1 * dy2); |
| double x2 = t2 * (p3.getX() - p2.getX()) + p2.getX(); |
| double y2 = t2 * (p3.getY() - p2.getY()) + p2.getY(); |
| |
| // calculate the center point |
| double x = x2 - r * Math.cos(theta2); |
| double y = y2 + r * Math.sin(theta2); |
| |
| setArc(x - r, y - r, 2 * r, 2 * r, Math.toDegrees(theta1), |
| Math.toDegrees(theta2 - theta1), getArcType()); |
| } |
| |
| /** |
| * Set the start, in degrees. |
| * |
| * @param start the new start angle |
| * @see #getAngleStart() |
| */ |
| public abstract void setAngleStart(double start); |
| |
| /** |
| * Set the extent, in degrees. |
| * |
| * @param extent the new extent angle |
| * @see #getAngleExtent() |
| */ |
| public abstract void setAngleExtent(double extent); |
| |
| /** |
| * Sets the starting angle to the angle of the given point relative to |
| * the center of the arc. The extent remains constant; in other words, |
| * this rotates the arc. |
| * |
| * @param p the new start point |
| * @throws NullPointerException if p is null |
| * @see #getStartPoint() |
| * @see #getAngleStart() |
| */ |
| public void setAngleStart(Point2D p) |
| { |
| // Normalize. |
| double x = p.getX() - (getX() + getWidth() / 2); |
| double y = p.getY() - (getY() + getHeight() / 2); |
| setAngleStart(Math.toDegrees(Math.atan2(-y, x))); |
| } |
| |
| /** |
| * Sets the starting and extent angles to those of the given points |
| * relative to the center of the arc. The arc will be non-empty, and will |
| * extend counterclockwise. |
| * |
| * @param x1 the first x coordinate |
| * @param y1 the first y coordinate |
| * @param x2 the second x coordinate |
| * @param y2 the second y coordinate |
| * @see #setAngleStart(Point2D) |
| */ |
| public void setAngles(double x1, double y1, double x2, double y2) |
| { |
| // Normalize the points. |
| double mx = getX(); |
| double my = getY(); |
| double mw = getWidth(); |
| double mh = getHeight(); |
| x1 = x1 - (mx + mw / 2); |
| y1 = y1 - (my + mh / 2); |
| x2 = x2 - (mx + mw / 2); |
| y2 = y2 - (my + mh / 2); |
| double start = Math.toDegrees(Math.atan2(-y1, x1)); |
| double extent = Math.toDegrees(Math.atan2(-y2, x2)) - start; |
| if (extent < 0) |
| extent += 360; |
| setAngleStart(start); |
| setAngleExtent(extent); |
| } |
| |
| /** |
| * Sets the starting and extent angles to those of the given points |
| * relative to the center of the arc. The arc will be non-empty, and will |
| * extend counterclockwise. |
| * |
| * @param p1 the first point |
| * @param p2 the second point |
| * @throws NullPointerException if either point is null |
| * @see #setAngleStart(Point2D) |
| */ |
| public void setAngles(Point2D p1, Point2D p2) |
| { |
| setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY()); |
| } |
| |
| /** |
| * Set the closure type of this arc. |
| * |
| * @param type one of {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| * @see #getArcType() |
| */ |
| public void setArcType(int type) |
| { |
| if (type < OPEN || type > PIE) |
| throw new IllegalArgumentException(); |
| this.type = type; |
| } |
| |
| /** |
| * Sets the location and bounds of the ellipse of which this arc is a part. |
| * |
| * @param x the new x coordinate |
| * @param y the new y coordinate |
| * @param w the new width |
| * @param h the new height |
| * @see #getFrame() |
| */ |
| public void setFrame(double x, double y, double w, double h) |
| { |
| setArc(x, y, w, h, getAngleStart(), getAngleExtent(), type); |
| } |
| |
| /** |
| * Gets the bounds of the arc. This is much tighter than |
| * <code>getBounds</code>, as it takes into consideration the start and |
| * end angles, and the center point of a pie wedge, rather than just the |
| * overall ellipse. |
| * |
| * @return the bounds of the arc |
| * @see #getBounds() |
| */ |
| public Rectangle2D getBounds2D() |
| { |
| double extent = getAngleExtent(); |
| if (Math.abs(extent) >= 360) |
| return makeBounds(getX(), getY(), getWidth(), getHeight()); |
| |
| // Find the minimal bounding box. This determined by its extrema, |
| // which are the center, the endpoints of the arc, and any local |
| // maximum contained by the arc. |
| double rX = getWidth() / 2; |
| double rY = getHeight() / 2; |
| double centerX = getX() + rX; |
| double centerY = getY() + rY; |
| |
| Point2D p1 = getStartPoint(); |
| Rectangle2D result = makeBounds(p1.getX(), p1.getY(), 0, 0); |
| result.add(getEndPoint()); |
| |
| if (type == PIE) |
| result.add(centerX, centerY); |
| if (containsAngle(0)) |
| result.add(centerX + rX, centerY); |
| if (containsAngle(90)) |
| result.add(centerX, centerY - rY); |
| if (containsAngle(180)) |
| result.add(centerX - rX, centerY); |
| if (containsAngle(270)) |
| result.add(centerX, centerY + rY); |
| |
| return result; |
| } |
| |
| /** |
| * Construct a bounding box in a precision appropriate for the subclass. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| * @return the rectangle for use in getBounds2D |
| */ |
| protected abstract Rectangle2D makeBounds(double x, double y, double w, |
| double h); |
| |
| /** |
| * Tests if the given angle, in degrees, is included in the arc. |
| * All angles are normalized to be between 0 and 360 degrees. |
| * |
| * @param a the angle to test |
| * @return true if it is contained |
| */ |
| public boolean containsAngle(double a) |
| { |
| double start = getAngleStart(); |
| double extent = getAngleExtent(); |
| double end = start + extent; |
| |
| if (extent == 0) |
| return false; |
| |
| if (extent >= 360 || extent <= -360) |
| return true; |
| |
| if (extent < 0) |
| { |
| end = start; |
| start += extent; |
| } |
| |
| start %= 360; |
| while (start < 0) |
| start += 360; |
| |
| end %= 360; |
| while (end < start) |
| end += 360; |
| |
| a %= 360; |
| while (a < start) |
| a += 360; |
| |
| return a >= start && a < end; // starting angle included, ending angle not |
| } |
| |
| /** |
| * Determines if the arc contains the given point. If the bounding box |
| * is empty, then this will return false. |
| * |
| * The area considered 'inside' an arc of type OPEN is the same as the |
| * area inside an equivalent filled CHORD-type arc. The area considered |
| * 'inside' a CHORD-type arc is the same as the filled area. |
| * |
| * @param x the x coordinate to test |
| * @param y the y coordinate to test |
| * @return true if the point is inside the arc |
| */ |
| public boolean contains(double x, double y) |
| { |
| double w = getWidth(); |
| double h = getHeight(); |
| double extent = getAngleExtent(); |
| if (w <= 0 || h <= 0 || extent == 0) |
| return false; |
| |
| double mx = getX() + w / 2; |
| double my = getY() + h / 2; |
| double dx = (x - mx) * 2 / w; |
| double dy = (y - my) * 2 / h; |
| if ((dx * dx + dy * dy) >= 1.0) |
| return false; |
| |
| double angle = Math.toDegrees(Math.atan2(-dy, dx)); |
| if (getArcType() == PIE) |
| return containsAngle(angle); |
| |
| double a1 = Math.toRadians(getAngleStart()); |
| double a2 = Math.toRadians(getAngleStart() + extent); |
| double x1 = mx + getWidth() * Math.cos(a1) / 2; |
| double y1 = my - getHeight() * Math.sin(a1) / 2; |
| double x2 = mx + getWidth() * Math.cos(a2) / 2; |
| double y2 = my - getHeight() * Math.sin(a2) / 2; |
| double sgn = ((x2 - x1) * (my - y1) - (mx - x1) * (y2 - y1)) * ((x2 - x1) * (y |
| - y1) - (x - x1) * (y2 - y1)); |
| |
| if (Math.abs(extent) > 180) |
| { |
| if (containsAngle(angle)) |
| return true; |
| return sgn > 0; |
| } |
| else |
| { |
| if (! containsAngle(angle)) |
| return false; |
| return sgn < 0; |
| } |
| } |
| |
| /** |
| * Tests if a given rectangle intersects the area of the arc. |
| * |
| * For a definition of the 'inside' area, see the contains() method. |
| * @see #contains(double, double) |
| * |
| * @param x the x coordinate of the rectangle |
| * @param y the y coordinate of the rectangle |
| * @param w the width of the rectangle |
| * @param h the height of the rectangle |
| * @return true if the two shapes share common points |
| */ |
| public boolean intersects(double x, double y, double w, double h) |
| { |
| double extent = getAngleExtent(); |
| if (extent == 0) |
| return false; |
| |
| if (contains(x, y) || contains(x, y + h) || contains(x + w, y) |
| || contains(x + w, y + h)) |
| return true; |
| |
| Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); |
| |
| double a = getWidth() / 2.0; |
| double b = getHeight() / 2.0; |
| |
| double mx = getX() + a; |
| double my = getY() + b; |
| double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); |
| double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); |
| double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); |
| double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); |
| |
| if (getArcType() != CHORD) |
| { |
| // check intersections against the pie radii |
| if (rect.intersectsLine(mx, my, x1, y1)) |
| return true; |
| if (rect.intersectsLine(mx, my, x2, y2)) |
| return true; |
| } |
| else// check the chord |
| if (rect.intersectsLine(x1, y1, x2, y2)) |
| return true; |
| |
| // Check the Arc segment against the four edges |
| double dx; |
| |
| // Check the Arc segment against the four edges |
| double dy; |
| dy = y - my; |
| dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); |
| if (! java.lang.Double.isNaN(dx)) |
| { |
| if (mx + dx >= x && mx + dx <= x + w |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) |
| return true; |
| if (mx - dx >= x && mx - dx <= x + w |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) |
| return true; |
| } |
| dy = (y + h) - my; |
| dx = a * Math.sqrt(1 - ((dy * dy) / (b * b))); |
| if (! java.lang.Double.isNaN(dx)) |
| { |
| if (mx + dx >= x && mx + dx <= x + w |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) |
| return true; |
| if (mx - dx >= x && mx - dx <= x + w |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, -dx)))) |
| return true; |
| } |
| dx = x - mx; |
| dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); |
| if (! java.lang.Double.isNaN(dy)) |
| { |
| if (my + dy >= y && my + dy <= y + h |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) |
| return true; |
| if (my - dy >= y && my - dy <= y + h |
| && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) |
| return true; |
| } |
| |
| dx = (x + w) - mx; |
| dy = b * Math.sqrt(1 - ((dx * dx) / (a * a))); |
| if (! java.lang.Double.isNaN(dy)) |
| { |
| if (my + dy >= y && my + dy <= y + h |
| && containsAngle(Math.toDegrees(Math.atan2(-dy, dx)))) |
| return true; |
| if (my - dy >= y && my - dy <= y + h |
| && containsAngle(Math.toDegrees(Math.atan2(dy, dx)))) |
| return true; |
| } |
| |
| // Check whether the arc is contained within the box |
| if (rect.contains(mx, my)) |
| return true; |
| |
| return false; |
| } |
| |
| /** |
| * Tests if a given rectangle is contained in the area of the arc. |
| * |
| * @param x the x coordinate of the rectangle |
| * @param y the y coordinate of the rectangle |
| * @param w the width of the rectangle |
| * @param h the height of the rectangle |
| * @return true if the arc contains the rectangle |
| */ |
| public boolean contains(double x, double y, double w, double h) |
| { |
| double extent = getAngleExtent(); |
| if (extent == 0) |
| return false; |
| |
| if (! (contains(x, y) && contains(x, y + h) && contains(x + w, y) |
| && contains(x + w, y + h))) |
| return false; |
| |
| Rectangle2D rect = new Rectangle2D.Double(x, y, w, h); |
| |
| double a = getWidth() / 2.0; |
| double b = getHeight() / 2.0; |
| |
| double mx = getX() + a; |
| double my = getY() + b; |
| double x1 = mx + a * Math.cos(Math.toRadians(getAngleStart())); |
| double y1 = my - b * Math.sin(Math.toRadians(getAngleStart())); |
| double x2 = mx + a * Math.cos(Math.toRadians(getAngleStart() + extent)); |
| double y2 = my - b * Math.sin(Math.toRadians(getAngleStart() + extent)); |
| if (getArcType() != CHORD) |
| { |
| // check intersections against the pie radii |
| if (rect.intersectsLine(mx, my, x1, y1)) |
| return false; |
| |
| if (rect.intersectsLine(mx, my, x2, y2)) |
| return false; |
| } |
| else if (rect.intersectsLine(x1, y1, x2, y2)) |
| return false; |
| return true; |
| } |
| |
| /** |
| * Tests if a given rectangle is contained in the area of the arc. |
| * |
| * @param r the rectangle |
| * @return true if the arc contains the rectangle |
| */ |
| public boolean contains(Rectangle2D r) |
| { |
| return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); |
| } |
| |
| /** |
| * Returns an iterator over this arc, with an optional transformation. |
| * This iterator is threadsafe, so future modifications to the arc do not |
| * affect the iteration. |
| * |
| * @param at the transformation, or null |
| * @return a path iterator |
| */ |
| public PathIterator getPathIterator(AffineTransform at) |
| { |
| return new ArcIterator(this, at); |
| } |
| |
| /** |
| * This class is used to iterate over an arc. Since ellipses are a subclass |
| * of arcs, this is used by Ellipse2D as well. |
| * |
| * @author Eric Blake (ebb9@email.byu.edu) |
| */ |
| static final class ArcIterator implements PathIterator |
| { |
| /** The current iteration. */ |
| private int current; |
| |
| /** The last iteration. */ |
| private final int limit; |
| |
| /** The optional transformation. */ |
| private final AffineTransform xform; |
| |
| /** The x coordinate of the bounding box. */ |
| private final double x; |
| |
| /** The y coordinate of the bounding box. */ |
| private final double y; |
| |
| /** The width of the bounding box. */ |
| private final double w; |
| |
| /** The height of the bounding box. */ |
| private final double h; |
| |
| /** The start angle, in radians (not degrees). */ |
| private final double start; |
| |
| /** The extent angle, in radians (not degrees). */ |
| private final double extent; |
| |
| /** The arc closure type. */ |
| private final int type; |
| |
| /** |
| * Construct a new iterator over an arc. |
| * |
| * @param a the arc |
| * @param xform the transform |
| */ |
| public ArcIterator(Arc2D a, AffineTransform xform) |
| { |
| this.xform = xform; |
| x = a.getX(); |
| y = a.getY(); |
| w = a.getWidth(); |
| h = a.getHeight(); |
| double start = Math.toRadians(a.getAngleStart()); |
| double extent = Math.toRadians(a.getAngleExtent()); |
| |
| this.start = start; |
| this.extent = extent; |
| |
| type = a.type; |
| if (w < 0 || h < 0) |
| limit = -1; |
| else if (extent == 0) |
| limit = type; |
| else if (Math.abs(extent) <= Math.PI / 2.0) |
| limit = type + 1; |
| else if (Math.abs(extent) <= Math.PI) |
| limit = type + 2; |
| else if (Math.abs(extent) <= 3.0 * (Math.PI / 2.0)) |
| limit = type + 3; |
| else |
| limit = type + 4; |
| } |
| |
| /** |
| * Construct a new iterator over an ellipse. |
| * |
| * @param e the ellipse |
| * @param xform the transform |
| */ |
| public ArcIterator(Ellipse2D e, AffineTransform xform) |
| { |
| this.xform = xform; |
| x = e.getX(); |
| y = e.getY(); |
| w = e.getWidth(); |
| h = e.getHeight(); |
| start = 0; |
| extent = 2 * Math.PI; |
| type = CHORD; |
| limit = (w < 0 || h < 0) ? -1 : 5; |
| } |
| |
| /** |
| * Return the winding rule. |
| * |
| * @return {@link PathIterator#WIND_NON_ZERO} |
| */ |
| public int getWindingRule() |
| { |
| return WIND_NON_ZERO; |
| } |
| |
| /** |
| * Test if the iteration is complete. |
| * |
| * @return true if more segments exist |
| */ |
| public boolean isDone() |
| { |
| return current > limit; |
| } |
| |
| /** |
| * Advance the iterator. |
| */ |
| public void next() |
| { |
| current++; |
| } |
| |
| /** |
| * Put the current segment into the array, and return the segment type. |
| * |
| * @param coords an array of 6 elements |
| * @return the segment type |
| * @throws NullPointerException if coords is null |
| * @throws ArrayIndexOutOfBoundsException if coords is too small |
| */ |
| public int currentSegment(float[] coords) |
| { |
| double[] double_coords = new double[6]; |
| int code = currentSegment(double_coords); |
| for (int i = 0; i < 6; ++i) |
| coords[i] = (float) double_coords[i]; |
| return code; |
| } |
| |
| /** |
| * Put the current segment into the array, and return the segment type. |
| * |
| * @param coords an array of 6 elements |
| * @return the segment type |
| * @throws NullPointerException if coords is null |
| * @throws ArrayIndexOutOfBoundsException if coords is too small |
| */ |
| public int currentSegment(double[] coords) |
| { |
| double rx = w / 2; |
| double ry = h / 2; |
| double xmid = x + rx; |
| double ymid = y + ry; |
| |
| if (current > limit) |
| throw new NoSuchElementException("arc iterator out of bounds"); |
| |
| if (current == 0) |
| { |
| coords[0] = xmid + rx * Math.cos(start); |
| coords[1] = ymid - ry * Math.sin(start); |
| if (xform != null) |
| xform.transform(coords, 0, coords, 0, 1); |
| return SEG_MOVETO; |
| } |
| |
| if (type != OPEN && current == limit) |
| return SEG_CLOSE; |
| |
| if ((current == limit - 1) && (type == PIE)) |
| { |
| coords[0] = xmid; |
| coords[1] = ymid; |
| if (xform != null) |
| xform.transform(coords, 0, coords, 0, 1); |
| return SEG_LINETO; |
| } |
| |
| // note that this produces a cubic approximation of the arc segment, |
| // not a true ellipsoid. there's no ellipsoid path segment code, |
| // unfortunately. the cubic approximation looks about right, though. |
| double kappa = (Math.sqrt(2.0) - 1.0) * (4.0 / 3.0); |
| double quad = (Math.PI / 2.0); |
| |
| double curr_begin; |
| double curr_extent; |
| if (extent > 0) |
| { |
| curr_begin = start + (current - 1) * quad; |
| curr_extent = Math.min((start + extent) - curr_begin, quad); |
| } |
| else |
| { |
| curr_begin = start - (current - 1) * quad; |
| curr_extent = Math.max((start + extent) - curr_begin, -quad); |
| } |
| |
| double portion_of_a_quadrant = Math.abs(curr_extent / quad); |
| |
| double x0 = xmid + rx * Math.cos(curr_begin); |
| double y0 = ymid - ry * Math.sin(curr_begin); |
| |
| double x1 = xmid + rx * Math.cos(curr_begin + curr_extent); |
| double y1 = ymid - ry * Math.sin(curr_begin + curr_extent); |
| |
| AffineTransform trans = new AffineTransform(); |
| double[] cvec = new double[2]; |
| double len = kappa * portion_of_a_quadrant; |
| double angle = curr_begin; |
| |
| // in a hypothetical "first quadrant" setting, our first control |
| // vector would be sticking up, from [1,0] to [1,kappa]. |
| // |
| // let us recall however that in java2d, y coords are upside down |
| // from what one would consider "normal" first quadrant rules, so we |
| // will *subtract* the y value of this control vector from our first |
| // point. |
| cvec[0] = 0; |
| if (extent > 0) |
| cvec[1] = len; |
| else |
| cvec[1] = -len; |
| |
| trans.scale(rx, ry); |
| trans.rotate(angle); |
| trans.transform(cvec, 0, cvec, 0, 1); |
| coords[0] = x0 + cvec[0]; |
| coords[1] = y0 - cvec[1]; |
| |
| // control vector #2 would, ideally, be sticking out and to the |
| // right, in a first quadrant arc segment. again, subtraction of y. |
| cvec[0] = 0; |
| if (extent > 0) |
| cvec[1] = -len; |
| else |
| cvec[1] = len; |
| |
| trans.rotate(curr_extent); |
| trans.transform(cvec, 0, cvec, 0, 1); |
| coords[2] = x1 + cvec[0]; |
| coords[3] = y1 - cvec[1]; |
| |
| // end point |
| coords[4] = x1; |
| coords[5] = y1; |
| |
| if (xform != null) |
| xform.transform(coords, 0, coords, 0, 3); |
| |
| return SEG_CUBICTO; |
| } |
| } // class ArcIterator |
| |
| /** |
| * This class implements an arc in double precision. |
| * |
| * @author Eric Blake (ebb9@email.byu.edu) |
| * @since 1.2 |
| */ |
| public static class Double extends Arc2D |
| { |
| /** The x coordinate of the box bounding the ellipse of this arc. */ |
| public double x; |
| |
| /** The y coordinate of the box bounding the ellipse of this arc. */ |
| public double y; |
| |
| /** The width of the box bounding the ellipse of this arc. */ |
| public double width; |
| |
| /** The height of the box bounding the ellipse of this arc. */ |
| public double height; |
| |
| /** The start angle of this arc, in degrees. */ |
| public double start; |
| |
| /** The extent angle of this arc, in degrees. */ |
| public double extent; |
| |
| /** |
| * Create a new, open arc at (0,0) with 0 extent. |
| */ |
| public Double() |
| { |
| super(OPEN); |
| } |
| |
| /** |
| * Create a new arc of the given type at (0,0) with 0 extent. |
| * |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public Double(int type) |
| { |
| super(type); |
| } |
| |
| /** |
| * Create a new arc with the given dimensions. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public Double(double x, double y, double w, double h, double start, |
| double extent, int type) |
| { |
| super(type); |
| this.x = x; |
| this.y = y; |
| width = w; |
| height = h; |
| this.start = start; |
| this.extent = extent; |
| } |
| |
| /** |
| * Create a new arc with the given dimensions. |
| * |
| * @param r the bounding box |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| * @throws NullPointerException if r is null |
| */ |
| public Double(Rectangle2D r, double start, double extent, int type) |
| { |
| super(type); |
| x = r.getX(); |
| y = r.getY(); |
| width = r.getWidth(); |
| height = r.getHeight(); |
| this.start = start; |
| this.extent = extent; |
| } |
| |
| /** |
| * Return the x coordinate of the bounding box. |
| * |
| * @return the value of x |
| */ |
| public double getX() |
| { |
| return x; |
| } |
| |
| /** |
| * Return the y coordinate of the bounding box. |
| * |
| * @return the value of y |
| */ |
| public double getY() |
| { |
| return y; |
| } |
| |
| /** |
| * Return the width of the bounding box. |
| * |
| * @return the value of width |
| */ |
| public double getWidth() |
| { |
| return width; |
| } |
| |
| /** |
| * Return the height of the bounding box. |
| * |
| * @return the value of height |
| */ |
| public double getHeight() |
| { |
| return height; |
| } |
| |
| /** |
| * Return the start angle of the arc, in degrees. |
| * |
| * @return the value of start |
| */ |
| public double getAngleStart() |
| { |
| return start; |
| } |
| |
| /** |
| * Return the extent of the arc, in degrees. |
| * |
| * @return the value of extent |
| */ |
| public double getAngleExtent() |
| { |
| return extent; |
| } |
| |
| /** |
| * Tests if the arc contains points. |
| * |
| * @return true if the arc has no interior |
| */ |
| public boolean isEmpty() |
| { |
| return width <= 0 || height <= 0; |
| } |
| |
| /** |
| * Sets the arc to the given dimensions. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public void setArc(double x, double y, double w, double h, double start, |
| double extent, int type) |
| { |
| this.x = x; |
| this.y = y; |
| width = w; |
| height = h; |
| this.start = start; |
| this.extent = extent; |
| setArcType(type); |
| } |
| |
| /** |
| * Sets the start angle of the arc. |
| * |
| * @param start the new start angle |
| */ |
| public void setAngleStart(double start) |
| { |
| this.start = start; |
| } |
| |
| /** |
| * Sets the extent angle of the arc. |
| * |
| * @param extent the new extent angle |
| */ |
| public void setAngleExtent(double extent) |
| { |
| this.extent = extent; |
| } |
| |
| /** |
| * Creates a tight bounding box given dimensions that more precise than |
| * the bounding box of the ellipse. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| */ |
| protected Rectangle2D makeBounds(double x, double y, double w, double h) |
| { |
| return new Rectangle2D.Double(x, y, w, h); |
| } |
| } // class Double |
| |
| /** |
| * This class implements an arc in float precision. |
| * |
| * @author Eric Blake (ebb9@email.byu.edu) |
| * @since 1.2 |
| */ |
| public static class Float extends Arc2D |
| { |
| /** The x coordinate of the box bounding the ellipse of this arc. */ |
| public float x; |
| |
| /** The y coordinate of the box bounding the ellipse of this arc. */ |
| public float y; |
| |
| /** The width of the box bounding the ellipse of this arc. */ |
| public float width; |
| |
| /** The height of the box bounding the ellipse of this arc. */ |
| public float height; |
| |
| /** The start angle of this arc, in degrees. */ |
| public float start; |
| |
| /** The extent angle of this arc, in degrees. */ |
| public float extent; |
| |
| /** |
| * Create a new, open arc at (0,0) with 0 extent. |
| */ |
| public Float() |
| { |
| super(OPEN); |
| } |
| |
| /** |
| * Create a new arc of the given type at (0,0) with 0 extent. |
| * |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public Float(int type) |
| { |
| super(type); |
| } |
| |
| /** |
| * Create a new arc with the given dimensions. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public Float(float x, float y, float w, float h, float start, |
| float extent, int type) |
| { |
| super(type); |
| this.x = x; |
| this.y = y; |
| width = w; |
| height = h; |
| this.start = start; |
| this.extent = extent; |
| } |
| |
| /** |
| * Create a new arc with the given dimensions. |
| * |
| * @param r the bounding box |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| * @throws NullPointerException if r is null |
| */ |
| public Float(Rectangle2D r, float start, float extent, int type) |
| { |
| super(type); |
| x = (float) r.getX(); |
| y = (float) r.getY(); |
| width = (float) r.getWidth(); |
| height = (float) r.getHeight(); |
| this.start = start; |
| this.extent = (float) extent; |
| } |
| |
| /** |
| * Return the x coordinate of the bounding box. |
| * |
| * @return the value of x |
| */ |
| public double getX() |
| { |
| return x; |
| } |
| |
| /** |
| * Return the y coordinate of the bounding box. |
| * |
| * @return the value of y |
| */ |
| public double getY() |
| { |
| return y; |
| } |
| |
| /** |
| * Return the width of the bounding box. |
| * |
| * @return the value of width |
| */ |
| public double getWidth() |
| { |
| return width; |
| } |
| |
| /** |
| * Return the height of the bounding box. |
| * |
| * @return the value of height |
| */ |
| public double getHeight() |
| { |
| return height; |
| } |
| |
| /** |
| * Return the start angle of the arc, in degrees. |
| * |
| * @return the value of start |
| */ |
| public double getAngleStart() |
| { |
| return start; |
| } |
| |
| /** |
| * Return the extent of the arc, in degrees. |
| * |
| * @return the value of extent |
| */ |
| public double getAngleExtent() |
| { |
| return extent; |
| } |
| |
| /** |
| * Tests if the arc contains points. |
| * |
| * @return true if the arc has no interior |
| */ |
| public boolean isEmpty() |
| { |
| return width <= 0 || height <= 0; |
| } |
| |
| /** |
| * Sets the arc to the given dimensions. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| * @param start the start angle, in degrees |
| * @param extent the extent, in degrees |
| * @param type the arc type: {@link #OPEN}, {@link #CHORD}, or {@link #PIE} |
| * @throws IllegalArgumentException if type is invalid |
| */ |
| public void setArc(double x, double y, double w, double h, double start, |
| double extent, int type) |
| { |
| this.x = (float) x; |
| this.y = (float) y; |
| width = (float) w; |
| height = (float) h; |
| this.start = (float) start; |
| this.extent = (float) extent; |
| setArcType(type); |
| } |
| |
| /** |
| * Sets the start angle of the arc. |
| * |
| * @param start the new start angle |
| */ |
| public void setAngleStart(double start) |
| { |
| this.start = (float) start; |
| } |
| |
| /** |
| * Sets the extent angle of the arc. |
| * |
| * @param extent the new extent angle |
| */ |
| public void setAngleExtent(double extent) |
| { |
| this.extent = (float) extent; |
| } |
| |
| /** |
| * Creates a tight bounding box given dimensions that more precise than |
| * the bounding box of the ellipse. |
| * |
| * @param x the x coordinate |
| * @param y the y coordinate |
| * @param w the width |
| * @param h the height |
| */ |
| protected Rectangle2D makeBounds(double x, double y, double w, double h) |
| { |
| return new Rectangle2D.Float((float) x, (float) y, (float) w, (float) h); |
| } |
| } // class Float |
| } // class Arc2D |