用四叉树加速碰撞检测

长话短说

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)
  • 2
    点赞
  • 21
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
四叉树是一种常用的空间分割数据结构,可以用于优化场景中物体的碰撞检测四叉树将整个场景划分为四个象限,并将每个象限再次划分为四个象限,递归下去直到每个区域内只包含一个或零个物体。这样,可以快速地判断两个物体是否在同一个区域内,从而减少不必要的碰撞检测。 以下是基于四叉树碰撞检测的示例代码: ```javascript // 创建四叉树 function QuadTree(bound, n) { this.bound = bound; // 区域范围 this.n = n; // 最大物体数量 this.objects = []; // 包含的物体 this.quadrants = []; // 四个象限 } // 将物体添加到四叉树中 QuadTree.prototype.addObject = function(obj) { // 如果当前节点还没有四个象限,则创建四个象限 if (!this.quadrants.length) { var x = this.bound.x; var y = this.bound.y; var w = this.bound.width / 2; var h = this.bound.height / 2; this.quadrants.push(new QuadTree(new Rectangle(x, y, w, h), this.n)); this.quadrants.push(new QuadTree(new Rectangle(x + w, y, w, h), this.n)); this.quadrants.push(new QuadTree(new Rectangle(x, y + h, w, h), this.n)); this.quadrants.push(new QuadTree(new Rectangle(x + w, y + h, w, h), this.n)); } // 将物体添加到对应的象限中 for (var i = 0; i < this.quadrants.length; i++) { if (this.quadrants[i].bound.contains(obj)) { this.quadrants[i].addObject(obj); return; } } // 如果物体不在任何一个象限中,则将其添加到当前节点 this.objects.push(obj); // 如果当前节点已经包含了最大数量的物体,则分裂成四个象限 if (this.objects.length > this.n) { for (var i = 0; i < this.objects.length; i++) { for (var j = 0; j < this.quadrants.length; j++) { if (this.quadrants[j].bound.contains(this.objects[i])) { this.quadrants[j].addObject(this.objects[i]); this.objects.splice(i, 1); i--; break; } } } } }; // 获取当前节点以及子节点包含的所有物体 QuadTree.prototype.getObjects = function() { var objects = this.objects; for (var i = 0; i < this.quadrants.length; i++) { objects = objects.concat(this.quadrants[i].getObjects()); } return objects; }; // 检测两个物体是否相交 function intersects(obj1, obj2) { return obj1.x + obj1.width > obj2.x && obj1.y + obj1.height > obj2.y && obj1.x < obj2.x + obj2.width && obj1.y < obj2.y + obj2.height; } // 碰撞检测函数 function collisionDetection(objects) { var quadTree = new QuadTree(new Rectangle(0, 0, canvas.width, canvas.height), 4); // 将所有物体添加到四叉树中 for (var i = 0; i < objects.length; i++) { quadTree.addObject(objects[i]); } // 遍历四叉树,检测相交的物体 var checked = []; for (var i = 0; i < objects.length; i++) { var obj = objects[i]; var candidates = quadTree.getObjects().filter(function(candidate) { return candidate !== obj && !checked.includes(candidate); }); for (var j = 0; j < candidates.length; j++) { if (intersects(obj, candidates[j])) { // 处理碰撞 } } checked.push(obj); } } ``` 在上面的代码中,`QuadTree` 表示四叉树节点,`Rectangle` 表示矩形区域。首先,创建一个 `QuadTree` 对象,将所有物体添加到其中,然后遍历 `QuadTree`,检测相交的物体并进行处理。 这里使用的是基于递归的方法,即先将物体添加到对应的象限中,如果某个象限包含的物体数量超过了最大值,则将其继续分割成四个象限,直到每个区域内只包含一个或零个物体。然后通过遍历四叉树来获取所有物体,并检测相交的物体。由于相交的物体可能会被检测多次,所以需要使用 `checked` 数组来避免重复检测。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值