オクトリヌの玹介





octreeずは䜕ですか この抂念をたったく知らない堎合は、 りィキペディアの蚘事を読むこずをお勧めしたす これには玄5分かかりたす。 それは十分なアむデアを䞎えたすが、それらがなぜ䜿甚され、どのようにそれらを実装するのかを理解するのに十分ではありたせん。



この蚘事では、抂念、図、およびコヌドを説明する䟋を䜿甚しお、octreeツリヌのデヌタ構造を䜜成するために必芁なすべおの手順に぀いお説明したす。 たた、各段階で䞋した決定に぀いおも説明したす。 この蚘事がオクタヌブの実装に関する唯䞀の真のガむドになるずは思わないが、それはあなたに良い基瀎を䞎えるはずであり、参照のために䜿甚するこずができる。



必芁な知識



この蚘事を曞くずき、読者は次のこずを前提ずしおいたす。



  1. Cスタむルの構文を持぀蚀語のプログラミングに粟通しおいるXNAでCを䜿甚する。
  2. バむナリ怜玢ツリヌ などのデヌタ構造のような 、ある皮のツリヌはすでにプログラミングされおおり、再垰、その長所ず短所に粟通しおいたす。
  3. 境界長方圢、球䜓、および角錐台で衝突認識がどのように実珟されるかを知っおいたす。
  4. 圌は暙準のデヌタ構造配列、リストなどを理解し、 倧きな「O」が䜕であるかを理解しおいたす このGDnetの蚘事で読むこずができる倧きな「O」 に぀いお 。
  5. 圌は、衝突をチェックする必芁がある機胜があるプロゞェクトに取り組んでいたす。


シヌンの準備



非垞に倧芏暡なゲヌムを䜜成し、さたざたな皮類、圢状、サむズの䜕千もの物理オブゞェクトを含めるこずができ、それらのいく぀かは互いに衝突する必芁があるずしたす。 各フレヌムで、どのオブゞェクトが互いに亀差するかを刀断し、これらの亀差を䜕らかの方法で凊理する必芁がありたす。 ゲヌムの速床が萜ちないようにこれを行う方法は







単玔なブルヌトフォヌスによる衝突怜出



最も簡単な解決策は、各オブゞェクトを䞖界のすべおのオブゞェクトず比范するこずです。 これは通垞、2぀のforルヌプで実行できたす。 コヌドは次のようになりたす。



foreach(gameObject myObject in ObjList) { foreach(gameObject otherObject in ObjList) { if(myObject == otherObject) continue; //       if(myObject.CollidesWith(otherObject)) { //   } } }
      
      





グラフィカルには、次のように衚すこずができたす。







赀い線はそれぞれ、CPUの亀差点のコストの高いチェックです。



圓然、そのようなコヌドはON 2 回実行されるため、恐ろしいはずです。 10,000個のオブゞェクトがある堎合、1億個の衝突チェック1億個を実行する必芁がありたす。 プロセッサの速床や数孊コヌドをどれだけ改善したかは関係ありたせん。そのようなプログラムはコンピュヌタの速床を䜎䞋させたす。 ゲヌムが毎秒60フレヌムで動䜜する堎合、毎秒60 * 1億の蚈算が実行されたす これは完党な狂気です。



これを回避できる堎合は、少なくずも倧芏暡なオブゞェクトのセットに぀いおは、これを行わないでください。 これは、たずえば、チェックが10個のオブゞェクト100個のチェックに察しおのみ実行される堎合にのみ受け入れられたす。 ゲヌム内にオブゞェクトたずえば小惑星がほずんどないこずを事前に知っおいる堎合は、単玔な枯枇が適切であり、八分朚を完党に攟棄するこずができたす。 フレヌムごずの衝突チェックが倚すぎるためにパフォヌマンスの問題に気付き始めた堎合、非垞に簡単な最適化を䜿甚できたす。



1.衝突蚈算手順にはいく぀の蚈算が必芁ですか たぶんその䞭のどこかに平方根の蚈算が隠されおいるかもしれたせんたずえば、距離をチェックするずき 詳现な衝突チェックピクセル、䞉角圢などの亀差を実行しおいたすか 暙準的な手法がありたす。最初に衝突の倧たかなチェックを実行しおから、より詳现なものに進みたす。 長方圢たたは球圢の境界線を蚘述するオブゞェクトを远加し、境界線の亀差を確認しおから、より倚くの数孊的蚈算ずリ゜ヌスを必芁ずする詳现なチェックを実行できたす。



オブゞェクト間の距離を比范するには、オブゞェクト間の平方平方距離を䜿甚しお、平方根の蚈算を避けたす。 平方根を蚈算するには、通垞ニュヌトン近䌌が䜿甚され、非垞に高䟡になる可胜性がありたす。


2.より少ない衝突チェックを蚈算するこずで取埗できたすか ゲヌムが毎秒60フレヌムで実行される堎合、いく぀かのフレヌムをスキップできたすか 䞀郚のオブゞェクトの動䜜が決定的である堎合、それらの衝突は事前に蚈算できたすたずえば、ビリダヌドボヌルずテヌブルの偎面の間。 スキャンされるオブゞェクトの数を枛らすこずは可胜ですか オブゞェクトを異なるリストに分割しおみおください。 1぀のリストには、「静止」オブゞェクトを含めるこずができたす。 互いの競合をチェックするこずはできたせん。 別のリストには、「移動する」オブゞェクトが含たれる堎合がありたす。このオブゞェクトに぀いおは、他のすべおの移動オブゞェクトおよびすべおの静止オブゞェクトずの衝突をチェックする必芁がありたす。 これにより、必芁な衝突チェックの数を蚱容可胜なパフォヌマンスレベルたで枛らすこずができたす。



3.パフォヌマンスの問題がある堎合、䞀郚のオブゞェクトの衝突のチェックを拒吊するこずは可胜ですか たずえば、煙の粒子は衚面オブゞェクトず盞互䜜甚し、その茪郭をたどっお矎しい効果を生み出すこずができたすが、これはゲヌムプレむを劚げるべきではありたせん。 チェックの数が特定の制限を超えた堎合、煙粒子の衝突の蚈算を停止できたす。 ただし、 重芁なゲヌムオブゞェクトの動きを無芖するず、ゲヌムプレむも砎壊されたすたずえば、プレむダヌの匟䞞がモンスタヌず盞互䜜甚しなくなりたす。 ぀たり、衝突の優先床チェックのリストを保持しおおくず䟿利です。 たず、優先床の高いコリゞョンが凊理され、制限を超えない堎合、優先床の䜎いコリゞョンが凊理されたす。 制限に達するず、ゲヌムは優先順䜍リストに残っおいるすべおの芁玠を砎棄するか、将来の怜蚌を遅らせる必芁がありたす。



4.実行時にON 2 を取り陀くために、衝突を怜出するためのより高速でありながら簡単な方法を䜿甚するこずは可胜ですか 衝突がすでにチェックされおいるオブゞェクトを削陀するず、ランタむムはONN + 1/ 2に短瞮されたす。これははるかに高速ですが、実装は非垞に簡単です技術的には、これはただON 2 です  ゜フトりェア開発の芳点から芋るず、結果ずしお、貧匱なアルゎリズムず䞍正確なデヌタ構造の改善によるパフォヌマンス䜎䞋を抑えるためにかかる費甚よりも倚くの時間を費やすこずができたす。 費甚䟿益比は益々䞍採算になり぀぀あり、衝突認識の凊理により適切なデヌタ構造を遞択する時が来たした。



実行時の衝突の認識の問題の解決策ずしお、スペヌスを分割するアルゎリズムが積極的に䜿甚されおいたす。 速床の䞀郚を事前に遞択したすが、察数的に衝突チェックの回数を枛らしたす。 開発時間ずCPUの初期費甚は、スケヌラビリティず高速化の利点によっお簡単に盞殺されたす。



スペヌスブレむクのコンセプト



䞀歩埌退しお、八分朚に移る前に、䞀般に空間ず朚を分割する考えを考えおみたしょう。 この抂念を理解しおいない堎合、コヌドで実装するこずはできたせん。



䞊蚘の単玔な怜玢の実装では、ゲヌム内の各オブゞェクトを取埗し、その䜍眮を他のすべおのオブゞェクトの䜍眮ず比范しお、関連するかどうかを確認したした。 これらのオブゞェクトはすべお、ゲヌムの䞖界に空間的に配眮されおいたす。 ゲヌムの䞖界を制限する図を䜜成し、この図に含たれるオブゞェクトを芋぀けるず、そこに含たれるオブゞェクトのリストを含むスペヌス領域ができたす。 この堎合、ゲヌムのすべおのオブゞェクトが含たれたす。



1぀のオブゞェクトが䞖界の䞀端にあり、もう1぀のオブゞェクトが反察偎にあるこずがわかりたす。したがっお、各フレヌムでオブゞェクト間の衝突チェックを蚈算する必芁はありたせん。 これは貎重なプロセッサ時間の無駄になりたす。 おもしろいこずをしよう 䞖界を正確に半分に分割するず、オブゞェクトの3぀の個別のリストを䜜成できたす。



最初のリスト、リストAには、ワヌルドの巊偎にあるすべおのオブゞェクトが含たれたす。



2番目のリストList Bには、䞖界の右偎にオブゞェクトが含たれおいたす。



䞀郚のオブゞェクトは、パヌツ間の境界線、぀たりその䞡偎にある境界線に觊れる堎合がありたす。 そのようなオブゞェクトに察しお、3番目のリスト、リストCを䜜成したす。







各区分で、䞖界を半分に空間的に瞮小し、結果の半分にあるオブゞェクトのリストを収集するこずに気付くかもしれたせん。 これらのリストを保存するために、バむナリ怜玢ツリヌを䜜成できたす。 このようなツリヌの抂念は次のようになりたす。







擬䌌コヌドのツリヌのデヌタ構造は次のようになりたす。



 public class BinaryTree { //   ,      private List m_objectList; //         private BinaryTree m_left, m_right; //     (   ). private BinaryTree m_parent; }
      
      





リストAのすべおのオブゞェクトがリストBのオブゞェクトず亀差するこずはないため、衝突チェックのほが半分を攟棄できたす。 リストCには、リストAおよびBのオブゞェクトに觊れるこずができるオブゞェクトが残っおいるため、リストA、B、Cのすべおのオブゞェクトずの衝突がないか、リストCのすべおのオブゞェクトを確認する必芁がありたす



䞖界をたすたす小さな郚分に分割し続けるなら、毎回半分ず぀チェックの数を枛らし続けたす。 これが、スペヌスを分割する䞻なアむデアです。 䞖界をツリヌのようなデヌタ構造に分割する方法は倚数ありたす バむナリスペヌスパヌティションBSP 、 四分朚 、 K次元朚 、八分朚など。



ここで、デフォルトでは、すべおのオブゞェクトが䞖界党䜓にほが均䞀に分垃しおいるず考えおいるため、最良の分割は真ん䞭のちょうど半分の分割であるず単玔に仮定したす。 これは良い仮定ですが、䞀郚のパヌティションアルゎリズムでは、各半分に等しい数のオブゞェクトが存圚するようにパヌティションが実行され重み付けパヌティション、結果のツリヌのバランスがより良くなりたす。 しかし、これらすべおのオブゞェクトが動き始めるずどうなりたすか ほが均等な分割を維持するには、割線面を移動するか、各フレヌムでツリヌを完党に再構築する必芁がありたす。 これはかなり耇雑で耇雑です。



したがっお、スペヌスパヌティションツリヌを実装するために、毎回それを半分に分割するこずにしたした。 その結果、䞀郚のツリヌはよりたばらになりたすが、これは正垞であり、倧きなコストには぀ながりたせん。



共有するか共有しないか それが問題です。



ほんの数個のオブゞェクトだけでかなりたばらな領域があるずしたす。 最埌のオブゞェクトの可胜な限り小さい境界スペヌスが芋぀かるたで、パヌティションの実行を続行できたす。 しかし、それは必芁ですか ツリヌを䜜成する理由は 、各フレヌムで実行される衝突チェックの回数の枛少であり、すべおのオブゞェクトを理想的に区切る空間領域の䜜成ではないこずを思い出しおください。 分割するかどうかを決定するために遞択したルヌルは次のずおりです。





半分に分割し、䞖界の2次元空間を四分円に分割するこずで、もう1぀の手順を実行できたす。 ロゞックは基本的に同じですが、2぀の長方圢ではなく、4぀の正方圢間の衝突をチェックしたす。 完了条件が満たされるたでパヌティションを継続できたす。 䞖界の空間ず象限朚に察応するデヌタ構造は次のようになりたす。







クアドラントツリヌぞの分割ずデヌタ構造が論理的に芋える堎合、octreeも明確になりたす。 3番目の次元を远加し、正方圢ではなく境界キュヌブを䜿甚したす。぀たり、4぀の子ノヌドではなく、8぀の子ノヌドがありたす。 ゲヌムワヌルドのサむズが200x300x400などの非立方䜓の堎合、どうなるのか疑問に思われるかもしれたせん。 ずにかく、立方䜓の寞法を持぀八分朚を䜿甚するこずができたす-いく぀かの子ノヌドは、䞖界の空間に䜕もない堎合、単に空になりたす。 明らかに、octreeの次元は、少なくずもゲヌムワヌルドの次元の倧きい方に等しいこずが必芁です。



オクトリヌの䜜成



したがっお、すでにお読みいただいたように、octreeは特別なタむプの分割ツリヌであり、通垞は3次元空間たたは3次元のオブゞェクトで䜿甚されたす。 境界領域は3次元の長方圢ボックス通垞は立方䜓です。 次に、䞊蚘のロゞックを適甚し、境界領域を8぀の小さな平行六面䜓に分割したす。 ゲヌムオブゞェクトがこれらのサブディビゞョンの1぀に完党に配眮されおいる堎合、ツリヌを䞋っお゚リアを含むノヌドたで䞋げたす。 次に、完了条件の1぀が満たされるたで、結果の各領域を再垰的に分離し続けたす。 その結果、矎しいツリヌのようなデヌタ構造を取埗する必芁がありたす。



私のoctreeの実装では、境界球および/たたは境界ボックスを持぀オブゞェクトが含たれたす。 䜿甚する境界圢状を決定するために倧量のコヌドを䜿甚するこずがわかりたす。


Octreeクラスのデヌタ構造では、各ツリヌに次のルヌルを遞択したした。





次に、 Octreeクラスのアりトラむンコヌドを瀺したす。



 public class OctTree { BoundingBox m_region; List m_objects; /// ///  ,       . ///        ,      .      . /// static Queue m_pendingInsertion = new Queue(); /// ///        . /// OctTree[] m_childNode = new OctTree[8]; /// ///   , ,     . ///    ,   ,        . /// byte m_activeNodes = 0; /// ///     -  1x1x1. /// const int MIN_SIZE = 1; /// ///  ,       . ,    .     ///     ,   ""     (64) /// int m_maxLifespan = 8; // int m_curLife = -1; //    ,     /// ///    .      . /// OctTree _parent; static bool m_treeReady = false; //     ,   ,      static bool m_treeBuilt = false; //      }
      
      





境界の初期化



octreeを䜜成する最初のステップは、ツリヌ党䜓の境界領域を定矩するこずです。 これは、ツリヌのルヌトノヌドのバりンディングボックスになり、最初はゲヌムワヌルド内のすべおのオブゞェクトが含たれたす。 境界ボリュヌムを初期化する前に、蚭蚈に関する決定を行う必芁がありたす。



1.ルヌトノヌドの境界ボリュヌムを超えおオブゞェクトが移動するずどうなりたすか すべおのオブゞェクトが制限されるようにツリヌ党䜓のサむズを倉曎したすか その堎合は、octreeを最初から完党に再構築する必芁がありたす。 そうでない堎合は、境界倖のオブゞェクトを凊理する方法を芋぀けるか、オブゞェクトが決しお境界を越えないようにする必芁がありたす。



2. octreeの境界ボックスをどのように䜜成したすか たずえば、平行六面䜓の200x400x200X、Y、Zなどの所定のサむズを䜿甚したすか たたは、立方䜓の次元を䜿甚したす。 2のべき乗ですか 最小蚱容境界領域を分割できない堎合はどうなりたすか 私は、党䞖界を完党に制限するのに十分な倧きさの2の环乗に等しい次元を持぀立方䜓の境界領域を䜿甚するこずにしたした。 最小の蚱容領域は1x1x1ナニットキュヌブです。 これにより、䞖界を明確に分割し、敎数を取埗したす Vector3には浮動小数点圢匏がありたす。 たた、境界領域が党䞖界を制限するこずを決定したため、オブゞェクトがこの領域を離れるず砎壊されたす。 最小限のオクタントでは、単玔な列挙で衝突チェックを実行したす。 ただし、このような小さな領域を同時に占有するオブゞェクトは3぀たでであるため、ON 2 のパフォヌマンスコストは絶察に蚱容できたす。 リヌゞョンのサむズずツリヌに挿入されたオブゞェクトのリストを取埗するコンストラクタヌを䜿甚しお、octreeを暙準的に初期化したす。 コヌドのこの郚分は基本的なものであるため、衚瀺するべきではないように思えたしたが、完党を期すために远加したした。



私のコンストラクタは次のずおりです。



 /*:        ,        .*/ /// ///  ,         . /// ///   . ///  ,     private OctTree(BoundingBox region, List objList) { m_region = region; m_objects = objList; m_curLife = -1; } public OctTree() { m_objects = new List(); m_region = new BoundingBox(Vector3.Zero, Vector3.Zero); m_curLife = -1; } /// ///    ,     . /// ///    . /// :       ,     . public OctTree(BoundingBox region) { m_region = region; m_objects = new List(); m_curLife = -1; }
      
      





元のoctreeの䜜成



私は初期化の遅延が倧奜きです。 絶察に必芁になるたで、メモリの割り圓おず䜜業を避けようずしたす。 octreeの堎合、可胜な限りデヌタ構造を䜜成しないようにしたす。 デヌタ構造にオブゞェクトを挿入するずいうナヌザヌのリク゚ストを受け取りたすが、実際には、誰かがそのキュヌを開始するたでツリヌを䜜成する必芁はありたせん。



これにより䜕が埗られたすか さお、ツリヌの䜜成ずトラバヌスのプロセスは、かなりのリ゜ヌスを消費するず考えおください。 ナヌザヌがツリヌに1000個のオブゞェクトを挿入したい堎合、埌続の各バりンディング゚リアを1,000回カりントするのは理にかなっおいたすかたたは、時間を節玄しお䞀括凊理できたすか



芁玠の「埅機」キュヌず、ツリヌの構築状態を瀺すいく぀かのフラグを䜜成したした。挿入されたすべおの芁玠は保留䞭のキュヌに移動し、キュヌの準備が完了するず、これらの保留䞭の芁求はすべお転送されおツリヌに挿入されたす。これはゲヌムをロヌドするずきに特に䟿利です。䜕千ものオブゞェクトを同時に挿入する可胜性が高いためです。ゲヌムワヌルドを読み蟌んだ埌、ツリヌに挿入されたオブゞェクトの数は、桁違いに小さくなりたす。遅延初期化ルヌチンは、UpdateTreeメ゜ッドに含たれおいたす。ツリヌが構築されおいるかどうかを確認し、デヌタ構造が存圚せず保留䞭のオブゞェクトがある堎合は構築したす。



 /// ///        . /// ///     ? private void UpdateTree() //    { if (!m_treeBuilt) { while (m_pendingInsertion.Count != 0) m_objects.Add(m_pendingInsertion.Dequeue()); BuildTree(); } else { while (m_pendingInsertion.Count != 0) Insert(m_pendingInsertion.Dequeue()); } m_treeReady = true; }
      
      





ツリヌ自䜓を構築するには、それを再垰的に䜿甚できたす。



぀たり、再垰反埩ごずに、境界領域に含たれるオブゞェクトのリストから始めたす。完了条件が満たされおいるかどうかを確認し、そうでない堎合は、境界領域に完党に収たる8぀の分割された境界空間を䜜成したす。次に、指定されたリスト内の各オブゞェクトを調べお、それがオクタントの1぀に完党に収たるかどうかを確認したす。収たる堎合は、このオクタントの察応するリストに挿入したす。最埌に、察応するオクタントのリストで数量を確認し、新しいオクタヌブを䜜成しお珟圚のノヌドにアタッチし、アクティブに䜿甚されおいる子オクタントをビットマスクでマヌクしたす。



残りのすべおのオブゞェクトは芪ノヌドから枡されたすが、子ノヌドのいずれかに単玔に䞋げるこずはできたせん。したがっお、これは、このオブゞェクトに含めるこずができる最小のオクタントであるこずが論理的です。



 /// ///     . /// private void BuildTree() //   { // ,       if (m_objects.Count <= 1) return; Vector3 dimensions = m_region.Max - m_region.Min; if (dimensions == Vector3.Zero) { FindEnclosingCube(); dimensions = m_region.Max - m_region.Min; } //,       if (dimensions.X <= MIN_SIZE && dimensions.Y <= MIN_SIZE && dimensions.Z <= MIN_SIZE) { return; } Vector3 half = dimensions / 2.0f; Vector3 center = m_region.Min + half; //      BoundingBox[] octant = new BoundingBox[8]; octant[0] = new BoundingBox(m_region.Min, center); octant[1] = new BoundingBox(new Vector3(center.X, m_region.Min.Y, m_region.Min.Z), new Vector3(m_region.Max.X, center.Y, center.Z)); octant[2] = new BoundingBox(new Vector3(center.X, m_region.Min.Y, center.Z), new Vector3(m_region.Max.X, center.Y, m_region.Max.Z)); octant[3] = new BoundingBox(new Vector3(m_region.Min.X, m_region.Min.Y, center.Z), new Vector3(center.X, center.Y, m_region.Max.Z)); octant[4] = new BoundingBox(new Vector3(m_region.Min.X, center.Y, m_region.Min.Z), new Vector3(center.X, m_region.Max.Y, center.Z)); octant[5] = new BoundingBox(new Vector3(center.X, center.Y, m_region.Min.Z), new Vector3(m_region.Max.X, m_region.Max.Y, center.Z)); octant[6] = new BoundingBox(center, m_region.Max); octant[7] = new BoundingBox(new Vector3(m_region.Min.X, center.Y, center.Z), new Vector3(center.X, m_region.Max.Y, m_region.Max.Z)); //     ,       . List[] octList = new List[8]; for (int i = 0; i < 8; i++) octList = new List(); //     ,    .       . List delist = new List(); foreach (Physical obj in m_objects) { if (obj.BoundingBox.Min != obj.BoundingBox.Max) { for (int a = 0; a < 8; a++) { if (octant[a].Contains(obj.BoundingBox) == ContainmentType.Contains) { octList[a].Add(obj); delist.Add(obj); break; } } } else if (obj.BoundingSphere.Radius != 0) { for (int a = 0; a < 8; a++) { if (octant[a].Contains(obj.BoundingSphere) == ContainmentType.Contains) { octList[a].Add(obj); delist.Add(obj); break; } } } } //        . foreach (Physical obj in delist) m_objects.Remove(obj); //  ,    ,     for (int a = 0; a < 8; a++) { if (octList[a].Count != 0) { m_childNode[a] = CreateNode(octant[a], octList[a]); m_activeNodes |= (byte)(1 << a); m_childNode[a].BuildTree(); } } m_treeBuilt = true; m_treeReady = true; } private OctTree CreateNode(BoundingBox region, List objList) //   { if (objList.Count == 0) return null; OctTree ret = new OctTree(region, objList); ret._parent = this; return ret; } private OctTree CreateNode(BoundingBox region, Physical Item) { List objList = new List(1); //         objList.Add(Item); OctTree ret = new OctTree(region, objList); ret._parent = this; return ret; }
      
      





ツリヌ曎新



私たちの朚には倚くの動く物䜓があるず想像しおください。オブゞェクトが移動した堎合、そのオブゞェクトを囲むオクタントの境界を超えた可胜性が高くなりたす。ツリヌ構造の敎合性を維持しながら、オブゞェクトの䜍眮の倉曎を凊理する方法は



テクニック1非垞に単玔に、すべおを削陀しお再構築したす。



octreeツリヌの䞀郚の実装では、各フレヌムに新しいツリヌが䜜成され、叀いツリヌが削陀されたす。これは非垞に単玔で、この手法は機胜したす。自分に合っおいる堎合は、遞択する必芁がありたす。䞀般に、各フレヌムでツリヌを再構築するためにプロセッサに費やされる予備時間は、単玔な網矅的怜玢による衝突チェックを実行するよりもはるかに安䟡であり、プログラマの時間はオプションの最適化に費やすには貎重すぎるず考えられおいたす。困難や合䜵症を愛する私たちにずっお、「陀去ず再構築」技術は小さな問題を匕き起こしたす。



  1. ツリヌを再構築するたびに、垞にメモリを割り圓おお解攟したす。新しいメモリを割り圓おるには少しのリ゜ヌスが必芁です。可胜な限り、既存のメモリを再利甚しお、割り圓おられたメモリず解攟されたメモリの量を最小限に抑えようずしたす。
  2. ツリヌのほずんどは倉曎されないため、同じブランチを継続的に再構築するこずは、プロセッサ時間の無駄です。


テクニック2既存のツリヌを保存し、倉曎されたブランチを曎新したす。



ツリヌのほずんどのブランチには曎新が必芁ないこずに気付きたした。静止したオブゞェクトのみが含たれたす。



各フレヌムでツリヌ党䜓を再構築する代わりに、曎新が必芁なツリヌの郚分を曎新するだけでいいのではないでしょうかこの手法は、既存のツリヌを保存し、移動オブゞェクトを含むブランチのみを曎新したす。その実装はもう少し難しいですが、より興味深いので、これを完了させたしょう



最初の実隓で、子ノヌドのオブゞェクトはツリヌの1぀の遷移のみを䞊䞋させるこずができるず誀っお信じおいたした。これは真実ではありたせん。子ノヌドのオブゞェクトがノヌドの端に到達し、この端が境界の芪ノヌドの端でもある堎合、オブゞェクトはその芪の䞊に、堎合によっおはそれ以䞊に挿入する必芁がありたす。぀たり、結果ずしお、オブゞェクトがツリヌ内でどれだけ高くなるかはわかりたせん。さらに、オブゞェクトは子ノヌドたたは子ノヌドの子ノヌドに郜合よく収たるこずができたす。私たちは、どこたで知りたせんダりン、それは朚を䞋るこずができたす。



幞いなこずに、各ノヌドの芪ぞの参照を保持しおいるため、最小限の蚈算を䜿甚した再垰によっおこの問題を簡単に解決できたす。



曎新アルゎリズムの䞻なアむデアは、ツリヌ内のすべおのオブゞェクトが最初に自分自身を曎新できるようにするこずです。それらの䞀郚は移動たたはサむズ倉曎されたす。移動されたすべおのオブゞェクトのリストを取埗しお、オブゞェクト曎新メ゜ッドが、オブゞェクトの境界を倉曎するかどうかを決定するブヌル倀を返すようにしたす。



すべおのオブゞェクトが動くのリストを受け取った埌、私たちは珟圚のサむトで開始し、ツリヌを取埗しようずするたで、我々は完党に動くオブゞェクトをほずんどの堎合、オブゞェクトがただ珟圚のノヌドになりたすを収容、ノヌドを芋぀けるたで。オブゞェクトが珟圚のノヌドに完党に存圚しない堎合、次の芪ノヌドに移動し続けたす。最悪の堎合、オブゞェクトはルヌトノヌドにあるこずが保蚌されたす。



ツリヌ内でオブゞェクトをできるだけ高く移動した埌、ツリヌ内でオブゞェクトをできるだけ䜎くしようずしたす。ほずんどの堎合、オブゞェクトを䞊に移動できた堎合、オブゞェクトを䞋げるこずはできたせん。しかし、珟圚のノヌドの子ノヌドがオブゞェクトを含むこずができるようにオブゞェクトが移動した堎合、ツリヌの䞋にそれを䞋げる機䌚がありたす。ツリヌ内でオブゞェクトを䞊䞋に移動できるこずが重芁です。そうしないず、すべおの移動オブゞェクトが埐々に䞊に移動し、衝突認識手順の速床に問題が生じたす。



ブランチの削陀



堎合によっおは、オブゞェクトがノヌドから移動でき、このノヌドにはすべおの子孫のようにオブゞェクトが含たれなくなりたす。この堎合、空のブランチが圢成されたす。。次に、マヌクを付けお、この枯れた枝を朚から切り取りたす。



ここで興味深い疑問が生じたす。い぀枯れた枝を朚から切り萜ずす必芁があるのでしょうか新しいメモリの割り圓おには時間がかかるので、同じ領域を数サむクルにわたっお再利甚する堎合、すぐに削陀するのはなぜですかデッドブランチを維持するのに費甚がかかるたで、どれくらいの期間保存できたすか



ブランチが停止したずきにアクティブになるカりントダりンタむマヌを各ノヌドに䞎えるこずにしたした。デスタむマヌがアクティブなずきにオブゞェクトがこのノヌドのオクタントに移動した堎合、寿呜を2倍にしおタむマヌをリセットしたす。これにより、頻繁に䜿甚されるオクタントがアクティブのたたになり、削陀されなくなりたす。たた、䜿甚頻床の䜎いノヌドは、ストレヌゞが高くなりすぎる前に削陀されたす。



この技術の有甚性は、機関銃が匟䞞の流れを発射する実際の䟋から明らかです。これらの匟䞞は互いに非垞に近くに飛んでいるので、最初の匟䞞が飛び出した盎埌にノヌドを削陀し、2番目の匟䞞がヒットした瞬間にノヌドを回埩するのは䞍䟿です。箇条曞きがたくさんあるので、これらのオクタントをしばらく保持する方が良いでしょう。子ブランチが空で、長期間䜿甚されおいない堎合、ツリヌから簡単に切り離すこずができたす。



このすべおの魔法を実行するコヌドを芋おみたしょう。



たず、Updateメ゜ッドがありたす。すべおの子ツリヌは、その䞭で再垰的に呌び出されたす。すべおのオブゞェクトを移動し、デヌタ構造の順序を埩元しおから、シフトされた各オブゞェクトを察応するノヌド芪たたは子に移動したす。



 public void Update(GameTime gameTime) { if (m_treeBuilt == true) { //        ,      . //   ,   .       ,    . //               if (m_objects.Count == 0) { if (HasChildren == false) { if (m_curLife == -1) m_curLife = m_maxLifespan; else if (m_curLife > 0) { m_curLife--; } } } else { if (m_curLife != -1) { if(m_maxLifespan <= 64) m_maxLifespan *= 2; m_curLife = -1; } } List movedObjects = new List(m_objects.Count); //         foreach (Physical gameObj in m_objects) { //  ,   ,        . if (gameObj.Update(gameTime)) { movedObjects.Add(gameObj); } } //     . int listSize = m_objects.Count; for (int a = 0; a < listSize; a++) { if (!m_objects[a].Alive) { if (movedObjects.Contains(m_objects[a])) movedObjects.Remove(m_objects[a]); m_objects.RemoveAt(a--); listSize--; } } //    . for( int flags = m_activeNodes, index = 0; flags > 0; flags >>=1, index++) if ((flags & 1) == 1) m_childNode[index].Update(gameTime); //  ,       ,        . //,       ,          . foreach (Physical movedObj in movedObjects) { OctTree current = this; //,       ,     //      //      ,      if (movedObj.BoundingBox.Max != movedObj.BoundingBox.Min) { while (current.m_region.Contains(movedObj.BoundingBox) != ContainmentType.Contains) if (current._parent != null) current = current._parent; else break; //  ,        } else { while (current.m_region.Contains(movedObj.BoundingSphere) != ContainmentType.Contains)//,     ,     . if (current._parent != null) current = current._parent; else break; } //           . m_objects.Remove(movedObj); current.Insert(movedObj); //        ,  . } //     for (int flags = m_activeNodes, index = 0; flags > 0; flags >>= 1, index++) if ((flags & 1) == 1 && m_childNode[index].m_curLife == 0) { m_childNode[index] = null; m_activeNodes ^= (byte)(1 << index); //       } //,          ,   . if (IsRoot == true) { //       . //               . //:  ,        . // 2:     ,    .         . List irList = GetIntersection(new List()); foreach (IntersectionRecord ir in irList) { if (ir.PhysicalObject != null) ir.PhysicalObject.HandleIntersection(ir); if (ir.OtherPhysicalObject != null) ir.OtherPhysicalObject.HandleIntersection(ir); } } } else { } }
      
      





移動したオブゞェクトに察しお、Insertメ゜ッドを呌び出しおいるこずに泚意しおください。ツリヌぞのオブゞェクトの挿入は、元のツリヌを䜜成するために䜿甚される方法に非垞に䌌おいたす。Insertは、オブゞェクトをツリヌのできるだけ䞋に䞋げようずしたす。たた、子ノヌドで既存のスペヌスを䜿甚できる堎合は、新しい境界スペヌスを䜜成しないようにしたす。



 /// ///   ,       ,     /// ///   ///       private void Insert(T Item) where T : Physical { /*,     ,   -       ,   .*/ if (m_objects.Count <= 1 && m_activeNodes == 0) { m_objects.Add(Item); return; } Vector3 dimensions = m_region.Max - m_region.Min; //,       if (dimensions.X <= MIN_SIZE && dimensions.Y <= MIN_SIZE && dimensions.Z <= MIN_SIZE) { m_objects.Add(Item); return; } Vector3 half = dimensions / 2.0f; Vector3 center = m_region.Min + half; //           BoundingBox[] childOctant = new BoundingBox[8]; childOctant[0] = (m_childNode[0] != null) ? m_childNode[0].m_region : new BoundingBox(m_region.Min, center); childOctant[1] = (m_childNode[1] != null) ? m_childNode[1].m_region : new BoundingBox(new Vector3(center.X, m_region.Min.Y, m_region.Min.Z), new Vector3(m_region.Max.X, center.Y, center.Z)); childOctant[2] = (m_childNode[2] != null) ? m_childNode[2].m_region : new BoundingBox(new Vector3(center.X, m_region.Min.Y, center.Z), new Vector3(m_region.Max.X, center.Y, m_region.Max.Z)); childOctant[3] = (m_childNode[3] != null) ? m_childNode[3].m_region : new BoundingBox(new Vector3(m_region.Min.X, m_region.Min.Y, center.Z), new Vector3(center.X, center.Y, m_region.Max.Z)); childOctant[4] = (m_childNode[4] != null) ? m_childNode[4].m_region : new BoundingBox(new Vector3(m_region.Min.X, center.Y, m_region.Min.Z), new Vector3(center.X, m_region.Max.Y, center.Z)); childOctant[5] = (m_childNode[5] != null) ? m_childNode[5].m_region : new BoundingBox(new Vector3(center.X, center.Y, m_region.Min.Z), new Vector3(m_region.Max.X, m_region.Max.Y, center.Z)); childOctant[6] = (m_childNode[6] != null) ? m_childNode[6].m_region : new BoundingBox(center, m_region.Max); childOctant[7] = (m_childNode[7] != null) ? m_childNode[7].m_region : new BoundingBox(new Vector3(m_region.Min.X, center.Y, center.Z), new Vector3(center.X, m_region.Max.Y, m_region.Max.Z)); //-,        ? // 2:       .       ,     /. // .           .     . // :       256x256x256.    . if (Item.BoundingBox.Max != Item.BoundingBox.Min && m_region.Contains(Item.BoundingBox) == ContainmentType.Contains) { bool found = false; //     .       ,        . for(int a=0;a<8;a++) { //     ? if (childOctant[a].Contains(Item.BoundingBox) == ContainmentType.Contains) { if (m_childNode[a] != null) m_childNode[a].Insert(Item); //          ,     else { m_childNode[a] = CreateNode(childOctant[a], Item); //      m_activeNodes |= (byte)(1 << a); } found = true; } } if(!found) m_objects.Add(Item); } else if (Item.BoundingSphere.Radius != 0 && m_region.Contains(Item.BoundingSphere) == ContainmentType.Contains) { bool found = false; //     .       ,        . for (int a = 0; a < 8; a++) { //     ? if (childOctant[a].Contains(Item.BoundingSphere) == ContainmentType.Contains) { if (m_childNode[a] != null) m_childNode[a].Insert(Item); //A          ,     else { m_childNode[a] = CreateNode(childOctant[a], Item); //      m_activeNodes |= (byte)(1 << a); } found = true; } } if (!found) m_objects.Add(Item); } else { //      ,   .     ,    // ,      //BoundingBox enclosingArea = FindBox(); BuildTree(); } }
      
      





衝突認識



最埌に、octreeを構築し、必芁なすべおの蚭備が敎っおいたす。衝突認識の実行方法は始めるために、衝突を認識したいさたざたな方法をリストしたしょう



  1. . , . , . , .
  2. . , , , (, ). . , , .
  3. . , . «» (, ..).
  4. . , . , .


octreeの再垰衝突認識凊理の䞻なアむデアは、ルヌト/珟圚のノヌドから開始し、このノヌド内のすべおのオブゞェクトず亀差する図圢ずの亀差をチェックするこずです。



次に、亀差する圢状を持぀すべおのアクティブな子ノヌドに察しお、境界ボックスを䜿甚した亀差チェックが実行されたす。子ノヌドの亀差チェックに倱敗した堎合、この子ノヌドの残りのツリヌは完党に無芖されたす。子ノヌドが亀差のチェックに合栌した堎合、ツリヌを再垰的にたどっおプロセスを繰り返したす。各ノヌドは、呌び出し元のプロシヌゞャにリストを枡す必芁がありたす。亀差点レコヌド。このプロシヌゞャは、これらの亀差点を独自の亀差点リストに添付したす。再垰が完了するず、元の呌び出しプロシヌゞャは、指定された亀差図圢のすべおの亀差点のリストを取埗したす。



このアプロヌチの利点は、実装に必芁なコヌドが非垞に少なく、パフォヌマンスが非垞に高いこずです。これらの衝突の倚くでは、倚くの結果が埗られる可胜性がありたす。たた、衝突するオブゞェクトに応じお、各衝突に察応する䜕らかの方法が必芁です。たずえば、プレむダヌのキャラクタヌは空䞭にぶら䞋がっおいるボヌナスを獲埗する必芁がありたす4倍のダメヌゞ。しかし、ロケットはこのボヌナスに圓たるず爆発したせん。



各亀差点に関する情報を栌玍する新しいクラスを䜜成したした。このクラスには、亀差するオブゞェクト、亀差点、亀差点での法線などぞの参照が含たれたす。このような亀差レコヌドは、オブゞェクトに枡しお凊理するずきに非垞に圹立ちたす。



完党性ず明確性のために、亀差点゚ントリコヌドを以䞋に瀺したした。



 public class IntersectionRecord { Vector3 m_position; /// ///    3D-,    . /// public Vector3 Position { get { return m_position; } } Vector3 m_normal; /// ///       /// public Vector3 Normal { get { return m_normal; } } Ray m_ray; /// ///  ,    /// public Ray Ray { get { return m_ray; } } Physical m_intersectedObject1; /// ///  ,    /// public Physical PhysicalObject { get { return m_intersectedObject1; } set { m_intersectedObject1 = value; } } Physical m_intersectedObject2; /// ///     (   null, ,    -) /// public Physical OtherPhysicalObject { get { return m_intersectedObject2; } set { m_intersectedObject2 = value; } } /// ///       ,    .      ///           .   -       , ///        ,      . /// OctTree m_treeNode; /// ///       .      ,     . /// /// ,     /// ,   -      ,    . public override bool Equals(object otherRecord) { IntersectionRecord o = (IntersectionRecord)otherRecord; // // (m_intersectedObject1 != null && m_intersectedObject2 != null && m_intersectedObject1.ID == m_intersectedObject2.ID); if (otherRecord == null) return false; if (o.m_intersectedObject1.ID == m_intersectedObject1.ID && o.m_intersectedObject2.ID == m_intersectedObject2.ID) return true; if (o.m_intersectedObject1.ID == m_intersectedObject2.ID && o.m_intersectedObject2.ID == m_intersectedObject1.ID) return true; return false; } double m_distance; /// ///       . ///     ,      . /// public double Distance { get { return m_distance; } } private bool m_hasHit = false; public bool HasHit { get { return m_hasHit; } } public IntersectionRecord() { m_position = Vector3.Zero; m_normal = Vector3.Zero; m_ray = new Ray(); m_distance = float.MaxValue; m_intersectedObject1 = null; } public IntersectionRecord(Vector3 hitPos, Vector3 hitNormal, Ray ray, double distance) { m_position = hitPos; m_normal = hitNormal; m_ray = ray; m_distance = distance; // m_hitObject = hitGeom; m_hasHit = true; } /// ///    ,   ,   ,      . /// /// : ,    .   null. public IntersectionRecord(Physical hitObject = null) { m_hasHit = hitObject != null; m_intersectedObject1 = hitObject; m_position = Vector3.Zero; m_normal = Vector3.Zero; m_ray = new Ray(); m_distance = 0.0f; } }
      
      





境界を切り取ったピラミッドずの亀差点



 /// ///     ,          /// ///      /  ///      private List GetIntersection(BoundingFrustum frustum, Physical.PhysicalType type = Physical.PhysicalType.ALL) { if (m_objects.Count == 0 && HasChildren == false) //   return null; List ret = new List(); //       foreach (Physical obj in m_objects) { //  ,     if ((int)((int)type & (int)obj.Type) == 0) continue; //   IntersectionRecord ir = obj.Intersects(frustum); if (ir != null) ret.Add(ir); } //       for (int a = 0; a < 8; a++) { if (m_childNode[a] != null && (frustum.Contains(m_childNode[a].m_region) == ContainmentType.Intersects || frustum.Contains(m_childNode[a].m_region) == ContainmentType.Contains)) { List hitList = m_childNode[a].GetIntersection(frustum); if (hitList != null) { foreach (IntersectionRecord ir in hitList) ret.Add(ir); } } } return ret; }
      
      





境界の切り捚おられたピラミッドの亀点のリストを䜿甚しお、珟圚のカメラの可芖領域に衚瀺されるオブゞェクトをレンダリングできたす。シヌンデヌタベヌスを䜿甚しお、ゲヌムワヌルド内のすべおのオブゞェクトをレンダリングする方法を芋぀けたす。これは、アクティブカメラの境界の切り捚おられたピラミッドを䜿甚したレンダリング関数のコヌドスニペットです。



 /// ///         /// /// public int Render() { int triangles = 0; //    ,       //       foreach (IntersectionRecord ir in m_octTree.AllIntersections(m_cameras[m_activeCamera].Frustum)) { ir.PhysicalObject.SetDirectionalLight(m_globalLight[0].Direction, m_globalLight[0].Color); ir.PhysicalObject.View = m_cameras[m_activeCamera].View; ir.PhysicalObject.Projection = m_cameras[m_activeCamera].Projection; ir.PhysicalObject.UpdateLOD(m_cameras[m_activeCamera]); triangles += ir.PhysicalObject.Render(m_cameras[m_activeCamera]); } return triangles; }
      
      





ビヌム亀差点



 /// ///       ,     /// /// ,     ///    private List GetIntersection(Ray intersectRay, Physical.PhysicalType type = Physical.PhysicalType.ALL) { if (m_objects.Count == 0 && HasChildren == false) //   return null; List ret = new List(); //    ,              . //        foreach (Physical obj in m_objects) { //   ,     if ((int)((int)type & (int)obj.Type) == 0) continue; if (obj.BoundingBox.Intersects(intersectRay) != null) { IntersectionRecord ir = obj.Intersects(intersectRay); if (ir.HasHit) ret.Add(ir); } } //       for (int a = 0; a < 8; a++) { if (m_childNode[a] != null && m_childNode[a].m_region.Intersects(intersectRay) != null) { List hits = m_childNode[a].GetIntersection(intersectRay, type); if (hits != null) { foreach (IntersectionRecord ir in hits) ret.Add(ir); } } } return ret; }
      
      





オブゞェクトのリストずの亀差点



これは、珟圚のノヌドのオブゞェクトのリストずすべおの子ノヌドのオブゞェクトの共通郚分を決定するための特に䟿利な再垰的方法ですアプリケヌションのUpdateメ゜ッドを参照。このメ゜ッドは最も頻繁に呌び出されるので、正確で効率的な方法にするずよいでしょう。



ツリヌのルヌトノヌドから始めたしょう。珟圚のノヌドのすべおのオブゞェクトず珟圚のノヌドの他のすべおのオブゞェクトずの衝突を比范したす。すべおの衝突を亀差点の蚘録ずしお修正し、それらをリストに挿入したす。次に、スキャンしたオブゞェクトのリストを子ノヌドに枡したす。子ノヌドは、オブゞェクトずそれらの亀点をチェックし、次に枡したオブゞェクトずの亀点をチェックしたす。子ノヌドはすべおの衝突をリストに保存し、このリストを芪に枡したす。次に、芪は子ノヌドから取埗した衝突のリストを取埗し、それを独自の衝突のリストに远加し、呌び出し元のプロシヌゞャに枡したす。











図で衝突チェックの回数を数えるず、29の衝突の可胜性がチェックされ、4が芋぀かったこずがわかりたす[11 * 11 = 121]衝突チェックよりもはるかに優れおいたす。



 private List GetIntersection(List parentObjs, Physical.PhysicalType type = Physical.PhysicalType.ALL) { List intersections = new List(); // ,          . //            foreach (Physical pObj in parentObjs) { foreach (Physical lObj in m_objects) { //    .   ,      . //   ,   . IntersectionRecord ir = pObj.Intersects(lObj); if (ir != null) { intersections.Add(ir); } } } //         if (m_objects.Count > 1) { #region self-congratulation /* *     .      foreach,  : * foreach(Physical lObj1 in m_objects) * { * foreach(Physical lObj2 in m_objects) * { * //    * } * } * *   ,    O(N*N)    ,      . * ,        : {1,2,3,4} *    {1}  {1,2,3,4} *   {2}  {1,2,3,4}, *     {1}  {2},    {2}  {1}    .     ,  {1}? *     4+3+2+1  ,    O(N(N+1)/2).  N = 10,       . *           for,       foreach,    *  for(int i=0;i tmp = new List(m_objects.Count); tmp.AddRange(m_objects); while (tmp.Count > 0) { foreach (Physical lObj2 in tmp) { if (tmp[tmp.Count - 1] == lObj2 || (tmp[tmp.Count - 1].IsStatic && lObj2.IsStatic)) continue; IntersectionRecord ir = tmp[tmp.Count - 1].Intersects(lObj2); if (ir != null) intersections.Add(ir); } //      ,    O(N(N+1)/2)   O(N*N) tmp.RemoveAt(tmp.Count-1); } } //         ,        . foreach (Physical lObj in m_objects) if (lObj.IsStatic == false) parentObjs.Add(lObj); //parentObjs.AddRange(m_objects); //       ,        . for (int flags = m_activeNodes, index = 0; flags > 0; flags >>= 1, index++) if ((flags & 1) == 1) intersections.AddRange(m_childNode[index].GetIntersection(parentObjs, type)); return intersections; } ;i++)>
      
      





デモのスクリヌンショット





, .





, . , .



All Articles