testss
This commit is contained in:
@@ -0,0 +1,679 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,448 @@
|
||||
using System.IO;
|
||||
using Unity.Collections;
|
||||
using UnityEditor;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.AssetImporters;
|
||||
#else
|
||||
using UnityEditor.Experimental.AssetImporters;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Rendering
|
||||
{
|
||||
// Photometric type coordinate system references:
|
||||
// https://www.ies.org/product/approved-method-guide-to-goniometer-measurements-and-types-and-photometric-coordinate-systems/
|
||||
// https://support.agi32.com/support/solutions/articles/22000209748-type-a-type-b-and-type-c-photometry
|
||||
/// <summary>
|
||||
/// IES class which is common for the Importers
|
||||
/// </summary>
|
||||
|
||||
[System.Serializable]
|
||||
public class IESEngine
|
||||
{
|
||||
const float k_HalfPi = 0.5f * Mathf.PI;
|
||||
const float k_TwoPi = 2.0f * Mathf.PI;
|
||||
|
||||
internal IESReader m_iesReader = new IESReader();
|
||||
|
||||
internal string FileFormatVersion { get => m_iesReader.FileFormatVersion; }
|
||||
|
||||
internal TextureImporterType m_TextureGenerationType = TextureImporterType.Cookie;
|
||||
|
||||
/// <summary>
|
||||
/// setter for the Texture generation Type
|
||||
/// </summary>
|
||||
public TextureImporterType TextureGenerationType
|
||||
{
|
||||
set { m_TextureGenerationType = value; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Method to read the IES File
|
||||
/// </summary>
|
||||
/// <param name="iesFilePath">Path to the IES file in the Disk.</param>
|
||||
/// <returns>An error message or warning otherwise null if no error</returns>
|
||||
public string ReadFile(string iesFilePath)
|
||||
{
|
||||
if (!File.Exists(iesFilePath))
|
||||
{
|
||||
return "IES file does not exist.";
|
||||
}
|
||||
|
||||
string errorMessage;
|
||||
|
||||
try
|
||||
{
|
||||
errorMessage = m_iesReader.ReadFile(iesFilePath);
|
||||
}
|
||||
catch (IOException ioEx)
|
||||
{
|
||||
return ioEx.Message;
|
||||
}
|
||||
|
||||
return errorMessage;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Check a keyword
|
||||
/// </summary>
|
||||
/// <param name="keyword">A keyword to check if exist.</param>
|
||||
/// <returns>A Keyword if exist inside the internal Dictionary</returns>
|
||||
public string GetKeywordValue(string keyword)
|
||||
{
|
||||
return m_iesReader.GetKeywordValue(keyword);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Getter (as a string) for the Photometric Type
|
||||
/// </summary>
|
||||
/// <returns>The current Photometric Type</returns>
|
||||
public string GetPhotometricType()
|
||||
{
|
||||
switch (m_iesReader.PhotometricType)
|
||||
{
|
||||
case 3: // type A
|
||||
return "Type A";
|
||||
case 2: // type B
|
||||
return "Type B";
|
||||
default: // type C
|
||||
return "Type C";
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Get the CUrrent Max intensity
|
||||
/// </summary>
|
||||
/// <returns>A pair of the intensity follow by the used unit (candelas or lumens)</returns>
|
||||
public (float, string) GetMaximumIntensity()
|
||||
{
|
||||
if (m_iesReader.TotalLumens == -1f) // absolute photometry
|
||||
{
|
||||
return (m_iesReader.MaxCandelas, "Candelas");
|
||||
}
|
||||
else
|
||||
{
|
||||
return (m_iesReader.TotalLumens, "Lumens");
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Generated a Cube texture based on the internal PhotometricType
|
||||
/// </summary>
|
||||
/// <param name="compression">Compression parameter requestted.</param>
|
||||
/// <param name="textureSize">The resquested size.</param>
|
||||
/// <returns>A Cubemap representing this IES</returns>
|
||||
public (string, Texture) GenerateCubeCookie(TextureImporterCompression compression, int textureSize)
|
||||
{
|
||||
int width = 2 * textureSize;
|
||||
int height = 2 * textureSize;
|
||||
|
||||
NativeArray<Color32> colorBuffer;
|
||||
|
||||
switch (m_iesReader.PhotometricType)
|
||||
{
|
||||
case 3: // type A
|
||||
colorBuffer = BuildTypeACylindricalTexture(width, height);
|
||||
break;
|
||||
case 2: // type B
|
||||
colorBuffer = BuildTypeBCylindricalTexture(width, height);
|
||||
break;
|
||||
default: // type C
|
||||
colorBuffer = BuildTypeCCylindricalTexture(width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
return GenerateTexture(m_TextureGenerationType, TextureImporterShape.TextureCube, compression, width, height, colorBuffer);
|
||||
}
|
||||
|
||||
// Gnomonic projection reference:
|
||||
// http://speleotrove.com/pangazer/gnomonic_projection.html
|
||||
/// <summary>
|
||||
/// Generating a 2D Texture of this cookie, using a Gnomonic projection of the bottom of the IES
|
||||
/// </summary>
|
||||
/// <param name="compression">Compression parameter requestted.</param>
|
||||
/// <param name="coneAngle">Cone angle used to performe the Gnomonic projection.</param>
|
||||
/// <param name="textureSize">The resquested size.</param>
|
||||
/// <param name="applyLightAttenuation">Bool to enable or not the Light Attenuation based on the squared distance.</param>
|
||||
/// <returns>A Generated 2D texture doing the projection of the IES using the Gnomonic projection of the bottom half hemisphere with the given 'cone angle'</returns>
|
||||
public (string, Texture) Generate2DCookie(TextureImporterCompression compression, float coneAngle, int textureSize, bool applyLightAttenuation)
|
||||
{
|
||||
NativeArray<Color32> colorBuffer;
|
||||
|
||||
switch (m_iesReader.PhotometricType)
|
||||
{
|
||||
case 3: // type A
|
||||
colorBuffer = BuildTypeAGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
|
||||
break;
|
||||
case 2: // type B
|
||||
colorBuffer = BuildTypeBGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
|
||||
break;
|
||||
default: // type C
|
||||
colorBuffer = BuildTypeCGnomonicTexture(coneAngle, textureSize, applyLightAttenuation);
|
||||
break;
|
||||
}
|
||||
|
||||
return GenerateTexture(m_TextureGenerationType, TextureImporterShape.Texture2D, compression, textureSize, textureSize, colorBuffer);
|
||||
}
|
||||
|
||||
private (string, Texture) GenerateCylindricalTexture(TextureImporterCompression compression, int textureSize)
|
||||
{
|
||||
int width = 2 * textureSize;
|
||||
int height = textureSize;
|
||||
|
||||
NativeArray<Color32> colorBuffer;
|
||||
|
||||
switch (m_iesReader.PhotometricType)
|
||||
{
|
||||
case 3: // type A
|
||||
colorBuffer = BuildTypeACylindricalTexture(width, height);
|
||||
break;
|
||||
case 2: // type B
|
||||
colorBuffer = BuildTypeBCylindricalTexture(width, height);
|
||||
break;
|
||||
default: // type C
|
||||
colorBuffer = BuildTypeCCylindricalTexture(width, height);
|
||||
break;
|
||||
}
|
||||
|
||||
return GenerateTexture(TextureImporterType.Default, TextureImporterShape.Texture2D, compression, width, height, colorBuffer);
|
||||
}
|
||||
|
||||
(string, Texture) GenerateTexture(TextureImporterType type, TextureImporterShape shape, TextureImporterCompression compression, int width, int height, NativeArray<Color32> colorBuffer)
|
||||
{
|
||||
// Default values set by the TextureGenerationSettings constructor can be found in this file on GitHub:
|
||||
// https://github.com/Unity-Technologies/UnityCsReference/blob/master/Editor/Mono/AssetPipeline/TextureGenerator.bindings.cs
|
||||
|
||||
var settings = new TextureGenerationSettings(type);
|
||||
|
||||
SourceTextureInformation textureInfo = settings.sourceTextureInformation;
|
||||
textureInfo.containsAlpha = true;
|
||||
textureInfo.height = height;
|
||||
textureInfo.width = width;
|
||||
|
||||
TextureImporterSettings textureImporterSettings = settings.textureImporterSettings;
|
||||
textureImporterSettings.alphaSource = TextureImporterAlphaSource.FromInput;
|
||||
textureImporterSettings.aniso = 0;
|
||||
textureImporterSettings.borderMipmap = (textureImporterSettings.textureType == TextureImporterType.Cookie);
|
||||
textureImporterSettings.filterMode = FilterMode.Bilinear;
|
||||
textureImporterSettings.generateCubemap = TextureImporterGenerateCubemap.Cylindrical;
|
||||
textureImporterSettings.mipmapEnabled = false;
|
||||
textureImporterSettings.npotScale = TextureImporterNPOTScale.None;
|
||||
textureImporterSettings.readable = true;
|
||||
textureImporterSettings.sRGBTexture = false;
|
||||
textureImporterSettings.textureShape = shape;
|
||||
textureImporterSettings.wrapMode = textureImporterSettings.wrapModeU = textureImporterSettings.wrapModeV = textureImporterSettings.wrapModeW = TextureWrapMode.Clamp;
|
||||
|
||||
TextureImporterPlatformSettings platformSettings = settings.platformSettings;
|
||||
platformSettings.maxTextureSize = 2048;
|
||||
platformSettings.resizeAlgorithm = TextureResizeAlgorithm.Bilinear;
|
||||
platformSettings.textureCompression = compression;
|
||||
|
||||
TextureGenerationOutput output = TextureGenerator.GenerateTexture(settings, colorBuffer);
|
||||
|
||||
if (output.importWarnings.Length > 0)
|
||||
{
|
||||
Debug.LogWarning("Cannot properly generate IES texture:\n" + string.Join("\n", output.importWarnings));
|
||||
}
|
||||
|
||||
return (output.importInspectorWarnings, output.texture);
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeACylindricalTexture(int width, int height)
|
||||
{
|
||||
float stepU = 360f / (width - 1);
|
||||
float stepV = 180f / (height - 1);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
|
||||
|
||||
float latitude = y * stepV - 90f; // in range [-90..+90] degrees
|
||||
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
float longitude = x * stepU - 180f; // in range [-180..+180] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude);
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeBCylindricalTexture(int width, int height)
|
||||
{
|
||||
float stepU = k_TwoPi / (width - 1);
|
||||
float stepV = Mathf.PI / (height - 1);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
|
||||
|
||||
float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees
|
||||
|
||||
float sinV = Mathf.Sin(v);
|
||||
float cosV = Mathf.Cos(v);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees
|
||||
|
||||
float sinU = Mathf.Sin(u);
|
||||
float cosU = Mathf.Cos(u);
|
||||
|
||||
// Since a type B luminaire is turned on its side, rotate it to make its polar axis horizontal.
|
||||
float longitude = Mathf.Atan2(sinV, cosU * cosV) * Mathf.Rad2Deg; // in range [-180..+180] degrees
|
||||
float latitude = Mathf.Asin(-sinU * cosV) * Mathf.Rad2Deg; // in range [-90..+90] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeAorBHorizontalAnglePosition(longitude);
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeCCylindricalTexture(int width, int height)
|
||||
{
|
||||
float stepU = k_TwoPi / (width - 1);
|
||||
float stepV = Mathf.PI / (height - 1);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(width * height, Allocator.Temp, NativeArrayOptions.UninitializedMemory);
|
||||
|
||||
for (int y = 0; y < height; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * width, width);
|
||||
|
||||
float v = y * stepV - k_HalfPi; // in range [-90..+90] degrees
|
||||
|
||||
float sinV = Mathf.Sin(v);
|
||||
float cosV = Mathf.Cos(v);
|
||||
|
||||
for (int x = 0; x < width; x++)
|
||||
{
|
||||
float u = Mathf.PI - x * stepU; // in range [+180..-180] degrees
|
||||
|
||||
float sinU = Mathf.Sin(u);
|
||||
float cosU = Mathf.Cos(u);
|
||||
|
||||
// Since a type C luminaire is generally aimed at nadir, orient it toward +Z at the center of the cylindrical texture.
|
||||
float longitude = ((Mathf.Atan2(sinU * cosV, sinV) + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees
|
||||
float latitude = (Mathf.Asin(-cosU * cosV) + k_HalfPi) * Mathf.Rad2Deg; // in range [0..180] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / m_iesReader.MaxCandelas) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeAGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
|
||||
{
|
||||
float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
|
||||
float stepUV = (2 * limitUV) / (size - 3);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
|
||||
|
||||
// Leave a one-pixel black border around the texture to avoid cookie spilling.
|
||||
for (int y = 1; y < size - 1; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
|
||||
|
||||
float v = (y - 1) * stepUV - limitUV;
|
||||
|
||||
for (int x = 1; x < size - 1; x++)
|
||||
{
|
||||
float u = (x - 1) * stepUV - limitUV;
|
||||
|
||||
float rayLengthSquared = u * u + v * v + 1;
|
||||
|
||||
float longitude = Mathf.Atan(u) * Mathf.Rad2Deg; // in range [-90..+90] degrees
|
||||
float latitude = Mathf.Asin(v / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
// Factor in the light attenuation further from the texture center.
|
||||
float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f;
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeBGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
|
||||
{
|
||||
float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
|
||||
float stepUV = (2 * limitUV) / (size - 3);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
|
||||
|
||||
// Leave a one-pixel black border around the texture to avoid cookie spilling.
|
||||
for (int y = 1; y < size - 1; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
|
||||
|
||||
float v = (y - 1) * stepUV - limitUV;
|
||||
|
||||
for (int x = 1; x < size - 1; x++)
|
||||
{
|
||||
float u = (x - 1) * stepUV - limitUV;
|
||||
|
||||
float rayLengthSquared = u * u + v * v + 1;
|
||||
|
||||
// Since a type B luminaire is turned on its side, U and V are flipped.
|
||||
float longitude = Mathf.Atan(v) * Mathf.Rad2Deg; // in range [-90..+90] degrees
|
||||
float latitude = Mathf.Asin(u / Mathf.Sqrt(rayLengthSquared)) * Mathf.Rad2Deg; // in range [-90..+90] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
// Factor in the light attenuation further from the texture center.
|
||||
float lightAttenuation = applyLightAttenuation ? rayLengthSquared : 1f;
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
|
||||
NativeArray<Color32> BuildTypeCGnomonicTexture(float coneAngle, int size, bool applyLightAttenuation)
|
||||
{
|
||||
float limitUV = Mathf.Tan(0.5f * coneAngle * Mathf.Deg2Rad);
|
||||
float stepUV = (2 * limitUV) / (size - 3);
|
||||
|
||||
var textureBuffer = new NativeArray<Color32>(size * size, Allocator.Temp, NativeArrayOptions.ClearMemory);
|
||||
|
||||
// Leave a one-pixel black border around the texture to avoid cookie spilling.
|
||||
for (int y = 1; y < size - 1; y++)
|
||||
{
|
||||
var slice = new NativeSlice<Color32>(textureBuffer, y * size, size);
|
||||
|
||||
float v = (y - 1) * stepUV - limitUV;
|
||||
|
||||
for (int x = 1; x < size - 1; x++)
|
||||
{
|
||||
float u = (x - 1) * stepUV - limitUV;
|
||||
|
||||
float uvLength = Mathf.Sqrt(u * u + v * v);
|
||||
|
||||
float longitude = ((Mathf.Atan2(v, u) - k_HalfPi + k_TwoPi) % k_TwoPi) * Mathf.Rad2Deg; // in range [0..360] degrees
|
||||
float latitude = Mathf.Atan(uvLength) * Mathf.Rad2Deg; // in range [0..90] degrees
|
||||
|
||||
float horizontalAnglePosition = m_iesReader.ComputeTypeCHorizontalAnglePosition(longitude);
|
||||
float verticalAnglePosition = m_iesReader.ComputeVerticalAnglePosition(latitude);
|
||||
|
||||
// Factor in the light attenuation further from the texture center.
|
||||
float lightAttenuation = applyLightAttenuation ? (uvLength * uvLength + 1) : 1f;
|
||||
|
||||
byte value = (byte)((m_iesReader.InterpolateBilinear(horizontalAnglePosition, verticalAnglePosition) / (m_iesReader.MaxCandelas * lightAttenuation)) * 255);
|
||||
slice[x] = new Color32(value, value, value, value);
|
||||
}
|
||||
}
|
||||
|
||||
return textureBuffer;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.AssetImporters;
|
||||
#else
|
||||
using UnityEditor.Experimental.AssetImporters;
|
||||
#endif
|
||||
|
||||
namespace UnityEditor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Common class use to share code between implementation of IES Importeres
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
[ScriptedImporter(1, "ies")]
|
||||
public partial class IESImporter : ScriptedImporter
|
||||
{
|
||||
/// <summary>
|
||||
/// IES Engine
|
||||
/// </summary>
|
||||
public IESEngine engine = new IESEngine();
|
||||
|
||||
/// <summary>
|
||||
/// IES Meta data stored in the ies file
|
||||
/// </summary>
|
||||
public IESMetaData iesMetaData = new IESMetaData();
|
||||
|
||||
/// <summary>
|
||||
/// Delegate prototype which will be sent by the pipeline implementation of the IES Importer
|
||||
/// Must be initialized during the creation of the SRP
|
||||
/// </summary>
|
||||
public static event System.Action<AssetImportContext, string, bool, string, float, Light, Texture> createRenderPipelinePrefabLight;
|
||||
|
||||
/// <summary>
|
||||
/// Common method performing the import of the asset
|
||||
/// </summary>
|
||||
/// <param name="ctx">Asset importer context.</param>
|
||||
public override void OnImportAsset(AssetImportContext ctx)
|
||||
{
|
||||
engine.TextureGenerationType = TextureImporterType.Default;
|
||||
|
||||
Texture cookieTextureCube = null;
|
||||
Texture cookieTexture2D = null;
|
||||
|
||||
string iesFilePath = Path.Combine(Path.GetDirectoryName(Application.dataPath), ctx.assetPath);
|
||||
string errorMessage = engine.ReadFile(iesFilePath);
|
||||
|
||||
if (string.IsNullOrEmpty(errorMessage))
|
||||
{
|
||||
iesMetaData.FileFormatVersion = engine.FileFormatVersion;
|
||||
iesMetaData.IESPhotometricType = engine.GetPhotometricType();
|
||||
iesMetaData.Manufacturer = engine.GetKeywordValue("MANUFAC");
|
||||
iesMetaData.LuminaireCatalogNumber = engine.GetKeywordValue("LUMCAT");
|
||||
iesMetaData.LuminaireDescription = engine.GetKeywordValue("LUMINAIRE");
|
||||
iesMetaData.LampCatalogNumber = engine.GetKeywordValue("LAMPCAT");
|
||||
iesMetaData.LampDescription = engine.GetKeywordValue("LAMP");
|
||||
|
||||
(iesMetaData.IESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit) = engine.GetMaximumIntensity();
|
||||
|
||||
string warningMessage;
|
||||
|
||||
(warningMessage, cookieTextureCube) = engine.GenerateCubeCookie(iesMetaData.CookieCompression, (int)iesMetaData.iesSize);
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
ctx.LogImportWarning($"Cannot properly generate IES Cube texture: {warningMessage}");
|
||||
}
|
||||
cookieTextureCube.IncrementUpdateCount();
|
||||
|
||||
(warningMessage, cookieTexture2D) = engine.Generate2DCookie(iesMetaData.CookieCompression, iesMetaData.SpotAngle, (int)iesMetaData.iesSize, iesMetaData.ApplyLightAttenuation);
|
||||
if (!string.IsNullOrEmpty(warningMessage))
|
||||
{
|
||||
ctx.LogImportWarning($"Cannot properly generate IES 2D texture: {warningMessage}");
|
||||
}
|
||||
cookieTexture2D.IncrementUpdateCount();
|
||||
}
|
||||
else
|
||||
{
|
||||
ctx.LogImportError($"Cannot read IES file '{iesFilePath}': {errorMessage}");
|
||||
}
|
||||
|
||||
string iesFileName = Path.GetFileNameWithoutExtension(ctx.assetPath);
|
||||
|
||||
var iesObject = ScriptableObject.CreateInstance<IESObject>();
|
||||
iesObject.iesMetaData = iesMetaData;
|
||||
GameObject lightObject = new GameObject(iesFileName);
|
||||
|
||||
lightObject.transform.localEulerAngles = new Vector3(90f, 0f, iesMetaData.LightAimAxisRotation);
|
||||
|
||||
Light light = lightObject.AddComponent<Light>();
|
||||
light.type = (iesMetaData.PrefabLightType == IESLightType.Point) ? LightType.Point : LightType.Spot;
|
||||
light.intensity = 1f; // would need a better intensity value formula
|
||||
light.range = 10f; // would need a better range value formula
|
||||
light.spotAngle = iesMetaData.SpotAngle;
|
||||
|
||||
ctx.AddObjectToAsset("IES", iesObject);
|
||||
ctx.SetMainObject(iesObject);
|
||||
|
||||
IESImporter.createRenderPipelinePrefabLight?.Invoke(ctx, iesFileName, iesMetaData.UseIESMaximumIntensity, iesMetaData.IESMaximumIntensityUnit, iesMetaData.IESMaximumIntensity, light, (iesMetaData.PrefabLightType == IESLightType.Point) ? cookieTextureCube : cookieTexture2D);
|
||||
|
||||
if (cookieTextureCube != null)
|
||||
{
|
||||
cookieTextureCube.name = iesFileName + "-Cube-IES";
|
||||
ctx.AddObjectToAsset(cookieTextureCube.name, cookieTextureCube);
|
||||
}
|
||||
if (cookieTexture2D != null)
|
||||
{
|
||||
cookieTexture2D.name = iesFileName + "-2D-IES";
|
||||
ctx.AddObjectToAsset(cookieTexture2D.name, cookieTexture2D);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,332 @@
|
||||
using System.Reflection;
|
||||
using UnityEditor;
|
||||
#if UNITY_2020_2_OR_NEWER
|
||||
using UnityEditor.AssetImporters;
|
||||
#else
|
||||
using UnityEditor.Experimental.AssetImporters;
|
||||
#endif
|
||||
using UnityEngine;
|
||||
using UnityEngine.Rendering;
|
||||
|
||||
namespace UnityEditor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Common class for IES Importer Editor (currently implemented only on HDRP)
|
||||
/// </summary>
|
||||
public class IESImporterEditor
|
||||
{
|
||||
GUIStyle m_WordWrapStyle = new GUIStyle();
|
||||
|
||||
SerializedProperty m_FileFormatVersionProp;
|
||||
SerializedProperty m_IESPhotometricTypeProp;
|
||||
SerializedProperty m_IESMaximumIntensityProp;
|
||||
SerializedProperty m_IESMaximumIntensityUnitProp;
|
||||
|
||||
SerializedProperty m_ManufacturerProp;
|
||||
SerializedProperty m_LuminaireCatalogNumberProp;
|
||||
SerializedProperty m_LuminaireDescriptionProp;
|
||||
SerializedProperty m_LampCatalogNumberProp;
|
||||
SerializedProperty m_LampDescriptionProp;
|
||||
|
||||
SerializedProperty m_PrefabLightTypeProp;
|
||||
SerializedProperty m_SpotAngleProp;
|
||||
SerializedProperty m_IESSizeProp;
|
||||
SerializedProperty m_ApplyLightAttenuationProp;
|
||||
SerializedProperty m_UseIESMaximumIntensityProp;
|
||||
SerializedProperty m_CookieCompressionProp;
|
||||
|
||||
/// <summary>
|
||||
/// Property to the aim axis rotation for projection
|
||||
/// </summary>
|
||||
protected SerializedProperty m_LightAimAxisRotationProp;
|
||||
|
||||
bool m_ShowLuminaireProductInformation = true;
|
||||
bool m_ShowLightProperties = true;
|
||||
|
||||
/// <summary>
|
||||
/// Object used to setup Preview renderer
|
||||
/// </summary>
|
||||
protected PreviewRenderUtility m_PreviewRenderUtility = null;
|
||||
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
public delegate void LayoutRenderPipelineUseIesMaximumIntensity();
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
/// <param name="camera">Current camera</param>
|
||||
public delegate void SetupRenderPipelinePreviewCamera(Camera camera);
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
/// <param name="light">Light will be setuped, specialization for a given SRP</param>
|
||||
public delegate void SetupRenderPipelinePreviewLight(Light light);
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
/// <param name="wallRenderer">Setup a wall for the preview</param>
|
||||
public delegate void SetupRenderPipelinePreviewWallRenderer(MeshRenderer wallRenderer);
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
/// <param name="floorRenderer">Setup a floor for the preview</param>
|
||||
public delegate void SetupRenderPipelinePreviewFloorRenderer(MeshRenderer floorRenderer);
|
||||
/// <summary>
|
||||
/// Delegate prototype sent by the specialization of the IESImporterEditor per Render Pipeline
|
||||
/// </summary>
|
||||
/// <param name="light">Light used to setup the IES</param>
|
||||
/// <param name="useIESMaximumIntensityProp">Serialized property to the "useIESMaximumIntensity" property</param>
|
||||
/// <param name="iesMaximumIntensityUnitProp">Serialized property to the "iesMaximumIntensityUnit" property</param>
|
||||
/// <param name="iesMaximumIntensityProp">Serialized property to the "iesMaximumIntensity" property</param>
|
||||
public delegate void SetupRenderPipelinePreviewLightIntensity(Light light, SerializedProperty useIESMaximumIntensityProp, SerializedProperty iesMaximumIntensityUnitProp, SerializedProperty iesMaximumIntensityProp);
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
/// <param name="serializedObject">Serialized object which can be linked to IESMetadata</param>
|
||||
public void CommonOnEnable(SerializedProperty serializedObject)
|
||||
{
|
||||
m_WordWrapStyle.wordWrap = true;
|
||||
|
||||
m_FileFormatVersionProp = serializedObject.FindPropertyRelative("FileFormatVersion");
|
||||
m_IESPhotometricTypeProp = serializedObject.FindPropertyRelative("IESPhotometricType");
|
||||
m_IESMaximumIntensityProp = serializedObject.FindPropertyRelative("IESMaximumIntensity");
|
||||
m_IESMaximumIntensityUnitProp = serializedObject.FindPropertyRelative("IESMaximumIntensityUnit");
|
||||
|
||||
m_ManufacturerProp = serializedObject.FindPropertyRelative("Manufacturer");
|
||||
m_LuminaireCatalogNumberProp = serializedObject.FindPropertyRelative("LuminaireCatalogNumber");
|
||||
m_LuminaireDescriptionProp = serializedObject.FindPropertyRelative("LuminaireDescription");
|
||||
m_LampCatalogNumberProp = serializedObject.FindPropertyRelative("LampCatalogNumber");
|
||||
m_LampDescriptionProp = serializedObject.FindPropertyRelative("LampDescription");
|
||||
|
||||
m_PrefabLightTypeProp = serializedObject.FindPropertyRelative("PrefabLightType");
|
||||
m_SpotAngleProp = serializedObject.FindPropertyRelative("SpotAngle");
|
||||
m_IESSizeProp = serializedObject.FindPropertyRelative("iesSize");
|
||||
m_ApplyLightAttenuationProp = serializedObject.FindPropertyRelative("ApplyLightAttenuation");
|
||||
m_UseIESMaximumIntensityProp = serializedObject.FindPropertyRelative("UseIESMaximumIntensity");
|
||||
m_CookieCompressionProp = serializedObject.FindPropertyRelative("CookieCompression");
|
||||
m_LightAimAxisRotationProp = serializedObject.FindPropertyRelative("LightAimAxisRotation");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
/// <param name="scriptedImporter">The current specialized scripted importer using the common code</param>
|
||||
public void CommonOnInspectorGUI(ScriptedImporterEditor scriptedImporter)
|
||||
{
|
||||
scriptedImporter.serializedObject.Update();
|
||||
|
||||
EditorGUILayout.LabelField("File Format Version", m_FileFormatVersionProp.stringValue);
|
||||
EditorGUILayout.LabelField("Photometric Type", m_IESPhotometricTypeProp.stringValue);
|
||||
EditorGUILayout.LabelField("Maximum Intensity", $"{m_IESMaximumIntensityProp.floatValue} {m_IESMaximumIntensityUnitProp.stringValue}");
|
||||
|
||||
if (m_ShowLuminaireProductInformation = EditorGUILayout.Foldout(m_ShowLuminaireProductInformation, "Luminaire Product Information"))
|
||||
{
|
||||
EditorGUILayout.LabelField(m_ManufacturerProp.displayName, m_ManufacturerProp.stringValue, m_WordWrapStyle);
|
||||
EditorGUILayout.LabelField(m_LuminaireCatalogNumberProp.displayName, m_LuminaireCatalogNumberProp.stringValue, m_WordWrapStyle);
|
||||
EditorGUILayout.LabelField(m_LuminaireDescriptionProp.displayName, m_LuminaireDescriptionProp.stringValue, m_WordWrapStyle);
|
||||
EditorGUILayout.LabelField(m_LampCatalogNumberProp.displayName, m_LampCatalogNumberProp.stringValue, m_WordWrapStyle);
|
||||
EditorGUILayout.LabelField(m_LampDescriptionProp.displayName, m_LampDescriptionProp.stringValue, m_WordWrapStyle);
|
||||
}
|
||||
|
||||
if (m_ShowLightProperties = EditorGUILayout.Foldout(m_ShowLightProperties, "Light and Cookie Properties"))
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_PrefabLightTypeProp, new GUIContent("Light Type"));
|
||||
EditorGUILayout.PropertyField(m_SpotAngleProp);
|
||||
EditorGUILayout.PropertyField(m_IESSizeProp, new GUIContent("IES Size"));
|
||||
EditorGUILayout.PropertyField(m_ApplyLightAttenuationProp);
|
||||
EditorGUILayout.PropertyField(m_CookieCompressionProp, new GUIContent("IES Compression"));
|
||||
|
||||
// Before enabling this feature, more experimentation is needed with the addition of a Volume in the PreviewRenderUtility scene.
|
||||
EditorGUILayout.PropertyField(m_UseIESMaximumIntensityProp, new GUIContent("Use IES Maximum Intensity"));
|
||||
|
||||
using (new EditorGUILayout.HorizontalScope())
|
||||
{
|
||||
EditorGUILayout.PropertyField(m_LightAimAxisRotationProp, new GUIContent("Aim Axis Rotation"));
|
||||
|
||||
if (GUILayout.Button("Reset", GUILayout.Width(44)))
|
||||
{
|
||||
m_LightAimAxisRotationProp.floatValue = -90f;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
scriptedImporter.serializedObject.ApplyModifiedProperties();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
public void CommonApply()
|
||||
{
|
||||
if (m_PreviewRenderUtility != null)
|
||||
{
|
||||
m_PreviewRenderUtility.Cleanup();
|
||||
m_PreviewRenderUtility = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
/// <param name="setupRenderPipelinePreviewCamera">Delegate provided by the Render pipeline to setup the Preview Camera</param>
|
||||
/// <param name="setupRenderPipelinePreviewFloorRenderer">Delegate provided by the Render pipeline to setup the Preview Light</param>
|
||||
/// <param name="setupRenderPipelinePreviewLight">Delegate provided by the Render pipeline to setup the Preview Wall</param>
|
||||
/// <param name="setupRenderPipelinePreviewWallRenderer">Delegate provided by the Render pipeline to setup the Preview Floor</param>
|
||||
/// <returns>true to specified IES has a Preview</returns>
|
||||
public bool CommonHasPreviewGUI(SetupRenderPipelinePreviewCamera setupRenderPipelinePreviewCamera,
|
||||
SetupRenderPipelinePreviewLight setupRenderPipelinePreviewLight,
|
||||
SetupRenderPipelinePreviewWallRenderer setupRenderPipelinePreviewWallRenderer,
|
||||
SetupRenderPipelinePreviewFloorRenderer setupRenderPipelinePreviewFloorRenderer)
|
||||
{
|
||||
if (m_PreviewRenderUtility == null)
|
||||
{
|
||||
m_PreviewRenderUtility = new PreviewRenderUtility();
|
||||
|
||||
m_PreviewRenderUtility.ambientColor = Color.black;
|
||||
|
||||
m_PreviewRenderUtility.camera.fieldOfView = 60f;
|
||||
m_PreviewRenderUtility.camera.nearClipPlane = 0.1f;
|
||||
m_PreviewRenderUtility.camera.farClipPlane = 10f;
|
||||
m_PreviewRenderUtility.camera.transform.localPosition = new Vector3(1.85f, 0.71f, 0f);
|
||||
m_PreviewRenderUtility.camera.transform.localEulerAngles = new Vector3(15f, -90f, 0f);
|
||||
|
||||
setupRenderPipelinePreviewCamera(m_PreviewRenderUtility.camera);
|
||||
|
||||
m_PreviewRenderUtility.lights[0].type = (m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point) ? LightType.Point : LightType.Spot;
|
||||
m_PreviewRenderUtility.lights[0].color = Color.white;
|
||||
m_PreviewRenderUtility.lights[0].intensity = 1f;
|
||||
m_PreviewRenderUtility.lights[0].range = 10f;
|
||||
m_PreviewRenderUtility.lights[0].spotAngle = m_SpotAngleProp.floatValue;
|
||||
m_PreviewRenderUtility.lights[0].transform.localPosition = new Vector3(0.14f, 1f, 0f);
|
||||
m_PreviewRenderUtility.lights[0].transform.localEulerAngles = new Vector3(90f, 0f, -90f);
|
||||
|
||||
setupRenderPipelinePreviewLight(m_PreviewRenderUtility.lights[0]);
|
||||
|
||||
m_PreviewRenderUtility.lights[1].intensity = 0f;
|
||||
|
||||
GameObject previewWall = GameObject.CreatePrimitive(PrimitiveType.Plane);
|
||||
previewWall.name = "IESPreviewWall";
|
||||
previewWall.hideFlags = HideFlags.HideAndDontSave;
|
||||
previewWall.transform.localPosition = new Vector3(0f, 4f, 0f);
|
||||
previewWall.transform.localEulerAngles = new Vector3(0f, 0f, -90f);
|
||||
previewWall.transform.localScale = new Vector3(1f, 1f, 10f);
|
||||
MeshRenderer previewWallRenderer = previewWall.GetComponent<MeshRenderer>();
|
||||
previewWallRenderer.lightProbeUsage = LightProbeUsage.Off;
|
||||
previewWallRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
||||
previewWallRenderer.material = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Material.mat");
|
||||
|
||||
setupRenderPipelinePreviewWallRenderer(previewWallRenderer);
|
||||
|
||||
m_PreviewRenderUtility.AddSingleGO(previewWall);
|
||||
|
||||
GameObject previewFloor = GameObject.CreatePrimitive(PrimitiveType.Plane);
|
||||
previewFloor.name = "IESPreviewFloor";
|
||||
previewFloor.hideFlags = HideFlags.HideAndDontSave;
|
||||
previewFloor.transform.localPosition = new Vector3(4f, 0f, 0f);
|
||||
previewFloor.transform.localEulerAngles = new Vector3(0f, 0f, 0f);
|
||||
previewFloor.transform.localScale = new Vector3(1f, 1f, 10f);
|
||||
MeshRenderer previewFloorRenderer = previewFloor.GetComponent<MeshRenderer>();
|
||||
previewFloorRenderer.lightProbeUsage = LightProbeUsage.Off;
|
||||
previewFloorRenderer.reflectionProbeUsage = ReflectionProbeUsage.Off;
|
||||
previewFloorRenderer.material = AssetDatabase.GetBuiltinExtraResource<Material>("Default-Diffuse.mat");
|
||||
|
||||
setupRenderPipelinePreviewFloorRenderer(previewFloorRenderer);
|
||||
|
||||
m_PreviewRenderUtility.AddSingleGO(previewFloor);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
/// <returns>The title of the Preview</returns>
|
||||
public GUIContent CommonGetPreviewTitle()
|
||||
{
|
||||
return new GUIContent("IES Luminaire Profile");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
/// <param name="background">Background of the Preview</param>
|
||||
/// <param name="r">Rect of the Preview</param>
|
||||
/// <param name="target">ScriptedImporter targeted</param>
|
||||
/// <param name="setupRenderPipelinePreviewLightIntensity">Delegate provided by the Rendering Pipeline to setup the Light Intensity</param>
|
||||
public void CommonOnPreviewGUI(Rect r, GUIStyle background, ScriptedImporter target,
|
||||
SetupRenderPipelinePreviewLightIntensity setupRenderPipelinePreviewLightIntensity)
|
||||
{
|
||||
if (Event.current.type == EventType.Repaint)
|
||||
{
|
||||
Texture cookieTexture = null;
|
||||
Texture previewTexture = null;
|
||||
|
||||
if (m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point)
|
||||
{
|
||||
foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(target.assetPath))
|
||||
{
|
||||
if (subAsset.name.EndsWith("-Cube-IES"))
|
||||
{
|
||||
cookieTexture = subAsset as Texture;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
else // LightType.Spot
|
||||
{
|
||||
foreach (var subAsset in AssetDatabase.LoadAllAssetRepresentationsAtPath(target.assetPath))
|
||||
{
|
||||
if (subAsset.name.EndsWith("-2D-IES"))
|
||||
{
|
||||
cookieTexture = subAsset as Texture;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (cookieTexture != null)
|
||||
{
|
||||
m_PreviewRenderUtility.lights[0].transform.localEulerAngles = new Vector3(90f, 0f, m_LightAimAxisRotationProp.floatValue);
|
||||
setupRenderPipelinePreviewLightIntensity(m_PreviewRenderUtility.lights[0], m_UseIESMaximumIntensityProp, m_IESMaximumIntensityUnitProp, m_IESMaximumIntensityProp);
|
||||
m_PreviewRenderUtility.lights[0].cookie = cookieTexture;
|
||||
m_PreviewRenderUtility.lights[0].type = m_PrefabLightTypeProp.enumValueIndex == (int)IESLightType.Point ? LightType.Point : LightType.Spot;
|
||||
|
||||
m_PreviewRenderUtility.BeginPreview(r, background);
|
||||
|
||||
bool fog = RenderSettings.fog;
|
||||
Unsupported.SetRenderSettingsUseFogNoDirty(false);
|
||||
|
||||
m_PreviewRenderUtility.camera.Render();
|
||||
|
||||
Unsupported.SetRenderSettingsUseFogNoDirty(fog);
|
||||
|
||||
previewTexture = m_PreviewRenderUtility.EndPreview();
|
||||
}
|
||||
|
||||
if (previewTexture == null)
|
||||
{
|
||||
GUI.DrawTexture(r, Texture2D.blackTexture, ScaleMode.StretchToFill, false);
|
||||
}
|
||||
else
|
||||
{
|
||||
GUI.DrawTexture(r, previewTexture, ScaleMode.ScaleToFit, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Callback called on the Implemented IESImporterEditor (currently on HDRP Only)
|
||||
/// </summary>
|
||||
public void CommonOnDisable()
|
||||
{
|
||||
if (m_PreviewRenderUtility != null)
|
||||
{
|
||||
m_PreviewRenderUtility.Cleanup();
|
||||
m_PreviewRenderUtility = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,171 @@
|
||||
using System.IO;
|
||||
using UnityEditor;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Various possible type for IES, in HDRP for Rectangular light we use spot version
|
||||
/// </summary>
|
||||
public enum IESLightType
|
||||
{
|
||||
/// <summary>
|
||||
/// Point for the IES
|
||||
/// </summary>
|
||||
Point,
|
||||
/// <summary>
|
||||
/// Spot for IES (compatible with Area Light)
|
||||
/// </summary>
|
||||
Spot,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Possible values for the IES Size.
|
||||
/// </summary>
|
||||
public enum IESResolution
|
||||
{
|
||||
/// <summary>Size 16</summary>
|
||||
IESResolution16 = 16,
|
||||
/// <summary>Size 32</summary>
|
||||
IESResolution32 = 32,
|
||||
/// <summary>Size 64</summary>
|
||||
IESResolution64 = 64,
|
||||
/// <summary>Size 128</summary>
|
||||
IESResolution128 = 128,
|
||||
/// <summary>Size 256</summary>
|
||||
IESResolution256 = 256,
|
||||
/// <summary>Size 512</summary>
|
||||
IESResolution512 = 512,
|
||||
/// <summary>Size 1024</summary>
|
||||
IESResolution1024 = 1024,
|
||||
/// <summary>Size 2048</summary>
|
||||
IESResolution2048 = 2048,
|
||||
/// <summary>Size 4096</summary>
|
||||
IESResolution4096 = 4096
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Common class to store metadata of an IES file
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IESMetaData
|
||||
{
|
||||
/// <summary>
|
||||
/// Version of the IES File
|
||||
/// </summary>
|
||||
public string FileFormatVersion;
|
||||
/// <summary>
|
||||
/// Total light intensity (in Lumens) stored on the file, usage of it is optional (through the prefab subasset inside the IESObject)
|
||||
/// </summary>
|
||||
public string IESPhotometricType;
|
||||
/// <summary>
|
||||
/// IES Max Intensity depends on the various information stored on the IES file
|
||||
/// </summary>
|
||||
public float IESMaximumIntensity;
|
||||
/// <summary>
|
||||
/// Unit used to measure the IESMaximumIntensity
|
||||
/// </summary>
|
||||
public string IESMaximumIntensityUnit;
|
||||
|
||||
// IES luminaire product information.
|
||||
/// <summary>
|
||||
/// Manufacturer of the current IES file
|
||||
/// </summary>
|
||||
public string Manufacturer; // IES keyword MANUFAC
|
||||
/// <summary>
|
||||
/// Luninaire Catalog Number
|
||||
/// </summary>
|
||||
public string LuminaireCatalogNumber; // IES keyword LUMCAT
|
||||
/// <summary>
|
||||
/// Luminaire Description
|
||||
/// </summary>
|
||||
public string LuminaireDescription; // IES keyword LUMINAIRE
|
||||
/// <summary>
|
||||
/// Lamp Catalog Number
|
||||
/// </summary>
|
||||
public string LampCatalogNumber; // IES keyword LAMPCAT
|
||||
/// <summary>
|
||||
/// Lamp Description
|
||||
/// </summary>
|
||||
public string LampDescription; // IES keyword LAMP
|
||||
|
||||
/// <summary>
|
||||
/// Prefab Light Type (optional to generate the texture used by the renderer)
|
||||
/// </summary>
|
||||
public IESLightType PrefabLightType = IESLightType.Point;
|
||||
|
||||
/// <summary>
|
||||
/// Spot angle used for the Gnomonic projection of the IES. This parameter will be responsible of the pixel footprint in the 2D Texture
|
||||
/// https://en.wikipedia.org/wiki/Gnomonic_projection
|
||||
/// </summary>
|
||||
[Range(1f, 179f)]
|
||||
public float SpotAngle = 120f;
|
||||
|
||||
/// <summary>
|
||||
/// IES Size of the texture used (same parameter for Point and Spot)
|
||||
/// </summary>
|
||||
public IESResolution iesSize = IESResolution.IESResolution128;
|
||||
|
||||
/// <summary>
|
||||
/// Enable attenuation used for Spot recommanded to be true, particulary with large angle of "SpotAngle" (cf. Gnomonic Projection)
|
||||
/// </summary>
|
||||
public bool ApplyLightAttenuation = true;
|
||||
/// <summary>
|
||||
/// Enable max intensity for the texture generation
|
||||
/// </summary>
|
||||
public bool UseIESMaximumIntensity = true;
|
||||
|
||||
/// <summary>
|
||||
/// Compression used to generate the texture (CompressedHQ by default (BC7))
|
||||
/// </summary>
|
||||
public TextureImporterCompression CookieCompression = TextureImporterCompression.CompressedHQ;
|
||||
|
||||
/// <summary>
|
||||
/// Internally we use 2D projection, we have to choose one axis to project the IES propertly
|
||||
/// </summary>
|
||||
[Range(-180f, 180f)]
|
||||
public float LightAimAxisRotation = -90f;
|
||||
|
||||
/// <summary>
|
||||
/// Get Hash describing an unique IES
|
||||
/// </summary>
|
||||
/// <returns>The Hash of the IES Object</returns>
|
||||
public override int GetHashCode()
|
||||
{
|
||||
int hash = base.GetHashCode();
|
||||
|
||||
hash = hash * 23 + FileFormatVersion.GetHashCode();
|
||||
hash = hash * 23 + IESPhotometricType.GetHashCode();
|
||||
hash = hash * 23 + IESMaximumIntensity.GetHashCode();
|
||||
hash = hash * 23 + IESMaximumIntensityUnit.GetHashCode();
|
||||
|
||||
hash = hash * 23 + Manufacturer.GetHashCode();
|
||||
hash = hash * 23 + LuminaireCatalogNumber.GetHashCode();
|
||||
hash = hash * 23 + LuminaireDescription.GetHashCode();
|
||||
hash = hash * 23 + LampCatalogNumber.GetHashCode();
|
||||
hash = hash * 23 + LampDescription.GetHashCode();
|
||||
|
||||
hash = hash * 23 + PrefabLightType.GetHashCode();
|
||||
|
||||
hash = hash * 23 + SpotAngle.GetHashCode();
|
||||
|
||||
hash = hash * 23 + iesSize.GetHashCode();
|
||||
hash = hash * 23 + ApplyLightAttenuation.GetHashCode();
|
||||
hash = hash * 23 + UseIESMaximumIntensity.GetHashCode();
|
||||
|
||||
return hash;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// IESObject manipulated internally (in the UI)
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IESObject : ScriptableObject
|
||||
{
|
||||
/// <summary>
|
||||
/// Metadata of the IES file
|
||||
/// </summary>
|
||||
public IESMetaData iesMetaData = new IESMetaData();
|
||||
}
|
||||
}
|
@@ -0,0 +1,540 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.Rendering
|
||||
{
|
||||
/// <summary>
|
||||
/// Class to Parse IES File
|
||||
/// </summary>
|
||||
[System.Serializable]
|
||||
public class IESReader
|
||||
{
|
||||
string m_FileFormatVersion;
|
||||
/// <summary>
|
||||
/// Version of the IES File
|
||||
/// </summary>
|
||||
public string FileFormatVersion
|
||||
{
|
||||
get { return m_FileFormatVersion; }
|
||||
}
|
||||
|
||||
float m_TotalLumens;
|
||||
/// <summary>
|
||||
/// Total light intensity (in Lumens) stored on the file, usage of it is optional (through the prefab subasset inside the IESObject)
|
||||
/// </summary>
|
||||
public float TotalLumens
|
||||
{
|
||||
get { return m_TotalLumens; }
|
||||
}
|
||||
|
||||
float m_MaxCandelas;
|
||||
/// <summary>
|
||||
/// Maximum of Candela in the IES File
|
||||
/// </summary>
|
||||
public float MaxCandelas
|
||||
{
|
||||
get { return m_MaxCandelas; }
|
||||
}
|
||||
|
||||
int m_PhotometricType;
|
||||
|
||||
/// <summary>
|
||||
/// Type of Photometric light in the IES file, varying per IES-Type and version
|
||||
/// </summary>
|
||||
public int PhotometricType
|
||||
{
|
||||
get { return m_PhotometricType; }
|
||||
}
|
||||
|
||||
Dictionary<string, string> m_KeywordDictionary = new Dictionary<string, string>();
|
||||
|
||||
int m_VerticalAngleCount;
|
||||
int m_HorizontalAngleCount;
|
||||
float[] m_VerticalAngles;
|
||||
float[] m_HorizontalAngles;
|
||||
float[] m_CandelaValues;
|
||||
|
||||
float m_MinDeltaVerticalAngle;
|
||||
float m_MinDeltaHorizontalAngle;
|
||||
float m_FirstHorizontalAngle;
|
||||
float m_LastHorizontalAngle;
|
||||
|
||||
// File format references:
|
||||
// https://www.ies.org/product/standard-file-format-for-electronic-transfer-of-photometric-data/
|
||||
// http://lumen.iee.put.poznan.pl/kw/iesna.txt
|
||||
// https://seblagarde.wordpress.com/2014/11/05/ies-light-format-specification-and-reader/
|
||||
/// <summary>
|
||||
/// Main function to read the file
|
||||
/// </summary>
|
||||
/// <param name="iesFilePath">The path to the IES File on disk.</param>
|
||||
/// <returns>Return the error during the import otherwise null if no error</returns>
|
||||
public string ReadFile(string iesFilePath)
|
||||
{
|
||||
using (var iesReader = File.OpenText(iesFilePath))
|
||||
{
|
||||
string versionLine = iesReader.ReadLine();
|
||||
|
||||
if (versionLine == null)
|
||||
{
|
||||
return "Premature end of file (empty file).";
|
||||
}
|
||||
|
||||
switch (versionLine.Trim())
|
||||
{
|
||||
case "IESNA91":
|
||||
m_FileFormatVersion = "LM-63-1991";
|
||||
break;
|
||||
case "IESNA:LM-63-1995":
|
||||
m_FileFormatVersion = "LM-63-1995";
|
||||
break;
|
||||
case "IESNA:LM-63-2002":
|
||||
m_FileFormatVersion = "LM-63-2002";
|
||||
break;
|
||||
case "IES:LM-63-2019":
|
||||
m_FileFormatVersion = "LM-63-2019";
|
||||
break;
|
||||
default:
|
||||
m_FileFormatVersion = "LM-63-1986";
|
||||
break;
|
||||
}
|
||||
|
||||
var keywordRegex = new Regex(@"\s*\[(?<keyword>\w+)\]\s*(?<data>.*)", RegexOptions.Compiled);
|
||||
var tiltRegex = new Regex(@"TILT=(?<data>.*)", RegexOptions.Compiled);
|
||||
|
||||
string currentKeyword = string.Empty;
|
||||
|
||||
for (string keywordLine = (m_FileFormatVersion == "LM-63-1986") ? versionLine : iesReader.ReadLine(); true; keywordLine = iesReader.ReadLine())
|
||||
{
|
||||
if (keywordLine == null)
|
||||
{
|
||||
return "Premature end of file (missing TILT=NONE).";
|
||||
}
|
||||
|
||||
if (string.IsNullOrWhiteSpace(keywordLine))
|
||||
{
|
||||
continue;
|
||||
}
|
||||
|
||||
Match keywordMatch = keywordRegex.Match(keywordLine);
|
||||
|
||||
if (keywordMatch.Success)
|
||||
{
|
||||
string keyword = keywordMatch.Groups["keyword"].Value;
|
||||
string data = keywordMatch.Groups["data"].Value.Trim();
|
||||
|
||||
if (keyword == currentKeyword || keyword == "MORE")
|
||||
{
|
||||
m_KeywordDictionary[currentKeyword] += $" {data}";
|
||||
}
|
||||
else
|
||||
{
|
||||
// Many separate occurrences of keyword OTHER will need to be handled properly once exposed in the inspector.
|
||||
currentKeyword = keyword;
|
||||
m_KeywordDictionary[currentKeyword] = data;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
Match tiltMatch = tiltRegex.Match(keywordLine);
|
||||
|
||||
if (tiltMatch.Success)
|
||||
{
|
||||
string data = tiltMatch.Groups["data"].Value.Trim();
|
||||
|
||||
if (data == "NONE")
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
return $"TILT format not supported: TILT={data}";
|
||||
}
|
||||
}
|
||||
|
||||
string[] iesDataTokens = Regex.Split(iesReader.ReadToEnd().Trim(), @"[\s,]+");
|
||||
var iesDataTokenEnumerator = iesDataTokens.GetEnumerator();
|
||||
string iesDataToken;
|
||||
|
||||
|
||||
if (iesDataTokens.Length == 1 && string.IsNullOrWhiteSpace(iesDataTokens[0]))
|
||||
{
|
||||
return "Premature end of file (missing IES data).";
|
||||
}
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing lamp count value).";
|
||||
}
|
||||
|
||||
int lampCount;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out lampCount))
|
||||
{
|
||||
return $"Invalid lamp count value: {iesDataToken}";
|
||||
}
|
||||
if (lampCount < 1) lampCount = 1;
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing lumens per lamp value).";
|
||||
}
|
||||
|
||||
float lumensPerLamp;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out lumensPerLamp))
|
||||
{
|
||||
return $"Invalid lumens per lamp value: {iesDataToken}";
|
||||
}
|
||||
m_TotalLumens = (lumensPerLamp < 0f) ? -1f : lampCount * lumensPerLamp;
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing candela multiplier value).";
|
||||
}
|
||||
|
||||
float candelaMultiplier;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out candelaMultiplier))
|
||||
{
|
||||
return $"Invalid candela multiplier value: {iesDataToken}";
|
||||
}
|
||||
if (candelaMultiplier < 0f) candelaMultiplier = 0f;
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing vertical angle count value).";
|
||||
}
|
||||
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_VerticalAngleCount))
|
||||
{
|
||||
return $"Invalid vertical angle count value: {iesDataToken}";
|
||||
}
|
||||
if (m_VerticalAngleCount < 1)
|
||||
{
|
||||
return $"Invalid number of vertical angles: {m_VerticalAngleCount}";
|
||||
}
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing horizontal angle count value).";
|
||||
}
|
||||
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_HorizontalAngleCount))
|
||||
{
|
||||
return $"Invalid horizontal angle count value: {iesDataToken}";
|
||||
}
|
||||
if (m_HorizontalAngleCount < 1)
|
||||
{
|
||||
return $"Invalid number of horizontal angles: {m_HorizontalAngleCount}";
|
||||
}
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing photometric type value).";
|
||||
}
|
||||
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!int.TryParse(iesDataToken, NumberStyles.AllowLeadingSign, CultureInfo.InvariantCulture, out m_PhotometricType))
|
||||
{
|
||||
return $"Invalid photometric type value: {iesDataToken}";
|
||||
}
|
||||
if (m_PhotometricType < 1 || m_PhotometricType > 3)
|
||||
{
|
||||
return $"Invalid photometric type: {m_PhotometricType}";
|
||||
}
|
||||
|
||||
// Skip luminous dimension unit type.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing luminous dimension unit type value).";
|
||||
}
|
||||
|
||||
// Skip luminous dimension width.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing luminous dimension width value).";
|
||||
}
|
||||
|
||||
// Skip luminous dimension length.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing luminous dimension length value).";
|
||||
}
|
||||
|
||||
// Skip luminous dimension height.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing luminous dimension height value).";
|
||||
}
|
||||
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing ballast factor value).";
|
||||
}
|
||||
|
||||
float ballastFactor;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out ballastFactor))
|
||||
{
|
||||
return $"Invalid ballast factor value: {iesDataToken}";
|
||||
}
|
||||
if (ballastFactor < 0f) ballastFactor = 0f;
|
||||
|
||||
// Skip future use.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing future use value).";
|
||||
}
|
||||
|
||||
// Skip input watts.
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing input watts value).";
|
||||
}
|
||||
|
||||
m_VerticalAngles = new float[m_VerticalAngleCount];
|
||||
float previousVerticalAngle = float.MinValue;
|
||||
|
||||
m_MinDeltaVerticalAngle = 180f;
|
||||
|
||||
for (int v = 0; v < m_VerticalAngleCount; ++v)
|
||||
{
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing vertical angle values).";
|
||||
}
|
||||
|
||||
float angle;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out angle))
|
||||
{
|
||||
return $"Invalid vertical angle value: {iesDataToken}";
|
||||
}
|
||||
|
||||
if (angle <= previousVerticalAngle)
|
||||
{
|
||||
return $"Vertical angles are not in ascending order near: {angle}";
|
||||
}
|
||||
|
||||
float deltaVerticalAngle = angle - previousVerticalAngle;
|
||||
if (deltaVerticalAngle < m_MinDeltaVerticalAngle)
|
||||
{
|
||||
m_MinDeltaVerticalAngle = deltaVerticalAngle;
|
||||
}
|
||||
|
||||
m_VerticalAngles[v] = previousVerticalAngle = angle;
|
||||
}
|
||||
|
||||
m_HorizontalAngles = new float[m_HorizontalAngleCount];
|
||||
float previousHorizontalAngle = float.MinValue;
|
||||
|
||||
m_MinDeltaHorizontalAngle = 360f;
|
||||
|
||||
for (int h = 0; h < m_HorizontalAngleCount; ++h)
|
||||
{
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing horizontal angle values).";
|
||||
}
|
||||
|
||||
float angle;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out angle))
|
||||
{
|
||||
return $"Invalid horizontal angle value: {iesDataToken}";
|
||||
}
|
||||
|
||||
if (angle <= previousHorizontalAngle)
|
||||
{
|
||||
return $"Horizontal angles are not in ascending order near: {angle}";
|
||||
}
|
||||
|
||||
float deltaHorizontalAngle = angle - previousHorizontalAngle;
|
||||
if (deltaHorizontalAngle < m_MinDeltaHorizontalAngle)
|
||||
{
|
||||
m_MinDeltaHorizontalAngle = deltaHorizontalAngle;
|
||||
}
|
||||
|
||||
m_HorizontalAngles[h] = previousHorizontalAngle = angle;
|
||||
}
|
||||
|
||||
m_FirstHorizontalAngle = m_HorizontalAngles[0];
|
||||
m_LastHorizontalAngle = m_HorizontalAngles[m_HorizontalAngleCount - 1];
|
||||
|
||||
m_CandelaValues = new float[m_HorizontalAngleCount * m_VerticalAngleCount];
|
||||
m_MaxCandelas = 0f;
|
||||
|
||||
for (int h = 0; h < m_HorizontalAngleCount; ++h)
|
||||
{
|
||||
for (int v = 0; v < m_VerticalAngleCount; ++v)
|
||||
{
|
||||
if (!iesDataTokenEnumerator.MoveNext())
|
||||
{
|
||||
return "Premature end of file (missing candela values).";
|
||||
}
|
||||
|
||||
float value;
|
||||
iesDataToken = iesDataTokenEnumerator.Current.ToString();
|
||||
if (!float.TryParse(iesDataToken, NumberStyles.AllowLeadingSign | NumberStyles.AllowDecimalPoint, CultureInfo.InvariantCulture, out value))
|
||||
{
|
||||
return $"Invalid candela value: {iesDataToken}";
|
||||
}
|
||||
value *= candelaMultiplier * ballastFactor;
|
||||
|
||||
m_CandelaValues[h * m_VerticalAngleCount + v] = value;
|
||||
|
||||
if (value > m_MaxCandelas)
|
||||
{
|
||||
m_MaxCandelas = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
internal string GetKeywordValue(string keyword)
|
||||
{
|
||||
return m_KeywordDictionary.ContainsKey(keyword) ? m_KeywordDictionary[keyword] : string.Empty;
|
||||
}
|
||||
|
||||
internal int GetMinVerticalSampleCount()
|
||||
{
|
||||
if (m_PhotometricType == 2) // type B
|
||||
{
|
||||
// Factor in the 90 degree rotation that will be done when building the cylindrical texture.
|
||||
return 1 + (int)Mathf.Ceil(360 / m_MinDeltaHorizontalAngle); // 360 is 2 * 180 degrees
|
||||
}
|
||||
else // type A or C
|
||||
{
|
||||
return 1 + (int)Mathf.Ceil(360 / m_MinDeltaVerticalAngle); // 360 is 2 * 180 degrees
|
||||
}
|
||||
}
|
||||
|
||||
internal int GetMinHorizontalSampleCount()
|
||||
{
|
||||
switch (m_PhotometricType)
|
||||
{
|
||||
case 3: // type A
|
||||
return 1 + (int)Mathf.Ceil(720 / m_MinDeltaHorizontalAngle); // 720 is 2 * 360 degrees
|
||||
case 2: // type B
|
||||
// Factor in the 90 degree rotation that will be done when building the cylindrical texture.
|
||||
return 1 + (int)Mathf.Ceil(720 / m_MinDeltaVerticalAngle); // 720 is 2 * 360 degrees
|
||||
default: // type C
|
||||
// Factor in the 90 degree rotation that will be done when building the cylindrical texture.
|
||||
return 1 + (int)Mathf.Ceil(720 / Mathf.Min(m_MinDeltaHorizontalAngle, m_MinDeltaVerticalAngle)); // 720 is 2 * 360 degrees
|
||||
}
|
||||
}
|
||||
|
||||
internal float ComputeVerticalAnglePosition(float angle)
|
||||
{
|
||||
return ComputeAnglePosition(angle, m_VerticalAngles);
|
||||
}
|
||||
|
||||
internal float ComputeTypeAorBHorizontalAnglePosition(float angle) // angle in range [-180..+180] degrees
|
||||
{
|
||||
return ComputeAnglePosition(((m_FirstHorizontalAngle == 0f) ? Mathf.Abs(angle) : angle), m_HorizontalAngles);
|
||||
}
|
||||
|
||||
internal float ComputeTypeCHorizontalAnglePosition(float angle) // angle in range [0..360] degrees
|
||||
{
|
||||
switch (m_LastHorizontalAngle)
|
||||
{
|
||||
case 0f: // the luminaire is assumed to be laterally symmetric in all planes
|
||||
angle = 0f;
|
||||
break;
|
||||
case 90f: // the luminaire is assumed to be symmetric in each quadrant
|
||||
angle = 90f - Mathf.Abs(Mathf.Abs(angle - 180f) - 90f);
|
||||
break;
|
||||
case 180f: // the luminaire is assumed to be symmetric about the 0 to 180 degree plane
|
||||
angle = 180f - Mathf.Abs(angle - 180f);
|
||||
break;
|
||||
default: // the luminaire is assumed to exhibit no lateral symmetry
|
||||
break;
|
||||
}
|
||||
|
||||
return ComputeAnglePosition(angle, m_HorizontalAngles);
|
||||
}
|
||||
|
||||
internal float ComputeAnglePosition(float value, float[] angles)
|
||||
{
|
||||
int start = 0;
|
||||
int end = angles.Length - 1;
|
||||
|
||||
if (value < angles[start])
|
||||
{
|
||||
return start;
|
||||
}
|
||||
|
||||
if (value > angles[end])
|
||||
{
|
||||
return end;
|
||||
}
|
||||
|
||||
while (start < end)
|
||||
{
|
||||
int index = (start + end + 1) / 2;
|
||||
|
||||
float angle = angles[index];
|
||||
|
||||
if (value >= angle)
|
||||
{
|
||||
start = index;
|
||||
}
|
||||
else
|
||||
{
|
||||
end = index - 1;
|
||||
}
|
||||
}
|
||||
|
||||
float leftValue = angles[start];
|
||||
float fraction = 0f;
|
||||
|
||||
if (start + 1 < angles.Length)
|
||||
{
|
||||
float rightValue = angles[start + 1];
|
||||
float deltaValue = rightValue - leftValue;
|
||||
|
||||
if (deltaValue > 0.0001f)
|
||||
{
|
||||
fraction = (value - leftValue) / deltaValue;
|
||||
}
|
||||
}
|
||||
|
||||
return start + fraction;
|
||||
}
|
||||
|
||||
internal float InterpolateBilinear(float x, float y)
|
||||
{
|
||||
int ix = (int)Mathf.Floor(x);
|
||||
int iy = (int)Mathf.Floor(y);
|
||||
|
||||
float fractionX = x - ix;
|
||||
float fractionY = y - iy;
|
||||
|
||||
float p00 = InterpolatePoint(ix + 0, iy + 0);
|
||||
float p10 = InterpolatePoint(ix + 1, iy + 0);
|
||||
float p01 = InterpolatePoint(ix + 0, iy + 1);
|
||||
float p11 = InterpolatePoint(ix + 1, iy + 1);
|
||||
|
||||
float p0 = Mathf.Lerp(p00, p01, fractionY);
|
||||
float p1 = Mathf.Lerp(p10, p11, fractionY);
|
||||
|
||||
return Mathf.Lerp(p0, p1, fractionX);
|
||||
}
|
||||
|
||||
internal float InterpolatePoint(int x, int y)
|
||||
{
|
||||
x %= m_HorizontalAngles.Length;
|
||||
y %= m_VerticalAngles.Length;
|
||||
|
||||
return m_CandelaValues[y + x * m_VerticalAngles.Length];
|
||||
}
|
||||
}
|
||||
}
|
Binary file not shown.
After Width: | Height: | Size: 4.1 KiB |
Binary file not shown.
After Width: | Height: | Size: 4.4 KiB |
Reference in New Issue
Block a user