ฉันใช้ SAT ตาม:
ในหน้า 7 ในตารางมันหมายถึง 15 แกนเพื่อทดสอบเพื่อให้เราสามารถหาการชนกัน แต่ด้วย Axe, Ay และ Az ฉันได้รับการชนกันแล้ว
ทำไมฉันต้องทดสอบเคสอื่นทั้งหมด มีสถานการณ์ใดที่ Axe, Ay และ Az ไม่พอ?
ฉันใช้ SAT ตาม:
ในหน้า 7 ในตารางมันหมายถึง 15 แกนเพื่อทดสอบเพื่อให้เราสามารถหาการชนกัน แต่ด้วย Axe, Ay และ Az ฉันได้รับการชนกันแล้ว
ทำไมฉันต้องทดสอบเคสอื่นทั้งหมด มีสถานการณ์ใดที่ Axe, Ay และ Az ไม่พอ?
คำตอบ:
คุณอาจได้รับผลบวกผิด ๆ ตรวจพบการชน แต่ไม่ชนจริง ๆ
หมายเลข 15 มาจาก
แกน 9 ประกอบขึ้นจากผลคูณไขว้ของขอบของ A และขอบของ B
6 แกนแรก (จากใบหน้าปกติ) ใช้เพื่อตรวจสอบว่ามีมุมหนึ่งของวัตถุหนึ่งกำลังตัดกับใบหน้าของวัตถุอื่นหรือไม่ (หรือมากกว่านั้นถูกต้องเพื่อกำจัดการชนประเภทนี้)
ชุดของแกน 9 แกนที่เกิดขึ้นจากผลิตภัณฑ์ไขว้ของขอบถูกนำมาใช้เพื่อพิจารณาถึงขอบของการตรวจจับการชนกันของขอบซึ่งไม่มีจุดยอดที่เจาะทะลุวัตถุอื่น ชอบการชน 'เกือบ' ในภาพด้านล่าง ให้สมมติว่าคำตอบที่เหลือนี้ว่ากล่องทั้งสองในภาพไม่ได้เกิดการชนกันจริง ๆ แต่ถูกคั่นด้วยระยะห่างเล็กน้อย
มาดูกันว่าจะเกิดอะไรขึ้นถ้าเราแค่ใช้ 6 ใบหน้าปกติสำหรับ SAT ภาพแรกด้านล่างแสดงแกนหนึ่งจากกล่องสีน้ำเงินและ 2 แกนจากกล่องสีเหลือง หากเราฉายวัตถุทั้งสองไปยังแกนเหล่านี้เราจะได้รับการทับซ้อนกันทั้งสาม ภาพที่สองด้านล่างแสดงสองแกนที่เหลือของกล่องสีน้ำเงินและแกนที่เหลือของกล่องสีเหลือง การฉายอีกครั้งบนแกนเหล่านี้จะแสดงการทับซ้อนกันทั้ง 3 รายการ
ดังนั้นเพียงแค่ตรวจสอบใบหน้าธรรมดาทั้ง 6 หน้าจะแสดงให้เห็นทับซ้อนกันบนแกนทั้ง 6 แกนซึ่งตาม SAT หมายความว่าวัตถุนั้นชนกันเพราะเราไม่สามารถแยกได้ แต่แน่นอนวัตถุเหล่านี้ไม่ได้ชนกัน เหตุผลที่เราไม่พบการแยกเป็นเพราะเรายังดูไม่พอ!
แล้วเราจะหาช่องว่างนี้ได้อย่างไร ภาพด้านล่างแสดงแกนซึ่งการฉายภาพของวัตถุทั้งสองจะเผยให้เห็นการแยก
เราจะได้แกนนี้มาจากไหน
หากคุณจินตนาการว่าเลื่อนการ์ดแข็งลงไปในช่องว่างการ์ดนั้นจะเป็นส่วนหนึ่งของระนาบแยก หากเราคาดว่าจะเป็นไปตามปกติของเครื่องบิน (ลูกศรสีดำในภาพด้านบน) เราจะเห็นการแยก เรารู้ว่าระนาบนั้นคืออะไรเพราะเรามีเวกเตอร์สองตัวที่วางอยู่บนระนาบนั้น) เวกเตอร์หนึ่งอยู่ในแนวเดียวกับขอบของสีน้ำเงินและเวกเตอร์อีกตัวนั้นอยู่ในแนวเดียวกับขอบของสีเหลืองและในขณะที่เรารู้ว่า กากบาทของเวกเตอร์สองตัวนอนอยู่บนระนาบ
ดังนั้นสำหรับ OOBB เราจำเป็นต้องตรวจสอบการรวมกัน (9 ของพวกเขา) ของผลิตภัณฑ์ข้ามของขอบของวัตถุทั้งสองเพื่อให้แน่ใจว่าเราจะไม่ขาดการแยกขอบที่ขอบใด ๆ
บันทึกคำตอบของเคน :
แกน 9 ประกอบขึ้นจากผลคูณไขว้ของขอบของ A และขอบของ B
มันค่อนข้างสับสนในการอ้างถึงขอบเนื่องจากมี 12 edge เทียบกับ 6 normals เมื่อคุณอาจใช้ normal หลักสามตัวสำหรับเอาต์พุตเดียวกัน - ขอบทั้งหมดสอดคล้องกับ normals ดังนั้นฉันแนะนำให้ใช้มันแทน !
นอกจากนี้โปรดสังเกตว่าบรรทัดฐานที่ชี้ไปตามแกนเดียวกัน แต่ในทิศทางที่แตกต่างจะถูกเพิกเฉยดังนั้นเราจึงเหลือสามแกนที่ไม่เหมือนใคร
อีกสิ่งหนึ่งที่ฉันต้องการเพิ่มคือคุณสามารถเพิ่มประสิทธิภาพการคำนวณนี้โดยออกก่อนถ้าคุณพบแกนที่แยกก่อนที่คุณจะคำนวณแกนทั้งหมดที่คุณต้องการทดสอบ ดังนั้นไม่คุณไม่จำเป็นต้องทดสอบแกนทั้งหมดในทุกกรณี แต่คุณต้องพร้อมที่จะทดสอบพวกเขาทั้งหมด :)
นี่คือรายการของแกนที่สมบูรณ์เพื่อทดสอบโดยให้ OBB สองอันคือ A และ B โดยที่ x, y และ z อ้างถึงเวกเตอร์พื้นฐาน / สามบรรทัดฐานพิเศษ 0 = แกน x, 1 = แกน y, 2 = แกน z
นอกจากนี้ยังมีข้อแม้เล็ก ๆ น้อย ๆ ที่คุณควรระวัง
ผลิตภัณฑ์กากบาทจะให้เวกเตอร์เป็นศูนย์ {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
}
ทำงานตัวอย่าง 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
}
}