This commit is contained in:
2021-06-13 10:28:03 +02:00
parent eb70603c85
commit df2d24cbd3
7487 changed files with 943244 additions and 0 deletions

View File

@@ -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;
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}
}

View File

@@ -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;
}
}
}
}

View File

@@ -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();
}
}

View File

@@ -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