/* file : EllipseArc2D.java
 * 
 * Project : geometry
 *
 * ===========================================
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library. if not, write to :
 * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 * 
 * Created on 24 avr. 2006
 *
 */

package math.geom2d.conic;

import static java.lang.Math.*;

import java.awt.Graphics2D;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Locale;

import math.geom2d.*;
import math.geom2d.curve.*;
import math.geom2d.domain.SmoothOrientedCurve2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.line.Ray2D;
import math.geom2d.line.StraightLine2D;
import math.geom2d.polygon.Polyline2D;
import math.utils.EqualUtils;

/**
 * An arc of ellipse. It is defined by a supporting ellipse, a starting angle,
 * and a signed angle extent, both in radians. The ellipse arc is oriented
 * counter-clockwise if angle extent is positive, and clockwise otherwise.
 * 
 * @author dlegland
 */
public class EllipseArc2D extends AbstractSmoothCurve2D
implements SmoothOrientedCurve2D, EllipseArcShape2D, Cloneable {

    // ====================================================================
    // methods specific to EllipseArc2D

    /**
     * Specify supporting ellipse, start angle and end angle, and a flag
     * indicating whether the arc is directed or not.
     * 
     * @param ell the supporting ellipse
     * @param start the starting angle
     * @param extent the (signed) angle extent
	 *
	 * @deprecated since 0.11.1
	 */
	@Deprecated
    public static EllipseArc2D create(Ellipse2D ell, double start, 
    		double extent) {
        return new EllipseArc2D(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta,
        		start, extent);
    }

    /**
     * Specify supporting ellipse, start angle and end angle, and a flag
     * indicating whether the arc is directed or not.
     * 
     * @param ell the supporting ellipse
     * @param start the starting angle
     * @param end the ending angle
     * @param direct flag indicating if the arc is direct
     *
	 * @deprecated since 0.11.1
	 */
	@Deprecated
public static EllipseArc2D create(Ellipse2D ell, double start, double end,
    		boolean direct) {
    	return new EllipseArc2D(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta,
    			start, end, direct);
    }


    // ====================================================================
    // Class variables

    /** The supporting ellipse */
    protected Ellipse2D ellipse;

    /** The starting position on ellipse, in radians between 0 and +2PI */
    protected double    startAngle  = 0;

    /** The signed angle extent, in radians between -2PI and +2PI. */
    protected double    angleExtent = PI;

    
    // ====================================================================
    // Constructors

    /**
     * Construct a default Ellipse arc, centered on (0,0), with radii equal to 1
     * and 1, orientation equal to 0, start angle equal to 0, and angle extent
     * equal to PI/2.
     */
    public EllipseArc2D() {
        this(0, 0, 1, 1, 0, 0, PI/2);
    }

    /**
     * Specify supporting ellipse, start angle and angle extent.
     * 
     * @param ell the supporting ellipse
     * @param start the starting angle (angle between 0 and 2*PI)
     * @param extent the angle extent (signed angle)
     */
    public EllipseArc2D(Ellipse2D ell, double start, double extent) {
    	this(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta, start, extent);
    }

    /**
     * Specify supporting ellipse, start angle and end angle, and a flag
     * indicating whether the arc is directed or not.
     * 
     * @param ell the supporting ellipse
     * @param start the starting angle
     * @param end the ending angle
     * @param direct flag indicating if the arc is direct
     */
    public EllipseArc2D(Ellipse2D ell, double start, double end, boolean direct) {
        this(ell.xc, ell.yc, ell.r1, ell.r2, ell.theta, start, end, direct);
    }

    /**
     * Specify parameters of supporting ellipse, start angle, and angle extent.
     */
    public EllipseArc2D(double xc, double yc, double a, double b, double theta,
            double start, double extent) {
        this.ellipse = new Ellipse2D(xc, yc, a, b, theta);
        this.startAngle = start;
        this.angleExtent = extent;
    }

    /**
     * Specify parameters of supporting ellipse, bounding angles and flag for
     * direct ellipse.
     */
    public EllipseArc2D(double xc, double yc, double a, double b, double theta,
            double start, double end, boolean direct) {
        this.ellipse = new Ellipse2D(xc, yc, a, b, theta);
        this.startAngle = start;
        this.angleExtent = Angle2D.formatAngle(end-start);
        if (!direct)
			this.angleExtent = this.angleExtent - PI * 2;
    }

    // ====================================================================
    // methods specific to EllipseArc2D

    public Ellipse2D getSupportingEllipse() {
    	return ellipse;
    }
    
    public double getStartAngle() {
    	return startAngle;
    }
    
    public double getAngleExtent() {
    	return angleExtent;
    }
    
    /**
     * Returns true if the ellipse arc is direct, i.e. if the angle extent is
     * positive (or zero).
     */
    public boolean isDirect() {
    	return angleExtent >= 0;
    }
    
    public boolean containsAngle(double angle) {
        return Angle2D.containsAngle(
        		startAngle, startAngle+angleExtent, angle, angleExtent>0);
    }

    /** Returns the angle associated with the given position */
    public double getAngle(double position) {
		if (position < 0)
			position = 0;
		if (position > abs(angleExtent))
			position = abs(angleExtent);
		if (angleExtent < 0)
			position = -position;
		return Angle2D.formatAngle(startAngle + position);
    }

    // ====================================================================
    // methods from interface OrientedCurve2D

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.OrientedCurve2D#windingAngle(math.geom2d.Point2D)
     */
    public double windingAngle(Point2D point) {
        Point2D p1 = point(0);
        Point2D p2 = point(abs(angleExtent));

        // compute angle of point with extreme points
		double angle1 = Angle2D.horizontalAngle(point, p1);
		double angle2 = Angle2D.horizontalAngle(point, p2);

        // test on which 'side' of the arc the point lie
        boolean b1 = (new StraightLine2D(p1, p2)).isInside(point);
        boolean b2 = ellipse.isInside(point);

		if (angleExtent > 0) {
			if (b1 || b2) { // inside of ellipse arc
				if (angle2 > angle1)
					return angle2 - angle1;
				else
					return 2 * PI - angle1 + angle2;
			} else { // outside of ellipse arc
				if (angle2 > angle1)
					return angle2 - angle1 - 2 * PI;
				else
					return angle2 - angle1;
			}
		} else {
			if (!b1 || b2) {
				if (angle1 > angle2)
					return angle2 - angle1;
				else
					return angle2 - angle1 - 2 * PI;
			} else {
				if (angle1 > angle2)
					return angle2 - angle1 + 2 * PI;
				else
					return angle2 - angle1;
			}
        }
    }

    public boolean isInside(Point2D p) {
		return signedDistance(p.x(), p.y()) < 0;
    }

    public double signedDistance(Point2D p) {
        return signedDistance(p.x(), p.y());
    }

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Shape2D#signedDistance(math.geom2d.Point2D)
     */
    public double signedDistance(double x, double y) {
        boolean direct = angleExtent >= 0;

        double dist = distance(x, y);
        Point2D point = new Point2D(x, y);

        boolean inside = ellipse.isInside(point);
        if (inside)
			return angleExtent > 0 ? -dist : dist;

        Point2D p1 = point(startAngle);
		double endAngle = startAngle + angleExtent;
		Point2D p2 = point(endAngle);
		boolean onLeft = (new StraightLine2D(p1, p2)).isInside(point);

		if (direct && !onLeft)
			return dist;
		if (!direct && onLeft)
			return -dist;

		
        Ray2D ray = new Ray2D(p1, -sin(startAngle), cos(startAngle));
        boolean left1 = ray.isInside(point);
		if (direct && !left1)
			return dist;
		if (!direct && left1)
			return -dist;

		ray = new Ray2D(p2, -sin(endAngle), cos(endAngle));
        boolean left2 = ray.isInside(point);
		if (direct && !left2)
			return dist;
		if (!direct && left2)
			return -dist;

        if (direct)
            return -dist;
        else
            return dist;
    }

    // ====================================================================
    // methods from interface SmoothCurve2D

    public Vector2D tangent(double t) {
    	// format between min and max admissible values
    	t = min(max(0, t), abs(angleExtent));
    	
    	// compute tangent vector depending on position
		if (angleExtent < 0) {
			// need to invert vector for indirect arcs
			return ellipse.tangent(startAngle - t).times(-1);
		} else {
			return ellipse.tangent(startAngle + t);
        }
    }

    /**
     * Returns the curvature of the ellipse arc. 
     * Curvature is negative if the arc is indirect.
     */
    public double curvature(double t) {
        // convert position to angle
		if (angleExtent < 0)
			t = startAngle - t;
		else
			t = startAngle + t;
		double kappa = ellipse.curvature(t);
		return this.isDirect() ? kappa : -kappa;
    }

    // ====================================================================
    // methods from interface ContinuousCurve2D

    /** Returns false, as an ellipse arc is never closed. */
    public boolean isClosed() {
        return false;
    }

	/**
	 * Returns a collection of curves containing only this circle arc.
	 */
    public Collection smoothPieces() {
    	return wrapCurve(this);
    }

	/* (non-Javadoc)
	 * @see math.geom2d.curve.ContinuousCurve2D#asPolyline(int)
	 */
	public Polyline2D asPolyline(int n) {

        // compute increment value
        double dt = Math.abs(this.angleExtent) / n;

        // allocate array of points, and compute each value.
        // Computes also value for last point.
        Point2D[] points = new Point2D[n + 1];
        for (int i = 0; i < n + 1; i++)
        	points[i] = this.point(i * dt);

        return new Polyline2D(points);
	}

    // ====================================================================
    // methods from interface Curve2D

    /** Always returns 0 */
    public double t0() {
        return 0;
    }

    /**
     * @deprecated replaced by t0() (since 0.11.1).
     */
    @Deprecated
    public double getT0() {
    	return t0();
    }
    
    /** Always returns the absolute value of the angle extent */
    public double t1() {
        return abs(angleExtent);
    }

    /**
     * @deprecated replaced by t1() (since 0.11.1).
     */
    @Deprecated
    public double getT1() {
    	return t1();
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Curve2D#point(double, math.geom2d.Point2D)
     */
    public Point2D point(double t) {
        // check bounds
        t = max(t, 0);
        t = min(t, abs(angleExtent));

        // convert position to angle
		if (angleExtent < 0)
			t = startAngle - t;
		else
			t = startAngle + t;

        // return corresponding point
        return ellipse.point(t);
    }

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Curve2D#position(math.geom2d.Point2D)
     */
    public double position(Point2D point) {
        double angle = Angle2D.horizontalAngle(ellipse.center(), point);
		if (this.containsAngle(angle))
			if (angleExtent > 0)
				return Angle2D.formatAngle(angle - startAngle);
			else
				return Angle2D.formatAngle(startAngle - angle);

        // If the point is not contained in the arc, return NaN.
        return Double.NaN;
    }

    public double project(Point2D point) {
        double angle = ellipse.project(point);

        // Case of an angle contained in the ellipse arc
		if (this.containsAngle(angle)) {
			if (angleExtent > 0)
				return Angle2D.formatAngle(angle - startAngle);
			else
				return Angle2D.formatAngle(startAngle - angle);
        }

        // return either 0 or T1, depending on which extremity is closer.
        double d1 = this.firstPoint().distance(point);
        double d2 = this.lastPoint().distance(point);
		return d1 < d2 ? 0 : abs(angleExtent);
    }
    
    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Curve2D#intersections(math.geom2d.LinearShape2D)
     */
    public Collection intersections(LinearShape2D line) {

        // check point contained in it
        ArrayList array = new ArrayList();
        for (Point2D point : ellipse.intersections(line))
            if (contains(point))
                array.add(point);

        return array;
    }

    /**
     * Returns the ellipse arc which refers to the reversed parent ellipse, with
     * same start angle, and with opposite angle extent.
     */
    public EllipseArc2D reverse() {
    	double newStart = Angle2D.formatAngle(startAngle + angleExtent);
		return new EllipseArc2D(ellipse, newStart, -angleExtent);
    }

	@Override
    public Collection continuousCurves() {
    	return wrapCurve(this);
    }

    /**
     * Returns a new EllipseArc2D.
     */
    public EllipseArc2D subCurve(double t0, double t1) {
        // convert position to angle
		t0 = Angle2D.formatAngle(startAngle + t0);
		t1 = Angle2D.formatAngle(startAngle + t1);

        // check bounds of angles
		if (!Angle2D.containsAngle(startAngle, startAngle + angleExtent, t0,
				angleExtent > 0))
            t0 = startAngle;
		if (!Angle2D.containsAngle(startAngle, startAngle + angleExtent, t1,
				angleExtent > 0))
            t1 = angleExtent;

        // create new arc
		return new EllipseArc2D(ellipse, t0, t1, angleExtent > 0);
    }

    // ====================================================================
    // methods from interface Shape2D

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Shape2D#distance(math.geom2d.Point2D)
     */
    public double distance(Point2D point) {
        return distance(point.x(), point.y());
    }

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Shape2D#distance(double, double)
     */
    public double distance(double x, double y) {
        Point2D p = point(project(new Point2D(x, y)));
        return p.distance(x, y);
    }

    /** Always return true: an ellipse arc is bounded by definition */
    public boolean isBounded() {
        return true;
    }

    /**
     * Returns false.
     */
    public boolean isEmpty() {
        return false;
    }

    /**
     * Clips the ellipse arc by a box. The result is an instance of CurveSet2D,
     * which contains only instances of EllipseArc2D. If the ellipse arc is not
     * clipped, the result is an instance of CurveSet2D which contains 0
     * curves.
     */
    public CurveSet2D clip(Box2D box) {
        // Clip the curve
        CurveSet2D set = Curves2D.clipSmoothCurve(this, box);

        // Stores the result in appropriate structure
        CurveArray2D result = 
        	new CurveArray2D(set.size());

        // convert the result
        for (Curve2D curve : set.curves()) {
            if (curve instanceof EllipseArc2D)
                result.add((EllipseArc2D) curve);
        }
        return result;
    }

    public Box2D boundingBox() {

        // first get ending points
        Point2D p0 = firstPoint();
        Point2D p1 = lastPoint();

        // get coordinate of ending points
        double x0 = p0.x();
        double y0 = p0.y();
        double x1 = p1.x();
        double y1 = p1.y();

        // initialize min and max coords
        double xmin = min(x0, x1);
        double xmax = max(x0, x1);
        double ymin = min(y0, y1);
        double ymax = max(y0, y1);

        // precomputes some values
        Point2D center = ellipse.center();
        double xc = center.x();
		double yc = center.y();
		double endAngle = startAngle + angleExtent;
		boolean direct = angleExtent >= 0;
		
        // check cases arc contains one maximum
		if (Angle2D.containsAngle(startAngle, endAngle, PI / 2 + ellipse.theta, direct))
			ymax = max(ymax, yc + ellipse.r1);
		if (Angle2D.containsAngle(startAngle, endAngle, 3 * PI / 2
				+ ellipse.theta, direct))
			ymin = min(ymin, yc - ellipse.r1);
		if (Angle2D.containsAngle(startAngle, endAngle, ellipse.theta,
				direct))
			xmax = max(xmax, xc + ellipse.r2);
		if (Angle2D.containsAngle(startAngle, endAngle, PI + ellipse.theta,
				direct))
			xmin = min(xmin, xc - ellipse.r2);

        // return a bounding with computed limits
        return new Box2D(xmin, xmax, ymin, ymax);
    }

    /*
     * (non-Javadoc)
     * 
     * @see math.geom2d.Shape2D#transform(math.geom2d.AffineTransform2D)
     */
    public EllipseArc2D transform(AffineTransform2D trans) {
        // transform supporting ellipse
        Ellipse2D ell = ellipse.transform(trans);

        // ensure ellipse is direct
        if (!ell.isDirect())
            ell = ell.reverse();

        // Compute position of end points on the transformed ellipse
        double startPos = ell.project(this.firstPoint().transform(trans));
        double endPos = ell.project(this.lastPoint().transform(trans));

        // Compute the new arc
		boolean direct = !(angleExtent > 0 ^ trans.isDirect());
		return new EllipseArc2D(ell, startPos, endPos, direct);
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.Shape#contains(double, double)
     */
    public boolean contains(double x, double y) {
		return distance(x, y) > Shape2D.ACCURACY;
    }

    /*
     * (non-Javadoc)
     * 
     * @see java.awt.Shape#contains(Point2D)
     */
    public boolean contains(Point2D point) {
        return contains(point.x(), point.y());
    }

    public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
    	// number of curves to approximate the arc
		int nSeg = (int) ceil(abs(angleExtent) / (PI / 2));
    	nSeg = min(nSeg, 4);
    	
    	// angular extent of each curve
		double ext = angleExtent / nSeg;
    	
    	// compute coefficient 
    	double k = btan(abs(ext));
    	
		for (int i = 0; i < nSeg; i++) {
			// position of the two extremities
			double ti0 = abs(i * ext);
			double ti1 = abs((i + 1) * ext);
    		
    		// extremity points
    		Point2D p1 = this.point(ti0);
    		Point2D p2 = this.point(ti1);
    		
    		// tangent vectors, multiplied by appropriate coefficient
    		Vector2D v1 = this.tangent(ti0).times(k);
    		Vector2D v2 = this.tangent(ti1).times(k);
    		
    		// append a cubic curve to the path
    		path.curveTo(
    				p1.x() + v1.x(), p1.y() + v1.y(),
					p2.x() - v2.x(), p2.y() - v2.y(), 
					p2.x(), p2.y());
    	}
		return path;    		
    }

    public java.awt.geom.GeneralPath getGeneralPath() {
        // create new path
        java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();

        // move to the first point
        Point2D point = this.firstPoint();
        path.moveTo((float) point.x(), (float) point.y());

        // append the curve
        path = this.appendPath(path);

        // return the final path
        return path;
    }

	@Override
    public void draw(Graphics2D g2) {
        g2.draw(this.getGeneralPath());
    }

    /**
     * 
     * btan computes the length (k) of the control segments at
     * the beginning and end of a cubic Bezier that approximates
     * a segment of an arc with extent less than or equal to
     * 90 degrees.  This length (k) will be used to generate the
     * 2 Bezier control points for such a segment.
     *
     *   Assumptions:
     *     a) arc is centered on 0,0 with radius of 1.0
     *     b) arc extent is less than 90 degrees
     *     c) control points should preserve tangent
     *     d) control segments should have equal length
     *
     *   Initial data:
     *     start angle: ang1
     *     end angle:   ang2 = ang1 + extent
     *     start point: P1 = (x1, y1) = (cos(ang1), sin(ang1))
     *     end point:   P4 = (x4, y4) = (cos(ang2), sin(ang2))
     *
     *   Control points:
     *     P2 = (x2, y2)
     *     | x2 = x1 - k * sin(ang1) = cos(ang1) - k * sin(ang1)
     *     | y2 = y1 + k * cos(ang1) = sin(ang1) + k * cos(ang1)
     *
     *     P3 = (x3, y3)
     *     | x3 = x4 + k * sin(ang2) = cos(ang2) + k * sin(ang2)
     *     | y3 = y4 - k * cos(ang2) = sin(ang2) - k * cos(ang2)
     *
     * The formula for this length (k) can be found using the
     * following derivations:
     *
     *   Midpoints:
     *     a) Bezier (t = 1/2)
     *        bPm = P1 * (1-t)^3 +
     *              3 * P2 * t * (1-t)^2 +
     *              3 * P3 * t^2 * (1-t) +
     *              P4 * t^3 =
     *            = (P1 + 3P2 + 3P3 + P4)/8
     *
     *     b) arc
     *        aPm = (cos((ang1 + ang2)/2), sin((ang1 + ang2)/2))
     *
     *   Let angb = (ang2 - ang1)/2; angb is half of the angle
     *   between ang1 and ang2.
     *
     *   Solve the equation bPm == aPm
     *
     *     a) For xm coord:
     *        x1 + 3*x2 + 3*x3 + x4 = 8*cos((ang1 + ang2)/2)
     *
     *        cos(ang1) + 3*cos(ang1) - 3*k*sin(ang1) +
     *        3*cos(ang2) + 3*k*sin(ang2) + cos(ang2) =
     *        = 8*cos((ang1 + ang2)/2)
     *
     *        4*cos(ang1) + 4*cos(ang2) + 3*k*(sin(ang2) - sin(ang1)) =
     *        = 8*cos((ang1 + ang2)/2)
     *
     *        8*cos((ang1 + ang2)/2)*cos((ang2 - ang1)/2) +
     *        6*k*sin((ang2 - ang1)/2)*cos((ang1 + ang2)/2) =
     *        = 8*cos((ang1 + ang2)/2)
     *
     *        4*cos(angb) + 3*k*sin(angb) = 4
     *
     *        k = 4 / 3 * (1 - cos(angb)) / sin(angb)
     *
     *     b) For ym coord we derive the same formula.
     *
     * Since this formula can generate "NaN" values for small
     * angles, we will derive a safer form that does not involve
     * dividing by very small values:
     *     (1 - cos(angb)) / sin(angb) =
     *     = (1 - cos(angb))*(1 + cos(angb)) / sin(angb)*(1 + cos(angb)) =
     *     = (1 - cos(angb)^2) / sin(angb)*(1 + cos(angb)) =
     *     = sin(angb)^2 / sin(angb)*(1 + cos(angb)) =
     *     = sin(angb) / (1 + cos(angb))
     *
     * Function taken from java.awt.geom.ArcIterator.
     */
    private static double btan(double increment) {
        increment /= 2.0;
        return 4.0 / 3.0 * sin(increment) / (1.0 + cos(increment));
    }

    // ===================================================================
    // methods implementing GeometricObject2D interface

	/* (non-Javadoc)
	 * @see math.geom2d.GeometricObject2D#almostEquals(math.geom2d.GeometricObject2D, double)
	 */
	public boolean almostEquals(GeometricObject2D obj, double eps) {
    	if (this==obj)
    		return true;
    	
        if (!(obj instanceof EllipseArc2D))
            return false;
        EllipseArc2D arc = (EllipseArc2D) obj;

        // test whether supporting ellipses have same support
        if (abs(ellipse.xc-arc.ellipse.xc)>eps)
            return false;
        if (abs(ellipse.yc-arc.ellipse.yc)>eps)
            return false;
        if (abs(ellipse.r1-arc.ellipse.r1)>eps)
            return false;
        if (abs(ellipse.r2-arc.ellipse.r2)>eps)
            return false;
        if (abs(ellipse.theta-arc.ellipse.theta)>eps)
            return false;

        // test if angles are the same
        if (!Angle2D.equals(startAngle, arc.startAngle))
            return false;
        if (!Angle2D.equals(angleExtent, arc.angleExtent))
            return false;

        return true;
	}
	
    // ====================================================================
    // methods from interface Object

    @Override
    public String toString() {
    	Point2D center = ellipse.center();
        return String.format(Locale.US, 
                "EllipseArc2D(%7.2f,%7.2f,%7.2f,%7.2f,%7.5f,%7.5f,%7.5f)", 
                center.x(), center.y(), 
                ellipse.r1, ellipse.r2, ellipse.theta,
                startAngle, angleExtent);
    }

    @Override
    public boolean equals(Object obj) {
    	if (this == obj)
    		return true;
    	
        if (!(obj instanceof EllipseArc2D))
            return false;
        EllipseArc2D that = (EllipseArc2D) obj;

        // test whether supporting ellipses have same support
        if (!this.ellipse.equals(that.ellipse))
        	return false;

        // test if angles are the same
        if (!EqualUtils.areEqual(startAngle, that.startAngle))
            return false;
        if (!EqualUtils.areEqual(angleExtent, that.angleExtent))
            return false;

        return true;
    }
    
    @Override
    public EllipseArc2D clone() {
        return new EllipseArc2D(ellipse, startAngle, angleExtent);
    }
}

 

Ads help maintain this website.