1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-26 09:44:32 +02:00

curvature

This commit is contained in:
Pomax
2020-08-27 14:59:08 -07:00
parent 0730e3dda4
commit 17d71c7d70
22 changed files with 265 additions and 249 deletions

View File

@@ -1,30 +1,39 @@
# Curvature of a curve
Imagine we have two curves, and we want to line them in up in a way that "looks right". What would we use as metric to let a computer decide what "looks right" means? For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between leaving one curve and entering the next, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next. What we want is to ensure that the [_curvature_](https://en.wikipedia.org/wiki/Curvature) at the transition from one curve to the next "looks good". So, we could have them share an end coordinate, and then ensure that the derivatives for both curves match at that coordinate, and at a casual glance, that seems the perfect solution: if we make the derivatives match, then both the "direction" in which we travel from one curve to the next is the same, and the "speed" at which we travel the curve will be the same.
If we have two curves, and we want to line them in up in a way that "looks right", what would we use as metric to let a computer decide what "looks right" means?
For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between the end of one and the start of the next curve, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next.
What we want is to ensure that the [curvature](https://en.wikipedia.org/wiki/Curvature) at the transition from one curve to the next "looks good". So, we start with a shared coordinate, and then also require that derivatives for both curves match at that coordinate. That way, we're assured that their tangents line up, which must mean the curve transition is perfectly smooth. We can even make the second, third, etc. derivatives match up for better and better transitions.
Problem solved!
But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on [reordering curves](#reordering): what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the _exact same curve_ but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".
However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on [reordering curves](#reordering) showed us that the same looking curve can have an infinite number of curve expressions of arbitraryly high Bezier degree, and each of those will have _widly_ different derivative values.
How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that the thing we can use to define curvature is the thing we talked about in the previous section: arc length.
So what we really want is some kind of expression that's not based on any particular expression of `t`, but is based on something that is invariant to the _kind_ of function(s) we use to draw our curve. And the prime candidate for this is our curve expression, reparameterised for distance: no matter what order of Bezier curve we use, if we were able to rewrite it as a function of distance-along-the-curve, all those different degree Bezier functions would end up being _the same_ function for "coordinate at some distance D along the curve".
Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.
We've seen this before... that's the arc length function.
You might think that in order to find the curvature of a curve, we now need to find and then solve the arc length function, and that would be a problem because we just saw that there is no way to actually do that: don't worry, we don't. We do need to know the _form_ of the arc length function, which we saw above, but it's not the thing we're actually interested in, and we're going to be rewriting it in a way that makes most of the crazy complex things about it just... disappear.
So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the _form_ of the arc length function, which we saw above and is fairly simple, rather than needing to _solve_ the arc length function. If we start with the arc length expression and the [run through the steps necessary](http://mathworld.wolfram.com/Curvature.html) to determine _its_ derivative (with an alternative, shorter demonstration of how to do this found [over on Stackexchange](https://math.stackexchange.com/a/275324/71940)), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the [fundamental theorem of calculus](https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus)), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.
In fact, after [running through the steps necessary](http://mathworld.wolfram.com/Curvature.html) to determine what we're left with if we use the arclength function's derivative (with another run-through of the maths [here](https://math.stackexchange.com/a/275324/71940)), rather than the curve's original function's derivative, then the integral disappears entirely (because of the [fundamental theorem of calculus](https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus)), and we're left with some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.
Let me highlight what just happened, because it's pretty special:
Let me just highlight that before we move on: we calculate the curvature of a curve using the arc length function derivative, because the original function's derivative is entirely unreliable, and in doing so we end up with a formula that expresses curvature in terms of the original function's derivatives.
1. we wanted to make curves line up, and initially thought to match the curves' derivatives, but
2. that turned out to be a really bad choice, so instead
3. we picked a function that is basically impossible to work with, and then _worked with that_, which
4. gives us a simple formula that is _and expression using the curves' derivatives_.
*That's crazy!*
But, that's what makes maths such an interesting thing: it can show you that all your assumptions are completely wrong, only to then go "but actually, you were on the right track all along, here: ..." with a solution that is so easy to work with as to almost seem mundane. So: enough of all this text, how do we calculate curvature? What is the function for κ? Concisely, the function is this:
But that's also one of the things that makes maths so powerful: even if your initial ideas are off the mark, you might be much closer than you thought you were, and the journey from "thinking we're completely wrong" to "actually being remarkably close to being right" is where we can find a lot of insight.
So, what does the function look like? This:
\[
\kappa = \frac{{x}'{y}'' - {x}''{y}'}{({x}'^2+{y}'^2)^{\frac{3}{2}}}
\]
Which is really just a "short form" that glosses over the fact that we're dealing with functions:
Which is really just a "short form" that glosses over the fact that we're dealing with functions of `t`, so let's expand that a tiny bit:
\[
\kappa(t) = \frac{{B_x}'(t){B_y}''(t) - {B_x}''(t){B_y}'(t)}{({B_x}'(t)^2+{B_y}'(t)^2)^{\frac{3}{2}}}
@@ -32,40 +41,32 @@ Which is really just a "short form" that glosses over the fact that we're dealin
And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: _any_) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means [we know what the first and second derivatives are](#derivatives), and so evaluating this function for any **t** value is just a matter of basic arithematics.
<div class="howtocode">
### Implement the kappa function
In fact, let's just implement it right now:
```
function kappa(t, B):
d = B.getDerivative()
dd = d.getDerivative()
dx = d.getX(t)
dy = d.getY(t)
ddx = dd.getX(t)
ddy = dd.getY(t)
numerator = dx * ddy - ddx * dy
denominator = pow(dx*dx + dy*dy, 1.5)
d = B.getDerivative(t)
dd = B.getSecondDerivative(t)
numerator = d.x * dd.y - dd.x * d.y
denominator = pow(d.x*d.x + d.y*d.y, 3/2)
if denominator is 0: return NaN;
return numerator / denominator
```
That was easy!
In fact, it stays easy because we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest relation in this entire primer:
That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anwyay)
With all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest transition.
<graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" src="./curvature.js"></graphics-element>
One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. A way around that, of course, is to show the curvature on both sides of the curve, so let's just do that. But let's take it one step further: we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest bit of maths found in this entire primer:
\[
R(t) = \frac{1}{\kappa(t)}
\]
So that's a rather convenient fact to know, too.
So let's revisit the previous graphic with the curvature visualised on both sides of our curves, as well as showing the circle that "fits" our curve at some point that we can control by using a slider:
</div>
So with all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest looking transition we could ask for.
<Graphic title="Matching curvatures for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.draw} />
One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. In your code you typically solve this by matching absolute values, but that's not super easy to program visually... however, we _can_ just show the curvature on both sides of the curve, making lining things up a bit easier:
<Graphic title="(Easier) curvature matching for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.drawOmni} />
<graphics-element title="(Easier) curvature matching for a quadratic and cubic Bézier curve" width="825" src="./curvature.js" data-omni="true">
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
</graphics-element>

View File

@@ -0,0 +1,77 @@
let q, c;
setup() {
q = new Bezier(this, 60,55, 125,160, 365,165);
c = new Bezier(this, 385,165, 645,165, 645,70, 750,165);
setSlider(`.slide-control`, `position`, 0);
setMovable(q.points.concat(c.points));
}
draw() {
clear();
[q, c].forEach(curve => {
curve.drawSkeleton();
curve.drawCurve();
this.drawCurvature(curve);
curve.drawPoints();
});
if (this.parameters.omni) {
let t = this.position;
let curve = q;
if (t>1) { t -= 1; curve = c; }
this.drawIncidentCircle(curve, t)
}
}
drawCurvature(curve) {
let s, t, p, n, k, ox, oy;
for(s=0; s<256; s++) {
setStroke(`rgba(255,127,${s},0.6)`);
t = s/255;
p = curve.get(t);
n = curve.normal(t);
k = this.computeCurvature(curve, t) * 10000;
ox = k * n.x;
oy = k * n.y;
line(p.x, p.y, p.x + ox, p.y + oy);
// And if requested, also draw it along the anti-normal.
if (this.parameters.omni) {
setStroke(`rgba(${s},127,255,0.6)`);
line(p.x, p.y, p.x - ox, p.y - oy);
}
}
}
computeCurvature(curve, t) {
const d = curve.derivative(t),
dd = curve.dderivative(t),
num = d.x * dd.y - d.y * dd.x,
qdsum = d.x * d.x + d.y * d.y,
dnm = pow(qdsum, 3 / 2);
if (num === 0 || dnm === 0) return 0;
return num / dnm;
}
drawIncidentCircle(curve, t) {
let p = curve.get(t),
n = curve.normal(t),
k = this.computeCurvature(curve, t),
r = 1 / k,
rx = p.x + n.x * r,
ry = p.y + n.y * r;
setFill(`rgba(200,200,255,0.4)`);
setStroke(`red`);
line(p.x, p.y, rx, ry);
circle(p.x, p.y, 3);
circle(rx, ry, 3);
circle(rx, ry, abs(r));
}

View File

@@ -1,70 +0,0 @@
module.exports = {
setup: function(api) {
let d = api.defaultWidth;
api.setSize(d*3, api.defaultHeight);
// Set up two curves with identical form, but different functions:
var q = this.q = new api.Bezier(115, 250, 10, 35, 190, 45);
var c = this.c = q.raise();
q.points.forEach(p => (p.x += d/2));
c.points.forEach(p => (p.x += 3*d/2));
// And "fake" a master curve that we'll never draw, but which
// will allow us to move interact with the curve points.
api.setCurve({
points: q.points.concat(c.points)
});
},
updateCurves(api, curve) {
// update the quadratic and cubic curves by grabbing
// whatever the points in our "fake" master curve are
let q = this.q;
q.points = curve.points.slice(0,3);
q.update();
let c = this.c;
c.points = curve.points.slice(3,7);
c.update();
},
drawCurvature(api, curve, omni) {
api.drawSkeleton(curve);
api.drawCurve(curve);
var s, t, p, n, c, ox, oy;
for( s=0; s<256; s++) {
// Draw the curvature as a coloured line at the
// current point, along the normal.
api.setColor('rgba(255,127,'+s+',0.6)');
t = s/255;
p = curve.get(t);
n = curve.normal(t);
c = curve.curvature(t);
ox = c.k * n.x;
oy = c.k * n.y;
api.drawLine(p, { x: p.x + ox, y: p.y + oy });
// And if requested, also draw it along the anti-normal.
if (omni) {
api.setColor('rgba('+s+',127,255,0.6)');
api.drawLine(p, { x: p.x - ox, y: p.y - oy });
}
}
},
proxyDraw: function(api, curve, omni) {
api.reset();
this.updateCurves(api, curve);
[this.q, this.c].forEach(curve => this.drawCurvature(api, curve, omni));
},
draw: function(api, curve) {
this.proxyDraw(api, curve);
},
drawOmni: function(api, curve) {
this.proxyDraw(api, curve, true);
}
};

View File

@@ -2,7 +2,7 @@
We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.
We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for for doing true intersection detection, or curvature alignment.
We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for doing true intersection detection, or curvature alignment.
<div class="figure">
<graphics-element title="Flattening a quadratic curve" src="./flatten.js" data-type="quadratic">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

After

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.6 KiB

After

Width:  |  Height:  |  Size: 9.6 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.9 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 97 KiB

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="81px" height="37px" viewBox="0 0 61 28"><defs><symbol overflow="visible" id="a"><path d="M7.766-.094V-.53c-.61-.078-.657-.14-.985-.64L4.703-4.267c.438-.078.875-.265 1.313-.578.687-.5 1.093-1.25 1.093-1.984 0-1.031-1.234-1.703-2.218-1.703-.594 0-1.204.031-1.797.031l-1.938-.047-.062.61.828.03c.297 0 .281-.03.281.188 0 .172-.047.547-.11.907l-.905 5.187C1-.531 1.125-.641.64-.594l-.61.031-.093.61L1.688 0 3.28.047l.047-.61-.719-.03c-.328-.032-.296.015-.296-.235 0-.11 0-.219.046-.422l1.016-5.813.11-.484.046-.203c.25-.063.485-.094.813-.094 1.031 0 1.437.344 1.437 1.219 0 1.14-.781 1.844-2.031 1.844h-.625l-.094.281C4.687-2.094 5.062-1.516 5.984.047 6.36.016 6.641 0 6.938 0c.25 0 .468.016.828.063zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M4.203-7.828a.735.735 0 01-.187-.14c-.063-.063-.11-.126-.22-.329-1.593 1.61-2.5 3.266-2.5 4.781v.797c0 1.516.907 3.172 2.5 4.781.11-.203.157-.265.22-.328.062-.062.125-.109.312-.203C2.875.063 2.281-1.344 2.281-2.719v-.797c0-1.39.594-2.78 2.047-4.25zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M3.703-5.516c-.453.047-.86.063-1.156.063.172-.984.297-1.578.531-2.25l-.25-.328a7.16 7.16 0 01-1.094.531l-.296 2.031c-.391.203-.704.328-1.063.407l-.047.406h1l-.64 3.25C.625-1.11.53-.813.53-.5c0 .297.266.61.5.61.422 0 .922-.282 1.86-1.032.218-.172.14-.125.437-.36l-.25-.437-.672.469c-.36.25-.484.313-.625.313-.093 0-.031.046-.031-.11 0-.297.156-1.234.516-3l.14-.61h1.266l.203-.89zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M3.766-2.719v-.797c0-1.515-.907-3.171-2.516-4.78-.11.202-.156.265-.203.327-.063.063-.125.11-.313.203 1.438 1.47 2.032 2.86 2.032 4.25v.797c0 1.375-.594 2.781-2.032 4.25.188.094.25.14.313.203.047.063.094.125.203.329C2.86.452 3.766-1.204 3.766-2.72zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M8.266-4.078a1.419 1.419 0 01-.047-.36c0-.109.015-.234.062-.484h-7.5c.063.25.063.375.063.484 0 .125 0 .235-.063.5h7.5zm0 2.625a1.332 1.332 0 01-.047-.36c0-.109.015-.234.062-.484h-7.5c.063.25.063.375.063.485 0 .125 0 .25-.063.5h7.5zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M5.125-.094v-.484l-.75-.047c-.656-.031-.64-.031-.64-.656v-7.172l-.313-.125c-.875.469-1.61.781-2.86 1.219l.125.718h.235l1.547-.687.031-.016c.063 0-.047-.015-.047.266v5.797c0 .625.016.625-.64.656L1-.578v.625L3.125 0l2 .047zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M6.281-5.922a1.98 1.98 0 00-.562-.11c-.782 0-1.922.954-3.516 3.048l.235.093.203-.796c.078-.266.187-.75.265-1.016l.078-.313c.063-.203.094-.343.094-.5.016-.25-.203-.5-.375-.5-.25 0-.828.282-1.75.907l-.375.28.14.5.579-.359c.344-.218.375-.234.484-.234.125 0 .047-.031.047.11-.031.718-.656 3.156-1.219 4.75l.235.187.937-.25.39-1.719c.126-.515.407-.906.657-1.125C3.468-.844 3.953.11 4.25.11c.156 0 .531-.187 1.094-.562l.625-.422-.188-.469-.687.375c-.188.094-.14.078-.25.078-.125 0-.094-.03-.203-.234-.391-.766-.579-1.313-.97-2.734l.126-.125c.765-.72 1.234-.985 1.828-.985.11 0 .234.016.516.094l.296-.984zm0 0"/></symbol></defs><use xlink:href="#a" x=".463" y="17.538"/><use xlink:href="#b" x="8.437" y="17.538"/><use xlink:href="#c" x="13.506" y="17.538"/><use xlink:href="#d" x="17.535" y="17.538"/><use xlink:href="#e" x="25.928" y="17.538"/><use xlink:href="#f" x="46.952" y="8.929"/><path d="M39.54 14.55h20.804" fill="none" stroke-width=".717" stroke="#000" stroke-miterlimit="10"/><use xlink:href="#g" x="39.54" y="25.501"/><g><use xlink:href="#b" x="46.151" y="25.501"/><use xlink:href="#c" x="51.22" y="25.501"/></g><g><use xlink:href="#d" x="55.261" y="25.501"/></g></svg>
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="81px" height="37px" viewBox="0 0 61 28"><defs><symbol overflow="visible" id="a"><path d="M7.516-6.734c0 .406-.125 1.125-.594 1.593-.313.313-.797.657-1.875.657h-1.11l.735-2.985c.078-.281-.078-.281.14-.312.11-.016.5-.016.735-.016.844 0 1.969-.125 1.969 1.063zM9.219-1.25c0-.14-.203-.047-.203-.047l-.141-.219c-.094 0-.328.22-.36.297-.296.89-.609.953-.874.953-.391 0-.297-.125-.297-.593 0-.375.078-.985.125-1.36.031-.172.047-.406.047-.562 0-.922-.985-1.422-1.313-1.547v.266C7.406-4.329 9.031-5.298 9.031-6.5c0-1.031-1.281-1.922-2.844-1.922H2.781c-.234 0-.547.125-.547.36 0 .14.313.265.532.265l.14-.031s.125.031.328.062c.204.016.11-.093.11.063 0 .047-.016.078-.047.219L1.703-1.063c-.125.47.047.422-.89.422C.593-.64.28-.5.28-.266c0 .141.5.235.5.235L.656 0l1.5-.031L3.687 0c.094 0 .422-.125.422-.375 0-.125-.296-.266-.53-.266C3.14-.64 3-.5 3-.719c0-.062.031-.125.031-.203l.766-3.031h1.265c1.079 0 1.094.531 1.094.953 0 .172-.093.547-.172.828-.078.328-.187.766-.187 1.016C5.797.125 7.437.266 7.594.266c1.015 0 1.625-1.344 1.625-1.516zm0 0"/></symbol><symbol overflow="visible" id="b"><path d="M4.156 2.719c0-.047-.078-.172-.11-.203-1.234-.922-2-3.22-2-5.141v-1c0-1.906.767-4.203 2-5.14a.457.457 0 00.11-.188c0-.063-.25-.25-.312-.25a1.01 1.01 0 00-.203.062C2.312-8.156 1-5.67 1-3.625v1C1-.578 2.313 1.906 3.64 2.906c.016.016.188.063.204.063.062 0 .312-.203.312-.25zm0 0"/></symbol><symbol overflow="visible" id="c"><path d="M4.14-5.156c0-.125-.312-.266-.53-.266H2.78c.39-1.562.453-1.797.453-1.875 0-.203-.328-.453-.53-.453-.032 0-.579.14-.688.563l-.422 1.765H.64c-.25 0-.563.14-.563.36 0 .156.297.28.531.28h.813C.594-1.515.547-1.311.547-1.093c0 .64.656 1.219 1.312 1.219 1.22 0 2.079-1.875 2.079-1.969 0-.11-.282-.25-.329-.25-.109 0-.328.172-.375.297C2.72-.547 2.281-.39 1.875-.39c-.25 0-.172-.03-.172-.421 0-.282.016-.375.063-.579l.859-3.39h.953c.25 0 .563-.125.563-.375zm0 0"/></symbol><symbol overflow="visible" id="d"><path d="M3.64-2.625v-1c0-2.047-1.328-4.531-2.64-5.516a.774.774 0 00-.203-.062c-.063 0-.313.187-.313.25 0 .031.079.172.094.187 1.25.938 2.016 3.235 2.016 5.141v1c0 1.922-.766 4.219-2.016 5.14-.015.032-.094.157-.094.204 0 .047.25.25.313.25A.774.774 0 001 2.906c1.313-1 2.64-3.484 2.64-5.531zm0 0"/></symbol><symbol overflow="visible" id="e"><path d="M8.828-4.281c0-.125-.312-.375-.437-.375H.906c-.125 0-.437.25-.437.375 0 .14.312.375.437.375h7.485c.125 0 .437-.235.437-.375zm0 2.328c0-.14-.312-.375-.437-.375H.906c-.125 0-.437.234-.437.375 0 .125.312.36.437.36h7.485c.125 0 .437-.235.437-.36zm0 0"/></symbol><symbol overflow="visible" id="f"><path d="M5.203-.125v-.516h-.578c-1.078 0-.922 0-.922-.437v-6.703c0-.282-.187-.438-.578-.438-.766.797-1.672.766-2.266.766v.625c.438 0 1.282-.031 1.563-.172v5.922c0 .437.172.437-.906.437H.937v.672C1.547-.03 2.595-.03 3.079-.03c.469 0 1.516 0 2.125.062zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M6.719-1.844c0-.11-.297-.25-.328-.25-.125 0-.344.188-.375.344-.235.828-.297 1.36-.844 1.36-.25 0-.188-.032-.188-.454 0-.172.047-.453.094-.656.031-.11.063-.266.063-.406 0-1.125-1.844-1.328-2.516-1.36l.14.235c.391-.219.766-.547 1.094-.813.547-.469.766-.719.938-.797.078-.203.047-.062.047 0 0 .141.281.547.656.547s.828-.468.828-.765c0-.11-.234-.563-.656-.563-.734 0-1.578.64-2.11 1.078-.812.703-.953.844-1.14.89l.187-.702c.063-.313.22-.875.22-.938 0-.203-.345-.453-.548-.453-.031 0-.578.125-.672.563L.5-.594c-.047.156-.047.188-.047.266 0 .219.375.453.547.453.36 0 .625-.406.719-.797.125-.422.047-.203.531-2.062.844.015 1.688.093 1.688.875 0 .078 0 .14-.032.25a1.923 1.923 0 00-.062.421c0 .704.687 1.313 1.312 1.313.375 0 .813-.25 1.063-.656.297-.5.437-1.219.437-1.219zm0 0"/></symbol></defs><use xlink:href="#a" x=".092" y="16.689"/><use xlink:href="#b" x="9.453" y="16.689"/><use xlink:href="#c" x="14.103" y="16.689"/><use xlink:href="#d" x="18.419" y="16.689"/><use xlink:href="#e" x="26.381" y="16.689"/><use xlink:href="#f" x="47.472" y="8.595"/><path d="M40.21 13.7h20.5" fill="none" stroke-width=".478" stroke="#000" stroke-miterlimit="10"/><g><use xlink:href="#g" x="40.209" y="24.891"/><use xlink:href="#b" x="47.095" y="24.891"/><use xlink:href="#c" x="51.746" y="24.891"/><use xlink:href="#d" x="56.062" y="24.891"/></g></svg>

Before

Width:  |  Height:  |  Size: 3.6 KiB

After

Width:  |  Height:  |  Size: 4.2 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.6 KiB

After

Width:  |  Height:  |  Size: 9.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.7 KiB

After

Width:  |  Height:  |  Size: 10 KiB

View File

@@ -353,7 +353,7 @@ function Bezier(3,t):
<p>Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.</p>
<p>If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:</p>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy">
<script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<p>That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate, w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (110,150), is controlled by (25,190) and (210,250) and ends at (210,30), we use this Bézier curve:</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>Which gives us the curve we saw at the top of the article:</p>
@@ -556,7 +556,7 @@ function RationalBezier(3,t,w[],r[]):
<section id="flattening">
<h1><a href="#flattening">Simplified drawing</a></h1>
<p>We can also simplify the drawing process by "sampling" the curve at certain points, and then joining those points up with straight lines, a process known as "flattening", as we are reducing a curve to a simple sequence of straight, "flat" lines.</p>
<p>We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for for doing true intersection detection, or curvature alignment.</p>
<p>We can do this is by saying "we want X segments", and then sampling the curve at intervals that are spaced such that we end up with the number of segments we wanted. The advantage of this method is that it's fast: instead of evaluating 100 or even 1000 curve coordinates, we can sample a much lower number and still end up with a curve that sort-of-kind-of looks good enough. The disadvantage of course is that we lose the precision of working with "the real curve", so we usually can't use the flattened for doing true intersection detection, or curvature alignment.</p>
<div class="figure">
<graphics-element title="Flattening a quadratic curve" width="275" height="275" src="./chapters/flattening/flatten.js" data-type="quadratic">
<fallback-image>
@@ -637,7 +637,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -1391,45 +1391,54 @@ y = curve.get(t).y</code></pre>
</section>
<section id="curvature">
<h1><a href="#curvature">Curvature of a curve</a></h1>
<p>Imagine we have two curves, and we want to line them in up in a way that "looks right". What would we use as metric to let a computer decide what "looks right" means? For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between leaving one curve and entering the next, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next. What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature"><em>curvature</em></a> at the transition from one curve to the next "looks good". So, we could have them share an end coordinate, and then ensure that the derivatives for both curves match at that coordinate, and at a casual glance, that seems the perfect solution: if we make the derivatives match, then both the "direction" in which we travel from one curve to the next is the same, and the "speed" at which we travel the curve will be the same.</p>
<p>If we have two curves, and we want to line them in up in a way that "looks right", what would we use as metric to let a computer decide what "looks right" means?</p>
<p>For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between the end of one and the start of the next curve, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next.</p>
<p>What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature">curvature</a> at the transition from one curve to the next "looks good". So, we start with a shared coordinate, and then also require that derivatives for both curves match at that coordinate. That way, we're assured that their tangents line up, which must mean the curve transition is perfectly smooth. We can even make the second, third, etc. derivatives match up for better and better transitions.</p>
<p>Problem solved!</p>
<p>But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on <a href="#reordering">reordering curves</a>: what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the <em>exact same curve</em> but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".</p>
<p>How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that the thing we can use to define curvature is the thing we talked about in the previous section: arc length.</p>
<p>Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.</p>
<p>You might think that in order to find the curvature of a curve, we now need to find and then solve the arc length function, and that would be a problem because we just saw that there is no way to actually do that: don't worry, we don't. We do need to know the <em>form</em> of the arc length function, which we saw above, but it's not the thing we're actually interested in, and we're going to be rewriting it in a way that makes most of the crazy complex things about it just... disappear.</p>
<p>In fact, after <a href="http://mathworld.wolfram.com/Curvature.html">running through the steps necessary</a> to determine what we're left with if we use the arclength function's derivative (with another run-through of the maths <a href="https://math.stackexchange.com/a/275324/71940">here</a>), rather than the curve's original function's derivative, then the integral disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and we're left with some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me just highlight that before we move on: we calculate the curvature of a curve using the arc length function derivative, because the original function's derivative is entirely unreliable, and in doing so we end up with a formula that expresses curvature in terms of the original function's derivatives.</p>
<p>However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on <a href="#reordering">reordering curves</a> showed us that the same looking curve can have an infinite number of curve expressions of arbitraryly high Bezier degree, and each of those will have <em>widly</em> different derivative values.</p>
<p>So what we really want is some kind of expression that's not based on any particular expression of <code>t</code>, but is based on something that is invariant to the <em>kind</em> of function(s) we use to draw our curve. And the prime candidate for this is our curve expression, reparameterised for distance: no matter what order of Bezier curve we use, if we were able to rewrite it as a function of distance-along-the-curve, all those different degree Bezier functions would end up being <em>the same</em> function for "coordinate at some distance D along the curve".</p>
<p>We've seen this before... that's the arc length function.</p>
<p>So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the <em>form</em> of the arc length function, which we saw above and is fairly simple, rather than needing to <em>solve</em> the arc length function. If we start with the arc length expression and the <a href="http://mathworld.wolfram.com/Curvature.html">run through the steps necessary</a> to determine <em>its</em> derivative (with an alternative, shorter demonstration of how to do this found <a href="https://math.stackexchange.com/a/275324/71940">over on Stackexchange</a>), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me highlight what just happened, because it's pretty special:</p>
<ol>
<li>we wanted to make curves line up, and initially thought to match the curves' derivatives, but</li>
<li>that turned out to be a really bad choice, so instead</li>
<li>we picked a function that is basically impossible to work with, and then <em>worked with that</em>, which</li>
<li>gives us a simple formula that is based on <em>the curves' derivatives</em>.</li>
</ol>
<p><em>That's crazy!</em></p>
<p>But, that's what makes maths such an interesting thing: it can show you that all your assumptions are completely wrong, only to then go "but actually, you were on the right track all along, here: ..." with a solution that is so easy to work with as to almost seem mundane. So: enough of all this text, how do we calculate curvature? What is the function for κ? Concisely, the function is this:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="113px" height="47px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="239px" height="55px" loading="lazy">
<p>But that's also one of the things that makes maths so powerful: even if your initial ideas are off the mark, you might be much closer than you thought you were, and the journey from "thinking we're completely wrong" to "actually being remarkably close to being right" is where we can find a lot of insight.</p>
<p>So, what does the function look like? This:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="115px" height="41px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions of <code>t</code>, so let's expand that a tiny bit:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="248px" height="48px" loading="lazy">
<p>And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: <em>any</em>) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means <a href="#derivatives">we know what the first and second derivatives are</a>, and so evaluating this function for any <strong>t</strong> value is just a matter of basic arithematics.</p>
<div class="howtocode">
<h3>Implement the kappa function</h3>
<p>In fact, let's just implement it right now:</p>
<pre><code>function kappa(t, B):
d = B.getDerivative()
dd = d.getDerivative()
dx = d.getX(t)
dy = d.getY(t)
ddx = dd.getX(t)
ddy = dd.getY(t)
numerator = dx * ddy - ddx * dy
denominator = pow(dx*dx + dy*dy, 1.5)
d = B.getDerivative(t)
dd = B.getSecondDerivative(t)
numerator = d.x * dd.y - dd.x * d.y
denominator = pow(d.x*d.x + d.y*d.y, 3/2)
if denominator is 0: return NaN;
return numerator / denominator</code></pre>
<p>That was easy!</p>
<p>In fact, it stays easy because we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest relation in this entire primer:</p>
<p>That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anwyay)</p>
<p>With all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest transition.</p>
<graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\a98d37a0653461ad4e6065d8277c8834.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. A way around that, of course, is to show the curvature on both sides of the curve, so let's just do that. But let's take it one step further: we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest bit of maths found in this entire primer:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/6ed4fd2ead35c57984caddf9fe375a5f.svg" width="81px" height="37px" loading="lazy">
<p>So that's a rather convenient fact to know, too.</p>
</div>
<p>So with all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest looking transition we could ask for.</p>
<Graphic title="Matching curvatures for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.draw} />
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. In your code you typically solve this by matching absolute values, but that's not super easy to program visually... however, we <em>can</em> just show the curvature on both sides of the curve, making lining things up a bit easier:</p>
<Graphic title="(Easier) curvature matching for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.drawOmni} />
<p>So let's revisit the previous graphic with the curvature visualised on both sides of our curves, as well as showing the circle that "fits" our curve at some point that we can control by using a slider:</p>
<graphics-element title="(Easier) curvature matching for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" data-omni="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\1b2e086966d7e8088e4b51a11d9ec063.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
</graphics-element>
</section>
<section id="tracing">
@@ -1936,7 +1945,7 @@ with quadratic or cubic curves:</p>
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>

View File

@@ -634,7 +634,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="ja-JP/index.html#matrixsplit">行列による曲線の分割</a></h1>
<p>曲線分割には、ベジエ曲線の行列表現を利用する方法もあります。<a href="#matrix">行列についての節</a>では、行列の乗算で曲線が表現できることを確認しました。特に2次・3次のベジエ曲線に関しては、それぞれ以下のような形になりました読みやすさのため、ベジエの係数ベクトルを反転させています</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>ならびに</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>曲線をある点<code>t = z</code>で分割し、新しく2つの自明ですが、より短いベジエ曲線を作ることを考えましょう。曲線の行列表現と線形代数を利用すると、この2つのベジエ曲線の座標を求めることができます。まず、実際の「曲線上の点」の情報を分解し、新しい行列の積のかたちにします。</p>
@@ -1388,45 +1388,54 @@ y = curve.get(t).y</code></pre>
</section>
<section id="curvature">
<h1><a href="ja-JP/index.html#curvature">Curvature of a curve</a></h1>
<p>Imagine we have two curves, and we want to line them in up in a way that "looks right". What would we use as metric to let a computer decide what "looks right" means? For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between leaving one curve and entering the next, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next. What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature"><em>curvature</em></a> at the transition from one curve to the next "looks good". So, we could have them share an end coordinate, and then ensure that the derivatives for both curves match at that coordinate, and at a casual glance, that seems the perfect solution: if we make the derivatives match, then both the "direction" in which we travel from one curve to the next is the same, and the "speed" at which we travel the curve will be the same.</p>
<p>If we have two curves, and we want to line them in up in a way that "looks right", what would we use as metric to let a computer decide what "looks right" means?</p>
<p>For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between the end of one and the start of the next curve, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next.</p>
<p>What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature">curvature</a> at the transition from one curve to the next "looks good". So, we start with a shared coordinate, and then also require that derivatives for both curves match at that coordinate. That way, we're assured that their tangents line up, which must mean the curve transition is perfectly smooth. We can even make the second, third, etc. derivatives match up for better and better transitions.</p>
<p>Problem solved!</p>
<p>But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on <a href="#reordering">reordering curves</a>: what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the <em>exact same curve</em> but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".</p>
<p>How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that the thing we can use to define curvature is the thing we talked about in the previous section: arc length.</p>
<p>Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.</p>
<p>You might think that in order to find the curvature of a curve, we now need to find and then solve the arc length function, and that would be a problem because we just saw that there is no way to actually do that: don't worry, we don't. We do need to know the <em>form</em> of the arc length function, which we saw above, but it's not the thing we're actually interested in, and we're going to be rewriting it in a way that makes most of the crazy complex things about it just... disappear.</p>
<p>In fact, after <a href="http://mathworld.wolfram.com/Curvature.html">running through the steps necessary</a> to determine what we're left with if we use the arclength function's derivative (with another run-through of the maths <a href="https://math.stackexchange.com/a/275324/71940">here</a>), rather than the curve's original function's derivative, then the integral disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and we're left with some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me just highlight that before we move on: we calculate the curvature of a curve using the arc length function derivative, because the original function's derivative is entirely unreliable, and in doing so we end up with a formula that expresses curvature in terms of the original function's derivatives.</p>
<p>However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on <a href="#reordering">reordering curves</a> showed us that the same looking curve can have an infinite number of curve expressions of arbitraryly high Bezier degree, and each of those will have <em>widly</em> different derivative values.</p>
<p>So what we really want is some kind of expression that's not based on any particular expression of <code>t</code>, but is based on something that is invariant to the <em>kind</em> of function(s) we use to draw our curve. And the prime candidate for this is our curve expression, reparameterised for distance: no matter what order of Bezier curve we use, if we were able to rewrite it as a function of distance-along-the-curve, all those different degree Bezier functions would end up being <em>the same</em> function for "coordinate at some distance D along the curve".</p>
<p>We've seen this before... that's the arc length function.</p>
<p>So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the <em>form</em> of the arc length function, which we saw above and is fairly simple, rather than needing to <em>solve</em> the arc length function. If we start with the arc length expression and the <a href="http://mathworld.wolfram.com/Curvature.html">run through the steps necessary</a> to determine <em>its</em> derivative (with an alternative, shorter demonstration of how to do this found <a href="https://math.stackexchange.com/a/275324/71940">over on Stackexchange</a>), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me highlight what just happened, because it's pretty special:</p>
<ol>
<li>we wanted to make curves line up, and initially thought to match the curves' derivatives, but</li>
<li>that turned out to be a really bad choice, so instead</li>
<li>we picked a function that is basically impossible to work with, and then <em>worked with that</em>, which</li>
<li>gives us a simple formula that is based on <em>the curves' derivatives</em>.</li>
</ol>
<p><em>That's crazy!</em></p>
<p>But, that's what makes maths such an interesting thing: it can show you that all your assumptions are completely wrong, only to then go "but actually, you were on the right track all along, here: ..." with a solution that is so easy to work with as to almost seem mundane. So: enough of all this text, how do we calculate curvature? What is the function for κ? Concisely, the function is this:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="113px" height="47px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="239px" height="55px" loading="lazy">
<p>But that's also one of the things that makes maths so powerful: even if your initial ideas are off the mark, you might be much closer than you thought you were, and the journey from "thinking we're completely wrong" to "actually being remarkably close to being right" is where we can find a lot of insight.</p>
<p>So, what does the function look like? This:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="115px" height="41px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions of <code>t</code>, so let's expand that a tiny bit:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="248px" height="48px" loading="lazy">
<p>And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: <em>any</em>) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means <a href="#derivatives">we know what the first and second derivatives are</a>, and so evaluating this function for any <strong>t</strong> value is just a matter of basic arithematics.</p>
<div class="howtocode">
<h3>Implement the kappa function</h3>
<p>In fact, let's just implement it right now:</p>
<pre><code>function kappa(t, B):
d = B.getDerivative()
dd = d.getDerivative()
dx = d.getX(t)
dy = d.getY(t)
ddx = dd.getX(t)
ddy = dd.getY(t)
numerator = dx * ddy - ddx * dy
denominator = pow(dx*dx + dy*dy, 1.5)
d = B.getDerivative(t)
dd = B.getSecondDerivative(t)
numerator = d.x * dd.y - dd.x * d.y
denominator = pow(d.x*d.x + d.y*d.y, 3/2)
if denominator is 0: return NaN;
return numerator / denominator</code></pre>
<p>That was easy!</p>
<p>In fact, it stays easy because we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest relation in this entire primer:</p>
<p>That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anwyay)</p>
<p>With all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest transition.</p>
<graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\a98d37a0653461ad4e6065d8277c8834.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. A way around that, of course, is to show the curvature on both sides of the curve, so let's just do that. But let's take it one step further: we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest bit of maths found in this entire primer:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/6ed4fd2ead35c57984caddf9fe375a5f.svg" width="81px" height="37px" loading="lazy">
<p>So that's a rather convenient fact to know, too.</p>
</div>
<p>So with all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest looking transition we could ask for.</p>
<Graphic title="Matching curvatures for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.draw} />
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. In your code you typically solve this by matching absolute values, but that's not super easy to program visually... however, we <em>can</em> just show the curvature on both sides of the curve, making lining things up a bit easier:</p>
<Graphic title="(Easier) curvature matching for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.drawOmni} />
<p>So let's revisit the previous graphic with the curvature visualised on both sides of our curves, as well as showing the circle that "fits" our curve at some point that we can control by using a slider:</p>
<graphics-element title="(Easier) curvature matching for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" data-omni="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\1b2e086966d7e8088e4b51a11d9ec063.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
</graphics-element>
</section>
<section id="tracing">
@@ -1933,7 +1942,7 @@ with quadratic or cubic curves:</p>
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>

View File

@@ -66,11 +66,17 @@ class BaseAPI {
this.parameters = Object.fromEntries(
Object.entries(this.dataset)
.map((pair) => {
let name = pair[0];
let v = pair[1];
if (v === `null` || v === `undefined`) return [];
if (v === `true`) return [name, true];
else if (v === `false`) return [name, false];
else {
let d = parseFloat(v);
// evaluate "string is number" using == rather than ===
return [pair[0], v == d ? d : v];
// Use == to evaluate "is this a string number"
if (v == d) return [name, d];
}
return [name, v];
})
.filter((v) => v.length)
);

View File

@@ -331,29 +331,11 @@ class Bezier {
}
derivative(t) {
const mt = 1 - t;
let a,
b,
c = 0,
p = this.dpoints[0];
if (this.order === 2) {
p = [p[0], p[1], ZERO];
a = mt;
b = t;
return utils.compute(t, this.dpoints[0]);
}
if (this.order === 3) {
a = mt * mt;
b = mt * t * 2;
c = t * t;
}
const ret = {
x: a * p[0].x + b * p[1].x + c * p[2].x,
y: a * p[0].y + b * p[1].y + c * p[2].y,
};
if (this._3d) {
ret.z = a * p[0].z + b * p[1].z + c * p[2].z;
}
return ret;
dderivative(t) {
return utils.compute(t, this.dpoints[1]);
}
align() {
@@ -362,7 +344,7 @@ class Bezier {
}
curvature(t) {
return utils.curvature(t, this.points, this._3d);
return utils.curvature(t, this.dpoints[0], this.dpoints[1], this._3d);
}
inflections() {

View File

@@ -647,11 +647,7 @@ const utils = {
return [];
},
curvature: function (t, points, _3d, kOnly) {
const dpoints = utils.derive(points);
const d1 = dpoints[0];
const d2 = dpoints[1];
curvature: function (t, d1, d2, _3d, kOnly) {
let num,
dnm,
adk,
@@ -705,8 +701,8 @@ const utils = {
if (!kOnly) {
// compute k'(t) based on the interval before, and after it,
// to at least try to not introduce forward/backward pass bias.
const pk = utils.curvature(t - 0.001, points, _3d, true).k;
const nk = utils.curvature(t + 0.001, points, _3d, true).k;
const pk = utils.curvature(t - 0.001, d1, d2, _3d, true).k;
const nk = utils.curvature(t + 0.001, d1, d2, _3d, true).k;
dk = (nk - k + (k - pk)) / 2;
adk = (abs(nk - k) + abs(k - pk)) / 2;
}

View File

@@ -349,7 +349,7 @@ function Bezier(3,t):
<p>上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。</p>
<p>如果我们要改变曲线,就需要改变每个点的权重,有效地改变插值。可以很直接地做到这个:只要用一个值乘以每个点,来改变它的强度。这个值照惯例称为“权重”,我们可以将它加入我们原始的贝塞尔函数:</p>
<img class="LaTeX SVG" src="./images/chapters/control/14cb9fbbaae9e7d87ae6bef3ea7a782e.svg" width="379px" height="56px" loading="lazy">
<script>console.log("LaTeX for 14cb9fbbaae9e7d87ae6bef3ea7a782e failed!");</script>
<p>看起来很复杂但实际上“权重”只是我们想让曲线所拥有的坐标值对于一条n<sup>th</sup>阶曲线w<sup>0</sup>是起始坐标w<sup>n</sup>是终点坐标中间的所有点都是控制点坐标。假设说一条曲线的起点为120160终点为22040并受点35200和点220260的控制贝塞尔曲线方程就为</p>
<img class="LaTeX SVG" src="./images/chapters/control/c0d4dbc07b8ec7c0a18ea43c8a386935.svg" width="476px" height="40px" loading="lazy">
<p>这就是我们在文章开头看到的曲线:</p>
@@ -628,7 +628,7 @@ function drawCurve(points[], t):
<section id="matrixsplit">
<h1><a href="zh-CN/index.html#matrixsplit">Splitting curves using matrices</a></h1>
<p>Another way to split curves is to exploit the matrix representation of a Bézier curve. In <a href="#matrix">the section on matrices</a>, we saw that we can represent curves as matrix multiplications. Specifically, we saw these two forms for the quadratic and cubic curves respectively: (we'll reverse the Bézier coefficients vector for legibility)</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/77a11d65d7cffc4b84a85c4bec837792.svg" width="263px" height="55px" loading="lazy">
<script>console.log("LaTeX for 77a11d65d7cffc4b84a85c4bec837792 failed!");</script>
<p>and</p>
<img class="LaTeX SVG" src="./images/chapters/matrixsplit/c58330e12d25c678b593ddbd4afa7c52.svg" width="323px" height="73px" loading="lazy">
<p>Let's say we want to split the curve at some point <code>t = z</code>, forming two new (obviously smaller) Bézier curves. To find the coordinates for these two Bézier curves, we can use the matrix representation and some linear algebra. First, we separate out the actual "point on the curve" information into a new matrix multiplication:</p>
@@ -1382,45 +1382,54 @@ y = curve.get(t).y</code></pre>
</section>
<section id="curvature">
<h1><a href="zh-CN/index.html#curvature">Curvature of a curve</a></h1>
<p>Imagine we have two curves, and we want to line them in up in a way that "looks right". What would we use as metric to let a computer decide what "looks right" means? For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between leaving one curve and entering the next, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next. What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature"><em>curvature</em></a> at the transition from one curve to the next "looks good". So, we could have them share an end coordinate, and then ensure that the derivatives for both curves match at that coordinate, and at a casual glance, that seems the perfect solution: if we make the derivatives match, then both the "direction" in which we travel from one curve to the next is the same, and the "speed" at which we travel the curve will be the same.</p>
<p>If we have two curves, and we want to line them in up in a way that "looks right", what would we use as metric to let a computer decide what "looks right" means?</p>
<p>For instance, we can start by ensuring that the two curves share an end coordinate, so that there is no "gap" between the end of one and the start of the next curve, but that won't guarantee that things look right: both curves can be going in wildly different directions, and the resulting joined geometry will have a corner in it, rather than a smooth transition from one curve to the next.</p>
<p>What we want is to ensure that the <a href="https://en.wikipedia.org/wiki/Curvature">curvature</a> at the transition from one curve to the next "looks good". So, we start with a shared coordinate, and then also require that derivatives for both curves match at that coordinate. That way, we're assured that their tangents line up, which must mean the curve transition is perfectly smooth. We can even make the second, third, etc. derivatives match up for better and better transitions.</p>
<p>Problem solved!</p>
<p>But, if we think about this a little more, this cannot possible work, because of something that you may have noticed in the section on <a href="#reordering">reordering curves</a>: what a curve looks like, and the function that draws that curve, are not in some kind of universal, fixed, one-to-one relation. If we have some quadratic curve, then simply by raising the curve order we can get corresponding cubic, quartic, and higher and higher mathematical expressions that all draw the <em>exact same curve</em> but with wildly different derivatives. So: if we want to make a transition from one curve to the next look good, and we want to use the derivative, then we suddenly need to answer the question: "Which derivative?".</p>
<p>How would you even decide? What makes the cubic derivatives better or less suited than, say, quintic derivatives? Wouldn't it be nicer if we could use something that was inherent to the curve, without being tied to the functions that yield that curve? And (of course) as it turns out, there is a way to define curvature in such a way that it only relies on what the curve actually looks like, and given where this section is in the larger body of this Primer, it should hopefully not be surprising that the thing we can use to define curvature is the thing we talked about in the previous section: arc length.</p>
<p>Intuitively, this should make sense, even if we have no idea what the maths would look like: if we travel some fixed distance along some curve, then the point at that distance is simply the point at that distance. It doesn't matter what function we used to draw the curve: once we know what the curve looks like, the function(s) used to draw it become irrelevant: a point a third along the full distance of the curve is simply the point a third along the distance of the curve.</p>
<p>You might think that in order to find the curvature of a curve, we now need to find and then solve the arc length function, and that would be a problem because we just saw that there is no way to actually do that: don't worry, we don't. We do need to know the <em>form</em> of the arc length function, which we saw above, but it's not the thing we're actually interested in, and we're going to be rewriting it in a way that makes most of the crazy complex things about it just... disappear.</p>
<p>In fact, after <a href="http://mathworld.wolfram.com/Curvature.html">running through the steps necessary</a> to determine what we're left with if we use the arclength function's derivative (with another run-through of the maths <a href="https://math.stackexchange.com/a/275324/71940">here</a>), rather than the curve's original function's derivative, then the integral disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and we're left with some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me just highlight that before we move on: we calculate the curvature of a curve using the arc length function derivative, because the original function's derivative is entirely unreliable, and in doing so we end up with a formula that expresses curvature in terms of the original function's derivatives.</p>
<p>However, there's a problem with this approach: if we think about this a little more, we realise that "what a curve looks like" and its derivative values are pretty much entirely unrelated. After all, the section on <a href="#reordering">reordering curves</a> showed us that the same looking curve can have an infinite number of curve expressions of arbitraryly high Bezier degree, and each of those will have <em>widly</em> different derivative values.</p>
<p>So what we really want is some kind of expression that's not based on any particular expression of <code>t</code>, but is based on something that is invariant to the <em>kind</em> of function(s) we use to draw our curve. And the prime candidate for this is our curve expression, reparameterised for distance: no matter what order of Bezier curve we use, if we were able to rewrite it as a function of distance-along-the-curve, all those different degree Bezier functions would end up being <em>the same</em> function for "coordinate at some distance D along the curve".</p>
<p>We've seen this before... that's the arc length function.</p>
<p>So you might think that in order to find the curvature of a curve, we now need to solve the arc length function itself, and that this would be quite a problem because we just saw that there is no way to actually do that. Thankfully, we don't. We only need to know the <em>form</em> of the arc length function, which we saw above and is fairly simple, rather than needing to <em>solve</em> the arc length function. If we start with the arc length expression and the <a href="http://mathworld.wolfram.com/Curvature.html">run through the steps necessary</a> to determine <em>its</em> derivative (with an alternative, shorter demonstration of how to do this found <a href="https://math.stackexchange.com/a/275324/71940">over on Stackexchange</a>), then the integral that was giving us so much problems in solving the arc length function disappears entirely (because of the <a href="https://en.wikipedia.org/wiki/Fundamental_theorem_of_calculus">fundamental theorem of calculus</a>), and what we're left with us some surprisingly simple maths that relates curvature (denoted as κ, "kappa") to—and this is the truly surprising bit—a specific combination of derivatives of our original function.</p>
<p>Let me highlight what just happened, because it's pretty special:</p>
<ol>
<li>we wanted to make curves line up, and initially thought to match the curves' derivatives, but</li>
<li>that turned out to be a really bad choice, so instead</li>
<li>we picked a function that is basically impossible to work with, and then <em>worked with that</em>, which</li>
<li>gives us a simple formula that is based on <em>the curves' derivatives</em>.</li>
</ol>
<p><em>That's crazy!</em></p>
<p>But, that's what makes maths such an interesting thing: it can show you that all your assumptions are completely wrong, only to then go "but actually, you were on the right track all along, here: ..." with a solution that is so easy to work with as to almost seem mundane. So: enough of all this text, how do we calculate curvature? What is the function for κ? Concisely, the function is this:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="113px" height="47px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="239px" height="55px" loading="lazy">
<p>But that's also one of the things that makes maths so powerful: even if your initial ideas are off the mark, you might be much closer than you thought you were, and the journey from "thinking we're completely wrong" to "actually being remarkably close to being right" is where we can find a lot of insight.</p>
<p>So, what does the function look like? This:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/d9c893051586eb8d9de51c0ae1ef8fae.svg" width="115px" height="41px" loading="lazy">
<p>Which is really just a "short form" that glosses over the fact that we're dealing with functions of <code>t</code>, so let's expand that a tiny bit:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/828333034b4fed8e248683760d6bc6f4.svg" width="248px" height="48px" loading="lazy">
<p>And while that's a litte more verbose, it's still just as simple to work with as the first function: the curvature at some point on any (and this cannot be overstated: <em>any</em>) curve is a ratio between the first and second derivative cross product, and something that looks oddly similar to the standard Euclidean distance function. And nothing in these functions is hard to calculate either: for Bézier curves, simply knowing our curve coordinates means <a href="#derivatives">we know what the first and second derivatives are</a>, and so evaluating this function for any <strong>t</strong> value is just a matter of basic arithematics.</p>
<div class="howtocode">
<h3>Implement the kappa function</h3>
<p>In fact, let's just implement it right now:</p>
<pre><code>function kappa(t, B):
d = B.getDerivative()
dd = d.getDerivative()
dx = d.getX(t)
dy = d.getY(t)
ddx = dd.getX(t)
ddy = dd.getY(t)
numerator = dx * ddy - ddx * dy
denominator = pow(dx*dx + dy*dy, 1.5)
d = B.getDerivative(t)
dd = B.getSecondDerivative(t)
numerator = d.x * dd.y - dd.x * d.y
denominator = pow(d.x*d.x + d.y*d.y, 3/2)
if denominator is 0: return NaN;
return numerator / denominator</code></pre>
<p>That was easy!</p>
<p>In fact, it stays easy because we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest relation in this entire primer:</p>
<p>That was easy! (Well okay, that "not a number" value will need to be taken into account by downstream code, but that's a reality of programming anwyay)</p>
<p>With all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest transition.</p>
<graphics-element title="Matching curvatures for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" >
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\a98d37a0653461ad4e6065d8277c8834.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element>
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. A way around that, of course, is to show the curvature on both sides of the curve, so let's just do that. But let's take it one step further: we can also compute the associated "radius of curvature", which gives us the implicit circle that "fits" the curve's curvature at any point, using what is possibly the simplest bit of maths found in this entire primer:</p>
<img class="LaTeX SVG" src="./images/chapters/curvature/6ed4fd2ead35c57984caddf9fe375a5f.svg" width="81px" height="37px" loading="lazy">
<p>So that's a rather convenient fact to know, too.</p>
</div>
<p>So with all of that covered, let's line up some curves! The following graphic gives you two curves that look identical, but use quadratic and cubic functions, respectively. As you can see, despite their derivatives being necessarily different, their curvature (thanks to being derived based on maths that "ignores" specific function derivative, and instead gives a formulat that smooths out any differences) is exactly the same. And because of that, we can put them together such that the point where they overlap has the same curvature for both curves, giving us the smoothest looking transition we could ask for.</p>
<Graphic title="Matching curvatures for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.draw} />
<p>One thing you may have noticed in this sketch is that sometimes the curvature looks fine, but seems to be pointing in the wrong direction, making it hard to line up the curves properly. In your code you typically solve this by matching absolute values, but that's not super easy to program visually... however, we <em>can</em> just show the curvature on both sides of the curve, making lining things up a bit easier:</p>
<Graphic title="(Easier) curvature matching for a quadratic and cubic Bézier curve" setup={this.setup} draw={this.drawOmni} />
<p>So let's revisit the previous graphic with the curvature visualised on both sides of our curves, as well as showing the circle that "fits" our curve at some point that we can control by using a slider:</p>
<graphics-element title="(Easier) curvature matching for a quadratic and cubic Bézier curve" width="825" height="275" src="./chapters/curvature/curvature.js" data-omni="true">
<fallback-image>
<img width="825px" height="275px" src="images\chapters\curvature\1b2e086966d7e8088e4b51a11d9ec063.png" loading="lazy">
Scripts are disabled. Showing fallback image.
</fallback-image>
<input type="range" min="0" max="2" step="0.0005" value="0" class="slide-control">
</graphics-element>
</section>
<section id="tracing">
@@ -1927,7 +1936,7 @@ with quadratic or cubic curves:</p>
<p>which we can then substitute in the expression for <em>a</em>:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/ef3ab62bb896019c6157c85aae5d1ed3.svg" width="231px" height="195px" loading="lazy">
<p>A quick check shows that plugging these values for <em>a</em> and <em>b</em> into the expressions for C<sub>x</sub> and C<sub>y</sub> give the same x/y coordinates for both "<em>a</em> away from A" and "<em>b</em> away from B", so let's continue: now that we know the coordinate values for C, we know where our on-curve point T for <em>t=0.5</em> (or angle φ/2) is, because we can just evaluate the Bézier polynomial, and we know where the circle arc's actual point P is for angle φ/2:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/fe32474b4616ee9478e1308308f1b6bf.svg" width="188px" height="32px" loading="lazy">
<script>console.log("LaTeX for fe32474b4616ee9478e1308308f1b6bf failed!");</script>
<p>We compute T, observing that if <em>t=0.5</em>, the polynomial values (1-t)², 2(1-t)t, and t² are 0.25, 0.5, and 0.25 respectively:</p>
<img class="LaTeX SVG" src="./images/chapters/circles/e1059e611aa1e51db41f9ce0b4ebb95a.svg" width="252px" height="35px" loading="lazy">
<p>Which, worked out for the x and y components, gives:</p>