前回 2 点間で直線を表しましたが、今回は 3 点 を使ってベジエ曲線を表してみましょう。 2 点 $\boldsymbol{v}_1$ と $\boldsymbol{v}_2$ を通る直線 $\boldsymbol{v}_{12}$ をパラメーター $k$ で表すと、 $$\boldsymbol{v}_{12} = (1 - k) \boldsymbol{v}_1 + k \boldsymbol{v}_2 $$ となり、2 点 $\boldsymbol{v}_2$ と $\boldsymbol{v}_3$ を通る直線 $\boldsymbol{v}_{23}$ をパラメーター $k$ で表すと、 $$\boldsymbol{v}_{23} = (1 - k) \boldsymbol{v}_2 + k \boldsymbol{v}_3 $$ となります。これは前回と同様の直線ですが、さらに、この 2 つの通過点の中間点 $$\boldsymbol{v} = (1 - k) \boldsymbol{v}_{12} + k \boldsymbol{v}_{23} $$ は「2 次ベジエ曲線」と呼ばれる曲線になります。2 次ベジエ曲線は両端の $\boldsymbol{v}_1$ と $\boldsymbol{v}_3$ を通りますが、もう 1 つの $\boldsymbol{v}_2$ は通りません。この点を制御点と呼びます。 3 次ベジエ曲線はこの制御点が 2 つになります。
2 次というのはこの曲線が 2 次曲線という意味ではありません。つまり 2 次ベジエ曲線イコール放物線というわけではありません。 双曲線のようにも見えますが、一体どのような曲線なのでしょうか。計算を簡単にするために $\boldsymbol{v}_1 = (1, 0)$、$\boldsymbol{v}_2 = (0, 0)$、$\boldsymbol{v}_3 = (0, 1)$ として計算してみましょう。$\boldsymbol{v}_2$ は原点にしたので、掛け算すると消えます。 $$\boldsymbol{v}_{12} = (1 - k) \boldsymbol{v}_1 $$ $$\boldsymbol{v}_{23} = k \boldsymbol{v}_3 $$ $$\boldsymbol{v} = (1 - k)^2 \boldsymbol{v}_1 + k^2 \boldsymbol{v}_3 $$ でこれを縦ベクトルで書き直すと、 $$\begin{pmatrix} x \\ y \end{pmatrix} = (1 - k)^2 \begin{pmatrix} 1 \\ 0 \end{pmatrix} + k^2 \begin{pmatrix} 0 \\ 1 \end{pmatrix} $$ つまり、 $$x = (1 - k)^2 $$ $$y = k^2 $$ となります。パラメーター $k$ は $0$ から $1$ まで変化させるので負の値を無視して、 $$k = \sqrt{y} $$ と表せます。一方、$x$ の式も同様に、 $$k - 1 = \sqrt{x} $$ と表せるので、先ほどの式を代入して、 $$\sqrt{y} - 1 = \sqrt{x} $$ $$\sqrt{x} + \sqrt{y} = 1 $$ という式が得られます。これはスーパー楕円と呼ばれる式です。スーパー楕円は実際には $$\left|\frac{x}{a}\right|^n +\left|\frac{y}{b}\right|^n = 1 $$ という式なので、二次ベジエ曲線は $a = b = 1$、$n = 0.5$ のときのスーパー楕円に相当する曲線になります。
Small Basic オンラインで実行したスクリーンショットを以下に示します。 まず、座標 (100, 300)、座標 (300, 100)、座標 (500, 300) に $\boldsymbol{v}_1$ と $\boldsymbol{v}_2$ と $\boldsymbol{v}_3$ を表す黒い点を打っています。 下図では $k = 0.85$ のときの $\boldsymbol{v}_{12}$ と $\boldsymbol{v}_{23}$ を緑の点で表示しています。 さらに $\boldsymbol{v}$ を赤い点で表示し、その軌跡(ベジエ曲線)も赤で表示しています。
プログラムは前回の LineAnime.txt とよく似ています。
' Quadratic Bézier Curve
' Version 0.1.0
' Copyright © 2020 Nonki Takahshi. The MIT License.
' Last update 2020-08-31
DrawGrid()
size = 10 ' size of a point
x = 100
y = 300
DrawPoint()
x1 = x
y1 = y
x = 300
y = 100
DrawPoint()
x2 = x
y2 = y
x = 500
y = 300
DrawPoint()
x3 = x
y3 = y
AddPoints()
GraphicsWIndow.PenWidth = 2
GraphicsWindow.PenColor = "DarkRed"
While "True"
shL = ""
nL = 0
For k = 0 To 1 Step 0.05
MovePoints()
If 0 < k Then
nL = nL + 1
shL[nL] = Shapes.AddLine(_x, _y, x, y)
EndIf
_x = x
_y = y
Program.Delay(500)
EndFor
For i = 1 To nL
Shapes.Remove(shL[i])
EndFor
EndWhile
Sub AddPoints
shT = Shapes.AddText("")
Shapes.Move(shT, 40, 40)
GraphicsWindow.PenWidth = 0
GraphicsWindow.BrushColor = "DarkGreen"
shP[1] = Shapes.AddEllipse(size, size)
shP[2] = Shapes.AddEllipse(size, size)
GraphicsWindow.BrushColor = "DarkRed"
shP[3] = Shapes.AddEllipse(size, size)
EndSub
Sub DrawPoint
GraphicsWindow.BrushColor = "Black"
GraphicsWindow.FillEllipse(x - size / 2, y - size / 2, size, size)
GraphicsWindow.DrawText(x, y, "(" + x + ", " + y + ")")
EndSub
Sub DrawGrid
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
fn = GraphicsWindow.FontName
If (fn = "Tahoma") Or (fn = "Segoe UI") Then
c10 = "#33000000"
c100 = "#66000000"
bc = "#CC000000"
Else ' for SBO
c10 = "#00000033"
c100 = "#00000066"
bc = "#000000CC"
EndIf
GraphicsWindow.FontName = "Courier New"
GraphicsWindow.FontSize = 18
GraphicsWindow.BrushColor = bc
For x = 0 To gw Step 10
If Math.Remainder(x, 100) = 0 Then
GraphicsWindow.PenColor = c100
GraphicsWindow.DrawText(x + 2, 0, x)
Else
GraphicsWindow.PenColor = c10
EndIf
GraphicsWindow.DrawLine(x, 0, x, gh)
EndFor
For y = 0 To gh Step 10
If Math.Remainder(y, 100) = 0 Then
GraphicsWindow.PenColor = c100
GraphicsWindow.DrawText(2, y, y)
Else
GraphicsWindow.PenColor = c10
EndIf
GraphicsWindow.DrawLine(0, y, gw, y)
EndFor
EndSub
Sub MovePoints
Shapes.SetText(shT, "k = " + k)
x12 = (1 - k) * x1 + k * x2
y12 = (1 - k) * y1 + k * y2
Shapes.Move(shP[1], x12 - size / 2, y12 - size / 2)
x23 = (1 - k) * x2 + k * x3
y23 = (1 - k) * y2 + k * y3
Shapes.Move(shP[2], x23 - size / 2, y23 - size / 2)
x = (1 - k) * x12 + k * x23
y = (1 - k) * y12 + k * y23
Shapes.Move(shP[3], x - size / 2, y - size / 2)
EndSub