n個のオブジェクトのシステムの衝突チェック効率を上げる方法はありますか?


9

多くの画面上のオブジェクトで構成されるゲームを作成しています。その1つがプレーヤーです。どの反復でもどのオブジェクトが衝突しているかを知る必要があります。

私はこのようなものを作りました:

for (o in objects)
{
   o.stuff();
   for (other in objects)
      if (collision(o, other))
          doStuff();

   bla.draw();
}

これにはO(n ^ 2)があり、これは悪いと言われています。これをより効率的に行うにはどうすればよいですか?私はこれをJavascriptで行っています、そしてnは通常30より低くなります、これが同じままなら問題になりますか?


3
コードを実行して、そのパフォーマンスを確認してみましたか?
thedaian

いいえ、私はそうではありません。O(n ^ 2)のために、それが悪いと推測しているだけです。
jcora

1
オブジェクトは30個だけですか?空間分割をお勧めしますが、オブジェクトが30個しかない場合は効果がありません。他の人が指摘したいくつかのマイナーな最適化がありますが、それらはすべてあなたが話している規模でのマイナーな最適化です。
ジョンマクドナルド

回答:


16

最大30個のオブジェクトのみの場合、フレームごとに2回以上同じ2つのペアを相互にチェックしないことを除いて、多くの最適化は必要ありません。以下のコードサンプルでカバーします。しかし、物理エンジンが使用するさまざまな最適化に興味がある場合は、この投稿の残りの部分を読み進めてください。

必要なのは、Octree(3Dゲームの場合)またはQuadtree(2Dゲームの場合)などの空間分割実装です。これらは世界をサブセクションに分割し、各サブセクションは、同じサイズでさらに分割されて、最小サイズに分割されます。これにより、他のどのオブジェクトが別のオブジェクトと同じ世界の領域にあるかを非常にすばやく確認でき、確認する必要がある衝突の量が制限されます。

空間分割に加えて、物理オブジェクトごとにAABB(軸に沿った境界ボックス)を作成することをお勧めします。これにより、あるオブジェクトのAABBを別のオブジェクトに対してチェックできます。これは、オブジェクト間の詳細なポリゴン単位のチェックよりもはるかに高速です。

複雑なまたは大きな物理オブジェクトの場合は、これをさらに一歩進めることができます。物理メッシュ自体を細分割して、2つのオブジェクトのAABBが重複している場合にのみチェックできる独自のAABBを各サブシェイプに与えることができます。

ほとんどの物理エンジンは、静止した物理ボディのアクティブな物理シミュレーションを無効にします。物理ボディが非アクティブ化されると、フレームごとにAABBとの衝突をチェックするだけでよく、何かがAABBと衝突すると、再アクティブ化され、より詳細な衝突チェックが行われます。これにより、シミュレーション時間が短縮されます。

また、多くの物理エンジンは「シミュレーションアイランド」を使用します。これは、互いに接近している物理ボディのグループが一緒にグループ化される場所です。シミュレーションアイランドのすべてが静止している場合、シミュレーションアイランド自体は非アクティブになります。シミュレーションアイランドの利点は、アイランドが非アクティブになると、その内部のすべてのボディが衝突のチェックを停止でき、各フレームのチェックは、何かがアイランドのAABBに入ったかどうかを確認することだけです。島のAABBに何かが入ると、島内の各ボディは衝突をチェックする必要があります。シミュレーションアイランドは、その内部のボディが自動的に動き始めた場合にも再アクティブ化します。ボディがグループの中心から十分に離れると、ボディはアイランドから削除されます。

最終的に、次のようなものが(擬似コードで)残ります。

// Go through each leaf node in the octree. This could be more efficient
// by keeping a list of leaf nodes with objects in it.
for ( node in octreeLeafNodes )
{
    // We only need to check for collision if more than one object
    // or island is in the bounds of this octree node.
    if ( node.numAABBsInBounds > 1)
    {
        for ( int i = 0; i < AABBNodes.size(); ++i )
        {
           // Using i+1 here allows us to skip duplicate checks between AABBS
           // e.g (If there are 5 bodies, and i = 0, we only check i against
           //      indexes 1,2,3,4. Once i = 1, we only check i against indexes
           //      2,3,4)
           for ( int j = i + 1; j < AABBNodes.size(); ++j )
           {
               if ( AABBOverlaps( AABBNodes[i], AABBNodes[j] ) )
               {
                   // If the AABB we checked against was a simulation island
                   // then we now check against the nodes in the simulation island

                   // Once you find overlaps between two actual object AABBs
                   // you can now check sub-nodes with each object, if you went
                   // that far in optimizing physics meshes.
               {
           }
        }
    }
}

このようなループ内にそれほど多くのループを持たないこともお勧めします。上記のサンプルは、あなたがアイデアを得るためだけのものでした。私はそれを複数の関数に分割し、上に示したものと同じ機能を提供します。

また、ループ中にAABBNodesコンテナを変更しないでください。変更すると、衝突チェックが失敗する可能性があります。これは常識のように聞こえるかもしれませんが、衝突に反応して物事が予期しない変化を引き起こすことがどれほど簡単であるかに驚くでしょう。たとえば、衝突によって、衝突しているオブジェクトの1つが十分に位置を変更して、チェックしていたOctreeノードのAABBからそれらを削除した場合、そのコンテナが変更される可能性があります。これを解決するには、チェック中に発生するすべての衝突イベントのリストを保持し、すべてのチェックが完了した後、リストを実行して衝突イベントを送信することをお勧めします。


4
読者の心を既存の方法に開放するための素晴らしく有用な技術的精度を備えた非常に一貫した回答。+1
Valkea

衝突しているオブジェクトを削除する必要がある場合はどうなりますか?コンテナを変更できますか?「破壊された」のでオブジェクトはもう必要ないので、コンテナーから削除することを意味します。衝突検出中に削除しない場合、衝突イベントを実行するためにもう1つのループが必要です。
newguy 2017

衝突するオブジェクトを削除することは問題ありませんが、シミュレーション全体で衝突パスが作成されるまで、待機することをお勧めします。通常は、削除する必要のあるオブジェクトにフラグを付けるか、削除するオブジェクトのリストを生成し、衝突シミュレーションが完了したら、それらの変更を適用します。
Nic Foster

4

この例では、オブジェクトの各ペアを複数回テストします。

0,1,2,3を含む配列を持つ非常に単純な例を見てみましょう

あなたのコードでこれを得る:

  • ループ0では、1、2、3に対してテストします
  • ループ1で、0、2、3に対してテストします===>(0-1はすでにテスト済み)
  • ループ2では、0、1、3に対してテストします===>(0-2 / 1-2はすでにテスト済み)
  • ループ3では、0、1、2に対してテストします===>(0-3 / 1-3 / 2-3はテスト済み)

次のコードを見てみましょう:

for(i=0;i<=objects.length;i++)
{
    objects[i].stuff();

    for(j=i+1;j<=objects.length;j++)
    {
        if (collision(objects[i], objects[j]))
        doStuff();
    }

    bla.draw();
}

もう一度0,1,2,3を含む配列を使用すると、次のようになります。

  • ループ0で、1、2、3に対してテストします
  • ループ1で、2、3に対してテストします
  • ループ2で3に対してテストします
  • ループ3では何もテストしません

2つ目のアルゴリズムでは6つの衝突テストがあり、前のアルゴリズムでは12の衝突テストが必要でした。


このアルゴリズムは、N(N-1)/2O(N ^ 2)パフォーマンスである比較を行います。
カイ

1
まあ、リクエストに応じて30個のオブジェクトがあるということは、870に対して465回の衝突テストが行​​われることを意味します。さらに、他の回答で提供されるソリューションはまったく同じアルゴリズムです:)
Valkea

1
@Valkea:まあ、その一部です。:)
ニックフォスター

@NicFoster:はい、あなたは正しいです;)私は、選択したオブジェクト間の衝突テストについて厳密に話していました。私はそれを書いていました。
Valkea

これは償却と呼ばれますか?まあありがとう!
jcora

3

ニーズに合わせてアルゴリズムを設計しますが、実装の詳細はカプセル化しておきます。Javascriptでも、基本的なOOPの概念が適用されます。

for N =~ 30, O(N*N)は問題ではありません。線形検索は、他の選択肢と同じくらい高速です。しかし、仮定をコードにハードコーディングしたくありません。疑似コードでは、インターフェースがあります

interface itemContainer { 
    add(BoundingBox);
    remove(BoundingBox);
    BoundingBox[] getIntersections();
}

これは、アイテムのリストで何ができるかを示しています。次に、このインターフェイスを実装するArrayContainerクラスを記述できます。JavaScriptでは、コードは次のようになります。

function ArrayContainer() { ... } // this uses an array to store my objects
ArrayContainer.prototype.add = function(box) { ... };
ArrayContainer.prototype.remove = function(box) { ... };
ArrayContainer.prototype.getIntersections = function() { ... };

function QuadTreeContainer { ... } // this uses a quadtree to store my objects
... and implement in the add/remove/getIntersections for QuadTreeContainer too

そして、300個の境界ボックスを作成し、すべての交差を取得するコードの例を次に示します。ArrayContainerとQuadTreeContainerを正しく実装した場合、コードで変更var allMyObjects = new ArrayContainer()する必要があるのはに変更することだけですvar allMyObjects = QuadTreeContainer()

var r = Math.random;
var allMyObjects = new ArrayContainer();
for(var i=0; i<300; i++)
    allMyObjects.add(new BoundingBox(r(), r()));
var intersections = allMyObjects.getIntersections();

私は先に進み、標準のArrayContainerの実装をここに打ち上げました:

http://jsfiddle.net/SKkN5/1/


注:この回答は、彼のコードベースが大きくなり、乱雑になり、管理が困難になったというBaneの不満に動機付けられました。配列とツリーの使用に関する説明にはそれほど多くはありませんが、具体的に彼がコードをより適切に整理する方法として、適切な回答であることを願っています。
ジミー

2

また、衝突する可能性のあるオブジェクトのタイプも考慮する必要があります。

たとえば、プレーヤーはおそらく自分の弾丸以外のすべてとの衝突をチェックする必要があります。ただし、敵はプレーヤーの弾丸をチェックするだけでよい場合があります。弾丸はほぼ確実に互いに衝突する必要はありません。

これを効率的に実装するには、オブジェクトタイプごとに1つずつ、オブジェクトの個別のリストを保持する必要があります。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.