View Javadoc

1   /* File QuadBezierCurve2D.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  
24  package math.geom2d.spline;
25  
26  import java.awt.geom.QuadCurve2D;
27  import java.util.Collection;
28  
29  import math.geom2d.AffineTransform2D;
30  import math.geom2d.Box2D;
31  import math.geom2d.Point2D;
32  import math.geom2d.Shape2D;
33  import math.geom2d.Vector2D;
34  import math.geom2d.curve.AbstractSmoothCurve2D;
35  import math.geom2d.curve.Curve2D;
36  import math.geom2d.curve.Curve2DUtils;
37  import math.geom2d.curve.CurveArray2D;
38  import math.geom2d.curve.CurveSet2D;
39  import math.geom2d.curve.SmoothCurve2D;
40  import math.geom2d.domain.ContinuousOrientedCurve2D;
41  import math.geom2d.line.LinearShape2D;
42  import math.geom2d.line.StraightLine2D;
43  
44  /**
45   * A quadratic bezier curve, defined by 3 points.
46   * 
47   * From javaGeom 0.8.0, this shape does not extends.
48   * java.awt.geom.QuadCurve2D.Double anymore
49   * 
50   * @author Legland
51   */
52  public class QuadBezierCurve2D extends AbstractSmoothCurve2D
53  implements SmoothCurve2D, ContinuousOrientedCurve2D, Cloneable {
54  
55  	protected double x1, y1;
56  	protected double ctrlx, ctrly;
57  	protected double x2, y2;
58  
59      // ===================================================================
60      // constructors
61  
62      public QuadBezierCurve2D() {
63          this(0, 0, 0, 0, 0, 0);
64      }
65  
66      /**
67       * Build a new Bezier curve from its array of coefficients. The array must
68       * have size 2*3.
69       * 
70       * @param coefs the coefficients of the QuadBezierCurve2D.
71       */
72      public QuadBezierCurve2D(double[][] coefs) {
73          this(coefs[0][0], coefs[1][0], coefs[0][0]+coefs[0][1]/2.0, coefs[1][0]
74                  +coefs[1][1]/2.0, coefs[0][0]+coefs[0][1]+coefs[0][2],
75                  coefs[1][0]+coefs[1][1]+coefs[1][2]);
76      }
77  
78      /**
79       * Build a new quadratic Bezier curve by specifying position of extreme
80       * points and position of control point. The resulting curve is totally
81       * contained in the convex polygon formed by the 3 control points.
82       * 
83       * @param p1 first point
84       * @param ctrl control point
85       * @param p2 last point
86       */
87      public QuadBezierCurve2D(java.awt.geom.Point2D p1, java.awt.geom.Point2D ctrl,
88              java.awt.geom.Point2D p2) {
89          this(p1.getX(), p1.getY(), ctrl.getX(), ctrl.getY(), p2.getX(), p2
90                  .getY());
91      }
92  
93      public QuadBezierCurve2D(java.awt.geom.Point2D[] pts) {
94          this(pts[0].getX(), pts[0].getY(), pts[1].getX(), pts[1].getY(), pts[2]
95                  .getX(), pts[2].getY());
96      }
97  
98      /**
99       * Build a new quadratic Bezier curve by specifying position of extreme
100      * points and position of control point. The resulting curve is totally
101      * contained in the convex polygon formed by the 3 control points.
102      */
103     public QuadBezierCurve2D(double x1, double y1, double xctrl, double yctrl,
104             double x2, double y2) {
105         this.x1 = x1;
106         this.y1 = y1;
107         this.ctrlx = xctrl;
108         this.ctrly = yctrl;
109         this.x2 = x2;
110         this.y2 = y2;
111     }
112 
113     // ===================================================================
114     // static methods
115    
116     /**
117      * Static factory for creating a new Quadratic Bezier curve from 3 points.
118      * @since 0.8.1
119      */
120     public static QuadBezierCurve2D create(Point2D p1, Point2D p2, Point2D p3) {
121     	return new QuadBezierCurve2D(p1, p2, p3);
122     }
123     
124 
125     // ===================================================================
126     // methods specific to QuadBezierCurve2D
127 
128     public Point2D getControl() {
129         return new Point2D(ctrlx, ctrly);
130     }
131 
132     public Point2D getP1() {
133     	return this.getFirstPoint();
134     }
135     
136     public Point2D getP2() {
137     	return this.getLastPoint();
138     }
139     
140     public Point2D getCtrl() {
141     	return this.getControl();
142     }
143     
144     /**
145      * Returns the matrix of parametric representation of the line. Result is a
146      * 2x3 array with coefficients:
147      * <p>
148      * <code>[ cx0  cx1 cx2] </code>
149      * <p>
150      * <code>[ cy0  cy1 cy2] </code>
151      * <p>
152      * Coefficients are from the parametric equation : <code>
153      * x(t) = cx0 + cx1*t + cx2*t^2 
154      * y(t) = cy0 + cy1*t + cy2*t^2
155      * </code>
156      */
157     public double[][] getParametric() {
158         double[][] tab = new double[2][3];
159         tab[0][0] = x1;
160         tab[0][1] = 2*ctrlx-2*x1;
161         tab[0][2] = x2-2*ctrlx+x1;
162 
163         tab[1][0] = y1;
164         tab[1][1] = 2*ctrly-2*y1;
165         tab[1][2] = y2-2*ctrly+y1;
166         return tab;
167     }
168 
169     // ===================================================================
170     // methods from OrientedCurve2D interface
171 
172     /**
173      * Use winding angle of approximated polyline
174      * 
175      * @see math.geom2d.domain.OrientedCurve2D#getWindingAngle(java.awt.geom.Point2D)
176      */
177     public double getWindingAngle(java.awt.geom.Point2D point) {
178         return this.getAsPolyline(100).getWindingAngle(point);
179     }
180 
181     /**
182      * return true if the point is 'inside' the domain bounded by the curve.
183      * Uses a polyline approximation.
184      * 
185      * @param pt a point in the plane
186      * @return true if the point is on the left side of the curve.
187      */
188     public boolean isInside(java.awt.geom.Point2D pt) {
189         return this.getAsPolyline(100).isInside(pt);
190     }
191 
192     public double getSignedDistance(java.awt.geom.Point2D point) {
193         if (isInside(point))
194             return -getDistance(point.getX(), point.getY());
195         else
196             return getDistance(point.getX(), point.getY());
197     }
198 
199     /**
200      * @see math.geom2d.domain.OrientedCurve2D#getSignedDistance(java.awt.geom.Point2D)
201      */
202     public double getSignedDistance(double x, double y) {
203         if (isInside(new Point2D(x, y)))
204             return -getDistance(x, y);
205         else
206             return getDistance(x, y);
207     }
208 
209     // ===================================================================
210     // methods from SmoothCurve2D interface
211 
212     public Vector2D getTangent(double t) {
213         double[][] c = getParametric();
214         double dx = c[0][1]+2*c[0][2]*t;
215         double dy = c[1][1]+2*c[1][2]*t;
216         return new Vector2D(dx, dy);
217     }
218 
219     /**
220      * returns the curvature of the Curve.
221      */
222     public double getCurvature(double t) {
223         double[][] c = getParametric();
224         double xp = c[0][1]+2*c[0][2]*t;
225         double yp = c[1][1]+2*c[1][2]*t;
226         double xs = 2*c[0][2];
227         double ys = 2*c[1][2];
228 
229         return (xp*ys-yp*xs)/Math.pow(Math.hypot(xp, yp), 3);
230     }
231 
232     // ===================================================================
233     // methods from ContinousCurve2D interface
234 
235     /**
236      * The cubic curve is never closed.
237      */
238     public boolean isClosed() {
239         return false;
240     }
241 
242     // ===================================================================
243     // methods from Curve2D interface
244 
245     /**
246      * Returns 0, as Bezier curve is parametrized between 0 and 1.
247      */
248     public double getT0() {
249         return 0;
250     }
251 
252     /**
253      * Returns 1, as Bezier curve is parametrized between 0 and 1.
254      */
255     public double getT1() {
256         return 1;
257     }
258 
259     /**
260      * Use approximation, by replacing Bezier curve with a polyline.
261      * 
262      * @see math.geom2d.curve.Curve2D#getIntersections(math.geom2d.line.LinearShape2D)
263      */
264     public Collection<Point2D> getIntersections(LinearShape2D line) {
265         return this.getAsPolyline(100).getIntersections(line);
266     }
267 
268     /**
269      * @see math.geom2d.curve.Curve2D#getPoint(double)
270      */
271     public Point2D getPoint(double t) {
272         t = Math.min(Math.max(t, 0), 1);
273         double[][] c = getParametric();
274         double x = c[0][0]+(c[0][1]+c[0][2]*t)*t;
275         double y = c[1][0]+(c[1][1]+c[1][2]*t)*t;
276         return new Point2D(x, y);
277     }
278 
279     /**
280      * Get the first point of the curve.
281      * 
282      * @return the first point of the curve
283      */
284 	@Override
285     public Point2D getFirstPoint() {
286         return new Point2D(this.x1, this.y1);
287     }
288 
289     /**
290      * Get the last point of the curve.
291      * 
292      * @return the last point of the curve.
293      */
294 	@Override
295     public Point2D getLastPoint() {
296         return new Point2D(this.x2, this.y2);
297     }
298 
299     /**
300      * Compute position by approximating cubic spline with a polyline.
301      */
302     public double getPosition(java.awt.geom.Point2D point) {
303         int N = 100;
304         return this.getAsPolyline(N).getPosition(point)/(N);
305     }
306 
307     /**
308      * Compute position by approximating cubic spline with a polyline.
309      */
310     public double project(java.awt.geom.Point2D point) {
311         int N = 100;
312         return this.getAsPolyline(N).project(point)/(N);
313     }
314 
315     /**
316      * Returns the bezier curve given by control points taken in reverse order.
317      */
318     public QuadBezierCurve2D getReverseCurve() {
319         return new QuadBezierCurve2D(
320         		this.getLastPoint(), this.getControl(), this.getFirstPoint());
321     }
322 
323     /**
324      * Computes portion of BezierCurve. If t1<t0, returns null.
325      */
326     public QuadBezierCurve2D getSubCurve(double t0, double t1) {
327         t0 = Math.max(t0, 0);
328         t1 = Math.min(t1, 1);
329         if (t0>t1)
330             return null;
331 
332         // Extreme points
333         Point2D p0 = getPoint(t0);
334         Point2D p1 = getPoint(t1);
335 
336         // tangent vectors at extreme points
337         Vector2D v0 = getTangent(t0);
338         Vector2D v1 = getTangent(t1);
339 
340         // compute position of control point as intersection of tangent lines
341         StraightLine2D tan0 = new StraightLine2D(p0, v0);
342         StraightLine2D tan1 = new StraightLine2D(p1, v1);
343         Point2D control = tan0.getIntersection(tan1);
344 
345         // build the new quad curve
346         return new QuadBezierCurve2D(p0, control, p1);
347     }
348 
349     // ===================================================================
350     // methods from Shape2D interface
351 
352 	/* (non-Javadoc)
353 	 * @see math.geom2d.Shape2D#contains(double, double)
354 	 */
355 	public boolean contains(double x, double y) {
356 		return new QuadCurve2D.Double(
357 				x1, y1, ctrlx, ctrly, x2, y2).contains(x, y);
358 	}
359 
360 	/* (non-Javadoc)
361 	 * @see math.geom2d.Shape2D#contains(java.awt.geom.Point2D)
362 	 */
363 	public boolean contains(java.awt.geom.Point2D p) {
364 		return this.contains(p.getX(), p.getY());
365 	}
366 
367 	/**
368      * @see math.geom2d.Shape2D#getDistance(java.awt.geom.Point2D)
369      */
370     public double getDistance(java.awt.geom.Point2D p) {
371         return this.getDistance(p.getX(), p.getY());
372     }
373 
374     /**
375      * Compute approximated distance, computed on a polyline.
376      * 
377      * @see math.geom2d.Shape2D#getDistance(double, double)
378      */
379     public double getDistance(double x, double y) {
380         return this.getAsPolyline(100).getDistance(x, y);
381     }
382 
383     /**
384      * return true, a cubic Bezier Curve is always bounded.
385      */
386     public boolean isBounded() {
387         return true;
388     }
389 
390     public boolean isEmpty() {
391         return false;
392     }
393 
394     /**
395      * Clip the circle arc by a box. The result is an instance of
396      * ContinuousOrientedCurveSet2D<QuadBezierCurve2D>, which contains only
397      * instances of EllipseArc2D. If the ellipse arc is not clipped, the result
398      * is an instance of ContinuousOrientedCurveSet2D<QuadBezierCurve2D>
399      * which contains 0 curves.
400      */
401     public CurveSet2D<? extends QuadBezierCurve2D> clip(Box2D box) {
402         // Clip the curve
403         CurveSet2D<SmoothCurve2D> set = Curve2DUtils.clipSmoothCurve(this, box);
404 
405         // Stores the result in appropriate structure
406         CurveArray2D<QuadBezierCurve2D> result = 
407         	new CurveArray2D<QuadBezierCurve2D>(set.getCurveNumber());
408 
409         // convert the result
410         for (Curve2D curve : set.getCurves()) {
411             if (curve instanceof QuadBezierCurve2D)
412                 result.addCurve((QuadBezierCurve2D) curve);
413         }
414         return result;
415     }
416 
417     public Box2D getBoundingBox() {
418     	Point2D p1 = this.getFirstPoint();
419         Point2D p2 = this.getControl();
420         Point2D p3 = this.getLastPoint();
421         double xmin = Math.min(Math.min(p1.getX(), p2.getX()), p3.getX());
422         double xmax = Math.max(Math.max(p1.getX(), p2.getX()), p3.getX());
423         double ymin = Math.min(Math.min(p1.getY(), p2.getY()), p3.getY());
424         double ymax = Math.max(Math.max(p1.getY(), p2.getY()), p3.getY());
425         return new Box2D(xmin, xmax, ymin, ymax);
426     }
427 
428     /**
429      * Returns the Bezier Curve transformed by the given AffineTransform2D. This
430      * is simply done by transforming control points of the curve.
431      */
432     public QuadBezierCurve2D transform(AffineTransform2D trans) {
433         return new QuadBezierCurve2D(
434                 trans.transform(this.getFirstPoint()), 
435                 trans.transform(this.getControl()),
436                 trans.transform(this.getLastPoint()));
437     }
438 
439     public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
440         Point2D p2 = this.getControl();
441         Point2D p3 = this.getLastPoint();
442         path.quadTo(p2.getX(), p2.getY(), p3.getX(), p3.getY());
443         return path;
444     }
445 
446     public java.awt.geom.GeneralPath getGeneralPath() {
447         java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
448         Point2D p1 = this.getFirstPoint();
449         Point2D p2 = this.getControl();
450         Point2D p3 = this.getLastPoint();
451         path.moveTo(p1.getX(), p1.getY());
452         path.quadTo(p2.getX(), p2.getY(), p3.getX(), p3.getY());
453         return path;
454     }
455 
456     @Override
457     public boolean equals(Object obj) {
458         if(!(obj instanceof QuadBezierCurve2D))
459             return false;
460         
461         // Class cast
462         QuadBezierCurve2D bezier = (QuadBezierCurve2D) obj;
463         
464         // Compare each field
465         if(Math.abs(this.x1-bezier.x1)>Shape2D.ACCURACY) return false;
466         if(Math.abs(this.y1-bezier.y1)>Shape2D.ACCURACY) return false;
467         if(Math.abs(this.ctrlx-bezier.ctrlx)>Shape2D.ACCURACY) return false;
468         if(Math.abs(this.ctrly-bezier.ctrly)>Shape2D.ACCURACY) return false;
469         if(Math.abs(this.x2-bezier.x2)>Shape2D.ACCURACY) return false;
470         if(Math.abs(this.y2-bezier.y2)>Shape2D.ACCURACY) return false;
471         
472         return true;
473     }
474     
475 	@Override
476     public QuadBezierCurve2D clone() {
477         return new QuadBezierCurve2D(x1, y1, ctrlx, ctrly, x2, y2);
478     }
479 }