cad.net 选择集技术1_四叉树,找邻居,最近图元

动图演示

原理

分裂

通过一个轴向矩形(非旋转)边界进行中心点分裂四个轴向矩形,
0x01 插入时候会一直分裂四个矩形(总是平均的面积),当分裂面积小于你插入 图元矩形面积 就停止分裂.
0x02 限制树的深度,停止分裂.
0x03 Rect字段是int/long的话,每次扩大就是2次幂,最小分裂就是1,到最小就停止分裂.

如下图:

插入的时候,紫色箭头指示图元属于紫色框框,
因为四个子节点都无法单独"拥有"它,它就是属于四个子节点的爸爸的.表现为压着.

还有一种方案是容器内大于某值(10个,20个...)就分裂,然后把当前层图元扔到所属最下一层.
测试了之后发现它建树快,不过搜索慢了一点(也没差多少),但是可以降低树高度,尤其是可以用在储存.

速度

四叉树有两个地方很快,
一个是四分法:
它在搜索每次丢掉3/4进行搜索,时间复杂度为 O(log4(N)),即使查询面积横跨几个区,那也只是复杂度常数.
一个是矩形包含另一个矩形用的是坐标相减,而不是叉乘.
这就是为什么强调它必须是轴向矩形(AABB包围盒)而不是旋转矩形,矩形特性.

试想一下为什么会诞生四叉树:
如果有十万个图元,每次一分为二(二分法)竖着切,那么切到最后还有一个列,把这个列也进行二分法,它就是二叉树.
也正因为它有最后这个列问题,发明的人就想不如把它扩展一个y维度...变成四叉树
那么顺应的有z轴,又扩展...变成八叉树

八叉树呢?
cad的平面化严重,其次是空间判断是利用两次平面(XOY,YOZ)判断....(能一次我为什么要两次呢?
时间复杂度为 O(log8(N)),但是表现起来不会那么快,因为树不均匀呀.

它可以扩展做什么?
优化之前说过的bo算法 == 优化后台选择集的速度

用来对比两份图(虽然我并不觉得这会比序列化对比更快,但是我没有cad源码,不存在序列化图元再反序列化为图元的技术)

优化求交点函数,桌子的求交点函数有问题,
例如一万个点的多段线和一条直线求交,桌子是轮询每个子段,无疑是比O(log4(N))慢很多.

重点步骤

搜索邻居节点和距离最近图元

第一版代码用了一个数组储存四个节点,这个操作不太适用在搜索邻居上面,
因为必须要知道图元所在节点的父节点的相对位置,所以用四个象限明确储存.

搜索邻居因为太复杂了,所以做了一张图说明:

因为北方邻居可能是空的,所以需要再从北方邻居找北方邻居..还是空的..再找...最后找到父节点指针是null,退出

但是最近的节点内的图元,并不是距离最近的图元!这只是亲属关系最近.
所以应该用节点范围作为选区(我节点范围,父节点范围,爷节点范围,曾爷爷节点范围...)平移到目标中心,进行不断扩大选择Query.

四叉树的点要怎么存放?

设计点存放的位置最终是为了搜索速度,
因此我改了用红黑树结构来进行,因为超过X就可以break,减少了很多判断.
而更好的方案是利用点云...但是这又是另一个课题...

c#优化

Gzxl在cpp实现了一个10w图元碰撞是毫秒级的.

我把foreach改成for,从3秒变成1.3秒,因为foreach内部有状态机
(状态机是为了防止某个循环全占用了CPU而发明,在网络开发中有很大的作用),
但是我们这里需要独占CPU速度,因此不需要它.

但是还不是毫秒级,然后他放出了代码给我看,
发现原来是Rect类的问题,微软的System.Windows.Rect类,慢的地方:
A: 在求包含的时候会通过 (x+Width)以及(y+Height)这样增加了不必要的计算,
B: 它的字段,多了一个{get},使得字段被封装成了属性(属性反编译查看就是一个带get_前缀的函数),
这样就跳转了一次函数地址,在10w图元碰撞面前,它从187毫秒变成了400毫秒.

public double X
{
    get { return _x;  }
}

因此,那就只能自己写一个Rect类了,然后节点继承矩形,因为节点就是矩形呀.
public Rect(double left, double bottom, double right, double top)

然后发现节点数组内容有null,用判断过滤比预先过滤更快,
因为CPU缓存预读机制(顺序读取)消耗四个节点比构造一个无null更快.
(SAP算法)最后10w图元碰撞用了74毫秒.

四叉树如果为了更快(粗检测),用float作为Rect的成员可以直接杀一半时间.

优化到极限:
1,改为Strucr of array,面向数据开发...博客链接
2,使用SIMD指令优化,利用512个寄存器...

相关阅读

空间索引-四叉树 这里有C代码.
cad.net 后台选择集

四叉树碰撞并不是最快的碰撞,因为还有SAP
SAP(Sweep and prune)
https://github.com/phenomLi/Blog/issues/22
https://zhuanlan.zhihu.com/p/163590893
https://learnopengl-cn.github.io/06 In Practice/2D-Game/05 Collisions/02 Collision detection/
本质是按列扫描中断法(我起的名字...)

代码

此代码已经收录于IFox

测试命令

#if !HC2020
using Acap = Autodesk.AutoCAD.ApplicationServices.Application;
using Color = Autodesk.AutoCAD.Colors.Color;
using Autodesk.AutoCAD.Runtime;
using Autodesk.AutoCAD.DatabaseServices;
using Autodesk.AutoCAD.Geometry;
using Autodesk.AutoCAD.EditorInput;
#else
using Acap = GrxCAD.ApplicationServices.Application;
using Color = GrxCAD.Colors.Color;
using GrxCAD.Runtime;
using GrxCAD.DatabaseServices;
using GrxCAD.Geometry;
using GrxCAD.EditorInput;
#endif
using System.Collections.Generic;
using System;
//using System.Windows; //cad用这个WPF的Rect会很慢噢
using JoinBox.BasalMath;

namespace JoinBox
{
    /*
     * 这里属于用户调用例子,
     * 调用时候必须要继承它,再提供给四叉树
     * 主要是用户可以扩展属性
     */
    public class CadEntity : QuadEntity
    {
        public ObjectId ObjectId;
        //这里加入其他字段
        public List<QuadEntity>? Link;//碰撞链
        public System.Drawing.Color Color;
        public double Angle;
        public CadEntity(ObjectId objectId, Rect box) : base(box)
        {
            ObjectId = objectId;
        }
        public int CompareTo(CadEntity? other)
        {
            if (other == null)
                return -1;
            return GetHashCode() ^ other.GetHashCode();
        }
        public override int GetHashCode()
        {
            return (base.GetHashCode(), ObjectId.GetHashCode()).GetHashCode();
        }
    }

    public class CmdTest_QuadTreeClass
    {
        QuadTree<CadEntity> _quadTreeRoot;


        #region 四叉树创建并加入
        [CommandMethod("CmdTest_QuadTree1", CommandFlags.DocExclusiveLock)]
        public void CmdTest_QuadTree1()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage("\n生成图元,并加入四叉树");

            Rect dbExt;
            //使用数据库边界来进行
            if (!db.GetValidExtents3d(out Extents3d dbExtent))
            {
                //throw new ArgumentException("画一个矩形");

                //这个初始值的矩形是很有意义,
                //主要是四叉树分裂过程中产生多个Rect,Rect内有很多重复的double值,是否可以内存复用,以此减少内存大小?
                //接着想了一下,Rect可以是int,long,这样可以利用位运算它扩展和缩小,
                //最小就是1,并且可以控制四叉树深度,不至于无限递归.
                //而且指针长度跟值是一样的,所以就不需要复用了,毕竟跳转一个函数地址挺麻烦的.
                //但是因为啊惊懒的原因,并没有单独制作这样的矩形,
                //而且非常糟糕的是,c#不支持模板约束运算符,使得值类型之间需要通过一层接口来委婉处理,拉低了效率..引用类型倒是无所谓..
                //要么忍着,要么换c++去搞四叉树吧
                dbExt = new Rect(0, 0, 1 << 10, 1 << 10);
            }
            else
            {
                var a = new Point(dbExtent.MinPoint.X, dbExtent.MinPoint.Y);
                var b = new Point(dbExtent.MaxPoint.X, dbExtent.MaxPoint.Y);
                dbExt = new Rect(a, b);
            }

            int maximumItems = 1_0000; //生成多少个图元,30万图元±0.5秒,导致cad会令undo出错(八叉树深度过大 treemax)
            //int maximumItems = 500; //生成多少个图元,30万图元±0.5秒,导致cad会令undo出错(八叉树深度过大 treemax)

            //创建四叉树
            _quadTreeRoot = new QuadTree<CadEntity>(dbExt);
            //释放内存,旧的四叉树
            GC.Collect();

            db.Action(tr => {
                //数据库边界
                var databaseBoundary = EntityAdd.AddPolyLineToEntity(dbExt.ToPoints().ToPoint2d());
                tr.AddEntityToMsPs(db, databaseBoundary);

                //随机图元生成
                List<CadEntity> ces = new();  //用于随机获取图元
                var allTime = BasalCurrency.TimeHelper.RunTime(() => {
                    //生成外边界和随机圆形
                    var grc = GenerateRandomCircle(maximumItems, dbExt);
                    foreach (var ent in grc)
                    {
                        //初始化图元颜色
                        ent.ColorIndex = 1; //Color.FromRgb(0, 0, 0);//黑色

                        var edge = new GeometricExtents(ent);

                        //四叉树数据
                        var entRect = new Rect(edge.Edge.LeftLower.ToPoint(), edge.Edge.RightUpper.ToPoint());
                        var entId = tr.AddEntityToMsPs(db, ent);
                        var ce = new CadEntity(entId, entRect);
                        ce.Color = Utility.RandomColor;
                        ces.Add(ce);

                        edge.Dispose();
                    }
                });

                long insertTime = 0;
                //测试只加入四叉树的时间
                insertTime = JoinBox.BasalCurrency.TimeHelper.RunTime(() => {
                    _quadTreeRoot.Insert(ces);
                });

                ed.WriteMessage($"\n加入图元数量:{maximumItems}, 插入四叉树时间:{insertTime / 1000.0}秒, 画圆消耗时间:{allTime / 1000.0}秒");
            });

            CmdTest_CreateNodesRect();


            //*随机刷新动画
#if true2
            ed.ZoomWindow();
            var ran = new Random();
            db.Action(tr => {
                for (int i = 0; i < maximumItems; i++)
                {
                    int n = ran.Next(0, ents.Count - 1);

                    var boEnt = ents[n].ObjectId.ToEntity(tr);
                    var gExt = new GeometricExtents(boEnt);
                    boEnt.Dispose();
                    var edge = gExt.Edge2;

                    var a = new Point(edge.LeftLower.X, edge.LeftLower.Y);
                    var b = new Point(edge.RightUpper.X, edge.RightUpper.Y);

                    var ces = _quadTreeRoot.Query(new Rect(a, b));
                    ces?.ForEach(item => {
                        var ent = item.ObjectId.ToEntity(tr);
                        ent.UpgradeOpen();
                        ent.Color = Color.FromColor(item.Color);
                        ed.UpdateScreenEx(ent);//刷新
                        ent.DowngradeOpen();
                        ent.Dispose();
                    });
                    gExt.Dispose();
                }
            });
#endif
        }

        /// <summary>
        /// 创建随机圆形
        /// </summary>
        /// <param name="createNumber">创建数量</param>
        /// <param name="dbExt">数据库边界</param>
        IEnumerable<Entity> GenerateRandomCircle(int createNumber, Rect dbExt)
        {
            var x1 = (int)dbExt._X;
            var x2 = (int)(dbExt._X + dbExt.Width);
            var y1 = (int)dbExt._Y;
            var y2 = (int)(dbExt._Y + dbExt.Height);

            var rand = Utility.GetRandom();
            for (int i = 0; i < createNumber; i++)
            {
                int x = rand.Next(x1, x2);
                int y = rand.Next(y1, y2);
                yield return EntityAdd.AddCircleToEntity(new Point3d(x, y, 0), 10);
            }
        }

        [CommandMethod("CmdTest_QuadTree2")]
        public void CmdTest_QuadTree2()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage("\n选择单个图元加入已有的四叉树");

            var ss = ed.Ssget();
            if (ss.Count == 0)
                return;

            if (_quadTreeRoot == null)
                ed.WriteMessage("\n请先运行 CmdTest_QuadTree1");

            /* 测试:
             * 为了测试删除内容释放了分支,再重复加入是否报错
             * 先创建 CmdTest_QuadTree1
             * 再减去 CmdTest_QuadTree0
             * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果.
             * 然后加入 CmdTest_QuadTree2
             * 然后原有黑色边界,再生成边界 CmdTest_Create00,对比删除效果.
             */

            List<CadEntity> ces = new();
            db.Action(tr => {
                ss.ForEach(entId => {
                    var ent = entId.ToEntity(tr);
                    var edge = new GeometricExtents(ent);

                    //四叉树数据
                    var entRect = new Rect(edge.Edge.LeftLower.ToPoint(), edge.Edge.RightUpper.ToPoint());
                    var ce = new CadEntity(entId, entRect);
                    ce.Color = Utility.RandomColor;
                    ces.Add(ce);

                    edge.Dispose();
                });
            });

            _quadTreeRoot.Insert(ces);
        }
        #endregion


        #region 四叉树减去节点
        [CommandMethod("CmdTest_QuadTree0")]
        public void CmdTest_QuadTree0()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage("\n四叉树减区");

            if (_quadTreeRoot == null)
            {
                ed.WriteMessage("\n四叉树是空的");
                return;
            }
            var rect = GetCorner(ed);
            if (rect == null)
                return;
            _quadTreeRoot.Remove(rect.Value);
        }

        [CommandMethod("CmdTest_Create00")]
        public void CmdTest_CreateNodesRect()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;
            ed.WriteMessage("\n创建边界");

            if (_quadTreeRoot == null)
            {
                ed.WriteMessage("\n四叉树是空的");
                return;
            }

            //此处发现了一个事务处理的bug,提交数量过多的时候,会导致 ctrl+z 无法回滚,
            //需要把事务放在循环体内部
            //报错: 0x6B00500A (msvcr80.dll)处(位于 acad.exe 中)引发的异常: 0xC0000005: 写入位置 0xFFE00000 时发生访问冲突。
            //画出所有的四叉树节点边界,因为事务放在外面引起
            var nodeRects = new List<Rect>();
            _quadTreeRoot.ForEach(node => {
                nodeRects.Add(node.Box);
                return false;
            });
            var rectIds = new List<ObjectId>();
            foreach (var item in nodeRects)//Count = 97341 当数量接近这个量级
            {
                db.Action(tr => {
                    var pts = item.ToPoints();
                    var rec = EntityAdd.AddPolyLineToEntity(pts.ToPoint2d());
                    rec.ColorIndex = 250;
                    rectIds.Add(tr.AddEntityToMsPs(db, rec));
                });
            }
            db.Action(tr => {
                db.CreateGroup(tr, rectIds, out string name);
            });

            //获取四叉树深度
            int dep = 0;
            _quadTreeRoot.ForEach(node => {
                dep = dep > node.Depth ? dep : node.Depth;
                return false;
            });
            ed.WriteMessage($"\n四叉树深度是: {dep}");
        }
        #endregion


        #region 四叉树查询节点
        [CommandMethod("CmdTest_QuadTree3")]
        public void CmdTest_QuadTree3()
        {
            Ssget(QuadTreeSelectMode.IntersectsWith);
        }

        [CommandMethod("CmdTest_QuadTree4")]
        public void CmdTest_QuadTree4()
        {
            Ssget(QuadTreeSelectMode.Contains);
        }

        void Ssget(QuadTreeSelectMode mode)
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            if (_quadTreeRoot == null)
                return;
            var rect = GetCorner(ed);
            if (rect == null)
                return;

            QuadTreeEvn.SelectMode = mode;
            ed.WriteMessage("选择模式:" + QuadTreeEvn.SelectMode);

            //仿选择集
            db.Action(tr => {
                var ces = _quadTreeRoot.Query(rect.Value);
                ces.ForEach(item => {
                    item.ObjectId.ToEntity(tr, ent => {
                        ent.Color = Color.FromColor(item.Color);
                    });
                });
            });
        }

        public Rect? GetCorner(Editor ed)
        {
            var optionsA = new PromptPointOptions($"{Environment.NewLine}起点位置:");
            var pprA = ed.GetPoint(optionsA);
            if (pprA.Status != PromptStatus.OK)
                return null;
            var optionsB = new PromptCornerOptions(Environment.NewLine + "输入矩形角点2:", pprA.Value)
            {
                UseDashedLine = true,//使用虚线
                AllowNone     = true,//回车
            };
            var pprB = ed.GetCorner(optionsB);
            if (pprB.Status != PromptStatus.OK)
                return null;

            return new Rect(pprA.Value.ToPointV().ToPoint(), pprB.Value.ToPointV().ToPoint());
        }
        #endregion


        #region 四叉树查找邻居和最近图元
        [CommandMethod("CmdTest_QuadTree5")]
        public void CmdTest_QuadTreeFindNeibor()
        {
            var dm = Acap.DocumentManager;
            var doc = dm.MdiActiveDocument;
            var db = doc.Database;
            var ed = doc.Editor;

            var res = ed.GetEntity("\n选择图元,搜索邻近: ");
            if (res.Status != PromptStatus.OK)
                return;

            if (_quadTreeRoot == null)
            {
                ed.WriteMessage("\n四叉树是空的");
                return;
            }

            CadEntity cadEnt = null;
            _quadTreeRoot.ForEach(nodes => {
                foreach (var ent in nodes.Contents)
                {
                    if (ent.ObjectId == res.ObjectId)
                    {
                        cadEnt = ent;
                        return true;
                    }
                }
                return false;
            });
            if (cadEnt == null)
            {
                ed.WriteMessage("\n四叉树上面没有找到你需要的图元");
                return;
            }
            var cadent = _quadTreeRoot.FindNearEntity(cadEnt.Box);
            if (cadent == null)
            {
                ed.WriteMessage("\n四叉树上面没有最近图元啊");
                return;
            }
            db.Action(tr => {
                cadent.ObjectId.ToEntity(tr, ent => {
                    ent.Color = Color.FromColor(cadent.Color);
                });
            });
        }
        #endregion
    }
}

测试命令子函数

db.Action

namespace JoinBox
{
    public static class DbHelper
    {
        /// <summary>
        /// 获取有效的数据库范围
        /// </summary>
        /// <param name="db">数据库</param>
        /// <param name="dbExtent">数据库边界</param>
        /// <param name="tolerance">容差值:图元包围盒会超过数据库边界,用此参数扩大<see langword="dbExtent"/></param>
        /// <returns></returns>
        public static bool GetValidExtents3d(this Database db, out Extents3d dbExtent, double tolerance = 1e-6)
        {
            db.UpdateExt(true);//更新当前模型空间的范围

            var ve = new Vector3d(tolerance, tolerance, tolerance);
            dbExtent = new Extents3d(Point3d.Origin, ve.ToPoint3d());
            //数据库没有图元的时候,min是大,max是小,导致新建出错
            //是这样的
            // min.X == 1E20 && min.Y == 1E20 && min.Z == 1E20 &&
            // max.X == -1E20 && max.Y == -1E20 && max.Z == -1E20)
            var min = db.Extmin;
            var max = db.Extmax;
            if (min.X < max.X)
            {
                dbExtent = new Extents3d(db.Extmin - ve, db.Extmax + ve);
                return true;
            }
            return false;
        }
    }
}

namespace JoinBox.BasalCurrency
{
    public static class TimeHelper
    {
        /// <summary>
        /// 测试运行时间
        /// </summary>
        /// <param name="action"></param>
        public static long RunTime(Action action, string str = null)
        {
            var stopwatch = new Stopwatch();
            stopwatch.Start(); //开始监视代码运行时间
            action.Invoke();
            stopwatch.Stop();//停止监视
            var timespan = stopwatch.ElapsedMilliseconds;          
            if (str != null)
                Console.WriteLine(str + timespan.ToString() + "毫秒");
            return timespan;
        }
    }
}

四叉树函数

四叉树根节点控制器

using System;
using System.Collections.Generic;

/*
 * 四叉树维基百科  http://en.wikipedia.org/wiki/Quadtree
 * 四叉树是一种分区空间的算法,更快找出内部或外部给定区域.
 * 通过一个正交矩形边界进行中心点分裂四个正交矩形,
 * 插入时候会一直分裂四个正交矩形,
 * 当分裂四个节点都无法单独拥有 图元包围盒 就停止分裂,并且你属于这四个节点的父亲.
 * (不包含就是面积少了,就这么一句话看代码看半天),
 * 还可以通过限制树的深度实现加速.
 *
 * 第一版: https://www.codeproject.com/script/Articles/ViewDownloads.aspx?aid=30535
 *
 * 第二版: 找邻居
 * https://blog.csdn.net/dive_shallow/article/details/112438050
 * https://geidav.wordpress.com/2017/12/02/advanced-octrees-4-finding-neighbor-nodes/
 *
 * 1.根节点:控制根节点从而控制所有节点
 * 2.子节点:包含自身根节点,插入矩形的时候进行递归分裂自身,和实现查找.
 * 3.接口:约束都要有正交矩形,否则无法调用"包含"方法
 * 4.选择模式:模仿cad的窗选和框选
 */
namespace JoinBox.BasalMath
{
    /// <summary>
    /// 根节点控制器
    /// </summary>
    /// <typeparam name="TEntity">类型接口约束必须有正交矩形</typeparam>
    public class QuadTree<TEntity> where TEntity : QuadEntity
    {
        #region 成员
        /// <summary>
        /// 根节点
        /// </summary>
        QuadTreeNode<TEntity> _rootNode;

        /// <summary>
        /// 四叉树节点的数目
        /// </summary>
        public int Count { get => _rootNode.CountSubTree; }

        /// <summary>
        /// 点容器(红黑树)
        /// </summary>
        SortedSet<TEntity> _points;
        #endregion

        #region 构造
        /// <summary>
        /// 四叉树根节点控制器
        /// </summary>
        /// <param name="rect">四叉树矩形范围</param>
        /// <param name="minArea">最后一个节点有最小面积</param>
        public QuadTree(Rect rect)
        {
            _rootNode = new QuadTreeNode<TEntity>(rect, null, 0);//初始化根节点
            _points = new();
        }
        #endregion

        #region 方法
        /// <summary>
        /// 通过根节点插入数据项
        /// </summary>
        /// <param name="ent"></param>
        public void Insert(TEntity ent)
        {
            if (ent.IsPoint)
            {
                _points.Add(ent);
                return;
            }
            while (!_rootNode.Contains(ent.Box))
            {
                /*
                 * 四叉树插入时候,如果超出根边界,就需要扩展
                 * 扩展时候有一个要求,当前边界要作为扩展边界的一个象限,也就是反向分裂
                 *
                 * 创建新根,计算原根在新根的位置,
                 * 替换指针:获取新分裂的节点的父节点,判断它哪个儿子是它,
                 * 替换之后可能仍然不包含图元边界,再循环计算.
                 */
                var sq_Left = _rootNode.X;
                var sq_Botton = _rootNode.Y;
                var sq_Right = _rootNode.Right;
                var sq_Top = _rootNode.Top;
                if (ent.Box.Y >= _rootNode.Y)//上↑增殖
                {
                    if (ent.Box.X >= _rootNode.X)
                    {
                        //右上↗增殖
                        sq_Right += _rootNode.Width;
                        sq_Top += _rootNode.Height;
                    }
                    else
                    {
                        //左上↖增殖
                        sq_Left -= _rootNode.Width;
                        sq_Top += _rootNode.Height;
                    }
                }
                else//在下↓
                {
                    if (ent.Box.X >= _rootNode.X)
                    {
                        //右下↘增殖
                        sq_Right += _rootNode.Width;
                        sq_Botton -= _rootNode.Height;
                    }
                    else
                    {
                        //左下↙增殖
                        sq_Left -= _rootNode.Width;
                        sq_Botton -= _rootNode.Height;
                    }
                }
                var rectSquare = new Rect(sq_Left, sq_Botton, sq_Right, sq_Top);

                //四叉树的旧根要作为四分之一插入
                //初始化根节点
                var newRoot = new QuadTreeNode<TEntity>(rectSquare, null, 0);

                //新根中计算原根
                //把 旧根节点 连接到 新根节点 上面,然后新根成为根
                var insert = newRoot.Insert(_rootNode);
                if (insert == null)
                    throw new ArgumentException("新根尺寸不对");
                if (!insert.IsEqualTo(_rootNode))
                    throw new ArgumentNullException("新节点大小不对,无法连接");

                _rootNode.Parent = insert.Parent;

                if (insert.Parent.RightTopTree.IsEqualTo(_rootNode))
                    insert.Parent.RightTopTree = _rootNode;
                else if (insert.Parent.RightBottomTree.IsEqualTo(_rootNode))
                    insert.Parent.RightBottomTree = _rootNode;
                else if (insert.Parent.LeftBottomTree.IsEqualTo(_rootNode))
                    insert.Parent.LeftBottomTree = _rootNode;
                else if (insert.Parent.LeftTopTree.IsEqualTo(_rootNode))
                    insert.Parent.LeftTopTree = _rootNode;
                else
                    throw new ArgumentNullException("新节点不对,无法连接");

                //其后的子节点层数全部增加层数,
                //要加多少层取决于当前根边界属于新根边界的所在层
                var depth = insert.Depth;
                if (depth == 0)
                    throw new ArgumentNullException("插入节点是0,造成错误");
                _rootNode.ForEach(node => {
                    node.Depth += depth;
                    return false;
                });

                //交换根控制
                _rootNode = newRoot;
            }

            _rootNode.Insert(ent);
        }
 
        /// <summary>
        /// 查询四叉树,返回给定区域的数据项
        /// </summary>
        /// <param name="rect">矩形选区查询</param>
        /// <returns></returns>
        public List<TEntity> Query(Rect rect, QuadTreeSelectMode selectMode = QuadTreeSelectMode.IntersectsWith)
        {
            QuadTreeEvn.SelectMode = selectMode;

            var results = new List<TEntity>();

            //选择图元
            _rootNode.Query(rect, results);

            //选择点
            var ptge = _points.GetEnumerator();
            switch (selectMode)
            {
                case QuadTreeSelectMode.IntersectsWith:
                case QuadTreeSelectMode.Contains:
                    /* 由于红黑树的方法 _points.GetViewBetween()
                     * 过滤只能过滤X区间,Y区间还是要过滤,
                     * 那么我就只能用这样的方法加速了
                     * 
                     * 而更好的方式是不用红黑树,去加入一个点云数据来进行,可谓是编程无极限....
                     */
                    while (ptge.MoveNext())
                    {
                        var ptEnt = ptge.Current;
                        if (rect._X <= ptEnt._X && ptEnt._X <= rect._Right)
                        {
                            if (rect._Y <= ptEnt._Y && ptEnt._Y <= rect.Top)
                                results.Add(ptEnt);
                        }
                        else if (ptEnt._X > rect._Right)
                            break;//超过后面范围就break,因为红黑树已经排序
                    }
                    break;
                default:
                    throw new ArgumentException(null, nameof(selectMode));
            }
            return results;
        }

        /// <summary>
        /// 删除子节点
        /// </summary>
        /// <param name="rect">根据范围删除</param>
        public void Remove(Rect rect)
        {
            _rootNode.Remove(rect);
        }

        /// <summary>
        /// 删除子节点
        /// </summary>
        /// <param name="ent">根据图元删除</param>
        public void Remove(TEntity ent)
        {
            _rootNode.Remove(ent);
        }

        /// <summary>
        /// 找到附近节点图元
        /// </summary>
        [Obsolete("找附近节点的并不是最近的图元")]
        public TEntity FindNeibor(Rect rect, QuadTreeFindMode findMode)
        {
            return _rootNode.FindNeibor(rect, findMode);
        }

        /// <summary>
        /// 找到附近图元
        /// </summary>
        /// <param name="rect"></param>
        /// <returns></returns>
        public TEntity FindNearEntity(Rect rect)
        {
            return _rootNode.FindNearEntity(rect);
        }

        /// <summary>
        /// 执行四叉树中特定的行为
        /// </summary>
        /// <param name="action"></param>
        public void ForEach(QTAction action)
        {
            _rootNode.ForEach(action);
        }

        /// <summary>
        /// 委托:四叉树节点上执行一个操作
        /// </summary>
        /// <param name="obj"></param>
        public delegate bool QTAction(QuadTreeNode<TEntity> obj);
        #endregion
    }
}

四叉树子节点

using System;
using System.Collections.Generic;
using System.Linq;

namespace JoinBox.BasalMath
{
    /// <summary>
    /// 子节点
    /// </summary>
    /// <typeparam name="TEntity"></typeparam>
    public class QuadTreeNode<TEntity> : Rect where TEntity : IHasRect
    {
        #region 成员
        /// <summary>
        /// 子节点:第一象限:右上↗
        /// </summary>
        public QuadTreeNode<TEntity> RightTopTree;
        /// <summary>
        /// 子节点:第二象限:左上↖
        /// </summary>
        public QuadTreeNode<TEntity> LeftTopTree;
        /// <summary>
        /// 子节点:第三象限:左下↙
        /// </summary>
        public QuadTreeNode<TEntity> LeftBottomTree;
        /// <summary>
        /// 子节点:第四象限:右下↘
        /// </summary>
        public QuadTreeNode<TEntity> RightBottomTree;
        /// <summary>
        /// 所有子节点
        /// </summary>
        QuadTreeNode<TEntity>[] _Nodes
        {
            get
            {
                return new QuadTreeNode<TEntity>[]
                {
                     RightTopTree,
                     LeftTopTree,
                     LeftBottomTree,
                     RightBottomTree,
                };
            }
        }
        /// <summary>
        /// 所有子节点是空的
        /// </summary>
        bool _nodesIsEmpty => RightTopTree == null && LeftTopTree == null && LeftBottomTree == null && RightBottomTree == null;

        /// <summary>
        /// 父节点
        /// </summary>
        public QuadTreeNode<TEntity> Parent;
        /// <summary>
        /// 节点的在四叉树的深度
        /// </summary>
        public int Depth;

        // 注意,内容没有限制:这不是 impement 四叉树的标准方法
        /// (节点图元是交叉线压着的,并不是矩形范围内全部,因为这是四叉树的特性决定)
        /// <summary>
        /// 本节点:内容
        /// </summary>
        public List<TEntity> Contents;

        /// <summary>
        /// 本节点和旗下所有子节点:内容群
        /// </summary>
        public void ContentsSubTree(List<TEntity> results)
        {
            if (Contents == null)
                return;
            results.AddRange(Contents);
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
                nodes[i]?.ContentsSubTree(results);
        }

        /// <summary>
        /// 本节点和旗下所有子节点:内容群数量
        /// </summary>
        public int CountSubTree
        {
            get
            {
                if (Contents == null)
                    return 0;
                int count = Contents.Count;

                var nodes = _Nodes;
                for (int i = 0; i < nodes.Length; i++)
                {
                    var node = nodes[i];
                    if (node == null)
                        continue;
                    count += node.CountSubTree;
                }
                return count;
            }
        }
        #endregion

        #region 构造
        /// <summary>
        /// 四叉树节点
        /// </summary>
        /// <param name="box">当前节点边界</param>
        /// <param name="parent">父节点</param>
        /// <param name="depth">节点深度</param>
        public QuadTreeNode(Rect box, QuadTreeNode<TEntity> parent, int depth)
        {
            X        = box.X;
            Y        = box.Y;
            Right    = box.Right;
            Top      = box.Top;

            Parent   = parent;
            Depth    = depth;
            Contents = new();
        }
        #endregion

        #region 增
        /// <summary>
        /// 将原有节点插入用
        /// </summary>
        /// <param name="rect"></param>
        internal QuadTreeNode<TEntity> Insert(Rect rect)
        {
            if (!Contains(rect))
                return null;

            //四叉树分裂,将当前节点分为四个子节点
            if (_nodesIsEmpty)
                CreateChildren();

            //当前节点边界 包含 图元包围盒 就插入
            //退出递归:4个节点都不完全包含
            //4个节点的上层
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;

                if (node.IsEqualTo(rect))
                {
                    rect = node;
                    return node.Insert(rect);
                }
            }
            return this;
        }

        /// <summary>
        /// 将数据项递归插入四叉树
        /// </summary>
        /// <param name="ent"></param>
        public QuadTreeNode<TEntity> Insert(TEntity ent)
        {
            if (!Contains(ent.Box))
            {
                //Debug.WriteLine("不在四叉树边界范围");
                //Trace.WriteLine("不在四叉树边界范围");
                return null;
            }

            if (ent.IsPoint)
            {
                //找到最后一层包含它的节点,然后加入它
                //因此是跳过分裂矩形的,以免造成无限递归
                var minNode = GetMinNode(ent.Box);
                minNode.Contents.Add(ent);
                return minNode;
            }

#if true2
            //方案二:内容数超过才分裂,防止树深度过高
            if (Contents.Count > QuadTreeEvn.QuadTreeContentsCountSplit)
            {
                //分裂出四个子节点
                if (_nodesIsEmpty)
                {
                    CreateChildren();
                    //分裂之后将当前层的内容扔到四个子节点,
                    //如果被压着,那么就不会扔到下面
                    for (int i = Contents.Count - 1; i >= 0; i--)
                    {
                        var minNode = GetMinNode(Contents[i].Box);
                        minNode.Contents.Add(Contents[i]);
                        Contents.RemoveAt(i);
                    }
                }
                else
                {
                    //没有分裂的话,就递归
                    //退出递归:4个节点都不完全包含,内容就是他们的父亲
                    var nodes = _Nodes;
                    for (int i = 0; i < nodes.Length; i++)
                    {
                        var node = nodes[i];
                        if (node == null)
                            continue;

                        //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容)
                        if (node.Contains(ent.Box))
                            return node.Insert(ent);
                    }
                }
            }
#else
            //方案一:分裂到最细节点

            //分裂出四个子节点
            if (_nodesIsEmpty)
                CreateChildren();

            //4个子节点开始递归
            //退出递归:4个节点都不完全包含,内容就是他们的父亲
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;
                //这里需要中断.(匿名方法ForEach无法中断,会造成父节点加入内容)
                if (node.Contains(ent.Box))
                    return node.Insert(ent);
            }
#endif

            //为什么要用容器?
            //相同包围盒或者四叉树分割线压着多个.
            this.Contents.Add(ent);
            return this;
        }

        /// <summary>
        /// 创建子节点
        /// </summary>
        void CreateChildren()
        {
            // 最小面积控制节点深度,但是这样可能导致树分成高,引起爆栈
            if (Depth > QuadTreeEvn.QuadTreeMaximumDepth)
                return;
            var recs = RectSplit(this);
            var de = Depth + 1;
            RightTopTree = new QuadTreeNode<TEntity>(recs[0], this, de);
            LeftTopTree = new QuadTreeNode<TEntity>(recs[1], this, de);
            LeftBottomTree = new QuadTreeNode<TEntity>(recs[2], this, de);
            RightBottomTree = new QuadTreeNode<TEntity>(recs[3], this, de);
        }

        /// <summary>
        /// 矩形分裂为四个
        /// </summary>
        /// <param name="box"></param>
        /// <returns></returns>
        Rect[] RectSplit(Rect box)
        {
            var halfWidth = box.Width / 2.0;
            var halfHeight = box.Height / 2.0;

            var upperRight = new Rect(box.X + halfWidth, box.Y + halfHeight, box.Right, box.Top);
            var upperLeft = new Rect(box.X, box.Y + halfHeight, box.Right - halfWidth, box.Top);
            var lowerleft = new Rect(box.X, box.Y, box.Right - halfWidth, box.Top - halfHeight);//基础
            var lowerRight = new Rect(box.X + halfWidth, box.Y, box.Right, box.Top - halfHeight);

            //依照象限顺序输出
            return new Rect[] { upperRight, upperLeft, lowerleft, lowerRight };
        }
        #endregion

        #region 删
        /// <summary>
        /// 删除图元
        /// </summary>
        /// <param name="easeEnt">根据图元删除</param>
        public bool Remove(TEntity easeEnt)
        {
            //通过图元id删除无疑是非常低效的,
            //1.相当于在所有的容器查找它,但是移除只会移除一次,
            //  因此必须要求图元只会加入一次,才能中断检索剩余分支.
            //2.这个代价还是太高,因此我们还是要默认条件,图元载入一次之后,不再改动.
            //3.不再改动也不太合理,因为cad图元还是可以修改的

            //1.处理内容
            if (Contents.Remove(easeEnt))
            {
                if (CountSubTree == 0)
                    this.Clear(this);
                return true;
            }

            //2.递归子节点移除
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;
                if (node.Remove(easeEnt))     //递归进入子节点删除内容
                    return true;              //删除成功就中断其他节点的搜索
            }
            return false;
        }

        /// <summary>
        /// 递归进入最下层节点,然后开始清理
        /// </summary>
        /// <param name="node"></param>
        void Clear(QuadTreeNode<TEntity> node)
        {
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
                nodes[i]?.Clear(nodes[i]);

            node.Contents.Clear();
            //node.Contents = null;//重复加入时候会出错
            node.RightTopTree = null;
            node.LeftTopTree = null;
            node.LeftBottomTree = null;
            node.RightBottomTree = null;
            node.Parent = null;
            //node.Box = zoreRect;
        }

        /// <summary>
        /// 删除子节点内容
        /// </summary>
        /// <param name="queryArea">根据范围删除</param>
        public void Remove(Rect queryArea)
        {
            //本节点内容移除
            if (Contents != null && Contents.Count > 0)//从最上层的根节点开始进入
            {
                for (int i = Contents.Count - 1; i >= 0; i--)
                {
                    var ent = Contents[i];
                    //移除之后,如果容器是0,那么这里不能直接 Contents=null,
                    //因为此节点下面可能还有节点,
                    //需要判断了其后数量0才可以清理.
                    //否则其后还有内容,那么此节点就是仍然可以用的.
                    if (queryArea.Contains(ent.Box))
                        Contents.Remove(ent);
                }
            }

            //同插入一样
            //跳到指定节点再搜索这个节点下面的图元
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;
                if (node._nodesIsEmpty)
                    continue;

                //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环)
                if (node.Contains(queryArea))
                {
                    node.Remove(queryArea);
                    break;
                }
                //查询区域 完全包含 此节点边界,提取此节点全部内容
                //跳过分析碰撞,并继续循环搜索其他节点
                if (queryArea.Contains(node))
                {
                    node.Clear(node);
                    continue;
                }
                //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点
                //1,角点碰撞 2,边界碰撞
                if (node.IntersectsWith(queryArea))
                    node.Remove(queryArea);
            }

            //本节点内容移除之后,旗下还有内容的话,
            //会跳过此处,再进入子节点进行递归,直到最后一个节点
            if (CountSubTree == 0)
                Clear(this);
        }
        #endregion

        #region 查
        /// <summary>
        /// 查询范围内的实体
        /// </summary>
        /// <param name="queryArea">查询矩形</pasram>
        /// <returns></returns>
        public void Query(Rect queryArea, List<TEntity> results)
        {
            GetCurrentContents(queryArea, results);

            //遍历子节点
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;
                //子节点的4个子节点都是空的,
                //那么表示元素会在子节点这一层啊...
                if (node._nodesIsEmpty)
                    continue;

                //此节点边界 完全包含 查询区域,则转到该节点,并跳过其余节点(打断此循环)
                if (node.Contains(queryArea))
                {
                    node.Query(queryArea, results);
                    break;
                }
                //查询区域 完全包含 此节点边界,提取此节点全部内容
                //跳过分析碰撞,并继续循环搜索其他节点
                if (queryArea.Contains(node))
                {
                    node.ContentsSubTree(results);
                    continue;
                }
                //查询区域 与 此节点四边形边线碰撞 查询该四边形中,并继续循环搜索其他节点
                //1,角点碰撞 2,边界碰撞
                if (node.IntersectsWith(queryArea))
                    node.Query(queryArea, results);
            }
        }

        /// <summary>
        /// 获取本节点内容
        /// </summary>
        /// <param name="queryArea"></param>
        /// <param name="results"></param>
        void GetCurrentContents(Rect queryArea, List<TEntity> results)
        {
            //遍历当前节点内容,加入方式取决于碰撞模式
            if (QuadTreeEvn.SelectMode == QuadTreeSelectMode.IntersectsWith)
            {
                for (int i = 0; i < Contents.Count; i++)
                {
                    var ent = Contents[i];
                    if (queryArea.IntersectsWith(ent.Box))
                        results.Add(ent);
                }
            }
            else
            {
                for (int i = 0; i < Contents.Count; i++)
                {
                    var ent = Contents[i];
                    if (queryArea.Contains(ent.Box))
                        results.Add(ent);
                }
            }
        }

        /// <summary>
        /// 找临近图元
        /// </summary>
        /// <param name="queryArea">查找矩形</param>
        /// <returns></returns>
        public TEntity FindNearEntity(Rect queryArea)
        {
            TEntity resultEntity = default;
            //1.找到 查找矩形 所在的节点,利用此节点的矩形.
            var queryNode = GetMinNode(queryArea);
            var queryAreaCenter = queryArea.CenterPoint;

            //2.从根开始搜索
            //  如果搜索父亲的父亲的...内容群,它不是距离最近的,只是节点(亲属关系)最近
            //  储存找过的<图元,距离>
            var entDic = new Dictionary<TEntity, double>();

            var old = QuadTreeEvn.SelectMode;
            QuadTreeEvn.SelectMode = QuadTreeSelectMode.IntersectsWith;
            while (true)
            {
                //循环找父节点大小
                var hw = queryNode.Width / 2.0;
                var hh = queryNode.Height / 2.0;
                //3.利用选区中心扩展一个节点边界大小的矩形.从而选择图元
                //  再判断图元的与目标的距离,找到最小距离,即为最近
                var minPt = new PointV(queryAreaCenter.X - hw, queryAreaCenter.Y - hh);
                var maxPt = new PointV(queryAreaCenter.X + hw, queryAreaCenter.Y + hh);
                var ents = new List<TEntity>();
                Query(new Rect(minPt, maxPt), ents);
                for (int i = 0; i < ents.Count; i++)
                {
                    var ent = ents[i];
                    if (entDic.ContainsKey(ent))
                        continue;
                    var dis = ent.Box.CenterPoint.DistanceTo(queryAreaCenter);
                    if (dis > 1e-6)//剔除本身
                        entDic.Add(ent, dis);
                }
                if (entDic.Count > 0)
                {
                    resultEntity = entDic.OrderBy(a => a.Value).First().Key;
                    break;
                }
                if (queryNode.Parent == null)//最顶层就退出
                    break;
                queryNode = queryNode.Parent;//利用父节点矩形进行变大选区
            }
            QuadTreeEvn.SelectMode = old;
            return resultEntity;
        }

        /// <summary>
        /// 找临近节点的图元
        /// </summary>
        /// <param name="queryArea">查找矩形</param>
        /// <param name="findMode">查找什么方向</param>
        /// <returns></returns>
        [Obsolete("找附近节点的并不是最近的图元")]
        public TEntity FindNeibor(Rect queryArea, QuadTreeFindMode findMode)
        {
            TEntity resultEntity = default;
            //1.找到 查找矩形 所在的节点,利用此节点的矩形.
            //2.利用节点矩形是分裂的特点,边和边必然贴合.
            //3.找到方向 findMode 拥有的节点,然后查找节点的内容
            var queryNode = GetMinNode(queryArea);

            bool whileFlag = true;
            //同一个节点可能包含邻居,因为四叉树的加入是图元压线,
            //那么就在这里搜就得了,用中心点决定空间位置
            //但是本空间的图元可能都比它矮,无法满足条件
            if (queryNode.CountSubTree > 1)
            {
                resultEntity = GetNearestNeighbor(queryNode, findMode, queryArea);
                if (resultEntity == null || resultEntity.Box.CenterPoint == queryArea.CenterPoint)
                    whileFlag = true;
                else
                    whileFlag = false;
            }

            while (whileFlag)
            {
                //同一个父节点是临近的,优先获取 兄弟节点 的内容.
                //循环了第二次是北方兄弟的节点,
                //但是这不是一个找到临近图元的方法,
                //因为临近的可能是父亲的父亲的父亲...另一个函数 FindNearEntity 写
                //本方案也仅仅作为找北方节点
                var parent = queryNode.Parent;
                if (parent != null)
                {
                    switch (findMode)
                    {
                        case QuadTreeFindMode.Top:
                        {
                            //下格才获取上格,否则导致做了无用功,上格就直接获取邻居了
                            if (parent.LeftBottomTree == queryNode || parent.RightBottomTree == queryNode)
                                resultEntity = GetNearestNeighbor(parent, findMode, queryArea);
                        }
                        break;
                        case QuadTreeFindMode.Bottom:
                        {
                            if (parent.LeftTopTree == queryNode || parent.RightTopTree == queryNode)
                                resultEntity = GetNearestNeighbor(parent, findMode, queryArea);
                        }
                        break;
                        case QuadTreeFindMode.Left:
                        {
                            if (parent.RightTopTree == queryNode || parent.RightBottomTree == queryNode)
                                resultEntity = GetNearestNeighbor(parent, findMode, queryArea);
                        }
                        break;
                        case QuadTreeFindMode.Right:
                        {
                            if (parent.LeftTopTree == queryNode || parent.LeftBottomTree == queryNode)
                                resultEntity = GetNearestNeighbor(parent, findMode, queryArea);
                        }
                        break;
                    }
                }
                if (resultEntity != null)
                    break;

                //通过 所在节点 找 邻居节点,
                //拿到 邻居节点 下面的所有内容(图元)
                //内容可能是空的,再从邻居那往北找...如果找到了四叉树最外层,仍然没有内容,退出循环
                var neiborNode = FindNeiborNode(queryNode, findMode);
                if (neiborNode.CountSubTree > 0)
                {
                    resultEntity = GetNearestNeighbor(neiborNode, findMode, queryArea);
                    break;
                }
                if (neiborNode.Parent == null)//如果找到了四叉树最外层,仍然没有内容,退出循环
                    break;
                queryNode = neiborNode;
            }

            return resultEntity;
        }

        /// <summary>
        /// 查找节点的(本内容和子内容)与(查找面积)矩形中点对比,找到最近一个内容
        /// </summary>
        /// <param name="queryArea">查找面积</param>
        /// <param name="findMode">查找方向</param>
        /// <param name="queryNode">查找节点</param>
        /// <returns></returns>
        TEntity GetNearestNeighbor(QuadTreeNode<TEntity> queryNode, QuadTreeFindMode findMode, Rect queryArea)
        {
            TEntity results = default;

            var lst = new List<TEntity>();
            var qcent = queryArea.CenterPoint;

            switch (findMode)
            {
                case QuadTreeFindMode.Top:
                {
                    //取出Y比queryArea的还大的一个,是最近的那个
                    var qy = qcent.Y;
                    queryNode.ContentsSubTree(lst);
                    lst.ForEach(ent => {
                        if (ent.Box.CenterPoint.Y > qy)
                            lst.Add(ent);
                    });
                    lst = lst.OrderBy(ent => ent.Box.CenterPoint.Y).ToList();
                }
                break;
                case QuadTreeFindMode.Bottom:
                {
                    var qy = qcent.Y;
                    queryNode.ContentsSubTree(lst);
                    lst.ForEach(ent => {
                        if (ent.Box.CenterPoint.Y < qy)
                            lst.Add(ent);
                    });
                    lst = lst.OrderByDescending(ent => ent.Box.CenterPoint.Y).ToList();
                }
                break;
                case QuadTreeFindMode.Left:
                {
                    var qx = qcent.Y;
                    queryNode.ContentsSubTree(lst);
                    lst.ForEach(ent => {
                        if (ent.Box.CenterPoint.X > qx)
                            lst.Add(ent);
                    });
                    lst = lst.OrderBy(ent => ent.Box.CenterPoint.X).ToList();
                }
                break;
                case QuadTreeFindMode.Right:
                {
                    var qx = qcent.Y;
                    queryNode.ContentsSubTree(lst);
                    lst.ForEach(ent => {
                        if (ent.Box.CenterPoint.X < qx)
                            lst.Add(ent);
                    });
                    lst = lst.OrderByDescending(ent => ent.Box.CenterPoint.X).ToList();
                }
                break;
            }

            if (lst.Count > 0)
                return lst[0];//可能就是本体重叠
            return results;
        }

        /// <summary>
        /// 找包含它的最小分支
        /// </summary>
        /// <param name="queryArea">查询的矩形</param>
        /// <returns>节点</returns>
        QuadTreeNode<TEntity> GetMinNode(Rect queryArea)
        {
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;

                //边界包含查询面积,那么再递归查询,
                //直到最后四个都不包含,那么上一个就是图元所在节点
                if (node.Contains(queryArea))
                    return node.GetMinNode(queryArea);//中断后面的范围,才可以返回正确的
            }
            return this;
        }

        /// <summary>
        /// 四叉树找邻居节点(相同或更大)
        /// </summary>
        /// <param name="tar">源节点</param>
        /// <param name="findMode">方向</param>
        /// <returns></returns>
        QuadTreeNode<TEntity> FindNeiborNode(QuadTreeNode<TEntity> tar, QuadTreeFindMode findMode)
        {
            var parent = tar.Parent;
            if (parent == null)
                return null;
            switch (findMode)
            {
                case QuadTreeFindMode.Top:
                {
                    //判断当前节点在父节点的位置,如果是在 下格 就取对应的 上格
                    if (tar == parent.LeftBottomTree)
                        return parent.LeftTopTree;
                    if (tar == parent.RightBottomTree)
                        return parent.RightTopTree;
                    //否则就是上格
                    //找父节点的北方邻居..也就是在爷节点上面找
                    //递归,此时必然是 下格,就必然返回 上格,然后退出递归
                    var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Top);
                    //父节点的北方邻居 无 子节点
                    if (parentNeibor.RightTopTree == null)
                        return parentNeibor;//返回父节点的北方邻居,比较大
                                            //父节点的北方邻居 有 子节点,剩下条件就只有这两

                    // 如果直接返回,那么是(相同或更大),
                    // 而找邻近图元需要的是这个(相同或更大)下面的图元,在外面对这个格子内图元用坐标分析就好了
                    if (tar == parent.LeftTopTree)
                        return parentNeibor.LeftBottomTree;
                    return parentNeibor.RightBottomTree;
                }
                case QuadTreeFindMode.Bottom:
                {
                    if (tar == parent.LeftTopTree)
                        return parent.LeftBottomTree;
                    if (tar == parent.RightTopTree)
                        return parent.RightBottomTree;
                    var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Bottom);
                    if (parentNeibor.RightTopTree == null)
                        return parentNeibor;
                    if (tar == parent.LeftBottomTree)
                        return parentNeibor.LeftTopTree;
                    return parentNeibor.RightTopTree;
                }
                case QuadTreeFindMode.Right:
                {
                    if (tar == parent.LeftTopTree)
                        return parent.RightTopTree;
                    if (tar == parent.LeftBottomTree)
                        return parent.RightBottomTree;
                    var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Right);
                    if (tar == parent.RightTopTree)
                        return parentNeibor.LeftTopTree;
                    return parentNeibor.LeftBottomTree;
                }
                case QuadTreeFindMode.Left:
                {
                    if (tar == parent.RightTopTree)
                        return parent.LeftTopTree;
                    if (tar == parent.RightBottomTree)
                        return parent.LeftBottomTree;
                    var parentNeibor = FindNeiborNode(parent, QuadTreeFindMode.Left);
                    if (tar == parent.LeftTopTree)
                        return parentNeibor.RightTopTree;
                    return parentNeibor.RightBottomTree;
                }
            }
            return null;
        }
        #endregion

        #region 改
        /// <summary>
        /// 所有的点归类到最小包围它的空间
        /// </summary>
        public void PointsToMinNode()
        {
            ForEach(node => {
                for (int i = 0; i < node.Contents.Count; i++)
                {
                    var ent = node.Contents[i];
                    if (ent.IsPoint)
                    {
                        //如果最小包含!=当前,就是没有放在最适合的位置
                        var queryNode = GetMinNode(ent.Box);
                        if (queryNode != node)
                        {
                            node.Remove(ent);
                            queryNode.Contents.Add(ent);
                        }
                    }
                }
                return false;
            });
        }
        #endregion

        #region 方法
        /// <summary>
        /// 递归全部节点(提供给根用的,所以是全部)
        /// </summary>
        /// <param name="action"></param>QTAction
        public bool ForEach(QuadTree<TEntity>.QTAction action)
        {
            //执行本节点
            if (action(this))
                return true;

            //递归执行本节点的子节点
            var nodes = _Nodes;
            for (int i = 0; i < nodes.Length; i++)
            {
                var node = nodes[i];
                if (node == null)
                    continue;
                if (node.ForEach(action))
                    break;
            }
            return false;
        }
        #endregion
    }
}

共同字段

namespace JoinBox.BasalMath
{
    /*
     * 这个类存在的意义是为了不暴露Rect类字段,从而约束用户继承
     */

    /// <summary>
    /// 四叉树图元
    /// </summary>
    public class QuadEntity : Rect
    {
        /// <summary>
        /// 四叉树图元
        /// </summary>
        /// <param name="box">包围盒</param>
        public QuadEntity(Rect box)
        {
            _X = box._X;
            _Y = box._Y;
            _Top = box._Top;
            _Right = box._Right;
        }
    }
}

全局变量类

namespace JoinBox.BasalMath
{
    public class QuadTreeEvn
    {
        /// <summary>
        /// 最小的节点有一个面积(一定要大于0)
        /// </summary>
        public static double MinArea = 1e-6;

        /// <summary>
        /// 选择模式
        /// </summary>
        public static QuadTreeSelectMode SelectMode;

        /// <summary>
        /// 最大深度
        /// </summary>
        public static int QuadTreeMaximumDepth = 2046;

        /// <summary>
        /// 节点内容超过就分裂
        /// </summary>
        public static int QuadTreeContentsCountSplit = 20;
    }
}

枚举

namespace JoinBox.BasalMath
{
    /// <summary>
    /// 四叉树选择模式
    /// </summary>
    public enum QuadTreeSelectMode
    {
        IntersectsWith, //碰撞到就选中
        Contains,       //全包含才选中
    }

    /// <summary>
    /// 四叉树查找方向
    /// </summary>
    public enum QuadTreeFindMode
    {
        Top    = 1,  //上
        Bottom = 2,  //下
        Left   = 4,  //左
        Right  = 8,  //右
    }
}

随机颜色

using System;
using System.Drawing;

namespace JoinBox.BasalMath
{
    public static class Utility
    {
        public static Random GetRandom()
        {
            var tick = DateTime.Now.Ticks;
            var tickSeeds = (int)(tick & 0xffffffffL) | (int)(tick >> 32);
            var ran = new Random(tickSeeds);
            return ran;
        }

        /// <summary>
        /// 随机颜色
        /// </summary>
        /// <returns></returns>
        public static Color RandomColor
        {
            get
            {
                var ran = GetRandom();
                int R = ran.Next(255);
                int G = ran.Next(255);
                int B = ran.Next(255);
                B = (R + G > 400) ? R + G - 400 : B;//0 : 380 - R - G;
                B = (B > 255) ? 255 : B;
                return Color.FromArgb(R, G, B);
            }
        }
    }
}

(完)

posted @ 2021-11-05 11:15  惊惊  阅读(2599)  评论(0编辑  收藏  举报