1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-23 16:53:06 +02:00

catmull rom

This commit is contained in:
Pomax
2020-09-03 20:08:21 -07:00
parent bb5adcaebd
commit d2992ebd15
31 changed files with 417 additions and 353 deletions

View File

@@ -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<e; i++) {
const p = points, n = points.length;
for (let i=1, e=n-2; i<e; i++) {
this.dragSegment(
full[i],
full[i+1],
full[i+2],
full[i+3]
p[i - 1], // used for tangent
p[i + 0], // "current" point
p[i + 1], // "next" point
p[i + 2] // used for tangent
);
}
setLineDash(4,2);
setStroke(`lightblue`);
line(p[0].x, p[0].y, p[1].x, p[1].y);
line(p[n-1].x, p[n-1].y, p[n-2].x, p[n-2].y);
noLineDash();
points.forEach(p => {
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();
}
}

View File

@@ -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.
<graphics-element title="A Catmull-Rom curve" src="./catmull-rom.js">
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
</graphics-element>
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!
<div class="note">
@@ -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 <em>T</em> 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 <em>T</em> 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 <em>T</em> is really more "arranging the numbers":
@@ -107,7 +154,7 @@ Thus:
\end{bmatrix}
\]
However, we're not <em>quite</em> 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 <em>T</em>:
However, we're not <em>quite</em> 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 <em>T</em>:
\[
\begin{bmatrix}

View File

@@ -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<e; i++) {
this.dragSegment(
full[i],
full[i+1],
full[i+2],
full[i+3]
);
}
points.forEach(p => {
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();
}
}

View File

@@ -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.

View File

@@ -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 <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> 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:
<Graphic title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
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.

View File

@@ -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);
}
};

View File

@@ -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);
})

View File

@@ -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();
}

View File

@@ -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();
}

View File

@@ -52,7 +52,7 @@ export default [
// A quick foray into Catmull-Rom splines
'catmullconv',
'catmullmolding',
'catmullfitting',
// "things made of more than on curve"
'polybezier',

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 18 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 17 KiB

View File

@@ -100,7 +100,7 @@
<li><a href="#molding">Molding a curve</a></li>
<li><a href="#curvefitting">Curve fitting</a></li>
<li><a href="#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="#catmullfitting">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="#shapes">Boolean shape operations</a></li>
<li><a href="#offsetting">Curve offsetting</a></li>
@@ -333,7 +333,7 @@ function Bezier(3,t):
<graphics-element title="Quadratic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
<img width="275px" height="275px" src="images\chapters\control\c7cebd1c54c120c3a9513062e562f3a6.png">
<label>Quadratic interpolations</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -342,7 +342,7 @@ function Bezier(3,t):
<graphics-element title="Cubic interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
<img width="275px" height="275px" src="images\chapters\control\2c5b710606f31ed8830397ad2a77d16e.png">
<label>Cubic interpolations</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -351,7 +351,7 @@ function Bezier(3,t):
<graphics-element title="15th degree interpolations" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
<img width="275px" height="275px" src="images\chapters\control\882ae425daeb3f449e5a4d649b8425e7.png">
<label>15th degree interpolations</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -1589,7 +1589,7 @@ lli = function(line1, line2):
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
<img width="825px" height="275px" src="images\chapters\curveintersection\eae3bb142567d9e2b8c1e4d42e8ef505.png">
<label></label>
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1744,7 +1744,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a quadratic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="quadratic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
<img width="825px" height="275px" src="images\chapters\molding\9a214cd85a1f0857b1b57db5e9c37b9c.png">
<label></label>
</fallback-image></graphics-element>
@@ -1753,7 +1753,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
<img width="825px" height="275px" src="images\chapters\molding\502de5e21415ee75ab5d2cffbc921a77.png">
<label></label>
</fallback-image></graphics-element>
@@ -1762,7 +1762,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic" data-interpolated="true">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
<img width="825px" height="275px" src="images\chapters\molding\610251fd14e24cd1378590de87ce2a74.png">
<label></label>
</fallback-image>
<input type="range" min="10" max="200" step="1" value="100" class="slide-control">
@@ -1860,35 +1860,61 @@ for (coordinate, index) in LUT:
</section>
<section id="catmullconv">
<h1><a href="#catmullconv">Bézier curves and Catmull-Rom curves</a></h1>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> 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.</p>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> each control point, so they offer a kind of "nuilt-in" curve fitting.</p>
<p>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.</p>
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\catmullconv\b0cb0cccdbea86dcdd610a871e86183f.png">
<img width="275px" height="275px" src="images\chapters\catmullconv\a6289e403f079730b7546ab94176d42f.png">
<label>A Catmull-Rom curve</label>
</fallback-image>
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
</graphics-element>
<p>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.</p>
<p>What you may <em>also</em> 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.</p>
<p>Now, a Catmull-Rom spline is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as it so happens, the cubic Bézier curve is <em>also</em> 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!</p>
<p>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 <em>very</em> similar, so let's see exactly <em>how</em> similar they are.</p>
<p>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:</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Spline_(mathematics)">spline</a>: 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 <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline">can trivially be derived</a> from knowing the previous and next point:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg" width="297px" height="84px" loading="lazy">
<p>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 <a href="#catmullfitting">the next section</a>.</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition">considerable</a> more <a href="https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline">complex</a>):</p>
<pre><code>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)</code></pre>
<p>Now, since a Catmull-Rom curve is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as cubic Bézier curves are <em>also</em> 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!</p>
<p>The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":</p>
<ul>
<li>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.</li>
<li>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.</li>
</ul>
<p>Those are <em>very</em> similar, so let's see exactly <em>how</em> 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?:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/9215d05705c8e8a7ebd718ae6f690371.svg" width="423px" height="75px" loading="lazy">
<p>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 <em>why</em>... let's go!</p>
<p>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 <em>how</em> we can get one from the other... let's get mathing!</p>
<div class="note">
<h2>Deriving the conversion formulae</h2>
<p>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.</p>
<p>So, let's start with the first, where we want to satisfy the following equality:</p>
<p>We start with the first part, to figure out how we can go from Catmull-Rom <strong>V</strong> coordinates to Bézier <strong>P</strong> coordinates, by applying "some matrix <strong>T</strong>". We don't know what that <strong>T</strong> is yet, but we'll get to that:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1811b59c5ab9233f08590396e5d03303.svg" width="196px" height="79px" loading="lazy">
<p>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 <em>T</em> 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.</p>
<p>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 <em>T</em> 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.</p>
<p>Computing <em>T</em> is really more "arranging the numbers":</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/4d524810417b4caffedd13af23135f5b.svg" width="621px" height="79px" loading="lazy">
<p>Thus:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f41487aff3e34fafd5d4ee5979f133f1.svg" width="145px" height="76px" loading="lazy">
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/06ae1e3fdc660e59d618e0760e8e9ab5.svg" width="291px" height="79px" loading="lazy">
<p>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:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="423px" height="75px" loading="lazy">
@@ -1948,15 +1974,11 @@ for (coordinate, index) in LUT:
<img class="LaTeX SVG" src="./images/chapters/catmullconv/eae7f01976e511ee38b08b6edc8765d2.svg" width="300px" height="80px" loading="lazy">
</section>
<section id="catmullmolding">
<h1><a href="#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>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.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
<p>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:</p>
<Graphic title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
<p>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.</p>
<section id="catmullfitting">
<h1><a href="#catmullfitting">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>Short and sweet: we don't.</p>
<p>We run through the maths that lets us <a href="pointcurves">create a cubic Bézier curve</a>, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.</p>
</section>
<section id="polybezier">

View File

@@ -100,7 +100,7 @@
<li><a href="ja-JP/index.html#molding">Molding a curve</a></li>
<li><a href="ja-JP/index.html#curvefitting">Curve fitting</a></li>
<li><a href="ja-JP/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="ja-JP/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="ja-JP/index.html#catmullfitting">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="ja-JP/index.html#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="ja-JP/index.html#shapes">Boolean shape operations</a></li>
<li><a href="ja-JP/index.html#offsetting">Curve offsetting</a></li>
@@ -335,7 +335,7 @@ function Bezier(3,t):
<graphics-element title="2次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
<img width="275px" height="275px" src="images\chapters\control\c7cebd1c54c120c3a9513062e562f3a6.png">
<label>2次の補間</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -344,7 +344,7 @@ function Bezier(3,t):
<graphics-element title="3次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
<img width="275px" height="275px" src="images\chapters\control\2c5b710606f31ed8830397ad2a77d16e.png">
<label>3次の補間</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -353,7 +353,7 @@ function Bezier(3,t):
<graphics-element title="15次の補間" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
<img width="275px" height="275px" src="images\chapters\control\882ae425daeb3f449e5a4d649b8425e7.png">
<label>15次の補間</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -1585,7 +1585,7 @@ lli = function(line1, line2):
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
<img width="825px" height="275px" src="images\chapters\curveintersection\eae3bb142567d9e2b8c1e4d42e8ef505.png">
<label></label>
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1740,7 +1740,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a quadratic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="quadratic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
<img width="825px" height="275px" src="images\chapters\molding\9a214cd85a1f0857b1b57db5e9c37b9c.png">
<label></label>
</fallback-image></graphics-element>
@@ -1749,7 +1749,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
<img width="825px" height="275px" src="images\chapters\molding\502de5e21415ee75ab5d2cffbc921a77.png">
<label></label>
</fallback-image></graphics-element>
@@ -1758,7 +1758,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic" data-interpolated="true">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
<img width="825px" height="275px" src="images\chapters\molding\610251fd14e24cd1378590de87ce2a74.png">
<label></label>
</fallback-image>
<input type="range" min="10" max="200" step="1" value="100" class="slide-control">
@@ -1856,35 +1856,61 @@ for (coordinate, index) in LUT:
</section>
<section id="catmullconv">
<h1><a href="ja-JP/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></h1>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> 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.</p>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> each control point, so they offer a kind of "nuilt-in" curve fitting.</p>
<p>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.</p>
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\catmullconv\b0cb0cccdbea86dcdd610a871e86183f.png">
<img width="275px" height="275px" src="images\chapters\catmullconv\a6289e403f079730b7546ab94176d42f.png">
<label>A Catmull-Rom curve</label>
</fallback-image>
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
</graphics-element>
<p>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.</p>
<p>What you may <em>also</em> 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.</p>
<p>Now, a Catmull-Rom spline is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as it so happens, the cubic Bézier curve is <em>also</em> 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!</p>
<p>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 <em>very</em> similar, so let's see exactly <em>how</em> similar they are.</p>
<p>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:</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Spline_(mathematics)">spline</a>: 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 <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline">can trivially be derived</a> from knowing the previous and next point:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg" width="297px" height="84px" loading="lazy">
<p>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 <a href="#catmullfitting">the next section</a>.</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition">considerable</a> more <a href="https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline">complex</a>):</p>
<pre><code>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)</code></pre>
<p>Now, since a Catmull-Rom curve is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as cubic Bézier curves are <em>also</em> 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!</p>
<p>The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":</p>
<ul>
<li>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.</li>
<li>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.</li>
</ul>
<p>Those are <em>very</em> similar, so let's see exactly <em>how</em> 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?:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/9215d05705c8e8a7ebd718ae6f690371.svg" width="423px" height="75px" loading="lazy">
<p>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 <em>why</em>... let's go!</p>
<p>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 <em>how</em> we can get one from the other... let's get mathing!</p>
<div class="note">
<h2>Deriving the conversion formulae</h2>
<p>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.</p>
<p>So, let's start with the first, where we want to satisfy the following equality:</p>
<p>We start with the first part, to figure out how we can go from Catmull-Rom <strong>V</strong> coordinates to Bézier <strong>P</strong> coordinates, by applying "some matrix <strong>T</strong>". We don't know what that <strong>T</strong> is yet, but we'll get to that:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1811b59c5ab9233f08590396e5d03303.svg" width="196px" height="79px" loading="lazy">
<p>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 <em>T</em> 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.</p>
<p>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 <em>T</em> 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.</p>
<p>Computing <em>T</em> is really more "arranging the numbers":</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/4d524810417b4caffedd13af23135f5b.svg" width="621px" height="79px" loading="lazy">
<p>Thus:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f41487aff3e34fafd5d4ee5979f133f1.svg" width="145px" height="76px" loading="lazy">
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/06ae1e3fdc660e59d618e0760e8e9ab5.svg" width="291px" height="79px" loading="lazy">
<p>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:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="423px" height="75px" loading="lazy">
@@ -1944,15 +1970,11 @@ for (coordinate, index) in LUT:
<img class="LaTeX SVG" src="./images/chapters/catmullconv/eae7f01976e511ee38b08b6edc8765d2.svg" width="300px" height="80px" loading="lazy">
</section>
<section id="catmullmolding">
<h1><a href="ja-JP/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>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.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
<p>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:</p>
<Graphic title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
<p>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.</p>
<section id="catmullfitting">
<h1><a href="ja-JP/index.html#catmullfitting">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>Short and sweet: we don't.</p>
<p>We run through the maths that lets us <a href="pointcurves">create a cubic Bézier curve</a>, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.</p>
</section>
<section id="polybezier">

View File

@@ -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
*/

View File

@@ -100,7 +100,7 @@
<li><a href="zh-CN/index.html#molding">Molding a curve</a></li>
<li><a href="zh-CN/index.html#curvefitting">Curve fitting</a></li>
<li><a href="zh-CN/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></li>
<li><a href="zh-CN/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="zh-CN/index.html#catmullfitting">Creating a Catmull-Rom curve from three points</a></li>
<li><a href="zh-CN/index.html#polybezier">Forming poly-Bézier curves</a></li>
<li><a href="zh-CN/index.html#shapes">Boolean shape operations</a></li>
<li><a href="zh-CN/index.html#offsetting">Curve offsetting</a></li>
@@ -329,7 +329,7 @@ function Bezier(3,t):
<graphics-element title="二次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="3">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\ecc15848fbe7b2176b0c89973f07c694.png">
<img width="275px" height="275px" src="images\chapters\control\c7cebd1c54c120c3a9513062e562f3a6.png">
<label>二次插值</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -338,7 +338,7 @@ function Bezier(3,t):
<graphics-element title="三次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="4">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\49423783987ac4bc49fbe4c519dbc1d1.png">
<img width="275px" height="275px" src="images\chapters\control\2c5b710606f31ed8830397ad2a77d16e.png">
<label>三次插值</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -347,7 +347,7 @@ function Bezier(3,t):
<graphics-element title="15次插值" width="275" height="275" src="./chapters/control/lerp.js" data-degree="15">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\control\afd21a9ba16965c2e7ec2d0d14892250.png">
<img width="275px" height="275px" src="images\chapters\control\882ae425daeb3f449e5a4d649b8425e7.png">
<label>15次插值</label>
</fallback-image>
<input type="range" min="0" max="1" step="0.01" value="0" class="slide-control">
@@ -1579,7 +1579,7 @@ lli = function(line1, line2):
<graphics-element title="Curve/curve intersections" width="825" height="275" src="./chapters/curveintersection/curve-curve.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\curveintersection\3689ed0c15eace45a1f6ae03909ad8ed.png">
<img width="825px" height="275px" src="images\chapters\curveintersection\eae3bb142567d9e2b8c1e4d42e8ef505.png">
<label></label>
</fallback-image>
<input type="range" min="0.01" max="1" step="0.01" value="1" class="slide-control">
@@ -1734,7 +1734,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a quadratic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="quadratic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\6b91671c2962530b863ae0da5789a9cc.png">
<img width="825px" height="275px" src="images\chapters\molding\9a214cd85a1f0857b1b57db5e9c37b9c.png">
<label></label>
</fallback-image></graphics-element>
@@ -1743,7 +1743,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\522f1edd37163772b81acb86d3a4f423.png">
<img width="825px" height="275px" src="images\chapters\molding\502de5e21415ee75ab5d2cffbc921a77.png">
<label></label>
</fallback-image></graphics-element>
@@ -1752,7 +1752,7 @@ for (coordinate, index) in LUT:
<graphics-element title="Molding a cubic Bézier curve" width="825" height="275" src="./chapters/molding/molding.js" data-type="cubic" data-interpolated="true">
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="825px" height="275px" src="images\chapters\molding\ea1656c068a60631135ad499e8a29453.png">
<img width="825px" height="275px" src="images\chapters\molding\610251fd14e24cd1378590de87ce2a74.png">
<label></label>
</fallback-image>
<input type="range" min="10" max="200" step="1" value="100" class="slide-control">
@@ -1850,35 +1850,61 @@ for (coordinate, index) in LUT:
</section>
<section id="catmullconv">
<h1><a href="zh-CN/index.html#catmullconv">Bézier curves and Catmull-Rom curves</a></h1>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> 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.</p>
<p>Taking an excursion to different splines, the other common design curve is the <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull.E2.80.93Rom_spline">Catmull-Rom spline</a>, which unlike Bézier curves pass <em>through</em> each control point, so they offer a kind of "nuilt-in" curve fitting.</p>
<p>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.</p>
<graphics-element title="A Catmull-Rom curve" width="275" height="275" src="./chapters/catmullconv/catmull-rom.js" >
<fallback-image>
<span class="view-source">Scripts are disabled. Showing fallback image.</span>
<img width="275px" height="275px" src="images\chapters\catmullconv\b0cb0cccdbea86dcdd610a871e86183f.png">
<img width="275px" height="275px" src="images\chapters\catmullconv\a6289e403f079730b7546ab94176d42f.png">
<label>A Catmull-Rom curve</label>
</fallback-image>
<input type="range" min="0.1" max="1" step="0.01" value="0.5" class="slide-control tension">
</graphics-element>
<p>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.</p>
<p>What you may <em>also</em> 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.</p>
<p>Now, a Catmull-Rom spline is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as it so happens, the cubic Bézier curve is <em>also</em> 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!</p>
<p>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 <em>very</em> similar, so let's see exactly <em>how</em> similar they are.</p>
<p>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:</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Spline_(mathematics)">spline</a>: 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 <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline#Catmull%E2%80%93Rom_spline">can trivially be derived</a> from knowing the previous and next point:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/2844a4f4d222374a25b5f673c94679d9.svg" width="297px" height="84px" loading="lazy">
<p>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 <a href="#catmullfitting">the next section</a>.</p>
<p>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 <a href="https://en.wikipedia.org/wiki/Centripetal_Catmull%E2%80%93Rom_spline#Definition">considerable</a> more <a href="https://en.wikipedia.org/wiki/Kochanek%E2%80%93Bartels_spline">complex</a>):</p>
<pre><code>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)</code></pre>
<p>Now, since a Catmull-Rom curve is a form of <a href="https://en.wikipedia.org/wiki/Cubic_Hermite_spline">cubic Hermite spline</a>, and as cubic Bézier curves are <em>also</em> 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!</p>
<p>The main difference between Catmull-Rom curves and Bézier curves is "what the points mean":</p>
<ul>
<li>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.</li>
<li>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.</li>
</ul>
<p>Those are <em>very</em> similar, so let's see exactly <em>how</em> 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?:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/9215d05705c8e8a7ebd718ae6f690371.svg" width="423px" height="75px" loading="lazy">
<p>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 <em>why</em>... let's go!</p>
<p>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 <em>how</em> we can get one from the other... let's get mathing!</p>
<div class="note">
<h2>Deriving the conversion formulae</h2>
<p>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.</p>
<p>So, let's start with the first, where we want to satisfy the following equality:</p>
<p>We start with the first part, to figure out how we can go from Catmull-Rom <strong>V</strong> coordinates to Bézier <strong>P</strong> coordinates, by applying "some matrix <strong>T</strong>". We don't know what that <strong>T</strong> is yet, but we'll get to that:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/1811b59c5ab9233f08590396e5d03303.svg" width="196px" height="79px" loading="lazy">
<p>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 <em>T</em> 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.</p>
<p>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 <em>T</em> 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.</p>
<p>Computing <em>T</em> is really more "arranging the numbers":</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/4d524810417b4caffedd13af23135f5b.svg" width="621px" height="79px" loading="lazy">
<p>Thus:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/f41487aff3e34fafd5d4ee5979f133f1.svg" width="145px" height="76px" loading="lazy">
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<p>However, we're not <em>quite</em> 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 <em>T</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/06ae1e3fdc660e59d618e0760e8e9ab5.svg" width="291px" height="79px" loading="lazy">
<p>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:</p>
<img class="LaTeX SVG" src="./images/chapters/catmullconv/cc1e2ff43350c32f0ae9ba9a7652b8fb.svg" width="423px" height="75px" loading="lazy">
@@ -1938,15 +1964,11 @@ for (coordinate, index) in LUT:
<img class="LaTeX SVG" src="./images/chapters/catmullconv/eae7f01976e511ee38b08b6edc8765d2.svg" width="300px" height="80px" loading="lazy">
</section>
<section id="catmullmolding">
<h1><a href="zh-CN/index.html#catmullmolding">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>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.</p>
<p>Using Catmull-Rom curves, we need virtually no computation, but even though we end up with one Catmull-Rom curve of <i>n</i> points, in order to draw the equivalent curve using cubic Bézier curves we need a massive <i>3n-2</i> points (and that's without double-counting points that are shared by consecutive cubic curves).</p>
<p>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:</p>
<Graphic title="Catmull-Rom curve fitting" setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
<p>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.</p>
<section id="catmullfitting">
<h1><a href="zh-CN/index.html#catmullfitting">Creating a Catmull-Rom curve from three points</a></h1>
<p>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?</p>
<p>Short and sweet: we don't.</p>
<p>We run through the maths that lets us <a href="pointcurves">create a cubic Bézier curve</a>, and then convert its coordinates to Catmull-Rom form using the conversion formulae we saw above.</p>
</section>
<section id="polybezier">