Singularity/Library/PackageCache/com.unity.render-pipelines..../Editor/Lighting/CoreLightEditorUtilities.cs
2024-05-06 11:45:45 -07:00

680 lines
27 KiB
C#

using System;
using UnityEditor.IMGUI.Controls;
using UnityEngine;
using UnityEngine.Rendering;
namespace UnityEditor.Rendering
{
/// <summary>Utility class for drawing light Editor gizmos</summary>
public static class CoreLightEditorUtilities
{
[Flags]
enum HandleDirections
{
Left = 1 << 0,
Up = 1 << 1,
Right = 1 << 2,
Down = 1 << 3,
All = Left | Up | Right | Down
}
static readonly Vector3[] directionalLightHandlesRayPositions =
{
new Vector3(1, 0, 0),
new Vector3(-1, 0, 0),
new Vector3(0, 1, 0),
new Vector3(0, -1, 0),
new Vector3(1, 1, 0).normalized,
new Vector3(1, -1, 0).normalized,
new Vector3(-1, 1, 0).normalized,
new Vector3(-1, -1, 0).normalized
};
/// <summary>
/// Draw a gizmo for a directional light.
/// </summary>
/// <param name="light">The light that is used for this gizmo.</param>
public static void DrawDirectionalLightGizmo(Light light)
{
// Sets the color of the Gizmo.
Color outerColor = GetLightAboveObjectWireframeColor(light.color);
Vector3 lightPos = light.transform.position;
float lightSize;
using (new Handles.DrawingScope(Matrix4x4.identity)) //be sure no matrix affect the size computation
{
lightSize = HandleUtility.GetHandleSize(lightPos);
}
float radius = lightSize * 0.2f;
using (new Handles.DrawingScope(outerColor))
{
Handles.DrawWireDisc(Vector3.zero, Vector3.forward, radius);
foreach (Vector3 normalizedPos in directionalLightHandlesRayPositions)
{
Vector3 pos = normalizedPos * radius;
Handles.DrawLine(pos, pos + new Vector3(0, 0, lightSize));
}
}
}
/// <summary>
/// Draw a gizmo for a point light.
/// </summary>
/// <param name="light">The light that is used for this gizmo.</param>
public static void DrawPointLightGizmo(Light light)
{
// Sets the color of the Gizmo.
Color outerColor = GetLightAboveObjectWireframeColor(light.color);
// Drawing the point light
DrawPointLight(light, outerColor);
// Draw the handles and labels
DrawPointHandlesAndLabels(light);
}
static void DrawPointLight(Light light, Color outerColor)
{
float range = light.range;
using (new Handles.DrawingScope(outerColor))
{
EditorGUI.BeginChangeCheck();
//range = Handles.RadiusHandle(Quaternion.identity, light.transform.position, range, false, true);
range = DoPointHandles(range);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(light, "Adjust Point Light");
light.range = range;
}
}
}
static void DrawPointHandlesAndLabels(Light light)
{
// Getting the first control on point handle
var firstControl = GUIUtility.GetControlID(s_PointLightHandle.GetHashCode(), FocusType.Passive) - 6; // BoxBoundsHandle allocates 6 control IDs
if (Event.current.type != EventType.Repaint)
return;
// var firstControl = GUIUtility.GetControlID(k_RadiusHandleHash, FocusType.Passive) - 6;
// if (Event.current.type != EventType.Repaint)
// return;
// Adding label /////////////////////////////////////
Vector3 labelPosition = Vector3.zero;
if (GUIUtility.hotControl != 0)
{
switch (GUIUtility.hotControl - firstControl)
{
case 0:
labelPosition = Vector3.right * light.range;
break;
case 1:
labelPosition = Vector3.left * light.range;
break;
case 2:
labelPosition = Vector3.up * light.range;
break;
case 3:
labelPosition = Vector3.down * light.range;
break;
case 4:
labelPosition = Vector3.forward * light.range;
break;
case 5:
labelPosition = Vector3.back * light.range;
break;
default:
return;
}
string labelText = FormattableString.Invariant($"Range: {light.range:0.00}");
DrawHandleLabel(labelPosition, labelText);
}
}
/// <summary>
/// Draw a gizmo for an area/rectangle light.
/// </summary>
/// <param name="light">The light that is used for this gizmo.</param>
public static void DrawRectangleLightGizmo(Light light)
{
// Color to use for gizmo drawing
Color outerColor = GetLightAboveObjectWireframeColor(light.color);
// Drawing the gizmo
DrawRectangleLight(light, outerColor);
// Draw the handles and labels
DrawRectangleHandlesAndLabels(light);
}
static void DrawRectangleLight(Light light, Color outerColor)
{
Vector2 size = light.areaSize;
var range = light.range;
var innerColor = GetLightBehindObjectWireframeColor(light.color);
DrawZTestedLine(range, outerColor, innerColor);
using (new Handles.DrawingScope(outerColor))
{
EditorGUI.BeginChangeCheck();
size = DoRectHandles(size);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(light, "Adjust Area Rectangle Light");
light.areaSize = size;
}
}
}
static void DrawRectangleHandlesAndLabels(Light light)
{
// Getting the first control on radius handle
var firstControl = GUIUtility.GetControlID(s_AreaLightHandle.GetHashCode(), FocusType.Passive) - 6; // BoxBoundsHandle allocates 6 control IDs
if (Event.current.type != EventType.Repaint)
return;
// Adding label /////////////////////////////////////
Vector3 labelPosition = Vector3.zero;
if (GUIUtility.hotControl != 0)
{
switch (GUIUtility.hotControl - firstControl)
{
case 0: // PositiveX
labelPosition = Vector3.right * (light.areaSize.x / 2);
break;
case 1: // NegativeX
labelPosition = Vector3.left * (light.areaSize.x / 2);
break;
case 2: // PositiveY
labelPosition = Vector3.up * (light.areaSize.y / 2);
break;
case 3: // NegativeY
labelPosition = Vector3.down * (light.areaSize.y / 2);
break;
default:
return;
}
string labelText = FormattableString.Invariant($"w:{light.areaSize.x:0.00} x h:{light.areaSize.y:0.00}");
DrawHandleLabel(labelPosition, labelText);
}
}
/// <summary>
/// Draw a gizmo for a disc light.
/// </summary>
/// <param name="light">The light that is used for this gizmo.</param>
public static void DrawDiscLightGizmo(Light light)
{
// Color to use for gizmo drawing.
Color outerColor = GetLightAboveObjectWireframeColor(light.color);
// Drawing before objects
DrawDiscLight(light, outerColor);
// Draw handles
DrawDiscHandlesAndLabels(light);
}
static void DrawDiscLight(Light light, Color outerColor)
{
float radius = light.areaSize.x;
var range = light.range;
var innerColor = GetLightBehindObjectWireframeColor(light.color);
DrawZTestedLine(range, outerColor, innerColor);
using (new Handles.DrawingScope(outerColor))
{
EditorGUI.BeginChangeCheck();
radius = DoDiscHandles(radius);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObject(light, "Adjust Area Disc Light");
light.areaSize = new Vector2(radius, light.areaSize.y);
}
}
}
static void DrawDiscHandlesAndLabels(Light light)
{
// Getting the first control on radius handle
var firstControl = GUIUtility.GetControlID(s_DiscLightHandle.GetHashCode(), FocusType.Passive) - 6; // BoxBoundsHandle allocates 6 control IDs
if (Event.current.type != EventType.Repaint)
return;
Vector3 labelPosition = Vector3.zero;
if (GUIUtility.hotControl != 0)
{
switch (GUIUtility.hotControl - firstControl)
{
case 0: // PositiveX
labelPosition = Vector3.right * light.areaSize.x;
break;
case 1: // NegativeX
labelPosition = Vector3.left * light.areaSize.x;
break;
case 2: // PositiveY
labelPosition = Vector3.up * light.areaSize.x;
break;
case 3: // NegativeY
labelPosition = Vector3.down * light.areaSize.x;
break;
default:
return;
}
string labelText = FormattableString.Invariant($"Radius: {light.areaSize.x:0.00}");
DrawHandleLabel(labelPosition, labelText);
}
}
static void DrawWithZTest(PrimitiveBoundsHandle primitiveHandle, float alpha = 0.2f)
{
primitiveHandle.center = Vector3.zero;
primitiveHandle.handleColor = Color.clear;
primitiveHandle.wireframeColor = Color.white;
Handles.zTest = CompareFunction.LessEqual;
primitiveHandle.DrawHandle();
primitiveHandle.wireframeColor = new Color(1f, 1f, 1f, alpha);
Handles.zTest = CompareFunction.Greater;
primitiveHandle.DrawHandle();
primitiveHandle.handleColor = Color.white;
primitiveHandle.wireframeColor = Color.clear;
Handles.zTest = CompareFunction.Always;
primitiveHandle.DrawHandle();
}
static void DrawZTestedLine(float range, Color outerColor, Color innerColor)
{
using (new Handles.DrawingScope(outerColor))
{
Handles.zTest = CompareFunction.LessEqual;
Handles.DrawLine(Vector3.zero, Vector3.forward * range);
}
using (new Handles.DrawingScope(innerColor))
{
Handles.zTest = CompareFunction.Greater;
Handles.DrawLine(Vector3.zero, Vector3.forward * range);
}
Handles.zTest = CompareFunction.Always;
}
static void DrawHandleLabel(Vector3 handlePosition, string labelText, float offsetFromHandle = 0.3f)
{
Vector3 labelPosition = Vector3.zero;
var style = new GUIStyle { normal = { background = Texture2D.whiteTexture } };
GUI.color = new Color(0.82f, 0.82f, 0.82f, 1);
labelPosition = handlePosition + Handles.inverseMatrix.MultiplyVector(Vector3.up) * HandleUtility.GetHandleSize(handlePosition) * offsetFromHandle;
Handles.Label(labelPosition, labelText, style);
}
static readonly BoxBoundsHandle s_AreaLightHandle =
new BoxBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y };
static Vector2 DoRectHandles(Vector2 size)
{
s_AreaLightHandle.center = Vector3.zero;
s_AreaLightHandle.size = size;
DrawWithZTest(s_AreaLightHandle);
return s_AreaLightHandle.size;
}
static readonly SphereBoundsHandle s_DiscLightHandle =
new SphereBoundsHandle { axes = PrimitiveBoundsHandle.Axes.X | PrimitiveBoundsHandle.Axes.Y };
static float DoDiscHandles(float radius)
{
s_DiscLightHandle.center = Vector3.zero;
s_DiscLightHandle.radius = radius;
DrawWithZTest(s_DiscLightHandle);
return s_DiscLightHandle.radius;
}
static readonly SphereBoundsHandle s_PointLightHandle =
new SphereBoundsHandle { axes = PrimitiveBoundsHandle.Axes.All };
static float DoPointHandles(float range)
{
s_PointLightHandle.radius = range;
DrawWithZTest(s_PointLightHandle);
return s_PointLightHandle.radius;
}
static bool drawInnerConeAngle = true;
/// <summary>
/// Draw a gizmo for a spot light.
/// </summary>
/// <param name="light">The light that is used for this gizmo.</param>
public static void DrawSpotLightGizmo(Light light)
{
// Saving the default colors
var defColor = Handles.color;
var defZTest = Handles.zTest;
// Default Color for outer cone will be Yellow if nothing has been provided.
Color outerColor = GetLightAboveObjectWireframeColor(light.color);
// The default z-test outer color will be 20% opacity of the outer color
Color outerColorZTest = GetLightBehindObjectWireframeColor(outerColor);
// Default Color for inner cone will be Yellow-ish if nothing has been provided.
Color innerColor = GetLightInnerConeColor(light.color);
// The default z-test outer color will be 20% opacity of the inner color
Color innerColorZTest = GetLightBehindObjectWireframeColor(innerColor);
// Drawing before objects
Handles.zTest = CompareFunction.LessEqual;
DrawSpotlightWireframe(light, outerColor, innerColor);
// Drawing behind objects
Handles.zTest = CompareFunction.Greater;
DrawSpotlightWireframe(light, outerColorZTest, innerColorZTest);
// Resets the compare function to always
Handles.zTest = CompareFunction.Always;
// Draw handles
if (!Event.current.alt)
{
DrawHandlesAndLabels(light, outerColor);
}
// Resets the handle colors
Handles.color = defColor;
Handles.zTest = defZTest;
}
static void DrawHandlesAndLabels(Light light, Color color)
{
// Zero position vector3
Vector3 zeroPos = Vector3.zero;
// Variable for which direction to draw the handles
HandleDirections DrawHandleDirections;
// Draw the handles ///////////////////////////////
Handles.color = color;
// Draw Center Handle
float range = light.range;
var id = GUIUtility.GetControlID(FocusType.Passive);
EditorGUI.BeginChangeCheck();
range = SliderLineHandle(id, Vector3.zero, Vector3.forward, range, "Range: ");
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(new[] { light }, "Undo range change.");
}
// Draw outer handles
DrawHandleDirections = HandleDirections.Down | HandleDirections.Up;
const string outerLabel = "Outer Angle: ";
EditorGUI.BeginChangeCheck();
float outerAngle = DrawConeHandles(zeroPos, light.spotAngle, range, DrawHandleDirections, outerLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(new[] { light }, "Undo outer angle change.");
}
// Draw inner handles
float innerAngle = 0;
const string innerLabel = "Inner Angle: ";
if (light.innerSpotAngle > 0f && drawInnerConeAngle)
{
DrawHandleDirections = HandleDirections.Left | HandleDirections.Right;
EditorGUI.BeginChangeCheck();
innerAngle = DrawConeHandles(zeroPos, light.innerSpotAngle, range, DrawHandleDirections, innerLabel);
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(new[] { light }, "Undo inner angle change.");
}
}
// Draw Near Plane Handle
float nearPlaneRange = light.shadowNearPlane;
if (light.shadows != LightShadows.None && light.lightmapBakeType != LightmapBakeType.Baked)
{
EditorGUI.BeginChangeCheck();
nearPlaneRange = SliderLineHandle(GUIUtility.GetControlID(FocusType.Passive), Vector3.zero, Vector3.forward, nearPlaneRange, "Near Plane: ");
if (EditorGUI.EndChangeCheck())
{
Undo.RecordObjects(new[] { light }, "Undo shadow near plane change.");
nearPlaneRange = Mathf.Clamp(nearPlaneRange, 0.1f, light.range);
}
}
// If changes has been made we update the corresponding property
if (GUI.changed)
{
light.spotAngle = outerAngle;
light.innerSpotAngle = innerAngle;
light.range = Math.Max(range, 0.01f);
light.shadowNearPlane = Mathf.Clamp(nearPlaneRange, 0.1f, light.range);
}
}
static Color GetLightInnerConeColor(Color wireframeColor)
{
Color color = wireframeColor;
color.a = 0.4f;
return RemapLightColor(CoreUtils.ConvertLinearToActiveColorSpace(color.linear));
}
static Color GetLightAboveObjectWireframeColor(Color wireframeColor)
{
Color color = wireframeColor;
color.a = 1f;
return RemapLightColor(CoreUtils.ConvertLinearToActiveColorSpace(color.linear));
}
static Color GetLightBehindObjectWireframeColor(Color wireframeColor)
{
Color color = wireframeColor;
color.a = 0.2f;
return RemapLightColor(CoreUtils.ConvertLinearToActiveColorSpace(color.linear));
}
static Color RemapLightColor(Color src)
{
Color color = src;
float max = Mathf.Max(Mathf.Max(color.r, color.g), color.b);
if (max > 0f)
{
float mult = 1f / max;
color.r *= mult;
color.g *= mult;
color.b *= mult;
}
else
{
color = Color.white;
}
return color;
}
static void DrawSpotlightWireframe(Light spotlight, Color outerColor, Color innerColor)
{
// Variable for which direction to draw the handles
HandleDirections DrawHandleDirections;
float outerAngle = spotlight.spotAngle;
float innerAngle = spotlight.innerSpotAngle;
float range = spotlight.range;
var outerDiscRadius = range * Mathf.Sin(outerAngle * Mathf.Deg2Rad * 0.5f);
var outerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * outerAngle * 0.5f) * range;
var vectorLineUp = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.up * outerDiscRadius);
var vectorLineLeft = Vector3.Normalize(Vector3.forward * outerDiscDistance + Vector3.left * outerDiscRadius);
// Need to check if we need to draw inner angle
// Need to disable this for now until we get all the inner angle baking working.
if (innerAngle > 0f && drawInnerConeAngle)
{
DrawHandleDirections = HandleDirections.Up | HandleDirections.Down;
var innerDiscRadius = range * Mathf.Sin(innerAngle * Mathf.Deg2Rad * 0.5f);
var innerDiscDistance = Mathf.Cos(Mathf.Deg2Rad * innerAngle * 0.5f) * range;
// Drawing the inner Cone and also z-testing it to draw another color if behind
Handles.color = innerColor;
DrawConeWireframe(innerDiscRadius, innerDiscDistance, DrawHandleDirections);
}
// Draw range line
Handles.color = innerColor;
var rangeCenter = Vector3.forward * range;
Handles.DrawLine(Vector3.zero, rangeCenter);
// Drawing the outer Cone and also z-testing it to draw another color if behind
Handles.color = outerColor;
DrawHandleDirections = HandleDirections.Left | HandleDirections.Right;
DrawConeWireframe(outerDiscRadius, outerDiscDistance, DrawHandleDirections);
// Bottom arcs, making a nice rounded shape
Handles.DrawWireArc(Vector3.zero, Vector3.right, vectorLineUp, outerAngle, range);
Handles.DrawWireArc(Vector3.zero, Vector3.up, vectorLineLeft, outerAngle, range);
// If we are using shadows we draw the near plane for shadows
if (spotlight.shadows != LightShadows.None && spotlight.lightmapBakeType != LightmapBakeType.Baked)
{
DrawShadowNearPlane(spotlight, innerColor);
}
}
static void DrawShadowNearPlane(Light spotlight, Color color)
{
Color previousColor = Handles.color;
Handles.color = color;
var shadowDiscRadius = Mathf.Tan(spotlight.spotAngle * Mathf.Deg2Rad * 0.5f) * spotlight.shadowNearPlane;
var shadowDiscDistance = spotlight.shadowNearPlane;
Handles.DrawWireDisc(Vector3.forward * shadowDiscDistance, Vector3.forward, shadowDiscRadius);
Handles.DrawLine(Vector3.forward * shadowDiscDistance, (Vector3.right * shadowDiscRadius) + (Vector3.forward * shadowDiscDistance));
Handles.DrawLine(Vector3.forward * shadowDiscDistance, (-Vector3.right * shadowDiscRadius) + (Vector3.forward * shadowDiscDistance));
Handles.color = previousColor;
}
static void DrawConeWireframe(float radius, float height, HandleDirections handleDirections)
{
var rangeCenter = Vector3.forward * height;
if (handleDirections.HasFlag(HandleDirections.Up))
{
var rangeUp = rangeCenter + Vector3.up * radius;
Handles.DrawLine(Vector3.zero, rangeUp);
}
if (handleDirections.HasFlag(HandleDirections.Down))
{
var rangeDown = rangeCenter - Vector3.up * radius;
Handles.DrawLine(Vector3.zero, rangeDown);
}
if (handleDirections.HasFlag(HandleDirections.Right))
{
var rangeRight = rangeCenter + Vector3.right * radius;
Handles.DrawLine(Vector3.zero, rangeRight);
}
if (handleDirections.HasFlag(HandleDirections.Left))
{
var rangeLeft = rangeCenter - Vector3.right * radius;
Handles.DrawLine(Vector3.zero, rangeLeft);
}
//Draw Circle
Handles.DrawWireDisc(rangeCenter, Vector3.forward, radius);
}
static float DrawConeHandles(Vector3 position, float angle, float range, HandleDirections handleDirections, string controlName)
{
if (handleDirections.HasFlag(HandleDirections.Left))
{
angle = SizeSliderSpotAngle(position, Vector3.forward, -Vector3.right, range, angle, controlName);
}
if (handleDirections.HasFlag(HandleDirections.Up))
{
angle = SizeSliderSpotAngle(position, Vector3.forward, Vector3.up, range, angle, controlName);
}
if (handleDirections.HasFlag(HandleDirections.Right))
{
angle = SizeSliderSpotAngle(position, Vector3.forward, Vector3.right, range, angle, controlName);
}
if (handleDirections.HasFlag(HandleDirections.Down))
{
angle = SizeSliderSpotAngle(position, Vector3.forward, -Vector3.up, range, angle, controlName);
}
return angle;
}
static float SliderLineHandle(int id, Vector3 position, Vector3 direction, float value, string labelText = "")
{
Vector3 pos = position + direction * value;
float sizeHandle = HandleUtility.GetHandleSize(pos);
bool temp = GUI.changed;
GUI.changed = false;
pos = Handles.Slider(id, pos, direction, sizeHandle * 0.03f, Handles.DotHandleCap, 0f);
if (GUI.changed)
{
value = Vector3.Dot(pos - position, direction);
}
GUI.changed |= temp;
if (GUIUtility.hotControl == id && !String.IsNullOrEmpty(labelText))
{
labelText += FormattableString.Invariant($"{value:0.00}");
DrawHandleLabel(pos, labelText);
}
return value;
}
static float SizeSliderSpotAngle(Vector3 position, Vector3 forward, Vector3 axis, float range, float spotAngle, string controlName)
{
if (Math.Abs(spotAngle) <= 0.05f)
return spotAngle;
var angledForward = Quaternion.AngleAxis(Mathf.Max(spotAngle, 0.05f) * 0.5f, axis) * forward;
var centerToLeftOnSphere = (angledForward * range + position) - (position + forward * range);
bool temp = GUI.changed;
GUI.changed = false;
var handlePosition = position + forward * range;
var id = GUIUtility.GetControlID(FocusType.Passive);
var newMagnitude = Mathf.Max(0f, SliderLineHandle(id, handlePosition, centerToLeftOnSphere.normalized, centerToLeftOnSphere.magnitude));
if (GUI.changed)
{
centerToLeftOnSphere = centerToLeftOnSphere.normalized * newMagnitude;
angledForward = (centerToLeftOnSphere + (position + forward * range) - position).normalized;
spotAngle = Mathf.Clamp(Mathf.Acos(Vector3.Dot(forward, angledForward)) * Mathf.Rad2Deg * 2, 0f, 179f);
if (spotAngle <= 0.05f || float.IsNaN(spotAngle))
spotAngle = 0f;
}
GUI.changed |= temp;
if (GUIUtility.hotControl == id)
{
var pos = handlePosition + centerToLeftOnSphere.normalized * newMagnitude;
string labelText = FormattableString.Invariant($"{controlName} {spotAngle:0.00}");
DrawHandleLabel(pos, labelText);
}
return spotAngle;
}
}
}