You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 

415 lines
14 KiB

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<BoxCollider>(); }
Bounds boundingBox = m_boxCollider.bounds;
FogVolumePrimitive[] primitives = FindObjectsOfType<FogVolumePrimitive>();
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<BoxCollider>();
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<Renderer>();
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<Renderer>();
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<BoxCollider>(); }
_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<FogVolume>();
m_fogVolumeData = FindObjectOfType<FogVolumeData>();
m_camera = null;
m_boxCollider = null;
CurrentPrimitiveCount = 0;
if (m_primitives == null)
{
m_primitives = new List<PrimitiveData>(MaxPrimitivesCount);
m_primitivesInFrustum = new List<PrimitiveData>();
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<PrimitiveData> m_primitives = null;
private List<PrimitiveData> 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;
}
}
}