SATとの3D OBBコリジョンに使用する軸の数と数


29

私は以下に基づいてSATを実装しています:

7ページの表では、衝突を見つけるためにテストする15軸を参照していますが、Ax、Ay、Azだけで、すでに衝突が発生しています。

他のすべてのケースをテストする必要があるのはなぜですか?Ax、Ay、Azだけでは不十分な状況はありますか?

回答:


56

誤検知が発生している可能性があります。衝突は検出されましたが、実際には衝突していません。

番号15は

  • オブジェクトAから3軸(面法線)
  • オブジェクトBから3軸(面法線)
  • AのエッジとBのエッジのすべてのペアからの9軸(3x3)
  • 合計= 15

9軸は、AのエッジとBのエッジの外積で構成されています

  1. Ae1 x Be1(Aのエッジ1、Bのエッジ1を交差)
  2. Ae1 x Be2
  3. Ae1 x Be3
  4. Ae2 x Be1
  5. ... 等々

最初の6軸(面法線から)は、1つのオブジェクトの角が他のオブジェクトの面と交差しているかどうかを確認するために使用されます。(またはこれらの種類の衝突を排除するためにより正確に)

エッジのクロス積によって形成される9軸のセットは、他のオブジェクトを貫通する頂点がないエッジエッジエッジ検出の検討に使用されます。下の写真の「ほぼ」衝突のように。この回答の残りの部分では、画像内の2つのボックスが実際に衝突するのではなく、わずかな距離だけ離れていると仮定しましょう。

ここに画像の説明を入力してください

SATに6つの面法線を使用した場合に何が起こるか見てみましょう。以下の最初の画像は、青いボックスの1つの軸と黄色のボックスの2つの軸を示しています。これらの軸に両方のオブジェクトを投影すると、3つすべてでオーバーラップが発生します。下の2番目の画像は、青いボックスの残りの2つの軸と黄色のボックスの残りの軸を示しています。再びこれらの軸に投影すると、3つすべてでオーバーラップが表示されます。

したがって、6つの面の法線のみをチェックすると、6つの軸すべてでオーバーラップが表示されます。これは、SATによると、オブジェクトが衝突していることを意味します。しかし、もちろん、これらのオブジェクトは衝突していません。分離を見つけられなかった理由は、私たちが一生懸命に見えなかったからです!

ここに画像の説明を入力してください ここに画像の説明を入力してください

それでは、このギャップをどのように見つけるのでしょうか?以下の画像は、両方のオブジェクトの投影が分離を明らかにする軸を示しています。

ここに画像の説明を入力してください

この軸はどこから取得しますか?

硬いカードを隙間に滑り込ませることを想像すると、そのカードは分離面の一部になります。その平面の法線に投影すると(上の図の黒い矢印)、分離が表示されます。その平面上にある2つのベクトルがあるため、その平面が何であるかを知っています)1つのベクトルは青のエッジに位置合わせされ、もう1つのベクトルは黄色のエッジに位置合わせされ、平面の法線は単純に平面上にある2つのベクトルの外積。

したがって、OOBBの場合、2つのオブジェクトのエッジの外積のすべての組み合わせ(9個)をチェックして、エッジとエッジの分離が欠落していないことを確認する必要があります。


2
素晴らしい説明!そして写真をありがとう。@Acegikmoが指摘しているように、「9軸はAのエッジとBのエッジの外積で構成されています」と言うと少し混乱します。エッジではなく法線を使用できるからです。再度ありがとう:)

5
@joeRocc正しい立方体の場合、法線とエッジが整列しているため、法線を使用しますが、他の形状(たとえば、四面体、他の多面体)の場合、法線はエッジと整列しません。
ケン

どうもありがとう!「Game Physics engine development」という素晴らしい本を読んでいて、この問題に出会いました。なぜ15軸を使用するのかわかりませんでした。どうもありがとう。今、私はそれについて自慢するのに十分自信があります。; D
アンキットシンクシュワ

11

ケンの回答ノート:

9軸は、AのエッジとBのエッジの外積で構成されています

エッジを参照するのはやや混乱します。6つの法線と比較して12のエッジがあるため、非常に同じ出力に3つのメイン法線を使用する場合もあります-エッジはすべて法線と揃っているので、代わりに使用することをお勧めします!

また、同じ軸に沿って異なる方向に向いている法線は無視されるため、3つの一意の軸が残っていることに注意してください。

もう1つ追加したいのは、テストするすべての軸を計算する前に、分離する軸が見つかった場合に早期に終了することで、この計算を最適化できることです。だから、いや、すべての軸をすべての場合にテストする必要はありませんが、それらをすべてテストする準備ができている必要があります:)

以下に、テストする軸の完全なリストを示します。2つのOBB、AおよびBが与えられます。ここで、x、yおよびzは、基底ベクトル/ 3つの一意の法線を指します。0 = x軸、1 = y軸、2 = z軸

  1. a0
  2. a1
  3. a2
  4. b0
  5. b1
  6. b2
  7. cross(a0、b0)
  8. cross(a0、b1)
  9. cross(a0、b2)
  10. cross(a1、b0)
  11. cross(a1、b1)
  12. cross(a1、b2)
  13. cross(a2、b0)
  14. cross(a2、b1)
  15. cross(a2、b2)

また、注意すべき小さな注意事項があります。

オブジェクト間の2つの軸が同じ方向を指している場合、外積はゼロベクトル{0,0,0}を提供します。

また、この部分は省略されているため、投影が重なっているかどうかを確認するための実装を次に示します。おそらくもっと良い方法がありますが、これは私にとってはうまくいきました!(UnityとそのC#APIを使用)

// aCorn and bCorn are arrays containing all corners (vertices) of the two OBBs
private static bool IntersectsWhenProjected( Vector3[] aCorn, Vector3[] bCorn, Vector3 axis ) {

    // Handles the cross product = {0,0,0} case
    if( axis == Vector3.zero ) 
        return true;

    float aMin = float.MaxValue;
    float aMax = float.MinValue;
    float bMin = float.MaxValue;
    float bMax = float.MinValue;

    // Define two intervals, a and b. Calculate their min and max values
    for( int i = 0; i < 8; i++ ) {
        float aDist = Vector3.Dot( aCorn[i], axis );
        aMin = ( aDist < aMin ) ? aDist : aMin;
        aMax = ( aDist > aMax ) ? aDist : aMax;
        float bDist = Vector3.Dot( bCorn[i], axis );
        bMin = ( bDist < bMin ) ? bDist : bMin;
        bMax = ( bDist > bMax ) ? bDist : bMax;
    }

    // One-dimensional intersection test between a and b
    float longSpan = Mathf.Max( aMax, bMax ) - Mathf.Min( aMin, bMin );
    float sumSpan = aMax - aMin + bMax - bMin;
    return longSpan < sumSpan; // Change this to <= if you want the case were they are touching but not overlapping, to count as an intersection
}

1
サイトへようこそ。ヘルプセンターをご覧ください。特に、このサイトはフォーラムではなく、他の回答に「返信」することはお勧めできません(返信する投稿の前に「返信」が表示されない場合があるため)。既存の投稿を明確にしたい場合は、独立した方法で回答を書き、コメントを使用することをお勧めします。
ジョシュ

Acegikmoを明確にしてくれてありがとう!エッジへの参照にも少し混乱しました。@ジョシュペトリーあなたはコメントの最後にスマイリーを置くべきですので、初心者はあなたがそれらをシャットダウンしていないことを知っています:)

上記のエッジと法線の私のコメントを参照してください
ケン

2

Acegikmoの答えに基づいたC#の例(ユニティAPIを使用):

using UnityEngine;

public class ObbTest : MonoBehaviour
{
 public Transform A;
 public Transform B;

 void Start()
 {
      Debug.Log(Intersects(ToObb(A), ToObb(B)));
 }

 static Obb ToObb(Transform t)
 {
      return new Obb(t.position, t.localScale, t.rotation);
 }

 class Obb
 {
      public readonly Vector3[] Vertices;
      public readonly Vector3 Right;
      public readonly Vector3 Up;
      public readonly Vector3 Forward;

      public Obb(Vector3 center, Vector3 size, Quaternion rotation)
      {
           var max = size / 2;
           var min = -max;

           Vertices = new[]
           {
                center + rotation * min,
                center + rotation * new Vector3(max.x, min.y, min.z),
                center + rotation * new Vector3(min.x, max.y, min.z),
                center + rotation * new Vector3(max.x, max.y, min.z),
                center + rotation * new Vector3(min.x, min.y, max.z),
                center + rotation * new Vector3(max.x, min.y, max.z),
                center + rotation * new Vector3(min.x, max.y, max.z),
                center + rotation * max,
           };

           Right = rotation * Vector3.right;
           Up = rotation * Vector3.up;
           Forward = rotation * Vector3.forward;
      }
 }

 static bool Intersects(Obb a, Obb b)
 {
      if (Separated(a.Vertices, b.Vertices, a.Right))
           return false;
      if (Separated(a.Vertices, b.Vertices, a.Up))
           return false;
      if (Separated(a.Vertices, b.Vertices, a.Forward))
           return false;

      if (Separated(a.Vertices, b.Vertices, b.Right))
           return false;
      if (Separated(a.Vertices, b.Vertices, b.Up))
           return false;
      if (Separated(a.Vertices, b.Vertices, b.Forward))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Right, b.Forward)))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Up, b.Forward)))
           return false;

      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Right)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Up)))
           return false;
      if (Separated(a.Vertices, b.Vertices, Vector3.Cross(a.Forward, b.Forward)))
           return false;

      return true;
 }

 static bool Separated(Vector3[] vertsA, Vector3[] vertsB, Vector3 axis)
 {
      // Handles the cross product = {0,0,0} case
      if (axis == Vector3.zero)
           return false;

      var aMin = float.MaxValue;
      var aMax = float.MinValue;
      var bMin = float.MaxValue;
      var bMax = float.MinValue;

      // Define two intervals, a and b. Calculate their min and max values
      for (var i = 0; i < 8; i++)
      {
           var aDist = Vector3.Dot(vertsA[i], axis);
           aMin = aDist < aMin ? aDist : aMin;
           aMax = aDist > aMax ? aDist : aMax;
           var bDist = Vector3.Dot(vertsB[i], axis);
           bMin = bDist < bMin ? bDist : bMin;
           bMax = bDist > bMax ? bDist : bMax;
      }

      // One-dimensional intersection test between a and b
      var longSpan = Mathf.Max(aMax, bMax) - Mathf.Min(aMin, bMin);
      var sumSpan = aMax - aMin + bMax - bMin;
      return longSpan >= sumSpan; // > to treat touching as intersection
 }
}
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.