blob: 56f5cd392d2283917c2889c42eda50438bb68318 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author Denis M. Kishenko
* @version $Revision$
*/
package java.awt.geom;
import java.util.NoSuchElementException;
import org.apache.harmony.awt.internal.nls.Messages;
/**
* The Class Arc2D represents a segment of a curve inscribed in a rectangle. The
* curve is defined by a start angle and an extent angle (the end angle minus
* the start angle) as a pie wedge whose point is in the center of the
* rectangle. The Arc2D as a shape may be either OPEN (including nothing but the
* curved arc segment itself), CHORD (the curved arc segment closed by a
* connecting segment from the end to the beginning of the arc, or PIE (the
* segments from the end of the arc to the center of the rectangle and from the
* center of the rectangle back to the arc's start point are included).
*
* @since Android 1.0
*/
public abstract class Arc2D extends RectangularShape {
/**
* The arc type OPEN indicates that the shape includes only the curved arc
* segment.
*/
public final static int OPEN = 0;
/**
* The arc type CHORD indicates that as a shape the connecting segment from
* the end point of the curved arc to the beginning point is included.
*/
public final static int CHORD = 1;
/**
* The arc type PIE indicates that as a shape the two segments from the
* arc's endpoint to the center of the rectangle and from the center of the
* rectangle to the arc's endpoint are included.
*/
public final static int PIE = 2;
/**
* The Class Float is a subclass of Arc2D in which all of the data values
* are given as floats.
*
* @see Arc2D.Double
* @since Android 1.0
*/
public static class Float extends Arc2D {
/**
* The x coordinate of the upper left corner of the rectangle that
* contains the arc.
*/
public float x;
/**
* The y coordinate of the upper left corner of the rectangle that
* contains the arc.
*/
public float y;
/**
* The width of the rectangle that contains the arc.
*/
public float width;
/**
* The height of the rectangle that contains the arc.
*/
public float height;
/**
* The start angle of the arc in degrees.
*/
public float start;
/**
* The width angle of the arc in degrees.
*/
public float extent;
/**
* Instantiates a new Arc2D of type OPEN with float values.
*/
public Float() {
super(OPEN);
}
/**
* Instantiates a new Arc2D of the specified type with float values.
*
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Float(int type) {
super(type);
}
/**
* Instantiates a Arc2D with the specified float-valued data.
*
* @param x
* the x coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param y
* the y coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param width
* the width of the rectangle that contains the arc.
* @param height
* the height of the rectangle that contains the arc.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the width angle of the arc in degrees.
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Float(float x, float y, float width, float height, float start, float extent,
int type) {
super(type);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.start = start;
this.extent = extent;
}
/**
* Instantiates a new Angle2D with the specified float-valued data and
* the bounding rectangle given by the parameter bounds.
*
* @param bounds
* the bounding rectangle of the Angle2D.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the width angle of the arc in degrees.
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Float(Rectangle2D bounds, float start, float extent, int type) {
super(type);
this.x = (float)bounds.getX();
this.y = (float)bounds.getY();
this.width = (float)bounds.getWidth();
this.height = (float)bounds.getHeight();
this.start = start;
this.extent = extent;
}
@Override
public double getX() {
return x;
}
@Override
public double getY() {
return y;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getHeight() {
return height;
}
@Override
public double getAngleStart() {
return start;
}
@Override
public double getAngleExtent() {
return extent;
}
@Override
public boolean isEmpty() {
return width <= 0.0f || height <= 0.0f;
}
@Override
public void setArc(double x, double y, double width, double height, double start,
double extent, int type) {
this.setArcType(type);
this.x = (float)x;
this.y = (float)y;
this.width = (float)width;
this.height = (float)height;
this.start = (float)start;
this.extent = (float)extent;
}
@Override
public void setAngleStart(double start) {
this.start = (float)start;
}
@Override
public void setAngleExtent(double extent) {
this.extent = (float)extent;
}
@Override
protected Rectangle2D makeBounds(double x, double y, double width, double height) {
return new Rectangle2D.Float((float)x, (float)y, (float)width, (float)height);
}
}
/**
* The Class Double is a subclass of Arc2D in which all of the data values
* are given as doubles.
*
* @see Arc2D.Float
* @since Android 1.0
*/
public static class Double extends Arc2D {
/**
* The x coordinate of the upper left corner of the rectangle that
* contains the arc.
*/
public double x;
/**
* The y coordinate of the upper left corner of the rectangle that
* contains the arc.
*/
public double y;
/**
* The width of the rectangle that contains the arc.
*/
public double width;
/**
* The height of the rectangle that contains the arc.
*/
public double height;
/**
* The start angle of the arc in degrees.
*/
public double start;
/**
* The width angle of the arc in degrees.
*/
public double extent;
/**
* Instantiates a new Arc2D of type OPEN with double values.
*/
public Double() {
super(OPEN);
}
/**
* Instantiates a new Arc2D of the specified type with double values.
*
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Double(int type) {
super(type);
}
/**
* Instantiates a Arc2D with the specified double-valued data.
*
* @param x
* the x coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param y
* the y coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param width
* the width of the rectangle that contains the arc.
* @param height
* the height of the rectangle that contains the arc.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the width angle of the arc in degrees.
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Double(double x, double y, double width, double height, double start, double extent,
int type) {
super(type);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.start = start;
this.extent = extent;
}
/**
* Instantiates a new Angle2D with the specified float-valued data and
* the bounding rectangle given by the parameter bounds.
*
* @param bounds
* the bounding rectangle of the Angle2D.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the width angle of the arc in degrees.
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public Double(Rectangle2D bounds, double start, double extent, int type) {
super(type);
this.x = bounds.getX();
this.y = bounds.getY();
this.width = bounds.getWidth();
this.height = bounds.getHeight();
this.start = start;
this.extent = extent;
}
@Override
public double getX() {
return x;
}
@Override
public double getY() {
return y;
}
@Override
public double getWidth() {
return width;
}
@Override
public double getHeight() {
return height;
}
@Override
public double getAngleStart() {
return start;
}
@Override
public double getAngleExtent() {
return extent;
}
@Override
public boolean isEmpty() {
return width <= 0.0 || height <= 0.0;
}
@Override
public void setArc(double x, double y, double width, double height, double start,
double extent, int type) {
this.setArcType(type);
this.x = x;
this.y = y;
this.width = width;
this.height = height;
this.start = start;
this.extent = extent;
}
@Override
public void setAngleStart(double start) {
this.start = start;
}
@Override
public void setAngleExtent(double extent) {
this.extent = extent;
}
@Override
protected Rectangle2D makeBounds(double x, double y, double width, double height) {
return new Rectangle2D.Double(x, y, width, height);
}
}
/**
* The Class Iterator is the subclass of PathIterator that is used to
* traverse the boundary of a shape of type Arc2D.
*/
class Iterator implements PathIterator {
/**
* The x coordinate of the center of the arc's bounding rectangle.
*/
double x;
/**
* The y coordinate of the center of the arc's bounding rectangle.
*/
double y;
/**
* Half of the width of the arc's bounding rectangle (the radius in the
* case of a circular arc).
*/
double width;
/**
* Half of the height of the arc's bounding rectangle (the radius in the
* case of a circular arc).
*/
double height;
/**
* The start angle of the arc in degrees.
*/
double angle;
/**
* The angle extent in degrees.
*/
double extent;
/**
* The closure type of the arc.
*/
int type;
/**
* The path iterator transformation.
*/
AffineTransform t;
/**
* The current segment index.
*/
int index;
/**
* The number of arc segments the source arc subdivided to be
* approximated by Bezier curves. Depends on extent value.
*/
int arcCount;
/**
* The number of line segments. Depends on closure type.
*/
int lineCount;
/**
* The step to calculate next arc subdivision point.
*/
double step;
/**
* The temporary value of cosinus of the current angle.
*/
double cos;
/**
* The temporary value of sinus of the current angle.
*/
double sin;
/** The coefficient to calculate control points of Bezier curves. */
double k;
/**
* The temporary value of x coordinate of the Bezier curve control
* vector.
*/
double kx;
/**
* The temporary value of y coordinate of the Bezier curve control
* vector.
*/
double ky;
/**
* The x coordinate of the first path point (MOVE_TO).
*/
double mx;
/**
* The y coordinate of the first path point (MOVE_TO).
*/
double my;
/**
* Constructs a new Arc2D.Iterator for given line and transformation
*
* @param a
* the source Arc2D object.
* @param t
* the AffineTransformation.
*/
Iterator(Arc2D a, AffineTransform t) {
if (width < 0 || height < 0) {
arcCount = 0;
lineCount = 0;
index = 1;
return;
}
this.width = a.getWidth() / 2.0;
this.height = a.getHeight() / 2.0;
this.x = a.getX() + width;
this.y = a.getY() + height;
this.angle = -Math.toRadians(a.getAngleStart());
this.extent = -a.getAngleExtent();
this.type = a.getArcType();
this.t = t;
if (Math.abs(extent) >= 360.0) {
arcCount = 4;
k = 4.0 / 3.0 * (Math.sqrt(2.0) - 1.0);
step = Math.PI / 2.0;
if (extent < 0.0) {
step = -step;
k = -k;
}
} else {
arcCount = (int)Math.rint(Math.abs(extent) / 90.0);
step = Math.toRadians(extent / arcCount);
k = 4.0 / 3.0 * (1.0 - Math.cos(step / 2.0)) / Math.sin(step / 2.0);
}
lineCount = 0;
if (type == Arc2D.CHORD) {
lineCount++;
} else if (type == Arc2D.PIE) {
lineCount += 2;
}
}
public int getWindingRule() {
return WIND_NON_ZERO;
}
public boolean isDone() {
return index > arcCount + lineCount;
}
public void next() {
index++;
}
public int currentSegment(double[] coords) {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
}
int type;
int count;
if (index == 0) {
type = SEG_MOVETO;
count = 1;
cos = Math.cos(angle);
sin = Math.sin(angle);
kx = k * width * sin;
ky = k * height * cos;
coords[0] = mx = x + cos * width;
coords[1] = my = y + sin * height;
} else if (index <= arcCount) {
type = SEG_CUBICTO;
count = 3;
coords[0] = mx - kx;
coords[1] = my + ky;
angle += step;
cos = Math.cos(angle);
sin = Math.sin(angle);
kx = k * width * sin;
ky = k * height * cos;
coords[4] = mx = x + cos * width;
coords[5] = my = y + sin * height;
coords[2] = mx + kx;
coords[3] = my - ky;
} else if (index == arcCount + lineCount) {
type = SEG_CLOSE;
count = 0;
} else {
type = SEG_LINETO;
count = 1;
coords[0] = x;
coords[1] = y;
}
if (t != null) {
t.transform(coords, 0, coords, 0, count);
}
return type;
}
public int currentSegment(float[] coords) {
if (isDone()) {
// awt.4B=Iterator out of bounds
throw new NoSuchElementException(Messages.getString("awt.4B")); //$NON-NLS-1$
}
int type;
int count;
if (index == 0) {
type = SEG_MOVETO;
count = 1;
cos = Math.cos(angle);
sin = Math.sin(angle);
kx = k * width * sin;
ky = k * height * cos;
coords[0] = (float)(mx = x + cos * width);
coords[1] = (float)(my = y + sin * height);
} else if (index <= arcCount) {
type = SEG_CUBICTO;
count = 3;
coords[0] = (float)(mx - kx);
coords[1] = (float)(my + ky);
angle += step;
cos = Math.cos(angle);
sin = Math.sin(angle);
kx = k * width * sin;
ky = k * height * cos;
coords[4] = (float)(mx = x + cos * width);
coords[5] = (float)(my = y + sin * height);
coords[2] = (float)(mx + kx);
coords[3] = (float)(my - ky);
} else if (index == arcCount + lineCount) {
type = SEG_CLOSE;
count = 0;
} else {
type = SEG_LINETO;
count = 1;
coords[0] = (float)x;
coords[1] = (float)y;
}
if (t != null) {
t.transform(coords, 0, coords, 0, count);
}
return type;
}
}
/**
* The closure type of the arc.
*/
private int type;
/**
* Instantiates a new arc2D.
*
* @param type
* the closure type.
*/
protected Arc2D(int type) {
setArcType(type);
}
/**
* Takes the double-valued data and creates the corresponding Rectangle2D
* object with values either of type float or of type double depending on
* whether this Arc2D instance is of type Float or Double.
*
* @param x
* the x coordinate of the upper left corner of the bounding
* rectangle.
* @param y
* the y coordinate of the upper left corner of the bounding
* rectangle.
* @param width
* the width of the bounding rectangle.
* @param height
* the height of the bounding rectangle.
* @return the corresponding Rectangle2D object.
*/
protected abstract Rectangle2D makeBounds(double x, double y, double width, double height);
/**
* Gets the start angle.
*
* @return the start angle.
*/
public abstract double getAngleStart();
/**
* Gets the width angle.
*
* @return the width angle.
*/
public abstract double getAngleExtent();
/**
* Sets the start angle.
*
* @param start
* the new start angle.
*/
public abstract void setAngleStart(double start);
/**
* Sets the width angle.
*
* @param extent
* the new width angle.
*/
public abstract void setAngleExtent(double extent);
/**
* Sets the data values that define the arc.
*
* @param x
* the x coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param y
* the y coordinate of the upper left corner of the rectangle
* that contains the arc.
* @param width
* the width of the rectangle that contains the arc.
* @param height
* the height of the rectangle that contains the arc.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the width angle of the arc in degrees.
* @param type
* the type of the new Arc2D, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public abstract void setArc(double x, double y, double width, double height, double start,
double extent, int type);
/**
* Gets the arc type, either {@link Arc2D#OPEN}, {@link Arc2D#CHORD}, or
* {@link Arc2D#PIE}.
*
* @return the arc type.
*/
public int getArcType() {
return type;
}
/**
* Sets the arc type, either {@link Arc2D#OPEN}, {@link Arc2D#CHORD}, or
* {@link Arc2D#PIE}.
*
* @param type
* the new arc type.
*/
public void setArcType(int type) {
if (type != OPEN && type != CHORD && type != PIE) {
// awt.205=Invalid type of Arc: {0}
throw new IllegalArgumentException(Messages.getString("awt.205", type)); //$NON-NLS-1$
}
this.type = type;
}
/**
* Gets the start point of the arc as a Point2D.
*
* @return the start point of the curved arc segment.
*/
public Point2D getStartPoint() {
double a = Math.toRadians(getAngleStart());
return new Point2D.Double(getX() + (1.0 + Math.cos(a)) * getWidth() / 2.0, getY()
+ (1.0 - Math.sin(a)) * getHeight() / 2.0);
}
/**
* Gets the end point of the arc as a Point2D.
*
* @return the end point of the curved arc segment.
*/
public Point2D getEndPoint() {
double a = Math.toRadians(getAngleStart() + getAngleExtent());
return new Point2D.Double(getX() + (1.0 + Math.cos(a)) * getWidth() / 2.0, getY()
+ (1.0 - Math.sin(a)) * getHeight() / 2.0);
}
public Rectangle2D getBounds2D() {
if (isEmpty()) {
return makeBounds(getX(), getY(), getWidth(), getHeight());
}
double rx1 = getX();
double ry1 = getY();
double rx2 = rx1 + getWidth();
double ry2 = ry1 + getHeight();
Point2D p1 = getStartPoint();
Point2D p2 = getEndPoint();
double bx1 = containsAngle(180.0) ? rx1 : Math.min(p1.getX(), p2.getX());
double by1 = containsAngle(90.0) ? ry1 : Math.min(p1.getY(), p2.getY());
double bx2 = containsAngle(0.0) ? rx2 : Math.max(p1.getX(), p2.getX());
double by2 = containsAngle(270.0) ? ry2 : Math.max(p1.getY(), p2.getY());
if (type == PIE) {
double cx = getCenterX();
double cy = getCenterY();
bx1 = Math.min(bx1, cx);
by1 = Math.min(by1, cy);
bx2 = Math.max(bx2, cx);
by2 = Math.max(by2, cy);
}
return makeBounds(bx1, by1, bx2 - bx1, by2 - by1);
}
@Override
public void setFrame(double x, double y, double width, double height) {
setArc(x, y, width, height, getAngleStart(), getAngleExtent(), type);
}
/**
* Sets the data that defines the arc.
*
* @param point
* the upper left corner of the bounding rectangle.
* @param size
* the size of the bounding rectangle.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the angle width of the arc in degrees.
* @param type
* the closure type, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public void setArc(Point2D point, Dimension2D size, double start, double extent, int type) {
setArc(point.getX(), point.getY(), size.getWidth(), size.getHeight(), start, extent, type);
}
/**
* Sets the data that defines the arc.
*
* @param rect
* the arc's bounding rectangle.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the angle width of the arc in degrees.
* @param type
* the closure type, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public void setArc(Rectangle2D rect, double start, double extent, int type) {
setArc(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight(), start, extent, type);
}
/**
* Sets the data that defines the arc by copying it from another Arc2D.
*
* @param arc
* the arc whose data is copied into this arc.
*/
public void setArc(Arc2D arc) {
setArc(arc.getX(), arc.getY(), arc.getWidth(), arc.getHeight(), arc.getAngleStart(), arc
.getAngleExtent(), arc.getArcType());
}
/**
* Sets the data for a circular arc by giving its center and radius.
*
* @param x
* the x coordinate of the center of the circle.
* @param y
* the y coordinate of the center of the circle.
* @param radius
* the radius of the circle.
* @param start
* the start angle of the arc in degrees.
* @param extent
* the angle width of the arc in degrees.
* @param type
* the closure type, either {@link Arc2D#OPEN},
* {@link Arc2D#CHORD}, or {@link Arc2D#PIE}.
*/
public void setArcByCenter(double x, double y, double radius, double start, double extent,
int type) {
setArc(x - radius, y - radius, radius * 2.0, radius * 2.0, start, extent, type);
}
/**
* Sets the arc data for a circular arc based on two tangent lines and the
* radius. The two tangent lines are the lines from p1 to p2 and from p2 to
* p3, which determine a unique circle with the given radius. The start and
* end points of the arc are the points where the circle touches the two
* lines, and the arc itself is the shorter of the two circle segments
* determined by the two points (in other words, it is the piece of the
* circle that is closer to the lines' intersection point p2 and forms a
* concave shape with the segments from p1 to p2 and from p2 to p3).
*
* @param p1
* a point which determines one of the two tangent lines (with
* p2).
* @param p2
* the point of intersection of the two tangent lines.
* @param p3
* a point which determines one of the two tangent lines (with
* p2).
* @param radius
* the radius of the circular arc.
*/
public void setArcByTangent(Point2D p1, Point2D p2, Point2D p3, double radius) {
// Used simple geometric calculations of arc center, radius and angles
// by tangents
double a1 = -Math.atan2(p1.getY() - p2.getY(), p1.getX() - p2.getX());
double a2 = -Math.atan2(p3.getY() - p2.getY(), p3.getX() - p2.getX());
double am = (a1 + a2) / 2.0;
double ah = a1 - am;
double d = radius / Math.abs(Math.sin(ah));
double x = p2.getX() + d * Math.cos(am);
double y = p2.getY() - d * Math.sin(am);
ah = ah >= 0.0 ? Math.PI * 1.5 - ah : Math.PI * 0.5 - ah;
a1 = getNormAngle(Math.toDegrees(am - ah));
a2 = getNormAngle(Math.toDegrees(am + ah));
double delta = a2 - a1;
if (delta <= 0.0) {
delta += 360.0;
}
setArcByCenter(x, y, radius, a1, delta, type);
}
/**
* Sets a new start angle to be the angle given by the the vector from the
* current center point to the specified point.
*
* @param point
* the point that determines the new start angle.
*/
public void setAngleStart(Point2D point) {
double angle = Math.atan2(point.getY() - getCenterY(), point.getX() - getCenterX());
setAngleStart(getNormAngle(-Math.toDegrees(angle)));
}
/**
* Sets the angles in terms of vectors from the current arc center to the
* points (x1, y1) and (x2, y2). The start angle is given by the vector from
* the current center to the point (x1, y1) and the end angle is given by
* the vector from the center to the point (x2, y2).
*
* @param x1
* the x coordinate of the point whose vector from the center
* point determines the new start angle of the arc.
* @param y1
* the y coordinate of the point whose vector from the center
* point determines the new start angle of the arc.
* @param x2
* the x coordinate of the point whose vector from the center
* point determines the new end angle of the arc.
* @param y2
* the y coordinate of the point whose vector from the center
* point determines the new end angle of the arc.
*/
public void setAngles(double x1, double y1, double x2, double y2) {
double cx = getCenterX();
double cy = getCenterY();
double a1 = getNormAngle(-Math.toDegrees(Math.atan2(y1 - cy, x1 - cx)));
double a2 = getNormAngle(-Math.toDegrees(Math.atan2(y2 - cy, x2 - cx)));
a2 -= a1;
if (a2 <= 0.0) {
a2 += 360.0;
}
setAngleStart(a1);
setAngleExtent(a2);
}
/**
* Sets the angles in terms of vectors from the current arc center to the
* points p1 and p2. The start angle is given by the vector from the current
* center to the point p1 and the end angle is given by the vector from the
* center to the point p2.
*
* @param p1
* the point whose vector from the center point determines the
* new start angle of the arc.
* @param p2
* the point whose vector from the center point determines the
* new end angle of the arc.
*/
public void setAngles(Point2D p1, Point2D p2) {
setAngles(p1.getX(), p1.getY(), p2.getX(), p2.getY());
}
/**
* Normalizes the angle by removing extra winding (past 360 degrees) and
* placing it in the positive degree range.
*
* @param angle
* the source angle in degrees.
* @return an angle between 0 and 360 degrees which corresponds to the same
* direction vector as the source angle.
*/
double getNormAngle(double angle) {
double n = Math.floor(angle / 360.0);
return angle - n * 360.0;
}
/**
* Determines whether the given angle is contained in the span of the arc.
*
* @param angle
* the angle to test in degrees.
* @return true, if the given angle is between the start angle and the end
* angle of the arc.
*/
public boolean containsAngle(double angle) {
double extent = getAngleExtent();
if (extent >= 360.0) {
return true;
}
angle = getNormAngle(angle);
double a1 = getNormAngle(getAngleStart());
double a2 = a1 + extent;
if (a2 > 360.0) {
return angle >= a1 || angle <= a2 - 360.0;
}
if (a2 < 0.0) {
return angle >= a2 + 360.0 || angle <= a1;
}
return extent > 0.0 ? a1 <= angle && angle <= a2 : a2 <= angle && angle <= a1;
}
public boolean contains(double px, double py) {
// Normalize point
double nx = (px - getX()) / getWidth() - 0.5;
double ny = (py - getY()) / getHeight() - 0.5;
if ((nx * nx + ny * ny) > 0.25) {
return false;
}
double extent = getAngleExtent();
double absExtent = Math.abs(extent);
if (absExtent >= 360.0) {
return true;
}
boolean containsAngle = containsAngle(Math.toDegrees(-Math.atan2(ny, nx)));
if (type == PIE) {
return containsAngle;
}
if (absExtent <= 180.0 && !containsAngle) {
return false;
}
Line2D l = new Line2D.Double(getStartPoint(), getEndPoint());
int ccw1 = l.relativeCCW(px, py);
int ccw2 = l.relativeCCW(getCenterX(), getCenterY());
return ccw1 == 0 || ccw2 == 0 || ((ccw1 + ccw2) == 0 ^ absExtent > 180.0);
}
public boolean contains(double rx, double ry, double rw, double rh) {
if (!(contains(rx, ry) && contains(rx + rw, ry) && contains(rx + rw, ry + rh) && contains(
rx, ry + rh))) {
return false;
}
double absExtent = Math.abs(getAngleExtent());
if (type != PIE || absExtent <= 180.0 || absExtent >= 360.0) {
return true;
}
Rectangle2D r = new Rectangle2D.Double(rx, ry, rw, rh);
double cx = getCenterX();
double cy = getCenterY();
if (r.contains(cx, cy)) {
return false;
}
Point2D p1 = getStartPoint();
Point2D p2 = getEndPoint();
return !r.intersectsLine(cx, cy, p1.getX(), p1.getY())
&& !r.intersectsLine(cx, cy, p2.getX(), p2.getY());
}
@Override
public boolean contains(Rectangle2D rect) {
return contains(rect.getX(), rect.getY(), rect.getWidth(), rect.getHeight());
}
public boolean intersects(double rx, double ry, double rw, double rh) {
if (isEmpty() || rw <= 0.0 || rh <= 0.0) {
return false;
}
// Check: Does arc contain rectangle's points
if (contains(rx, ry) || contains(rx + rw, ry) || contains(rx, ry + rh)
|| contains(rx + rw, ry + rh)) {
return true;
}
double cx = getCenterX();
double cy = getCenterY();
Point2D p1 = getStartPoint();
Point2D p2 = getEndPoint();
Rectangle2D r = new Rectangle2D.Double(rx, ry, rw, rh);
// Check: Does rectangle contain arc's points
if (r.contains(p1) || r.contains(p2) || (type == PIE && r.contains(cx, cy))) {
return true;
}
if (type == PIE) {
if (r.intersectsLine(p1.getX(), p1.getY(), cx, cy)
|| r.intersectsLine(p2.getX(), p2.getY(), cx, cy)) {
return true;
}
} else {
if (r.intersectsLine(p1.getX(), p1.getY(), p2.getX(), p2.getY())) {
return true;
}
}
// Nearest rectangle point
double nx = cx < rx ? rx : (cx > rx + rw ? rx + rw : cx);
double ny = cy < ry ? ry : (cy > ry + rh ? ry + rh : cy);
return contains(nx, ny);
}
public PathIterator getPathIterator(AffineTransform at) {
return new Iterator(this, at);
}
}