/*
* @(#)Bezier.java
*
* Copyright (c) 1996-2010 The authors and contributors of JHotDraw.
* You may not use, copy or modify this file, except in compliance with the
* accompanying license terms.
*/
package org.jhotdraw.geom;
import java.awt.geom.*;
import java.util.*;
/**
* Provides algorithms for fitting Bezier curves to a set of digitized points.
* <p>
* Source:<br>
* Phoenix: An Interactive Curve Design System Based on the Automatic Fitting
* of Hand-Sketched Curves.<br>
* © Copyright by Philip J. Schneider 1988.<br>
* A thesis submitted in partial fulfillment of the requirements for the degree
* of Master of Science, University of Washington.
* <p>
* http://autotrace.sourceforge.net/Interactive_Curve_Design.ps.gz
*
* @author Werner Randelshofer
* @version $Id$
*/
public class Bezier {
/** Prevent instance creation. */
private Bezier() {
}
public static void main(String[] args) {
ArrayList<Point2D.Double> d = new ArrayList<>();
d.add(new Point2D.Double(0, 0));
d.add(new Point2D.Double(5, 1));
d.add(new Point2D.Double(10, 0));
d.add(new Point2D.Double(10, 10));
d.add(new Point2D.Double(0, 10));
d.add(new Point2D.Double(0, 0));
ArrayList<ArrayList<Point2D.Double>> segments = (splitAtCorners(d, 45 / 180d * Math.PI, 2d));
for (ArrayList<Point2D.Double> seg : segments) {
for (int i = 0; i < 2; i++) {
seg = reduceNoise(seg, 0.8);
}
}
}
/**
* Fits a bezier path to the specified list of digitized points.
* <p>
* This is a convenience method for calling {@link #fitBezierPath}
*
* @param digitizedPoints digited points.
* @param error the maximal allowed error between the bezier path and the
* digitized points.
*/
public static BezierPath fitBezierPath(Point2D.Double[] digitizedPoints, double error) {
return fitBezierPath(Arrays.asList(digitizedPoints), error);
}
/**
* Fits a bezier path to the specified list of digitized points.
*
* @param digitizedPoints digited points.
* @param error the maximal allowed error between the bezier path and the
* digitized points.
*/
public static BezierPath fitBezierPath(java.util.List<Point2D.Double> digitizedPoints, double error) {
// Split into segments at corners
ArrayList<ArrayList<Point2D.Double>> segments;
segments = splitAtCorners(digitizedPoints, 77 / 180d * Math.PI, error * error);
// Clean up the data in the segments
for (int i = 0, n = segments.size(); i < n; i++) {
ArrayList<Point2D.Double> seg = segments.get(i);
seg = removeClosePoints(seg, error * 2);
seg = reduceNoise(seg, 0.8);
segments.set(i, seg);
}
// Create fitted bezier path
BezierPath fittedPath = new BezierPath();
// Quickly deal with empty dataset
boolean isEmpty = false;
for (ArrayList<Point2D.Double> seg : segments) {
if (seg.isEmpty()) {
isEmpty = false;
break;
}
}
if (!isEmpty) {
// Process each segment of digitized points
double errorSquared = error * error;
for (ArrayList<Point2D.Double> seg : segments) {
switch (seg.size()) {
case 0:
break;
case 1:
fittedPath.add(new BezierPath.Node(seg.get(0)));
break;
case 2:
if (fittedPath.isEmpty()) {
fittedPath.add(new BezierPath.Node(seg.get(0)));
}
fittedPath.lineTo(seg.get(1).x, seg.get(1).y);
break;
default:
if (fittedPath.isEmpty()) {
fittedPath.add(new BezierPath.Node(seg.get(0)));
}
/* Unit tangent vectors at endpoints */
Point2D.Double tHat1;
Point2D.Double tHat2;
tHat1 = computeLeftTangent(seg, 0);
tHat2 = computeRightTangent(seg, seg.size() - 1);
fitCubic(seg, 0, seg.size() - 1, tHat1, tHat2, errorSquared, fittedPath);
break;
}
}
}
return fittedPath;
}
/**
* Fits a bezier path to the specified list of digitized points.
* <p>
* This is a convenience method for calling {@link #fitBezierPath}.
*
* @param digitizedPoints digited points.
* @param error the maximal allowed error between the bezier path and the
* digitized points.
*/
public static BezierPath fitBezierPath(BezierPath digitizedPoints, double error) {
ArrayList<Point2D.Double> d = new ArrayList<>(digitizedPoints.size());
for (BezierPath.Node n : digitizedPoints) {
d.add(new Point2D.Double(n.x[0], n.y[0]));
}
return fitBezierPath(d, error);
}
/**
* Removes points which are closer together than the specified minimal
* distance.
* <p>
* The minimal distance should be chosen dependent on the size and resolution of the
* display device, and on the sampling rate. A good value for mouse input
* on a display with 100% Zoom factor is 2.
* <p>
* The purpose of this method, is to remove points, which add no additional
* information about the shape of the curve from the list of digitized points.
* <p>
* The cleaned up set of digitized points gives better results, when used
* as input for method {@link #splitAtCorners}.
*
* @param digitizedPoints Digitized points
* @param minDistance minimal distance between two points. If minDistance is
* 0, this method only removes sequences of coincident points.
* @return Digitized points with a minimal distance.
*/
public static ArrayList<Point2D.Double> removeClosePoints(java.util.List<Point2D.Double> digitizedPoints, double minDistance) {
if (minDistance == 0) {
return removeCoincidentPoints(digitizedPoints);
} else {
double squaredDistance = minDistance * minDistance;
java.util.ArrayList<Point2D.Double> cleaned = new ArrayList<>();
if (digitizedPoints.size() > 0) {
Point2D.Double prev = digitizedPoints.get(0);
cleaned.add(prev);
for (Point2D.Double p : digitizedPoints) {
if (v2SquaredDistanceBetween2Points(prev, p) > squaredDistance) {
cleaned.add(p);
prev = p;
}
}
if (!prev.equals(digitizedPoints.get(digitizedPoints.size() - 1))) {
cleaned.set(cleaned.size() - 1, digitizedPoints.get(digitizedPoints.size() - 1));
}
}
return cleaned;
}
}
/**
* Removes sequences of coincident points.
* <p>
* The purpose of this method, is to clean up a list of digitized points
* for later processing using method {@link #splitAtCorners}.
* <p>
* Use this method only, if you know that the digitized points contain no
* quantization errors - which is never the case, unless you want to debug
* the curve fitting algorithm of this class.
*
* @param digitizedPoints Digitized points
* @return Digitized points without subsequent duplicates.
*/
private static ArrayList<Point2D.Double> removeCoincidentPoints(java.util.List<Point2D.Double> digitizedPoints) {
java.util.ArrayList<Point2D.Double> cleaned = new ArrayList<>();
if (digitizedPoints.size() > 0) {
Point2D.Double prev = digitizedPoints.get(0);
cleaned.add(prev);
for (Point2D.Double p : digitizedPoints) {
if (!prev.equals(p)) {
cleaned.add(p);
prev = p;
}
}
}
return cleaned;
}
/**
* Splits the digitized points into multiple segments at each corner point.
* <p>
* Corner points are both contained as the last point of a segment and
* the first point of a subsequent segment.
*
* @param digitizedPoints Digitized points
* @param maxAngle maximal angle in radians between the current point and its
* predecessor and successor up to which the point does not break the
* digitized list into segments. Recommended value 44° = 44 * 180d / Math.PI
* @return Segments of digitized points, each segment having less than maximal
* angle between points.
*/
public static ArrayList<ArrayList<Point2D.Double>> splitAtCorners(java.util.List<Point2D.Double> digitizedPoints, double maxAngle, double minDistance) {
ArrayList<Integer> cornerIndices = findCorners(digitizedPoints, maxAngle, minDistance);
ArrayList<ArrayList<Point2D.Double>> segments = new ArrayList<>(cornerIndices.size() + 1);
if (cornerIndices.size() == 0) {
segments.add(new ArrayList<>(digitizedPoints));
} else {
segments.add(new ArrayList<>(digitizedPoints.subList(0, cornerIndices.get(0) + 1)));
for (int i = 1; i < cornerIndices.size(); i++) {
segments.add(new ArrayList<>(digitizedPoints.subList(cornerIndices.get(i - 1), cornerIndices.get(i) + 1)));
}
segments.add(new ArrayList<>(digitizedPoints.subList(cornerIndices.get(cornerIndices.size() - 1), digitizedPoints.size())));
}
return segments;
}
/**
* Finds corners in the provided point list, and returns their indices.
*
* @param digitizedPoints List of digitized points.
* @param minAngle Minimal angle for corner points
* @param minDistance Minimal distance between a point and adjacent points
* for corner detection
* @return list of corner indices.
*/
public static ArrayList<Integer> findCorners(java.util.List<Point2D.Double> digitizedPoints, double minAngle, double minDistance) {
ArrayList<Integer> cornerIndices = new ArrayList<>();
double squaredDistance = minDistance * minDistance;
int previousCorner = -1;
double previousCornerAngle = 0;
for (int i = 1, n = digitizedPoints.size(); i < n - 1; i++) {
Point2D.Double p = digitizedPoints.get(i);
// search for a preceding point for corner detection
Point2D.Double prev = null;
boolean intersectsPreviousCorner = false;
for (int j = i - 1; j >= 0; j--) {
if (j == previousCorner || v2SquaredDistanceBetween2Points(digitizedPoints.get(j), p) >= squaredDistance) {
prev = digitizedPoints.get(j);
intersectsPreviousCorner = j < previousCorner;
break;
}
}
if (prev == null) {
continue;
}
// search for a succeeding point for corner detection
Point2D.Double next = null;
for (int j = i + 1; j < n; j++) {
if (v2SquaredDistanceBetween2Points(digitizedPoints.get(j), p) >= squaredDistance) {
next = digitizedPoints.get(j);
break;
}
}
if (next == null) {
continue;
}
double aPrev = Math.atan2(prev.y - p.y, prev.x - p.x);
double aNext = Math.atan2(next.y - p.y, next.x - p.x);
double angle = Math.abs(aPrev - aNext);
if (angle < Math.PI - minAngle || angle > Math.PI + minAngle) {
if (intersectsPreviousCorner) {
cornerIndices.set(cornerIndices.size() - 1, i);
} else {
cornerIndices.add(i);
}
previousCorner = i;
previousCornerAngle = angle;
}
}
return cornerIndices;
}
/**
* Reduces noise from the digitized points, by applying an approximation
* of a gaussian filter to the data.
* <p>
* The filter does the following for each point P, with weight 0.5:
* <p>
* x[i] = 0.5*x[i] + 0.25*x[i-1] + 0.25*x[i+1];
* y[i] = 0.5*y[i] + 0.25*y[i-1] + 0.25*y[i+1];
*
*
*
* @param digitizedPoints Digitized points
* @param weight Weight of the current point
* @return Digitized points with reduced noise.
*/
public static ArrayList<Point2D.Double> reduceNoise(java.util.List<Point2D.Double> digitizedPoints, double weight) {
java.util.ArrayList<Point2D.Double> cleaned = new ArrayList<>();
if (digitizedPoints.size() > 0) {
Point2D.Double prev = digitizedPoints.get(0);
cleaned.add(prev);
double pnWeight = (1d - weight) / 2d; // weight of previous and next
for (int i = 1, n = digitizedPoints.size() - 1; i < n; i++) {
Point2D.Double cur = digitizedPoints.get(i);
Point2D.Double next = digitizedPoints.get(i + 1);
cleaned.add(new Point2D.Double(
cur.x * weight + pnWeight * prev.x + pnWeight * next.x,
cur.y * weight + pnWeight * prev.y + pnWeight * next.y));
prev = cur;
}
if (digitizedPoints.size() > 1) {
cleaned.add(digitizedPoints.get(digitizedPoints.size() - 1));
}
}
return cleaned;
}
/**
* Fit one or multiple subsequent cubic bezier curves to a (sub)set of
* digitized points. The digitized points represent a smooth curve without
* corners.
*
* @param d Array of digitized points. Must not contain subsequent
* coincident points.
* @param first Indice of first point in d.
* @param last Indice of last point in d.
* @param tHat1 Unit tangent vectors at start point.
* @param tHat2 Unit tanget vector at end point.
* @param errorSquared User-defined errorSquared squared.
* @param bezierPath Path to which the bezier curve segments are added.
*/
private static void fitCubic(ArrayList<Point2D.Double> d, int first, int last,
Point2D.Double tHat1, Point2D.Double tHat2,
double errorSquared, BezierPath bezierPath) {
Point2D.Double[] bezCurve; /*Control points of fitted Bezier curve*/
double[] u; /* Parameter values for point */
double maxError; /* Maximum fitting errorSquared */
int[] splitPoint = new int[1]; /* Point to split point set at.
This is an array of size one, because we need it as an input/output parameter.
*/
int nPts; /* Number of points in subset */
double iterationError; /* Error below which you try iterating */
int maxIterations = 4; /* Max times to try iterating */
Point2D.Double tHatCenter; /* Unit tangent vector at splitPoint */
int i;
// clone unit tangent vectors, so that we can alter their coordinates
// without affecting the input values.
tHat1 = (Point2D.Double) tHat1.clone();
tHat2 = (Point2D.Double) tHat2.clone();
iterationError = errorSquared * errorSquared;
nPts = last - first + 1;
/* Use heuristic if region only has two points in it */
if (nPts == 2) {
double dist = v2DistanceBetween2Points(d.get(last), d.get(first)) / 3.0;
bezCurve = new Point2D.Double[4];
for (i = 0; i < bezCurve.length; i++) {
bezCurve[i] = new Point2D.Double();
}
bezCurve[0] = d.get(first);
bezCurve[3] = d.get(last);
v2Add(bezCurve[0], v2Scale(tHat1, dist), bezCurve[1]);
v2Add(bezCurve[3], v2Scale(tHat2, dist), bezCurve[2]);
bezierPath.curveTo(
bezCurve[1].x, bezCurve[1].y,
bezCurve[2].x, bezCurve[2].y,
bezCurve[3].x, bezCurve[3].y);
return;
}
/* Parameterize points, and attempt to fit curve */
u = chordLengthParameterize(d, first, last);
bezCurve = generateBezier(d, first, last, u, tHat1, tHat2);
/* Find max deviation of points to fitted curve */
maxError = computeMaxError(d, first, last, bezCurve, u, splitPoint);
if (maxError < errorSquared) {
addCurveTo(bezCurve, bezierPath, errorSquared, first == 0 && last == d.size() - 1);
return;
}
/* If errorSquared not too large, try some reparameterization */
/* and iteration */
if (maxError < iterationError) {
double[] uPrime; /* Improved parameter values */
for (i = 0; i < maxIterations; i++) {
uPrime = reparameterize(d, first, last, u, bezCurve);
bezCurve = generateBezier(d, first, last, uPrime, tHat1, tHat2);
maxError = computeMaxError(d, first, last, bezCurve, uPrime, splitPoint);
if (maxError < errorSquared) {
addCurveTo(bezCurve, bezierPath, errorSquared, first == 0 && last == d.size() - 1);
return;
}
u = uPrime;
}
}
/* Fitting failed -- split at max errorSquared point and fit recursively */
tHatCenter = computeCenterTangent(d, splitPoint[0]);
if (first < splitPoint[0]) {
fitCubic(d, first, splitPoint[0], tHat1, tHatCenter, errorSquared, bezierPath);
} else {
bezierPath.lineTo(d.get(splitPoint[0]).x, d.get(splitPoint[0]).y);
// System.err.println("Can't split any further " + first + ".." + splitPoint[0]);
}
v2Negate(tHatCenter);
if (splitPoint[0] < last) {
fitCubic(d, splitPoint[0], last, tHatCenter, tHat2, errorSquared, bezierPath);
} else {
bezierPath.lineTo(d.get(last).x, d.get(last).y);
// System.err.println("Can't split any further " + splitPoint[0] + ".." + last);
}
}
/**
* Adds the curve to the bezier path.
*
* @param bezCurve
* @param bezierPath
*/
private static void addCurveTo(Point2D.Double[] bezCurve, BezierPath bezierPath, double errorSquared, boolean connectsCorners) {
BezierPath.Node lastNode = bezierPath.get(bezierPath.size() - 1);
double error = Math.sqrt(errorSquared);
if (connectsCorners && Geom.lineContainsPoint(lastNode.x[0], lastNode.y[0], bezCurve[3].x, bezCurve[3].y, bezCurve[1].x, bezCurve[1].y, error) &&
Geom.lineContainsPoint(lastNode.x[0], lastNode.y[0], bezCurve[3].x, bezCurve[3].y, bezCurve[2].x, bezCurve[2].y, error)) {
bezierPath.lineTo(
bezCurve[3].x, bezCurve[3].y);
} else {
bezierPath.curveTo(
bezCurve[1].x, bezCurve[1].y,
bezCurve[2].x, bezCurve[2].y,
bezCurve[3].x, bezCurve[3].y);
}
}
/**
* Approximate unit tangents at "left" endpoint of digitized curve.
*
* @param d Digitized points.
* @param end Index to "left" end of region.
*/
private static Point2D.Double computeLeftTangent(ArrayList<Point2D.Double> d, int end) {
Point2D.Double tHat1;
tHat1 = v2SubII(d.get(end + 1), d.get(end));
tHat1 = v2Normalize(tHat1);
return tHat1;
}
/**
* Approximate unit tangents at "right" endpoint of digitized curve.
*
* @param d Digitized points.
* @param end Index to "right" end of region.
*/
private static Point2D.Double computeRightTangent(ArrayList<Point2D.Double> d, int end) {
Point2D.Double tHat2;
tHat2 = v2SubII(d.get(end - 1), d.get(end));
tHat2 = v2Normalize(tHat2);
return tHat2;
}
/**
* Approximate unit tangents at "center" of digitized curve.
*
* @param d Digitized points.
* @param center Index to "center" end of region.
*/
private static Point2D.Double computeCenterTangent(ArrayList<Point2D.Double> d, int center) {
Point2D.Double V1, V2,
tHatCenter = new Point2D.Double();
V1 = v2SubII(d.get(center - 1), d.get(center));
V2 = v2SubII(d.get(center), d.get(center + 1));
tHatCenter.x = (V1.x + V2.x) / 2.0;
tHatCenter.y = (V1.y + V2.y) / 2.0;
tHatCenter = v2Normalize(tHatCenter);
return tHatCenter;
}
/**
* Assign parameter values to digitized points
* using relative distances between points.
*
* @param d Digitized points.
* @param first Indice of first point of region in d.
* @param last Indice of last point of region in d.
*/
private static double[] chordLengthParameterize(ArrayList<Point2D.Double> d, int first, int last) {
int i;
double[] u; /* Parameterization */
u = new double[last - first + 1];
u[0] = 0.0;
for (i = first + 1; i <= last; i++) {
u[i - first] = u[i - first - 1] +
v2DistanceBetween2Points(d.get(i), d.get(i - 1));
}
for (i = first + 1; i <= last; i++) {
u[i - first] = u[i - first] / u[last - first];
}
return (u);
}
/**
* Given set of points and their parameterization, try to find
* a better parameterization.
*
* @param d Array of digitized points.
* @param first Indice of first point of region in d.
* @param last Indice of last point of region in d.
* @param u Current parameter values.
* @param bezCurve Current fitted curve.
*/
private static double[] reparameterize(ArrayList<Point2D.Double> d, int first, int last, double[] u, Point2D.Double[] bezCurve) {
int nPts = last - first + 1;
int i;
double[] uPrime; /* New parameter values */
uPrime = new double[nPts];
for (i = first; i <= last; i++) {
uPrime[i - first] = newtonRaphsonRootFind(bezCurve, d.get(i), u[i - first]);
}
return (uPrime);
}
/**
* Use Newton-Raphson iteration to find better root.
*
* @param Q Current fitted bezier curve.
* @param P Digitized point.
* @param u Parameter value vor P.
*/
private static double newtonRaphsonRootFind(Point2D.Double[] Q, Point2D.Double P, double u) {
double numerator, denominator;
Point2D.Double[] Q1 = new Point2D.Double[3], Q2 = new Point2D.Double[2]; /* Q' and Q'' */
Point2D.Double Q_u, Q1_u, Q2_u; /*u evaluated at Q, Q', & Q'' */
double uPrime; /* Improved u */
int i;
/* Compute Q(u) */
Q_u = bezierII(3, Q, u);
/* Generate control vertices for Q' */
for (i = 0; i <= 2; i++) {
Q1[i] = new Point2D.Double(
(Q[i + 1].x - Q[i].x) * 3.0,
(Q[i + 1].y - Q[i].y) * 3.0);
}
/* Generate control vertices for Q'' */
for (i = 0; i <= 1; i++) {
Q2[i] = new Point2D.Double(
(Q1[i + 1].x - Q1[i].x) * 2.0,
(Q1[i + 1].y - Q1[i].y) * 2.0);
}
/* Compute Q'(u) and Q''(u) */
Q1_u = bezierII(2, Q1, u);
Q2_u = bezierII(1, Q2, u);
/* Compute f(u)/f'(u) */
numerator = (Q_u.x - P.x) * (Q1_u.x) + (Q_u.y - P.y) * (Q1_u.y);
denominator = (Q1_u.x) * (Q1_u.x) + (Q1_u.y) * (Q1_u.y) +
(Q_u.x - P.x) * (Q2_u.x) + (Q_u.y - P.y) * (Q2_u.y);
/* u = u - f(u)/f'(u) */
uPrime = u - (numerator / denominator);
return (uPrime);
}
/**
* Find the maximum squared distance of digitized points
* to fitted curve.
*
* @param d Digitized points.
* @param first Indice of first point of region in d.
* @param last Indice of last point of region in d.
* @param bezCurve Fitted Bezier curve
* @param u Parameterization of points*
* @param splitPoint Point of maximum error (input/output parameter, must be
* an array of 1)
*/
private static double computeMaxError(ArrayList<Point2D.Double> d, int first, int last, Point2D.Double[] bezCurve, double[] u, int[] splitPoint) {
int i;
double maxDist; /* Maximum error */
double dist; /* Current error */
Point2D.Double P; /* Point on curve */
Point2D.Double v; /* Vector from point to curve */
splitPoint[0] = (last - first + 1) / 2;
maxDist = 0.0;
for (i = first + 1; i < last; i++) {
P = bezierII(3, bezCurve, u[i - first]);
v = v2SubII(P, d.get(i));
dist = v2SquaredLength(v);
if (dist >= maxDist) {
maxDist = dist;
splitPoint[0] = i;
}
}
return (maxDist);
}
/**
* Use least-squares method to find Bezier control points for region.
*
* @param d Array of digitized points.
* @param first Indice of first point in d.
* @param last Indice of last point in d.
* @param uPrime Parameter values for region .
* @param tHat1 Unit tangent vectors at start point.
* @param tHat2 Unit tanget vector at end point.
* @return A cubic bezier curve consisting of 4 control points.
*/
private static Point2D.Double[] generateBezier(ArrayList<Point2D.Double> d, int first, int last, double[] uPrime, Point2D.Double tHat1, Point2D.Double tHat2) {
Point2D.Double[] bezCurve;
bezCurve = new Point2D.Double[4];
for (int i = 0; i < bezCurve.length; i++) {
bezCurve[i] = new Point2D.Double();
}
/* Use the Wu/Barsky heuristic*/
double dist = v2DistanceBetween2Points(d.get(last), d.get(first)) / 3.0;
bezCurve[0] = d.get(first);
bezCurve[3] = d.get(last);
v2Add(bezCurve[0], v2Scale(tHat1, dist), bezCurve[1]);
v2Add(bezCurve[3], v2Scale(tHat2, dist), bezCurve[2]);
return (bezCurve);
}
/**
* Evaluate a Bezier curve at a particular parameter value.
*
* @param degree The degree of the bezier curve.
* @param V Array of control points.
* @param t Parametric value to find point for.
*/
private static Point2D.Double bezierII(int degree, Point2D.Double[] V, double t) {
int i, j;
Point2D.Double q; /* Point on curve at parameter t */
Point2D.Double[] vTemp; /* Local copy of control points */
/* Copy array */
vTemp = new Point2D.Double[degree + 1];
for (i = 0; i <= degree; i++) {
vTemp[i] = (Point2D.Double) V[i].clone();
}
/* Triangle computation */
for (i = 1; i <= degree; i++) {
for (j = 0; j <= degree - i; j++) {
vTemp[j].x = (1.0 - t) * vTemp[j].x + t * vTemp[j + 1].x;
vTemp[j].y = (1.0 - t) * vTemp[j].y + t * vTemp[j + 1].y;
}
}
q = vTemp[0];
return q;
}
/* -------------------------------------------------------------------------
* GraphicsGems.c
* 2d and 3d Vector C Library
* by Andrew Glassner
* from "Graphics Gems", Academic Press, 1990
* -------------------------------------------------------------------------
*/
/**
* Return the distance between two points
*/
private static double v2DistanceBetween2Points(Point2D.Double a, Point2D.Double b) {
return Math.sqrt(v2SquaredDistanceBetween2Points(a, b));
}
/**
* Return the distance between two points
*/
private static double v2SquaredDistanceBetween2Points(Point2D.Double a, Point2D.Double b) {
double dx = a.x - b.x;
double dy = a.y - b.y;
return (dx * dx) + (dy * dy);
}
/**
* Scales the input vector to the new length and returns it.
* <p>
* This method alters the value of the input point!
*/
private static Point2D.Double v2Scale(Point2D.Double v, double newlen) {
double len = v2Length(v);
if (len != 0.0) {
v.x *= newlen / len;
v.y *= newlen / len;
}
return v;
}
/**
* Scales the input vector by the specified factor and returns it.
* <p>
* This method alters the value of the input point!
*/
private static Point2D.Double v2ScaleIII(Point2D.Double v, double s) {
Point2D.Double result = new Point2D.Double();
result.x = v.x * s;
result.y = v.y * s;
return result;
}
/**
* Returns length of input vector.
*/
private static double v2Length(Point2D.Double a) {
return Math.sqrt(v2SquaredLength(a));
}
/**
* Returns squared length of input vector.
*/
private static double v2SquaredLength(Point2D.Double a) {
return (a.x * a.x) + (a.y * a.y);
}
/**
* Return vector sum c = a+b.
* <p>
* This method alters the value of c.
*/
private static Point2D.Double v2Add(Point2D.Double a, Point2D.Double b, Point2D.Double c) {
c.x = a.x + b.x;
c.y = a.y + b.y;
return c;
}
/**
* Return vector sum = a+b.
*/
private static Point2D.Double v2AddII(Point2D.Double a, Point2D.Double b) {
Point2D.Double c = new Point2D.Double();
c.x = a.x + b.x;
c.y = a.y + b.y;
return c;
}
/**
* Negates the input vector and returns it.
*/
private static Point2D.Double v2Negate(Point2D.Double v) {
v.x = -v.x;
v.y = -v.y;
return v;
}
/**
* Return the dot product of vectors a and b.
*/
private static double v2Dot(Point2D.Double a, Point2D.Double b) {
return (a.x * b.x) + (a.y * b.y);
}
/**
* Normalizes the input vector and returns it.
*/
private static Point2D.Double v2Normalize(Point2D.Double v) {
double len = v2Length(v);
if (len != 0.0) {
v.x /= len;
v.y /= len;
}
return v;
}
/**
* Subtract Vector a from Vector b.
*
* @param a Vector a - the value is not changed by this method
* @param b Vector b - the value is not changed by this method
* @return Vector a subtracted by Vector v.
*/
private static Point2D.Double v2SubII(Point2D.Double a, Point2D.Double b) {
Point2D.Double c = new Point2D.Double();
c.x = a.x - b.x;
c.y = a.y - b.y;
return (c);
}
/**
* B0, B1, B2, B3 :
* Bezier multipliers
*/
private static double b0(double u) {
double tmp = 1.0 - u;
return (tmp * tmp * tmp);
}
private static double b1(double u) {
double tmp = 1.0 - u;
return (3 * u * (tmp * tmp));
}
private static double b2(double u) {
double tmp = 1.0 - u;
return (3 * u * u * tmp);
}
private static double b3(double u) {
return (u * u * u);
}
}