View Javadoc

1   /* file : Parabola2D.java
2    * 
3    * Project : geometry
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   * Created on 29 janv. 2007
24   *
25   */
26  
27  package math.geom2d.conic;
28  
29  import java.awt.Graphics2D;
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.UnboundedShapeException;
39  import math.geom2d.Vector2D;
40  import math.geom2d.curve.AbstractSmoothCurve2D;
41  import math.geom2d.curve.Curve2D;
42  import math.geom2d.curve.Curve2DUtils;
43  import math.geom2d.curve.CurveArray2D;
44  import math.geom2d.curve.CurveSet2D;
45  import math.geom2d.curve.SmoothCurve2D;
46  import math.geom2d.domain.ContinuousBoundary2D;
47  import math.geom2d.domain.Domain2D;
48  import math.geom2d.domain.GenericDomain2D;
49  import math.geom2d.line.LinearShape2D;
50  import math.geom2d.line.StraightLine2D;
51  
52  /**
53   * A parabola, defined by its vertex, its orientation, and its pedal.
54   * Orientation is defined as the orientation of derivative at vertex point, with
55   * the second derivative pointing to the top.
56   * <p>
57   * Following parametric representation is used:
58   * <p>
59   * <code>x(t)=t </code>
60   * <p>
61   * <code>y(t)=a*t^2</code>
62   * <p>
63   * This is a signed parameter (negative a makes the parabola point to opposite
64   * side).
65   * 
66   * @author dlegland
67   */
68  public class Parabola2D extends AbstractSmoothCurve2D 
69  implements ContinuousBoundary2D, Conic2D, Cloneable {
70  
71      /** Coordinate of the vertex */
72      protected double xv    = 0, yv = 0;
73  
74      /** orientation of the parabola */
75      protected double theta = 0;
76  
77      /** The parameter of the parabola. If positive, the parabola is direct. */
78      protected double a     = 1;
79  
80      private boolean  debug = false;
81  
82      /**
83       * Creates a parabola by supplying the vertex and the focus.
84       * 
85       * @param vertex the vertex point of the parabola
86       * @param focus the focal point of the parabola
87       * @return the parabola with given vertex and focus
88       */
89      public final static Parabola2D create(Point2D vertex, Point2D focus) {
90          double p = Point2D.getDistance(vertex, focus);
91          double theta = Angle2D.getHorizontalAngle(vertex, focus)-Math.PI/2;
92          return new Parabola2D(vertex, 1/(4*p), theta);
93      }
94  
95      public Parabola2D() {
96          super();
97      }
98  
99      public Parabola2D(Point2D vertex, double a, double theta) {
100         this(vertex.getX(), vertex.getY(), a, theta);
101     }
102 
103     public Parabola2D(double xv, double yv, double a, double theta) {
104         super();
105         this.xv = xv;
106         this.yv = yv;
107         this.a = a;
108         this.theta = theta;
109     }
110 
111     // ==========================================================
112     // methods specific to Parabola2D
113 
114     /**
115      * Returns the focus of the parabola.
116      */
117     public Point2D getFocus() {
118         double c = 1/a/4.0;
119         return new Point2D(xv-c*Math.sin(theta), yv+c*Math.cos(theta));
120     }
121 
122     public double getParameter() {
123         return a;
124     }
125 
126     public double getFocusDistance() {
127         return 1.0/(4*a);
128     }
129 
130     public Point2D getVertex() {
131         return new Point2D(xv, yv);
132     }
133 
134     /**
135      * Returns the first direction vector of the parabola
136      */
137     public Vector2D getVector1() {
138         Vector2D vect = new Vector2D(1, 0);
139         return vect.transform(AffineTransform2D.createRotation(theta));
140     }
141 
142     /**
143      * Returns the second direction vector of the parabola.
144      */
145     public Vector2D getVector2() {
146         Vector2D vect = new Vector2D(1, 0);
147         return vect
148                 .transform(AffineTransform2D.createRotation(theta+Math.PI/2));
149     }
150 
151     /**
152      * Returns orientation angle of parabola. It is defined as the angle of the
153      * derivative at the vertex.
154      */
155     public double getAngle() {
156         return theta;
157     }
158 
159     /**
160      * Returns true if the parameter a is positive.
161      */
162     public boolean isDirect() {
163         return a>0;
164     }
165 
166     /**
167      * Changes coordinate of the point to correspond to a standard parabola.
168      * Standard parabola s such that y=x^2 for every point of the parabola.
169      * 
170      * @param point
171      * @return
172      */
173     private Point2D formatPoint(java.awt.geom.Point2D point) {
174         Point2D p2 = new Point2D(point);
175         p2 = p2.transform(AffineTransform2D.createTranslation(-xv, -yv));
176         p2 = p2.transform(AffineTransform2D.createRotation(-theta));
177         p2 = p2.transform(AffineTransform2D.createScaling(1, 1.0/a));
178         return p2;
179     }
180 
181     /**
182      * Changes coordinate of the line to correspond to a standard parabola.
183      * Standard parabola s such that y=x^2 for every point of the parabola.
184      * 
185      * @param point
186      * @return
187      */
188     private LinearShape2D formatLine(LinearShape2D line) {
189         line = line.transform(AffineTransform2D.createTranslation(-xv, -yv));
190         line = line.transform(AffineTransform2D.createRotation(-theta));
191         line = line.transform(AffineTransform2D.createScaling(1, 1.0/a));
192         return line;
193     }
194 
195     // ==========================================================
196     // methods implementing the Conic2D interface
197 
198     public Conic2D.Type getConicType() {
199         return Conic2D.Type.PARABOLA;
200     }
201 
202     public double[] getConicCoefficients() {
203 //        // computation shortcuts
204 //        double cot = Math.cos(theta);
205 //        double sit = Math.sin(theta);
206 //        double cot2 = cot*cot;
207 //        double sit2 = sit*sit;
208 //        // Compute new coefficients after rotation of parabola located at
209 //        // (xv,yv) by a rotation of angle theta around origin.
210 //        return new double[] {
211 //        		a*cot2, -2*a*cot*sit, a*sit2, 
212 //        		-2*a*xv*cot-sit, 2*a*xv*sit-cot, a*xv*xv+yv };
213 
214     	// The transformation matrix from base parabola y=x^2
215     	AffineTransform2D transform =
216     		AffineTransform2D.createRotation(theta).chain(
217     				AffineTransform2D.createTranslation(xv, yv));
218         	
219     	// Extract coefficients of inverse transform
220         double[][] coefs = transform.invert().getAffineMatrix();
221         double m00 = coefs[0][0];
222         double m01 = coefs[0][1];
223         double m02 = coefs[0][2];
224         double m10 = coefs[1][0];
225         double m11 = coefs[1][1];
226         double m12 = coefs[1][2];
227         
228         // Default conic coefficients are A=a, F=1.
229         // Compute result of transformed coefficients, which simplifies in:
230         double A = a*m00*m00;
231         double B = 2*a*m00*m01;
232         double C = a*m01*m01;
233         double D = 2*a*m00*m02 - m10;
234         double E = 2*a*m01*m02 - m11;
235         double F = a*m02*m02 - m12;
236         
237         // arrange into array
238         return new double[]{A, B, C, D, E, F};
239     }
240 
241     /**
242      * Return 1, by definition for a parabola.
243      */
244     public double getEccentricity() {
245         return 1.0;
246     }
247 
248     // ==========================================================
249     // methods implementing the Boundary2D interface
250 
251     public Collection<ContinuousBoundary2D> getBoundaryCurves() {
252         ArrayList<ContinuousBoundary2D> list = new ArrayList<ContinuousBoundary2D>(
253                 1);
254         list.add(this);
255         return list;
256     }
257 
258     public Domain2D getDomain() {
259         return new GenericDomain2D(this);
260     }
261 
262     // ==========================================================
263     // methods implementing the OrientedCurve2D interface
264 
265     public double getWindingAngle(java.awt.geom.Point2D point) {
266         if (isDirect()) {
267             if (isInside(point))
268                 return Math.PI*2;
269             else
270                 return 0.0;
271         } else {
272             if (isInside(point))
273                 return 0.0;
274             else
275                 return -Math.PI*2;
276         }
277     }
278 
279     public double getSignedDistance(java.awt.geom.Point2D p) {
280         return getSignedDistance(p.getX(), p.getY());
281     }
282 
283     public double getSignedDistance(double x, double y) {
284         if (isInside(new Point2D(x, y)))
285             return -getDistance(x, y);
286         return -getDistance(x, y);
287     }
288 
289     public boolean isInside(java.awt.geom.Point2D point) {
290         // Process the point to be in a referentiel such that parabola is
291         // vertical
292         Point2D p2 = formatPoint(point);
293 
294         // get coordinate of transformed point
295         double x = p2.getX();
296         double y = p2.getY();
297 
298         // check condition of parabola
299         return y>x*x^a<0;
300     }
301 
302     // ==========================================================
303     // methods implementing the SmoothCurve2D interface
304 
305     public Vector2D getTangent(double t) {
306         Vector2D vect = new Vector2D(1, 2.0*a*t);
307         return vect.transform(AffineTransform2D.createRotation(theta));
308     }
309 
310     /**
311      * Returns the curvature of the parabola at the given position.
312      */
313     public double getCurvature(double t) {
314         return 2*a/Math.pow(Math.hypot(1, 2*a*t), 3);
315     }
316 
317     // ==========================================================
318     // methods implementing the ContinuousCurve2D interface
319 
320     /**
321      * Returns false, as a parabola is an open curve.
322      */
323     public boolean isClosed() {
324         return false;
325     }
326 
327     // ==========================================================
328     // methods implementing the Curve2D interface
329 
330     /**
331      * Returns the parameter of the first point of the line, which is always
332      * Double.NEGATIVE_INFINITY.
333      */
334     public double getT0() {
335         return Double.NEGATIVE_INFINITY;
336     }
337 
338     /**
339      * Returns the parameter of the last point of the line, which is always
340      * Double.POSITIVE_INFINITY.
341      */
342     public double getT1() {
343         return Double.POSITIVE_INFINITY;
344     }
345 
346     public Point2D getPoint(double t) {
347         Point2D point = new Point2D(t, a*t*t);
348         point = AffineTransform2D.createRotation(theta).transform(point);
349         point = AffineTransform2D.createTranslation(xv, yv).transform(point);
350         return point;
351     }
352 
353     /**
354      * Returns position of point on the parabola. If point is not on the
355      * parabola returns the positions on its "vertical" projection (i.e. its
356      * projection parallel to the symetry axis of the parabola).
357      */
358     public double getPosition(java.awt.geom.Point2D point) {
359         // t parameter is x-coordinate of point
360         return formatPoint(point).getX();
361     }
362 
363     /**
364      * Returns position of point on the parabola. If point is not on the
365      * parabola returns the positions on its "vertical" projection (i.e. its
366      * projection parallel to the symetry axis of the parabola).
367      */
368     public double project(java.awt.geom.Point2D point) {
369         // t parameter is x-coordinate of point
370         return formatPoint(point).getX();
371     }
372 
373     public Collection<Point2D> getIntersections(LinearShape2D line) {
374         // Computes the lines which corresponds to a "Unit" parabola.
375         LinearShape2D line2 = this.formatLine(line);
376         double dx = line2.getVector().getX();
377         double dy = line2.getVector().getY();
378 
379         ArrayList<Point2D> points = new ArrayList<Point2D>();
380 
381         // case of vertical or quasi-vertical line
382         if (Math.abs(dx)<Shape2D.ACCURACY) {
383             if (debug)
384                 System.out.println("intersect parabola with vertical line ");
385             double x = line2.getOrigin().getX();
386             Point2D point = new Point2D(x, x*x);
387             if (line2.contains(point))
388                 points.add(line.getPoint(line2.getPosition(point)));
389             return points;
390         }
391 
392         // Extract formatted line parameters
393         Point2D origin = line2.getOrigin();
394         double x0 = origin.getX();
395         double y0 = origin.getY();
396 
397         // Solve second order equation
398         double k = dy/dx; // slope of the line
399         double yl = k*x0-y0;
400         double delta = k*k-4*yl;
401 
402         // Case of a line 'below' the parabola
403         if (delta<0)
404             return points;
405 
406         // There are two intersections with supporting line,
407         // need to check these points belong to the line.
408 
409         double x;
410         Point2D point;
411         StraightLine2D support = line2.getSupportingLine();
412 
413         // test first intersection point
414         x = (k-Math.sqrt(delta))*.5;
415         point = new Point2D(x, x*x);
416         if (line2.contains(support.getProjectedPoint(point)))
417             points.add(line.getPoint(line2.getPosition(point)));
418 
419         // test second intersection point
420         x = (k+Math.sqrt(delta))*.5;
421         point = new Point2D(x, x*x);
422         if (line2.contains(support.getProjectedPoint(point)))
423             points.add(line.getPoint(line2.getPosition(point)));
424 
425         return points;
426     }
427 
428     /**
429      * Returns the parabola with same vertex, direction vector in opposite
430      * direction and opposite parameter <code>p</code>.
431      */
432     public Parabola2D getReverseCurve() {
433         return new Parabola2D(xv, yv, -a, Angle2D.formatAngle(theta+Math.PI));
434     }
435 
436     /**
437      * Returns a new ParabolaArc2D, or null if t1<t0.
438      */
439     public ParabolaArc2D getSubCurve(double t0, double t1) {
440         if (debug)
441             System.out.println("theta = "+Math.toDegrees(theta));
442         if (t1<t0)
443             return null;
444         return new ParabolaArc2D(this, t0, t1);
445     }
446 
447     public double getDistance(java.awt.geom.Point2D p) {
448         return getDistance(p.getX(), p.getY());
449     }
450 
451     public double getDistance(double x, double y) {
452         // TODO Computes on polyline approximation, needs to compute on whole
453         // curve
454         return new ParabolaArc2D(this, -100, 100).getDistance(x, y);
455     }
456 
457     // ===============================================
458     // Drawing methods (curve interface)
459 
460     /** Throws an infiniteShapeException */
461     public java.awt.geom.GeneralPath appendPath(
462     		java.awt.geom.GeneralPath path) {
463         throw new UnboundedShapeException(this);
464     }
465 
466     /** Throws an infiniteShapeException */
467     public void fill(Graphics2D g2) {
468         throw new UnboundedShapeException(this);
469     }
470 
471     // ===============================================
472     // methods implementing the Shape2D interface
473 
474     /** Always returns false, because a parabola is not bounded. */
475     public boolean isBounded() {
476         return false;
477     }
478 
479     /**
480      * Returns false, as a parabola is never empty.
481      */
482     public boolean isEmpty() {
483         return false;
484     }
485 
486     /**
487      * Clip the parabola by a box. The result is an instance of CurveSet2D<ParabolaArc2D>,
488      * which contains only instances of ParabolaArc2D. If the parabola is not
489      * clipped, the result is an instance of CurveSet2D<ParabolaArc2D> which
490      * contains 0 curves.
491      */
492     public CurveSet2D<ParabolaArc2D> clip(Box2D box) {
493         // Clip the curve
494         CurveSet2D<SmoothCurve2D> set = Curve2DUtils.clipSmoothCurve(this, box);
495 
496         // Stores the result in appropriate structure
497         CurveArray2D<ParabolaArc2D> result = 
498         	new CurveArray2D<ParabolaArc2D>(set.getCurveNumber());
499 
500         // convert the result
501         for (Curve2D curve : set.getCurves()) {
502             if (curve instanceof ParabolaArc2D)
503                 result.addCurve((ParabolaArc2D) curve);
504         }
505         return result;
506     }
507 
508     public Box2D getBoundingBox() {
509         // TODO: manage parabolas with horizontal or vertical orientations
510         return new Box2D(
511         		Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY,
512                 Double.NEGATIVE_INFINITY, Double.POSITIVE_INFINITY);
513     }
514 
515     /**
516      * Transforms the parabola by an affine transform. The transformed parabola
517      * is direct if this parabola and the affine transform are both either
518      * direct or indirect.
519      */
520     public Parabola2D transform(AffineTransform2D trans) {
521     	//TODO: check if transform work also for non-motion transforms...
522         Point2D vertex = this.getVertex().transform(trans);
523         Point2D focus = this.getFocus().transform(trans);
524         double a = 1/(4.0*Point2D.getDistance(vertex, focus));
525         double theta = Angle2D.getHorizontalAngle(vertex, focus)-Math.PI/2;
526 
527         // check orientation of resulting parabola
528         if (this.a<0^trans.isDirect())
529             // normal case
530             return new Parabola2D(vertex, a, theta);
531         else
532             // inverted case
533             return new Parabola2D(vertex, -a, theta+Math.PI);
534     }
535 
536     // ===============================================
537     // methods implementing the Shape interface
538 
539     public boolean contains(double x, double y) {
540         // Process the point to be in a basis such that parabola is vertical
541         Point2D p2 = formatPoint(new Point2D(x, y));
542 
543         // get coordinate of transformed point
544         double xp = p2.getX();
545         double yp = p2.getY();
546 
547         // check condition of parabola
548         return Math.abs(yp-xp*xp)<Shape2D.ACCURACY;
549     }
550 
551     public boolean contains(java.awt.geom.Point2D point) {
552         return contains(point.getX(), point.getY());
553     }
554 
555     // ====================================================================
556     // Methods inherited from the object class
557 
558     @Override
559     public String toString() {
560         return String.format("Parabola2D(%f,%f,%f,%f)", 
561                 xv, yv, a, theta);
562     }
563 
564     @Override
565     public boolean equals(Object obj) {
566         if (!(obj instanceof Parabola2D))
567             return false;
568         Parabola2D parabola = (Parabola2D) obj;
569 
570         if ((this.xv-parabola.xv)>Shape2D.ACCURACY) 
571             return false;
572         if ((this.yv-parabola.yv)>Shape2D.ACCURACY) 
573             return false;
574         if ((this.a-parabola.a)>Shape2D.ACCURACY)
575             return false;
576         if (!Angle2D.equals(this.theta, parabola.theta))
577             return false;
578 
579         return true;
580     }
581     
582     @Override
583     public Parabola2D clone() {
584         return new Parabola2D(xv, yv, a, theta);
585     }
586 }