Duplicateで旋回運動の続き:放射+分身+残像(完成)

昨日は、円を旋回運動させるマクロにおいて、指定数の分身を指定数の残像を残すことに挑戦した。
infoment.hatenablog.com
結果、何とか思った通りの動きをさせることが出来た。

ただ、円周上に突然円が登場するので、唐突感が否めない。そこで今回は更に動作を追加して、一連の締めくくりとする。
f:id:Infoment:20190127111825p:plain

まず、昨日のテスト結果だが、分身が円周上にバラバラに配置されているように見える。
f:id:Infoment:20190126125837g:plain
その辺り考慮せずにテストしたため、当たり前と言えば、当たり前。

思うに、仕掛けを作るのも大変だが、その動きを調整するのも同じか、それ以上に難しい。ちなみに、ある程度調整した結果が↓こちら。
f:id:Infoment:20190127112217g:plain

今回は更に、中心から円周上に放射状に配置させてみた。

クラスモジュール(DuplicateShapeClass)
Option Explicit
Public Enum AngleType
    myDeg   ' ※1回転で360°
    myRad   ' ※1回転で2π
End Enum

Public x0 As Double
Public y0 As Double
Public RotateRadius As Double
Public RotateShapeRadius As Double
Public PhantomNumber As Long

Dim StartAngles() As Double

Private Property Get π() As Double
    π = WorksheetFunction.Pi
End Property

Public Sub GetStartAngle(angle_value As Double, angle_type As AngleType)
    If PhantomNumber = 0 Then PhantomNumber = 1
    ReDim StartAngles(PhantomNumber - 1)

    Dim i As Long
    For i = 0 To PhantomNumber - 1
        Select Case angle_type
            Case myDeg
                StartAngles(i) = (angle_value + 360 / PhantomNumber * i) * π / 180
            Case myRad
                StartAngles(i) = angle_value + 2 * π * i / PhantomNumber
        End Select
    Next
End Sub

Private Function SetStartShape(phantom_index As Long) As Shape
    Dim r As Double
        r = RotateShapeRadius
        If r = 0 Then r = 30
    Dim x As Double
    Dim y As Double
        x = x0 - RotateRadius * Cos(StartAngles(phantom_index)) + r / 2
        y = y0 - RotateRadius * Sin(StartAngles(phantom_index)) + r / 2
        Set SetStartShape = ActiveSheet.Shapes.AddShape(msoShapeOval, x, y, r, r)
End Function

Sub RotateShape(Optional rotation_number As Double = 1, _
                Optional rotation_angle As Double = 5, _
                Optional after_image As Long = 10)

    ' 分身の数を確認。なければ1(つまり本体のみ)をセット。
    If PhantomNumber = 0 Then PhantomNumber = 1
    ' 半径が未設定の場合、200をセット。
    If RotateRadius = 0 Then RotateRadius = 200
    ' 残像の数を、設定値×分身の数に再設定。
    after_image = after_image * PhantomNumber

    ' 旋回数と旋回角度、分身の数から、ループ回数を決定。
    Dim iMax As Long
        iMax = (rotation_number * 360 / rotation_angle) * PhantomNumber
    
    ' 配置する円を格納するための配列。
    Dim Shapes() As Shape
    ReDim Shapes(iMax)
    
    ' 移動前の、原点から見た移動物のxy座標における角度。
    Dim θ1() As Double
    ReDim θ1(PhantomNumber)
    ' 移動後の、原点から見た移動物のxy座標における角度。
    Dim θ2() As Double
    ReDim θ2(PhantomNumber)
    
    Dim i As Long
    Dim j As Long
    
    ' 最初に配置される円の座標。
    Dim x() As Double: ReDim x(PhantomNumber - 1)
    Dim y() As Double: ReDim y(PhantomNumber - 1)
    
    ' 座標を取得するために、一旦円を配置する。
        For i = 0 To PhantomNumber - 1
            Set Shapes(i) = SetStartShape(i)
            x(i) = Shapes(i).Left
            y(i) = Shapes(i).Top
            Shapes(i).Delete
        Next
    ' 放射状に展開する円を格納する変数。
    Dim TempShape As Shape
    Dim r As Double: r = RotateShapeRadius
    ' 放射状に展開する円の、中心から見た距離。
    Dim dx As Double
    Dim dy As Double
    
    ' 放射状に展開した円を格納するコレクション。
    ' ※円を消去するために使用。
    Dim col As Collection
    Set col = New Collection
    
        ' 分身の数だけ、中央から円を放射状に展開する。
        For j = 1 To 10
            For i = 0 To PhantomNumber - 1
                dx = (x(i) - x0 - r / 2) / 10 * j
                dy = (y(i) - y0 - r / 2) / 10 * j
                Set TempShape = ActiveSheet.Shapes.AddShape(msoShapeOval, x0 + r / 2 + dx, y0 + r / 2 + dy, r, r)
                    TempShape.ShapeStyle = msoShapeStylePreset37
                Application.Wait [now()+"0:00:00.01"]
                col.Add TempShape
            Next
        Next
    
    ' 放射状に展開した円を、配置した順に削除する。
    Dim c As Variant
        For Each c In col
            c.Delete
            Application.Wait [now()+"0:00:00.01"]
        Next
    
    ' ↓ここからは、円周上に配置された円を回転させる部分。
    ' 円周上に、分身を含めた円を配置する。
        For i = 0 To PhantomNumber - 1
            Set Shapes(i) = SetStartShape(i)
                Shapes(i).ShapeStyle = msoShapeStylePreset37
            ' 移動前の円の角度。
            θ1(i) = StartAngles(i)
            ' 移動後の円の角度。
            θ2(i) = θ1(i) - rotation_angle * π / 180
        Next
        
    ' 円の配置と分身の透過度変更、消去。
    Dim PhantomIndex As Long
        For i = PhantomNumber To iMax + after_image
            If i <= iMax Then
                ' 移動後の円の配置。
                Set Shapes(i) = Shapes(i - PhantomNumber).Duplicate
                ' 移動前の円の透過度を変更。
                Shapes(i - PhantomNumber).Fill.Transparency = 0.8
                
                ' ループカウンターから、何番目の分身かを求める。
                PhantomIndex = i Mod PhantomNumber
                
                ' 移動前の円と移動後の円の座標の差。
                dx = RotateRadius * (Cos(θ2(PhantomIndex)) - Cos(θ1(PhantomIndex)))
                dy = RotateRadius * (Sin(θ2(PhantomIndex)) - Sin(θ1(PhantomIndex)))
            
                ' 複製した円を、移動後の円の位置に移動させる。
                Shapes(i).Left = Shapes(i - PhantomNumber).Left - dx
                Shapes(i).Top = Shapes(i - PhantomNumber).Top - dy
                
                ' 次の移動後の角度を求める。
                θ1(PhantomIndex) = θ2(PhantomIndex)
                θ2(PhantomIndex) = θ1(PhantomIndex) - rotation_angle * π / 180
            End If
            
            ' 回り切った後、消し残りの残像を削除する。
            If i >= after_image Then
                Shapes(i - after_image).Delete
            End If
            Application.Wait [now()+"0:00:00.01"]
        Next

End Sub
標準モジュール
Sub RotateTest()
    Dim DSC As DuplicateShapeClass
    Set DSC = New DuplicateShapeClass
        DSC.x0 = 400
        DSC.y0 = 300
        DSC.RotateRadius = 200
        DSC.RotateShapeRadius = 30

        Dim i As Long
        For i = 1 To 6
            DSC.PhantomNumber = i
            Call DSC.GetStartAngle(90, myDeg)
            Call DSC.RotateShape(rotation_number:=5 / i, _
                                 rotation_angle:=60 / i, _
                                 after_image:=30 / i)
        Next
End Sub

その結果、このようになった。
f:id:Infoment:20190127112821g:plain

それなりに、それっぽくなったと思う。
しかし、個人的には面白かったが、このブログが目指す「仕事を早く終わらせて家に帰る」という点において、直接の役には立たない。

ということで、今回のシリーズは、これでおしまい。

参考まで。