View Javadoc

1   /**
2    * 
3    */
4   
5   package math.geom2d.conic;
6   
7   import math.geom2d.AffineTransform2D;
8   import math.geom2d.Angle2D;
9   import math.geom2d.Point2D;
10  import math.geom2d.Shape2D;
11  import math.geom2d.domain.BoundarySet2D;
12  import math.geom2d.line.StraightLine2D;
13  
14  /**
15   * Generic class providing utilities for manipulating conics. Provides in
16   * particular methods for reducing a conic.
17   * 
18   * @author dlegland
19   */
20  public class Conic2DUtils {
21  
22      public final static Conic2D reduceConic(double[] coefs) {
23          if (coefs.length<6) {
24              System.err.println(
25              		"Conic2DUtils.reduceConic: must provide 6 coefficients");
26              return null;
27          }
28          boolean debug = false;
29  
30          // precision for tests
31          double eps = Shape2D.ACCURACY;
32  
33          // Extract coefficients
34          double a = coefs[0];
35          double b = coefs[1];
36          double c = coefs[2];
37          double d = coefs[3];
38          double e = coefs[4];
39          double f = coefs[5];
40  
41          // Transform the generic conic into a conic symmetric with respect to
42          // one of the basis axes.
43          // This results in fixing the coefficient b to 0.
44  
45          // coefficients of transformed conic;
46          double a1, b1, c1, d1, e1, f1;
47  
48          double theta0 = 0;
49          // Check if b is zero
50          if (Math.abs(b)<eps) {
51              // Simply keep the same coefficients
52              a1 = a;
53              b1 = b;
54              c1 = c;
55              d1 = d;
56              e1 = e;
57              f1 = f;
58              theta0 = 0;
59          } else {
60              // determine rotation angle (between 0 and PI/2).
61              if (Math.abs(a-c)<eps)
62                  theta0 = Math.PI/4; // conic symmetric wrt diagonal
63              else
64                  theta0 = Angle2D.formatAngle(Math.atan2(b, a-c)/2);
65              
66              if(debug)
67              	System.out.println("conic main angle: " + 
68              			Math.toDegrees(theta0));
69  
70              // computation shortcuts
71              double cot = Math.cos(theta0);
72              double sit = Math.sin(theta0);
73              double co2t = Math.cos(2*theta0);
74              double si2t = Math.sin(2*theta0);
75              double cot2 = cot*cot;
76              double sit2 = sit*sit;
77  
78              // Compute coefficients of the conic rotated around origin
79              a1 = a*cot2+b*sit*cot+c*sit2;
80              b1 = si2t*(c-a)+b*co2t; // should be equal to zero
81              c1 = a*sit2-b*sit*cot+c*cot2;
82              d1 = d*cot+e*sit;
83              e1 = -d*sit+e*cot;
84              f1 = f;
85          }
86  
87          // small control on the value of b1
88          if (Math.abs(b1)>eps) {
89              System.err.println(
90              		"Conic2DUtils.reduceConic: " + 
91              		"conic was not correctly transformed");
92              return null;
93          }
94  
95          // Test degenerate cases
96          if (Math.abs(a)<eps&&Math.abs(c)<eps) {
97              if (Math.abs(d)>eps||Math.abs(e)>eps)
98                  return new ConicStraightLine2D(d, e, f);
99              else
100                 return null;
101         }
102 
103         // Case of a parabola
104         if (Math.abs(a1)<eps) {
105             // case of a1 close to 0 -> parabola parallel to horizontal axis
106             if (debug)
107                 System.out.println("horizontal parabola");
108 
109             // Check degenerate case d=0
110             if (Math.abs(d1)<eps) {
111                 double delta = e1*e1-4*c1*f1;
112                 if (delta>=0) {
113                     // find the 2 roots
114                     double ys = -e1/2.0/c1;
115                     double dist = Math.sqrt(delta)/2.0/c1;
116                     Point2D center = new Point2D(0, ys).transform(
117                             AffineTransform2D.createRotation(theta0));
118                     return new ConicTwoLines2D(center, dist, theta0);
119                 } else
120                     return null;
121             }
122 
123             // compute reduced coefficients
124             double c2 = -c1/d1;
125             double e2 = -e1/d1;
126             double f2 = -f1/d1;
127 
128             // vertex of the parabola
129             double xs = -(e2*e2-4*c2*f2)/(4*c2);
130             double ys = -e2*.5/c2;
131             
132             // create and return result
133             return new Parabola2D(xs, ys, c2, theta0-Math.PI/2);
134 
135         } else if (Math.abs(c1)<eps) {
136             // Case of c1 close to 0 -> parabola parallel to vertical axis
137             if (debug)
138                 System.out.println("vertical parabola");
139 
140             // Check degenerate case d=0
141             if (Math.abs(e1)<eps) {
142                 double delta = d1*d1-4*a1*f1;
143                 if (delta>=0) {
144                     // find the 2 roots
145                     double xs = -d1/2.0/a1;
146                     double dist = Math.sqrt(delta)/2.0/a1;
147                     Point2D center = new Point2D(0, xs).transform(
148                     		AffineTransform2D.createRotation(theta0));
149                     return new ConicTwoLines2D(center, dist, theta0);
150                 } else
151                     return null;
152             }
153 
154             // compute reduced coefficients
155             double a2 = -a1/e1;
156             double d2 = -d1/e1;
157             double f2 = -f1/e1;
158 
159             // vertex of parabola
160             double xs = -d2*.5/a2;
161             double ys = -(d2*d2-4*a2*f2)/(4*a2);
162             
163             // create and return result
164             return new Parabola2D(xs, ys, a2, theta0);
165         }
166 
167         // Remaining cases: ellipse or hyperbola
168 
169         // compute coordinate of conic center
170         Point2D center = new Point2D(-d1/(2*a1), -e1/(2*c1));
171         center = center.transform(AffineTransform2D.createRotation(theta0));
172 
173         // length of semi axes
174         double num = (c1*d1*d1+a1*e1*e1-4*a1*c1*f1)/(4*a1*c1);
175         double at = num/a1;
176         double bt = num/c1;
177 
178         if (at<0&&bt<0) {
179             System.err.println(
180             		"Conic2DUtils.reduceConic(): found A<0 and C<0");
181             return null;
182         }
183 
184         // Case of an ellipse
185         if (at>0&&bt>0) {
186             if (debug)
187                 System.out.println("ellipse");
188             if (at>bt)
189                 return new Ellipse2D(center, Math.sqrt(at), Math.sqrt(bt),
190                         theta0);
191             else
192                 return new Ellipse2D(center, Math.sqrt(bt), Math.sqrt(at),
193                         Angle2D.formatAngle(theta0+Math.PI/2));
194         }
195 
196         // remaining case is the hyperbola
197 
198         // Case of east-west hyperbola
199         if (at>0) {
200             if (debug)
201                 System.out.println("east-west hyperbola");
202             return new Hyperbola2D(center, Math.sqrt(at), Math.sqrt(-bt),
203                     theta0);
204         } else {
205             if (debug)
206                 System.out.println("north-south hyperbola");
207             return new Hyperbola2D(center, Math.sqrt(bt), Math.sqrt(-at),
208                     theta0+Math.PI/2);
209         }
210     }
211 
212     /**
213      * Transforms a conic centered around the origin, by dropping the
214      * translation part of the transform. The array must be contains at least
215      * 3 elements. If it contains 6 elements, the 3 remaining elements are
216      * supposed to be 0, 0, and -1 in that order.
217      * 
218      * @param coefs an array of double with at least 3 coefficients
219      * @param trans an affine transform
220      * @return an array of double with as many elements as the input array
221      */
222     public final static double[] transformCentered(double[] coefs,
223             AffineTransform2D trans) {
224         // Extract transform coefficients
225         double[][] mat = trans.getAffineMatrix();
226         double a = mat[0][0];
227         double b = mat[1][0];
228         double c = mat[0][1];
229         double d = mat[1][1];
230 
231         // Extract first conic coefficients
232         double A = coefs[0];
233         double B = coefs[1];
234         double C = coefs[2];
235 
236         // compute matrix determinant
237         double delta = a*d-b*c;
238         delta = delta*delta;
239 
240         double A2 = (A*d*d+C*b*b-B*b*d)/delta;
241         double B2 = (B*(a*d+b*c)-2*(A*c*d+C*a*b))/delta;
242         double C2 = (A*c*c+C*a*a-B*a*c)/delta;
243 
244         // return only 3 parameters if needed
245         if (coefs.length==3)
246             return new double[] { A2, B2, C2 };
247 
248         // Compute other coefficients
249         double D = coefs[3];
250         double E = coefs[4];
251         double F = coefs[5];
252         double D2 = D*d-E*b;
253         double E2 = E*a-D*c;
254         return new double[] { A2, B2, C2, D2, E2, F };
255     }
256 
257     /**
258      * Transforms a conic by an affine transform.
259      * 
260      * @param coefs an array of double with 6 coefficients
261      * @param trans an affine transform
262      * @return the coefficients of the transformed conic
263      */
264     public final static double[] transform(double[] coefs,
265             AffineTransform2D trans) {
266         // Extract coefficients of the inverse transform
267         double[][] mat = trans.invert().getAffineMatrix();
268         double a = mat[0][0];
269         double b = mat[1][0];
270         double c = mat[0][1];
271         double d = mat[1][1];
272         double e = mat[0][2];
273         double f = mat[1][2];
274 
275         // Extract conic coefficients
276         double A = coefs[0];
277         double B = coefs[1];
278         double C = coefs[2];
279         double D = coefs[3];
280         double E = coefs[4];
281         double F = coefs[5];
282 
283         // Compute coefficients of the transformed conic
284         double A2 = A*a*a+B*a*b+C*b*b;
285         double B2 = 2*(A*a*c+C*b*d)+B*(a*d+b*c);
286         double C2 = A*c*c+B*c*d+C*d*d;
287         double D2 = 2*(A*a*e+C*b*f)+B*(a*f+b*e)+D*a+E*b;
288         double E2 = 2*(A*c*e+C*d*f)+B*(c*f+d*e)+D*c+E*d;
289         double F2 = A*e*e+B*e*f+C*f*f+D*e+E*f+F;
290 
291         // Return the array of coefficients
292         return new double[] { A2, B2, C2, D2, E2, F2 };
293     }
294 
295     // -----------------------------------------------------------------
296     // Some special conics
297 
298     static class ConicStraightLine2D extends StraightLine2D implements Conic2D {
299 
300         double[] coefs = new double[] { 0, 0, 0, 1, 0, 0 };
301 
302         public ConicStraightLine2D(StraightLine2D line) {
303             super(line);
304             coefs = new double[] { 0, 0, 0, dy, -dx, dx*y0-dy*x0 };
305         }
306 
307         public ConicStraightLine2D(double a, double b, double c) {
308             super(StraightLine2D.createCartesian(a, b, c));
309             coefs = new double[] { 0, 0, 0, a, b, c };
310         }
311 
312         public double[] getConicCoefficients() {
313             return coefs;
314         }
315 
316         public Type getConicType() {
317             return Conic2D.Type.STRAIGHT_LINE;
318         }
319 
320         /** Return NaN. */
321         public double getEccentricity() {
322             return Double.NaN;
323         }
324 
325         @Override
326         public ConicStraightLine2D getReverseCurve() {
327             return new ConicStraightLine2D(super.getReverseCurve());
328         }
329 
330         @Override
331         public ConicStraightLine2D transform(AffineTransform2D trans) {
332             return new ConicStraightLine2D(super.transform(trans));
333         }
334     }
335 
336     static class ConicTwoLines2D extends BoundarySet2D<StraightLine2D>
337             implements Conic2D {
338 
339         double xc = 0, yc = 0, d = 1, theta = 0;
340 
341         public ConicTwoLines2D(Point2D point, double d, double theta) {
342             this(point.x, point.y, d, theta);
343         }
344 
345         public ConicTwoLines2D(double xc, double yc, double d, double theta) {
346             super();
347 
348             this.xc = xc;
349             this.yc = yc;
350             this.d = d;
351             this.theta = theta;
352 
353             StraightLine2D baseLine = StraightLine2D.create(
354                     new Point2D(xc, yc), theta);
355             this.addCurve(baseLine.getParallel(d));
356             this.addCurve(baseLine.getParallel(-d).getReverseCurve());
357         }
358 
359         public double[] getConicCoefficients() {
360             double[] coefs = { 0, 0, 1, 0, 0, -1 };
361             AffineTransform2D sca = AffineTransform2D.createScaling(0, d), rot = AffineTransform2D
362                     .createRotation(theta), tra = AffineTransform2D
363                     .createTranslation(xc, yc);
364             // AffineTransform2D trans = tra.compose(rot).compose(sca);
365             AffineTransform2D trans = sca.chain(rot).chain(tra);
366             return Conic2DUtils.transform(coefs, trans);
367         }
368 
369         public Type getConicType() {
370             return Conic2D.Type.TWO_LINES;
371         }
372 
373         public double getEccentricity() {
374             return Double.NaN;
375         }
376 
377         @Override
378         public ConicTwoLines2D transform(AffineTransform2D trans) {
379             Point2D center = new Point2D(xc, yc).transform(trans);
380             StraightLine2D line = this.getFirstCurve().transform(trans);
381 
382             return new ConicTwoLines2D(center, line.getDistance(center), line
383                     .getHorizontalAngle());
384         }
385 
386         @Override
387         public ConicTwoLines2D getReverseCurve() {
388             return new ConicTwoLines2D(xc, yc, -d, theta);
389         }
390     }
391 
392     // TODO: add CrossConic2D
393 }