长话短说

1.Define Quad Tree

四叉树的作用就是从原点开始根据一定的范围画出周围的四个象限,然后每个子象限继续划分为四个孙子象限,以此类推,到达递归深度最大值时停止。

2.在碰撞检测中的作用

如果场景里有1万个障碍物和2个子弹,那么子弹需要和每个障碍物进行一次碰撞检测,复杂度Omn。而我们进行了空间划分之后,只需要将子弹与其所在的区域内的障碍物进行检测即可,复杂度Omlogn。
相当于将碰撞检测分成了两部分,第一部分(粗测)尽可能将不会碰到的物体扔掉,从所有的物体中筛选出与子弹最接近最可能碰到的物体,第二部分(精测)使用尽可能精确的碰撞检测公式去检测是否碰撞,简单的有几行代码就能写出来的简单几何体碰撞,复杂的有GJK算法等。

3.Unity中的实现

如果把二分查找的每个步骤都存储起来,那么最终的结果看起来就像是一个二叉树,那么可以参考二分查找的步骤去创建。

3.1.设计

类名 作用
Obstacle 障碍物
QTree 四叉树集合的封装类
QNode 四叉树的每个节点
Bound 包围盒,定义物体的碰撞体积,这里用的无旋转矩形

需要的初始化参数:

参数
每个节点内最大物体数量
四叉树坐标和大小
四叉树的最大递归深度

3.2 坑

3.2.1 边界问题

如果一个物体正好脚踩N个象限,如何对敌?
两种方法,1.脚踩的每个象限都存储该物体的索引,好处是分的清楚,检测的效率高,但缺点是不好管理,因为这样会产生一大堆同样的索引,占内存。2.将该物体交给能完整的包住该物体的父节点,优点是号管理,每个物体都只属于一个节点,但缺点是,举个例子,如果有个比较小的物体正好在四叉树根节点的中心位置,那么任何要进行碰撞检测的物体都要与该物体检测,增加了许多无用的检测,效率低。
我选的第二个方法,因为我觉得游戏中需要移动的碰撞体相比静态物体还是少数,可以通过提前手动修改静态物体位置的方法尽量减少效率低的问题。

3.2.2 动态更新

假设最坏的情况,场景中的每个碰撞体都是移动的,那么问题来了,怎么更新呢?很好理解,删除+插入。但是每一帧重建整个四叉树显然是不现实的,所以这里面有很多优化的trick,比如只更新可能移动的物体;比如只搜索原物体附近的区域,进行删除和插入,但是这样解决不了闪现的问题;或者将物体新坐标与旧坐标检测是否在同一区域的方法来剪枝,等等。
本文暂时不考虑优化。

3.2.3 单个插入和集体插入的不一致

这个是我自己造的词和坑。
举个例子,假如有100个障碍物,每个节点最多存1个,如果是一起插入四叉树,那么根节点大概率是不存物体的,因为有100个障碍物都可以插入根节点,早就超出单个节点的最大容量了。而单个插入就不同了,你在插入时并不知道其他节点的信息,那么第一个插入的物体一定就在根节点。
不过我感觉这两者区别的影响也并不是很大。

3.3 思路

3.3.1 创建四叉树

拿到要插入的物体的集合后
(1).计算该节点四个象限内和跨象限的物体
(2).(1)如果全部节点个数小于节点最大物体个数或者到达最大递归深度,直接全部存到该节点中
(2).(2)否则跨象限的物体存到该节点中,而四个象限的物体传入四个子节点的构造函数中。

3.3.2 检索

(1).遍历节点内的所有物体,进行碰撞检测
(2).如果节点为空,检查该物体在该节点的哪个象限,递归的去找该象限的所属节点

3.3.2 删除

(1).从物体的所属节点中删除该物体
(2).如果该物体的所属节点内没有物体且该节点没有子节点,则删除该节点。
(3).递归的检查该节点的父节点是否需要删除

3.3.3 插入

(1).(1)遇到最大深度的节点或者该节点内的物体未到达最大容量,直接插入该节点
(1).(2)否则,仿照创建四叉树的套路

3.4 代码

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
public enum QTNodeType
{/// <summary>/// 左上/// </summary>LT = 0,/// <summary>/// 右上/// </summary>RT = 1,/// <summary>/// 右下/// </summary>RB = 2,/// <summary>/// 左下/// </summary>LB = 3,/// <summary>/// 根节点/// </summary>Root = 4,
}public class Bound
{public float X;public float Y;public float Width;public float Height;public Bound(float x, float y, float w,float h){X = x;Y = y;Width = w;Height = h;}public static bool CheckCollision(Bound b1, Bound b2){float[] rec1 = { b1.X - b1.Width / 2, b1.Y - b1.Height / 2, b1.X + b1.Width / 2, b1.Y + b1.Height / 2, };float[] rec2 = { b2.X - b2.Width / 2, b2.Y - b2.Height / 2, b2.X + b2.Width / 2, b2.Y + b2.Height / 2, };return !(rec1[2] <= rec2[0] || rec2[2] <= rec1[0] || rec1[3] <= rec2[1] || rec2[3] <= rec1[1]);}
}public class QTree
{public int MaxObjCnt;public int MaxDepth;public QNode Root;public Bound Bound;public List<Obstacle> ObjList;public QTree(List<Obstacle> objList,Bound bound,int maxObjCnt,int maxDepth){this.MaxObjCnt = maxObjCnt;this.ObjList = objList;this.MaxDepth = maxDepth;Bound = bound;Root = new QNode(objList,Bound, 0,Root, this,QTNodeType.Root);}public void RenderTree(){Root.RenderNode();}public void SearchNode(Bullet bullet){Root.SearchNode(bullet);}public void InsertObj(Obstacle obj){Root.InsertObj(obj);}public void DeleteObj(Obstacle obj){obj.BelongedNode.DeleteObj(obj);}public void UpdateObj(Obstacle obj){// 删除后重新插入DeleteObj(obj);InsertObj(obj);}
}public class QNode
{private QTNodeType Type;private QTree BelongedTree;private Bound Bound;private int Depth;private List<Obstacle> ObjList;private QNode Father;private List<QNode> ChildList;public QNode(List<Obstacle> intersectionObjs, Bound bound, int depth, QNode father, QTree qTree, QTNodeType type){this.Bound = bound;this.Depth = depth;this.Father = father;this.BelongedTree = qTree;this.Type = type;ObjList = new List<Obstacle>();ChildList = new List<QNode>();List<Obstacle> _LTlist = new List<Obstacle>();List<Obstacle> _RTlist = new List<Obstacle>();List<Obstacle> _RBlist = new List<Obstacle>();List<Obstacle> _LBlist = new List<Obstacle>();List<Obstacle> _Rootlist = new List<Obstacle>();List<List<Obstacle>> _AllLists = new List<List<Obstacle>> { _LTlist, _RTlist, _RBlist, _LBlist, _Rootlist };foreach (Obstacle obj in intersectionObjs){if (obj.isLoaded == false){#region 检测物体与几个子节点相交List<bool> _blist = new List<bool>{CheckIntersection(obj.Bound, QTNodeType.LT),CheckIntersection(obj.Bound, QTNodeType.RT),CheckIntersection(obj.Bound, QTNodeType.RB),CheckIntersection(obj.Bound, QTNodeType.LB)};int _intersectionTimes = 0;foreach (bool b in _blist)_intersectionTimes += b ? 1 : 0;#endregion#region 该物体穿过多个子节点if (_intersectionTimes >= 2) {_Rootlist.Add(obj);}#endregion#region 完全在某个子节点内else if (_intersectionTimes==1){for (int i = 0; i < 4; i++)if (_blist[i]){_AllLists[i].Add(obj);break;}}#endregionelsethrow new Exception("正在检测父节点外的物体");}}int _totalCnt = 0;foreach (List<Obstacle> list in _AllLists)_totalCnt += list.Count();if (_totalCnt <= BelongedTree.MaxObjCnt || Depth >= BelongedTree.MaxDepth){#region 直接全部放入父节点foreach (List<Obstacle> list in _AllLists)foreach (Obstacle obj in list){AddObj(obj);}#endregion}else{#region 父节点物体放入根节点,子节点物体继续向下递归foreach (Obstacle obj in _Rootlist){AddObj(obj);}#region 递归创建四个节点for (int i = 0; i < 4; i++){if (_AllLists[i].Count() != 0){QTNodeType _type = (QTNodeType)i;ChildList.Add(new QNode(_AllLists[i], GenBound(_type), depth + 1, this, BelongedTree, _type));}}#endregion#endregion}}public QNode(Bound bound, int depth, QNode father, QTree qTree, QTNodeType type){this.Bound = bound;this.Depth = depth;this.Father = father;this.BelongedTree = qTree;this.Type = type;ObjList = new List<Obstacle>();ChildList = new List<QNode>();}public void RenderNode(){foreach (QNode node in ChildList){node.RenderNode();}Gizmos.DrawWireCube(new Vector3(Bound.X, 0, Bound.Y), new Vector3(Bound.Width,0, Bound.Height));}public void SearchNode(Bullet bullet){if (ObjList.Count() != 0 && CheckPointInside(bullet.Bound, QTNodeType.Root)){// 检查每次递归遇到的节点内的物体(非叶子节点内为交叉物体,叶子节点内为非交叉物体)// 在这里检查碰撞for (int i = 0;i<ObjList.Count;i++){ObjList[i].HighLightObs = true;if (Bound.CheckCollision(bullet.Bound, ObjList[i].Bound)){ObjList[i].DestroySelf();bullet.DestroySelf();return;}}}// 找不到就从最可能的子节点入手继续递归的找foreach (QNode qNode in ChildList){if (CheckPointInside(bullet.Bound, qNode.Type)){qNode.SearchNode(bullet);return;}}}public void InsertObj(Obstacle obj){// 遇到最大深度的叶子,直接添加,不再递归if (Depth == BelongedTree.MaxDepth||ObjList.Count()<BelongedTree.MaxObjCnt){AddObj(obj);return;}#region 检测与节点的几个子节点相交List<bool> _bList = new List<bool>{CheckIntersection(obj.Bound, QTNodeType.LT),CheckIntersection(obj.Bound, QTNodeType.RT),CheckIntersection(obj.Bound, QTNodeType.RB),CheckIntersection(obj.Bound, QTNodeType.LB)};int _intersectionTimes = 0;foreach (bool b in _bList)_intersectionTimes += b ? 1 : 0;if (_intersectionTimes >= 2)  //要添加的物体与多个子节点相交{//  直接加入父节点AddObj(obj);return;}else if (_intersectionTimes == 1)  // 在子节点的位置内{QNode _node;for (int i = 0; i < 4; i++){if (_bList[i]){QTNodeType _type = (QTNodeType)i;_node = GetNode((QTNodeType)i);if (_node == null){_node = new QNode(GenBound(_type), Depth + 1, this, BelongedTree, _type);ChildList.Add(_node);_node.AddObj(obj);}else{_node.InsertObj(obj);}}}}elsethrow new Exception("该物体不在树的范围内");#endregion}public void DeleteObj(Obstacle obj){obj.BelongedNode = null;ObjList.Remove(obj);QNode qNode = this;// 该节点无物体且无叶子,即删除while (qNode.ObjList.Count() == 0 && qNode.ChildList.Count() == 0){QNode _deleteNode = qNode;qNode = qNode.Father;_deleteNode.Father.ChildList.Remove(_deleteNode);}}public Vector2 GetPos(){return new Vector3(Bound.X,Bound.Y);}public Vector2 GetSize(){return new Vector2(Bound.Width, Bound.Height);}private Bound GenLT(){return new Bound(Bound.X - Bound.Width / 4, Bound.Y + Bound.Height / 4, Bound.Width / 2, Bound.Height / 2);}private Bound GenRT(){return new Bound(Bound.X + Bound.Width / 4, Bound.Y + Bound.Height / 4, Bound.Width / 2, Bound.Height / 2);}private Bound GenRB(){return new Bound(Bound.X + Bound.Width / 4, Bound.Y - Bound.Height/ 4, Bound.Width / 2, Bound.Height / 2);}private Bound GenLB(){return new Bound(Bound.X - Bound.Width / 4, Bound.Y - Bound.Height / 4, Bound.Width / 2, Bound.Height / 2);}private Bound GenBound(QTNodeType type){switch (type){case QTNodeType.LT:return GenLT();case QTNodeType.RT:return GenRT();case QTNodeType.RB:return GenRB();case QTNodeType.LB:return GenLB();default:throw new Exception("不支持的type类型");}}private bool CheckIntersection(Bound b,QTNodeType type){Bound _nb;switch (type){case QTNodeType.LT: _nb = GenLT(); break;case QTNodeType.RT: _nb = GenRT(); break;case QTNodeType.RB: _nb = GenRB(); break;case QTNodeType.LB: _nb = GenLB(); break;default: throw new NotImplementedException("未指定的QTNodeType");}float[] rec1 = {b.X - b.Width / 2,b.Y - b.Height / 2,b.X + b.Width / 2,b.Y + b.Height / 2,};float[] rec2 = {_nb.X - _nb.Width / 2,_nb.Y - _nb.Height / 2,_nb.X + _nb.Width / 2,_nb.Y + _nb.Height / 2,};return !(rec1[2] <= rec2[0] || rec2[2] <= rec1[0] || rec1[3] <= rec2[1] || rec2[3] <= rec1[1]);}private bool CheckPointInside(Bound b, QTNodeType type){switch (type){case QTNodeType.Root:return (b.X < Bound.X + Bound.Width / 2) && (b.X > Bound.X - Bound.Width / 2) &&(b.Y < Bound.Y + Bound.Height / 2) && (b.Y > Bound.Y - Bound.Height / 2);case QTNodeType.LB:return (b.X < Bound.X) && (b.X > Bound.X - Bound.Width / 2) &&(b.Y < Bound.Y) && (b.Y > Bound.Y - Bound.Height / 2);case QTNodeType.LT:return (b.X < Bound.X) && (b.X > Bound.X - Bound.Width / 2) &&(b.Y < Bound.Y + Bound.Height / 2) && (b.Y > Bound.Y);case QTNodeType.RB:return (b.X < Bound.X + Bound.Width / 2) && (b.X > Bound.X) &&(b.Y < Bound.Y) && (b.Y > Bound.Y - Bound.Height / 2);case QTNodeType.RT:return (b.X < Bound.X + Bound.Width / 2) && (b.X > Bound.X) &&(b.Y < Bound.Y + Bound.Height / 2) && (b.Y > Bound.Y);default: throw new NotImplementedException("未指定的QTNodeType");}}private QNode GetNode(QTNodeType type){foreach (QNode node in ChildList){if (node.Type == type){return node;}}return null;}private void AddObj(Obstacle obj){ObjList.Add(obj);obj.isLoaded = true;obj.BelongedNode = this;}
}

四.结果

五.参考

空间划分的数据结构(四叉树/八叉树/BVH树/BSP树/k-d树)
【物理篇】从零搭建2D物理系统②——用松散四叉树结合网格法来划分场景
空间索引-四叉树的实现及其应用

用四叉树加速碰撞检测相关推荐

  1. js小球与边框碰撞反弹_四叉树在碰撞检测中的应用

    缘起 <你被追尾了>中预告了加速碰撞检测的算法--四叉树(for 2D),所以本文就来学习一下. 分析 首先是为什么要使用四叉树进行优化,其实<你被追尾了>中已经说了,这里简单 ...

  2. Box2D v2.0.1 用户手册

    Box2D v2.0.1 用户手册 原文:Box2D v2.0.2 User Manual 译者:Aman JIANG(江超宇),翻译信息. 1. 导言 1.1 关于 Box2D 是一个用于游戏的 2 ...

  3. Cocos2d之Box2d基础知识

    一.box2d基础知识 1.关于 Box2D 是一个用于游戏的 2D 刚体仿真库.从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你 ...

  4. Cocos2d-X Box2D内容讲解

    一.box2d基础知识 1.关于 Box2D 是一个用于游戏的 2D 刚体仿真库.从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你 ...

  5. 创建一个urdf机器人_ROS机械臂开发:创建机器人URDF模型

    1. URDF建模原理 URDF是一种使用XML格式描述的机器人模型文件 Links:坐标系与几何关系 Joints:Links之间的关系 标签 描述机器人某个刚体部分的外观和物理属性: 描述连杆尺寸 ...

  6. jBox2D v2.0.1 用户手册

    原文:Box2D v2.0.2 User Manual 译者:Aman JIANG(江超宇),翻译信息. 1. 导言 1.1 关于 Box2D 是一个用于游戏的 2D 刚体仿真库.程序员可以在他们的游 ...

  7. 短视频特效“耍花招”:线上投篮、摆摊,让画中人摇摆,浅谈腾讯微视的AR基建...

    出品 | AI科技大本营 魔性的背景音乐.酷炫的AR特效.多元的内容题材,让大众欲罢不能的短视频App正在成为内容生产和传播主要渠道之一. 而短视频平台为了抢夺用户,正在通过各种方式提升用户体验.其中 ...

  8. Box2D一:基础知识

    一.box2d基础知识 1.关于 Box2D 是一个用于游戏的 2D 刚体仿真库.从游戏的视角来看,物理引擎就是一个程序性动画(procedural animation)的系统,而不是由动画师去移动你 ...

  9. 在这里!修练成一个值得托付的人,焦虑会顿减......

    大家好,我是晓衡! Cocos Dashboard 商城悄然上线两周时间,你有注意到吗? 晓衡时常吹嘘,立志助力社区开发者『技术精进,副业变现』打造个人品牌.实现个人长期价值!哈哈,我们又前进了一大步 ...

  10. Havok Physics 2012(1)

    Havok Physics 2012 Chapter 1. Introduction 文章目录 Havok Physics 2012 Chapter 1. Introduction 1. What i ...

最新文章

  1. Redis持久化实践及灾难恢复模拟
  2. 解决:build_attrs() takes at most 2 arguments (3 given)
  3. matplotlib设置画布大小_PyTorch 49.matplotlib模块
  4. QT每日一练day19:设计一个记事本
  5. [Leetcode] 70. Climbing Stairs Java
  6. ASP.NET MVC 4 中Jquery上传插件Uploadify简单使用-版本:3.2.1
  7. Photoshop中预设资源安装方式-笔刷、动作、渐变、形状、样式等
  8. 《完全写作指南》随笔
  9. 任天堂Switch蓝牙发射器方案
  10. BZOJ 3224: Tyvj 1728 普通平衡树(替罪羊树)
  11. dubbo服务端线程池耗尽Server side threadpool is exhausted
  12. 爱奇艺财报电话会议实录:第三季度可能还无法实现盈利
  13. 计算机d盘可以格式化吗,电脑d盘格式化对电脑有影响吗
  14. 【小程序】微信小程序自定义导航栏及其封装
  15. 如何使用ArcGIS将Excel数据转换为shp格式
  16. 同时支持手机号、用户名、邮箱登录
  17. spring容器destory时没有调用destory-method解决方法
  18. 【机器学习】实战系列
  19. C语言中的cbp文件是什么,C语言编程入门——函数(上)
  20. DNS域名解析服务详解

热门文章

  1. RocketMq中MessageQueue的分配
  2. LeapFTP 使用指南
  3. 非常实用的“绿色电子地图”
  4. 情人节程序员用HTML网页表白【表白对话】 HTML5七夕情人节表白网页源码 HTML+CSS+JavaScript
  5. Useful webs
  6. android手机向电脑传输文件,手机怎么用数据线连接电脑传输文件
  7. 基于Neo4j计算网络节点中心性(Closeness、betweenness Centrality)
  8. 联想小新锁屏壁纸怎么换_联想_ThinkPad|ThinkCentre|ThinkStation服务与驱动下载_常见问题...
  9. Google Chrome Your browser is not compatible with FlexPaper问题解决
  10. 【黑苹果教程】说点闲话