UnityGame/Library/PackageCache/com.unity.render-pipelines.universal/Editor/2D/Light2DEditor.cs

890 lines
45 KiB
C#
Raw Normal View History

2024-10-27 10:53:47 +03:00
using System.Collections.Generic;
using System.Linq;
using UnityEditor.EditorTools;
using UnityEditor.Rendering.Universal.Path2D;
using UnityEngine;
using UnityEngine.Rendering.Universal;
namespace UnityEditor.Rendering.Universal
{
[CustomEditor(typeof(Light2D))]
[CanEditMultipleObjects]
internal class Light2DEditor : PathComponentEditor<ScriptablePath>
{
[EditorTool("Edit Freeform Shape", typeof(Light2D))]
class FreeformShapeTool : PathEditorTool<ScriptablePath>
{
const string k_ShapePath = "m_ShapePath";
public override bool IsAvailable()
{
var light = target as Light2D;
if (light == null)
return false;
else
return base.IsAvailable() && light.lightType == Light2D.LightType.Freeform;
}
protected override IShape GetShape(Object target)
{
return (target as Light2D).shapePath.ToPolygon(false);
}
protected override void SetShape(ScriptablePath shapeEditor, SerializedObject serializedObject)
{
serializedObject.Update();
var pointsProperty = serializedObject.FindProperty(k_ShapePath);
pointsProperty.arraySize = shapeEditor.pointCount;
for (var i = 0; i < shapeEditor.pointCount; ++i)
pointsProperty.GetArrayElementAtIndex(i).vector3Value = shapeEditor.GetPoint(i).position;
((Light2D)(serializedObject.targetObject)).UpdateMesh();
// This is untracked right now...
serializedObject.ApplyModifiedProperties();
}
}
private struct ToggleFoldoutResult
{
public bool foldoutState;
public bool toggleState;
}
private static class Styles
{
public static readonly GUIContent InnerOuterSpotAngle = EditorGUIUtility.TrTextContent("Inner / Outer Spot Angle", "Adjusts the inner / outer angles of this light to change the angle ranges of this Spot Lights beam.");
public static Texture lightCapTopRight = Resources.Load<Texture>("LightCapTopRight");
public static Texture lightCapTopLeft = Resources.Load<Texture>("LightCapTopLeft");
public static Texture lightCapBottomLeft = Resources.Load<Texture>("LightCapBottomLeft");
public static Texture lightCapBottomRight = Resources.Load<Texture>("LightCapBottomRight");
public static Texture lightCapUp = Resources.Load<Texture>("LightCapUp");
public static Texture lightCapDown = Resources.Load<Texture>("LightCapDown");
public static GUIContent lightTypeFreeform = new GUIContent("Freeform", Resources.Load("InspectorIcons/FreeformLight") as Texture);
public static GUIContent lightTypeSprite = new GUIContent("Sprite", Resources.Load("InspectorIcons/SpriteLight") as Texture);
public static GUIContent lightTypePoint = new GUIContent("Spot", Resources.Load("InspectorIcons/PointLight") as Texture);
public static GUIContent lightTypeGlobal = new GUIContent("Global", Resources.Load("InspectorIcons/GlobalLight") as Texture);
public static GUIContent[] lightTypeOptions = new GUIContent[] { lightTypeFreeform, lightTypeSprite, lightTypePoint, lightTypeGlobal };
public static GUIContent blendingSettingsFoldout = EditorGUIUtility.TrTextContent("Blending", "Options used for blending");
public static GUIContent shadowsSettingsFoldout = EditorGUIUtility.TrTextContent("Shadows", "Options used for shadows");
public static GUIContent volumetricSettingsFoldout = EditorGUIUtility.TrTextContent("Volumetric", "Options used for volumetric lighting");
public static GUIContent normalMapsSettingsFoldout = EditorGUIUtility.TrTextContent("Normal Maps", "Options used for normal maps");
public static GUIContent generalLightType = EditorGUIUtility.TrTextContent("Light Type", "Select the light type. \n\nGlobal Light: For ambient light. \nSpot Light: For a spot light / point light. \nFreeform Light: For a custom shape light. \nSprite Light: For a custom light cookie using Sprites.");
public static GUIContent generalFalloffSize = EditorGUIUtility.TrTextContent("Falloff", "Adjusts the falloff area of this light. The higher the falloff value, the larger area the falloff spans.");
public static GUIContent generalFalloffIntensity = EditorGUIUtility.TrTextContent("Falloff Strength", "Adjusts the falloff curve to control the softness of this lights edges. The higher the falloff strength, the softer the edges of this light.");
public static GUIContent generalLightColor = EditorGUIUtility.TrTextContent("Color", "Adjusts this lights color.");
public static GUIContent generalLightIntensity = EditorGUIUtility.TrTextContent("Intensity", "Adjusts this lights color intensity by using multiply to brighten the Sprite beyond its original color.");
public static GUIContent generalVolumeIntensity = EditorGUIUtility.TrTextContent("Intensity", "Adjusts the intensity of this additional light volume that's additively blended on top of this light. To enable the Volumetric Shadow Strength, increase this Intensity to be greater than 0.");
public static GUIContent generalBlendStyle = EditorGUIUtility.TrTextContent("Blend Style", "Adjusts how this light blends with the Sprites on the Target Sorting Layers. Different Blend Styles can be customized in the 2D Renderer Data Asset.");
public static GUIContent generalLightOverlapOperation = EditorGUIUtility.TrTextContent("Overlap Operation", "Determines how this light blends with the other lights either through additive or alpha blending.");
public static GUIContent generalLightOrder = EditorGUIUtility.TrTextContent("Light Order", "Determines the relative order in which lights of the same Blend Style get rendered. Lights with lower values are rendered first.");
public static GUIContent generalShadowIntensity = EditorGUIUtility.TrTextContent("Strength", "Adjusts the amount of light occlusion from the Shadow Caster 2D component(s) when blocking this light.The higher the value, the more opaque the shadow becomes.");
public static GUIContent generalShadowSoftness = EditorGUIUtility.TrTextContent("Softness", "Adjusts the amount of softness at the edge of the shadow.");
public static GUIContent generalShadowSoftnessFalloffIntensity = EditorGUIUtility.TrTextContent("Falloff Strength", "Adjusts the falloff curve to control the softness of the shadow edges. The higher the falloff strength, the softer the edges of this shadow.");
public static GUIContent generalShadowVolumeIntensity = EditorGUIUtility.TrTextContent("Shadow Strength", "Adjusts the amount of volume light occlusion from the Shadow Caster 2D component(s) when blocking this light.");
public static GUIContent generalSortingLayerPrefixLabel = EditorGUIUtility.TrTextContent("Target Sorting Layers", "Determines which layers this light affects. To optimize performance, minimize the number of layers this light affects.");
public static GUIContent generalLightNoLightEnabled = EditorGUIUtility.TrTextContentWithIcon("No valid blend styles are enabled.", MessageType.Error);
public static GUIContent generalNormalMapZDistance = EditorGUIUtility.TrTextContent("Distance", "Adjusts the z-axis distance of this light and the lit Sprite(s). Do note that this distance does not Transform the position of this light in the Scene.");
public static GUIContent generalNormalMapLightQuality = EditorGUIUtility.TrTextContent("Quality", "Determines the accuracy of the lighting calculations when normal map is used. To optimize for performance, select Fast.");
public static GUIContent pointLightRadius = EditorGUIUtility.TrTextContent("Radius", "Adjusts the inner / outer radius of this light to change the size of this light.");
public static GUIContent pointLightInner = EditorGUIUtility.TrTextContent("Inner", "Specify the inner radius of the light");
public static GUIContent pointLightOuter = EditorGUIUtility.TrTextContent("Outer", "Specify the outer radius of the light");
public static GUIContent pointLightSprite = EditorGUIUtility.TrTextContent("Sprite", "Specify the sprite (deprecated)");
public static GUIContent shapeLightSprite = EditorGUIUtility.TrTextContent("Sprite", "Assign a Sprite which acts as a mask to create a light cookie.");
public static GUIContent deprecatedParametricLightWarningSingle = EditorGUIUtility.TrTextContentWithIcon("Parametic Lights have been deprecated. To continue, upgrade your Parametric Light to a Freeform Light to enjoy similar light functionality.", MessageType.Warning);
public static GUIContent deprecatedParametricLightWarningMulti = EditorGUIUtility.TrTextContentWithIcon("Parametic Lights have been deprecated. To continue, upgrade your Parametric Lights to Freeform Lights to enjoy similar light functionality.", MessageType.Warning);
public static GUIContent deprecatedParametricLightInstructions = EditorGUIUtility.TrTextContent("Alternatively, you may choose to upgrade from the menu. Window > Rendering > Render Pipeline Converter > URP 2D Converters");
public static GUIContent deprecatedParametricLightButtonSingle = EditorGUIUtility.TrTextContent("Upgrade Parametric Light");
public static GUIContent deprecatedParametricLightButtonMulti = EditorGUIUtility.TrTextContent("Upgrade Parametric Lights");
public static GUIContent renderPipelineUnassignedWarning = EditorGUIUtility.TrTextContentWithIcon("Universal scriptable renderpipeline asset must be assigned in Graphics Settings or Quality Settings.", MessageType.Warning);
public static GUIContent asset2DUnassignedWarning = EditorGUIUtility.TrTextContentWithIcon("2D renderer data must be assigned to your universal render pipeline asset or camera.", MessageType.Warning);
public static string deprecatedParametricLightDialogTextSingle = "The upgrade will convert the selected parametric light into a freeform light. You can't undo this operation.";
public static string deprecatedParametricLightDialogTextMulti = "The upgrade will convert the selected parametric lights into freeform lights. You can't undo this operation.";
public static string deprecatedParametricLightDialogTitle = "Parametric Light Upgrader";
public static string deprecatedParametricLightDialogProceed = "Proceed";
public static string deprecatedParametricLightDialogCancel = "Cancel";
}
const float k_GlobalLightGizmoSize = 1.2f;
const float k_AngleCapSize = 0.16f * k_GlobalLightGizmoSize;
const float k_AngleCapOffset = 0.08f * k_GlobalLightGizmoSize;
const float k_AngleCapOffsetSecondary = -0.05f;
const float k_RangeCapSize = 0.025f * k_GlobalLightGizmoSize;
const float k_InnerRangeCapSize = 0.08f * k_GlobalLightGizmoSize;
SerializedProperty m_LightType;
SerializedProperty m_LightColor;
SerializedProperty m_LightIntensity;
SerializedProperty m_UseNormalMap;
SerializedProperty m_ShadowsEnabled;
SerializedProperty m_ShadowIntensity;
SerializedProperty m_ShadowSoftness;
SerializedProperty m_ShadowSoftnessFalloffIntensity;
SerializedProperty m_ShadowVolumeIntensity;
SerializedProperty m_ShadowVolumeIntensityEnabled;
SerializedProperty m_ApplyToSortingLayers;
SerializedProperty m_VolumetricIntensity;
SerializedProperty m_VolumetricEnabled;
SerializedProperty m_BlendStyleIndex;
SerializedProperty m_FalloffIntensity;
SerializedProperty m_NormalMapZDistance;
SerializedProperty m_NormalMapQuality;
SerializedProperty m_LightOrder;
SerializedProperty m_OverlapOperation;
// Point Light Properties
SerializedProperty m_PointInnerAngle;
SerializedProperty m_PointOuterAngle;
SerializedProperty m_PointInnerRadius;
SerializedProperty m_PointOuterRadius;
SerializedProperty m_DeprecatedPointLightSprite;
// Shape Light Properties
SerializedProperty m_ShapeLightParametricRadius;
SerializedProperty m_ShapeLightFalloffSize;
SerializedProperty m_ShapeLightParametricSides;
SerializedProperty m_ShapeLightSprite;
SavedBool m_BlendingSettingsFoldout;
SavedBool m_ShadowsSettingsFoldout;
SavedBool m_VolumetricSettingsFoldout;
SavedBool m_NormalMapsSettingsFoldout;
int[] m_BlendStyleIndices;
GUIContent[] m_BlendStyleNames;
bool m_AnyBlendStyleEnabled = false;
SortingLayerDropDown m_SortingLayerDropDown;
Light2D lightObject => target as Light2D;
Analytics.Renderer2DAnalytics m_Analytics;
HashSet<Light2D> m_ModifiedLights;
private void AnalyticsTrackChanges(SerializedObject serializedObject)
{
if (serializedObject.hasModifiedProperties)
{
foreach (Object targetObj in serializedObject.targetObjects)
{
Light2D light2d = (Light2D)targetObj;
if (!m_ModifiedLights.Contains(light2d))
m_ModifiedLights.Add(light2d);
}
}
}
private ToggleFoldoutResult DrawHeaderFoldoutWithToggle(GUIContent title, bool foldoutState, bool toggleState, string documentationURL = "")
{
ToggleFoldoutResult foldoutResult = new ToggleFoldoutResult();
const float height = 17f;
var backgroundRect = GUILayoutUtility.GetRect(0, 0);
float xMin = backgroundRect.xMin;
var labelRect = backgroundRect;
labelRect.yMax += height;
labelRect.xMin += 16f;
labelRect.xMax -= 20f;
foldoutResult.toggleState = GUI.Toggle(labelRect, toggleState, " "); // Needs a space because the checkbox won't have a proper outline if we don't make a space here
foldoutResult.foldoutState = CoreEditorUtils.DrawHeaderFoldout("", foldoutState);
labelRect.xMin += 20;
EditorGUI.LabelField(labelRect, title, EditorStyles.boldLabel);
return foldoutResult;
}
void OnEnable()
{
m_Analytics = Analytics.Renderer2DAnalytics.instance;
m_ModifiedLights = new HashSet<Light2D>();
m_SortingLayerDropDown = new SortingLayerDropDown();
m_BlendingSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPBlendingSettingsFoldout", false);
m_ShadowsSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPShadowsSettingsFoldout", false);
m_VolumetricSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPVolumetricSettingsFoldout", false);
m_NormalMapsSettingsFoldout = new SavedBool($"{target.GetType()}.2DURPNormalMapsSettingsFoldout", false);
m_LightType = serializedObject.FindProperty("m_LightType");
m_LightColor = serializedObject.FindProperty("m_Color");
m_LightIntensity = serializedObject.FindProperty("m_Intensity");
m_UseNormalMap = serializedObject.FindProperty("m_UseNormalMap");
m_ShadowsEnabled = serializedObject.FindProperty("m_ShadowsEnabled");
m_ShadowIntensity = serializedObject.FindProperty("m_ShadowIntensity");
m_ShadowSoftness = serializedObject.FindProperty("m_ShadowSoftness");
m_ShadowSoftnessFalloffIntensity = serializedObject.FindProperty("m_ShadowSoftnessFalloffIntensity");
m_ShadowVolumeIntensity = serializedObject.FindProperty("m_ShadowVolumeIntensity");
m_ShadowVolumeIntensityEnabled = serializedObject.FindProperty("m_ShadowVolumeIntensityEnabled");
m_ApplyToSortingLayers = serializedObject.FindProperty("m_ApplyToSortingLayers");
m_VolumetricIntensity = serializedObject.FindProperty("m_LightVolumeIntensity");
m_VolumetricEnabled = serializedObject.FindProperty("m_LightVolumeEnabled");
m_BlendStyleIndex = serializedObject.FindProperty("m_BlendStyleIndex");
m_FalloffIntensity = serializedObject.FindProperty("m_FalloffIntensity");
m_NormalMapZDistance = serializedObject.FindProperty("m_NormalMapDistance");
m_NormalMapQuality = serializedObject.FindProperty("m_NormalMapQuality");
m_LightOrder = serializedObject.FindProperty("m_LightOrder");
m_OverlapOperation = serializedObject.FindProperty("m_OverlapOperation");
// Point Light
m_PointInnerAngle = serializedObject.FindProperty("m_PointLightInnerAngle");
m_PointOuterAngle = serializedObject.FindProperty("m_PointLightOuterAngle");
m_PointInnerRadius = serializedObject.FindProperty("m_PointLightInnerRadius");
m_PointOuterRadius = serializedObject.FindProperty("m_PointLightOuterRadius");
m_DeprecatedPointLightSprite = serializedObject.FindProperty("m_DeprecatedPointLightCookieSprite");
// Shape Light
m_ShapeLightParametricRadius = serializedObject.FindProperty("m_ShapeLightParametricRadius");
m_ShapeLightFalloffSize = serializedObject.FindProperty("m_ShapeLightFalloffSize");
m_ShapeLightParametricSides = serializedObject.FindProperty("m_ShapeLightParametricSides");
m_ShapeLightSprite = serializedObject.FindProperty("m_LightCookieSprite");
m_AnyBlendStyleEnabled = false;
var blendStyleIndices = new List<int>();
var blendStyleNames = new List<string>();
var rendererData = Light2DEditorUtility.GetRenderer2DData();
if (rendererData != null)
{
for (int i = 0; i < rendererData.lightBlendStyles.Length; ++i)
{
blendStyleIndices.Add(i);
ref var blendStyle = ref rendererData.lightBlendStyles[i];
if (blendStyle.maskTextureChannel == Light2DBlendStyle.TextureChannel.None)
blendStyleNames.Add(blendStyle.name);
else
{
var name = string.Format("{0} ({1})", blendStyle.name, blendStyle.maskTextureChannel);
blendStyleNames.Add(name);
}
m_AnyBlendStyleEnabled = true;
}
}
else
{
for (int i = 0; i < 4; ++i)
{
blendStyleIndices.Add(i);
blendStyleNames.Add("Operation" + i);
}
}
m_BlendStyleIndices = blendStyleIndices.ToArray();
m_BlendStyleNames = blendStyleNames.Select(x => new GUIContent(x)).ToArray();
m_SortingLayerDropDown.OnEnable(serializedObject, "m_ApplyToSortingLayers");
}
internal void SendModifiedAnalytics(Analytics.Renderer2DAnalytics analytics, Light2D light)
{
Analytics.LightDataAnalytic lightData = new Analytics.LightDataAnalytic(light.GetInstanceID(), false, light.lightType);
Analytics.Renderer2DAnalytics.instance.SendData(lightData);
}
void OnDestroy()
{
if (m_ModifiedLights != null && m_ModifiedLights.Count > 0)
{
foreach (Light2D light in m_ModifiedLights)
{
SendModifiedAnalytics(m_Analytics, light);
}
}
}
void DrawBlendingGroup()
{
CoreEditorUtils.DrawSplitter(false);
m_BlendingSettingsFoldout.value = CoreEditorUtils.DrawHeaderFoldout(Styles.blendingSettingsFoldout, m_BlendingSettingsFoldout.value);
if (m_BlendingSettingsFoldout.value)
{
if (!m_AnyBlendStyleEnabled)
EditorGUILayout.HelpBox(Styles.generalLightNoLightEnabled);
else
EditorGUILayout.IntPopup(m_BlendStyleIndex, m_BlendStyleNames, m_BlendStyleIndices, Styles.generalBlendStyle);
EditorGUILayout.PropertyField(m_LightOrder, Styles.generalLightOrder);
EditorGUILayout.PropertyField(m_OverlapOperation, Styles.generalLightOverlapOperation);
}
}
void DrawShadowsGroup()
{
CoreEditorUtils.DrawSplitter(false);
ToggleFoldoutResult result = DrawHeaderFoldoutWithToggle(Styles.shadowsSettingsFoldout, m_ShadowsSettingsFoldout.value, m_ShadowsEnabled.boolValue);
m_ShadowsEnabled.boolValue = result.toggleState;
m_ShadowsSettingsFoldout.value = result.foldoutState;
if (m_ShadowsSettingsFoldout.value)
{
EditorGUI.indentLevel++;
EditorGUI.BeginDisabledGroup(!m_ShadowsEnabled.boolValue);
EditorGUILayout.PropertyField(m_ShadowIntensity, Styles.generalShadowIntensity);
EditorGUILayout.PropertyField(m_ShadowSoftness, Styles.generalShadowSoftness);
EditorGUILayout.PropertyField(m_ShadowSoftnessFalloffIntensity,Styles.generalShadowSoftnessFalloffIntensity);
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
}
}
void DrawVolumetricGroup()
{
CoreEditorUtils.DrawSplitter(false);
ToggleFoldoutResult result = DrawHeaderFoldoutWithToggle(Styles.volumetricSettingsFoldout, m_VolumetricSettingsFoldout.value, m_VolumetricEnabled.boolValue);
m_VolumetricSettingsFoldout.value = result.foldoutState;
m_VolumetricEnabled.boolValue = result.toggleState;
if (m_VolumetricSettingsFoldout.value)
{
EditorGUI.indentLevel++;
EditorGUI.BeginDisabledGroup(!m_VolumetricEnabled.boolValue);
EditorGUILayout.PropertyField(m_VolumetricIntensity, Styles.generalVolumeIntensity);
if (m_VolumetricIntensity.floatValue < 0)
m_VolumetricIntensity.floatValue = 0;
DrawToggleProperty(Styles.generalShadowVolumeIntensity, m_ShadowVolumeIntensityEnabled, m_ShadowVolumeIntensity);
EditorGUI.EndDisabledGroup();
EditorGUI.indentLevel--;
}
}
void DrawNormalMapGroup()
{
CoreEditorUtils.DrawSplitter(false);
m_NormalMapsSettingsFoldout.value = CoreEditorUtils.DrawHeaderFoldout(Styles.normalMapsSettingsFoldout, m_NormalMapsSettingsFoldout.value);
if (m_NormalMapsSettingsFoldout.value)
{
EditorGUILayout.PropertyField(m_NormalMapQuality, Styles.generalNormalMapLightQuality);
EditorGUI.BeginDisabledGroup(m_NormalMapQuality.intValue == (int)Light2D.NormalMapQuality.Disabled);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_NormalMapZDistance, Styles.generalNormalMapZDistance);
if (EditorGUI.EndChangeCheck())
m_NormalMapZDistance.floatValue = Mathf.Max(0.0f, m_NormalMapZDistance.floatValue);
EditorGUI.EndDisabledGroup();
}
}
void DrawFoldouts()
{
DrawBlendingGroup();
DrawShadowsGroup();
DrawVolumetricGroup();
DrawNormalMapGroup();
}
void DrawRadiusProperties(GUIContent label, SerializedProperty innerRadius, GUIContent content1, SerializedProperty outerRadius, GUIContent content2)
{
GUIStyle style = GUI.skin.box;
float savedLabelWidth = EditorGUIUtility.labelWidth;
int savedIndentLevel = EditorGUI.indentLevel;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PrefixLabel(label);
EditorGUILayout.BeginHorizontal();
EditorGUI.indentLevel = 0;
EditorGUIUtility.labelWidth = style.CalcSize(content1).x;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(innerRadius, content1);
if (EditorGUI.EndChangeCheck())
{
if (innerRadius.floatValue > outerRadius.floatValue)
innerRadius.floatValue = outerRadius.floatValue;
else if (innerRadius.floatValue < 0)
innerRadius.floatValue = 0;
}
EditorGUIUtility.labelWidth = style.CalcSize(content2).x;
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(outerRadius, content2);
if (EditorGUI.EndChangeCheck() && outerRadius.floatValue < innerRadius.floatValue)
outerRadius.floatValue = innerRadius.floatValue;
EditorGUILayout.EndHorizontal();
EditorGUILayout.EndHorizontal();
EditorGUIUtility.labelWidth = savedLabelWidth;
EditorGUI.indentLevel = savedIndentLevel;
}
void DrawToggleProperty(GUIContent label, SerializedProperty boolProperty, SerializedProperty property)
{
int savedIndentLevel = EditorGUI.indentLevel;
float savedLabelWidth = EditorGUIUtility.labelWidth;
const int kCheckboxWidth = 20;
EditorGUILayout.BeginHorizontal();
EditorGUILayout.PropertyField(boolProperty, GUIContent.none, GUILayout.MaxWidth(kCheckboxWidth));
EditorGUIUtility.labelWidth = EditorGUIUtility.labelWidth - kCheckboxWidth;
EditorGUI.BeginDisabledGroup(!boolProperty.boolValue);
EditorGUILayout.PropertyField(property, label);
EditorGUI.EndDisabledGroup();
EditorGUILayout.EndHorizontal();
EditorGUI.indentLevel = savedIndentLevel;
EditorGUIUtility.labelWidth = savedLabelWidth;
}
public void DrawInnerAndOuterSpotAngle(SerializedProperty minProperty, SerializedProperty maxProperty, GUIContent label)
{
float textFieldWidth = 45f;
float min = minProperty.floatValue;
float max = maxProperty.floatValue;
var rect = EditorGUILayout.GetControlRect(true, EditorGUIUtility.singleLineHeight);
// This widget is a little bit of a special case.
// The right hand side of the min max slider will control the reset of the max value
// The left hand side of the min max slider will control the reset of the min value
// The label itself will not have a right click and reset value.
rect = EditorGUI.PrefixLabel(rect, label);
EditorGUI.BeginProperty(new Rect(rect) { width = rect.width * 0.5f }, label, minProperty);
EditorGUI.BeginProperty(new Rect(rect) { xMin = rect.x + rect.width * 0.5f }, GUIContent.none, maxProperty);
var minRect = new Rect(rect) { width = textFieldWidth };
var maxRect = new Rect(rect) { xMin = rect.xMax - textFieldWidth };
var sliderRect = new Rect(rect) { xMin = minRect.xMax + 4, xMax = maxRect.xMin - 4 };
EditorGUI.BeginChangeCheck();
EditorGUI.DelayedFloatField(minRect, minProperty, GUIContent.none);
if (EditorGUI.EndChangeCheck())
{
if (minProperty.floatValue > maxProperty.floatValue)
minProperty.floatValue = maxProperty.floatValue;
else if (minProperty.floatValue < 0)
minProperty.floatValue = 0;
}
EditorGUI.BeginChangeCheck();
EditorGUI.MinMaxSlider(sliderRect, ref min, ref max, 0f, 360f);
if (EditorGUI.EndChangeCheck())
{
minProperty.floatValue = min;
maxProperty.floatValue = max;
}
EditorGUI.BeginChangeCheck();
EditorGUI.DelayedFloatField(maxRect, m_PointOuterAngle, GUIContent.none);
if (EditorGUI.EndChangeCheck())
{
if (minProperty.floatValue > maxProperty.floatValue)
maxProperty.floatValue = minProperty.floatValue;
else if (maxProperty.floatValue > 360)
maxProperty.floatValue = 360;
}
EditorGUI.EndProperty();
EditorGUI.EndProperty();
}
void DrawGlobalLight(SerializedObject serializedObject)
{
m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges);
DrawBlendingGroup();
}
void DrawParametricDeprecated(SerializedObject serializedObject)
{
GUIContent buttonText = targets.Length > 1 ? Styles.deprecatedParametricLightButtonMulti : Styles.deprecatedParametricLightButtonSingle;
GUIContent helpText = targets.Length > 1 ? Styles.deprecatedParametricLightWarningMulti : Styles.deprecatedParametricLightWarningSingle;
string dialogText = targets.Length > 1 ? Styles.deprecatedParametricLightDialogTextMulti : Styles.deprecatedParametricLightDialogTextSingle;
EditorGUILayout.HelpBox(helpText);
EditorGUILayout.Space();
if (GUILayout.Button(buttonText))
{
if (EditorUtility.DisplayDialog(Styles.deprecatedParametricLightDialogTitle, dialogText, Styles.deprecatedParametricLightDialogProceed, Styles.deprecatedParametricLightDialogCancel))
{
for (int i = 0; i < targets.Length; i++)
{
Light2D light = (Light2D)targets[i];
if (light.lightType == (Light2D.LightType)Light2D.DeprecatedLightType.Parametric)
ParametricToFreeformLightUpgrader.UpgradeParametricLight(light);
}
}
}
EditorGUILayout.Space();
EditorGUILayout.HelpBox(Styles.deprecatedParametricLightInstructions);
}
bool DrawLightCommon()
{
var meshChanged = false;
Rect lightTypeRect = EditorGUILayout.GetControlRect();
EditorGUI.BeginProperty(lightTypeRect, GUIContent.none, m_LightType);
EditorGUI.BeginChangeCheck();
int newLightType = EditorGUI.Popup(lightTypeRect, Styles.generalLightType, m_LightType.intValue - 1, Styles.lightTypeOptions); // -1 is a bit hacky its to support compatibiltiy. We need something better.
if (EditorGUI.EndChangeCheck())
{
m_LightType.intValue = newLightType + 1; // -1 is a bit hacky its to support compatibiltiy. We need something better.
meshChanged = true;
}
EditorGUI.EndProperty();
// Color and intensity
EditorGUILayout.PropertyField(m_LightColor, Styles.generalLightColor);
EditorGUI.BeginChangeCheck();
EditorGUILayout.PropertyField(m_LightIntensity, Styles.generalLightIntensity);
if (EditorGUI.EndChangeCheck())
m_LightIntensity.floatValue = Mathf.Max(m_LightIntensity.floatValue, 0);
return meshChanged;
}
void DrawSpotLight(SerializedObject serializedObject)
{
DrawRadiusProperties(Styles.pointLightRadius, m_PointInnerRadius, Styles.pointLightInner, m_PointOuterRadius, Styles.pointLightOuter);
DrawInnerAndOuterSpotAngle(m_PointInnerAngle, m_PointOuterAngle, Styles.InnerOuterSpotAngle);
EditorGUILayout.Slider(m_FalloffIntensity, 0, 1, Styles.generalFalloffIntensity);
if (m_DeprecatedPointLightSprite.objectReferenceValue != null)
EditorGUILayout.PropertyField(m_DeprecatedPointLightSprite, Styles.pointLightSprite);
m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges);
DrawFoldouts();
}
void DrawSpriteLight(SerializedObject serializedObject)
{
EditorGUILayout.PropertyField(m_ShapeLightSprite, Styles.shapeLightSprite);
m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges);
DrawFoldouts();
}
void DrawShapeLight(SerializedObject serializedObject)
{
EditorGUILayout.PropertyField(m_ShapeLightFalloffSize, Styles.generalFalloffSize);
if (m_ShapeLightFalloffSize.floatValue < 0)
m_ShapeLightFalloffSize.floatValue = 0;
EditorGUILayout.Slider(m_FalloffIntensity, 0, 1, Styles.generalFalloffIntensity);
m_SortingLayerDropDown.OnTargetSortingLayers(serializedObject, targets, Styles.generalSortingLayerPrefixLabel, AnalyticsTrackChanges);
if (m_LightType.intValue == (int)Light2D.LightType.Freeform)
{
DoEditButton<FreeformShapeTool>(PathEditorToolContents.icon, "Edit Shape");
DoPathInspector<FreeformShapeTool>();
DoSnappingInspector<FreeformShapeTool>();
}
DrawFoldouts();
}
Vector3 DrawAngleSlider2D(Transform transform, Quaternion rotation, float radius, float offset, Handles.CapFunction capFunc, float capSize, bool leftAngle, bool drawLine, bool useCapOffset, ref float angle)
{
float oldAngle = angle;
float angleBy2 = (angle / 2) * (leftAngle ? -1.0f : 1.0f);
Vector3 trcwPos = Quaternion.AngleAxis(angleBy2, -transform.forward) * (transform.up);
Vector3 cwPos = transform.position + trcwPos * (radius + offset);
float direction = leftAngle ? 1 : -1;
// Offset the handle
float size = .25f * capSize;
Vector3 handleOffset = useCapOffset ? rotation * new Vector3(direction * size, 0, 0) : Vector3.zero;
EditorGUI.BeginChangeCheck();
var id = GUIUtility.GetControlID("AngleSlider".GetHashCode(), FocusType.Passive);
Vector3 cwHandle = Handles.Slider2D(id, cwPos, handleOffset, Vector3.forward, rotation * Vector3.up, rotation * Vector3.right, capSize, capFunc, Vector3.zero);
if (EditorGUI.EndChangeCheck())
{
Vector3 toCwHandle = (transform.position - cwHandle).normalized;
angle = 360 - 2 * Quaternion.Angle(Quaternion.FromToRotation(transform.up, toCwHandle), Quaternion.identity);
angle = Mathf.Round(angle * 100) / 100f;
float side = Vector3.Dot(direction * transform.right, toCwHandle);
if (side < 0)
{
if (oldAngle < 180)
angle = 0;
else
angle = 360;
}
}
if (drawLine)
Handles.DrawLine(transform.position, cwHandle);
return cwHandle;
}
private float DrawAngleHandle(Transform transform, float radius, float offset, Handles.CapFunction capLeft, Handles.CapFunction capRight, ref float angle)
{
float old = angle;
float handleOffset = HandleUtility.GetHandleSize(transform.position) * offset;
float handleSize = HandleUtility.GetHandleSize(transform.position) * k_AngleCapSize;
Quaternion rotLt = Quaternion.AngleAxis(-angle / 2, -transform.forward) * transform.rotation;
DrawAngleSlider2D(transform, rotLt, radius, handleOffset, capLeft, handleSize, true, true, true, ref angle);
Quaternion rotRt = Quaternion.AngleAxis(angle / 2, -transform.forward) * transform.rotation;
DrawAngleSlider2D(transform, rotRt, radius, handleOffset, capRight, handleSize, false, true, true, ref angle);
return angle - old;
}
private void DrawRadiusArc(Transform transform, float radius, float angle, int steps, Handles.CapFunction capFunc, float capSize, bool even)
{
Handles.DrawWireArc(transform.position, transform.forward, Quaternion.AngleAxis(180 - angle / 2, transform.forward) * -transform.up, angle, radius);
}
Handles.CapFunction GetCapFunc(Texture texture, bool isAngleHandle)
{
return (controlID, position, rotation, size, eventType) => Light2DEditorUtility.GUITextureCap(controlID, texture, position, rotation, size, eventType, isAngleHandle);
}
private void DrawAngleHandles(Light2D light)
{
var oldColor = Handles.color;
Handles.color = Color.yellow;
float outerAngle = light.pointLightOuterAngle;
float diff = DrawAngleHandle(light.transform, light.pointLightOuterRadius, k_AngleCapOffset, GetCapFunc(Styles.lightCapTopRight, true), GetCapFunc(Styles.lightCapBottomRight, true), ref outerAngle);
light.pointLightOuterAngle = outerAngle;
if (diff != 0.0f)
light.pointLightInnerAngle = Mathf.Max(0.0f, light.pointLightInnerAngle + diff);
float innerAngle = light.pointLightInnerAngle;
diff = DrawAngleHandle(light.transform, light.pointLightOuterRadius, -k_AngleCapOffset, GetCapFunc(Styles.lightCapTopLeft, true), GetCapFunc(Styles.lightCapBottomLeft, true), ref innerAngle);
light.pointLightInnerAngle = innerAngle;
if (diff != 0.0f)
light.pointLightInnerAngle = light.pointLightInnerAngle < light.pointLightOuterAngle ? light.pointLightInnerAngle : light.pointLightOuterAngle;
light.pointLightInnerAngle = Mathf.Min(light.pointLightInnerAngle, light.pointLightOuterAngle);
Handles.color = oldColor;
}
private void DrawRangeHandles(Light2D light)
{
var dummy = 0.0f;
bool radiusChanged = false;
Vector3 handlePos = Vector3.zero;
Quaternion rotLeft = Quaternion.AngleAxis(0, -light.transform.forward) * light.transform.rotation;
float handleOffset = HandleUtility.GetHandleSize(light.transform.position) * k_AngleCapOffsetSecondary;
float handleSize = HandleUtility.GetHandleSize(light.transform.position) * k_AngleCapSize;
var oldColor = Handles.color;
Handles.color = Color.yellow;
float outerRadius = light.pointLightOuterRadius;
EditorGUI.BeginChangeCheck();
Vector3 returnPos = DrawAngleSlider2D(light.transform, rotLeft, outerRadius, -handleOffset, GetCapFunc(Styles.lightCapUp, false), handleSize, false, false, false, ref dummy);
if (EditorGUI.EndChangeCheck())
{
var vec = (returnPos - light.transform.position).normalized;
light.transform.up = new Vector3(vec.x, vec.y, 0);
outerRadius = (returnPos - light.transform.position).magnitude;
outerRadius = outerRadius + handleOffset;
radiusChanged = true;
}
DrawRadiusArc(light.transform, light.pointLightOuterRadius, light.pointLightOuterAngle, 0, Handles.DotHandleCap, k_RangeCapSize, false);
Handles.color = Color.gray;
float innerRadius = light.pointLightInnerRadius;
EditorGUI.BeginChangeCheck();
returnPos = DrawAngleSlider2D(light.transform, rotLeft, innerRadius, handleOffset, GetCapFunc(Styles.lightCapDown, false), handleSize, true, false, false, ref dummy);
if (EditorGUI.EndChangeCheck())
{
innerRadius = (returnPos - light.transform.position).magnitude;
innerRadius = innerRadius - handleOffset;
radiusChanged = true;
}
DrawRadiusArc(light.transform, light.pointLightInnerRadius, light.pointLightOuterAngle, 0, Handles.SphereHandleCap, k_InnerRangeCapSize, false);
Handles.color = oldColor;
if (radiusChanged)
{
light.pointLightInnerRadius = (outerRadius < innerRadius) ? outerRadius : innerRadius;
light.pointLightOuterRadius = (innerRadius > outerRadius) ? innerRadius : outerRadius;
}
}
void OnSceneGUI()
{
var light = target as Light2D;
if (light == null)
return;
Transform t = light.transform;
switch (light.lightType)
{
case Light2D.LightType.Point:
{
Undo.RecordObject(light.transform, "Edit Point Light Transform");
Undo.RecordObject(light, "Edit Point Light");
DrawRangeHandles(light);
DrawAngleHandles(light);
if (GUI.changed)
EditorUtility.SetDirty(light);
}
break;
case Light2D.LightType.Sprite:
{
var cookieSprite = light.lightCookieSprite;
if (cookieSprite != null)
{
Vector3 min = cookieSprite.bounds.min;
Vector3 max = cookieSprite.bounds.max;
Vector3 v0 = t.TransformPoint(new Vector3(min.x, min.y));
Vector3 v1 = t.TransformPoint(new Vector3(max.x, min.y));
Vector3 v2 = t.TransformPoint(new Vector3(max.x, max.y));
Vector3 v3 = t.TransformPoint(new Vector3(min.x, max.y));
Handles.DrawLine(v0, v1);
Handles.DrawLine(v1, v2);
Handles.DrawLine(v2, v3);
Handles.DrawLine(v3, v0);
}
}
break;
case Light2D.LightType.Freeform:
{
// Draw the falloff shape's outline
List<Vector2> falloffShape = light.GetFalloffShape();
Handles.color = Color.white;
for (int i = 0; i < falloffShape.Count - 1; ++i)
{
Handles.DrawLine(t.TransformPoint(falloffShape[i]), t.TransformPoint(falloffShape[i + 1]));
}
if (falloffShape.Count > 0)
Handles.DrawLine(t.TransformPoint(falloffShape[falloffShape.Count - 1]), t.TransformPoint(falloffShape[0]));
for (int i = 0; i < light.shapePath.Length - 1; ++i)
{
Handles.DrawLine(t.TransformPoint(light.shapePath[i]),
t.TransformPoint(light.shapePath[i + 1]));
}
if (light.shapePath.Length > 0)
Handles.DrawLine(t.TransformPoint(light.shapePath[light.shapePath.Length - 1]), t.TransformPoint(light.shapePath[0]));
}
break;
}
}
public override void OnInspectorGUI()
{
var meshChanged = false;
serializedObject.Update();
UniversalRenderPipelineAsset asset = UniversalRenderPipeline.asset;
if (asset != null)
{
if (!Light2DEditorUtility.IsUsing2DRenderer())
{
EditorGUILayout.HelpBox(Styles.asset2DUnassignedWarning);
}
else
{
if (m_LightType.intValue != (int)Light2D.DeprecatedLightType.Parametric)
meshChanged = DrawLightCommon();
switch (m_LightType.intValue)
{
case (int)Light2D.LightType.Point:
{
DrawSpotLight(serializedObject);
}
break;
case (int)Light2D.LightType.Freeform:
{
DrawShapeLight(serializedObject);
}
break;
case (int)Light2D.LightType.Sprite:
{
DrawSpriteLight(serializedObject);
}
break;
case (int)Light2D.LightType.Global:
{
DrawGlobalLight(serializedObject);
}
break;
case (int)Light2D.DeprecatedLightType.Parametric:
{
DrawParametricDeprecated(serializedObject);
}
break;
}
AnalyticsTrackChanges(serializedObject);
if (serializedObject.ApplyModifiedProperties())
{
if (meshChanged)
lightObject.UpdateMesh();
}
}
}
else
{
EditorGUILayout.HelpBox(Styles.renderPipelineUnassignedWarning);
if (meshChanged)
lightObject.UpdateMesh();
}
}
}
internal class Light2DPostProcess : AssetPostprocessor
{
void OnPostprocessSprites(Texture2D texture, Sprite[] sprites)
{
var lights = Resources.FindObjectsOfTypeAll<Light2D>().Where(x => x.lightType == Light2D.LightType.Sprite && x.lightCookieSprite == null);
foreach (var light in lights)
light.MarkForUpdate();
}
}
}