มีกี่แกนที่จะใช้สำหรับการชน OBB 3D กับ SAT


29

ฉันใช้ SAT ตาม:

ในหน้า 7 ในตารางมันหมายถึง 15 แกนเพื่อทดสอบเพื่อให้เราสามารถหาการชนกัน แต่ด้วย Axe, Ay และ Az ฉันได้รับการชนกันแล้ว

ทำไมฉันต้องทดสอบเคสอื่นทั้งหมด มีสถานการณ์ใดที่ Axe, Ay และ Az ไม่พอ?

คำตอบ:


56

คุณอาจได้รับผลบวกผิด ๆ ตรวจพบการชน แต่ไม่ชนจริง ๆ

หมายเลข 15 มาจาก

  • 3 แกนจากวัตถุ A (หน้าปกติ)
  • 3 แกนจากวัตถุ B (ใบหน้าปกติ)
  • 9 แกนจากทุกคู่ของขอบ A และขอบของ B (3x3)
  • = 15 รวม

แกน 9 ประกอบขึ้นจากผลคูณไขว้ของขอบของ A และขอบของ B

  1. Ae1 x Be1 (Edge 1 of A cross edge 1 of 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 และ edge ของ B" เนื่องจากเราสามารถใช้บรรทัดฐานได้มากกว่าที่จะเป็นขอบ ขอขอบคุณอีกครั้ง :)

5
@joeRocc สำหรับ cuboids คุณถูกต้องเพียงใช้ normals เพราะ normals และ edge นั้นอยู่ในแนวเดียวกัน แต่สำหรับรูปร่างอื่น (เช่น tetrahedrons, polyhedrons อื่น ๆ ) normals นั้นไม่ได้อยู่ในแนวเดียวกันกับขอบ
Ken

ขอบคุณมาก ๆ ! ฉันอ่านหนังสือดีเล่มนี้ชื่อว่า "การพัฒนากลไกเกมฟิสิกส์" และพบกับปัญหานี้ ไม่รู้เลยว่าทำไมเราถึงใช้แกน 15 แกน ขอบคุณมาก. ตอนนี้ฉันมั่นใจที่จะคุยโม้เรื่องนี้ได้แล้ว ; D
Ankit singh kushwah

11

บันทึกคำตอบของเคน :

แกน 9 ประกอบขึ้นจากผลคูณไขว้ของขอบของ A และขอบของ B

มันค่อนข้างสับสนในการอ้างถึงขอบเนื่องจากมี 12 edge เทียบกับ 6 normals เมื่อคุณอาจใช้ normal หลักสามตัวสำหรับเอาต์พุตเดียวกัน - ขอบทั้งหมดสอดคล้องกับ normals ดังนั้นฉันแนะนำให้ใช้มันแทน !

นอกจากนี้โปรดสังเกตว่าบรรทัดฐานที่ชี้ไปตามแกนเดียวกัน แต่ในทิศทางที่แตกต่างจะถูกเพิกเฉยดังนั้นเราจึงเหลือสามแกนที่ไม่เหมือนใคร

อีกสิ่งหนึ่งที่ฉันต้องการเพิ่มคือคุณสามารถเพิ่มประสิทธิภาพการคำนวณนี้โดยออกก่อนถ้าคุณพบแกนที่แยกก่อนที่คุณจะคำนวณแกนทั้งหมดที่คุณต้องการทดสอบ ดังนั้นไม่คุณไม่จำเป็นต้องทดสอบแกนทั้งหมดในทุกกรณี แต่คุณต้องพร้อมที่จะทดสอบพวกเขาทั้งหมด :)

นี่คือรายการของแกนที่สมบูรณ์เพื่อทดสอบโดยให้ 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 คุณควรใส่รอยยิ้มในตอนท้ายของความคิดเห็นของคุณเพื่อให้มือใหม่รู้ว่าคุณไม่ได้ปิดมันลง :)

ดูความคิดเห็นของฉันด้านบนอีกครั้งขอบ vs normals
Ken

2

ทำงานตัวอย่าง c # ตามคำตอบของ Acegikmo (โดยใช้ unity 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.