1
0
mirror of https://github.com/Pomax/BezierInfo-2.git synced 2025-08-31 12:01:54 +02:00

this rename is absolutely stupid

This commit is contained in:
Pomax
2020-08-20 13:01:32 -07:00
parent 59fdafb2c5
commit d92e370bd1
470 changed files with 22 additions and 9 deletions

View File

@@ -0,0 +1,75 @@
# Controlling Bézier curvatures
Bézier curves are, like all "splines", interpolation functions. This means that they take a set of points, and generate values somewhere "between" those points. (One of the consequences of this is that you'll never be able to generate a point that lies outside the outline for the control points, commonly called the "hull" for the curve. Useful information!). In fact, we can visualize how each point contributes to the value generated by the function, so we can see which points are important, where, in the curve.
The following graphs show the interpolation functions for quadratic and cubic curves, with "S" being the strength of a point's contribution to the total sum of the Bézier function. Click-and-drag to see the interpolation percentages for each curve-defining point at a specific <i>t</i> value.
<div class="figure">
<graphics-element title="Quadratic interpolations" src="./lerp-quadratic.js"></graphics-element>
<graphics-element title="Cubic interpolations" src="./lerp-cubic.js"></graphics-element>
<graphics-element title="15th degree interpolations" src="./lerp-fifteenth.js"></graphics-element>
</div>
Also shown is the interpolation function for a 15<sup>th</sup> order Bézier function. As you can see, the start and end point contribute considerably more to the curve's shape than any other point in the control point set.
If we want to change the curve, we need to change the weights of each point, effectively changing the interpolations. The way to do this is about as straightforward as possible: just multiply each point with a value that changes its strength. These values are conventionally called "weights", and we can add them to our original Bézier function:
\[
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}}}
\cdot\
\underset{weight}{\underbrace{w_i}}
\]
That looks complicated, but as it so happens, the "weights" are actually just the coordinate values we want our curve to have: for an <i>n<sup>th</sup></i> order curve, w<sub>0</sub> is our start coordinate, w<sub>n</sub> is our last coordinate, and everything in between is a controlling coordinate. Say we want a cubic curve that starts at (120,160), is controlled by (35,200) and (220,260) and ends at (220,40), we use this Bézier curve:
\[
\left \{ \begin{matrix}
x = DARKRED[110] \cdot (1-t)^3 + DARKGREEN[25] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[210] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[210] \cdot t^3 \\
y = DARKRED[150] \cdot (1-t)^3 + DARKGREEN[190] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[250] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[30] \cdot t^3
\end{matrix} \right.
\]
Which gives us the curve we saw at the top of the article:
<graphics-element title="Our cubic Bézier curve" src="../introduction/cubic.js"></graphics-element>
What else can we do with Bézier curves? Quite a lot, actually. The rest of this article covers a multitude of possible operations and algorithms that we can apply, and the tasks they achieve.
<div class="howtocode">
### How to implement the weighted basis function
Given that we already know how to implement basis function, adding in the control points is remarkably easy:
```
function Bezier(n,t,w[]):
sum = 0
for(k=0; k<=n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
And now for the extremely optimized versions:
```
function Bezier(2,t,w[]):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2
function Bezier(3,t,w[]):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3
```
And now we know how to program the weighted basis function.
</div>

View File

@@ -0,0 +1,75 @@
# ベジエ曲線の曲率の制御
ベジエ曲線は(すべての「スプライン」と同様に)補間関数です。これは点の集合を受け取って、それらの点のどこか「内側」の値を生成するということです。(このことから、制御点同士を結んで輪郭をつくったとき、その外側に位置する点は決して生成されないことがわかります。なお、この輪郭を曲線の「包」と呼びます。お役立ち情報でした!)実際に、補間関数によって生成された値に対する、各点の寄与の大きさを可視化することができますが、これを見れば、ベジエ曲線のどの場所でどの点が重要になるのかがわかります。
下のグラフは、2次ベジエ曲線や3次ベジエ曲線の補間関数を表しています。ここでSは、ベジエ関数全体に対しての、その点の寄与の大きさを示します。ある<i>t</i>において、ベジエ曲線を定義する各点の補間率がどのようになっているのか、クリックドラッグをして確かめてみてください。
<div class="figure">
<Graphic inline={true} title="2次の補間" draw={this.drawQuadraticLerp}/>
<Graphic inline={true} title="3次の補間" draw={this.drawCubicLerp}/>
<Graphic inline={true} title="15次の補間" draw={this.draw15thLerp}/>
</div>
あわせて、15次ベジエ関数における補間関数も示しています。始点と終点は他の制御点と比較して、曲線の形に対してかなり大きな影響を与えていることがわかります。
曲線を変更したい場合は、各点の重みを変える(実質的には補間率を変える)必要があります。これはとても単純で、寄与の大きさを変えるための値を、各点にただ掛ければいいのです。この値は「重み」と呼ばれていますが、これを元のベジエ関数に組み込めば、次のようになります。
\[
Bézier(n,t) = \sum_{i=0}^{n}
\underset{二項係数部分の項}{\underbrace{\binom{n}{i}}}
\cdot\
\underset{多項式部分の項}{\underbrace{(1-t)^{n-i} \cdot t^{i}}}
\cdot\
\underset{重み}{\underbrace{w_i}}
\]
複雑そうに見えますが、運がいいことに「重み」というのは実はただの座標値です。というのは<i>n</i>次の曲線の場合、w<sub>0</sub>が始点の座標、w<sub>n</sub>が終点の座標となり、その間はどれも制御点の座標になります。例えば、始点が(120,160)、制御点が(35,200)と(220,260)、終点が(220,40)となる3次ベジエ曲線は、次のようになります。
\[
\left \{ \begin{matrix}
x = DARKRED[110] \cdot (1-t)^3 + DARKGREEN[25] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[210] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[210] \cdot t^3 \\
y = DARKRED[150] \cdot (1-t)^3 + DARKGREEN[190] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[250] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[30] \cdot t^3
\end{matrix} \right.
\]
この式からは、記事の冒頭に出てきた曲線が得られます。
<Graphic title="あの3次ベジエ曲線" setup={this.drawCubic} draw={this.drawCurve}/>
ベジエ曲線で、他にはどんなことができるでしょうか?実は、非常にたくさんのことが可能です。この記事の残りの部分では、実現可能な各種操作や適用可能なアルゴリズム、そしてこれによって達成できるタスクについて扱います。
<div class="howtocode">
### 重みつき基底関数の実装方法
基底関数の実装方法はすでに知っていますし、これに制御点を組み込むのは非常に簡単です。
```
function Bezier(n,t,w[]):
sum = 0
for(k=0; k<n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
そして、最適化を行ったバージョンは以下のようになります。
```
function Bezier(2,t,w[]):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2
function Bezier(3,t,w[]):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3
```
これで、重みつき基底関数をプログラムする方法がわかりました。
</div>

View File

@@ -0,0 +1,75 @@
# 控制贝塞尔的曲率
贝塞尔曲线是插值方程(就像所有曲线一样),这表示它们取一系列的点,生成一些处于这些点之间的值。(一个推论就是你永远无法生成一个位于这些控制点轮廓线外面的点,更普遍是称为曲线的外壳。这信息很有用!)实际上,我们可以将每个点对方程产生的曲线做出的贡献进行可视化,因此可以看出曲线上哪些点是重要的,它们处于什么位置。
下面的图形显示了二次曲线和三次曲线的差值方程“S”代表了点对贝塞尔方程总和的贡献。点击拖动点来看看在特定的<i>t</i>值时,每个曲线定义的点的插值百分比。
<div class="figure">
<Graphic inline={true} title="二次插值" draw={this.drawQuadraticLerp}/>
<Graphic inline={true} title="三次插值" draw={this.drawCubicLerp}/>
<Graphic inline={true} title="15次插值" draw={this.draw15thLerp}/>
</div>
上面有一张是15<sup>th</sup>阶的插值方程。如你所见,在所有控制点中,起点和终点对曲线形状的贡献比其他点更大些。
如果我们要改变曲线,就需要改变每个点的权重,有效地改变插值。可以很直接地做到这个:只要用一个值乘以每个点,来改变它的强度。这个值照惯例称为“权重”,我们可以将它加入我们原始的贝塞尔函数:
\[
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}}}
\cdot\
\underset{weight}{\underbrace{w_i}}
\]
看起来很复杂但实际上“权重”只是我们想让曲线所拥有的坐标值对于一条n<sup>th</sup>阶曲线w<sup>0</sup>是起始坐标w<sup>n</sup>是终点坐标中间的所有点都是控制点坐标。假设说一条曲线的起点为120160终点为22040并受点35200和点220260的控制贝塞尔曲线方程就为
\[
\left \{ \begin{matrix}
x = DARKRED[110] \cdot (1-t)^3 + DARKGREEN[25] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[210] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[210] \cdot t^3 \\
y = DARKRED[150] \cdot (1-t)^3 + DARKGREEN[190] \cdot 3 \cdot (1-t)^2 \cdot t + DARKBLUE[250] \cdot 3 \cdot (1-t) \cdot t^2 + AMBER[30] \cdot t^3
\end{matrix} \right.
\]
这就是我们在文章开头看到的曲线:
<Graphic title="我们的三次贝塞尔曲线" setup={this.drawCubic} draw={this.drawCurve}/>
我们还能对贝塞尔曲线做些什么?实际上还有很多。文章接下来涉及到我们可能运用到的一系列操作和算法,以及它们可以完成的任务。
<div class="howtocode">
### 如何实现权重基本函数
鉴于我们已经知道怎样实现基本函数,在其加入控制点是非常简单的:
```
function Bezier(n,t,w[]):
sum = 0
for(k=0; k<n; k++):
sum += w[k] * binomial(n,k) * (1-t)^(n-k) * t^(k)
return sum
```
下面是优化过的版本:
```
function Bezier(2,t,w[]):
t2 = t * t
mt = 1-t
mt2 = mt * mt
return w[0]*mt2 + w[1]*2*mt*t + w[2]*t2
function Bezier(3,t,w[]):
t2 = t * t
t3 = t2 * t
mt = 1-t
mt2 = mt * mt
mt3 = mt2 * mt
return w[0]*mt3 + 3*w[1]*mt2*t + 3*w[2]*mt*t2 + w[3]*t3
```
现在我们知道如何编程实现基本权重函数了。
</div>

View File

@@ -0,0 +1,62 @@
setup() {
const w = this.width,
h = this.height;
this.f = [
t => ({ x: t * w, y: h * (1-t) ** 3 }),
t => ({ x: t * w, y: h * 3 * (1-t) ** 2 * t }),
t => ({ x: t * w, y: h * 3 * (1-t) * t ** 2 }),
t => ({ x: t * w, y: h * t ** 3})
];
this.s = this.f.map(f => plot(f) );
}
draw() {
resetTransform();
clear();
setFill(`black`);
setStroke(`black`);
scale(0.8, 0.9);
translate(40,20);
drawAxes(`t`, 0, 1, `S`, `0%`, `100%`);
noFill();
this.s.forEach(s => {
setStroke( randomColor() );
drawShape(s);
})
this.drawHighlight();
}
drawHighlight() {
if (this.cursor.down) {
let c = screenToWorld(this.cursor);
if (c.x < 0) return;
if (c.x > this.width) return;
noStroke();
setFill(`rgba(255,0,0,0.3)`);
rect(c.x - 2, 0, 5, this.height);
const p = this.f.map(f => f(c.x / this.width));
setFill(`black`);
p.forEach(p => {
circle(p.x, p.y, 3);
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
});
}
}
onMouseMove() {
redraw();
}
onMouseUp() {
redraw();
}

View File

@@ -0,0 +1,81 @@
setup() {
this.degree = 15;
this.triangle = [[1], [1,1]];
this.generate();
}
binomial(n,k) {
if (!this.triangle[n]) {
while(!this.triangle[n]) {
let last = this.triangle.slice(-1)[0];
let next = last.map((v,i) => v + last[i+1]);
next.pop();
this.triangle.push([1, ...next, 1]);
}
}
return this.triangle[n][k];
}
generate() {
const w = this.width,
h = this.height,
d = this.degree;
this.f = [...new Array(d+1)].map((_,i) => {
return t => ({
x: t * w,
y: h * this.binomial(d,i) * (1-t) ** (d-i) * t ** (i)
});
});
this.s = this.f.map(f => plot(f, 0, 1, d*4) );
}
draw() {
resetTransform();
clear();
setFill(`black`);
setStroke(`black`);
scale(0.8, 0.9);
translate(40,20);
drawAxes(`t`, 0, 1, `S`, `0%`, `100%`);
noFill();
this.s.forEach(s => {
setStroke( randomColor() );
drawShape(s);
})
this.drawHighlight();
}
drawHighlight() {
if (this.cursor.down) {
let c = screenToWorld(this.cursor);
if (c.x < 0) return;
if (c.x > this.width) return;
noStroke();
setFill(`rgba(255,0,0,0.3)`);
rect(c.x - 2, 0, 5, this.height);
const p = this.f.map(f => f(c.x / this.width));
setFill(`black`);
p.forEach(p => {
circle(p.x, p.y, 3);
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
});
}
}
onMouseMove() {
redraw();
}
onMouseUp() {
redraw();
}

View File

@@ -0,0 +1,61 @@
setup() {
const w = this.width,
h = this.height;
this.f = [
t => ({ x: t * w, y: h * (1-t) ** 2 }),
t => ({ x: t * w, y: h * 2 * (1-t) * t }),
t => ({ x: t * w, y: h * t ** 2 })
];
this.s = this.f.map(f => plot(f) );
}
draw() {
resetTransform();
clear();
setFill(`black`);
setStroke(`black`);
scale(0.8, 0.9);
translate(40,20);
drawAxes(`t`, 0, 1, `S`, `0%`, `100%`);
noFill();
this.s.forEach(s => {
setStroke( randomColor() );
drawShape(s);
})
this.drawHighlight();
}
drawHighlight() {
if (this.cursor.down) {
let c = screenToWorld(this.cursor);
if (c.x < 0) return;
if (c.x > this.width) return;
noStroke();
setFill(`rgba(255,0,0,0.3)`);
rect(c.x - 2, 0, 5, this.height);
const p = this.f.map(f => f(c.x / this.width));
setFill(`black`);
p.forEach(p => {
circle(p.x, p.y, 3);
text(`${ round(100 * p.y/this.height) }%`, p.x + 10, p.y);
});
}
}
onMouseMove() {
redraw();
}
onMouseUp() {
redraw();
}