Last active
November 14, 2024 13:25
-
-
Save nicholaswmin/c2661eb11cad5671d816 to your computer and use it in GitHub Desktop.
catmull-rom
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// The "Catmull-Rom" Interpolating Splines in ES6 | |
// - Authors: Nicholas Kyriakides (2017) & Unknown | |
// - From "A class of local interpolating splines" | |
// - by Catmull, Edwin; Rom, Raphael | |
// - University of Utah, 1974 | |
// | |
// | |
// Barnhill, Robert E.; Riesenfeld, Richard F. (eds.). | |
// Computer Aided Geometric Design. | |
// | |
// Interpolates a Catmull-Rom spline through a series of x/y points | |
// | |
// - If `alpha` is `0` then the 'Uniform' variant is used. | |
// - If `alpha` is `0.5` then the 'Centripetal' variant is used. | |
// Read: https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline | |
// - If `alpha` is `1` then the 'Chordal' variant is used. | |
// | |
// @param {Array} `points` - `Point` is an object like: `{ x: 10, y: 20 }` | |
// @param {Number} `alpha` - knot parameterization, can be `0`, `0.5` or `1` | |
// | |
// @return {String} `d` - An SVG path of cubic beziers | |
// | |
const toCatmullRom = (points, alpha = 0.5) => { | |
if (!Array.isArray(points)) | |
throw TypeError(`'points' should be an Array. Got: ${typeof points}`) | |
if (![0.5, 1].includes(alpha)) | |
throw RangeError(`'alpha' should be: 1 or 0.5. Got: ${alpha}`) | |
let p0, p1, p2, p3, bp1, bp2, d1, d2, d3, A, B, N, M | |
let d3powA, d2powA, d3pow2A, d2pow2A, d1pow2A, d1powA | |
let d = 'M' + Math.round(points.at(0).x) + ',' + Math.round(points.at(0).y) + ' ' | |
for (let i = 0; i < points.length - 1; i++) { | |
p0 = i == 0 ? points[0] : points[i - 1] | |
p1 = points[i] | |
p2 = points[i + 1] | |
p3 = i + 2 < points.length ? points[i + 2] : p2 | |
d1 = Math.sqrt(Math.pow(p0.x - p1.x, 2) + Math.pow(p0.y - p1.y, 2)) | |
d2 = Math.sqrt(Math.pow(p1.x - p2.x, 2) + Math.pow(p1.y - p2.y, 2)) | |
d3 = Math.sqrt(Math.pow(p2.x - p3.x, 2) + Math.pow(p2.y - p3.y, 2)) | |
// Catmull-Rom to Cubic Bezier conversion matrix | |
// A = 2d1^2a + 3d1^a * d2^a + d3^2a | |
// B = 2d3^2a + 3d3^a * d2^a + d2^2a | |
// [ 0 1 0 0 ] | |
// [ -d2^2a /N A/N d1^2a /N 0 ] | |
// [ 0 d3^2a /M B/M -d2^2a /M ] | |
// [ 0 0 1 0 ] | |
d3powA = Math.pow(d3, alpha) | |
d3pow2A = Math.pow(d3, 2 * alpha) | |
d2powA = Math.pow(d2, alpha) | |
d2pow2A = Math.pow(d2, 2 * alpha) | |
d1powA = Math.pow(d1, alpha) | |
d1pow2A = Math.pow(d1, 2 * alpha) | |
A = 2 * d1pow2A + 3 * d1powA * d2powA + d2pow2A | |
B = 2 * d3pow2A + 3 * d3powA * d2powA + d2pow2A | |
N = 3 * d1powA * (d1powA + d2powA) | |
if (N > 0) | |
N = 1 / N | |
M = 3 * d3powA * (d3powA + d2powA) | |
if (M > 0) | |
M = 1 / M | |
bp1 = { | |
x: (-d2pow2A * p0.x + A * p1.x + d1pow2A * p2.x) * N, | |
y: (-d2pow2A * p0.y + A * p1.y + d1pow2A * p2.y) * N | |
} | |
bp2 = { | |
x: (d3pow2A * p1.x + B * p2.x - d2pow2A * p3.x) * M, | |
y: (d3pow2A * p1.y + B * p2.y - d2pow2A * p3.y) * M | |
} | |
if (bp1.x == 0 && bp1.y == 0) | |
bp1 = p1 | |
if (bp2.x == 0 && bp2.y == 0) | |
bp2 = p2 | |
d += 'C' + bp1.x + ',' + bp1.y + ' ' + bp2.x + ',' + bp2.y + ' ' + p2.x + ',' + p2.y + ' ' | |
} | |
return d | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@sivakrishna551
I've chopped some parts of the code from somewhere else, was some kind of chart-drawing library I think.
I'm almost positive it was MIT licensed but there's zero-chance I'll remember which one it was, nor do I think it makes any difference,
I don't remember it being anything too significant.
It's MIT from me, so feel free. I'l add it below.
Adding a bit more detail; some people seem to be using this.
I've cleaned this up a little bit but bear in mind that I hacked this up haphazardly in a rush, back in 2015.
Somebody forked this and converted it to TypeScript, probably even improved it; click the "Forks" button above
and you'll figure it out.
Original papers
A Class of Interpolating Splines
On the Parameterization of Catmull-Rom Curves
This implementation contains parts from another author, I don't remember which one but IIRC it's the parts
conversion to Cubics. I don't personally care about citation but at least cite them as Unknown Authors in the
MIT redistribution.
If you came across this for implementing handwriting interpolation, the following is relevant,
otherwise skip it since it's not going to make any sense to you.
This implementation came about ~ 2015, during the R&D phase of a project about a collaborative whiteboard, geared specifically for online tutoring by professional tutors.
Excerpts from 2015:
The Frisken algorithm mentioned above:
From S.O: Catmull-Rom interpolation on SVG Paths.
Jurg Lehni added the above (with additional improvements), into the PaperJS library.
You can follow our discussion here.
Although it's a bit enmeshed with PaperJS internals, it's an improvement over this implementation.
Our discussion above goes in much more detail than I'd rather to repeat here.
The image below shows the self-intersection issues (rows 4, 5-7) in other algorithms vs Centripetal Catmull-Rom.
Thanks,
Nik