Скільки і які осі використовувати для зіткнення 3D OBB з SAT


29

Я впроваджував SAT на основі:

На сторінці 7, в таблиці, він посилається на тест 15 осі, щоб ми могли знайти зіткнення, але тільки з Axe, Ay та Az я вже отримую зіткнення.

Чому мені потрібно перевірити всі інші випадки? Чи є якась ситуація, коли просто Оси, Ай та Аз недостатньо?

Відповіді:


56

Можливо, ви отримаєте помилкові позитиви. Зіткнення виявлені, але насправді не стикаються.

Походить число 15

  • 3 осі від об'єкта A (гранічні нормали)
  • 3 осі від об'єкта B (гранічні нормали)
  • 9 осей від усіх пар ребер A і ребер B (3x3)
  • = 15 загалом

9 осей складаються з поперечних виробів ребер A і ребер B

  1. Ae1 x Be1 (край 1 поперечного краю 1 B)
  2. Ae1 x Be2
  3. Ae1 x Be3
  4. Ae2 x Be1
  5. ... і так далі

Перші 6 осей (від нормальних граней обличчя) використовуються для перевірки, чи кут одного об’єкта перетинає обличчя іншого об'єкта. (або правильніше усунути подібні зіткнення)

Набір з 9 осей, утворених поперечними виробами ребер, використовується для розгляду крайового виявлення крайових зіткнень, де немає вершини, що пронизує інший об’єкт. Як і «майже» зіткнення на фото нижче. Давайте припустимо для решти цієї відповіді, що два поля на малюнку насправді не стикаються, а розділені невеликою відстані.

введіть тут опис зображення

Давайте подивимось, що станеться, якщо ми просто використаємо 6-ох нормальних норм для SAT. На першому зображенні нижче показана одна вісь із синього поля та 2 осі із жовтого поля. Якщо ми спроектуємо обидва об'єкти на ці осі, ми отримаємо перекриття на всіх трьох. На другому зображенні нижче показані дві осі синього поля та вісь жовтого поля. Знову ж проеціювання на ці осі покаже перекриття на всіх 3.

Тож лише перевірка норми 6 граней покаже перекриття на всіх 6 осях, що, за даними SAT, означає, що об’єкти стикаються, оскільки нам не вдалося знайти поділ. Але, звичайно, ці об’єкти не стикаються. Причина, по якій ми не знайшли розлуку, полягає в тому, що ми не виглядали досить важко!

введіть тут опис зображення введіть тут опис зображення

То як нам знайти цю прогалину? На зображенні нижче зображено вісь, на якій проекція обох об’єктів виявить поділ.

введіть тут опис зображення

Звідки ми беремо цю вісь?

Якщо ви уявляєте, як засунути шматок жорсткої картки в проміжок, ця карта буде частиною розділової площини. Якщо проектувати на нормальну площину (чорна стрілка на малюнку вгорі), ми побачимо поділ. Ми знаємо, що це за площина, тому що у нас є два вектори, які лежать на цій площині. Один вектор вирівняний із синім краєм, а другий вектор вирівнюється з краєм жовтого кольору, і як ми всі знаємо, нормальне для площини - це просто поперечний добуток двох векторів, що лежать на площині.

Отже, для OOBB нам потрібно перевірити кожну комбінацію (9 з них) перехресних добутків країв двох об’єктів, щоб переконатися, що ми не пропускаємо жодних крайових розділень.


2
Дивовижне пояснення! І дякую за фотографії. Як зазначає @Acegikmo, це трохи заплутано, коли ви говорите "9 осей складаються з поперечних добутків ребер A і ребер B", оскільки ми можемо просто використовувати нормали, а не ребра. Ще раз дякую :)

5
@joeRocc Для кубоїдів ви праві, просто використовуйте нормали, оскільки нормалі та ребра вирівнюються, а для інших фігур (наприклад, тетраедрів, інших багатогранників) нормали не вирівнюються по краях.
Кен

Велике спасибі людино! Я читав цю приємну книгу під назвою "Розвиток двигуна фізики ігор" і стикався з цією проблемою. Поняття не мав, для чого ми використовуємо 15 осей. Дуже дякую. Зараз я досить впевнений, щоб похвалитися цим. ; D
Ankit singh kushwah

11

Зауваження Кена :

9 осей складаються з поперечних виробів ребер A і ребер B

Це дещо заплутано посилатися на ребра, оскільки 12 країв порівняно з 6 нормальними, коли ви можете також використовувати три основні нормалі для того самого виводу - краї всі вирівняні з нормалами, тому я рекомендую використовувати їх замість !

Також зауважте, що нормали, спрямовані вздовж однієї осі, але в іншому напрямку, ігноруються, і тому нам залишаються три унікальні осі.

Ще одне, що я хотів би додати, це те, що ви можете оптимізувати цей обчислення, виїжджаючи рано, якщо знайдете розділову вісь, перш ніж обчислити всі осі, які ви хочете перевірити. Отже, ні, вам не потрібно тестувати всі осі в кожному випадку, але вам потрібно бути готовим протестувати їх усі :)

Ось повний перелік осей для тестування з двома 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
Ласкаво просимо на сайт. Будь ласка, перегляньте довідковий центр , і, зокрема, зауважте, що цей сайт не є форумом і що "відповідати" на інші відповіді не є хорошою ідеєю (тому що ваша "відповідь" може не з’являтися перед публікацією, на яку ви відповідаєте). Краще написати свої відповіді самостійно, і використовувати коментарі, якщо ви конкретно хочете уточнити існуючий пост.
Джош

Дякуємо за уточнення Acegikmo! Мене трохи збентежило посилання на краї. @Josh Petrie, ти повинен розмістити посмішки в кінці своїх коментарів, щоб новачки знали, що ти їх не закриваєш :)

дивіться мій коментар вище про краї проти нормалів
Кен

2

робочий c # приклад на основі відповіді Acegikmo (використовуючи деякі 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
 }
}
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.