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

2023-11-01

  • 前言

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

        而常见的包围盒有:

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


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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

❤️UNITY实战进阶-三维AABB包围盒详解-6 的相关文章

  • Unity—UGUI

    每日一句 读数 学习 去更远的地方 才能摆脱那些你不屑一顾的圈子 目录 InputFiled输入框 例 用户名和密码 Toggle组件 案例 冷却效果 InputFiled输入框 Text Component 输入文本组件 Text输入内容
  • unity game界面按下play会不断闪烁,按下暂停键(pause)或者中止/下一步(step),game界面的画面会接连变化

    没找到答案 改了两个下午的程序 改完还是这样 后来发现是FixedUpdate Update与OnDrawGizmos的问题 OnDrawGizmos是每帧都会绘制 用FixedUpdate理所当然就那啥了 分析的时候 就突然想到是不是这俩
  • Unity中loading页加载的实现

    首先创建一个Global cs 使用单例用于存储场景的名字 便于后续脚本的调用 此脚本不必挂载在游戏物体上 using UnityEngine using System Collections public class Global Mon
  • c#获取cpu序列号

  • Unity动画控制器animator.CrossFade

    需要特别注意 1 CrossFade虽然可以不用任何逻辑来链接而直接跳转 但是CrossFade只能覆盖其他动画 当当前动画播放完毕而没有跳出这个动画时再次调用CrossFade将会失败 造成动画依旧停在原位 参数animator Cros
  • Unity Shader入门精要第七章 基础纹理之遮罩纹理

    Unity系列文章目录 文章目录 Unity系列文章目录 前言 一 实践 参考 前言 遮罩纹理 mask texture 是本章要介绍的最后一种纹理 它非常有用 在很多商业游戏中 都可以见到它的身影 那么什么是遮罩呢 简单来讲 遮罩允许我们
  • Unity中UI框架的使用1-添加面板、显示Loading页面

    其中BasePanel和Canvas都是挂在面板的预制物上的 1 导入我们的UI框架 本篇文章中有用的是两个UIPanelType NUIManager和NBasePanel 会放在文章最后供大家使用 2 先将我们做好的Panel设置成预制
  • Unity保存图片到相册

    Unity保存图片到Android相册 Java 纯文本查看 复制代码 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
  • unity3d切换场景Application.LoadLevel(1)含义

    Application LoadLevel 1 场景ID
  • unity3d image组件不显示

    需要将UI组件放到画布下面
  • 【原神游戏开发日志1】缘起

    原神游戏开发日志1 缘起 版权声明 本文为 优梦创客 原创文章 您可以自由转载 但必须加入完整的版权声明 文章内容不得删减 修改 演绎 相关学习资源见文末 大家好 最近看到原神在TGA上频频获奖 作为一个14年经验的游戏开发行业的老兵 我就
  • 【Unity】如何让Unity程序一打开就运行命令行命令

    背景 Unity程序有时依赖于某些服务去实现一些功能 此时可能需要类似打开程序就自动运行Windows命令行命令的功能 方法 using UnityEngine using System Diagnostics using System T
  • 【Unity】运行时创建曲线(贝塞尔的运用)

    Unity 运行时创建线 贝塞尔的运用 1 实现的目标 在运行状态下创建一条可以使用贝塞尔方法实时编辑的网格曲线 2 原理介绍 2 1 曲线的创建 unity建立网格曲线可以参考 Unity程序化网格体 的实现方法 主要分为顶点 三角面 U
  • Unity学习笔记

    一 旋转欧拉角 四元数 Vector3 rotate new Vector3 0 30 0 Quaternion quaternion Quaternion identity quaternion Quaternion Euler rota
  • U3D游戏开发中摇杆的制作(UGUI版)

    在PC端模拟摇杆 实现玩家通过控制摇杆让玩家移动 以下是完整代码 using System Collections using System Collections Generic using UnityEngine using Unity
  • 游戏开发常见操作梳理之NPC任务系统

    多数游戏存在任务系统 接下来介绍通过NPC触发任务的游戏制作代码 using System Collections using System Collections Generic using UnityEngine
  • 游戏开发创建操作之玩家信息系统的建立

    游戏一般都需要玩家信息系统 那么我们应该如何搭建玩家信息系统 接下来我将展示一种简单的方法 完整代码如下 using System Collections using System Collections Generic using Uni
  • 游戏开发常见操作梳理之NPC药品商店系统(NGUI版)

    后续会出UGUI Json的版本 敬请期待 游戏开发中经常会出现药品商店 实际操作与武器商店类似 甚至根据实际情况可以简化设置 废话不多说 直接上代码 药品商店的源码 using System Collections using Syste
  • 游戏开发常见操作梳理之小地图的制作

    游戏中一般存在小地图系统 实际上就是设置一个新的摄像机放置在玩家的正上方 然后在小地图上显示新摄像机看见的东西就可以了 在小地图上一般存在放大地图和缩小地图的按钮可以方便放大和缩小地图 这些操作是如何实现的呢 接下来直接上核心代码 usin
  • 游戏开发常见操作系列之敌人系统的开发一(U3D)

    在开发游戏的过程中 我们常常会出现一些敌人攻击我们玩家 并且实现掉血以及死亡的现象 敌人还会源源不断地生成 这是怎么制作的呢 接下来为大家提供方法 其中使用了NGUI 后续会更新其它方法 敬请期待 使用HUDText实现扣血时显示文本 直接

随机推荐

  • 解决Could not load dynamic library ‘libcudart.so.11.0‘; dlerror: libcudart.so.11.0妙招

    一 问题 最近学习AI 需要在Ubuntu下安装TensorFlow 结果TensorFlow装好后 import TensorFlow时报错 Could not load dynamic library libcudart so 11 0
  • IDEA发布全新字体,极度舒适!

    近日 知名设计软件IDEA IntelliJ IDEA 发布了一款全新的字体 Ideographic 这款字体不仅在设计上独具匠心 同时还具有极高的舒适度 下面 我们就来详细了解一下这款字体的特点 IDEA是一款广泛应用于软件开发和设计领域
  • Android Studio 新建drawable-hdpi、drawable-mdpi等

    Android Studio 新建drawable hdpi drawable mdpi 在不同的模式 Project Android 的文件夹中查看文件夹 如果文件夹丢失 您可以轻松添加它们 1 在 res 文件夹上右键 New gt A
  • C++ VScode配置libtorch,并调用PyTorch模型

    C VS配置libtorch 调用TorchScript模型 获取pytorch模型 转成C 模型 下载配置libtorch库 VS 1 首先是下载libtorch库 2 VS中配置 C 调用模型 获取pytorch模型 将训练好的模型保存
  • discuz手机客户端java,Discuz 触屏手机版支持视频播放的方法

    if defined IN MOBILE if strpos msglower media FALSE message preg replace media w s url message if strpos msglower audio
  • WEB安全之:Oracle 数据库 SQL 注入

    郑重声明 本笔记编写目的只用于安全知识提升 并与更多人共享安全知识 切勿使用笔记中的技术进行违法活动 利用笔记中的技术造成的后果与作者本人无关 倡导维护网络安全人人有责 共同维护网络文明和谐 SQL 注入 Oracle 数据库 1 Orac
  • as汇编命令(来自 深入理解Linux内核)

    as汇编命令 来自 深入理解Linux内核 as汇编命令 汇编命令是指示汇编器操作方式的伪指令 汇编命令用于要求汇编器为变量分配空间 确定程序开始地址 指定当前汇编的区 修改位置计数器值等 所有汇编命令的名称都以 开始 其余是字符 并且大小
  • 基于R语言的数据可视化:平行坐标轴图

    基于R语言的数据可视化 平行坐标轴图 数据可视化是数据分析过程中重要的一环 它能够帮助我们更直观地理解数据之间的关系 趋势和模式 在R语言中 有许多包可以用于实现不同类型的数据可视化 其中 GGally包提供了丰富的功能 可以用来绘制多变量
  • 工业机器人三点工具定位法图文_工业机器人工具坐标系的设置

    何淼 摘要 该文以FANUC工业机器人为例 从工具坐标系设置的意义出发 分析了不同机器人设置工具坐标系的方法 成功建立了机器人新的工具坐标系 为机器人精确的运动控制奠定基础 关键词 工业机器人 坐标系 TCP 中图分类号 TP319 文献标
  • 垂直同步

    vblank时间 显示器显示一帧的时间 显卡或芯片输出的图像数据写在后缓存里 屏幕读取前缓存的图像数据并显示 后缓存数据写入完毕后 前后缓存进行交换 如果前缓存的图片写入速度与屏幕读取图像的刷新率不匹配会发生画面撕裂 解决办法 垂直同步 当
  • JAVA中那些令人眼花缭乱的锁

    一 开局一张图带你了解java相关的锁 二 乐观锁和悲观锁 1 悲观锁 悲观锁对应于生活中悲观的人 悲观的人总是想着事情往坏的方向发展 举个生活中的例子 假设厕所只有一个坑位了 悲观锁上厕所会第一时间把门反锁上 这样其他人上厕所只能在门外等
  • mysql 优化杂记

    慢日志分析 SELECT user id AS userId charm AS num FROM db charm WHERE user id NOT IN ORDER BY num DESC userId LIMIT 性能分析 not i
  • Python 爬虫逆向

    Python 爬虫逆向是指使用 Python 语言来编写爬虫程序 用于爬取网站的信息 爬虫逆向一般指的是通过分析网站的网页代码和加载流程 来确定网站信息获取的方式 并使用爬虫程序来模拟这种方式 从而获取网站的信息 爬虫逆向的主要目的是通过自
  • 为什么spark读取本地的json文件会自动去hdfs上去找?读取不到本地的文件

    问题描述 遇到的问题如上图所示 我在spark中读取的json文件明明是集群本地的文件 可是他偏偏就自作聪明去了hdfs上面去找我的文件 那肯定会报错啊 我hdfs上怎么可能有这个文件嘛 原因 我在spark中的运行环境配置了yarn关联
  • 协调器是如何获取终端的IEEE地址并自动对其分配网络短地址呢

    协调器是如何获取终端的IEEE地址并自动对其分配网络短地址呢 字号 订阅 协调器是如何获取终端的IEEE地址并自动对其分配网络短地址呢 猜想如下 终端上电后稳定后就开始寻找周围是否存在网络 向周围发射广播 该广播内容包含自己的身份信息 即M
  • Koa2框架中使用bcrypt实现密码加密

    bcrypt是一个用于密码哈希的库 它使用了单向哈希函数 因此即使在数据库中存储了哈希后的密码 也不容易还原出原始密码 下面是在Koa 2中使用bcrypt的基本步骤 使用npm npm install bcrypt 使用yarn yarn
  • 手把手教你mysql基本操作

    MySQL的基本操作 首先我们要知道怎么打开Mysql客户端 打开开始界面 这两个都是Mysql的客户端 随便打开哪个都可以 如果设置了密码的就输入自己的密码 没有设置密码的则不需要输入 直接就可以进行操作 输入密码正确后界面如下 接下来我
  • java 简述类的封装性、继承性、多态性

    一 什么是封装性 继承性 多态性 封装性 通俗说就是一个盒子 多个对象 功能 组件等装在一个盒子里 内部具体是什么不知道 用到它时 使用特定方法或功能去调用它 即声明一个变量 其属性值是private 不能给其变量直接赋值 但可以通过方法构
  • 利用尾插法建立单链表

    PTA 5 3本题目要求利用尾插法建立单链表 输入格式 输入数据为若干正整数 最后以 1表示结尾 1不算在序列内 不要处理 所有数据之间用空格分隔 输入样例 1 2 3 4 5 6 7 8 9 1 输出样例 1 2 3 4 5 6 7 8
  • ❤️UNITY实战进阶-三维AABB包围盒详解-6

    前言 碰撞检测问题在虚拟现实 计算机辅助设计与制造 游戏 机器人等方面都有着广泛的应用 而包围盒算法是进行碰撞检测的重要方法之一 而常见的包围盒有 AABB包围盒 Axis aligned bounding box 包围球 Sphere O