A game about forced loneliness, made by TACStudios
at master 466 lines 22 kB view raw
1using System; 2using System.Collections.Generic; 3using UnityEngine; 4using UnityEngine.Rendering; 5using UnityEngine.UIElements; 6 7namespace UnityEditor.Rendering 8{ 9 /// <summary> 10 /// Utility functions for handling VolumeProfiles in the editor. 11 /// </summary> 12 public static class VolumeProfileUtils 13 { 14 internal static class Styles 15 { 16 public static readonly GUIContent newVolumeProfile = EditorGUIUtility.TrTextContent("New Volume Profile..."); 17 public static readonly GUIContent clone = EditorGUIUtility.TrTextContent("Clone"); 18 public static readonly GUIContent collapseAll = EditorGUIUtility.TrTextContent("Collapse All"); 19 public static readonly GUIContent expandAll = EditorGUIUtility.TrTextContent("Expand All"); 20 public static readonly GUIContent reset = EditorGUIUtility.TrTextContent("Reset"); 21 public static readonly GUIContent resetAll = EditorGUIUtility.TrTextContent("Reset All"); 22 public static readonly GUIContent openInRenderingDebugger = EditorGUIUtility.TrTextContent("Open In Rendering Debugger"); 23 public static readonly GUIContent copySettings = EditorGUIUtility.TrTextContent("Copy Settings"); 24 public static readonly GUIContent copyAllSettings = EditorGUIUtility.TrTextContent("Copy All Settings"); 25 public static readonly GUIContent pasteSettings = EditorGUIUtility.TrTextContent("Paste Settings"); 26 } 27 28 internal static void CopyValuesToProfile(VolumeComponent component, VolumeProfile profile) 29 { 30 var profileComponent = profile.GetVolumeComponentOfType(component.GetType()); 31 Undo.RecordObject(profileComponent, "Copy component to profile"); 32 CopyValuesToComponent(component, profileComponent, true); 33 VolumeManager.instance.OnVolumeProfileChanged(profile); 34 } 35 36 internal static void CopyValuesToComponent(VolumeComponent component, VolumeComponent targetComponent, bool copyOnlyOverriddenParams) 37 { 38 if (targetComponent == null) 39 return; 40 41 for (int i = 0; i < component.parameters.Count; i++) 42 { 43 var param = component.parameters[i]; 44 if (copyOnlyOverriddenParams && !param.overrideState) 45 continue; 46 var targetParam = targetComponent.parameters[i]; 47 targetParam.SetValue(param); 48 } 49 } 50 51 internal static void AssignValuesToProfile(VolumeProfile targetProfile, VolumeComponent component, SerializedProperty newPropertyValue) 52 { 53 var defaultComponent = targetProfile.GetVolumeComponentOfType(component.GetType()); 54 if (defaultComponent != null) 55 { 56 var defaultObject = new SerializedObject(defaultComponent); 57 var defaultProperty = defaultObject.FindProperty(newPropertyValue.propertyPath); 58 if (defaultProperty != null) 59 { 60 defaultProperty.serializedObject.CopyFromSerializedProperty(newPropertyValue); 61 defaultProperty.serializedObject.ApplyModifiedProperties(); 62 VolumeManager.instance.OnVolumeProfileChanged(targetProfile); 63 } 64 } 65 } 66 67 /// <summary> 68 /// Assign the global default default profile to VolumeManager. Ensures that defaultVolumeProfile contains 69 /// overrides for every component. If defaultValueSource is provided, it will be used as the source for 70 /// default values instead of default-constructing them. 71 /// If components will be added to the profile, a confirmation dialog is displayed. 72 /// </summary> 73 /// <param name="globalDefaultVolumeProfile">VolumeProfile asset assigned in pipeline global settings.</param> 74 /// <param name="defaultValueSource">An optional VolumeProfile asset containing default values to use for 75 /// any components that are added to <see cref="globalDefaultVolumeProfile"/>.</param> 76 /// <typeparam name="TRenderPipeline">The type of RenderPipeline that this VolumeProfile is used for. If it is 77 /// not the active pipeline, the function does nothing.</typeparam> 78 /// <returns>Whether the operation was confirmed</returns> 79 public static bool UpdateGlobalDefaultVolumeProfileWithConfirmation<TRenderPipeline>(VolumeProfile globalDefaultVolumeProfile, VolumeProfile defaultValueSource = null) 80 where TRenderPipeline : RenderPipeline 81 { 82 if (RenderPipelineManager.currentPipeline is not TRenderPipeline) 83 return false; 84 85 int numComponentsMissingFromProfile = GetTypesMissingFromDefaultProfile(globalDefaultVolumeProfile).Count; 86 if (numComponentsMissingFromProfile == 0 || 87 EditorUtility.DisplayDialog( 88 "New Default Volume Profile", 89 $"Assigning {globalDefaultVolumeProfile.name} as the Default Volume Profile will add {numComponentsMissingFromProfile} Volume Components to it. Are you sure?", "Yes", "Cancel")) 90 { 91 UpdateGlobalDefaultVolumeProfile<TRenderPipeline>(globalDefaultVolumeProfile, defaultValueSource); 92 return true; 93 } 94 95 return false; 96 } 97 98 /// <summary> 99 /// Assign the global default default profile to VolumeManager. Ensures that defaultVolumeProfile contains 100 /// overrides for every component. If defaultValueSource is provided, it will be used as the source for 101 /// default values instead of default-constructing them. 102 /// </summary> 103 /// <param name="globalDefaultVolumeProfile">VolumeProfile asset assigned in pipeline global settings.</param> 104 /// <param name="defaultValueSource">An optional VolumeProfile asset containing default values to use for 105 /// any components that are added to <see cref="globalDefaultVolumeProfile"/>.</param> 106 /// <typeparam name="TRenderPipeline">The type of RenderPipeline that this VolumeProfile is used for. If it is 107 /// not the active pipeline, the function does nothing.</typeparam> 108 public static void UpdateGlobalDefaultVolumeProfile<TRenderPipeline>(VolumeProfile globalDefaultVolumeProfile, VolumeProfile defaultValueSource = null) 109 where TRenderPipeline : RenderPipeline 110 { 111 if (RenderPipelineManager.currentPipeline is not TRenderPipeline) 112 return; 113 114 Undo.RecordObject(globalDefaultVolumeProfile, $"Ensure {globalDefaultVolumeProfile.name} has all Volume Components"); 115 foreach (var comp in globalDefaultVolumeProfile.components) 116 Undo.RecordObject(comp, $"Save {comp.name} state"); 117 118 EnsureAllOverridesForDefaultProfile(globalDefaultVolumeProfile, defaultValueSource); 119 VolumeManager.instance.SetGlobalDefaultProfile(globalDefaultVolumeProfile); 120 } 121 122 // Helper extension method: Returns the VolumeComponent of given type from the profile if present, or null 123 static VolumeComponent GetVolumeComponentOfType(this VolumeProfile profile, Type type) 124 { 125 if (profile != null) 126 { 127 foreach (var component in profile.components) 128 if (component.GetType() == type) 129 return component; 130 } 131 return null; 132 } 133 134 // Helper extension method: Returns the VolumeComponent of given type from the profile if present, or a default-constructed one 135 static VolumeComponent GetVolumeComponentOfTypeOrDefault(this VolumeProfile profile, Type type) 136 { 137 return profile.GetVolumeComponentOfType(type) ?? (VolumeComponent) ScriptableObject.CreateInstance(type); 138 } 139 140 static List<Type> GetTypesMissingFromDefaultProfile(VolumeProfile profile) 141 { 142 List<Type> missingTypes = new List<Type>(); 143 var volumeComponentTypes = VolumeManager.instance.baseComponentTypeArray; 144 foreach (var type in volumeComponentTypes) 145 { 146 if (profile.components.Find(c => c.GetType() == type) == null) 147 { 148 if (type.IsDefined(typeof(ObsoleteAttribute), false) || 149 type.IsDefined(typeof(HideInInspector), false)) 150 continue; 151 152 missingTypes.Add(type); 153 } 154 } 155 156 return missingTypes; 157 } 158 159 /// <summary> 160 /// Ensure the provided VolumeProfile contains every VolumeComponent, they are active and overrideState for 161 /// every VolumeParameter is true. Obsolete and hidden components are excluded. 162 /// </summary> 163 /// <param name="profile">VolumeProfile to use.</param> 164 /// <param name="defaultValueSource">An optional VolumeProfile asset containing default values to use for 165 /// any components that are added to <see cref="profile"/>.</param> 166 public static void EnsureAllOverridesForDefaultProfile(VolumeProfile profile, VolumeProfile defaultValueSource = null) 167 { 168 // It's possible that the volume profile is assigned to the default asset inside the HDRP package. In 169 // this case it cannot be modified. User is expected to use HDRP Wizard "Fix" to create a local profile. 170 var path = AssetDatabase.GetAssetPath(profile); 171 if (CoreEditorUtils.IsAssetInReadOnlyPackage(path)) 172 return; 173 174 bool changed = false; 175 int numComponentsBefore = profile.components.Count; 176 177 // Remove any obsolete VolumeComponents 178 profile.components.RemoveAll( 179 comp => comp == null || comp.GetType().IsDefined(typeof(ObsoleteAttribute), false)); 180 181 changed |= profile.components.Count != numComponentsBefore; 182 183 // Ensure all existing VolumeComponents are active & all overrides enabled 184 foreach (var comp in profile.components) 185 { 186 bool resetAll = false; 187 if (!comp.active) 188 { 189 changed = true; 190 comp.active = true; 191 resetAll = true; 192 } 193 194 VolumeComponent defaultValueComponent = null; 195 for (int i = 0; i < comp.parameters.Count; ++i) 196 { 197 var param = comp.parameters[i]; 198 if (resetAll || !param.overrideState) 199 { 200 if (defaultValueComponent == null) 201 defaultValueComponent = defaultValueSource.GetVolumeComponentOfTypeOrDefault(comp.GetType()); 202 203 // Because the parameter values for inactive VolumeComponents or non-overriden parameters are 204 // not in effect, we must reset these values to avoid unexpected changes when assigning an 205 // existing profile as a Default Profile. 206 param.SetValue(defaultValueComponent.parameters[i]); 207 } 208 if (!param.overrideState) 209 { 210 changed = true; 211 param.overrideState = true; 212 } 213 } 214 } 215 216 // Add missing VolumeComponents to profile 217 var missingTypes = GetTypesMissingFromDefaultProfile(profile); 218 foreach (var type in missingTypes) 219 { 220 var comp = profile.Add(type, overrides: true); 221 comp.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy; 222 223 // Copy values from default value source if present & overridden 224 var defaultValueSourceComponent = defaultValueSource.GetVolumeComponentOfType(type); 225 if (defaultValueSourceComponent != null) 226 { 227 for (int i = 0; i < comp.parameters.Count; i++) 228 { 229 var defaultValueSourceParam = defaultValueSourceComponent.parameters[i]; 230 if (defaultValueSourceParam.overrideState) 231 comp.parameters[i].SetValue(defaultValueSourceParam); 232 } 233 } 234 235 AssetDatabase.AddObjectToAsset(comp, profile); 236 changed = true; 237 } 238 239 if (changed) 240 { 241 VolumeManager.instance.OnVolumeProfileChanged(profile); 242 EditorUtility.SetDirty(profile); 243 AssetDatabase.SaveAssets(); 244 AssetDatabase.Refresh(); 245 } 246 } 247 248 /// <summary> 249 /// Adds context menu dropdown items for a Volume Profile. 250 /// </summary> 251 /// <param name="menu">Dropdown menu to add items to</param> 252 /// <param name="volumeProfile">VolumeProfile associated with the context menu</param> 253 /// <param name="componentEditors">List of VolumeComponentEditors associated with the profile</param> 254 /// <param name="overrideStateOnReset">Default override state for components when they are reset</param> 255 /// <param name="defaultVolumeProfilePath">Default path for the new volume profile&lt;</param> 256 /// <param name="onNewVolumeProfileCreated">Callback when new volume profile has been created</param> 257 /// <param name="onComponentEditorsExpandedCollapsed">Callback when all editors are collapsed or expanded</param> 258 /// <param name="canCreateNewProfile">Whether it is allowed to create a new profile</param> 259 public static void AddVolumeProfileContextMenuItems( 260 ref GenericMenu menu, 261 VolumeProfile volumeProfile, 262 List<VolumeComponentEditor> componentEditors, 263 bool overrideStateOnReset, 264 string defaultVolumeProfilePath, 265 Action<VolumeProfile> onNewVolumeProfileCreated, 266 Action onComponentEditorsExpandedCollapsed = null, 267 bool canCreateNewProfile = true) 268 { 269 if (canCreateNewProfile) 270 { 271 menu.AddItem(Styles.newVolumeProfile, false, () => 272 { 273 VolumeProfileFactory.CreateVolumeProfileWithCallback(defaultVolumeProfilePath, 274 onNewVolumeProfileCreated); 275 }); 276 } 277 else 278 { 279 menu.AddDisabledItem(Styles.newVolumeProfile, false); 280 } 281 282 if (volumeProfile != null) 283 { 284 if (canCreateNewProfile) 285 { 286 menu.AddItem(Styles.clone, false, () => 287 { 288 var pathName = AssetDatabase.GenerateUniqueAssetPath(AssetDatabase.GetAssetPath(volumeProfile)); 289 var clone = VolumeProfileFactory.CreateVolumeProfileAtPath(pathName, volumeProfile); 290 onNewVolumeProfileCreated(clone); 291 }); 292 } 293 else 294 { 295 menu.AddDisabledItem(Styles.clone, false); 296 } 297 298 menu.AddSeparator(string.Empty); 299 300 menu.AddItem(Styles.collapseAll, false, () => 301 { 302 SetComponentEditorsExpanded(componentEditors, false); 303 onComponentEditorsExpandedCollapsed?.Invoke(); 304 }); 305 menu.AddItem(Styles.expandAll, false, () => 306 { 307 SetComponentEditorsExpanded(componentEditors, true); 308 onComponentEditorsExpandedCollapsed?.Invoke(); 309 }); 310 } 311 312 menu.AddSeparator(string.Empty); 313 314 menu.AddAdvancedPropertiesBoolMenuItem(); 315 316 menu.AddSeparator(string.Empty); 317 318 menu.AddItem(Styles.openInRenderingDebugger, false, DebugDisplaySettingsVolume.OpenInRenderingDebugger); 319 320 if (volumeProfile != null) 321 { 322 menu.AddSeparator(string.Empty); 323 324 menu.AddItem(Styles.copyAllSettings, false, 325 () => VolumeComponentCopyPaste.CopySettings(volumeProfile.components)); 326 327 if (VolumeComponentCopyPaste.CanPaste(volumeProfile.components)) 328 menu.AddItem(Styles.pasteSettings, false, () => 329 { 330 VolumeComponentCopyPaste.PasteSettings(volumeProfile.components); 331 VolumeManager.instance.OnVolumeProfileChanged(volumeProfile); 332 }); 333 else 334 menu.AddDisabledItem(Styles.pasteSettings, false); 335 } 336 } 337 338 /// <summary> 339 /// Draws the context menu dropdown for a Volume Profile. 340 /// </summary> 341 /// <param name="position">Context menu position</param> 342 /// <param name="volumeProfile">VolumeProfile associated with the context menu</param> 343 /// <param name="componentEditors">List of VolumeComponentEditors associated with the profile</param> 344 /// <param name="defaultVolumeProfilePath">Default path for the new volume profile</param> 345 /// <param name="overrideStateOnReset">Default override state for components when they are reset</param> 346 /// <param name="onNewVolumeProfileCreated">Callback when new volume profile has been created</param> 347 /// <param name="onComponentEditorsExpandedCollapsed">Callback when all editors are collapsed or expanded</param> 348 public static void OnVolumeProfileContextClick( 349 Vector2 position, 350 VolumeProfile volumeProfile, 351 List<VolumeComponentEditor> componentEditors, 352 bool overrideStateOnReset, 353 string defaultVolumeProfilePath, 354 Action<VolumeProfile> onNewVolumeProfileCreated, 355 Action onComponentEditorsExpandedCollapsed = null) 356 { 357 var menu = new GenericMenu(); 358 menu.AddItem(Styles.newVolumeProfile, false, () => 359 { 360 VolumeProfileFactory.CreateVolumeProfileWithCallback(defaultVolumeProfilePath, 361 onNewVolumeProfileCreated); 362 }); 363 364 if (volumeProfile != null) 365 { 366 menu.AddItem(Styles.clone, false, () => 367 { 368 var pathName = AssetDatabase.GenerateUniqueAssetPath(AssetDatabase.GetAssetPath(volumeProfile)); 369 var clone = VolumeProfileFactory.CreateVolumeProfileAtPath(pathName, volumeProfile); 370 onNewVolumeProfileCreated(clone); 371 }); 372 373 menu.AddSeparator(string.Empty); 374 375 menu.AddItem(Styles.collapseAll, false, () => 376 { 377 SetComponentEditorsExpanded(componentEditors, false); 378 onComponentEditorsExpandedCollapsed?.Invoke(); 379 }); 380 menu.AddItem(Styles.expandAll, false, () => 381 { 382 SetComponentEditorsExpanded(componentEditors, true); 383 onComponentEditorsExpandedCollapsed?.Invoke(); 384 }); 385 386 menu.AddSeparator(string.Empty); 387 388 menu.AddItem(Styles.resetAll, false, () => 389 { 390 VolumeComponent[] components = new VolumeComponent[componentEditors.Count]; 391 for (int i = 0; i < componentEditors.Count; i++) 392 components[i] = componentEditors[i].volumeComponent; 393 394 ResetComponentsInternal(new SerializedObject(volumeProfile), volumeProfile, components, overrideStateOnReset); 395 }); 396 } 397 398 menu.AddSeparator(string.Empty); 399 400 menu.AddAdvancedPropertiesBoolMenuItem(); 401 402 menu.AddSeparator(string.Empty); 403 404 menu.AddItem(Styles.openInRenderingDebugger, false, DebugDisplaySettingsVolume.OpenInRenderingDebugger); 405 406 if (volumeProfile != null) 407 { 408 menu.AddSeparator(string.Empty); 409 410 menu.AddItem(Styles.copyAllSettings, false, 411 () => VolumeComponentCopyPaste.CopySettings(volumeProfile.components)); 412 413 if (VolumeComponentCopyPaste.CanPaste(volumeProfile.components)) 414 menu.AddItem(Styles.pasteSettings, false, () => 415 { 416 VolumeComponentCopyPaste.PasteSettings(volumeProfile.components); 417 VolumeManager.instance.OnVolumeProfileChanged(volumeProfile); 418 }); 419 else 420 menu.AddDisabledItem(Styles.pasteSettings); 421 } 422 423 menu.DropDown(new Rect(new Vector2(position.x, position.y), Vector2.zero)); 424 } 425 426 internal static VolumeComponent CreateNewComponent(Type type) 427 { 428 var volumeComponent = (VolumeComponent) ScriptableObject.CreateInstance(type); 429 volumeComponent.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy; 430 volumeComponent.name = type.Name; 431 return volumeComponent; 432 } 433 434 internal static void ResetComponentsInternal( 435 SerializedObject serializedObject, 436 VolumeProfile asset, 437 VolumeComponent[] components, 438 bool newComponentDefaultOverrideState) 439 { 440 Undo.RecordObjects(components, "Reset All Volume Overrides"); 441 442 foreach (var targetComponent in components) 443 { 444 var newComponent = CreateNewComponent(targetComponent.GetType()); 445 CopyValuesToComponent(newComponent, targetComponent, false); 446 targetComponent.SetAllOverridesTo(newComponentDefaultOverrideState); 447 } 448 449 serializedObject.ApplyModifiedProperties(); 450 451 VolumeManager.instance.OnVolumeProfileChanged(asset); 452 453 // Force save / refresh 454 EditorUtility.SetDirty(asset); 455 AssetDatabase.SaveAssets(); 456 } 457 458 internal static void SetComponentEditorsExpanded(List<VolumeComponentEditor> editors, bool expanded) 459 { 460 foreach (var editor in editors) 461 { 462 editor.expanded = expanded; 463 } 464 } 465 } 466}