View Javadoc

1   /* File Ellipse2D.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.awt.Shape;
30  import java.util.ArrayList;
31  import java.util.Collection;
32  
33  import math.geom2d.AffineTransform2D;
34  import math.geom2d.Angle2D;
35  import math.geom2d.Box2D;
36  import math.geom2d.Point2D;
37  import math.geom2d.Shape2D;
38  import math.geom2d.Vector2D;
39  import math.geom2d.curve.AbstractSmoothCurve2D;
40  import math.geom2d.curve.Curve2D;
41  import math.geom2d.curve.Curve2DUtils;
42  import math.geom2d.curve.CurveArray2D;
43  import math.geom2d.curve.CurveSet2D;
44  import math.geom2d.curve.SmoothCurve2D;
45  import math.geom2d.domain.Domain2D;
46  import math.geom2d.domain.GenericDomain2D;
47  import math.geom2d.domain.SmoothBoundary2D;
48  import math.geom2d.domain.SmoothOrientedCurve2D;
49  import math.geom2d.line.LinearShape2D;
50  
51  // Imports
52  
53  /**
54   * An ellipse in the plane. It is defined by the center, the orientation angle,
55   * and the lengths of the two axis. No convention is taken about lengths of
56   * semiaxis : the second semi axis can be greater than the first one.
57   */
58  public class Ellipse2D extends AbstractSmoothCurve2D
59  implements SmoothBoundary2D, Conic2D, Cloneable {
60  
61  
62      // ===================================================================
63      // constants
64  
65      // ===================================================================
66      // class variables
67  
68      /** coordinate of center. */
69      protected double  xc;
70      protected double  yc;
71  
72      /** length of major semi-axis */
73      protected double  r1;
74      
75      /** length of minor semi-axis */
76      protected double  r2;
77  
78      /** orientation of major semi-axis */
79      protected double  theta  = 0;
80  
81      /** directed ellipse or not */
82      protected boolean direct = true;
83  
84      // ===================================================================
85      // constructors
86  
87      /**
88       * Empty constructor, define ellipse centered at origin with both major and
89       * minor semi-axis with length equal to 1.
90       */
91      public Ellipse2D() {
92          this(0, 0, 1, 1, 0, true);
93      }
94  
95      /** Main constructor: define center by a point plus major and minor semi axis */
96      public Ellipse2D(Point2D center, double l1, double l2) {
97          this(center.getX(), center.getY(), l1, l2, 0, true);
98      }
99  
100     /** Define center by coordinate, plus major and minor semi axis */
101     public Ellipse2D(double xc, double yc, double l1, double l2) {
102         this(xc, yc, l1, l2, 0, true);
103     }
104 
105     /**
106      * Define center by point, major and minor semi axis lengths, and
107      * orientation angle.
108      */
109     public Ellipse2D(Point2D center, double l1, double l2, double theta) {
110         this(center.getX(), center.getY(), l1, l2, theta, true);
111     }
112 
113     /**
114      * Define center by coordinate, major and minor semi axis lengths, and
115      * orientation angle.
116      */
117     public Ellipse2D(double xc, double yc, double l1, double l2, double theta) {
118         this(xc, yc, l1, l2, theta, true);
119     }
120 
121     /**
122      * Define center by coordinate, major and minor semi axis lengths,
123      * orientation angle, and boolean flag for directed ellipse.
124      */
125     public Ellipse2D(double xc, double yc, double l1, double l2, double theta,
126             boolean direct) {
127         this.xc = xc;
128         this.yc = yc;
129 
130         r1 = l1;
131         r2 = l2;
132 
133         this.theta = theta;
134         this.direct = direct;
135     }
136 
137     /**
138      * construct an ellipse from the java.awt.geom class for ellipse.
139      */
140     public Ellipse2D(java.awt.geom.Ellipse2D ellipse) {
141         this(new Point2D(ellipse.getCenterX(), ellipse.getCenterY()), 
142         		ellipse.getWidth()/2, ellipse.getHeight()/2);
143     }
144 
145     
146     // ===================================================================
147     // Static factories
148 
149     /**
150      * Create a new Ellipse by specifying the two focii, and the length of the
151      * chord. The chord equals the sum of distances between a point of the
152      * ellipse and each focus.
153      * 
154      * @param focus1 the first focus
155      * @param focus2 the second focus
156      * @param chord the sum of distances to focii
157      * @return a new instance of Ellipse2D
158      */
159     public static Ellipse2D create(Point2D focus1, Point2D focus2, 
160     		double chord) {
161         double x1 = focus1.getX();
162         double y1 = focus1.getY();
163         double x2 = focus2.getX();
164         double y2 = focus2.getY();
165 
166         double xc = (x1+x2)/2;
167         double yc = (y1+y2)/2;
168         double theta = Angle2D.getHorizontalAngle(x1, y1, x2, y2);
169 
170         double dist = focus1.getDistance(focus2);
171         if (dist<Shape2D.ACCURACY)
172             return new Circle2D(xc, yc, chord/2);
173 
174         double r1 = chord/2;
175         double r2 = Math.sqrt(chord*chord-dist*dist)/2;
176 
177         return new Ellipse2D(xc, yc, r1, r2, theta);
178     }
179 
180     /** Main constructor: define center by a point plus major and minor semi axis */
181     public static Ellipse2D create(Point2D center, double l1, double l2) {
182         return new Ellipse2D(center.getX(), center.getY(), l1, l2, 0, true);
183     }
184 
185     /**
186      * Define center by point, major and minor semi axis lengths, and
187      * orientation angle.
188      */
189     public static Ellipse2D create(Point2D center, double l1, double l2, 
190     		double theta) {
191     	return new Ellipse2D(center.getX(), center.getY(), l1, l2, theta, true);
192     }
193 
194     /**
195      * Define center by point, major and minor semi axis lengths,
196      * orientation angle, and boolean flag for direct ellipse.
197      */
198     public static Ellipse2D create(Point2D center, double l1, double l2, 
199     		double theta, boolean direct) {
200     	return new Ellipse2D(center.getX(), center.getY(), l1, l2, theta, direct);
201     }
202 
203     /**
204      * Constructs an ellipse from the java.awt.geom class for ellipse.
205      */
206     public static Ellipse2D create(java.awt.geom.Ellipse2D ellipse) {
207         return new Ellipse2D(
208         		new Point2D(ellipse.getCenterX(), ellipse.getCenterY()), 
209         		ellipse.getWidth()/2, ellipse.getHeight()/2);
210     }
211 
212     
213     // ===================================================================
214     // Static methods
215 
216     /**
217      * Creates a new Ellipse by reducing the conic coefficients, assuming conic
218      * type is ellipse, and ellipse is centered.
219      * 
220      * @param coefs an array of double with at least 3 coefficients containing
221      *            coefficients for x^2, xy, and y^2 factors.
222      * @return the Ellipse2D corresponding to given coefficients
223      */
224     public final static Ellipse2D reduceCentered(double[] coefs) {
225         double A = coefs[0];
226         double B = coefs[1];
227         double C = coefs[2];
228 
229         // Compute orientation angle of the ellipse
230         double theta;
231         if (Math.abs(A-C)<Shape2D.ACCURACY) {
232             theta = Math.PI/4;
233         } else {
234             theta = Math.atan2(B, (A-C))/2.0;
235             if (B<0)
236                 theta -= Math.PI;
237             theta = Angle2D.formatAngle(theta);
238         }
239 
240         // compute ellipse in isothetic basis
241         double[] coefs2 = Conic2DUtils.transformCentered(coefs,
242                 AffineTransform2D.createRotation(-theta));
243 
244         // extract coefficients f if present
245         double f = 1;
246         if (coefs2.length>5)
247             f = Math.abs(coefs[5]);
248 
249         assert Math.abs(coefs2[1]/f)<Shape2D.ACCURACY : 
250         	"Second conic coefficient should be zero";
251         
252         // extract major and minor axis lengths, ensuring r1 is greater
253         double r1, r2;
254         if (coefs2[0]<coefs2[2]) {
255             r1 = Math.sqrt(f/coefs2[0]);
256             r2 = Math.sqrt(f/coefs2[2]);
257         } else {
258             r1 = Math.sqrt(f/coefs2[2]);
259             r2 = Math.sqrt(f/coefs2[0]);
260             theta = Angle2D.formatAngle(theta+Math.PI/2);
261             theta = Math.min(theta, Angle2D.formatAngle(theta+Math.PI));
262         }
263 
264         // If both semi-axes are equal, return a circle
265         if (Math.abs(r1-r2)<Shape2D.ACCURACY)
266             return new Circle2D(0, 0, r1);
267         
268         // return the reduced ellipse
269         return new Ellipse2D(0, 0, r1, r2, theta);
270     }
271 
272     /**
273      * Transform an ellipse, by supposing both the ellipse is centered and the
274      * transform has no translation part.
275      * 
276      * @param ellipse an ellipse
277      * @param trans an affine transform
278      * @return the transformed ellipse, centered around origin
279      */
280     public final static Ellipse2D transformCentered(Ellipse2D ellipse,
281             AffineTransform2D trans) {
282         // Extract inner parameter of ellipse
283         double r1 = ellipse.r1;
284         double r2 = ellipse.r2;
285         double theta = ellipse.theta;
286 
287         // precompute some parts
288         double r1Sq = r1*r1;
289         double r2Sq = r2*r2;
290         double cot = Math.cos(theta);
291         double sit = Math.sin(theta);
292         double cotSq = cot*cot;
293         double sitSq = sit*sit;
294 
295         // compute coefficients of the centered conic
296         double A = cotSq/r1Sq+sitSq/r2Sq;
297         double B = 2*cot*sit*(1/r1Sq-1/r2Sq);
298         double C = cotSq/r2Sq+sitSq/r1Sq;
299         double[] coefs = new double[] { A, B, C };
300 
301         // Compute coefficients of the transformed conic
302         double[] coefs2 = Conic2DUtils.transformCentered(coefs, trans);
303 
304         // reduce conic coefficients to Ellipse
305         return Ellipse2D.reduceCentered(coefs2);
306     }
307 
308 
309     // ===================================================================
310     // Methods specific to Ellipse2D
311 
312     /**
313      * @deprecated conics will become immutable in a future release
314      */
315     @Deprecated
316     public void setEllipse(double xc, double yc, double r1, double r2,
317             double theta) {
318         this.setEllipse(xc, yc, r1, r2, theta, true);
319     }
320 
321     /**
322      * @deprecated conics will become immutable in a future release
323      */
324     @Deprecated
325     public void setEllipse(double xc, double yc, double r1, double r2,
326             double theta, boolean direct) {
327         this.xc = xc;
328         this.yc = yc;
329         this.r1 = r1;
330         this.r2 = r2;
331         this.theta = theta;
332         this.direct = direct;
333     }
334 
335     /**
336      * @deprecated conics will become immutable in a future release
337      */
338     @Deprecated
339     public void setEllipse(Point2D center, double r1, double r2, double theta) {
340         this.setEllipse(center.getX(), center.getY(), r1, r2, theta, true);
341     }
342 
343     /**
344      * @deprecated conics will become immutable in a future release
345      */
346     @Deprecated
347     public void setEllipse(Point2D center, double r1, double r2, double theta,
348             boolean direct) {
349         this.setEllipse(center.getX(), center.getY(), r1, r2, theta, direct);
350     }
351 
352     /**
353      * @deprecated conics will become immutable in a future release
354      */
355     @Deprecated
356     public void setCenter(Point2D center) {
357         this.setCenter(center.getX(), center.getY());
358     }
359 
360     /**
361      * @deprecated conics will become immutable in a future release
362      */
363     @Deprecated
364     public void setCenter(double x, double y) {
365         this.xc = x;
366         this.yc = y;
367     }
368 
369     /**
370      * Return the RHO parameter, in a polar representation of the ellipse,
371      * centered at the center of ellipse.
372      * 
373      * @param angle : angle from horizontal
374      * @return distance of ellipse from ellipse center in direction theta
375      */
376     public double getRho(double angle) {
377         double cot = Math.cos(angle-theta);
378         double sit = Math.cos(angle-theta);
379         return Math.sqrt(r1*r1*r2*r2/(r2*r2*cot*cot+r1*r1*sit*sit));
380     }
381 
382     public Point2D getProjectedPoint(java.awt.geom.Point2D point) {
383         Vector2D polar = this.getProjectedVector(point, Shape2D.ACCURACY);
384         return new Point2D(point.getX()+polar.getX(), point.getY()+polar.getY());
385     }
386 
387     /**
388      * Compute projection of a point onto an ellipse. Return the polar vector
389      * representing the translation from point <code>point</point> to its
390      * projection on the ellipse, with the direction parallel to the local 
391      * normal to the ellipse. The parameter <code>rho</code> of the
392      * PolarVector2D is positive if point lies 
393      * Refs : <p>
394      * http://www.spaceroots.org/documents/distance/distance-to-ellipse.pdf, 
395      * http://www.spaceroots.org/downloads.html
396      * @param point
397      * @param eMax
398      * @return the projection vector
399      */
400     public Vector2D getProjectedVector(java.awt.geom.Point2D point, double eMax) {
401 
402         double ot = 1.0/3.0;
403 
404         // center the ellipse
405         double x = point.getX()-xc;
406         double y = point.getY()-yc;
407 
408         double la, lb, theta;
409         if (r1>=r2) {
410             la = r1;
411             lb = r2;
412             theta = this.theta;
413         } else {
414             la = r2;
415             lb = r1;
416             theta = this.theta+Math.PI/2;
417             double tmp = x;
418             x = -y;
419             y = tmp;
420         }
421 
422         double cot = Math.cos(theta);
423         double sit = Math.sin(theta);
424         double tmpx = x, tmpy = y;
425         x = tmpx*cot-tmpy*sit;
426         y = tmpx*sit+tmpy*cot;
427 
428         double ae = la;
429         double f = 1-lb/la;
430         double e2 = f*(2.0-f);
431         double g = 1.0-f;
432         double g2 = g*g;
433         // double e2ae = e2 * ae;
434         double ae2 = ae*ae;
435 
436         // compute some miscellaneous variables outside of the loop
437         double z = y;
438         double z2 = y*y;
439         double r = x;
440         double r2 = x*x;
441         double g2r2ma2 = g2*(r2-ae2);
442         // double g2r2ma2mz2 = g2r2ma2 - z2;
443         double g2r2ma2pz2 = g2r2ma2+z2;
444         double dist = Math.sqrt(r2+z2);
445         // double threshold = Math.max(1.0e-14 * dist, eMax);
446         boolean inside = (g2r2ma2pz2<=0);
447 
448         // point at the center
449         if (dist<(1.0e-10*ae)) {
450             System.out.println("point at the center");
451             return Vector2D.createPolar(r, 0);
452         }
453 
454         double cz = r/dist;
455         double sz = z/dist;
456         double t = z/(dist+r);
457 
458         // distance to the ellipse along the current line
459         // as the smallest root of a 2nd degree polynom :
460         // a k^2 - 2 b k + c = 0
461         double a = 1.0-e2*cz*cz;
462         double b = g2*r*cz+z*sz;
463         double c = g2r2ma2pz2;
464         double b2 = b*b;
465         double ac = a*c;
466         double k = c/(b+Math.sqrt(b2-ac));
467         // double lambda = Math.atan2(cart.y, cart.x);
468         double phi = Math.atan2(z-k*sz, g2*(r-k*cz));
469 
470         // point on the ellipse
471         if (Math.abs(k)<(1.0e-10*dist)) {
472             // return new Ellipsoidic(lambda, phi, k);
473             return Vector2D.createPolar(k, phi);
474         }
475 
476         for (int iterations = 0; iterations<100; ++iterations) {
477 
478             // 4th degree normalized polynom describing
479             // circle/ellipse intersections
480             // tau^4 + b tau^3 + c tau^2 + d tau + e = 0
481             // (there is no need to compute e here)
482             a = g2r2ma2pz2+g2*(2.0*r+k)*k;
483             b = -4.0*k*z/a;
484             c = 2.0*(g2r2ma2pz2+(1.0+e2)*k*k)/a;
485             double d = b;
486 
487             // reduce the polynom to degree 3 by removing
488             // the already known real root
489             // tau^3 + b tau^2 + c tau + d = 0
490             b += t;
491             c += t*b;
492             d += t*c;
493 
494             // find the other real root
495             b2 = b*b;
496             double Q = (3.0*c-b2)/9.0;
497             double R = (b*(9.0*c-2.0*b2)-27.0*d)/54.0;
498             double D = Q*Q*Q+R*R;
499             double tildeT, tildePhi;
500             if (D>=0) {
501                 double rootD = Math.sqrt(D);
502                 double rMr = R-rootD;
503                 double rPr = R+rootD;
504                 tildeT = ((rPr>0) ? Math.pow(rPr, ot) : -Math.pow(-rPr, ot))
505                         +((rMr>0) ? Math.pow(rMr, ot) : -Math.pow(-rMr, ot))-b
506                         *ot;
507                 double tildeT2 = tildeT*tildeT;
508                 double tildeT2P1 = 1.0+tildeT2;
509                 tildePhi = Math.atan2(z*tildeT2P1-2*k*tildeT, g2
510                         *(r*tildeT2P1-k*(1.0-tildeT2)));
511             } else {
512                 Q = -Q;
513                 double qRoot = Math.sqrt(Q);
514                 double alpha = Math.acos(R/(Q*qRoot));
515                 tildeT = 2*qRoot*Math.cos(alpha*ot)-b*ot;
516                 double tildeT2 = tildeT*tildeT;
517                 double tildeT2P1 = 1.0+tildeT2;
518                 tildePhi = Math.atan2(z*tildeT2P1-2*k*tildeT, g2
519                         *(r*tildeT2P1-k*(1.0-tildeT2)));
520                 if ((tildePhi*phi)<0) {
521                     tildeT = 2*qRoot*Math.cos((alpha+2*Math.PI)*ot)-b*ot;
522                     tildeT2 = tildeT*tildeT;
523                     tildeT2P1 = 1.0+tildeT2;
524                     tildePhi = Math.atan2(z*tildeT2P1-2*k*tildeT, g2
525                             *(r*tildeT2P1-k*(1.0-tildeT2)));
526                     if (tildePhi*phi<0) {
527                         tildeT = 2*qRoot*Math.cos((alpha+4*Math.PI)*ot)-b*ot;
528                         tildeT2 = tildeT*tildeT;
529                         tildeT2P1 = 1.0+tildeT2;
530                         tildePhi = Math.atan2(z*tildeT2P1-2*k*tildeT, g2
531                                 *(r*tildeT2P1-k*(1.0-tildeT2)));
532                     }
533                 }
534             }
535 
536             // midpoint on the ellipse
537             double dPhi = Math.abs(0.5*(tildePhi-phi));
538             phi = 0.5*(phi+tildePhi);
539             double cPhi = Math.cos(phi);
540             double sPhi = Math.sin(phi);
541             double coeff = Math.sqrt(1.0-e2*sPhi*sPhi);
542 
543             // Eventually display result of iterations
544             if (false)
545                 System.out.println(iterations+": phi = "+Math.toDegrees(phi)
546                         +" +/- "+Math.toDegrees(dPhi)+", k = "+k);
547 
548             b = ae/coeff;
549             double dR = r-cPhi*b;
550             double dZ = z-sPhi*b*g2;
551             k = Math.sqrt(dR*dR+dZ*dZ);
552             if (inside) {
553                 k = -k;
554             }
555             t = dZ/(k+dR);
556 
557             if (dPhi<1.0e-14) {
558                 if (this.r1>=this.r2)
559                     return Vector2D.createPolar(-k, phi+theta);
560                 // -(r * cPhi + z * sPhi - ae * coeff), phi+theta);
561                 else
562                     return Vector2D.createPolar(-k, phi+theta-Math.PI/2);
563                 // -(r * cPhi + z * sPhi - ae * coeff),
564                 // phi+theta-Math.PI/2);
565             }
566         }
567 
568         return null;
569     }
570 
571     /**
572      * Return the parallel ellipse located at a distance d from this ellipse.
573      * For direct ellipse, distance is positive outside of the ellipse, and
574      * negative inside
575      */
576     public Ellipse2D getParallel(double d) {
577         return new Ellipse2D(xc, yc, Math.abs(r1+d), Math.abs(r2+d), theta,
578                 direct);
579     }
580 
581     /**
582      * return true if ellipse has a direct orientation.
583      */
584     public boolean isDirect() {
585         return direct;
586     }
587 
588     public boolean isCircle() {
589         return Math.abs(r1-r2)<Shape2D.ACCURACY;
590     }
591 
592     // ===================================================================
593     // methods of Conic2D
594 
595     public Conic2D.Type getConicType() {
596         if (Math.abs(r1-r2)<Shape2D.ACCURACY)
597             return Conic2D.Type.CIRCLE;
598         else
599             return Conic2D.Type.ELLIPSE;
600     }
601 
602     /**
603      * Returns the conic coefficients of the ellipse. Algorithm taken from
604      * http://tog.acm.org/GraphicsGems/gemsv/ch2-6/conmat.c
605      */
606     public double[] getConicCoefficients() {
607 
608         // common coefficients
609         double r1Sq = this.r1*this.r1;
610         double r2Sq = this.r2*this.r2;
611 
612         // angle of ellipse, and trigonometric formulas
613         double sint = Math.sin(this.theta);
614         double cost = Math.cos(this.theta);
615         double sin2t = 2.0*sint*cost;
616         double sintSq = sint*sint;
617         double costSq = cost*cost;
618 
619         // coefs from ellipse center
620         double xcSq = xc*xc;
621         double ycSq = yc*yc;
622         double r1SqInv = 1.0/r1Sq;
623         double r2SqInv = 1.0/r2Sq;
624 
625         /*
626          * Compute the coefficients. These formulae are the transformations on
627          * the unit circle written out long hand
628          */
629 
630         double a = costSq/r1Sq+sintSq/r2Sq;
631         double b = (r2Sq-r1Sq)*sin2t/(r1Sq*r2Sq);
632         double c = costSq/r2Sq+sintSq/r1Sq;
633         double d = -yc*b-2*xc*a;
634         double e = -xc*b-2*yc*c;
635         double f = -1.0
636         	+(xcSq+ycSq)*(r1SqInv+r2SqInv)/2.0
637         	+(costSq-sintSq)*(xcSq-ycSq)*(r1SqInv-r2SqInv)/2.0
638         	+xc*yc*(r1SqInv-r2SqInv)*sin2t;
639 
640         // Return array of results
641         return new double[] { a, b, c, d, e, f };
642     }
643 
644     /**
645      * Returns the length of the major semi-axis of the ellipse.
646      */
647     public double getSemiMajorAxisLength() {
648         return r1;
649     }
650 
651     /**
652      * Returns the length of the minor semi-axis of the ellipse.
653      */
654     public double getSemiMinorAxisLength() {
655         return r2;
656     }
657 
658     /**
659      * Computes eccentricity of ellipse, depending on the lengths of the
660      * semi-axes. Eccentricity is 0 for a circle (r1==r2), and tends to 1 when
661      * ellipse elongates.
662      */
663     public double getEccentricity() {
664         double a = Math.max(r1, r2);
665         double b = Math.min(r1, r2);
666         double r = b/a;
667         return Math.sqrt(1-r*r);
668     }
669 
670     /**
671      * Returns center of the ellipse.
672      */
673     public Point2D getCenter() {
674         return new Point2D(xc, yc);
675     }
676 
677     /**
678      * Return the first focus. It is defined as the first focus on the Major
679      * axis, in the direction given by angle theta.
680      */
681     public Point2D getFocus1() {
682         double a, b, theta;
683         if (r1>r2) {
684             a = r1;
685             b = r2;
686             theta = this.theta;
687         } else {
688             a = r2;
689             b = r1;
690             theta = this.theta+Math.PI/2;
691         }
692         return Point2D.createPolar(xc, yc, Math.sqrt(a*a-b*b), theta+Math.PI);
693     }
694 
695     /**
696      * Returns the second focus. It is defined as the second focus on the Major
697      * axis, in the direction given by angle theta.
698      */
699     public Point2D getFocus2() {
700         double a, b, theta;
701         if (r1>r2) {
702             a = r1;
703             b = r2;
704             theta = this.theta;
705         } else {
706             a = r2;
707             b = r1;
708             theta = this.theta+Math.PI/2;
709         }
710         return Point2D.createPolar(xc, yc, Math.sqrt(a*a-b*b), theta);
711     }
712 
713     public Vector2D getVector1() {
714         return new Vector2D(Math.cos(theta), Math.sin(theta));
715     }
716 
717     public Vector2D getVector2() {
718         if (direct)
719             return new Vector2D(-Math.sin(theta), Math.cos(theta));
720         else
721             return new Vector2D(Math.sin(theta), -Math.cos(theta));
722     }
723 
724     /**
725      * return the angle of the ellipse first axis with the Ox axis.
726      */
727     public double getAngle() {
728         return theta;
729     }
730 
731     // ===================================================================
732     // methods of Boundary2D interface
733 
734     public Collection<? extends Ellipse2D> getBoundaryCurves() {
735     	return wrapCurve(this);
736     }
737 
738     public Domain2D getDomain() {
739         return new GenericDomain2D(this);
740     }
741 
742     public void fill(Graphics2D g2) {
743     	// convert ellipse to awt shape
744         java.awt.geom.Ellipse2D.Double ellipse = 
745         	new java.awt.geom.Ellipse2D.Double(xc-r1, yc-r2, 2*r1, 2*r2);
746         
747         // need to rotate by angle theta
748         java.awt.geom.AffineTransform trans = java.awt.geom.AffineTransform
749                 .getRotateInstance(theta, xc, yc);
750         Shape shape = trans.createTransformedShape(ellipse);
751         
752         // draw the awt ellipse
753         g2.fill(shape);
754     }
755 
756     // ===================================================================
757     // methods implementing OrientedCurve2D interface
758 
759     /**
760      * Return either 0, 2*PI or -2*PI, depending whether the point is located
761      * inside the interior of the ellipse or not.
762      */
763     public double getWindingAngle(java.awt.geom.Point2D point) {
764         if (this.getSignedDistance(point)>0)
765             return 0;
766         else
767             return direct ? Math.PI*2 : -Math.PI*2;
768     }
769 
770     /**
771      * Test whether the point is inside the ellipse. The test is performed by
772      * rotating the ellipse and the point to align with axis, rescaling in each
773      * direction, then computing distance to origin.
774      */
775     public boolean isInside(java.awt.geom.Point2D point) {
776         AffineTransform2D rot = AffineTransform2D.createRotation(this.xc,
777                 this.yc, -this.theta);
778         Point2D pt = rot.transform(point);
779         double xp = (pt.getX()-this.xc)/this.r1;
780         double yp = (pt.getY()-this.yc)/this.r2;
781         return (xp*xp+yp*yp<1)^!direct;
782     }
783 
784     public double getSignedDistance(java.awt.geom.Point2D point) {
785         Vector2D vector = this.getProjectedVector(point, 1e-10);
786         if (isInside(point))
787             return -vector.getNorm();
788         else
789             return vector.getNorm();
790     }
791 
792     public double getSignedDistance(double x, double y) {
793         return getSignedDistance(new Point2D(x, y));
794     }
795 
796     // ===================================================================
797     // methods of SmoothCurve2D interface
798 
799     public Vector2D getTangent(double t) {
800         if (!direct)
801             t = -t;
802         double cot = Math.cos(theta);
803         double sit = Math.sin(theta);
804 
805         if (direct)
806             return new Vector2D(
807                     -r1*Math.sin(t)*cot-r2*Math.cos(t)*sit,
808                     -r1*Math.sin(t)*sit+r2*Math.cos(t)*cot);
809         else
810             return new Vector2D(
811                     r1*Math.sin(t)*cot+r2*Math.cos(t)*sit,
812                     r1*Math.sin(t)*sit-r2*Math.cos(t)*cot);
813     }
814 
815     /**
816      * Returns the curvature of the ellipse.
817      */
818     public double getCurvature(double t) {
819         if (!direct)
820             t = -t;
821         double cot = Math.cos(t);
822         double sit = Math.sin(t);
823         double k = r1*r2/Math.pow(r2*r2*cot*cot+r1*r1*sit*sit, 1.5);
824         return direct ? k : -k;
825     }
826 
827     // ===================================================================
828     // methods of ContinuousCurve2D interface
829 
830     /**
831      * Returns true, as an ellipse is always closed.
832      */
833     public boolean isClosed() {
834         return true;
835     }
836 
837     // ===================================================================
838     // methods of Curve2D interface
839 
840     /**
841      * Returns the parameter of the first point of the ellipse, set to 0.
842      */
843     public double getT0() {
844         return 0;
845     }
846 
847     /**
848      * Returns the parameter of the last point of the ellipse, set to 2*PI.
849      */
850     public double getT1() {
851         return 2*Math.PI;
852     }
853 
854     /**
855      * get the position of the curve from internal parametric representation,
856      * depending on the parameter t. This parameter is between the two limits 0
857      * and 2*Math.PI.
858      */
859     public Point2D getPoint(double t) {
860         if (!direct)
861             t = -t;
862         double cot = Math.cos(theta);
863         double sit = Math.sin(theta);
864         return new Point2D(xc+r1*Math.cos(t)*cot-r2*Math.sin(t)*sit, yc+r1
865                 *Math.cos(t)*sit+r2*Math.sin(t)*cot);
866     }
867 
868     /**
869      * Get the first point of the ellipse, which is the same as the last point.
870      * 
871      * @return the first point of the curve
872      */
873 	@Override
874     public Point2D getFirstPoint() {
875         return new Point2D(xc+r1*Math.cos(theta), yc+r1*Math.sin(theta));
876     }
877 
878     /**
879      * Get the last point of the ellipse, which is the same as the first point.
880      * 
881      * @return the last point of the curve.
882      */
883 	@Override
884     public Point2D getLastPoint() {
885         return new Point2D(xc+r1*Math.cos(theta), yc+r1*Math.sin(theta));
886     }
887 
888     public double getPosition(java.awt.geom.Point2D point) {
889         double xp = point.getX();
890         double yp = point.getY();
891 
892         // translate
893         xp = xp-this.xc;
894         yp = yp-this.yc;
895 
896         // rotate
897         double xp1 = xp*Math.cos(theta)+yp*Math.sin(theta);
898         double yp1 = -xp*Math.sin(theta)+yp*Math.cos(theta);
899         xp = xp1;
900         yp = yp1;
901 
902         // scale
903         xp = xp/this.r1;
904         yp = yp/this.r2;
905 
906         if (!direct)
907             yp = -yp;
908 
909         // compute angle
910         double angle = Angle2D.getHorizontalAngle(xp, yp);
911 
912         if (Math.abs(Math.hypot(xp, yp)-1)<Shape2D.ACCURACY)
913             return angle;
914         else
915             return Double.NaN;
916     }
917 
918     /**
919      * Computes the approximate projection position of the point on the ellipse.
920      * The ellipse is first converted to a unit circle, then the angular
921      * position of the point is computed in the transformed basis.
922      */
923     public double project(java.awt.geom.Point2D point) {
924         double xp = point.getX();
925         double yp = point.getY();
926 
927         // translate
928         xp = xp-this.xc;
929         yp = yp-this.yc;
930 
931         // rotate
932         double xp1 = xp*Math.cos(theta)+yp*Math.sin(theta);
933         double yp1 = -xp*Math.sin(theta)+yp*Math.cos(theta);
934         xp = xp1;
935         yp = yp1;
936 
937         // scale
938         xp = xp/this.r1;
939         yp = yp/this.r2;
940 
941         // compute angle
942         double angle = Angle2D.getHorizontalAngle(xp, yp);
943 
944         return angle;
945     }
946 
947     /**
948      * Returns the ellipse with same center and same radius, but with the other
949      * orientation.
950      */
951     public Ellipse2D getReverseCurve() {
952         return new Ellipse2D(xc, yc, r1, r2, theta, !direct);
953     }
954 
955 	@Override
956     public Collection<? extends Ellipse2D> getContinuousCurves() {
957     	return wrapCurve(this);
958     }
959 
960     /**
961      * return a new EllipseArc2D.
962      */
963     public EllipseArc2D getSubCurve(double t0, double t1) {
964         double startAngle, extent;
965         if (this.direct) {
966             startAngle = t0;
967             extent = Angle2D.formatAngle(t1-t0);
968         } else {
969             extent = -Angle2D.formatAngle(t1-t0);
970             startAngle = Angle2D.formatAngle(-t0);
971         }
972         return new EllipseArc2D(this, startAngle, extent);
973     }
974 
975     // ===================================================================
976     // methods of Shape2D interface
977 
978     /** Always returns true, because an ellipse is bounded. */
979     public boolean isBounded() {
980         return true;
981     }
982 
983     public boolean isEmpty() {
984         return false;
985     }
986 
987     public double getDistance(java.awt.geom.Point2D point) {
988         // PolarVector2D vector = this.getProjectedVector(point, 1e-10);
989         // return Math.abs(vector.getRho());
990         return this.getAsPolyline(128).getDistance(point);
991     }
992 
993     public double getDistance(double x, double y) {
994         return getDistance(new Point2D(x, y));
995     }
996 
997     /**
998      * Clip the ellipse by a box. The result is an instance of CurveSet2D<ContinuousOrientedCurve2D>,
999      * which contains only instances of Ellipse2D or EllipseArc2D. If the
1000      * ellipse is not clipped, the result is an instance of CurveSet2D<ContinuousOrientedCurve2D>
1001      * which contains 0 curves.
1002      */
1003     public CurveSet2D<? extends SmoothOrientedCurve2D> clip(Box2D box) {
1004         // Clip the curve
1005         CurveSet2D<SmoothCurve2D> set = Curve2DUtils.clipSmoothCurve(this, box);
1006 
1007         // Stores the result in appropriate structure
1008         CurveArray2D<SmoothOrientedCurve2D> result = 
1009         	new CurveArray2D<SmoothOrientedCurve2D>(set.getCurveNumber());
1010 
1011         // convert the result
1012         for (Curve2D curve : set.getCurves()) {
1013             if (curve instanceof EllipseArc2D)
1014                 result.addCurve((EllipseArc2D) curve);
1015             if (curve instanceof Ellipse2D)
1016                 result.addCurve((Ellipse2D) curve);
1017         }
1018         return result;
1019     }
1020 
1021     /**
1022      * Return more precise bounds for the ellipse. Return an instance of Box2D.
1023      */
1024     public Box2D getBoundingBox() {
1025         // we consider the two parametric equations x(t) and y(t). From the
1026         // ellipse
1027         // definition, x(t)=r1*cos(t), y(t)=r2*sin(t), and the result is moved
1028         // (rotated with angle theta, and translated with (xc,yc) ).
1029         // Each equation can then be written in the form : x(t) =
1030         // Xm*cos(t+theta_X).
1031         // We compute Xm and Ym, and use it to calculate bounds.
1032         double cot = Math.cos(theta);
1033         double sit = Math.sin(theta);
1034         double xm = Math.sqrt(r1*r1*cot*cot+r2*r2*sit*sit);
1035         double ym = Math.sqrt(r1*r1*sit*sit+r2*r2*cot*cot);
1036         return new Box2D(xc-xm, xc+xm, yc-ym, yc+ym);
1037     }
1038 
1039     /**
1040      * Compute intersections of the ellipse with a straight object (line, line
1041      * segment, ray...).
1042      * <p>
1043      * Principle of the algorithm is to transform line and ellipse such that
1044      * ellipse becomes a circle, then using the intersections computation from
1045      * circle.
1046      */
1047     public Collection<Point2D> getIntersections(LinearShape2D line) {
1048         // Compute the transform2D which transforms ellipse into unit circle
1049         AffineTransform2D sca, rot, tra;
1050         sca = AffineTransform2D.createScaling(r1, r2);
1051         rot = AffineTransform2D.createRotation(theta);
1052         tra = AffineTransform2D.createTranslation(xc, yc);
1053         AffineTransform2D toUnit = sca.chain(rot).chain(tra).invert();
1054 
1055         // transform the line accordingly
1056         LinearShape2D line2 = line.transform(toUnit);
1057 
1058         // The list of intersections
1059         Collection<Point2D> points;
1060 
1061         // Compute intersection points with circle
1062         Circle2D circle = new Circle2D(0, 0, 1);
1063         points = circle.getIntersections(line2);
1064         if (points.size()==0)
1065             return points;
1066 
1067         // convert points on circle as angles
1068         ArrayList<Point2D> res = new ArrayList<Point2D>(points.size());
1069         for (Point2D point : points)
1070             res.add(this.getPoint(circle.getPosition(point)));
1071 
1072         // return the result
1073         return res;
1074     }
1075 
1076     /**
1077      * Transforms this ellipse by an affine transform. If the transformed shape
1078      * is a circle (ellipse with equal axis lengths), returns an instance of
1079      * Circle2D. The resulting ellipse is direct if this ellipse and the
1080      * transform are either both direct or both indirect.
1081      */
1082     public Ellipse2D transform(AffineTransform2D trans) {
1083         Ellipse2D result = Ellipse2D.transformCentered(this, trans);
1084         result.setCenter(this.getCenter().transform(trans));
1085         result.direct = !(this.direct^trans.isDirect());
1086         return result;
1087     }
1088 
1089     // ===================================================================
1090     // methods implementing the Shape interface
1091 
1092     /**
1093      * Return true if the point p lies on the ellipse, with precision given by
1094      * Shape2D.ACCURACY.
1095      */
1096     public boolean contains(java.awt.geom.Point2D p) {
1097         return contains(p.getX(), p.getY());
1098     }
1099 
1100     /**
1101      * Return true if the point (x, y) lies on the ellipse, with precision given
1102      * by Shape2D.ACCURACY.
1103      */
1104     public boolean contains(double x, double y) {
1105         return this.getDistance(x, y)<Shape2D.ACCURACY;
1106     }
1107 
1108     public java.awt.geom.GeneralPath getGeneralPath() {
1109         // precompute cosine and sine of angle
1110         double cot = Math.cos(theta);
1111         double sit = Math.sin(theta);
1112         
1113         // create new path
1114         java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
1115         
1116         // move to the first point
1117         path.moveTo((float) (xc+r1*cot), (float) (yc+r1*sit));
1118 
1119         // return path after adding curve
1120         return this.appendPath(path);
1121     }
1122     
1123     /**
1124      * Add the path of the ellipse to the given path.
1125      * 
1126      * @param path the path to be completed
1127      * @return the completed path
1128      */
1129     public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
1130         double cot = Math.cos(theta);
1131         double sit = Math.sin(theta);
1132 
1133         // draw each line of the boundary
1134         if (direct)
1135             for (double t = .1; t<=2*Math.PI; t += .1)
1136                 path.lineTo((float) (xc+r1*Math.cos(t)*cot-r2*Math.sin(t)*sit),
1137                         (float) (yc+r2*Math.sin(t)*cot+r1*Math.cos(t)*sit));
1138         else
1139             for (double t = .1; t<=2*Math.PI; t += .1)
1140                 path.lineTo((float) (xc+r1*Math.cos(t)*cot+r2*Math.sin(t)*sit),
1141                         (float) (yc-r2*Math.sin(t)*cot+r1*Math.cos(t)*sit));
1142 
1143         // loop to the first/last point
1144         path.lineTo((float) (xc+r1*cot), (float) (yc+r1*sit));
1145 
1146         return path;
1147     }
1148 
1149 	@Override
1150     public void draw(Graphics2D g2) {
1151         java.awt.geom.Ellipse2D.Double ellipse = 
1152             new java.awt.geom.Ellipse2D.Double(xc-r1, yc-r2, 2*r1, 2*r2);
1153         java.awt.geom.AffineTransform trans = 
1154             java.awt.geom.AffineTransform.getRotateInstance(theta, xc, yc);
1155         g2.draw(trans.createTransformedShape(ellipse));
1156     }
1157 
1158     // ===================================================================
1159     // methods of Object superclass
1160 
1161     @Override
1162     public boolean equals(Object obj) {
1163         if (!(obj instanceof Ellipse2D))
1164             return false;
1165 
1166         Ellipse2D ell = (Ellipse2D) obj;
1167 
1168         if (!ell.getCenter().equals(this.getCenter()))
1169             return false;
1170         if (Math.abs(ell.r1-this.r1)>Shape2D.ACCURACY)
1171             return false;
1172         if (Math.abs(ell.r2-this.r2)>Shape2D.ACCURACY)
1173             return false;
1174         if (Math.abs(Angle2D.formatAngle(ell.getAngle()-this.getAngle()))>Shape2D.ACCURACY)
1175             return false;
1176         if (ell.isDirect()!=this.isDirect())
1177             return false;
1178         return true;
1179     }
1180 
1181     @Override
1182     public Ellipse2D clone() {
1183         return new Ellipse2D(xc, yc, r1, r2, theta, direct);
1184     }
1185     
1186     @Override
1187     public String toString() {
1188         return String.format("Ellipse2D(%f,%f,%f,%f,%f,%s)", 
1189                 xc, yc, r1, r2, theta, direct?"true":"false");
1190     }
1191 
1192     // /**
1193     // * A class to compute shortest distance of a point to an ellipse.
1194     // * @author dlegland
1195     // */
1196     // private class Ellipsoidic {
1197     // /** angle of the line joining current point to ref point.*/
1198     // public final double lambda;
1199     //		
1200     // /** normal angle of ellipse at the cuurent point */
1201     // public final double phi;
1202     //		
1203     // /** shortest signed distance of the point to the ellipse
1204     // * (negative if inside ellipse). */
1205     // public final double h;
1206     //		
1207     // public Ellipsoidic (double lambda, double phi, double h) {
1208     // this.lambda = lambda;
1209     // this.phi = phi;
1210     // this.h = h;
1211     // }
1212     // }
1213 }