1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-01 06:20:52 +02:00

sections 21-30 converted for localization

This commit is contained in:
Pomax
2017-02-25 18:16:03 -08:00
parent 3894fddb17
commit a889d16f36
21 changed files with 819 additions and 1028 deletions

View File

@@ -0,0 +1,81 @@
# The projection identity
De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve, by picking it up at some point, and dragging that point around to change the curve's shape.
How does that work? Succinctly: we run de Casteljau's algorithm in reverse!
In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point on the curve that want to be moving around, which has an associated *t* value, and a point we've not explicitly talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau process then our on-curve point does. I like to call it "A" for reasons that will become obvious.
So let's use graphics instead of text to see where this "A" is, because text only gets us so far: in the following graphic, click anywhere on the curves to see the identity information that we'll be using to run de Casteljau in reverse (you can manipulate the curve even after picking a point. Note the "ratio" value when you do so: does it change?):
<div className="figure">
<Graphic inline={true} preset="abc" title="Projections in a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.draw} onClick={this.onClick} />
<Graphic inline={true} preset="abc" title="Projections in a cubic Bézier curve" setup={this.setupCubic} draw={this.draw} onClick={this.onClick} />
</div>
Clicking anywhere on the curves shows us three things:
1. our on-curve point; let's call that <b>B</b>,
2. a point at the tip of B's "hat", on de Casteljau step up; let's call that <b>A</b>, and
3. a point that we get by projecting B onto the start--end baseline; let's call that <b>C</b>.
These three values ABC hide an important identity formula for quadratic and cubic Bézier curves: for any point on the curve with some *t* value, the ratio distance of C along baseline is fixed: if some *t* value sets up a C that is 20% away from the start and 80% away from the end, then it doesn't matter where the start, end, or control points are: for that *t* value, C will *always* lie at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic and then move all the other points around: if you only move the control points, start and end won't move, and so neither will C, and if you move either start or end point, C will move but its relative position will not change. The following function stays true:
\[
C = u \cdot P_{start} + (1-u) \cdot P_{end}
\]
So that just leaves finding A.
<div className="note">
While that relation is fixed, the function *u(t)* differs depending on whether we're working
with quadratic or cubic curves:
\[\begin{align}
& u(t)_{quadratic} = \frac{(1-t)^2}{t^2 + (1-t)^2} \\
& u(t)_{cubic} = \frac{(1-t)^3}{t^3 + (1-t)^3}
\end{align}\]
So, if we know the start and end coordinates, and we know the *t* value, we know C:
<div className="figure">
<Graphic inline={true} preset="abc" title="Quadratic value of C for t" draw={this.drawQCT} onMouseMove={this.setCT}/>
<Graphic inline={true} preset="abc" title="Cubic value of C for t" draw={this.drawCCT} onMouseMove={this.setCT}/>
</div>
Mouse-over the graphs to see the expression for C, given the *t* value at the mouse pointer.
</div>
There's also another important bit of information that is inherent to the ABC values: while the distances between A and B, and B and C, are dynamic (based on where we put B), the *ratio* between the two distances is stable: given some *t* value, the following always holds:
\[
ratio(t) = \frac{distance(B,C)}{distance(A,B)} = Constant
\]
This leads to a pretty powerful bit of knowledge: merely by knowing the *t* value of some on curve point, we know where C has to be (as per the above note), and because we know B and C, and thus have the distance between them, we know where A has to be:
\[
A = B - \frac{C - B}{ratio(t)} = B + \frac{B - C}{ratio(t)}
\]
And that's it, all values found.
<div className="note">
Much like the *u(t)* function in the above note, the *ratio(t)* function depends on whether we're looking at quadratic or cubic curves. Their form is intrinsically related to the *u(t)* function in that they both come rolling out of the same function evalution, explained over on [MathOverflow](http://mathoverflow.net/questions/122257/finding-the-formula-for-Bézier-curve-ratios-hull-point-point-baseline) by Boris Zbarsky and myself. The ratio functions are the "s(t)" functions from the answers there, while the "u(t)" functions have the same name both here and on MathOverflow.
\[
ratio(t)_{quadratic} = \left | \frac{t^2 + (1-t)^2 - 1}{t^2 + (1-t)^2} \right |
\]
\[
ratio(t)_{cubic} = \left | \frac{t^3 + (1-t)^3 - 1}{t^3 + (1-t)^3} \right |
\]
Unfortunately, this trick only works for quadratic and cubic curves. Once we hit higher order curves, things become a lot less predictable; the "fixed point *C*" is no longer fixed, moving around as we move the control points, and projections of *B* onto the line between start and end may actually lie on that line before the start, or after the end, and there are no simple ratios that we can exploit.
</div>
So: if we know B and its corresponding *t* value, then we know all the ABC values, which —together with a start and end coordinate— gives us the necessary information to reconstruct a curve's "de Casteljau skeleton", which means that two points and a value between 0 and 1, we can come up with a curve. And that opens up possibilities: curve manipulation by dragging an on-curve point, curve fitting of "a bunch of coordinates", these are useful things, and we'll look at both in the next sections.

View File

@@ -1,11 +1,13 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "abc";
var ABC = React.createClass({
getDefaultProps: function() {
return {
title: "The projection identity"
title: locale.getTitle(page)
};
},
@@ -131,123 +133,7 @@ var ABC = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>De Casteljau's algorithm is the pivotal algorithm when it comes to Bézier curves. You can use it not just to split
curves, but also to draw them efficiently (especially for high-order Bézier curves), as well as to come up with curves
based on three points and a tangent. Particularly this last thing is really useful because it lets us "mould" a curve,
by picking it up at some point, and dragging that point around to change the curve's shape.</p>
<p>How does that work? Succinctly: we run de Casteljau's algorithm in reverse!</p>
<p>In order to run de Casteljau's algorithm in reverse, we need a few basic things: a start and end point, a point
on the curve that want to be moving around, which has an associated <i>t</i> value, and a point we've not explicitly
talked about before, and as far as I know has no explicit name, but lives one iteration higher in the de Casteljau
process then our on-curve point does. I like to call it "A" for reasons that will become obvious.</p>
<p>So let's use graphics instead of text to see where this "A" is, because text only gets us so far: in the
following graphic, click anywhere on the curves to see the identity information that we'll be using to run
de Casteljau in reverse (you can manipulate the curve even after picking a point. Note the "ratio" value
when you do so: does it change?):</p>
<div className="figure">
<Graphic inline={true} preset="abc" title="Projections in a quadratic Bézier curve"
setup={this.setupQuadratic} draw={this.draw} onClick={this.onClick} />
<Graphic inline={true} preset="abc" title="Projections in a cubic Bézier curve"
setup={this.setupCubic} draw={this.draw} onClick={this.onClick} />
</div>
<p>Clicking anywhere on the curves shows us three things:</p>
<ol>
<li>our on-curve point; let's call that <b>B</b>,</li>
<li>a point at the tip of B's "hat", on de Casteljau step up; let's call that <b>A</b>, and</li>
<li>a point that we get by projecting B onto the start--end baseline; let's call that <b>C</b>.</li>
</ol>
<p>These three values ABC hide an important identity formula for quadratic and cubic Bézier curves:
for any point on the curve with some <i>t</i> value, the ratio distance of C along baseline is fixed:
if some <i>t</i> value sets up a C that is 20% away from the start and 80% away from the end, then it
doesn't matter where the start, end, or control points are: for that <i>t</i> value, C will <em>always</em> lie
at 20% from the start and 80% from the end point. Go ahead, pick an on-curve point in either graphic
and then move all the other points around: if you only move the control points, start and end won't move,
and so neither will C, and if you move either start or end point, C will move but its relative position
will not change. The following function stays true:</p>
<p>\[
C = u \cdot P_{start} + (1-u) \cdot P_{end}
\]</p>
<p>So that just leaves finding A.</p>
<div className="note">
<p>While that relation is fixed, the function <i>u(t)</i> differs depending on whether we're working
with quadratic or cubic curves:</p>
<p>\[\begin{align}
& u(t)_{quadratic} = \frac{(1-t)^2}{t^2 + (1-t)^2} \\
& u(t)_{cubic} = \frac{(1-t)^3}{t^3 + (1-t)^3}
\end{align}\]</p>
<p>So, if we know the start and end coordinates, and we know the <i>t</i> value, we know C:</p>
<div className="figure">
<Graphic inline={true} preset="abc" title="Quadratic value of C for t" draw={this.drawQCT} onMouseMove={this.setCT}/>
<Graphic inline={true} preset="abc" title="Cubic value of C for t" draw={this.drawCCT} onMouseMove={this.setCT}/>
</div>
<p>Mouse-over the graphs to see the expression for C, given the <i>t</i> value at the mouse pointer.</p>
</div>
<p>There's also another important bit of information that is inherent to the ABC values: while the distances
between A and B, and B and C, are dynamic (based on where we put B), the <em>ratio</em> between the two
distances is stable: given some <i>t</i> value, the following always holds:</p>
<p>\[
ratio(t) = \frac{distance(B,C)}{distance(A,B)} = Constant
\]</p>
<p>This leads to a pretty powerful bit of knowledge: merely by knowing the <i>t</i> value of some on curve
point, we know where C has to be (as per the above note), and because we know B and C, and thus have the
distance between them, we know where A has to be:</p>
<p>\[
A = B - \frac{C - B}{ratio(t)} = B + \frac{B - C}{ratio(t)}
\]</p>
<p>And that's it, all values found.</p>
<div className="note">
<p>Much like the <i>u(t)</i> function in the above note, the <i>ratio(t)</i> function depends
on whether we're looking at quadratic or cubic curves. Their form is intrinsically related to
the <i>u(t)</i> function in that they both come rolling out of the same function evalution,
explained over on <a href="http://mathoverflow.net/questions/122257/finding-the-formula-for-Bézier-curve-ratios-hull-point-point-baseline">MathOverflow</a> by
Boris Zbarsky and myself. The ratio functions are the "s(t)" functions from the answers there,
while the "u(t)" functions have the same name both here and on MathOverflow.</p>
<p>\[
ratio(t)_{quadratic} = \left | \frac{t^2 + (1-t)^2 - 1}{t^2 + (1-t)^2} \right |
\]</p>
<p>\[
ratio(t)_{cubic} = \left | \frac{t^3 + (1-t)^3 - 1}{t^3 + (1-t)^3} \right |
\]</p>
<p>Unfortunately, this trick only works for quadratic and cubic curves. Once we hit higher order curves,
things become a lot less predictable; the "fixed point <i>C</i>" is no longer fixed, moving around as we
move the control points, and projections of <i>B</i> onto the line between start and end may actually
lie on that line before the start, or after the end, and there are no simple ratios that we can exploit.</p>
</div>
<p>So: if we know B and its corresponding <i>t</i> value, then we know all the ABC values, which
—together with a start and end coordinate— gives us the necessary information to reconstruct a curve's
"de Casteljau skeleton", which means that two points and a value between 0 and 1, we can come up with
a curve. And that opens up possibilities: curve manipulation by dragging an on-curve point, curve fitting
of "a bunch of coordinates", these are useful things, and we'll look at both in the next sections.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,101 @@
# Arc length
How long is a Bézier curve? As it turns out, that's not actually an easy question, because the answer requires maths that —much like root finding— cannot generally be solved the traditional way. If we have a parametric curve with *f<sub>x</sub>(t)* and *f<sub>y</sub>(t)*, then the length of the curve, measured from start point to some point *t = z*, is computed using the following seemingly straight forward (if a bit overwhelming) formula:
\[
\int_{0}^{z}\sqrt{f_x'(t)^2+f_y'(t)^2} dt
\]
or, more commonly written using Leibnitz notation as:
\[
length = \int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\]
This formula says that the length of a parametric curve is in fact equal to the **area** underneath a function that looks a remarkable amount like Pythagoras' rule for computing the diagonal of a straight angled triangle. This sounds pretty simple, right? Sadly, it's far from simple... cutting straight to after the chase is over: for quadratic curves, this formula generates an [unwieldy computation](http://www.wolframalpha.com/input/?i=antiderivative+for+sqrt((2*(1-t)*t*B+%2B+t%5E2*C)%27%5E2+%2B+(2*(1-t)*t*E)%27%5E2)&incParTime=true), and we're simply not going to implement things that way. For cubic Bézier curves, things get even more fun, because there is no "closed form" solution, meaning that due to the way calculus works, there is no generic formula that allows you to calculate the arc length. Let me just repeat this, because it's fairly crucial: ***for cubic and higher Bézier curves, there is no way to solve this function if you want to use it "for all possible coordinates"***.
Seriously: [It cannot be done](https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem).
So we turn to numerical approaches again. The method we'll look at here is the [Gauss quadrature](http://www.youtube.com/watch?v=unWguclP-Ds&feature=BFa&list=PLC8FC40C714F5E60F&index=1). This approximation is a really neat trick, because for any *n<sup>th</sup>* degree polynomial it finds approximated values for an integral really efficiently. Explaining this procedure in length is way beyond the scope of this page, so if you're interested in finding out why it works, I can recommend the University of South Florida video lecture on the procedure, linked in this very paragraph. The general solution we're looking for is the following:
\[
\int_{-1}^{1}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\simeq
\left [
\underset{strip\ 1}{ \underbrace{ C_1 \cdot f\left(t_1\right) }}
\ +\ ...
\ +\ \underset{strip\ n}{ \underbrace{ C_n \cdot f\left(t_n\right) }}
\right ]
=
\underset{strips\ 1\ through\ n}{
\underbrace{
\sum_{i=1}^{n}{
C_i \cdot f\left(t_i\right)
}
}
}
\]
In plain text: an integral function can always be treated as the sum of an (infinite) number of (infinitely thin) rectangular strips sitting "under" the function's plotted graph. To illustrate this idea, the following graph shows the integral for a sinoid function. The more strips we use (and of course the more we use, the thinner they get) the closer we get to the true area under the curve, and thus the better the approximation:
<div className="figure">
<Graphic inline={true} static={true} preset="empty" title="A function's approximated integral" setup={this.setup} draw={this.drawCoarseIntegral}/>
<Graphic inline={true} static={true} preset="empty" title="A better approximation" setup={this.setup} draw={this.drawFineIntegral}/>
<Graphic inline={true} static={true} preset="empty" title="An even better approximation" setup={this.setup} draw={this.drawSuperFineIntegral}/>
</div>
Now, infinitely many terms to sum and infinitely thin rectangles are not something that computers can work with, so instead we're going to approximate the infinite summation by using a sum of a finite number of "just thin" rectangular strips. As long as we use a high enough number of thin enough rectangular strips, this will give us an approximation that is pretty close to what the real value is.
So, the trick is to come up with useful rectangular strips. A naive way is to simply create *n* strips, all with the same width, but there is a far better way using special values for *C* and *f(t)* depending on the value of *n*, which indicates how many strips we'll use, and it's called the Legendre-Gauss quadrature.
This approach uses strips that are *not* spaced evenly, but instead spaces them in a special way that works remarkably well. If you look at the earlier sinoid graphic, you could imagine that we could probably get a result similar to the one with 99 strips if we used fewer strips, but spaced them so that the steeper the curve is, the thinner we make the strip, and conversely, the flatter the curve is (especially near the tops of the function), the wider we make the strip. That's akin to how the Legendre values work.
<div className="note">
Note that one requirement for the approach we'll use is that the integral must run from -1 to 1. That's no good, because we're dealing with Bézier curves, and the length of a section of curve applies to values which run from 0 to "some value smaller than or equal to 1" (let's call that value *z*). Thankfully, we can quite easily transform any integral interval to any other integral interval, by shifting and scaling the inputs. Doing so, we get the following:
\[
\begin{array}{l}
\int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\\
\simeq \
\frac{z}{2} \cdot \left [ C_1 \cdot f\left(\frac{z}{2} \cdot t_1 + \frac{z}{2}\right)
+ ...
+ C_n \cdot f\left(\frac{z}{2} \cdot t_n + \frac{z}{2}\right)
\right ]
\\
= \
\frac{z}{2} \cdot \sum_{i=1}^{n}{C_i \cdot f\left(\frac{z}{2} \cdot t_i + \frac{z}{2}\right)}
\end{array}
\]
That may look a bit more complicated, but the fraction involving *z* is a fixed number, so the summation, and the evaluation of the *f(t)* values are still pretty simple.
So, what do we need to perform this calculation? For one, we'll need an explicit formula for *f(t)*, because that derivative notation is handy on paper, but not when we have to implement it. We'll also need to know what these *C<sub>i</sub>* and *t<sub>i</sub>* values should be. Luckily, that's less work because there are actually many tables available that give these values, for any *n*, so if we want to approximate our integral with only two terms (which is a bit low, really) then [these tables](legendre-gauss.html) would tell us that for *n=2* we must use the following values:
\[
\begin{array}{l}
C_1 = 1 \\
C_2 = 1 \\
t_1 = - \frac{1}{\sqrt{3}} \\
t_2 = + \frac{1}{\sqrt{3}}
\end{array}
\]
Which means that in order for us to approximate the integral, we must plug these values into the approximate function, which gives us:
\[
\int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\frac{z}{2} \cdot \left [ f\left( \frac{z}{2} \cdot \frac{-1}{\sqrt{3}} + \frac{z}{2} \right)
+ f\left( \frac{z}{2} \cdot \frac{1}{\sqrt{3}} + \frac{z}{2} \right)
\right ]
\]
We can program that pretty easily, provided we have that *f(t)* available, which we do, as we know the full description for the Bézier curve functions B<sub>x</sub>(t) and B<sub>y</sub>(t).
</div>
If we use the Legendre-Gauss values for our *C* values (thickness for each strip) and *t* values (location of each strip), we can determine the approximate length of a Bézier curve by computing the Legendre-Gauss sum. The following graphic shows a cubic curve, with its computed lengths; Go ahead and change the curve, to see how its length changes. One thing worth trying is to see if you can make a straight line, and see if the length matches what you'd expect. What if you form a line with the control points on the outside, and the start/end points on the inside?
<Graphic preset="simple" title="Arc length for a Bézier curve" setup={this.setupCurve} draw={this.drawCurve}/>

View File

@@ -1,6 +1,8 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "arclength";
var sin = Math.sin;
var tau = Math.PI*2;
@@ -8,7 +10,7 @@ var tau = Math.PI*2;
var Arclength = React.createClass({
getDefaultProps: function() {
return {
title: "Arc length"
title: locale.getTitle(page)
};
},
@@ -100,156 +102,7 @@ var Arclength = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>How long is a Bézier curve? As it turns out, that's not actually an easy question, because the answer
requires maths that —much like root finding— cannot generally be solved the traditional way. If we
have a parametric curve with <i>f<sub>x</sub>(t)</i> and <i>f<sub>y</sub>(t)</i>, then the length of the
curve, measured from start point to some point <i>t = z</i>, is computed using the following seemingly
straight forward (if a bit overwhelming) formula:</p>
<p>\[
\int_{0}^{z}\sqrt{f_x'(t)^2+f_y'(t)^2} dt
\]</p>
<p>or, more commonly written using Leibnitz notation as:</p>
<p>\[
length = \int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\]</p>
<p>This formula says that the length of a parametric curve is in fact equal to the <b>area</b> underneath a function that
looks a remarkable amount like Pythagoras' rule for computing the diagonal of a straight angled triangle. This sounds
pretty simple, right? Sadly, it's far from simple... cutting straight to after the chase is over: for quadratic curves,
this formula generates an <a href="http://www.wolframalpha.com/input/?i=antiderivative+for+sqrt((2*(1-t)*t*B+%2b+t^2*C)'^2+%2b+(2*(1-t)*t*E)'^2)&incParTime=true">unwieldy computation</a>,
and we're simply not going to implement things that way. For cubic Bézier curves, things get even more fun, because there
is no "closed form" solution, meaning that due to the way calculus works, there is no generic formula that allows you to
calculate the arc length. Let me just repeat this, because it's fairly crucial: <strong><em>for cubic and higher Bézier curves,
there is no way to solve this function if you want to use it "for all possible coordinates".</em></strong></p>
<p>Seriously: <a href="https://en.wikipedia.org/wiki/Abel%E2%80%93Ruffini_theorem">It cannot be done.</a></p>
<p>So we turn to numerical approaches again. The method we'll look at here is the
<a href="http://www.youtube.com/watch?v=unWguclP-Ds&feature=BFa&list=PLC8FC40C714F5E60F&index=1">Gauss
quadrature</a>. This approximation is a really neat trick, because for any <i>n<sup>th</sup></i> degree polynomial
it finds approximated values for an integral really efficiently. Explaining this procedure in length is way beyond
the scope of this page, so if you're interested in finding out why it works, I can recommend the University of
South Florida video lecture on the procedure, linked in this very paragraph. The general solution we're looking
for is the following:</p>
<p>\[
\int_{-1}^{1}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\simeq
\left [
\underset{strip\ 1}{ \underbrace{ C_1 \cdot f\left(t_1\right) }}
\ +\ ...
\ +\ \underset{strip\ n}{ \underbrace{ C_n \cdot f\left(t_n\right) }}
\right ]
=
\underset{strips\ 1\ through\ n}{
\underbrace{
\sum_{i=1}^{n}{
C_i \cdot f\left(t_i\right)
}
}
}
\]</p>
<p>In plain text: an integral function can always be treated as the sum of an (infinite) number of
(infinitely thin) rectangular strips sitting "under" the function's plotted graph. To illustrate
this idea, the following graph shows the integral for a sinoid function. The more strips we use (and
of course the more we use, the thinner they get) the closer we get to the true area under the curve, and
thus the better the approximation:</p>
<div className="figure">
<Graphic inline={true} static={true} preset="empty" title="A function's approximated integral" setup={this.setup} draw={this.drawCoarseIntegral}/>
<Graphic inline={true} static={true} preset="empty" title="A better approximation" setup={this.setup} draw={this.drawFineIntegral}/>
<Graphic inline={true} static={true} preset="empty" title="An even better approximation" setup={this.setup} draw={this.drawSuperFineIntegral}/>
</div>
<p>Now, infinitely many terms to sum and infinitely thin rectangles are not something that computers
can work with, so instead we're going to approximate the infinite summation by using a sum of a finite
number of "just thin" rectangular strips. As long as we use a high enough number of thin enough rectangular
strips, this will give us an approximation that is pretty close to what the real value is.</p>
<p>So, the trick is to come up with useful rectangular strips. A naive way is to simply create <i>n</i> strips,
all with the same width, but there is a far better way using special values for <i>C</i> and <i>f(t)</i> depending
on the value of <i>n</i>, which indicates how many strips we'll use, and it's called the Legendre-Gauss quadrature.</p>
<p>This approach uses strips that are <em>not</em> spaced evenly, but instead spaces them in a special way that works
remarkably well. If you look at the earlier sinoid graphic, you could imagine that we could probably get a result
similar to the one with 99 strips if we used fewer strips, but spaced them so that the steeper the curve is, the
thinner we make the strip, and conversely, the flatter the curve is (especially near the tops of the function),
the wider we make the strip. That's akin to how the Legendre values work.</p>
<div className="note">
<p>Note that one requirement for the approach we'll use is that the integral must run from -1 to 1. That's no good, because
we're dealing with Bézier curves, and the length of a section of curve applies to values which run from 0 to "some
value smaller than or equal to 1" (let's call that value <i>z</i>). Thankfully, we can quite easily transform any
integral interval to any other integral interval, by shifting and scaling the inputs. Doing so, we get the
following:</p>
<p>\[\begin{array}{l}
\int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\\
\simeq \
\frac{z}{2} \cdot \left [ C_1 \cdot f\left(\frac{z}{2} \cdot t_1 + \frac{z}{2}\right)
+ ...
+ C_n \cdot f\left(\frac{z}{2} \cdot t_n + \frac{z}{2}\right)
\right ]
\\
= \
\frac{z}{2} \cdot \sum_{i=1}^{n}{C_i \cdot f\left(\frac{z}{2} \cdot t_i + \frac{z}{2}\right)}
\end{array}\]</p>
<p>That may look a bit more complicated, but the fraction involving <i>z</i> is a fixed number,
so the summation, and the evaluation of the <i>f(t)</i> values are still pretty simple.</p>
<p>So, what do we need to perform this calculation? For one, we'll need an explicit formula for <i>f(t)</i>,
because that derivative notation is handy on paper, but not when we have to implement it. We'll also
need to know what these <i>C<sub>i</sub></i> and <i>t<sub>i</sub></i> values should be. Luckily, that's
less work because there are actually many tables available that give these values, for any <i>n</i>,
so if we want to approximate our integral with only two terms (which is a bit low, really)
then <a href="legendre-gauss.html">these tables</a> would tell us that for <i>n=2</i> we must use the
following values:</p>
<p>\[\begin{array}{l}
C_1 = 1 \\
C_2 = 1 \\
t_1 = - \frac{1}{\sqrt{3}} \\
t_2 = + \frac{1}{\sqrt{3}}
\end{array}\]</p>
<p>Which means that in order for us to approximate the integral, we must plug these values into the approximate
function, which gives us:</p>
<p>\[
\int_{0}^{z}\sqrt{ \left (dx/dt \right )^2+\left (dy/dt \right )^2} dt
\frac{z}{2} \cdot \left [ f\left( \frac{z}{2} \cdot \frac{-1}{\sqrt{3}} + \frac{z}{2} \right)
+ f\left( \frac{z}{2} \cdot \frac{1}{\sqrt{3}} + \frac{z}{2} \right)
\right ]
\]</p>
<p>We can program that pretty easily, provided we have that <i>f(t)</i> available, which we do,
as we know the full description for the Bézier curve functions B<sub>x</sub>(t) and B<sub>y</sub>(t).</p>
</div>
<p>If we use the Legendre-Gauss values for our <i>C</i> values (thickness for each strip) and <i>t</i> values
(location of each strip), we can determine the approximate length of a Bézier curve by computing the
Legendre-Gauss sum. The following graphic shows a cubic curve, with its computed lengths; Go ahead and
change the curve, to see how its length changes. One thing worth trying is to see if you can make a straight
line, and see if the length matches what you'd expect. What if you form a line with the control points
on the outside, and the start/end points on the inside?</p>
<Graphic preset="simple" title="Arc length for a Bézier curve" setup={this.setupCurve} draw={this.drawCurve}/>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,10 @@
# Approximated arc length
Sometimes, we don't actually need the precision of a true arc length, and we can get away with simply computing the approximate arc length instead. The by far fastest way to do this is to flatten the curve and then simply calculate the linear distance from point to point. This will come with an error, but this can be made arbitrarily small by increasing the segment count.
If we combine the work done in the previous sections on curve flattening and arc length computation, we can implement these with minimal effort:
<Graphic preset="twopanel" title="Approximate quadratic curve arc length" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<Graphic preset="twopanel" title="Approximate cubic curve arc length" setup={this.setupCubic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both the quadratic and cubic curve. You may notice that the error in length is actually pretty significant, even if the percentage is fairly low: if the number of segments used yields an error of 0.1% or higher, the flattened curve already looks fairly obviously flattened. And of course, the longer the curve, the more significant the error will be.

View File

@@ -1,6 +1,9 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "arclengthapprox";
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var ArclengthApprox = React.createClass({
@@ -21,7 +24,7 @@ var ArclengthApprox = React.createClass({
getDefaultProps: function() {
return {
title: "Approximated arc length"
title: locale.getTitle(page)
};
},
@@ -68,28 +71,7 @@ var ArclengthApprox = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Sometimes, we don't actually need the precision of a true arc length, and we can get away with simply computing the
approximate arc length instead. The by far fastest way to do this is to flatten the curve and then simply calculate
the linear distance from point to point. This will come with an error, but this can be made arbitrarily small by
increasing the segment count.</p>
<p>If we combine the work done in the previous sections on curve flattening and arc length computation, we can
implement these with minimal effort:</p>
<Graphic preset="twopanel" title="Approximate quadratic curve arc length" setup={this.setupQuadratic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<Graphic preset="twopanel" title="Approximate cubic curve arc length" setup={this.setupCubic} draw={this.draw} onKeyDown={this.props.onKeyDown} />
<p>Try clicking on the sketch and using your up and down arrow keys to lower the number of segments for both
the quadratic and cubic curve. You may notice that the error in length is actually pretty significant, even if
the percentage is fairly low: if the number of segments used yields an error of 0.1% or higher, the flattened
curve already looks fairly obviously flattened. And of course, the longer the curve, the more significant the
error will be.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,366 @@
# 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). Now, a Catmull-Rom spline is a form of cubic Hermite spline, and as it so happens the cubic Bézier curve is also a cubic Hermite spline, so maybe... maybe we can convert one into the other, and back, with some simple substitutions?
Unlike Bézier curves, Catmull-Rom splines pass through each point used to define the curve, except the first and last, which makes sense if you read the "natural language" descriptionfor how a Catmull-Rom spline works: a Catmull-Rom spline is a curve that, at each point P<sub>x</sub>, has a tangent along the line P<sub>x-1</sub> to P<sub>x+1</sub>. The curve runs from points P<sub>2</sub> to P<sub>n-1</sub>, and has a "tension" that determines how fast the curve passes through each point. The lower the tension, the faster the curve goes through each point, and the bigger its local tangent is.
I'll be showing the conversion to and from Catmull-Rom curves for the tension that the Processing language uses for its Catmull-Rom algorithm.
We start with showing the Catmull-Rom matrix form:
\[
CatmullRom(t) =
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
\]
However, there's something funny going on here: the coordinate column matrix looks weird. The reason is that Catmull-Rom curves are actually curve segments that are described by two points, and two tangents; the curve leaves a point V1 (if we have four coordinates instead, this is coordinate 2), arriving at a point V2 (coordinate 3), with the curve departing V1 with a tangent vector V'1 (equal to the tangent from coordinate 1 to coordinate 3) and arriving at V2 with tangent vector V'2 (equal to the tangent from coordinate 2 to coordinate 4). So if we want to express this as a matrix form based on four coordinates, we get this representation instead:
\[
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
=
T
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_2 \\ P_3 \\ \frac{P_3 - P_1}{2} \\ \frac{P_4 - P_2}{2}
\end{bmatrix}
\ \Rightarrow \
T
=
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
-1 & 0 & 1 & 0 \\
0 & -1 & 0 & 1
\end{bmatrix}
\]
<div className="note">
## Where did that 2 come from?
Catmull-Rom splines are based on the concept of tension: the higher the tensions, the shorter the tangents at the departure and arrival points. The basic Catmull-Rom curve arrives and departs with tangents equal to half the distance between the two adjacent points, so that's where that 2 came from.
However, the "real" matrix is this:
\[
T
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_2 \\ P_3 \\ \frac{P_3 - P_1}{2 \cdot τ} \\ \frac{P_4 - P_2}{2 \cdot τ}
\end{bmatrix}
\Rightarrow \
T
=
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
& 0 & τ & 0 \\
0 && 0 & τ
\end{bmatrix}
\]
This bakes in the tension factor τ explicitly.
</div>
Plugging this into the "two coordinates and two tangent vectors" matrix form, we get:
\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\left (
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
& 0 & τ & 0 \\
0 && 0 & τ
\end{bmatrix}
\right )
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
& 0 & τ & 0 \\
& τ-6 & -2(τ-3) & -τ \\
& 4-τ & τ-4 & τ
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
So let's find out which transformation matrix we need in order to convert from Catmull-Rom to Bézier:
\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
& 0 & τ & 0 \\
& τ-6 & -2(τ-3) & -τ \\
& 4-τ & τ-4 & τ
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
The difference is somewhere in the actual hermite matrix, since the <em>t</em> and coordinate values are identical, so let's solve that matrix equasion:
\[
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
& 0 & τ & 0 \\
& τ-6 & -2(τ-3) & -τ \\
& 4-τ & τ-4 & τ
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\]
We left-multiply both sides by the inverse of the Bézier matrix, to get rid of the Bézier matrix on the right side of the equals sign:
\[
{
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
}^{-1}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
& 0 & τ & 0 \\
& τ-6 & -2(τ-3) & -τ \\
& 4-τ & τ-4 & τ
\end{bmatrix}
=
{
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
}^{-1}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\ =\
I \cdot A
\ =\
A
\]
Which gives us:
\[
\frac{1}{6}
\cdot
\begin{bmatrix}
0 & 6 & 0 & 0 \\
& 6 & τ & 0 \\
0 & τ & 0 & -τ \\
0 & 0 & 6 & 0
\end{bmatrix}
=
A
\]
Multiplying this ***A*** with our coordinates will give us a proper Bézier matrix expression again:
\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\frac{1}{6}
\cdot
\begin{bmatrix}
0 & 6 & 0 & 0 \\
& 6 & τ & 0 \\
0 & τ & 0 & -τ \\
0 & 0 & 6 & 0
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]
\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_2 \\
P_2 + \frac{P_3-P_1}{6 \cdot τ} \\
P_3 - \frac{P_4-P_2}{6 \cdot τ} \\
P_3
\end{bmatrix}
\]
So a Catmull-Rom to Bézier conversion, based on coordinates, requires turning the Catmull-Rom coordinates on the left into the Bézier coordinates on the right (with τ being our tension factor):
\[
\begin{bmatrix}
P_1 \\
P_2 \\
P_3 \\
P_4
\end{bmatrix}_{CatmullRom}
\Rightarrow
\begin{bmatrix}
P_2 \\
P_2 + \frac{P_3-P_1}{6 \cdot τ} \\
P_3 - \frac{P_4-P_2}{6 \cdot τ} \\
P_3
\end{bmatrix}_{Bézier}
\]
And the other way around, a Bézier to Catmull-Rom conversion requires turning the Bézier coordinates on the left this time into the Catmull-Rom coordinates on the right. Note that there is no tension this time, because Bézier curves don't have any. Converting from Bézier to Catmull-Rom is simply a default-tension Catmull-Rom curve:
\[
\begin{bmatrix}
P_1 \\
P_2 \\
P_3 \\
P_4
\end{bmatrix}_{Bézier}
\Rightarrow
\begin{bmatrix}
P_4 + 6 \cdot (P_1 - P_2) \\
P_1 \\
P_4 \\
P_1 + 6 \cdot (P_4 - P_3)
\end{bmatrix}_{CatmullRom}
\]
Done. We can now draw the curves we want using either Bézier curves or Catmull-Rom splines, the choice mostly being which drawing algorithms we have natively available.

View File

@@ -1,415 +1,18 @@
var React = require("react");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "catmullconv";
var CatmullRomConversion = React.createClass({
getDefaultProps: function() {
return {
title: "Bézier curves and Catmull-Rom curves"
title: locale.getTitle(page)
};
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<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>. Now, a Catmull-Rom spline is a form of cubic Hermite spline, and as it so happens
the cubic Bézier curve is also a cubic Hermite spline, so maybe... maybe we can convert one
into the other, and back, with some simple substitutions?</p>
<p>Unlike Bézier curves, Catmull-Rom splines pass through each point used to define the curve,
except the first and last, which makes sense if you read the "natural language" description
for how a Catmull-Rom spline works: a Catmull-Rom spline is a curve that, at each point
P<sub>x</sub>, has a tangent along the line P<sub>x-1</sub> to P<sub>x+1</sub>. The curve
runs from points P<sub>2</sub> to P<sub>n-1</sub>, and has a "tension" that determines
how fast the curve passes through each point. The lower the tension, the faster the curve
goes through each point, and the bigger its local tangent is.</p>
<p>I'll be showing the conversion to and from Catmull-Rom curves for the tension that the
Processing language uses for its Catmull-Rom algorithm.</p>
<p>We start with showing the Catmull-Rom matrix form:</p>
<p>\[
CatmullRom(t) =
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
\]
</p>
<p>However, there's something funny going on here: the coordinate column matrix looks weird.
The reason is that Catmull-Rom curves are actually curve segments that are described by two
points, and two tangents; the curve leaves a point V1 (if we have four coordinates instead,
this is coordinate 2), arriving at a point V2 (coordinate 3), with the curve departing V1
with a tangent vector V'1 (equal to the tangent from coordinate 1 to coordinate 3) and
arriving at V2 with tangent vector V'2 (equal to the tangent from coordinate 2 to coordinate
4). So if we want to express this as a matrix form based on four coordinates, we get this
representation instead:</p>
<p>\[
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
=
T
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_2 \\ P_3 \\ \frac{P_3 - P_1}{2} \\ \frac{P_4 - P_2}{2}
\end{bmatrix}
\ \Rightarrow \
T
=
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
-1 & 0 & 1 & 0 \\
0 & -1 & 0 & 1
\end{bmatrix}
\]</p>
<div className="note">
<h2>Where did that 2 come from?</h2>
<p>Catmull-Rom splines are based on the concept of tension: the higher the tensions,
the shorter the tangents at the departure and arrival points. The basic Catmull-Rom
curve arrives and departs with tangents equal to half the distance between the two
adjacent points, so that's where that 2 came from.</p>
<p>However, the "real" matrix is this:</p>
<p>\[
T
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
P_2 \\ P_3 \\ \frac{P_3 - P_1}{2 \cdot τ} \\ \frac{P_4 - P_2}{2 \cdot τ}
\end{bmatrix}
\Rightarrow \
T
=
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
-τ & 0 & τ & 0 \\
0 & -τ & 0 & τ
\end{bmatrix}
\]</p>
<p>This bakes in the tension factor τ explicitly.</p>
</div>
<p>Plugging this into the "two coordinates and two tangent vectors" matrix form,
we get:</p>
<p>\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
V_1 \\ V_2 \\ V'_1 \\ V'_2
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
0 & 0 & 1 & 0 \\
-3 & 3 & -2 & -1 \\
2 & -2 & 1 & 1
\end{bmatrix}
\cdot
\left (
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
0 & 0 & 2 & 0 \\
-τ & 0 & τ & 0 \\
0 & -τ & 0 & τ
\end{bmatrix}
\right )
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
-τ & 0 & τ & 0 \\
2τ & τ-6 & -2(τ-3) & -τ \\
-τ & 4-τ & τ-4 & τ
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>So let's find out which transformation matrix we need in order to convert from Catmull-Rom to Bézier:</p>
<p>\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
-τ & 0 & τ & 0 \\
2τ & τ-6 & -2(τ-3) & -τ \\
-τ & 4-τ & τ-4 & τ
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>The difference is somewhere in the actual hermite matrix, since the <em>t</em> and coordinate
values are identical, so let's solve that matrix equasion:</p>
<p>\[
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
-τ & 0 & τ & 0 \\
2τ & τ-6 & -2(τ-3) & -τ \\
-τ & 4-τ & τ-4 & τ
\end{bmatrix}
=
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\]</p>
<p>We left-multiply both sides by the inverse of the Bézier matrix, to get rid of the
Bézier matrix on the right side of the equals sign:</p>
<p>\[
{
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
}^{-1}
\cdot
\frac{1}{2}
\cdot
\begin{bmatrix}
0 & 2 & 0 & 0 \\
-τ & 0 & τ & 0 \\
2τ & τ-6 & -2(τ-3) & -τ \\
-τ & 4-τ & τ-4 & τ
\end{bmatrix}
=
{
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
}^{-1}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
A
\ =\
I \cdot A
\ =\
A
\]
</p>
<p>Which gives us:</p>
<p>\[
\frac{1}{6}
\cdot
\begin{bmatrix}
0 & 6 & 0 & 0 \\
-τ & 6 & τ & 0 \\
0 & τ & 0 & -τ \\
0 & 0 & 6 & 0
\end{bmatrix}
=
A
\]</p>
<p>Multiplying this <strong><em>A</em></strong> with our coordinates
will give us a proper Bézier matrix expression again:</p>
<p>\[
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\frac{1}{6}
\cdot
\begin{bmatrix}
0 & 6 & 0 & 0 \\
-τ & 6 & τ & 0 \\
0 & τ & 0 & -τ \\
0 & 0 & 6 & 0
\end{bmatrix}
\cdot
\begin{bmatrix}
P_1 \\ P_2 \\ P_3 \\ P_4
\end{bmatrix}
\]</p>
<p>\[
=
\begin{bmatrix}
1 & t & t^2 & t^3
\end{bmatrix}
\cdot
\begin{bmatrix}
1 & 0 & 0 & 0 \\
-3 & 3 & 0 & 0 \\
3 & -6 & 3 & 0 \\
-1 & 3 & -3 & 1
\end{bmatrix}
\cdot
\begin{bmatrix}
P_2 \\
P_2 + \frac{P_3-P_1}{6 \cdot τ} \\
P_3 - \frac{P_4-P_2}{6 \cdot τ} \\
P_3
\end{bmatrix}
\]</p>
<p>So a Catmull-Rom to Bézier conversion, based on coordinates, requires turning
the Catmull-Rom coordinates on the left into the Bézier coordinates on the right
(with τ being our tension factor):</p>
<p>\[
\begin{bmatrix}
P_1 \\
P_2 \\
P_3 \\
P_4
\end{bmatrix}_{CatmullRom}
\Rightarrow
\begin{bmatrix}
P_2 \\
P_2 + \frac{P_3-P_1}{6 \cdot τ} \\
P_3 - \frac{P_4-P_2}{6 \cdot τ} \\
P_3
\end{bmatrix}_{Bézier}
\]</p>
<p>And the other way around, a Bézier to Catmull-Rom conversion requires turning
the Bézier coordinates on the left this time into the Catmull-Rom coordinates
on the right. Note that there is no tension this time, because Bézier curves
don't have any. Converting from Bézier to Catmull-Rom is simply a default-tension
Catmull-Rom curve:</p>
<p>\[
\begin{bmatrix}
P_1 \\
P_2 \\
P_3 \\
P_4
\end{bmatrix}_{Bézier}
\Rightarrow
\begin{bmatrix}
P_4 + 6 \cdot (P_1 - P_2) \\
P_1 \\
P_4 \\
P_1 + 6 \cdot (P_4 - P_3)
\end{bmatrix}_{CatmullRom}
\]</p>
<p>Done. We can now draw the curves we want using either Bézier curves or Catmull-Rom
splines, the choice mostly being which drawing algorithms we have natively available.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,13 @@
# 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-1</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 preset="threepanel" 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,6 +1,9 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "catmullmoulding";
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var CatmullRomMoulding = React.createClass({
@@ -16,7 +19,7 @@ var CatmullRomMoulding = React.createClass({
getDefaultProps: function() {
return {
title: "Creating a Catmull-Rom curve from three points"
title: locale.getTitle(page)
};
},
@@ -141,41 +144,7 @@ var CatmullRomMoulding = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<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-1</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 preset="threepanel" 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>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,19 @@
# Curve/curve intersection
Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection finding using a "divide and conquer" technique:
- Take two curves *C<sub>1</sub>* and *C<sub>2</sub>*, and treat them as a pair.
- If their bounding boxes overlap, split up each curve into two sub-curves
- With *C<sub>1.1</sub>*, *C<sub>1.2</sub>*, *C<sub>2.1</sub>* and *C<sub>2.2</sub>*, form four new pairs (*C<sub>1.1</sub>*,*C<sub>2.1</sub>*), (*C<sub>1.1</sub>*, *C<sub>2.2</sub>*), (*C<sub>1.2</sub>*,*C<sub>2.1</sub>*), and (*C<sub>1.2</sub>*,*C<sub>2.2</sub>*).
- For each pair, check whether their bounding boxes overlap.
- If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.
- If there <em>is</em> overlap, rerun all steps for this pair.
- Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found.
This algorithm will start with a single pair, "balloon" until it runs in parallel for a large number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates, ending up with as many pairs as there are intersections.
The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so you can see the algorithm in action. Click the button to run a single step in the algorithm, after setting up your curves in some creative arrangement. The algorithm resets once it's found a solution, so you can try this with lots of different curves (can you find the configuration that yields the maximum number of intersections between two cubic curves? Nine intersections!)
<Graphic preset="clipping" title="Curve/curve intersections" setup={this.setup} draw={this.draw} children={[<button onClick={this.stepUp}>advance one step</button>]}/>
Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first based on the inflection points. We then form all possible curve pairs with the resultant segments, and run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration, and the remaining steps home in on the curve's self-intersection points.

View File

@@ -1,13 +1,15 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "curveintersection";
var abs = Math.abs;
var CurveIntersections = React.createClass({
getDefaultProps: function() {
return {
title: "Curve/curve intersection"
title: locale.getTitle(page)
};
},
@@ -126,47 +128,7 @@ var CurveIntersections = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Using de Casteljau's algorithm to split the curve we can now implement curve/curve intersection
finding using a "divide and conquer" technique:</p>
<ul>
<li>Take two curves <i>C<sub>1</sub></i> and <i>C<sub>2</sub></i>, and treat them as a pair.</li>
<li>If their bounding boxes overlap, split up each curve into two sub-curves</li>
<li>With <i>C<sub>1.1</sub></i>, <i>C<sub>1.2</sub></i>, <i>C<sub>2.1</sub></i> and <i>C<sub>2.2</sub></i>, form
four new pairs (<i>C<sub>1.1</sub></i>,<i>C<sub>2.1</sub></i>), (<i>C<sub>1.1</sub></i>, <i>C<sub>2.2</sub></i>), (<i>C<sub>1.2</sub></i>,<i>C<sub>2.1</sub></i>), and (<i>C<sub>1.2</sub></i>,<i>C<sub>2.2</sub></i>).</li>
<li>For each pair, check whether their bounding boxes overlap.
<ul>
<li>If their bounding boxes do not overlap, discard the pair, as there is no intersection between this pair of curves.</li>
<li>If there <em>is</em> overlap, rerun all steps for this pair.</li>
</ul>
</li>
<li>Once the sub-curves we form are so small that they effectively occupy sub-pixel areas, we consider an intersection found.</li>
</ul>
<p>This algorithm will start with a single pair, "balloon" until it runs in parallel for a large
number of potential sub-pairs, and then taper back down as it homes in on intersection coordinates,
ending up with as many pairs as there are intersections.</p>
<p>The following graphic applies this algorithm to a pair of cubic curves, one step at a time, so
you can see the algorithm in action. Click the button to run a single step in the algorithm,
after setting up your curves in some creative arrangement. The algorithm resets once it's found
a solution, so you can try this with lots of different curves (can you find the configuration
that yields the maximum number of intersections between two cubic curves? Nine intersections!)</p>
<Graphic preset="clipping" title="Curve/curve intersections" setup={this.setup} draw={this.draw}>
<button onClick={this.stepUp}>advance one step</button>
</Graphic>
<p>Self-intersection is dealt with in the same way, except we turn a curve into two or more curves first
based on the inflection points. We then form all possible curve pairs with the resultant segments, and
run exactly the same algorithm. All non-overlapping curve pairs will be removed after the first iteration,
and the remaining steps home in on the curve's self-intersection points.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,50 @@
# Intersections
Let's look at some more things we will want to do with Bézier curves. Almost immediately after figuring out how to get bounding boxes to work, people tend to run into the problem that even though the minimal bounding box (based on rotation) is tight, it's not sufficient to perform true collision detection. It's a good first step to make sure there *might* be a collision (if there is no bounding box overlap, there can't be one), but in order to do real collision detection we need to know whether or not there's an intersection on the actual curve.
We'll do this in steps, because it's a bit of a journey to get to curve/curve intersection checking. First, let's start simple, by implementing a line-line intersection checker. While we can solve this the traditional calculus way (determine the functions for both lines, then compute the intersection by equating them and solving for two unknowns), linear algebra actually offers a nicer solution.
### Line-line intersections
if we have two line segments with two coordinates each, segments A-B and C-D, we can find the intersection of the lines these segments are an intervals on by linear algebra, using the procedure outlined in this [top coder](http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#line_line_intersection) article. Of course, we need to make sure that the intersection isn't just on the lines our line segments lie on, but also on our line segments themselves, so after we find the intersection we need to verify it lies without the bounds of our original line segments.
The following graphic implements this intersection detection, showing a red point for an intersection on the lines our segments lie on (thus being a virtual intersection point), and a green point for an intersection that lies on both segments (being a real intersection point).
<Graphic preset="simple" title="Line/line intersections" setup={this.setupLines} draw={this.drawLineIntersection} />
<div className="howtocode">
### Implementing line-line intersections
Let's have a look at how to implement a line-line intersection checking function. The basics are covered in the article mentioned above, but sometimes you need more function signatures, because you might not want to call your function with eight distinct parameters. Maybe you're using point structs or the line. Let's get coding:
```
lli8 = function(x1,y1,x2,y2,x3,y3,x4,y4):
var nx=(x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4),
ny=(x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4),
d=(x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
if d=0:
return false
return point(nx/d, ny/d)
lli4 = function(p1, p2, p3, p4):
var x1 = p1.x, y1 = p1.y,
x2 = p2.x, y2 = p2.y,
x3 = p3.x, y3 = p3.y,
x4 = p4.x, y4 = p4.y;
return lli8(x1,y1,x2,y2,x3,y3,x4,y4)
lli = function(line1, line2):
return lli4(line1.p1, line1.p2, line2.p1, line2.p2)
```
</div>
### What about curve-line intersections?
Curve/line intersection is more work, but we've already seen the techniques we need to use in order to perform it: first we translate/rotate both the line and curve together, in such a way that the line coincides with the x-axis. This will position the curve in a way that makes it cross the line at points where its y-function is zero. By doing this, the problem of finding intersections between a curve and a line has now become the problem of performing root finding on our translated/rotated curve, as we already covered in the section on finding extremities.
<Graphic preset="simple" title="Quadratic curve/line intersections" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic curve/line intersections" setup={this.setupCubic} draw={this.draw}/>
Curve/curve intersection, however, is more complicated. Since we have no straight line to align to, we can't simply align one of the curves and be left with a simple procedure. Instead, we'll need to apply two techniques we've not covered yet: de Casteljau's algorithm, and curve splitting.

View File

@@ -1,6 +1,8 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "intersections";
var min = Math.min, max = Math.max;
@@ -84,81 +86,7 @@ var Intersections = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Let's look at some more things we will want to do with Bézier curves. Almost immediately after figuring out how to
get bounding boxes to work, people tend to run into the problem that even though the minimal bounding box (based on
rotation) is tight, it's not sufficient to perform true collision detection. It's a good first step to make sure
there <em>might</em> be a collision (if there is no bounding box overlap, there can't be one), but in order to do
real collision detection we need to know whether or not there's an intersection on the actual curve.</p>
<p>We'll do this in steps, because it's a bit of a journey to get to curve/curve intersection checking. First, let's
start simple, by implementing a line-line intersection checker. While we can solve this the traditional calculus way
(determine the functions for both lines, then compute the intersection by equating them and solving for two unknowns),
linear algebra actually offers a nicer solution.</p>
<h3>Line-line intersections</h3>
<p id="intersection_ll">if we have two line segments with two coordinates each, segments A-B and C-D, we can find the
intersection of the lines these segments are an intervals on by linear algebra, using the procedure outlined in
this <a href="http://www.topcoder.com/tc?module=Static&d1=tutorials&d2=geometry2#line_line_intersection">top coder</a> article.
Of course, we need to make sure that the intersection isn't just on the lines our line segments lie on, but also on our
line segments themselves, so after we find the intersection we need to verify it lies without the bounds of our original
line segments.</p>
<p>The following graphic implements this intersection detection, showing a red point for an intersection on the lines
our segments lie on (thus being a virtual intersection point), and a green point for an intersection that lies on
both segments (being a real intersection point).</p>
<Graphic preset="simple" title="Line/line intersections" setup={this.setupLines} draw={this.drawLineIntersection} />
<div className="howtocode">
<h3>Implementing line-line intersections</h3>
<p>Let's have a look at how to implement a line-line intersection checking function. The basics are covered in the
article mentioned above, but sometimes you need more function signatures, because you might not want to call your
function with eight distinct parameters. Maybe you're using point structs or the line. Let's get coding:</p>
<pre>lli8 = function(x1,y1,x2,y2,x3,y3,x4,y4):
var nx=(x1*y2-y1*x2)*(x3-x4)-(x1-x2)*(x3*y4-y3*x4),
ny=(x1*y2-y1*x2)*(y3-y4)-(y1-y2)*(x3*y4-y3*x4),
d=(x1-x2)*(y3-y4)-(y1-y2)*(x3-x4);
if d=0:
return false
return point(nx/d, ny/d)
lli4 = function(p1, p2, p3, p4):
var x1 = p1.x, y1 = p1.y,
x2 = p2.x, y2 = p2.y,
x3 = p3.x, y3 = p3.y,
x4 = p4.x, y4 = p4.y;
return lli8(x1,y1,x2,y2,x3,y3,x4,y4)
lli = function(line1, line2):
return lli4(line1.p1, line1.p2, line2.p1, line2.p2)</pre>
</div>
<h3>What about curve-line intersections?</h3>
<p>Curve/line intersection is more work, but we've already seen the techniques we need to use in order
to perform it: first we translate/rotate both the line and curve together, in such a way that the line
coincides with the x-axis. This will position the curve in a way that makes it cross the line at
points where its y-function is zero. By doing this, the problem of finding intersections between a
curve and a line has now become the problem of performing root finding on our translated/rotated curve,
as we already covered in the section on finding extremities.
</p>
<Graphic preset="simple" title="Quadratic curve/line intersections" setup={this.setupQuadratic} draw={this.draw}/>
<Graphic preset="simple" title="Cubic curve/line intersections" setup={this.setupCubic} draw={this.draw}/>
<p>Curve/curve intersection, however, is more complicated. Since we have no straight line to align to, we
can't simply align one of the curves and be left with a simple procedure. Instead, we'll need to apply two
techniques we've not covered yet: de Casteljau's algorithm, and curve splitting.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,37 @@
# Manipulating a curve
Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the original curve, passing through the user-selected point B, with correct new control points.
<Graphic preset="moulding" title="Moulding a quadratic Bézier curve" setup={this.setupQuadratic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markQB} onMouseDrag={this.dragQB} onMouseUp={this.saveCurve}/>
**Click-dragging the curve itself** shows what we're using to compute the new coordinates: while dragging you will see the original points B and its corresponding <i>t</i>-value, the original point C for that <i>t</i>-value, as well as the new point B' based on the mouse cursor. Since we know the <i>t</i>-value for this configuration, we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:
\[
A' = B' - \frac{C - B'}{ratio} = B' + \frac{B - C}{ratio}
\]
For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point. For cubic curves, we need to do a little more work:
<Graphic preset="moulding" title="Moulding a cubic Bézier curve" setup={this.setupCubic} draw={this.drawMould} onClick={this.placeMouldPoint} onMouseDown={this.markCB} onMouseDrag={this.dragCB} onMouseUp={this.saveCurve}/>
To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning point B. We compute A` in exactly the same way as before, but we also record the final strut line that forms B in the original curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points should be. Remember that B' lies on line e1--e2 at a distance <i>t</i>, because that's how Bézier curves work. In the same manner, we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing the new control points is fairly easy.
First, we construct the one-level-of-de-Casteljau-up points:
\[
\left \{ \begin{align}
v1 &= A' + \frac{e1 - A'}{t} \\
v2 &= A' + \frac{e2 - A'}{1 - t}
\end{align} \right .
\]
And then we can compute the new control points:
\[
\left \{ \begin{align}
C1' &= v1 + \frac{v1 - start}{t} \\
C2' &= v2 + \frac{v2 - end}{1 - t}
\end{align} \right .
\]
And that's cubic curve manipulation.

View File

@@ -1,13 +1,15 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "moulding";
var abs = Math.abs;
var Moulding = React.createClass({
getDefaultProps: function() {
return {
title: "Manipulating a curve"
title: locale.getTitle(page)
};
},
@@ -208,64 +210,7 @@ var Moulding = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Armed with knowledge of the "ABC" relation, we can now update a curve interactively, by letting people click anywhere
on the curve, find the <em>t</em>-value matching that coordinate, and then letting them drag that point around. With every
drag update we'll have a new point "B", which we can combine with the fixed point "C" to find our new point A. Once we have
those, we can reconstruct the de Casteljau skeleton and thus construct a new curve with the same start/end points as the
original curve, passing through the user-selected point B, with correct new control points.</p>
<Graphic preset="moulding" title="Moulding a quadratic Bézier curve"
setup={this.setupQuadratic} draw={this.drawMould}
onClick={this.placeMouldPoint} onMouseDown={this.markQB} onMouseDrag={this.dragQB} onMouseUp={this.saveCurve}/>
<p><strong>Click-dragging the curve itself</strong> shows what we're using to compute the new coordinates: while dragging you will
see the original points B and its corresponding <i>t</i>-value, the original point C for that <i>t</i>-value,
as well as the new point B' based on the mouse cursor. Since we know the <i>t</i>-value for this configuration,
we can compute the ABC ratio for this configuration, and we know that our new point A' should like at a distance:</p>
<p>\[
A' = B' - \frac{C - B'}{ratio} = B' + \frac{B - C}{ratio}
\]</p>
<p>For quadratic curves, this means we're done, since the new point A' is equivalent to the new quadratic control point.
For cubic curves, we need to do a little more work:</p>
<Graphic preset="moulding" title="Moulding a cubic Bézier curve"
setup={this.setupCubic} draw={this.drawMould}
onClick={this.placeMouldPoint} onMouseDown={this.markCB} onMouseDrag={this.dragCB} onMouseUp={this.saveCurve}/>
<p>To help understand what's going on, the cubic graphic shows the full de Casteljau construction "hull" when repositioning
point B. We compute A` in exactly the same way as before, but we also record the final strut line that forms B in the original
curve. Given A', B', and the endpoints e1 and e2 of the strut line relative to B', we can now compute where the new control points
should be. Remember that B' lies on line e1--e2 at a distance <i>t</i>, because that's how Bézier curves work. In the same manner,
we know the distance A--e1 is only line-interval [0,t] of the full segment, and A--e2 is only line-interval [t,1], so constructing
the new control points is fairly easy.</p>
<p>First, we construct the one-level-of-de-Casteljau-up points:</p>
<p>\[
\left \{ \begin{align}
v1 &= A' + \frac{e1 - A'}{t} \\
v2 &= A' + \frac{e2 - A'}{1 - t}
\end{align} \right .
\]</p>
<p>And then we can compute the new control points:</p>
<p>\[
\left \{ \begin{align}
C1' &= v1 + \frac{v1 - start}{t} \\
C2' &= v2 + \frac{v2 - end}{1 - t}
\end{align} \right .
\]</p>
<p>And that's cubic curve manipulation.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,13 @@
# Creating a curve from three points
Given the preceding section on curve manipulation, we can also generate quadratic and cubic curves from any three points. However, unlike circle-fitting, which requires just three points, Bézier curve fitting requires three points, as well as a *t* value, so we can figure out where point 'C' needs to be.
The following graphic lets you place three points, and will use the preceding sections on the ABC ratio and curve construction to form a quadratic curve through them. You can move the points you've placed around by click-dragging, or try a new curve by drawing new points with pure clicks. (There's some freedom here, so for illustrative purposes we clamped *t* to simply be 0.5, lets us bypass some maths, since a *t* value of 0.5 always puts C in the middle of the start--end line segment)
<Graphic preset="generate" title="Fitting a quadratic Bézier curve" setup={this.setup} draw={this.drawQuadratic} onClick={this.onClick} />
For cubic curves we also need some values to construct the "de Casteljau line through B" with, and that gives us quite a bit of choice. Since we've clamped *t* to 0.5, we'll set up a line through B parallel to the line start--end, with a length that is proportional to the length of the line B--C: the further away from the baseline B is, the wider its construction line will be, and so the more "bulby" the curve will look. This still gives us some freedom in terms of exactly how to scale the length of the construction line as we move B closer or further away from the baseline, so I simply picked some values that sort-of-kind-of look right in that if a circle through (start,B,end) forms a perfect hemisphere, the cubic curve constructed forms something close to a hemisphere, too, and if the points lie on a line, then the curve constructed has the control points very close to B, while still lying between B and the correct curve end point:
<Graphic preset="generate" title="Fitting a cubic Bézier curve" setup={this.setup} draw={this.drawCubic} onClick={this.onClick} />
In each graphic, the blue parts are the values that we "just have" simply by setting up our three points, combined with our decision on which *t* value to use (and construction line orientation and length for cubic curves). There are of course many ways to determine a combination of *t* and tangent values that lead to a more "æsthetic" curve, but this will be left as an exercise to the reader, since there are many, and æsthetics are often quite personal.

View File

@@ -1,13 +1,15 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "pointcurves";
var abs = Math.abs;
var PointCurves = React.createClass({
getDefaultProps: function() {
return {
title: "Creating a curve from three points"
title: locale.getTitle(page)
};
},
@@ -173,46 +175,7 @@ var PointCurves = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Given the preceding section on curve manipulation, we can also generate quadratic and cubic
curves from any three points. However, unlike circle-fitting, which requires just three points,
Bézier curve fitting requires three points, as well as a <i>t</i> value, so we can figure out
where point 'C' needs to be.</p>
<p>The following graphic lets you place three points, and will use the preceding sections on the
ABC ratio and curve construction to form a quadratic curve through them. You can move the points
you've placed around by click-dragging, or try a new curve by drawing new points with pure clicks.
(There's some freedom here, so for illustrative purposes we clamped <i>t</i> to simply be
0.5, lets us bypass some maths, since a <i>t</i> value of 0.5 always puts C in the middle of
the start--end line segment)</p>
<Graphic preset="generate" title="Fitting a quadratic Bézier curve" setup={this.setup} draw={this.drawQuadratic}
onClick={this.onClick} />
<p>For cubic curves we also need some values to construct the "de Casteljau line through B" with,
and that gives us quite a bit of choice. Since we've clamped <i>t</i> to 0.5, we'll set up a line
through B parallel to the line start--end, with a length that is proportional to the length of the
line B--C: the further away from the baseline B is, the wider its construction line will be, and so
the more "bulby" the curve will look. This still gives us some freedom in terms of exactly how to
scale the length of the construction line as we move B closer or further away from the baseline, so
I simply picked some values that sort-of-kind-of look right in that if a circle through (start,B,end)
forms a perfect hemisphere, the cubic curve constructed forms something close to a hemisphere, too,
and if the points lie on a line, then the curve constructed has the control points very close to
B, while still lying between B and the correct curve end point:</p>
<Graphic preset="generate" title="Fitting a cubic Bézier curve" setup={this.setup} draw={this.drawCubic}
onClick={this.onClick} />
<p>In each graphic, the blue parts are the values that we "just have" simply by setting up
our three points, combined with our decision on which <i>t</i> value to use (and construction line
orientation and length for cubic curves). There are of course many ways to determine a combination
of <i>t</i> and tangent values that lead to a more "æsthetic" curve, but this will be left as an
exercise to the reader, since there are many, and æsthetics are often quite personal.</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -0,0 +1,23 @@
# Tracing a curve at fixed distance intervals
Say you want to draw a curve with a dashed line, rather than a solid line, or you want to move something along the curve at fixed distance intervals over time, like a train along a track, and you want to use Bézier curves.
Now you have a problem.
The reason you have a problem is that Bézier curves are parametric functions with non-linear behaviour, whereas moving a train along a track is about as close to a practical example of linear behaviour as you can get. The problem we're faced with is that we can't just pick *t* values at some fixed interval and expect the Bézier functions to generate points that are spaced a fixed distance apart. In fact, let's look at the relation between "distance long a curve" and "*t* value", by plotting them against one another.
The following graphic shows a particularly illustrative curve, and it's length-to-t plot. For linear traversal, this line needs to be straight, running from (0,0) to (length,1). This is, it's safe to say, not what we'll see, we'll see something wobbly instead. To make matters even worse, the length-to-*t* function is also of a much higher order than our curve is: while the curve we're using for this exercise is a cubic curve, which can switch concave/convex form once at best, the plot shows that the distance function along the curve is able to switch forms three times (to see this, try creating an S curve with the start/end close together, but the control points far apart).
<Graphic preset="twopanel" title="The t-for-distance function" setup={this.setup} draw={this.plotOnly}/>
We see a function that might be invertible, but we won't be able to do so, symbolically. You may remember from the section on arc length that we cannot actually compute the true arc length function as an expression of *t*, which means we also can't compute the true inverted function that gives *t* as an expression of length. So how do we fix this?
One way is to do what the graphic does: simply run through the curve, determine its *t*-for-length values as a set of discrete values at some high resolution (the graphic uses 100 discrete points), and then use those as a basis for finding an appropriate *t* value, given a distance along the curve. This works quite well, actually, and is fairly fast.
We can use some colour to show the difference between distance-based and time based intervals: the following graph is similar to the previous one, except it segments the curve in terms of equal-distance intervals. This shows as regular colour intervals going down the graph, but the mapping to *t* values is not linear, so there will be (highly) irregular intervals along the horizontal axis. It also shows the curve in an alternating colouring based on the t-for-distance values we find our LUT:
<Graphic preset="threepanel" title="Fixed-interval coloring a curve" setup={this.setup} draw={this.drawColoured} onKeyDown={this.props.onKeyDown}/>
Use your up and down arrow keys to increase or decrease the number of equidistant segments used to colour the curve.
However, are there better ways? One such way is discussed in "[Moving Along a Curve with Specified Speed](http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf)" by David Eberly of Geometric Tools, LLC, but basically because we have no explicit length function (or rather, one we don't have to constantly compute for different intervals), you may simply be better off with a traditional lookup table (LUT).

View File

@@ -1,6 +1,9 @@
var React = require("react");
var Graphic = require("../../Graphic.jsx");
var SectionHeader = require("../../SectionHeader.jsx");
var Locale = require("../../../lib/locale");
var locale = new Locale();
var page = "tracing";
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
var Tracing = React.createClass({
@@ -21,7 +24,7 @@ var Tracing = React.createClass({
getDefaultProps: function() {
return {
title: "Tracing a curve at fixed distance intervals"
title: locale.getTitle(page)
};
},
@@ -136,65 +139,7 @@ var Tracing = React.createClass({
},
render: function() {
return (
<section>
<SectionHeader {...this.props} />
<p>Say you want to draw a curve with a dashed line, rather than a solid line,
or you want to move something along the curve at fixed distance intervals over
time, like a train along a track, and you want to use Bézier curves.</p>
<p>Now you have a problem.</p>
<p>The reason you have a problem is that Bézier curves are parametric functions
with non-linear behaviour, whereas moving a train along a track is about as
close to a practical example of linear behaviour as you can get. The problem
we're faced with is that we can't just pick <i>t</i> values at some fixed interval
and expect the Bézier functions to generate points that are spaced a fixed distance
apart. In fact, let's look at the relation between "distance long a curve" and
"<i>t</i> value", by plotting them against one another.</p>
<p>The following graphic shows a particularly illustrative curve, and it's length-to-t plot.
For linear traversal, this line needs to be straight, running from (0,0) to (length,1). This is,
it's safe to say, not what we'll see, we'll see something wobbly instead. To make matters even
worse, the length-to-<i>t</i> function is also of a much higher order than our curve is: while
the curve we're using for this exercise is a cubic curve, which can switch concave/convex form once
at best, the plot shows that the distance function along the curve is able to switch forms three
times (to see this, try creating an S curve with the start/end close together, but the control
points far apart).</p>
<Graphic preset="twopanel" title="The t-for-distance function" setup={this.setup} draw={this.plotOnly}/>
<p>We see a function that might be invertible, but we won't be able to do so, symbolically.
You may remember from the section on arc length that we cannot actually compute the true
arc length function as an expression of <i>t</i>, which means we also can't compute the true
inverted function that gives <i>t</i> as an expression of length. So how do we fix this?</p>
<p>One way is to do what the graphic does: simply run through the curve, determine its
<i>t</i>-for-length values as a set of discrete values at some high resolution (the graphic
uses 100 discrete points), and then use those as a basis for finding an appropriate <i>t</i> value,
given a distance along the curve. This works quite well, actually, and is fairly fast.</p>
<p>We can use some colour to show the difference between distance-based and time based intervals:
the following graph is similar to the previous one, except it segments the curve in terms of
equal-distance intervals. This shows as regular colour intervals going down the graph, but
the mapping to <i>t</i> values is not linear, so there will be (highly) irregular intervals
along the horizontal axis. It also shows the curve in an alternating colouring based on the
t-for-distance values we find our LUT:</p>
<Graphic preset="threepanel" title="Fixed-interval coloring a curve" setup={this.setup} draw={this.drawColoured} onKeyDown={this.props.onKeyDown}/>
<p>Use your up and down arrow keys to increase or decrease the number of equidistant segments
used to colour the curve.</p>
<p>However, are there better ways? One such way is discussed
in "<a href="http://www.geometrictools.com/Documentation/MovingAlongCurveSpecifiedSpeed.pdf">Moving
Along a Curve with Specified Speed</a>" by David Eberly of Geometric Tools, LLC, but basically because
we have no explicit length function (or rather, one we don't have to constantly compute for different
intervals), you may simply be better off with a traditional lookup table (LUT).</p>
</section>
);
return <section>{ locale.getContent(page, this) }</section>;
}
});

View File

@@ -18,6 +18,48 @@ Module.prototype.require = function() {
}
};
/**
* fix the stupid nonsense inability for markdown parsers to see link
* syntax with `)` in the links themselves.
*/
function fixMarkDownLinks(data, chunks, chunkMore) {
var p = 0,
next = chunkMore ? chunkMore[0] : false,
otherChunkers = chunkMore ? chunkMore.slice(1) : false;
var fixes = [];
data.replace(/\[[^\]]+\]\(/g, function(_match, pos, _fullstring) {
// this is the start of a link. Find the offset at which the next `)`
// is actually the link closer.
var offset = 0;
var start = pos + _match.length;
var complex = false;
for (let d=0, i=start; i<data.length; i++) {
if (data[i] === '(') { d++; complex = true; }
else if (data[i] === ')') { d--; }
if (d<0) { offset = i - start; break; }
}
var end = start + offset;
// we now know the *actual* link length. Safify it.
if (complex) { fixes.push({ start, end, data: data.substring(start,end) }); }
// and return the matched text because we don't want to replace right now.
return _match
});
// let's safify this data, if there was a complex pattern that needs fixin'
if (fixes.length>0) {
fixes.forEach(fix => {
let s = fix.start,
e = fix.end,
newdata = fix.data.replace(/\(/g, '%28').replace(/\)/g, '%29');
// I can't believe I still have to do this in 2017...
data = data.substring(0,s) + newdata + data.substring(e);
});
}
// alright, let "the rest" deal with this data now.
performChunking(data, chunks, next, otherChunkers);
}
/**
*
*/
@@ -163,7 +205,7 @@ function performChunking(data, chunks, chunker, moreChunkers) {
*/
function chunk(data) {
var chunks = [];
performChunking(data, chunks, chunkLatex, [chunkDivs, chunkDivEnds, chunkGraphicJSX]);
performChunking(data, chunks, chunkLatex, [chunkDivs, chunkDivEnds, chunkGraphicJSX, fixMarkDownLinks]);
return chunks;
}
@@ -266,7 +308,7 @@ function processLocale(locale) {
var bundle = `var React = require('react');\nvar Graphic = require("../../components/Graphic.jsx");\nvar SectionHeader = require("../../components/SectionHeader.jsx");\n\nmodule.exports = ${bcode};\n`;
var dir = `./locales/${locale}`;
fs.ensureDir(dir);
fs.ensureDirSync(dir);
fs.writeFileSync(`${dir}/content.js`, bundle);
}