/* File Hyperbola2D.java 
 *
 * Project : Java Geometry Library
 *
 * ===========================================
 * 
 * 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.
 */

// package

package math.geom2d.conic;

import static java.lang.Math.*;

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

import math.geom2d.*;
import math.geom2d.domain.ContourArray2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.line.StraightLine2D;
import math.utils.EqualUtils;

// Imports

/**
 * An Hyperbola, which is represented as a curve set of two boundary curves
 * which are instances of HyperbolaBranch2D.
 */
public class Hyperbola2D extends ContourArray2D 
implements Conic2D, Cloneable {

    // ===================================================================
    // Static factories
    
    public static Hyperbola2D create(Point2D center, double a, double b,
    		double theta) {
        return new Hyperbola2D(center.x(), center.y(), a, b, theta, true);
    }

    public static Hyperbola2D create(Point2D center, double a, double b,
    		double theta, boolean d) {
    	return new Hyperbola2D(center.x(), center.y(), a, b, theta, d);
    }

    
    // ===================================================================
    // static methods

    /**
     * Creates a new Hyperbola by reducing the conic coefficients, assuming
     * conic type is Hyperbola, and hyperbola is centered.
     * 
     * @param coefs an array of double with at least 3 coefficients containing
     *            coefficients for x^2, xy, and y^2 factors. If the array is
     *            longer, remaining coefficients are ignored.
     * @return the Hyperbola2D corresponding to given coefficients
     */
    public static Hyperbola2D reduceCentered(double[] coefs) {
        double A = coefs[0];
        double B = coefs[1];
        double C = coefs[2];

        // Compute orientation angle of the hyperbola
		double theta;
		if (abs(A - C) < Shape2D.ACCURACY) {
			theta = PI / 4;
		} else {
			theta = atan2(B, (A - C)) / 2.0;
			if (B < 0)
				theta -= PI;
			theta = Angle2D.formatAngle(theta);
        }

        // compute ellipse in isothetic basis
        double[] coefs2 = Conics2D.transformCentered(coefs,
                AffineTransform2D.createRotation(-theta));

        // extract coefficient f if present
        double f = 1;
		if (coefs2.length > 5)
			f = abs(coefs[5]);

		assert abs(coefs2[1] / f) < Shape2D.ACCURACY :
			"Second conic coefficient should be zero";

		assert coefs2[0] * coefs2[2] < 0 :
            "Transformed conic is not an Hyperbola";
        

        // extract major and minor axis lengths, ensuring r1 is greater
        double r1, r2;
		if (coefs2[0] > 0) {
			// East-West hyperbola
			r1 = sqrt(f / coefs2[0]);
			r2 = sqrt(-f / coefs2[2]);
        } else {
            // North-South hyperbola
			r1 = sqrt(f / coefs2[2]);
			r2 = sqrt(-f / coefs2[0]);
			theta = Angle2D.formatAngle(theta + PI / 2);
			theta = Math.min(theta, Angle2D.formatAngle(theta + PI));
        }

        // Return the new Hyperbola
        return new Hyperbola2D(0, 0, r1, r2, theta, true);
    }

    /**
     * Transforms an hyperbola, by supposing both the hyperbola is centered
     * and the transform has no translation part.
     * 
     * @param hyper an hyperbola
     * @param trans an affine transform
     * @return the transformed hyperbola, centered around origin
     */
    public static Hyperbola2D transformCentered(Hyperbola2D hyper,
            AffineTransform2D trans) {
        // Extract inner parameter of hyperbola
        double a = hyper.a;
        double b = hyper.b;
        double theta = hyper.theta;

        // precompute some parts
		double aSq = a * a;
		double bSq = b * b;
		double cot = cos(theta);
		double sit = sin(theta);
		double cotSq = cot * cot;
		double sitSq = sit * sit;

        // compute coefficients of the centered conic
		double A = cotSq / aSq - sitSq / bSq;
		double B = 2 * cot * sit * (1 / aSq + 1 / bSq);
		double C = sitSq / aSq - cotSq / bSq;
		double[] coefs = new double[] { A, B, C };

        // Compute coefficients of the transformed conic, still centered
        double[] coefs2 = Conics2D.transformCentered(coefs, trans);

        // reduce conic coefficients to an hyperbola
        return Hyperbola2D.reduceCentered(coefs2);
    }


    // ===================================================================
    // class variables

    /** Center of the hyperbola */
    protected double            xc      = 0;
    protected double            yc      = 0;

    /** first focal parameter */
    protected double            a       = 1;

    /** second focal parameter */
    protected double            b       = 1;

    /** angle of rotation of the hyperbola */
    protected double            theta   = 0;

    /** a flag indicating whether the hyperbola is direct or not */
    protected boolean           direct  = true;

    /** The negative branch of the hyperbola */
    protected HyperbolaBranch2D branch1 = null;
    
    /** The positive branch of the hyperbola */
    protected HyperbolaBranch2D branch2 = null;

    // ===================================================================
    // constructors

    /**
     * Assume centered hyperbola, with a = b = 1 (orthogonal hyperbola), theta=0
     * (hyperbola is oriented East-West), and direct orientation.
     */
    public Hyperbola2D() {
        this(0, 0, 1, 1, 0, true);
    }

    /**
     * Copy constructor
     * @param hyp the hyperbola to copy
     */
    public Hyperbola2D(Hyperbola2D hyp) {
    	this(hyp.xc, hyp.yc, hyp.a, hyp.b, hyp.theta, hyp.direct); 
    }
    
    public Hyperbola2D(Point2D center, double a, double b, double theta) {
        this(center.x(), center.y(), a, b, theta, true);
    }

    public Hyperbola2D(Point2D center, double a, double b, double theta,
            boolean d) {
        this(center.x(), center.y(), a, b, theta, d);
    }

    public Hyperbola2D(double xc, double yc, double a, double b, double theta) {
        this(xc, yc, a, b, theta, true);
    }

    /** Main constructor */
    public Hyperbola2D(double xc, double yc, double a, double b, double theta,
            boolean d) {
        this.xc = xc;
        this.yc = yc;
        this.a = a;
        this.b = b;
        this.theta = theta;
        this.direct = d;

        branch1 = new HyperbolaBranch2D(this, false);
        branch2 = new HyperbolaBranch2D(this, true);
        this.add(branch1);
        this.add(branch2);
    }

    
    // ===================================================================
    // methods specific to Hyperbola2D

    /**
     * Transforms a point in local coordinate (ie orthogonal centered hyberbola
     * with a=b=1) to global coordinate system.
     */
    public Point2D toGlobal(Point2D point) {
        point = point.transform(AffineTransform2D.createScaling(a, b));
        point = point.transform(AffineTransform2D.createRotation(theta));
        point = point.transform(AffineTransform2D.createTranslation(xc, yc));
        return point;
    }

    public Point2D toLocal(Point2D point) {
		point = point.transform(AffineTransform2D.createTranslation(-xc, -yc));
		point = point.transform(AffineTransform2D.createRotation(-theta));
		point = point.transform(AffineTransform2D.createScaling(1.0 / a, 1.0 / b));
        return point;
    }

    /**
     * Changes coordinates of the line to correspond to a standard hyperbola.
     * Standard hyperbola is such that x^2-y^2=1 for every point.
     * 
     * @param point
     * @return
     */
    private LinearShape2D formatLine(LinearShape2D line) {
        line = line.transform(AffineTransform2D.createTranslation(-xc, -yc));
        line = line.transform(AffineTransform2D.createRotation(-theta));
        line = line.transform(AffineTransform2D.createScaling(1.0/a, 1.0/b));
        return line;
    }

    /**
     * Returns the center of the Hyperbola. This point does not belong to the
     * Hyperbola.
     * @return the center point of the Hyperbola.
     */
    public Point2D getCenter() {
        return new Point2D(xc, yc);
    }

    /** 
     * Returns the angle made by the first direction vector with the horizontal
     * axis.
     */
    public double getAngle() {
        return theta;
    }

    /** Returns a */
    public double getLength1() {
        return a;
    }

    /** Returns b */
    public double getLength2() {
        return b;
    }

    public boolean isDirect() {
        return direct;
    }

    public Vector2D getVector1() {
        return new Vector2D(cos(theta), sin(theta));
    }

    public Vector2D getVector2() {
        return new Vector2D(-sin(theta), cos(theta));
    }

    /**
     * Returns the focus located on the positive side of the main hyperbola
     * axis.
     */
    public Point2D getFocus1() {
        double c = hypot(a, b);
		return new Point2D(xc + c * cos(theta), yc + c * sin(theta));
    }

    /**
     * Returns the focus located on the negative side of the main hyperbola
     * axis.
     */
    public Point2D getFocus2() {
        double c = hypot(a, b);
		return new Point2D(xc - c * cos(theta), yc - c * sin(theta));
    }
    
    public HyperbolaBranch2D positiveBranch() {
    	return branch2;
    }

    public HyperbolaBranch2D negativeBranch() {
    	return branch1;
    }
    
    public Collection branches() {
    	ArrayList array = 
    		new ArrayList(2);
    	array.add(branch1);
    	array.add(branch2);
    	return array;
    }
    
    /**
     * Returns the asymptotes of the hyperbola.
     */
    public Collection asymptotes() {    	
    	// Compute base direction vectors
    	Vector2D v1 = new Vector2D(a, b);
    	Vector2D v2 = new Vector2D(a, -b);
    	
    	// rotate by the angle of the hyperbola with Ox axis
    	AffineTransform2D rot = AffineTransform2D.createRotation(this.theta);
    	v1 = v1.transform(rot);
    	v2 = v2.transform(rot);

    	// init array for storing lines
    	ArrayList array = new ArrayList(2);
    	
    	// add each asymptote
    	Point2D center = this.getCenter();
    	array.add(new StraightLine2D(center, v1));
    	array.add(new StraightLine2D(center, v2));
    	
    	// return the array of asymptotes
    	return array;
    }

    // ===================================================================
    // methods inherited from Conic2D interface

    public double[] conicCoefficients() {
        // scaling coefficients
		double aSq = this.a * this.a;
		double bSq = this.b * this.b;
		double aSqInv = 1.0 / aSq;
		double bSqInv = 1.0 / bSq;

        // angle of hyperbola with horizontal, and trigonometric formulas
		double sint = sin(this.theta);
		double cost = cos(this.theta);
		double sin2t = 2.0 * sint * cost;
		double sintSq = sint * sint;
		double costSq = cost * cost;

        // coefs from hyperbola center
		double xcSq = xc * xc;
		double ycSq = yc * yc;

        /*
         * Compute the coefficients. These formulae are the transformations on
         * the unit hyperbola written out long hand
         */

		double a = costSq / aSq - sintSq / bSq;
		double b = (bSq + aSq) * sin2t / (aSq * bSq);
		double c = sintSq / aSq - costSq / bSq;
		double d = -yc * b - 2 * xc * a;
		double e = -xc * b - 2 * yc * c;
		double f = -1.0 + (xcSq + ycSq) * (aSqInv - bSqInv) / 2.0
				+ (costSq - sintSq) * (xcSq - ycSq) * (aSqInv + bSqInv) / 2.0
				+ xc * yc * (aSqInv + bSqInv) * sin2t;
        // Equivalent to:
        // double f = (xcSq*costSq + xc*yc*sin2t + ycSq*sintSq)*aSqInv
        // - (xcSq*sintSq - xc*yc*sin2t + ycSq*costSq)*bSqInv - 1;

        // Return array of results
        return new double[] { a, b, c, d, e, f };
    }

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

    public double eccentricity() {
		return hypot(1, b * b / a / a);
    }

    
    // ===================================================================
    // methods implementing the Curve2D interface

    @Override
    public Hyperbola2D reverse() {
        return new Hyperbola2D(this.xc, this.yc, this.a, this.b, this.theta,
                !this.direct);
    }

    @Override
    public Collection intersections(LinearShape2D line) {

        Collection points = new ArrayList();

        // format to 'standard' hyperbola
        LinearShape2D line2 = formatLine(line);

        // Extract formatted line parameters
        Point2D origin = line2.origin();
        double dx = line2.direction().x();
        double dy = line2.direction().y();

        // extract line parameters
        // different strategy depending if line is more horizontal or more
        // vertical
		if (abs(dx) > abs(dy)) {
            // Line is mainly horizontal

            // slope and intercept of the line: y(x) = k*x + yi
			double k = dy / dx;
			double yi = origin.y() - k * origin.x();

            // compute coefficients of second order equation
			double a = 1 - k * k;
			double b = -2 * k * yi;
			double c = -yi * yi - 1;

			double delta = b * b - 4 * a * c;
			if (delta <= 0) {
				System.out.println("Intersection with horizontal line should alays give positive delta");
				return points;
			}

            // x coordinate of intersection points
			double x1 = (-b - sqrt(delta)) / (2 * a);
			double x2 = (-b + sqrt(delta)) / (2 * a);

            // support line of formatted line
            StraightLine2D support = line2.supportingLine();

            // check first point is on the line
			double pos1 = support.project(new Point2D(x1, k * x1 + yi));
			if (line2.contains(support.point(pos1)))
				points.add(line.point(pos1));

			// check second point is on the line
			double pos2 = support.project(new Point2D(x2, k * x2 + yi));
            if (line2.contains(support.point(pos2)))
                points.add(line.point(pos2));

        } else {
            // Line is mainly vertical

            // slope and intercept of the line: x(y) = k*y + xi
			double k = dx / dy;
			double xi = origin.x() - k * origin.y();

			// compute coefficients of second order equation
			double a = k * k - 1;
			double b = 2 * k * xi;
			double c = xi * xi - 1;

			double delta = b * b - 4 * a * c;
			if (delta <= 0) {
				// No intersection with the hyperbola
				return points;
			}

			// x coordinate of intersection points
			double y1 = (-b - sqrt(delta)) / (2 * a);
			double y2 = (-b + sqrt(delta)) / (2 * a);

            // support line of formatted line
            StraightLine2D support = line2.supportingLine();

            // check first point is on the line
			double pos1 = support.project(new Point2D(k * y1 + xi, y1));
			if (line2.contains(support.point(pos1)))
				points.add(line.point(pos1));

			// check second point is on the line
			double pos2 = support.project(new Point2D(k * y2 + xi, y2));
            if (line2.contains(support.point(pos2)))
                points.add(line.point(pos2));
        }

        return points;
    }

    // ===================================================================
    // methods implementing the Shape2D interface

    @Override
    public boolean contains(Point2D point) {
        return this.contains(point.x(), point.y());
    }

    @Override
    public boolean contains(double x, double y) {
		Point2D point = toLocal(new Point2D(x, y));
		double xa = point.x() / a;
		double yb = point.y() / b;
		double res = xa * xa - yb * yb - 1;
		return abs(res) < 1e-6;
    }

    /**
     * Transforms this Hyperbola by an affine transform.
     */
    @Override
    public Hyperbola2D transform(AffineTransform2D trans) {
        Hyperbola2D result = Hyperbola2D.transformCentered(this, trans);
        Point2D center = this.getCenter().transform(trans);
        result.xc = center.x();
        result.yc = center.y();
        //TODO: check convention for transform with indirect transform, see Curve2D.
		result.direct = this.direct ^ !trans.isDirect();
        return result;
    }

    /** Returns a bounding box with infinite bounds in every direction */
    @Override
    public Box2D boundingBox() {
        return Box2D.INFINITE_BOX;
    }

    /** Throws an UnboundedShapeException */
    @Override
    public void draw(Graphics2D g) {
        throw new UnboundedShape2DException(this);
    }

	// ===================================================================
	// methods implementing the 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 Hyperbola2D))
            return false;

        // Cast to hyperbola
        Hyperbola2D that = (Hyperbola2D) obj;

		// check if each parameter is the same
		if (abs(that.xc - this.xc) > eps)
			return false;
		if (abs(that.yc - this.yc) > eps)
			return false;
		if (abs(that.a - this.a) > eps)
			return false;
		if (abs(that.b - this.b) > eps)
			return false;
		if (abs(that.theta - this.theta) > eps)
			return false;
		if (this.direct != that.direct)
			return false;

        // same parameters, then same parabola
        return true;
    }

	// ===================================================================
	// methods implementing the Object interface

    /**
     * Tests whether this hyperbola equals another object.
     */
    @Override
    public boolean equals(Object obj) {
        if (!(obj instanceof Hyperbola2D))
            return false;

        // Cast to hyperbola
        Hyperbola2D that = (Hyperbola2D) obj;

        // check if each parameter is the same
        if (!EqualUtils.areEqual(this.xc, that.xc)) 
			return false;
		if (!EqualUtils.areEqual(this.yc, that.yc)) 
			return false;
		if (!EqualUtils.areEqual(this.a, that.a)) 
			return false;
		if (!EqualUtils.areEqual(this.b, that.b)) 
			return false;
		if (!EqualUtils.areEqual(this.theta, that.theta)) 
			return false;
        if (this.direct!=that.direct)
            return false;

        // same parameters, then same parabola
        return true;
    }

	/**
	 * @deprecated use copy constructor instead (0.11.2)
	 */
	@Deprecated
    @Override
    public Hyperbola2D clone() {
        return new Hyperbola2D(xc, yc, a, b, theta, direct);
    }
}

 

Ads help maintain this website.