2021-09-09 20:42:29 -04:00

411 lines
21 KiB
C#

using System;
using UnityEngine;
using System.Linq;
using UnityEditor.Rendering;
namespace UnityEditor
{
static class ShadowCascadeGUI
{
private const int kSliderbarTopMargin = 2;
private const int kSliderbarHeight = 25;
private const int kSliderbarBottomMargin = 2;
private const int kPartitionHandleWidth = 2;
private const int kPartitionHandleExtraHitAreaWidth = 2;
private static GUIStyle s_UpSwatch = "Grad Up Swatch";
private static GUIStyle s_DownSwatch = "Grad Down Swatch";
private static int s_BlendHandleSelected = -1;
private static readonly GUIContent s_Text = new GUIContent();
private static GUIContent TempGUIContent(string label, string tooltip)
{
s_Text.text = label;
s_Text.tooltip = tooltip;
return s_Text;
}
private static readonly Color[] kCascadeColors =
{
new Color(0.5f, 0.5f, 0.6f, 1.0f),
new Color(0.5f, 0.6f, 0.5f, 1.0f),
new Color(0.6f, 0.6f, 0.5f, 1.0f),
new Color(0.6f, 0.5f, 0.5f, 1.0f),
};
private static readonly Color kDisabledColor = new Color(0.5f, 0.5f, 0.5f, 0.4f); //works with both personal and pro skin
class LazyTextureArray
{
Texture2D[] values = new[]
{
new Texture2D(1, 1),
new Texture2D(1, 1),
new Texture2D(1, 1),
new Texture2D(1, 1),
};
public Texture2D this[int index]
{
get
{
if (index < 0 || 3 < index)
throw new IndexOutOfRangeException();
if (values.Length != 4)
{
values = new[]
{
new Texture2D(1, 1),
new Texture2D(1, 1),
new Texture2D(1, 1),
new Texture2D(1, 1),
};
}
var value = values[index];
if (value == null || value.Equals(null))
value = values[index] = new Texture2D(1, 1);
return value;
}
}
}
private static readonly Lazy<LazyTextureArray> kBorderBlends = new Lazy<LazyTextureArray>();
// using a LODGroup skin
private static readonly GUIStyle s_CascadeSliderBG = "LODSliderRange";
private static readonly GUIStyle s_TextCenteredStyle = new GUIStyle(EditorStyles.whiteMiniLabel)
{
alignment = TextAnchor.MiddleCenter
};
// Internal struct to bundle drag information
private class DragCache
{
public int activePartition; // the cascade partition that we are currently dragging/resizing
public float normalizedPartitionSize; // the normalized size of the partition (0.0f < size < 1.0f)
public float endBlendAreaPercent;
public Vector2 lastCachedMousePosition; // mouse position the last time we registered a drag or mouse down.
public bool isEndBlendArea;
public DragCache(int activePartition, float normalizedPartitionSize, float endBlendAreaPercent, Vector2 currentMousePos, bool isEndBlendArea)
{
this.activePartition = activePartition;
this.normalizedPartitionSize = normalizedPartitionSize;
this.endBlendAreaPercent = endBlendAreaPercent;
this.isEndBlendArea = isEndBlendArea;
lastCachedMousePosition = currentMousePos;
}
};
private static DragCache s_DragCache;
private static readonly int s_CascadeSliderId = "s_CascadeSliderId".GetHashCode();
private static SceneView s_RestoreSceneView;
private static SceneView.CameraMode s_OldSceneDrawMode;
private static bool s_OldSceneLightingMode;
/**
* Static function to handle the GUI and User input related to the cascade slider.
*
* @param normalizedCascadePartition The array of partition sizes in the range 0.0f - 1.0f; expects ONE entry if cascades = 2, and THREE if cascades=4
* The last entry will be automatically determined by summing up the array, and doing 1.0f - sum
*/
static void HandleCascadeSliderGUI(ref float[] normalizedCascadePartitions, ref float[] endPartitionBordersPercent, bool[] enabledCascadePartitionHandles, bool[] enabledEndPartitionBorderHandles, bool drawEndPartitionHandles, bool useMetric, float baseMetric)
{
EditorGUILayout.BeginVertical();
GUILayout.Space(10);
EditorGUILayout.BeginHorizontal();
// get the inspector width since we need it while drawing the partition rects.
// Only way currently is to reserve the block in the layout using GetRect(), and then immediately drawing the empty box
// to match the call to GetRect.
// From this point on, we move to non-layout based code.
var sliderRect = GUILayoutUtility.GetRect(GUIContent.none
, s_CascadeSliderBG
, GUILayout.Height(kSliderbarTopMargin + kSliderbarHeight + kSliderbarBottomMargin)
, GUILayout.ExpandWidth(true));
GUI.Box(sliderRect, GUIContent.none);
float currentX = sliderRect.x + 3;
float cascadeBoxStartY = sliderRect.y + kSliderbarTopMargin;
int borderAdjustment = (3 - normalizedCascadePartitions.Length) * 2;
float cascadeSliderWidth = sliderRect.width - (normalizedCascadePartitions.Length * kPartitionHandleWidth) - borderAdjustment;
Color origTextColor = GUI.color;
Color origBackgroundColor = GUI.backgroundColor;
int colorIndex = -1;
// setup the array locally with the last partition
float[] adjustedCascadePartitions = new float[normalizedCascadePartitions.Length + 1];
Array.Copy(normalizedCascadePartitions, adjustedCascadePartitions, normalizedCascadePartitions.Length);
adjustedCascadePartitions[adjustedCascadePartitions.Length - 1] = 1.0f - normalizedCascadePartitions.Sum();
// check for user input on any of the partition handles
// this mechanism gets the current event in the queue... make sure that the mouse is over our control before consuming the event
int sliderControlId = GUIUtility.GetControlID(s_CascadeSliderId, FocusType.Passive);
Event currentEvent = Event.current;
int hotPartitionHandleIndex = -1; // the index of any partition handle that we are hovering over or dragging
EventType eventType = currentEvent.GetTypeForControl(sliderControlId);
// draw each cascade partition
for (int i = 0; i < adjustedCascadePartitions.Length; ++i)
{
float currentPartition = adjustedCascadePartitions[i];
colorIndex = (colorIndex + 1) % kCascadeColors.Length;
GUI.backgroundColor = kCascadeColors[colorIndex];
float boxLength = Mathf.RoundToInt(cascadeSliderWidth * currentPartition);
// main cascade box
Rect partitionRect = new Rect(currentX, cascadeBoxStartY, boxLength, kSliderbarHeight);
GUI.Box(partitionRect, GUIContent.none, s_CascadeSliderBG);
currentX += boxLength;
// cascade box texts preparation
Rect fullCascadeText = partitionRect;
Rect blendCascadeText = partitionRect;
blendCascadeText.x += partitionRect.width;
blendCascadeText.width = 0f;
float fullCascadeValue = currentPartition * 100.0f;
float blendCascadeValue = 0f;
Rect separationRect = partitionRect;
if (i < endPartitionBordersPercent.Length)
{
// partition blend background and separators
GUI.backgroundColor = Color.black;
separationRect.width = Mathf.Max(kPartitionHandleWidth, Mathf.CeilToInt(endPartitionBordersPercent[i] * partitionRect.width));
separationRect.x = Mathf.CeilToInt(partitionRect.x + partitionRect.width - separationRect.width);
GUI.Box(separationRect, GUIContent.none, s_CascadeSliderBG);
//update cascade box texts update
blendCascadeValue = endPartitionBordersPercent[i] * currentPartition * 100f;
fullCascadeValue -= blendCascadeValue;
blendCascadeText.x -= separationRect.width - 1; //remove left border
blendCascadeText.width = endPartitionBordersPercent[i] * boxLength;
fullCascadeText.width -= separationRect.width;
blendCascadeText.width -= 3; //remove right border
}
// full cascade box text
GUI.color = Color.white;
float textValue = fullCascadeValue;
if (useMetric)
textValue *= baseMetric / 100;
var cascadeText = String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat, "{0}\n{1:F1}{2}", i, textValue, useMetric ? 'm' : '%');
GUI.Label(fullCascadeText, TempGUIContent(cascadeText, cascadeText), s_TextCenteredStyle);
if (i >= endPartitionBordersPercent.Length)
break;
// partition blend gradient
Rect gradientRect = separationRect;
gradientRect.x += 1;
gradientRect.width -= 3;
if (gradientRect.width > 0)
{
kBorderBlends.Value[i].Resize((int)gradientRect.width, 1);
FillWithGradient(kBorderBlends.Value[i], kCascadeColors[i], i < adjustedCascadePartitions.Length - 1 ? kCascadeColors[i + 1] : Color.black);
GUI.DrawTexture(gradientRect, kBorderBlends.Value[i]);
}
// blend cascade box text
textValue = blendCascadeValue;
if (useMetric)
textValue *= baseMetric / 100;
if (i == normalizedCascadePartitions.Length)
{
cascadeText = String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat, "{0}\u2192{1}\n{2:F1}{3}", i, blendCascadeText.width < 57 ? "F." : "Fallback", textValue, useMetric ? 'm' : '%');
string cascadeToolTip = String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat, "{0}\u2192{1}\n{2:F1}{3}", i, "Fallback", textValue, useMetric ? 'm' : '%');
GUI.Label(blendCascadeText, TempGUIContent(cascadeText, cascadeToolTip), s_TextCenteredStyle);
}
else
{
cascadeText = String.Format(System.Globalization.CultureInfo.InvariantCulture.NumberFormat, "{0}\u2192{1}\n{2:F1}{3}", i, i + 1, textValue, useMetric ? 'm' : '%');
GUI.Label(blendCascadeText, TempGUIContent(cascadeText, cascadeText), s_TextCenteredStyle);
}
// init rect for Swatches
Rect cascadeHandleRect = default;
if (i < normalizedCascadePartitions.Length)
{
cascadeHandleRect = separationRect;
cascadeHandleRect.x += separationRect.width - 6f;
cascadeHandleRect.width = enabledCascadePartitionHandles[i] ? 10 : 0;
cascadeHandleRect.y -= 14;
cascadeHandleRect.height = 15;
}
Rect blendHandleRect = default;
if (drawEndPartitionHandles)
{
blendHandleRect = separationRect;
blendHandleRect.x -= 5f;
blendHandleRect.width = enabledEndPartitionBorderHandles[i] ? 10 : 0;
blendHandleRect.y += 22;
blendHandleRect.height = 15;
}
if (eventType == EventType.Repaint) //Can only draw the snatch in repaint event
{
// Add handle to change end of cascade
if (i < normalizedCascadePartitions.Length)
{
GUI.backgroundColor = enabledCascadePartitionHandles[i] ? kCascadeColors[colorIndex + 1] : kDisabledColor;
s_DownSwatch.Draw(cascadeHandleRect, false, false, s_BlendHandleSelected == i, false);
}
if (drawEndPartitionHandles)
{
GUI.backgroundColor = enabledEndPartitionBorderHandles[i] ? kCascadeColors[colorIndex] : kDisabledColor;
s_UpSwatch.Draw(blendHandleRect, false, false, s_BlendHandleSelected == i + 100, false);
}
}
if (cascadeHandleRect.Contains(currentEvent.mousePosition))
hotPartitionHandleIndex = i;
if (blendHandleRect.Contains(currentEvent.mousePosition))
hotPartitionHandleIndex = i + 100;
// add regions to slider where the cursor changes to Resize-Horizontal
EditorGUIUtility.AddCursorRect(cascadeHandleRect, MouseCursor.ResizeHorizontal, sliderControlId);
EditorGUIUtility.AddCursorRect(blendHandleRect, MouseCursor.ResizeHorizontal, sliderControlId);
}
GUI.color = origTextColor;
GUI.backgroundColor = origBackgroundColor;
switch (eventType)
{
case EventType.MouseDown:
if (hotPartitionHandleIndex >= 0)
{
if (hotPartitionHandleIndex < 100)
s_DragCache = new DragCache(hotPartitionHandleIndex, normalizedCascadePartitions[hotPartitionHandleIndex], hotPartitionHandleIndex >= endPartitionBordersPercent.Length ? 0f : endPartitionBordersPercent[hotPartitionHandleIndex], currentEvent.mousePosition, isEndBlendArea: false);
else
{
int endIndex = hotPartitionHandleIndex - 100;
s_DragCache = new DragCache(endIndex, adjustedCascadePartitions[endIndex], endPartitionBordersPercent[endIndex], currentEvent.mousePosition, isEndBlendArea: true);
}
if (GUIUtility.hotControl == 0)
GUIUtility.hotControl = sliderControlId;
currentEvent.Use();
}
break;
case EventType.MouseUp:
// mouseUp event anywhere should release the hotcontrol (if it belongs to us), drags (if any)
if (GUIUtility.hotControl == sliderControlId)
{
GUIUtility.hotControl = 0;
currentEvent.Use();
}
s_DragCache = null;
break;
case EventType.MouseDrag:
if (GUIUtility.hotControl != sliderControlId)
break;
// convert the mouse movement to normalized cascade width. Make sure that we are safe to apply the delta before using it.
if (s_DragCache.isEndBlendArea)
{
float delta = (currentEvent.mousePosition - s_DragCache.lastCachedMousePosition).x / (cascadeSliderWidth * adjustedCascadePartitions[s_DragCache.activePartition]);
s_DragCache.endBlendAreaPercent = Mathf.Clamp01(s_DragCache.endBlendAreaPercent - delta);
endPartitionBordersPercent[s_DragCache.activePartition] = s_DragCache.endBlendAreaPercent;
GUI.changed = true;
}
else
{
float delta = (currentEvent.mousePosition - s_DragCache.lastCachedMousePosition).x / cascadeSliderWidth;
bool isLeftPartitionPositive = ((adjustedCascadePartitions[s_DragCache.activePartition] + delta) > 0.0f);
bool isRightPartitionPositive = ((adjustedCascadePartitions[s_DragCache.activePartition + 1] - delta) > 0.0f);
if (isLeftPartitionPositive && isRightPartitionPositive)
{
s_DragCache.normalizedPartitionSize += delta;
normalizedCascadePartitions[s_DragCache.activePartition] = s_DragCache.normalizedPartitionSize;
if (s_DragCache.activePartition < normalizedCascadePartitions.Length - 1)
normalizedCascadePartitions[s_DragCache.activePartition + 1] -= delta;
GUI.changed = true;
}
}
s_DragCache.lastCachedMousePosition = currentEvent.mousePosition;
currentEvent.Use();
break;
}
EditorGUILayout.EndHorizontal();
GUILayout.Space(6);
EditorGUILayout.EndVertical();
}
public static void FillWithGradient(Texture2D tex, Color left, Color right)
{
if (tex == null || tex.height != 1 || tex.width < 1)
throw new ArgumentException("The given texture must be initialized with only one pixel in height");
int width = tex.width;
Color[] colours = new Color[width];
for (int i = 0; i < width; ++i)
colours[i] = Color.Lerp(left, right, i / (float)(width - 1));
tex.SetPixels(colours);
tex.Apply();
}
public static void DrawCascadeSplitGUI(SerializedDataParameter[] splits, SerializedDataParameter[] borders, uint cascadeCount, bool blendLastCascade = false, bool useMetric = false, float baseMetric = 10f)
{
if (cascadeCount <= 0)
throw new ArgumentException("Cascade amount must be strictly positive");
uint splitCount = cascadeCount - 1;
if (splitCount > splits.Length)
throw new ArgumentException("Cannot use more splits than provided.");
float[] cascadePartitionSizes = new float[splitCount]; //does not handle remaining (last partition)
float[] cascadeEndBlendPercent = new float[blendLastCascade ? cascadeCount : splitCount];
bool[] enabledPartitionHandles = new bool[splitCount];
for (int i = 0; i < splitCount; ++i)
enabledPartitionHandles[i] = splits[i].overrideState.boolValue;
bool[] enabledEndPartitionHandles = new bool[cascadeEndBlendPercent.Length];
for (int i = 0; i < cascadeEndBlendPercent.Length; ++i)
enabledEndPartitionHandles[i] = borders == null || borders.Length <= i ? false : borders[i].overrideState.boolValue;
if (splitCount > 0)
{
cascadePartitionSizes[0] = Mathf.Max(0f, splits[0].value.floatValue);
cascadeEndBlendPercent[0] = borders == null || borders.Length <= 0 ? 0f : Mathf.Clamp01(borders[0].value.floatValue);
}
for (int index = 1; index < splitCount; ++index)
{
cascadePartitionSizes[index] = Mathf.Max(0f, splits[index].value.floatValue - splits[index - 1].value.floatValue);
cascadeEndBlendPercent[index] = borders == null || borders.Length <= index ? 0f : Mathf.Clamp01(borders[index].value.floatValue);
}
if (blendLastCascade && borders != null && borders.Length > splitCount)
cascadeEndBlendPercent[splitCount] = Mathf.Clamp01(borders[splitCount].value.floatValue);
if (cascadePartitionSizes != null)
{
EditorGUI.BeginChangeCheck();
HandleCascadeSliderGUI(ref cascadePartitionSizes, ref cascadeEndBlendPercent, enabledPartitionHandles, enabledEndPartitionHandles, drawEndPartitionHandles: borders != null, useMetric, baseMetric);
if (EditorGUI.EndChangeCheck())
{
if (splitCount > 0)
{
splits[0].value.floatValue = Mathf.Max(0f, cascadePartitionSizes[0]);
if (borders != null)
borders[0].value.floatValue = Mathf.Clamp01(cascadeEndBlendPercent[0]);
}
for (int index = 1; index < splitCount; ++index)
{
splits[index].value.floatValue = splits[index - 1].value.floatValue + Mathf.Max(0f, cascadePartitionSizes[index]);
if (borders != null)
borders[index].value.floatValue = Mathf.Clamp01(cascadeEndBlendPercent[index]);
}
if (blendLastCascade && borders != null)
borders[splitCount].value.floatValue = Mathf.Clamp01(cascadeEndBlendPercent[splitCount]);
}
}
}
}
}