#define RENDER_PIPELINE
using System.Collections.Generic;
using System.Linq;
using UnityEngine;
#if RENDER_PIPELINE
using UnityEngine.Rendering;
#endif
namespace DunGen
{
[AddComponentMenu("DunGen/Culling/Adjacent Room Culling (Multi-Camera)")]
public class BasicRoomCullingCamera : MonoBehaviour
{
#region Helpers
protected struct RendererData
{
public Renderer Renderer;
public bool Enabled;
public RendererData(Renderer renderer, bool enabled)
{
Renderer = renderer;
Enabled = enabled;
}
}
protected struct LightData
{
public Light Light;
public bool Enabled;
public LightData(Light light, bool enabled)
{
Light = light;
Enabled = enabled;
}
}
protected struct ReflectionProbeData
{
public ReflectionProbe Probe;
public bool Enabled;
public ReflectionProbeData(ReflectionProbe probe, bool enabled)
{
Probe = probe;
Enabled = enabled;
}
}
#endregion
///
/// Determines how deep a tile must be before it's culled.
/// 0: Only the current tile is visible
/// 1: Only the current tile and all of its immediate neighbours are visible
/// 2: Same as 1 but all of their neighbours are also visible
/// ... etc
///
public int AdjacentTileDepth = 1;
///
/// If true, any tiles behind a closed door will be culled even if they're in range
///
public bool CullBehindClosedDoors = true;
///
/// The target object to use (defaults to this object). For third-person games,
/// this would be the player character
///
public Transform TargetOverride = null;
///
/// Is culling enabled in the scene view of the editor?
///
public bool CullInEditor = false;
///
/// Should we cull light components?
///
public bool CullLights = true;
public bool IsReady { get; protected set; }
protected bool isCulling;
protected bool isDirty;
protected DungeonGenerator generator;
protected Tile currentTile;
protected List allTiles;
protected List allDoors;
protected List visibleTiles;
protected Dictionary> rendererVisibilities = new Dictionary>();
protected Dictionary> lightVisibilities = new Dictionary>();
protected Dictionary> reflectionProbeVisibilities = new Dictionary>();
protected Dictionary> doorRendererVisibilities = new Dictionary>();
protected virtual void Awake()
{
var runtimeDungeon = UnityUtil.FindObjectByType();
if (runtimeDungeon != null)
{
generator = runtimeDungeon.Generator;
generator.OnGenerationStatusChanged += OnDungeonGenerationStatusChanged;
if (generator.Status == GenerationStatus.Complete)
SetDungeon(generator.CurrentDungeon);
}
}
protected virtual void OnDestroy()
{
if (generator != null)
generator.OnGenerationStatusChanged -= OnDungeonGenerationStatusChanged;
}
protected virtual void OnEnable()
{
#if RENDER_PIPELINE
if (RenderPipelineManager.currentPipeline != null)
{
RenderPipelineManager.beginCameraRendering += OnBeginCameraRendering;
RenderPipelineManager.endCameraRendering += OnEndCameraRendering;
return;
}
#endif
Camera.onPreCull += EnableCulling;
Camera.onPostRender += DisableCulling;
}
protected virtual void OnDisable()
{
#if RENDER_PIPELINE
RenderPipelineManager.beginCameraRendering -= OnBeginCameraRendering;
RenderPipelineManager.endCameraRendering -= OnEndCameraRendering;
#endif
Camera.onPreCull -= EnableCulling;
Camera.onPostRender -= DisableCulling;
}
#if RENDER_PIPELINE
private void OnBeginCameraRendering(ScriptableRenderContext context, Camera camera)
{
EnableCulling(camera);
}
private void OnEndCameraRendering(ScriptableRenderContext context, Camera camera)
{
DisableCulling(camera);
}
#endif
protected virtual void OnDungeonGenerationStatusChanged(DungeonGenerator generator, GenerationStatus status)
{
if (status == GenerationStatus.Complete)
SetDungeon(generator.CurrentDungeon);
else if (status == GenerationStatus.Failed)
ClearDungeon();
}
protected virtual void EnableCulling(Camera camera)
{
SetCullingEnabled(camera, true);
}
protected virtual void DisableCulling(Camera camera)
{
SetCullingEnabled(camera, false);
}
protected void SetCullingEnabled(Camera camera, bool enabled)
{
if (!IsReady || camera == null)
return;
bool cullThisCameras = camera.gameObject == gameObject;
#if UNITY_EDITOR
if (CullInEditor)
{
var sceneCameras = UnityEditor.SceneView.GetAllSceneCameras();
if (sceneCameras != null && sceneCameras.Contains(camera))
cullThisCameras = true;
}
#endif
if (cullThisCameras)
SetIsCulling(enabled);
}
protected virtual void LateUpdate()
{
if (!IsReady)
return;
Transform target = (TargetOverride != null) ? TargetOverride : transform;
bool hasPositionChanged = currentTile == null || !currentTile.Bounds.Contains(target.position);
if (hasPositionChanged)
{
// Update current tile
foreach (var tile in allTiles)
{
if (tile == null)
continue;
if (tile.Bounds.Contains(target.position))
{
currentTile = tile;
break;
}
}
isDirty = true;
}
if (isDirty)
{
UpdateCulling();
// Update the list of renderers for tiles about to be culled
foreach (var tile in allTiles)
if (!visibleTiles.Contains(tile))
UpdateRendererList(tile);
}
}
protected void UpdateRendererList(Tile tile)
{
// Renderers
List renderers;
if (!rendererVisibilities.TryGetValue(tile, out renderers))
rendererVisibilities[tile] = renderers = new List();
else
renderers.Clear();
foreach (var renderer in tile.GetComponentsInChildren())
renderers.Add(new RendererData(renderer, renderer.enabled));
// Lights
if (CullLights)
{
List lights;
if (!lightVisibilities.TryGetValue(tile, out lights))
lightVisibilities[tile] = lights = new List();
else
lights.Clear();
foreach (var light in tile.GetComponentsInChildren())
lights.Add(new LightData(light, light.enabled));
}
// Reflection Probes
List probes;
if (!reflectionProbeVisibilities.TryGetValue(tile, out probes))
reflectionProbeVisibilities[tile] = probes = new List();
else
probes.Clear();
foreach (var probe in tile.GetComponentsInChildren())
probes.Add(new ReflectionProbeData(probe, probe.enabled));
}
protected void SetIsCulling(bool isCulling)
{
this.isCulling = isCulling;
for (int i = 0; i < allTiles.Count; i++)
{
var tile = allTiles[i];
if (visibleTiles.Contains(tile))
continue;
// Renderers
List rendererData;
if (rendererVisibilities.TryGetValue(tile, out rendererData))
{
foreach (var r in rendererData) // Removed null check on r.Renderer because it was expensive. Shouldn't be necessary
r.Renderer.enabled = (isCulling) ? false : r.Enabled;
}
// Lights
if (CullLights)
{
List lightData;
if (lightVisibilities.TryGetValue(tile, out lightData))
{
foreach (var l in lightData)
l.Light.enabled = (isCulling) ? false : l.Enabled;
}
}
// Reflection Probes
List probeData;
if (reflectionProbeVisibilities.TryGetValue(tile, out probeData))
{
foreach (var p in probeData)
p.Probe.enabled = (isCulling) ? false : p.Enabled;
}
}
foreach (var door in allDoors)
{
bool isVisible = visibleTiles.Contains(door.DoorwayA.Tile) || visibleTiles.Contains(door.DoorwayB.Tile);
List rendererData;
if (doorRendererVisibilities.TryGetValue(door, out rendererData))
{
foreach (var r in rendererData)
r.Renderer.enabled = (isCulling) ? isVisible : r.Enabled;
}
}
}
protected void UpdateCulling()
{
isDirty = false;
visibleTiles.Clear();
if (currentTile != null)
visibleTiles.Add(currentTile);
int processTileStart = 0;
// Add neighbours down to RoomDepth (0 = just tiles containing characters, 1 = plus adjacent tiles, etc)
for (int i = 0; i < AdjacentTileDepth; i++)
{
int processTileEnd = visibleTiles.Count;
for (int t = processTileStart; t < processTileEnd; t++)
{
var tile = visibleTiles[t];
// Get all connections to adjacent tiles
foreach (var doorway in tile.UsedDoorways)
{
var adjacentTile = doorway.ConnectedDoorway.Tile;
// Skip the tile if it's already visible
if (visibleTiles.Contains(adjacentTile))
continue;
// No need to add adjacent rooms to the visible list when the door between them is closed
if (CullBehindClosedDoors)
{
var door = doorway.DoorComponent;
if (door != null && door.ShouldCullBehind)
continue;
}
visibleTiles.Add(adjacentTile);
}
}
processTileStart = processTileEnd;
}
}
public void SetDungeon(Dungeon dungeon)
{
if (IsReady)
ClearDungeon();
if (dungeon == null)
return;
allTiles = new List(dungeon.AllTiles);
allDoors = new List(GetAllDoorsInDungeon(dungeon));
visibleTiles = new List(allTiles.Count);
doorRendererVisibilities.Clear();
foreach (var door in allDoors)
{
var renderers = new List();
doorRendererVisibilities[door] = renderers;
foreach (var renderer in door.GetComponentsInChildren(true))
renderers.Add(new RendererData(renderer, renderer.enabled));
door.OnDoorStateChanged += OnDoorStateChanged;
}
IsReady = true;
isDirty = true;
}
protected IEnumerable GetAllDoorsInDungeon(Dungeon dungeon)
{
foreach (var doorObj in dungeon.Doors)
{
if (doorObj == null)
continue;
var door = doorObj.GetComponent();
if (door != null)
yield return door;
}
}
protected virtual void ClearDungeon()
{
foreach (var door in allDoors)
door.OnDoorStateChanged -= OnDoorStateChanged;
IsReady = false;
}
protected virtual void OnDoorStateChanged(Door door, bool isOpen)
{
isDirty = true;
}
}
}