Coverage Report - math.geom2d.conic.Hyperbola2D
 
Classes in this File Line Coverage Branch Coverage Complexity
Hyperbola2D
0%
0/203
0%
0/50
1,78
 
 1  
 /* File Hyperbola2D.java 
 2  
  *
 3  
  * Project : Java Geometry Library
 4  
  *
 5  
  * ===========================================
 6  
  * 
 7  
  * This library is free software; you can redistribute it and/or modify it 
 8  
  * under the terms of the GNU Lesser General Public License as published by
 9  
  * the Free Software Foundation, either version 2.1 of the License, or (at
 10  
  * your option) any later version.
 11  
  *
 12  
  * This library is distributed in the hope that it will be useful, but 
 13  
  * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
 14  
  * or FITNESS FOR A PARTICULAR PURPOSE.
 15  
  *
 16  
  * See the GNU Lesser General Public License for more details.
 17  
  *
 18  
  * You should have received a copy of the GNU Lesser General Public License
 19  
  * along with this library. if not, write to :
 20  
  * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 21  
  * Boston, MA 02111-1307, USA.
 22  
  */
 23  
 
 24  
 // package
 25  
 
 26  
 package math.geom2d.conic;
 27  
 
 28  
 import java.awt.Graphics2D;
 29  
 import java.util.ArrayList;
 30  
 import java.util.Collection;
 31  
 
 32  
 import math.geom2d.AffineTransform2D;
 33  
 import math.geom2d.Angle2D;
 34  
 import math.geom2d.Point2D;
 35  
 import math.geom2d.Shape2D;
 36  
 import math.geom2d.UnboundedShapeException;
 37  
 import math.geom2d.Vector2D;
 38  
 import math.geom2d.domain.BoundarySet2D;
 39  
 import math.geom2d.line.LinearShape2D;
 40  
 import math.geom2d.line.StraightLine2D;
 41  
 
 42  
 // Imports
 43  
 
 44  
 /**
 45  
  * An Hyperbola, which is represented as a curve set of two boundary curves
 46  
  * which are instances of HyperbolaBranch2D.
 47  
  */
 48  0
 public class Hyperbola2D extends BoundarySet2D<HyperbolaBranch2D> 
 49  
 implements Conic2D, Cloneable {
 50  
 
 51  
     // ===================================================================
 52  
     // constants
 53  
 
 54  
     // ===================================================================
 55  
     // class variables
 56  
 
 57  
     /** Center of the hyperbola */
 58  0
     protected double            xc      = 0;
 59  0
     protected double            yc      = 0;
 60  
 
 61  
     /** first focal parameter */
 62  0
     protected double            a       = 1;
 63  
 
 64  
     /** second focal parameter */
 65  0
     protected double            b       = 1;
 66  
 
 67  
     /** angle of rotation of the hyperbola */
 68  0
     protected double            theta   = 0;
 69  
 
 70  
     /** a flag indicating whether the hyperbola is direct or not */
 71  0
     protected boolean           direct  = true;
 72  
 
 73  
     /** The negative branch of the hyperbola */
 74  0
     protected HyperbolaBranch2D branch1 = null;
 75  
     
 76  
     /** The positive branch of the hyperbola */
 77  0
     protected HyperbolaBranch2D branch2 = null;
 78  
 
 79  
     // ===================================================================
 80  
     // constructors
 81  
 
 82  
     /**
 83  
      * Assume centered hyperbola, with a = b = 1 (orthogonal hyperbola), theta=0
 84  
      * (hyperbola is oriented East-West), and direct orientation.
 85  
      */
 86  
     public Hyperbola2D() {
 87  0
         this(0, 0, 1, 1, 0, true);
 88  0
     }
 89  
 
 90  
     public Hyperbola2D(Point2D center, double a, double b, double theta) {
 91  0
         this(center.getX(), center.getY(), a, b, theta, true);
 92  0
     }
 93  
 
 94  
     public Hyperbola2D(Point2D center, double a, double b, double theta,
 95  
             boolean d) {
 96  0
         this(center.getX(), center.getY(), a, b, theta, d);
 97  0
     }
 98  
 
 99  
     public Hyperbola2D(double xc, double yc, double a, double b, double theta) {
 100  0
         this(xc, yc, a, b, theta, true);
 101  0
     }
 102  
 
 103  
     /** Main constructor */
 104  
     public Hyperbola2D(double xc, double yc, double a, double b, double theta,
 105  0
             boolean d) {
 106  0
         this.xc = xc;
 107  0
         this.yc = yc;
 108  0
         this.a = a;
 109  0
         this.b = b;
 110  0
         this.theta = theta;
 111  0
         this.direct = d;
 112  
 
 113  0
         branch1 = new HyperbolaBranch2D(this, false);
 114  0
         branch2 = new HyperbolaBranch2D(this, true);
 115  0
         this.addCurve(branch1);
 116  0
         this.addCurve(branch2);
 117  0
     }
 118  
 
 119  
     
 120  
     // ===================================================================
 121  
     // Static factories
 122  
     
 123  
     public static Hyperbola2D create(Point2D center, double a, double b,
 124  
                     double theta) {
 125  0
         return new Hyperbola2D(center.getX(), center.getY(), a, b, theta, true);
 126  
     }
 127  
 
 128  
     public static Hyperbola2D create(Point2D center, double a, double b,
 129  
                     double theta, boolean d) {
 130  0
             return new Hyperbola2D(center.getX(), center.getY(), a, b, theta, d);
 131  
     }
 132  
 
 133  
     
 134  
     // ===================================================================
 135  
     // static methods
 136  
 
 137  
     /**
 138  
      * Creates a new Hyperbola by reducing the conic coefficients, assuming
 139  
      * conic type is Hyperbola, and hyperbola is centered.
 140  
      * 
 141  
      * @param coefs an array of double with at least 3 coefficients containing
 142  
      *            coefficients for x^2, xy, and y^2 factors. If the array is
 143  
      *            longer, remaining coefficients are ignored.
 144  
      * @return the Hyperbola2D corresponding to given coefficients
 145  
      */
 146  
     public static Hyperbola2D reduceCentered(double[] coefs) {
 147  0
         double A = coefs[0];
 148  0
         double B = coefs[1];
 149  0
         double C = coefs[2];
 150  
 
 151  
         // Compute orientation angle of the hyperbola
 152  
         double theta;
 153  0
         if (Math.abs(A-C)<Shape2D.ACCURACY) {
 154  0
             theta = Math.PI/4;
 155  
         } else {
 156  0
             theta = Math.atan2(B, (A-C))/2.0;
 157  0
             if (B<0)
 158  0
                 theta -= Math.PI;
 159  0
             theta = Angle2D.formatAngle(theta);
 160  
         }
 161  
 
 162  
         // compute ellipse in isothetic basis
 163  0
         double[] coefs2 = Conic2DUtils.transformCentered(coefs,
 164  
                 AffineTransform2D.createRotation(-theta));
 165  
 
 166  
         // extract coefficient f if present
 167  0
         double f = 1;
 168  0
         if (coefs2.length>5)
 169  0
             f = Math.abs(coefs[5]);
 170  
 
 171  
         assert Math.abs(coefs2[1]/f)<Shape2D.ACCURACY :
 172  0
                 "Second conic coefficient should be zero";
 173  
 
 174  
         assert coefs2[0]*coefs2[2]<0:
 175  0
             "Transformed conic is not an Hyperbola";
 176  
         
 177  
 
 178  
         // extract major and minor axis lengths, ensuring r1 is greater
 179  
         double r1, r2;
 180  0
         if (coefs2[0]>0) {
 181  
             // East-West hyperbola
 182  0
             r1 = Math.sqrt(f/coefs2[0]);
 183  0
             r2 = Math.sqrt(-f/coefs2[2]);
 184  
         } else {
 185  
             // North-South hyperbola
 186  0
             r1 = Math.sqrt(f/coefs2[2]);
 187  0
             r2 = Math.sqrt(-f/coefs2[0]);
 188  0
             theta = Angle2D.formatAngle(theta+Math.PI/2);
 189  0
             theta = Math.min(theta, Angle2D.formatAngle(theta+Math.PI));
 190  
         }
 191  
 
 192  
         // Return the new Hyperbola
 193  0
         return new Hyperbola2D(0, 0, r1, r2, theta, true);
 194  
     }
 195  
 
 196  
     /**
 197  
      * Transforms an hyperbola, by supposing both the hyperbola is centered
 198  
      * and the transform has no translation part.
 199  
      * 
 200  
      * @param hyper an hyperbola
 201  
      * @param trans an affine transform
 202  
      * @return the transformed hyperbola, centered around origin
 203  
      */
 204  
     public static Hyperbola2D transformCentered(Hyperbola2D hyper,
 205  
             AffineTransform2D trans) {
 206  
         // Extract inner parameter of hyperbola
 207  0
         double a = hyper.a;
 208  0
         double b = hyper.b;
 209  0
         double theta = hyper.theta;
 210  
 
 211  
         // precompute some parts
 212  0
         double aSq = a*a;
 213  0
         double bSq = b*b;
 214  0
         double cot = Math.cos(theta);
 215  0
         double sit = Math.sin(theta);
 216  0
         double cotSq = cot*cot;
 217  0
         double sitSq = sit*sit;
 218  
 
 219  
         // compute coefficients of the centered conic
 220  0
         double A = cotSq/aSq-sitSq/bSq;
 221  0
         double B = 2*cot*sit*(1/aSq+1/bSq);
 222  0
         double C = sitSq/aSq-cotSq/bSq;
 223  0
         double[] coefs = new double[] { A, B, C };
 224  
 
 225  
         // Compute coefficients of the transformed conic, still centered
 226  0
         double[] coefs2 = Conic2DUtils.transformCentered(coefs, trans);
 227  
 
 228  
         // reduce conic coefficients to an hyperbola
 229  0
         return Hyperbola2D.reduceCentered(coefs2);
 230  
     }
 231  
 
 232  
 
 233  
     // ===================================================================
 234  
     // methods specific to Hyperbola2D
 235  
 
 236  
     /**
 237  
      * transform a point in local coordinate (ie orthogonal centered hyberbola
 238  
      * with a=b=1) to global coordinate system.
 239  
      */
 240  
     public Point2D toGlobal(Point2D point) {
 241  0
         point = point.transform(AffineTransform2D.createScaling(a, b));
 242  0
         point = point.transform(AffineTransform2D.createRotation(theta));
 243  0
         point = point.transform(AffineTransform2D.createTranslation(xc, yc));
 244  0
         return point;
 245  
     }
 246  
 
 247  
     public Point2D toLocal(Point2D point) {
 248  0
         point = point.transform(AffineTransform2D.createTranslation(-xc, -yc));
 249  0
         point = point.transform(AffineTransform2D.createRotation(-theta));
 250  0
         point = point.transform(AffineTransform2D.createScaling(1/a, 1/b));
 251  0
         return point;
 252  
     }
 253  
 
 254  
     /**
 255  
      * Change coordinate of the line to correspond to a standard hyperbola.
 256  
      * Standard hyperbola is such that x^2-y^2=1 for every point.
 257  
      * 
 258  
      * @param point
 259  
      * @return
 260  
      */
 261  
     private LinearShape2D formatLine(LinearShape2D line) {
 262  0
         line = line.transform(AffineTransform2D.createTranslation(-xc, -yc));
 263  0
         line = line.transform(AffineTransform2D.createRotation(-theta));
 264  0
         line = line.transform(AffineTransform2D.createScaling(1.0/a, 1.0/b));
 265  0
         return line;
 266  
     }
 267  
 
 268  
     /**
 269  
      * Returns the center of the Hyperbola. This point does not belong to the
 270  
      * Hyperbola.
 271  
      * @return the center point of the Hyperbola.
 272  
      */
 273  
     public Point2D getCenter() {
 274  0
         return new Point2D(xc, yc);
 275  
     }
 276  
 
 277  
     /** 
 278  
      * Returns the angle made by the first direction vector with the horizontal
 279  
      * axis.
 280  
      */
 281  
     public double getAngle() {
 282  0
         return theta;
 283  
     }
 284  
 
 285  
     /** Returns a */
 286  
     public double getLength1() {
 287  0
         return a;
 288  
     }
 289  
 
 290  
     /** Returns b */
 291  
     public double getLength2() {
 292  0
         return b;
 293  
     }
 294  
 
 295  
     public boolean isDirect() {
 296  0
         return direct;
 297  
     }
 298  
 
 299  
     public Vector2D getVector1() {
 300  0
         return new Vector2D(Math.cos(theta), Math.sin(theta));
 301  
     }
 302  
 
 303  
     public Vector2D getVector2() {
 304  0
         return new Vector2D(-Math.sin(theta), Math.cos(theta));
 305  
     }
 306  
 
 307  
     /**
 308  
      * Returns the focus located on the positive side of the main hyperbola
 309  
      * axis.
 310  
      */
 311  
     public Point2D getFocus1() {
 312  0
         double c = Math.hypot(a, b);
 313  0
         return new Point2D(xc+c*Math.cos(theta), yc+c*Math.sin(theta));
 314  
     }
 315  
 
 316  
     /**
 317  
      * Returns the focus located on the negative side of the main hyperbola
 318  
      * axis.
 319  
      */
 320  
     public Point2D getFocus2() {
 321  0
         double c = Math.hypot(a, b);
 322  0
         return new Point2D(xc-c*Math.cos(theta), yc-c*Math.sin(theta));
 323  
     }
 324  
     
 325  
     public HyperbolaBranch2D getPositiveBranch() {
 326  0
             return branch2;
 327  
     }
 328  
 
 329  
     public HyperbolaBranch2D getNegativeBranch() {
 330  0
             return branch1;
 331  
     }
 332  
     
 333  
     public Collection<HyperbolaBranch2D> getBranches() {
 334  0
             ArrayList<HyperbolaBranch2D> array = 
 335  
                     new ArrayList<HyperbolaBranch2D>(2);
 336  0
             array.add(branch1);
 337  0
             array.add(branch2);
 338  0
             return array;
 339  
     }
 340  
     
 341  
     /**
 342  
      * Returns the asymptotes of the hyperbola.
 343  
      */
 344  
     public Collection<StraightLine2D> getAsymptotes() {            
 345  
             // Compute base direction vectors
 346  0
             Vector2D v1 = new Vector2D(a, b);
 347  0
             Vector2D v2 = new Vector2D(a, -b);
 348  
             
 349  
             // rotate by the angle of the hyperbola with Ox axis
 350  0
             AffineTransform2D rot = AffineTransform2D.createRotation(this.theta);
 351  0
             v1 = v1.transform(rot);
 352  0
             v2 = v2.transform(rot);
 353  
 
 354  
             // init array for storing lines
 355  0
             ArrayList<StraightLine2D> array = new ArrayList<StraightLine2D>(2);
 356  
             
 357  
             // add each asymptote
 358  0
             Point2D center = this.getCenter();
 359  0
             array.add(new StraightLine2D(center, v1));
 360  0
             array.add(new StraightLine2D(center, v2));
 361  
             
 362  
             // return the array of asymptotes
 363  0
             return array;
 364  
     }
 365  
 
 366  
     // ===================================================================
 367  
     // methods inherited from Conic2D interface
 368  
 
 369  
     public double[] getConicCoefficients() {
 370  
         // scaling coefficients
 371  0
         double aSq = this.a*this.a;
 372  0
         double bSq = this.b*this.b;
 373  0
         double aSqInv = 1.0/aSq;
 374  0
         double bSqInv = 1.0/bSq;
 375  
 
 376  
         // angle of hyperbola with horizontal, and trigonometric formulas
 377  0
         double sint = Math.sin(this.theta);
 378  0
         double cost = Math.cos(this.theta);
 379  0
         double sin2t = 2.0*sint*cost;
 380  0
         double sintSq = sint*sint;
 381  0
         double costSq = cost*cost;
 382  
 
 383  
         // coefs from hyperbola center
 384  0
         double xcSq = xc*xc;
 385  0
         double ycSq = yc*yc;
 386  
 
 387  
         /*
 388  
          * Compute the coefficients. These formulae are the transformations on
 389  
          * the unit hyperbola written out long hand
 390  
          */
 391  
 
 392  0
         double a = costSq/aSq-sintSq/bSq;
 393  0
         double b = (bSq+aSq)*sin2t/(aSq*bSq);
 394  0
         double c = sintSq/aSq-costSq/bSq;
 395  0
         double d = -yc*b-2*xc*a;
 396  0
         double e = -xc*b-2*yc*c;
 397  0
         double f = -1.0+(xcSq+ycSq)*(aSqInv-bSqInv)/2.0+(costSq-sintSq)
 398  
                 *(xcSq-ycSq)*(aSqInv+bSqInv)/2.0+xc*yc*(aSqInv+bSqInv)*sin2t;
 399  
         // Equivalent to:
 400  
         // double f = (xcSq*costSq + xc*yc*sin2t + ycSq*sintSq)*aSqInv
 401  
         // - (xcSq*sintSq - xc*yc*sin2t + ycSq*costSq)*bSqInv - 1;
 402  
 
 403  
         // Return array of results
 404  0
         return new double[] { a, b, c, d, e, f };
 405  
     }
 406  
 
 407  
     public Conic2D.Type getConicType() {
 408  0
         return Conic2D.Type.HYPERBOLA;
 409  
     }
 410  
 
 411  
     public double getEccentricity() {
 412  0
         return Math.hypot(1, b*b/a/a);
 413  
     }
 414  
 
 415  
     
 416  
     // ===================================================================
 417  
     // methods implementing the Curve2D interface
 418  
 
 419  
     @Override
 420  
     public Hyperbola2D getReverseCurve() {
 421  0
         return new Hyperbola2D(this.xc, this.yc, this.a, this.b, this.theta,
 422  
                 !this.direct);
 423  
     }
 424  
 
 425  
     @Override
 426  
     public Collection<Point2D> getIntersections(LinearShape2D line) {
 427  
 
 428  0
         Collection<Point2D> points = new ArrayList<Point2D>();
 429  
 
 430  
         // format to 'standard' hyperbola
 431  0
         LinearShape2D line2 = formatLine(line);
 432  
 
 433  
         // Extract formatted line parameters
 434  0
         Point2D origin = line2.getOrigin();
 435  0
         double dx = line2.getVector().getX();
 436  0
         double dy = line2.getVector().getY();
 437  
 
 438  
         // extract line parameters
 439  
         // different strategy depending if line is more horizontal or more
 440  
         // vertical
 441  0
         if (Math.abs(dx)>Math.abs(dy)) {
 442  
             // Line is mainly horizontal
 443  
 
 444  
             // slope and intercept of the line: y(x) = k*x + yi
 445  0
             double k = dy/dx;
 446  0
             double yi = origin.getY()-k*origin.getX();
 447  
 
 448  
             // compute coefficients of second order equation
 449  0
             double a = 1-k*k;
 450  0
             double b = -2*k*yi;
 451  0
             double c = -yi*yi-1;
 452  
 
 453  0
             double delta = b*b-4*a*c;
 454  0
             if (delta<=0) {
 455  0
                 System.out.println(
 456  
                         "Intersection with horizontal line should alays give positive delta");
 457  0
                 return points;
 458  
             }
 459  
 
 460  
             // x coordinate of intersection points
 461  0
             double x1 = (-b-Math.sqrt(delta))/(2*a);
 462  0
             double x2 = (-b+Math.sqrt(delta))/(2*a);
 463  
 
 464  
             // support line of formatted line
 465  0
             StraightLine2D support = line2.getSupportingLine();
 466  
 
 467  
             // check first point is on the line
 468  0
             double pos1 = support.project(new Point2D(x1, k*x1+yi));
 469  0
             if (line2.contains(support.getPoint(pos1)))
 470  0
                 points.add(line.getPoint(pos1));
 471  
 
 472  
             // check second point is on the line
 473  0
             double pos2 = support.project(new Point2D(x2, k*x2+yi));
 474  0
             if (line2.contains(support.getPoint(pos2)))
 475  0
                 points.add(line.getPoint(pos2));
 476  
 
 477  0
         } else {
 478  
             // Line is mainly vertical
 479  
 
 480  
             // slope and intercept of the line: x(y) = k*y + xi
 481  0
             double k = dx/dy;
 482  0
             double xi = origin.getX()-k*origin.getY();
 483  
 
 484  
             // compute coefficients of second order equation
 485  0
             double a = k*k-1;
 486  0
             double b = 2*k*xi;
 487  0
             double c = xi*xi-1;
 488  
 
 489  0
             double delta = b*b-4*a*c;
 490  0
             if (delta<=0) {
 491  
                 // No intersection with the hyperbola
 492  0
                 return points;
 493  
             }
 494  
 
 495  
             // x coordinate of intersection points
 496  0
             double y1 = (-b-Math.sqrt(delta))/(2*a);
 497  0
             double y2 = (-b+Math.sqrt(delta))/(2*a);
 498  
 
 499  
             // support line of formatted line
 500  0
             StraightLine2D support = line2.getSupportingLine();
 501  
 
 502  
             // check first point is on the line
 503  0
             double pos1 = support.project(new Point2D(k*y1+xi, y1));
 504  0
             if (line2.contains(support.getPoint(pos1)))
 505  0
                 points.add(line.getPoint(pos1));
 506  
 
 507  
             // check second point is on the line
 508  0
             double pos2 = support.project(new Point2D(k*y2+xi, y2));
 509  0
             if (line2.contains(support.getPoint(pos2)))
 510  0
                 points.add(line.getPoint(pos2));
 511  
         }
 512  
 
 513  0
         return points;
 514  
     }
 515  
 
 516  
     // ===================================================================
 517  
     // methods implementing the Shape2D interface
 518  
 
 519  
     @Override
 520  
     public boolean contains(java.awt.geom.Point2D point) {
 521  0
         return this.contains(point.getX(), point.getY());
 522  
     }
 523  
 
 524  
     @Override
 525  
     public boolean contains(double x, double y) {
 526  0
         Point2D point = toLocal(new Point2D(x, y));
 527  0
         double xa = point.getX()/a;
 528  0
         double yb = point.getY()/b;
 529  0
         double res = xa*xa-yb*yb-1;
 530  
         // double res = x*x*b*b - y*y*a*a - a*a*b*b;
 531  0
         return Math.abs(res)<1e-6;
 532  
     }
 533  
 
 534  
     /**
 535  
      * Transforms this Hyperbola by an affine transform.
 536  
      */
 537  
     @Override
 538  
     public Hyperbola2D transform(AffineTransform2D trans) {
 539  0
         Hyperbola2D result = Hyperbola2D.transformCentered(this, trans);
 540  0
         Point2D center = this.getCenter().transform(trans);
 541  0
         result.xc = center.getX();
 542  0
         result.yc = center.getY();
 543  
         //TODO: check convention for transform with indirect transform, see Curve2D.
 544  0
         result.direct = this.direct^!trans.isDirect();
 545  0
         return result;
 546  
     }
 547  
 
 548  
     /** Throws an UnboundedShapeException */
 549  
     @Override
 550  
     public void draw(Graphics2D g) {
 551  0
         throw new UnboundedShapeException(this);
 552  
     }
 553  
 
 554  
     /**
 555  
      * Tests whether this hyperbola equals another object.
 556  
      */
 557  
     @Override
 558  
     public boolean equals(Object obj) {
 559  0
         if (!(obj instanceof Hyperbola2D))
 560  0
             return false;
 561  
 
 562  
         // Cast to hyperbola
 563  0
         Hyperbola2D that = (Hyperbola2D) obj;
 564  
 
 565  
         // check if each parameter is the same
 566  0
         double eps = 1e-6;
 567  0
         if (Math.abs(that.xc-this.xc)>eps)
 568  0
             return false;
 569  0
         if (Math.abs(that.yc-this.yc)>eps)
 570  0
             return false;
 571  0
         if (Math.abs(that.a-this.a)>eps)
 572  0
             return false;
 573  0
         if (Math.abs(that.b-this.b)>eps)
 574  0
             return false;
 575  0
         if (Math.abs(that.theta-this.theta)>eps)
 576  0
             return false;
 577  0
         if (this.direct!=that.direct)
 578  0
             return false;
 579  
 
 580  
         // same parameters, then same parabola
 581  0
         return true;
 582  
     }
 583  
 
 584  
     @Override
 585  
     public Hyperbola2D clone() {
 586  0
         return new Hyperbola2D(xc, yc, a, b, theta, direct);
 587  
     }
 588  
 }