Help language development. Donate to The Perl Foundation
// Copyright (c) 2003-2004, Luc Maisonobe // All rights reserved. // // Redistribution and use in source and binary forms, with // or without modification, are permitted provided that // the following conditions are met: // // Redistributions of source code must retain the // above copyright notice, this list of conditions and // the following disclaimer. // Redistributions in binary form must reproduce the // above copyright notice, this list of conditions and // the following disclaimer in the documentation // and/or other materials provided with the // distribution. // Neither the names of spaceroots.org, spaceroots.com // nor the names of their contributors may be used to // endorse or promote products derived from this // software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND // CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED // WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A // PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL // THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF // USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) // HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER // IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING // NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE // USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE // POSSIBILITY OF SUCH DAMAGE. //package org.spaceroots.sphere.geometry; //import java.awt.Shape; //import java.awt.Rectangle; //import java.awt.geom.Point2D; //import java.awt.geom.Rectangle2D; //import java.awt.geom.AffineTransform; //import java.awt.geom.PathIterator; //import java.awt.geom.GeneralPath; /** This class represents an elliptical arc on a 2D plane. * <p>It is designed as an implementation of the * <code>java.awt.Shape</code> interface and can therefore be drawn * easily as any of the more traditional shapes provided by the * standard Java API.</p> * <p>This class differs from the <code>java.awt.geom.Ellipse2D</code> * in the fact it can handles parts of ellipse in addition to full * ellipses and it can handle ellipses which are not aligned with the * x and y reference axes of the plane. <p> * <p>Another improvement is that this class can handle degenerated * cases like for example very flat ellipses (semi-minor axis much * smaller than semi-major axis) and drawing of very small parts of * such ellipses at very high magnification scales. This imply * monitoring the drawing approximation error for extremely small * values. Such cases occur for example while drawing orbits of comets * near the perihelion.</p> * <p>When the arc does not cover the complete ellipse, the lines * joining the center of the ellipse to the endpoints can optionally * be included or not in the outline, hence allowing to use it for * pie-charts rendering. If these lines are not included, the curve is * not naturally closed.</p> * @author L. Maisonobe */ class EllipticalArc implements Shape { private static final double twoPi = 2 * Math.PI; // coefficients for error estimation // while using quadratic Bezier curves for approximation // 0 < b/a < 1/4 private static final double[][][] coeffs2Low = new double[][][] { { { 3.92478, -13.5822, -0.233377, 0.0128206 }, { -1.08814, 0.859987, 0.000362265, 0.000229036 }, { -0.942512, 0.390456, 0.0080909, 0.00723895 }, { -0.736228, 0.20998, 0.0129867, 0.0103456 } }, { { -0.395018, 6.82464, 0.0995293, 0.0122198 }, { -0.545608, 0.0774863, 0.0267327, 0.0132482 }, { 0.0534754, -0.0884167, 0.012595, 0.0343396 }, { 0.209052, -0.0599987, -0.00723897, 0.00789976 } } }; // coefficients for error estimation // while using quadratic Bezier curves for approximation // 1/4 <= b/a <= 1 private static final double[][][] coeffs2High = new double[][][] { { { 0.0863805, -11.5595, -2.68765, 0.181224 }, { 0.242856, -1.81073, 1.56876, 1.68544 }, { 0.233337, -0.455621, 0.222856, 0.403469 }, { 0.0612978, -0.104879, 0.0446799, 0.00867312 } }, { { 0.028973, 6.68407, 0.171472, 0.0211706 }, { 0.0307674, -0.0517815, 0.0216803, -0.0749348 }, { -0.0471179, 0.1288, -0.0781702, 2.0 }, { -0.0309683, 0.0531557, -0.0227191, 0.0434511 } } }; // safety factor to convert the "best" error approximation // into a "max bound" error private static final double[] safety2 = new double[] { 0.02, 2.83, 0.125, 0.01 }; // coefficients for error estimation // while using cubic Bezier curves for approximation // 0 < b/a < 1/4 private static final double[][][] coeffs3Low = new double[][][] { { { 3.85268, -21.229, -0.330434, 0.0127842 }, { -1.61486, 0.706564, 0.225945, 0.263682 }, { -0.910164, 0.388383, 0.00551445, 0.00671814 }, { -0.630184, 0.192402, 0.0098871, 0.0102527 } }, { { -0.162211, 9.94329, 0.13723, 0.0124084 }, { -0.253135, 0.00187735, 0.0230286, 0.01264 }, { -0.0695069, -0.0437594, 0.0120636, 0.0163087 }, { -0.0328856, -0.00926032, -0.00173573, 0.00527385 } } }; // coefficients for error estimation // while using cubic Bezier curves for approximation // 1/4 <= b/a <= 1 private static final double[][][] coeffs3High = new double[][][] { { { 0.0899116, -19.2349, -4.11711, 0.183362 }, { 0.138148, -1.45804, 1.32044, 1.38474 }, { 0.230903, -0.450262, 0.219963, 0.414038 }, { 0.0590565, -0.101062, 0.0430592, 0.0204699 } }, { { 0.0164649, 9.89394, 0.0919496, 0.00760802 }, { 0.0191603, -0.0322058, 0.0134667, -0.0825018 }, { 0.0156192, -0.017535, 0.00326508, -0.228157 }, { -0.0236752, 0.0405821, -0.0173086, 0.176187 } } }; // safety factor to convert the "best" error approximation // into a "max bound" error private static final double[] safety3 = new double[] { 0.001, 4.98, 0.207, 0.0067 }; /** Abscissa of the center of the ellipse. */ protected double cx; /** Ordinate of the center of the ellipse. */ protected double cy; /** Semi-major axis. */ protected double a; /** Semi-minor axis. */ protected double b; /** Orientation of the major axis with respect to the x axis. */ protected double theta; private double cosTheta; private double sinTheta; /** Start angle of the arc. */ protected double eta1; /** End angle of the arc. */ protected double eta2; /** Abscissa of the start point. */ protected double x1; /** Ordinate of the start point. */ protected double y1; /** Abscissa of the end point. */ protected double x2; /** Ordinate of the end point. */ protected double y2; /** Abscissa of the first focus. */ protected double xF1; /** Ordinate of the first focus. */ protected double yF1; /** Abscissa of the second focus. */ protected double xF2; /** Ordinate of the second focus. */ protected double yF2; /** Abscissa of the leftmost point of the arc. */ private double xLeft; /** Ordinate of the highest point of the arc. */ private double yUp; /** Horizontal width of the arc. */ private double width; /** Vertical height of the arc. */ private double height; /** Indicator for center to endpoints line inclusion. */ protected boolean isPieSlice; /** Maximal degree for Bezier curve approximation. */ private int maxDegree; /** Default flatness for Bezier curve approximation. */ private double defaultFlatness; protected double f; protected double e2; protected double g; protected double g2; /** Simple constructor. * Build an elliptical arc composed of the full unit circle centered * on origin */ public EllipticalArc() { cx = 0; cy = 0; a = 1; b = 1; theta = 0; eta1 = 0; eta2 = 2 * Math.PI; cosTheta = 1; sinTheta = 0; isPieSlice = false; maxDegree = 3; defaultFlatness = 0.5; // half a pixel computeFocii(); computeEndPoints(); computeBounds(); computeDerivedFlatnessParameters(); } /** Build an elliptical arc from its canonical geometrical elements. * @param center center of the ellipse * @param a semi-major axis * @param b semi-minor axis * @param theta orientation of the major axis with respect to the x axis * @param lambda1 start angle of the arc * @param lambda2 end angle of the arc * @param isPieSlice if true, the lines between the center of the ellipse * and the endpoints are part of the shape (it is pie slice like) */ public EllipticalArc(Point2D.Double center, double a, double b, double theta, double lambda1, double lambda2, boolean isPieSlice) { this(center.x, center.y, a, b, theta, lambda1, lambda2, isPieSlice); } /** Build an elliptical arc from its canonical geometrical elements. * @param cx abscissa of the center of the ellipse * @param cy ordinate of the center of the ellipse * @param a semi-major axis * @param b semi-minor axis * @param theta orientation of the major axis with respect to the x axis * @param lambda1 start angle of the arc * @param lambda2 end angle of the arc * @param isPieSlice if true, the lines between the center of the ellipse * and the endpoints are part of the shape (it is pie slice like) */ public EllipticalArc(double cx, double cy, double a, double b, double theta, double lambda1, double lambda2, boolean isPieSlice) { this.cx = cx; this.cy = cy; this.a = a; this.b = b; this.theta = theta; this.isPieSlice = isPieSlice; eta1 = Math.atan2(Math.sin(lambda1) / b, Math.cos(lambda1) / a); eta2 = Math.atan2(Math.sin(lambda2) / b, Math.cos(lambda2) / a); cosTheta = Math.cos(theta); sinTheta = Math.sin(theta); maxDegree = 3; defaultFlatness = 0.5; // half a pixel // make sure we have eta1 <= eta2 <= eta1 + 2 PI eta2 -= twoPi * Math.floor((eta2 - eta1) / twoPi); // the preceding correction fails if we have exactly et2 - eta1 = 2 PI // it reduces the interval to zero length if ((lambda2 - lambda1 > Math.PI) && (eta2 - eta1 < Math.PI)) { eta2 += 2 * Math.PI; } computeFocii(); computeEndPoints(); computeBounds(); computeDerivedFlatnessParameters(); } /** Build a full ellipse from its canonical geometrical elements. * @param center center of the ellipse * @param a semi-major axis * @param b semi-minor axis * @param theta orientation of the major axis with respect to the x axis */ public EllipticalArc(Point2D.Double center, double a, double b, double theta) { this(center.x, center.y, a, b, theta); } /** Build a full ellipse from its canonical geometrical elements. * @param cx abscissa of the center of the ellipse * @param cy ordinate of the center of the ellipse * @param a semi-major axis * @param b semi-minor axis * @param theta orientation of the major axis with respect to the x axis */ public EllipticalArc(double cx, double cy, double a, double b, double theta) { this.cx = cx; this.cy = cy; this.a = a; this.b = b; this.theta = theta; this.isPieSlice = false; eta1 = 0; eta2 = 2 * Math.PI; cosTheta = Math.cos(theta); sinTheta = Math.sin(theta); maxDegree = 3; defaultFlatness = 0.5; // half a pixel computeFocii(); computeEndPoints(); computeBounds(); computeDerivedFlatnessParameters(); } /** Set the maximal degree allowed for Bezier curve approximation. * @param maxDegree maximal allowed degree (must be between 1 and 3) * @exception IllegalArgumentException if maxDegree is not between 1 and 3 */ public void setMaxDegree(int maxDegree) { if ((maxDegree < 1) || (maxDegree > 3)) { throw new IllegalArgumentException("maxDegree must be between 1 and 3"); } this.maxDegree = maxDegree; } /** Set the default flatness for Bezier curve approximation. * @param defaultFlatness default flatness (must be greater than 1.0e-10) * @exception IllegalArgumentException if defaultFlatness is lower * than 1.0e-10 */ public void setDefaultFlatness(double defaultFlatness) { if (defaultFlatness < 1.0e-10) { throw new IllegalArgumentException("defaultFlatness must be" + " greater than 1.0e-10"); } this.defaultFlatness = defaultFlatness; } /** Compute the locations of the focii. */ private void computeFocii() { double d = Math.sqrt(a * a - b * b); double dx = d * cosTheta; double dy = d * sinTheta; xF1 = cx - dx; yF1 = cy - dy; xF2 = cx + dx; yF2 = cy + dy; } /** Compute the locations of the endpoints. */ private void computeEndPoints() { // start point double aCosEta1 = a * Math.cos(eta1); double bSinEta1 = b * Math.sin(eta1); x1 = cx + aCosEta1 * cosTheta - bSinEta1 * sinTheta; y1 = cy + aCosEta1 * sinTheta + bSinEta1 * cosTheta; // end point double aCosEta2 = a * Math.cos(eta2); double bSinEta2 = b * Math.sin(eta2); x2 = cx + aCosEta2 * cosTheta - bSinEta2 * sinTheta; y2 = cy + aCosEta2 * sinTheta + bSinEta2 * cosTheta; } /** Compute the bounding box. */ private void computeBounds() { double bOnA = b / a; double etaXMin, etaXMax, etaYMin, etaYMax; if (Math.abs(sinTheta) < 0.1) { double tanTheta = sinTheta / cosTheta; if (cosTheta < 0) { etaXMin = -Math.atan(tanTheta * bOnA); etaXMax = etaXMin + Math.PI; etaYMin = 0.5 * Math.PI - Math.atan(tanTheta / bOnA); etaYMax = etaYMin + Math.PI; } else { etaXMax = -Math.atan(tanTheta * bOnA); etaXMin = etaXMax - Math.PI; etaYMax = 0.5 * Math.PI - Math.atan(tanTheta / bOnA); etaYMin = etaYMax - Math.PI; } } else { double invTanTheta = cosTheta / sinTheta; if (sinTheta < 0) { etaXMax = 0.5 * Math.PI + Math.atan(invTanTheta / bOnA); etaXMin = etaXMax - Math.PI; etaYMin = Math.atan(invTanTheta * bOnA); etaYMax = etaYMin + Math.PI; } else { etaXMin = 0.5 * Math.PI + Math.atan(invTanTheta / bOnA); etaXMax = etaXMin + Math.PI; etaYMax = Math.atan(invTanTheta * bOnA); etaYMin = etaYMax - Math.PI; } } etaXMin -= twoPi * Math.floor((etaXMin - eta1) / twoPi); etaYMin -= twoPi * Math.floor((etaYMin - eta1) / twoPi); etaXMax -= twoPi * Math.floor((etaXMax - eta1) / twoPi); etaYMax -= twoPi * Math.floor((etaYMax - eta1) / twoPi); xLeft = (etaXMin <= eta2) ? (cx + a * Math.cos(etaXMin) * cosTheta - b * Math.sin(etaXMin) * sinTheta) : Math.min(x1, x2); yUp = (etaYMin <= eta2) ? (cy + a * Math.cos(etaYMin) * sinTheta + b * Math.sin(etaYMin) * cosTheta) : Math.min(y1, y2); width = ((etaXMax <= eta2) ? (cx + a * Math.cos(etaXMax) * cosTheta - b * Math.sin(etaXMax) * sinTheta) : Math.max(x1, x2)) - xLeft; height = ((etaYMax <= eta2) ? (cy + a * Math.cos(etaYMax) * sinTheta + b * Math.sin(etaYMax) * cosTheta) : Math.max(y1, y2)) - yUp; } private void computeDerivedFlatnessParameters() { f = (a - b) / a; e2 = f * (2.0 - f); g = 1.0 - f; g2 = g * g; } /** Compute the value of a rational function. * This method handles rational functions where the numerator is * quadratic and the denominator is linear * @param x absissa for which the value should be computed * @param c coefficients array of the rational function */ private static double rationalFunction(double x, double[] c) { return (x * (x * c[0] + c[1]) + c[2]) / (x + c[3]); } /** Estimate the approximation error for a sub-arc of the instance. * @param degree degree of the Bezier curve to use (1, 2 or 3) * @param tA start angle of the sub-arc * @param tB end angle of the sub-arc * @return upper bound of the approximation error between the Bezier * curve and the real ellipse */ protected double estimateError(int degree, double etaA, double etaB) { double eta = 0.5 * (etaA + etaB); if (degree < 2) { // start point double aCosEtaA = a * Math.cos(etaA); double bSinEtaA = b * Math.sin(etaA); double xA = cx + aCosEtaA * cosTheta - bSinEtaA * sinTheta; double yA = cy + aCosEtaA * sinTheta + bSinEtaA * cosTheta; // end point double aCosEtaB = a * Math.cos(etaB); double bSinEtaB = b * Math.sin(etaB); double xB = cx + aCosEtaB * cosTheta - bSinEtaB * sinTheta; double yB = cy + aCosEtaB * sinTheta + bSinEtaB * cosTheta; // maximal error point double aCosEta = a * Math.cos(eta); double bSinEta = b * Math.sin(eta); double x = cx + aCosEta * cosTheta - bSinEta * sinTheta; double y = cy + aCosEta * sinTheta + bSinEta * cosTheta; double dx = xB - xA; double dy = yB - yA; return Math.abs(x * dy - y * dx + xB * yA - xA * yB) / Math.sqrt(dx * dx + dy * dy); } else { double x = b / a; double dEta = etaB - etaA; double cos2 = Math.cos(2 * eta); double cos4 = Math.cos(4 * eta); double cos6 = Math.cos(6 * eta); // select the right coeficients set according to degree and b/a double[][][] coeffs; double[] safety; if (degree == 2) { coeffs = (x < 0.25) ? coeffs2Low : coeffs2High; safety = safety2; } else { coeffs = (x < 0.25) ? coeffs3Low : coeffs3High; safety = safety3; } double c0 = rationalFunction(x, coeffs[0][0]) + cos2 * rationalFunction(x, coeffs[0][1]) + cos4 * rationalFunction(x, coeffs[0][2]) + cos6 * rationalFunction(x, coeffs[0][3]); double c1 = rationalFunction(x, coeffs[1][0]) + cos2 * rationalFunction(x, coeffs[1][1]) + cos4 * rationalFunction(x, coeffs[1][2]) + cos6 * rationalFunction(x, coeffs[1][3]); return rationalFunction(x, safety) * a * Math.exp(c0 + c1 * dEta); } } /** Get the elliptical arc point for a given angular parameter. * @param lambda angular parameter for which point is desired * @param p placeholder where to put the point, if null a new Point * well be allocated * @return the object p or a new object if p was null, set to the * desired elliptical arc point location */ public Point2D.Double pointAt(double lambda, Point2D.Double p) { if (p == null) { p = new Point2D.Double(); } double eta = Math.atan2(Math.sin(lambda) / b, Math.cos(lambda) / a); double aCosEta = a * Math.cos(eta); double bSinEta = b * Math.sin(eta); p.x = cx + aCosEta * cosTheta - bSinEta * sinTheta; p.y = cy + aCosEta * sinTheta + bSinEta * cosTheta; return p; } /** Tests if the specified coordinates are inside the boundary of the Shape. * @param x abscissa of the test point * @param y ordinate of the test point * @return true if the specified coordinates are inside the Shape * boundary; false otherwise */ public boolean contains(double x, double y) { // position relative to the focii double dx1 = x - xF1; double dy1 = y - yF1; double dx2 = x - xF2; double dy2 = y - yF2; if ((dx1 * dx1 + dy1 * dy1 + dx2 * dx2 + dy2 * dy2) > (4 * a * a)) { // the point is outside of the ellipse return false; } if (isPieSlice) { // check the location of the test point with respect to the // angular sector counted from the center of the ellipse double dxC = x - cx; double dyC = y - cy; double u = dxC * cosTheta + dyC * sinTheta; double v = dyC * cosTheta - dxC * sinTheta; double eta = Math.atan2(v / b, u / a); eta -= twoPi * Math.floor((eta - eta1) / twoPi); return (eta <= eta2); } else { // check the location of the test point with respect to the // line joining the start and end points double dx = x2 - x1; double dy = y2 - y1; return ((x * dy - y * dx + x2 * y1 - x1 * y2) >= 0); } } /** Tests if a line segment intersects the arc. * @param xA abscissa of the first point of the line segment * @param yA ordinate of the first point of the line segment * @param xB abscissa of the second point of the line segment * @param yB ordinate of the second point of the line segment * @return true if the two line segments intersect */ private boolean intersectArc(double xA, double yA, double xB, double yB) { double dx = xA - xB; double dy = yA - yB; double l = Math.sqrt(dx * dx + dy * dy); if (l < (1.0e-10 * a)) { // too small line segment, we consider it doesn't intersect anything return false; } double cz = (dx * cosTheta + dy * sinTheta) / l; double sz = (dy * cosTheta - dx * sinTheta) / l; // express position of the first point in canonical frame dx = xA - cx; dy = yA - cy; double u = dx * cosTheta + dy * sinTheta; double v = dy * cosTheta - dx * sinTheta; double u2 = u * u; double v2 = v * v; double g2u2ma2 = g2 * (u2 - a * a); double g2u2ma2mv2 = g2u2ma2 - v2; double g2u2ma2pv2 = g2u2ma2 + v2; // compute intersections with the ellipse along the line // as the roots of a 2nd degree polynom : c0 k^2 - 2 c1 k + c2 = 0 double c0 = 1.0 - e2 * cz * cz; double c1 = g2 * u * cz + v * sz; double c2 = g2u2ma2pv2; double c12 = c1 * c1; double c0c2 = c0 * c2; if (c12 < c0c2) { // the line does not intersect the ellipse at all return false; } double k = (c1 >= 0) ? (c1 + Math.sqrt(c12 - c0c2)) / c0 : c2 / (c1 - Math.sqrt(c12 - c0c2)); if ((k >= 0) && (k <= l)) { double uIntersect = u - k * cz; double vIntersect = v - k * sz; double eta = Math.atan2(vIntersect / b, uIntersect / a); eta -= twoPi * Math.floor((eta - eta1) / twoPi); if (eta <= eta2) { return true; } } k = c2 / (k * c0); if ((k >= 0) && (k <= l)) { double uIntersect = u - k * cz; double vIntersect = v - k * sz; double eta = Math.atan2(vIntersect / b, uIntersect / a); eta -= twoPi * Math.floor((eta - eta1) / twoPi); if (eta <= eta2) { return true; } } return false; } /** Tests if two line segments intersect. * @param x1 abscissa of the first point of the first line segment * @param y1 ordinate of the first point of the first line segment * @param x2 abscissa of the second point of the first line segment * @param y2 ordinate of the second point of the first line segment * @param xA abscissa of the first point of the second line segment * @param yA ordinate of the first point of the second line segment * @param xB abscissa of the second point of the second line segment * @param yB ordinate of the second point of the second line segment * @return true if the two line segments intersect */ private static boolean intersect(double x1, double y1, double x2, double y2, double xA, double yA, double xB, double yB) { // elements of the equation of the (1, 2) line segment double dx12 = x2 - x1; double dy12 = y2 - y1; double k12 = x2 * y1 - x1 * y2; // elements of the equation of the (A, B) line segment double dxAB = xB - xA; double dyAB = yB - yA; double kAB = xB * yA - xA * yB; // compute relative positions of endpoints versus line segments double pAvs12 = xA * dy12 - yA * dx12 + k12; double pBvs12 = xB * dy12 - yB * dx12 + k12; double p1vsAB = x1 * dyAB - y1 * dxAB + kAB; double p2vsAB = x2 * dyAB - y2 * dxAB + kAB; return (pAvs12 * pBvs12 <= 0) && (p1vsAB * p2vsAB <= 0); } /** Tests if a line segment intersects the outline. * @param xA abscissa of the first point of the line segment * @param yA ordinate of the first point of the line segment * @param xB abscissa of the second point of the line segment * @param yB ordinate of the second point of the line segment * @return true if the two line segments intersect */ private boolean intersectOutline(double xA, double yA, double xB, double yB) { if (intersectArc(xA, yA, xB, yB)) { return true; } if (isPieSlice) { return (intersect(cx, cy, x1, y1, xA, yA, xB, yB) || intersect(cx, cy, x2, y2, xA, yA, xB, yB)); } else { return intersect(x1, y1, x2, y2, xA, yA, xB, yB); } } /** Tests if the interior of the Shape entirely contains the * specified rectangular area. * @param x abscissa of the upper-left corner of the test rectangle * @param y ordinate of the upper-left corner of the test rectangle * @param w width of the test rectangle * @param h height of the test rectangle * @return true if the interior of the Shape entirely contains the * specified rectangular area; false otherwise */ public boolean contains(double x, double y, double w, double h) { double xPlusW = x + w; double yPlusH = y + h; return ( contains(x, y) && contains(xPlusW, y) && contains(x, yPlusH) && contains(xPlusW, yPlusH) && (! intersectOutline(x, y, xPlusW, y)) && (! intersectOutline(xPlusW, y, xPlusW, yPlusH)) && (! intersectOutline(xPlusW, yPlusH, x, yPlusH)) && (! intersectOutline(x, yPlusH, x, y))); } /** Tests if a specified Point2D is inside the boundary of the Shape. * @param p test point * @return true if the specified point is inside the Shape * boundary; false otherwise */ public boolean contains(Point2D p) { return contains(p.getX(), p.getY()); } /** Tests if the interior of the Shape entirely contains the * specified Rectangle2D. * @param r test rectangle * @return true if the interior of the Shape entirely contains the * specified rectangular area; false otherwise */ public boolean contains(Rectangle2D r) { return contains(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } /** Returns an integer Rectangle that completely encloses the Shape. */ public Rectangle getBounds() { int xMin = (int) Math.rint(xLeft - 0.5); int yMin = (int) Math.rint(yUp - 0.5); int xMax = (int) Math.rint(xLeft + width + 0.5); int yMax = (int) Math.rint(yUp + height + 0.5); return new Rectangle(xMin, yMin, xMax - xMin, yMax - yMin); } /** Returns a high precision and more accurate bounding box of the * Shape than the getBounds method. */ public Rectangle2D getBounds2D() { return new Rectangle2D.Double(xLeft, yUp, width, height); } /** Build an approximation of the instance outline. * @param degree degree of the Bezier curve to use * @param threshold acceptable error * @param at affine transformation to apply * @return a path iterator */ private PathIterator buildPathIterator(int degree, double threshold, AffineTransform at) { // find the number of Bezier curves needed boolean found = false; int n = 1; while ((! found) && (n < 1024)) { double dEta = (eta2 - eta1) / n; if (dEta <= 0.5 * Math.PI) { double etaB = eta1; found = true; for (int i = 0; found && (i < n); ++i) { double etaA = etaB; etaB += dEta; found = (estimateError(degree, etaA, etaB) <= threshold); } } n = n << 1; } GeneralPath path = new GeneralPath(PathIterator.WIND_EVEN_ODD); double dEta = (eta2 - eta1) / n; double etaB = eta1; double cosEtaB = Math.cos(etaB); double sinEtaB = Math.sin(etaB); double aCosEtaB = a * cosEtaB; double bSinEtaB = b * sinEtaB; double aSinEtaB = a * sinEtaB; double bCosEtaB = b * cosEtaB; double xB = cx + aCosEtaB * cosTheta - bSinEtaB * sinTheta; double yB = cy + aCosEtaB * sinTheta + bSinEtaB * cosTheta; double xBDot = -aSinEtaB * cosTheta - bCosEtaB * sinTheta; double yBDot = -aSinEtaB * sinTheta + bCosEtaB * cosTheta; if (isPieSlice) { path.moveTo((float) cx, (float) cy); path.lineTo((float) xB, (float) yB); } else { path.moveTo((float) xB, (float) yB); } double t = Math.tan(0.5 * dEta); double alpha = Math.sin(dEta) * (Math.sqrt(4 + 3 * t * t) - 1) / 3; for (int i = 0; i < n; ++i) { double etaA = etaB; double xA = xB; double yA = yB; double xADot = xBDot; double yADot = yBDot; etaB += dEta; cosEtaB = Math.cos(etaB); sinEtaB = Math.sin(etaB); aCosEtaB = a * cosEtaB; bSinEtaB = b * sinEtaB; aSinEtaB = a * sinEtaB; bCosEtaB = b * cosEtaB; xB = cx + aCosEtaB * cosTheta - bSinEtaB * sinTheta; yB = cy + aCosEtaB * sinTheta + bSinEtaB * cosTheta; xBDot = -aSinEtaB * cosTheta - bCosEtaB * sinTheta; yBDot = -aSinEtaB * sinTheta + bCosEtaB * cosTheta; if (degree == 1) { path.lineTo((float) xB, (float) yB); } else if (degree == 2) { double k = (yBDot * (xB - xA) - xBDot * (yB - yA)) / (xADot * yBDot - yADot * xBDot); path.quadTo((float) (xA + k * xADot), (float) (yA + k * yADot), (float) xB, (float) yB); } else { path.curveTo((float) (xA + alpha * xADot), (float) (yA + alpha * yADot), (float) (xB - alpha * xBDot), (float) (yB - alpha * yBDot), (float) xB, (float) yB); } } if (isPieSlice) { path.closePath(); } return path.getPathIterator(at); } /** Returns an iterator object that iterates along the Shape * boundary and provides access to the geometry of the Shape * outline. */ public PathIterator getPathIterator(AffineTransform at) { return buildPathIterator(maxDegree, defaultFlatness, at); } /** Returns an iterator object that iterates along the Shape * boundary and provides access to a flattened view of the Shape * outline geometry. */ public PathIterator getPathIterator(AffineTransform at, double flatness) { return buildPathIterator(1, flatness, at); } /** Tests if the interior of the Shape intersects the interior of a * specified rectangular area. */ public boolean intersects(double x, double y, double w, double h) { double xPlusW = x + w; double yPlusH = y + h; return contains(x, y) || contains(xPlusW, y) || contains(x, yPlusH) || contains(xPlusW, yPlusH) || intersectOutline(x, y, xPlusW, y) || intersectOutline(xPlusW, y, xPlusW, yPlusH) || intersectOutline(xPlusW, yPlusH, x, yPlusH) || intersectOutline(x, yPlusH, x, y); } /** Tests if the interior of the Shape intersects the interior of a * specified Rectangle2D. */ public boolean intersects(Rectangle2D r) { return intersects(r.getX(), r.getY(), r.getWidth(), r.getHeight()); } }