A game about forced loneliness, made by TACStudios
at master 354 lines 17 kB view raw
1using System; 2using UnityEngine; 3using UnityEngine.Rendering; 4 5namespace UnityEditor.Rendering 6{ 7 internal static class LightIntensitySlider 8 { 9 // Note: To have the right icons along the skin, we do not use the editor resource loading mechanism at the moment. This could be revisited once this is converted to UITK. 10 static Texture2D GetLightUnitIcon(string name) 11 { 12 return CoreEditorUtils.LoadIcon(@"Packages/com.unity.render-pipelines.core/Editor/Lighting/Icons/LightUnitIcons", name, ".png"); 13 } 14 15 // TODO: Move all light unit icons from the package into the built-in resources. 16 static Texture2D Candlelight = GetLightUnitIcon("Candlelight"); 17 static Texture2D DecorativeLight = GetLightUnitIcon("DecorativeLight"); 18 static Texture2D ExteriorLight = GetLightUnitIcon("ExteriorLight"); 19 static Texture2D InteriorLight = GetLightUnitIcon("InteriorLight"); 20 static Texture2D Moonlight = GetLightUnitIcon("Moonlight"); 21 static Texture2D Overcast = GetLightUnitIcon("Overcast"); 22 static Texture2D SunriseSunset = GetLightUnitIcon("SunriseSunset"); 23 static Texture2D BrightSky = GetLightUnitIcon("BrightSky"); 24 25 static GUIStyle k_IconButton = new ("IconButton"); 26 27 private static readonly LightUnitSliderUIRange[] LumenRanges = 28 { 29 new (Candlelight, "Candle", new Vector2(0, 15), 12.5f), 30 new (DecorativeLight, "Decorative", new Vector2(15, 300), 100), 31 new (InteriorLight, "Interior", new Vector2(300, 3000), 1000), 32 new (ExteriorLight, "Exterior", new Vector2(3000, 40000), 10000), 33 }; 34 private static readonly float[] LumenDistribution = { 0f, 0.25f, 0.5f, 0.75f, 1f }; 35 36 private static readonly LightUnitSliderUIRange[] LuxRanges = 37 { 38 new (Moonlight, "Moon", new Vector2(0, 1), 0.5f), 39 new (SunriseSunset, "Low Sun", new Vector2(1, 10000), 5000), 40 new (Overcast, "Cloudy", new Vector2(10000, 80000), 20000), 41 new (BrightSky, "High Sun", new Vector2(80000, 130000), 100000), 42 }; 43 private static readonly float[] LuxDistribution = { 0.0f, 0.05f, 0.5f, 0.9f, 1.0f }; 44 45 private const float ConstantNitsToLumenArea = 200.0f; 46 47 internal static void Draw(ISerializedLight serialized, Editor owner, Rect baseRect) 48 { 49 // Calculate UI rects 50 Rect sliderRect, iconRect; 51 { 52 sliderRect = baseRect; 53 sliderRect.width -= EditorGUIUtility.singleLineHeight; 54 55 iconRect = baseRect; 56 iconRect.x += sliderRect.width; 57 iconRect.width = EditorGUIUtility.singleLineHeight; 58 } 59 60 Light light = serialized.settings.light; 61 LightType lightType = serialized.settings.lightType.GetEnumValue<LightType>(); 62 LightUnit nativeUnit = LightUnitUtils.GetNativeLightUnit(lightType); 63 LightUnit lightUnit = serialized.settings.lightUnit.GetEnumValue<LightUnit>(); 64 bool usesLuxBasedRange = lightType == LightType.Directional; 65 66 LightUnitSliderUIRange[] ranges = usesLuxBasedRange ? LuxRanges : LumenRanges; 67 float[] distribution = usesLuxBasedRange ? LuxDistribution : LumenDistribution; 68 69 // Verify that ui light unit is in fact supported or revert to native. 70 lightUnit = LightUnitUtils.IsLightUnitSupported(lightType, lightUnit) ? lightUnit : nativeUnit; 71 72 Debug.Assert(ranges.Length == distribution.Length - 1); 73 74 // This intensity is in the native light unit for the light's type 75 float nativeIntensity = serialized.settings.intensity.floatValue; 76 // This is the intensity above converted to the unit (either lux or lumen) that's the basis of the ranges/distribution for this light type 77 float convertedIntensity; 78 bool isSpotReflectorRelevant = (lightType == LightType.Pyramid || lightType == LightType.Spot) && 79 lightUnit == LightUnit.Lumen && 80 serialized.settings.enableSpotReflector.boolValue; 81 82 if (lightType == LightType.Pyramid || lightType == LightType.Spot || lightType == LightType.Box) 83 { 84 // For Box light, we want to use the Lumen style ranges, 85 // but Lumen is not defined for Box lights, 86 // so we just pretend its native type is Candela. 87 float solidAngle = LightUnitUtils.SphereSolidAngle; 88 if (isSpotReflectorRelevant) 89 { 90 // If spot reflector matters for this type of light, 91 // calculate lumen as if spot reflector is on; 92 // This prevents the slider from moving around when solid angle params change. 93 solidAngle = LightUnitUtils.GetSolidAngle(lightType, true, light.spotAngle, light.areaSize.x); 94 } 95 convertedIntensity = LightUnitUtils.CandelaToLumen(nativeIntensity, solidAngle); 96 } 97 else if (nativeUnit == LightUnit.Nits && lightUnit != LightUnit.Lumen) 98 { 99 convertedIntensity = LightUnitUtils.NitsToLumen(nativeIntensity, ConstantNitsToLumenArea); 100 } 101 else 102 { 103 LightUnit toUnit = usesLuxBasedRange ? LightUnit.Lux : LightUnit.Lumen; 104 convertedIntensity = LightUnitUtils.ConvertIntensity(light, nativeIntensity, nativeUnit, toUnit); 105 } 106 107 // Check which preset level we are in. If we're within a preset range, 108 // this index will contain the index of that preset. If we're below all 109 // preset ranges, the value will be -2, and if we're above all preset 110 // ranges, the value will be -1. Also calculate the min and max values 111 // of the slider. 112 int rangeIndex = -3; 113 float minValue = float.MaxValue; 114 float maxValue = float.MinValue; 115 for (int i = 0; i < ranges.Length; i++) 116 { 117 var l = ranges[i]; 118 if (convertedIntensity >= l.value.x && convertedIntensity <= l.value.y) 119 { 120 rangeIndex = i; 121 } 122 123 minValue = Mathf.Min(minValue, l.value.x); 124 maxValue = Mathf.Max(maxValue, l.value.y); 125 } 126 127 if (rangeIndex < 0) 128 { 129 // ^ The current value doesn't lie within a preset range. 130 // If it is less than the minimum preset, it is below the 131 // whole slider's range, otherwise, it is above it. 132 rangeIndex = (convertedIntensity < minValue) ? -2 : -1; 133 } 134 135 // Draw the slider 136 float sliderValue; 137 using (new EditorGUI.IndentLevelScope(-EditorGUI.indentLevel)) 138 { 139 if (rangeIndex == -2) 140 { 141 // ^ The current value is below the slider's range 142 sliderValue = 0f; 143 } 144 else if (rangeIndex == -1) 145 { 146 // ^ The current value is above the slider's range 147 sliderValue = 1f; 148 } 149 else 150 { 151 // Map the intensity value into the [0, 1] range via a non-linear piecewise mapping 152 Vector2 r = ranges[rangeIndex].value; 153 Vector2 d = new Vector2(distribution[rangeIndex], distribution[rangeIndex + 1]); 154 sliderValue = (d.x - d.y) / (r.x - r.y) * (convertedIntensity - r.x) + d.x; 155 } 156 157 EditorGUI.BeginChangeCheck(); 158 float newSliderValue = GUI.HorizontalSlider(sliderRect, sliderValue, 0f, 1f); 159 if (EditorGUI.EndChangeCheck()) 160 { 161 bool newRangeFound = false; 162 for (int i = 0; i < ranges.Length; i++) 163 { 164 if (newSliderValue >= distribution[i] && newSliderValue <= distribution[i + 1]) 165 { 166 rangeIndex = i; 167 newRangeFound = true; 168 break; 169 } 170 } 171 172 Debug.Assert(newRangeFound); 173 // Map the slider value in the [0, 1] range to the intensity value via a non-linear piecewise 174 // mapping 175 Vector2 r = ranges[rangeIndex].value; 176 Vector2 d = new Vector2(distribution[rangeIndex], distribution[rangeIndex + 1]); 177 float newConvertedIntensity = (r.x - r.y) / (d.x - d.y) * (newSliderValue - d.x) + r.x; 178 179 if (lightType == LightType.Pyramid || lightType == LightType.Spot || lightType == LightType.Box) 180 { 181 float solidAngle = LightUnitUtils.SphereSolidAngle; 182 if (isSpotReflectorRelevant) 183 { 184 solidAngle = LightUnitUtils.GetSolidAngle(lightType, true, light.spotAngle, light.areaSize.x); 185 } 186 serialized.settings.intensity.floatValue = LightUnitUtils.LumenToCandela(newConvertedIntensity, solidAngle); 187 } 188 else if (nativeUnit == LightUnit.Nits && lightUnit != LightUnit.Lumen) 189 { 190 serialized.settings.intensity.floatValue = LightUnitUtils.LumenToNits(newConvertedIntensity, ConstantNitsToLumenArea); 191 } 192 else 193 { 194 LightUnit fromUnit = usesLuxBasedRange ? LightUnit.Lux : LightUnit.Lumen; 195 serialized.settings.intensity.floatValue = LightUnitUtils.ConvertIntensity(light, newConvertedIntensity, fromUnit, nativeUnit); 196 } 197 } 198 } 199 200 GUIContent GetTooltip(string rangeName, float intensity) 201 { 202 float uiIntensity; 203 204 if (lightType == LightType.Box) 205 { 206 float candelaIntensity = LightUnitUtils.LumenToCandela(intensity, LightUnitUtils.SphereSolidAngle); 207 uiIntensity = candelaIntensity; 208 } 209 else if (nativeUnit == LightUnit.Nits && lightUnit != LightUnit.Lumen) 210 { 211 uiIntensity = LightUnitUtils.LumenToNits(intensity, ConstantNitsToLumenArea); 212 } 213 else 214 { 215 LightUnit fromUnit = usesLuxBasedRange ? LightUnit.Lux : LightUnit.Lumen; 216 uiIntensity = LightUnitUtils.ConvertIntensity(light, intensity, fromUnit, lightUnit); 217 } 218 219 string formatValue = uiIntensity < 100 ? $"{uiIntensity:n}" : $"{uiIntensity:n0}"; 220 return new GUIContent(string.Empty, $"{rangeName} | {formatValue} {lightUnit.ToString()}"); 221 } 222 223 // Draw the markers on the slider 224 for (int i = 0; i < ranges.Length; i++) 225 { 226 const float kMarkerWidth = 2f; 227 const float kMarkerHeight = 2f; 228 const float kMarkerTooltipSize = 16f; 229 230 var markerRect = new Rect( 231 sliderRect.x + distribution[i + 1] * sliderRect.width - kMarkerWidth * 0.5f, 232 sliderRect.y + (EditorGUIUtility.singleLineHeight / 2f) - 1, 233 kMarkerWidth, 234 kMarkerHeight 235 ); 236 237 // Draw marker by manually drawing the rect, and an empty label with the tooltip. 238 Color kDarkThemeColor = new Color32(153, 153, 153, 255); 239 Color kLiteThemeColor = new Color32(97, 97, 97, 255); 240 EditorGUI.DrawRect(markerRect, EditorGUIUtility.isProSkin ? kDarkThemeColor : kLiteThemeColor); 241 242 // Scale the marker tooltip for easier discovery 243 Rect markerTooltipRect = new( 244 markerRect.x - kMarkerTooltipSize * 0.5f, 245 markerRect.y - kMarkerTooltipSize * 0.5f, 246 kMarkerTooltipSize * (i < ranges.Length - 1 ? 1f : 0.5f), 247 kMarkerTooltipSize 248 ); 249 250 // Temporarily remove indent level, otherwise our custom-positioned tooltip label field will also be 251 // indented 252 int indent = EditorGUI.indentLevel; 253 EditorGUI.indentLevel = 0; 254 EditorGUI.LabelField(markerTooltipRect, GetTooltip(ranges[i].content.tooltip, ranges[i].value.y)); 255 EditorGUI.indentLevel = indent; 256 } 257 258 GUIContent content; 259 Vector2 range; 260 if (rangeIndex < 0) 261 { 262 string tooltip = usesLuxBasedRange ? "Higher than Sunlight" : "Very high intensity light"; 263 content = new GUIContent(EditorGUIUtility.TrIconContent("console.warnicon").image, tooltip); 264 float minOrMaxValue = (convertedIntensity < minValue) ? minValue : maxValue; 265 range = new Vector2(-1, minOrMaxValue); 266 } 267 else 268 { 269 content = ranges[rangeIndex].content; 270 range = ranges[rangeIndex].value; 271 } 272 // Draw the context menu feedback before the icon 273 GUI.Box(iconRect, GUIContent.none, k_IconButton); 274 // Draw the icon 275 { 276 var oldColor = GUI.color; 277 GUI.color = Color.clear; 278 EditorGUI.DrawTextureTransparent(iconRect, content.image); 279 GUI.color = oldColor; 280 } 281 // Draw the thumbnail tooltip and the knob tooltip 282 { 283 // Temporarily remove indent level, otherwise our custom-positioned tooltip label field will also be 284 // indented 285 int indent = EditorGUI.indentLevel; 286 EditorGUI.indentLevel = 0; 287 288 EditorGUI.LabelField(iconRect, GetTooltip(content.tooltip, range.y)); 289 290 const float knobSize = 10f; 291 Rect knobRect = new( 292 sliderRect.x + (sliderRect.width - knobSize) * sliderValue, 293 sliderRect.y + (sliderRect.height - knobSize) * 0.5f, 294 knobSize, 295 knobSize 296 ); 297 EditorGUI.LabelField(knobRect, GetTooltip(content.tooltip, convertedIntensity)); 298 299 EditorGUI.indentLevel = indent; 300 } 301 // Handle events for context menu 302 var e = Event.current; 303 if (e.type == EventType.MouseDown && e.button == 0) 304 { 305 if (iconRect.Contains(e.mousePosition)) 306 { 307 var menuPosition = iconRect.position + iconRect.size; 308 var menu = new GenericMenu(); 309 310 for (int i = ranges.Length - 1; i >= 0; --i) 311 { 312 // Indicate a checkmark if the value is within this preset range. 313 LightUnitSliderUIRange preset = ranges[i]; 314 float nativePresetValue; 315 if (lightType == LightType.Pyramid || lightType == LightType.Spot || lightType == LightType.Box) 316 { 317 float solidAngle = LightUnitUtils.SphereSolidAngle; 318 if (isSpotReflectorRelevant) 319 { 320 solidAngle = LightUnitUtils.GetSolidAngle(lightType, true, light.spotAngle, light.areaSize.x); 321 } 322 nativePresetValue = LightUnitUtils.LumenToCandela(preset.presetValue, solidAngle); 323 } 324 else if (nativeUnit == LightUnit.Nits && lightUnit != LightUnit.Lumen) 325 { 326 nativePresetValue = LightUnitUtils.LumenToNits(preset.presetValue, ConstantNitsToLumenArea); 327 } 328 else 329 { 330 LightUnit fromUnit = usesLuxBasedRange ? LightUnit.Lux : LightUnit.Lumen; 331 nativePresetValue = LightUnitUtils.ConvertIntensity(light, preset.presetValue, fromUnit, nativeUnit); 332 } 333 334 menu.AddItem( 335 EditorGUIUtility.TrTextContent(preset.content.tooltip), 336 rangeIndex == i, 337 () => SetIntensityValue(serialized, nativePresetValue) 338 ); 339 } 340 341 menu.DropDown(new Rect(menuPosition, Vector2.zero)); 342 e.Use(); 343 } 344 } 345 } 346 347 static void SetIntensityValue(ISerializedLight serialized, float intensity) 348 { 349 serialized.Update(); 350 serialized.settings.intensity.floatValue = intensity; 351 serialized.Apply(); 352 } 353 } 354}