️UNITY实战进阶-三维AABB包围盒详解-6_unity aabb-程序员宅基地

技术标签: unity  UNITY实战进阶  

  • 前言

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

        而常见的包围盒有:

  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 连续检测


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

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/flj135792468/article/details/120654391

智能推荐

AT24 I2C EEPROM解析及测试-程序员宅基地

文章浏览阅读937次,点赞2次,收藏3次。 关键词:AT24、I2C、nvmem、EEPROM。1. AT24C介绍AT24C是一款采用I2C通信的EEPROM,相关驱动涉及到I2C和nvmem。I2C是读写数据的通道,nvmem将AT24C注册为nvmem设备。 2.源码分析 2.1 DTSat24是挂在i2c总线下的设备,硬件接到哪个i2c,DTS中也需要对应修改。其中需要注意的是,status不能是disabled..._i2c读字节 大于128怎么办

Struts_001_struts-001-程序员宅基地

文章浏览阅读601次。Struts2 实例1 1.新建一个MyEclipse的企业级WebProject,接着在WEB-INF/lib下添加Struts2必须用的JAR文件(commons-fileupload-1.2.1.jar、commons-io-1.3.2.jar、commons-logging-1.0.4.jar、freemarker-2.3.15.jar、ognl-2.7.3.jar、struts2_struts-001

梯形图c语言转换软件,梯形图转化为HEX软件-程序员宅基地

文章浏览阅读857次。当前软件可以支持使用STC10F08E芯片了,该芯片速度比普通89C52芯片快8~12倍,性能更优。不论使用STC89C52还是STC10F08E芯片,在转换时都不需要专门设置,直接转换即可。软件包中的STC下载程序也已更新,可以支持对STC10系列芯片进行编程。2008年10月28日修订:增加2个按钮,可以直接软件中调用fxgpwin软件和stc-isp软件,方便使用;帮助文件改为通用的CHM格..._梯形图转c语言软件

L1-049 天梯赛座位分配(模拟)_隔座位安排一个人,自动换行-程序员宅基地

文章浏览阅读231次。题目链接 https://pintia.cn/problem-sets/994805046380707840/problems/994805081289900032天梯赛每年有大量参赛队员,要保证同一所学校的所有队员都不能相邻,分配座位就成为一件比较麻烦的事情。为此我们制定如下策略:假设某赛场有 N 所学校参赛,第 i 所学校有 M[i] 支队伍,每队 10 位参赛选手。令每校选手排成一列..._隔座位安排一个人,自动换行

Python图像水平方向错切变换数学原理及实现_错切变换系数-程序员宅基地

文章浏览阅读1k次,点赞2次,收藏7次。图像的几何变换在图像处理中被经常使用,其中图像错切变换也是常用的图像变换。图像错切错切变换包括水平方向错切变换和竖直方向错切变换,其数学原理很简单,就是简单的矩阵乘法。本文给出了图像水平方向错切变换的Python详细实现过程(纯手工)。_错切变换系数

分布式快照-程序员宅基地

文章浏览阅读834次。这篇论文是Chandy和Lamport大神的作品,理论性非常强。如果想设计分布式死锁检测算法、分布式数据库全局快照或者判断分布式系统是否处于某个稳定状态(stable),可以深入阅读。原文更精彩。_分布式快照

随便推点

Modbus TCP通信协议详解_modbus tcp通讯协议-程序员宅基地

文章浏览阅读3.6k次,点赞3次,收藏12次。MODBUS/TCP是简单的、中立厂商的用于管理和控制自动化设备的MODBUS系列通讯协议的派生产品,显而易见,它覆盖了使用TCP/IP协议的“Intranet”和“Internet”环境中MODBUS报文的用途。_modbus tcp通讯协议

aix开启图形化界面_用xmanager远程连接AIX图形界面方法-程序员宅基地

文章浏览阅读756次。版本Xmanager Enterprise 4AIX Version 6.1.4.0(确认已启动CDE图形界面)安装产品前先查看IBM官网关于AIX的信息连接过程方法一安装完Xmanager Enterprise 4会有下面几个快捷方式:打开最下面的Xstart,配置对应的ip和密码,Protocol配置成TELNET,如下:在Command选项下点点红框中的小三角,选6 CDE;先后点右上方的S..._aix远程图形界面

如何获取 ExecutorService 当前活动的线程数_查询executorservice当前信息-程序员宅基地

文章浏览阅读1.1w次,点赞3次,收藏8次。我们在做多线程的时候,想要查看下当前线程池有多少活动的线程,如何获取呢?请看下面做的例子即可得到:import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;/*** @Descri..._查询executorservice当前信息

photoshop cc2019快捷键_pscc2019快捷键ctrl+shift+j-程序员宅基地

文章浏览阅读4.7k次,点赞2次,收藏8次。一、工具箱(多种工具共用一个快捷键的可同时按【Shift】加此快捷键选取)矩形、椭圆选框工具 【M】移动工具 【V】套索、多边形套索、磁性套索 【L】魔棒工具 【W】裁剪工具 【C】切片工具、切片选择工具 【K】喷枪工具 【J】画笔工具、铅笔工具 【B】像皮图章、图案图章 【S】历史画笔工具、艺术历史画笔 【Y】像皮擦、背景擦除、魔术像皮擦 【E】渐变工具、油漆桶工具 【G】模糊、锐化、涂抹工具 【R】减淡、加深、海棉工具 【O】路径选择工具、直接..._pscc2019快捷键ctrl+shift+j

DDD概念复杂难懂,实际落地如何设计代码实现模型?-程序员宅基地

文章浏览阅读1.3k次,点赞2次,收藏8次。伴随着业务系统复杂度的不断提升,以及微服务架构等分布式技术体系的大行其道,领域驱动设计(Domain Driven Design,DDD),日渐成为系统建模领域的主流设计思想和模式。在DDD中,引入了限界上下文、聚合、实体、值对象、领域事件、资源库、应用服务等一系列核心概念。通过这些概念,开发人员可以开展系统设计和实现工作。但是,DDD中的这些概念相对都比较抽象,甚至有些晦涩难懂。再往相通或类似问题点上靠,我认为实质上对于复杂难懂的概念的理解和把握,我们一开始不必过于纠结这些概念本身,而是可以把它们与现实中

产品经理进修第二天 从想法到开发_乱子xx-程序员宅基地

文章浏览阅读358次。产品经理每天最常说的词是什么?那必须是用户需求啊。一个产品存在的意义就是,能够帮助用户解决一个之前无法解决的问题, 或者提供一个比之前的解决方法要强 10、100 倍的解决方案。所以如果这个产品无法解决用户需求, 那么这个产品根本没有存在的意义, 这也就是为什么用户需求是产品开发最重要的部分。用户需求,是大家每天最常说的词,也是误区最多的词。 一个最大的问题就是,产品经理写了几十页的用户需求文档,挂了各种各样精彩绝妙的韦恩图、曲线图、UI 图,结果却发现这个产品解决的痛点实际上根本就不存在。用户需求一定要立_乱子xx

推荐文章

热门文章

相关标签