/**
 * 
 */

package math.geom2d.conic;
import static java.lang.Math.*;


import math.geom2d.AffineTransform2D;
import math.geom2d.Angle2D;
import math.geom2d.Point2D;
import math.geom2d.Shape2D;
import math.geom2d.domain.ContourArray2D;
import math.geom2d.line.StraightLine2D;

/**
 * Generic class providing utilities for manipulating conics. Provides in
 * particular methods for reducing a conic.
 * 
 * @author dlegland
 */
public class Conics2D {

    public final static Conic2D reduceConic(double[] coefs) {
		if (coefs.length < 6) {
            System.err.println(
            		"Conic2DUtils.reduceConic: must provide 6 coefficients");
            return null;
        }
        boolean debug = false;

        // precision for tests
        double eps = Shape2D.ACCURACY;

        // Extract coefficients
        double a = coefs[0];
        double b = coefs[1];
        double c = coefs[2];
        double d = coefs[3];
        double e = coefs[4];
        double f = coefs[5];

        // Transform the generic conic into a conic symmetric with respect to
        // one of the basis axes.
        // This results in fixing the coefficient b to 0.

        // coefficients of transformed conic;
        double a1, b1, c1, d1, e1, f1;

        double theta0 = 0;
        // Check if b is zero
		if (abs(b) < eps) {
            // Simply keep the same coefficients
            a1 = a;
            b1 = b;
            c1 = c;
            d1 = d;
            e1 = e;
            f1 = f;
            theta0 = 0;
        } else {
            // determine rotation angle (between 0 and PI/2).
			if (abs(a - c) < eps)
				theta0 = PI / 4; // conic symmetric wrt diagonal
			else
				theta0 = Angle2D.formatAngle(atan2(b, a - c) / 2);

			if (debug)
				System.out.println("conic main angle: " + toDegrees(theta0));

			// computation shortcuts
			double cot = cos(theta0);
			double sit = sin(theta0);
			double co2t = cos(2 * theta0);
			double si2t = sin(2 * theta0);
			double cot2 = cot * cot;
			double sit2 = sit * sit;

			// Compute coefficients of the conic rotated around origin
			a1 = a * cot2 + b * sit * cot + c * sit2;
			b1 = si2t * (c - a) + b * co2t; // should be equal to zero
			c1 = a * sit2 - b * sit * cot + c * cot2;
			d1 = d * cot + e * sit;
			e1 = -d * sit + e * cot;
			f1 = f;
        }

        // small control on the value of b1
		if (abs(b1) > eps) {
            System.err.println(
            		"Conic2DUtils.reduceConic: " + 
            		"conic was not correctly transformed");
            return null;
        }

		// Test degenerate cases
		if (abs(a) < eps && abs(c) < eps) {
			if (abs(d) > eps || abs(e) > eps)
				return new ConicStraightLine2D(d, e, f);
			else
				return null; // TODO: throw exception ?
        }

        // Case of a parabola
		if (abs(a1) < eps) {
            // case of a1 close to 0 -> parabola parallel to horizontal axis
            if (debug)
                System.out.println("horizontal parabola");

			// Check degenerate case d=0
			if (abs(d1) < eps) {
				double delta = e1 * e1 - 4 * c1 * f1;
				if (delta >= 0) {
					// find the 2 roots
					double ys = -e1 / 2.0 / c1;
					double dist = sqrt(delta) / 2.0 / c1;
					Point2D center = new Point2D(0, ys)
							.transform(AffineTransform2D.createRotation(theta0));
					return new ConicTwoLines2D(center, dist, theta0);
				} else
					return null; // TODO: throw exception ?
            }

            // compute reduced coefficients
			double c2 = -c1 / d1;
			double e2 = -e1 / d1;
			double f2 = -f1 / d1;

            // vertex of the parabola
			double xs = -(e2 * e2 - 4 * c2 * f2) / (4 * c2);
			double ys = -e2 * .5 / c2;
            
            // create and return result
			return new Parabola2D(xs, ys, c2, theta0 - PI / 2);

		} else if (abs(c1) < eps) {
			// Case of c1 close to 0 -> parabola parallel to vertical axis
			if (debug)
				System.out.println("vertical parabola");

            // Check degenerate case d=0
			if (abs(e1) < eps) {
				double delta = d1 * d1 - 4 * a1 * f1;
				if (delta >= 0) {
					// find the 2 roots
					double xs = -d1 / 2.0 / a1;
					double dist = sqrt(delta) / 2.0 / a1;
					Point2D center = new Point2D(0, xs)
							.transform(AffineTransform2D.createRotation(theta0));
					return new ConicTwoLines2D(center, dist, theta0);
				} else
					return null; // TODO: throw exception ?
			}

			// compute reduced coefficients
			double a2 = -a1 / e1;
			double d2 = -d1 / e1;
			double f2 = -f1 / e1;

			// vertex of parabola
			double xs = -d2 * .5 / a2;
			double ys = -(d2 * d2 - 4 * a2 * f2) / (4 * a2);

			// create and return result
			return new Parabola2D(xs, ys, a2, theta0);
        }

        // Remaining cases: ellipse or hyperbola

        // compute coordinate of conic center
		Point2D center = new Point2D(-d1 / (2 * a1), -e1 / (2 * c1));
		center = center.transform(AffineTransform2D.createRotation(theta0));

		// length of semi axes
		double num = (c1 * d1 * d1 + a1 * e1 * e1 - 4 * a1 * c1 * f1)
				/ (4 * a1 * c1);
		double at = num / a1;
		double bt = num / c1;

		if (at < 0 && bt < 0) {
			System.err.println("Conic2DUtils.reduceConic(): found A<0 and C<0");
			return null;
        }

        // Case of an ellipse
		if (at > 0 && bt > 0) {
			if (debug)
				System.out.println("ellipse");
			if (at > bt)
				return new Ellipse2D(center, sqrt(at), sqrt(bt), theta0);
			else
				return new Ellipse2D(center, sqrt(bt), sqrt(at),
						Angle2D.formatAngle(theta0 + PI / 2));
        }

        // remaining case is the hyperbola

		// Case of east-west hyperbola
		if (at > 0) {
			if (debug)
				System.out.println("east-west hyperbola");
			return new Hyperbola2D(center, sqrt(at), sqrt(-bt), theta0);
		} else {
			if (debug)
				System.out.println("north-south hyperbola");
			return new Hyperbola2D(center, sqrt(bt), sqrt(-at), theta0 + PI / 2);
        }
    }

    /**
     * Transforms a conic centered around the origin, by dropping the
     * translation part of the transform. The array must be contains at least
     * 3 elements. If it contains 6 elements, the 3 remaining elements are
     * supposed to be 0, 0, and -1 in that order.
     * 
     * @param coefs an array of double with at least 3 coefficients
     * @param trans an affine transform
     * @return an array of double with as many elements as the input array
     */
    public final static double[] transformCentered(double[] coefs,
            AffineTransform2D trans) {
        // Extract transform coefficients
        double[][] mat = trans.affineMatrix();
        double a = mat[0][0];
        double b = mat[1][0];
        double c = mat[0][1];
        double d = mat[1][1];

        // Extract first conic coefficients
        double A = coefs[0];
        double B = coefs[1];
        double C = coefs[2];

        // compute matrix determinant
		double delta = a * d - b * c;
		delta = delta * delta;

		double A2 = (A * d * d + C * b * b - B * b * d) / delta;
		double B2 = (B * (a * d + b * c) - 2 * (A * c * d + C * a * b)) / delta;
		double C2 = (A * c * c + C * a * a - B * a * c) / delta;

        // return only 3 parameters if needed
        if (coefs.length==3)
            return new double[] { A2, B2, C2 };

        // Compute other coefficients
		double D = coefs[3];
		double E = coefs[4];
		double F = coefs[5];
		double D2 = D * d - E * b;
		double E2 = E * a - D * c;
        return new double[] { A2, B2, C2, D2, E2, F };
    }

    /**
     * Transforms a conic by an affine transform.
     * 
     * @param coefs an array of double with 6 coefficients
     * @param trans an affine transform
     * @return the coefficients of the transformed conic
     */
    public final static double[] transform(double[] coefs,
            AffineTransform2D trans) {
        // Extract coefficients of the inverse transform
        double[][] mat = trans.invert().affineMatrix();
        double a = mat[0][0];
        double b = mat[1][0];
        double c = mat[0][1];
        double d = mat[1][1];
        double e = mat[0][2];
        double f = mat[1][2];

        // Extract conic coefficients
        double A = coefs[0];
        double B = coefs[1];
        double C = coefs[2];
        double D = coefs[3];
        double E = coefs[4];
        double F = coefs[5];

		// Compute coefficients of the transformed conic
		double A2 = A * a * a + B * a * b + C * b * b;
		double B2 = 2 * (A * a * c + C * b * d) + B * (a * d + b * c);
		double C2 = A * c * c + B * c * d + C * d * d;
		double D2 = 2 * (A * a * e + C * b * f) + B * (a * f + b * e) + D * a + E * b;
		double E2 = 2 * (A * c * e + C * d * f) + B * (c * f + d * e) + D * c + E * d;
		double F2 = A * e * e + B * e * f + C * f * f + D * e + E * f + F;

        // Return the array of coefficients
        return new double[] { A2, B2, C2, D2, E2, F2 };
    }

    // -----------------------------------------------------------------
    // Some special conics

    static class ConicStraightLine2D extends StraightLine2D implements Conic2D {

        double[] coefs = new double[] { 0, 0, 0, 1, 0, 0 };

        public ConicStraightLine2D(StraightLine2D line) {
            super(line);
			coefs = new double[] { 0, 0, 0, dy, -dx, dx * y0 - dy * x0 };
        }

        public ConicStraightLine2D(double a, double b, double c) {
            super(StraightLine2D.createCartesian(a, b, c));
            coefs = new double[] { 0, 0, 0, a, b, c };
        }

        public double[] conicCoefficients() {
            return coefs;
        }

        public Type conicType() {
            return Conic2D.Type.STRAIGHT_LINE;
        }

        /** Return NaN. */
        public double eccentricity() {
            return Double.NaN;
        }

        @Override
        public ConicStraightLine2D reverse() {
            return new ConicStraightLine2D(super.reverse());
        }

        @Override
        public ConicStraightLine2D transform(AffineTransform2D trans) {
            return new ConicStraightLine2D(super.transform(trans));
        }
    }


    static class ConicTwoLines2D extends ContourArray2D
            implements Conic2D {

        double xc = 0, yc = 0, d = 1, theta = 0;

        public ConicTwoLines2D(Point2D point, double d, double theta) {
            this(point.x(), point.y(), d, theta);
        }

        public ConicTwoLines2D(double xc, double yc, double d, double theta) {
            super();

            this.xc = xc;
            this.yc = yc;
            this.d = d;
            this.theta = theta;

            StraightLine2D baseLine = new StraightLine2D(
                    new Point2D(xc, yc), theta);
            this.add(baseLine.parallel(d));
            this.add(baseLine.parallel(-d).reverse());
        }

        public double[] conicCoefficients() {
            double[] coefs = { 0, 0, 1, 0, 0, -1 };
            AffineTransform2D sca = AffineTransform2D.createScaling(0, d);
            AffineTransform2D rot = AffineTransform2D.createRotation(theta);
            AffineTransform2D tra = AffineTransform2D.createTranslation(xc, yc);
            
            // AffineTransform2D trans = tra.compose(rot).compose(sca);
            AffineTransform2D trans = sca.chain(rot).chain(tra);
            return Conics2D.transform(coefs, trans);
        }

        public Type conicType() {
            return Conic2D.Type.TWO_LINES;
        }

        public double eccentricity() {
            return Double.NaN;
        }

        @Override
        public ConicTwoLines2D transform(AffineTransform2D trans) {
            Point2D center = new Point2D(xc, yc).transform(trans);
            StraightLine2D line = this.firstCurve().transform(trans);

            double dist = line.distance(center);
            double angle = line.horizontalAngle();
            return new ConicTwoLines2D(center, dist, angle);
        }

        @Override
        public ConicTwoLines2D reverse() {
            return new ConicTwoLines2D(xc, yc, -d, theta);
        }
    }

    // TODO: add CrossConic2D
}

 

Ads help maintain this website.