下面是我自己的读取代码的笔记:
树节点的定义:
数据+孩子节点
数据:就是当前节点包含的区域
孩子节点,可以使用链表或者是数组。
树节点的构造函数:
public SceneTreeNode(Bounds bounds, int depth, int childCount)
{
m_Bounds = bounds; //数据
m_CurrentDepth = depth; //当前节点的深度
m_ObjectList = new LinkedList<T>(); //如果孩子使用的是链表则是用LinkedList
m_ChildNodes = new SceneTreeNode<T>[childCount]; //如果孩子使用的是数组
if (childCount == 8) //八叉树,后面会分析到
m_HalfSize = new Vector3(m_Bounds.size.x / 2, m_Bounds.size.y / 2, m_Bounds.size.z / 2);
else
m_HalfSize = new Vector3(m_Bounds.size.x / 2, m_Bounds.size.y, m_Bounds.size.z / 2); //四叉树,xz平面取bound中心点
m_ChildCount = childCount; //孩子的个数
}
树节点的clear函数:
public void Clear()
{
for (int i = 0; i < m_ChildNodes.Length; i++) //遍历所有孩子节点,然后进行清空
{
if (m_ChildNodes[i] != null)
m_ChildNodes[i].Clear();
}
if (m_ObjectList != null) //如果使用链表,则清空链表即可
m_ObjectList.Clear();
}
树节点的查找函数:
public bool Contains(T obj)
{
for (int i = 0; i < m_ChildNodes.Length; i++)//遍历所有孩子是否包含给定的节点,如果包含返回true
{
if (m_ChildNodes[i] != null && m_ChildNodes[i].Contains(obj))
return true;
}
if (m_ObjectList != null && m_ObjectList.Contains(obj))//同上
return true;
return false;
}
树节点的插入:
public SceneTreeNode<T> Insert(T obj, int depth, int maxDepth)
{
if (m_ObjectList.Contains(obj)) //如果链表中包含了obj,则直接返回即可
return this;
if (depth < maxDepth) //如果插入节点的深度小于最大深度
{
SceneTreeNode<T> node = GetContainerNode(obj, depth); //转入下面的分析
if (node != null)
return node.Insert(obj, depth + 1, maxDepth);
}
var n = m_ObjectList.AddFirst(obj);
obj.SetLinkedListNode(0, n);
return this;
}
树节点的GetContainerNode方法:
protected SceneTreeNode<T> GetContainerNode(T obj, int depth)
{
SceneTreeNode<T> result = null;
int ix = -1;
int iz = -1;
int iy = m_ChildNodes.Length == 4 ? 0 : -1; //如果是四叉树,则iy=0
int nodeIndex = 0;
for (int i = ix; i <= 1; i += 2)
{
for (int k = iy; k <= 1; k += 2)
{
for (int j = iz; j <= 1; j += 2)
{
result = CreateNode(ref m_ChildNodes[nodeIndex], depth,
m_Bounds.center + new Vector3(i * m_HalfSize.x * 0.5f, k * m_HalfSize.y * 0.5f, j * m_HalfSize.z * 0.5f),
m_HalfSize, obj);
if (result != null)
{
return result;
}
nodeIndex += 1; //累加索引,看看四个节点中的位置是哪个?
}
}
}
return null;
}
从上面的那段代码,我们应该明白作者的意图是什么?可以使用下图展示出来:
注释:
正如上图所示,中心小白点,表示当前树的节点,其bounds为蓝、粉、浅蓝、红色四个矩形框。
然后分别将其bounds划分为四个小矩形框,作为其子节点。
从上图可以看出,根节点的bounds是所有子节点的bounds的和。
创建节点的函数:CreateNode
protected SceneTreeNode<T> CreateNode(ref SceneTreeNode<T> node, int depth, Vector3 centerPos, Vector3 size, T obj)
{
SceneTreeNode<T> result = null;
if (node == null) //如果当前根节点为null,则进行创建
{
Bounds bounds = new Bounds(centerPos, size);
if (bounds.IsBoundsContainsAnotherBounds(obj.Bounds))//如果这个节点的bounds能够圈住obj的bounds则创建这个节点
{
SceneTreeNode<T> newNode = new SceneTreeNode<T>(bounds, depth + 1, m_ChildNodes.Length);
node = newNode;
result = node;
}
}
else if (node.Bounds.IsBoundsContainsAnotherBounds(obj.Bounds)) //如果已经创建并且当前节点的bounds能圈住obj的bounds则直接返回
{
result = node;
}
return result;
}
节点创建函数的总结:如果当前节点就已经是包含了这个bounds,那么直接返回,否则要去创建一个新的node,而两者的检测条件都是是否当前节点的bounds包含了obj的bounds。
ok,到这里我相信所有的人都会懵了,到底是如何构建四叉树的呢?下面我们通过实例的数据和图的形式展示下源代码的构建过程。
首先以6个数据进行构建,并且树的高度最大为3,6个数据足以说明四叉树的构建过程了。
哪6个数据,在unity中找到:
6个element的bounds,就是我们要构建的数据。首先确定根节点,根节点应该包含了整个场景的大小,也就是bounds为:
如上图所示,地形的俯视图。
6个节点的数据表如下:
构建图如下:
解释下上图:
可以看到节点的颜色有红色,有黑色,红色表示这个节点有数据,黑色表示这个节点为null。
因为是四叉树,所以节点最多为四个。
对于最终element落在哪个树节点,遵循下面的流程,如果没有达到树的最大高度则继续往下插入,直到不能插入为止。
对于某个树节点的bounds不能在细分的情况下,则说明只能在本节点进行存储了。
比如下面:
这两个bounds是不能继续往下分了,所以就存储在当前的节点数据中。
以上为四叉树为高度为3的情况的构建过程。
也就是说,我们的大地形是一开始全部加载出来的,而每个块的物件(静态物件)是一个个添加然后构建树的节点的。
定时刷新,检测是否有新的物体需要加载,老的物体需要预删除。
public void RefreshDetector(IDetector detector)
{
if (!m_IsInitialized)
return;
//只有坐标发生改变才调用
if (m_OldRefreshPosition != detector.Position)
{
m_RefreshTime += Time.deltaTime;
//达到刷新时间才刷新,避免区域更新频繁
if (m_RefreshTime > m_MaxRefreshTime)
{
m_OldRefreshPosition = detector.Position;
m_RefreshTime = 0;
m_CurrentDetector = detector;
//进行触发检测
m_Tree.Trigger(detector, m_TriggerHandle);
//标记超出区域的物体
MarkOutofBoundsObjs();
//m_IsInitLoadComplete = true;
}
}
什么是进行触发检测?
public void Trigger(IDetector detector, TriggerHandle<T> handle)
{
if (handle == null)
return;
if (detector.IsDetected(Bounds) == false)
return;
m_Root.Trigger(detector, handle);
}
if (detector.IsDetected(Bounds) == false)
干嘛的?
public override bool IsDetected(Bounds bounds)
{
RefreshBounds();
return bounds.Intersects(m_Bounds);
}
protected override void RefreshBounds()
{
m_Bounds.center = Position + m_PosOffset;
m_Bounds.size = detectorSize + m_SizeEx;
}
计算当前位置所属的象限
public override int GetDetectedCode(float x, float y, float z, bool ignoreY)
{
RefreshBounds();
int code = 0;
if (ignoreY)
{
float minx = m_Bounds.min.x;
float minz = m_Bounds.min.z;
float maxx = m_Bounds.max.x;
float maxz = m_Bounds.max.z;
if (minx <= x && minz <= z)
code |= 1;
if (minx <= x && maxz >= z)
code |= 2;
if (maxx >= x && minz <= z)
code |= 4;
if (maxx >= x && maxz >= z)
code |= 8;
}
void TriggerHandle(SceneObject data)
{
if (data == null)
return;
if (data.Flag == SceneObject.CreateFlag.Old) //如果发生触发的物体已经被创建则标记为新物体,以确保不会被删掉
{
data.Weight++;
data.Flag = SceneObject.CreateFlag.New;
}
else if (data.Flag == SceneObject.CreateFlag.OutofBounds)//如果发生触发的物体已经被标记为超出区域,则从待删除列表移除该物体,并标记为新物体
{
data.Flag = SceneObject.CreateFlag.New;
//if (m_PreDestroyObjectList.Remove(data))
{
m_LoadedObjectList.Add(data);
}
}
else if (data.Flag == SceneObject.CreateFlag.None) //如果发生触发的物体未创建则创建该物体并加入已加载的物体列表
{
DoCreateInternal(data);
}
}
//执行创建物体
private void DoCreateInternal(SceneObject data)
{
//加入已加载列表
m_LoadedObjectList.Add(data);
//创建物体
CreateObject(data, m_Asyn);
}
/// <summary>
/// 标记离开视野的物体
/// </summary>
void MarkOutofBoundsObjs()
{
if (m_LoadedObjectList == null)
return;
int i = 0;
while (i < m_LoadedObjectList.Count)
{
if (m_LoadedObjectList[i].Flag == SceneObject.CreateFlag.Old)//已加载物体标记仍然为Old,说明该物体没有进入触发区域,即该物体在区域外
{
m_LoadedObjectList[i].Flag = SceneObject.CreateFlag.OutofBounds;
//m_PreDestroyObjectList.Add(m_LoadedObjectList[i]);
if (m_MinCreateCount == 0)//如果最小创建数为0直接删除
{
DestroyObject(m_LoadedObjectList[i], m_Asyn);
}
else
{
//m_PreDestroyObjectQueue.Enqueue(m_LoadedObjectList[i]);
m_PreDestroyObjectQueue.Push(m_LoadedObjectList[i]);//加入待删除队列
}
m_LoadedObjectList.RemoveAt(i);
}
else
{
m_LoadedObjectList[i].Flag = SceneObject.CreateFlag.Old;//其它物体标记为旧
i++;
}
}
}