package org.andengine.util; import org.andengine.util.math.MathUtils; /** * (c) 2013 Nicolas Gramlich * * @author Nicolas Gramlich * @since 23:09:15 - 01.06.2013 * * @see <a href="http://en.wikipedia.org/wiki/Bernstein_polynomial">Bézier curve (Wikipedia)</a> * @see <a href="http://www.cs.mtu.edu/~shene/COURSES/cs3621/NOTES/spline/Bezier/bezier-der.html">Derivatives of a Bézier Curve (Michigan Tech University)</a> * @see <a href="http://www.iaeng.org/IJAM/issues_v40/issue_2/IJAM_40_2_07.pdf">Continuous Curvature Path Generation Based on Bezier Curves for Autonomous Vehicles (Choi, Curry, Elkaim)</a> * @see <a href="http://www.whizkidtech.redprince.net/bezier/">Bézier Curves (Stanislav)</a> * @see <a href="http://www.cad.zju.edu.cn/home/hwlin/Curvature-of-singular-Bezier-curves-and-surfaces.pdf">Curvature of singular Bézier curves and surfaces (Zhejiang University)</a> * @see <a href="http://en.wikipedia.org/wiki/Bernstein_polynomial">Bernstein polynomial (Wikipedia)</a> */ public final class BezierCurveUtils { // =========================================================== // Constants // =========================================================== private static final int LENGTH_SAMPLES_DEFAULT = 10; private static final float[] COORDINATES_TMP = new float[2]; // =========================================================== // Fields // =========================================================== // =========================================================== // Constructors // =========================================================== private BezierCurveUtils() { } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods for/from SuperClass/Interfaces // =========================================================== // =========================================================== // Methods // =========================================================== /** * Calculates the (x|y|z|...)-coordinate for the given control point (x|y|z|...)-coordinates at value <code>t</code>. * * @param t in the interval <code>[0, 1]</code>. * @param pControlPointCoordinates (x|y|z|...)-coordinates of control points of the Bézier curve. * @return */ public static final float getBezierCurveCoordinate(final float t, final float[] pControlPointCoordinates) { float result = 0; final int n = pControlPointCoordinates.length - 1; for (int i = 0; i <= n; i++) { result += pControlPointCoordinates[i] * BezierCurveUtils.getBernsteinPolynomial(t, i, n); } return result; } /** * Calculates the x/y-coordinate for the given control point x/y-coordinates at value <code>t</code>. * * @param t in the interval <code>[0, 1]</code>. * @param pXs x-coordinates of the control points of the Bézier curve. * @param pYs y-coordinates of the control points of the Bézier curve. * @return a shared(!) float[] of length 2. */ public static final float[] getBezierCurveCoordinates(final float t, final float[] pXs, final float[] pYs) { return BezierCurveUtils.getBezierCurveCoordinates(t, pXs, pYs, COORDINATES_TMP); } /** * Calculates the x/y-coordinate for the given control point x/y-coordinates at value <code>t</code>. * * @param t in the interval <code>[0, 1]</code>. * @param pXs x-coordinates of the control points of the Bézier curve. * @param pYs y-coordinates of the control points of the Bézier curve. * @param pReuse must be of length 2. * @return <code>pReuse</code> as a convenience. */ public static final float[] getBezierCurveCoordinates(final float t, final float[] pXs, final float[] pYs, final float[] pReuse) { final int n = pXs.length - 1; pReuse[Constants.VERTEX_INDEX_X] = 0; pReuse[Constants.VERTEX_INDEX_Y] = 0; for (int i = 0; i <= n; i++) { final float bernsteinPolynomial = BezierCurveUtils.getBernsteinPolynomial(t, i, n); pReuse[Constants.VERTEX_INDEX_X] += pXs[i] * bernsteinPolynomial; pReuse[Constants.VERTEX_INDEX_Y] += pYs[i] * bernsteinPolynomial; } return pReuse; } /** * Calculates the length of a Bézier curve by taking {@link #LENGTH_SAMPLES_DEFAULT} samples. * * @param pXs x-coordinates of the control points of the Bézier curve. * @param pYs y-coordinates of the control points of the Bézier curve. */ public static final float getBezierCurveLength(final float[] pXs, final float[] pYs) { return BezierCurveUtils.getBezierCurveLength(pXs, pYs, LENGTH_SAMPLES_DEFAULT); } /** * Calculates the length of a Bézier curve by taking <code>pSamples</code> samples. * * @param pXs x-coordinates of the control points of the Bézier curve. * @param pYs y-coordinates of the control points of the Bézier curve. * @param pSamples the number of samples to take. The higher the more accurate. * @return */ public static final float getBezierCurveLength(final float[] pXs, final float[] pYs, final int pSamples) { float length = 0; final int n = pXs.length - 1; float lastX = pXs[0]; float lastY = pYs[0]; for (int k = 1; k <= LENGTH_SAMPLES_DEFAULT; k++) { final float t = (1f * k) / LENGTH_SAMPLES_DEFAULT; float x = 0; float y = 0; for (int i = 0; i <= n; i++) { final float bernstein = BezierCurveUtils.getBernsteinPolynomial(t, i, n); x += pXs[i] * bernstein; y += pYs[i] * bernstein; } length += MathUtils.distance(lastX, lastY, x, y); lastX = x; lastY = y; } return length; } /** * Calculates the curvature for the given control points of a Bézier curve at value <code>t</code>. * * @param t in the interval <code>[0, 1]</code>. * @param pXs x-coordinates of the control points of the Bézier curve. * @param pYs y-coordinates of the control points of the Bézier curve. */ public static final float getBezierCurveCurvature(final float t, final float[] pXs, final float[] pYs) { final float dxt = BezierCurveUtils.getBezierCurveDerivativeValue(t, 1, pXs); final float dyt = BezierCurveUtils.getBezierCurveDerivativeValue(t, 1, pYs); final float ddxt = BezierCurveUtils.getBezierCurveDerivativeValue(t, 2, pXs); final float ddyt = BezierCurveUtils.getBezierCurveDerivativeValue(t, 2, pYs); return BezierCurveUtils.getCurvature(dxt, dyt, ddxt, ddyt); } /** * Calculates the value of the <code>k</code>th derivative of given control point (x|y|z|...)-coordinates of a Bézier curve at value <code>t</code>. * * @param t in the interval <code>[0, 1]</code>. * @param k the <code>k</code>th derivative. * @param pControlPointCoordinates (x|y|z|...)-coordinates of the Bézier curve. * @return */ public static final float getBezierCurveDerivativeValue(final float t, final int k, final float[] pControlPointCoordinates) { final int n = pControlPointCoordinates.length - 1; float result = 1; for (int i = n; i > n - k; i--) { result *= i; } float sum = 0; for (int i = 0; i <= n - k; i++) { final float bernstein = BezierCurveUtils.getBernsteinPolynomial(t, i, n - k); final float d = BezierCurveUtils.getLevelDifference(i, k, pControlPointCoordinates); sum += bernstein * d; } result *= sum; return result; } private static float getCurvature(final float dxt, final float dyt, final float ddxt, final float ddyt) { return Math.abs(dxt * ddyt - dyt * ddxt) / (float) Math.pow(dxt * dxt + dyt * dyt, 3f / 2f); } private static final float getBernsteinPolynomial(final float x, final int i, final int n) { return ((float) MathUtils.factorial(n) / (float) (MathUtils.factorial(i) * MathUtils.factorial(n - i))) * (float) Math.pow(x, i) * (float) Math.pow(1 - x, n - i); } private static float getLevelDifference(final int i, final int level, final float[] pControlPointCoordinates) { if (level == 0) { return pControlPointCoordinates[i]; } else { return BezierCurveUtils.getLevelDifference(i + 1, level - 1, pControlPointCoordinates) - BezierCurveUtils.getLevelDifference(i, level - 1, pControlPointCoordinates); } } // =========================================================== // Inner and Anonymous Classes // =========================================================== }