四叉树(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
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值