前回ベジエ曲線はスーパー楕円の $n = 0.5$ の形だという話をしました。 今回はスーパー楕円を描くプログラムを描いてみました。スーパー楕円の式は $$\left|\frac{x}{a}\right|^n +\left|\frac{y}{b}\right|^n = 1 $$ ですが、このままでは計算しずらいので、パラメーター $\theta$ を使って表します。 $$\DeclareMathOperator{\sgn}{sgn} x = \sgn(\cos \theta) \; a \; \left| \cos \theta \right| ^{\frac{2}{n}} $$ $$ y = \sgn(\sin \theta ) \; b \; \left| \sin \theta \right| ^{\frac{2}{n}} $$ ただし符号関数、 $$ \sgn(w) = \begin{cases} -1, & w \lt 0 \\ 0, & w = 0 \\ +1, & w \gt 0 \end{cases} $$ となります。
Small Basic オンラインで実行したスクリーンショットを以下に示します。 $a = 1.2$、$b = 1$ に固定しました。 $n = 0.5$ のときは星形となり、ベジエ曲線と同じ形になります。$n = 1.0$ のときはひし形になります。 $n = 2.0$ のときは楕円となり、$n > 2.0$ で長方形に近づいていきます。
今回は座標系を数学で使う(原点が中心にあり、$y$ 座標が上向きの)ものにするために Map と MapInv というグラフィックスの座標系と相互に変換するサブルーチンを用意しました。 スーパー楕円は上記のパラメーター $\theta$ による式で計算しています。
' Superellipse
' Version 0.1.0
' Copyright © 2020 Nonki Takahshi. The MIT License.
' Last update 2020-09-01
scale = 140
DrawGrid()
a = 1.2
GraphicsWindow.BrushColor = "Black"
GraphicsWindow.DrawText(40, 10, "a = " + a)
b = 1
GraphicsWindow.DrawText(40, 30, "b = " + b)
shN = Shapes.AddText("")
Shapes.Move(shN, 40, 50)
While "True"
For n = 0.5 To 2.5 Step 0.5
Shapes.SetText(shN, "n = " + n)
For i = 1 To nL
Shapes.Remove(shL[i])
EndFor
GraphicsWindow.PenWidth = 2
GraphicsWindow.PenColor = "Black"
DrawSuperEllipse()
keyDown = "False"
Program.Delay(3000)
EndFor
EndWhile
Sub DrawSuperEllipse
' param gxo, gyo - center position in the graphics window
' param a, b - major and minor semi axis
' param n
' param scale
nL = 0
shL = ""
For θ = -Math.Pi To Math.Pi Step Math.Pi / 32
abs = Math.Abs(Math.Cos(θ))
If abs = 0 Then
sgn = 0
Else
sgn = Math.Cos(θ) / abs
EndIf
x = sgn * a * Math.Power(abs, 2 / n)
abs = Math.Abs(Math.Sin(θ))
If abs = 0 Then
sgn = 0
Else
sgn = Math.Sin(θ) / abs
EndIf
y = sgn * b * Math.Power(abs, 2 / n)
Map()
gx2 = gx
gy2 = gy
If gx1 <> "" Then
nL = nL + 1
shL[nL] = Shapes.AddLine(gx1, gy1, gx2, gy2)
Program.Delay(100)
EndIf
gx1 = gx2
gy1 = gy2
EndFor
EndSub
Sub DrawGrid
gw = GraphicsWindow.Width
gh = GraphicsWindow.Height
gxo = gw / 2
gyo = gh / 2
fn = GraphicsWindow.FontName
If (fn = "Tahoma") Or (fn = "Segoe UI") Then
c10 = "#33009999"
c100 = "#66009999"
bc = "#00CCCC"
Else ' for SBO
c10 = "#00999933"
c100 = "#00999966"
bc = "#00CCCC"
EndIf
GraphicsWindow.FontName = "Courier New"
GraphicsWindow.FontSize = 14
GraphicsWindow.BrushColor = bc
dx = 0.1
dy = -0.1
gx = Math.Remainder(gw / 2, dx * scale)
gy = Math.Remainder(gh / 2, dy * scale)
MapInv()
x1 = x
y1 = y
gx = gw - Math.Remainder(gw / 2, 10)
gy = gh - Math.Remainder(gh / 2, 10)
MapInv()
x2 = x
y2 = y
For x = x1 To x2 Step dx
Map()
rem = Math.Remainder(x, 1)
If rem = 0.0 Then
GraphicsWindow.PenColor = c100
GraphicsWindow.DrawText(gx + 2, gh / 2, x)
Else
GraphicsWindow.PenColor = c10
EndIf
GraphicsWindow.DrawLine(gx, 0, gx, gh)
EndFor
For y = y1 To y2 Step dy
Map()
If Math.Remainder(y, 1) = 0.0 Then
GraphicsWindow.PenColor = c100
If y <> 0 Then
GraphicsWindow.DrawText(gw / 2 + 2, gy, y)
EndIf
Else
GraphicsWindow.PenColor = c10
EndIf
GraphicsWindow.DrawLine(0, gy, gw, gy)
EndFor
EndSub
Sub Map
gx = gxo + scale * x
gy = gyo - scale * y
EndSub
Sub MapInv
x = (gx - gxo) / scale
y = -(gy - gyo) / scale
EndSub