与SAT进行3D OBB碰撞时使用多少个轴和哪些轴


Answers:


56

您可能会得到误报。检测到碰撞,但未真正碰撞。

数字15来自

  • 距对象A 3个轴(面法线)
  • 从对象B开始的3个轴(面法线)
  • 从A的所有边缘对和B的所有边缘对成9个轴(3x3)
  • 总共= 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对于长方体,您是正确的,只需使用法线,因为法线和边缘是对齐的,但是对于其他形状(例如四面体,其他多面体),法线不与边缘对齐。
肯(Ken)2015年

非常感谢男人!我正在阅读这本名为“游戏物理引擎开发”的好书,遇到了这个问题。不知道为什么我们要使用15个轴。非常感谢。现在我有足够的信心吹嘘它。; D
Ankit singh kushwah

11

肯的​​答案记录:

9个轴由A边和B边的叉积组成

提及这些边线有点令人困惑,因为与6个法线相比,有12条边线,而当您最好将三个主要法线用于相同的输出时-边线都与法线对齐,因此我建议改用它们!

还要注意,指向同一轴线但方向不同的法线将被忽略,因此我们剩下三个唯一的轴线。

我想补充的另一件事是,如果在找到要测试的所有轴之前找到分离轴,则可以通过提前退出来优化此计算。所以,不,您不需要在每种情况下都测试所有轴,但是您确实需要准备好对所有轴进行测试:)

给定两个OBB,A和B,这是要测试的轴的完整列表,其中x,y和z表示基向量/三个唯一法线。0 = x轴,1 = y轴,2 = z轴

  1. 00
  2. a1
  3. a2
  4. 00
  5. 11
  6. 22
  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
欢迎来到该网站。请查看帮助中心,特别要注意的是,该网站不是论坛,并且“回复”其他答案不是一个好主意(因为您的“回复”可能不会出现在您回复的帖子之前)。最好以独立的方式编写答案,如果您特别想澄清现有的帖子,请使用评论。
2015年

感谢您澄清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
 }
}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.