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
}
}
测试命令子函数
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);
}
}
}
}
(完)