OBB2D相交检测
什么是 OBB?
用来界定物体几何图元的矩形边界框, 矩形边界框可以是轴对齐的或者是任意方向的,轴对齐矩形边界框有一个限制,就是它的边必须垂直于坐标轴,缩写 AABB常用来表示 axially aligned bounding box(轴对齐矩形边界框),OBB 用来表示 oriented bounding box (方向矩形边界框)。
AABB 在 2D 中:没有旋转的 矩形框 (边与轴垂直)
AABB 在 3D 中 :没有旋转的立方体 (边与轴垂直)
OBB 在 2D 中:可以旋转的矩形框
OBB 在 3D 中:可以旋转的立方体
本篇将OBB2D 中的相交检测
判断2D平面上两个矩形(可以旋转)是否相交?
如果两个矩形 A、B 不相交,则存在至少一条直线能够将两个矩形分割在两侧。对于凸多边形依然成立。
能将两个矩形分割的直线叫分离线 separatingLine、垂直于分割线的直线叫分离轴 separatingAxis
如下图
如何确定是否有一条能够分割两个矩形,它又在哪里。
经过科学的证明如果存在能够将两个矩形分割的线,则至少有一条线是平行于两个矩形的一条边。
则 separatingAxis 也平行于两个矩形的某一条边。我们只需要证明这四条线。
如果两个矩形不相交,则A、B 在 separatingAxis 上的投影没有重合部分,看下图紫色、红色线段就是两个矩形在分割轴上的投影
看下图A、B分别在 separatingAxis1 和 separatingAxis2上投影,
在separatingAxis1 上紫色和红色投影相交了,则separatingAxis1 不是A、B的分离轴,不能判定A、B是否相交。
在separatingAxis2 上绿色和蓝色投影不相交,则separatingAxis2 是A、B的分离轴,可以判定A、B不相交。
首先确认需要判断的四个分离轴,下图中 A、B 的axisX、axisY
将 A 的四个顶点在分离轴上做投影,保存一个最小值 a_minT,一个最大值 a_maxT,则A在分离轴上的投影范围是 [a_minT,a_maxT]
同理,将 B 的四个顶点在分离轴上做投影,保存一个最小值 b_minT,一个最大值 b_maxT,则B在分离轴上的投影范围是 [b_minT,b_maxT]
看下图,以 B 为例,四个顶点A、B、C、D 分别向轴上做垂线, a < b < c < d,则最终B的投影范围为(a,d)
a、b、c、d的值如何计算?
令 axis 为计算的轴, A坐标、B坐标、C坐标、D坐标 为一个 OBB的四个顶点,每个顶点分别点乘 axis 得到值
a = Dot(A坐标, axis)
b = Dot(B坐标, axis)
c = Dot(C坐标, axis)
d = Dot(D坐标, axis)
minT = Math.min(a,b,c,d)
maxT = Math.max(a,b,c,d)
代码逻辑如下
public class OBB2D
{
/// <summary>
/// X 轴方向向量
/// </summary>
public Vector2 _axisX;
/// <summary>
/// Y 轴方向向量
/// </summary>
public Vector2 _axisY;
/// <summary>
/// 中心点坐标
/// </summary>
public Vector2 _center;
/// <summary>
/// 边长度
/// </summary>
public Vector2 _size;
/// <summary>
/// 八个顶点坐标
/// </summary>
public Vector2[] _vertexs;
public OBB2D() { }
public void Set(Vector2 axisX, Vector2 axisY, Vector2 center, Vector2 size)
{
_axisX = axisX;
_axisY = axisY;
_center = center;
_size = size;
_vertexs = new Vector2[4];
_vertexs[0] = center + (axisX * size.x + axisY * size.y) * 0.5f;
_vertexs[1] = center + (axisX * size.x - axisY * size.y) * 0.5f;
_vertexs[2] = center + (-axisX * size.x - axisY * size.y) * 0.5f;
_vertexs[3] = center + (-axisX * size.x + axisY * size.y) * 0.5f;
}
}
/// <summary>
/// OBB2D相交检测
/// </summary>
public class OBB2DIntersect
{
public bool IsIntersect2D(OBB2D a, OBB2D b)
{
// 如果有一个分离轴上不相交,则OBB1 和 OBB2 不相交
return !(IsNotIntersectInAxis(a._vertexs, b._vertexs, a._axisX)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, a._axisY)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, b._axisX)
|| IsNotIntersectInAxis(a._vertexs, b._vertexs, b._axisY));
}
/// <summary>
/// OBB 在 axis 轴上没有相交
/// </summary>
/// <param name="vertexs1">OBB1 的所有顶点</param>
/// <param name="vertexs2">OBB2 的所有顶点</param>
/// <param name="axis">分离轴</param>
/// <returns></returns>
private bool IsNotIntersectInAxis(Vector2[] vertexs1, Vector2[] vertexs2, Vector2 axis)
{
float[] range1 = VertexProject(vertexs1, axis);
float[] range2 = VertexProject(vertexs2, axis);
return range1[0] > range2[1] || range2[0] > range1[1];
}
/// <summary>
/// 顶点在轴上的投影的最小值和最大值
/// </summary>
/// <param name="vertexs">顶点</param>
/// <param name="axis">分离轴</param>
/// <returns></returns>
private float[] VertexProject(Vector2[] vertexs, Vector2 axis)
{
float[] range = new float[] { float.MaxValue, float.MinValue };
for (int i = 0; i < vertexs.Length; ++i)
{
Vector2 vertex = vertexs[i];
float dot = Vector2.Dot(vertex, axis);
range[0] = Math.Min(range[0], dot);
range[1] = Math.Max(range[1], dot);
}
return range;
}
}
测试代码
public class OBB2DCollisionController : MonoBehaviour
{
// 两个立方体 A、B
public Transform A;
public Transform B;
// OBB2D 定义
private OBB2D a;
private OBB2D b;
private OBB2DIntersect obb2DIntersect;
// Start is called before the first frame update
void Start()
{
// 实例化
a = new OBB2D();
b = new OBB2D();
obb2DIntersect = new OBB2DIntersect();
}
private bool result;
// Update is called once per frame
void Update()
{
// 将立方体数据赋值给 OBB2D
SetOBB(A, a);
SetOBB(B, b);
// 判断两立方体是否相交
bool isIntersect = obb2DIntersect.IsIntersect2D(a, b);
if (result != isIntersect)
{
result = isIntersect;
// 如果两个立方体相交,则设置为红色
A.GetComponent<Renderer>().material.color = result ? Color.red : Color.white;
B.GetComponent<Renderer>().material.color = result ? Color.red : Color.white;
}
}
private void SetOBB(Transform tr, OBB2D oBB)
{
// OBB2D 的两个轴分别使用 Cube.transform:right、up
// OBB2D 的坐标使用 Cube.transform.position
// OBB2D 的size 使用 Cube.transform.localScale
oBB.Set(tr.right, tr.up, tr.position, tr.localScale);
}
}
测试结果如下