/* File QuadBezierCurve2D.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.awt.geom.QuadCurve2D;
import java.util.Collection;
import math.geom2d.*;
import math.geom2d.curve.*;
import math.geom2d.domain.ContinuousOrientedCurve2D;
import math.geom2d.line.LinearShape2D;
import math.geom2d.line.StraightLine2D;
import math.geom2d.polygon.Polyline2D;
/**
* A quadratic Bezier curve, defined by 3 control points.
* The curve starts at the first control point and finished at the third
* control point. The second point is used to defined the curvature of the
* curve.
*
* From javaGeom 0.8.0, this shape does not extends.
* java.awt.geom.QuadCurve2D.Double anymore
*
* @author Legland
*/
public class QuadBezierCurve2D extends AbstractSmoothCurve2D
implements SmoothCurve2D, ContinuousOrientedCurve2D, Cloneable {
// ===================================================================
// static methods
/**
* Static factory for creating a new Quadratic Bezier curve from 3 points.
*
* @deprecated since 0.11.1
*/
@Deprecated
public static QuadBezierCurve2D create(Point2D p1, Point2D p2, Point2D p3) {
return new QuadBezierCurve2D(p1, p2, p3);
}
// ===================================================================
// class variables
/**
* Coordinates of the first point of the curve
*/
protected double x1, y1;
/**
* Coordinates of the control point of the curve
*/
protected double ctrlx, ctrly;
/**
* Coordinates of the last point of the curve
*/
protected double x2, y2;
// ===================================================================
// constructors
/**
* Creates an empty quadratic bezier curve.
*/
public QuadBezierCurve2D() {
this(0, 0, 0, 0, 0, 0);
}
/**
* Build a new Bezier curve from its array of coefficients. The array must
* have size 2*3.
*
* @param coefs the coefficients of the QuadBezierCurve2D.
*/
public QuadBezierCurve2D(double[][] coefs) {
this(coefs[0][0], coefs[1][0],
coefs[0][0] + coefs[0][1] / 2.0,
coefs[1][0] + coefs[1][1] / 2.0,
coefs[0][0] + coefs[0][1] + coefs[0][2],
coefs[1][0] + coefs[1][1] + coefs[1][2]);
}
/**
* Build a new quadratic Bezier curve by specifying position of extreme
* points and position of control point. The resulting curve is totally
* contained in the convex polygon formed by the 3 control points.
*
* @param p1 first point
* @param ctrl control point
* @param p2 last point
*/
public QuadBezierCurve2D(Point2D p1, Point2D ctrl, Point2D p2) {
this(p1.x(), p1.y(), ctrl.x(), ctrl.y(), p2.x(), p2.y());
}
public QuadBezierCurve2D(Point2D[] pts) {
this(pts[0].x(), pts[0].y(), pts[1].x(), pts[1].y(),
pts[2].x(), pts[2].y());
}
/**
* Build a new quadratic Bezier curve by specifying position of extreme
* points and position of control point. The resulting curve is totally
* contained in the convex polygon formed by the 3 control points.
*/
public QuadBezierCurve2D(double x1, double y1, double xctrl, double yctrl,
double x2, double y2) {
this.x1 = x1;
this.y1 = y1;
this.ctrlx = xctrl;
this.ctrly = yctrl;
this.x2 = x2;
this.y2 = y2;
}
// ===================================================================
// methods specific to QuadBezierCurve2D
public Point2D getControl() {
return new Point2D(ctrlx, ctrly);
}
public Point2D getP1() {
return this.firstPoint();
}
public Point2D getP2() {
return this.lastPoint();
}
public Point2D getCtrl() {
return this.getControl();
}
/**
* Returns the matrix of parametric representation of the line. Result is a
* 2x3 array with coefficients:
*
* [ cx0 cx1 cx2]
*
* [ cy0 cy1 cy2]
*
* Coefficients are from the parametric equation :
* x(t) = cx0 + cx1*t + cx2*t^2
* y(t) = cy0 + cy1*t + cy2*t^2
*
*/
public double[][] getParametric() {
double[][] tab = new double[2][3];
tab[0][0] = x1;
tab[0][1] = 2 * ctrlx - 2 * x1;
tab[0][2] = x2 - 2 * ctrlx + x1;
tab[1][0] = y1;
tab[1][1] = 2 * ctrly - 2 * y1;
tab[1][2] = y2 - 2 * ctrly + 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] * t;
double dy = c[1][1] + 2 * c[1][2] * 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] * t;
double yp = c[1][1] + 2 * c[1][2] * t;
double xs = 2 * c[0][2];
double ys = 2 * c[1][2];
return (xp * ys - yp * xs) / Math.pow(Math.hypot(xp, yp), 3);
}
// ===================================================================
// methods from ContinousCurve2D interface
/**
* Returns false, as a quadratic 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 parametrized 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] * t) * t;
double y = c[1][0] + (c[1][1] + c[1][2] * t) * t;
return new Point2D(x, y);
}
/**
* Returns the first point of the curve, that corresponds to the first control point.
*
* @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, that corresponds to the third control point.
*
* @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 QuadBezierCurve2D reverse() {
return new QuadBezierCurve2D(
this.lastPoint(), this.getControl(), this.firstPoint());
}
/**
* Computes portion of BezierCurve. If t1 t1)
return null;
// Extreme points
Point2D p0 = point(t0);
Point2D p1 = point(t1);
// tangent vectors at extreme points
Vector2D v0 = tangent(t0);
Vector2D v1 = tangent(t1);
// compute position of control point as intersection of tangent lines
StraightLine2D tan0 = new StraightLine2D(p0, v0);
StraightLine2D tan1 = new StraightLine2D(p1, v1);
Point2D control = tan0.intersection(tan1);
// build the new quad curve
return new QuadBezierCurve2D(p0, control, p1);
}
// ===================================================================
// methods from Shape2D interface
/* (non-Javadoc)
* @see math.geom2d.Shape2D#contains(double, double)
*/
public boolean contains(double x, double y) {
return new QuadCurve2D.Double(
x1, y1, ctrlx, ctrly, x2, y2).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());
}
/**
* Computes 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 curve by a box. The result is an instance of
* CurveSet2D, which contains only instances of QuadBezierCurve2D.
* If the curve is not clipped, the result is an instance of CurveSet2D
* which contains 0 curves.
*/
public CurveSet2D extends QuadBezierCurve2D> 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 QuadBezierCurve2D)
result.add((QuadBezierCurve2D) 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() {
Point2D p1 = this.firstPoint();
Point2D p2 = this.getControl();
Point2D p3 = this.lastPoint();
double xmin = Math.min(Math.min(p1.x(), p2.x()), p3.x());
double xmax = Math.max(Math.max(p1.x(), p2.x()), p3.x());
double ymin = Math.min(Math.min(p1.y(), p2.y()), p3.y());
double ymax = Math.max(Math.max(p1.y(), p2.y()), p3.y());
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 QuadBezierCurve2D transform(AffineTransform2D trans) {
return new QuadBezierCurve2D(
trans.transform(this.firstPoint()),
trans.transform(this.getControl()),
trans.transform(this.lastPoint()));
}
public java.awt.geom.GeneralPath appendPath(java.awt.geom.GeneralPath path) {
Point2D p2 = this.getControl();
Point2D p3 = this.lastPoint();
path.quadTo(p2.x(), p2.y(), p3.x(), p3.y());
return path;
}
public java.awt.geom.GeneralPath getGeneralPath() {
java.awt.geom.GeneralPath path = new java.awt.geom.GeneralPath();
Point2D p1 = this.firstPoint();
Point2D p2 = this.getControl();
Point2D p3 = this.lastPoint();
path.moveTo(p1.x(), p1.y());
path.quadTo(p2.x(), p2.y(), p3.x(), p3.y());
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 QuadBezierCurve2D))
return false;
// Class cast
QuadBezierCurve2D bezier = (QuadBezierCurve2D) 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.ctrlx-bezier.ctrlx)>eps) return false;
if(Math.abs(this.ctrly-bezier.ctrly)>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 QuadBezierCurve2D))
return false;
// Class cast
QuadBezierCurve2D bezier = (QuadBezierCurve2D) 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.ctrlx-bezier.ctrlx)>Shape2D.ACCURACY) return false;
if(Math.abs(this.ctrly-bezier.ctrly)>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 QuadBezierCurve2D clone() {
return new QuadBezierCurve2D(x1, y1, ctrlx, ctrly, x2, y2);
}
}
Ads help maintain this website.