2D空间的OBB碰撞实现

OBB全称Oriented bounding box,方向包围盒算法。其表现效果与Unity的BoxCollider并无二致。由于3D空间的OBB需要多考虑一些情况

这里仅关注2D空间下的OBB。

 

实现效果:

网上有许多OBB的讲解,具体步骤也未必一样,我的做法:

在两个凸多边形中找到一根轴,凸多边形所有在这根轴上的投影点不产生相交,则这两个凸多边形不相交。

这根轴一般取每个边的垂线,逐个投影进行测试。

 

这里先上一个BOX的版本,如下图:

 可以看见在右侧方块的投影轴上,得到了非相交结果

Box版本代码如下:

using UnityEngine;

//注意,只有2D空间(XY坐标)下有效
public class Obb : MonoBehaviour
{
    public Vector2 size;
    public Color gizmosColor = Color.white;

    private Vector2 P0 => transform.localToWorldMatrix.MultiplyPoint3x4(-size * 0.5f);
    private Vector2 P1 => transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(size.x * 0.5f, -size.y * 0.5f, 0));
    private Vector2 P2 => transform.localToWorldMatrix.MultiplyPoint3x4(size * 0.5f);
    private Vector2 P3 => transform.localToWorldMatrix.MultiplyPoint3x4(new Vector3(-size.x * 0.5f, size.y * 0.5f, 0));


    public bool Intersects(Obb other)
    {
        Vector2 axis1 = (P1 - P0).normalized;
        Vector2 axis2 = (P3 - P0).normalized;

        Vector2 axis3 = (other.P1 - other.P0).normalized;
        Vector2 axis4 = (other.P3 - other.P0).normalized;

        if (IsIntersect(this, other, axis1)) return false;
        if (IsIntersect(this, other, axis2)) return false;
        if (IsIntersect(this, other, axis3)) return false;
        if (IsIntersect(this, other, axis4)) return false;

        return true;
    }

    bool IsIntersect(Obb x, Obb y, Vector2 axis)
    {
        float xP0 = Vector3.Dot(x.P0, axis);
        float xP1 = Vector3.Dot(x.P1, axis);
        float xP2 = Vector3.Dot(x.P2, axis);
        float xP3 = Vector3.Dot(x.P3, axis);
        float yP0 = Vector3.Dot(y.P0, axis);
        float yP1 = Vector3.Dot(y.P1, axis);
        float yP2 = Vector3.Dot(y.P2, axis);
        float yP3 = Vector3.Dot(y.P3, axis);

        float xMin = Mathf.Min(xP0, xP1, xP2, xP3);
        float xMax = Mathf.Max(xP0, xP1, xP2, xP3);
        float yMin = Mathf.Min(yP0, yP1, yP2, yP3);
        float yMax = Mathf.Max(yP0, yP1, yP2, yP3);

        if (yMin > xMin && yMin < xMax) return false;
        if (yMax > xMin && yMax < xMax) return false;
        if (xMin > yMin && xMin < yMax) return false;
        if (xMax > yMin && xMax < yMax) return false;

        return true;
    }

    private void OnDrawGizmos()
    {
        Gizmos.matrix = transform.localToWorldMatrix;

        Gizmos.color = gizmosColor;
        Gizmos.DrawWireCube(Vector3.zero, new Vector3(size.x, size.y, 1f));
    }
}
Obb.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//注意,只有2D空间(XY坐标)下有效
public class Test : MonoBehaviour
{
    public Obb a;
    public Obb b;


    private void Update()
    {
        bool isIntersects = a.Intersects(b);
        if (isIntersects)
        {
            a.gizmosColor = Color.red;
            b.gizmosColor = Color.red;
        }
        else
        {
            a.gizmosColor = Color.white;
            b.gizmosColor = Color.white;
        }
    }
}
Test.cs

 

那么下面是凸多边形的版本,垂线通过叉乘获取:

脚本如下:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

//凸多边形版本,注意只在2D空间(XY轴)下有效
public class Obb : MonoBehaviour
{
    public Vector2[] points = new Vector2[0];
    public int debug_Index;
    public Color gizmosColor = Color.white;
    private int mDebug_Index;


    public bool Intersects(Obb other)
    {
        bool isNotIntersect = false;

        mDebug_Index = 0;
        for (int i = 1; i <= points.Length; i++)
        {
            Vector3 p0 = transform.localToWorldMatrix.MultiplyPoint3x4(points[i - 1]);
            Vector3 p1 = transform.localToWorldMatrix.MultiplyPoint3x4(points[i % points.Length]);

            Vector3 axis = Vector3.Cross((p1 - p0), Vector3.forward).normalized;
            isNotIntersect |= ProjectionIsNotIntersect(this, other, axis);

            mDebug_Index++;
        }

        return isNotIntersect ? false : true;
    }

    private bool ProjectionIsNotIntersect(Obb x, Obb y, Vector2 axis)
    {
        float xMin, xMax, yMin, yMax;
        GetMinMax(x.transform.localToWorldMatrix, x.points, axis, out xMin, out xMax);
        GetMinMax(y.transform.localToWorldMatrix, y.points, axis, out yMin, out yMax);

        if (yMin >= xMin && yMin <= xMax) return false;
        if (yMax >= xMin && yMax <= xMax) return false;
        if (xMin >= yMin && xMin <= yMax) return false;
        if (xMax >= yMin && xMax <= yMax) return false;

        return true;
    }

    private void GetMinMax(Matrix4x4 matrix, Vector2[] points, Vector2 projectAxis, out float min, out float max)
    {
        min = float.MaxValue;
        max = float.MinValue;

        for (int i = 0; i < points.Length; i++)
        {
            Vector3 p = matrix.MultiplyPoint3x4(points[i]);

            float projectValue = Vector3.Project(p, projectAxis).magnitude * Mathf.Sign(Vector3.Dot(Vector3.Project(p, projectAxis), projectAxis));

            if (projectValue > max)
                max = projectValue;
        }

        for (int i = 0; i < points.Length; i++)
        {
            Vector3 p = matrix.MultiplyPoint3x4(points[i]);

            float projectValue = Vector3.Project(p, projectAxis).magnitude * Mathf.Sign(Vector3.Dot(Vector3.Project(p, projectAxis), projectAxis));

            if (projectValue < min)
                min = projectValue;
        }
    }

    private void OnDrawGizmos()
    {
        Gizmos.color = gizmosColor;
        for (int i = 1; i <= points.Length; i++)
        {
            Vector3 p0 = transform.localToWorldMatrix.MultiplyPoint3x4(points[i - 1]);
            Vector3 p1 = transform.localToWorldMatrix.MultiplyPoint3x4(points[i % points.Length]);

            Gizmos.DrawLine(p0, p1);
        }
    }
}
View Code

 

posted @ 2018-08-19 14:21  HONT  阅读(1710)  评论(0编辑  收藏  举报