View Javadoc

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  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      protected double            xc      = 0;
59      protected double            yc      = 0;
60  
61      /** first focal parameter */
62      protected double            a       = 1;
63  
64      /** second focal parameter */
65      protected double            b       = 1;
66  
67      /** angle of rotation of the hyperbola */
68      protected double            theta   = 0;
69  
70      /** a flag indicating whether the hyperbola is direct or not */
71      protected boolean           direct  = true;
72  
73      /** The negative branch of the hyperbola */
74      protected HyperbolaBranch2D branch1 = null;
75      
76      /** The positive branch of the hyperbola */
77      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          this(0, 0, 1, 1, 0, true);
88      }
89  
90      public Hyperbola2D(Point2D center, double a, double b, double theta) {
91          this(center.getX(), center.getY(), a, b, theta, true);
92      }
93  
94      public Hyperbola2D(Point2D center, double a, double b, double theta,
95              boolean d) {
96          this(center.getX(), center.getY(), a, b, theta, d);
97      }
98  
99      public Hyperbola2D(double xc, double yc, double a, double b, double theta) {
100         this(xc, yc, a, b, theta, true);
101     }
102 
103     /** Main constructor */
104     public Hyperbola2D(double xc, double yc, double a, double b, double theta,
105             boolean d) {
106         this.xc = xc;
107         this.yc = yc;
108         this.a = a;
109         this.b = b;
110         this.theta = theta;
111         this.direct = d;
112 
113         branch1 = new HyperbolaBranch2D(this, false);
114         branch2 = new HyperbolaBranch2D(this, true);
115         this.addCurve(branch1);
116         this.addCurve(branch2);
117     }
118 
119     
120     // ===================================================================
121     // Static factories
122     
123     public static Hyperbola2D create(Point2D center, double a, double b,
124     		double theta) {
125         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     	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         double A = coefs[0];
148         double B = coefs[1];
149         double C = coefs[2];
150 
151         // Compute orientation angle of the hyperbola
152         double theta;
153         if (Math.abs(A-C)<Shape2D.ACCURACY) {
154             theta = Math.PI/4;
155         } else {
156             theta = Math.atan2(B, (A-C))/2.0;
157             if (B<0)
158                 theta -= Math.PI;
159             theta = Angle2D.formatAngle(theta);
160         }
161 
162         // compute ellipse in isothetic basis
163         double[] coefs2 = Conic2DUtils.transformCentered(coefs,
164                 AffineTransform2D.createRotation(-theta));
165 
166         // extract coefficient f if present
167         double f = 1;
168         if (coefs2.length>5)
169             f = Math.abs(coefs[5]);
170 
171         assert Math.abs(coefs2[1]/f)<Shape2D.ACCURACY :
172         	"Second conic coefficient should be zero";
173 
174         assert coefs2[0]*coefs2[2]<0:
175             "Transformed conic is not an Hyperbola";
176         
177 
178         // extract major and minor axis lengths, ensuring r1 is greater
179         double r1, r2;
180         if (coefs2[0]>0) {
181             // East-West hyperbola
182             r1 = Math.sqrt(f/coefs2[0]);
183             r2 = Math.sqrt(-f/coefs2[2]);
184         } else {
185             // North-South hyperbola
186             r1 = Math.sqrt(f/coefs2[2]);
187             r2 = Math.sqrt(-f/coefs2[0]);
188             theta = Angle2D.formatAngle(theta+Math.PI/2);
189             theta = Math.min(theta, Angle2D.formatAngle(theta+Math.PI));
190         }
191 
192         // Return the new Hyperbola
193         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         double a = hyper.a;
208         double b = hyper.b;
209         double theta = hyper.theta;
210 
211         // precompute some parts
212         double aSq = a*a;
213         double bSq = b*b;
214         double cot = Math.cos(theta);
215         double sit = Math.sin(theta);
216         double cotSq = cot*cot;
217         double sitSq = sit*sit;
218 
219         // compute coefficients of the centered conic
220         double A = cotSq/aSq-sitSq/bSq;
221         double B = 2*cot*sit*(1/aSq+1/bSq);
222         double C = sitSq/aSq-cotSq/bSq;
223         double[] coefs = new double[] { A, B, C };
224 
225         // Compute coefficients of the transformed conic, still centered
226         double[] coefs2 = Conic2DUtils.transformCentered(coefs, trans);
227 
228         // reduce conic coefficients to an hyperbola
229         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         point = point.transform(AffineTransform2D.createScaling(a, b));
242         point = point.transform(AffineTransform2D.createRotation(theta));
243         point = point.transform(AffineTransform2D.createTranslation(xc, yc));
244         return point;
245     }
246 
247     public Point2D toLocal(Point2D point) {
248         point = point.transform(AffineTransform2D.createTranslation(-xc, -yc));
249         point = point.transform(AffineTransform2D.createRotation(-theta));
250         point = point.transform(AffineTransform2D.createScaling(1/a, 1/b));
251         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         line = line.transform(AffineTransform2D.createTranslation(-xc, -yc));
263         line = line.transform(AffineTransform2D.createRotation(-theta));
264         line = line.transform(AffineTransform2D.createScaling(1.0/a, 1.0/b));
265         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         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         return theta;
283     }
284 
285     /** Returns a */
286     public double getLength1() {
287         return a;
288     }
289 
290     /** Returns b */
291     public double getLength2() {
292         return b;
293     }
294 
295     public boolean isDirect() {
296         return direct;
297     }
298 
299     public Vector2D getVector1() {
300         return new Vector2D(Math.cos(theta), Math.sin(theta));
301     }
302 
303     public Vector2D getVector2() {
304         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         double c = Math.hypot(a, b);
313         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         double c = Math.hypot(a, b);
322         return new Point2D(xc-c*Math.cos(theta), yc-c*Math.sin(theta));
323     }
324     
325     public HyperbolaBranch2D getPositiveBranch() {
326     	return branch2;
327     }
328 
329     public HyperbolaBranch2D getNegativeBranch() {
330     	return branch1;
331     }
332     
333     public Collection<HyperbolaBranch2D> getBranches() {
334     	ArrayList<HyperbolaBranch2D> array = 
335     		new ArrayList<HyperbolaBranch2D>(2);
336     	array.add(branch1);
337     	array.add(branch2);
338     	return array;
339     }
340     
341     /**
342      * Returns the asymptotes of the hyperbola.
343      */
344     public Collection<StraightLine2D> getAsymptotes() {    	
345     	// Compute base direction vectors
346     	Vector2D v1 = new Vector2D(a, b);
347     	Vector2D v2 = new Vector2D(a, -b);
348     	
349     	// rotate by the angle of the hyperbola with Ox axis
350     	AffineTransform2D rot = AffineTransform2D.createRotation(this.theta);
351     	v1 = v1.transform(rot);
352     	v2 = v2.transform(rot);
353 
354     	// init array for storing lines
355     	ArrayList<StraightLine2D> array = new ArrayList<StraightLine2D>(2);
356     	
357     	// add each asymptote
358     	Point2D center = this.getCenter();
359     	array.add(new StraightLine2D(center, v1));
360     	array.add(new StraightLine2D(center, v2));
361     	
362     	// return the array of asymptotes
363     	return array;
364     }
365 
366     // ===================================================================
367     // methods inherited from Conic2D interface
368 
369     public double[] getConicCoefficients() {
370         // scaling coefficients
371         double aSq = this.a*this.a;
372         double bSq = this.b*this.b;
373         double aSqInv = 1.0/aSq;
374         double bSqInv = 1.0/bSq;
375 
376         // angle of hyperbola with horizontal, and trigonometric formulas
377         double sint = Math.sin(this.theta);
378         double cost = Math.cos(this.theta);
379         double sin2t = 2.0*sint*cost;
380         double sintSq = sint*sint;
381         double costSq = cost*cost;
382 
383         // coefs from hyperbola center
384         double xcSq = xc*xc;
385         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         double a = costSq/aSq-sintSq/bSq;
393         double b = (bSq+aSq)*sin2t/(aSq*bSq);
394         double c = sintSq/aSq-costSq/bSq;
395         double d = -yc*b-2*xc*a;
396         double e = -xc*b-2*yc*c;
397         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         return new double[] { a, b, c, d, e, f };
405     }
406 
407     public Conic2D.Type getConicType() {
408         return Conic2D.Type.HYPERBOLA;
409     }
410 
411     public double getEccentricity() {
412         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         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         Collection<Point2D> points = new ArrayList<Point2D>();
429 
430         // format to 'standard' hyperbola
431         LinearShape2D line2 = formatLine(line);
432 
433         // Extract formatted line parameters
434         Point2D origin = line2.getOrigin();
435         double dx = line2.getVector().getX();
436         double dy = line2.getVector().getY();
437 
438         // extract line parameters
439         // different strategy depending if line is more horizontal or more
440         // vertical
441         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             double k = dy/dx;
446             double yi = origin.getY()-k*origin.getX();
447 
448             // compute coefficients of second order equation
449             double a = 1-k*k;
450             double b = -2*k*yi;
451             double c = -yi*yi-1;
452 
453             double delta = b*b-4*a*c;
454             if (delta<=0) {
455                 System.out.println(
456                         "Intersection with horizontal line should alays give positive delta");
457                 return points;
458             }
459 
460             // x coordinate of intersection points
461             double x1 = (-b-Math.sqrt(delta))/(2*a);
462             double x2 = (-b+Math.sqrt(delta))/(2*a);
463 
464             // support line of formatted line
465             StraightLine2D support = line2.getSupportingLine();
466 
467             // check first point is on the line
468             double pos1 = support.project(new Point2D(x1, k*x1+yi));
469             if (line2.contains(support.getPoint(pos1)))
470                 points.add(line.getPoint(pos1));
471 
472             // check second point is on the line
473             double pos2 = support.project(new Point2D(x2, k*x2+yi));
474             if (line2.contains(support.getPoint(pos2)))
475                 points.add(line.getPoint(pos2));
476 
477         } else {
478             // Line is mainly vertical
479 
480             // slope and intercept of the line: x(y) = k*y + xi
481             double k = dx/dy;
482             double xi = origin.getX()-k*origin.getY();
483 
484             // compute coefficients of second order equation
485             double a = k*k-1;
486             double b = 2*k*xi;
487             double c = xi*xi-1;
488 
489             double delta = b*b-4*a*c;
490             if (delta<=0) {
491                 // No intersection with the hyperbola
492                 return points;
493             }
494 
495             // x coordinate of intersection points
496             double y1 = (-b-Math.sqrt(delta))/(2*a);
497             double y2 = (-b+Math.sqrt(delta))/(2*a);
498 
499             // support line of formatted line
500             StraightLine2D support = line2.getSupportingLine();
501 
502             // check first point is on the line
503             double pos1 = support.project(new Point2D(k*y1+xi, y1));
504             if (line2.contains(support.getPoint(pos1)))
505                 points.add(line.getPoint(pos1));
506 
507             // check second point is on the line
508             double pos2 = support.project(new Point2D(k*y2+xi, y2));
509             if (line2.contains(support.getPoint(pos2)))
510                 points.add(line.getPoint(pos2));
511         }
512 
513         return points;
514     }
515 
516     // ===================================================================
517     // methods implementing the Shape2D interface
518 
519     @Override
520     public boolean contains(java.awt.geom.Point2D point) {
521         return this.contains(point.getX(), point.getY());
522     }
523 
524     @Override
525     public boolean contains(double x, double y) {
526         Point2D point = toLocal(new Point2D(x, y));
527         double xa = point.getX()/a;
528         double yb = point.getY()/b;
529         double res = xa*xa-yb*yb-1;
530         // double res = x*x*b*b - y*y*a*a - a*a*b*b;
531         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         Hyperbola2D result = Hyperbola2D.transformCentered(this, trans);
540         Point2D center = this.getCenter().transform(trans);
541         result.xc = center.getX();
542         result.yc = center.getY();
543         //TODO: check convention for transform with indirect transform, see Curve2D.
544         result.direct = this.direct^!trans.isDirect();
545         return result;
546     }
547 
548     /** Throws an UnboundedShapeException */
549     @Override
550     public void draw(Graphics2D g) {
551         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         if (!(obj instanceof Hyperbola2D))
560             return false;
561 
562         // Cast to hyperbola
563         Hyperbola2D that = (Hyperbola2D) obj;
564 
565         // check if each parameter is the same
566         double eps = 1e-6;
567         if (Math.abs(that.xc-this.xc)>eps)
568             return false;
569         if (Math.abs(that.yc-this.yc)>eps)
570             return false;
571         if (Math.abs(that.a-this.a)>eps)
572             return false;
573         if (Math.abs(that.b-this.b)>eps)
574             return false;
575         if (Math.abs(that.theta-this.theta)>eps)
576             return false;
577         if (this.direct!=that.direct)
578             return false;
579 
580         // same parameters, then same parabola
581         return true;
582     }
583 
584     @Override
585     public Hyperbola2D clone() {
586         return new Hyperbola2D(xc, yc, a, b, theta, direct);
587     }
588 }