A game about forced loneliness, made by TACStudios
1using System;
2using System.Collections.Generic;
3using System.Linq;
4using System.Reflection;
5using Unity.Mathematics;
6using UnityEditor;
7
8namespace UnityEngine.Rendering
9{
10 /// <summary>
11 /// The volume settings
12 /// </summary>
13 /// <typeparam name="T">A <see cref="MonoBehaviour"/> with <see cref="IAdditionalData"/></typeparam>
14 public abstract partial class VolumeDebugSettings<T> : IVolumeDebugSettings
15 where T : MonoBehaviour, IAdditionalData
16 {
17 /// <summary>Current volume component to debug.</summary>
18 public int selectedComponent { get; set; } = 0;
19
20 /// <summary>Current camera to debug.</summary>
21 public Camera selectedCamera => selectedCameraIndex < 0 ? null : cameras.ElementAt(selectedCameraIndex);
22
23 /// <summary>
24 /// The selected camera index, use the property for better handling
25 /// </summary>
26 protected int m_SelectedCameraIndex = -1;
27
28 /// <summary>Selected camera index.</summary>
29 public int selectedCameraIndex
30 {
31 get
32 {
33 var count = cameras.Count();
34 return count > 0 ? Math.Clamp(m_SelectedCameraIndex, 0, count-1) : -1;
35 }
36 set
37 {
38 var count = cameras.Count();
39 m_SelectedCameraIndex = Math.Clamp(value, 0, count-1);
40 }
41 }
42
43 private Camera[] m_CamerasArray;
44 private List<Camera> m_Cameras = new List<Camera>();
45
46 /// <summary>Returns the collection of registered cameras.</summary>
47 public IEnumerable<Camera> cameras
48 {
49 get
50 {
51 m_Cameras.Clear();
52
53#if UNITY_EDITOR
54 if (SceneView.lastActiveSceneView != null)
55 {
56 var sceneCamera = SceneView.lastActiveSceneView.camera;
57 if (sceneCamera != null)
58 m_Cameras.Add(sceneCamera);
59 }
60#endif
61
62 if (m_CamerasArray == null || m_CamerasArray.Length != Camera.allCamerasCount)
63 {
64 m_CamerasArray = new Camera[Camera.allCamerasCount];
65 }
66
67 Camera.GetAllCameras(m_CamerasArray);
68
69 foreach (var camera in m_CamerasArray)
70 {
71 if (camera == null)
72 continue;
73
74 if (camera.cameraType != CameraType.Preview && camera.cameraType != CameraType.Reflection)
75 {
76 if (camera.TryGetComponent<T>(out T additionalData))
77 m_Cameras.Add(camera);
78 }
79 }
80
81 return m_Cameras;
82 }
83 }
84
85 /// <summary>Selected camera volume stack.</summary>
86 public abstract VolumeStack selectedCameraVolumeStack { get; }
87
88 /// <summary>Selected camera volume layer mask.</summary>
89 public abstract LayerMask selectedCameraLayerMask { get; }
90
91 /// <summary>Selected camera volume position.</summary>
92 public abstract Vector3 selectedCameraPosition { get; }
93
94 /// <summary>Type of the current component to debug.</summary>
95 public Type selectedComponentType
96 {
97 get => selectedComponent > 0 ? volumeComponentsPathAndType[selectedComponent - 1].Item2 : null;
98 set
99 {
100 var index = volumeComponentsPathAndType.FindIndex(t => t.Item2 == value);
101 if (index != -1)
102 selectedComponent = index + 1;
103 }
104 }
105
106 /// <summary>List of Volume component types.</summary>
107 public List<(string, Type)> volumeComponentsPathAndType => VolumeManager.instance.GetVolumeComponentsForDisplay(GraphicsSettings.currentRenderPipelineAssetType);
108
109 /// <summary>
110 /// Specifies the render pipeline for this volume settings
111 /// </summary>
112 [Obsolete("This property is obsolete and kept only for not breaking user code. VolumeDebugSettings will use current pipeline when it needs to gather volume component types and paths. #from(23.2)", false)]
113 public virtual Type targetRenderPipeline { get; }
114
115 internal VolumeParameter GetParameter(VolumeComponent component, FieldInfo field)
116 {
117 return (VolumeParameter)field.GetValue(component);
118 }
119
120 internal VolumeParameter GetParameter(FieldInfo field)
121 {
122 VolumeStack stack = selectedCameraVolumeStack;
123 return stack == null ? null : GetParameter(stack.GetComponent(selectedComponentType), field);
124 }
125
126 internal VolumeParameter GetParameter(Volume volume, FieldInfo field)
127 {
128 var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile;
129 if (!profile.TryGet(selectedComponentType, out VolumeComponent component))
130 return null;
131 var param = GetParameter(component, field);
132 if (!param.overrideState)
133 return null;
134 return param;
135 }
136
137 float[] weights = null;
138 float ComputeWeight(Volume volume, Vector3 triggerPos)
139 {
140 if (volume == null) return 0;
141
142 var profile = volume.HasInstantiatedProfile() ? volume.profile : volume.sharedProfile;
143
144 if (!volume.gameObject.activeInHierarchy) return 0;
145 if (!volume.enabled || profile == null || volume.weight <= 0f) return 0;
146 if (!profile.TryGet(selectedComponentType, out VolumeComponent component)) return 0;
147 if (!component.active) return 0;
148
149 float weight = Mathf.Clamp01(volume.weight);
150 if (!volume.isGlobal)
151 {
152 var colliders = volume.GetComponents<Collider>();
153
154 // Find closest distance to volume, 0 means it's inside it
155 float closestDistanceSqr = float.PositiveInfinity;
156 foreach (var collider in colliders)
157 {
158 if (!collider.enabled)
159 continue;
160
161 var closestPoint = collider.ClosestPoint(triggerPos);
162 var d = (closestPoint - triggerPos).sqrMagnitude;
163
164 if (d < closestDistanceSqr)
165 closestDistanceSqr = d;
166 }
167 float blendDistSqr = volume.blendDistance * volume.blendDistance;
168 if (closestDistanceSqr > blendDistSqr)
169 weight = 0f;
170 else if (blendDistSqr > 0f)
171 weight *= 1f - (closestDistanceSqr / blendDistSqr);
172 }
173 return weight;
174 }
175
176 Volume[] volumes = null;
177
178 /// <summary>Get an array of volumes on the <see cref="selectedCameraLayerMask"/></summary>
179 /// <returns>An array of volumes sorted by influence.</returns>
180 public Volume[] GetVolumes()
181 {
182 return VolumeManager.instance.GetVolumes(selectedCameraLayerMask)
183 .Where(v => v.sharedProfile != null)
184 .Reverse().ToArray();
185 }
186
187 VolumeParameter[,] savedStates = null;
188 VolumeParameter[,] GetStates()
189 {
190 var fields = selectedComponentType
191 .GetFields(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance)
192 .Where(t => t.FieldType.IsSubclassOf(typeof(VolumeParameter)))
193 .ToArray();
194
195 VolumeParameter[,] states = new VolumeParameter[volumes.Length, fields.Length];
196 for (int i = 0; i < volumes.Length; i++)
197 {
198 var profile = volumes[i].HasInstantiatedProfile() ? volumes[i].profile : volumes[i].sharedProfile;
199 if (!profile.TryGet(selectedComponentType, out VolumeComponent component))
200 continue;
201
202 for (int j = 0; j < fields.Length; j++)
203 {
204 var param = GetParameter(component, fields[j]); ;
205 states[i, j] = param.overrideState ? param : null;
206 }
207 }
208 return states;
209 }
210
211 bool ChangedStates(VolumeParameter[,] newStates)
212 {
213 if (savedStates.GetLength(1) != newStates.GetLength(1))
214 return true;
215 for (int i = 0; i < savedStates.GetLength(0); i++)
216 {
217 for (int j = 0; j < savedStates.GetLength(1); j++)
218 {
219 if ((savedStates[i, j] == null) != (newStates[i, j] == null))
220 return true;
221 }
222 }
223 return false;
224 }
225
226 /// <summary>
227 /// Refreshes the volumes, fetches the stored volumes on the panel
228 /// </summary>
229 /// <param name="newVolumes">The list of <see cref="Volume"/> to refresh</param>
230 /// <returns>If the volumes have been refreshed</returns>
231 public bool RefreshVolumes(Volume[] newVolumes)
232 {
233 bool ret = false;
234 if (volumes == null || !newVolumes.SequenceEqual(volumes))
235 {
236 volumes = (Volume[])newVolumes.Clone();
237 savedStates = GetStates();
238 ret = true;
239 }
240 else
241 {
242 var newStates = GetStates();
243 if (savedStates == null || ChangedStates(newStates))
244 {
245 savedStates = newStates;
246 ret = true;
247 }
248 }
249
250 var triggerPos = selectedCameraPosition;
251 weights = new float[volumes.Length];
252 for (int i = 0; i < volumes.Length; i++)
253 weights[i] = ComputeWeight(volumes[i], triggerPos);
254
255 return ret;
256 }
257
258 /// <summary>
259 /// Obtains the volume weight
260 /// </summary>
261 /// <param name="volume"><see cref="Volume"/></param>
262 /// <returns>The weight of the volume</returns>
263 public float GetVolumeWeight(Volume volume)
264 {
265 if (weights == null)
266 return 0;
267
268 float total = 0f, weight = 0f;
269 for (int i = 0; i < volumes.Length; i++)
270 {
271 weight = weights[i];
272 weight *= 1f - total;
273 total += weight;
274
275 if (volumes[i] == volume)
276 return weight;
277 }
278
279 return 0f;
280 }
281
282 /// <summary>
283 /// Return if the <see cref="Volume"/> has influence
284 /// </summary>
285 /// <param name="volume"><see cref="Volume"/> to check the influence</param>
286 /// <returns>If the volume has influence</returns>
287 public bool VolumeHasInfluence(Volume volume)
288 {
289 if (weights == null)
290 return false;
291
292 int index = Array.IndexOf(volumes, volume);
293 if (index == -1)
294 return false;
295
296 return weights[index] != 0f;
297 }
298 }
299}