mirror of
https://github.com/Pomax/BezierInfo-2.git
synced 2025-08-31 20:11:59 +02:00
this rename is absolutely stupid
This commit is contained in:
63
docs/chapters/explanation/circle.js
Normal file
63
docs/chapters/explanation/circle.js
Normal file
@@ -0,0 +1,63 @@
|
||||
setup() {
|
||||
this.step = 5;
|
||||
}
|
||||
|
||||
draw() {
|
||||
clear();
|
||||
|
||||
const dim = this.height,
|
||||
w = dim,
|
||||
h = dim,
|
||||
w2 = w/2,
|
||||
h2 = h/2,
|
||||
w4 = w2/2,
|
||||
h4 = h2/2;
|
||||
|
||||
setStroke(`black`);
|
||||
line(0, h2, w, h2);
|
||||
line(w2, 0, w2, h);
|
||||
|
||||
var offset = {x:w2, y:h2};
|
||||
|
||||
for(let t=0, p, mod; t<=this.step; t+=0.1) {
|
||||
p = {
|
||||
x: w2 + w4 * cos(t),
|
||||
y: h2 + h4 * sin(t)
|
||||
};
|
||||
circle(p.x, p.y, 1);
|
||||
|
||||
mod = t % 1;
|
||||
if(mod >= 0.9) {
|
||||
text(`t = ${ round(t) }`,
|
||||
w2 + 1.25 * w4 * cos(t) - 10,
|
||||
h2 + 1.25 * h4 * sin(t) + 10
|
||||
);
|
||||
circle(p.x, p.y, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onKeyDown() {
|
||||
const key = this.keyboard.currentKey;
|
||||
if (key === `ArrowUp`) { this.step += 0.1; }
|
||||
if (key === `ArrowDown`) { this.step -= 0.1; }
|
||||
if (this.step < 1) this.step = 1;
|
||||
redraw();
|
||||
}
|
||||
|
||||
onMouseDown() {
|
||||
this.mark = this.cursor.y;
|
||||
}
|
||||
|
||||
onMouseUp() {
|
||||
this.mark = false;
|
||||
}
|
||||
|
||||
onMouseMove() {
|
||||
if (this.mark) {
|
||||
let diff = this.mark - this.cursor.y,
|
||||
mapped = map(diff, -this.height/2, this.height/2, 0, 10, true);
|
||||
this.step = mapped;
|
||||
redraw();
|
||||
}
|
||||
}
|
171
docs/chapters/explanation/content.en-GB.md
Normal file
171
docs/chapters/explanation/content.en-GB.md
Normal 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):
|
||||
|
||||
<graphics-element title="A (partial) circle: x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
|
||||
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>x³</i>, they're called "cubic" polynomials; if it's <i>x²</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 ...=<value> and ending at the value listed on top of the Σ).
|
||||
|
||||
<div class="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.
|
169
docs/chapters/explanation/content.ja-JP.md
Normal file
169
docs/chapters/explanation/content.ja-JP.md
Normal 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まで変化させてプロットした場合は、このようになります(上下キーでプロットの上限を変更できます)。
|
||||
|
||||
<graphics-element title="(部分)円 x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
|
||||
ベジエ曲線はパラメトリック関数の一種であり、どの次元に対しても同じ基底関数を使うという点で特徴づけられます。先ほどの例では、<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>x³</i>であれば3次多項式、<i>x²</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 class="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>
|
||||
|
||||
というわけで、基底関数がどのようなものか理解できました。今度はベジエ曲線を特別にする魔法――制御点を導入する時間です。
|
169
docs/chapters/explanation/content.zh-CN.md
Normal file
169
docs/chapters/explanation/content.zh-CN.md
Normal 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时的值,将得到如下图像(你可以用上下键来改变画的点和值):
|
||||
|
||||
<graphics-element title="(一部分的)圆: x=sin(t), y=cos(t)" src="./circle.js"></graphics-element>
|
||||
|
||||
贝塞尔曲线是(一种)参数方程,并在它的多个维度上使用相同的基本方程。在上述的例子中<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>x³</i>就称为“三次”多项式,如果最高次项是<i>x²</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+2,6相当于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 class="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>
|
||||
|
||||
既然我们已经知道基本函数的样子,是时候添加一些魔法来使贝塞尔曲线变得特殊了:控制点。
|
Reference in New Issue
Block a user