// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved. #include "Engine/Core/Log.h" #include "Engine/Core/RandomStream.h" #include "Engine/Core/Math/Packed.h" #include "IESLoader.h" #define MAX_LINE 200 static void SkipWhiteSpace(const uint8*& bufferPos) { while (*bufferPos) { if (*bufferPos == 13 && *(bufferPos + 1) == 10) { bufferPos += 2; continue; } if (*bufferPos == 10) { bufferPos++; continue; } if (*bufferPos <= ' ') { bufferPos++; continue; } break; } } static void ReadLine(const uint8*& bufferPos, char line[MAX_LINE], bool skipUntilWhitespace) { SkipWhiteSpace(bufferPos); char* linePtr = line; uint32 i; for (i = 0; i < 255; i++) { if (*bufferPos == 0) { break; } if (*bufferPos == 13 && *(bufferPos + 1) == 10) { bufferPos += 2; break; } if (*bufferPos == 10) { bufferPos++; continue; } if (skipUntilWhitespace && *bufferPos <= ' ') { bufferPos++; break; } *linePtr++ = *bufferPos++; } line[i] = 0; } static bool ReadFloat(const uint8*& bufferPos, float& ret) { char line[MAX_LINE]; ReadLine(bufferPos, line, true); ret = static_cast(atof(line)); return true; } static bool ReadLine(const uint8*& bufferPos, int32& ret) { char line[MAX_LINE]; ReadLine(bufferPos, line, true); ret = atoi(line); return true; } #define PARSE_FLOAT(x) float x; if (!ReadFloat(bufferPos, x)) { return true; } #define PARSE_INT(x) int32 x; if (!ReadLine(bufferPos, x)) { return true; } bool IESLoader::Load(const byte* buffer) { // Referenced IES file format: // http://www.ltblight.com/English.lproj/LTBLhelp/pages/iesformat.html const uint8* bufferPos = buffer; const char* version; { char line[MAX_LINE]; ReadLine(bufferPos, line, false); if (StringUtils::CompareIgnoreCase(line, "IESNA:LM-63-1995") == 0) { version = "EIESV_1995"; } else if (StringUtils::CompareIgnoreCase(line, "IESNA91") == 0) { version = "EIESV_1991"; } else if (StringUtils::CompareIgnoreCase(line, "IESNA:LM-63-2002") == 0) { version = "EIESV_2002"; } else { version = "EIESV_1986"; } } while (*bufferPos) { char line[MAX_LINE]; ReadLine(bufferPos, line, false); if (StringUtils::Compare(line, "TILT=NONE") == 0) { break; } if (StringUtils::Compare(line, "TILT=", 5) == 0) { return true; } } PARSE_INT(lights); PARSE_FLOAT(lumensPerLight); PARSE_FLOAT(candalaScale); PARSE_INT(vAnglesCount); PARSE_INT(hAnglesCount); PARSE_INT(photometricType); PARSE_INT(unitType); PARSE_FLOAT(width); PARSE_FLOAT(length); PARSE_FLOAT(height); PARSE_FLOAT(ballastWeight); PARSE_FLOAT(dummy); PARSE_FLOAT(watts); if (lights < 1) { return true; } if (candalaScale < 0 || vAnglesCount < 0 || hAnglesCount < 0) { return true; } _brightness = lumensPerLight / lights; { float minValue = MIN_float; _vAngles.SetCapacity(vAnglesCount, false); for (int32 y = 0; y < vAnglesCount; y++) { PARSE_FLOAT(value); if (value < minValue) return true; minValue = value; _vAngles.Add(value); } } { float minValue = MIN_float; _hAngles.SetCapacity(hAnglesCount, false); for (int32 x = 0; x < hAnglesCount; x++) { PARSE_FLOAT(value); if (value < minValue) return true; minValue = value; _hAngles.Add(value); } } _candalaValues.SetCapacity(hAnglesCount * vAnglesCount, false); for (int32 y = 0; y < hAnglesCount; y++) { for (int32 x = 0; x < vAnglesCount; x++) { PARSE_FLOAT(value); _candalaValues.Add(value * candalaScale); } } SkipWhiteSpace(bufferPos); if (*bufferPos) { char line[MAX_LINE]; ReadLine(bufferPos, line, true); if (StringUtils::Compare(line, "END") == 0) { SkipWhiteSpace(bufferPos); } } if (*bufferPos) { return true; } if (_brightness <= 0) { _brightness = 1000; } return false; } #undef PARSE_FLOAT #undef PARSE_INT float IESLoader::ExtractInR16(Array& output) { const uint32 width = GetWidth(); const uint32 height = GetHeight(); output.Clear(); output.Resize(width * height * sizeof(Half), false); Half* out = reinterpret_cast(output.Get()); const float invWidth = 1.0f / (float)width; float maxValue = _candalaValues[0]; for (int32 i = 1; i < _candalaValues.Count(); i++) maxValue = Math::Max(maxValue, _candalaValues[i]); const float invMaxValue = 1.0f / maxValue; const uint32 hAnglesCount = static_cast(_hAngles.Count()); for (uint32 y = 0; y < height; y++) { for (uint32 x = 0; x < width; x++) { const float vAngle = (float)x * invWidth * 180.0f; const float v = ComputeFilterPos(vAngle, _vAngles); float result = 0.0f; for (uint32 i = 0; i < hAnglesCount; i++) result += InterpolateBilinear(static_cast(i), v); *out++ = ConvertFloatToHalf(invMaxValue * result / (float)hAnglesCount); } } float integral; { // Calculate integral using Monte Carlo const int32 count = 500000; const RandomStream randomStream(0x1234); double sum = 0; for (uint32 i = 0; i < count; i++) { const Vector3 v = randomStream.GetUnitVector(); const float hAngle = Math::Acos(v.Z) / PI * 180; const float vAngle = Math::Atan2(v.Y, v.X) / PI * 180 + 180; sum += InterpolateBilinear(ComputeFilterPos(hAngle, _hAngles), ComputeFilterPos(vAngle, _vAngles)); } integral = static_cast(sum / count); } return maxValue / integral; } float IESLoader::InterpolatePoint(int32 x, int32 y) const { x %= _hAngles.Count(); y %= _vAngles.Count(); return _candalaValues[y + _vAngles.Count() * x]; } float IESLoader::InterpolateBilinear(float x, float y) const { const int32 xInt = static_cast(x); const int32 yInt = static_cast(y); const float xFrac = x - xInt; const float yFrac = y - yInt; const float p00 = InterpolatePoint(xInt + 0, yInt + 0); const float p10 = InterpolatePoint(xInt + 1, yInt + 0); const float p01 = InterpolatePoint(xInt + 0, yInt + 1); const float p11 = InterpolatePoint(xInt + 1, yInt + 1); const float p0 = Math::Lerp(p00, p01, yFrac); const float p1 = Math::Lerp(p10, p11, yFrac); return Math::Lerp(p0, p1, xFrac); } float IESLoader::ComputeFilterPos(float value, const Array& sortedValues) { ASSERT(sortedValues.HasItems()); uint32 startPos = 0; uint32 endPos = sortedValues.Count() - 1; if (value < sortedValues[startPos]) { return 0.0f; } if (value > sortedValues[endPos]) { return static_cast(endPos); } while (startPos < endPos) { const uint32 testPos = (startPos + endPos + 1) / 2; const float testValue = sortedValues[testPos]; if (value >= testValue) { ASSERT(startPos != testPos); startPos = testPos; } else { ASSERT(endPos != testPos - 1); endPos = testPos - 1; } } const float leftValue = sortedValues[startPos]; float fraction = 0.0f; if (startPos + 1 < static_cast(sortedValues.Count())) { const float rightValue = sortedValues[startPos + 1]; const float deltaValue = rightValue - leftValue; if (deltaValue > 0.00005f) { fraction = (value - leftValue) / deltaValue; } } return startPos + fraction; }