using System; using System.Collections.Generic; using UnityEngine.Assertions; namespace UnityEngine.Rendering { internal class VolumeCollection { // Max amount of layers available in Unity internal const int k_MaxLayerCount = 32; // Cached lists of all volumes (sorted by priority) by layer mask readonly Dictionary> m_SortedVolumes = new(); // Holds all the registered volumes readonly List m_Volumes = new(); // Keep track of sorting states for layer masks readonly Dictionary m_SortNeeded = new(); public int count => m_Volumes.Count; public bool Register(Volume volume, int layer) { if (volume == null) throw new ArgumentNullException(nameof(volume), "The volume to register is null"); if (m_Volumes.Contains(volume)) return false; m_Volumes.Add(volume); // Look for existing cached layer masks and add it there if needed foreach (var kvp in m_SortedVolumes) { // We add the volume to sorted lists only if the layer match and if it doesn't contain the volume already. if ((kvp.Key & (1 << layer)) != 0 && !kvp.Value.Contains(volume)) kvp.Value.Add(volume); } SetLayerIndexDirty(layer); return true; } public bool Unregister(Volume volume, int layer) { if (volume == null) throw new ArgumentNullException(nameof(volume), "The volume to unregister is null"); m_Volumes.Remove(volume); foreach (var kvp in m_SortedVolumes) { // Skip layer masks this volume doesn't belong to if ((kvp.Key & (1 << layer)) == 0) continue; kvp.Value.Remove(volume); } SetLayerIndexDirty(layer); return true; } public bool ChangeLayer(Volume volume, int previousLayerIndex, int currentLayerIndex) { if (volume == null) throw new ArgumentNullException(nameof(volume), "The volume to change layer is null"); Assert.IsTrue(previousLayerIndex >= 0 && previousLayerIndex <= k_MaxLayerCount, "Invalid layer bit"); Unregister(volume, previousLayerIndex); return Register(volume, currentLayerIndex); } // Stable insertion sort. Faster than List.Sort() for our needs. internal static void SortByPriority(List volumes) { for (int i = 1; i < volumes.Count; i++) { var temp = volumes[i]; int j = i - 1; // Sort order is ascending while (j >= 0 && volumes[j].priority > temp.priority) { volumes[j + 1] = volumes[j]; j--; } volumes[j + 1] = temp; } } public List GrabVolumes(LayerMask mask) { List list; 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(); var numVolumes = m_Volumes.Count; for (int i = 0; i < numVolumes; i++) { var volume = m_Volumes[i]; if ((mask & (1 << volume.gameObject.layer)) == 0) continue; list.Add(volume); m_SortNeeded[mask] = true; } m_SortedVolumes.Add(mask, list); } // Check sorting state if (m_SortNeeded.TryGetValue(mask, out var sortNeeded) && sortNeeded) { m_SortNeeded[mask] = false; SortByPriority(list); } return list; } public void SetLayerIndexDirty(int layerIndex) { Assert.IsTrue(layerIndex >= 0 && layerIndex <= k_MaxLayerCount, "Invalid layer bit"); foreach (var kvp in m_SortedVolumes) { var mask = kvp.Key; if ((mask & (1 << layerIndex)) != 0) m_SortNeeded[mask] = true; } } public bool IsComponentActiveInMask(LayerMask layerMask) where T : VolumeComponent { int mask = layerMask.value; foreach (var kvp in m_SortedVolumes) { if (kvp.Key != mask) continue; foreach (var volume in kvp.Value) { if (!volume.enabled || volume.profileRef == null) continue; if (volume.profileRef.TryGet(out T component) && component.active) return true; } } return false; } } }