OBB2D相交检测

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);
    }
}

测试结果如下
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值