/* File CubicBezierCurve2D.java 
 *
 * Project : geometry
 *
 * ===========================================
 * 
 * This library is free software; you can redistribute it and/or modify it 
 * under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 2.1 of the License, or (at
 * your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but 
 * WITHOUT ANY WARRANTY, without even the implied warranty of MERCHANTABILITY
 * or FITNESS FOR A PARTICULAR PURPOSE.
 *
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this library. if not, write to :
 * The Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 */

package math.geom2d.spline;

import java.util.Collection;

import math.geom2d.*;
import math.geom2d.curve.*;
import math.geom2d.domain.ContinuousOrientedCurve2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.polygon.Polyline2D;

/**
 * A cubic bezier curve, defined by 4 control points.
 * The curve passes through the first and the last control points.
 * The second and the third control points defines the tangents at the
 * extremities of the curve.
 * 
 * From javaGeom 0.8.0, this shape does not extends
 * java.awt.geom.CubicCurve2D.Double anymore
 * 
 * @author Legland
 */
public class CubicBezierCurve2D extends AbstractSmoothCurve2D
implements SmoothCurve2D, ContinuousOrientedCurve2D, Cloneable {

    // ===================================================================
    // static methods

	/**
	 * @deprecated since 0.11.1
	 */
	@Deprecated
	public final static CubicBezierCurve2D create(Point2D p1, Point2D c1,
			Point2D c2, Point2D p2) {
		return new CubicBezierCurve2D(p1, c1, c2, p2);
	}
    
	/**
	 * @deprecated since 0.11.1
	 */
	@Deprecated
	public final static CubicBezierCurve2D create(Point2D p1, Vector2D v1,
			Point2D p2, Vector2D v2) {
    	return new CubicBezierCurve2D(p1, v1, p2, v2);
    }
    
     
    // ===================================================================
    // class variables

	protected double x1, y1;
	protected double ctrlx1, ctrly1;
	protected double ctrlx2, ctrly2;
	protected double x2, y2;
	
	
    // ===================================================================
    // constructors

    public CubicBezierCurve2D() {
        this(0, 0, 0, 0, 0, 0, 0, 0);
    }

    /**
     * Build a new Bezier curve from its array of coefficients. The array must
     * have size 2*4.
     * 
     * @param coefs the coefficients of the CubicBezierCurve2D.
     */
    public CubicBezierCurve2D(double[][] coefs) {
		this(coefs[0][0], coefs[1][0], 
				coefs[0][0] + coefs[0][1] / 3.0, 
				coefs[1][0] + coefs[1][1] / 3.0, 
				coefs[0][0] + 2 * coefs[0][1] / 3.0 + coefs[0][2] / 3.0, 
				coefs[1][0] + 2 * coefs[1][1] / 3.0 + coefs[1][2] / 3.0, 
				coefs[0][0] + coefs[0][1] + coefs[0][2] + coefs[0][3], 
				coefs[1][0] + coefs[1][1] + coefs[1][2] + coefs[1][3]);
    }

    /**
     * Build a new Bezier curve of degree 3 by specifying position of extreme
     * points and position of 2 control points. The resulting curve is totally
     * contained in the convex polygon formed by the 4 control points.
     * 
     * @param p1 first point
     * @param ctrl1 first control point
     * @param ctrl2 second control point
     * @param p2 last point
     */
	public CubicBezierCurve2D(Point2D p1, Point2D ctrl1, Point2D ctrl2,
			Point2D p2) {
		this(p1.x(), p1.y(), ctrl1.x(), ctrl1.y(), 
				ctrl2.x(), ctrl2.y(), p2.x(), p2.y());
    }

    /**
     * Build a new Bezier curve of degree 3 by specifying position and tangent
     * of first and last points.
     * 
     * @param p1 first point
     * @param v1 first tangent vector
     * @param p2 position of last point
     * @param v2 last tangent vector
     */
	public CubicBezierCurve2D(Point2D p1, Vector2D v1, Point2D p2, Vector2D v2) {
		this(p1.x(), p1.y(), 
				p1.x() + v1.x() / 3, p1.y() + v1.y() / 3, 
				p2.x() - v2.x() / 3, p2.y() - v2.y() / 3, 
				p2.x(), p2.y());
    }

    /**
     * Build a new Bezier curve of degree 3 by specifying position of extreme
     * points and position of 2 control points. The resulting curve is totally
     * containe in the convex polygon formed by the 4 control points.
     */
    public CubicBezierCurve2D(double x1, double y1, double xctrl1, double yctrl1,
            double xctrl2, double yctrl2, double x2, double y2) {
        this.x1 = x1;
        this.y1 = y1;
        this.ctrlx1 = xctrl1;
        this.ctrly1 = yctrl1;
        this.ctrlx2 = xctrl2;
        this.ctrly2 = yctrl2;
        this.x2 = x2;
        this.y2 = y2;
       }
    

    // ===================================================================
    // methods specific to CubicBezierCurve2D

    public Point2D getControl1() {
        return new Point2D(ctrlx1, ctrly1);
    }

    public Point2D getControl2() {
        return new Point2D(ctrlx2, ctrly2);
    }

    public Point2D getP1() {
    	return this.firstPoint();
    }
    
    public Point2D getP2() {
    	return this.lastPoint();
    }
    
    public Point2D getCtrlP1() {
    	return this.getControl1();
    }
    
    public Point2D getCtrlP2() {
    	return this.getControl2();
    }
    
   /**
     * Returns the matrix of parametric representation of the line. Result has
     * the form :
     * 

* [ x0 dx dx2 dx3] *

* [ y0 dy dy2 dy3] *

* Coefficients are from the parametric equation : x(t) = x0 + dx*t + * dx2*t^2 + dx3*t^3 y(t) = y0 + dy*t + dy2*t^2 + dy3*t^3 */ public double[][] getParametric() { double[][] tab = new double[2][4]; tab[0][0] = x1; tab[0][1] = 3 * ctrlx1 - 3 * x1; tab[0][2] = 3 * x1 - 6 * ctrlx1 + 3 * ctrlx2; tab[0][3] = x2 - 3 * ctrlx2 + 3 * ctrlx1 - x1; tab[1][0] = y1; tab[1][1] = 3 * ctrly1 - 3 * y1; tab[1][2] = 3 * y1 - 6 * ctrly1 + 3 * ctrly2; tab[1][3] = y2 - 3 * ctrly2 + 3 * ctrly1 - y1; return tab; } // =================================================================== // methods from OrientedCurve2D interface /** * Use winding angle of approximated polyline * * @see math.geom2d.domain.OrientedCurve2D#windingAngle(Point2D) */ public double windingAngle(Point2D point) { return this.asPolyline(100).windingAngle(point); } /** * Returns true if the point is 'inside' the domain bounded by the curve. * Uses a polyline approximation. * * @param pt a point in the plane * @return true if the point is on the left side of the curve. */ public boolean isInside(Point2D pt) { return this.asPolyline(100).isInside(pt); } public double signedDistance(Point2D point) { if (isInside(point)) return -distance(point.x(), point.y()); else return distance(point.x(), point.y()); } /** * @see math.geom2d.domain.OrientedCurve2D#signedDistance(Point2D) */ public double signedDistance(double x, double y) { if (isInside(new Point2D(x, y))) return -distance(x, y); else return distance(x, y); } // =================================================================== // methods from SmoothCurve2D interface public Vector2D tangent(double t) { double[][] c = getParametric(); double dx = c[0][1] + (2 * c[0][2] + 3 * c[0][3] * t) * t; double dy = c[1][1] + (2 * c[1][2] + 3 * c[1][3] * t) * t; return new Vector2D(dx, dy); } /** * Returns the curvature of the Curve. */ public double curvature(double t) { double[][] c = getParametric(); double xp = c[0][1] + (2 * c[0][2] + 3 * c[0][3] * t) * t; double yp = c[1][1] + (2 * c[1][2] + 3 * c[1][3] * t) * t; double xs = 2 * c[0][2] + 6 * c[0][3] * t; double ys = 2 * c[1][2] + 6 * c[1][3] * t; return (xp * ys - yp * xs) / Math.pow(Math.hypot(xp, yp), 3); } // =================================================================== // methods from ContinousCurve2D interface /** * The cubic curve is never closed. */ public boolean isClosed() { return false; } /* (non-Javadoc) * @see math.geom2d.curve.ContinuousCurve2D#asPolyline(int) */ public Polyline2D asPolyline(int n) { // compute increment value double dt = 1.0 / n; // allocate array of points, and compute each value. // Computes also value for last point. Point2D[] points = new Point2D[n + 1]; for (int i = 0; i < n + 1; i++) points[i] = this.point(i * dt); return new Polyline2D(points); } // =================================================================== // methods from Curve2D interface /** * returns 0, as Bezier curve is parameterized between 0 and 1. */ public double t0() { return 0; } /** * @deprecated replaced by t0() (since 0.11.1). */ @Deprecated public double getT0() { return t0(); } /** * Returns 1, as Bezier curve is parameterized between 0 and 1. */ public double t1() { return 1; } /** * @deprecated replaced by t1() (since 0.11.1). */ @Deprecated public double getT1() { return t1(); } /** * Use approximation, by replacing Bezier curve with a polyline. * * @see math.geom2d.curve.Curve2D#intersections(math.geom2d.line.LinearShape2D) */ public Collection intersections(LinearShape2D line) { return this.asPolyline(100).intersections(line); } /** * @see math.geom2d.curve.Curve2D#point(double) */ public Point2D point(double t) { t = Math.min(Math.max(t, 0), 1); double[][] c = getParametric(); double x = c[0][0] + (c[0][1] + (c[0][2] + c[0][3] * t) * t) * t; double y = c[1][0] + (c[1][1] + (c[1][2] + c[1][3] * t) * t) * t; return new Point2D(x, y); } /** * Returns the first point of the curve. * * @return the first point of the curve */ @Override public Point2D firstPoint() { return new Point2D(this.x1, this.y1); } /** * Returns the last point of the curve. * * @return the last point of the curve. */ @Override public Point2D lastPoint() { return new Point2D(this.x2, this.y2); } /** * Computes position by approximating cubic spline with a polyline. */ public double position(Point2D point) { int N = 100; return this.asPolyline(N).position(point)/(N); } /** * Computes position by approximating cubic spline with a polyline. */ public double project(Point2D point) { int N = 100; return this.asPolyline(N).project(point) / (N); } /** * Returns the Bezier curve given by control points taken in reverse * order. */ public CubicBezierCurve2D reverse() { return new CubicBezierCurve2D( this.lastPoint(), this.getControl1(), this.getControl2(), this.firstPoint()); } /** * Computes portion of BezierCurve. If t1 t1) return null; double dt = t1 - t0; Vector2D v0 = tangent(t0).times(dt); Vector2D v1 = tangent(t1).times(dt); return new CubicBezierCurve2D(point(t0), v0, point(t1), v1); } // =================================================================== // methods from Shape2D interface /* (non-Javadoc) * @see math.geom2d.Shape2D#contains(double, double) */ public boolean contains(double x, double y) { return this.asPolyline(180).contains(x, y); } /* (non-Javadoc) * @see math.geom2d.Shape2D#contains(Point2D) */ public boolean contains(Point2D p) { return this.contains(p.x(), p.y()); } /** * @see math.geom2d.Shape2D#distance(Point2D) */ public double distance(Point2D p) { return this.distance(p.x(), p.y()); } /** * Compute approximated distance, computed on a polyline. * * @see math.geom2d.Shape2D#distance(double, double) */ public double distance(double x, double y) { return this.asPolyline(100).distance(x, y); } /** * Returns true, a cubic Bezier Curve is always bounded. */ public boolean isBounded() { return true; } public boolean isEmpty() { return false; } /** * Clip the Bezier curve by a box. Return a set of CubicBezierCurve2D. */ public CurveSet2D clip(Box2D box) { // Clip the curve CurveSet2D set = Curves2D.clipSmoothCurve(this, box); // Stores the result in appropriate structure CurveArray2D result = new CurveArray2D(set.size()); // convert the result for (Curve2D curve : set.curves()) { if (curve instanceof CubicBezierCurve2D) result.add((CubicBezierCurve2D) curve); } return result; } /** * Returns the approximate bounding box of this curve. Actually, computes * the bounding box of the set of control points. */ public Box2D boundingBox() { double xmin = Math.min(Math.min(x1, ctrlx1), Math.min(ctrlx2, x2)); double xmax = Math.max(Math.max(x1, ctrlx1), Math.max(ctrlx2, x2)); double ymin = Math.min(Math.min(y1, ctrly1), Math.min(ctrly2, y2)); double ymax = Math.max(Math.max(y1, ctrly1), Math.max(ctrly2, y2)); return new Box2D(xmin, xmax, ymin, ymax); } /** * Returns the Bezier Curve transformed by the given AffineTransform2D. This * is simply done by transforming control points of the curve. */ public CubicBezierCurve2D transform(AffineTransform2D trans) { return new CubicBezierCurve2D( trans.transform(this.firstPoint()), trans.transform(this.getControl1()), trans.transform(this.getControl2()), trans.transform(this.lastPoint())); } public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) { path.moveTo(x1, y1); path.curveTo(ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); return path; } public java.awt.geom.GeneralPath getGeneralPath() { java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath(); path.moveTo(x1, y1); path.curveTo(ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); return path; } // =================================================================== // methods implementing the GeometricObject2D interface /* (non-Javadoc) * @see math.geom2d.GeometricObject2D#almostEquals(math.geom2d.GeometricObject2D, double) */ public boolean almostEquals(GeometricObject2D obj, double eps) { if (this==obj) return true; if(!(obj instanceof CubicBezierCurve2D)) return false; // class cast CubicBezierCurve2D bezier = (CubicBezierCurve2D) obj; // compare each field if (Math.abs(this.x1 - bezier.x1) > eps) return false; if (Math.abs(this.y1 - bezier.y1) > eps) return false; if (Math.abs(this.ctrlx1 - bezier.ctrlx1) > eps) return false; if (Math.abs(this.ctrly1 - bezier.ctrly1) > eps) return false; if (Math.abs(this.ctrlx2 - bezier.ctrlx2) > eps) return false; if (Math.abs(this.ctrly2 - bezier.ctrly2) > eps) return false; if (Math.abs(this.x2 - bezier.x2) > eps) return false; if (Math.abs(this.y2 - bezier.y2) > eps) return false; return true; } // =================================================================== // methods overriding the class Object @Override public boolean equals(Object obj) { if (this==obj) return true; if(!(obj instanceof CubicBezierCurve2D)) return false; // class cast CubicBezierCurve2D bezier = (CubicBezierCurve2D) obj; // compare each field if(Math.abs(this.x1-bezier.x1)>Shape2D.ACCURACY) return false; if(Math.abs(this.y1-bezier.y1)>Shape2D.ACCURACY) return false; if(Math.abs(this.ctrlx1-bezier.ctrlx1)>Shape2D.ACCURACY) return false; if(Math.abs(this.ctrly1-bezier.ctrly1)>Shape2D.ACCURACY) return false; if(Math.abs(this.ctrlx2-bezier.ctrlx2)>Shape2D.ACCURACY) return false; if(Math.abs(this.ctrly2-bezier.ctrly2)>Shape2D.ACCURACY) return false; if(Math.abs(this.x2-bezier.x2)>Shape2D.ACCURACY) return false; if(Math.abs(this.y2-bezier.y2)>Shape2D.ACCURACY) return false; return true; } /** * @deprecated not necessary to clone immutable objects (0.11.2) */ @Deprecated @Override public CubicBezierCurve2D clone() { return new CubicBezierCurve2D( x1, y1, ctrlx1, ctrly1, ctrlx2, ctrly2, x2, y2); } }

Ads help maintain this website.