123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541 |
- 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];
- }
- }
- }
|