using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.Assertions; public class FogVolumePrimitiveManager : MonoBehaviour { public int CurrentPrimitiveCount { get; private set; } public int VisiblePrimitiveCount { get; private set; } public bool AlreadyUsesTransformForPoI { get { return m_pointOfInterestTf != null; } } public void FindPrimitivesInFogVolume() { CurrentPrimitiveCount = 0; VisiblePrimitiveCount = 0; m_primitives.Clear(); m_primitivesInFrustum.Clear(); for (int i = 0; i < MaxPrimitivesCount; i++) { m_primitives.Add(new PrimitiveData()); m_primitivesInFrustum.Add(new PrimitiveData()); } if (m_boxCollider == null) { m_boxCollider = gameObject.GetComponent(); } Bounds boundingBox = m_boxCollider.bounds; FogVolumePrimitive[] primitives = FindObjectsOfType(); for (int i = 0; i < primitives.Length; i++) { var data = primitives[i]; if (boundingBox.Intersects(data.Bounds)) { if (data.BoxColl != null) { data.Type = EFogVolumePrimitiveType.Box; } else if (data.SphereColl != null) { data.Type = EFogVolumePrimitiveType.Sphere; } else { data.BoxColl = data.GetTransform.gameObject.AddComponent(); data.Type = EFogVolumePrimitiveType.Box; } if (data.Type == EFogVolumePrimitiveType.Box) { AddPrimitiveBox(data); } else if (data.Type == EFogVolumePrimitiveType.Sphere) { AddPrimitiveSphere(data); } } } } public bool AddPrimitiveBox(FogVolumePrimitive _box) { Assert.IsTrue(CurrentPrimitiveCount < MaxPrimitivesCount, "The maximum amount of primitives is already reached!"); int index = _FindFirstFreePrimitive(); if (index != InvalidIndex) { PrimitiveData data = m_primitives[index]; CurrentPrimitiveCount++; data.PrimitiveType = EFogVolumePrimitiveType.Box; data.Transform = _box.transform; data.Renderer = _box.GetComponent(); data.Primitive = _box; data.Bounds = new Bounds(data.Transform.position, _box.GetPrimitiveScale); return true; } return false; } public bool AddPrimitiveSphere(FogVolumePrimitive _sphere) { Assert.IsTrue(CurrentPrimitiveCount < MaxPrimitivesCount, "The maximum amount of primitives is already reached!"); int index = _FindFirstFreePrimitive(); if (index != InvalidIndex) { PrimitiveData data = m_primitives[index]; CurrentPrimitiveCount++; data.PrimitiveType = EFogVolumePrimitiveType.Sphere; data.Transform = _sphere.transform; data.Renderer = _sphere.GetComponent(); data.Primitive = _sphere; data.Bounds = new Bounds(data.Transform.position, _sphere.GetPrimitiveScale); return true; } return false; } public bool RemovePrimitive(Transform _primitiveToRemove) { int count = m_primitives.Count; for (int i = 0; i < count; i++) { PrimitiveData data = m_primitives[i]; if (ReferenceEquals(m_primitives[i].Transform, _primitiveToRemove)) { data.Reset(); CurrentPrimitiveCount--; return true; } } return false; } //============================================================================================= /** * @brief Sets the point of interest to a fixed position. * * @param _pointOfInterest The point that will be used for prioritizing which primitives need * to be rendered. * *********************************************************************************************/ public void SetPointOfInterest(Vector3 _pointOfInterest) { m_pointOfInterestTf = null; m_pointOfInterest = _pointOfInterest; } //============================================================================================= /** * @brief Sets the point of interest to the specified transform. * * The point of interest will be updated from the position of the transform. It is therefore * not necessary to call this method more than once. * *********************************************************************************************/ public void SetPointOfInterest(Transform _pointOfInterest) { Assert.IsTrue(_pointOfInterest != null, "_pointOfInterest must not be null!"); m_pointOfInterestTf = _pointOfInterest; } public void OnDrawGizmos() { hideFlags = HideFlags.HideInInspector; } public void ManualUpdate(ref Plane[] _frustumPlanes) { m_camera = m_fogVolumeData != null ? m_fogVolumeData.GameCamera : null; if (m_camera == null) { return; } FrustumPlanes = _frustumPlanes; if (m_boxCollider == null) { m_boxCollider = m_fogVolume.GetComponent(); } _UpdateBounds(); _FindPrimitivesInFrustum(); if (m_primitivesInFrustum.Count > MaxVisiblePrimitives) { _SortPrimitivesInFrustum(); } _PrepareShaderArrays(); } public void SetVisibility(bool _enabled) { int count = m_primitives.Count; for (int i = 0; i < count; i++) { if (m_primitives[i].Renderer != null) { m_primitives[i].Renderer.enabled = _enabled; } } } public void Initialize() { m_fogVolume = gameObject.GetComponent(); m_fogVolumeData = FindObjectOfType(); m_camera = null; m_boxCollider = null; CurrentPrimitiveCount = 0; if (m_primitives == null) { m_primitives = new List(MaxPrimitivesCount); m_primitivesInFrustum = new List(); for (int i = 0; i < MaxPrimitivesCount; i++) { m_primitives.Add(new PrimitiveData()); m_primitivesInFrustum.Add(new PrimitiveData()); } } } public void Deinitialize() { VisiblePrimitiveCount = 0; } public Vector4[] GetPrimitivePositionArray() { return m_primitivePos; } public Vector4[] GetPrimitiveScaleArray() { return m_primitiveScale; } public Matrix4x4[] GetPrimitiveTransformArray() { return m_primitiveTf; } public Vector4[] GetPrimitiveDataArray() { return m_primitiveData; } private void _UpdateBounds() { int count = m_primitives.Count; for (int i = 0; i < count; i++) { PrimitiveData data = m_primitives[i]; if (data.PrimitiveType == EFogVolumePrimitiveType.None) { continue; } if (data.Primitive == null) { RemovePrimitive(data.Transform); continue; } if (data.PrimitiveType == EFogVolumePrimitiveType.Box) { // Check if collider was removed by the user and add it again. if (data.Primitive.BoxColl == null) { Debug.LogWarning("FogVolumePrimitive requires a collider.\nThe collider will be automatically created."); data.Primitive.AddColliderIfNeccessary(EFogVolumePrimitiveType.Box); } data.Bounds = data.Primitive.BoxColl.bounds; } else if (data.PrimitiveType == EFogVolumePrimitiveType.Sphere) { // Check if collider was removed by the user and add it again. if (data.Primitive.SphereColl == null) { Debug.LogWarning("FogVolumePrimitive requires a collider.\nThe collider will be automatically created."); data.Primitive.AddColliderIfNeccessary(EFogVolumePrimitiveType.Sphere); } data.Bounds = data.Primitive.SphereColl.bounds; } } } private int _FindFirstFreePrimitive() { if (CurrentPrimitiveCount < MaxPrimitivesCount) { int count = m_primitives.Count; for (int i = 0; i < count; i++) { if (m_primitives[i].PrimitiveType == EFogVolumePrimitiveType.None) { return i; } } } return InvalidIndex; } private void _FindPrimitivesInFrustum() { m_inFrustumCount = 0; Vector3 cameraPos = m_camera.gameObject.transform.position; int count = m_primitives.Count; for (int i = 0; i < count; i++) { PrimitiveData primitive = m_primitives[i]; if (primitive.Transform == null) { primitive.PrimitiveType = EFogVolumePrimitiveType.None; } if (primitive.PrimitiveType == EFogVolumePrimitiveType.None) { continue; } if (primitive.Primitive.IsPersistent) { Vector3 pos = primitive.Transform.position; primitive.SqDistance = (pos - m_pointOfInterest).sqrMagnitude; primitive.Distance2Camera = (pos - cameraPos).magnitude; m_primitivesInFrustum[m_inFrustumCount++] = primitive; } else if (GeometryUtility.TestPlanesAABB(FrustumPlanes, m_primitives[i].Bounds)) { Vector3 pos = primitive.Transform.position; primitive.SqDistance = (pos - m_pointOfInterest).sqrMagnitude; primitive.Distance2Camera = (pos - cameraPos).magnitude; m_primitivesInFrustum[m_inFrustumCount++] = primitive; } } } private void _SortPrimitivesInFrustum() { bool finishedSorting = false; do { finishedSorting = true; for (int i = 0; i < m_inFrustumCount - 1; i++) { if (m_primitivesInFrustum[i].SqDistance > m_primitivesInFrustum[i + 1].SqDistance) { PrimitiveData tempData = m_primitivesInFrustum[i]; m_primitivesInFrustum[i] = m_primitivesInFrustum[i + 1]; m_primitivesInFrustum[i + 1] = tempData; finishedSorting = false; } } } while (!finishedSorting); } private void _PrepareShaderArrays() { VisiblePrimitiveCount = 0; Quaternion fogVolumeRotation = m_fogVolume.gameObject.transform.rotation; for (int i = 0; i < MaxVisiblePrimitives; i++) { if (i >= m_inFrustumCount) { break; } PrimitiveData data = m_primitivesInFrustum[i]; Vector3 position = data.Transform.position; m_primitivePos[i] = gameObject.transform.InverseTransformPoint(position); m_primitiveTf[i].SetTRS(position, Quaternion.Inverse(data.Transform.rotation) * fogVolumeRotation, Vector3.one); m_primitiveScale[i] = data.Primitive.GetPrimitiveScale; m_primitiveData[i] = new Vector4(data.PrimitiveType == EFogVolumePrimitiveType.Box ? 0.5f : 1.5f, data.Primitive.IsSubtractive ? 1.5f : 0.5f, 0.0f, 0.0f); VisiblePrimitiveCount++; } } #if UNITY_EDITOR private void Update() { hideFlags = HideFlags.HideInInspector; } #endif private FogVolume m_fogVolume = null; private FogVolumeData m_fogVolumeData = null; private Camera m_camera = null; private BoxCollider m_boxCollider = null; private Transform m_pointOfInterestTf = null; private Vector3 m_pointOfInterest = Vector3.zero; private List m_primitives = null; private List m_primitivesInFrustum = null; private int m_inFrustumCount = 0; private Plane[] FrustumPlanes = null; private readonly Vector4[] m_primitivePos = new Vector4[MaxVisiblePrimitives]; private readonly Vector4[] m_primitiveScale = new Vector4[MaxVisiblePrimitives]; private readonly Matrix4x4[] m_primitiveTf = new Matrix4x4[MaxVisiblePrimitives]; private readonly Vector4[] m_primitiveData = new Vector4[MaxVisiblePrimitives]; private const int InvalidIndex = -1; private const int MaxVisiblePrimitives = 20; private const int MaxPrimitivesCount = 1000; protected class PrimitiveData { public PrimitiveData() { PrimitiveType = EFogVolumePrimitiveType.None; Primitive = null; Transform = null; Renderer = null; SqDistance = 0.0f; Distance2Camera = 0.0f; Bounds = new Bounds(); } public EFogVolumePrimitiveType PrimitiveType { get; set; } public FogVolumePrimitive Primitive { get; set; } public Transform Transform { get; set; } public Renderer Renderer { get; set; } public float SqDistance { get; set; } public float Distance2Camera { get; set; } public Bounds Bounds { get; set; } public void Reset() { PrimitiveType = EFogVolumePrimitiveType.None; Primitive = null; Transform = null; Renderer = null; SqDistance = 0.0f; Distance2Camera = 0.0f; } } }