|
|
|
|
|
|
using System; |
|
|
|
using System.Collections.Generic; |
|
|
|
using System.Diagnostics; |
|
|
|
using UnityObject = UnityEngine.Object; |
|
|
|
|
|
|
|
static object s_LockObj = new Object(); |
|
|
|
static object s_LockObj = new UnityObject(); |
|
|
|
|
|
|
|
public static VolumeManager instance |
|
|
|
{ |
|
|
|
|
|
|
// Max amount of layers available in Unity
|
|
|
|
const int k_MaxLayerCount = 32; |
|
|
|
|
|
|
|
// List of all volumes (sorted by priority) per layer
|
|
|
|
readonly List<Volume>[] m_Volumes; |
|
|
|
// Cached lists of all volumes (sorted by priority) by layer mask
|
|
|
|
readonly Dictionary<LayerMask, List<Volume>> m_SortedVolumes; |
|
|
|
|
|
|
|
// Holds all the registered volumes
|
|
|
|
readonly List<Volume> m_Volumes; |
|
|
|
// Keep track of sorting states for all layers
|
|
|
|
readonly bool[] m_SortNeeded; |
|
|
|
// Keep track of sorting states for layer masks
|
|
|
|
readonly Dictionary<LayerMask, bool> m_SortNeeded; |
|
|
|
|
|
|
|
// Internal state of all component types
|
|
|
|
readonly Dictionary<Type, VolumeComponent> m_Components; |
|
|
|
|
|
|
|
|
|
|
VolumeManager() |
|
|
|
{ |
|
|
|
m_Volumes = new List<Volume>[k_MaxLayerCount]; |
|
|
|
m_SortNeeded = new bool[k_MaxLayerCount]; |
|
|
|
m_SortedVolumes = new Dictionary<LayerMask, List<Volume>>(); |
|
|
|
m_Volumes = new List<Volume>(); |
|
|
|
m_SortNeeded = new Dictionary<LayerMask, bool>(); |
|
|
|
m_TempColliders = new List<Collider>(8); |
|
|
|
m_Components = new Dictionary<Type, VolumeComponent>(); |
|
|
|
m_ComponentsDefaultState = new List<VolumeComponent>(); |
|
|
|
|
|
|
|
|
|
|
public void Register(Volume volume, int layer) |
|
|
|
{ |
|
|
|
var volumes = m_Volumes[layer]; |
|
|
|
m_Volumes.Add(volume); |
|
|
|
if (volumes == null) |
|
|
|
// Look for existing cached layer masks and add it there if needed
|
|
|
|
foreach (var kvp in m_SortedVolumes) |
|
|
|
volumes = new List<Volume>(); |
|
|
|
m_Volumes[layer] = volumes; |
|
|
|
var mask = kvp.Key; |
|
|
|
|
|
|
|
if ((mask & (1 << layer)) != 0) |
|
|
|
kvp.Value.Add(volume); |
|
|
|
Assert.IsFalse(volumes.Contains(volume), "Volume has already been registered"); |
|
|
|
volumes.Add(volume); |
|
|
|
var volumes = m_Volumes[layer]; |
|
|
|
m_Volumes.Remove(volume); |
|
|
|
if (volumes == null) |
|
|
|
return; |
|
|
|
foreach (var kvp in m_SortedVolumes) |
|
|
|
{ |
|
|
|
var mask = kvp.Key; |
|
|
|
volumes.Remove(volume); |
|
|
|
// Skip layer masks this volume doesn't belong to
|
|
|
|
if ((mask & (1 << layer)) == 0) |
|
|
|
continue; |
|
|
|
|
|
|
|
kvp.Value.Remove(volume); |
|
|
|
} |
|
|
|
m_SortNeeded[layer] = true; |
|
|
|
|
|
|
|
foreach (var kvp in m_SortedVolumes) |
|
|
|
{ |
|
|
|
var mask = kvp.Key; |
|
|
|
|
|
|
|
if ((mask & (1 << layer)) != 0) |
|
|
|
m_SortNeeded[mask] = true; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
internal void UpdateVolumeLayer(Volume volume, int prevLayer, int newLayer) |
|
|
|
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Update the global state - should be called once per frame in the update loop before
|
|
|
|
// anything else
|
|
|
|
public void Update(Transform trigger, LayerMask layerMask) |
|
|
|
[Conditional("UNITY_EDITOR")] |
|
|
|
public void CheckBaseTypes() |
|
|
|
#if UNITY_EDITOR
|
|
|
|
// play mode -> re-create the world when bad things happen
|
|
|
|
if (m_ComponentsDefaultState == null |
|
|
|
|| (m_ComponentsDefaultState.Count > 0 && m_ComponentsDefaultState[0] == null)) |
|
|
|
{ |
|
|
|
if (m_ComponentsDefaultState == null || (m_ComponentsDefaultState.Count > 0 && m_ComponentsDefaultState[0] == null)) |
|
|
|
} |
|
|
|
else |
|
|
|
#endif
|
|
|
|
{ |
|
|
|
// Start by resetting the global state to default values
|
|
|
|
ReplaceData(m_ComponentsDefaultState); |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// Update the global state - should be called once per frame per transform/layer mask combo
|
|
|
|
// in the update loop before rendering
|
|
|
|
public void Update(Transform trigger, LayerMask layerMask) |
|
|
|
{ |
|
|
|
CheckBaseTypes(); |
|
|
|
|
|
|
|
// Start by resetting the global state to default values
|
|
|
|
ReplaceData(m_ComponentsDefaultState); |
|
|
|
// Do magic
|
|
|
|
int mask = layerMask.value; |
|
|
|
for (int i = 0; i < k_MaxLayerCount; i++) |
|
|
|
// Sort the cached volume list(s) for the given layer mask if needed and return it
|
|
|
|
var volumes = GrabVolumes(layerMask); |
|
|
|
|
|
|
|
// Traverse all volumes
|
|
|
|
foreach (var volume in volumes) |
|
|
|
// Skip layers not in the mask
|
|
|
|
if ((mask & (1 << i)) == 0) |
|
|
|
// Skip disabled volumes and volumes without any data or weight
|
|
|
|
if (!volume.enabled || volume.weight <= 0f) |
|
|
|
// Skip empty layers
|
|
|
|
var volumes = m_Volumes[i]; |
|
|
|
// Global volumes always have influence
|
|
|
|
if (volume.isGlobal) |
|
|
|
{ |
|
|
|
OverrideData(volume.components, Mathf.Clamp01(volume.weight)); |
|
|
|
continue; |
|
|
|
} |
|
|
|
|
|
|
|
if (onlyGlobal) |
|
|
|
continue; |
|
|
|
if (volumes == null) |
|
|
|
// If volume isn't global and has no collider, skip it as it's useless
|
|
|
|
var colliders = m_TempColliders; |
|
|
|
volume.GetComponents(colliders); |
|
|
|
if (colliders.Count == 0) |
|
|
|
// Sort the volume list if needed
|
|
|
|
if (m_SortNeeded[i]) |
|
|
|
{ |
|
|
|
SortByPriority(volumes); |
|
|
|
m_SortNeeded[i] = false; |
|
|
|
} |
|
|
|
// Find closest distance to volume, 0 means it's inside it
|
|
|
|
float closestDistanceSqr = float.PositiveInfinity; |
|
|
|
// Traverse all volumes
|
|
|
|
foreach (var volume in volumes) |
|
|
|
foreach (var collider in colliders) |
|
|
|
// Skip disabled volumes and volumes without any data or weight
|
|
|
|
if (!volume.enabled || volume.weight <= 0f) |
|
|
|
if (!collider.enabled) |
|
|
|
var components = volume.components; |
|
|
|
var closestPoint = collider.ClosestPoint(triggerPos); |
|
|
|
var d = (closestPoint - triggerPos).sqrMagnitude; |
|
|
|
// Global volumes always have influence
|
|
|
|
if (volume.isGlobal) |
|
|
|
{ |
|
|
|
OverrideData(components, Mathf.Clamp01(volume.weight)); |
|
|
|
continue; |
|
|
|
} |
|
|
|
if (d < closestDistanceSqr) |
|
|
|
closestDistanceSqr = d; |
|
|
|
} |
|
|
|
if (onlyGlobal) |
|
|
|
continue; |
|
|
|
colliders.Clear(); |
|
|
|
float blendDistSqr = volume.blendDistance * volume.blendDistance; |
|
|
|
// If volume isn't global and has no collider, skip it as it's useless
|
|
|
|
var colliders = m_TempColliders; |
|
|
|
volume.GetComponents(colliders); |
|
|
|
if (colliders.Count == 0) |
|
|
|
continue; |
|
|
|
// Volume has no influence, ignore it
|
|
|
|
// Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
|
|
|
|
// we can't use a >= comparison as blendDistSqr could be set to 0 in which
|
|
|
|
// case volume would have total influence
|
|
|
|
if (closestDistanceSqr > blendDistSqr) |
|
|
|
continue; |
|
|
|
// Find closest distance to volume, 0 means it's inside it
|
|
|
|
float closestDistanceSqr = float.PositiveInfinity; |
|
|
|
// Volume has influence
|
|
|
|
float interpFactor = 1f; |
|
|
|
foreach (var collider in colliders) |
|
|
|
{ |
|
|
|
if (!collider.enabled) |
|
|
|
continue; |
|
|
|
if (blendDistSqr > 0f) |
|
|
|
interpFactor = 1f - (closestDistanceSqr / blendDistSqr); |
|
|
|
var closestPoint = collider.ClosestPoint(triggerPos); |
|
|
|
var d = (closestPoint - triggerPos).sqrMagnitude; |
|
|
|
// No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
|
|
|
|
OverrideData(volume.components, interpFactor * Mathf.Clamp01(volume.weight)); |
|
|
|
} |
|
|
|
} |
|
|
|
if (d < closestDistanceSqr) |
|
|
|
closestDistanceSqr = d; |
|
|
|
} |
|
|
|
List<Volume> GrabVolumes(LayerMask mask) |
|
|
|
{ |
|
|
|
List<Volume> list; |
|
|
|
colliders.Clear(); |
|
|
|
float blendDistSqr = volume.blendDistance * volume.blendDistance; |
|
|
|
if (!m_SortedVolumes.TryGetValue(mask, out list)) |
|
|
|
{ |
|
|
|
// New layer mask detected, create a new list and cache all the volumes that belong
|
|
|
|
// to this mask in it
|
|
|
|
list = new List<Volume>(); |
|
|
|
// Volume has no influence, ignore it
|
|
|
|
// Note: Volume doesn't do anything when `closestDistanceSqr = blendDistSqr` but
|
|
|
|
// we can't use a >= comparison as blendDistSqr could be set to 0 in which
|
|
|
|
// case volume would have total influence
|
|
|
|
if (closestDistanceSqr > blendDistSqr) |
|
|
|
foreach (var volume in m_Volumes) |
|
|
|
{ |
|
|
|
if ((mask & (1 << volume.gameObject.layer)) == 0) |
|
|
|
// Volume has influence
|
|
|
|
float interpFactor = 1f; |
|
|
|
list.Add(volume); |
|
|
|
m_SortNeeded[mask] = true; |
|
|
|
} |
|
|
|
if (blendDistSqr > 0f) |
|
|
|
interpFactor = 1f - (closestDistanceSqr / blendDistSqr); |
|
|
|
m_SortedVolumes.Add(mask, list); |
|
|
|
} |
|
|
|
// No need to clamp01 the interpolation factor as it'll always be in [0;1[ range
|
|
|
|
OverrideData(components, interpFactor * Mathf.Clamp01(volume.weight)); |
|
|
|
} |
|
|
|
// Check sorting state
|
|
|
|
bool sortNeeded; |
|
|
|
if (m_SortNeeded.TryGetValue(mask, out sortNeeded) && sortNeeded) |
|
|
|
{ |
|
|
|
m_SortNeeded[mask] = false; |
|
|
|
SortByPriority(list); |
|
|
|
|
|
|
|
return list; |
|
|
|
} |
|
|
|
|
|
|
|
// Stable insertion sort. Faster than List<T>.Sort() for our needs.
|
|
|
|