円の内側で円を転がす ~n個の円に対応(式の一般化)~

先日から、Excelでスピログラフを作成している。
infoment.hatenablog.com
今日も、前回の続きから。
f:id:Infoment:20211010095409p:plain
前回は、三つの小さな円を同時に回転させてみた。作ってみると、円が一つ
増えるたびに

  1. 変数宣言 6回
  2. 変数の再宣言(Redim) 6回
  3. 変数の計算 6回
  4. 計算結果を配列にセット 6回

ということで、「:」で複数行をつながなければ、コードが24行増えることが
分かった。円が増えるたびに変数を増やすのも、それをどう計算するか考える
のも大変なので、式を一般化してみた。

Sub 複数ぐるぐる()

    Dim π As Double
        π = WorksheetFunction.Pi
    
    Dim r(1 To 3) As Long
    ' 大きな円の半径。
        r(1) = 13
    ' 小さな円の半径。
        r(2) = 7
    ' 小さな円の中心からのオフセット量。
        r(3) = 4
    
    ' ※r(1)とr(2)の最小公倍数÷r(1) ← 小さな円を何周回すか。
    ' RoundNumberの略。
    Dim rn As Long
        rn = WorksheetFunction.Lcm(r(1), r(2)) / r(1)
    
    ' 大きな円の中心座標。
    Dim ox1 As Double
    Dim oy1 As Double
        ox1 = 0: oy1 = 0
        
    ' 円周の分割数(Division Count)。
    Dim dc As Double
        dc = 36
        
    ' 小さい円の個数(Small Circle Count)。
    Dim scc As Long
        scc = 3
    
    ' 表の最大列数。
    Dim cMax As Long
        cMax = 2 + 3 * scc

    ' グラフリセット。
        Cells(1, 1).Resize(Rows.Count - 1, Columns.Count).Clear

    
    ' 大きい円と小さい円を分割する角度(rad)。
    Dim(1 To 2) As Double(1) = 2 * π / dc
        dθ(2) = -r(1) / r(2) *(1)
    
    ' 全描画に必要なグラフの行数。
    ' ※大きな円の描画に必要な点 dc+1 ←始点と終点を重ねないと円が閉じない。
    ' ※小さな円の描画に必要な点と動点Pの軌跡描画に必要な点の数は等しい。
    Dim rMax As Long
        rMax = (dc + 1) * (scc + 1) + (dc * rn + 1) * scc + scc+1
               
    Dim myChart As ChartObject
    Set myChart = ActiveSheet.ChartObjects(1)
        myChart.Chart.SetSourceData Source:=Cells(1, 1).Resize(rMax + 1, cMax)
               
    ' 各座標を格納するための配列。
    ' 一次元を0始まりにするのは、ラベル名を格納するため。
    Dim c As Long
    Dim arr() As Variant
    ReDim arr(rMax, 1 To cMax)
        arr(0, 1) = "X"
        For c = 2 To cMax
            arr(0, c) = "Y" & c - 1
        Next
    
    ' 大きい円の座標。
    Dim x1() As Double: ReDim x1(1 To dc + 1)
    Dim y1() As Double: ReDim y1(1 To dc + 1)
    ' 小さい円の座標。
    Dim x2() As Double: ReDim x2(1 To dc * rn + 1)
    Dim y2() As Double: ReDim y2(1 To dc * rn + 1)
    
    ' 小さい円の中心。
    Dim ox2() As Double: ReDim ox2(1 To dc * rn + 1)
    Dim oy2() As Double: ReDim oy2(1 To dc * rn + 1)

    ' 小さい円の中心からr(3)オフセットしたポイント。
    Dim x3() As Double: ReDim x3(1 To dc * rn + 1)
    Dim y3() As Double: ReDim y3(1 To dc * rn + 1)
        
    Dim i As Long
    Dim j As Long
    Dim k As Long: k = scc
    
        ' 大きな円の描画。
        For i = 1 To dc + 1
            x1(i) = r(1) * Cos((1) * (i - 1))
            y1(i) = r(1) * Sin((1) * (i - 1))
            arr(i, 1) = x1(i)
            arr(i, 2) = y1(i)
        Next
    
        ' 続けて描画するため、iの最終値を保存。
    Dim LastIndex As Long
        LastIndex = i
        
        For i = 1 To dc * rn + 1
        
            Application.Wait [Now() + "00:00:00.03"]
            DoEvents
            
            For c = 1 To scc
        
                ' 小さな円の中心。
                ox2(i) = r(1) * Cos((1) * (i - 1) + 2 * π / scc * (c - 1)) * (r(1) - r(2)) / r(1)
                oy2(i) = r(1) * Sin((1) * (i - 1) + 2 * π / scc * (c - 1)) * (r(1) - r(2)) / r(1)
    
                
                ' 小さい円を描画。
                For j = 1 To dc + 1
                    x2(i) = ox2(i) + r(2) * Cos((1) * (j - 1))
                    y2(i) = oy2(i) + r(2) * Sin((1) * (j - 1))
                    arr(LastIndex + j - 1 + (c - 1) * (dc + 1), 1) = ox2(i) + r(2) * Cos((1) * (j - 1))
                    arr(LastIndex + j - 1 + (c - 1) * (dc + 1), c + 2) = oy2(i) + r(2) * Sin((1) * (j - 1))
                Next
                
'                ' 中心からオフセットしたポイント。
                x3(i) = ox2(i) + r(3) * Cos((2) * (i - 1))
                y3(i) = oy2(i) + r(3) * Sin((2) * (i - 1))

                arr(LastIndex + scc * (dc + 1) + (c - 1), 1) = ox2(i) + r(3) * Cos((2) * (i - 1))
                arr(LastIndex + scc * (dc + 1) + (c - 1), 2 + scc + c) = oy2(i) + r(3) * Sin((2) * (i - 1))

                arr(LastIndex + scc * (dc + 1) + (c - 1) * (dc * rn + 1) + k, 1) = x3(i)
                arr(LastIndex + scc * (dc + 1) + (c - 1) * (dc * rn + 1) + k, 2 + 2 * scc + c) = y3(i)
               
            Next

            k = k + 1
            
            Cells(1, 1).Resize(rMax, cMax) = arr
        Next

End Sub

小さい円の個数3個の場合。
f:id:Infoment:20211010100949g:plain

やっとできた。一般化するうえで、脳みそが何度もパンクしてしまった。
と思ったのも束の間。

小さい円の個数が2個の場合。
軌跡が点のみで、線になっていない。
f:id:Infoment:20211010101159g:plain

小さい円の個数が8個の場合。
小さい円の一部が点になっていて、動点p_nが点になっていない。
f:id:Infoment:20211010101641g:plain

などの問題が発生した。そりゃそうだ、列毎のグラフの種類で、点にしたり
線にしたり表現を変えているのだから、セットする列が変われば意図しない
結果になるのは当たり前か。

ということで、もう少し調整が必要。次回に続きます。

参考まで。