using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Rendering;
using UnityEngine.UIElements;
namespace UnityEditor.Rendering
{
///
/// Utility functions for handling VolumeProfiles in the editor.
///
public static class VolumeProfileUtils
{
internal static class Styles
{
public static readonly GUIContent newVolumeProfile = EditorGUIUtility.TrTextContent("New Volume Profile...");
public static readonly GUIContent clone = EditorGUIUtility.TrTextContent("Clone");
public static readonly GUIContent collapseAll = EditorGUIUtility.TrTextContent("Collapse All");
public static readonly GUIContent expandAll = EditorGUIUtility.TrTextContent("Expand All");
public static readonly GUIContent reset = EditorGUIUtility.TrTextContent("Reset");
public static readonly GUIContent resetAll = EditorGUIUtility.TrTextContent("Reset All");
public static readonly GUIContent openInRenderingDebugger = EditorGUIUtility.TrTextContent("Open In Rendering Debugger");
public static readonly GUIContent copySettings = EditorGUIUtility.TrTextContent("Copy Settings");
public static readonly GUIContent copyAllSettings = EditorGUIUtility.TrTextContent("Copy All Settings");
public static readonly GUIContent pasteSettings = EditorGUIUtility.TrTextContent("Paste Settings");
}
internal static void CopyValuesToProfile(VolumeComponent component, VolumeProfile profile)
{
var profileComponent = profile.GetVolumeComponentOfType(component.GetType());
Undo.RecordObject(profileComponent, "Copy component to profile");
CopyValuesToComponent(component, profileComponent, true);
VolumeManager.instance.OnVolumeProfileChanged(profile);
}
internal static void CopyValuesToComponent(VolumeComponent component, VolumeComponent targetComponent, bool copyOnlyOverriddenParams)
{
if (targetComponent == null)
return;
for (int i = 0; i < component.parameters.Count; i++)
{
var param = component.parameters[i];
if (copyOnlyOverriddenParams && !param.overrideState)
continue;
var targetParam = targetComponent.parameters[i];
targetParam.SetValue(param);
}
}
internal static void AssignValuesToProfile(VolumeProfile targetProfile, VolumeComponent component, SerializedProperty newPropertyValue)
{
var defaultComponent = targetProfile.GetVolumeComponentOfType(component.GetType());
if (defaultComponent != null)
{
var defaultObject = new SerializedObject(defaultComponent);
var defaultProperty = defaultObject.FindProperty(newPropertyValue.propertyPath);
if (defaultProperty != null)
{
defaultProperty.serializedObject.CopyFromSerializedProperty(newPropertyValue);
defaultProperty.serializedObject.ApplyModifiedProperties();
VolumeManager.instance.OnVolumeProfileChanged(targetProfile);
}
}
}
///
/// Assign the global default default profile to VolumeManager. Ensures that defaultVolumeProfile contains
/// overrides for every component. If defaultValueSource is provided, it will be used as the source for
/// default values instead of default-constructing them.
/// If components will be added to the profile, a confirmation dialog is displayed.
///
/// VolumeProfile asset assigned in pipeline global settings.
/// An optional VolumeProfile asset containing default values to use for
/// any components that are added to .
/// The type of RenderPipeline that this VolumeProfile is used for. If it is
/// not the active pipeline, the function does nothing.
/// Whether the operation was confirmed
public static bool UpdateGlobalDefaultVolumeProfileWithConfirmation(VolumeProfile globalDefaultVolumeProfile, VolumeProfile defaultValueSource = null)
where TRenderPipeline : RenderPipeline
{
if (RenderPipelineManager.currentPipeline is not TRenderPipeline)
return false;
int numComponentsMissingFromProfile = GetTypesMissingFromDefaultProfile(globalDefaultVolumeProfile).Count;
if (numComponentsMissingFromProfile == 0 ||
EditorUtility.DisplayDialog(
"New Default Volume Profile",
$"Assigning {globalDefaultVolumeProfile.name} as the Default Volume Profile will add {numComponentsMissingFromProfile} Volume Components to it. Are you sure?", "Yes", "Cancel"))
{
UpdateGlobalDefaultVolumeProfile(globalDefaultVolumeProfile, defaultValueSource);
return true;
}
return false;
}
///
/// Assign the global default default profile to VolumeManager. Ensures that defaultVolumeProfile contains
/// overrides for every component. If defaultValueSource is provided, it will be used as the source for
/// default values instead of default-constructing them.
///
/// VolumeProfile asset assigned in pipeline global settings.
/// An optional VolumeProfile asset containing default values to use for
/// any components that are added to .
/// The type of RenderPipeline that this VolumeProfile is used for. If it is
/// not the active pipeline, the function does nothing.
public static void UpdateGlobalDefaultVolumeProfile(VolumeProfile globalDefaultVolumeProfile, VolumeProfile defaultValueSource = null)
where TRenderPipeline : RenderPipeline
{
if (RenderPipelineManager.currentPipeline is not TRenderPipeline)
return;
Undo.RecordObject(globalDefaultVolumeProfile, $"Ensure {globalDefaultVolumeProfile.name} has all Volume Components");
foreach (var comp in globalDefaultVolumeProfile.components)
Undo.RecordObject(comp, $"Save {comp.name} state");
EnsureAllOverridesForDefaultProfile(globalDefaultVolumeProfile, defaultValueSource);
VolumeManager.instance.SetGlobalDefaultProfile(globalDefaultVolumeProfile);
}
// Helper extension method: Returns the VolumeComponent of given type from the profile if present, or null
static VolumeComponent GetVolumeComponentOfType(this VolumeProfile profile, Type type)
{
if (profile != null)
{
foreach (var component in profile.components)
if (component.GetType() == type)
return component;
}
return null;
}
// Helper extension method: Returns the VolumeComponent of given type from the profile if present, or a default-constructed one
static VolumeComponent GetVolumeComponentOfTypeOrDefault(this VolumeProfile profile, Type type)
{
return profile.GetVolumeComponentOfType(type) ?? (VolumeComponent) ScriptableObject.CreateInstance(type);
}
static List GetTypesMissingFromDefaultProfile(VolumeProfile profile)
{
List missingTypes = new List();
var volumeComponentTypes = VolumeManager.instance.baseComponentTypeArray;
foreach (var type in volumeComponentTypes)
{
if (profile.components.Find(c => c.GetType() == type) == null)
{
if (type.IsDefined(typeof(ObsoleteAttribute), false) ||
type.IsDefined(typeof(HideInInspector), false))
continue;
missingTypes.Add(type);
}
}
return missingTypes;
}
///
/// Ensure the provided VolumeProfile contains every VolumeComponent, they are active and overrideState for
/// every VolumeParameter is true. Obsolete and hidden components are excluded.
///
/// VolumeProfile to use.
/// An optional VolumeProfile asset containing default values to use for
/// any components that are added to .
public static void EnsureAllOverridesForDefaultProfile(VolumeProfile profile, VolumeProfile defaultValueSource = null)
{
// It's possible that the volume profile is assigned to the default asset inside the HDRP package. In
// this case it cannot be modified. User is expected to use HDRP Wizard "Fix" to create a local profile.
var path = AssetDatabase.GetAssetPath(profile);
if (CoreEditorUtils.IsAssetInReadOnlyPackage(path))
return;
bool changed = false;
int numComponentsBefore = profile.components.Count;
// Remove any obsolete VolumeComponents
profile.components.RemoveAll(
comp => comp == null || comp.GetType().IsDefined(typeof(ObsoleteAttribute), false));
changed |= profile.components.Count != numComponentsBefore;
// Ensure all existing VolumeComponents are active & all overrides enabled
foreach (var comp in profile.components)
{
bool resetAll = false;
if (!comp.active)
{
changed = true;
comp.active = true;
resetAll = true;
}
VolumeComponent defaultValueComponent = null;
for (int i = 0; i < comp.parameters.Count; ++i)
{
var param = comp.parameters[i];
if (resetAll || !param.overrideState)
{
if (defaultValueComponent == null)
defaultValueComponent = defaultValueSource.GetVolumeComponentOfTypeOrDefault(comp.GetType());
// Because the parameter values for inactive VolumeComponents or non-overriden parameters are
// not in effect, we must reset these values to avoid unexpected changes when assigning an
// existing profile as a Default Profile.
param.SetValue(defaultValueComponent.parameters[i]);
}
if (!param.overrideState)
{
changed = true;
param.overrideState = true;
}
}
}
// Add missing VolumeComponents to profile
var missingTypes = GetTypesMissingFromDefaultProfile(profile);
foreach (var type in missingTypes)
{
var comp = profile.Add(type, overrides: true);
comp.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
// Copy values from default value source if present & overridden
var defaultValueSourceComponent = defaultValueSource.GetVolumeComponentOfType(type);
if (defaultValueSourceComponent != null)
{
for (int i = 0; i < comp.parameters.Count; i++)
{
var defaultValueSourceParam = defaultValueSourceComponent.parameters[i];
if (defaultValueSourceParam.overrideState)
comp.parameters[i].SetValue(defaultValueSourceParam);
}
}
AssetDatabase.AddObjectToAsset(comp, profile);
changed = true;
}
if (changed)
{
VolumeManager.instance.OnVolumeProfileChanged(profile);
EditorUtility.SetDirty(profile);
}
}
///
/// Adds context menu dropdown items for a Volume Profile.
///
/// Dropdown menu to add items to
/// VolumeProfile associated with the context menu
/// List of VolumeComponentEditors associated with the profile
/// Default override state for components when they are reset
/// Default path for the new volume profile<
/// Callback when new volume profile has been created
/// Callback when all editors are collapsed or expanded
/// Whether it is allowed to create a new profile
public static void AddVolumeProfileContextMenuItems(
ref GenericMenu menu,
VolumeProfile volumeProfile,
List componentEditors,
bool overrideStateOnReset,
string defaultVolumeProfilePath,
Action onNewVolumeProfileCreated,
Action onComponentEditorsExpandedCollapsed = null,
bool canCreateNewProfile = true)
{
if (canCreateNewProfile)
{
menu.AddItem(Styles.newVolumeProfile, false, () =>
{
VolumeProfileFactory.CreateVolumeProfileWithCallback(defaultVolumeProfilePath,
onNewVolumeProfileCreated);
});
}
else
{
menu.AddDisabledItem(Styles.newVolumeProfile, false);
}
if (volumeProfile != null)
{
if (canCreateNewProfile)
{
menu.AddItem(Styles.clone, false, () =>
{
var pathName = AssetDatabase.GenerateUniqueAssetPath(AssetDatabase.GetAssetPath(volumeProfile));
var clone = VolumeProfileFactory.CreateVolumeProfileAtPath(pathName, volumeProfile);
onNewVolumeProfileCreated(clone);
});
}
else
{
menu.AddDisabledItem(Styles.clone, false);
}
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.collapseAll, false, () =>
{
SetComponentEditorsExpanded(componentEditors, false);
onComponentEditorsExpandedCollapsed?.Invoke();
});
menu.AddItem(Styles.expandAll, false, () =>
{
SetComponentEditorsExpanded(componentEditors, true);
onComponentEditorsExpandedCollapsed?.Invoke();
});
}
menu.AddSeparator(string.Empty);
menu.AddAdvancedPropertiesBoolMenuItem();
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.openInRenderingDebugger, false, DebugDisplaySettingsVolume.OpenInRenderingDebugger);
if (volumeProfile != null)
{
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.copyAllSettings, false,
() => VolumeComponentCopyPaste.CopySettings(volumeProfile.components));
if (VolumeComponentCopyPaste.CanPaste(volumeProfile.components))
menu.AddItem(Styles.pasteSettings, false, () =>
{
VolumeComponentCopyPaste.PasteSettings(volumeProfile.components);
VolumeManager.instance.OnVolumeProfileChanged(volumeProfile);
});
else
menu.AddDisabledItem(Styles.pasteSettings, false);
}
}
///
/// Draws the context menu dropdown for a Volume Profile.
///
/// Context menu position
/// VolumeProfile associated with the context menu
/// List of VolumeComponentEditors associated with the profile
/// Default path for the new volume profile
/// Default override state for components when they are reset
/// Callback when new volume profile has been created
/// Callback when all editors are collapsed or expanded
public static void OnVolumeProfileContextClick(
Vector2 position,
VolumeProfile volumeProfile,
List componentEditors,
bool overrideStateOnReset,
string defaultVolumeProfilePath,
Action onNewVolumeProfileCreated,
Action onComponentEditorsExpandedCollapsed = null)
{
var menu = new GenericMenu();
menu.AddItem(Styles.newVolumeProfile, false, () =>
{
VolumeProfileFactory.CreateVolumeProfileWithCallback(defaultVolumeProfilePath,
onNewVolumeProfileCreated);
});
if (volumeProfile != null)
{
menu.AddItem(Styles.clone, false, () =>
{
var pathName = AssetDatabase.GenerateUniqueAssetPath(AssetDatabase.GetAssetPath(volumeProfile));
var clone = VolumeProfileFactory.CreateVolumeProfileAtPath(pathName, volumeProfile);
onNewVolumeProfileCreated(clone);
});
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.collapseAll, false, () =>
{
SetComponentEditorsExpanded(componentEditors, false);
onComponentEditorsExpandedCollapsed?.Invoke();
});
menu.AddItem(Styles.expandAll, false, () =>
{
SetComponentEditorsExpanded(componentEditors, true);
onComponentEditorsExpandedCollapsed?.Invoke();
});
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.resetAll, false, () =>
{
VolumeComponent[] components = new VolumeComponent[componentEditors.Count];
for (int i = 0; i < componentEditors.Count; i++)
components[i] = componentEditors[i].volumeComponent;
ResetComponentsInternal(new SerializedObject(volumeProfile), volumeProfile, components, overrideStateOnReset);
});
}
menu.AddSeparator(string.Empty);
menu.AddAdvancedPropertiesBoolMenuItem();
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.openInRenderingDebugger, false, DebugDisplaySettingsVolume.OpenInRenderingDebugger);
if (volumeProfile != null)
{
menu.AddSeparator(string.Empty);
menu.AddItem(Styles.copyAllSettings, false,
() => VolumeComponentCopyPaste.CopySettings(volumeProfile.components));
if (VolumeComponentCopyPaste.CanPaste(volumeProfile.components))
menu.AddItem(Styles.pasteSettings, false, () =>
{
VolumeComponentCopyPaste.PasteSettings(volumeProfile.components);
VolumeManager.instance.OnVolumeProfileChanged(volumeProfile);
});
else
menu.AddDisabledItem(Styles.pasteSettings);
}
menu.DropDown(new Rect(new Vector2(position.x, position.y), Vector2.zero));
}
internal static VolumeComponent CreateNewComponent(Type type)
{
var volumeComponent = (VolumeComponent) ScriptableObject.CreateInstance(type);
volumeComponent.hideFlags = HideFlags.HideInInspector | HideFlags.HideInHierarchy;
volumeComponent.name = type.Name;
return volumeComponent;
}
internal static void ResetComponentsInternal(
SerializedObject serializedObject,
VolumeProfile asset,
VolumeComponent[] components,
bool newComponentDefaultOverrideState)
{
Undo.RecordObjects(components, "Reset All Volume Overrides");
foreach (var targetComponent in components)
{
var newComponent = CreateNewComponent(targetComponent.GetType());
CopyValuesToComponent(newComponent, targetComponent, false);
targetComponent.SetAllOverridesTo(newComponentDefaultOverrideState);
}
serializedObject.ApplyModifiedProperties();
VolumeManager.instance.OnVolumeProfileChanged(asset);
// Force save / refresh
EditorUtility.SetDirty(asset);
AssetDatabase.SaveAssets();
}
internal static void SetComponentEditorsExpanded(List editors, bool expanded)
{
foreach (var editor in editors)
{
editor.expanded = expanded;
}
}
}
}