using System; using System.Collections.Generic; using UnityEngine; namespace XericUI.VisualForm { /// /// 轻量泛型四叉树,用于空间加速碰撞检测。 /// 通过 Func{T, Rect} 委托获取任意类型对象的边界矩形。 /// 委托在每次查询时实时调用,因此对象可在树重建后移动位置,查询仍能获得准确结果。 /// /// 典型用法: /// 1. 构造 QuadTree{T}(bounds, getRectFunc) /// 2. Rebuild(items) — 清空并重新插入 /// 3. Retrieve(area, result) — 查询与area重叠的所有对象 /// public class QuadTree { private readonly int _maxPerNode; private readonly int _maxDepth; private readonly Func _getRectFunc; private Node _root; public int Count { get; private set; } /// /// 初始化四叉树。 /// /// 根节点边界矩形 /// 获取对象边界的委托(每次查询实时调用) /// 每节点最大容纳对象数 /// 最大递归深度 public QuadTree(Rect boundary, Func getRectFunc, int maxPerNode = 5, int maxDepth = 4) { _maxPerNode = Math.Max(1, maxPerNode); _maxDepth = Math.Max(1, maxDepth); _getRectFunc = getRectFunc ?? throw new ArgumentNullException(nameof(getRectFunc)); _root = new Node(boundary, 0, this); } /// /// 清空四叉树,保留边界和配置以便重用。 /// public void Clear() { _root.ClearAll(); Count = 0; } /// /// 清空并重新插入所有对象。 /// public void Rebuild(IEnumerable items) { Clear(); foreach (var item in items) Insert(item); } /// /// 插入一个对象。 /// public bool Insert(T item) { return _root.Insert(item); } /// /// 查询与指定区域重叠的所有对象。 /// /// 查询区域 /// 结果容器(会先清空再填入) public void Retrieve(Rect area, HashSet result) { if (result == null) return; result.Clear(); _root.Retrieve(area, result); } #region 内部节点 private class Node { private readonly Rect _boundary; private readonly int _depth; private readonly QuadTree _owner; private HashSet _objects; private Node[] _children; public Node(Rect boundary, int depth, QuadTree owner) { _boundary = boundary; _depth = depth; _owner = owner; _objects = new HashSet(); } public bool Insert(T item) { Rect itemRect = _owner._getRectFunc(item); if (!Overlaps(_boundary, itemRect)) return false; if (_objects != null && (_objects.Count < _owner._maxPerNode || _depth >= _owner._maxDepth)) { _objects.Add(item); _owner.Count++; return true; } if (_children == null) Split(); bool inserted = false; for (int i = 0; i < 4; i++) { if (_children[i].Insert(item)) inserted = true; } return inserted; } private void Split() { float hw = _boundary.width * 0.5f; float hh = _boundary.height * 0.5f; float mx = _boundary.x + hw; float my = _boundary.y + hh; _children = new Node[4]; _children[0] = new Node(new Rect(_boundary.x, _boundary.y, hw, hh), _depth + 1, _owner); _children[1] = new Node(new Rect(mx, _boundary.y, hw, hh), _depth + 1, _owner); _children[2] = new Node(new Rect(_boundary.x, my, hw, hh), _depth + 1, _owner); _children[3] = new Node(new Rect(mx, my, hw, hh), _depth + 1, _owner); var temp = new List(_objects); _objects.Clear(); _objects = null; for (int i = 0; i < temp.Count; i++) Insert(temp[i]); } public void Retrieve(Rect area, HashSet result) { if (!Overlaps(_boundary, area)) return; if (_objects != null) { foreach (var obj in _objects) { if (Overlaps(_owner._getRectFunc(obj), area)) result.Add(obj); } return; } if (_children != null) { for (int i = 0; i < 4; i++) _children[i].Retrieve(area, result); } } public void ClearAll() { if (_objects != null) { _objects.Clear(); } if (_children != null) { for (int i = 0; i < _children.Length; i++) _children[i]?.ClearAll(); _children = null; } _objects = new HashSet(); } private static bool Overlaps(Rect a, Rect b) { return a.xMin < b.xMax && a.xMax > b.xMin && a.yMin < b.yMax && a.yMax > b.yMin; } } #endregion } }