#author("2018-06-23T01:59:11+09:00","","") *八分木 [#r39dcf25] [[モートン番号テーブル化]]を利用して当たり判定用の八分木モジュールを作ってみました。~ 内部で自動的にオクツリーの分割レベルを調整しています。~ 二次元や四次元への変更も比較的簡単にできると思います。~ ☆★ このモジュールを利用した当たり判定の仕方 ☆★~ まずオクツリーを生成する。~ あとはフレーム毎に~ 1.オクツリーに全てのオブジェクトを登録する。~ 2.オクツリーから衝突候補リストを取得する。~ 3.衝突候補リストを元に詳細な当たり判定を行う。~ を繰り返すだけです。~ **ソース [#wc1634e4] //--"Octree.hsp" #ifndef Octree #include "MortonNo.hsp" ; ←モートン番号モジュールをインクルードしてください。 //八分木モジュール #module Octree data, dn, EnabledSpace, en , _EnabledSpace, _en, level, MaxLevel, LeafSpace, GridMax, table, areax, areay, areaz, areasx, areasy, areasz, divsizex, divsizey, divsizez, count #const MAXPARTITIONLEVEL 6 ; (0〜8) #const DEFPARTITIONLEVEL 3 ; (0〜↑の値まで) #const FRAMES 60 ; 何フレームごとに分割レベル調整を行うか #const DOWNLEVEL 30 ; 有効空間のうち葉空間が占める割合が何パーセント以下なら分割レベルを下げるか #const UPLEVEL 57 ; 有効空間のうち葉空間が占める割合が何パーセント以上なら分割レベルを上げるか #const DIMENSIONALITY 3 ; 次元数 #const DATASIZE 2 + DIMENSIONALITY * 2 #modinit double p1, double p2, double p3, double p4, double p5, double p6 MaxLevel = -1 level = DEFPARTITIONLEVEL gosub *SetArea *LevelChange gosub *SetDivSize LeafSpace = 1 << level * DIMENSIONALITY ; この数値未満は子を持つ空間、この数値以上は葉空間 MakeMortonNoTable DIMENSIONALITY, level, table if level > MaxLevel{ MaxLevel = level sdim data,, LeafSpace << 1 dim dn, LeafSpace << 1 } return #modfunc SetOctreeArea double p1, double p2, double p3, double p4, double p5, double p6 gosub *SetArea *SetDivSize GridMax = 1 << level divsizex = areasx / GridMax divsizey = areasy / GridMax divsizez = areasz / GridMax GridMax- return *SetArea if p1 < p4{ areax = p1: areasx = p4 - p1} else{ areax = p4: areasx = p1 - p4} if p2 < p5{ areay = p2: areasy = p5 - p2} else{ areay = p5: areasy = p2 - p5} if p3 < p6{ areaz = p3: areasz = p6 - p3} else{ areaz = p6: areasz = p3 - p6} return #modfunc AddOctree int ID, double p1, double p2, double p3, double p4, double p5, double p6 x = limit((p1 - areax) / divsizex, 0, GridMax) y = limit((p2 - areay) / divsizey, 0, GridMax) z = limit((p3 - areaz) / divsizez, 0, GridMax) xn = limit((p4 - areax) / divsizex, 0, GridMax) yn = limit((p5 - areay) / divsizey, 0, GridMax) zn = limit((p6 - areaz) / divsizez, 0, GridMax) N = GetLinerNo3d(table, x, y, z, xn, yn, zn) ; オブジェクトが所属すべき空間番号 index = dn(N) * DATASIZE memexpand data(N), index + DATASIZE if N < LeafSpace{ lpoke data(N), index, ID | limit(x, 0, xn) << 16 | abs(xn - x) + 1 << 24 lpoke data(N), index + 4, limit(y, 0, yn) | abs(yn - y) + 1 << 8 | limit(z, 0, zn) << 16 | abs(zn - z) + 1 << 24 if dn(N): else: EnabledSpace(en) = N: en+ }else{ wpoke data(N), index, ID if dn(N): else: _EnabledSpace(_en) = N: _en+ } dn(N)+ return #modcfunc OctreeListing array list lpoke ln //子空間を持つすべての有効な空間に対して ( 子空間との組み合わせと空間内総当たり ) repeat en dup S, EnabledSpace(cnt) ; この空間に対して作業開始 dup ndata, data(S) dup NN, dn(S) poke UnScanned, S ; 自空間は走査済みとする lpoke index //現在空間の全てのオブジェクトに対して repeat NN, 1 //オブジェクト情報取得 ID = wpeek(ndata, index) << 16 x = peek(ndata, index + 2) xn = peek(ndata, index + 3) y = peek(ndata, index + 4) yn = peek(ndata, index + 5) //オブジェクトが占有する全ての葉空間に対して repeat peek(ndata, index + 7), peek(ndata, index + 6) i = cnt repeat yn, y dup t, table(0, cnt, i) repeat xn, x N = t(cnt) ; 葉空間番号 *@ //自空間に向かってループ開始 repeat dn(N) list(ln) = ID | wpeek(data(N), cnt * DATASIZE): ln+ loop N >> 1 ; 親空間に移動 if peek(UnScanned, N){ ; 未走査なら poke UnScanned, N ; 空間を走査済みにして Checked(cn) = N: cn+ ; 走査済みにした空間を記録して goto *@b ; ループ } loop loop loop //次のオブジェクトのために記録をもとに走査情報クリア repeat cn poke UnScanned, Checked(cnt), 1 loop lpoke cn //現在空間内オブジェクト同士 repeat NN - cnt, cnt list(ln) = ID | wpeek(ndata, cnt * DATASIZE): ln+ loop index += DATASIZE ; 次のオブジェクトへ loop poke UnScanned, S, 1 ; 次の空間のために自空間を未走査に戻す loop repeat en lpoke dn(EnabledSpace(cnt)) loop //全ての有効な葉空間に対して ( 空間内総当たりのみ ) repeat _en dup ndata, data(_EnabledSpace(cnt)) dup NN, dn(_EnabledSpace(cnt)) repeat NN - 1, 1 ID = wpeek(ndata, cnt * DATASIZE - DATASIZE) << 16 repeat NN - cnt, cnt list(ln) = ID | wpeek(ndata, cnt * DATASIZE): ln+ loop loop lpoke NN loop //分割レベル調整 if count \ FRAMES: else{ if _en: else: if en: else: return 0 rate = _en * 100 / (en + _en) if rate >= UPLEVEL{ if level < MAXPARTITIONLEVEL{ level+ gosub *LevelChange } }else: if rate <= DOWNLEVEL{ level- gosub *LevelChange } } lpoke en lpoke _en count+ return ln #modcfunc GetOctreeLevel return level #deffunc InitOctree i = 1 << MAXPARTITIONLEVEL * DIMENSIONALITY sdim UnScanned, i memset UnScanned, 1, i - 1, 1 return #global InitOctree #endif //--"Octree.hsp" --End Of File //八分木のテスト #include "d3m.hsp" gsel , 1 //対象範囲 sx = -10000. sy = -10000. sz = -10000. ex = 10000. ey = 10000. ez = 10000. //八分木作成 newmod oct, octree, sx, sy, sz, ex, ey, ez N = 300 ; 球の数 repeat N ; 球生成 r(cnt) = 350. ; 球の半径 px(cnt) = 0. + rnd(18000) - 9000 py(cnt) = 0. + rnd(18000) - 9000 pz(cnt) = 0. + rnd(18000) - 9000 spx(cnt) = (1. + 1./ (rnd(1000) + 1)) * (rnd(2) * 2 - 1) spy(cnt) = (1. + 1./ (rnd(1000) + 1)) * (rnd(2) * 2 - 1) spz(cnt) = (1. + 1./ (rnd(1000) + 1)) * (rnd(2) * 2 - 1) col(cnt) = $FFFFFF loop カメラ移動 = 1 球運動 = 1 八分木 = 1 title "左クリックで球一時停止 右クリックでカメラ一時停止 Enterで判定モード変更" pt = -d3timer() *main nt = limit(pt + d3timer(), 0, 100) pt = -d3timer() stick key if key & 512: カメラ移動 ^ 1 if key & 256: 球運動 ^ 1 if key & 32 : 八分木 ^ 1 if カメラ移動{ count += nt d3setcam cos(double(count) / 6500) * 20000, sin(double(count) / 6600) * 20000, cos(double(count) / 6700) * 20000 } if 球運動{ repeat N dup dr, r(cnt) dup dpx, px(cnt) dup dpy, py(cnt) dup dpz, pz(cnt) dpx += spx(cnt) * nt dpy += spy(cnt) * nt dpz += spz(cnt) * nt //壁跳ね返り処理 if dpx + dr > ex{ spx(cnt) = -absf(spx(cnt)) }else:if dpx - dr < sx: spx(cnt) = absf(spx(cnt)) if dpy + dr > ey{ spy(cnt) = -absf(spy(cnt)) }else:if dpy - dr < sy: spy(cnt) = absf(spy(cnt)) if dpz + dr > ez{ spz(cnt) = -absf(spz(cnt)) }else:if dpz - dr < sz: spz(cnt) = absf(spz(cnt)) loop } if 八分木{ bp = -d3timer() repeat N dup dr, r(cnt) //八分木にオブジェクト登録 AddOctree oct, cnt, px(cnt) - dr, py(cnt) - dr, pz(cnt) - dr, px(cnt) + dr, py(cnt) + dr, pz(cnt) + dr ; ┃ ┗━━━━━━━━┳━━━━━━━━━━┛┗━━━━━━━━┳━━━━━━━━━━┛ ; ID 占有範囲起点 占有範囲終点 loop //八分木からリスト取得 判定数 = OctreeListing(oct, list) bp += d3timer() np = -d3timer() repeat 判定数 ID1 = wpeek(list(cnt), 2) ; 上位2バイトと ID2 = wpeek(list(cnt) ) ; 下位2バイトに衝突可能性ありのIDペアが入っている あたり判定 loop np += d3timer() }else{ 判定数 = N * (N - 1) / 2 bp = 0 np = -d3timer() repeat N - 1 ID1 = cnt repeat N - cnt - 1, cnt + 1 ID2 = cnt あたり判定 loop loop np += d3timer() } //以下描画処理 color: boxf ; 画面初期化 color $66, $FF, $FF: d3box sx, sy, sz, ex, ey, ez ; エリアボックス描画 //球描画 repeat N color col(cnt) >> 16, col(cnt) >> 8 & 255, col(cnt) & 255 d3circle px(cnt), py(cnt), pz(cnt), r(cnt) col(cnt) = $FFFFFF loop //情報描画 pos 0,0: color 0, 255 if 八分木{ mes "八分木で判定中 : 分割レベル = " + GetOctreeLevel(oct) }else{ mes "総当たりで判定中" } mes "球の数 = " + N mes "ブロードフェーズ: " + bp + " ms" mes "ナローフェーズ: " + np + " ms" mes "判定回数 = " + 判定数 mes "衝突数 = " + 衝突: 衝突 = 0 mes "FPS = " + d3getfps() redraw redraw 2 await 16 goto *main #deffunc あたり判定 if powf(px(ID1) - px(ID2), 2) + powf(py(ID1) - py(ID2), 2) + powf(pz(ID1) - pz(ID2), 2) <= powf(r(ID1) + r(ID2), 2){ col(ID1) = $FF0000 col(ID2) = $FF0000 衝突+ } return **八分木のメモリ占有量 [#wc1634e4] |分割レベル|空間数 |占有メモリ(KB)| | 0 |1 | 0 | | 1 |15 | 1 | | 2 |127 | 8 | | 3 |1023 | 64 | | 4 |8191 | 552 | | 5 |65535 | 4096 | | 6 |524287 | 32768 | | 7 |4194303 | 262144 | | 8 |33554431|2097152 | | 9 |非対応 |非対応 |