/// <summary> /// Add parabola to the bezier /// </summary> /// <param name="data">In: Data points</param> /// <param name="from">In: The index of the parabola's first point</param> private void AddParabola(CuspData data, int from) { /* Denote s = 1-t. We construct the parabola with Bezier points A,B,C that * goes thru the point P at parameter value t, that is * P = s^2A + 2stB + t^2C * * We know A and C, and we solve for B: * B = (P - s^2A - t^2C) / 2st. * * Elevating the degree to cubic replaces B with 2 points, the first at * 2B/3 + A/3, and the second at 2B/3 + C/3. * * That is, one point at * (P/(st) - Ct/s + A(-s/t + 1)) / 3 * and the other point at * (P/(st) + C(-t/s + 1) - As/t) / 3 */ // By the way the nodes were constructed: //ASSERT(data.Node(from+2) - data.Node(from) > // data.Node(from+1) - data.Node(from)); double t = (data.Node(from + 1) - data.Node(from)) / (data.Node(from + 2) - data.Node(from)); double s = 1 - t; if (t < .001 || s < .001) { // A straight line will be a better approximation AddLine(data, from, from + 2); return; } double tt = 1 / t; double ss = 1 / s; const double third = 1.0d / 3.0d; Vector P = (tt * ss) * data.XY(from + 1); Vector B = third * (P + (1 - s * tt) * data.XY(from) - (t * ss) * data.XY(from + 1)); AddBezierPoint(B); B = third * (P - (s * tt) * data.XY(from) + (1 - t * ss) * data.XY(from + 2)); AddBezierPoint(B); AddSegmentPoint(data, from + 2); }
/// <summary> /// Checks whether five points are co-cubic within tolerance /// </summary> /// <param name="data">In: Data points</param> /// <param name="i">In: Array of 5 indices</param> /// <param name="fitError">In: tolerated error - squared</param> /// <returns>Return true if extended</returns> private static bool CoCubic(CuspData data, int[] i, double fitError) { /* Our error estimate is (t[4]-t[0])^4 times the 4th divided difference * of the points with resect to the nodes. The divided difference is * equal to Sum(c(i)*p[i]), where c(i)=Product(t[i]-t[j]: j != i) * (See Conte & deBoor's Elementary Numerical Analysis, Excercise 2.2-1). * We multiply each factor in the product by t[4]-t[0]. */ double d04 = data.Node(i[4]) - data.Node(i[0]); double d01 = d04 / (data.Node(i[1]) - data.Node(i[0])); double d02 = d04 / (data.Node(i[2]) - data.Node(i[0])); double d03 = d04 / (data.Node(i[3]) - data.Node(i[0])); double d12 = d04 / (data.Node(i[2]) - data.Node(i[1])); double d13 = d04 / (data.Node(i[3]) - data.Node(i[1])); double d14 = d04 / (data.Node(i[4]) - data.Node(i[1])); double d23 = d04 / (data.Node(i[3]) - data.Node(i[2])); double d24 = d04 / (data.Node(i[4]) - data.Node(i[2])); double d34 = d04 / (data.Node(i[4]) - data.Node(i[3])); Vector P = d01 * d02 * d03 * data.XY(i[0]) - d01 * d12 * d13 * d14 * data.XY(i[1]) + d02 * d12 * d23 * d24 * data.XY(i[2]) - d03 * d13 * d23 * d34 * data.XY(i[3]) + d14 * d24 * d34 * data.XY(i[4]); return((P * P) < fitError); }
/// <summary> /// Add least square fit curve to the bezier /// </summary> /// <param name="data">In: Data points</param> /// <param name="from">In: Index of the first point</param> /// <param name="V">In: Unit tangent vector at the start</param> /// <param name="to">In: Index of the last point, updated here</param> /// <param name="W">In: Unit tangent vector at the end</param> /// <returns>Return true segment added</returns> private bool AddLeastSquares(CuspData data, int from, ref Vector V, int to, ref Vector W) { /* To do: When there is a cusp at either one of the ends, we'll get a * better approximation if we use a construction without a prescribed * tangent there */ /* * The Bezier points of this segment are A, A+sV, B+uW, and B, where A,B are the * endpoints, and V,W are the end tangents. For the node tj, denote f0j=(1-tj)^3, * f1j=3(1-tj)^2tj, f2j=3(1-tj)tj^2, f3j=tj^3. Let Pj be the jth point. * We are lookig for s,u that minimize * Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)^2. * * Equate the partial derivatives of this w.r.t. s and u to 0: * Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)*(V*f1j)=0 * Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)*(W*f2j)=0 * hence * * s*Sum(V*V*f1j*f1j) + u*Sum(W*V*f1j*f2j)= -Sum(A*(f0j+f1j) + B*(f2j+f3j) - Pj)*V*f1j * s*Sum(V*W*f1j*f2j) + u*Sum(W*W*f2j*f2j)= -Sum(A*(f0j+f1j) + B*(f2j+f3j) - Pj)*W*f2j * * so the equations are * s*a11 + u*a12 = b1 * s*a12 * u*a22 = b2 * * with * a11 = W*W*Sum(f1j^2), a22 = V*V*Sum(f2j^2), a12 = W*V*Sum(f1j*f2j) * b1 = -V*A*Sum(f0j + f1j)*f1j - V*B*Sum(f2j + f3j)*f1j + Sum(f1j*Pj*V) * b2 = -W*A*Sum(f0j + f1j)*f2j - W*B*Sum(f2j + f3j)*f2j + Sum(f2j*Pj*W) * * V and W ae unit vectors, so V*V = W*W = 1. * For computational efficiency, we will break b1 and b2 into 3 sums each, and add * them up at the end * * The solution is * s = (b1*a22 - b2*a12) / det * u = (b2*a11 - b1*a12) / det * where det = a11*a22 - a22^2 */ // Compute the coefficients double a11 = 0, a12 = 0, a22 = 0, b1 = 0, b2 = 0; double b11 = 0, b12 = 0, b21 = 0, b22 = 0; for (int j = checked (from + 1); j < to; j++) { // By the way the nodes were constructed - Debug.Assert(data.Node(to) - data.Node(from) > data.Node(j) - data.Node(from)); double tj = (data.Node(j) - data.Node(from)) / (data.Node(to) - data.Node(from)); double tj2 = tj * tj; double rj = 1 - tj; double rj2 = rj * rj; double f0j = rj2 * rj; double f1j = 3 * rj2 * tj; double f2j = 3 * rj * tj2; double f3j = tj2 * tj; a11 += f1j * f1j; a22 += f2j * f2j; a12 += f1j * f2j; b11 -= (f0j + f1j) * f1j; b12 -= (f2j + f3j) * f1j; b1 += f1j * (data.XY(j) * V); b21 -= (f0j + f1j) * f2j; b22 -= (f2j + f3j) * f2j; b2 += f2j * (data.XY(j) * W); } a12 *= (V * W); b1 += ((V * data.XY(from)) * b11 + (V * data.XY(to)) * b12); b2 += ((W * data.XY(from)) * b21 + (W * data.XY(to)) * b22); // Solve the equations double s = b1 * a22 - b2 * a12; double u = b2 * a11 - b1 * a12; double det = a11 * a22 - a12 * a12; bool accept = (Math.Abs(det) > Math.Abs(s) * DoubleUtil.DBL_EPSILON && Math.Abs(det) > Math.Abs(u) * DoubleUtil.DBL_EPSILON); if (accept) { s /= det; u /= det; // We'll only accept large enough positive solutions accept = s > 1.0e-6 && u > 1.0e-6; } if (!accept) { s = u = (data.Node(to) - data.Node(from)) / 3; } AddBezierPoint(data.XY(from) + s * V); AddBezierPoint(data.XY(to) + u * W); AddSegmentPoint(data, to); return(true); }
/// <summary> /// Checks whether five points are co-cubic within tolerance /// </summary> /// <param name="data">In: Data points</param> /// <param name="i">In: Array of 5 indices</param> /// <param name="fitError">In: tolerated error - squared</param> /// <returns>Return true if extended</returns> private static bool CoCubic(CuspData data, int[] i, double fitError) { /* Our error estimate is (t[4]-t[0])^4 times the 4th divided difference * of the points with resect to the nodes. The divided difference is * equal to Sum(c(i)*p[i]), where c(i)=Product(t[i]-t[j]: j != i) * (See Conte & deBoor's Elementary Numerical Analysis, Excercise 2.2-1). * We multiply each factor in the product by t[4]-t[0]. */ double d04 = data.Node(i[4]) - data.Node(i[0]); double d01 = d04 / (data.Node(i[1]) - data.Node(i[0])); double d02 = d04 / (data.Node(i[2]) - data.Node(i[0])); double d03 = d04 / (data.Node(i[3]) - data.Node(i[0])); double d12 = d04 / (data.Node(i[2]) - data.Node(i[1])); double d13 = d04 / (data.Node(i[3]) - data.Node(i[1])); double d14 = d04 / (data.Node(i[4]) - data.Node(i[1])); double d23 = d04 / (data.Node(i[3]) - data.Node(i[2])); double d24 = d04 / (data.Node(i[4]) - data.Node(i[2])); double d34 = d04 / (data.Node(i[4]) - data.Node(i[3])); Vector P = d01 * d02 * d03 * data.XY(i[0]) - d01 * d12 * d13 * d14 * data.XY(i[1]) + d02 * d12 * d23 * d24 * data.XY(i[2]) - d03 * d13 * d23 * d34 * data.XY(i[3]) + d14 * d24 * d34 * data.XY(i[4]); return ((P * P) < fitError); }
/// <summary> /// Add least square fit curve to the bezier /// </summary> /// <param name="data">In: Data points</param> /// <param name="from">In: Index of the first point</param> /// <param name="V">In: Unit tangent vector at the start</param> /// <param name="to">In: Index of the last point, updated here</param> /// <param name="W">In: Unit tangent vector at the end</param> /// <returns>Return true segment added</returns> private bool AddLeastSquares(CuspData data, int from, ref Vector V, int to, ref Vector W) { /* To do: When there is a cusp at either one of the ends, we'll get a better approximation if we use a construction without a prescribed tangent there */ /* The Bezier points of this segment are A, A+sV, B+uW, and B, where A,B are the endpoints, and V,W are the end tangents. For the node tj, denote f0j=(1-tj)^3, f1j=3(1-tj)^2tj, f2j=3(1-tj)tj^2, f3j=tj^3. Let Pj be the jth point. We are lookig for s,u that minimize Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)^2. Equate the partial derivatives of this w.r.t. s and u to 0: Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)*(V*f1j)=0 Sum(A*f0j + (A+sV)*f1j + (B+uW)*f2j + B*f3j - Pj)*(W*f2j)=0 hence s*Sum(V*V*f1j*f1j) + u*Sum(W*V*f1j*f2j)= -Sum(A*(f0j+f1j) + B*(f2j+f3j) - Pj)*V*f1j s*Sum(V*W*f1j*f2j) + u*Sum(W*W*f2j*f2j)= -Sum(A*(f0j+f1j) + B*(f2j+f3j) - Pj)*W*f2j so the equations are s*a11 + u*a12 = b1 s*a12 * u*a22 = b2 with a11 = W*W*Sum(f1j^2), a22 = V*V*Sum(f2j^2), a12 = W*V*Sum(f1j*f2j) b1 = -V*A*Sum(f0j + f1j)*f1j - V*B*Sum(f2j + f3j)*f1j + Sum(f1j*Pj*V) b2 = -W*A*Sum(f0j + f1j)*f2j - W*B*Sum(f2j + f3j)*f2j + Sum(f2j*Pj*W) V and W ae unit vectors, so V*V = W*W = 1. For computational efficiency, we will break b1 and b2 into 3 sums each, and add them up at the end The solution is s = (b1*a22 - b2*a12) / det u = (b2*a11 - b1*a12) / det where det = a11*a22 - a22^2 */ // Compute the coefficients double a11 = 0, a12 = 0, a22 = 0, b1 = 0, b2 = 0; double b11 = 0, b12 = 0, b21 = 0, b22 = 0; for (int j = checked(from + 1); j < to; j++) { // By the way the nodes were constructed - Debug.Assert(data.Node(to) - data.Node(from) > data.Node(j) - data.Node(from)); double tj = (data.Node(j) - data.Node(from)) / (data.Node(to) - data.Node(from)); double tj2 = tj * tj; double rj = 1 - tj; double rj2 = rj * rj; double f0j = rj2 * rj; double f1j = 3 * rj2 * tj; double f2j = 3 * rj * tj2; double f3j = tj2 * tj; a11 += f1j * f1j; a22 += f2j * f2j; a12 += f1j * f2j; b11 -= (f0j + f1j) * f1j; b12 -= (f2j + f3j) * f1j; b1 += f1j * (data.XY(j) * V); b21 -= (f0j + f1j) * f2j; b22 -= (f2j + f3j) * f2j; b2 += f2j * (data.XY(j) * W); } a12 *= (V * W); b1 += ((V * data.XY(from)) * b11 + (V * data.XY(to)) * b12); b2 += ((W * data.XY(from)) * b21 + (W * data.XY(to)) * b22); // Solve the equations double s = b1 * a22 - b2 * a12; double u = b2 * a11 - b1 * a12; double det = a11 * a22 - a12 * a12; bool accept = (Math.Abs(det) > Math.Abs(s) * DoubleUtil.DBL_EPSILON && Math.Abs(det) > Math.Abs(u) * DoubleUtil.DBL_EPSILON); if (accept) { s /= det; u /= det; // We'll only accept large enough positive solutions accept = s > 1.0e-6 && u > 1.0e-6; } if (!accept) s = u = (data.Node(to) - data.Node(from)) / 3; AddBezierPoint(data.XY(from) + s * V); AddBezierPoint(data.XY(to) + u * W); AddSegmentPoint(data, to); return true; }
/// <summary> /// Add parabola to the bezier /// </summary> /// <param name="data">In: Data points</param> /// <param name="from">In: The index of the parabola's first point</param> private void AddParabola(CuspData data, int from) { /* Denote s = 1-t. We construct the parabola with Bezier points A,B,C that goes thru the point P at parameter value t, that is P = s^2A + 2stB + t^2C We know A and C, and we solve for B: B = (P - s^2A - t^2C) / 2st. Elevating the degree to cubic replaces B with 2 points, the first at 2B/3 + A/3, and the second at 2B/3 + C/3. That is, one point at (P/(st) - Ct/s + A(-s/t + 1)) / 3 and the other point at (P/(st) + C(-t/s + 1) - As/t) / 3 */ // By the way the nodes were constructed: //ASSERT(data.Node(from+2) - data.Node(from) > // data.Node(from+1) - data.Node(from)); double t = (data.Node(from + 1) - data.Node(from)) / (data.Node(from + 2) - data.Node(from)); double s = 1 - t; if (t < .001 || s < .001) { // A straight line will be a better approximation AddLine(data, from, from + 2); return; } double tt = 1 / t; double ss = 1 / s; const double third = 1.0d / 3.0d; Vector P = (tt * ss) * data.XY(from + 1); Vector B = third * (P + (1 - s * tt) * data.XY(from) - (t * ss) * data.XY(from + 1)); AddBezierPoint(B); B = third * (P - (s * tt) * data.XY(from) + (1 - t * ss) * data.XY(from + 2)); AddBezierPoint(B); AddSegmentPoint(data, from + 2); }