前回ベジエ曲線はスーパー楕円の $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