387 lines
13 KiB
C#
387 lines
13 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using UnityEditor;
|
|
using UnityEngine;
|
|
|
|
namespace Unity.HLODSystem.SpaceManager
|
|
{
|
|
public static class BoundsExtension
|
|
{
|
|
public static bool IsPartOf(this Bounds bounds, Bounds target)
|
|
{
|
|
return (double) bounds.min.x >= (double) target.min.x &&
|
|
(double) bounds.max.x <= (double) target.max.x &&
|
|
(double) bounds.min.y >= (double) target.min.y &&
|
|
(double) bounds.max.y <= (double) target.max.y &&
|
|
(double) bounds.min.z >= (double) target.min.z &&
|
|
(double) bounds.max.z <= (double) target.max.z;
|
|
}
|
|
}
|
|
public class QuadTreeSpaceSplitter : ISpaceSplitter
|
|
{
|
|
|
|
[InitializeOnLoadMethod]
|
|
static void RegisterType()
|
|
{
|
|
SpaceSplitterTypes.RegisterSpaceSplitterType(typeof(QuadTreeSpaceSplitter));
|
|
}
|
|
|
|
private float m_looseSizeFromOptions;
|
|
|
|
private bool m_useSubHLODTree;
|
|
private float m_subHLODTreeSize;
|
|
|
|
|
|
public QuadTreeSpaceSplitter(SerializableDynamicObject spaceSplitterOptions)
|
|
{
|
|
m_looseSizeFromOptions = 0.0f;
|
|
|
|
m_useSubHLODTree = false;
|
|
m_subHLODTreeSize = 0.0f;
|
|
|
|
if (spaceSplitterOptions == null)
|
|
{
|
|
return;
|
|
}
|
|
|
|
dynamic options = spaceSplitterOptions;
|
|
if(options.LooseSize1 != null)
|
|
m_looseSizeFromOptions = options.LooseSize;
|
|
if(options.UseSubHLODTree != null)
|
|
m_useSubHLODTree = options.UseSubHLODTree;
|
|
if(options.SubHLODTreeSize != null)
|
|
m_subHLODTreeSize = options.SubHLODTreeSize;
|
|
|
|
}
|
|
|
|
public int CalculateSubTreeCount(Bounds bounds)
|
|
{
|
|
if (m_useSubHLODTree == false)
|
|
return 1;
|
|
|
|
List<Bounds> splittedBounds = SplitBounds(bounds, m_subHLODTreeSize);
|
|
return splittedBounds.Count;
|
|
}
|
|
|
|
public int CalculateTreeDepth(Bounds bounds, float chunkSize)
|
|
{
|
|
float maxLength = 0.0f;
|
|
if (m_useSubHLODTree)
|
|
{
|
|
List<Bounds> splittedBounds = SplitBounds(bounds, m_subHLODTreeSize);
|
|
if (splittedBounds.Count > 0)
|
|
{
|
|
maxLength = Mathf.Max(splittedBounds[0].extents.x, splittedBounds[0].extents.z);
|
|
}
|
|
else
|
|
{
|
|
maxLength = Mathf.Max(bounds.extents.x, bounds.extents.z);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
maxLength = Mathf.Max(bounds.extents.x, bounds.extents.z);
|
|
}
|
|
|
|
int depth = 1;
|
|
|
|
while (maxLength > chunkSize)
|
|
{
|
|
depth += 1;
|
|
maxLength *= 0.5f;
|
|
}
|
|
|
|
return depth;
|
|
}
|
|
|
|
public List<SpaceNode> CreateSpaceTree(Bounds initBounds, float chunkSize, Transform transform,
|
|
List<GameObject> targetObjects, Action<float> onProgress)
|
|
{
|
|
List<SpaceNode> nodes = new List<SpaceNode>();
|
|
|
|
List<TargetInfo> targetInfos = CreateTargetInfoList(targetObjects, transform);
|
|
|
|
if (m_useSubHLODTree == true)
|
|
{
|
|
List<Bounds> splittedBounds = SplitBounds(initBounds, m_subHLODTreeSize);
|
|
List<List<TargetInfo>> splittedTargetInfos = SplitTargetObjects(targetInfos, splittedBounds);
|
|
|
|
float progressSize = 1.0f / splittedTargetInfos.Count;
|
|
for (int i = 0; i < splittedTargetInfos.Count; ++i)
|
|
{
|
|
nodes.Add(CreateSpaceTreeImpl(splittedBounds[i], chunkSize, splittedTargetInfos[i], (p =>
|
|
{
|
|
float startProgress = i * progressSize;
|
|
onProgress?.Invoke(startProgress + p * progressSize);
|
|
})));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
nodes.Add(CreateSpaceTreeImpl(initBounds, chunkSize, targetInfos, onProgress));
|
|
}
|
|
|
|
return nodes;
|
|
}
|
|
private SpaceNode CreateSpaceTreeImpl(Bounds initBounds, float chunkSize, List<TargetInfo> targetObjects, Action<float> onProgress)
|
|
{
|
|
float looseSize = CalcLooseSize(chunkSize);
|
|
SpaceNode rootNode = new SpaceNode();
|
|
rootNode.Bounds = initBounds;
|
|
|
|
if ( onProgress != null)
|
|
onProgress(0.0f);
|
|
|
|
//space split first
|
|
Stack<SpaceNode> nodeStack = new Stack<SpaceNode>();
|
|
nodeStack.Push(rootNode);
|
|
|
|
while(nodeStack.Count > 0 )
|
|
{
|
|
SpaceNode node = nodeStack.Pop();
|
|
if ( node.Bounds.size.x > chunkSize )
|
|
{
|
|
List<SpaceNode> childNodes = CreateChildSpaceNodes(node, looseSize);
|
|
|
|
for ( int i = 0; i < childNodes.Count; ++i )
|
|
{
|
|
childNodes[i].ParentNode = node;
|
|
nodeStack.Push(childNodes[i]);
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
if (targetObjects == null)
|
|
return rootNode;
|
|
|
|
for (int oi = 0; oi < targetObjects.Count; ++oi)
|
|
{
|
|
Bounds objectBounds = targetObjects[oi].Bounds;
|
|
SpaceNode target = rootNode;
|
|
|
|
while (true)
|
|
{
|
|
if (target.HasChild())
|
|
{
|
|
//the object can be in the over 2 nodes.
|
|
//we should figure out which node is more close with the object.
|
|
int nearestChild = -1;
|
|
float nearestDistance = float.MaxValue;
|
|
|
|
for (int ci = 0; ci < target.GetChildCount(); ++ci)
|
|
{
|
|
if (objectBounds.IsPartOf(target.GetChild(ci).Bounds))
|
|
{
|
|
float dist = Vector3.Distance(target.GetChild(ci).Bounds.center, objectBounds.center);
|
|
|
|
if (dist < nearestDistance)
|
|
{
|
|
nearestChild = ci;
|
|
nearestDistance = dist;
|
|
}
|
|
}
|
|
}
|
|
|
|
//We should find out it until we get the fit size from the bottom.
|
|
//this means the object is small to add in the current node.
|
|
if (nearestChild >= 0)
|
|
{
|
|
target = target.GetChild(nearestChild);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
target.Objects.Add(targetObjects[oi].GameObject);
|
|
break;
|
|
}
|
|
|
|
if ( onProgress != null)
|
|
onProgress((float)oi/ (float)targetObjects.Count);
|
|
}
|
|
|
|
return rootNode;
|
|
}
|
|
|
|
struct TargetInfo
|
|
{
|
|
public GameObject GameObject;
|
|
public Bounds Bounds;
|
|
}
|
|
|
|
private List<TargetInfo> CreateTargetInfoList(List<GameObject> gameObjects, Transform transform)
|
|
{
|
|
if (gameObjects == null)
|
|
{
|
|
return new List<TargetInfo>();
|
|
}
|
|
List<TargetInfo> targetInfos = new List<TargetInfo>(gameObjects.Count);
|
|
|
|
for (int i = 0; i < gameObjects.Count; ++i)
|
|
{
|
|
Bounds? bounds = CalculateBounds(gameObjects[i], transform);
|
|
if ( bounds == null )
|
|
continue;
|
|
targetInfos.Add(new TargetInfo()
|
|
{
|
|
GameObject = gameObjects[i],
|
|
Bounds = bounds.Value,
|
|
});
|
|
}
|
|
|
|
return targetInfos;
|
|
}
|
|
|
|
private Bounds? CalculateBounds(GameObject obj, Transform transform)
|
|
{
|
|
MeshRenderer[] renderers = obj.GetComponentsInChildren<MeshRenderer>();
|
|
if (renderers.Length == 0)
|
|
return null;
|
|
|
|
Bounds result = Utils.BoundsUtils.CalcLocalBounds(renderers[0], transform);
|
|
for (int i = 1; i < renderers.Length; ++i)
|
|
{
|
|
result.Encapsulate(Utils.BoundsUtils.CalcLocalBounds(renderers[i], transform));
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private List<Bounds> SplitBounds(Bounds bounds, float splitSize)
|
|
{
|
|
int xcount = Mathf.CeilToInt(bounds.size.x / splitSize);
|
|
int zcount = Mathf.CeilToInt(bounds.size.z / splitSize);
|
|
|
|
float xsize = bounds.size.x / xcount;
|
|
float zsize = bounds.size.z / zcount;
|
|
|
|
List<Bounds> boundsList = new List<Bounds>();
|
|
Vector3 splitBoundSize = new Vector3(xsize, bounds.size.y, zsize);
|
|
|
|
for (int z = 0; z < zcount; ++z)
|
|
{
|
|
for (int x = 0; x < xcount; ++x)
|
|
{
|
|
Vector3 center = new Vector3(
|
|
x * xsize + xsize * 0.5f,
|
|
bounds.extents.y,
|
|
z * zsize + zsize * 0.5f) + bounds.min;
|
|
|
|
boundsList.Add(new Bounds(center,splitBoundSize));
|
|
}
|
|
}
|
|
|
|
return boundsList;
|
|
}
|
|
|
|
private List<List<TargetInfo>> SplitTargetObjects(List<TargetInfo> targetInfoList, List<Bounds> targetBoundList)
|
|
{
|
|
List<List<TargetInfo>> targetObjectsList = new List<List<TargetInfo>>();
|
|
for (int i = 0; i < targetBoundList.Count; ++i)
|
|
{
|
|
targetObjectsList.Add(new List<TargetInfo>());
|
|
}
|
|
|
|
foreach (var targetInfo in targetInfoList)
|
|
{
|
|
for (int bi = 0; bi < targetBoundList.Count; ++bi)
|
|
{
|
|
if (targetBoundList[bi].Contains(targetInfo.Bounds.center))
|
|
{
|
|
targetObjectsList[bi].Add(targetInfo);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return targetObjectsList;
|
|
}
|
|
|
|
private float CalcLooseSize(float chunkSize)
|
|
{
|
|
//If the chunk size is small, there is a problem that it may get caught in an infinite loop.
|
|
//So, the size can be determined according to the chunk size.
|
|
return Mathf.Min(chunkSize * 0.3f, m_looseSizeFromOptions);
|
|
|
|
}
|
|
|
|
|
|
|
|
private List<SpaceNode> CreateChildSpaceNodes(SpaceNode parentNode, float looseSize)
|
|
{
|
|
List<SpaceNode> childSpaceNodes = new List<SpaceNode>(4);
|
|
|
|
float size = parentNode.Bounds.size.x;
|
|
float extend = size * 0.5f;
|
|
float offset = extend * 0.5f;
|
|
|
|
Vector3 center = parentNode.Bounds.center;
|
|
Vector3 looseBoundsSize = new Vector3(extend + looseSize, size, extend + looseSize);
|
|
|
|
childSpaceNodes.Add(
|
|
SpaceNode.CreateSpaceNodeWithBounds(
|
|
new Bounds(center + new Vector3(-offset, 0.0f, -offset), looseBoundsSize)
|
|
));
|
|
childSpaceNodes.Add(
|
|
SpaceNode.CreateSpaceNodeWithBounds(
|
|
new Bounds(center + new Vector3(-offset, 0.0f, offset), looseBoundsSize)
|
|
));
|
|
childSpaceNodes.Add(
|
|
SpaceNode.CreateSpaceNodeWithBounds(
|
|
new Bounds(center + new Vector3(offset, 0.0f, -offset), looseBoundsSize)
|
|
));
|
|
childSpaceNodes.Add(
|
|
SpaceNode.CreateSpaceNodeWithBounds(
|
|
new Bounds(center + new Vector3(offset, 0.0f, offset), looseBoundsSize)
|
|
));
|
|
|
|
return childSpaceNodes;
|
|
}
|
|
|
|
public static SerializableDynamicObject CreateOptions(
|
|
bool autoCalcLooseSize = true,
|
|
float looseSize = 5.0f,
|
|
bool useSubHLODTree = false,
|
|
float subHLODTreeSize = 100.0f)
|
|
{
|
|
var dynamicObject = new SerializableDynamicObject();
|
|
dynamic options = dynamicObject;
|
|
|
|
options.AutoCalcLooseSize = autoCalcLooseSize;
|
|
options.LooseSize = looseSize;
|
|
options.UseSubHLODTree = useSubHLODTree;
|
|
options.SubHLODTreeSize = subHLODTreeSize;
|
|
|
|
return dynamicObject;
|
|
}
|
|
|
|
public static void OnGUI(SerializableDynamicObject spaceSplitterOptions)
|
|
{
|
|
dynamic options = spaceSplitterOptions;
|
|
|
|
//initialize values
|
|
if (options.LooseSize == null)
|
|
options.LooseSize = 5.0f;
|
|
if (options.UseSubHLODTree == null)
|
|
options.UseSubHLODTree = false;
|
|
if (options.SubHLODTreeSize == null)
|
|
options.SubHLODTreeSize = 100.0f;
|
|
|
|
//Draw UI
|
|
options.LooseSize = EditorGUILayout.FloatField("Loose size", options.LooseSize);
|
|
|
|
options.UseSubHLODTree = EditorGUILayout.ToggleLeft("Use sub HLOD tree", options.UseSubHLODTree);
|
|
if (options.UseSubHLODTree == true)
|
|
{
|
|
EditorGUI.indentLevel += 1;
|
|
options.SubHLODTreeSize = EditorGUILayout.FloatField("Sub tree size", options.SubHLODTreeSize);
|
|
EditorGUI.indentLevel -= 1;
|
|
}
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
} |