❤️UNITY实战进阶-OBB包围盒详解-6

❤️UNITY实战进阶-三维AABB包围盒详解-6_欲望如海水,越喝越渴。-CSDN博客前言 碰撞检测问题在虚拟现实、计算机辅助设计与制造、游戏、机器人等方面都有着广泛的应用,而包围盒算法是进行碰撞检测的重要方法之一。 而常见的包围盒有:AABB包围盒(Axis-aligned bounding box)包围球(Sphere)OBB包围盒(Oriented bounding box)凸包包围盒(Convex Hull)...在Unity中的Collider包含:介绍在游戏中,为了简化物体之间的碰...https://blog.csdn.net/flj135792468/article/details/120654391在前文中,我们可以通过2个点来确定一个立方体。在此基础上添加3个轴向

        public Vector3 XAxis { get { return transform.right; } }

        public Vector3 YAxis { get { return transform.up; } }

        public Vector3 ZAxis { get { return transform.forward; } }

已知一个点的坐标和物体的四元数,我们就可以得知旋转完的点 

public void GetCorners()
{
    // 朝着Z轴正方向的面
    // 左上顶点坐标
    m_Corners[0].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMax.z);
    // 左下顶点坐标
    m_Corners[1].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMax.z);
    // 右下顶点坐标
    m_Corners[2].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMax.z);
    // 右上顶点坐标
    m_Corners[3].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMax.z);

    // 朝着Z轴负方向的面
    // 右上顶点坐标
    m_Corners[4].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMin.z);
    // 右下顶点坐标.
    m_Corners[5].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMin.z);
    // 左下顶点坐标.
    m_Corners[6].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMin.z);
    // 左上顶点坐标.
    m_Corners[7].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMin.z);

    // 根据旋转修改8个点的坐标
    for (int i = 0; i < m_Corners.Length; i++)
    {
        Vector3 dis = m_Corners[i] - transform.position;
        m_Corners[i] = transform.position + transform.localRotation * dis;
    }
}

这样一个可以跟着旋转的8个顶点坐标就算出来了


OBB包围盒的碰撞检测方法

检测方法常采用的是分离轴定理。说白了就是去检测并判断两个图形之间是否有间隙。

找到一个轴,两个凸形状在该轴上的投影不重叠,则这两个形状不相交。如果这个轴不存在,并且那些形状是凸形的,则可以确定两个形状相交。

在算法上就是取两个OBB的坐标轴各3个以及垂直于每个轴的9个轴。

1.先将点投影到坐标抽上

/// <summary>
/// 将点投影到坐标轴
/// </summary>
/// <param name="point"></param>
/// <param name="axis"></param>
/// <returns>投影的坐标</returns>
private float ProjectPoint(Vector3 point, Vector3 axis)
{
    Vector3 projectPoint = Vector3.Project(point, axis);
    float result = projectPoint.magnitude * Mathf.Sign(Vector3.Dot(projectPoint, axis));
    return result;
}

2.获取8个点在轴上投影的最大值和最小值

/// <summary>
/// 计算最大最小投影值
/// </summary>
/// <param name="corners"></param>
/// <param name="axis"></param>
/// <param name="min"></param>
/// <param name="max"></param>
private void GetInterval(Vector3[] corners, Vector3 axis, out float min, out float max)
{
    float value;
    //分别投影八个点,取最大和最小值
    min = max = ProjectPoint(corners[0], axis);
    for (int i = 1; i < corners.Length; i++)
    {
        value = ProjectPoint(corners[i], axis);
        min = Mathf.Min(min, value);
        max = Mathf.Max(max, value);
    }
}

3.判断是不是交叉点

/// <summary>
/// 预测是不是交叉点
/// </summary>
/// <param name="aCorners"></param>
/// <param name="bCorners"></param>
/// <param name="axis"></param>
/// <returns></returns>
private bool AxisProjection(Vector3[] aCorners, Vector3[] bCorners, Vector3 axis)
{
    GetInterval(aCorners, axis, out float xMin, out float xMax);
    GetInterval(bCorners, axis, out float yMin, out float 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;
}

4.根据2个包围盒的3个轴以及垂直于每个轴的9个轴判断是否有交集

public bool Intersects(IMathAABB aabb)
{
    m_IsNotIntersect = false;
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, XAxis);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, YAxis);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, ZAxis);
                                           
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.XAxis);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.YAxis);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.ZAxis);
                                                
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.XAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.YAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.ZAxis).normalized);
                                                
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.XAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.YAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.ZAxis).normalized);
                                              
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.XAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.YAxis).normalized);
    m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.ZAxis).normalized);

    return !m_IsNotIntersect;
}

完整代码

public class OBB : MonoBehaviour, IMathAABB
    {
        //修改此值控制m_CalcMin
        [SerializeField, AABBModify("MinVector")]
        private Vector3 m_Min = -Vector3.one;

        //修改此值控制m_CalcMax
        [SerializeField, AABBModify("MaxVector")]
        private Vector3 m_Max = Vector3.one;

        /// <summary>
        /// 中心点
        /// </summary>
        [SerializeField, AABBDisable]
        private Vector3 m_Center = Vector3.zero;

        /// <summary>
        /// 保存包围盒八个顶点
        /// </summary>
        [SerializeField, AABBDisable]
        private Vector3[] m_Corners = new Vector3[8];

#if UNITY_EDITOR
        [SerializeField]
        private Color m_DebugLineColor = Color.green;
#endif

        public Vector3 MinVector
        {
            set
            {
                SetMinMax(m_Min, m_Max);
            }
            get
            {
                return m_RealCalcMin;
            }
        }

        public Vector3 MaxVector
        {
            set
            {
                SetMinMax(m_Min, m_Max);
            }
            get
            {
                return m_RealCalcMax;
            }
        }

        public Vector3[] Corners
        {
            get
            {
                return m_Corners;
            }
        }

        public Vector3 Center
        {
            get
            {
                return m_Center;
            }
        }

        public Vector3 XAxis { get { return transform.right; } }

        public Vector3 YAxis { get { return transform.up; } }

        public Vector3 ZAxis { get { return transform.forward; } }


        /// <summary>
        /// 实际计算的最小值
        /// </summary>
        private Vector3 m_RealCalcMin;

        /// <summary>
        /// 实际计算的最大值
        /// </summary>
        private Vector3 m_RealCalcMax;

        private bool m_IsNotIntersect = false;

        /// <summary>
        /// 防止在update之前产生碰撞
        /// </summary>
        private void Awake()
        {
            SetMinMax(m_Min, m_Max);
        }

        // Update is called once per frame
        private void Update()
        {
            SetMinMax(m_Min, m_Max);
        }

#if UNITY_EDITOR
        void OnDrawGizmos()
        {
            //
            // draw lines
            Gizmos.color = m_DebugLineColor;
            Gizmos.DrawLine(Corners[0], Corners[1]);
            Gizmos.DrawLine(Corners[1], Corners[2]);
            Gizmos.DrawLine(Corners[2], Corners[3]);
            Gizmos.DrawLine(Corners[3], Corners[0]);

            Gizmos.DrawLine(Corners[4], Corners[5]);
            Gizmos.DrawLine(Corners[5], Corners[6]);
            Gizmos.DrawLine(Corners[6], Corners[7]);
            Gizmos.DrawLine(Corners[7], Corners[4]);

            Gizmos.DrawLine(Corners[0], Corners[7]);
            Gizmos.DrawLine(Corners[1], Corners[6]);
            Gizmos.DrawLine(Corners[2], Corners[5]);
            Gizmos.DrawLine(Corners[3], Corners[4]);
        }
#endif

        public Vector3 GetCenter()
        {
            m_Center.x = 0.5f * (m_RealCalcMin.x + m_RealCalcMax.x);
            m_Center.y = 0.5f * (m_RealCalcMin.y + m_RealCalcMax.y);
            m_Center.z = 0.5f * (m_RealCalcMin.z + m_RealCalcMax.z);
            return m_Center;
        }

        public void GetCorners()
        {
            // 朝着Z轴正方向的面
            // 左上顶点坐标
            m_Corners[0].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMax.z);
            // 左下顶点坐标
            m_Corners[1].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMax.z);
            // 右下顶点坐标
            m_Corners[2].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMax.z);
            // 右上顶点坐标
            m_Corners[3].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMax.z);

            // 朝着Z轴负方向的面
            // 右上顶点坐标
            m_Corners[4].Set(m_RealCalcMax.x, m_RealCalcMax.y, m_RealCalcMin.z);
            // 右下顶点坐标.
            m_Corners[5].Set(m_RealCalcMax.x, m_RealCalcMin.y, m_RealCalcMin.z);
            // 左下顶点坐标.
            m_Corners[6].Set(m_RealCalcMin.x, m_RealCalcMin.y, m_RealCalcMin.z);
            // 左上顶点坐标.
            m_Corners[7].Set(m_RealCalcMin.x, m_RealCalcMax.y, m_RealCalcMin.z);

            // 根据旋转修改8个点的坐标
            for (int i = 0; i < m_Corners.Length; i++)
            {
                Vector3 dis = m_Corners[i] - transform.position;
                m_Corners[i] = transform.position + transform.localRotation * dis;
            }
        }

        public bool Intersects(IMathAABB aabb)
        {
            m_IsNotIntersect = false;
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, XAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, YAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, ZAxis);
                                                   
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.XAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.YAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, aabb.ZAxis);
                                                        
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.XAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.YAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(XAxis, aabb.ZAxis).normalized);
                                                        
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.XAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.YAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(YAxis, aabb.ZAxis).normalized);
                                                      
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.XAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.YAxis).normalized);
            m_IsNotIntersect |= AxisProjection(this.Corners, aabb.Corners, Vector3.Cross(ZAxis, aabb.ZAxis).normalized);

            return !m_IsNotIntersect;
        }

        public bool ContainPoint(Vector3 point)
        {
            m_IsNotIntersect = false;
            m_IsNotIntersect |= AxisProjection(this.Corners, point, XAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, point, YAxis);
            m_IsNotIntersect |= AxisProjection(this.Corners, point, ZAxis);
            return !m_IsNotIntersect;
        }

        public void Merge(IMathAABB box)
        {
            throw new System.NotImplementedException("Please ingore 'Merge(IMathAABB box)' function !");
        }

        public void SetMinMax(Vector3 min, Vector3 max)
        {
            this.m_RealCalcMin = min * 0.5f + transform.position;
            this.m_RealCalcMax = max * 0.5f + transform.position;
            GetCenter();
            GetCorners();
        }

        public bool IsEmpty()
        {
            return m_RealCalcMin.x > m_RealCalcMax.x || m_RealCalcMin.y > m_RealCalcMax.y || m_RealCalcMin.z > m_RealCalcMax.z;
        }

        public void ResetMinMax()
        {
            m_RealCalcMin.Set(-1, -1, -1);
            m_RealCalcMax.Set(1, 1, 1);
            GetCenter();
            GetCorners();
        }

        /// <summary>
        /// 预测是不是交叉点
        /// </summary>
        /// <param name="aCorners"></param>
        /// <param name="bCorners"></param>
        /// <param name="axis"></param>
        /// <returns></returns>
        private bool AxisProjection(Vector3[] aCorners, Vector3[] bCorners, Vector3 axis)
        {
            GetInterval(aCorners, axis, out float xMin, out float xMax);
            GetInterval(bCorners, axis, out float yMin, out float 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;
        }

        /// <summary>
        /// 预测是不是交叉点
        /// </summary>
        /// <param name="aCorners"></param>
        /// <param name="point"></param>
        /// <param name="axis"></param>
        /// <returns></returns>
        private bool AxisProjection(Vector3[] aCorners, Vector3 point, Vector3 axis)
        {
            GetInterval(aCorners, axis, out float xMin, out float xMax);
            float yMin = ProjectPoint(point, axis);
            float yMax = ProjectPoint(point, axis);
            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;
        }

        /// <summary>
        /// 计算最大最小投影值
        /// </summary>
        /// <param name="corners"></param>
        /// <param name="axis"></param>
        /// <param name="min"></param>
        /// <param name="max"></param>
        private void GetInterval(Vector3[] corners, Vector3 axis, out float min, out float max)
        {
            float value;
            //分别投影八个点,取最大和最小值
            min = max = ProjectPoint(corners[0], axis);
            for (int i = 1; i < corners.Length; i++)
            {
                value = ProjectPoint(corners[i], axis);
                min = Mathf.Min(min, value);
                max = Mathf.Max(max, value);
            }
        }

        /// <summary>
        /// 将点投影到坐标轴
        /// </summary>
        /// <param name="point"></param>
        /// <param name="axis"></param>
        /// <returns>投影的坐标</returns>
        private float ProjectPoint(Vector3 point, Vector3 axis)
        {
            Vector3 projectPoint = Vector3.Project(point, axis);
            float result = projectPoint.magnitude * Mathf.Sign(Vector3.Dot(projectPoint, axis));
            return result;
        }
    }

  有兴趣的小伙伴可以关注一波

 o(* ̄▽ ̄*)ブ

猜你喜欢

转载自blog.csdn.net/flj135792468/article/details/120759839