luncheon / simplify-svg-path

Extracts `Path#simplify()` from Paper.js.
https://luncheon.github.io/simplify-svg-path/index.html
MIT License
34 stars 7 forks source link

Implementation of generateBezier #4

Closed sickopickle closed 1 year ago

sickopickle commented 1 year ago

I am trying to implement something similar to that of https://arxiv.org/pdf/1902.10525.pdf, where they fit the coefficients of cubic polynomials of x, y, and t by minimizing the summed cost function over x, y, and t.

I want to change the index.js file to do this. Can you explain what is going on in the below code @luncheon ?

const generateBezier = (points, first, last, uPrime, tan1, tan2) => { const epsilon = /#=/ EPSILON, abs = Math.abs, pt1 = points[first], pt2 = points[last], // Create the C and X matrices C = [ [0, 0], [0, 0], ], X = [0, 0]; for (let i = 0, l = last - first + 1; i < l; i++) { const u = uPrime[i], t = 1 - u, b = 3 u t, b0 = t t t, b1 = b t, b2 = b u, b3 = u u u, a1 = tan1._normalize(b1), a2 = tan2._normalize(b2), tmp = points[first + i]._subtract(pt1._multiply(b0 + b1))._subtract(pt2._multiply(b2 + b3)); C[0][0] += a1._dot(a1); C[0][1] += a1._dot(a2); // C[1][0] += a1.dot(a2); C[1][0] = C[0][1]; C[1][1] += a2._dot(a2); X[0] += a1._dot(tmp); X[1] += a2._dot(tmp); } // Compute the determinants of C and X const detC0C1 = C[0][0] C[1][1] - C[1][0] C[0][1]; let alpha1; let alpha2; if (abs(detC0C1) > epsilon) { // Kramer's rule const detC0X = C[0][0] X[1] - C[1][0] X[0], detXC1 = X[0] C[1][1] - X[1] C[0][1]; // Derive alpha values alpha1 = detXC1 / detC0C1; alpha2 = detC0X / detC0C1; } else { // Matrix is under-determined, try assuming alpha1 == alpha2 const c0 = C[0][0] + C[0][1], c1 = C[1][0] + C[1][1]; alpha1 = alpha2 = abs(c0) > epsilon ? X[0] / c0 : abs(c1) > epsilon ? X[1] / c1 : 0; } // If alpha negative, use the Wu/Barsky heuristic (see text) // (if alpha is 0, you get coincident control points that lead to // divide by zero in any subsequent NewtonRaphsonRootFind() call. const segLength = pt2._getDistance(pt1), eps = epsilon segLength; let handle1, handle2; if (alpha1 < eps || alpha2 < eps) { // fall back on standard (probably inaccurate) formula, // and subdivide further if needed. alpha1 = alpha2 = segLength / 3; } else { // Check if the found control points are in the right order when // projected onto the line through pt1 and pt2. const line = pt2._subtract(pt1); // Control points 1 and 2 are positioned an alpha distance out // on the tangent vectors, left and right, respectively handle1 = tan1._normalize(alpha1); handle2 = tan2._normalize(alpha2); if (handle1._dot(line) - handle2._dot(line) > segLength segLength) { // Fall back to the Wu/Barsky heuristic above. alpha1 = alpha2 = segLength / 3; handle1 = handle2 = null; // Force recalculation } } // First and last control points of the Bezier curve are // positioned exactly at the first and last data points return [pt1, pt1._add(handle1 || tan1._normalize(alpha1)), pt2._add(handle2 || tan2._normalize(alpha2)), pt2]; };

luncheon commented 1 year ago

@sickopickle Its logic is copied from Paper.js. https://github.com/paperjs/paper.js/blob/db82f41151be5cd4a347ab368ac093c633551acf/src/path/PathFitter.js#L114

I don't understand the algorithm. I'm sorry.

sickopickle commented 1 year ago

Ah. No problem.