我一直在基于以下条件实施SAT:
- 使用定向边界框进行动态碰撞检测 [PDF]
在表格的第7页上,它指的是要测试的15个轴,所以我们可以找到一个碰撞,但是仅使用Ax,Ay和Az时,我已经遇到了碰撞。
为什么我需要测试所有其他情况?在任何情况下,仅Ax,Ay和Az都不够吗?
我一直在基于以下条件实施SAT:
在表格的第7页上,它指的是要测试的15个轴,所以我们可以找到一个碰撞,但是仅使用Ax,Ay和Az时,我已经遇到了碰撞。
为什么我需要测试所有其他情况?在任何情况下,仅Ax,Ay和Az都不够吗?
Answers:
您可能会得到误报。检测到碰撞,但未真正碰撞。
数字15来自
9个轴由A边和B边的叉积组成
前6个轴(从面部法线开始)用于检查一个对象的角是否与另一个对象的面相交。(或更正确地消除此类冲突)
由边缘的叉积形成的9个轴的集合用于考虑边缘碰撞检测中的边缘,其中没有一个顶点会穿透另一个对象。就像下面照片中的“几乎”碰撞一样。对于该答案的其余部分,我们假设图片中的两个框实际上没有碰撞,而是相隔很小的距离。
让我们看看如果仅对SAT使用6个面法线会发生什么。下面的第一张图片显示了蓝色框的一个轴和黄色框的两个轴。如果将两个对象都投影到这些轴上,则三个对象都将重叠。下面的第二个图像显示了蓝色框的剩余两个轴和黄色框的剩余轴。再次在这些轴上投影将显示所有3个重叠。
因此,仅检查这6个面法线就会在所有6个轴上显示出重叠,根据SAT,这意味着对象正在碰撞,因为我们无法找到分离点。但是,当然,这些对象并不冲突。我们没有发现分离的原因是因为我们看起来不够努力!
那么我们如何找到这个差距呢?下图显示了一个轴,两个对象的投影将在该轴上显示出分离。
我们从哪里得到这个轴?
如果您想将一块刚性卡滑入该间隙,则该卡将成为分离平面的一部分。如果投影到该平面的法线(上图中的黑色箭头),我们将看到分离。我们知道该平面是什么,因为我们在该平面上有两个向量)一个向量与蓝色的边缘对齐,另一个向量与黄色的边缘对齐,众所周知,平面的法线就是躺在飞机上的两个向量的叉积。
因此,对于OOBB,我们需要检查两个对象的边缘的叉积的每个组合(其中的9个),以确保我们没有丢失任何边缘-边缘分离。
肯的答案记录:
9个轴由A边和B边的叉积组成
提及这些边线有点令人困惑,因为与6个法线相比,有12条边线,而当您最好将三个主要法线用于相同的输出时-边线都与法线对齐,因此我建议改用它们!
还要注意,指向同一轴线但方向不同的法线将被忽略,因此我们剩下三个唯一的轴线。
我想补充的另一件事是,如果在找到要测试的所有轴之前找到分离轴,则可以通过提前退出来优化此计算。所以,不,您不需要在每种情况下都测试所有轴,但是您确实需要准备好对所有轴进行测试:)
给定两个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
}
基于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
}
}