半径内のすべてのエンティティを効率的に継続的に見つける方法は?


14

非常に多数のエンティティ(ユニット)があります。各ステップで、各ユニットは近くのすべてのユニットの位置を知る必要があります(距離は定数Rより小さい場合)。すべてのユニットは連続的に移動します。これは3Dです。

平均すると、特定の制約を持つ他の特定のユニットの近くに、合計ユニット数の1%が存在します。

ブルートフォースなしでこれを効率的に行うにはどうすればよいですか?


7
:あなたは、空間分割システムのいくつかの種類をお勧めしますen.wikipedia.org/wiki/Space_partitioning
テトラッド

回答:


15

Quadtree、Octree、BSPツリー、または単純なグリッドシステムなど、共通スペースパーティションアルゴリズムのいずれかを使用します。それぞれに、特定のシナリオごとに長所と短所があります。これらの本でそれらについてもっと読むことができます。

一般的に(または私が聞いたところでは、この背後にある理由にあまり詳しくありません)、QuadtreeまたはOctreeは屋外環境により適していますが、BSPツリーは屋内シーンにより適しています。そして、Quadtreeを使用するかOctreeを使用するかの選択は、世界がどれだけ平坦であるかによって異なります。Octreeを使用してY軸の変動がほとんどない場合は無駄です。Octreeは、基本的に追加のディメンションを持つQuadtreeです。

最後に、グリッドソリューションのシンプルさを無視しないでください。多くの人は、単純なグリッドで問題を解決できる場合があることを無視し、より複雑なソリューションに直行します。

グリッドを使用するには、世界を等間隔の領域に分割し、世界の適切な領域にエンティティを保存するだけです。次に、位置を指定して、隣接するエンティティを見つけることは、検索半径と交差する領域を反復処理することです。

XZ平面で、あなたの世界が(-1000、-1000)から(1000、1000)の範囲であったとしましょう。たとえば、次のように10x10グリッドに分割できます。

var grid = new List<Entity>[10, 10];

次に、グリッド内の適切なセルにエンティティを配置します。たとえば、XZ(-1000、-1000)のエンティティはセル(0,0)に該当し、XZ(1000、1000)のエンティティはセル(9、9)に該当します。次に、ワールド内の位置と半径を指定すると、この「円」と交差するセルを判別し、単純なdouble forを使用して、それらのセルのみを反復処理できます。

とにかく、すべての選択肢を調査し、ゲームに適していると思われる選択肢を選択してください。どちらのアルゴリズムが最適かを判断するのに十分な知識がないことは認めます。

編集別のフォーラムでこれを見つけて、それがあなたの決定に役立つかもしれません:

グリッドは、大多数のオブジェクトがグリッドの正方形内に収まり、分布がかなり均一な場合に最適に機能します。逆に、オブジェクトのサイズが可変の場合、または小さな領域にクラスター化されている場合、四分木は機能します。

あなたの問題のあいまいな説明を考えると、私はグリッドソリューションにも傾いています(つまり、ユニットが小さく、かなり均一に分布していると仮定しています)。


詳細な回答をありがとう。はい、単純なグリッドソリューションで十分なようです。
OCyril

0

しばらく前にこれを書きました。現在は商用サイトにありますが、個人用のソースは無料で入手できます。それはやり過ぎかもしれませんし、Javaで書かれていますが、十分に文書化されているので、別の言語でトリムして書き直すのが難しくないはずです。基本的にOctreeを使用し、非常に大きなオブジェクトとマルチスレッドを処理するための調整を行います。

Octreeが柔軟性と効率性の最適な組み合わせを提供することがわかりました。私はグリッドから始めましたが、正方形のサイズを適切に設定することは不可能であり、空の正方形の大きなパッチはスペースと計算能力を無駄に使い果たしました。(これは2次元でした。)私のコードは複数のスレッドからのクエリを処理するため、複雑さが大幅に増しますが、ドキュメントは、必要ない場合は回避するのに役立ちます。


0

効率を高めるには、非常に安価な境界ボックスチェックを使用して、ターゲットユニットの近くにない「ユニット」の99%を簡単に拒否してください。そして、データを空間的に構造化せずにこれを行えることを願っています。したがって、すべてのユニットがフラットなデータ構造に格納されている場合、最初から最後までそのユニットをレースしようとすることができます。

関心のあるユニットの特大の境界ボックスを定義して、「近く」と見なされる可能性のないアイテムを安全に拒否できるようにします。境界ボックスからの除外のチェックは、半径のチェックよりも安くできます。ただし、これがテストされた一部のシステムでは、そうではないことがわかりました。2つはほぼ同等に機能します。これは、以下の多くの議論の後に編集されました。

最初:2Dバウンディングボックスクリップ。

// returns true if the circle supplied is completely OUTSIDE the bounding box, rectClip
bool canTrivialRejectCircle(Vertex2D& vCentre, WorldUnit radius, Rect& rectClip) {
  if (vCentre.x + radius < rectClip.l ||
    vCentre.x - radius > rectClip.r ||
    vCentre.y + radius < rectClip.b ||
    vCentre.y - radius > rectClip.t)
    return true;
  else
    return false;
}

このようなものと比較(3D):

BOOL bSphereTest(CObject3D* obj1, CObject3D* obj2 )
{
  D3DVECTOR relPos = obj1->prPosition - obj2->prPosition;
  float dist = relPos.x * relPos.x + relPos.y * relPos.y + relPos.z * relPos.z;
  float minDist = obj1->fRadius + obj2->fRadius;
  return dist <= minDist * minDist;
}.

オブジェクトが簡単に拒否されない場合は、より高価で正確な衝突テストを実行します。しかし、近さを探しているだけなので、球体テストはそれに適していますが、些細な拒絶を生き延びたオブジェクトの1%のみです。

この記事では、些細な拒否のボックスをサポートしています。 http://www.h3xed.com/programming/bounding-box-vs-bounding-circle-collision-detection-performance-as3

この線形アプローチでは、必要なパフォーマンスが得られない場合、他のポスターが話しているような階層データ構造が必要になる場合があります。Rツリーは検討する価値があります。動的な変更をサポートします。それらは空間世界のBTreeです。

あなたがそれを避けることができるなら、私はあなたがそのような複雑さを導入するすべてのそのようなトラブルに行くことを望まなかった。さらに、オブジェクトが1秒間に数回移動するときに、この複雑なデータ構造を最新の状態に保つコストについてはどうでしょうか。

グリッドは1レベルの深さの空間データ構造であることに注意してください。この制限は、真にスケーラブルではないことを意味します。世界が大きくなるにつれて、カバーする必要があるセルの数も増えます。最終的に、その数のセル自体がパフォーマンスの問題になります。ただし、特定のサイズの世界では、空間分割なしよりもパフォーマンスが大幅に向上します。


1
OPは、ブルートフォースアプローチを避けたいと明確に述べています。これは、最初の段落で説明したとおりです。また、バウンディングボックスチェックがバウンディング球チェックよりも安価であるとどのように考えますか?!それは間違っています。
-notlesh

はい、彼は彼のアプリケーションに階層データ構造を導入する努力をすることによって回避される総当たり攻撃を避けたいと思っています。しかし、それは多大な労力を要する可能性があります。彼がまだそれをしたくない場合、彼はブルートフォースである線形アプローチを試すことができますが、彼のリストがあまり大きくなければパフォーマンスはそれほど悪くないかもしれません。上記のコードを編集して、2Dバウンディングボックストリビアルリジェクト関数を追加しようとします。私は間違っていたとは思わない。
シアラン

GDnetへのリンクが壊れているが、標準的な球のテストは非常に安い、非常にシンプルでないブランチ:inside = (dot(p-p0, p-p0) <= r*r)
ラースViklund

代わりに上記のコードを貼り付けました。それはバウンディングボックスに比べて安いものではありません。
シアラン

1
@Ciaran正直なところ、その記事は本当に悪いようです。まず、現実的なデータでテストを行うのではなく、同じ値を何度も繰り返し使用します。実際のシナリオでは発生しません。いいえ、記事によると、BBは衝突がない場合にのみ高速になります(たとえば、最初のifステートメントでチェックが失敗します)。また、あまり現実的ではありません。しかし、正直なところ、このようなことを最適化し始めているなら、間違いなく間違った場所から始めていることになります。
bummzack

0

私はこれを答えにしなければなりません。なぜなら、私はコメントしたり、賛成したりするポイントがないからです。この質問をする人の99%にとって、Ciaranによると、境界ボックスが解決策です。コンパイルされた言語では、瞬く間に100,000個の無関係なユニットを拒否します。総当たり以外のソリューションには多くのオーバーヘッドが伴います。小さい数値(たとえば1000未満)では、ブルートフォースチェックよりも処理時間が長くなります。そして、彼らは非常に多くのプログラミング時間がかかります。

質問の「非常に大きな数」が何を意味するのか、または答えを求めている他の人々がそれによって何を意味するのかはわかりません。上記の数字は控えめで、10倍することができると思います。私は個人的にはブルートフォーステクニックにかなり偏見を抱いており、それらがうまく機能することに真剣に悩まされています。しかし、数万行のコードでうまくいくと、たとえば10,000単位のユーザーが派手なソリューションで時間を無駄にしたくはありません。必要に応じて、後からいつでも空想することができます。

また、バウンディングボックスが必要としない場合、バウンディング球のチェックには乗算が必要であることに注意してください。乗算は、その性質上、加算と比較の数倍の時間がかかります。球体チェックはボックスチェックよりも高速になる言語、OS、ハードウェアの組み合わせが必ずありますが、ほとんどの場所と時間では、球体がいくつかの無関係なユニットを拒否しても、ボックスチェックはより高速でなければなりませんボックスが受け入れます。(そして、球体がより速い場合、コンパイラ/インタープリター/オプティマイザーの新しいリリースはそれを変更する可能性が非常に高いです。)


答えには何の問題もありませんが、質問には答えていません。「非ブルートフォース」アプローチが特に求められました。また、あなたはシアランがすでに書いたことを繰り返しているようで、AABB対円テストについて長いコメントの議論がありました。パフォーマンスの違いは、単に無関係です。実際の狭位相テストの量を減らすため、衝突候補のほとんどに適合する境界ボリュームを選択することをお勧めします。これは、全体的なパフォーマンスに大きな影響を与えます。
-bummzack
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.