❤️UNITY实战进阶-三维AABB包围盒详解-6

  • 前言

        碰撞检测问题在虚拟现实、计算机辅助设计与制造、游戏、机器人等方面都有着广泛的应用,而包围盒算法是进行碰撞检测的重要方法之一。

        而常见的包围盒有:

  1. AABB包围盒(Axis-aligned bounding box)
  2. 包围球(Sphere)
  3. OBB包围盒(Oriented bounding box)
  4. 凸包包围盒(Convex Hull)
  5. ...

 在Unity中的Collider包含:


  • 介绍

        在游戏中,为了简化物体之间的碰撞检测运算,通常会对物体创建一个规则的几何外形将其包围。故AABB包围盒被称为轴对齐包围盒
        AABB包围盒构造比较简单存储空间小,但紧密性差,尤其对不规则几何形体,冗余空间很大,当对象旋转时,无法对其进行相应的旋转(使用OBB包围盒)。
       从算法角度来看,只需要2个点pointMinpointMax即可描述AABB包围盒。

AABB包围盒与OBB包围盒的最直接的区别就是:
1.AABB包围盒是不可以旋转的
2.OBB包围盒是可以旋转的,也就是有向的。 


  • 二维场景中AABB包围盒

        二维场景中的面片碰撞如下图所示:

我们将蓝黄两个面片各自的4个角投影到XY轴上
蓝色面片的Y轴方向最大点坐标为Y1,最小点坐标Y2,X轴方向最小点坐标X1,最大点坐标X2
黄色面片的Y轴方向最大点坐标为Y3,最小点坐标Y4,X轴方向最小点坐标X3,最大点坐标X4

        图中红色区域为各轴上的的重叠部分。
        可以看出,AABB碰撞检测具有如下规则:
        蓝色面片与黄色面片分别沿两个坐标轴的投影,只有在两个坐标轴都发生重叠的情况下,两个物体才意味着发生了碰撞。


  • 三维场景中AABB包围盒

        三维场景中物体的AABB包围盒是一个六面体,对于二维坐标系来讲只是多了一个Z轴
        所以实际上在三维场景中物体的AABB碰撞检测依然可以采用四个点信息的判定来实现。

        即:从A物体的八个顶点与B物体的八个顶点分别选出两个最大与最小的顶点进行对比。

         如上图所示:只要确定了图中黑色点部分的坐标,就可以确定八个顶点的全部信息了。

 代码中定义接口:

 public interface IMathAABB
 {
     Vector3 MinVector { get; }

     Vector3 MaxVector { get; }

     Vector3 Center { get; }

     Vector3[] Corners { get; }

     /// <summary>
     /// Gets the center point of the bounding box.
     /// </summary>
     /// <returns>获取中心点</returns>
     Vector3 GetCenter();

     /// <summary>
     ///  Near face, specified counter-clockwise looking towards the origin from the positive z-axis.
     ///  verts[0] : left top front
     ///  verts[1] : left bottom front
     ///  verts[2] : right bottom front
     ///  verts[3] : right top front
     ///  Far face, specified counter-clockwise looking towards the origin from the negative z-axis.
     ///  verts[4] : right top back
     ///  verts[5] : right bottom back
     ///  verts[6] : left bottom back
     ///  verts[7] : left top back
     /// </summary>
     /// <returns>获取包围盒八个顶点信息</returns>
     void GetCorners();

     /// <summary>
     /// Tests whether this bounding box intersects the specified bounding object.
     /// </summary>
     /// <returns>判断两个包围盒是否碰撞</returns>
     bool Intersects(IMathAABB aabb);

     /// <summary>
     /// check whether the point is in.
     /// </summary>
     /// <returns>返回这个点是否在包围盒中</returns>
     bool ContainPoint(Vector3 point);

     /// <summary>
     /// Sets this bounding box to the smallest bounding box
     /// that contains both this bounding object and the specified bounding box.
     /// </summary>
     /// <returns>生成一个新的包围盒 同时容纳两个包围盒,新的包围盒: min各轴要是其他两个最小的那个,max各轴要是其他两个最大的那个</returns>
     void Merge(IMathAABB box);

     /// <summary>
     /// Sets this bounding box to the specified values.
     /// </summary>
     /// <param name="min"></param>
     /// <param name="max"></param>
     /// <returns>设置</returns>
     void SetMinMax(Vector3 min, Vector3 max);

     /// <summary>
     /// reset min and max value.
     /// </summary>
     /// <returns>重置</returns>
     void ResetMinMax();

     bool IsEmpty();


 }

  • AABBCC类

  public class AABBCC : MonoBehaviour, IMathAABB
    {
        //修改此值控制m_CalcMin
        [SerializeField]
        private Vector3 m_Min = -Vector3.one;

        //修改此值控制m_CalcMax
        [SerializeField]
        private Vector3 m_Max = Vector3.one;

        [SerializeField, AABBDisable]
        private Vector3 m_Center = Vector3.zero;

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

        [SerializeField]
        private Transform Target;

        public Vector3 MinVector
        {
            get
            {
                return m_RealCalcMin;
            }
        }

        public Vector3 MaxVector
        {
            get
            {
                return m_RealCalcMax;
            }
        }

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

        public Vector3 Center
        {
            get
            {
                return m_Center;
            }
        }

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

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

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

        // Update is called once per frame
        private void Update()
        {
            UpdatePosition();
        }


        /// <summary>
        /// 更新位置
        /// </summary>
        private void UpdatePosition()
        {
            // position
            if (Target != null)
            {
                SetMinMax(m_Min * 0.5f + Target.position, m_Max * 0.5f + Target.position);
            }
            else
            {
                SetMinMax(m_Min * 0.5f + transform.position, m_Max * 0.5f + transform.position);
            }
        }

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

        public bool Intersects(IMathAABB aabb)
        {
            //就是各轴 互相是否包含,(aabb 包含  当前包围盒)||  (当前的包围盒 包含 aabb)
            return ((m_RealCalcMin.x >= aabb.MinVector.x && m_RealCalcMin.x <= aabb.MaxVector.x) || (aabb.MinVector.x >= m_RealCalcMin.x && aabb.MinVector.x <= m_RealCalcMax.x)) &&
                   ((m_RealCalcMin.y >= aabb.MinVector.y && m_RealCalcMin.y <= aabb.MaxVector.y) || (aabb.MinVector.y >= m_RealCalcMin.y && aabb.MinVector.y <= m_RealCalcMax.y)) &&
                   ((m_RealCalcMin.z >= aabb.MinVector.z && m_RealCalcMin.z <= aabb.MaxVector.z) || (aabb.MinVector.z >= m_RealCalcMin.z && aabb.MinVector.z <= m_RealCalcMax.z));
        }

        public bool ContainPoint(Vector3 point)
        {
            if (point.x < m_RealCalcMin.x) return false;
            if (point.y < m_RealCalcMin.y) return false;
            if (point.z < m_RealCalcMin.z) return false;
            if (point.x > m_RealCalcMax.x) return false;
            if (point.y > m_RealCalcMax.y) return false;
            if (point.z > m_RealCalcMax.z) return false;
            return true;
        }

        public void Merge(IMathAABB box)
        {
            // 计算新的最小点坐标
            m_RealCalcMin.x = Mathf.Min(m_RealCalcMin.x, box.MinVector.x);
            m_RealCalcMin.y = Mathf.Min(m_RealCalcMin.y, box.MinVector.y);
            m_RealCalcMin.z = Mathf.Min(m_RealCalcMin.z, box.MinVector.z);

            // 计算新的最大点坐标
            m_RealCalcMax.x = Mathf.Max(m_RealCalcMax.x, box.MaxVector.x);
            m_RealCalcMax.y = Mathf.Max(m_RealCalcMax.y, box.MaxVector.y);
            m_RealCalcMax.z = Mathf.Max(m_RealCalcMax.z, box.MaxVector.z);

            GetCenter();
            GetCorners();
        }

        public void SetMinMax(Vector3 min, Vector3 max)
        {
            this.m_RealCalcMin = min;
            this.m_RealCalcMax = max;
            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();
        }

    }

AABBDisable属性为在视图窗口无法修改此值

画线:使用Unity.Debug.DrawLine使用:

#if UNITY_EDITOR
            // draw lines
            Debug.DrawLine(Corners[0], Corners[1], m_DebugLineColor);
            Debug.DrawLine(Corners[1], Corners[2], m_DebugLineColor);
            Debug.DrawLine(Corners[2], Corners[3], m_DebugLineColor);
            Debug.DrawLine(Corners[3], Corners[0], m_DebugLineColor);

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

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

  •   效果

当然你也可以是用Unity封装好的UnityEngine.Bounds


  •  注意事项

        由于是在Update中每一帧去检测碰撞,所以当物体在某一帧移速过快,超过包围盒的距离,就会导致碰撞不产生。

       可以参考:Discrete 离散检测、Continuous 连续检测


 如果对你有帮助的话,能否关注一波

猜你喜欢

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