/* @file BezierCurve.java * * @author marco corvi * @date nov 2011 * * @brief TopoDroid cubic bezier curve (spline) * -------------------------------------------------------- * Copyright This sowftare is distributed under GPL-3.0 or later * See the file COPYING. * -------------------------------------------------------- * CHANGES */ package com.topodroid.DistoX; import java.util.ArrayList; class BezierCurve { private BezierPoint c[]; // control points of the cubic spline private BezierPoint Vtemp[]; // work vector of four points private int splitIndex; // Point of split (criteria: maximum error) public BezierCurve() { c = new BezierPoint[4]; Vtemp = new BezierPoint[4]; for (int i=0; i<4; ++i ) { c[i] = new BezierPoint(); Vtemp[i] = new BezierPoint(); } splitIndex = -1; } public BezierCurve( BezierPoint c0, BezierPoint c1, BezierPoint c2, BezierPoint c3 ) { c = new BezierPoint[4]; Vtemp = new BezierPoint[4]; c[0] = new BezierPoint( c0 ); c[1] = new BezierPoint( c1 ); c[2] = new BezierPoint( c2 ); c[3] = new BezierPoint( c3 ); for (int i=0; i<4; ++i ) { Vtemp[i] = new BezierPoint(); } splitIndex = -1; } // control points public void setPoint(int k, BezierPoint p ) { c[k].set(p); } public BezierPoint getPoint( int k ) { return c[k]; } public int getSplitIndex() { return splitIndex; } /** ComputeMaxError: Find max squared distance of digitized points to fitted curve. d; Array of digitized points first, last; Indices defining region bezCurve; Fitted Bezier curve u; Parameterization of points */ public float computeMaxError( ArrayList<BezierPoint> d, int first, int last, float[] u ) { splitIndex = (last - first + 1)/2; float maxDist = 0.0f; for (int i = first + 1; i < last; i++) { BezierPoint P = evaluate( u[i-first] ); BezierPoint v = P.sub( d.get(i) ); // vector from point to curve float dist = v.squareLength(); if ( dist >= maxDist ) { maxDist = dist; splitIndex = i; } } return maxDist; } /** Reparameterize: Given set of points and their parameterization, * try to find a better parameterization. * * @param d Array of digitized points * @param first, last Indices defining region * @param u Current parameter values * @param bezCurve Current fitted curve */ public void reparameterize( ArrayList<BezierPoint> d, int first, int last, float[] u ) { // int nPts = last-first+1; // float[] uPrime = new float[ nPts ]; /* New parameter values */ for (int i = first; i <= last; i++) { // uPrime[i-first] = findRootNewtonRaphson( d[i], u[i-first] ); u[i-first] = findRootNewtonRaphson( d.get(i), u[i-first] ); } // return uPrime; } /** Bezier: Evaluate a Bezier curve at a particular parameter value * degree The degree of the bezier curve * t Parametric value to find point for */ public BezierPoint evaluate( float t ) { return evaluate(3, c, t ); } private BezierPoint evaluate( int degree, BezierPoint[] V, float t ) { float t1 = 1.0f - t; for (int i = 0; i <= degree; i++) { // copy array Vtemp[i].set( V[i] ); } for (int i = 1; i <= degree; i++) { // triangle computation for (int j = 0; j <= degree-i; j++) { Vtemp[j].mX = t1 * Vtemp[j].mX + t * Vtemp[j+1].mX; Vtemp[j].mY = t1 * Vtemp[j].mY + t * Vtemp[j+1].mY; } } return Vtemp[0]; } /** findRootNewtonRaphson: Use Newton-Raphson iteration to find better root. P Digitized point u Parameter value for "P" */ private float findRootNewtonRaphson( BezierPoint P, float u) { BezierPoint Q1[] = new BezierPoint[3]; // Q' BezierPoint Q2[] = new BezierPoint[2]; // Q" /* Generate control vertices for Q' */ for (int i = 0; i < 3; i++) { Q1[i] = c[i+1].sub( c[i] ).times( 3.0f ); } /* Generate control vertices for Q'' */ for (int i = 0; i < 2; i++) { Q2[i] = Q1[i+1].sub( Q1[i] ).times( 2.0f ); } BezierPoint Q_u = evaluate(u); // Compute Q(u) BezierPoint Q1_u = evaluate(2, Q1, u); // Q'(u) BezierPoint Q2_u = evaluate(1, Q2, u); // Q"(u) /* Compute f(u)/f'(u) */ float num = (Q_u.mX - P.mX) * (Q1_u.mX) + (Q_u.mY - P.mY) * (Q1_u.mY); float den = (Q1_u.mX) * (Q1_u.mX) + (Q1_u.mY) * (Q1_u.mY) + (Q_u.mX - P.mX) * (Q2_u.mX) + (Q_u.mY - P.mY) * (Q2_u.mY); /* u = u - f(u)/f'(u) improved u */ return u - num / den; } }