四叉树(QuadTree)图例、应用、实现

图例

  • 划分示例图
    在这里插入图片描述
  • 查询示例图
    在这里插入图片描述
  • 解释一下,黄线是射线表面点到哪里,记为点P
    • 小蓝色框表明能包含点P的最小空间节点
    • 红框表明能包含点P的三角形,记为三角形T
    • 大蓝色框表明能包含三角形T的最小空间节点
  • 可以看到已经使用四叉树只需要遍历少量三角形就可以查询到目标三角形

应用

  • 本文应用于2D三角形导航网格中三角形查询
    • 给一个坐标,找到包含这个坐标的三角形

情景分析

  • 导航三角形网格中的三角形不可能互相包含
  • 从根节点开始找到包含三角形的最小的子节点,遍历这个节点下的所有物体,找到包含顶点的三角形

要解决的问题

  • 肯定会存在多个很大的shape横跨多个区域,所以非叶子节点要能存物体
  • 需要根据一个物体的位置和尺寸定位到节点
  • 最小的尺寸怎么确定
    • 如果用最大的物体的尺寸作为分割大小会导致树节点很少,性能退化成O(n)
    • 如果用最小的物体的尺寸作为分割大小会导致节点特别细碎
    • 根据使用时的性能,看自己的数据情况,如果小三角形比较多,最小节点设置的小一些,否则大一些

实现

  • 本文的运行环境:Unity + c#
using System;
using System.Collections.Generic;
using UnityEngine;

namespace DC.Lockstep.navigation
{
    public struct TriangleShape
    {
        public Vector2[] vertices;
        public int a, b, c;

        public Vector2 pA => vertices[a];
        
        public Vector2 pB => vertices[b];

        public Vector2 pC => vertices[c];

        public Vector2 GetCenter()
        {
            return (vertices[a] + vertices[b] + vertices[c]) / 3f;
        }
    }

    public class QuadTreeNode
    {
        public static float min_size = 4;

        protected QuadTreeNode[] mChildren;

        protected List<TriangleShape> mContent;

        protected Rect mArea;

        public QuadTreeNode()
        {
        }

        public QuadTreeNode(Rect area)
        {
            SetArea(area);
        }

        public void SetArea(Rect area)
        {
            mArea = area;
        }

        public Rect GetArea()
        {
            return mArea;
        }

        public QuadTreeNode[] GetChildren()
        {
            return mChildren;
        }

        public List<TriangleShape> GetContent()
        {
            if (null == mContent)
            {
                return new List<TriangleShape>();
            }
            return new List<TriangleShape>(mContent);
        }

        public int GetShapeCnt()
        {
            return mContent?.Count ?? 0;
        }

        public bool Insert(TriangleShape shape)
        {
            if (!Contains(shape))
            {
                return false;
            }

            // 已经是最小分割
            if (mArea.width <= min_size)
            {
                Append(shape);
                return true;
            }

            var index = GetIndex(shape);
            // 如果在某个子节点里面
            if (index >= 0)
            {
                if (null == mChildren)
                {
                    mChildren = new QuadTreeNode[4];
                }

                if (mChildren[index] == null)
                {
                    mChildren[index] = CreateChild(index);
                }

                var suc = mChildren[index].Insert(shape);
                if (suc) return true;
            }

            // 在当前节点
            Append(shape);

            return true;
        }

        protected void Append(TriangleShape shape)
        {
            if (null == mContent)
            {
                mContent = new List<TriangleShape>();
            }
            mContent.Add(shape);
        }

        public bool Contains(TriangleShape shape)
        {
            return Contains(shape.pA) && Contains(shape.pB) && Contains(shape.pC);
        }

        public bool Contains(Vector2 pos)
        {
            return mArea.Contains(pos);
        }

        protected QuadTreeNode CreateChild(int index)
        {
            if (index < 0 || 3 < index)
            {
                throw new ArgumentException("index must in [0,3], now is " + index);
            }
            //左下角0,左上角2,右上角3,右下角1
            var child = new QuadTreeNode();
            switch (index)
            {
                case 0:
                    child.SetArea(Rect.MinMaxRect(mArea.xMin, mArea.yMin, mArea.center.x, mArea.center.y));
                    break;
                case 1:
                    child.SetArea(Rect.MinMaxRect(mArea.center.x, mArea.yMin, mArea.xMax, mArea.center.y));
                    break;
                case 2:
                    child.SetArea(Rect.MinMaxRect(mArea.xMin, mArea.center.y, mArea.center.x, mArea.yMax));
                    break;
                case 3:
                    child.SetArea(Rect.MinMaxRect(mArea.center.x, mArea.center.y, mArea.xMax, mArea.yMax));
                    break;
            }
            return child;
        }

        public void Remove(TriangleShape shape)
        {
            if (Contains(shape))
            {
                var index = GetIndex(shape);
                if (index >= 0 && mChildren[index] != null)
                {
                    mChildren[index].Remove(shape);
                    return;
                }

                mContent?.Remove(shape);
            }
        }

        public int GetIndex(TriangleShape shape)
        {
            var i1 = GetIndex(shape.pA);
            var i2 = GetIndex(shape.pA);
            if (i1 != i2)
            {
                return -1;
            }
            var i3 = GetIndex(shape.pA);
            if (i2 != i3)
            {
                return -1;
            }
            return i1;
        }

        public int GetIndex(Vector2 point)
        {
            if (!Contains(point))
            {
                return -1;
            }

            //左下角0,左上角2,右上角3,右下角1
            var index = 0;

            if (point.x > mArea.center.x)
            {
                index += 1;
            }

            if (point.y > mArea.center.y)
            {
                index += 2;
            }
            return index;
        }

        public void GetTriangleShapes(List<TriangleShape> list)
        {
            if (null != mChildren)
            {
                for (int i = 0; i < mChildren.Length; i++)
                {
                    mChildren[i]?.GetTriangleShapes(list);
                }
            }
            if (null != mContent)
            {
                list.AddRange(mContent);
            }
        }

        public QuadTreeNode GetMinContainsNode(Vector2 pos)
        {
            if (!Contains(pos))
            {
                return null;
            }

            if (null != mChildren)
            {
                for (int i = 0; i < mChildren.Length; i++)
                {
                    if(mChildren[i] == null) continue;

                    var node = mChildren[i].GetMinContainsNode(pos);
                    if (null != node && node.GetShapeCnt() > 0)
                    {
                        return node;
                    }
                }
            }
            
            return this;
        }

    }
}

我的测试方式

  • 使用Unity生成导航数据
  • 合并导航网格中相同的三角形,导出成mesh模型文件
  • 使用mesh模型文件中的三角形构造QuadTreeNode
  • 编写QuadTreeNode的可视化脚本获得文中的示例图
  • 将mesh模型放到unity场景,添加检测脚本,检测鼠标点击到模型上的位置,定位到对应的QuadTreeNode
  • 2
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: 下面是一个简单的四叉树的 Python 代码实现四叉树是一种用于管理空间分割的数据结构。它通常用于地图或其他空间中的对象的管理,可以帮助快速查找或搜索某个区域内的对象。 这里我们以一个简单的二维四叉树为例,四叉树的节点中存储着一个矩形,表示节点所代表的区域。每个节点最多有四个子节点,分别代表节点所代表的矩形被四等分之后的四个子区域。 ```python class QuadTreeNode: def __init__(self, rect, depth=0, max_depth=5, max_objects=10): &quot;&quot;&quot; 创建一个四叉树节点。 参数: rect: 节点所代表的矩形区域。 depth: 节点的深度。 max_depth: 四叉树的最大深度。 max_objects: 当节点内的对象数量超过该数量时,需要将节点分裂为四个子节点。 &quot;&quot;&quot; self.rect = rect # 节点所代表的矩形区域 self.depth = depth # 节点的深度 self.max_depth = max_depth # 四叉树的最大深度 self.max_objects = max_objects # 当节点内的对象数量超过该数量时,需要将节点分裂为四个子节点 self.objects = [] # 节点内的对象列表 self.nodes = [] # 子节点列表 def insert ### 回答2: 四叉树是一种特殊的数据结构,将二维空间划分为四个相等的象限,并对每个象限进行相同的划分,直到达到某个预定的停止条件。下面是一个简单的四叉树的Python代码实现: ```python class QuadTreeNode: def __init__(self, x, y, width, height): self.x = x # 节点在二维空间的x坐标 self.y = y # 节点在二维空间的y坐标 self.width = width # 节点的宽度 self.height = height # 节点的高度 self.children = [] # 子节点列表,最多包含4个子节点 self.is_leaf = True # 是否为叶节点 def insert(self, point): # 如果节点不是叶节点,则将点插入到相应的子节点中 if not self.is_leaf: for child in self.children: if child.contains(point): child.insert(point) return # 如果节点是叶节点并且剩余容量大于1,则创建四个子节点并插入到相应的子节点中 if self.is_leaf and (self.width > 1 or self.height > 1): self.is_leaf = False half_width = self.width // 2 half_height = self.height // 2 # 创建四个子节点并插入到相应的子节点中 self.children.append(QuadTreeNode(self.x, self.y, half_width, half_height)) self.children.append(QuadTreeNode(self.x + half_width, self.y, half_width, half_height)) self.children.append(QuadTreeNode(self.x, self.y + half_height, half_width, half_height)) self.children.append(QuadTreeNode(self.x + half_width, self.y + half_height, half_width, half_height)) # 将点插入到相应的子节点中 for child in self.children: if child.contains(point): child.insert(point) return def contains(self, point): return self.x <= point[0] < self.x + self.width and self.y <= point[1] < self.y + self.height # 使用示例 root_node = QuadTreeNode(0, 0, 8, 8) points = [(1, 1), (2, 2), (5, 5), (6, 6)] for point in points: root_node.insert(point) ``` 上述代码实现了一个简单的四叉树结构,可以将二维空间中的点按照空间位置进行分割和存储。代码中的`QuadTreeNode`类表示四叉树的节点,其中包含了节点在二维空间中的坐标、宽度、高度以及子节点列表。`insert`方法用于将一个点插入到相应的节点中,如果节点已经有子节点,则将点插入到相应的子节点中。如果节点是叶节点并且还有剩余容量,则会创建四个子节点,并将点插入到相应的子节点中。`contains`方法用于判断一个点是否在节点的范围内。 使用示例中,创建了一个根节点,并将四个点插入到根节点中。根节点的宽度和高度为8,每个子节点的宽度和高度为根节点的一半。 ### 回答3: 四叉树Quadtree)是一种空间索引树结构,用于将二维平面划分成四个象限。以下是一个简单的四叉树的Python代码实现: ```python class QuadtreeNode: def __init__(self, x, y, width, height): self.x = x # 节点左上角的x坐标 self.y = y # 节点左上角的y坐标 self.width = width # 节点的宽度 self.height = height # 节点的高度 self.children = [] # 节点的子节点列表 self.is_leaf = True # 是否为叶子节点 def divide(self): if self.is_leaf: half_width = self.width // 2 half_height = self.height // 2 # 创建四个子节点 nw = QuadtreeNode(self.x, self.y, half_width, half_height) ne = QuadtreeNode(self.x + half_width, self.y, half_width, half_height) sw = QuadtreeNode(self.x, self.y + half_height, half_width, half_height) se = QuadtreeNode(self.x + half_width, self.y + half_height, half_width, half_height) # 将子节点添加到当前节点 self.children = [nw, ne, sw, se] self.is_leaf = False def insert(self, x, y): if not self.is_in_bound(x, y): return False if not self.children: self.divide() # 将坐标插入子节点中 for child in self.children: if child.is_in_bound(x, y): child.insert(x, y) break def is_in_bound(self, x, y): return self.x <= x < self.x + self.width and self.y <= y < self.y + self.height # 测试代码 root = QuadtreeNode(0, 0, 100, 100) root.insert(10, 10) root.insert(20, 20) root.insert(80, 80) ``` 上述代码中,QuadtreeNode类表示四叉树的节点,每个节点包含了该节点左上角的坐标、宽度和高度等信息,以及子节点列表。节点的is_leaf属性表示是否为叶子节点。在插入数据时,如果当前节点是叶子节点且包含的区域内没有数据,则将节点划分成四个子节点,然后将数据插入到合适的子节点中。这个过程会递归进行,直到节点不能再划分或数据插入到叶子节点中。 上述代码只是一个简单的四叉树实现示例,可以根据具体需求进行扩展和优化。四叉树在空间索引、碰撞检测等领域有广泛应用

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值