1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-09-01 04:22:28 +02:00

explanation

This commit is contained in:
Pomax
2020-08-08 13:45:25 -07:00
parent b85a87386b
commit e784a5e33d
35 changed files with 1204 additions and 951 deletions

View File

@@ -1,7 +0,0 @@
setup() {
// ...
}
draw() {
clear(`white`);
}

View File

@@ -1,79 +0,0 @@
# 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?):
<graphics-element title="Projections in a quadratic Bézier curve" width="275" height="275" src="./quadratic.js"></graphics-element>
<graphics-element title="Projections in a cubic Bézier curve" width="275" height="275" src="./cubic.js"></graphics-element>
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 A, B, and C 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 the 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 class="note">
While that relation is fixed, the function *u(t)* differs depending on whether we're working
with quadratic or cubic curves:
\[
\begin{aligned}
& 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{aligned}
\]
So, if we know the start and end coordinates, and we know the *t* value, we know C:
<graphics-element title="Quadratic value of C for t" width="275" height="275" src="./qct.js"></graphics-element>
<graphics-element title="Cubic value of C for t" width="275" height="275" src="./cct.js"></graphics-element>
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 class="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 evaluation, 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, as well as curve fitting of "a bunch of coordinates". These are useful things, and we'll look at both in the next sections.

View File

@@ -1,7 +0,0 @@
setup() {
// ...
}
draw() {
clear(`white`);
}

View File

@@ -1,173 +0,0 @@
module.exports = {
// ============== first sketch set =====================
/**
* The entry point for the quadratic curve example
*/
setupQuadratic: function(api) {
var curve = api.getDefaultQuadratic();
curve.points[0].y -= 10;
api.setCurve(curve);
},
/**
* The entry point for the cubic curve example
*/
setupCubic: function(api) {
var curve = api.getDefaultCubic();
curve.points[2].y -= 20;
api.setCurve(curve);
api.lut = curve.getLUT(100);
},
/**
* When someone clicks a graphic, find the associated
* on-curve t value and redraw with that new knowledge.
*/
onClick: function(evt, api) {
api.t = api.curve.on({x: evt.offsetX, y: evt.offsetY},7);
if (api.t < 0.05 || api.t > 0.95) api.t = false;
api.redraw();
},
/**
* The master draw function for the "projection" sketches
*/
draw: function(api, curve) {
// draw the basic curve and curve control points
api.reset();
api.drawSkeleton(curve);
api.drawCurve(curve);
api.setColor("black");
if (!api.t) return;
// draw the user-clicked on-curve point
api.drawCircle(api.curve.get(api.t),3);
api.setColor("lightgrey");
var utils = api.utils;
// find the A/B/C values as described in the section text
var hull = api.drawHull(curve, api.t);
var A, B, C;
if(hull.length === 6) {
A = curve.points[1];
B = hull[5];
C = utils.lli4(A, B, curve.points[0], curve.points[2]);
api.setColor("lightgrey");
api.drawLine(curve.points[0], curve.points[2]);
} else if(hull.length === 10) {
A = hull[5];
B = hull[9];
C = utils.lli4(A, B, curve.points[0], curve.points[3]);
api.setColor("lightgrey");
api.drawLine(curve.points[0], curve.points[3]);
}
// show the lines between the A/B/C values
api.setColor("#00FF00");
api.drawLine(A,B);
api.setColor("red");
api.drawLine(B,C);
api.setColor("black");
api.drawCircle(C,3);
// with their associated labels
api.setFill("black");
api.text("A", {x:10 + A.x, y: A.y});
api.text("B (t = " + api.utils.round(api.t,2) + ")", {x:10 + B.x, y: B.y});
api.text("C", {x:10 + C.x, y: C.y});
// and show the distance ratio, which we see does not change irrespective of whether A/B/C change.
var d1 = utils.dist(A, B);
var d2 = utils.dist(B, C);
var ratio = d1/d2;
var h = api.getPanelHeight();
api.text("d1 (A-B): " + utils.round(d1,2) + ", d2 (B-C): "+ utils.round(d2,2) + ", ratio (d1/d2): " + utils.round(ratio,4), {x:10, y:h-7});
},
// ============== second sketch set =====================
/**
* on mouse move, fix the t value for drawing based on the
* cursor position over the sketch. All the way on the left
* is t=0, all the way on the right is t=1, with a linear
* interpolation for anything in between.
*/
setCT: function(evt,api) {
api.t = evt.offsetX / api.getPanelWidth();
},
/**
* Draw the quadratic C(t) values
*/
drawQCT: function(api) {
api.u = api.u || function(t) {
var top = (t-1) * (t-1),
bottom = 2*t*t - 2*t + 1;
return top/bottom;
};
this.drawCTgraph(api);
},
/**
* Draw the cubic C(t) values
*/
drawCCT: function(api) {
api.u = api.u || function(t) {
var top = (1-t) * (1-t) * (1-t),
bottom = t*t*t + top;
return top/bottom;
};
this.drawCTgraph(api);
},
/**
* Draw a C(t) curve
*/
drawCTgraph: function(api) {
api.reset();
var w = api.getPanelWidth();
var pad = 20;
var fwh = w - 2*pad;
// draw some axes
api.setColor("black");
api.drawAxes(pad, "t",0,1, "u",0,1);
// draw the C(t) function using an
// indirection function that takes a
// t value and spits out the C(t) value
// as a point coordinate.
api.setColor("blue");
var uPoint = function(t) {
var value = api.u(t),
res = { x: pad + t*fwh, y: pad + value*fwh };
return res;
};
api.drawFunction(uPoint);
// if the cursor is (or was ever) over this
// graphic, draw the "crosshair" that pinpoints
// where in the function the associated t/C(t)
// coordinate is.
if (api.t) {
var v = api.u(api.t),
v1 = api.utils.round(v,3),
v2 = api.utils.round(1-v,3),
up = uPoint(api.t);
api.drawLine({x:up.x,y:pad}, up);
api.drawLine({x:pad,y:up.y}, up);
api.drawCircle(up,3);
// with some handy text that shows the actual computed values
api.setFill("blue");
api.text(" t = " + api.utils.round(api.t,3), {x:up.x+10, y:up.y-7});
api.text("u(t) = " + api.utils.round(v,3), {x:up.x+10, y:up.y+7});
api.setFill("black");
api.text("C = "+v1+" * start + "+v2+" * end", {x:w/2 - pad, y:pad+fwh});
}
}
};

View File

@@ -1,7 +0,0 @@
setup() {
// ...
}
draw() {
clear(`white`);
}

View File

@@ -1,7 +0,0 @@
setup() {
// ...
}
draw() {
clear(`white`);
}

View File

@@ -0,0 +1,171 @@
# The mathematics of Bézier curves
Bézier curves are a form of "parametric" function. Mathematically speaking, parametric functions are cheats: a "function" is actually a well defined term representing a mapping from any number of inputs to a <strong>single</strong> output. Numbers go in, a single number comes out. Change the numbers that go in, and the number that comes out is still a single number.
Parametric functions cheat. They basically say "alright, well, we want multiple values coming out, so we'll just use more than one function". An illustration: Let's say we have a function that maps some value, let's call it <i>x</i>, to some other value, using some kind of number manipulation:
\[
f(x) = \cos(x)
\]
The notation <i>f(x)</i> is the standard way to show that it's a function (by convention called <i>f</i> if we're only listing one) and its output changes based on one variable (in this case, <i>x</i>). Change <i>x</i>, and the output for <i>f(x)</i> changes.
So far, so good. Now, let's look at parametric functions, and how they cheat. Let's take the following two functions:
\[
\begin{matrix}
f(a) = \cos(a) \\
f(b) = \sin(b)
\end{matrix}
\]
There's nothing really remarkable about them, they're just a sine and cosine function, but you'll notice the inputs have different names. If we change the value for <i>a</i>, we're not going to change the output value for <i>f(b)</i>, since <i>a</i> isn't used in that function. Parametric functions cheat by changing that. In a parametric function all the different functions share a variable, like this:
\[
\left \{ \begin{matrix}
f_a(t) = \cos(t) \\
f_b(t) = \sin(t)
\end{matrix} \right.
\]
Multiple functions, but only one variable. If we change the value for <i>t</i>, we change the outcome of both <i>f<sub>a</sub>(t)</i> and <i>f<sub>b</sub>(t)</i>. You might wonder how that's useful, and the answer is actually pretty simple: if we change the labels <i>f<sub>a</sub>(t)</i> and <i>f<sub>b</sub>(t)</i> with what we usually mean with them for parametric curves, things might be a lot more obvious:
\[
\left \{ \begin{matrix}
x = \cos(t) \\
y = \sin(t)
\end{matrix} \right.
\]
There we go. <i>x</i>/<i>y</i> coordinates, linked through some mystery value <i>t</i>.
So, parametric curves don't define a <i>y</i> coordinate in terms of an <i>x</i> coordinate, like normal functions do, but they instead link the values to a "control" variable. If we vary the value of <i>t</i>, then with every change we get <strong>two</strong> values, which we can use as (<i>x</i>,<i>y</i>) coordinates in a graph. The above set of functions, for instance, generates points on a circle: We can range <i>t</i> from negative to positive infinity, and the resulting (<i>x</i>,<i>y</i>) coordinates will always lie on a circle with radius 1 around the origin (0,0). If we plot it for <i>t</i> from 0 to 5, we get this (use your up and down arrow keys to change the plot end value):
<Graphic title="A (partial) circle: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
Bézier curves are just one out of the many classes of parametric functions, and are characterised by using the same base function for all of the output values. In the example we saw above, the <i>x</i> and <i>y</i> values were generated by different functions (one uses a sine, the other a cosine); but Bézier curves use the "binomial polynomial" for both the <i>x</i> and <i>y</i> outputs. So what are binomial polynomials?
You may remember polynomials from high school. They're those sums that look like this:
\[
f(x) = a \cdot x^3 + b \cdot x^2 + c \cdot x + d
\]
If the highest order term they have is <i></i>, they're called "cubic" polynomials; if it's <i></i>, it's a "square" polynomial; if it's just <i>x</i>, it's a line (and if there aren't even any terms with <i>x</i> it's not a polynomial!)
Bézier curves are polynomials of <i>t</i>, rather than <i>x</i>, with the value for <i>t</i> being fixed between 0 and 1, with coefficients <i>a</i>, <i>b</i> etc. taking the "binomial" form, which sounds fancy but is actually a pretty simple description for mixing values:
\[
\begin{aligned}
linear &= (1-t) + t \\
square &= (1-t)^2 + 2 \cdot (1-t) \cdot t + t^2 \\
cubic &= (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\end{aligned}
\]
I know what you're thinking: that doesn't look too simple! But if we remove <i>t</i> and add in "times one", things suddenly look pretty easy. Check out these binomial terms:
\[
\begin{aligned}
linear &= \hspace{2.5em} 1 + 1 \\
square &= \hspace{1.7em} 1 + 2 + 1\\
cubic &= \hspace{0.85em} 1 + 3 + 3 + 1\\
quartic &= 1 + 4 + 6 + 4 + 1
\end{aligned}
\]
Notice that 2 is the same as 1+1, and 3 is 2+1 and 1+2, and 6 is 3+3... As you can see, each time we go up a dimension, we simply start and end with 1, and everything in between is just "the two numbers above it, added together", giving us a simple number sequence known as [Pascal's triangle](https://en.wikipedia.org/wiki/Pascal%27s_triangle). Now <i>that's</i> easy to remember.
There's an equally simple way to figure out how the polynomial terms work: if we rename <i>(1-t)</i> to <i>a</i> and <i>t</i> to <i>b</i>, and remove the weights for a moment, we get this:
\[
\begin{aligned}
linear &= BLUE[a] + RED[b] \\
square &= BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot RED[b] + RED[b] \cdot RED[b] \\
cubic &= BLUE[a] \cdot BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot BLUE[a] \cdot RED[b] + BLUE[a] \cdot RED[b] \cdot RED[b] + RED[b] \cdot RED[b] \cdot RED[b]\\
\end{aligned}
\]
It's basically just a sum of "every combination of <i>a</i> and <i>b</i>", progressively replacing <i>a</i>'s with <i>b</i>'s after every + sign. So that's actually pretty simple too. So now you know binomial polynomials, and just for completeness I'm going to show you the generic function for this:
\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{binomial\ term}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\]
And that's the full description for Bézier curves. Σ in this function indicates that this is a series of additions (using the variable listed below the Σ, starting at ...=&lt;value&gt; and ending at the value listed on top of the Σ).
<div className="howtocode">
### How to implement the basis function
We could naively implement the basis function as a mathematical construct, using the function as our guide, like this:
```
function Bezier(n,t):
sum = 0
for(k=0; k<n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum
```
I say we could, because we're not going to: the factorial function is *incredibly* expensive. And, as we can see from the above explanation, we can actually create Pascal's triangle quite easily without it: just start at [1], then [1,1], then [1,2,1], then [1,3,3,1], and so on, with each next row fitting 1 more number than the previous row, starting and ending with "1", with all the numbers in between being the sum of the previous row's elements on either side "above" the one we're computing.
We can generate this as a list of lists lightning fast, and then never have to compute the binomial terms because we have a lookup table:
```
lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
function binomial(n,k):
while(n >= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i<s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]
```
So what's going on here? First, we declare a lookup table with a size that's reasonably large enough to accommodate most lookups. Then, we declare a function to get us the values we need, and we make sure that if an <i>n/k</i> pair is requested that isn't in the LUT yet, we expand it first. Our basis function now looks like this:
```
function Bezier(n,t):
sum = 0
for(k=0; k<=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
Perfect. Of course, we can optimize further. For most computer graphics purposes, we don't need arbitrary curves (although we will also provide code for arbitrary curves in this primer); we need quadratic and cubic curves, and that means we can drastically simplify the code:
```
function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3
```
And now we know how to program the basis function. Excellent.
</div>
So, now we know what the basis function looks like, time to add in the magic that makes Bézier curves so special: control points.

View File

@@ -0,0 +1,169 @@
# ベジエ曲線の数学
ベジエ曲線は「パラメトリック」関数の一種です。数学的に言えば、パラメトリック関数というのはインチキです。というのも、「関数」はきっちり定義された用語であり、いくつかの入力を<strong>1つ</strong>の出力に対応させる写像を表すものだからです。いくつかの数値を入れると、1つの数値が出てきます。入れる数値が変わっても、出てくる数値はやはり1つだけです。パラメトリック関数はインチキです。基本的には「じゃあわかった、値を複数個出したいから、関数を複数個使うことにするよ」ということです。例として、ある値<i>x</i>に何らかの操作を行い、別の値へと写す関数があるとします。
\[
f(x) = \cos(x)
\]
<i>f(x)</i>という記法は、これが関数1つしかない場合は慣習的に<i>f</i>と呼びますであり、その出力が1つの変数この場合は<i>x</i>です)に応じて変化する、ということを示す標準的な方法です。<i>x</i>を変化させると、<i>f(x)</i>の出力が変化します。
ここまでは順調です。では、パラメトリック関数について、これがどうインチキなのかを見てみましょう。以下の2つの関数を考えます。
\[
\begin{matrix}
f(a) = \cos(a) \\
f(b) = \sin(b)
\end{matrix}
\]
注目すべき箇所は特に何もありません。ただの正弦関数と余弦関数です。ただし、入力が別々の名前になっていることに気づくでしょう。仮に<i>a</i>の値を変えたとしても、<i>f(b)</i>の出力の値は変わらないはずです。なぜなら、こちらの関数には<i>a</i>は使われていないからです。パラメトリック関数は、これを変えてしまうのでインチキなのです。パラメトリック関数においては、どの関数も変数を共有しています。例えば、
\[
\left \{ \begin{matrix}
f_a(t) = \cos(t) \\
f_b(t) = \sin(t)
\end{matrix} \right.
\]
複数の関数がありますが、変数は1つだけです。<i>t</i>の値を変えた場合、<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>の両方の出力が変わります。これがどのように役に立つのか、疑問に思うかもしれません。しかし、実際には答えは至ってシンプルです。<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>のラベルを、パラメトリック曲線の表示によく使われているもので置き換えてやれば、ぐっとはっきりするかと思います。
\[
\left \{ \begin{matrix}
x = \cos(t) \\
y = \sin(t)
\end{matrix} \right.
\]
きました。<i>x</i>/<i>y</i>座標です。謎の値<i>t</i>を通して繫がっています。
というわけで、普通の関数では<i>y</i>座標を<i>x</i>座標によって定義しますが、パラメトリック曲線ではそうではなく、座標の値を「制御」変数と結びつけます。<i>t</i>の値を変化させるたびに<strong>2つ</strong>の値が変化するので、これをグラフ上の座標 (<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります上下キーでプロットの上限を変更できます
<Graphic title="(部分)円 x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<i>x</i>の値と<i>y</i>の値とで異なる関数(正弦関数と余弦関数)を使っていましたが、ベジエ曲線では<i>x</i><i>y</i>の両方で「二項係数多項式」を使います。では、二項係数多項式とは何でしょう?
高校で習った、こんな形の多項式を思い出すかもしれません。
\[
f(x) = a \cdot x^3 + b \cdot x^2 + c \cdot x + d
\]
最高次の項が<i></i>であれば3次多項式、<i></i>であれば2次多項式と呼び、<i>x</i>だけの場合は1次多項式――ただの直線です。そして<i>x</i>の入った項が何もなければ、多項式ではありません!)
ベジエ曲線は<i>x</i>の多項式ではなく、<i>t</i>の多項式です。<i>t</i>の値は0から1までの間に制限され、その係数<i>a</i><i>b</i>などは「二項係数」の形をとります。というと複雑そうに聞こえますが、実際には値を組み合わせて、とてもシンプルに記述できます。
\[
\begin{aligned}
1次 &= (1-t) + t \\
2次 &= (1-t)^2 + 2 \cdot (1-t) \cdot t + t^2 \\
3次 &= (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\end{aligned}
\]
「そこまでシンプルには見えないよ」と思っていることでしょう。しかし仮に、<i>t</i>を取り去って係数に1を掛けることにしてしまえば、急激に簡単になります。これが二項係数部分の項です。
\[
\begin{aligned}
1次 &= \hspace{2.5em} 1 + 1 \\
2次 &= \hspace{1.7em} 1 + 2 + 1\\
3次 &= \hspace{0.85em} 1 + 3 + 3 + 1\\
4次 &= 1 + 4 + 6 + 4 + 1
\end{aligned}
\]
2は1+1に等しく、3は2+1や1+2に等しく、6は3+3に等しく、……ということに注目してください。見てわかるように、先頭と末尾は単に1になっていますが、中間はどれも次数が増えるたびに「上の2つの数を足し合わせた」ものになっています。<i>これなら</i>覚えやいですね。
多項式部分の項がどうなっているのか、同じぐらい簡単な方法で考えることができます。仮に、<i>(1-t)</i><i>a</i>に、<i>t</i><i>b</i>に書き換え、さらに重みを一旦削除してしまえば、このようになります。
\[
\begin{aligned}
1次 &= BLUE[a] + RED[b] \\
2次 &= BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot RED[b] + RED[b] \cdot RED[b] \\
3次 &= BLUE[a] \cdot BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot BLUE[a] \cdot RED[b] + BLUE[a] \cdot RED[b] \cdot RED[b] + RED[b] \cdot RED[b] \cdot RED[b]\\
\end{aligned}
\]
これは要するに、「<i>a</i><i>b</i>のすべての組み合わせ」の単なる和です。プラスが出てくるたびに、<i>a</i><i>b</i>へと1つずつ置き換えていけばよいのです。こちらも本当に単純です。さて、これで「二項係数多項式」がわかりました。完璧を期するため、この関数の一般の形を示しておきます。
\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{二項係数部分の項}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{多項式部分の項}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\]
そして、これがベジエ曲線の完全な表現です。この関数中のΣは、加算の繰り返し(Σの下にある変数を使って、...=<>から始めてΣの下にある値まで)を表します。
<div className="howtocode">
### 基底関数の実装方法
上で説明した関数を使えば、数学的な組み立て方で、基底関数をナイーブに実装することもできます。
```
function Bezier(n,t):
sum = 0
for(k=0; k<n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum
```
「こともできる」と書いたのは、この方法では実装しない方が良いからです。階乗は*とてつもなく*重い計算なのです。また、先ほどの説明からわかるように、実際は階乗を使わなくても、かなり簡単にパスカルの三角形を作ることができます。[1]から始めて[1,1]、[1,2,1]、[1,3,3,1]、……としていくだけです。下の段は上の段よりも1つ要素が増え、各段の先頭と末尾は1になります。中間の数はどれも、左右斜め上にある両要素の和になります。
このパスカルの三角形は、「リストのリスト」として瞬時に生成できます。そして、これをルックアップテーブルとして利用すれば、二項係数を計算する必要はまったくなくなります。
```
lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
binomial(n,k):
while(n >= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i<s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]
```
これはどのように動くのでしょう最初に、十分に大きなサイズのルックアップテーブルを宣言します。次に、求めたい値を得るための関数を定義します。この関数は、求めたい値のn/kのペアがテーブル中にまだ存在しない場合、先にテーブルを拡張するようになっています。さて、これで基底関数は次のようになりました。
```
function Bezier(n,t):
sum = 0
for(k=0; k<=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
完璧です。もちろん、さらなる最適化を施すこともできます。コンピュータグラフィクス用途ではたいてい、任意の次数の曲線が必要になるわけではありません。2次と3次の曲線だけが必要であれば、以下のようにコードを劇的に単純化することができます実際、この入門では任意の次数までは扱いませんので、これに似たようなコードが出てきます
```
function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3
```
これで基底関数をプログラムする方法がわかりました。すばらしい。
</div>
というわけで、基底関数がどのようなものか理解できました。今度はベジエ曲線を特別にする魔法――制御点を導入する時間です。

View File

@@ -0,0 +1,169 @@
# 贝塞尔曲线的数学原理
贝塞尔曲线是“参数”方程的一种形式。从数学上讲,参数方程作弊了:“方程”实际上是一个从输入到<strong>唯一</strong>输出的、良好定义的映射关系。几个输入进来,一个输出返回。改变输入变量,还是只有一个输出值。参数方程在这里作弊了。它们基本上干了这么件事,“好吧,我们想要更多的输出值,所以我们用了多个方程”。举个例子:假如我们有一个方程,通过一些计算,将假设为<i>x</i>的一些值映射到另外的值:
\[
f(x) = \cos(x)
\]
记号<i>f(x)</i>是表示函数的标准方式(为了方便起见,如果只有一个的话,我们称函数为<i>f</i>),函数的输出根据一个变量(本例中是<i>x</i>)变化。改变<i>x</i><i>f(x)</i>的输出值也会变。
到目前没什么问题。现在,让我们来看一下参数方程,以及它们是怎么作弊的。我们取以下两个方程:
\[
\begin{matrix}
f(a) = \cos(a) \\
f(b) = \sin(b)
\end{matrix}
\]
这俩方程没什么让人印象深刻的,只不过是正弦函数和余弦函数,但正如你所见,输入变量有两个不同的名字。如果我们改变了<i>a</i>的值,<i>f(b)</i>的输出不会有变化,因为这个方程没有用到<i>a</i>。参数方程通过改变这点来作弊。在参数方程中,所有不同的方程共用一个变量,如下所示:
\[
\left \{ \begin{matrix}
f_a(t) = \cos(t) \\
f_b(t) = \sin(t)
\end{matrix} \right.
\]
多个方程,但只有一个变量。如果我们改变了<i>t</i>的值,<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>的输出都会发生变化。你可能会好奇这有什么用,答案其实很简单:对于参数曲线,如果我们用常用的标记来替代<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>,看起来就有些明朗了:
\[
\left \{ \begin{matrix}
x = \cos(t) \\
y = \sin(t)
\end{matrix} \right.
\]
好了,通过一些神秘的<i>t</i>值将<i>x</i>/<i>y</i>坐标系联系起来。
所以,参数曲线不像一般函数那样,通过<i>x</i>坐标来定义<i>y</i>坐标,而是用一个“控制”变量将它们连接起来。如果改变<i>t</i>的值,每次变化时我们都能得到<strong>两个</strong>值,这可以作为图形中的(<i>x</i>,<i>y</i>)坐标。比如上面的方程组,生成位于一个圆上的点:我们可以使<i>t</i>在正负极值间变化,得到的输出(<i>x</i>,<i>y</i>)都会位于一个以原点(0,0)为中心且半径为1的圆上。如果我们画出<i>t</i>从0到5时的值将得到如下图像你可以用上下键来改变画的点和值
<Graphic title="(一部分的)圆: x=sin(t), y=cos(t)" static={true} setup={this.setup} draw={this.draw} onKeyDown={this.props.onKeyDown}/>
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<i>x</i>值和<i>y</i>值使用了不同的方程,与此不同的是,贝塞尔曲线的<i>x</i><i>y</i>都用了“二项多项式”。那什么是二项多项式呢?
你可能记得高中所学的多项式,看起来像这样:
\[
f(x) = a \cdot x^3 + b \cdot x^2 + c \cdot x + d
\]
如果它的最高次项是<i></i>就称为“三次”多项式,如果最高次项是<i></i>,称为“二次”多项式,如果只含有<i>x</i>的项,它就是一条线(不过不含任何<i>x</i>的项它就不是一个多项式!)
贝塞尔曲线不是x的多项式它是<i>t</i>的多项式,<i>t</i>的值被限制在0和1之间并且含有<i>a</i><i>b</i>等参数。它采用了二次项的形式,听起来很神奇但实际上就是混合不同值的简单描述:
\[
\begin{aligned}
linear &= (1-t) + t \\
square &= (1-t)^2 + 2 \cdot (1-t) \cdot t + t^2 \\
cubic &= (1-t)^3 + 3 \cdot (1-t)^2 \cdot t + 3 \cdot (1-t) \cdot t^2 + t^3
\end{aligned}
\]
我明白你在想什么:这看起来并不简单,但如果我们拿掉<i>t</i>并让系数乘以1事情就会立马简单很多看看这些二次项
\[
\begin{aligned}
linear &= \hspace{2.5em} 1 + 1 \\
square &= \hspace{1.7em} 1 + 2 + 1\\
cubic &= \hspace{0.85em} 1 + 3 + 3 + 1\\
quartic &= 1 + 4 + 6 + 4 + 1
\end{aligned}
\]
需要注意的是2与1+1相同3相当于2+1或1+26相当于3+3...如你所见每次我们增加一个维度只要简单地将头尾置为1中间的操作都是“将上面的两个数字相加”。现在就能很容易地记住了。
还有一个简单的办法可以弄清参数项怎么工作的:如果我们将<i>(1-t)</i>重命名为<i>a</i>,将<i>t</i>重命名为<i>b</i>,暂时把权重删掉,可以得到这个:
\[
\begin{aligned}
linear &= BLUE[a] + RED[b] \\
square &= BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot RED[b] + RED[b] \cdot RED[b] \\
cubic &= BLUE[a] \cdot BLUE[a] \cdot BLUE[a] + BLUE[a] \cdot BLUE[a] \cdot RED[b] + BLUE[a] \cdot RED[b] \cdot RED[b] + RED[b] \cdot RED[b] \cdot RED[b]\\
\end{aligned}
\]
基本上它就是“每个<i>a</i><i>b</i>结合项”的和,在每个加号后面逐步的将<i>a</i>换成<i>b</i>。因此这也很简单。现在你已经知道了二次多项式,为了叙述的完整性,我将给出一般方程:
\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{binomial\ term}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{polynomial\ term}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\]
这就是贝塞尔曲线完整的描述。在这个函数中的Σ表示了这是一系列的加法(用Σ下面的变量,从...=<>开始,直到Σ上面的数字结束)。
<div className="howtocode">
### 如何实现基本方程
我们可以用之前说过的方程,来简单地实现基本方程作为数学构造,如下:
```
function Bezier(n,t):
sum = 0
for(k=0; k<n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum
```
我说我们“可以用”是因为我们不会这么去做:因为阶乘函数开销*非常大*。并且,正如我们在上面所看到的,我们不用阶乘也能够很容易地构造出帕斯卡三角形:一开始是[1],接着是[1,2,1],然后是[1,3,3,1]等等。下一行都比上一行多一个数首尾都为1中间的数字是上一行两边元素的和。
我们可以很快的生成这个列表,并在之后使用这个查找表而不用再计算二次多项式的系数:
```
lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
binomial(n,k):
while(n >= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i<s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]
```
这里做了些什么首先我们声明了一个足够大的查找表。然后我们声明了一个函数来获取我们想要的值并且确保当一个请求的n/k对不在LUT查找表中时先将表扩大。我们的基本函数如下所示
```
function Bezier(n,t):
sum = 0
for(k=0; k<=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
完美。当然我们可以进一步优化。为了大部分的计算机图形学目的,我们不需要任意的曲线。我们需要二次曲线和三次曲线(实际上这篇文章没有涉及任意次的曲线,因此你会在其他地方看到与这些类似的代码),这说明我们可以彻底简化代码:
```
function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3
```
现在我们知道如何代用码实现基本方程了。很好。
</div>
既然我们已经知道基本函数的样子,是时候添加一些魔法来使贝塞尔曲线变得特殊了:控制点。

View File

@@ -0,0 +1,52 @@
module.exports = {
statics: {
keyHandlingOptions: {
propName: "step",
values: {
"38": 0.1, // up arrow
"40": -0.1 // down arrow
},
controller: function(api) {
if (api.step < 0.1) {
api.step = 0.1;
}
}
}
},
setup: function(api) {
api.step = 5;
},
draw: function(api, curve) {
var dim = api.getPanelWidth(),
w = dim,
h = dim,
w2 = w/2,
h2 = h/2,
w4 = w2/2,
h4 = h2/2;
api.reset();
api.setColor("black");
api.drawLine({x:0,y:h2},{x:w,y:h2});
api.drawLine({x:w2,y:0},{x:w2,y:h});
var offset = {x:w2, y:h2};
for(var t=0, p; t<=api.step; t+=0.1) {
p = {
x: w4 * Math.cos(t),
y: h4 * Math.sin(t)
};
api.drawPoint(p, offset);
var modulo = t % 1;
if(modulo<0.05 || modulo> 0.95) {
api.text("t = " + Math.round(t), {
x: offset.x + 1.25 * w4 * Math.cos(t) - 10,
y: offset.y + 1.25 * h4 * Math.sin(t) + 5
});
api.drawCircle(p, 2, offset);
}
}
}
};

View File

@@ -0,0 +1,4 @@
var handler = require("./handler.js");
var generateBase = require("../../generate-base");
var keyHandling = require("../../decorators/keyhandling-decorator.jsx");
module.exports = keyHandling(generateBase("explanation", handler));

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.0 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.0 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 7.1 KiB

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="96" height="13pt" viewBox="0 0 72 13"><defs><symbol overflow="visible" id="a"><path d="M4.938-8.875c-.204-.094-.547-.156-.844-.156-.125 0-.422.078-.578.156-.125.063-1.204.828-1.485 1.125-.265.25-.437.703-.578 1.39l-.156.876c-.36.171-.61.265-1.016.421l-.125.485h.985l-.094.531C.687-1.703.25.516-.016 1.343c-.218.704-.421.938-.859.938-.266 0-.313-.047-.578-.375l-.328.11c-.078.375-.203.859-.328 1.109.171.11.453.172.593.172.485 0 1.235-.406 1.688-.969.734-.86 1.016-1.781 1.906-5.984.063-.297.156-.656.203-.922h1.25c.063-.36.156-.64.219-.813l-.11-.234-.156.063c-.28.078-.406.078-1 .078h-.03l.218-1.204c.203-1.03.406-1.359.953-1.359.36 0 .578.14.797.5l.281-.11.313-.905.078-.235zm0 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="M5.875-5.875c-.14-.078-.344-.156-.453-.156-.469 0-1.063.468-1.797 1.578l-.453.656.25.094-.078-.578c-.14-1.188-.594-1.75-1.297-1.75-.313 0-.688.14-.797.297L.484-4.61l.407.25c.437-.485.578-.61.812-.61.39 0 .516.36.719 1.532l.125.718-.438.656C1.594-1.25 1.297-.905.953-.905c-.172 0-.172.078-.203.015l-.188-.468-.453.062c0 .36-.03.516-.093.89-.032.141-.047.173-.063.329.219.125.531.203.672.203.39 0 .953-.375 1.328-.938l.906-1.406-.234-.094.125.829c.172 1.015.578 1.609 1 1.609.266 0 .75-.234 1.125-.578l.64-.578-.218-.469c-.578.453-.766.61-.953.61-.188 0-.25-.094-.453-.5.015.046-.11-.407-.172-.75L3.5-3.313l.39-.532c.563-.765.782-1.015 1.157-1.015.187 0 .187-.032.312.312l.36-.11.203-1.171zm0 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="M.172-2.64A2.858 2.858 0 003.047.233c.625 0 1.61-.265 1.734-.437l.313-.484-.235-.344c-.593.312-.859.39-1.296.39C2.234-.64 1.5-1.547 1.5-3.125c0-1.281.453-1.86 1.531-1.86.531 0 1 .204 1.172.454l.094.812h.61c0-.687.046-1.172.171-1.656-.531-.328-1.031-.5-1.531-.5-.578 0-1.344.266-2.063.719C.641-4.625.172-3.688.172-2.641zm0 0"/></symbol><symbol overflow="visible" id="g"><path d="M.234-2.719c0 1.672 1.22 2.953 2.75 2.953 1.844 0 3.297-1.406 3.297-3.203 0-1.61-1.312-2.906-2.953-2.906C1.594-5.875.234-4.5.234-2.719zm1.391-.406c0-1.438.438-2.094 1.5-2.094 1.094 0 1.766 1.078 1.766 2.703 0 1.375-.47 2.094-1.438 2.094-1.172 0-1.828-1.016-1.828-2.703zm0 0"/></symbol><symbol overflow="visible" id="h"><path d="M.36-.047C.983.156 1.452.234 2 .234c1.563 0 2.813-.968 2.813-2.14 0-.36-.141-.735-.375-.969-.313-.313-.797-.484-1.86-.688-.969-.187-1.172-.296-1.172-.843 0-.61.297-.813 1.016-.813.812 0 1.265.297 1.265.86v.406h.61c.016-.828.031-1.11.078-1.563-.781-.265-1.203-.359-1.703-.359C1.297-5.875.297-5.078.297-4c0 .563.406 1.11.953 1.36.328.14.969.328 1.781.515.531.11.61.234.61.688 0 .656-.454.968-1.297.968-.875 0-1.36-.281-1.36-.875v-.625h-.64c0 .953-.031 1.313-.14 1.875zm0 0"/></symbol></defs><use xlink:href="#a" x="2.788" y="9.082"/><use xlink:href="#b" x="8.06" y="9.082"/><use xlink:href="#c" x="13.129" y="9.082"/><use xlink:href="#d" x="19.25" y="9.082"/><use xlink:href="#e" x="27.631" y="9.082"/><use xlink:href="#f" x="40.048" y="9.082"/><use xlink:href="#g" x="45.356" y="9.082"/><use xlink:href="#h" x="51.884" y="9.082"/><g><use xlink:href="#b" x="56.953" y="9.082"/><use xlink:href="#c" x="62.022" y="9.082"/></g><g><use xlink:href="#d" x="68.131" y="9.082"/></g></svg>

After

Width:  |  Height:  |  Size: 4.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 14 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 8.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 18 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 6.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 16 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 8.8 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 20 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 12 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 9.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 7.1 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 5.7 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 24 KiB

View File

@@ -86,7 +86,7 @@
<ol>
<li><a href="#introduction">A lightning introduction</a></li>
<li><a href="#whatis">So what makes a Bézier Curve?</a></li>
<li><a href="#abc">The projection identity</a></li>
<li><a href="#explanation">The mathematics of Bézier curves</a></li>
</ol>
</nav>
</header>
@@ -546,239 +546,281 @@
and the various things we can do to, and with, Bézier curves.
</p>
</section>
<section id="abc">
<h1>The projection identity</h1>
<section id="explanation">
<h1>The mathematics of Bézier curves</h1>
<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.
Bézier curves are a form of "parametric" function. Mathematically
speaking, parametric functions are cheats: a "function" is actually
a well defined term representing a mapping from any number of inputs
to a <strong>single</strong> output. Numbers go in, a single number
comes out. Change the numbers that go in, and the number that comes
out is still a single number.
</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 <em>t</em> 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>
<graphics-element
title="Projections in a quadratic Bézier curve"
width="275"
height="275"
src="./chapters/abc/quadratic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/quadratic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Projections in a cubic Bézier curve"
width="275"
height="275"
src="./chapters/abc/cubic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cubic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<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 A, B, and C hide an important identity formula
for quadratic and cubic Bézier curves: for any point on the curve
with some <em>t</em> value, the ratio distance of C along the
baseline is fixed: if some <em>t</em> 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
<em>t</em> 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:
Parametric functions cheat. They basically say "alright, well, we
want multiple values coming out, so we'll just use more than one
function". An illustration: Let's say we have a function that maps
some value, let's call it <i>x</i>, to some other value, using some
kind of number manipulation:
</p>
<img
class="LaTeX SVG"
src="images/latex/34fe255294faf45ab02128f7997b92ce.svg"
width="197px"
height="16px"
src="images/latex/1caef9931f954e32eae5067b732c1018.svg"
width="96px"
height="17px"
loading="lazy"
/>
<p>So that just leaves finding A.</p>
<div class="note">
<p>
While that relation is fixed, the function <em>u(t)</em> differs
depending on whether we're working with quadratic or cubic curves:
</p>
<img
class="LaTeX SVG"
src="images/latex/62f2f984e43a22a6b4bda4d399dedfc6.svg"
width="197px"
height="87px"
loading="lazy"
/>
<p>
So, if we know the start and end coordinates, and we know the
<em>t</em> value, we know C:
</p>
<graphics-element
title="Quadratic value of C for t"
width="275"
height="275"
src="./chapters/abc/qct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/qct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Cubic value of C for t"
width="275"
height="275"
src="./chapters/abc/cct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<p>
The notation <i>f(x)</i> is the standard way to show that it's a
function (by convention called <i>f</i> if we're only listing one)
and its output changes based on one variable (in this case,
<i>x</i>). Change <i>x</i>, and the output for <i>f(x)</i> changes.
</p>
<p>
So far, so good. Now, let's look at parametric functions, and how
they cheat. Let's take the following two functions:
</p>
<img
class="LaTeX SVG"
src="images/latex/0f5cffd58e864fec6739a57664eb8cbd.svg"
width="93px"
height="36px"
loading="lazy"
/>
<p>
There's nothing really remarkable about them, they're just a sine
and cosine function, but you'll notice the inputs have different
names. If we change the value for <i>a</i>, we're not going to
change the output value for <i>f(b)</i>, since <i>a</i> isn't used
in that function. Parametric functions cheat by changing that. In a
parametric function all the different functions share a variable,
like this:
</p>
<img
class="LaTeX SVG"
src="images/latex/066a910ae6aba69c40a338320759cdd1.svg"
width="100px"
height="40px"
loading="lazy"
/>
<p>
Multiple functions, but only one variable. If we change the value
for <i>t</i>, we change the outcome of both
<i>f<sub>a</sub>(t)</i> and <i>f<sub>b</sub>(t)</i>. You might
wonder how that's useful, and the answer is actually pretty simple:
if we change the labels <i>f<sub>a</sub>(t)</i> and
<i>f<sub>b</sub>(t)</i> with what we usually mean with them for
parametric curves, things might be a lot more obvious:
</p>
<img
class="LaTeX SVG"
src="images/latex/4cf6fb369841e2c5d36e5567a8db4306.svg"
width="77px"
height="40px"
loading="lazy"
/>
<p>
There we go. <i>x</i>/<i>y</i> coordinates, linked through some
mystery value <i>t</i>.
</p>
<p>
So, parametric curves don't define a <i>y</i> coordinate in terms of
an <i>x</i> coordinate, like normal functions do, but they instead
link the values to a "control" variable. If we vary the value of
<i>t</i>, then with every change we get <strong>two</strong> values,
which we can use as (<i>x</i>,<i>y</i>) coordinates in a graph. The
above set of functions, for instance, generates points on a circle:
We can range <i>t</i> from negative to positive infinity, and the
resulting (<i>x</i>,<i>y</i>) coordinates will always lie on a
circle with radius 1 around the origin (0,0). If we plot it for
<i>t</i> from 0 to 5, we get this (use your up and down arrow keys
to change the plot end value):
</p>
<Graphic
title="A (partial) circle: x=sin(t), y=cos(t)"
static="{true}"
setup="{this.setup}"
draw="{this.draw}"
onKeyDown="{this.props.onKeyDown}"
/>
<p>
Bézier curves are just one out of the many classes of parametric
functions, and are characterised by using the same base function for
all of the output values. In the example we saw above, the
<i>x</i> and <i>y</i> values were generated by different functions
(one uses a sine, the other a cosine); but Bézier curves use the
"binomial polynomial" for both the <i>x</i> and <i>y</i> outputs. So
what are binomial polynomials?
</p>
<p>
You may remember polynomials from high school. They're those sums
that look like this:
</p>
<img
class="LaTeX SVG"
src="images/latex/bb06cb82d372f822a7b35e661502bd72.svg"
width="213px"
height="20px"
loading="lazy"
/>
<p>
If the highest order term they have is <i></i>, they're called
"cubic" polynomials; if it's <i></i>, it's a "square" polynomial;
if it's just <i>x</i>, it's a line (and if there aren't even any
terms with <i>x</i> it's not a polynomial!)
</p>
<p>
Bézier curves are polynomials of <i>t</i>, rather than <i>x</i>,
with the value for <i>t</i> being fixed between 0 and 1, with
coefficients <i>a</i>, <i>b</i> etc. taking the "binomial" form,
which sounds fancy but is actually a pretty simple description for
mixing values:
</p>
<img
class="LaTeX SVG"
src="images/latex/2adc12d0cff01d40d9e1702014a7dc19.svg"
width="367px"
height="64px"
loading="lazy"
/>
<p>
I know what you're thinking: that doesn't look too simple! But if we
remove <i>t</i> and add in "times one", things suddenly look pretty
easy. Check out these binomial terms:
</p>
<img
class="LaTeX SVG"
src="images/latex/9c18f76e76cf684ecd217ad8facc2e93.svg"
width="184px"
height="87px"
loading="lazy"
/>
<p>
Notice that 2 is the same as 1+1, and 3 is 2+1 and 1+2, and 6 is
3+3... As you can see, each time we go up a dimension, we simply
start and end with 1, and everything in between is just "the two
numbers above it, added together", giving us a simple number
sequence known as
<a href="https://en.wikipedia.org/wiki/Pascal%27s_triangle"
>Pascal's triangle</a
>. Now <i>that's</i> easy to remember.
</p>
<p>
There's an equally simple way to figure out how the polynomial terms
work: if we rename <i>(1-t)</i> to <i>a</i> and <i>t</i> to
<i>b</i>, and remove the weights for a moment, we get this:
</p>
<img
class="LaTeX SVG"
src="images/latex/449850dead8abbdd11cd4aec1bac082e.svg"
width="903px"
height="63px"
loading="lazy"
/>
<p>
It's basically just a sum of "every combination of <i>a</i> and
<i>b</i>", progressively replacing <i>a</i>'s with <i>b</i>'s after
every + sign. So that's actually pretty simple too. So now you know
binomial polynomials, and just for completeness I'm going to show
you the generic function for this:
</p>
<img
class="LaTeX SVG"
src="images/latex/9a6d17c362980775f1425d0d2ad9a36a.svg"
width="300px"
height="55px"
loading="lazy"
/>
<p>
And that's the full description for Bézier curves. Σ in this
function indicates that this is a series of additions (using the
variable listed below the Σ, starting at ...=&lt;value&gt; and
ending at the value listed on top of the Σ).
</p>
<div className="howtocode">
<h3>How to implement the basis function</h3>
<p>
Mouse-over the graphs to see the expression for C, given the
<em>t</em> value at the mouse pointer.
We could naively implement the basis function as a mathematical
construct, using the function as our guide, like this:
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
I say we could, because we're not going to: the factorial function
is <em>incredibly</em> expensive. And, as we can see from the
above explanation, we can actually create Pascal's triangle quite
easily without it: just start at [1], then [1,1], then [1,2,1],
then [1,3,3,1], and so on, with each next row fitting 1 more
number than the previous row, starting and ending with "1", with
all the numbers in between being the sum of the previous row's
elements on either side "above" the one we're computing.
</p>
<p>
We can generate this as a list of lists lightning fast, and then
never have to compute the binomial terms because we have a lookup
table:
</p>
<pre><code>lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
function binomial(n,k):
while(n &gt;= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i&lt;s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]</code></pre>
<p>
So what's going on here? First, we declare a lookup table with a
size that's reasonably large enough to accommodate most lookups.
Then, we declare a function to get us the values we need, and we
make sure that if an <i>n/k</i> pair is requested that isn't in
the LUT yet, we expand it first. Our basis function now looks like
this:
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
Perfect. Of course, we can optimize further. For most computer
graphics purposes, we don't need arbitrary curves (although we
will also provide code for arbitrary curves in this primer); we
need quadratic and cubic curves, and that means we can drastically
simplify the code:
</p>
<pre><code>function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3</code></pre>
<p>And now we know how to program the basis function. Excellent.</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 <em>t</em> value, the
following always holds:
</p>
<img
class="LaTeX SVG"
src="images/latex/385d1fd4aecbd2066e6e284a84408be6.svg"
width="251px"
height="39px"
loading="lazy"
/>
<p>
This leads to a pretty powerful bit of knowledge: merely by knowing
the <em>t</em> 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>
<img
class="LaTeX SVG"
src="images/latex/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg"
width="217px"
height="37px"
loading="lazy"
/>
<p>And that's it, all values found.</p>
<div class="note">
<p>
Much like the <em>u(t)</em> function in the above note, the
<em>ratio(t)</em> function depends on whether we're looking at
quadratic or cubic curves. Their form is intrinsically related to
the <em>u(t)</em> function in that they both come rolling out of
the same function evaluation, explained over on
<a
href="http://mathoverflow.net/questions/122257/finding-the-formula-for-B%C3%A9zier-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>
<img
class="LaTeX SVG"
src="images/latex/059000c5c8a37dcc8d7fa04154a05df3.svg"
width="245px"
height="41px"
loading="lazy"
/>
<img
class="LaTeX SVG"
src="images/latex/b4987e9b77b0df604238b88596c5f7c3.svg"
width="223px"
height="41px"
loading="lazy"
/>
<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 <em>C</em>" is no longer fixed,
moving around as we move the control points, and projections of
<em>B</em> 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 <em>t</em> 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,
as well as curve fitting of "a bunch of coordinates". These are
useful things, and we'll look at both in the next sections.
So, now we know what the basis function looks like, time to add in
the magic that makes Bézier curves so special: control points.
</p>
</section>
</section>

View File

@@ -90,7 +90,7 @@
<li>
<a href="#whatis">ではベジエ曲線はどうやってできるのでしょう?</a>
</li>
<li><a href="#abc">The projection identity</a></li>
<li><a href="#explanation">ベジエ曲線の数学</a></li>
</ol>
</nav>
</header>
@@ -435,239 +435,192 @@
それでは、もう少し詳しくベジエ曲線を見ていきましょう。数学的な表現やそこから導かれる性質、さらには、ベジエ曲線に対して/ベジエ曲線を使ってできるさまざまな内容についてです。
</p>
</section>
<section id="abc">
<h1>The projection identity</h1>
<section id="explanation">
<h1>ベジエ曲線の数学</h1>
<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 <em>t</em> 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>
<graphics-element
title="Projections in a quadratic Bézier curve"
width="275"
height="275"
src="./chapters/abc/quadratic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/quadratic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Projections in a cubic Bézier curve"
width="275"
height="275"
src="./chapters/abc/cubic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cubic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<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 A, B, and C hide an important identity formula
for quadratic and cubic Bézier curves: for any point on the curve
with some <em>t</em> value, the ratio distance of C along the
baseline is fixed: if some <em>t</em> 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
<em>t</em> 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:
ベジエ曲線は「パラメトリック」関数の一種です。数学的に言えば、パラメトリック関数というのはインチキです。というのも、「関数」はきっちり定義された用語であり、いくつかの入力を<strong>1つ</strong>の出力に対応させる写像を表すものだからです。いくつかの数値を入れると、1つの数値が出てきます。入れる数値が変わっても、出てくる数値はやはり1つだけです。パラメトリック関数はインチキです。基本的には「じゃあわかった、値を複数個出したいから、関数を複数個使うことにするよ」ということです。例として、ある値<i>x</i>に何らかの操作を行い、別の値へと写す関数があるとします。
</p>
<img
class="LaTeX SVG"
src="images/latex/34fe255294faf45ab02128f7997b92ce.svg"
width="197px"
height="16px"
src="images/latex/1caef9931f954e32eae5067b732c1018.svg"
width="96px"
height="17px"
loading="lazy"
/>
<p>So that just leaves finding A.</p>
<div class="note">
<p>
While that relation is fixed, the function <em>u(t)</em> differs
depending on whether we're working with quadratic or cubic curves:
</p>
<img
class="LaTeX SVG"
src="images/latex/62f2f984e43a22a6b4bda4d399dedfc6.svg"
width="197px"
height="87px"
loading="lazy"
/>
<p>
So, if we know the start and end coordinates, and we know the
<em>t</em> value, we know C:
</p>
<graphics-element
title="Quadratic value of C for t"
width="275"
height="275"
src="./chapters/abc/qct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/qct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Cubic value of C for t"
width="275"
height="275"
src="./chapters/abc/cct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<p>
<i>f(x)</i
>という記法は、これが関数1つしかない場合は慣習的に<i>f</i>と呼びますであり、その出力が1つの変数この場合は<i>x</i>です)に応じて変化する、ということを示す標準的な方法です。<i>x</i>を変化させると、<i>f(x)</i>の出力が変化します。
</p>
<p>
ここまでは順調です。では、パラメトリック関数について、これがどうインチキなのかを見てみましょう。以下の2つの関数を考えます。
</p>
<img
class="LaTeX SVG"
src="images/latex/0f5cffd58e864fec6739a57664eb8cbd.svg"
width="93px"
height="36px"
loading="lazy"
/>
<p>
注目すべき箇所は特に何もありません。ただの正弦関数と余弦関数です。ただし、入力が別々の名前になっていることに気づくでしょう。仮に<i>a</i>の値を変えたとしても、<i>f(b)</i>の出力の値は変わらないはずです。なぜなら、こちらの関数には<i>a</i>は使われていないからです。パラメトリック関数は、これを変えてしまうのでインチキなのです。パラメトリック関数においては、どの関数も変数を共有しています。例えば、
</p>
<img
class="LaTeX SVG"
src="images/latex/066a910ae6aba69c40a338320759cdd1.svg"
width="100px"
height="40px"
loading="lazy"
/>
<p>
複数の関数がありますが、変数は1つだけです。<i>t</i>の値を変えた場合、<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>の両方の出力が変わります。これがどのように役に立つのか、疑問に思うかもしれません。しかし、実際には答えは至ってシンプルです。<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>のラベルを、パラメトリック曲線の表示によく使われているもので置き換えてやれば、ぐっとはっきりするかと思います。
</p>
<img
class="LaTeX SVG"
src="images/latex/4cf6fb369841e2c5d36e5567a8db4306.svg"
width="77px"
height="40px"
loading="lazy"
/>
<p>
きました。<i>x</i>/<i>y</i>座標です。謎の値<i>t</i>を通して繫がっています。
</p>
<p>
というわけで、普通の関数では<i>y</i>座標を<i>x</i>座標によって定義しますが、パラメトリック曲線ではそうではなく、座標の値を「制御」変数と結びつけます。<i>t</i>の値を変化させるたびに<strong>2つ</strong>の値が変化するので、これをグラフ上の座標
(<i>x</i>,<i>y</i>)として使うことができます。例えば、先ほどの関数の組は円周上の点を生成します。負の無限大から正の無限大へと<i>t</i>を動かすと、得られる座標(<i>x</i>,<i>y</i>)は常に中心(0,0)・半径1の円の上に乗ります。<i>t</i>を0から5まで変化させてプロットした場合は、このようになります上下キーでプロットの上限を変更できます
</p>
<Graphic
title="(部分)円 x=sin(t), y=cos(t)"
static="{true}"
setup="{this.setup}"
draw="{this.draw}"
onKeyDown="{this.props.onKeyDown}"
/>
<p>
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<i>x</i>の値と<i>y</i>の値とで異なる関数(正弦関数と余弦関数)を使っていましたが、ベジエ曲線では<i>x</i><i>y</i>の両方で「二項係数多項式」を使います。では、二項係数多項式とは何でしょう?
</p>
<p>高校で習った、こんな形の多項式を思い出すかもしれません。</p>
<img
class="LaTeX SVG"
src="images/latex/bb06cb82d372f822a7b35e661502bd72.svg"
width="213px"
height="20px"
loading="lazy"
/>
<p>
最高次の項が<i></i>であれば3次多項式、<i></i>であれば2次多項式と呼び、<i>x</i>だけの場合は1次多項式――ただの直線です。そして<i>x</i>の入った項が何もなければ、多項式ではありません!)
</p>
<p>
ベジエ曲線は<i>x</i>の多項式ではなく、<i>t</i>の多項式です。<i>t</i>の値は0から1までの間に制限され、その係数<i>a</i><i>b</i>などは「二項係数」の形をとります。というと複雑そうに聞こえますが、実際には値を組み合わせて、とてもシンプルに記述できます。
</p>
<img
class="LaTeX SVG"
src="images/latex/6e15c433dc2340271e007742009e3532.svg"
width="347px"
height="64px"
loading="lazy"
/>
<p>
「そこまでシンプルには見えないよ」と思っていることでしょう。しかし仮に、<i>t</i>を取り去って係数に1を掛けることにしてしまえば、急激に簡単になります。これが二項係数部分の項です。
</p>
<img
class="LaTeX SVG"
src="images/latex/c605597fb629b964921c6a4bca7fa4c9.svg"
width="163px"
height="85px"
loading="lazy"
/>
<p>
2は1+1に等しく、3は2+1や1+2に等しく、6は3+3に等しく、……ということに注目してください。見てわかるように、先頭と末尾は単に1になっていますが、中間はどれも次数が増えるたびに「上の2つの数を足し合わせた」ものになっています。<i>これなら</i>覚えやいですね。
</p>
<p>
多項式部分の項がどうなっているのか、同じぐらい簡単な方法で考えることができます。仮に、<i>(1-t)</i><i>a</i>に、<i>t</i><i>b</i>に書き換え、さらに重みを一旦削除してしまえば、このようになります。
</p>
<img
class="LaTeX SVG"
src="images/latex/5f6e6bf033c7aa3ae00a935714d14724.svg"
width="916px"
height="65px"
loading="lazy"
/>
<p>
これは要するに、「<i>a</i><i>b</i>のすべての組み合わせ」の単なる和です。プラスが出てくるたびに、<i>a</i><i>b</i>へと1つずつ置き換えていけばよいのです。こちらも本当に単純です。さて、これで「二項係数多項式」がわかりました。完璧を期するため、この関数の一般の形を示しておきます。
</p>
<img
class="LaTeX SVG"
src="images/latex/f24fd5e27968d96957ba706b16d8e90b.svg"
width="321px"
height="59px"
loading="lazy"
/>
<p>
そして、これがベジエ曲線の完全な表現です。この関数中のΣは、加算の繰り返し(Σの下にある変数を使って、...=&lt;&gt;から始めてΣの下にある値まで)を表します。
</p>
<div className="howtocode">
<h3>基底関数の実装方法</h3>
<p>
Mouse-over the graphs to see the expression for C, given the
<em>t</em> value at the mouse pointer.
上で説明した関数を使えば、数学的な組み立て方で、基底関数をナイーブに実装することもできます。
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
「こともできる」と書いたのは、この方法では実装しない方が良いからです。階乗は<em>とてつもなく</em>重い計算なのです。また、先ほどの説明からわかるように、実際は階乗を使わなくても、かなり簡単にパスカルの三角形を作ることができます。[1]から始めて[1,1]、[1,2,1]、[1,3,3,1]、……としていくだけです。下の段は上の段よりも1つ要素が増え、各段の先頭と末尾は1になります。中間の数はどれも、左右斜め上にある両要素の和になります。
</p>
<p>
このパスカルの三角形は、「リストのリスト」として瞬時に生成できます。そして、これをルックアップテーブルとして利用すれば、二項係数を計算する必要はまったくなくなります。
</p>
<pre><code>lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
binomial(n,k):
while(n &gt;= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i&lt;s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]</code></pre>
<p>
これはどのように動くのでしょう最初に、十分に大きなサイズのルックアップテーブルを宣言します。次に、求めたい値を得るための関数を定義します。この関数は、求めたい値のn/kのペアがテーブル中にまだ存在しない場合、先にテーブルを拡張するようになっています。さて、これで基底関数は次のようになりました。
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
完璧です。もちろん、さらなる最適化を施すこともできます。コンピュータグラフィクス用途ではたいてい、任意の次数の曲線が必要になるわけではありません。2次と3次の曲線だけが必要であれば、以下のようにコードを劇的に単純化することができます実際、この入門では任意の次数までは扱いませんので、これに似たようなコードが出てきます
</p>
<pre><code>function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3</code></pre>
<p>
これで基底関数をプログラムする方法がわかりました。すばらしい。
</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 <em>t</em> value, the
following always holds:
</p>
<img
class="LaTeX SVG"
src="images/latex/385d1fd4aecbd2066e6e284a84408be6.svg"
width="251px"
height="39px"
loading="lazy"
/>
<p>
This leads to a pretty powerful bit of knowledge: merely by knowing
the <em>t</em> 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>
<img
class="LaTeX SVG"
src="images/latex/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg"
width="217px"
height="37px"
loading="lazy"
/>
<p>And that's it, all values found.</p>
<div class="note">
<p>
Much like the <em>u(t)</em> function in the above note, the
<em>ratio(t)</em> function depends on whether we're looking at
quadratic or cubic curves. Their form is intrinsically related to
the <em>u(t)</em> function in that they both come rolling out of
the same function evaluation, explained over on
<a
href="http://mathoverflow.net/questions/122257/finding-the-formula-for-B%C3%A9zier-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>
<img
class="LaTeX SVG"
src="images/latex/059000c5c8a37dcc8d7fa04154a05df3.svg"
width="245px"
height="41px"
loading="lazy"
/>
<img
class="LaTeX SVG"
src="images/latex/b4987e9b77b0df604238b88596c5f7c3.svg"
width="223px"
height="41px"
loading="lazy"
/>
<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 <em>C</em>" is no longer fixed,
moving around as we move the control points, and projections of
<em>B</em> 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 <em>t</em> 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,
as well as curve fitting of "a bunch of coordinates". These are
useful things, and we'll look at both in the next sections.
というわけで、基底関数がどのようなものか理解できました。今度はベジエ曲線を特別にする魔法――制御点を導入する時間です。
</p>
</section>
</section>

View File

@@ -72,8 +72,24 @@ div.note:before {
img.LaTeX.SVG {
display: block;
margin-left: 2em;
}
img.LaTeX.SVG + img.LaTeX.SVG {
margin-top: 1em;
margin-left: 1em;
}
pre {
display: inline-block;
margin-left: 1em;
background-color: lightyellow;
padding: 0.5em;
border: 1px dotted gray;
}
code {
position: relative;
font-family: monospace;
font-size: 0.9rem;
}

View File

@@ -23,7 +23,8 @@ fs.ensureDirSync(sourceDir);
* so that the document won't constantly reflow as it loads images
* in.
*/
export default async function latexToSVG(latex, chapter, locale, block) {
export default async function latexToSVG(latex, chapter, localeStrings, block) {
const locale = localeStrings.getCurrentLocale();
const hash = createHash(`md5`).update(latex).digest(`hex`);
const TeXfilename = path.join(sourceDir, hash + `.tex`);

View File

@@ -88,7 +88,7 @@
<ol>
<li><a href="#introduction">简单介绍</a></li>
<li><a href="#whatis">什么构成了贝塞尔曲线?</a></li>
<li><a href="#abc">The projection identity</a></li>
<li><a href="#explanation">贝塞尔曲线的数学原理</a></li>
</ol>
</nav>
</header>
@@ -422,239 +422,188 @@
让我们从更深的层次来观察贝塞尔曲线。看看它们的数学表达式,从这些表达式衍生得到的属性,以及我们可以对贝塞尔曲线做的事。
</p>
</section>
<section id="abc">
<h1>The projection identity</h1>
<section id="explanation">
<h1>贝塞尔曲线的数学原理</h1>
<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 <em>t</em> 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>
<graphics-element
title="Projections in a quadratic Bézier curve"
width="275"
height="275"
src="./chapters/abc/quadratic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/quadratic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Projections in a cubic Bézier curve"
width="275"
height="275"
src="./chapters/abc/cubic.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cubic.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<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 A, B, and C hide an important identity formula
for quadratic and cubic Bézier curves: for any point on the curve
with some <em>t</em> value, the ratio distance of C along the
baseline is fixed: if some <em>t</em> 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
<em>t</em> 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:
贝塞尔曲线是“参数”方程的一种形式。从数学上讲,参数方程作弊了:“方程”实际上是一个从输入到<strong>唯一</strong>输出的、良好定义的映射关系。几个输入进来,一个输出返回。改变输入变量,还是只有一个输出值。参数方程在这里作弊了。它们基本上干了这么件事,“好吧,我们想要更多的输出值,所以我们用了多个方程”。举个例子:假如我们有一个方程,通过一些计算,将假设为<i>x</i>的一些值映射到另外的值:
</p>
<img
class="LaTeX SVG"
src="images/latex/34fe255294faf45ab02128f7997b92ce.svg"
width="197px"
height="16px"
src="images/latex/1caef9931f954e32eae5067b732c1018.svg"
width="96px"
height="17px"
loading="lazy"
/>
<p>So that just leaves finding A.</p>
<div class="note">
<p>
While that relation is fixed, the function <em>u(t)</em> differs
depending on whether we're working with quadratic or cubic curves:
</p>
<img
class="LaTeX SVG"
src="images/latex/62f2f984e43a22a6b4bda4d399dedfc6.svg"
width="197px"
height="87px"
loading="lazy"
/>
<p>
So, if we know the start and end coordinates, and we know the
<em>t</em> value, we know C:
</p>
<graphics-element
title="Quadratic value of C for t"
width="275"
height="275"
src="./chapters/abc/qct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/qct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<graphics-element
title="Cubic value of C for t"
width="275"
height="275"
src="./chapters/abc/cct.js"
>
<fallback-image>
<img
width="275px"
height="275px"
src="./images/chapters/abc/cct.png"
loading="lazy"
/>
Scripts are disabled. Showing fallback image.
</fallback-image></graphics-element
>
<p>
记号<i>f(x)</i>是表示函数的标准方式(为了方便起见,如果只有一个的话,我们称函数为<i>f</i>),函数的输出根据一个变量(本例中是<i>x</i>)变化。改变<i>x</i><i>f(x)</i>的输出值也会变。
</p>
<p>
到目前没什么问题。现在,让我们来看一下参数方程,以及它们是怎么作弊的。我们取以下两个方程:
</p>
<img
class="LaTeX SVG"
src="images/latex/0f5cffd58e864fec6739a57664eb8cbd.svg"
width="93px"
height="36px"
loading="lazy"
/>
<p>
这俩方程没什么让人印象深刻的,只不过是正弦函数和余弦函数,但正如你所见,输入变量有两个不同的名字。如果我们改变了<i>a</i>的值,<i>f(b)</i>的输出不会有变化,因为这个方程没有用到<i>a</i>。参数方程通过改变这点来作弊。在参数方程中,所有不同的方程共用一个变量,如下所示:
</p>
<img
class="LaTeX SVG"
src="images/latex/066a910ae6aba69c40a338320759cdd1.svg"
width="100px"
height="40px"
loading="lazy"
/>
<p>
多个方程,但只有一个变量。如果我们改变了<i>t</i>的值,<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>的输出都会发生变化。你可能会好奇这有什么用,答案其实很简单:对于参数曲线,如果我们用常用的标记来替代<i>f<sub>a</sub>(t)</i><i>f<sub>b</sub>(t)</i>,看起来就有些明朗了:
</p>
<img
class="LaTeX SVG"
src="images/latex/4cf6fb369841e2c5d36e5567a8db4306.svg"
width="77px"
height="40px"
loading="lazy"
/>
<p>
好了,通过一些神秘的<i>t</i>值将<i>x</i>/<i>y</i>坐标系联系起来。
</p>
<p>
所以,参数曲线不像一般函数那样,通过<i>x</i>坐标来定义<i>y</i>坐标,而是用一个“控制”变量将它们连接起来。如果改变<i>t</i>的值,每次变化时我们都能得到<strong>两个</strong>值,这可以作为图形中的(<i>x</i>,<i>y</i>)坐标。比如上面的方程组,生成位于一个圆上的点:我们可以使<i>t</i>在正负极值间变化,得到的输出(<i>x</i>,<i>y</i>)都会位于一个以原点(0,0)为中心且半径为1的圆上。如果我们画出<i>t</i>从0到5时的值将得到如下图像你可以用上下键来改变画的点和值
</p>
<Graphic
title="(一部分的)圆: x=sin(t), y=cos(t)"
static="{true}"
setup="{this.setup}"
draw="{this.draw}"
onKeyDown="{this.props.onKeyDown}"
/>
<p>
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<i>x</i>值和<i>y</i>值使用了不同的方程,与此不同的是,贝塞尔曲线的<i>x</i><i>y</i>都用了“二项多项式”。那什么是二项多项式呢?
</p>
<p>你可能记得高中所学的多项式,看起来像这样:</p>
<img
class="LaTeX SVG"
src="images/latex/bb06cb82d372f822a7b35e661502bd72.svg"
width="213px"
height="20px"
loading="lazy"
/>
<p>
如果它的最高次项是<i></i>就称为“三次”多项式,如果最高次项是<i></i>,称为“二次”多项式,如果只含有<i>x</i>的项,它就是一条线(不过不含任何<i>x</i>的项它就不是一个多项式!)
</p>
<p>
贝塞尔曲线不是x的多项式它是<i>t</i>的多项式,<i>t</i>的值被限制在0和1之间并且含有<i>a</i><i>b</i>等参数。它采用了二次项的形式,听起来很神奇但实际上就是混合不同值的简单描述:
</p>
<img
class="LaTeX SVG"
src="images/latex/2adc12d0cff01d40d9e1702014a7dc19.svg"
width="367px"
height="64px"
loading="lazy"
/>
<p>
我明白你在想什么:这看起来并不简单,但如果我们拿掉<i>t</i>并让系数乘以1事情就会立马简单很多看看这些二次项
</p>
<img
class="LaTeX SVG"
src="images/latex/9c18f76e76cf684ecd217ad8facc2e93.svg"
width="184px"
height="87px"
loading="lazy"
/>
<p>
需要注意的是2与1+1相同3相当于2+1或1+26相当于3+3...如你所见每次我们增加一个维度只要简单地将头尾置为1中间的操作都是“将上面的两个数字相加”。现在就能很容易地记住了。
</p>
<p>
还有一个简单的办法可以弄清参数项怎么工作的:如果我们将<i>(1-t)</i>重命名为<i>a</i>,将<i>t</i>重命名为<i>b</i>,暂时把权重删掉,可以得到这个:
</p>
<img
class="LaTeX SVG"
src="images/latex/449850dead8abbdd11cd4aec1bac082e.svg"
width="903px"
height="63px"
loading="lazy"
/>
<p>
基本上它就是“每个<i>a</i><i>b</i>结合项”的和,在每个加号后面逐步的将<i>a</i>换成<i>b</i>。因此这也很简单。现在你已经知道了二次多项式,为了叙述的完整性,我将给出一般方程:
</p>
<img
class="LaTeX SVG"
src="images/latex/9a6d17c362980775f1425d0d2ad9a36a.svg"
width="300px"
height="55px"
loading="lazy"
/>
<p>
这就是贝塞尔曲线完整的描述。在这个函数中的Σ表示了这是一系列的加法(用Σ下面的变量,从...=&lt;&gt;开始,直到Σ上面的数字结束)。
</p>
<div className="howtocode">
<h3>如何实现基本方程</h3>
<p>
Mouse-over the graphs to see the expression for C, given the
<em>t</em> value at the mouse pointer.
我们可以用之前说过的方程,来简单地实现基本方程作为数学构造,如下:
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;n; k++):
sum += n!/(k!*(n-k)!) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
我说我们“可以用”是因为我们不会这么去做:因为阶乘函数开销<em>非常大</em>。并且,正如我们在上面所看到的,我们不用阶乘也能够很容易地构造出帕斯卡三角形:一开始是[1],接着是[1,2,1],然后是[1,3,3,1]等等。下一行都比上一行多一个数首尾都为1中间的数字是上一行两边元素的和。
</p>
<p>
我们可以很快的生成这个列表,并在之后使用这个查找表而不用再计算二次多项式的系数:
</p>
<pre><code>lut = [ [1], // n=0
[1,1], // n=1
[1,2,1], // n=2
[1,3,3,1], // n=3
[1,4,6,4,1], // n=4
[1,5,10,10,5,1], // n=5
[1,6,15,20,15,6,1]] // n=6
binomial(n,k):
while(n &gt;= lut.length):
s = lut.length
nextRow = new array(size=s+1)
nextRow[0] = 1
for(i=1, prev=s-1; i&lt;s; i++):
nextRow[i] = lut[prev][i-1] + lut[prev][i]
nextRow[s] = 1
lut.add(nextRow)
return lut[n][k]</code></pre>
<p>
这里做了些什么首先我们声明了一个足够大的查找表。然后我们声明了一个函数来获取我们想要的值并且确保当一个请求的n/k对不在LUT查找表中时先将表扩大。我们的基本函数如下所示
</p>
<pre><code>function Bezier(n,t):
sum = 0
for(k=0; k&lt;=n; k++):
sum += binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum</code></pre>
<p>
完美。当然我们可以进一步优化。为了大部分的计算机图形学目的,我们不需要任意的曲线。我们需要二次曲线和三次曲线(实际上这篇文章没有涉及任意次的曲线,因此你会在其他地方看到与这些类似的代码),这说明我们可以彻底简化代码:
</p>
<pre><code>function Bezier(2,t):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return mt2 + 2*mt*t + t2
function Bezier(3,t):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return mt3 + 3*mt2*t + 3*mt*t2 + t3</code></pre>
<p>现在我们知道如何代用码实现基本方程了。很好。</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 <em>t</em> value, the
following always holds:
</p>
<img
class="LaTeX SVG"
src="images/latex/385d1fd4aecbd2066e6e284a84408be6.svg"
width="251px"
height="39px"
loading="lazy"
/>
<p>
This leads to a pretty powerful bit of knowledge: merely by knowing
the <em>t</em> 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>
<img
class="LaTeX SVG"
src="images/latex/12aaf0d7fd20b3c551a0ec76b18bd7d2.svg"
width="217px"
height="37px"
loading="lazy"
/>
<p>And that's it, all values found.</p>
<div class="note">
<p>
Much like the <em>u(t)</em> function in the above note, the
<em>ratio(t)</em> function depends on whether we're looking at
quadratic or cubic curves. Their form is intrinsically related to
the <em>u(t)</em> function in that they both come rolling out of
the same function evaluation, explained over on
<a
href="http://mathoverflow.net/questions/122257/finding-the-formula-for-B%C3%A9zier-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>
<img
class="LaTeX SVG"
src="images/latex/059000c5c8a37dcc8d7fa04154a05df3.svg"
width="245px"
height="41px"
loading="lazy"
/>
<img
class="LaTeX SVG"
src="images/latex/b4987e9b77b0df604238b88596c5f7c3.svg"
width="223px"
height="41px"
loading="lazy"
/>
<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 <em>C</em>" is no longer fixed,
moving around as we move the control points, and projections of
<em>B</em> 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 <em>t</em> 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,
as well as curve fitting of "a bunch of coordinates". These are
useful things, and we'll look at both in the next sections.
既然我们已经知道基本函数的样子,是时候添加一些魔法来使贝塞尔曲线变得特殊了:控制点。
</p>
</section>
</section>