package fr.orsay.lri.varna.models; import java.awt.geom.Point2D; /** * This class implements a cubic Bezier curve * with a constant speed parametrization. * The Bezier curve is approximated by a sequence of n straight lines, * where the n+1 points between the lines are * { B(k/n), k=0,1,...,n } where B is the standard * parametrization given here: * http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves * You can then use the constant speed parametrization over this sequence * of straight lines. * * @author Raphael Champeimont */ public class CubicBezierCurve { /** * The four points defining the curve. */ private Point2D.Double P0, P1, P2, P3; private int n; /** * The number of lines approximating the Bezier curve. */ public int getN() { return n; } /** * Get the (exact) length of the approximation curve. */ public double getApproxCurveLength() { return lengths[n-1]; } /** * The n+1 points between the n lines. */ private Point2D.Double[] points; /** * Array of length n. * lengths[i] is the sum of lengths of lines up to and including the * line starting at point points[i]. */ private double[] lengths; /** * Array of length n. * The vectors along each line, with a norm of 1. */ private Point2D.Double[] unitVectors; /** * The standard exact cubic Bezier curve parametrization. * Argument t must be in [0,1]. */ public Point2D.Double standardParam(double t) { double x = Math.pow(1-t,3) * P0.x + 3 * Math.pow(1-t,2) * t * P1.x + 3 * (1-t) * t * t * P2.x + t * t * t * P3.x; double y = Math.pow(1-t,3) * P0.y + 3 * Math.pow(1-t,2) * t * P1.y + 3 * (1-t) * t * t * P2.y + t * t * t * P3.y; return new Point2D.Double(x, y); } /** * Uniform approximated parametrization. * A value in t must be in [0, getApproxCurveLength()]. * We have built a function f such that f(t) is the position of * the point on the approximation curve (n straight lines). * The interesting property is that the length of the curve * { f(t), t in [0,l] } is exactly l. * The java function is simply the application of f over each element * of a sorted array, ie. uniformParam(t)[k] = f(t[k]). * Computation time is O(n+m) where n is the number of lines in which * the curve is divided and m is the length of the array given as an * argument. The use of a sorted array instead of m calls to the * function enables us to have a complexity of O(n+m) instead of O(n*m) * because we don't need to search in all the n possible lines for * each value in t (as we know their are in increasing order). */ public Point2D.Double[] uniformParam(double[] t) { int m = t.length; Point2D.Double[] result = new Point2D.Double[m]; int line = 0; for (int i=0; i<m; i++) { while ((line<n)&&(lengths[line] < t[i])) { line++; } if (line >= n || t[i] < 0) { // if line >= n // then it means that lenghts[n-1] < t[i] // which is equivalent to getApproxCurveLength() < t[i] throw (new IllegalArgumentException("element t[" + i + "] is not in [0, getApproxCurveLength()]")); } // So now we know on which line we are double lengthOnLine = t[i] - (line != 0 ? lengths[line-1] : 0); double x = points[line].x + unitVectors[line].x * lengthOnLine; double y = points[line].y + unitVectors[line].y * lengthOnLine; result[i] = new Point2D.Double(x, y); } return result; } /** * A Bezier curve can be defined by four points, * see http://en.wikipedia.org/wiki/Bezier_curve#Cubic_B.C3.A9zier_curves * Here we give this four points and a integer to say in how many * line segments we want to cut the Bezier curve (if n is bigger * the computation takes longer but the precision is better). * The number of lines must be at least 1. */ public CubicBezierCurve( Point2D.Double P0, Point2D.Double P1, Point2D.Double P2, Point2D.Double P3, int n) { this.P0 = P0; this.P1 = P1; this.P2 = P2; this.P3 = P3; this.n = n; if (n < 1) { throw (new IllegalArgumentException("n must be at least 1")); } computeData(); } private void computeData() { points = new Point2D.Double[n+1]; for (int k=0; k<=n; k++) { points[k] = standardParam(((double) k) / n); } lengths = new double[n]; unitVectors = new Point2D.Double[n]; double sum = 0; for (int i=0; i<n; i++) { double l = lineLength(points[i], points[i+1]); double dx = (points[i+1].x - points[i].x) / l; double dy = (points[i+1].y - points[i].y) / l; unitVectors[i] = new Point2D.Double(dx, dy); sum += l; lengths[i] = sum; } } private double lineLength(Point2D.Double P1, Point2D.Double P2) { return P2.distance(P1); } public Point2D.Double getP0() { return P0; } public Point2D.Double getP1() { return P1; } public Point2D.Double getP2() { return P2; } public Point2D.Double getP3() { return P3; } }