View Javadoc

1   /* File CubicBezierCurve2D.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.CubicCurve2D;
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  
43  /**
44   * A cubic bezier curve, defined by 4 points.
45   * 
46   * From javaGeom 0.8.0, this shape does not extends
47   * java.awt.geom.CubicCurve2D.Double anymore
48   * 
49   * @author Legland
50   */
51  public class CubicBezierCurve2D extends AbstractSmoothCurve2D
52  implements SmoothCurve2D, ContinuousOrientedCurve2D, Cloneable {
53  
54  	protected double x1, y1;
55  	protected double ctrlx1, ctrly1;
56  	protected double ctrlx2, ctrly2;
57  	protected double x2, y2;
58  	
59  	
60      // ===================================================================
61      // constructors
62  
63      public CubicBezierCurve2D() {
64          this(0, 0, 0, 0, 0, 0, 0, 0);
65      }
66  
67      /**
68       * Build a new Bezier curve from its array of coefficients. The array must
69       * have size 2*4.
70       * 
71       * @param coefs the coefficients of the CubicBezierCurve2D.
72       */
73      public CubicBezierCurve2D(double[][] coefs) {
74          this(coefs[0][0], coefs[1][0], coefs[0][0]+coefs[0][1]/3.0, coefs[1][0]
75                  +coefs[1][1]/3.0,
76                  coefs[0][0]+2*coefs[0][1]/3.0+coefs[0][2]/3.0, coefs[1][0]+2
77                          *coefs[1][1]/3.0+coefs[1][2]/3.0, coefs[0][0]
78                          +coefs[0][1]+coefs[0][2]+coefs[0][3], coefs[1][0]
79                          +coefs[1][1]+coefs[1][2]+coefs[1][3]);
80      }
81  
82      /**
83       * Build a new Bezier curve of degree 3 by specifying position of extreme
84       * points and position of 2 control points. The resulting curve is totally
85       * contained in the convex polygon formed by the 4 control points.
86       * 
87       * @param p1 first point
88       * @param ctrl1 first control point
89       * @param ctrl2 second control point
90       * @param p2 last point
91       */
92      public CubicBezierCurve2D(java.awt.geom.Point2D p1, java.awt.geom.Point2D ctrl1,
93              java.awt.geom.Point2D ctrl2, java.awt.geom.Point2D p2) {
94          this(p1.getX(), p1.getY(), ctrl1.getX(), ctrl1.getY(), ctrl2.getX(),
95                  ctrl2.getY(), p2.getX(), p2.getY());
96      }
97  
98      /**
99       * Build a new Bezier curve of degree 3 by specifying position and tangent
100      * of first and last points.
101      * 
102      * @param p1 first point
103      * @param v1 first tangent vector
104      * @param p2 position of last point
105      * @param v2 last tangent vector
106      */
107     public CubicBezierCurve2D(
108     		java.awt.geom.Point2D p1, Vector2D v1,
109             java.awt.geom.Point2D p2, Vector2D v2) {
110         this(	p1.getX(), p1.getY(), 
111         		p1.getX()+v1.getX()/3, p1.getY()+v1.getY()/3,
112         		p2.getX()-v2.getX()/3, p2.getY()-v2.getY()/3,
113         		p2.getX(), p2.getY());
114     }
115 
116     /**
117      * Build a new Bezier curve of degree 3 by specifying position of extreme
118      * points and position of 2 control points. The resulting curve is totally
119      * containe in the convex polygon formed by the 4 control points.
120      */
121     public CubicBezierCurve2D(double x1, double y1, double xctrl1, double yctrl1,
122             double xctrl2, double yctrl2, double x2, double y2) {
123         this.x1 = x1;
124         this.y1 = y1;
125         this.ctrlx1 = xctrl1;
126         this.ctrly1 = yctrl1;
127         this.ctrlx2 = xctrl2;
128         this.ctrly2 = yctrl2;
129         this.x2 = x2;
130         this.y2 = y2;
131        }
132     
133 
134     // ===================================================================
135     // static methods
136 
137     public final static CubicBezierCurve2D create (
138     		Point2D p1, Point2D c1, Point2D c2, Point2D p2) {
139     	return new CubicBezierCurve2D(p1, c1, c2, p2);
140     }
141     
142     public final static CubicBezierCurve2D create (
143     		Point2D p1, Vector2D v1, Point2D p2, Vector2D v2) {
144     	return new CubicBezierCurve2D(p1, v1, p2, v2);
145     }
146     
147      
148     // ===================================================================
149     // methods specific to CubicBezierCurve2D
150 
151     public Point2D getControl1() {
152         return new Point2D(ctrlx1, ctrly1);
153     }
154 
155     public Point2D getControl2() {
156         return new Point2D(ctrlx2, ctrly2);
157     }
158 
159     public Point2D getP1() {
160     	return this.getFirstPoint();
161     }
162     
163     public Point2D getP2() {
164     	return this.getLastPoint();
165     }
166     
167     public Point2D getCtrlP1() {
168     	return this.getControl1();
169     }
170     
171     public Point2D getCtrlP2() {
172     	return this.getControl2();
173     }
174     
175    /**
176      * Returns the matrix of parametric representation of the line. Result has
177      * the form :
178      * <p>
179      * <code>[ x0  dx dx2 dx3] </code>
180      * <p>
181      * <code>[ y0  dy dy2 dy3] </code>
182      * <p>
183      * Coefficients are from the parametric equation : x(t) = x0 + dx*t +
184      * dx2*t^2 + dx3*t^3 y(t) = y0 + dy*t + dy2*t^2 + dy3*t^3
185      */
186     public double[][] getParametric() {
187         double[][] tab = new double[2][4];
188         tab[0][0] = x1;
189         tab[0][1] = 3*ctrlx1-3*x1;
190         tab[0][2] = 3*x1-6*ctrlx1+3*ctrlx2;
191         tab[0][3] = x2-3*ctrlx2+3*ctrlx1-x1;
192 
193         tab[1][0] = y1;
194         tab[1][1] = 3*ctrly1-3*y1;
195         tab[1][2] = 3*y1-6*ctrly1+3*ctrly2;
196         tab[1][3] = y2-3*ctrly2+3*ctrly1-y1;
197         return tab;
198     }
199 
200     // /**
201     // * Return the parallel curve located at a distance d from this Bezier
202     // * curve.
203     // */
204     // public CubicBezierCurve2D getParallel(double d){
205     // double[][] tab = this.getParametric();
206     // double[][] tab2 = new double[2][4];
207     //		
208     // d = d/Math.hypot(tab[0][1], tab[1][1]);
209     //		
210     // tab2[0][0] = tab[0][0] + d*tab[1][1];
211     // tab2[1][0] = tab[1][0] - d*tab[0][1];
212     //		
213     // tab2[0][1] = tab[0][1] + 2*d*tab[1][2];
214     // tab2[1][1] = tab[1][1] - 2*d*tab[0][2];
215     //		
216     // tab2[0][2] = tab[0][2] + 3*d*tab[1][3];
217     // tab2[1][2] = tab[1][2] - 3*d*tab[0][3];
218     //
219     // tab2[0][3] = tab[0][3];
220     // tab2[1][3] = tab[1][3];
221     //
222     // return new CubicBezierCurve2D(tab2);
223     // }
224 
225     // ===================================================================
226     // methods from OrientedCurve2D interface
227 
228     /**
229      * Use winding angle of approximated polyline
230      * 
231      * @see math.geom2d.domain.OrientedCurve2D#getWindingAngle(java.awt.geom.Point2D)
232      */
233     public double getWindingAngle(java.awt.geom.Point2D point) {
234         return this.getAsPolyline(100).getWindingAngle(point);
235     }
236 
237     /**
238      * return true if the point is 'inside' the domain bounded by the curve.
239      * Uses a polyline approximation.
240      * 
241      * @param pt a point in the plane
242      * @return true if the point is on the left side of the curve.
243      */
244     public boolean isInside(java.awt.geom.Point2D pt) {
245         return this.getAsPolyline(100).isInside(pt);
246     }
247 
248     public double getSignedDistance(java.awt.geom.Point2D point) {
249         if (isInside(point))
250             return -getDistance(point.getX(), point.getY());
251         else
252             return getDistance(point.getX(), point.getY());
253     }
254 
255     /**
256      * @see math.geom2d.domain.OrientedCurve2D#getSignedDistance(java.awt.geom.Point2D)
257      */
258     public double getSignedDistance(double x, double y) {
259         if (isInside(new Point2D(x, y)))
260             return -getDistance(x, y);
261         else
262             return getDistance(x, y);
263     }
264 
265     // ===================================================================
266     // methods from SmoothCurve2D interface
267 
268     public Vector2D getTangent(double t) {
269         double[][] c = getParametric();
270         double dx = c[0][1]+(2*c[0][2]+3*c[0][3]*t)*t;
271         double dy = c[1][1]+(2*c[1][2]+3*c[1][3]*t)*t;
272         return new Vector2D(dx, dy);
273     }
274 
275     /**
276      * returns the curvature of the Curve.
277      */
278     public double getCurvature(double t) {
279         double[][] c = getParametric();
280         double xp = c[0][1]+(2*c[0][2]+3*c[0][3]*t)*t;
281         double yp = c[1][1]+(2*c[1][2]+3*c[1][3]*t)*t;
282         double xs = 2*c[0][2]+6*c[0][3]*t;
283         double ys = 2*c[1][2]+6*c[1][3]*t;
284 
285         return (xp*ys-yp*xs)/Math.pow(Math.hypot(xp, yp), 3);
286     }
287 
288     // ===================================================================
289     // methods from ContinousCurve2D interface
290 
291     /**
292      * The cubic curve is never closed.
293      */
294     public boolean isClosed() {
295         return false;
296     }
297 
298     // ===================================================================
299     // methods from Curve2D interface
300 
301     /**
302      * returns 0, as Bezier curve is parameterized between 0 and 1.
303      */
304     public double getT0() {
305         return 0;
306     }
307 
308     /**
309      * Returns 1, as Bezier curve is parameterized between 0 and 1.
310      */
311     public double getT1() {
312         return 1;
313     }
314 
315     /**
316      * Use approximation, by replacing Bezier curve with a polyline.
317      * 
318      * @see math.geom2d.curve.Curve2D#getIntersections(math.geom2d.line.LinearShape2D)
319      */
320     public Collection<Point2D> getIntersections(LinearShape2D line) {
321         return this.getAsPolyline(100).getIntersections(line);
322     }
323 
324     /**
325      * @see math.geom2d.curve.Curve2D#getPoint(double)
326      */
327     public Point2D getPoint(double t) {
328         t = Math.min(Math.max(t, 0), 1);
329         double[][] c = getParametric();
330         double x = c[0][0]+(c[0][1]+(c[0][2]+c[0][3]*t)*t)*t;
331         double y = c[1][0]+(c[1][1]+(c[1][2]+c[1][3]*t)*t)*t;
332         return new Point2D(x, y);
333     }
334 
335     /**
336      * Returns the first point of the curve.
337      * 
338      * @return the first point of the curve
339      */
340 	@Override
341     public Point2D getFirstPoint() {
342         return new Point2D(this.x1, this.y1);
343     }
344 
345     /**
346      * Returns the last point of the curve.
347      * 
348      * @return the last point of the curve.
349      */
350 	@Override
351     public Point2D getLastPoint() {
352         return new Point2D(this.x2, this.y2);
353     }
354 
355     /**
356      * Computes position by approximating cubic spline with a polyline.
357      */
358     public double getPosition(java.awt.geom.Point2D point) {
359         int N = 100;
360         return this.getAsPolyline(N).getPosition(point)/(N);
361     }
362 
363     /**
364      * Computes position by approximating cubic spline with a polyline.
365      */
366     public double project(java.awt.geom.Point2D point) {
367         int N = 100;
368         return this.getAsPolyline(N).project(point)/(N);
369     }
370 
371     /**
372      * Returns the Bezier curve given by control points taken in reverse
373      * order.
374      */
375     public CubicBezierCurve2D getReverseCurve() {
376         return new CubicBezierCurve2D(
377                 this.getLastPoint(), this.getControl1(),
378                 this.getControl2(), this.getFirstPoint());
379     }
380 
381     /**
382      * Computes portion of BezierCurve. If t1<t0, returns null.
383      */
384     public CubicBezierCurve2D getSubCurve(double t0, double t1) {
385         t0 = Math.max(t0, 0);
386         t1 = Math.min(t1, 1);
387         if (t0>t1)
388             return null;
389 
390         double dt = t1-t0;
391         Vector2D v0 = getTangent(t0).times(dt);
392         Vector2D v1 = getTangent(t1).times(dt);
393         return new CubicBezierCurve2D(getPoint(t0), v0, getPoint(t1), v1);
394     }
395 
396     // ===================================================================
397     // methods from Shape2D interface
398 
399 	/* (non-Javadoc)
400 	 * @see math.geom2d.Shape2D#contains(double, double)
401 	 */
402 	public boolean contains(double x, double y) {
403 		return new CubicCurve2D.Double(
404 				x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2).contains(x, y);
405 	}
406 
407 	/* (non-Javadoc)
408 	 * @see math.geom2d.Shape2D#contains(java.awt.geom.Point2D)
409 	 */
410 	public boolean contains(java.awt.geom.Point2D p) {
411 		return this.contains(p.getX(), p.getY());
412 	}
413 
414 	/**
415      * @see math.geom2d.Shape2D#getDistance(java.awt.geom.Point2D)
416      */
417     public double getDistance(java.awt.geom.Point2D p) {
418         return this.getDistance(p.getX(), p.getY());
419     }
420 
421     /**
422      * Compute approximated distance, computed on a polyline.
423      * 
424      * @see math.geom2d.Shape2D#getDistance(double, double)
425      */
426     public double getDistance(double x, double y) {
427     	return this.getAsPolyline(100).getDistance(x, y);
428     }
429 
430     /**
431      * Returns true, a cubic Bezier Curve is always bounded.
432      */
433     public boolean isBounded() {
434         return true;
435     }
436 
437     public boolean isEmpty() {
438         return false;
439     }
440 
441     /**
442      * Clip the Bezier curve by a box. Return a set of CubicBezierCurve2D.
443      */
444     public CurveSet2D<? extends CubicBezierCurve2D> clip(Box2D box) {
445         // Clip the curve
446         CurveSet2D<SmoothCurve2D> set = 
447             Curve2DUtils.clipSmoothCurve(this, box);
448 
449         // Stores the result in appropriate structure
450         CurveArray2D<CubicBezierCurve2D> result = 
451         	new CurveArray2D<CubicBezierCurve2D>(set.getCurveNumber());
452 
453         // convert the result
454         for (Curve2D curve : set.getCurves()) {
455             if (curve instanceof CubicBezierCurve2D)
456                 result.addCurve((CubicBezierCurve2D) curve);
457         }
458         return result;
459     }
460 
461     public Box2D getBoundingBox() {
462     	Point2D p1 = this.getFirstPoint();
463         Point2D p2 = this.getControl1();
464         Point2D p3 = this.getControl2();
465         Point2D p4 = this.getLastPoint();
466         double xmin = Math.min(Math.min(p1.getX(), p2.getX()), 
467         		Math.min(p3.getX(), p4.getX()));
468         double xmax = Math.max(Math.max(p1.getX(), p2.getX()), 
469         		Math.max(p3.getX(), p4.getX()));
470         double ymin = Math.min(Math.min(p1.getY(), p2.getY()), 
471         		Math.min(p3.getY(), p4.getY()));
472         double ymax = Math.max(Math.max(p1.getY(), p2.getY()), 
473         		Math.max(p3.getY(), p4.getY()));
474         return new Box2D(xmin, xmax, ymin, ymax);
475     }
476 
477     /**
478      * Returns the Bezier Curve transformed by the given AffineTransform2D. This
479      * is simply done by transforming control points of the curve.
480      */
481     public CubicBezierCurve2D transform(AffineTransform2D trans) {
482         return new CubicBezierCurve2D(
483                 trans.transform(this.getFirstPoint()), 
484                 trans.transform(this.getControl1()),
485                 trans.transform(this.getControl2()),
486                 trans.transform(this.getLastPoint()));
487     }
488 
489     public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
490         Point2D p2 = this.getControl1();
491         Point2D p3 = this.getControl2();
492         Point2D p4 = this.getLastPoint();
493         path.curveTo(
494         		p2.getX(), p2.getY(), 
495         		p3.getX(), p3.getY(), 
496         		p4.getX(), p4.getY());
497         return path;
498     }
499 
500     public java.awt.geom.GeneralPath getGeneralPath() {
501         java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
502         Point2D p1 = this.getFirstPoint();
503         Point2D p2 = this.getControl1();
504         Point2D p3 = this.getControl2();
505         Point2D p4 = this.getLastPoint();
506         path.moveTo(p1.getX(), p1.getY());
507         path.curveTo(
508         		p2.getX(), p2.getY(), 
509         		p3.getX(), p3.getY(), 
510         		p4.getX(), p4.getY());
511         return path;
512     }
513 
514     @Override
515     public boolean equals(Object obj) {
516         if(!(obj instanceof CubicBezierCurve2D))
517             return false;
518         
519         // class cast
520         CubicBezierCurve2D bezier = (CubicBezierCurve2D) obj;
521         
522         // compare each field
523         if(Math.abs(this.x1-bezier.x1)>Shape2D.ACCURACY) return false;
524         if(Math.abs(this.y1-bezier.y1)>Shape2D.ACCURACY) return false;
525         if(Math.abs(this.ctrlx1-bezier.ctrlx1)>Shape2D.ACCURACY) return false;
526         if(Math.abs(this.ctrly1-bezier.ctrly1)>Shape2D.ACCURACY) return false;
527         if(Math.abs(this.ctrlx2-bezier.ctrlx2)>Shape2D.ACCURACY) return false;
528         if(Math.abs(this.ctrly2-bezier.ctrly2)>Shape2D.ACCURACY) return false;
529         if(Math.abs(this.x2-bezier.x2)>Shape2D.ACCURACY) return false;
530         if(Math.abs(this.y2-bezier.y2)>Shape2D.ACCURACY) return false;
531         
532         return true;
533     }
534     
535     @Override
536     public CubicBezierCurve2D clone() {
537         return new CubicBezierCurve2D(
538         		x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2);
539     }
540 }