【原理】[宽相检测]空间划分-四叉树八叉树松散划分

【从Unity物理系统开始探索游戏物理】专栏-直达

碰撞检测与宽相检测

解决的问题‌:

碰撞检测用于判断物体间是否发生交集,而宽相检测(Broad Phase)是其前置阶段,旨在快速筛选可能碰撞的物体对,减少精确检测的计算量。

解决方案‌:

  • 宽相检测‌:通过空间划分(如四叉树、网格)或包围盒层次结构(BVH)快速过滤不相交物体。
  • 窄相检测‌:对宽相筛选后的物体对使用分离轴定理(SAT)或GJK算法进行精确碰撞判断。

流程‌:

  • 构建空间数据结构(如四叉树);
  • 遍历物体,插入到对应空间节点;
  • 查询相邻节点中的物体,生成候选碰撞对;
  • 对候选对执行精确碰撞检测

四叉树(2D空间划分)

核心原理

四叉树通过递归将2D空间划分为四个相等的象限(通常为正方形或矩形),每个节点最多包含4个子节点‌。划分过程基于以下规则:

  • 终止条件‌:节点内物体数量≤阈值(如10个)或达到最大递归深度(如5层)‌。
  • 插入逻辑‌:若物体完全包含于某子节点,则插入该子节点;否则保留在当前节点(松散四叉树允许物体跨节点)‌。

具体流程示例

场景‌:划分一个100×100的2D区域,包含15个物体。

  • 根节点‌:包含所有15个物体,超过阈值(10个),触发分割;
  • 第一次分割‌:划分为4个50×50的子区域(NW、NE、SW、SE);
  • 二次分割‌:若NE区域仍有8个物体,继续分割为4个25×25子区域;
  • 终止‌:当所有子区域物体数≤10或深度达5层时停止‌

四叉树实现‌:

  • 采用C#实现二维空间划分,包含边界检查、插入和范围查询功能

  • 每个节点最多存储4个点后触发分割,递归构建树结构

  • 提供区域查询方法用于空间检索

  • QuadTree.cs

    using System;
    using System.Collections.Generic;
    
    public class QuadTree
    {
        public class Bounds
        {
            public float xMin, yMin, xMax, yMax;
            public bool Contains(float x, float y) => 
                x >= xMin && x <= xMax && y >= yMin && y <= yMax;
            public bool Intersects(Bounds other) => 
                !(other.xMin > xMax || other.xMax < xMin || 
                  other.yMin > yMax || other.yMax < yMin);
        }
    
        const int CAPACITY = 4;
        Bounds boundary;
        List<(float x, float y)> points = new List<(float, float)>();
        QuadTree[] children = new QuadTree[4];
        bool divided = false;
    
        public QuadTree(Bounds bounds) => boundary = bounds;
    
        void Subdivide()
        {
            float halfW = (boundary.xMax - boundary.xMin) / 2;
            float halfH = (boundary.yMax - boundary.yMin) / 2;
            children[0] = new QuadTree(new Bounds { 
                xMin = boundary.xMin, yMin = boundary.yMin,
                xMax = boundary.xMin + halfW, yMax = boundary.yMin + halfH
            });
            // 其余3个子节点类似定义...
            divided = true;
        }
    
        public bool Insert(float x, float y)
        {
            if (!boundary.Contains(x, y)) return false;
            if (points.Count < CAPACITY) {
                points.Add((x, y));
                return true;
            }
            if (!divided) Subdivide();
            foreach (var child in children) {
                if (child.Insert(x, y)) return true;
            }
            return false;
        }
    
        public List<(float, float)> Query(Bounds range)
        {
            var found = new List<(float, float)>();
            if (!boundary.Intersects(range)) return found;
            foreach (var p in points) {
                if (range.Contains(p.x, p.y)) found.Add(p);
            }
            if (divided) {
                foreach (var child in children) {
                    found.AddRange(child.Query(range));
                }
            }
            return found;
        }
    }
    

八叉树(3D空间划分)

核心原理

八叉树是四叉树的三维扩展,将空间划分为8个立方体子节点(每个轴方向均分)‌。关键特性包括:

  • 松散划分‌:允许子节点边界扩展(如1.5倍原尺寸),减少物体跨节点导致的频繁更新‌。
  • 编码管理‌:采用Morton码对叶子节点线性编码,加速空间查询。

具体流程示例

场景‌:划分一个8×8×8的3D空间,包含20个点云数据。

  • 根节点‌:包含所有20个点,超过阈值(如8个),触发分割;
  • 第一次分割‌:划分为8个4×4×4子立方体;
  • 二次分割‌:若某子立方体仍有6个点且未达最大深度,继续分割为2×2×2;
  • 终止‌:子立方体尺寸≤阈值或点数量≤阈值时停止。

八叉树实现‌:

  • 扩展至三维空间,每个节点最多8个子节点

  • 包含三维边界检测和空间查询方法

  • 采用相同的容量触发分割策略

  • Octree.cs

    using System;
    using System.Collections.Generic;
    
    public class Octree
    {
        public class Bounds
        {
            public float xMin, yMin, zMin, xMax, yMax, zMax;
            public bool Contains(float x, float y, float z) => 
                x >= xMin && x <= xMax && 
                y >= yMin && y <= yMax && 
                z >= zMin && z <= zMax;
        }
    
        const int CAPACITY = 8;
        Bounds boundary;
        List<(float x, float y, float z)> points = new List<(float, float, float)>();
        Octree[] children = new Octree[8];
        bool divided = false;
    
        public Octree(Bounds bounds) => boundary = bounds;
    
        void Subdivide()
        {
            float halfX = (boundary.xMax - boundary.xMin) / 2;
            float halfY = (boundary.yMax - boundary.yMin) / 2;
            float halfZ = (boundary.zMax - boundary.zMin) / 2;
            children[0] = new Octree(new Bounds {
                xMin = boundary.xMin, yMin = boundary.yMin, zMin = boundary.zMin,
                xMax = boundary.xMin + halfX, yMax = boundary.yMin + halfY, zMax = boundary.zMin + halfZ
            });
            // 其余7个子节点类似定义...
            divided = true;
        }
    
        public bool Insert(float x, float y, float z)
        {
            if (!boundary.Contains(x, y, z)) return false;
            if (points.Count < CAPACITY) {
                points.Add((x, y, z));
                return true;
            }
            if (!divided) Subdivide();
            foreach (var child in children) {
                if (child.Insert(x, y, z)) return true;
            }
            return false;
        }
    
        public List<(float, float, float)> Query(Bounds range)
        {
            var found = new List<(float, float, float)>();
            if (!Intersects(boundary, range)) return found;
            foreach (var p in points) {
                if (range.Contains(p.x, p.y, p.z)) found.Add(p);
            }
            if (divided) {
                foreach (var child in children) {
                    found.AddRange(child.Query(range));
                }
            }
            return found;
        }
    
        static bool Intersects(Bounds a, Bounds b) => 
            !(b.xMin > a.xMax || b.xMax < a.xMin || 
              b.yMin > a.yMax || b.yMax < a.yMin ||
              b.zMin > a.zMax || b.zMax < a.zMin);
    }
    
  • 均采用值元组存储坐标点

  • 使用泛型集合提高类型安全性

  • 支持动态数据插入和高效空间查询

松散树

松散树(如松散四叉树/八叉树)与普通树的核心区别在于节点边界处理方式和空间划分策略。松散树通过扩大子节点覆盖范围来减少动态对象频繁跨节点移动时的重构开销,而普通树采用严格的几何划分。

松散树的实现原理

  • 节点边界扩展‌:每个子节点的实际物理范围是标准树的k倍(通常k=1.5-2),但逻辑划分点保持不变。例如松散四叉树中,子节点覆盖区域扩大为原区域的2倍,而中心点与标准四叉树一致。
  • 对象分配规则‌:对象只需部分落入扩展后的节点边界即可被该节点管理,避免了严格几何划分导致的频繁节点切换。
  • 层级关系保留‌:父节点与子节点的包含关系仍遵循树形结构,但允许相邻节点存在重叠区域。

与普通树的对比特性

特性普通树松散树
节点边界严格几何划分(无重叠)扩展边界(相邻节点可重叠)
动态对象适应性频繁触发重构减少重构次数
查询效率精确但可能漏检跨边界对象冗余检测但覆盖更全面
存储开销较低较高(需维护重叠区域)

松散四叉树划分2D空间

  • LooseQuadtree.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    public class LooseQuadtree {
        private class Node {
            public Bounds bounds;
            public List<GameObject> objects = new List<GameObject>();
            public Node[] children;
            public int level;
            
            public Node(Bounds bounds, int level) {
                this.bounds = bounds;
                this.level = level;
            }
        }
    
        private Node root;
        private int maxLevels = 5;
        private float looseness = 1.5f; // 松散系数
    
        public LooseQuadtree(Bounds bounds) {
            root = new Node(bounds, 0);
        }
    
        public void Insert(GameObject obj) {
            Insert(root, obj);
        }
    
        private void Insert(Node node, GameObject obj) {
            if (node.level == maxLevels) {
                node.objects.Add(obj);
                return;
            }
    
            if (node.children == null) {
                Split(node);
            }
    
            foreach (var child in node.children) {
                if (Contains(child.bounds, obj.transform.position)) {
                    Insert(child, obj);
                    return;
                }
            }
            node.objects.Add(obj);
        }
    
        private void Split(Node node) {
            node.children = new Node[4];
            float size = node.bounds.size.x / 2 * looseness;
            Vector3 center = node.bounds.center;
    
            node.children[0] = new Node(new Bounds(
                new Vector3(center.x - size/2, center.y, center.z - size/2), 
                new Vector3(size, float.MaxValue, size)), node.level + 1);
    
            // 类似创建其他3个象限节点...
        }
    
        public List<GameObject> Query(Bounds area) {
            List<GameObject> results = new List<GameObject>();
            Query(root, area, results);
            return results;
        }
    
        private void Query(Node node, Bounds area, List<GameObject> results) {
            if (!node.bounds.Intersects(area)) return;
    
            results.AddRange(node.objects);
            if (node.children != null) {
                foreach (var child in node.children) {
                    Query(child, area, results);
                }
            }
        }
    }
    

松散八叉树划分3D空间

  • LooseOctree.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    public class LooseOctree {
        private class Node {
            public Bounds bounds;
            public List<GameObject> objects = new List<GameObject>();
            public Node[] children;
            public int level;
        }
    
        private Node root;
        private int maxLevels = 5;
        private float looseness = 1.5f;
    
        public LooseOctree(Bounds bounds) {
            root = new Node() { bounds = bounds, level = 0 };
        }
    
        public void Insert(GameObject obj) {
            Insert(root, obj);
        }
    
        private void Insert(Node node, GameObject obj) {
            if (node.level == maxLevels) {
                node.objects.Add(obj);
                return;
            }
    
            if (node.children == null) {
                Split(node);
            }
    
            foreach (var child in node.children) {
                if (child.bounds.Contains(obj.transform.position)) {
                    Insert(child, obj);
                    return;
                }
            }
            node.objects.Add(obj);
        }
    
        private void Split(Node node) {
            node.children = new Node[8];
            float size = node.bounds.size.x / 2 * looseness;
            Vector3 center = node.bounds.center;
    
            // 创建8个子节点(示例仅显示第一个)
            node.children[0] = new Node() {
                bounds = new Bounds(
                    new Vector3(center.x - size/2, center.y - size/2, center.z - size/2),
                    new Vector3(size, size, size)),
                level = node.level + 1
            };
            // 类似创建其他7个八分体节点...
        }
    
        public List<GameObject> Query(Bounds area) {
            List<GameObject> results = new List<GameObject>();
            Query(root, area, results);
            return results;
        }
    
        private void Query(Node node, Bounds area, List<GameObject> results) {
            if (!node.bounds.Intersects(area)) return;
    
            results.AddRange(node.objects);
            if (node.children != null) {
                foreach (var child in node.children) {
                    Query(child, area, results);
                }
            }
        }
    }
    

查询示例代码

代码说明:

  • 松散性通过looseness系数实现,子节点尺寸大于父节点一半
  • 四叉树处理2D空间(Y轴忽略),八叉树处理3D空间
  • 查询时通过边界相交测试实现空间过滤
  • 递归分割策略直到达到最大深度
  • 动态扩展结构支持插入和查询操作
csharp
// 2D松散四叉树查询示例
var quadtree = new LooseQuadtree(new Bounds(Vector3.zero, new Vector3(100, 0, 100)));
quadtree.Insert(gameObject1);
var results2D = quadtree.Query(new Bounds(new Vector3(10,0,10), new Vector3(20,0,20)));

// 3D松散八叉树查询示例
var octree = new LooseOctree(new Bounds(Vector3.zero, new Vector3(100, 100, 100)));
octree.Insert(gameObject2);
var results3D = octree.Query(new Bounds(new Vector3(10,10,10), new Vector3(20,20,20)));

松散树相比标准树的主要优势在于减少了边界物体导致的频繁节点分裂,通过扩大节点覆盖范围来提升性能。


【从Unity物理系统开始探索游戏物理】专栏-直达
(欢迎点赞留言探讨,更多人加入进来能更加完善这个探索的过程,🙏)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

淡海水

感谢支持 共同进步 好运++

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值