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