环境
Unity : 2019.4.0f1
这个内容时前端时间写的
整体效果
- 蓝色空心矩形 就是被 剔除的 cube 集合
- 红色实心矩形 就是在四叉树 + 视锥cascaded 内的 cube 集合
更加精准
Code
QuadTree.cs
#define __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
#define __ENABLE_QT_PROFILER__
using System;
using System.Collections.Generic;
using UnityEngine;
#if __ENABLE_QT_PROFILER__
using UnityEngine.Profiling;
#endif
public class ListPool<T> : IDisposable
{
private Stack<List<T>> _list_pool = new Stack<List<T>>();
public List<T> FromPool()
{
return _list_pool.Count > 0 ? _list_pool.Pop() : new List<T>();
}
public void ToPool(List<T> list)
{
list.Clear();
_list_pool.Push(list);
}
public void Clear()
{
_list_pool.Clear();
}
public void Dispose()
{
if (_list_pool != null)
{
_list_pool.Clear();
_list_pool = null;
}
}
}
[Serializable]
public struct QTAABB : IEquatable<QTAABB>
{
public static readonly QTAABB Zero = new QTAABB();
public const int DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL = 3;
public const float DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING = 0;
public float x;
public float y;
public float w;
public float h;
public float left { get => x; set => x = value; }
public float top { get => y; set => y = value; }
public float right { get => x + w; set => w = value - x; }
public float bottom { get => y + h; set => h = value - y; }
public float centerX { get => x + w * 0.5f; set => x = value - (w * 0.5f); }
public float centerY { get => y + h * 0.5f; set=> y = value - (h * 0.5f); }
public Vector2 center { get => new Vector2(centerX, centerY); set { centerX = value.x; centerY = value.y; } }
public float extentX { get => w * 0.5f; set => w = value * 2; }
public float extentY { get => y * 0.5f; set => h = value * 2; }
public Vector2 extent { get => new Vector2(extentX, extentY); set { extentX = value.x; extentY = value.y; } }
public Vector2 min { get => new Vector2(left, top); set { left = value.x; top = value.y; } }
public Vector2 max { get => new Vector2(right, bottom); set { right = value.x; bottom = value.y; } }
public Vector2 top_left { get => new Vector2(left, top); }
public Vector2 top_right { get => new Vector2(right, top); }
public Vector2 bottom_left { get => new Vector2(left, bottom); }
public Vector2 bottom_right { get => new Vector2(right, bottom); }
public bool IsZero()
{
return left == right || top == bottom;
}
public void Set(float x, float y, float w, float h)
{
this.x = x;
this.y = y;
this.w = w;
this.h = h;
}
public bool IsIntersect(ref QTAABB other, out QTAABB outAABB)
{
outAABB = new QTAABB();
outAABB.x = Mathf.Max(left, other.left);
outAABB.right = Mathf.Min(right, other.right);
outAABB.y = Mathf.Max(top, other.top);
outAABB.bottom = Mathf.Min(bottom, other.bottom);
return !outAABB.IsZero();
}
public bool IsIntersect(ref QTAABB other)
{
return x < other.right && y < other.bottom && right > other.x && bottom > other.y;
}
public bool IsIntersect(QTAABB other)
{
return IsIntersect(ref other);
}
public bool Contains(ref QTAABB other)
{
return other.x >= x && other.y >= y && other.right <= right && other.bottom <= bottom;
}
public bool Contains(QTAABB other)
{
return Contains(ref other);
}
public bool Contains(float x, float y)
{
return x >= left && x < right && y >= top && y < bottom;
}
public bool Contains(Vector2 pos)
{
return Contains(ref pos);
}
public bool Contains(ref Vector2 pos)
{
return Contains(pos.x, pos.y);
}
public void Union(QTAABB aabb)
{
Union(ref aabb);
}
public void Union(ref QTAABB aabb)
{
Union(aabb.min);
Union(aabb.max);
}
public void Union(Vector2 pos)
{
Union(ref pos);
}
public void Union(ref Vector2 pos)
{
Union(pos.x, pos.y);
}
public void Union(Vector3 pos)
{
Union(ref pos);
}
public void Union(ref Vector3 pos)
{
Union(pos.x, pos.z);
}
public void Union(float _x, float _z)
{
var src_x = x;
var src_y = y;
var src_r = right;
var src_b = bottom;
x = Mathf.Min(src_x, _x);
x = Mathf.Min(x, src_r);
y = Mathf.Min(src_y, _z);
y = Mathf.Min(y, src_b);
right = Mathf.Max(src_x, _x);
right = Mathf.Max(right, src_r);
bottom = Mathf.Max(src_y, _z);
bottom = Mathf.Max(bottom, src_b);
}
public bool AnyIntersect(List<QTAABB> aabbs)
{
foreach (var aabb in aabbs)
{
if (IsIntersect(aabb))
{
return true;
}
}
return false;
}
public bool AnyContainsBy(List<QTAABB> aabbs)
{
foreach (var aabb in aabbs)
{
if (aabb.Contains(this))
{
return true;
}
}
return false;
}
public bool Equals(QTAABB other)
{
return x == other.x && y == other.y && w == other.w && h == other.h;
}
public void Reorder()
{
var src_x = x;
var src_y = y;
var src_r = right;
var src_b = bottom;
x = Mathf.Min(src_x, src_r);
y = Mathf.Min(src_y, src_b);
x = Mathf.Max(src_x, src_r);
y = Mathf.Max(src_y, src_b);
}
public static void GetCameraAABBs(Camera cam, List<QTAABB> ret,
int level = DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
{
ret.Clear();
if (cam.orthographic)
{
var aabb = new QTAABB();
GetOrthorCameraAABB(cam, ref aabb, h_padding);
ret.Add(aabb);
}
else
{
GetFrustumCameraAABBs(cam, ret, level, h_padding);
}
}
public static void GetOrthorCameraAABB(Camera cam, ref QTAABB aabb, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
{
System.Diagnostics.Debug.Assert(cam.orthographic == true);
var far = cam.farClipPlane;
var near = cam.nearClipPlane;
var delta_fn = far - near;
var half_height = cam.orthographicSize;
var half_with = cam.aspect * half_height;
var forward = cam.transform.forward;
var right = cam.transform.right;
var up = cam.transform.up;
var start_pos = cam.transform.position + forward * near;
var top_left = start_pos + forward * delta_fn + (-right * half_with) + (up * half_height);
var top_right = top_left + (right * (2 * half_with));
var bottom_right = top_right + (-up * (2 * half_height));
var bottom_left = bottom_right + (-right * (2 * half_with));
var h_padding_vec = right * h_padding;
top_left -= h_padding_vec;
top_right += h_padding_vec;
bottom_right += h_padding_vec;
bottom_left -= h_padding_vec;
aabb.w = aabb.h = 0;
aabb.x = start_pos.x;
aabb.y = start_pos.z;
aabb.Union(ref top_left);
aabb.Union(ref top_right);
aabb.Union(ref bottom_right);
aabb.Union(ref bottom_left);
}
public static void GetFrustumCameraAABBs(Camera cam, List<QTAABB> aabbs, int level = DEFAULT_GET_CAM_FRUSTUM_TO_AABB_LEVEL, float h_padding = DEFAULT_GET_CAM_FRUSTUM_AABB_H_PADDING)
{
System.Diagnostics.Debug.Assert(cam.orthographic == false);
System.Diagnostics.Debug.Assert(level > 0);
var far = cam.farClipPlane;
var near = cam.nearClipPlane;
var tan = Mathf.Tan(cam.fieldOfView * 0.5f * Mathf.Deg2Rad);
var far_plane_half_height = tan * far;
var far_plane_half_with = cam.aspect * far_plane_half_height;
var near_plane_half_height = tan * near;
var near_plane_half_with = cam.aspect * near_plane_half_height;
var forward = cam.transform.forward;
var right = cam.transform.right;
var up = cam.transform.up;
var far_top_left = cam.transform.position + forward * far + (-right * far_plane_half_with) + (up * far_plane_half_height);
var far_top_right = far_top_left + (right * (2 * far_plane_half_with));
var far_bottom_right = far_top_right + (-up * (2 * far_plane_half_height));
var far_bottom_left = far_bottom_right + (-right * (2 * far_plane_half_with));
var near_top_left = cam.transform.position + forward * near + (-right * near_plane_half_with) + (up * near_plane_half_height);
var near_top_right = near_top_left + (right * (2 * near_plane_half_with));
var near_bottom_right = near_top_right + (-up * (2 * near_plane_half_height));
var near_bottom_left = near_bottom_right + (-right * (2 * near_plane_half_with));
var n2f_top_left_vec = far_top_left - near_top_left;
var n2f_top_right_vec = far_top_right - near_top_right;
var n2f_bottom_right_vec = far_bottom_right - near_bottom_right;
var n2f_bottom_left_vec = far_bottom_left - near_bottom_left;
var h_padding_vec = right * h_padding;
for (int i = 0; i < level; i++)
{
var rate_start = (float)i / level;
var rate_end = (float)(i + 1) / level;
var top_left_start = near_top_left + n2f_top_left_vec * rate_start;
var top_right_start = near_top_right + n2f_top_right_vec * rate_start;
var bottom_right_start = near_bottom_right + n2f_bottom_right_vec * rate_start;
var bottom_left_start = near_bottom_left + n2f_bottom_left_vec * rate_start;
top_left_start -= h_padding_vec;
top_right_start += h_padding_vec;
bottom_right_start += h_padding_vec;
bottom_left_start -= h_padding_vec;
var top_left_end = near_top_left + n2f_top_left_vec * rate_end;
var top_right_end = near_top_right + n2f_top_right_vec * rate_end;
var bottom_right_end = near_bottom_right + n2f_bottom_right_vec * rate_end;
var bottom_left_end = near_bottom_left + n2f_bottom_left_vec * rate_end;
top_left_end -= h_padding_vec;
top_right_end += h_padding_vec;
bottom_right_end += h_padding_vec;
bottom_left_end -= h_padding_vec;
var aabb = new QTAABB();
aabb.Set(top_left_start.x, top_left_start.z, 0, 0);
aabb.Union(ref top_left_start);
aabb.Union(ref top_right_start);
aabb.Union(ref bottom_right_start);
aabb.Union(ref bottom_left_start);
aabb.Union(ref top_left_end);
aabb.Union(ref top_right_end);
aabb.Union(ref bottom_right_end);
aabb.Union(ref bottom_left_end);
aabbs.Add(aabb);
}
}
public static implicit operator QTAABB(Bounds v)
{
var b_min = v.min;
var b_max = v.max;
return new QTAABB
{
min = new Vector2(b_min.x, b_min.z),
max = new Vector2(b_max.x, b_max.z),
};
}
public override string ToString()
{
return base.ToString() + $", x:{x}, y:{y}, w:{w}, h:{h}, right:{right}, bottom:{bottom}";
}
}
public class QuadTree<T> : IDisposable
{
public class Leaf
{
public Branch branch;
public T value;
public QTAABB aabb;
}
public class Branch
{
public QuadTree<T> belongTree;
public Branch parent;
public QTAABB aabb;
public QTAABB[] aabbs = new QTAABB[4];
public Branch[] branches = new Branch[4];
public List<Leaf> leaves = new List<Leaf>();
public List<Leaf> crossBranchesLeaves = new List<Leaf>();
public int depth;
public bool hasSplit;
public bool hasRecycle;
}
public const int MAX_LIMIT_LEVEL = 32;
public const int DEFAULT_MAX_LEVEL = 10;
public const int DEFAULT_MAX_LEAF_PER_BRANCH = 50;
public float cullingDistance = float.NaN;
private Dictionary<T, Leaf> leavesDict = new Dictionary<T, Leaf>();
public Branch root;
private int maxLevel;
private int maxLeafPerBranch;
private bool[] insert_helper = new bool[4];
private Stack<Branch> branchPool = new Stack<Branch>();
private Stack<Leaf> leafPool = new Stack<Leaf>();
private ListPool<T> listDataPool = new ListPool<T>();
private List<QTAABB> aabbs_helper = new List<QTAABB>();
public QuadTree(QTAABB aabb,
int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
: this(aabb.x, aabb.y, aabb.w, aabb.h, maxLevel, maxLeafPerBranch)
{
}
public QuadTree(float x, float y, float w, float h,
int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
{
_Reset(x, y, w, h, maxLevel, maxLeafPerBranch);
}
public void Dispose()
{
root = null;
if (leavesDict != null)
{
leavesDict.Clear();
leavesDict = null;
}
if (branchPool != null)
{
branchPool.Clear();
branchPool = null;
}
if (leafPool != null)
{
leafPool.Clear();
leafPool = null;
}
if (listDataPool != null)
{
listDataPool.Clear();
listDataPool = null;
}
if (aabbs_helper != null)
{
aabbs_helper.Clear();
aabbs_helper = null;
}
}
public void Clear()
{
if (root != null)
{
var src_aabb = root.aabb;
_RecycleBranchToPool(root);
leavesDict.Clear();
root = _GetBranchFromPool(null, 0, ref src_aabb);
}
}
public bool Insert(T value, QTAABB aabb)
{
return Insert(value, ref aabb);
}
public bool Insert(T value, ref QTAABB aabb)
{
Leaf leaf;
if (leavesDict.TryGetValue(value, out leaf))
{
leaf.aabb = aabb;
}
else
{
leaf = _GetLeafFromPool(value, ref aabb);
leavesDict[value] = leaf;
}
return _Insert(root, leaf);
}
public void Select(QTAABB aabb, List<T> ret)
{
Select(ref aabb, ret);
}
public void Select(ref QTAABB aabb, List<T> ret, bool moreActuallySelect = true)
{
ret.Clear();
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABB");
#endif
List<T> compeleteInAABB_ret = listDataPool.FromPool();
_SelectByAABB(ref aabb, ret, compeleteInAABB_ret, root);
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
if (moreActuallySelect)
{
for (int i = ret.Count - 1; i > -1; i--)
{
if (!aabb.IsIntersect(leavesDict[ret[i]].aabb))
{
ret.RemoveAt(i);
}
}
}
if (compeleteInAABB_ret.Count > 0)
{
ret.AddRange(compeleteInAABB_ret);
}
listDataPool.ToPool(compeleteInAABB_ret);
#else
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABB");
#endif
_SelectByAABB(ref aabb, ret, root);
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
if (moreActuallySelect)
{
for (int i = ret.Count - 1; i > -1; i--)
{
if (!aabb.IsIntersect(leavesDict[ret[i]].aabb))
{
ret.RemoveAt(i);
}
}
}
#endif
}
public void Select(float x, float y, float w, float h, List<T> ret)
{
Select(new QTAABB { x = x, y = y, w = w, h = h }, ret);
}
public void Select(Vector2 pos, List<T> ret)
{
Select(ref pos, ret);
}
public void Select(ref Vector2 pos, List<T> ret, bool moreActuallySelect = true)
{
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select(ref Vector2 pos, List<T> ret, bool moreActuallySelect = true)");
#endif
ret.Clear();
_SelectByPos(ref pos, ret, root);
if (moreActuallySelect)
{
for (int i = ret.Count - 1; i > -1; i--)
{
if (!leavesDict[ret[i]].aabb.Contains(pos))
{
ret.RemoveAt(i);
}
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
public void Select(float x, float y, List<T> ret)
{
Select(new Vector2(x, y), ret);
}
public void Select(List<QTAABB> aabbs, List<T> ret, bool moreActuallySelect = true)
{
ret.Clear();
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select(List<QTAABB> aabbs, List<T> ret, bool moreActuallySelect = true)");
#endif
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
# if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select.1");
# endif
List<T> compeleteInAABB_ret = listDataPool.FromPool();
_SelectByAABBS(aabbs, ret, compeleteInAABB_ret, root);
# if __ENABLE_QT_PROFILER__
Profiler.EndSample();
# endif
# if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select.2");
# endif
if (moreActuallySelect)
{
for (int i = ret.Count - 1; i > -1; i--)
{
var intersect = false;
foreach (var aabb in aabbs)
{
if (aabb.IsIntersect(leavesDict[ret[i]].aabb))
{
intersect = true;
break;
}
}
if (!intersect)
{
ret.RemoveAt(i);
}
}
}
# if __ENABLE_QT_PROFILER__
Profiler.EndSample();
# endif
if (compeleteInAABB_ret.Count > 0)
{
ret.AddRange(compeleteInAABB_ret);
}
listDataPool.ToPool(compeleteInAABB_ret);
#else
# if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select.1");
# endif
_SelectByAABBS(aabbs, ret, root);
# if __ENABLE_QT_PROFILER__
Profiler.EndSample();
# endif
# if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree.Select.2");
# endif
if (moreActuallySelect)
{
for (int i = ret.Count - 1; i > -1; i--)
{
var intersect = false;
foreach (var aabb in aabbs)
{
if (aabb.IsIntersect(leavesDict[ret[i]].aabb))
{
intersect = true;
break;
}
}
if (!intersect)
{
ret.RemoveAt(i);
}
}
}
# if __ENABLE_QT_PROFILER__
Profiler.EndSample();
# endif
#endif
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
public void Select(QTAABB aabb, List<T> ret, Vector2 selectFromPos)
{
Select(ref aabb, ret, selectFromPos);
}
public void Select(ref QTAABB aabb, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
{
if (_IsCullingDistance(ref aabb, selectFromPos))
{
ret.Clear();
return;
}
Select(ref aabb, ret, moreActuallySelect);
}
public void Select(float x, float y, float w, float h, List<T> ret, Vector2 selectFromPos)
{
if (_IsCullingDistance(new QTAABB { x = x, y = y, w = w, h = h }, selectFromPos))
{
ret.Clear();
return;
}
Select(x, y, w, h, ret);
}
public void Select(Vector2 pos, List<T> ret, Vector2 selectFromPos)
{
if (_IsCullingDistance(pos, selectFromPos))
{
ret.Clear();
return;
}
Select(ref pos, ret);
}
public void Select(ref Vector2 pos, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
{
if (_IsCullingDistance(pos, selectFromPos))
{
ret.Clear();
return;
}
Select(ref pos, ret, moreActuallySelect);
}
public void Select(float x, float y, List<T> ret, Vector2 selectFromPos)
{
Select(new Vector2(x, y), ret, selectFromPos);
}
public void Select(List<QTAABB> aabbs, List<T> ret, Vector2 selectFromPos, bool moreActuallySelect = true)
{
if (aabbs.Count == 0)
{
return;
}
aabbs_helper.Clear();
aabbs_helper.AddRange(aabbs);
for (int i = 0; i < aabbs.Count; i++)
{
if (_IsCullingDistance(aabbs_helper[i], selectFromPos))
{
aabbs_helper.RemoveRange(i, aabbs.Count - i);
break;
}
}
if (aabbs_helper.Count == 0)
{
return;
}
Select(aabbs_helper, ret, moreActuallySelect);
}
private bool _IsCullingDistance(Vector2 selectPos, Vector2 selectFormPos)
{
return _IsCullingDistance(ref selectPos, selectFormPos);
}
private bool _IsCullingDistance(ref Vector2 selectPos, Vector2 selectFormPos)
{
var cd = cullingDistance;
if (!float.IsNaN(cd) && cd > 0)
{
var pow2cd = cd * cd;
if (pow2cd < Vector2.SqrMagnitude(selectPos - selectFormPos))
{
return true;
}
}
return false;
}
private bool _IsCullingDistance(QTAABB aabb, Vector2 relactivePos)
{
return _IsCullingDistance(ref aabb, relactivePos);
}
private bool _IsCullingDistance(ref QTAABB aabb, Vector2 relactivePos)
{
var cd = cullingDistance;
if (!float.IsNaN(cd) && cd > 0)
{
var pow2cd = cd * cd;
var tl = aabb.top_left;
var tr = aabb.top_right;
var br = aabb.bottom_right;
var bl = aabb.bottom_left;
var culling_count = 0;
if (pow2cd < Vector2.SqrMagnitude(tl - relactivePos))
{
culling_count++;
}
if (pow2cd < Vector2.SqrMagnitude(tr - relactivePos))
{
culling_count++;
}
if (pow2cd < Vector2.SqrMagnitude(br - relactivePos))
{
culling_count++;
}
if (pow2cd < Vector2.SqrMagnitude(bl - relactivePos))
{
culling_count++;
}
return (culling_count == 4);
}
return false;
}
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
private void _SelectByAABB(ref QTAABB aabb, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)
{
if (branch == null)
{
return;
}
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABB(ref QTAABB aabb, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)");
#endif
if (branch.aabb.Contains(ref aabb))
{
_SelectAllValues(branch, compeleteInAABB_ret);
}
else
{
if (branch.aabb.IsIntersect(ref aabb))
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectByAABB(ref aabb, ret, compeleteInAABB_ret, b);
}
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
#else
private void _SelectByAABB(ref QTAABB aabb, List<T> ret, Branch branch)
{
if (branch == null)
{
return;
}
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABB(ref QTAABB aabb, List<T> ret, Branch branch)");
#endif
if (branch.aabb.Contains(ref aabb))
{
_SelectAllValues(branch, ret);
}
else
{
if (branch.aabb.IsIntersect(ref aabb))
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectByAABB(ref aabb, ret, b);
}
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
#endif
private void _SelectAllValues(Branch branch, List<T> ret)
{
if (branch == null)
{
return;
}
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectAllValues(Branch branch, List<T> ret)");
#endif
if (branch != null)
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectAllValues(b, ret);
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
private void _SelectByPos(ref Vector2 pos, List<T> ret, Branch branch)
{
if (branch == null)
{
return;
}
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByPos(ref Vector2 pos, List<T> ret, Branch branch)");
#endif
if (branch.aabb.Contains(ref pos))
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectByPos(ref pos, ret, b);
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
#if __ENABLE_COMPLETE_CONTAINS_BRANCH_HANDLE__
private void _SelectByAABBS(List<QTAABB> aabbs, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)
{
if (branch == null)
{
return;
}
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABBS(List<QTAABB> aabbs, List<T> ret, List<T> compeleteInAABB_ret, Branch branch)");
#endif
if (branch.aabb.AnyContainsBy(aabbs))
{
_SelectAllValues(branch, compeleteInAABB_ret);
}
else
{
if (branch.aabb.AnyIntersect(aabbs))
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectByAABBS(aabbs, ret, compeleteInAABB_ret, b);
}
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
#else
private void _SelectByAABBS(List<QTAABB> aabbs, List<T> ret, Branch branch)
{
if (branch == null)
{
return;
}
# if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SelectByAABBS");
# endif
if (branch.aabb.AnyContainsBy(aabbs))
{
_SelectAllValues(branch, ret);
}
else
{
if (branch.aabb.AnyIntersect(aabbs))
{
foreach (var l in branch.leaves)
{
ret.Add(l.value);
}
foreach (var l in branch.crossBranchesLeaves)
{
ret.Add(l.value);
}
foreach (var b in branch.branches)
{
_SelectByAABBS(aabbs, ret, b);
}
}
}
# if __ENABLE_QT_PROFILER__
Profiler.EndSample();
# endif
}
#endif
private bool _Insert(Branch branch, Leaf leaf)
{
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._Insert(Branch branch, Leaf leaf)");
#endif
var ret = false;
if (!branch.aabb.IsIntersect(ref leaf.aabb))
{
}
else
{
if (branch.hasSplit)
{
if (branch.leaves.Count > 0)
{
_SrcLeavesInsertToSubBranches(branch);
}
ret = _InsertSingleLeaf(branch, leaf);
}
else
{
if (branch.depth <= maxLevel && (branch.leaves.Count + branch.crossBranchesLeaves.Count) >= maxLeafPerBranch)
{
branch.hasSplit = true;
ret = _Insert(branch, leaf);
}
else
{
branch.leaves.Add(leaf);
leaf.branch = branch;
ret = true;
}
}
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
return ret;
}
private void _SrcLeavesInsertToSubBranches(Branch branch)
{
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._SrcLeavesInsertToSubBranches(Branch branch)");
#endif
for (int i = 0; i < branch.leaves.Count; i++)
{
var l = branch.leaves[i];
_InsertSingleLeaf(branch, l);
}
branch.leaves.Clear();
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
}
private bool _InsertSingleLeaf(Branch branch, Leaf leaf)
{
#if __ENABLE_QT_PROFILER__
Profiler.BeginSample($"QuadTree._InsertSingleLeaf(Branch branch, Leaf leaf)");
#endif
var contains_with_branch_count = 0;
var branch_idx = -1;
for (int i = 0; i < 4; i++)
{
if (branch.aabbs[i].IsIntersect(ref leaf.aabb))
{
contains_with_branch_count++;
insert_helper[i] = true;
branch_idx = i;
if (branch.branches[i] == null || branch.branches[i].hasRecycle)
{
branch.branches[i] = _GetBranchFromPool(branch, branch.depth + 1, ref branch.aabbs[i]);
}
}
}
var ret = false;
if (contains_with_branch_count > 1)
{
branch.crossBranchesLeaves.Add(leaf);
leaf.branch = branch;
ret = true;
}
else
{
if (branch_idx < 0 || branch_idx > 3) branch_idx = 3;
ret = _Insert(branch.branches[branch_idx], leaf);
}
#if __ENABLE_QT_PROFILER__
Profiler.EndSample();
#endif
return ret;
}
private Branch _GetBranchFromPool(Branch parent, int depth, ref QTAABB aabb)
{
var ret = branchPool.Count > 0 ? branchPool.Pop() : new Branch();
ret.belongTree = this;
ret.parent = parent;
ret.depth = depth;
ret.aabb = aabb;
ret.hasRecycle = false;
float halfW = aabb.w * 0.5f;
float halfH = aabb.h * 0.5f;
float midX = aabb.x + halfW;
float midY = aabb.y + halfH;
ret.aabbs[0].Set(aabb.x, aabb.y, halfW, halfH);
ret.aabbs[1].Set(midX, aabb.y, halfW, halfH);
ret.aabbs[2].Set(midX, midY, halfW, halfH);
ret.aabbs[3].Set(aabb.x, midY, halfW, halfH);
return ret;
}
private void _RecycleBranchToPool(Branch branch)
{
if (branch == null)
{
return;
}
branch.belongTree = null;
branch.parent = null;
branch.hasSplit = false;
branch.hasRecycle = true;
branchPool.Push(branch);
foreach (var l in branch.leaves)
{
_RecycleLeafToPool(l);
}
branch.leaves.Clear();
foreach (var l in branch.crossBranchesLeaves)
{
_RecycleLeafToPool(l);
}
branch.crossBranchesLeaves.Clear();
for (int i = 0; i < branch.branches.Length; i++)
{
_RecycleBranchToPool(branch.branches[i]);
branch.branches[i] = null;
}
}
private Leaf _GetLeafFromPool(T value, ref QTAABB aabb)
{
var ret = leafPool.Count > 0 ? leafPool.Pop() : new Leaf();
ret.value = value;
ret.aabb = aabb;
return ret;
}
private void _RecycleLeafToPool(Leaf leaf)
{
leaf.branch = null;
leaf.value = default;
leafPool.Push(leaf);
}
private void _Reset(float x, float y, float w, float h,
int maxLevel = DEFAULT_MAX_LEVEL, int maxLeafPerBranch = DEFAULT_MAX_LEAF_PER_BRANCH)
{
System.Diagnostics.Debug.Assert(w == 0 || h == 0, "QTAABB is Zero");
System.Diagnostics.Debug.Assert(maxLevel < MAX_LIMIT_LEVEL, $"QuadTree MaxLevel cannot more than : {MAX_LIMIT_LEVEL}");
this.maxLevel = maxLevel;
this.maxLeafPerBranch = maxLeafPerBranch;
var aabb = new QTAABB { x = x, y = y, w = w, h = h };
if (root != null)
{
root.aabb = aabb;
}
else
{
root = _GetBranchFromPool(null, 0, ref aabb);
}
}
}
TestingQuadTree.cs
using System.Collections.Generic;
using UnityEngine;
public class TestingQuadTree : MonoBehaviour
{
public enum eShowGOType
{
None = 0,
ShowInFrustum = 1,
ShowNotInFrustum = 2,
All = -1,
}
[Header("镜头")]
public Camera cam;
[Header("四叉树内的对象")]
public GameObject[] goes;
[Header("原始 四叉树 的根对象")]
public GameObject goes_root;
[Header("绘制镜头的 aabb 的颜色")]
public Color cam_aabb_color = Color.red;
[Header("绘制镜头视锥的 aabb 的颜色")]
public Color cam_frustum_color = Color.yellow;
[Header("绘制四叉树的枝干 aabb 的颜色")]
public Color qt_branches_color = Color.cyan;
[Header("绘制四叉树的叶子 aabb 的颜色")]
public Color qt_leaves_color = Color.green;
[Header("绘制镜头视锥内的 aabb 叉树的叶子的 aabb 的颜色")]
public Color qt_leaves_in_frustum_color = Color.blue;
[Header("是否绘制镜头的 aabb")]
public bool draw_gizmos_cam_aabb = true;
[Header("是否绘制镜头视锥的 wireframe")]
public bool draw_gizmos_cam_wireframe = true;
[Header("是否绘制四叉树的枝干 aabb")]
public bool draw_gizmos_qt_branches = true;
[Header("是否绘制四叉树的叶子 aabb")]
public bool draw_gizmos_qt_leaves = true;
[Header("是否绘制镜头视锥内的 aabb 叉树的叶子的 aabb")]
public bool draw_gizmos_qt_leaves_in_frustum_aabb = true;
[Header("控制 GameObject 的显示类型")]
public eShowGOType show_GO_type = eShowGOType.ShowInFrustum;
[Header("视锥水平剔除空间扩大的 unit 单位")]
public float frustum_h_padding = 5;
[Header("视锥分段的多层 AABB 的级别")]
[Range(1, 20)]
public int frustum_AABB_level = 3;
[Header("开启的话,结果会再精准一些,但会增加部分计算量(也可以关闭后,让外部来处理:稍微精确一些的筛选)")]
public bool more_actually_select = true;
[Header("测试时实时重插四叉树(暴力重构会导致很卡,消耗 CPU 很大,3456 个对象就需要花费 8.5ms-+)")]
public bool reconstrct_qt_runtime = false;
private QuadTree<GameObject> qt;
private List<GameObject> qt_select_ret_helper;
private List<QTAABB> cam_aabbs = new List<QTAABB>();
private void Start()
{
var scene_bounds = CalculateSceneAABB();
qt = new QuadTree<GameObject>(scene_bounds);
qt_select_ret_helper = new List<GameObject>();
InsertQTObjs();
}
private void Update()
{
QT_Select();
UpdateGoVisible();
if (reconstrct_qt_runtime)
{
ReconstructQT();
}
}
private Bounds CalculateSceneAABB()
{
var ret = new Bounds();
foreach (var go in goes)
{
var renderer = go.GetComponent<Renderer>();
if (renderer != null)
{
ret.Encapsulate(renderer.bounds);
}
}
return ret;
}
private void QT_Select()
{
qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
}
private void UpdateGoVisible()
{
switch (show_GO_type)
{
case eShowGOType.None:
goes_root.SetAct(false);
break;
case eShowGOType.ShowInFrustum:
goes_root.SetAct(true);
foreach (var go in goes)
{
go.SetAct(false);
}
if (qt != null && qt_select_ret_helper != null)
{
qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
foreach (var go in qt_select_ret_helper)
{
go.SetAct(true);
}
}
break;
case eShowGOType.ShowNotInFrustum:
goes_root.SetAct(true);
foreach (var go in goes)
{
go.SetAct(true);
}
if (qt != null && qt_select_ret_helper != null)
{
qt.Select(cam_aabbs, qt_select_ret_helper, more_actually_select);
foreach (var go in qt_select_ret_helper)
{
go.SetAct(false);
}
}
break;
case eShowGOType.All:
goes_root.SetAct(true);
foreach (var go in goes)
{
go.SetAct(true);
}
break;
default:
break;
}
}
private void InsertQTObjs()
{
foreach (var go in goes)
{
var renderer = go.GetComponent<Renderer>();
if (renderer != null)
{
qt.Insert(go, renderer.bounds);
}
}
}
private void ReconstructQT()
{
qt.Clear();
InsertQTObjs();
}
private void OnDrawGizmos()
{
if (draw_gizmos_cam_aabb) _DrawCameraAABB();
if (draw_gizmos_cam_wireframe) _DrawCameraWireframe();
if (draw_gizmos_qt_branches) _DrawQTBranchesAABBs();
if (draw_gizmos_qt_leaves) _DrawQTLeavesAABBs();
if (draw_gizmos_qt_leaves_in_frustum_aabb) _DrawQTInFrustumLeavesAABBs();
}
private void _DrawCameraAABB()
{
QTAABB.GetCameraAABBs(cam, cam_aabbs, frustum_AABB_level, frustum_h_padding);
foreach (var aabb in cam_aabbs)
{
_DrawQTAABB(aabb, cam_aabb_color);
}
}
private void _DrawCameraWireframe()
{
Gizmos.color = cam_frustum_color;
Matrix4x4 temp = Gizmos.matrix;
Gizmos.matrix = Matrix4x4.TRS(cam.transform.position, cam.transform.rotation, Vector3.one);
if (!cam.orthographic)
{
Gizmos.DrawFrustum(Vector3.zero, cam.fieldOfView, cam.farClipPlane, cam.nearClipPlane, cam.aspect);
}
else
{
var far = cam.farClipPlane;
var near = cam.nearClipPlane;
var delta_fn = far - near;
var half_height = cam.orthographicSize;
var half_with = cam.aspect * half_height;
var pos = Vector3.forward * (delta_fn * 0.5f + near);
var size = new Vector3(half_with * 2, half_height * 2, delta_fn);
Gizmos.DrawWireCube(pos, size);
}
Gizmos.matrix = temp;
}
private void _DrawQTBranchesAABBs()
{
if (qt != null)
{
_DrawBranch(qt.root, qt_branches_color);
}
}
private void _DrawQTLeavesAABBs()
{
if (qt != null)
{
_DrawLeafsOfBrances(qt.root, qt_leaves_color);
}
}
private void _DrawQTInFrustumLeavesAABBs()
{
if (qt != null && qt_select_ret_helper != null)
{
foreach (var go in qt_select_ret_helper)
{
var renderer = go.GetComponent<Renderer>();
_DrawBoundsXZ(renderer.bounds, qt_leaves_in_frustum_color);
}
}
}
private void _DrawBranch(QuadTree<GameObject>.Branch branch, Color color)
{
if (branch == null)
{
return;
}
_DrawQTAABB(branch.aabb, color);
foreach (var b in branch.branches)
{
_DrawBranch(b, color);
}
}
private void _DrawLeafsOfBrances(QuadTree<GameObject>.Branch branch, Color color)
{
if (branch == null)
{
return;
}
foreach (var b in branch.branches)
{
if (b == null)
{
continue;
}
foreach (var l in b.leaves)
{
_DrawQTAABB(l.aabb, color);
}
_DrawLeafsOfBrances(b, color);
}
}
private void _DrawBoundsXZ(Bounds bounds, Color color)
{
Gizmos.color = color;
var min = bounds.min;
var max = bounds.max;
var start_pos = min;
var end_pos = min;
end_pos.x = max.x;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.z = max.z;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.x = min.x;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.z = min.z;
Gizmos.DrawLine(start_pos, end_pos);
}
private void _DrawQTAABB(QTAABB aabb, Color color)
{
Gizmos.color = color;
var min = aabb.min;
var max = aabb.max;
var start_pos = new Vector3(min.x, 0, min.y);
var end_pos = start_pos;
end_pos.x = max.x;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.z = max.y;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.x = min.x;
Gizmos.DrawLine(start_pos, end_pos);
start_pos = end_pos;
end_pos = start_pos;
end_pos.z = min.y;
Gizmos.DrawLine(start_pos, end_pos);
}
}
public static class MyExt
{
public static void SetAct(this GameObject go, bool value)
{
if (go.activeSelf != value)
{
go.SetActive(value);
}
}
}
问题
我这个例子是应用在:3D 顶视角下的 2D 四叉树剔除
Project
扩展方案
后面还有 四叉树 + 视锥cascaded + instancing + custom lod 的场景优化方案
之前制作到一半,但是给人使绊子,就没再继续撸下去了,后续在家重新路一套完整的
|