C#ATIA

↑タイトル詐欺 主にFusion360API 偶にCATIA V5 VBA(絶賛ネタ切れ中)

端点一致を探る、組み合わせテスト10

こちらの続きです。
端点一致を探る、組み合わせテスト9 - C#ATIA


昨日の悩みが全て解消するような、良い方法を思い付きました。
時間が無い為、先にコードです。

'vba
'モートン順序を利用した8分木空間分割の為のクズコード3
Option Explicit

Private Const CLINER8TREEMANAGER_MAXLEVEL = 7   '有効空間分割最大レベル

Sub CATMain()
   Dim min: min = Array(-40#, -40#, -40#)   '最小位置
   Dim Max: Max = Array(40#, 40#, 40#)      '最大位置
   Dim lv&: lv = 2                          '分割レベル
   Dim Pnt: Pnt = Array(0, 0, 0)            '調べたい座標値
   Dim Tolerance#: Tolerance = 0.001        '端点一致トレランス
   
   Dim w: w = ArySub(Max, min)
   Dim unit: unit = AryDiv(w, CDbl(sl(1, lv)))
   
   If Not Init(lv) Then Exit Sub
   Dim SpaNo As Variant: SpaNo = GetMortonNum(Pnt, min, unit, Tolerance)
   Dim msg$
   msg = "最小位置 : " + Join(min, ",") + vbNewLine + _
         "最大位置 : " + Join(Max, ",") + vbNewLine + _
         "分割レベル : " + CStr(lv) + vbNewLine + _
         "の8分木空間で" + vbNewLine + _
         "座標値  : " + Join(Pnt, ",") + vbNewLine + _
         "線形8分木インデックスNo: " + Join(SpaNo, " , ")
   MsgBox msg
End Sub

'*** VBA不足関数 ***

'配列同士の引き算-細かいチェック無し
Private Function ArySub(ByVal a, ByVal b) As Variant
    ArySub = Array(a(0) - b(0), a(1) - b(1), a(2) - b(2))
End Function

'配列と実数の割り算-細かいチェック無し
Private Function AryDiv(ByVal a, ByVal b#) As Variant
    AryDiv = Array(a(0) / b, a(1) / b, a(2) / b)
End Function


'*** Octree ***
'http://marupeke296.com/COL_3D_No15_Octree.html

'線形8分木配列を構築
Private Function Init(ByVal Level&) As Boolean
    Init = False
    
    '設定最高レベル以上の空間は作れない
    If Level >= CLINER8TREEMANAGER_MAXLEVEL Then Exit Function
    
    Init = True
End Function

'ビット分割関数
Private Function BitSeparateFor3D(ByVal n As Byte) As Long
    Dim s As Long: s = n
    s = (s Or sl(s, 8)) And &HF00F
    s = (s Or sl(s, 4)) And &HC30C3
    s = (s Or sl(s, 2)) And &H249249
    BitSeparateFor3D = s
End Function

'8分木モートン順序算出関数
Private Function Get3DMortonNumber(ByVal x As Byte, ByVal y As Byte, ByVal z As Byte) As Long
   Get3DMortonNumber = BitSeparateFor3D(x) Or _
                       sl(BitSeparateFor3D(y), 1) Or _
                       sl(BitSeparateFor3D(z), 2)
End Function

'座標→最小レベル空間番号郡取得
''' @param :p-array(Double)-座標値
'''        :Min-array(Double)-空間最小座標値
'''        :unit-array(Double)-空間単位サイズ
'''        :tol-Double-トレランス
''' @return:array(Long)
Private Function GetMortonNum(ByVal p As Variant, _
                              ByVal min As Variant, _
                              ByVal unit As Variant, _
                              ByVal tol As Double) As Variant
    '最小位置から各軸の比率算出
    Dim ratio#(2), inte&(2), dec#(2) '比率,整数,小数
    Dim tolratio#(2) '各軸のトレランスの比率
    Dim i&
    For i = 0 To 2
        ratio(i) = (p(i) - min(i)) / unit(i)
        inte(i) = Fix(ratio(i))
        dec(i) = ratio(i) - inte(i)
        tolratio(i) = tol / unit(i)
    Next
    
    'トレランスを考慮
    Dim axis(2) As Variant
    Dim spapos As Collection
    For i = 0 To 2
        Set spapos = New Collection
        spapos.add inte(i)
        If dec(i) <= tolratio(i) Then spapos.add inte(i) - 1
        If 1# - dec(i) <= tolratio(i) Then spapos.add inte(i) + 1
        Set axis(i) = spapos
    Next
    
    '空間番号算出
    Dim x, y, z
    Dim SpaNo(): ReDim SpaNo(axis(0).Count * axis(1).Count * axis(2).Count)
    Dim j&, k&, cnt&: cnt = -1
    For Each x In axis(0)
        For Each y In axis(1)
            For Each z In axis(2)
                cnt = cnt + 1
                '本来なら不正値のチェックをすべき
                SpaNo(cnt) = Get3DMortonNumber(x, y, z)
    Next: Next: Next
    ReDim Preserve SpaNo(cnt)
    GetMortonNum = SpaNo
End Function


'*** BitShift ***
'http://www.geocities.co.jp/SiliconValley/4334/unibon/asp/bitshift2.html
' 左シフト
Private Function sl(ByVal x&, ByVal n&) As Long
    If n = 0 Then
        sl = x
    Else
        Dim k: k = CLng(2 ^ (32 - n - 1))
        Dim d: d = x And (k - 1)
        Dim c: c = d * CLng(2 ^ n)
        If x And k Then c = c Or &H80000000
        sl = c
    End If
End Function

細かなチェック、センスの無い変数・関数名は見逃してください。

今までは点の座標値とトレランスを考慮し、ボリュームとして線形8分木のインデックスを
求めていましたが、トレランスを考慮して点として最小レベルのモートン順序の空間番号
を求めるようにしました。(わかり難い表現です・・・)

上記のコードを実行した結果はこちら

f:id:kandennti:20161222194142p:plain

空間の重心位置(要はど真ん中)の場合、以前ではルート空間に属する事に
なっていました。 この場合、他に生成された全ての空間と端点一致の
処理が必要でした。 それに対し今回は、最低レベルの空間の8ヶ所のみの
チェックで済みます。 この方法であれば
・空間ポインタ配列(オリジナルのppCellAry)が、最低レベル分のみ
・衝突リストの巡回が、再帰・非再帰とかではなく単純なループ1回のみ
・同一空間での要素数が多い際、再度空間分割することが容易
 (極端に離れた要素がある場合への対応が楽)
他にも、オリジナルコードではチェック漏れを起こす可能性がある場合に気が付き
それにも対応したつもりです。


オリジナルコードをVBA化する事ばかり考えていたので、計算式の意味を
把握していませんでした。 VBAの商が切り下げじゃないと騒いだ時に
計算式の意味を良く考えれば良かったです。