1
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"name": "AdvancedCullingSystem.Runtime",
|
||||
"rootNamespace": "",
|
||||
"references": [
|
||||
"GUID:d8b63aba1907145bea998dd612889d6b",
|
||||
"GUID:e0cd26848372d4e5c891c569017e11f1",
|
||||
"GUID:2665a8d13d1b3f18800f46e256720795"
|
||||
],
|
||||
"includePlatforms": [],
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"overrideReferences": false,
|
||||
"precompiledReferences": [],
|
||||
"autoReferenced": true,
|
||||
"defineConstraints": [],
|
||||
"versionDefines": [
|
||||
{
|
||||
"name": "com.unity.collections",
|
||||
"expression": "1.3.1",
|
||||
"define": "COLLECTIONS_1_3_1_OR_NEWER"
|
||||
}
|
||||
],
|
||||
"noEngineReferences": false
|
||||
}
|
@@ -0,0 +1,240 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem
|
||||
{
|
||||
public abstract class BinaryTree<TNode, TData> where TNode : BinaryTreeNode
|
||||
{
|
||||
public BinaryTreeNode Root
|
||||
{
|
||||
get
|
||||
{
|
||||
return RootInternal;
|
||||
}
|
||||
}
|
||||
|
||||
[field: SerializeReference]
|
||||
protected TNode RootInternal { get; private set; }
|
||||
|
||||
[field: SerializeField]
|
||||
public int Height { get; private set; }
|
||||
|
||||
[field : SerializeField]
|
||||
public float CellSize { get; private set; }
|
||||
|
||||
private int _maxDepth = -1;
|
||||
|
||||
|
||||
public BinaryTree(float cellSize)
|
||||
{
|
||||
CellSize = Mathf.Max(cellSize, 0.1f);
|
||||
}
|
||||
|
||||
public BinaryTree(IList<TData> datas, int maxDepth)
|
||||
{
|
||||
if (maxDepth <= 1)
|
||||
throw new ArgumentException("Max depth should be greater than 1");
|
||||
|
||||
_maxDepth = maxDepth;
|
||||
|
||||
Vector3 min = Vector3.one * float.MaxValue;
|
||||
Vector3 max = Vector3.one * float.MinValue;
|
||||
|
||||
foreach (var data in datas)
|
||||
{
|
||||
Bounds dBounds = GetBounds(data);
|
||||
Vector3 dMin = dBounds.min;
|
||||
Vector3 dMax = dBounds.max;
|
||||
|
||||
min.x = Mathf.Min(min.x, dMin.x);
|
||||
min.y = Mathf.Min(min.y, dMin.y);
|
||||
min.z = Mathf.Min(min.z, dMin.z);
|
||||
|
||||
max.x = Mathf.Max(max.x, dMax.x);
|
||||
max.y = Mathf.Max(max.y, dMax.y);
|
||||
max.z = Mathf.Max(max.z, dMax.z);
|
||||
}
|
||||
|
||||
RootInternal = CreateNode(min + ((max - min) / 2), max - min + Vector3.one * 0.01f, false);
|
||||
Height = 1;
|
||||
|
||||
foreach (var data in datas)
|
||||
Add(data);
|
||||
}
|
||||
|
||||
public void Add(TData data)
|
||||
{
|
||||
if (Root == null)
|
||||
{
|
||||
RootInternal = CreateNode(GetBounds(data).center, Vector3.one * CellSize, true);
|
||||
Height = 1;
|
||||
}
|
||||
|
||||
if (!Includes(RootInternal, data))
|
||||
GrowTreeUp(data);
|
||||
|
||||
AddInternal(RootInternal, data, 1);
|
||||
}
|
||||
|
||||
|
||||
private TNode ExpandRoot(TNode root, TData target)
|
||||
{
|
||||
Bounds rootBounds = root.Bounds;
|
||||
Bounds targetBounds = GetBounds(target);
|
||||
|
||||
Vector3 parentCenter = Vector3.zero;
|
||||
Vector3 parentSize = Vector3.zero;
|
||||
|
||||
Vector3 childCenter = Vector3.zero;
|
||||
|
||||
bool rootIsLeft = false;
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
if (targetBounds.min[i] < rootBounds.min[i])
|
||||
{
|
||||
parentSize = rootBounds.size;
|
||||
parentSize[i] *= 2;
|
||||
|
||||
parentCenter = rootBounds.center;
|
||||
parentCenter[i] -= rootBounds.size[i] / 2;
|
||||
|
||||
childCenter = rootBounds.center;
|
||||
childCenter[i] -= rootBounds.size[i];
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
if (targetBounds.max[i] > rootBounds.max[i])
|
||||
{
|
||||
parentSize = rootBounds.size;
|
||||
parentSize[i] *= 2;
|
||||
|
||||
parentCenter = rootBounds.center;
|
||||
parentCenter[i] += rootBounds.size[i] / 2;
|
||||
|
||||
childCenter = rootBounds.center;
|
||||
childCenter[i] += rootBounds.size[i];
|
||||
|
||||
rootIsLeft = true;
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
TNode parent = CreateNode(parentCenter, parentSize, false);
|
||||
TNode child = CreateNode(childCenter, rootBounds.size, root.IsLeaf);
|
||||
|
||||
if (rootIsLeft)
|
||||
SetChildsToNode(parent, RootInternal, child);
|
||||
else
|
||||
SetChildsToNode(parent, child, RootInternal);
|
||||
|
||||
return parent;
|
||||
}
|
||||
|
||||
protected void GrowTreeUp(TData target)
|
||||
{
|
||||
if (Includes(RootInternal, target))
|
||||
return;
|
||||
|
||||
RootInternal = ExpandRoot(RootInternal, target);
|
||||
Height++;
|
||||
|
||||
GrowTreeUp(target);
|
||||
}
|
||||
|
||||
protected void GrowTreeDown(TNode node, TData target, int depth)
|
||||
{
|
||||
if (node.HasChilds)
|
||||
throw new Exception("GrowTreeDown::" + depth + " node already has childs");
|
||||
|
||||
Bounds nodeBounds = node.Bounds;
|
||||
Vector3 offset;
|
||||
Vector3 size;
|
||||
|
||||
if (nodeBounds.size.x >= nodeBounds.size.y && nodeBounds.size.x >= nodeBounds.size.z)
|
||||
{
|
||||
offset = new Vector3(nodeBounds.size.x / 4, 0, 0);
|
||||
size = new Vector3(nodeBounds.size.x / 2, nodeBounds.size.y, nodeBounds.size.z);
|
||||
}
|
||||
else if (nodeBounds.size.y >= nodeBounds.size.x && nodeBounds.size.y >= nodeBounds.size.z)
|
||||
{
|
||||
offset = new Vector3(0, nodeBounds.size.y / 4, 0);
|
||||
size = new Vector3(nodeBounds.size.x, nodeBounds.size.y / 2, nodeBounds.size.z);
|
||||
}
|
||||
else
|
||||
{
|
||||
offset = new Vector3(0, 0, nodeBounds.size.z / 4);
|
||||
size = new Vector3(nodeBounds.size.x, nodeBounds.size.y, nodeBounds.size.z / 2);
|
||||
}
|
||||
|
||||
bool isLeafs = (depth == _maxDepth) ||
|
||||
(size.x <= CellSize && size.y <= CellSize && size.z <= CellSize);
|
||||
|
||||
TNode left = CreateNode(nodeBounds.center - offset, size, isLeafs);
|
||||
TNode right = CreateNode(nodeBounds.center + offset, size, isLeafs);
|
||||
|
||||
SetChildsToNode(node, left, right);
|
||||
|
||||
if (isLeafs)
|
||||
{
|
||||
if (Height < depth)
|
||||
Height = depth;
|
||||
|
||||
if (CellSize == 0)
|
||||
CellSize = Mathf.Min(size.x, size.y, size.z);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (Intersects(left, target))
|
||||
GrowTreeDown(left, target, depth + 1);
|
||||
|
||||
if (Intersects(right, target))
|
||||
GrowTreeDown(right, target, depth + 1);
|
||||
}
|
||||
|
||||
protected bool Intersects(TNode node, TData data)
|
||||
{
|
||||
return node.Bounds.Intersects(GetBounds(data));
|
||||
}
|
||||
|
||||
protected bool Includes(TNode node, TData data)
|
||||
{
|
||||
return node.Bounds.Contains(GetBounds(data));
|
||||
}
|
||||
|
||||
|
||||
protected virtual void AddInternal(TNode node, TData data, int depth)
|
||||
{
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
AddDataToNode(node, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.HasChilds)
|
||||
GrowTreeDown(node, data, depth + 1);
|
||||
|
||||
TNode left = (TNode)node.GetLeft();
|
||||
TNode right = (TNode)node.GetRight();
|
||||
|
||||
if (Intersects(left, data))
|
||||
AddInternal(left, data, depth + 1);
|
||||
|
||||
if (Intersects(right, data))
|
||||
AddInternal(right, data, depth + 1);
|
||||
}
|
||||
|
||||
protected abstract Bounds GetBounds(TData data);
|
||||
|
||||
protected abstract TNode CreateNode(Vector3 center, Vector3 size, bool isLeaf);
|
||||
|
||||
protected abstract void SetChildsToNode(TNode parent, TNode leftChild, TNode rightChild);
|
||||
|
||||
protected abstract void AddDataToNode(TNode node, TData data);
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem
|
||||
{
|
||||
public class BinaryTreeDrawer
|
||||
{
|
||||
public Color Color { get; set; }
|
||||
|
||||
public void DrawTreeGizmos(BinaryTreeNode root)
|
||||
{
|
||||
if (root == null)
|
||||
return;
|
||||
|
||||
Bounds bounds = root.Bounds;
|
||||
|
||||
Gizmos.color = Color;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
|
||||
DrawTreeGizmos(root.GetLeft());
|
||||
DrawTreeGizmos(root.GetRight());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,59 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem
|
||||
{
|
||||
public abstract class BinaryTreeNode
|
||||
{
|
||||
public Vector3 Center
|
||||
{
|
||||
get
|
||||
{
|
||||
return _bounds.center;
|
||||
}
|
||||
}
|
||||
public Vector3 Size
|
||||
{
|
||||
get
|
||||
{
|
||||
return _bounds.size;
|
||||
}
|
||||
}
|
||||
public Bounds Bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _bounds;
|
||||
}
|
||||
}
|
||||
public bool HasChilds
|
||||
{
|
||||
get
|
||||
{
|
||||
return GetLeft() != null;
|
||||
}
|
||||
}
|
||||
public bool IsLeaf
|
||||
{
|
||||
get
|
||||
{
|
||||
return _isLeaf;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private Bounds _bounds;
|
||||
|
||||
[SerializeField]
|
||||
private bool _isLeaf;
|
||||
|
||||
public BinaryTreeNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
{
|
||||
_bounds = new Bounds(center, size);
|
||||
_isLeaf = isLeaf;
|
||||
}
|
||||
|
||||
public abstract BinaryTreeNode GetLeft();
|
||||
|
||||
public abstract BinaryTreeNode GetRight();
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem
|
||||
{
|
||||
public static class BoundsHelper
|
||||
{
|
||||
public static bool Contains(this Bounds bounds, Bounds target)
|
||||
{
|
||||
return bounds.Contains(target.min) && bounds.Contains(target.max);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,53 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem
|
||||
{
|
||||
public static class LODGroupHelper
|
||||
{
|
||||
public static int Count(this LODGroup group, Func<Renderer, bool> filter)
|
||||
{
|
||||
LOD[] lods = group.GetLODs();
|
||||
|
||||
return Count(lods, filter);
|
||||
}
|
||||
|
||||
public static int Count(this LOD[] lods, Func<Renderer, bool> filter)
|
||||
{
|
||||
int count = 0;
|
||||
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
Renderer[] renderers = lods[i].renderers;
|
||||
|
||||
for (int c = 0; c < renderers.Length; c++)
|
||||
{
|
||||
Renderer renderer = renderers[c];
|
||||
|
||||
if (renderer == null)
|
||||
continue;
|
||||
|
||||
if (filter(renderer))
|
||||
count++;
|
||||
}
|
||||
}
|
||||
|
||||
return count;
|
||||
}
|
||||
|
||||
public static bool ContainsAny(this LOD[] lods, Func<Renderer, bool> filter)
|
||||
{
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
Renderer[] renderers = lods[i].renderers;
|
||||
|
||||
for (int c = 0; c < renderers.Length; c++)
|
||||
if (filter(renderers[c]))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,208 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Collections;
|
||||
using Unity.Jobs;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public partial class DC_Camera : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private int _raysCount = 1500;
|
||||
|
||||
[SerializeField]
|
||||
private DistributionMethod _raysDistribution = DistributionMethod.R2;
|
||||
|
||||
[Space]
|
||||
|
||||
[Range(0, 90)]
|
||||
[SerializeField]
|
||||
private int _fovAddition = 5;
|
||||
|
||||
[SerializeField]
|
||||
private bool _autoCheckChanges = false;
|
||||
|
||||
private IReadOnlyDictionary<Collider, IHitable> _hitablesDic;
|
||||
|
||||
private Camera _camera;
|
||||
private DC_CameraSettings _settings;
|
||||
private bool _updateSettings;
|
||||
private bool _updateRaysCount;
|
||||
private int _newRaysCount;
|
||||
|
||||
private Vector3[] _rayDirs;
|
||||
|
||||
private NativeArray<RaycastCommand> _rayCommands;
|
||||
private NativeArray<RaycastHit> _rayHits;
|
||||
private JobHandle _jobHandle;
|
||||
private int _layerMask;
|
||||
private int _currentRay;
|
||||
private bool _cameraEnabled;
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
|
||||
private QueryParameters _query;
|
||||
|
||||
#endif
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_camera = GetComponent<Camera>();
|
||||
_newRaysCount = _raysCount;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_hitablesDic = DC_Controller.GetHitables();
|
||||
_layerMask = LayerMask.GetMask(DC_Controller.GetCullingLayerName());
|
||||
|
||||
_updateRaysCount = true;
|
||||
_updateSettings = true;
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
|
||||
_query = new QueryParameters(_layerMask, false, QueryTriggerInteraction.Ignore, false);
|
||||
|
||||
#endif
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
_cameraEnabled = _camera.enabled && gameObject.activeInHierarchy;
|
||||
|
||||
if (!_cameraEnabled)
|
||||
return;
|
||||
|
||||
UpdateIfNeeded();
|
||||
|
||||
int totalCount = _rayDirs.Length;
|
||||
float distance = _settings.farPlane;
|
||||
|
||||
Vector3 origin = _camera.transform.position;
|
||||
Matrix4x4 matrix = _camera.transform.localToWorldMatrix;
|
||||
|
||||
for (int i = 0; i < _raysCount; i++)
|
||||
{
|
||||
|
||||
#if UNITY_2022_2_OR_NEWER
|
||||
|
||||
_rayCommands[i] = new RaycastCommand(origin,
|
||||
matrix.MultiplyVector(_rayDirs[_currentRay]), _query, distance);
|
||||
|
||||
#else
|
||||
|
||||
_rayCommands[i] = new RaycastCommand(origin,
|
||||
matrix.MultiplyVector(_rayDirs[_currentRay]), distance, _layerMask);
|
||||
|
||||
#endif
|
||||
|
||||
_currentRay++;
|
||||
|
||||
if (_currentRay >= totalCount)
|
||||
_currentRay = 0;
|
||||
}
|
||||
|
||||
_jobHandle = RaycastCommand.ScheduleBatch(_rayCommands, _rayHits, 1);
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (!_cameraEnabled)
|
||||
return;
|
||||
|
||||
_jobHandle.Complete();
|
||||
|
||||
for (int i = 0; i < _raysCount; i++)
|
||||
{
|
||||
Collider collider = _rayHits[i].collider;
|
||||
|
||||
if (collider != null)
|
||||
{
|
||||
if (_hitablesDic.TryGetValue(collider, out IHitable hitable))
|
||||
hitable.OnHit();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (_rayCommands.IsCreated)
|
||||
_rayCommands.Dispose();
|
||||
|
||||
if (_rayHits.IsCreated)
|
||||
_rayHits.Dispose();
|
||||
}
|
||||
|
||||
|
||||
public void CameraSettingsChanged()
|
||||
{
|
||||
_updateSettings = true;
|
||||
}
|
||||
|
||||
public void SetRaysCount(int count)
|
||||
{
|
||||
_updateRaysCount = true;
|
||||
_newRaysCount = count;
|
||||
}
|
||||
|
||||
|
||||
private bool IsCameraSettingsChanged()
|
||||
{
|
||||
if (_settings.width != _camera.pixelWidth)
|
||||
return true;
|
||||
|
||||
if (_settings.height != _camera.pixelHeight)
|
||||
return true;
|
||||
|
||||
if (_settings.fov != _camera.fieldOfView)
|
||||
return true;
|
||||
|
||||
if (_settings.farPlane != _camera.farClipPlane)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private void UpdateIfNeeded()
|
||||
{
|
||||
if (!_updateSettings && _autoCheckChanges)
|
||||
{
|
||||
if (IsCameraSettingsChanged())
|
||||
_updateSettings = true;
|
||||
}
|
||||
|
||||
if (_updateSettings)
|
||||
{
|
||||
UpdateCameraSettings();
|
||||
_updateSettings = false;
|
||||
}
|
||||
|
||||
if (_updateRaysCount)
|
||||
{
|
||||
UpdateRaysCount(_newRaysCount);
|
||||
_updateRaysCount = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void UpdateRaysCount(int count)
|
||||
{
|
||||
if (_rayCommands.IsCreated)
|
||||
_rayCommands.Dispose();
|
||||
|
||||
if (_rayHits.IsCreated)
|
||||
_rayHits.Dispose();
|
||||
|
||||
_rayCommands = new NativeArray<RaycastCommand>(count, Allocator.Persistent);
|
||||
_rayHits = new NativeArray<RaycastHit>(count, Allocator.Persistent);
|
||||
_raysCount = _newRaysCount;
|
||||
}
|
||||
|
||||
private void UpdateCameraSettings()
|
||||
{
|
||||
_rayDirs = DC_CameraUtil.GetRaysDirections(_camera, _raysDistribution, _fovAddition);
|
||||
_settings = new DC_CameraSettings(_camera);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public struct DC_CameraSettings
|
||||
{
|
||||
public int width;
|
||||
public int height;
|
||||
public float fov;
|
||||
public float farPlane;
|
||||
|
||||
public DC_CameraSettings(Camera camera)
|
||||
{
|
||||
width = camera.pixelWidth;
|
||||
height = camera.pixelHeight;
|
||||
fov = camera.fieldOfView;
|
||||
farPlane = camera.farClipPlane;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public partial class DC_Camera : MonoBehaviour
|
||||
{
|
||||
public enum DistributionMethod { Halton, R2 }
|
||||
|
||||
private static class DC_CameraUtil
|
||||
{
|
||||
private static Dictionary<DC_CameraSettings, Vector3[]> _rayDirsTable;
|
||||
|
||||
private static double _r2a1;
|
||||
private static double _r2a2;
|
||||
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ReloadDomain()
|
||||
{
|
||||
_rayDirsTable?.Clear();
|
||||
}
|
||||
|
||||
static DC_CameraUtil()
|
||||
{
|
||||
_rayDirsTable = new Dictionary<DC_CameraSettings, Vector3[]>();
|
||||
|
||||
double g = 1.32471795724474602596;
|
||||
|
||||
_r2a1 = 1.0 / g;
|
||||
_r2a2 = 1.0 / (g * g);
|
||||
}
|
||||
|
||||
public static Vector3[] GetRaysDirections(Camera camera, DistributionMethod distribution, int fovAddition)
|
||||
{
|
||||
DC_CameraSettings settings = new DC_CameraSettings(camera);
|
||||
|
||||
if (_rayDirsTable.TryGetValue(settings, out Vector3[] result))
|
||||
return result;
|
||||
|
||||
float cameraFov = camera.fieldOfView;
|
||||
Matrix4x4 cameraInvTransform = camera.transform.localToWorldMatrix.inverse;
|
||||
|
||||
int count = (Screen.width * Screen.height) / 8;
|
||||
Vector3[] dirs = new Vector3[count];
|
||||
|
||||
camera.fieldOfView = cameraFov + fovAddition;
|
||||
|
||||
for (int i = 0; i < count; i++)
|
||||
{
|
||||
Vector2 viewPoint;
|
||||
|
||||
if (distribution == DistributionMethod.Halton)
|
||||
viewPoint = new Vector2(HaltonSequence(i, 2), HaltonSequence(i, 3));
|
||||
else
|
||||
viewPoint = R2Distribution(i);
|
||||
|
||||
Ray ray = camera.ViewportPointToRay(viewPoint);
|
||||
|
||||
dirs[i] = cameraInvTransform.MultiplyVector(ray.direction);
|
||||
}
|
||||
|
||||
camera.fieldOfView = cameraFov;
|
||||
|
||||
_rayDirsTable.Add(settings, dirs);
|
||||
|
||||
return dirs;
|
||||
}
|
||||
|
||||
|
||||
private static float HaltonSequence(int index, int b)
|
||||
{
|
||||
float res = 0f;
|
||||
float f = 1f / b;
|
||||
|
||||
int i = index;
|
||||
|
||||
while (i > 0)
|
||||
{
|
||||
res = res + f * (i % b);
|
||||
i = Mathf.FloorToInt(i / b);
|
||||
f = f / b;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private static Vector2 R2Distribution(int index)
|
||||
{
|
||||
float x = (float)((0.5 + _r2a1 * index) % 1);
|
||||
float y = (float)((0.5 + _r2a2 * index) % 1);
|
||||
|
||||
return new Vector2(x, y);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,216 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_Controller : MonoBehaviour
|
||||
{
|
||||
private static Dictionary<int, DC_Controller> _controllersDic;
|
||||
private static Dictionary<Collider, IHitable> _hitablesDic;
|
||||
|
||||
public int ControllerID
|
||||
{
|
||||
get
|
||||
{
|
||||
return _controllerID;
|
||||
}
|
||||
set
|
||||
{
|
||||
_controllerID = value;
|
||||
}
|
||||
}
|
||||
public float ObjectsLifetime
|
||||
{
|
||||
get
|
||||
{
|
||||
return _objectsLifetime;
|
||||
}
|
||||
set
|
||||
{
|
||||
_objectsLifetime = Mathf.Max(0.1f, value);
|
||||
}
|
||||
}
|
||||
public bool MergeInGroups
|
||||
{
|
||||
get
|
||||
{
|
||||
return _mergeInGroups;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (_sourcesProvider != null)
|
||||
{
|
||||
Debug.Log("You can set 'MergeInGroups' option only before initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
_mergeInGroups = value;
|
||||
}
|
||||
}
|
||||
public float CellSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cellSize;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
if (_sourcesProvider != null)
|
||||
{
|
||||
Debug.Log("You can set 'Cell Size' option only before initialized");
|
||||
return;
|
||||
}
|
||||
|
||||
_cellSize = Mathf.Max(value, 0.1f);
|
||||
}
|
||||
}
|
||||
public bool DrawGizmos { get; set; }
|
||||
|
||||
[SerializeField]
|
||||
private int _controllerID;
|
||||
|
||||
[SerializeField, Min(0.1f)]
|
||||
private float _objectsLifetime = 2f;
|
||||
|
||||
[SerializeField]
|
||||
private bool _mergeInGroups = true;
|
||||
|
||||
[SerializeField]
|
||||
private float _cellSize = 10f;
|
||||
|
||||
private IDC_SourcesProvider _sourcesProvider;
|
||||
private BinaryTreeDrawer _treeDrawer;
|
||||
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ReloadDomain()
|
||||
{
|
||||
_controllersDic = null;
|
||||
_hitablesDic = null;
|
||||
}
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_controllersDic == null)
|
||||
_controllersDic = new Dictionary<int, DC_Controller>();
|
||||
|
||||
if (_hitablesDic == null)
|
||||
_hitablesDic = new Dictionary<Collider, IHitable>();
|
||||
|
||||
if (!_controllersDic.ContainsKey(_controllerID))
|
||||
_controllersDic.Add(_controllerID, this);
|
||||
else
|
||||
Debug.Log("DynamicCullingController with id : " + _controllerID + " already exists!");
|
||||
|
||||
if (_mergeInGroups)
|
||||
{
|
||||
_sourcesProvider = new DC_SourcesTree(_cellSize);
|
||||
_treeDrawer = new BinaryTreeDrawer();
|
||||
}
|
||||
else
|
||||
{
|
||||
_sourcesProvider = new DC_SingleSourcesProvider();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!DrawGizmos)
|
||||
return;
|
||||
|
||||
if (_treeDrawer == null)
|
||||
return;
|
||||
|
||||
DC_SourcesTree tree = _sourcesProvider as DC_SourcesTree;
|
||||
|
||||
if (tree.Root == null)
|
||||
return;
|
||||
|
||||
_treeDrawer.Color = Color.white;
|
||||
_treeDrawer.DrawTreeGizmos(tree.Root);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_controllersDic.Remove(_controllerID);
|
||||
}
|
||||
|
||||
|
||||
public DC_Camera AddCamera(Camera camera, int raysPerFrame)
|
||||
{
|
||||
if (camera.TryGetComponent(out DC_Camera cullingCamera))
|
||||
{
|
||||
Debug.Log(camera.name + " already has DynamicCullingCamera component");
|
||||
}
|
||||
else
|
||||
{
|
||||
cullingCamera = camera.gameObject.AddComponent<DC_Camera>();
|
||||
cullingCamera.SetRaysCount(raysPerFrame);
|
||||
}
|
||||
|
||||
return cullingCamera;
|
||||
}
|
||||
|
||||
public DC_SourceSettings AddObjectForCulling(MeshRenderer renderer,
|
||||
CullingMethod cullingMethod = CullingMethod.FullDisable)
|
||||
{
|
||||
DC_SourceSettings settings = renderer.gameObject.AddComponent<DC_SourceSettings>();
|
||||
|
||||
settings.ControllerID = _controllerID;
|
||||
settings.SourceType = SourceType.SingleMesh;
|
||||
settings.GetStrategy<DC_RendererSourceSettingsStrategy>().CullingMethod = cullingMethod;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public DC_SourceSettings AddObjectForCulling(LODGroup lodGroup,
|
||||
CullingMethod cullingMethod = CullingMethod.FullDisable)
|
||||
{
|
||||
DC_SourceSettings settings = lodGroup.gameObject.AddComponent<DC_SourceSettings>();
|
||||
|
||||
settings.ControllerID = _controllerID;
|
||||
settings.SourceType = SourceType.LODGroup;
|
||||
settings.GetStrategy<DC_LODGroupSourceSettingsStrategy>().CullingMethod = cullingMethod;
|
||||
|
||||
return settings;
|
||||
}
|
||||
|
||||
public void AddObjectForCulling(ICullingTarget cullingTarget, IEnumerable<Collider> colliders)
|
||||
{
|
||||
DC_Source source = _sourcesProvider.GetSource(cullingTarget);
|
||||
|
||||
source.Lifetime = _objectsLifetime;
|
||||
source.transform.parent = transform;
|
||||
|
||||
DC_CullingTargetObserver observer = cullingTarget.GameObject.AddComponent<DC_CullingTargetObserver>();
|
||||
observer.Initialize(source, cullingTarget);
|
||||
|
||||
foreach (var collider in colliders)
|
||||
_hitablesDic.Add(collider, source);
|
||||
}
|
||||
|
||||
|
||||
public static DC_Controller GetById(int id)
|
||||
{
|
||||
return _controllersDic[id];
|
||||
}
|
||||
|
||||
public static int GetCullingLayer()
|
||||
{
|
||||
return LayerMask.NameToLayer(GetCullingLayerName());
|
||||
}
|
||||
|
||||
public static string GetCullingLayerName()
|
||||
{
|
||||
return "ACSCulling";
|
||||
}
|
||||
|
||||
public static IReadOnlyDictionary<Collider, IHitable> GetHitables()
|
||||
{
|
||||
return _hitablesDic;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public interface ICullingTarget
|
||||
{
|
||||
GameObject GameObject { get; }
|
||||
Bounds Bounds { get; }
|
||||
|
||||
void MakeVisible();
|
||||
|
||||
void MakeInvisible();
|
||||
}
|
||||
}
|
@@ -0,0 +1,43 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_CustomTarget : ICullingTarget
|
||||
{
|
||||
public GameObject GameObject { get; private set; }
|
||||
public Bounds Bounds { get; private set; }
|
||||
|
||||
private DC_CustomTargetEvent _onVisible;
|
||||
private DC_CustomTargetEvent _onInvisible;
|
||||
|
||||
public DC_CustomTarget(GameObject go, Bounds bounds,
|
||||
DC_CustomTargetEvent onVisible,
|
||||
DC_CustomTargetEvent onInvisible)
|
||||
{
|
||||
GameObject = go;
|
||||
Bounds = bounds;
|
||||
|
||||
_onVisible = onVisible != null ? onVisible : new DC_CustomTargetEvent();
|
||||
_onInvisible = onInvisible != null ? onInvisible : new DC_CustomTargetEvent();
|
||||
}
|
||||
|
||||
public void MakeVisible()
|
||||
{
|
||||
_onVisible?.Invoke(this);
|
||||
}
|
||||
|
||||
public void MakeInvisible()
|
||||
{
|
||||
_onInvisible?.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class DC_CustomTargetEvent : UnityEvent<DC_CustomTarget>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_LODGroupShadowsTarget : DC_LODGroupTargetBase
|
||||
{
|
||||
private Renderer[] _renderers;
|
||||
private ShadowCastingMode _shadowMode;
|
||||
|
||||
public DC_LODGroupShadowsTarget(LODGroup group, Renderer[] renderers, Bounds bounds)
|
||||
: base(group, renderers, bounds)
|
||||
{
|
||||
_renderers = Renderers;
|
||||
_shadowMode = _renderers[0].shadowCastingMode;
|
||||
}
|
||||
|
||||
public override void MakeVisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].shadowCastingMode = _shadowMode;
|
||||
}
|
||||
|
||||
public override void MakeInvisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_LODGroupTarget : DC_LODGroupTargetBase
|
||||
{
|
||||
private Renderer[] _renderers;
|
||||
|
||||
public DC_LODGroupTarget(LODGroup group, Renderer[] renderers, Bounds bounds)
|
||||
: base(group, renderers, bounds)
|
||||
{
|
||||
_renderers = Renderers;
|
||||
}
|
||||
|
||||
public override void MakeVisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].enabled = true;
|
||||
}
|
||||
|
||||
public override void MakeInvisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public abstract class DC_LODGroupTargetBase : ICullingTarget
|
||||
{
|
||||
public GameObject GameObject
|
||||
{
|
||||
get
|
||||
{
|
||||
return Group.gameObject;
|
||||
}
|
||||
}
|
||||
public Bounds Bounds { get; private set; }
|
||||
|
||||
protected LODGroup Group { get; private set; }
|
||||
protected Renderer[] Renderers { get; private set; }
|
||||
|
||||
public DC_LODGroupTargetBase(LODGroup group, Renderer[] renderers, Bounds bounds)
|
||||
{
|
||||
Group = group;
|
||||
Renderers = renderers;
|
||||
Bounds = bounds;
|
||||
}
|
||||
|
||||
public abstract void MakeVisible();
|
||||
|
||||
public abstract void MakeInvisible();
|
||||
}
|
||||
}
|
@@ -0,0 +1,30 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_RendererShadowsTarget : DC_RendererTargetBase
|
||||
{
|
||||
private Renderer _renderer;
|
||||
private ShadowCastingMode _shadowMode;
|
||||
|
||||
public DC_RendererShadowsTarget(Renderer renderer)
|
||||
: base(renderer)
|
||||
{
|
||||
_renderer = Renderer;
|
||||
_shadowMode = _renderer.shadowCastingMode;
|
||||
}
|
||||
|
||||
public override void MakeVisible()
|
||||
{
|
||||
_renderer.shadowCastingMode = _shadowMode;
|
||||
}
|
||||
|
||||
public override void MakeInvisible()
|
||||
{
|
||||
_renderer.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,27 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_RendererTarget : DC_RendererTargetBase
|
||||
{
|
||||
private Renderer _renderer;
|
||||
|
||||
public DC_RendererTarget(Renderer renderer)
|
||||
: base(renderer)
|
||||
{
|
||||
_renderer = Renderer;
|
||||
}
|
||||
|
||||
public override void MakeVisible()
|
||||
{
|
||||
_renderer.enabled = true;
|
||||
}
|
||||
|
||||
public override void MakeInvisible()
|
||||
{
|
||||
_renderer.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,35 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public abstract class DC_RendererTargetBase : ICullingTarget
|
||||
{
|
||||
public GameObject GameObject
|
||||
{
|
||||
get
|
||||
{
|
||||
return Renderer.gameObject;
|
||||
}
|
||||
}
|
||||
public Bounds Bounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return Renderer.bounds;
|
||||
}
|
||||
}
|
||||
|
||||
protected Renderer Renderer { get; private set; }
|
||||
|
||||
public DC_RendererTargetBase(Renderer renderer)
|
||||
{
|
||||
Renderer = renderer;
|
||||
}
|
||||
|
||||
public abstract void MakeInvisible();
|
||||
|
||||
public abstract void MakeVisible();
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_CullingTargetObserver : MonoBehaviour
|
||||
{
|
||||
private DC_Source _source;
|
||||
private ICullingTarget _target;
|
||||
|
||||
public void Initialize(DC_Source source, ICullingTarget target)
|
||||
{
|
||||
_source = source;
|
||||
_target = target;
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (!gameObject.scene.isLoaded)
|
||||
return;
|
||||
|
||||
_source?.RemoveCullingTarget(_target);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public enum OccluderType { Collider, Mesh, LODGroup }
|
||||
|
||||
public class DC_Occluder : MonoBehaviour
|
||||
{
|
||||
[field : SerializeField]
|
||||
public OccluderType OccluderType { get; set; }
|
||||
|
||||
private Bounds? _bounds;
|
||||
private int _layer;
|
||||
|
||||
|
||||
public bool TryGetBounds(ref Bounds bounds)
|
||||
{
|
||||
if (_bounds != null)
|
||||
{
|
||||
bounds = _bounds.Value;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (OccluderType == OccluderType.Collider)
|
||||
{
|
||||
if (TryGetComponent(out Collider collider))
|
||||
{
|
||||
_bounds = bounds = collider.bounds;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else if (OccluderType == OccluderType.Mesh)
|
||||
{
|
||||
if (TryGetComponent(out MeshRenderer renderer))
|
||||
{
|
||||
_bounds = bounds = renderer.bounds;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (TryGetComponent(out LODGroup group))
|
||||
{
|
||||
LOD[] lods = group.GetLODs();
|
||||
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
foreach (var renderer in lods[i].renderers)
|
||||
{
|
||||
if (renderer != null)
|
||||
{
|
||||
_bounds = bounds = renderer.bounds;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
if (GetComponent<MeshRenderer>() != null)
|
||||
OccluderType = OccluderType.Mesh;
|
||||
|
||||
else if (GetComponent<LODGroup>() != null)
|
||||
OccluderType = OccluderType.LODGroup;
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_layer = DC_Controller.GetCullingLayer();
|
||||
|
||||
if (OccluderType == OccluderType.Collider)
|
||||
{
|
||||
gameObject.layer = _layer;
|
||||
}
|
||||
else if (OccluderType == OccluderType.Mesh)
|
||||
{
|
||||
MeshFilter filter = GetComponent<MeshFilter>();
|
||||
|
||||
if (filter == null || filter.sharedMesh == null)
|
||||
{
|
||||
Debug.Log(gameObject.name + " unable to create occluder, mesh not found");
|
||||
return;
|
||||
}
|
||||
|
||||
CreateCollider(gameObject, filter.sharedMesh);
|
||||
}
|
||||
else
|
||||
{
|
||||
LODGroup group = GetComponent<LODGroup>();
|
||||
|
||||
if (group == null)
|
||||
{
|
||||
Debug.Log(gameObject.name + " unable to create occluder, LODGroup not found");
|
||||
return;
|
||||
}
|
||||
|
||||
LOD lod = group.GetLODs()[0];
|
||||
|
||||
foreach (var renderer in lod.renderers)
|
||||
{
|
||||
MeshFilter filter = renderer.GetComponent<MeshFilter>();
|
||||
|
||||
if (filter != null && filter.sharedMesh != null)
|
||||
CreateCollider(renderer.gameObject, filter.sharedMesh);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void CreateCollider(GameObject go, Mesh mesh)
|
||||
{
|
||||
GameObject colliderGO = new GameObject("DC_Collider");
|
||||
colliderGO.layer = _layer;
|
||||
|
||||
Transform colliderTransform = colliderGO.transform;
|
||||
|
||||
colliderTransform.parent = go.transform;
|
||||
colliderTransform.localPosition = Vector3.zero;
|
||||
colliderTransform.localEulerAngles = Vector3.zero;
|
||||
colliderTransform.localScale = Vector3.one;
|
||||
|
||||
MeshCollider collider = colliderGO.AddComponent<MeshCollider>();
|
||||
collider.sharedMesh = mesh;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,85 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public abstract class DC_Source : MonoBehaviour, IHitable
|
||||
{
|
||||
public float Lifetime
|
||||
{
|
||||
get
|
||||
{
|
||||
return _lifetime;
|
||||
}
|
||||
set
|
||||
{
|
||||
_lifetime = Mathf.Max(0.01f, value);
|
||||
}
|
||||
}
|
||||
|
||||
private float _lifetime;
|
||||
private float _currentTime;
|
||||
|
||||
|
||||
private void Update()
|
||||
{
|
||||
try
|
||||
{
|
||||
_currentTime += Time.deltaTime;
|
||||
|
||||
if (_currentTime > _lifetime)
|
||||
{
|
||||
OnTimeout();
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex.Message + ex.StackTrace);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void OnHit()
|
||||
{
|
||||
try
|
||||
{
|
||||
enabled = true;
|
||||
|
||||
_currentTime = 0;
|
||||
|
||||
OnHitInternal();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Debug.LogError(ex.Message + ex.StackTrace);
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void Enable()
|
||||
{
|
||||
OnTimeout();
|
||||
|
||||
enabled = true;
|
||||
}
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
OnHitInternal();
|
||||
|
||||
enabled = false;
|
||||
}
|
||||
|
||||
|
||||
public abstract void SetCullingTarget(ICullingTarget target);
|
||||
|
||||
public abstract void RemoveCullingTarget(ICullingTarget target);
|
||||
|
||||
|
||||
protected abstract void OnHitInternal();
|
||||
|
||||
protected abstract void OnTimeout();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public interface IHitable
|
||||
{
|
||||
void OnHit();
|
||||
}
|
||||
}
|
@@ -0,0 +1,79 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_MultiSource : DC_Source
|
||||
{
|
||||
private List<ICullingTarget> _cullingTargets;
|
||||
private bool _visible;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_cullingTargets = new List<ICullingTarget>();
|
||||
}
|
||||
|
||||
|
||||
public override void SetCullingTarget(ICullingTarget target)
|
||||
{
|
||||
if (_cullingTargets == null)
|
||||
_cullingTargets = new List<ICullingTarget>();
|
||||
|
||||
_cullingTargets.Add(target);
|
||||
|
||||
if (_visible)
|
||||
target.MakeVisible();
|
||||
else
|
||||
target.MakeInvisible();
|
||||
}
|
||||
|
||||
public override void RemoveCullingTarget(ICullingTarget target)
|
||||
{
|
||||
_cullingTargets.Remove(target);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHitInternal()
|
||||
{
|
||||
if (_visible)
|
||||
return;
|
||||
|
||||
int i = 0;
|
||||
while (i < _cullingTargets.Count)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cullingTargets[i].MakeVisible();
|
||||
i++;
|
||||
}
|
||||
catch(MissingReferenceException)
|
||||
{
|
||||
RemoveCullingTarget(_cullingTargets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_visible = true;
|
||||
}
|
||||
|
||||
protected override void OnTimeout()
|
||||
{
|
||||
int i = 0;
|
||||
while (i < _cullingTargets.Count)
|
||||
{
|
||||
try
|
||||
{
|
||||
_cullingTargets[i].MakeInvisible();
|
||||
i++;
|
||||
}
|
||||
catch (MissingReferenceException)
|
||||
{
|
||||
RemoveCullingTarget(_cullingTargets[i]);
|
||||
}
|
||||
}
|
||||
|
||||
_visible = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_SingleSource : DC_Source
|
||||
{
|
||||
private ICullingTarget _cullingTarget;
|
||||
private bool _visible;
|
||||
|
||||
|
||||
public override void SetCullingTarget(ICullingTarget target)
|
||||
{
|
||||
_cullingTarget = target;
|
||||
_cullingTarget.MakeInvisible();
|
||||
}
|
||||
|
||||
public override void RemoveCullingTarget(ICullingTarget target)
|
||||
{
|
||||
enabled = false;
|
||||
Destroy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
protected override void OnHitInternal()
|
||||
{
|
||||
if (_visible)
|
||||
return;
|
||||
|
||||
_cullingTarget.MakeVisible();
|
||||
_visible = true;
|
||||
}
|
||||
|
||||
protected override void OnTimeout()
|
||||
{
|
||||
_cullingTarget.MakeInvisible();
|
||||
_visible = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,189 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public enum CullingMethod { FullDisable, KeepShadows }
|
||||
public enum SourceType { SingleMesh, LODGroup, Custom }
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public class DC_SourceSettings : MonoBehaviour
|
||||
{
|
||||
public bool ReadyForCulling
|
||||
{
|
||||
get
|
||||
{
|
||||
return _strategy != null && _strategy.ReadyForCulling;
|
||||
}
|
||||
}
|
||||
public int CullingLayer
|
||||
{
|
||||
get
|
||||
{
|
||||
return DC_Controller.GetCullingLayer();
|
||||
}
|
||||
}
|
||||
public SourceType SourceType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sourceType;
|
||||
}
|
||||
set
|
||||
{
|
||||
if (value == _sourceType)
|
||||
return;
|
||||
|
||||
_sourceType = value;
|
||||
|
||||
OnSourceTypeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[field: SerializeField]
|
||||
public int ControllerID { get; set; }
|
||||
|
||||
[field: SerializeField]
|
||||
public bool IsIncompatible { get; private set; }
|
||||
|
||||
[field: SerializeField]
|
||||
public string IncompatibilityReason { get; private set; }
|
||||
|
||||
[SerializeField]
|
||||
private SourceType _sourceType;
|
||||
|
||||
[SerializeReference]
|
||||
private IDC_SourceSettingsStrategy _strategy;
|
||||
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
DetectSourceType();
|
||||
CheckCompatibility();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_strategy == null)
|
||||
CreateStrategy();
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!CheckCompatibility())
|
||||
{
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
if (!_strategy.ReadyForCulling)
|
||||
_strategy.PrepareForCulling();
|
||||
|
||||
DC_Controller.GetById(ControllerID).AddObjectForCulling(
|
||||
_strategy.CreateCullingTarget(),
|
||||
_strategy.GetColliders());
|
||||
|
||||
Destroy(this);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
IsIncompatible = true;
|
||||
IncompatibilityReason = ex.Message + ex.StackTrace;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public T GetStrategy<T>() where T : IDC_SourceSettingsStrategy
|
||||
{
|
||||
return (T)_strategy;
|
||||
}
|
||||
|
||||
public bool TryGetBounds(ref Bounds bounds)
|
||||
{
|
||||
if (_strategy == null)
|
||||
return false;
|
||||
|
||||
return _strategy.TryGetBounds(ref bounds);
|
||||
}
|
||||
|
||||
public bool CheckCompatibility()
|
||||
{
|
||||
if (_strategy == null)
|
||||
CreateStrategy();
|
||||
|
||||
IsIncompatible = !_strategy.CheckCompatibilityAndGetComponents(out string reason);
|
||||
IncompatibilityReason = reason;
|
||||
|
||||
return !IsIncompatible;
|
||||
}
|
||||
|
||||
public void Bake()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Debug.Log("'Bake' can only be called in editor mode");
|
||||
return;
|
||||
}
|
||||
|
||||
if (_strategy != null && _strategy.ReadyForCulling)
|
||||
_strategy.ClearData();
|
||||
|
||||
if (CheckCompatibility())
|
||||
_strategy.PrepareForCulling();
|
||||
}
|
||||
|
||||
public void ClearBakedData()
|
||||
{
|
||||
if (Application.isPlaying)
|
||||
{
|
||||
Debug.Log("'ClearBakedData' can only be called in editor mode");
|
||||
return;
|
||||
}
|
||||
|
||||
_strategy?.ClearData();
|
||||
}
|
||||
|
||||
|
||||
private void DetectSourceType()
|
||||
{
|
||||
if (GetComponent<LODGroup>() != null)
|
||||
SourceType = SourceType.LODGroup;
|
||||
|
||||
else if (GetComponent<MeshRenderer>() != null)
|
||||
SourceType = SourceType.SingleMesh;
|
||||
|
||||
else
|
||||
SourceType = SourceType.Custom;
|
||||
}
|
||||
|
||||
private void OnSourceTypeChanged()
|
||||
{
|
||||
if (_strategy != null && _strategy.ReadyForCulling)
|
||||
_strategy.ClearData();
|
||||
|
||||
CreateStrategy();
|
||||
CheckCompatibility();
|
||||
}
|
||||
|
||||
private void CreateStrategy()
|
||||
{
|
||||
if (SourceType == SourceType.SingleMesh)
|
||||
{
|
||||
_strategy = new DC_RendererSourceSettingsStrategy(this);
|
||||
}
|
||||
else if (SourceType == SourceType.LODGroup)
|
||||
{
|
||||
_strategy = new DC_LODGroupSourceSettingsStrategy(this);
|
||||
}
|
||||
else if (SourceType == SourceType.Custom)
|
||||
{
|
||||
_strategy = new DC_CustomSourceSettingsStrategy(this);
|
||||
}
|
||||
else
|
||||
throw new NotSupportedException();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,131 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
[Serializable]
|
||||
public class DC_CustomSourceSettingsStrategy : IDC_SourceSettingsStrategy
|
||||
{
|
||||
[field: SerializeField]
|
||||
public bool ReadyForCulling { get; private set; }
|
||||
public Bounds LocalBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _localBounds;
|
||||
}
|
||||
set
|
||||
{
|
||||
_localBounds = value;
|
||||
}
|
||||
}
|
||||
|
||||
public DC_CustomTargetEvent OnVisibleEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _onVisible;
|
||||
}
|
||||
}
|
||||
public DC_CustomTargetEvent OnInvisibleEvent
|
||||
{
|
||||
get
|
||||
{
|
||||
return _onInvisible;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private DC_SourceSettings _context;
|
||||
|
||||
[SerializeField]
|
||||
private Bounds _localBounds;
|
||||
|
||||
[SerializeField]
|
||||
private DC_CustomTargetEvent _onVisible;
|
||||
|
||||
[SerializeField]
|
||||
private DC_CustomTargetEvent _onInvisible;
|
||||
|
||||
[SerializeField]
|
||||
private BoxCollider _collider;
|
||||
|
||||
|
||||
public DC_CustomSourceSettingsStrategy(DC_SourceSettings context)
|
||||
{
|
||||
_context = context;
|
||||
_localBounds = new Bounds(Vector3.zero, Vector3.one);
|
||||
|
||||
_onVisible = new DC_CustomTargetEvent();
|
||||
_onInvisible = new DC_CustomTargetEvent();
|
||||
}
|
||||
|
||||
public bool CheckCompatibilityAndGetComponents(out string incompatibilityReason)
|
||||
{
|
||||
incompatibilityReason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareForCulling()
|
||||
{
|
||||
if (ReadyForCulling)
|
||||
return;
|
||||
|
||||
GameObject go = new GameObject("DC_Collider");
|
||||
|
||||
go.transform.parent = _context.transform;
|
||||
go.layer = _context.CullingLayer;
|
||||
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
go.transform.localEulerAngles = Vector3.zero;
|
||||
go.transform.localScale = Vector3.one;
|
||||
|
||||
_collider = go.AddComponent<BoxCollider>();
|
||||
_collider.center = _localBounds.center;
|
||||
_collider.size = _localBounds.size;
|
||||
|
||||
ReadyForCulling = true;
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
if (!ReadyForCulling)
|
||||
return;
|
||||
|
||||
if (_collider != null)
|
||||
UnityEngine.Object.DestroyImmediate(_collider.gameObject);
|
||||
|
||||
_collider = null;
|
||||
|
||||
ReadyForCulling = false;
|
||||
}
|
||||
|
||||
|
||||
public bool TryGetBounds(ref Bounds bounds)
|
||||
{
|
||||
bounds.center = _context.transform.position + _localBounds.center;
|
||||
bounds.size = _localBounds.size;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public ICullingTarget CreateCullingTarget()
|
||||
{
|
||||
Bounds bounds = new Bounds();
|
||||
|
||||
TryGetBounds(ref bounds);
|
||||
|
||||
return new DC_CustomTarget(_context.gameObject, bounds, _onVisible, _onInvisible);
|
||||
}
|
||||
|
||||
public IEnumerable<Collider> GetColliders()
|
||||
{
|
||||
if (_collider == null)
|
||||
yield break;
|
||||
|
||||
yield return _collider;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_LODGroupSourceSettingsStrategy : IDC_SourceSettingsStrategy
|
||||
{
|
||||
[field: SerializeField]
|
||||
public bool ReadyForCulling { get; private set; }
|
||||
|
||||
public CullingMethod CullingMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cullingMethod;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cullingMethod = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private DC_SourceSettings _context;
|
||||
|
||||
[SerializeField]
|
||||
private CullingMethod _cullingMethod;
|
||||
|
||||
[SerializeField]
|
||||
private LODGroup _group;
|
||||
|
||||
[SerializeField]
|
||||
private Renderer[] _renderers;
|
||||
|
||||
[SerializeField]
|
||||
private MeshCollider[] _colliders;
|
||||
|
||||
[SerializeField]
|
||||
private Bounds _bounds;
|
||||
|
||||
|
||||
public DC_LODGroupSourceSettingsStrategy(DC_SourceSettings context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public void PrepareForCulling()
|
||||
{
|
||||
if (ReadyForCulling)
|
||||
return;
|
||||
|
||||
_colliders = _group.GetLODs()[0].renderers
|
||||
.Where(r => (r != null && IsCompatibleRenderer(r)))
|
||||
.Select(r =>
|
||||
{
|
||||
GameObject go = new GameObject("DC_Collider");
|
||||
|
||||
go.transform.parent = r.transform;
|
||||
go.layer = _context.CullingLayer;
|
||||
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
go.transform.localEulerAngles = Vector3.zero;
|
||||
go.transform.localScale = Vector3.one;
|
||||
|
||||
MeshCollider collider = go.AddComponent<MeshCollider>();
|
||||
collider.sharedMesh = r.GetComponent<MeshFilter>().sharedMesh;
|
||||
|
||||
return collider;
|
||||
}).ToArray();
|
||||
|
||||
ReadyForCulling = true;
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
if (!ReadyForCulling)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _colliders.Length; i++)
|
||||
{
|
||||
Collider collider = _colliders[i];
|
||||
|
||||
if (collider == null || collider.gameObject == null)
|
||||
continue;
|
||||
|
||||
UnityEngine.Object.DestroyImmediate(collider.gameObject);
|
||||
}
|
||||
_colliders = null;
|
||||
|
||||
ReadyForCulling = false;
|
||||
}
|
||||
|
||||
|
||||
public bool TryGetBounds(ref Bounds bounds)
|
||||
{
|
||||
if (_renderers != null)
|
||||
{
|
||||
bounds = _bounds;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ICullingTarget CreateCullingTarget()
|
||||
{
|
||||
if (CullingMethod == CullingMethod.KeepShadows)
|
||||
return new DC_LODGroupShadowsTarget(_group, _renderers, _bounds);
|
||||
|
||||
return new DC_LODGroupTarget(_group, _renderers, _bounds);
|
||||
}
|
||||
|
||||
public IEnumerable<Collider> GetColliders()
|
||||
{
|
||||
if (_colliders == null)
|
||||
yield break;
|
||||
|
||||
for (int i = 0; i < _colliders.Length; i++)
|
||||
yield return _colliders[i];
|
||||
}
|
||||
|
||||
|
||||
public bool CheckCompatibilityAndGetComponents(out string incompatibilityReason)
|
||||
{
|
||||
if (_group == null)
|
||||
{
|
||||
if (!_context.TryGetComponent(out _group))
|
||||
{
|
||||
incompatibilityReason = "LODGroup not found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_renderers == null)
|
||||
{
|
||||
LOD[] lods = _group.GetLODs();
|
||||
|
||||
int count = lods.Count(IsCompatibleRenderer);
|
||||
|
||||
if (count == 0)
|
||||
{
|
||||
incompatibilityReason = "Can't find any compatible renderer";
|
||||
return false;
|
||||
}
|
||||
|
||||
_renderers = new Renderer[count];
|
||||
_bounds = new Bounds(_group.transform.position, Vector3.zero);
|
||||
|
||||
int idx = 0;
|
||||
for (int i = 0; i < lods.Length; i++)
|
||||
{
|
||||
Renderer[] lodRenderers = lods[i].renderers;
|
||||
|
||||
for (int c = 0; c < lodRenderers.Length; c++)
|
||||
{
|
||||
Renderer renderer = lodRenderers[c];
|
||||
|
||||
if (renderer != null && IsCompatibleRenderer(renderer))
|
||||
{
|
||||
_renderers[idx++] = renderer;
|
||||
_bounds.Encapsulate(renderer.bounds);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
{
|
||||
if (_renderers[i] == null)
|
||||
{
|
||||
incompatibilityReason = "Missing renderer at index : " + i;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
incompatibilityReason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool IsCompatibleRenderer(Renderer renderer)
|
||||
{
|
||||
MeshFilter filter = renderer.GetComponent<MeshFilter>();
|
||||
|
||||
return filter != null && filter.sharedMesh != null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,175 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
[Serializable]
|
||||
public class DC_RendererSourceSettingsStrategy : IDC_SourceSettingsStrategy
|
||||
{
|
||||
[field: SerializeField]
|
||||
public bool ReadyForCulling { get; private set; }
|
||||
|
||||
public CullingMethod CullingMethod
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cullingMethod;
|
||||
}
|
||||
set
|
||||
{
|
||||
_cullingMethod = value;
|
||||
}
|
||||
}
|
||||
public bool ConvexCollider
|
||||
{
|
||||
get
|
||||
{
|
||||
return _convexCollider;
|
||||
}
|
||||
set
|
||||
{
|
||||
_convexCollider = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private DC_SourceSettings _context;
|
||||
|
||||
[SerializeField]
|
||||
private CullingMethod _cullingMethod;
|
||||
|
||||
[SerializeField]
|
||||
private bool _convexCollider;
|
||||
|
||||
[SerializeField]
|
||||
private MeshRenderer _renderer;
|
||||
|
||||
[SerializeField]
|
||||
private Mesh _mesh;
|
||||
|
||||
[SerializeField]
|
||||
private MeshCollider _collider;
|
||||
|
||||
[SerializeField]
|
||||
private bool _rigibodiesChecked;
|
||||
|
||||
|
||||
public DC_RendererSourceSettingsStrategy(DC_SourceSettings context)
|
||||
{
|
||||
_context = context;
|
||||
}
|
||||
|
||||
public bool CheckCompatibilityAndGetComponents(out string incompatibilityReason)
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
if (!_context.TryGetComponent(out _renderer))
|
||||
{
|
||||
incompatibilityReason = "MeshRenderer not found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (_mesh == null)
|
||||
{
|
||||
MeshFilter filter = _context.GetComponent<MeshFilter>();
|
||||
|
||||
if (filter == null)
|
||||
{
|
||||
incompatibilityReason = "MeshFilter not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
_mesh = filter.sharedMesh;
|
||||
|
||||
if (_mesh == null)
|
||||
{
|
||||
incompatibilityReason = "Mesh not found";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!_rigibodiesChecked)
|
||||
{
|
||||
foreach (var rb in _context.GetComponentsInParent<Rigidbody>())
|
||||
{
|
||||
if (!rb.isKinematic)
|
||||
{
|
||||
ConvexCollider = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
_rigibodiesChecked = true;
|
||||
}
|
||||
|
||||
incompatibilityReason = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public void PrepareForCulling()
|
||||
{
|
||||
if (ReadyForCulling)
|
||||
return;
|
||||
|
||||
GameObject go = new GameObject("DC_Collider");
|
||||
|
||||
go.transform.parent = _renderer.transform;
|
||||
go.layer = _context.CullingLayer;
|
||||
|
||||
go.transform.localPosition = Vector3.zero;
|
||||
go.transform.localEulerAngles = Vector3.zero;
|
||||
go.transform.localScale = Vector3.one;
|
||||
|
||||
_collider = go.AddComponent<MeshCollider>();
|
||||
_collider.sharedMesh = _mesh;
|
||||
|
||||
if (ConvexCollider)
|
||||
_collider.convex = true;
|
||||
|
||||
ReadyForCulling = true;
|
||||
}
|
||||
|
||||
public void ClearData()
|
||||
{
|
||||
if (!ReadyForCulling)
|
||||
return;
|
||||
|
||||
if (_collider != null)
|
||||
UnityEngine.Object.DestroyImmediate(_collider.gameObject);
|
||||
|
||||
_collider = null;
|
||||
|
||||
ReadyForCulling = false;
|
||||
}
|
||||
|
||||
|
||||
public bool TryGetBounds(ref Bounds bounds)
|
||||
{
|
||||
if (_renderer != null)
|
||||
{
|
||||
bounds = _renderer.bounds;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public ICullingTarget CreateCullingTarget()
|
||||
{
|
||||
if (CullingMethod == CullingMethod.KeepShadows)
|
||||
return new DC_RendererShadowsTarget(_renderer);
|
||||
|
||||
return new DC_RendererTarget(_renderer);
|
||||
}
|
||||
|
||||
public IEnumerable<Collider> GetColliders()
|
||||
{
|
||||
if (_collider == null)
|
||||
yield break;
|
||||
|
||||
yield return _collider;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public interface IDC_SourceSettingsStrategy
|
||||
{
|
||||
bool ReadyForCulling { get; }
|
||||
|
||||
|
||||
bool CheckCompatibilityAndGetComponents(out string incompatibilityReason);
|
||||
|
||||
void PrepareForCulling();
|
||||
|
||||
void ClearData();
|
||||
|
||||
|
||||
bool TryGetBounds(ref Bounds bounds);
|
||||
|
||||
ICullingTarget CreateCullingTarget();
|
||||
|
||||
IEnumerable<Collider> GetColliders();
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public interface IDC_SourcesProvider
|
||||
{
|
||||
DC_Source GetSource(ICullingTarget cullingTarget);
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_SingleSourcesProvider : IDC_SourcesProvider
|
||||
{
|
||||
public DC_Source GetSource(ICullingTarget cullingTarget)
|
||||
{
|
||||
GameObject go = new GameObject("DC_SingleSource");
|
||||
|
||||
DC_SingleSource source = go.AddComponent<DC_SingleSource>();
|
||||
source.SetCullingTarget(cullingTarget);
|
||||
|
||||
return source;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,68 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_SourcesTree : BinaryTree<DC_SourcesTreeNode, ICullingTarget>, IDC_SourcesProvider
|
||||
{
|
||||
private DC_Source _lastModifiedSource;
|
||||
|
||||
|
||||
public DC_SourcesTree(float nodeSize)
|
||||
: base(nodeSize)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public DC_Source GetSource(ICullingTarget cullingTarget)
|
||||
{
|
||||
_lastModifiedSource = null;
|
||||
|
||||
Add(cullingTarget);
|
||||
|
||||
return _lastModifiedSource;
|
||||
}
|
||||
|
||||
|
||||
protected override void AddInternal(DC_SourcesTreeNode node, ICullingTarget data, int depth)
|
||||
{
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
AddDataToNode(node, data);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!node.HasChilds)
|
||||
GrowTreeDown(node, data, depth + 1);
|
||||
|
||||
if (Intersects(node.Left, data))
|
||||
AddInternal(node.Left, data, depth + 1);
|
||||
else
|
||||
AddInternal(node.Right, data, depth + 1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
protected override Bounds GetBounds(ICullingTarget data)
|
||||
{
|
||||
return data.Bounds;
|
||||
}
|
||||
|
||||
protected override DC_SourcesTreeNode CreateNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
{
|
||||
return new DC_SourcesTreeNode(center, size, isLeaf);
|
||||
}
|
||||
|
||||
protected override void SetChildsToNode(DC_SourcesTreeNode parent, DC_SourcesTreeNode leftChild, DC_SourcesTreeNode rightChild)
|
||||
{
|
||||
parent.SetChilds(leftChild, rightChild);
|
||||
}
|
||||
|
||||
protected override void AddDataToNode(DC_SourcesTreeNode node, ICullingTarget data)
|
||||
{
|
||||
node.AddCullingTarget(data);
|
||||
_lastModifiedSource = node.Source;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Dynamic
|
||||
{
|
||||
public class DC_SourcesTreeNode : BinaryTreeNode
|
||||
{
|
||||
public DC_SourcesTreeNode Left { get; private set; }
|
||||
public DC_SourcesTreeNode Right { get; private set; }
|
||||
public DC_MultiSource Source { get; private set; }
|
||||
|
||||
public DC_SourcesTreeNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
: base(center, size, isLeaf)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetLeft()
|
||||
{
|
||||
return Left;
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetRight()
|
||||
{
|
||||
return Right;
|
||||
}
|
||||
|
||||
|
||||
public void SetChilds(DC_SourcesTreeNode left, DC_SourcesTreeNode right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
|
||||
public void AddCullingTarget(ICullingTarget cullingTarget)
|
||||
{
|
||||
if (Source == null)
|
||||
Source = new GameObject("DC_MultiSource").AddComponent<DC_MultiSource>();
|
||||
|
||||
Source.SetCullingTarget(cullingTarget);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,108 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class CameraZone : MonoBehaviour
|
||||
{
|
||||
public static List<CameraZone> Instances { get; private set; }
|
||||
|
||||
[field: SerializeReference]
|
||||
public VisibilityTree VisibilityTree { get; private set; }
|
||||
|
||||
[field: SerializeField]
|
||||
public int CellsCount { get; private set; }
|
||||
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void ReloadDomain()
|
||||
{
|
||||
if (Instances != null)
|
||||
Instances.Clear();
|
||||
}
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (Instances == null)
|
||||
Instances = new List<CameraZone>();
|
||||
|
||||
Instances.Add(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
Instances.Remove(this);
|
||||
}
|
||||
|
||||
|
||||
public bool CreateVisibilityTree(float cellSize)
|
||||
{
|
||||
if (cellSize < 0.01f)
|
||||
{
|
||||
Debug.Log("Unable to create VisibilityTree with so small cell size : " + cellSize);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VisibilityTree != null)
|
||||
ClearVisibilityTree();
|
||||
|
||||
Vector3 position = transform.position;
|
||||
Vector3 size = transform.lossyScale;
|
||||
|
||||
size.x = Mathf.Abs(size.x);
|
||||
size.y = Mathf.Abs(size.y);
|
||||
size.z = Mathf.Abs(size.z);
|
||||
|
||||
int countX = Mathf.CeilToInt(size.x / cellSize);
|
||||
int countY = Mathf.CeilToInt(size.y / cellSize);
|
||||
int countZ = Mathf.CeilToInt(size.z / cellSize);
|
||||
|
||||
if (countX == 0 || countY == 0 || countZ == 0)
|
||||
{
|
||||
Debug.Log("Unable to create VisibilityTree with side size equals zero");
|
||||
return false;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
VisibilityTree = new VisibilityTree(cellSize);
|
||||
|
||||
Vector3 start = (position - size / 2);
|
||||
|
||||
for (int x = 0; x < countX; x++)
|
||||
{
|
||||
for (int y = 0; y < countY; y++)
|
||||
{
|
||||
for (int z = 0; z < countZ; z++)
|
||||
{
|
||||
Vector3 offset = new Vector3()
|
||||
{
|
||||
x = cellSize * x,
|
||||
y = cellSize * y,
|
||||
z = cellSize * z
|
||||
};
|
||||
|
||||
VisibilityTree.Add(start + offset);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
CellsCount = countX * countY * countZ;
|
||||
}
|
||||
catch(Exception ex)
|
||||
{
|
||||
Debug.Log("Unable to create VisibilityTree, reason : " + ex.Message + ex.StackTrace);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void ClearVisibilityTree()
|
||||
{
|
||||
VisibilityTree = null;
|
||||
CellsCount = 0;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,54 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public abstract class CullingTarget : MonoBehaviour
|
||||
{
|
||||
[field: SerializeField]
|
||||
public Bounds Bounds { get; set; }
|
||||
|
||||
private bool _isVisible;
|
||||
private bool _makedVisible;
|
||||
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
if (_isVisible)
|
||||
{
|
||||
if (!_makedVisible)
|
||||
{
|
||||
MakeVisible();
|
||||
_makedVisible = true;
|
||||
}
|
||||
|
||||
_isVisible = false;
|
||||
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
MakeInvisible();
|
||||
|
||||
_makedVisible = false;
|
||||
enabled = false;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetVisible()
|
||||
{
|
||||
if (!_isVisible)
|
||||
{
|
||||
enabled = true;
|
||||
_isVisible = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected abstract void MakeVisible();
|
||||
|
||||
protected abstract void MakeInvisible();
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class CustomCullingTarget : CullingTarget
|
||||
{
|
||||
public CustomTargetEvent OnVisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _onVisible;
|
||||
}
|
||||
}
|
||||
public CustomTargetEvent OnInvisible
|
||||
{
|
||||
get
|
||||
{
|
||||
return _onInvisible;
|
||||
}
|
||||
}
|
||||
|
||||
[field : SerializeField, HideInInspector]
|
||||
public bool IsOccluder { get; set; }
|
||||
|
||||
[SerializeField]
|
||||
private CustomTargetEvent _onVisible;
|
||||
|
||||
[SerializeField]
|
||||
private CustomTargetEvent _onInvisible;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (_onVisible == null)
|
||||
_onInvisible = new CustomTargetEvent();
|
||||
|
||||
if (_onInvisible == null)
|
||||
_onInvisible = new CustomTargetEvent();
|
||||
}
|
||||
|
||||
public void SetActions(CustomTargetEvent onVisible, CustomTargetEvent onInvisible)
|
||||
{
|
||||
_onVisible = onVisible;
|
||||
_onInvisible = onInvisible;
|
||||
}
|
||||
|
||||
protected override void MakeVisible()
|
||||
{
|
||||
_onVisible.Invoke(this);
|
||||
}
|
||||
|
||||
protected override void MakeInvisible()
|
||||
{
|
||||
_onInvisible.Invoke(this);
|
||||
}
|
||||
}
|
||||
|
||||
[System.Serializable]
|
||||
public class CustomTargetEvent : UnityEvent<CullingTarget>
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,78 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class LODGroupCullingTarget : CullingTarget
|
||||
{
|
||||
[field: SerializeField]
|
||||
public CullingMethod CullingMethod { get; set; }
|
||||
|
||||
[field: SerializeField, HideInInspector]
|
||||
public bool IsOccluder { get; set; }
|
||||
|
||||
[SerializeField, HideInInspector]
|
||||
private Renderer[] _renderers;
|
||||
|
||||
private Action _makeVisibleAction;
|
||||
private Action _makeInvisibleAction;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (CullingMethod == CullingMethod.FullDisable)
|
||||
{
|
||||
_makeVisibleAction = MakeRenderersVisible;
|
||||
_makeInvisibleAction = MakeRenderersInvisible;
|
||||
}
|
||||
else
|
||||
{
|
||||
_makeVisibleAction = MakeRenderersVisibleKeepShadows;
|
||||
_makeInvisibleAction = MakeRenderersInvisibleKeepShadows;
|
||||
}
|
||||
}
|
||||
|
||||
public void SetRenderers(IEnumerable<Renderer> renderers)
|
||||
{
|
||||
_renderers = renderers.ToArray();
|
||||
}
|
||||
|
||||
protected override void MakeVisible()
|
||||
{
|
||||
_makeVisibleAction();
|
||||
}
|
||||
|
||||
protected override void MakeInvisible()
|
||||
{
|
||||
_makeInvisibleAction();
|
||||
}
|
||||
|
||||
|
||||
private void MakeRenderersVisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].enabled = true;
|
||||
}
|
||||
|
||||
private void MakeRenderersInvisible()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].enabled = false;
|
||||
}
|
||||
|
||||
private void MakeRenderersVisibleKeepShadows()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].shadowCastingMode = ShadowCastingMode.On;
|
||||
}
|
||||
|
||||
private void MakeRenderersInvisibleKeepShadows()
|
||||
{
|
||||
for (int i = 0; i < _renderers.Length; i++)
|
||||
_renderers[i].shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class LightCullingTarget : CullingTarget
|
||||
{
|
||||
private Light _light;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_light = GetComponent<Light>();
|
||||
}
|
||||
|
||||
protected override void MakeVisible()
|
||||
{
|
||||
_light.enabled = true;
|
||||
}
|
||||
|
||||
protected override void MakeInvisible()
|
||||
{
|
||||
_light.enabled = false;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,72 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public enum CullingMethod { KeepShadows, FullDisable }
|
||||
|
||||
public class MeshRendererCullingTarget : CullingTarget
|
||||
{
|
||||
[field: SerializeField]
|
||||
public CullingMethod CullingMethod { get; set; }
|
||||
|
||||
[field: SerializeField, HideInInspector]
|
||||
public bool IsOccluder { get; set; }
|
||||
|
||||
private MeshRenderer _renderer;
|
||||
|
||||
private Action _makeVisAction;
|
||||
private Action _makeInvisAction;
|
||||
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_renderer = GetComponent<MeshRenderer>();
|
||||
|
||||
if (CullingMethod == CullingMethod.FullDisable)
|
||||
{
|
||||
_makeVisAction = EnableRenderer;
|
||||
_makeInvisAction = DisableRenderer;
|
||||
}
|
||||
else
|
||||
{
|
||||
_makeVisAction = EnableRendererKeepShadows;
|
||||
_makeInvisAction = DisableRendererKeepShadows;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override void MakeInvisible()
|
||||
{
|
||||
_makeInvisAction();
|
||||
}
|
||||
|
||||
protected override void MakeVisible()
|
||||
{
|
||||
_makeVisAction();
|
||||
}
|
||||
|
||||
|
||||
private void EnableRenderer()
|
||||
{
|
||||
_renderer.enabled = true;
|
||||
}
|
||||
|
||||
private void DisableRenderer()
|
||||
{
|
||||
_renderer.enabled = false;
|
||||
}
|
||||
|
||||
private void EnableRendererKeepShadows()
|
||||
{
|
||||
_renderer.shadowCastingMode = ShadowCastingMode.On;
|
||||
}
|
||||
|
||||
private void DisableRendererKeepShadows()
|
||||
{
|
||||
_renderer.shadowCastingMode = ShadowCastingMode.ShadowsOnly;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class GeometryTree : BinaryTree<GeometryTreeNode, CullingTarget>
|
||||
{
|
||||
public IReadOnlyList<CullingTarget> CullingTargets { get; private set; }
|
||||
public int NodesCount { get; private set; }
|
||||
|
||||
public GeometryTree(CullingTarget[] targets, int maxDepth) :
|
||||
base(targets, maxDepth)
|
||||
{
|
||||
CullingTargets = targets;
|
||||
}
|
||||
|
||||
protected override GeometryTreeNode CreateNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
{
|
||||
NodesCount++;
|
||||
return new GeometryTreeNode(center, size, isLeaf);
|
||||
}
|
||||
|
||||
protected override Bounds GetBounds(CullingTarget target)
|
||||
{
|
||||
return target.Bounds;
|
||||
}
|
||||
|
||||
protected override void AddInternal(GeometryTreeNode node, CullingTarget data, int depth)
|
||||
{
|
||||
node.IsEmpty = false;
|
||||
|
||||
base.AddInternal(node, data, depth);
|
||||
}
|
||||
|
||||
protected override void AddDataToNode(GeometryTreeNode node, CullingTarget target)
|
||||
{
|
||||
node.AddCullingTarget(target);
|
||||
}
|
||||
|
||||
protected override void SetChildsToNode(GeometryTreeNode parent, GeometryTreeNode left, GeometryTreeNode right)
|
||||
{
|
||||
parent.SetChilds(left, right);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class GeometryTreeNode : BinaryTreeNode
|
||||
{
|
||||
public int Index { get; set; }
|
||||
public bool IsEmpty { get; set; }
|
||||
|
||||
public GeometryTreeNode Left { get; private set; }
|
||||
public GeometryTreeNode Right { get; private set; }
|
||||
public IReadOnlyList<CullingTarget> CullingTargets
|
||||
{
|
||||
get
|
||||
{
|
||||
return _targets;
|
||||
}
|
||||
}
|
||||
|
||||
private HashSet<CullingTarget> _targetsSet;
|
||||
private List<CullingTarget> _targets;
|
||||
|
||||
|
||||
public GeometryTreeNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
: base(center, size, isLeaf)
|
||||
{
|
||||
IsEmpty = true;
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetLeft()
|
||||
{
|
||||
return Left;
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetRight()
|
||||
{
|
||||
return Right;
|
||||
}
|
||||
|
||||
public void SetChilds(GeometryTreeNode left, GeometryTreeNode right)
|
||||
{
|
||||
Left = left;
|
||||
Right = right;
|
||||
}
|
||||
|
||||
public void AddCullingTarget(CullingTarget target)
|
||||
{
|
||||
if (_targetsSet == null)
|
||||
{
|
||||
_targetsSet = new HashSet<CullingTarget>();
|
||||
_targets = new List<CullingTarget>();
|
||||
}
|
||||
|
||||
if (_targetsSet.Add(target))
|
||||
_targets.Add(target);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,284 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
using Unity.Jobs;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Burst;
|
||||
|
||||
#if COLLECTIONS_1_3_1_OR_NEWER
|
||||
|
||||
using NativeHashMap_Int_UnsafeListInt = Unity.Collections.NativeParallelHashMap<int, Unity.Collections.LowLevel.Unsafe.UnsafeList<int>>;
|
||||
|
||||
#else
|
||||
|
||||
using NativeHashMap_Int_UnsafeListInt = Unity.Collections.NativeHashMap<int, Unity.Collections.LowLevel.Unsafe.UnsafeList<int>>;
|
||||
|
||||
#endif
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public partial class StaticCullingBaker : IDisposable
|
||||
{
|
||||
private const int MAX_PROCESSES = 50;
|
||||
private const int COMMANDS_LIMIT = 100000;
|
||||
|
||||
private GeometryTree _geometryTree;
|
||||
private IReadOnlyList<CullingTarget> _cullingTargets;
|
||||
|
||||
private NativeArray<GeometryNodeStruct> _geometryTreeStruct;
|
||||
private NativeArray<CullingTargetStruct> _cullingTargetsStruct;
|
||||
private NativeHashMap_Int_UnsafeListInt _cellTargetsMap;
|
||||
|
||||
private int _startDepth;
|
||||
private float _raysPerUnit;
|
||||
private int _maxRays;
|
||||
private float _maxDistance;
|
||||
private int _layerMask;
|
||||
|
||||
private List<VisibilityComputingProcess> _processes;
|
||||
|
||||
public StaticCullingBaker(GeometryTree geometryTree)
|
||||
{
|
||||
int index = 0;
|
||||
|
||||
_geometryTree = geometryTree;
|
||||
_geometryTreeStruct = new NativeArray<GeometryNodeStruct>(_geometryTree.NodesCount, Allocator.Persistent);
|
||||
|
||||
FillGeometryTreeStruct((GeometryTreeNode)_geometryTree.Root, -1, false, ref index);
|
||||
|
||||
_cullingTargets = geometryTree.CullingTargets;
|
||||
_cullingTargetsStruct = new NativeArray<CullingTargetStruct>(_cullingTargets.Count, Allocator.Persistent);
|
||||
_cellTargetsMap = new NativeHashMap_Int_UnsafeListInt(_geometryTreeStruct.Length, Allocator.Persistent);
|
||||
|
||||
index = 0;
|
||||
FillTargetsIndexesDic(out Dictionary<CullingTarget, int> targetsIndexes);
|
||||
FillCellTargetsMap((GeometryTreeNode)_geometryTree.Root, targetsIndexes, ref index);
|
||||
|
||||
_startDepth = 7;
|
||||
_maxDistance = _geometryTree.Root.Bounds.size.magnitude;
|
||||
_layerMask = LayerMask.GetMask(StaticCullingPreferences.LayerName);
|
||||
|
||||
_processes = new List<VisibilityComputingProcess>();
|
||||
}
|
||||
|
||||
public bool Bake(VisibilityTree visibilityTree, float raysPerUnit, int maxRaysPerSource, out string error)
|
||||
{
|
||||
error = "";
|
||||
bool success = false;
|
||||
|
||||
_raysPerUnit = raysPerUnit;
|
||||
_maxRays = maxRaysPerSource;
|
||||
|
||||
try
|
||||
{
|
||||
_processes.Clear();
|
||||
|
||||
List<VisibilityTreeNode> cells = new List<VisibilityTreeNode>();
|
||||
|
||||
FillVisibilityCells((VisibilityTreeNode)visibilityTree.Root, cells);
|
||||
|
||||
int current = 0;
|
||||
int finishedCount = 0;
|
||||
int cellsCount = cells.Count;
|
||||
|
||||
string title = "Processing...";
|
||||
|
||||
while (finishedCount < cellsCount)
|
||||
{
|
||||
#if UNITY_EDITOR
|
||||
string info = string.Format("Finished : {0} / {1}", finishedCount, cellsCount);
|
||||
float progress = (float)finishedCount / cellsCount;
|
||||
|
||||
if (UnityEditor.EditorUtility.DisplayCancelableProgressBar(title, info, progress))
|
||||
{
|
||||
error = "Cancelled";
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
while (_processes.Count < MAX_PROCESSES && current < cellsCount)
|
||||
{
|
||||
_processes.Add(new VisibilityComputingProcess(cells[current], this));
|
||||
|
||||
current++;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < _processes.Count)
|
||||
{
|
||||
VisibilityComputingProcess process = _processes[i];
|
||||
|
||||
process.Update(out bool finished);
|
||||
|
||||
if (finished)
|
||||
{
|
||||
process.ApplyData();
|
||||
process.Dispose();
|
||||
|
||||
_processes.RemoveAt(i);
|
||||
|
||||
finishedCount++;
|
||||
}
|
||||
else
|
||||
i++;
|
||||
}
|
||||
}
|
||||
|
||||
success = finishedCount >= cellsCount;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message + "\n" + ex.StackTrace;
|
||||
}
|
||||
|
||||
if (success)
|
||||
{
|
||||
visibilityTree.SetTargets(_geometryTree.CullingTargets.ToArray());
|
||||
visibilityTree.Optimize();
|
||||
visibilityTree.Apply();
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var process in _processes)
|
||||
process.Dispose();
|
||||
|
||||
_processes.Clear();
|
||||
}
|
||||
|
||||
#if UNITY_EDITOR
|
||||
UnityEditor.EditorUtility.ClearProgressBar();
|
||||
#endif
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
foreach (var process in _processes)
|
||||
process.Dispose();
|
||||
|
||||
_processes.Clear();
|
||||
|
||||
if (_geometryTreeStruct.IsCreated)
|
||||
_geometryTreeStruct.Dispose();
|
||||
|
||||
if (_cullingTargetsStruct.IsCreated)
|
||||
_cullingTargetsStruct.Dispose();
|
||||
|
||||
if (_cellTargetsMap.IsCreated)
|
||||
{
|
||||
#if COLLECTIONS_1_3_1_OR_NEWER
|
||||
|
||||
foreach (var pair in _cellTargetsMap)
|
||||
pair.Value.Dispose();
|
||||
|
||||
#else
|
||||
using (var keys = _cellTargetsMap.GetKeyArray(Allocator.Temp))
|
||||
{
|
||||
foreach (var key in keys)
|
||||
{
|
||||
_cellTargetsMap[key].Dispose();
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
_cellTargetsMap.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void FillGeometryTreeStruct(GeometryTreeNode current, int parentIndex, bool isLeft, ref int index)
|
||||
{
|
||||
GeometryNodeStruct nodeStruct = new GeometryNodeStruct()
|
||||
{
|
||||
index = index,
|
||||
bounds = current.Bounds,
|
||||
left = -1,
|
||||
right = -1,
|
||||
isEmpty = current.IsEmpty,
|
||||
isLeaf = current.IsLeaf,
|
||||
};
|
||||
|
||||
if (parentIndex >= 0)
|
||||
{
|
||||
GeometryNodeStruct parent = _geometryTreeStruct[parentIndex];
|
||||
|
||||
if (isLeft)
|
||||
parent.left = index;
|
||||
else
|
||||
parent.right = index;
|
||||
|
||||
_geometryTreeStruct[parentIndex] = parent;
|
||||
}
|
||||
|
||||
_geometryTreeStruct[index++] = nodeStruct;
|
||||
|
||||
if (current.HasChilds)
|
||||
{
|
||||
FillGeometryTreeStruct(current.Left, nodeStruct.index, true, ref index);
|
||||
FillGeometryTreeStruct(current.Right, nodeStruct.index, false, ref index);
|
||||
}
|
||||
}
|
||||
|
||||
private void FillTargetsIndexesDic(out Dictionary<CullingTarget, int> targetsIndexes)
|
||||
{
|
||||
targetsIndexes = new Dictionary<CullingTarget, int>();
|
||||
|
||||
for (int i = 0; i < _cullingTargets.Count; i++)
|
||||
{
|
||||
_cullingTargetsStruct[i] = new CullingTargetStruct
|
||||
{
|
||||
index = i,
|
||||
bounds = _cullingTargets[i].Bounds
|
||||
};
|
||||
|
||||
targetsIndexes.Add(_cullingTargets[i], i);
|
||||
}
|
||||
}
|
||||
|
||||
private void FillCellTargetsMap(GeometryTreeNode current, Dictionary<CullingTarget, int> targetToIndexDic, ref int index)
|
||||
{
|
||||
int nodeIndex = index;
|
||||
|
||||
index++;
|
||||
|
||||
if (current.IsLeaf)
|
||||
{
|
||||
UnsafeList<int> targets;
|
||||
|
||||
if (current.CullingTargets == null || current.CullingTargets.Count == 0)
|
||||
{
|
||||
targets = new UnsafeList<int>(0, Allocator.Persistent);
|
||||
}
|
||||
else
|
||||
{
|
||||
targets = new UnsafeList<int>(current.CullingTargets.Count, Allocator.Persistent);
|
||||
|
||||
foreach (var target in current.CullingTargets)
|
||||
targets.Add(targetToIndexDic[target]);
|
||||
}
|
||||
|
||||
_cellTargetsMap.Add(nodeIndex, targets);
|
||||
}
|
||||
else if (current.HasChilds)
|
||||
{
|
||||
FillCellTargetsMap(current.Left, targetToIndexDic, ref index);
|
||||
FillCellTargetsMap(current.Right, targetToIndexDic, ref index);
|
||||
}
|
||||
}
|
||||
|
||||
private void FillVisibilityCells(VisibilityTreeNode current, List<VisibilityTreeNode> result)
|
||||
{
|
||||
if (current.IsLeaf)
|
||||
{
|
||||
result.Add(current);
|
||||
}
|
||||
else if (current.HasChilds)
|
||||
{
|
||||
FillVisibilityCells(current.Left, result);
|
||||
FillVisibilityCells(current.Right, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,884 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using Unity.Jobs;
|
||||
using Unity.Collections;
|
||||
using Unity.Collections.LowLevel.Unsafe;
|
||||
using Unity.Burst;
|
||||
|
||||
#if COLLECTIONS_1_3_1_OR_NEWER
|
||||
|
||||
using NativeHashMap_Int_UnsafeListInt = Unity.Collections.NativeParallelHashMap<int, Unity.Collections.LowLevel.Unsafe.UnsafeList<int>>;
|
||||
|
||||
#else
|
||||
|
||||
using NativeHashMap_Int_UnsafeListInt = Unity.Collections.NativeHashMap<int, Unity.Collections.LowLevel.Unsafe.UnsafeList<int>>;
|
||||
|
||||
#endif
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public partial class StaticCullingBaker
|
||||
{
|
||||
private class VisibilityComputingProcess : IDisposable
|
||||
{
|
||||
private VisibilityTreeNode _cell;
|
||||
private Vector3 _cellPosition;
|
||||
private float _cellSize;
|
||||
|
||||
private NativeArray<GeometryNodeStruct> _geometryTreeStruct;
|
||||
private NativeArray<CullingTargetStruct> _cullingTargetsStruct;
|
||||
private NativeHashMap_Int_UnsafeListInt _nodeTargetsMap;
|
||||
|
||||
private NativeArray<bool> _geometryTreeVisibility;
|
||||
private NativeArray<bool> _cullingTargetsVisibility;
|
||||
private NativeArray<bool> _computedCullingTargets;
|
||||
|
||||
private NativeList<RaycastBatchInfo> _raycastBatches;
|
||||
private NativeList<RaycastCommand> _commands;
|
||||
private NativeList<RaycastHit> _hits;
|
||||
private NativeArray<int> _lastNodeIndex;
|
||||
private NativeArray<int> _lastTargetIndex;
|
||||
|
||||
private float _raysPerUnit;
|
||||
private int _maxRays;
|
||||
private float _maxDistance;
|
||||
private int _layerMask;
|
||||
|
||||
private int _startDepth;
|
||||
private int _treeHeight;
|
||||
private int _depth;
|
||||
|
||||
private JobHandle _handle;
|
||||
private int _jobIndex;
|
||||
|
||||
public VisibilityComputingProcess(VisibilityTreeNode cell, StaticCullingBaker context)
|
||||
{
|
||||
_cell = cell;
|
||||
_cellPosition = _cell.Center;
|
||||
_cellSize = Mathf.Min(cell.Size.x, cell.Size.y, cell.Size.z);
|
||||
|
||||
_geometryTreeStruct = context._geometryTreeStruct;
|
||||
_cullingTargetsStruct = context._cullingTargetsStruct;
|
||||
_nodeTargetsMap = context._cellTargetsMap;
|
||||
|
||||
_geometryTreeVisibility = new NativeArray<bool>(_geometryTreeStruct.Length, Allocator.TempJob);
|
||||
_cullingTargetsVisibility = new NativeArray<bool>(_cullingTargetsStruct.Length, Allocator.TempJob);
|
||||
_computedCullingTargets = new NativeArray<bool>(_cullingTargetsStruct.Length, Allocator.TempJob);
|
||||
|
||||
_raycastBatches = new NativeList<RaycastBatchInfo>(COMMANDS_LIMIT, Allocator.TempJob);
|
||||
_commands = new NativeList<RaycastCommand>(COMMANDS_LIMIT + context._maxRays, Allocator.TempJob);
|
||||
_hits = new NativeList<RaycastHit>(COMMANDS_LIMIT + context._maxRays, Allocator.TempJob);
|
||||
_lastNodeIndex = new NativeArray<int>(1, Allocator.TempJob);
|
||||
_lastTargetIndex = new NativeArray<int>(1, Allocator.TempJob);
|
||||
|
||||
_startDepth = context._startDepth;
|
||||
_raysPerUnit = context._raysPerUnit;
|
||||
_maxRays = context._maxRays;
|
||||
_maxDistance = context._maxDistance;
|
||||
_layerMask = context._layerMask;
|
||||
_treeHeight = context._geometryTree.Height;
|
||||
|
||||
_depth = _startDepth;
|
||||
}
|
||||
|
||||
public void Update(out bool finished)
|
||||
{
|
||||
finished = false;
|
||||
|
||||
if (!_handle.IsCompleted)
|
||||
return;
|
||||
|
||||
_handle.Complete();
|
||||
|
||||
if (_jobIndex == 0)
|
||||
{
|
||||
RunJob0();
|
||||
|
||||
_jobIndex = 1;
|
||||
}
|
||||
else if (_jobIndex == 1)
|
||||
{
|
||||
RunJob1();
|
||||
|
||||
if (_lastNodeIndex[0] == 0)
|
||||
{
|
||||
if (_depth == _treeHeight)
|
||||
{
|
||||
_jobIndex = 2;
|
||||
}
|
||||
else
|
||||
{
|
||||
_depth++;
|
||||
_jobIndex = 0;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
_jobIndex = 0;
|
||||
}
|
||||
}
|
||||
else if (_jobIndex == 2)
|
||||
{
|
||||
RunJob2();
|
||||
|
||||
_jobIndex = 3;
|
||||
}
|
||||
else if (_jobIndex == 3)
|
||||
{
|
||||
RunJob3();
|
||||
|
||||
if (_lastNodeIndex[0] == 0 && _lastTargetIndex[0] == 0)
|
||||
{
|
||||
_jobIndex = 4;
|
||||
}
|
||||
else
|
||||
{
|
||||
_jobIndex = 2;
|
||||
}
|
||||
}
|
||||
else if (_jobIndex == 4)
|
||||
{
|
||||
finished = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyData()
|
||||
{
|
||||
for (int i = 0; i < _cullingTargetsVisibility.Length; i++)
|
||||
{
|
||||
if (_cullingTargetsVisibility[i])
|
||||
_cell.AddVisibleCullingTarget(i);
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_handle.Complete();
|
||||
|
||||
_geometryTreeVisibility.Dispose();
|
||||
_cullingTargetsVisibility.Dispose();
|
||||
_computedCullingTargets.Dispose();
|
||||
|
||||
_raycastBatches.Dispose();
|
||||
_commands.Dispose();
|
||||
_hits.Dispose();
|
||||
_lastNodeIndex.Dispose();
|
||||
_lastTargetIndex.Dispose();
|
||||
}
|
||||
|
||||
|
||||
private void RunJob0()
|
||||
{
|
||||
_handle = new TreeCreateRaysJob()
|
||||
{
|
||||
|
||||
cellPosition = _cellPosition,
|
||||
cellSize = _cellSize,
|
||||
|
||||
geometryTreeStruct = _geometryTreeStruct,
|
||||
geometryTreeVisibility = _geometryTreeVisibility,
|
||||
|
||||
rayBatches = _raycastBatches,
|
||||
commands = _commands,
|
||||
hits = _hits,
|
||||
|
||||
lastNodeIndex = _lastNodeIndex,
|
||||
|
||||
raysPerUnit = _raysPerUnit,
|
||||
maxRays = _maxRays,
|
||||
maxDistance = _maxDistance,
|
||||
layerMask = _layerMask,
|
||||
|
||||
startDepth = _startDepth,
|
||||
targetDepth = _depth,
|
||||
commandsLimit = COMMANDS_LIMIT
|
||||
|
||||
}.Schedule();
|
||||
}
|
||||
|
||||
private void RunJob1()
|
||||
{
|
||||
_handle = new TreeComputeResultsJob()
|
||||
{
|
||||
|
||||
cellPosition = _cellPosition,
|
||||
|
||||
geometryTree = _geometryTreeStruct,
|
||||
cullingTargetsStruct = _cullingTargetsStruct,
|
||||
nodeTargetsMap = _nodeTargetsMap,
|
||||
|
||||
rayBatches = _raycastBatches,
|
||||
commands = _commands,
|
||||
hits = _hits,
|
||||
|
||||
geometryTreeVisibility = _geometryTreeVisibility,
|
||||
cullingTargetsVisibility = _cullingTargetsVisibility
|
||||
|
||||
}.Schedule(RaycastCommand.ScheduleBatch(_commands, _hits, 1));
|
||||
}
|
||||
|
||||
private void RunJob2()
|
||||
{
|
||||
_handle = new TargetsCreateRaysJob()
|
||||
{
|
||||
|
||||
cellPosition = _cellPosition,
|
||||
cellSize = _cellSize,
|
||||
|
||||
geometryTreeStruct = _geometryTreeStruct,
|
||||
cullingTargetsStruct = _cullingTargetsStruct,
|
||||
|
||||
geometryTreeVisibility = _geometryTreeVisibility,
|
||||
cullingTargetsVisibility = _cullingTargetsVisibility,
|
||||
computedCullingTargets = _computedCullingTargets,
|
||||
nodeTargetsMap = _nodeTargetsMap,
|
||||
|
||||
rayBatches = _raycastBatches,
|
||||
commands = _commands,
|
||||
hits = _hits,
|
||||
|
||||
lastNodeIndex = _lastNodeIndex,
|
||||
lastTargetIndex = _lastTargetIndex,
|
||||
|
||||
raysPerUnit = _raysPerUnit,
|
||||
maxRays = _maxRays,
|
||||
maxDistance = _maxDistance,
|
||||
layerMask = _layerMask,
|
||||
|
||||
startDepth = _startDepth,
|
||||
targetDepth = _depth,
|
||||
commandsLimit = COMMANDS_LIMIT
|
||||
|
||||
}.Schedule();
|
||||
}
|
||||
|
||||
private void RunJob3()
|
||||
{
|
||||
_handle = new TargetsComputeResultsJob()
|
||||
{
|
||||
|
||||
cellPosition = _cellPosition,
|
||||
geometryTree = _geometryTreeStruct,
|
||||
cullingTargetsStruct = _cullingTargetsStruct,
|
||||
nodeTargetsMap = _nodeTargetsMap,
|
||||
|
||||
rayBatches = _raycastBatches,
|
||||
commands = _commands,
|
||||
hits = _hits,
|
||||
|
||||
cullingTargetsVisibility = _cullingTargetsVisibility
|
||||
|
||||
}.Schedule(RaycastCommand.ScheduleBatch(_commands, _hits, 1));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
[BurstCompile]
|
||||
private struct GeometryNodeStruct
|
||||
{
|
||||
public int index;
|
||||
public int left;
|
||||
public int right;
|
||||
public Bounds bounds;
|
||||
public bool isLeaf;
|
||||
public bool isEmpty;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct CullingTargetStruct
|
||||
{
|
||||
public int index;
|
||||
public Bounds bounds;
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct RaycastBatchInfo
|
||||
{
|
||||
public int targetIndex;
|
||||
public int raysStart;
|
||||
public int raysEnd;
|
||||
}
|
||||
|
||||
|
||||
[BurstCompile]
|
||||
private struct TreeCreateRaysJob : IJob
|
||||
{
|
||||
private static readonly double g = 1.22074408460575947536;
|
||||
private static readonly double a1 = 1.0 / g;
|
||||
private static readonly double a2 = 1.0 / (g * g);
|
||||
private static readonly double a3 = 1.0 / (g * g * g);
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<GeometryNodeStruct> geometryTreeStruct;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<bool> geometryTreeVisibility;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastCommand> commands;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastHit> hits;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastBatchInfo> rayBatches;
|
||||
|
||||
public NativeArray<int> lastNodeIndex;
|
||||
|
||||
public Vector3 cellPosition;
|
||||
public float cellSize;
|
||||
public float raysPerUnit;
|
||||
public int maxRays;
|
||||
public float maxDistance;
|
||||
public int layerMask;
|
||||
|
||||
public int startDepth;
|
||||
public int targetDepth;
|
||||
public int commandsLimit;
|
||||
|
||||
private int _commandsCount;
|
||||
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
rayBatches.Clear();
|
||||
commands.Clear();
|
||||
hits.Clear();
|
||||
|
||||
TraverseTree(geometryTreeStruct[0], 1);
|
||||
|
||||
hits.Length = _commandsCount;
|
||||
|
||||
if (_commandsCount <= commandsLimit)
|
||||
lastNodeIndex[0] = 0;
|
||||
}
|
||||
|
||||
private void TraverseTree(GeometryNodeStruct node, int depth)
|
||||
{
|
||||
if (node.isEmpty)
|
||||
return;
|
||||
|
||||
if (_commandsCount > commandsLimit)
|
||||
return;
|
||||
|
||||
if (depth == targetDepth)
|
||||
{
|
||||
if (node.index > lastNodeIndex[0])
|
||||
{
|
||||
if (!geometryTreeVisibility[node.index])
|
||||
{
|
||||
CreateRaysBatch(node);
|
||||
|
||||
if (_commandsCount > commandsLimit)
|
||||
lastNodeIndex[0] = node.index;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!geometryTreeVisibility[node.index] && depth >= startDepth)
|
||||
return;
|
||||
|
||||
if (node.left < 0)
|
||||
return;
|
||||
|
||||
TraverseTree(geometryTreeStruct[node.left], depth + 1);
|
||||
TraverseTree(geometryTreeStruct[node.right], depth + 1);
|
||||
}
|
||||
|
||||
private void CreateRaysBatch(GeometryNodeStruct node)
|
||||
{
|
||||
Bounds bounds = node.bounds;
|
||||
|
||||
if (bounds.Contains(cellPosition))
|
||||
{
|
||||
rayBatches.AddNoResize(new RaycastBatchInfo()
|
||||
{
|
||||
targetIndex = node.index,
|
||||
raysStart = -1,
|
||||
raysEnd = -1,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float distance = Vector3.Distance(bounds.center, cellPosition);
|
||||
float distanceRatio = Mathf.Max((distance / maxDistance), 0.01f);
|
||||
|
||||
int raysCount = Mathf.RoundToInt(5 + bounds.size.magnitude * raysPerUnit * distanceRatio);
|
||||
raysCount = Mathf.Min(raysCount, maxRays);
|
||||
|
||||
rayBatches.AddNoResize(new RaycastBatchInfo()
|
||||
{
|
||||
targetIndex = node.index,
|
||||
raysStart = _commandsCount,
|
||||
raysEnd = _commandsCount + raysCount,
|
||||
});
|
||||
|
||||
_commandsCount += raysCount;
|
||||
|
||||
for (int i = 0; i < raysCount; i++)
|
||||
{
|
||||
Vector3 targetPoint = GetPointInsideBoundingBox(i, bounds);
|
||||
Vector3 dir = (targetPoint - cellPosition).normalized;
|
||||
|
||||
RaycastCommand command = new RaycastCommand(cellPosition, dir, layerMask: layerMask);
|
||||
|
||||
commands.AddNoResize(command);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetPointInsideBoundingBox(int index, Bounds bounds)
|
||||
{
|
||||
Vector3 size = bounds.size;
|
||||
|
||||
float x = (float)(0.5 + a1 * index) % 1;
|
||||
float y = (float)(0.5 + a2 * index) % 1;
|
||||
float z = (float)(0.5 + a3 * index) % 1;
|
||||
|
||||
Vector3 offset = new Vector3(x * size.x, y * size.y, z * size.z);
|
||||
|
||||
return bounds.min + offset;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct TreeComputeResultsJob : IJob
|
||||
{
|
||||
[ReadOnly]
|
||||
public Vector3 cellPosition;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<GeometryNodeStruct> geometryTree;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<CullingTargetStruct> cullingTargetsStruct;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeHashMap_Int_UnsafeListInt nodeTargetsMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastBatchInfo> rayBatches;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastCommand> commands;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastHit> hits;
|
||||
|
||||
public NativeArray<bool> geometryTreeVisibility;
|
||||
public NativeArray<bool> cullingTargetsVisibility;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (int i = 0; i < rayBatches.Length; i++)
|
||||
{
|
||||
RaycastBatchInfo raycastBatch = rayBatches[i];
|
||||
GeometryNodeStruct node = geometryTree[raycastBatch.targetIndex];
|
||||
|
||||
if (raycastBatch.raysStart < 0)
|
||||
{
|
||||
geometryTreeVisibility[node.index] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = raycastBatch.raysStart;
|
||||
int end = raycastBatch.raysEnd;
|
||||
|
||||
int tracedRays = 0;
|
||||
int tracedPoints = 0;
|
||||
|
||||
for (int c = start; c < end; c++)
|
||||
{
|
||||
RaycastHit hit = hits[c];
|
||||
RaycastCommand command = commands[c];
|
||||
Ray ray = new Ray(command.from, command.direction);
|
||||
|
||||
float targetDistance = 0;
|
||||
float hitDistance = hit.distance;
|
||||
|
||||
node.bounds.IntersectRay(ray, out targetDistance);
|
||||
|
||||
if (hitDistance < 0.0001f)
|
||||
{
|
||||
hitDistance = float.MaxValue;
|
||||
}
|
||||
else if (tracedPoints < 5)
|
||||
{
|
||||
TracePoint(geometryTree[0], hit.point);
|
||||
tracedPoints++;
|
||||
}
|
||||
|
||||
if (hitDistance > targetDistance)
|
||||
{
|
||||
TraceRay(geometryTree[0], ray, hitDistance);
|
||||
|
||||
tracedRays++;
|
||||
|
||||
if (tracedRays >= 5)
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TraceRay(GeometryNodeStruct node, Ray ray, float hitDistance)
|
||||
{
|
||||
if (node.isEmpty)
|
||||
return;
|
||||
|
||||
if (node.bounds.IntersectRay(ray, out float nodeIntersectDistance))
|
||||
{
|
||||
if (nodeIntersectDistance < hitDistance)
|
||||
{
|
||||
geometryTreeVisibility[node.index] = true;
|
||||
|
||||
if (node.left != -1)
|
||||
{
|
||||
TraceRay(geometryTree[node.left], ray, hitDistance);
|
||||
TraceRay(geometryTree[node.right], ray, hitDistance);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TracePoint(GeometryNodeStruct node, Vector3 hitPoint)
|
||||
{
|
||||
if (node.isEmpty)
|
||||
return;
|
||||
|
||||
if (!node.bounds.Contains(hitPoint))
|
||||
return;
|
||||
|
||||
if (node.isLeaf)
|
||||
{
|
||||
UnsafeList<int> targets = nodeTargetsMap[node.index];
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
int idx = targets[i];
|
||||
|
||||
if (cullingTargetsVisibility[idx])
|
||||
continue;
|
||||
|
||||
CullingTargetStruct target = cullingTargetsStruct[idx];
|
||||
|
||||
if (target.bounds.Contains(hitPoint))
|
||||
cullingTargetsVisibility[idx] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node.left >= 0)
|
||||
{
|
||||
TracePoint(geometryTree[node.left], hitPoint);
|
||||
TracePoint(geometryTree[node.right], hitPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct TargetsCreateRaysJob : IJob
|
||||
{
|
||||
private static readonly double g = 1.22074408460575947536;
|
||||
private static readonly double a1 = 1.0 / g;
|
||||
private static readonly double a2 = 1.0 / (g * g);
|
||||
private static readonly double a3 = 1.0 / (g * g * g);
|
||||
|
||||
[ReadOnly]
|
||||
public Vector3 cellPosition;
|
||||
|
||||
[ReadOnly]
|
||||
public float cellSize;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<GeometryNodeStruct> geometryTreeStruct;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<CullingTargetStruct> cullingTargetsStruct;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeHashMap_Int_UnsafeListInt nodeTargetsMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<bool> geometryTreeVisibility;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<bool> cullingTargetsVisibility;
|
||||
|
||||
public NativeArray<bool> computedCullingTargets;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastBatchInfo> rayBatches;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastCommand> commands;
|
||||
|
||||
[WriteOnly]
|
||||
public NativeList<RaycastHit> hits;
|
||||
public NativeArray<int> lastNodeIndex;
|
||||
public NativeArray<int> lastTargetIndex;
|
||||
|
||||
public float raysPerUnit;
|
||||
public int maxRays;
|
||||
public float maxDistance;
|
||||
public int layerMask;
|
||||
|
||||
public int startDepth;
|
||||
public int targetDepth;
|
||||
public int commandsLimit;
|
||||
|
||||
private int _commandsCount;
|
||||
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
rayBatches.Clear();
|
||||
commands.Clear();
|
||||
hits.Clear();
|
||||
|
||||
TraverseTree(geometryTreeStruct[0], 1);
|
||||
|
||||
hits.Length = _commandsCount;
|
||||
|
||||
if (_commandsCount <= commandsLimit)
|
||||
{
|
||||
lastNodeIndex[0] = 0;
|
||||
lastTargetIndex[0] = 0;
|
||||
}
|
||||
}
|
||||
|
||||
private void TraverseTree(GeometryNodeStruct node, int depth)
|
||||
{
|
||||
if (node.isEmpty)
|
||||
return;
|
||||
|
||||
if (_commandsCount > commandsLimit)
|
||||
return;
|
||||
|
||||
if (!geometryTreeVisibility[node.index] && depth >= startDepth)
|
||||
return;
|
||||
|
||||
if (node.isLeaf)
|
||||
{
|
||||
if (node.index >= lastNodeIndex[0])
|
||||
{
|
||||
UnsafeList<int> targets = nodeTargetsMap[node.index];
|
||||
|
||||
int lastTarget = lastTargetIndex[0];
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
if (lastTarget != 0 && i <= lastTarget)
|
||||
continue;
|
||||
|
||||
int targetIdx = targets[i];
|
||||
|
||||
if (computedCullingTargets[targetIdx])
|
||||
continue;
|
||||
|
||||
if (cullingTargetsVisibility[targetIdx])
|
||||
{
|
||||
computedCullingTargets[targetIdx] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
CreateRaysBatch(cullingTargetsStruct[targetIdx]);
|
||||
computedCullingTargets[targetIdx] = true;
|
||||
|
||||
if (_commandsCount > commandsLimit)
|
||||
{
|
||||
lastNodeIndex[0] = node.index;
|
||||
lastTargetIndex[0] = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (_commandsCount <= commandsLimit)
|
||||
{
|
||||
if (lastNodeIndex[0] == node.index)
|
||||
lastTargetIndex[0] = 0;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (node.left < 0)
|
||||
return;
|
||||
|
||||
TraverseTree(geometryTreeStruct[node.left], depth + 1);
|
||||
TraverseTree(geometryTreeStruct[node.right], depth + 1);
|
||||
}
|
||||
|
||||
private void CreateRaysBatch(CullingTargetStruct target)
|
||||
{
|
||||
Bounds bounds = target.bounds;
|
||||
|
||||
if (bounds.Contains(cellPosition))
|
||||
{
|
||||
rayBatches.AddNoResize(new RaycastBatchInfo()
|
||||
{
|
||||
targetIndex = target.index,
|
||||
raysStart = -1,
|
||||
raysEnd = -1,
|
||||
});
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
float distance = Vector3.Distance(bounds.center, cellPosition);
|
||||
float distanceRatio = Mathf.Max((distance / maxDistance), 0.01f);
|
||||
|
||||
int raysCount = Mathf.RoundToInt(5 + bounds.size.magnitude * raysPerUnit * distanceRatio);
|
||||
raysCount = Mathf.Min(raysCount, maxRays);
|
||||
|
||||
rayBatches.AddNoResize(new RaycastBatchInfo()
|
||||
{
|
||||
targetIndex = target.index,
|
||||
raysStart = _commandsCount,
|
||||
raysEnd = _commandsCount + raysCount,
|
||||
});
|
||||
|
||||
_commandsCount += raysCount;
|
||||
|
||||
for (int i = 0; i < raysCount; i++)
|
||||
{
|
||||
Vector3 targetPoint = GetPointInsideBoundingBox(i, bounds);
|
||||
Vector3 dir = (targetPoint - cellPosition).normalized;
|
||||
|
||||
RaycastCommand command = new RaycastCommand(cellPosition, dir, layerMask: layerMask);
|
||||
|
||||
commands.AddNoResize(command);
|
||||
}
|
||||
}
|
||||
|
||||
private Vector3 GetPointInsideBoundingBox(int index, Bounds bounds)
|
||||
{
|
||||
Vector3 size = bounds.size;
|
||||
|
||||
float x = (float)(0.5 + a1 * index) % 1;
|
||||
float y = (float)(0.5 + a2 * index) % 1;
|
||||
float z = (float)(0.5 + a3 * index) % 1;
|
||||
|
||||
Vector3 offset = new Vector3(x * size.x, y * size.y, z * size.z);
|
||||
|
||||
return bounds.min + offset;
|
||||
}
|
||||
}
|
||||
|
||||
[BurstCompile]
|
||||
private struct TargetsComputeResultsJob : IJob
|
||||
{
|
||||
[ReadOnly]
|
||||
public Vector3 cellPosition;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<GeometryNodeStruct> geometryTree;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeArray<CullingTargetStruct> cullingTargetsStruct;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeHashMap_Int_UnsafeListInt nodeTargetsMap;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastBatchInfo> rayBatches;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastCommand> commands;
|
||||
|
||||
[ReadOnly]
|
||||
public NativeList<RaycastHit> hits;
|
||||
|
||||
public NativeArray<bool> cullingTargetsVisibility;
|
||||
|
||||
public void Execute()
|
||||
{
|
||||
for (int i = 0; i < rayBatches.Length; i++)
|
||||
{
|
||||
RaycastBatchInfo raycastBatch = rayBatches[i];
|
||||
CullingTargetStruct target = cullingTargetsStruct[raycastBatch.targetIndex];
|
||||
|
||||
if (raycastBatch.raysStart < 0)
|
||||
{
|
||||
cullingTargetsVisibility[target.index] = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
int start = raycastBatch.raysStart;
|
||||
int end = raycastBatch.raysEnd;
|
||||
|
||||
int tracedPoints = 0;
|
||||
|
||||
for (int c = start; c < end; c++)
|
||||
{
|
||||
RaycastHit hit = hits[c];
|
||||
RaycastCommand command = commands[c];
|
||||
Ray ray = new Ray(command.from, command.direction);
|
||||
|
||||
float targetDistance = 0;
|
||||
float hitDistance = hit.distance;
|
||||
|
||||
target.bounds.IntersectRay(ray, out targetDistance);
|
||||
targetDistance -= 0.01f;
|
||||
|
||||
if (hitDistance < 0.0001f)
|
||||
{
|
||||
hitDistance = float.MaxValue;
|
||||
}
|
||||
else if (tracedPoints < 2)
|
||||
{
|
||||
TracePoint(geometryTree[0], hit.point);
|
||||
tracedPoints++;
|
||||
}
|
||||
|
||||
if (hitDistance > targetDistance)
|
||||
{
|
||||
cullingTargetsVisibility[target.index] = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void TracePoint(GeometryNodeStruct node, Vector3 hitPoint)
|
||||
{
|
||||
if (node.isEmpty)
|
||||
return;
|
||||
|
||||
if (!node.bounds.Contains(hitPoint))
|
||||
return;
|
||||
|
||||
if (node.isLeaf)
|
||||
{
|
||||
UnsafeList<int> targets = nodeTargetsMap[node.index];
|
||||
|
||||
for (int i = 0; i < targets.Length; i++)
|
||||
{
|
||||
int idx = targets[i];
|
||||
|
||||
if (cullingTargetsVisibility[idx])
|
||||
continue;
|
||||
|
||||
CullingTargetStruct target = cullingTargetsStruct[idx];
|
||||
|
||||
if (target.bounds.Contains(hitPoint))
|
||||
cullingTargetsVisibility[idx] = true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (node.left >= 0)
|
||||
{
|
||||
TracePoint(geometryTree[node.left], hitPoint);
|
||||
TracePoint(geometryTree[node.right], hitPoint);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
[DisallowMultipleComponent]
|
||||
[RequireComponent(typeof(Camera))]
|
||||
public class StaticCullingCamera : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private bool _drawCells;
|
||||
|
||||
[Range(0, 1)]
|
||||
[SerializeField]
|
||||
private float _tolerance = 1f;
|
||||
|
||||
private VisibilityTree _tree;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (CameraZone.Instances.Count == 0)
|
||||
{
|
||||
Debug.Log("StaticCullingCamera : Not found Camera Zones in scene");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
_tree = FindNearestVisibilityTree();
|
||||
|
||||
if (_tree == null)
|
||||
{
|
||||
Debug.Log("StaticCullingCamera : Can't find nearest CameraZone");
|
||||
enabled = false;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
Vector3 point = transform.position;
|
||||
|
||||
if (_tree == null || !_tree.Root.Bounds.Contains(point))
|
||||
{
|
||||
_tree = FindNearestVisibilityTree();
|
||||
|
||||
if (_tree == null)
|
||||
return;
|
||||
}
|
||||
|
||||
_tree.SetVisible(point, _tolerance);
|
||||
}
|
||||
|
||||
private VisibilityTree FindNearestVisibilityTree()
|
||||
{
|
||||
if (CameraZone.Instances.Count == 0)
|
||||
return null;
|
||||
|
||||
Vector3 point = transform.position;
|
||||
|
||||
foreach (var zone in CameraZone.Instances)
|
||||
{
|
||||
if (zone == null)
|
||||
continue;
|
||||
|
||||
VisibilityTree tree = zone.VisibilityTree;
|
||||
|
||||
if (tree == null || tree.CullingTargets == null)
|
||||
continue;
|
||||
|
||||
if (tree.Root.Bounds.Contains(point))
|
||||
return tree;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public static bool DrawGizmo;
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (DrawGizmo)
|
||||
{
|
||||
Gizmos.color = Color.cyan;
|
||||
Gizmos.DrawWireCube(transform.position, Vector3.one);
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmosSelected()
|
||||
{
|
||||
if (!_drawCells || _tree == null)
|
||||
return;
|
||||
|
||||
_tree.DrawCellsGizmo(transform.position, _tolerance);
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,456 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class StaticCullingController : MonoBehaviour
|
||||
{
|
||||
public IReadOnlyList<CameraZone> CameraZones
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cameraZones;
|
||||
}
|
||||
}
|
||||
|
||||
public int GeometryTreeDepth
|
||||
{
|
||||
get
|
||||
{
|
||||
return _geometryTreeDepth;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_geometryTreeDepth = Mathf.Clamp(value, 7, 20);
|
||||
}
|
||||
}
|
||||
public float CellSize
|
||||
{
|
||||
get
|
||||
{
|
||||
return _cellSize;
|
||||
}
|
||||
|
||||
set
|
||||
{
|
||||
_cellSize = Mathf.Max(value, 0.1f);
|
||||
}
|
||||
}
|
||||
public int TotalCellsCount
|
||||
{
|
||||
get
|
||||
{
|
||||
return _totalCellsCount;
|
||||
}
|
||||
}
|
||||
public float RaysPerUnit
|
||||
{
|
||||
get
|
||||
{
|
||||
return _raysPerUnit;
|
||||
}
|
||||
set
|
||||
{
|
||||
_raysPerUnit = Mathf.Max(0.1f, value);
|
||||
}
|
||||
}
|
||||
public int MaxRaysPerSource
|
||||
{
|
||||
get
|
||||
{
|
||||
return _maxRaysPerSource;
|
||||
}
|
||||
set
|
||||
{
|
||||
_maxRaysPerSource = Mathf.Max(10, value);
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private List<CameraZone> _cameraZones;
|
||||
|
||||
[SerializeField]
|
||||
private GeometryTree _geometryTree;
|
||||
|
||||
[SerializeField]
|
||||
private int _geometryTreeDepth = 11;
|
||||
|
||||
[SerializeField]
|
||||
private float _cellSize = 5f;
|
||||
|
||||
[SerializeField]
|
||||
private int _totalCellsCount;
|
||||
|
||||
[SerializeField]
|
||||
private float _raysPerUnit = 10f;
|
||||
|
||||
[SerializeField]
|
||||
private int _maxRaysPerSource = 300;
|
||||
|
||||
|
||||
public bool AddCameraZone(CameraZone zone)
|
||||
{
|
||||
if (zone == null)
|
||||
return false;
|
||||
|
||||
if (_cameraZones == null)
|
||||
_cameraZones = new List<CameraZone>();
|
||||
|
||||
if (_cameraZones.Contains(zone))
|
||||
return false;
|
||||
|
||||
_cameraZones.Add(zone);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool RemoveCameraZone(CameraZone zone)
|
||||
{
|
||||
if (zone == null)
|
||||
return false;
|
||||
|
||||
if (_cameraZones == null)
|
||||
return false;
|
||||
|
||||
if (!_cameraZones.Contains(zone))
|
||||
return false;
|
||||
|
||||
return _cameraZones.Remove(zone);
|
||||
}
|
||||
|
||||
public void CreatePreviewGeometryTree()
|
||||
{
|
||||
string error;
|
||||
|
||||
if (!ReadyToCreateGeometryTree(out error))
|
||||
{
|
||||
Debug.Log("Unable to create GeometryTree : " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
List<StaticCullingSource> validSources = new List<StaticCullingSource>();
|
||||
|
||||
foreach (var source in FindObjectsOfType<StaticCullingSource>())
|
||||
{
|
||||
if (source.Validate())
|
||||
{
|
||||
source.PrepareForBaking();
|
||||
validSources.Add(source);
|
||||
}
|
||||
}
|
||||
|
||||
CreateGeometryTree(validSources, out error);
|
||||
|
||||
foreach (var source in validSources)
|
||||
{
|
||||
source.ClearAfterBaking();
|
||||
DestroyImmediate(source.gameObject.GetComponent<CullingTarget>());
|
||||
}
|
||||
}
|
||||
|
||||
public void CreatePreviewCameraZones()
|
||||
{
|
||||
string error;
|
||||
|
||||
if (!ReadyToBakeCameraZones(out error))
|
||||
{
|
||||
Debug.Log("Unable to bake camera zones : " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!CreateVisibilityTrees(out error))
|
||||
{
|
||||
Debug.Log("Unable to bake camera zones : " + error);
|
||||
}
|
||||
}
|
||||
|
||||
public void Bake()
|
||||
{
|
||||
string error;
|
||||
|
||||
if (!ReadyToBake(out error))
|
||||
{
|
||||
Debug.Log("Unable to bake scene : " + error);
|
||||
return;
|
||||
}
|
||||
|
||||
List<StaticCullingSource> sources;
|
||||
|
||||
if (!PrepareForBake(out sources, out error))
|
||||
{
|
||||
Debug.Log("Baking process aborted");
|
||||
Debug.Log("Reason : " + error);
|
||||
ClearBakedData();
|
||||
}
|
||||
|
||||
if (!CreateGeometryTree(sources, out error))
|
||||
{
|
||||
Debug.Log("Baking process aborted");
|
||||
Debug.Log("Reason : " + error);
|
||||
ClearBakedData();
|
||||
}
|
||||
|
||||
if (!CreateVisibilityTrees(out error))
|
||||
{
|
||||
Debug.Log("Baking process aborted");
|
||||
Debug.Log("Reason : " + error);
|
||||
ClearBakedData();
|
||||
}
|
||||
|
||||
if (BakeScene(_geometryTree, out error))
|
||||
{
|
||||
ClearAfterBaking(sources);
|
||||
Debug.Log("Scene sucessfully baked!");
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.Log("Baking process aborted");
|
||||
Debug.Log("Reason : " + error);
|
||||
ClearBakedData();
|
||||
}
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
ClearBakedData();
|
||||
}
|
||||
|
||||
|
||||
private bool ReadyToCreateGeometryTree(out string error)
|
||||
{
|
||||
StaticCullingSource[] sources = FindObjectsOfType<StaticCullingSource>();
|
||||
|
||||
if (sources == null || sources.Length == 0)
|
||||
{
|
||||
error = "StaticCullingSources not found. Add in 'Step 1'";
|
||||
return false;
|
||||
}
|
||||
|
||||
foreach (var source in sources)
|
||||
{
|
||||
if (source.Validate())
|
||||
{
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
error = "Valid StaticCullingSources not found. Check in 'Step 1'";
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool ReadyToBakeCameraZones(out string error)
|
||||
{
|
||||
if (_cameraZones == null || _cameraZones.Count == 0)
|
||||
{
|
||||
error = "Camera Zones not added. Add in 'Step 3'";
|
||||
return false;
|
||||
}
|
||||
|
||||
int i = 0;
|
||||
while (i < _cameraZones.Count)
|
||||
{
|
||||
if (_cameraZones[i] == null)
|
||||
_cameraZones.RemoveAt(i);
|
||||
else
|
||||
i++;
|
||||
}
|
||||
|
||||
if (_cameraZones.Count == 0)
|
||||
{
|
||||
error = "Camera Zones not added. Add in 'Step 3'";
|
||||
return false;
|
||||
}
|
||||
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool ReadyToBake(out string error)
|
||||
{
|
||||
if (!ReadyToCreateGeometryTree(out error))
|
||||
return false;
|
||||
|
||||
if (!ReadyToBakeCameraZones(out error))
|
||||
return false;
|
||||
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
private bool PrepareForBake(out List<StaticCullingSource> sources, out string error)
|
||||
{
|
||||
sources = new List<StaticCullingSource>();
|
||||
|
||||
try
|
||||
{
|
||||
StaticCullingSource[] sceneSources = FindObjectsOfType<StaticCullingSource>();
|
||||
|
||||
foreach (var source in sceneSources)
|
||||
{
|
||||
if (source.Validate())
|
||||
{
|
||||
source.PrepareForBaking();
|
||||
sources.Add(source);
|
||||
}
|
||||
}
|
||||
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message + ex.StackTrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateGeometryTree(List<StaticCullingSource> sources, out string error)
|
||||
{
|
||||
try
|
||||
{
|
||||
_geometryTree = new GeometryTree(sources
|
||||
.Select(s => s.CullingTarget)
|
||||
.ToArray(), _geometryTreeDepth);
|
||||
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message + ex.StackTrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool CreateVisibilityTrees(out string error)
|
||||
{
|
||||
try
|
||||
{
|
||||
_totalCellsCount = 0;
|
||||
|
||||
foreach (var zone in _cameraZones)
|
||||
{
|
||||
if (zone != null)
|
||||
{
|
||||
zone.ClearVisibilityTree();
|
||||
zone.CreateVisibilityTree(_cellSize);
|
||||
|
||||
_totalCellsCount += zone.CellsCount;
|
||||
}
|
||||
}
|
||||
|
||||
error = "";
|
||||
return true;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
error = ex.Message + ex.StackTrace;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private bool BakeScene(GeometryTree geometryTree, out string error)
|
||||
{
|
||||
StaticCullingBaker baker = new StaticCullingBaker(geometryTree);
|
||||
|
||||
error = "";
|
||||
bool aborted = false;
|
||||
|
||||
foreach (var zone in _cameraZones)
|
||||
{
|
||||
if (!baker.Bake(zone.VisibilityTree, _raysPerUnit, _maxRaysPerSource, out error))
|
||||
{
|
||||
aborted = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
baker.Dispose();
|
||||
|
||||
return !aborted;
|
||||
}
|
||||
|
||||
private void ClearAfterBaking(List<StaticCullingSource> sources)
|
||||
{
|
||||
foreach (var source in sources)
|
||||
{
|
||||
source.ClearAfterBaking();
|
||||
DestroyImmediate(source);
|
||||
}
|
||||
}
|
||||
|
||||
private void ClearBakedData()
|
||||
{
|
||||
foreach (var source in FindObjectsOfType<StaticCullingSource>())
|
||||
source.ClearAfterBaking();
|
||||
|
||||
int clearedCullingTargets = 0;
|
||||
|
||||
foreach (var target in FindObjectsOfType<CullingTarget>())
|
||||
{
|
||||
target.gameObject.AddComponent<StaticCullingSource>();
|
||||
DestroyImmediate(target);
|
||||
clearedCullingTargets++;
|
||||
}
|
||||
|
||||
int clearedCameraZones = 0;
|
||||
|
||||
if (_cameraZones != null)
|
||||
{
|
||||
foreach (var zone in _cameraZones)
|
||||
{
|
||||
if (zone != null)
|
||||
{
|
||||
zone.ClearVisibilityTree();
|
||||
clearedCameraZones++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Debug.Log("Cleared Culling Targets : " + clearedCullingTargets);
|
||||
Debug.Log("Cleared Camera Zones : " + clearedCameraZones);
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public bool DrawGeometryTreeGizmo;
|
||||
public bool DrawCameraZones;
|
||||
|
||||
private BinaryTreeDrawer _treeDrawer;
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (_treeDrawer == null)
|
||||
_treeDrawer = new BinaryTreeDrawer();
|
||||
|
||||
if (DrawGeometryTreeGizmo && _geometryTree != null)
|
||||
{
|
||||
_treeDrawer.Color = Color.blue;
|
||||
_treeDrawer.DrawTreeGizmos(_geometryTree.Root);
|
||||
}
|
||||
|
||||
if (DrawCameraZones)
|
||||
{
|
||||
if (_cameraZones != null)
|
||||
{
|
||||
foreach (var zone in _cameraZones)
|
||||
{
|
||||
if (zone.VisibilityTree != null)
|
||||
{
|
||||
_treeDrawer.Color = Color.white;
|
||||
_treeDrawer.DrawTreeGizmos(zone.VisibilityTree.Root);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public static class StaticCullingPreferences
|
||||
{
|
||||
public static string LayerName
|
||||
{
|
||||
get
|
||||
{
|
||||
return "ACSCulling";
|
||||
}
|
||||
}
|
||||
|
||||
public static int Layer
|
||||
{
|
||||
get
|
||||
{
|
||||
return LayerMask.NameToLayer(LayerName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public interface IStaticCullingSourceStrategy
|
||||
{
|
||||
bool Validate(out string errorMessage);
|
||||
|
||||
bool TryGetBounds(out Bounds bounds);
|
||||
|
||||
CullingTarget CreateCullingTarget();
|
||||
|
||||
void PrepareForBaking();
|
||||
|
||||
void ClearAfterBaking();
|
||||
}
|
||||
}
|
@@ -0,0 +1,191 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public enum SourceType { MeshRenderer, LODGroup, Light, Custom }
|
||||
|
||||
[DisallowMultipleComponent]
|
||||
public class StaticCullingSource : MonoBehaviour
|
||||
{
|
||||
public SourceType SourceType
|
||||
{
|
||||
get
|
||||
{
|
||||
return _sourceType;
|
||||
}
|
||||
set
|
||||
{
|
||||
_sourceType = value;
|
||||
|
||||
OnSourceTypeChanged();
|
||||
}
|
||||
}
|
||||
public string ValidationError
|
||||
{
|
||||
get
|
||||
{
|
||||
return _validationError;
|
||||
}
|
||||
}
|
||||
public CullingTarget CullingTarget
|
||||
{
|
||||
get
|
||||
{
|
||||
return _target;
|
||||
}
|
||||
}
|
||||
public IStaticCullingSourceStrategy Strategy
|
||||
{
|
||||
get
|
||||
{
|
||||
return _strategy;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private SourceType _sourceType;
|
||||
|
||||
[SerializeField]
|
||||
private string _validationError;
|
||||
|
||||
[SerializeField]
|
||||
private CullingTarget _target;
|
||||
|
||||
[SerializeReference]
|
||||
private IStaticCullingSourceStrategy _strategy;
|
||||
|
||||
|
||||
private void Reset()
|
||||
{
|
||||
AutoDetectSourceType();
|
||||
CreateStrategy();
|
||||
Validate();
|
||||
}
|
||||
|
||||
private void AutoDetectSourceType()
|
||||
{
|
||||
if (GetComponent<MeshRenderer>() != null)
|
||||
{
|
||||
_sourceType = SourceType.MeshRenderer;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetComponent<LODGroup>() != null)
|
||||
{
|
||||
_sourceType = SourceType.LODGroup;
|
||||
return;
|
||||
}
|
||||
|
||||
if (GetComponent<Light>() != null)
|
||||
{
|
||||
_sourceType = SourceType.Light;
|
||||
return;
|
||||
}
|
||||
|
||||
_sourceType = SourceType.Custom;
|
||||
}
|
||||
|
||||
private void OnSourceTypeChanged()
|
||||
{
|
||||
_validationError = "";
|
||||
|
||||
CreateStrategy();
|
||||
Validate();
|
||||
}
|
||||
|
||||
private void CreateStrategy()
|
||||
{
|
||||
if (_sourceType == SourceType.MeshRenderer)
|
||||
_strategy = new MeshRendererStaticCullingSourceStrategy(gameObject);
|
||||
|
||||
else if (_sourceType == SourceType.LODGroup)
|
||||
_strategy = new LODGroupStaticCullingSourceStrategy(gameObject);
|
||||
|
||||
else if (_sourceType == SourceType.Light)
|
||||
_strategy = new LightStaticCullingSourceStrategy(gameObject);
|
||||
|
||||
else if (_sourceType == SourceType.Custom)
|
||||
_strategy = new CustomStaticCullingSourceStrategy(gameObject);
|
||||
}
|
||||
|
||||
|
||||
public bool Validate()
|
||||
{
|
||||
if (_strategy == null)
|
||||
{
|
||||
AutoDetectSourceType();
|
||||
CreateStrategy();
|
||||
}
|
||||
|
||||
_validationError = "";
|
||||
|
||||
return _strategy.Validate(out _validationError);
|
||||
}
|
||||
|
||||
public bool TryGetBounds(out Bounds bounds)
|
||||
{
|
||||
return _strategy.TryGetBounds(out bounds);
|
||||
}
|
||||
|
||||
public void PrepareForBaking()
|
||||
{
|
||||
if (_validationError != "")
|
||||
{
|
||||
if (!Validate())
|
||||
throw new Exception("StaticCullingSource::" + gameObject.name + " has validation errors");
|
||||
}
|
||||
|
||||
_target = _strategy.CreateCullingTarget();
|
||||
|
||||
_strategy.PrepareForBaking();
|
||||
}
|
||||
|
||||
public void ClearAfterBaking()
|
||||
{
|
||||
_strategy.ClearAfterBaking();
|
||||
}
|
||||
|
||||
|
||||
#if UNITY_EDITOR
|
||||
|
||||
public static bool DrawGizmoRenderers;
|
||||
public static bool DrawGizmoLODGroups;
|
||||
public static bool DrawGizmoLights;
|
||||
public static bool DrawGizmoCustom;
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (_sourceType == SourceType.MeshRenderer && DrawGizmoRenderers)
|
||||
DrawGizmo(Color.blue);
|
||||
|
||||
else if (_sourceType == SourceType.LODGroup && DrawGizmoLODGroups)
|
||||
DrawGizmo(Color.yellow);
|
||||
|
||||
else if (_sourceType == SourceType.Light && DrawGizmoLights)
|
||||
DrawGizmo(Color.white);
|
||||
|
||||
else if (_sourceType == SourceType.Custom && DrawGizmoCustom)
|
||||
DrawGizmo(Color.green);
|
||||
}
|
||||
|
||||
private void DrawGizmo(Color color)
|
||||
{
|
||||
if (!TryGetBounds(out Bounds bounds))
|
||||
return;
|
||||
|
||||
if (ValidationError != "")
|
||||
{
|
||||
Gizmos.color = Color.red;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
}
|
||||
else
|
||||
{
|
||||
Gizmos.color = color;
|
||||
Gizmos.DrawWireCube(bounds.center, bounds.size);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}
|
@@ -0,0 +1,157 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class CustomStaticCullingSourceStrategy : IStaticCullingSourceStrategy
|
||||
{
|
||||
public Bounds LocalBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _localBounds;
|
||||
}
|
||||
set
|
||||
{
|
||||
_localBounds = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private GameObject _context;
|
||||
|
||||
[SerializeField]
|
||||
private bool _isOccluder;
|
||||
|
||||
[SerializeField]
|
||||
private Bounds _localBounds;
|
||||
|
||||
[SerializeField]
|
||||
private CustomTargetEvent _onVisible;
|
||||
|
||||
[SerializeField]
|
||||
private CustomTargetEvent _onInvisible;
|
||||
|
||||
[SerializeField]
|
||||
private List<Collider> _colliders;
|
||||
|
||||
[SerializeField]
|
||||
private List<Collider> _createdColliders;
|
||||
|
||||
|
||||
public CustomStaticCullingSourceStrategy(GameObject context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
CustomCullingTarget target = context.GetComponent<CustomCullingTarget>();
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
_localBounds = new Bounds(
|
||||
target.Bounds.center - target.transform.position,
|
||||
target.Bounds.size);
|
||||
|
||||
_onVisible = target.OnVisible;
|
||||
_onInvisible = target.OnInvisible;
|
||||
_isOccluder = target.IsOccluder;
|
||||
}
|
||||
else
|
||||
{
|
||||
_localBounds = new Bounds(Vector3.zero, Vector3.one * 3);
|
||||
_isOccluder = false;
|
||||
}
|
||||
|
||||
_onVisible = new CustomTargetEvent();
|
||||
_onInvisible = new CustomTargetEvent();
|
||||
|
||||
_colliders = new List<Collider>();
|
||||
_createdColliders = new List<Collider>();
|
||||
}
|
||||
|
||||
public bool TryGetBounds(out Bounds bounds)
|
||||
{
|
||||
bounds = _localBounds;
|
||||
bounds.center += _context.transform.position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool Validate(out string errorMessage)
|
||||
{
|
||||
if (_onVisible == null && _onInvisible == null)
|
||||
{
|
||||
errorMessage = "Visible and Invisible actions not assigned";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isOccluder)
|
||||
{
|
||||
if (_colliders == null || _colliders.Count == 0)
|
||||
{
|
||||
errorMessage = "Source marked as occluder but colliders not assigned";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
errorMessage = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public CullingTarget CreateCullingTarget()
|
||||
{
|
||||
CustomCullingTarget cullingTarget = _context.AddComponent<CustomCullingTarget>();
|
||||
|
||||
TryGetBounds(out Bounds bounds);
|
||||
|
||||
cullingTarget.Bounds = bounds;
|
||||
cullingTarget.SetActions(_onVisible, _onInvisible);
|
||||
cullingTarget.IsOccluder = _isOccluder;
|
||||
|
||||
return cullingTarget;
|
||||
}
|
||||
|
||||
public void PrepareForBaking()
|
||||
{
|
||||
if (!_isOccluder)
|
||||
return;
|
||||
|
||||
if (_colliders == null || _colliders.Count == 0)
|
||||
return;
|
||||
|
||||
_createdColliders.Clear();
|
||||
|
||||
for (int i = 0; i < _colliders.Count; i++)
|
||||
{
|
||||
_createdColliders.Add(CreateCollider(_colliders[i]));
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAfterBaking()
|
||||
{
|
||||
if (_createdColliders == null || _createdColliders.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _createdColliders.Count; i++)
|
||||
Object.Destroy(_createdColliders[i].gameObject);
|
||||
|
||||
_createdColliders.Clear();
|
||||
}
|
||||
|
||||
|
||||
public Collider CreateCollider(Collider source)
|
||||
{
|
||||
Collider collider = Object.Instantiate(source);
|
||||
GameObject colliderGo = collider.gameObject;
|
||||
|
||||
colliderGo.layer = StaticCullingPreferences.Layer;
|
||||
colliderGo.transform.parent = _context.transform;
|
||||
colliderGo.transform.localPosition = Vector3.zero;
|
||||
colliderGo.transform.localEulerAngles = Vector3.zero;
|
||||
colliderGo.transform.localScale = Vector3.one;
|
||||
|
||||
return collider;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,236 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class LODGroupStaticCullingSourceStrategy : IStaticCullingSourceStrategy
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject _context;
|
||||
|
||||
[SerializeField]
|
||||
private CullingMethod _cullingMethod;
|
||||
|
||||
[SerializeField]
|
||||
private bool _isOccluder;
|
||||
|
||||
[SerializeField]
|
||||
private LODGroup _lodGroup;
|
||||
|
||||
[SerializeField]
|
||||
private List<Renderer> _renderers;
|
||||
|
||||
[SerializeField]
|
||||
private Bounds? _localBounds;
|
||||
|
||||
[SerializeField]
|
||||
private List<Collider> _colliders;
|
||||
|
||||
|
||||
public LODGroupStaticCullingSourceStrategy(GameObject context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
LODGroupCullingTarget target = context.GetComponent<LODGroupCullingTarget>();
|
||||
|
||||
if (target != null)
|
||||
_isOccluder = target.IsOccluder;
|
||||
else
|
||||
_isOccluder = true;
|
||||
}
|
||||
|
||||
public bool Validate(out string errorMessage)
|
||||
{
|
||||
_lodGroup = _context.GetComponent<LODGroup>();
|
||||
|
||||
if (_lodGroup == null)
|
||||
{
|
||||
errorMessage = "LODGroup not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_isOccluder)
|
||||
{
|
||||
LOD lod = _lodGroup.GetLODs()[0];
|
||||
bool containsRenderersForColliders = false;
|
||||
|
||||
for (int i = 0; i < lod.renderers.Length; i++)
|
||||
{
|
||||
if (CheckRenderer(lod.renderers[i]))
|
||||
{
|
||||
containsRenderersForColliders = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!containsRenderersForColliders)
|
||||
{
|
||||
errorMessage = "Not found valid Renderers on LOD0 for creating colliders";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
CollectRenderers();
|
||||
|
||||
if (_renderers.Count == 0)
|
||||
{
|
||||
errorMessage = "Not found valid Renderers";
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetBounds(out Bounds bounds)
|
||||
{
|
||||
if (_localBounds.HasValue)
|
||||
{
|
||||
bounds = _localBounds.Value;
|
||||
bounds.center += _context.transform.position;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (_renderers == null || _renderers.Count == 0)
|
||||
{
|
||||
bounds = default;
|
||||
return false;
|
||||
}
|
||||
|
||||
Vector3 min = Vector3.one * float.MaxValue;
|
||||
Vector3 max = -Vector3.one * float.MaxValue;
|
||||
|
||||
for (int i = 0; i < _renderers.Count; i++)
|
||||
{
|
||||
Bounds rBounds = _renderers[i].bounds;
|
||||
|
||||
Vector3 rMin = rBounds.min;
|
||||
Vector3 rMax = rBounds.max;
|
||||
|
||||
min.x = Mathf.Min(min.x, rMin.x);
|
||||
min.y = Mathf.Min(min.y, rMin.y);
|
||||
min.z = Mathf.Min(min.z, rMin.z);
|
||||
|
||||
max.x = Mathf.Max(max.x, rMax.x);
|
||||
max.y = Mathf.Max(max.y, rMax.y);
|
||||
max.z = Mathf.Max(max.z, rMax.z);
|
||||
}
|
||||
|
||||
_localBounds = new Bounds(min + ((max - min) / 2) - _context.transform.position, max - min);
|
||||
|
||||
bounds = _localBounds.Value;
|
||||
bounds.center += _context.transform.position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public CullingTarget CreateCullingTarget()
|
||||
{
|
||||
LODGroupCullingTarget cullingTarget = _context.AddComponent<LODGroupCullingTarget>();
|
||||
|
||||
CollectRenderers();
|
||||
|
||||
_localBounds = null;
|
||||
|
||||
TryGetBounds(out Bounds bounds);
|
||||
|
||||
cullingTarget.Bounds = bounds;
|
||||
cullingTarget.SetRenderers(_renderers);
|
||||
cullingTarget.CullingMethod = _cullingMethod;
|
||||
cullingTarget.IsOccluder = _isOccluder;
|
||||
|
||||
return cullingTarget;
|
||||
}
|
||||
|
||||
public void PrepareForBaking()
|
||||
{
|
||||
if (!_isOccluder)
|
||||
return;
|
||||
|
||||
LOD lod = _lodGroup.GetLODs()[0];
|
||||
|
||||
for (int c = 0; c < lod.renderers.Length; c++)
|
||||
{
|
||||
Renderer renderer = lod.renderers[c];
|
||||
|
||||
if (CheckRenderer(renderer))
|
||||
{
|
||||
Collider collider = CreateCollider(renderer.GetComponent<MeshFilter>());
|
||||
|
||||
if (_colliders == null)
|
||||
_colliders = new List<Collider>();
|
||||
|
||||
_colliders.Add(collider);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearAfterBaking()
|
||||
{
|
||||
if (_colliders == null || _colliders.Count == 0)
|
||||
return;
|
||||
|
||||
for (int i = 0; i < _colliders.Count; i++)
|
||||
UnityEngine.Object.DestroyImmediate(_colliders[i].gameObject);
|
||||
|
||||
_colliders.Clear();
|
||||
}
|
||||
|
||||
|
||||
private void CollectRenderers()
|
||||
{
|
||||
if (_renderers == null)
|
||||
_renderers = new List<Renderer>();
|
||||
else
|
||||
_renderers.Clear();
|
||||
|
||||
LOD[] lods = _lodGroup.GetLODs();
|
||||
|
||||
for (int i = 0; i < _lodGroup.lodCount; i++)
|
||||
{
|
||||
LOD lod = lods[i];
|
||||
|
||||
for (int c = 0; c < lod.renderers.Length; c++)
|
||||
{
|
||||
Renderer renderer = lod.renderers[c];
|
||||
|
||||
if (CheckRenderer(renderer))
|
||||
_renderers.Add(renderer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private bool CheckRenderer(Renderer renderer)
|
||||
{
|
||||
if (renderer == null)
|
||||
return false;
|
||||
|
||||
MeshFilter filter = renderer.GetComponent<MeshFilter>();
|
||||
|
||||
if (filter == null || filter.sharedMesh == null)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public Collider CreateCollider(MeshFilter filter)
|
||||
{
|
||||
Mesh mesh = filter.sharedMesh;
|
||||
|
||||
GameObject colliderGo = new GameObject("SC_Collider");
|
||||
|
||||
colliderGo.layer = StaticCullingPreferences.Layer;
|
||||
colliderGo.transform.parent = _context.transform;
|
||||
colliderGo.transform.localPosition = Vector3.zero;
|
||||
colliderGo.transform.localEulerAngles = Vector3.zero;
|
||||
colliderGo.transform.localScale = Vector3.one;
|
||||
|
||||
MeshCollider collider = colliderGo.AddComponent<MeshCollider>();
|
||||
collider.sharedMesh = mesh;
|
||||
|
||||
return collider;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,101 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class LightStaticCullingSourceStrategy : IStaticCullingSourceStrategy
|
||||
{
|
||||
public Bounds LocalBounds
|
||||
{
|
||||
get
|
||||
{
|
||||
return _localBounds;
|
||||
}
|
||||
set
|
||||
{
|
||||
_localBounds = value;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private GameObject _context;
|
||||
|
||||
[SerializeField]
|
||||
private Light _light;
|
||||
|
||||
[SerializeField]
|
||||
private Bounds _localBounds;
|
||||
|
||||
public LightStaticCullingSourceStrategy(GameObject context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
_light = _context.GetComponent<Light>();
|
||||
|
||||
LightCullingTarget target = _context.GetComponent<LightCullingTarget>();
|
||||
|
||||
if (target != null)
|
||||
{
|
||||
_localBounds = new Bounds(
|
||||
target.Bounds.center - target.transform.position,
|
||||
target.Bounds.size);
|
||||
}
|
||||
else if (_light != null)
|
||||
{
|
||||
_localBounds = new Bounds
|
||||
{
|
||||
center = Vector3.zero,
|
||||
size = Vector3.one
|
||||
};
|
||||
|
||||
if (_light.type == LightType.Point)
|
||||
_localBounds.size = _light.range * Vector3.one;
|
||||
}
|
||||
}
|
||||
|
||||
public bool Validate(out string errorMessage)
|
||||
{
|
||||
_light = _context.GetComponent<Light>();
|
||||
|
||||
if (_light == null)
|
||||
{
|
||||
errorMessage = "Light component not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetBounds(out Bounds bounds)
|
||||
{
|
||||
bounds = _localBounds;
|
||||
|
||||
bounds.center += _context.transform.position;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public CullingTarget CreateCullingTarget()
|
||||
{
|
||||
LightCullingTarget target = _context.gameObject.AddComponent<LightCullingTarget>();
|
||||
|
||||
TryGetBounds(out Bounds bounds);
|
||||
|
||||
target.Bounds = bounds;
|
||||
|
||||
return target;
|
||||
}
|
||||
|
||||
public void PrepareForBaking()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void ClearAfterBaking()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,147 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class MeshRendererStaticCullingSourceStrategy : IStaticCullingSourceStrategy
|
||||
{
|
||||
[SerializeField]
|
||||
private GameObject _context;
|
||||
|
||||
[SerializeField]
|
||||
private MeshRenderer _renderer;
|
||||
|
||||
[SerializeField]
|
||||
private MeshFilter _filter;
|
||||
|
||||
[SerializeField]
|
||||
private MeshCollider _collider;
|
||||
|
||||
[SerializeField]
|
||||
private CullingMethod _cullingMethod;
|
||||
|
||||
[SerializeField]
|
||||
private bool _isOccluder;
|
||||
|
||||
|
||||
public MeshRendererStaticCullingSourceStrategy(GameObject context)
|
||||
{
|
||||
_context = context;
|
||||
|
||||
MeshRendererCullingTarget target = context.GetComponent<MeshRendererCullingTarget>();
|
||||
|
||||
if (target != null)
|
||||
_isOccluder = target.IsOccluder;
|
||||
else
|
||||
_isOccluder = !AllMaterialsIsTransarent(context);
|
||||
}
|
||||
|
||||
public bool Validate(out string errorMessage)
|
||||
{
|
||||
_renderer = _context.GetComponent<MeshRenderer>();
|
||||
|
||||
if (_renderer == null)
|
||||
{
|
||||
errorMessage = "MeshRenderer not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
_filter = _context.GetComponent<MeshFilter>();
|
||||
|
||||
if (_filter == null)
|
||||
{
|
||||
errorMessage = "MeshFilter not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (_filter.sharedMesh == null)
|
||||
{
|
||||
errorMessage = "Mesh not found";
|
||||
return false;
|
||||
}
|
||||
|
||||
errorMessage = "";
|
||||
return true;
|
||||
}
|
||||
|
||||
public bool TryGetBounds(out Bounds bounds)
|
||||
{
|
||||
if (_renderer == null)
|
||||
{
|
||||
if ((_renderer = _context.GetComponent<MeshRenderer>()) == null)
|
||||
{
|
||||
bounds = default(Bounds);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bounds = _renderer.bounds;
|
||||
return true;
|
||||
}
|
||||
|
||||
public CullingTarget CreateCullingTarget()
|
||||
{
|
||||
var cullingTarget = _context.AddComponent<MeshRendererCullingTarget>();
|
||||
|
||||
cullingTarget.Bounds = _renderer.bounds;
|
||||
cullingTarget.CullingMethod = _cullingMethod;
|
||||
cullingTarget.IsOccluder = _isOccluder;
|
||||
|
||||
return cullingTarget;
|
||||
}
|
||||
|
||||
public void PrepareForBaking()
|
||||
{
|
||||
if (!_isOccluder)
|
||||
return;
|
||||
|
||||
Mesh mesh = _filter.sharedMesh;
|
||||
|
||||
GameObject colliderGo = new GameObject("SC_Collider");
|
||||
|
||||
colliderGo.layer = StaticCullingPreferences.Layer;
|
||||
colliderGo.transform.parent = _context.transform;
|
||||
colliderGo.transform.localPosition = Vector3.zero;
|
||||
colliderGo.transform.localEulerAngles = Vector3.zero;
|
||||
colliderGo.transform.localScale = Vector3.one;
|
||||
|
||||
_collider = colliderGo.AddComponent<MeshCollider>();
|
||||
_collider.sharedMesh = mesh;
|
||||
}
|
||||
|
||||
public void ClearAfterBaking()
|
||||
{
|
||||
if (_collider != null)
|
||||
UnityEngine.Object.DestroyImmediate(_collider.gameObject);
|
||||
}
|
||||
|
||||
|
||||
private bool AllMaterialsIsTransarent(GameObject context)
|
||||
{
|
||||
MeshRenderer renderer = context.GetComponent<MeshRenderer>();
|
||||
|
||||
if (renderer == null)
|
||||
return false;
|
||||
|
||||
Material[] materials = renderer.sharedMaterials;
|
||||
|
||||
if (materials == null || materials.Length == 0)
|
||||
return false;
|
||||
|
||||
bool allTransparent = true;
|
||||
|
||||
foreach (var material in materials)
|
||||
{
|
||||
if (material == null)
|
||||
continue;
|
||||
|
||||
if (material.renderQueue != (int)RenderQueue.Transparent)
|
||||
allTransparent = false;
|
||||
}
|
||||
|
||||
return allTransparent;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,46 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public interface IVisibilityData
|
||||
{
|
||||
void SetVisible(CullingTarget[] allTargets);
|
||||
}
|
||||
|
||||
public class VisibilityData : IVisibilityData
|
||||
{
|
||||
[SerializeField]
|
||||
private int[] _indexes;
|
||||
|
||||
public VisibilityData(ICollection<int> indexes)
|
||||
{
|
||||
_indexes = indexes.ToArray();
|
||||
}
|
||||
|
||||
public void SetVisible(CullingTarget[] allTargets)
|
||||
{
|
||||
for (int i = 0; i < _indexes.Length; i++)
|
||||
allTargets[_indexes[i]].SetVisible();
|
||||
}
|
||||
}
|
||||
|
||||
public class CompactVisibilityData : IVisibilityData
|
||||
{
|
||||
[SerializeField]
|
||||
private ushort[] _indexes;
|
||||
|
||||
public CompactVisibilityData(ICollection<int> indexes)
|
||||
{
|
||||
_indexes = indexes.Select(i => (ushort)i).ToArray();
|
||||
}
|
||||
|
||||
public void SetVisible(CullingTarget[] allTargets)
|
||||
{
|
||||
for (int i = 0; i < _indexes.Length; i++)
|
||||
allTargets[_indexes[i]].SetVisible();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,129 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class VisibilityTree : BinaryTree<VisibilityTreeNode, Vector3>
|
||||
{
|
||||
[field: SerializeField]
|
||||
public CullingTarget[] CullingTargets { get; private set; }
|
||||
|
||||
|
||||
public VisibilityTree(float cellSize)
|
||||
: base(cellSize)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public void SetTargets(CullingTarget[] targets)
|
||||
{
|
||||
CullingTargets = targets;
|
||||
}
|
||||
|
||||
public void Optimize()
|
||||
{
|
||||
Optimize(RootInternal);
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
ApplyData(RootInternal);
|
||||
}
|
||||
|
||||
public void SetVisible(Vector3 point, float includeCellsCount)
|
||||
{
|
||||
float radius = includeCellsCount * CellSize / 2f;
|
||||
|
||||
SetVisibleInternal(RootInternal, point, radius * radius);
|
||||
}
|
||||
|
||||
public void DrawCellsGizmo(Vector3 point, float cellsRadius)
|
||||
{
|
||||
float radius = cellsRadius * CellSize / 2f;
|
||||
|
||||
DrawCellsGizmoInternal(RootInternal, point, radius * radius);
|
||||
}
|
||||
|
||||
|
||||
private void SetVisibleInternal(VisibilityTreeNode node, Vector3 point, float sqrRadius)
|
||||
{
|
||||
if (node.Bounds.SqrDistance(point) > sqrRadius)
|
||||
return;
|
||||
|
||||
node.SetVisible();
|
||||
|
||||
if (node.HasChilds)
|
||||
{
|
||||
SetVisibleInternal(node.Left, point, sqrRadius);
|
||||
SetVisibleInternal(node.Right, point, sqrRadius);
|
||||
}
|
||||
}
|
||||
|
||||
private void Optimize(VisibilityTreeNode current)
|
||||
{
|
||||
if (current.IsLeaf)
|
||||
return;
|
||||
|
||||
if (current.HasChilds)
|
||||
{
|
||||
Optimize(current.Left);
|
||||
Optimize(current.Right);
|
||||
}
|
||||
|
||||
current.RemoveDuplicatesFromChilds();
|
||||
}
|
||||
|
||||
private void ApplyData(VisibilityTreeNode current)
|
||||
{
|
||||
current.ApplyData();
|
||||
|
||||
if (current.HasChilds)
|
||||
{
|
||||
ApplyData(current.Left);
|
||||
ApplyData(current.Right);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawCellsGizmoInternal(VisibilityTreeNode node, Vector3 point, float sqrRadius)
|
||||
{
|
||||
if (node.Bounds.SqrDistance(point) > sqrRadius)
|
||||
return;
|
||||
|
||||
if (node.IsLeaf)
|
||||
{
|
||||
Gizmos.color = Color.blue;
|
||||
Gizmos.DrawWireCube(node.Center, node.Size);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (node.HasChilds)
|
||||
{
|
||||
DrawCellsGizmoInternal(node.Left, point, sqrRadius);
|
||||
DrawCellsGizmoInternal(node.Right, point, sqrRadius);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
protected override Bounds GetBounds(Vector3 point)
|
||||
{
|
||||
return new Bounds(point, Vector3.one * 0.1f);
|
||||
}
|
||||
|
||||
protected override VisibilityTreeNode CreateNode(Vector3 center, Vector3 size, bool isLeaf)
|
||||
{
|
||||
return new VisibilityTreeNode(this, center, size, isLeaf);
|
||||
}
|
||||
|
||||
protected override void SetChildsToNode(VisibilityTreeNode parent, VisibilityTreeNode leftChild, VisibilityTreeNode rightChild)
|
||||
{
|
||||
parent.SetChilds(leftChild, rightChild);
|
||||
}
|
||||
|
||||
protected override void AddDataToNode(VisibilityTreeNode node, Vector3 point)
|
||||
{
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,125 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Static
|
||||
{
|
||||
public class VisibilityTreeNode : BinaryTreeNode
|
||||
{
|
||||
public VisibilityTreeNode Left
|
||||
{
|
||||
get
|
||||
{
|
||||
return _left;
|
||||
}
|
||||
}
|
||||
public VisibilityTreeNode Right
|
||||
{
|
||||
get
|
||||
{
|
||||
return _right;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeReference]
|
||||
private VisibilityTree _tree;
|
||||
|
||||
[SerializeReference]
|
||||
private VisibilityTreeNode _left;
|
||||
|
||||
[SerializeReference]
|
||||
private VisibilityTreeNode _right;
|
||||
|
||||
[SerializeReference]
|
||||
private IVisibilityData _visibilityData;
|
||||
|
||||
private HashSet<int> _uniqTargets;
|
||||
|
||||
|
||||
public VisibilityTreeNode(VisibilityTree tree, Vector3 center, Vector3 size, bool isLeaf)
|
||||
: base(center, size, isLeaf)
|
||||
{
|
||||
_tree = tree;
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetLeft()
|
||||
{
|
||||
return Left;
|
||||
}
|
||||
|
||||
public override BinaryTreeNode GetRight()
|
||||
{
|
||||
return Right;
|
||||
}
|
||||
|
||||
public void SetChilds(VisibilityTreeNode left, VisibilityTreeNode right)
|
||||
{
|
||||
_left = left;
|
||||
_right = right;
|
||||
}
|
||||
|
||||
public void AddVisibleCullingTarget(int targetIndex)
|
||||
{
|
||||
if (_uniqTargets == null)
|
||||
_uniqTargets = new HashSet<int>();
|
||||
|
||||
_uniqTargets.Add(targetIndex);
|
||||
}
|
||||
|
||||
public void RemoveDuplicatesFromChilds()
|
||||
{
|
||||
if (!HasChilds)
|
||||
return;
|
||||
|
||||
HashSet<int> leftTargets = Left._uniqTargets;
|
||||
HashSet<int> rightTargets = Right._uniqTargets;
|
||||
|
||||
if (leftTargets == null || rightTargets == null)
|
||||
return;
|
||||
|
||||
foreach (var target in leftTargets)
|
||||
{
|
||||
if (rightTargets.Contains(target))
|
||||
{
|
||||
AddVisibleCullingTarget(target);
|
||||
}
|
||||
}
|
||||
|
||||
if (_uniqTargets != null)
|
||||
{
|
||||
foreach (var target in _uniqTargets)
|
||||
{
|
||||
leftTargets.Remove(target);
|
||||
rightTargets.Remove(target);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ApplyData()
|
||||
{
|
||||
if (_uniqTargets == null || _uniqTargets.Count == 0)
|
||||
return;
|
||||
|
||||
if (_uniqTargets.Any(t => t >= 65535))
|
||||
_visibilityData = new VisibilityData(_uniqTargets);
|
||||
else
|
||||
_visibilityData = new CompactVisibilityData(_uniqTargets);
|
||||
}
|
||||
|
||||
public void SetVisible()
|
||||
{
|
||||
if (_visibilityData == null)
|
||||
return;
|
||||
|
||||
try
|
||||
{
|
||||
_visibilityData.SetVisible(_tree.CullingTargets);
|
||||
}
|
||||
catch(MissingReferenceException)
|
||||
{
|
||||
Debug.Log("Looks like some of baked objects was destroyed. Rebake scene");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,58 @@
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using NGS.AdvancedCullingSystem.Dynamic;
|
||||
|
||||
namespace NGS.AdvancedCullingSystem.Utils
|
||||
{
|
||||
public class DC_ActivateNearObjects : MonoBehaviour
|
||||
{
|
||||
[SerializeField]
|
||||
private bool _drawGizmos = false;
|
||||
|
||||
[Space]
|
||||
|
||||
[Min(0.1f)]
|
||||
[SerializeField]
|
||||
private float _radius = 20f;
|
||||
|
||||
[Min(1)]
|
||||
[SerializeField]
|
||||
private int _maxObjectsCount = 100;
|
||||
|
||||
private IReadOnlyDictionary<Collider, IHitable> _hitablesDic;
|
||||
private int _layer;
|
||||
|
||||
private Collider[] _hits;
|
||||
|
||||
|
||||
private void Start()
|
||||
{
|
||||
_hitablesDic = DC_Controller.GetHitables();
|
||||
_layer = LayerMask.GetMask(DC_Controller.GetCullingLayerName());
|
||||
_hits = new Collider[_maxObjectsCount];
|
||||
}
|
||||
|
||||
private void LateUpdate()
|
||||
{
|
||||
int hitsCount = Physics.OverlapSphereNonAlloc(transform.position, _radius, _hits, _layer);
|
||||
|
||||
for (int i = 0; i < hitsCount; i++)
|
||||
{
|
||||
Collider collider = _hits[i];
|
||||
|
||||
if (_hitablesDic.TryGetValue(collider, out IHitable hitable))
|
||||
hitable.OnHit();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDrawGizmos()
|
||||
{
|
||||
if (!_drawGizmos)
|
||||
return;
|
||||
|
||||
Gizmos.color = Color.white;
|
||||
Gizmos.DrawWireSphere(transform.position, _radius);
|
||||
}
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user