SAT와 3D OBB 충돌에 사용할 축 수 및 축


29

다음을 기반으로 SAT를 구현했습니다.

7 페이지의 표에서 15 축을 테스트하여 충돌을 찾을 수 있지만 Ax, Ay 및 Az 만 있으면 이미 충돌이 발생합니다.

다른 모든 사례를 테스트해야하는 이유는 무엇입니까? Ax, Ay 및 Az만으로는 충분하지 않은 상황이 있습니까?

답변:


56

당신은 거짓 긍정을 얻을 수 있습니다. 충돌이 감지되었지만 실제로 충돌하지는 않습니다.

숫자 15는

  • 객체 A에서 3 축 (얼굴 법선)
  • 객체 B에서 3 축 (얼굴 법선)
  • A의 모든 모서리와 B의 모서리 (3x3)의 9 축
  • 총 = 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 축 (면 법선에서)은 한 객체의 모서리가 다른 객체의면과 교차하는지 확인하는 데 사용됩니다. (또는 이러한 종류의 충돌을 제거하기 위해 더 정확하게)

모서리의 교차 곱에 의해 형성된 9 개의 축 세트는 다른 물체를 관통하는 정점이없는 모서리에서 모서리 충돌 감지를 고려하는 데 사용됩니다. 아래 사진에서 '거의'충돌처럼. 이 답변의 나머지 부분에서 그림의 두 상자가 실제로 충돌하는 것이 아니라 작은 거리로 분리되어 있다고 가정합시다.

여기에 이미지 설명을 입력하십시오

SAT에 6 개의면 법선을 사용하면 어떻게되는지 봅시다. 아래 첫 번째 이미지는 파란색 상자에서 하나의 축과 노란색 상자에서 두 개의 축을 보여줍니다. 두 물체를이 축에 투영하면 세 물체 모두에 겹칠 수 있습니다. 아래의 두 번째 이미지는 파란색 상자의 나머지 두 축과 노란색 상자의 나머지 축을 보여줍니다. 이 축에 다시 투영하면 3에서 모두 겹침이 표시됩니다.

따라서 6 개의면 법선 만 확인하면 6 개의 축 모두에 겹침이 표시됩니다. 이는 SAT에 따르면 객체가 충돌하고 있음을 의미합니다. 분리를 찾을 수 없기 때문입니다. 그러나 물론 이러한 객체는 충돌하지 않습니다. 우리가 별거를 찾지 못한 이유는 우리가 충분히 열심히 보지 않았기 때문입니다!

여기에 이미지 설명을 입력하십시오 여기에 이미지 설명을 입력하십시오

이 차이를 어떻게 찾을 수 있을까요? 아래 이미지는 두 물체의 투영이 분리를 나타내는 축을 보여줍니다.

여기에 이미지 설명을 입력하십시오

이 축을 어디서 얻습니까?

뻣뻣한 카드를 틈으로 밀어 넣는 것을 상상하면 해당 카드가 분리면의 일부가됩니다. 해당 평면의 법선 (위 그림에서 검은 색 화살표)으로 투사하면 분리 된 것을 볼 수 있습니다. 그 평면에있는 두 개의 벡터가 있기 때문에 그 평면이 무엇인지 알 수 있습니다.) 하나의 벡터는 파란색의 가장자리와 정렬되고 다른 벡터는 노란색의 가장자리와 정렬되며 평면의 법선은 단순히 비행기에 누워있는 두 벡터의 교차 곱.

따라서 OOBB의 경우 두 객체의 모서리에있는 교차 곱의 모든 조합 (9 개)을 확인하여 모서리 간 분리가 누락되지 않았는지 확인해야합니다.


2
멋진 설명! 그리고 사진 주셔서 감사합니다. @Acegikmo가 지적했듯이 "9 축은 A의 모서리와 B의 모서리의 교차 곱으로 구성되어 있습니다"라고 말하면 약간 혼란 스럽습니다. 모서리가 아닌 법선을 사용할 수 있기 때문입니다. 다시 한 번 감사드립니다 :)

5
@joeRocc 직육면체의 경우 법선과 모서리가 정렬되어 있기 때문에 법선을 사용하십시오. 다른 모양 (예 : 4 면체, 다른 다면체)의 경우 법선은 모서리와 정렬되지 않습니다.
Ken

고마워요! "Game Physics engine development"라는 멋진 책을 읽고이 문제를 겪었습니다. 왜 우리가 15 축을 사용하는지 전혀 몰랐습니다. 고마워 이제 나는 그것에 대해 자랑 할 정도로 확신합니다. ; D
Ankit singh kushwah

11

켄의 답변 노트 :

9 개의 축은 A의 모서리와 B의 모서리의 교차 곱으로 구성됩니다.

매우 동일한 출력에 대해 세 가지 주요 법선을 사용할 수도있을 때 6 법선에 비해 12 개의 모서리가 있으므로 모서리를 참조하는 것이 다소 혼란 스럽습니다. 가장자리가 모두 법선과 정렬되므로 대신 사용하는 것이 좋습니다 !

또한 같은 축을 따라 다른 방향을 가리키는 법선은 무시되므로 3 개의 고유 한 축이 남습니다.

내가 추가하고 싶은 또 다른 것은 테스트하려는 모든 축을 계산하기 전에 분리 축을 찾으면 일찍 종료 하여이 계산을 최적화 할 수 있다는 것입니다. 따라서 모든 경우에 모든 축을 테스트 할 필요는 없지만 모든 축을 테스트 할 준비가되어 있어야합니다. :)

여기에 두 개의 OBB (A 및 B)가 주어지면 테스트 할 축의 전체 목록이 있습니다. 여기서 x, y 및 z는 기본 벡터 / 세 개의 고유 법선을 나타냅니다. 0 = x 축, 1 = y 축, 2 = z 축

  1. a0
  2. a1
  3. a2
  4. b0
  5. b1
  6. b2
  7. 교차 (a0, b0)
  8. 교차 (a0, b1)
  9. 교차 (a0, b2)
  10. 교차 (a1, b0)
  11. 교차 (a1, b1)
  12. 교차 (a1, b2)
  13. 교차 (a2, b0)
  14. 교차 (a2, b1)
  15. 십자가 (a2, b2)

또한주의해야 할 사항이 있습니다.

교차 곱은 객체 사이의 두 축이 같은 방향을 가리킬 때 영점 벡터 {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
사이트에 오신 것을 환영합니다. 도움말 센터를 확인하고 특히이 사이트는 포럼이 아니며 다른 답변에 "답장"하는 것은 좋은 생각이 아니라는 점에 유의하십시오 (응답하는 게시물 앞에 "응답"이 표시되지 않을 수 있음). 독자적인 방식으로 답변을 작성하고 기존 게시물을 명확하게 설명하려면 주석을 사용하는 것이 좋습니다.
Josh

설명 Acegikmo 주셔서 감사합니다! 가장자리에 대한 참조로 약간 혼란 스러웠습니다. @Josh Petrie 당신은 당신의 코멘트의 끝에 웃음을 넣어야 초보자가 당신이 그들을 종료하지 않습니다 알고 :)

위 모서리와 법선 위의 내 의견 참조
Ken

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.