diff --git a/docs/chapters/catmullconv/catmull-rom.js b/docs/chapters/catmullconv/catmull-rom.js index 587b87b4..ff59dcbb 100644 --- a/docs/chapters/catmullconv/catmull-rom.js +++ b/docs/chapters/catmullconv/catmull-rom.js @@ -12,108 +12,71 @@ setup() { ]; setMovable(points); knots = [0, 1/3, 2/3, 1]; - setSlider(`.slide-control.tension`, `tension`, 0.5); + setSlider(`.slide-control.tension`, `tension`, 0.5, v => this.transformTension(v)); } -onTension(v) { - if (v < 0.5) { - v = map(v,0.5,0,1,4); - v = 1/v; - } else { - v = map(v,0.5,1,1,4); - } - this.tension = v; +transformTension(v) { + return (v < 0.5) ? 1 / map(v, 0.5,0, 1,10) : map(v, 0.5,1, 1,10); } draw() { clear(); - const [first, last] = this.generateVirtualPoints(); - const full = [first, ...points, last]; - - for (let i=0, e=full.length-3; i { setColor( randomColor() ); circle(p.x, p.y, 3); }); } -generateVirtualPoints() { - // see http://www.sdmath.com/math/geometry/reflection_across_line.html#formulasmb - const n = points.length, - p1 = points[0], - p2 = points[1], - p3 = points[n-2], - p4 = points[n-1], - m = (p4.y-p1.y)/(p4.x-p1.x), - b = (p4.x*p1.y-p1.x*p4.y)/(p4.x-p1.x), - ratio = 0.5; - - return [[p2,p1], [p3,p4]].map(pair => { - const p = pair[0], - M = pair[1], - reflected = { - x: M.x - (p.x - M.x), - y: M.y - (p.y - M.y), - }, - projected = { - x: ((1 - m**2)*p.x + 2*m*p.y - 2*m*b) / (m**2 + 1), - y: ((m**2 - 1)*p.y + 2*m*p.x + 2*b) / (m**2 + 1) - }; - return { - x: (1-ratio) * reflected.x + ratio * projected.x, - y: (1-ratio) * reflected.y + ratio * projected.y - }; - }); -} - dragSegment(p0, p1, p2, p3) { - const alpha = 0.5, - t0 = 0, - // See https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition - t1 = t0 + ((p1.x-p0.x)**2 + (p1.y-p0.y)**2)**alpha, - t2 = t1 + ((p2.x-p1.x)**2 + (p2.y-p1.y)**2)**alpha, - t3 = t2 + ((p3.x-p2.x)**2 + (p3.y-p2.y)**2)**alpha, - s = (t2 - t1) / this.tension, - // See https://stackoverflow.com/a/23980479/740553 - tangent1 = { - x: s * ((p1.x-p0.x)/(t1-t0) - (p2.x-p0.x)/(t2-t0) + (p2.x-p1.x)/(t2-t1)), - y: s * ((p1.y-p0.y)/(t1-t0) - (p2.y-p0.y)/(t2-t0) + (p2.y-p1.y)/(t2-t1)) + const s = 2 * this.tension, + m1 = { + x: (p2.x - p0.x) / s, + y: (p2.y - p0.y) / s }, - tangent2 = { - x: s * ((p2.x-p1.x)/(t2-t1) - (p3.x-p1.x)/(t3-t1) + (p3.x-p2.x)/(t3-t2)), - y: s * ((p2.y-p1.y)/(t2-t1) - (p3.y-p1.y)/(t3-t1) + (p3.y-p2.y)/(t3-t2)) + m2 = { + x: (p3.x - p1.x) / s, + y: (p3.y - p1.y) / s }; noFill(); setStroke( randomColor() ); start(); - this.markCoordinate(0, p1,p2,tangent1,tangent2); - for(let s=0.01, t=s; t<1; t+=0.01) this.markCoordinate(t, p1,p2,tangent1,tangent2); - this.markCoordinate(1, p1,p2,tangent1,tangent2); + this.addCoordinate(0, p1, p2, m1, m2); + for(let s=0.01, t=s; t<1; t+=0.01) this.addCoordinate(t, p1, p2, m1, m2); + this.addCoordinate(1, p1, p2, m1, m2); end(); } -markCoordinate(t, p0, p1, m0, m1) { +addCoordinate(t, p0, p1, m0, m1) { + // See https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Unit_interval_(0,_1) let c = 2*t**3 - 3*t**2, c0 = c + 1, c1 = t**3 - 2*t**2 + t, c2 = -c, - c3 = t**3 - t**2, - point = { - x: c0 * p0.x + c1 * m0.x + c2 * p1.x + c3 * m1.x, - y: c0 * p0.y + c1 * m0.y + c2 * p1.y + c3 * m1.y - }; - vertex(point.x, point.y); + c3 = t**3 - t**2; + + vertex( + c0 * p0.x + c1 * m0.x + c2 * p1.x + c3 * m1.x, + c0 * p0.y + c1 * m0.y + c2 * p1.y + c3 * m1.y + ) } onMouseDown() { @@ -121,7 +84,6 @@ onMouseDown() { let {x, y} = this.cursor; points.push({ x, y }); resetMovable(points); - console.log(JSON.stringify(points)) redraw(); } } diff --git a/docs/chapters/catmullconv/content.en-GB.md b/docs/chapters/catmullconv/content.en-GB.md index 4dbb291e..be538ba4 100644 --- a/docs/chapters/catmullconv/content.en-GB.md +++ b/docs/chapters/catmullconv/content.en-GB.md @@ -1,20 +1,67 @@ # Bézier curves and Catmull-Rom curves -Taking an excursion to different splines, the other common design curve is the [Catmull-Rom spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline), which unlike Bézier curves pass _through_ the points you define. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, or you can click/tap somewhere to extend the curve. +Taking an excursion to different splines, the other common design curve is the [Catmull-Rom spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline), which unlike Bézier curves pass _through_ each control point, so they offer a kind of "nuilt-in" curve fitting. + +In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets you add points by clicking/tapping the background, as well as let you control "how fast" the curve passes through its point using the tension slider. The tenser the curve, the more the curve tends towards straight lines from one point to the next. -You may have noticed the slider that seems to control the "tension" of the curve: that's a feature of Catmull-Rom curves; because Catmull-Rom curves pass through points, the curve tightness is controlled by a tension factor, rather than by moving control points around. +Now, it may look like Catmull-Rom curves are very different from Bézier curves, because these curves can get very long indeed, but what looks like a single Catmull-Rom curve is actually a [spline](https://en.wikipedia.org/wiki/Spline_(mathematics)): a single curve built up of lots of identically-computed pieces, similar to if you just took a whole bunch of Bézier curves, placed them end to end, and lined up their control points so that things look like a single curve. For a Catmull-Rom curve, each "piece" between two points is defined by the point's coordinates, and the tangent for those points, the latter of which [can trivially be derived](https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline) from knowing the previous and next point: -What you may _also_ have noticed is that the Catmull-Rom curve seems to just go on forever: add as many points as you like, same curve, rigth? Well, sort of. Catmull-Rom curves are splines, a type of curve with arbitrary number of points, technically consisting of "segments between sets of points", but with maths that makes all the segments line up neatly. As such, at its core a Catmull-Rom consists of two points, and draws a curve between them based on the tangents at those points. +\[ + \begin{bmatrix} + P_1 \\ + P_2 \\ + P_3 \\ + P_4 + \end{bmatrix}_{points} + = + \left [ + \begin{array}{rl} + V_1 &= P_2 \\ + V_2 &= P_3 \\ + V'_1 &= \frac{P_3 - P_1}{2} \\ + V'_2 &= \frac{P_4 - P_2}{2} + \end{array} + \right ]_{point-tangent} +\] -Now, a Catmull-Rom spline is a form of [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline), and as it so happens, the cubic Bézier curve is _also_ a cubic Hermite spline, so in an interesting bit of programming maths: we can losslessly convert between the two, and the maths (well, the final maths) is surprisingly simple! +One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on that in [the next section](#catmullfitting). -The main difference between Catmull-Rom curves and Bezier curves is "what the points mean". A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point. A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point. Those are _very_ similar, so let's see exactly _how_ similar they are. +In fact, before we move on, let's look at how to actually draw the basic form of these curves (I say basic, because there are a number of variations that make things [considerable](https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition) more [complex](https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline)): -We start with the matrix form of thee Catmull-Rom curve, which looks similar to the Bézier matrix form, with slightly different values in the matrix, and a slightly different coordinate vector: +``` +tension = some value greater than 0, defaulting to 1 +points = a list of at least 4 coordinates + +for p = 1 to points.length-3 (inclusive): + p0 = points[p-1] + v1 = p1 = points[p] + v2 = p2 = points[p+1] + p3 = points[p+2] + + s = 2 * tension + dv1 = (p2-p0) / s + dv2 = (p3-p1) / s + + for t = 0 to 1 (inclusive): + c0 = 2*t^3 - 3*t^2 + 1, + c1 = t^3 - 2*t^2 + t, + c2 = -2*t^3 + 3*t^2, + c3 = t^3 - t^2 + point(c0 * v1 + c1 * dv1 + c2 * v2 + c3 * dv2) +``` + +Now, since a Catmull-Rom curve is a form of [cubic Hermite spline](https://en.wikipedia.org/wiki/Cubic_Hermite_spline), and as cubic Bézier curves are _also_ a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can losslessly convert between the two, and the maths for doing so is surprisingly simple! + +The main difference between Catmull-Rom curves and Bézier curves is "what the points mean": + +- A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve coordinates. +- A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point, plus a characteristic matrix that we can multiple by the point vector to get on-curve coordinates. + +Those are _very_ similar, so let's see exactly _how_ similar they are. We've already see the matrix form for Bézier curves, so how different is the matrix form for Catmull-Rom curves?: \[ CatmullRom(t) = @@ -34,7 +81,7 @@ We start with the matrix form of thee Catmull-Rom curve, which looks similar to \end{bmatrix} \] -So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression that uses Bézier matrix and vector? The short answer is of course "by using linear algebra", but the real answer is a bit longer, and involves some maths that you may not even care for: just skip over the next bit to see the incredibly simple conversions between the formats, but if you want to know _why_... let's go! +That's pretty dang similar. So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression of the Bézier matrix and vector? The short answer is of course "by using linear algebra", but the longer answer is the rest of this section, and involves some maths that you may not even care for: if you just want to know the (incredibly simple) conversions between the two curve forms, feel free to skip to the end of the following explanation, but if you want to _how_ we can get one from the other... let's get mathing!
@@ -42,7 +89,7 @@ So the question is: how can we convert that expression with Catmull-Rom matrix a In order to convert between Catmull-Rom curves and Bézier curves, we need to know two things. Firstly, how to express the Catmull-Rom curve using a "set of four coordinates", rather than a mix of coordinates and tangents, and secondly, how to convert those Catmull-Rom coordinates to and from Bézier form. -So, let's start with the first, where we want to satisfy the following equality: +We start with the first part, to figure out how we can go from Catmull-Rom **V** coordinates to Bézier **P** coordinates, by applying "some matrix **T**". We don't know what that **T** is yet, but we'll get to that: \[ \begin{bmatrix} @@ -60,7 +107,7 @@ So, let's start with the first, where we want to satisfy the following equality: \end{bmatrix} \] -This mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively. +So, this mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively. Computing T is really more "arranging the numbers": @@ -107,7 +154,7 @@ Thus: \end{bmatrix} \] -However, we're not quite done, because Catmull-Rom curves have a parameter called "tension", written as τ ("tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T: +However, we're not quite done, because Catmull-Rom curves have that "tension" parameter, written as τ (a lowercase"tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T: \[ \begin{bmatrix} diff --git a/docs/chapters/catmullconv/notes.txt b/docs/chapters/catmullconv/notes.txt new file mode 100644 index 00000000..3312534d --- /dev/null +++ b/docs/chapters/catmullconv/notes.txt @@ -0,0 +1,129 @@ +THE FOLLOWING CODE IS FOR COMPUTING THE FAR MORE COMPLEX CENTRIPETAL CATMULL ROM CURVE AND IS PRETTY MUCH OUT OF SCOPE (for now?) + + +let points, knots; + +setup() { + points = [ + {x:38,y:136}, + {x:65,y:89}, + {x:99,y:178}, + {x:149,y:93}, + {x:191,y:163}, + {x:227,y:122}, + {x:251,y:132} + ]; + setMovable(points); + knots = [0, 1/3, 2/3, 1]; + setSlider(`.slide-control.tension`, `tension`, 0.5); +} + +onTension(v) { + if (v < 0.5) { + v = map(v,0.5,0,1,4); + v = 1/v; + } else { + v = map(v,0.5,1,1,4); + } + this.tension = v; +} + +draw() { + clear(); + + const [first, last] = this.generateVirtualPoints(); + const full = [first, ...points, last]; + + for (let i=0, e=full.length-3; i { + setColor( randomColor() ); + circle(p.x, p.y, 3); + }); +} + +generateVirtualPoints() { + // see http://www.sdmath.com/math/geometry/reflection_across_line.html#formulasmb + const n = points.length, + p1 = points[0], + p2 = points[1], + p3 = points[n-2], + p4 = points[n-1], + m = (p4.y-p1.y)/(p4.x-p1.x), + b = (p4.x*p1.y-p1.x*p4.y)/(p4.x-p1.x), + ratio = 0.5; + + return [[p2,p1], [p3,p4]].map(pair => { + const p = pair[0], + M = pair[1], + reflected = { + x: M.x - (p.x - M.x), + y: M.y - (p.y - M.y), + }, + projected = { + x: ((1 - m**2)*p.x + 2*m*p.y - 2*m*b) / (m**2 + 1), + y: ((m**2 - 1)*p.y + 2*m*p.x + 2*b) / (m**2 + 1) + }; + return { + x: (1-ratio) * reflected.x + ratio * projected.x, + y: (1-ratio) * reflected.y + ratio * projected.y + }; + }); +} + +dragSegment(p0, p1, p2, p3) { + const alpha = 0.5, + t0 = 0, + // See https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition + t1 = t0 + ((p1.x-p0.x)**2 + (p1.y-p0.y)**2)**alpha, + t2 = t1 + ((p2.x-p1.x)**2 + (p2.y-p1.y)**2)**alpha, + t3 = t2 + ((p3.x-p2.x)**2 + (p3.y-p2.y)**2)**alpha, + s = (t2 - t1) / this.tension, + // See https://stackoverflow.com/a/23980479/740553 + tangent1 = { + x: s * ((p1.x-p0.x)/(t1-t0) - (p2.x-p0.x)/(t2-t0) + (p2.x-p1.x)/(t2-t1)), + y: s * ((p1.y-p0.y)/(t1-t0) - (p2.y-p0.y)/(t2-t0) + (p2.y-p1.y)/(t2-t1)) + }, + tangent2 = { + x: s * ((p2.x-p1.x)/(t2-t1) - (p3.x-p1.x)/(t3-t1) + (p3.x-p2.x)/(t3-t2)), + y: s * ((p2.y-p1.y)/(t2-t1) - (p3.y-p1.y)/(t3-t1) + (p3.y-p2.y)/(t3-t2)) + }; + + noFill(); + setStroke( randomColor() ); + + start(); + this.markCoordinate(0, p1,p2,tangent1,tangent2); + for(let s=0.01, t=s; t<1; t+=0.01) this.markCoordinate(t, p1,p2,tangent1,tangent2); + this.markCoordinate(1, p1,p2,tangent1,tangent2); + end(); +} + +markCoordinate(t, p0, p1, m0, m1) { + let c = 2*t**3 - 3*t**2, + c0 = c + 1, + c1 = t**3 - 2*t**2 + t, + c2 = -c, + c3 = t**3 - t**2, + point = { + x: c0 * p0.x + c1 * m0.x + c2 * p1.x + c3 * m1.x, + y: c0 * p0.y + c1 * m0.y + c2 * p1.y + c3 * m1.y + }; + vertex(point.x, point.y); +} + +onMouseDown() { + if (!this.currentPoint) { + let {x, y} = this.cursor; + points.push({ x, y }); + resetMovable(points); + redraw(); + } +} diff --git a/docs/chapters/catmullfitting/content.en-GB.md b/docs/chapters/catmullfitting/content.en-GB.md new file mode 100644 index 00000000..8d69cae1 --- /dev/null +++ b/docs/chapters/catmullfitting/content.en-GB.md @@ -0,0 +1,7 @@ +# Creating a Catmull-Rom curve from three points + +Much shorter than the previous section: we saw that Catmull-Rom curves need at least 4 points to draw anything sensible, so how do we create a Catmull-Rom curve from three points? + +Short and sweet: we don't. + +We run through the maths that lets us [create a cubic Bézier curve](pointcurves), and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above. diff --git a/docs/chapters/catmullmolding/content.en-GB.md b/docs/chapters/catmullmolding/content.en-GB.md deleted file mode 100644 index 0bf1fa67..00000000 --- a/docs/chapters/catmullmolding/content.en-GB.md +++ /dev/null @@ -1,13 +0,0 @@ -# Creating a Catmull-Rom curve from three points - -Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead? - -As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve. - -Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of n points, in order to draw the equivalent curve using cubic Bézier curves we need a massive 3n-2 points (and that's without double-counting points that are shared by consecutive cubic curves). - -In the following graphic, on the left we see three points that we want to draw a Catmull-Rom curve through (which we can move around freely, by the way), with in the second panel some of the "interesting" Catmull-Rom information: in black there's the baseline start--end, which will act as tangent orientation for the curve at point p2. We also see a virtual point p0 and p4, which are initially just point p2 reflected over the baseline. However, by using the up and down cursor key we can offset these points parallel to the baseline. Why would we want to do this? Because the line p0--p2 acts as departure tangent at p1, and the line p2--p4 acts as arrival tangent at p3. Play around with the graphic a bit to get an idea of what all of that meant: - - - -As should be obvious by now, Catmull-Rom curves are great for "fitting a curvature to some points", but if we want to convert that curve to Bézier form we're going to end up with a lot of separate (but visually joined) Bézier curves. Depending on what we want to do, that'll be either unnecessary work, or exactly what we want: which it is depends entirely on you. diff --git a/docs/chapters/catmullmolding/handler.js b/docs/chapters/catmullmolding/handler.js deleted file mode 100644 index 01729130..00000000 --- a/docs/chapters/catmullmolding/handler.js +++ /dev/null @@ -1,131 +0,0 @@ -module.exports = { - statics: { - keyHandlingOptions: { - propName: "distance", - values: { - "38": 1, // up arrow - "40": -1 // down arrow - } - } - }, - - setup: function(api) { - api.setPanelCount(3); - api.lpts = [ - {x:56, y:153}, - {x:144,y:83}, - {x:188,y:185} - ]; - api.distance = 0; - }, - - convert: function(p1, p2, p3, p4) { - var t = 0.5; - return [ - p2, { - x: p2.x + (p3.x-p1.x)/(6*t), - y: p2.y + (p3.y-p1.y)/(6*t) - }, { - x: p3.x - (p4.x-p2.x)/(6*t), - y: p3.y - (p4.y-p2.y)/(6*t) - }, p3 - ]; - }, - - draw: function(api) { - api.reset(); - api.setColor("lightblue"); - api.drawGrid(10,10); - - var pts = api.lpts; - api.setColor("black"); - api.setFill("black"); - pts.forEach((p,pos) => { - api.drawCircle(p, 3); - api.text("point "+(pos+1), p, {x:10, y:7}); - }); - - var w = api.getPanelWidth(); - var h = api.getPanelHeight(); - var offset = {x:w, y:0}; - api.setColor("lightblue"); - api.drawGrid(10,10,offset); - api.setColor("black"); - api.drawLine({x:0,y:0}, {x:0,y:h}, offset); - - pts.forEach((p,pos) => { - api.drawCircle(p, 3, offset); - }); - var p1 = pts[0], p2 = pts[1], p3 = pts[2]; - var dx = p3.x - p1.x, - dy = p3.y - p1.y, - m = Math.sqrt(dx*dx + dy*dy); - dx /= m; - dy /= m; - api.drawLine(p1, p3, offset); - - var p0 = { - x: p1.x + (p3.x - p2.x) - api.distance * dx, - y: p1.y + (p3.y - p2.y) - api.distance * dy - }; - var p4 = { - x: p1.x + (p3.x - p2.x) + api.distance * dx, - y: p1.y + (p3.y - p2.y) + api.distance * dy - }; - var center = api.utils.lli4(p1,p3,p2,{ - x: (p0.x + p4.x)/2, - y: (p0.y + p4.y)/2 - }); - api.setColor("blue"); - api.drawCircle(center, 3, offset); - api.drawLine(pts[1],center, offset); - api.setColor("#666"); - api.drawLine(center, p0, offset); - api.drawLine(center, p4, offset); - - api.setFill("blue"); - api.text("p0", p0, {x:-20 + offset.x, y:offset.y + 2}); - api.text("p4", p4, {x:+10 + offset.x, y:offset.y + 2}); - - // virtual point p0 - api.setColor("red"); - api.drawCircle(p0, 3, offset); - api.drawLine(p2, p0, offset); - api.drawLine(p1, { - x: p1.x + (p2.x - p0.x)/5, - y: p1.y + (p2.y - p0.y)/5 - }, offset); - - // virtual point p4 - api.setColor("#00FF00"); - api.drawCircle(p4, 3, offset); - api.drawLine(p2, p4, offset); - api.drawLine(p3, { - x: p3.x + (p4.x - p2.x)/5, - y: p3.y + (p4.y - p2.y)/5 - }, offset); - - // Catmull-Rom curve for p0-p1-p2-p3-p4 - var c1 = new api.Bezier(this.convert(p0,p1,p2,p3)), - c2 = new api.Bezier(this.convert(p1,p2,p3,p4)); - api.setColor("lightgrey"); - api.drawCurve(c1, offset); - api.drawCurve(c2, offset); - - - offset.x += w; - api.setColor("lightblue"); - api.drawGrid(10,10,offset); - api.setColor("black"); - api.drawLine({x:0,y:0}, {x:0,y:h}, offset); - - api.drawCurve(c1, offset); - api.drawCurve(c2, offset); - api.drawPoints(c1.points, offset); - api.drawPoints(c2.points, offset); - api.setColor("lightgrey"); - api.drawLine(c1.points[0], c1.points[1], offset); - api.drawLine(c1.points[2], c2.points[1], offset); - api.drawLine(c2.points[2], c2.points[3], offset); - } -}; diff --git a/docs/chapters/control/lerp.js b/docs/chapters/control/lerp.js index b78d1ae2..aa8ab297 100644 --- a/docs/chapters/control/lerp.js +++ b/docs/chapters/control/lerp.js @@ -22,27 +22,15 @@ setup() { this.f = [...new Array(degree + 1)].map((_,i) => { return t => ({ x: t * w, - y: h * this.binomial(degree,i) * (1-t) ** (degree-i) * t ** (i) + y: h * binomial(degree,i) * (1-t) ** (degree-i) * t ** (i) }); }); } - this.s = this.f.map(f => plot(f, 0, 1, degree*4) ); + this.s = this.f.map(f => plot(f, 0, 1, degree*5) ); setSlider(`.slide-control`, `position`, 0) } -binomial(n,k) { - if (!this.triangle[n]) { - while(!this.triangle[n]) { - let last = this.triangle.slice(-1)[0]; - let next = last.map((v,i) => v + last[i+1]); - next.pop(); - this.triangle.push([1, ...next, 1]); - } - } - return this.triangle[n][k]; -} - draw() { clear(); setFill(`black`); @@ -54,8 +42,13 @@ draw() { noFill(); - this.s.forEach(s => { - setStroke( randomColor() ); + this.s.forEach((s,i) => { + setStroke( randomColor(0.2)); + line( + i/(this.s.length-1) * this.width, 0, + i/(this.s.length-1) * this.width, this.height + ) + setStroke( randomColor(1.0, false )); drawShape(s); }) diff --git a/docs/chapters/curveintersection/curve-curve.js b/docs/chapters/curveintersection/curve-curve.js index 6a0a0d0b..6acf0a7c 100644 --- a/docs/chapters/curveintersection/curve-curve.js +++ b/docs/chapters/curveintersection/curve-curve.js @@ -4,7 +4,7 @@ setup() { setPanelCount(3); this.pairReset(); this.setupEventListening(); - setSlider(`.slide-control`, `epsilon`, 1.0); + setSlider(`.slide-control`, `epsilon`, 1.0, v => this.reset()); } pairReset() { @@ -124,7 +124,3 @@ onMouseMove() { redraw(); } } - -onEpsilon(value) { - this.reset(); -} diff --git a/docs/chapters/molding/molding.js b/docs/chapters/molding/molding.js index ec0adad2..152bf018 100644 --- a/docs/chapters/molding/molding.js +++ b/docs/chapters/molding/molding.js @@ -191,7 +191,7 @@ drawResult() { if (this.molded) last = this.molded; last.drawSkeleton(`lightblue`); - last.drawCurve(this.parameters.interpolated ? `lightblue` : `black`); + last.drawCurve(this.cursor.down ? `lightblue` : `black`); last.points.forEach(p => circle(p.x, p.y, 2)); if (this.mark) { @@ -253,10 +253,10 @@ onMouseMove() { onMouseUp() { this.mark = false; if (this.molded) { - curve = this.interpolated ?? this.molded; + curve = this.interpolated || this.molded; + resetMovable(curve.points, [this.position]); this.interpolated = false; this.molded = false; - resetMovable(curve.points, [this.position]); } redraw(); } diff --git a/docs/chapters/toc.js b/docs/chapters/toc.js index ac08ca25..50d882e0 100644 --- a/docs/chapters/toc.js +++ b/docs/chapters/toc.js @@ -52,7 +52,7 @@ export default [ // A quick foray into Catmull-Rom splines 'catmullconv', - 'catmullmolding', + 'catmullfitting', // "things made of more than on curve" 'polybezier', diff --git a/docs/images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg b/docs/images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg new file mode 100644 index 00000000..1c3bff2f --- /dev/null +++ b/docs/images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/docs/images/chapters/catmullconv/a6289e403f079730b7546ab94176d42f.png b/docs/images/chapters/catmullconv/a6289e403f079730b7546ab94176d42f.png new file mode 100644 index 00000000..cb6a47d3 Binary files /dev/null and b/docs/images/chapters/catmullconv/a6289e403f079730b7546ab94176d42f.png differ diff --git a/docs/images/chapters/catmullconv/b0cb0cccdbea86dcdd610a871e86183f.png b/docs/images/chapters/catmullconv/b0cb0cccdbea86dcdd610a871e86183f.png deleted file mode 100644 index 106a47ba..00000000 Binary files a/docs/images/chapters/catmullconv/b0cb0cccdbea86dcdd610a871e86183f.png and /dev/null differ diff --git a/docs/images/chapters/control/2c5b710606f31ed8830397ad2a77d16e.png b/docs/images/chapters/control/2c5b710606f31ed8830397ad2a77d16e.png new file mode 100644 index 00000000..39aaa2f1 Binary files /dev/null and b/docs/images/chapters/control/2c5b710606f31ed8830397ad2a77d16e.png differ diff --git a/docs/images/chapters/control/49423783987ac4bc49fbe4c519dbc1d1.png b/docs/images/chapters/control/49423783987ac4bc49fbe4c519dbc1d1.png deleted file mode 100644 index aa6f0a67..00000000 Binary files a/docs/images/chapters/control/49423783987ac4bc49fbe4c519dbc1d1.png and /dev/null differ diff --git a/docs/images/chapters/control/882ae425daeb3f449e5a4d649b8425e7.png b/docs/images/chapters/control/882ae425daeb3f449e5a4d649b8425e7.png new file mode 100644 index 00000000..9894b8c7 Binary files /dev/null and b/docs/images/chapters/control/882ae425daeb3f449e5a4d649b8425e7.png differ diff --git a/docs/images/chapters/control/afd21a9ba16965c2e7ec2d0d14892250.png b/docs/images/chapters/control/afd21a9ba16965c2e7ec2d0d14892250.png deleted file mode 100644 index bb0f9ac1..00000000 Binary files a/docs/images/chapters/control/afd21a9ba16965c2e7ec2d0d14892250.png and /dev/null differ diff --git a/docs/images/chapters/control/c7cebd1c54c120c3a9513062e562f3a6.png b/docs/images/chapters/control/c7cebd1c54c120c3a9513062e562f3a6.png new file mode 100644 index 00000000..29e2128f Binary files /dev/null and b/docs/images/chapters/control/c7cebd1c54c120c3a9513062e562f3a6.png differ diff --git a/docs/images/chapters/control/ecc15848fbe7b2176b0c89973f07c694.png b/docs/images/chapters/control/ecc15848fbe7b2176b0c89973f07c694.png deleted file mode 100644 index 35cc064a..00000000 Binary files a/docs/images/chapters/control/ecc15848fbe7b2176b0c89973f07c694.png and /dev/null differ diff --git a/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png b/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png deleted file mode 100644 index c48f987f..00000000 Binary files a/docs/images/chapters/curveintersection/3689ed0c15eace45a1f6ae03909ad8ed.png and /dev/null differ diff --git a/docs/images/chapters/curveintersection/eae3bb142567d9e2b8c1e4d42e8ef505.png b/docs/images/chapters/curveintersection/eae3bb142567d9e2b8c1e4d42e8ef505.png new file mode 100644 index 00000000..4dc1ed9c Binary files /dev/null and b/docs/images/chapters/curveintersection/eae3bb142567d9e2b8c1e4d42e8ef505.png differ diff --git a/docs/images/chapters/molding/502de5e21415ee75ab5d2cffbc921a77.png b/docs/images/chapters/molding/502de5e21415ee75ab5d2cffbc921a77.png new file mode 100644 index 00000000..84cc7218 Binary files /dev/null and b/docs/images/chapters/molding/502de5e21415ee75ab5d2cffbc921a77.png differ diff --git a/docs/images/chapters/molding/522f1edd37163772b81acb86d3a4f423.png b/docs/images/chapters/molding/522f1edd37163772b81acb86d3a4f423.png deleted file mode 100644 index 07b52bf4..00000000 Binary files a/docs/images/chapters/molding/522f1edd37163772b81acb86d3a4f423.png and /dev/null differ diff --git a/docs/images/chapters/molding/610251fd14e24cd1378590de87ce2a74.png b/docs/images/chapters/molding/610251fd14e24cd1378590de87ce2a74.png new file mode 100644 index 00000000..84cc7218 Binary files /dev/null and b/docs/images/chapters/molding/610251fd14e24cd1378590de87ce2a74.png differ diff --git a/docs/images/chapters/molding/6b91671c2962530b863ae0da5789a9cc.png b/docs/images/chapters/molding/6b91671c2962530b863ae0da5789a9cc.png deleted file mode 100644 index 0da8d01e..00000000 Binary files a/docs/images/chapters/molding/6b91671c2962530b863ae0da5789a9cc.png and /dev/null differ diff --git a/docs/images/chapters/molding/9a214cd85a1f0857b1b57db5e9c37b9c.png b/docs/images/chapters/molding/9a214cd85a1f0857b1b57db5e9c37b9c.png new file mode 100644 index 00000000..9be106b1 Binary files /dev/null and b/docs/images/chapters/molding/9a214cd85a1f0857b1b57db5e9c37b9c.png differ diff --git a/docs/images/chapters/molding/ea1656c068a60631135ad499e8a29453.png b/docs/images/chapters/molding/ea1656c068a60631135ad499e8a29453.png deleted file mode 100644 index a1bd3855..00000000 Binary files a/docs/images/chapters/molding/ea1656c068a60631135ad499e8a29453.png and /dev/null differ diff --git a/docs/index.html b/docs/index.html index fddc89d0..42983cc4 100644 --- a/docs/index.html +++ b/docs/index.html @@ -100,7 +100,7 @@
  • Molding a curve
  • Curve fitting
  • Bézier curves and Catmull-Rom curves
  • -
  • Creating a Catmull-Rom curve from three points
  • +
  • Creating a Catmull-Rom curve from three points
  • Forming poly-Bézier curves
  • Boolean shape operations
  • Curve offsetting
  • @@ -333,7 +333,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -342,7 +342,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -351,7 +351,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -1589,7 +1589,7 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + @@ -1744,7 +1744,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1753,7 +1753,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1762,7 +1762,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1860,35 +1860,61 @@ for (coordinate, index) in LUT:

    Bézier curves and Catmull-Rom curves

    -

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through the points you define. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, or you can click/tap somewhere to extend the curve.

    +

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through each control point, so they offer a kind of "nuilt-in" curve fitting.

    +

    In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets you add points by clicking/tapping the background, as well as let you control "how fast" the curve passes through its point using the tension slider. The tenser the curve, the more the curve tends towards straight lines from one point to the next.

    Scripts are disabled. Showing fallback image. - + -

    You may have noticed the slider that seems to control the "tension" of the curve: that's a feature of Catmull-Rom curves; because Catmull-Rom curves pass through points, the curve tightness is controlled by a tension factor, rather than by moving control points around.

    -

    What you may also have noticed is that the Catmull-Rom curve seems to just go on forever: add as many points as you like, same curve, rigth? Well, sort of. Catmull-Rom curves are splines, a type of curve with arbitrary number of points, technically consisting of "segments between sets of points", but with maths that makes all the segments line up neatly. As such, at its core a Catmull-Rom consists of two points, and draws a curve between them based on the tangents at those points.

    -

    Now, a Catmull-Rom spline is a form of cubic Hermite spline, and as it so happens, the cubic Bézier curve is also a cubic Hermite spline, so in an interesting bit of programming maths: we can losslessly convert between the two, and the maths (well, the final maths) is surprisingly simple!

    -

    The main difference between Catmull-Rom curves and Bezier curves is "what the points mean". A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point. A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point. Those are very similar, so let's see exactly how similar they are.

    -

    We start with the matrix form of thee Catmull-Rom curve, which looks similar to the Bézier matrix form, with slightly different values in the matrix, and a slightly different coordinate vector:

    +

    Now, it may look like Catmull-Rom curves are very different from Bézier curves, because these curves can get very long indeed, but what looks like a single Catmull-Rom curve is actually a spline: a single curve built up of lots of identically-computed pieces, similar to if you just took a whole bunch of Bézier curves, placed them end to end, and lined up their control points so that things look like a single curve. For a Catmull-Rom curve, each "piece" between two points is defined by the point's coordinates, and the tangent for those points, the latter of which can trivially be derived from knowing the previous and next point:

    + +

    One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

    +

    In fact, before we move on, let's look at how to actually draw the basic form of these curves (I say basic, because there are a number of variations that make things considerable more complex):

    +
    tension = some value greater than 0, defaulting to 1
    +points = a list of at least 4 coordinates
    +
    +for p = 1 to points.length-3 (inclusive):
    +       p0 = points[p-1]
    +  v1 = p1 = points[p]
    +  v2 = p2 = points[p+1]
    +       p3 = points[p+2]
    +
    +  s = 2 * tension
    +  dv1 = (p2-p0) / s
    +  dv2 = (p3-p1) / s
    +
    +  for t = 0 to 1 (inclusive):
    +    c0 = 2*t^3 - 3*t^2 + 1,
    +    c1 = t^3 - 2*t^2 + t,
    +    c2 = -2*t^3 + 3*t^2,
    +    c3 = t^3 - t^2
    +    point(c0 * v1 + c1 * dv1 + c2 * v2 + c3 * dv2)
    +

    Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can losslessly convert between the two, and the maths for doing so is surprisingly simple!

    +

    The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

    +
      +
    • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve coordinates.
    • +
    • A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point, plus a characteristic matrix that we can multiple by the point vector to get on-curve coordinates.
    • +
    +

    Those are very similar, so let's see exactly how similar they are. We've already see the matrix form for Bézier curves, so how different is the matrix form for Catmull-Rom curves?:

    -

    So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression that uses Bézier matrix and vector? The short answer is of course "by using linear algebra", but the real answer is a bit longer, and involves some maths that you may not even care for: just skip over the next bit to see the incredibly simple conversions between the formats, but if you want to know why... let's go!

    +

    That's pretty dang similar. So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression of the Bézier matrix and vector? The short answer is of course "by using linear algebra", but the longer answer is the rest of this section, and involves some maths that you may not even care for: if you just want to know the (incredibly simple) conversions between the two curve forms, feel free to skip to the end of the following explanation, but if you want to how we can get one from the other... let's get mathing!

    Deriving the conversion formulae

    In order to convert between Catmull-Rom curves and Bézier curves, we need to know two things. Firstly, how to express the Catmull-Rom curve using a "set of four coordinates", rather than a mix of coordinates and tangents, and secondly, how to convert those Catmull-Rom coordinates to and from Bézier form.

    -

    So, let's start with the first, where we want to satisfy the following equality:

    +

    We start with the first part, to figure out how we can go from Catmull-Rom V coordinates to Bézier P coordinates, by applying "some matrix T". We don't know what that T is yet, but we'll get to that:

    -

    This mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    +

    So, this mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    Computing T is really more "arranging the numbers":

    Thus:

    -

    However, we're not quite done, because Catmull-Rom curves have a parameter called "tension", written as τ ("tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    +

    However, we're not quite done, because Catmull-Rom curves have that "tension" parameter, written as τ (a lowercase"tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    With the mapping matrix properly done, let's rewrite the "point + tangent" Catmull-Rom matrix form to a matrix form in terms of four coordinates, and see what we end up with:

    @@ -1948,15 +1974,11 @@ for (coordinate, index) in LUT:
    -
    -

    Creating a Catmull-Rom curve from three points

    -

    Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?

    -

    As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.

    -

    Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of n points, in order to draw the equivalent curve using cubic Bézier curves we need a massive 3n-2 points (and that's without double-counting points that are shared by consecutive cubic curves).

    -

    In the following graphic, on the left we see three points that we want to draw a Catmull-Rom curve through (which we can move around freely, by the way), with in the second panel some of the "interesting" Catmull-Rom information: in black there's the baseline start--end, which will act as tangent orientation for the curve at point p2. We also see a virtual point p0 and p4, which are initially just point p2 reflected over the baseline. However, by using the up and down cursor key we can offset these points parallel to the baseline. Why would we want to do this? Because the line p0--p2 acts as departure tangent at p1, and the line p2--p4 acts as arrival tangent at p3. Play around with the graphic a bit to get an idea of what all of that meant:

    - - -

    As should be obvious by now, Catmull-Rom curves are great for "fitting a curvature to some points", but if we want to convert that curve to Bézier form we're going to end up with a lot of separate (but visually joined) Bézier curves. Depending on what we want to do, that'll be either unnecessary work, or exactly what we want: which it is depends entirely on you.

    +
    +

    Creating a Catmull-Rom curve from three points

    +

    Much shorter than the previous section: we saw that Catmull-Rom curves need at least 4 points to draw anything sensible, so how do we create a Catmull-Rom curve from three points?

    +

    Short and sweet: we don't.

    +

    We run through the maths that lets us create a cubic Bézier curve, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.

    diff --git a/docs/ja-JP/index.html b/docs/ja-JP/index.html index e85b189b..0a7dae7e 100644 --- a/docs/ja-JP/index.html +++ b/docs/ja-JP/index.html @@ -100,7 +100,7 @@
  • Molding a curve
  • Curve fitting
  • Bézier curves and Catmull-Rom curves
  • -
  • Creating a Catmull-Rom curve from three points
  • +
  • Creating a Catmull-Rom curve from three points
  • Forming poly-Bézier curves
  • Boolean shape operations
  • Curve offsetting
  • @@ -335,7 +335,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -344,7 +344,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -353,7 +353,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -1585,7 +1585,7 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + @@ -1740,7 +1740,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1749,7 +1749,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1758,7 +1758,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1856,35 +1856,61 @@ for (coordinate, index) in LUT:

    Bézier curves and Catmull-Rom curves

    -

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through the points you define. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, or you can click/tap somewhere to extend the curve.

    +

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through each control point, so they offer a kind of "nuilt-in" curve fitting.

    +

    In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets you add points by clicking/tapping the background, as well as let you control "how fast" the curve passes through its point using the tension slider. The tenser the curve, the more the curve tends towards straight lines from one point to the next.

    Scripts are disabled. Showing fallback image. - + -

    You may have noticed the slider that seems to control the "tension" of the curve: that's a feature of Catmull-Rom curves; because Catmull-Rom curves pass through points, the curve tightness is controlled by a tension factor, rather than by moving control points around.

    -

    What you may also have noticed is that the Catmull-Rom curve seems to just go on forever: add as many points as you like, same curve, rigth? Well, sort of. Catmull-Rom curves are splines, a type of curve with arbitrary number of points, technically consisting of "segments between sets of points", but with maths that makes all the segments line up neatly. As such, at its core a Catmull-Rom consists of two points, and draws a curve between them based on the tangents at those points.

    -

    Now, a Catmull-Rom spline is a form of cubic Hermite spline, and as it so happens, the cubic Bézier curve is also a cubic Hermite spline, so in an interesting bit of programming maths: we can losslessly convert between the two, and the maths (well, the final maths) is surprisingly simple!

    -

    The main difference between Catmull-Rom curves and Bezier curves is "what the points mean". A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point. A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point. Those are very similar, so let's see exactly how similar they are.

    -

    We start with the matrix form of thee Catmull-Rom curve, which looks similar to the Bézier matrix form, with slightly different values in the matrix, and a slightly different coordinate vector:

    +

    Now, it may look like Catmull-Rom curves are very different from Bézier curves, because these curves can get very long indeed, but what looks like a single Catmull-Rom curve is actually a spline: a single curve built up of lots of identically-computed pieces, similar to if you just took a whole bunch of Bézier curves, placed them end to end, and lined up their control points so that things look like a single curve. For a Catmull-Rom curve, each "piece" between two points is defined by the point's coordinates, and the tangent for those points, the latter of which can trivially be derived from knowing the previous and next point:

    + +

    One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

    +

    In fact, before we move on, let's look at how to actually draw the basic form of these curves (I say basic, because there are a number of variations that make things considerable more complex):

    +
    tension = some value greater than 0, defaulting to 1
    +points = a list of at least 4 coordinates
    +
    +for p = 1 to points.length-3 (inclusive):
    +       p0 = points[p-1]
    +  v1 = p1 = points[p]
    +  v2 = p2 = points[p+1]
    +       p3 = points[p+2]
    +
    +  s = 2 * tension
    +  dv1 = (p2-p0) / s
    +  dv2 = (p3-p1) / s
    +
    +  for t = 0 to 1 (inclusive):
    +    c0 = 2*t^3 - 3*t^2 + 1,
    +    c1 = t^3 - 2*t^2 + t,
    +    c2 = -2*t^3 + 3*t^2,
    +    c3 = t^3 - t^2
    +    point(c0 * v1 + c1 * dv1 + c2 * v2 + c3 * dv2)
    +

    Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can losslessly convert between the two, and the maths for doing so is surprisingly simple!

    +

    The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

    +
      +
    • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve coordinates.
    • +
    • A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point, plus a characteristic matrix that we can multiple by the point vector to get on-curve coordinates.
    • +
    +

    Those are very similar, so let's see exactly how similar they are. We've already see the matrix form for Bézier curves, so how different is the matrix form for Catmull-Rom curves?:

    -

    So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression that uses Bézier matrix and vector? The short answer is of course "by using linear algebra", but the real answer is a bit longer, and involves some maths that you may not even care for: just skip over the next bit to see the incredibly simple conversions between the formats, but if you want to know why... let's go!

    +

    That's pretty dang similar. So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression of the Bézier matrix and vector? The short answer is of course "by using linear algebra", but the longer answer is the rest of this section, and involves some maths that you may not even care for: if you just want to know the (incredibly simple) conversions between the two curve forms, feel free to skip to the end of the following explanation, but if you want to how we can get one from the other... let's get mathing!

    Deriving the conversion formulae

    In order to convert between Catmull-Rom curves and Bézier curves, we need to know two things. Firstly, how to express the Catmull-Rom curve using a "set of four coordinates", rather than a mix of coordinates and tangents, and secondly, how to convert those Catmull-Rom coordinates to and from Bézier form.

    -

    So, let's start with the first, where we want to satisfy the following equality:

    +

    We start with the first part, to figure out how we can go from Catmull-Rom V coordinates to Bézier P coordinates, by applying "some matrix T". We don't know what that T is yet, but we'll get to that:

    -

    This mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    +

    So, this mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    Computing T is really more "arranging the numbers":

    Thus:

    -

    However, we're not quite done, because Catmull-Rom curves have a parameter called "tension", written as τ ("tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    +

    However, we're not quite done, because Catmull-Rom curves have that "tension" parameter, written as τ (a lowercase"tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    With the mapping matrix properly done, let's rewrite the "point + tangent" Catmull-Rom matrix form to a matrix form in terms of four coordinates, and see what we end up with:

    @@ -1944,15 +1970,11 @@ for (coordinate, index) in LUT:
    -
    -

    Creating a Catmull-Rom curve from three points

    -

    Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?

    -

    As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.

    -

    Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of n points, in order to draw the equivalent curve using cubic Bézier curves we need a massive 3n-2 points (and that's without double-counting points that are shared by consecutive cubic curves).

    -

    In the following graphic, on the left we see three points that we want to draw a Catmull-Rom curve through (which we can move around freely, by the way), with in the second panel some of the "interesting" Catmull-Rom information: in black there's the baseline start--end, which will act as tangent orientation for the curve at point p2. We also see a virtual point p0 and p4, which are initially just point p2 reflected over the baseline. However, by using the up and down cursor key we can offset these points parallel to the baseline. Why would we want to do this? Because the line p0--p2 acts as departure tangent at p1, and the line p2--p4 acts as arrival tangent at p3. Play around with the graphic a bit to get an idea of what all of that meant:

    - - -

    As should be obvious by now, Catmull-Rom curves are great for "fitting a curvature to some points", but if we want to convert that curve to Bézier form we're going to end up with a lot of separate (but visually joined) Bézier curves. Depending on what we want to do, that'll be either unnecessary work, or exactly what we want: which it is depends entirely on you.

    +
    +

    Creating a Catmull-Rom curve from three points

    +

    Much shorter than the previous section: we saw that Catmull-Rom curves need at least 4 points to draw anything sensible, so how do we create a Catmull-Rom curve from three points?

    +

    Short and sweet: we don't.

    +

    We run through the maths that lets us create a cubic Bézier curve, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.

    diff --git a/docs/js/custom-element/api/graphics-api.js b/docs/js/custom-element/api/graphics-api.js index dee015e7..6853145e 100644 --- a/docs/js/custom-element/api/graphics-api.js +++ b/docs/js/custom-element/api/graphics-api.js @@ -157,7 +157,7 @@ class GraphicsAPI extends BaseAPI { * @param {float} initial the initial value for this property. * @param {boolean} redraw whether or not to redraw after updating the value from the slider. */ - setSlider(qs, propname, initial, redraw = true) { + setSlider(qs, propname, initial, transform) { if (typeof this[propname] !== `undefined`) { throw new Error(`this.${propname} already exists: cannot bind slider.`); } @@ -170,23 +170,16 @@ class GraphicsAPI extends BaseAPI { return undefined; } - this[propname] = parseFloat(slider.value); + const updateProperty = (evt) => { + let value = parseFloat(slider.value); + slider.setAttribute(`value`, value); + this[propname] = transform ? transform(value) : value; + if (!this.redrawing) this.redraw(); + }; - let handlerName = `on${propname[0].toUpperCase()}${propname - .substring(1) - .toLowerCase()}`; - - if (this[handlerName]) { - this[handlerName](initial); - } else { - slider.value = initial; - } - - slider.listen(`input`, (evt) => { - this[propname] = parseFloat(evt.target.value); - if (this[handlerName]) this[handlerName](this[propname]); - if (redraw && !this.redrawing) this.redraw(); - }); + slider.value = initial; + updateProperty({ target: { value: initial } }); + slider.listen(`input`, updateProperty); return slider; } @@ -308,8 +301,8 @@ class GraphicsAPI extends BaseAPI { /** * Get a random color */ - randomColor(a = 1.0) { - CURRENT_HUE = (CURRENT_HUE + 73) % 360; + randomColor(a = 1.0, cycle = true) { + if (cycle) CURRENT_HUE = (CURRENT_HUE + 73) % 360; return `hsla(${CURRENT_HUE},50%,50%,${a})`; } @@ -375,6 +368,20 @@ class GraphicsAPI extends BaseAPI { this.strokeWeight = false; } + /** + * Set the line-dash pattern + */ + setLineDash(...values) { + this.ctx.setLineDash(values); + } + + /** + * disable line-dash + */ + noLineDash() { + this.ctx.setLineDash([]); + } + /** * Set the context lineWidth */ diff --git a/docs/zh-CN/index.html b/docs/zh-CN/index.html index f22bd465..a8d82090 100644 --- a/docs/zh-CN/index.html +++ b/docs/zh-CN/index.html @@ -100,7 +100,7 @@
  • Molding a curve
  • Curve fitting
  • Bézier curves and Catmull-Rom curves
  • -
  • Creating a Catmull-Rom curve from three points
  • +
  • Creating a Catmull-Rom curve from three points
  • Forming poly-Bézier curves
  • Boolean shape operations
  • Curve offsetting
  • @@ -329,7 +329,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -338,7 +338,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -347,7 +347,7 @@ function Bezier(3,t): Scripts are disabled. Showing fallback image. - + @@ -1579,7 +1579,7 @@ lli = function(line1, line2): Scripts are disabled. Showing fallback image. - + @@ -1734,7 +1734,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1743,7 +1743,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1752,7 +1752,7 @@ for (coordinate, index) in LUT: Scripts are disabled. Showing fallback image. - + @@ -1850,35 +1850,61 @@ for (coordinate, index) in LUT:

    Bézier curves and Catmull-Rom curves

    -

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through the points you define. In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, or you can click/tap somewhere to extend the curve.

    +

    Taking an excursion to different splines, the other common design curve is the Catmull-Rom spline, which unlike Bézier curves pass through each control point, so they offer a kind of "nuilt-in" curve fitting.

    +

    In fact, let's start with just playing with one: the following graphic has a predefined curve that you manipulate the points for, and lets you add points by clicking/tapping the background, as well as let you control "how fast" the curve passes through its point using the tension slider. The tenser the curve, the more the curve tends towards straight lines from one point to the next.

    Scripts are disabled. Showing fallback image. - + -

    You may have noticed the slider that seems to control the "tension" of the curve: that's a feature of Catmull-Rom curves; because Catmull-Rom curves pass through points, the curve tightness is controlled by a tension factor, rather than by moving control points around.

    -

    What you may also have noticed is that the Catmull-Rom curve seems to just go on forever: add as many points as you like, same curve, rigth? Well, sort of. Catmull-Rom curves are splines, a type of curve with arbitrary number of points, technically consisting of "segments between sets of points", but with maths that makes all the segments line up neatly. As such, at its core a Catmull-Rom consists of two points, and draws a curve between them based on the tangents at those points.

    -

    Now, a Catmull-Rom spline is a form of cubic Hermite spline, and as it so happens, the cubic Bézier curve is also a cubic Hermite spline, so in an interesting bit of programming maths: we can losslessly convert between the two, and the maths (well, the final maths) is surprisingly simple!

    -

    The main difference between Catmull-Rom curves and Bezier curves is "what the points mean". A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point. A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point. Those are very similar, so let's see exactly how similar they are.

    -

    We start with the matrix form of thee Catmull-Rom curve, which looks similar to the Bézier matrix form, with slightly different values in the matrix, and a slightly different coordinate vector:

    +

    Now, it may look like Catmull-Rom curves are very different from Bézier curves, because these curves can get very long indeed, but what looks like a single Catmull-Rom curve is actually a spline: a single curve built up of lots of identically-computed pieces, similar to if you just took a whole bunch of Bézier curves, placed them end to end, and lined up their control points so that things look like a single curve. For a Catmull-Rom curve, each "piece" between two points is defined by the point's coordinates, and the tangent for those points, the latter of which can trivially be derived from knowing the previous and next point:

    + +

    One downside of this is that—as you may have noticed from the graphic—the first and last point of the overall curve don't actually join up with the rest of the curve: they don't have a previous/next point respectively, and so there is no way to calculate what their tangent should be. Which also makes it rather tricky to fit a Camull-Rom curve to three points like we were able to do for Bézier curves. More on that in the next section.

    +

    In fact, before we move on, let's look at how to actually draw the basic form of these curves (I say basic, because there are a number of variations that make things considerable more complex):

    +
    tension = some value greater than 0, defaulting to 1
    +points = a list of at least 4 coordinates
    +
    +for p = 1 to points.length-3 (inclusive):
    +       p0 = points[p-1]
    +  v1 = p1 = points[p]
    +  v2 = p2 = points[p+1]
    +       p3 = points[p+2]
    +
    +  s = 2 * tension
    +  dv1 = (p2-p0) / s
    +  dv2 = (p3-p1) / s
    +
    +  for t = 0 to 1 (inclusive):
    +    c0 = 2*t^3 - 3*t^2 + 1,
    +    c1 = t^3 - 2*t^2 + t,
    +    c2 = -2*t^3 + 3*t^2,
    +    c3 = t^3 - t^2
    +    point(c0 * v1 + c1 * dv1 + c2 * v2 + c3 * dv2)
    +

    Now, since a Catmull-Rom curve is a form of cubic Hermite spline, and as cubic Bézier curves are also a form of cubic Hermite spline, we run into an interesting bit of maths programming: we can losslessly convert between the two, and the maths for doing so is surprisingly simple!

    +

    The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":

    +
      +
    • A cubic Bézier curve is defined by a start point, a control point that implies the tangent at the start, a control point that implies the tangent at the end, and an end point, plus a characterising matrix that we can multiply by that point vector to get on-curve coordinates.
    • +
    • A Catmull-Rom curve is defined by a start point, a tangent that for that starting point, an end point, and a tangent for that end point, plus a characteristic matrix that we can multiple by the point vector to get on-curve coordinates.
    • +
    +

    Those are very similar, so let's see exactly how similar they are. We've already see the matrix form for Bézier curves, so how different is the matrix form for Catmull-Rom curves?:

    -

    So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression that uses Bézier matrix and vector? The short answer is of course "by using linear algebra", but the real answer is a bit longer, and involves some maths that you may not even care for: just skip over the next bit to see the incredibly simple conversions between the formats, but if you want to know why... let's go!

    +

    That's pretty dang similar. So the question is: how can we convert that expression with Catmull-Rom matrix and vector into an expression of the Bézier matrix and vector? The short answer is of course "by using linear algebra", but the longer answer is the rest of this section, and involves some maths that you may not even care for: if you just want to know the (incredibly simple) conversions between the two curve forms, feel free to skip to the end of the following explanation, but if you want to how we can get one from the other... let's get mathing!

    Deriving the conversion formulae

    In order to convert between Catmull-Rom curves and Bézier curves, we need to know two things. Firstly, how to express the Catmull-Rom curve using a "set of four coordinates", rather than a mix of coordinates and tangents, and secondly, how to convert those Catmull-Rom coordinates to and from Bézier form.

    -

    So, let's start with the first, where we want to satisfy the following equality:

    +

    We start with the first part, to figure out how we can go from Catmull-Rom V coordinates to Bézier P coordinates, by applying "some matrix T". We don't know what that T is yet, but we'll get to that:

    -

    This mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    +

    So, this mapping says that in order to map a Catmull-Rom "point + tangent" vector to something based on an "all coordinates" vector, we need to determine the mapping matrix such that applying T yields P2 as start point, P3 as end point, and two tangents based on the lines between P1 and P3, and P2 nd P4, respectively.

    Computing T is really more "arranging the numbers":

    Thus:

    -

    However, we're not quite done, because Catmull-Rom curves have a parameter called "tension", written as τ ("tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    +

    However, we're not quite done, because Catmull-Rom curves have that "tension" parameter, written as τ (a lowercase"tau"), which is a scaling factor for the tangent vectors: the bigger the tension, the smaller the tangents, and the smaller the tension, the bigger the tangents. As such, the tension factor goes in the denominator for the tangents, and before we continue, let's add that tension factor into both our coordinate vector representation, and mapping matrix T:

    With the mapping matrix properly done, let's rewrite the "point + tangent" Catmull-Rom matrix form to a matrix form in terms of four coordinates, and see what we end up with:

    @@ -1938,15 +1964,11 @@ for (coordinate, index) in LUT:
    -
    -

    Creating a Catmull-Rom curve from three points

    -

    Now, we saw how to fit a Bézier curve to three points, but if Catmull-Rom curves go through points, why can't we just use those to do curve fitting, instead?

    -

    As a matter of fact, we can, but there's a difference between the kind of curve fitting we did in the previous section, and the kind of curve fitting that we can do with Catmull-Rom curves. In the previous section we came up with a single curve that goes through three points. There was a decent amount of maths and computation involved, and the end result was three or four coordinates that described a single curve, depending on whether we were fitting a quadratic or cubic curve.

    -

    Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of n points, in order to draw the equivalent curve using cubic Bézier curves we need a massive 3n-2 points (and that's without double-counting points that are shared by consecutive cubic curves).

    -

    In the following graphic, on the left we see three points that we want to draw a Catmull-Rom curve through (which we can move around freely, by the way), with in the second panel some of the "interesting" Catmull-Rom information: in black there's the baseline start--end, which will act as tangent orientation for the curve at point p2. We also see a virtual point p0 and p4, which are initially just point p2 reflected over the baseline. However, by using the up and down cursor key we can offset these points parallel to the baseline. Why would we want to do this? Because the line p0--p2 acts as departure tangent at p1, and the line p2--p4 acts as arrival tangent at p3. Play around with the graphic a bit to get an idea of what all of that meant:

    - - -

    As should be obvious by now, Catmull-Rom curves are great for "fitting a curvature to some points", but if we want to convert that curve to Bézier form we're going to end up with a lot of separate (but visually joined) Bézier curves. Depending on what we want to do, that'll be either unnecessary work, or exactly what we want: which it is depends entirely on you.

    +
    +

    Creating a Catmull-Rom curve from three points

    +

    Much shorter than the previous section: we saw that Catmull-Rom curves need at least 4 points to draw anything sensible, so how do we create a Catmull-Rom curve from three points?

    +

    Short and sweet: we don't.

    +

    We run through the maths that lets us create a cubic Bézier curve, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.