This commit is contained in:
CortexCore
2024-11-13 17:47:45 +08:00
parent c4af12acd7
commit 416e3322db
208 changed files with 2591757 additions and 1497 deletions

View File

@@ -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
}

View File

@@ -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);
}
}

View File

@@ -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());
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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>
{
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -0,0 +1,11 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
namespace NGS.AdvancedCullingSystem.Dynamic
{
public interface IHitable
{
void OnHit();
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}

View File

@@ -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>
{
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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);
}
}
}
}
}
}

View File

@@ -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
}
}

View File

@@ -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
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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
}
}

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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()
{
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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();
}
}
}

View File

@@ -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)
{
}
}
}

View File

@@ -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");
}
}
}
}

View File

@@ -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);
}
}
}