// +---------------------------------------------------------------------- // | Project : ray. // | All rights reserved. // +---------------------------------------------------------------------- // | Copyright (c) 2013-2017. // +---------------------------------------------------------------------- // | * Redistribution and use of this software in source and binary forms, // | with or without modification, are permitted provided that the following // | conditions are met: // | // | * Redistributions of source code must retain the above // | copyright notice, this list of conditions and the // | following disclaimer. // | // | * Redistributions in binary form must reproduce the above // | copyright notice, this list of conditions and the // | following disclaimer in the documentation and/or other // | materials provided with the distribution. // | // | * Neither the name of the ray team, nor the names of its // | contributors may be used to endorse or promote products // | derived from this software without specific prior // | written permission of the ray team. // | // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. // +---------------------------------------------------------------------- #include "ies_loader.h" #include #include #include #include "math/mMathFn.h" IESFileInfo::IESFileInfo() : _cachedIntegral(F32_MAX) , _error("No data loaded") { } bool IESFileInfo::valid() const { return _error.empty(); } const std::string& IESFileInfo::error() const { return _error; } IESLoadHelper::IESLoadHelper() { } IESLoadHelper::~IESLoadHelper() { } bool IESLoadHelper::load(const char* data, std::size_t dataLength, IESFileInfo& info) { assert(!info.valid()); return this->load(std::string(data, dataLength), info); } bool IESLoadHelper::load(const std::string& data, IESFileInfo& info) { assert(!info.valid()); std::string dataPos; std::string version; this->getLineContent(data, dataPos, version, false, false); if (version.empty()) { info._error = "Unknown IES version"; return false; } else if (version == "IESNA:LM-63-1995") info._version = "IESNA:LM-63-1995"; else if (version == "IESNA91") info._version = "IESNA91"; else if (version == "IESNA:LM-63-2002") info._version = "IESNA:LM-63-2002"; else info._version = version; while (!dataPos.empty()) { std::string line; this->getLineContent(dataPos, dataPos, line, false, false); if (line.compare(0, 9, "TILT=NONE", 9) == 0 || line.compare(0, 10, "TILT= NONE", 10) == 0 || line.compare(0, 10, "TILT =NONE", 10) == 0 || line.compare(0, 11, "TILT = NONE", 11) == 0) { break; } else if (line.compare(0, 5, "TILT=", 5) == 0 || line.compare(0, 5, "TILT =", 5) == 0) { info._error = "Not supported yet."; return false; } } this->getFloat(dataPos, dataPos, info.totalLights); if (info.totalLights < 0 || info.totalLights > F32_MAX) { info._error = "Light Count is not valid"; return false; } this->getFloat(dataPos, dataPos, info.totalLumens); if (info.totalLumens < 0) { info._error = "TotalLumens is not positive number"; return false; } this->getFloat(dataPos, dataPos, info.candalaMult); if (info.candalaMult < 0) { info._error = "CandalaMult is not positive number"; return false; } this->getInt(dataPos, dataPos, info.anglesNumV); if (info.anglesNumV < 0 || info.anglesNumV > F32_MAX) { info._error = "VAnglesNum is not valid"; return false; } this->getInt(dataPos, dataPos, info.anglesNumH); if (info.anglesNumH < 0 || info.anglesNumH > F32_MAX) { info._error = "HAnglesNum is not valid"; return false; } this->getInt(dataPos, dataPos, info.typeOfPhotometric); this->getInt(dataPos, dataPos, info.typeOfUnit); this->getFloat(dataPos, dataPos, info.width); this->getFloat(dataPos, dataPos, info.length); this->getFloat(dataPos, dataPos, info.height); this->getFloat(dataPos, dataPos, info.ballastFactor); this->getFloat(dataPos, dataPos, info.futureUse); this->getFloat(dataPos, dataPos, info.inputWatts); float minSoFarV = F32_MIN_EX; float minSoFarH = F32_MIN_EX; info._anglesV.reserve(info.anglesNumV); info._anglesH.reserve(info.anglesNumH); for (std::int32_t y = 0; y < info.anglesNumV; ++y) { float value; this->getFloat(dataPos, dataPos, value, true, true); if (value < minSoFarV) { info._error = "V Values is not valid"; return false; } minSoFarV = value; info._anglesV.push_back(value); } for (std::int32_t x = 0; x < info.anglesNumH; ++x) { float value; this->getFloat(dataPos, dataPos, value, true, true); if (value < minSoFarH) { info._error = "H Values is not valid"; return false; } minSoFarH = value; info._anglesH.push_back(value); } info._candalaValues.reserve(info.anglesNumH * info.anglesNumV); for (std::int32_t y = 0; y < info.anglesNumH; ++y) { for (std::int32_t x = 0; x < info.anglesNumV; ++x) { float value; this->getFloat(dataPos, dataPos, value, true, true); info._candalaValues.push_back(value * info.candalaMult); } } skipSpaceAndLineEnd(dataPos, dataPos); if (!dataPos.empty()) { std::string line; this->getLineContent(dataPos, dataPos, line, true, false); if (line == "END") skipSpaceAndLineEnd(dataPos, dataPos); if (!dataPos.empty()) { info._error = "Unexpected content after END."; return false; } } info._error.clear(); return true; } bool IESLoadHelper::saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint8_t channel) noexcept { assert(data); assert(width > 0); assert(channel == 1 || channel == 3 || channel == 4); assert(info.valid()); float invW = 1.0f / width; float invMaxValue = this->computeInvMax(info._candalaValues); for (std::uint32_t x = 0; x < width; ++x) { float fraction = x * invW; float value = invMaxValue * interpolate1D(info, fraction * 180.0f); switch (channel) { case 1: *data++ = value; break; case 3: *data++ = value; *data++ = value; *data++ = value; break; case 4: *data++ = value; *data++ = value; *data++ = value; *data++ = 1.0f; break; default: return false; } } return true; } bool IESLoadHelper::saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept { assert(data); assert(width > 0 && height > 0); assert(channel == 1 || channel == 3 || channel == 4); assert(info.valid()); float invW = 1.0f / width; float invH = 1.0f / height; float invMaxValue = this->computeInvMax(info._candalaValues); for (std::uint32_t y = 0; y < height; ++y) { for (std::uint32_t x = 0; x < width; ++x) { float fractionV = x * invW * 180.0f; float fractionH = y * invH * 180.0f; float value = invMaxValue * interpolate2D(info, fractionV, fractionH); switch (channel) { case 1: *data++ = value; break; case 3: *data++ = value; *data++ = value; *data++ = value; break; case 4: *data++ = value; *data++ = value; *data++ = value; *data++ = 1.0; break; default: return false; } } } return true; } bool IESLoadHelper::saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept { assert(data); assert(width > 0 && height > 0); assert(channel == 1 || channel == 3 || channel == 4); assert(info.valid()); std::vector ies(256); if (!this->saveAs1D(info, ies.data(), ies.size(), 1)) return false; float maxValue = this->computeInvMax(info._candalaValues); auto TonemapHable = [](float x) { const float A = 0.22f; const float B = 0.30f; const float C = 0.10f; const float D = 0.20f; const float E = 0.01f; const float F = 0.30f; return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F; }; for (int y = 0; y < height; y++) { for (int x = 0; x < width; x++) { float u = ((float)x / width) * 2.0f - 1.0f; float v = 1.0f - ((float)y / height) * 2.0f - 1.0f; u *= 2.2f; v *= 2.4f; // float3(0.0f, 0.0f, -0.5f) - ray::float3(u, v, 0.0f) float lx = +0.0f - u; float ly = +0.0f - v; float lz = -0.5f - 0.0f; // normalize float length = mSqrt(lx * lx + ly * ly + lz * lz); lx /= length; ly /= length; lz /= length; float angle = 1.0 - mAcos(lx * 0.0 + ly * -1.0 + lz * 0.0f) / 3.141592654; float intensity = ies[angle * 255] * maxValue / length; std::uint8_t value = std::min(std::max((int)mFloor(TonemapHable(intensity) / TonemapHable(maxValue) * 255.0f), 0), 255); switch (channel) { case 1: *data++ = value; break; case 3: *data++ = value; *data++ = value; *data++ = value; break; case 4: *data++ = value; *data++ = value; *data++ = value; *data++ = 1.0; break; default: return false; } } } return true; } float IESLoadHelper::computeInvMax(const std::vector& candalaValues) const { assert(candalaValues.size()); float candala = *std::max_element(candalaValues.begin(), candalaValues.end()); return 1.0f / candala; } float IESLoadHelper::computeFilterPos(float value, const std::vector& angles) const { assert(angles.size()); std::size_t start = 0; std::size_t end = angles.size() - 1; if (value < angles[start]) return 0.0f; if (value > angles[end]) return (float)end; while (start < end) { std::size_t index = (start + end + 1) / 2; float angle = angles[index]; if (value >= angle) { assert(start != index); start = index; } else { assert(end != index - 1); end = index - 1; } } float leftValue = angles[start]; float fraction = 0.0f; if (start + 1 < (std::uint32_t)angles.size()) { float rightValue = angles[start + 1]; float deltaValue = rightValue - leftValue; if (deltaValue > 0.0001f) { fraction = (value - leftValue) / deltaValue; } } return start + fraction; } float IESLoadHelper::interpolate1D(const IESFileInfo& info, float angle) const { float angleV = this->computeFilterPos(angle, info._anglesV); float anglesNum = (float)info._anglesH.size(); float angleTotal = 0.0f; for (float x = 0; x < anglesNum; x++) angleTotal += this->interpolateBilinear(info, x, angleV); return angleTotal / anglesNum; } float IESLoadHelper::interpolate2D(const IESFileInfo& info, float angleV, float angleH) const { float u = this->computeFilterPos(angleH, info._anglesH); float v = this->computeFilterPos(angleV, info._anglesV); return this->interpolateBilinear(info, u, v); } float IESLoadHelper::interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const { assert(x >= 0); assert(y >= 0); std::size_t anglesNumH = info._anglesH.size(); std::size_t anglesNumV = info._anglesV.size(); x %= anglesNumH; y %= anglesNumV; assert(x < anglesNumH); assert(y < anglesNumV); return info._candalaValues[y + anglesNumV * x]; } float IESLoadHelper::interpolateBilinear(const IESFileInfo& info, float x, float y) const { int ix = (int)mFloor(x); int iy = (int)mFloor(y); float fracX = x - ix; float fracY = y - iy; float p00 = this->interpolatePoint(info, ix + 0, iy + 0); float p10 = this->interpolatePoint(info, ix + 1, iy + 0); float p01 = this->interpolatePoint(info, ix + 0, iy + 1); float p11 = this->interpolatePoint(info, ix + 1, iy + 1); auto lerp = [](float t1, float t2, float t3) -> float { return t1 + (t2 - t1) * t3; }; float p0 = lerp(p00, p01, fracY); float p1 = lerp(p10, p11, fracY); return lerp(p0, p1, fracX); } void IESLoadHelper::skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma) { std::size_t dataBegin = 0; std::size_t dataEnd = data.size(); while (dataBegin < dataEnd) { if (data[dataBegin] != '\r' && data[dataBegin] != '\n' && data[dataBegin] > ' ') break; dataBegin++; } if (stopOnComma) { while (dataBegin < dataEnd) { if (data[dataBegin] != ',') break; dataBegin++; } } out = data.substr(dataBegin, data.size() - dataBegin); } void IESLoadHelper::getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma) { skipSpaceAndLineEnd(data, next); auto it = data.begin(); auto end = data.end(); for (; it < end; ++it) { if ((*it == '\r') || (*it == '\n') || (*it <= ' ' && stopOnWhiteSpace) || (*it == ',' && stopOnComma)) { break; } } line.assign(data, 0, it - data.begin()); next.assign(data, it - data.begin(), end - it); skipSpaceAndLineEnd(next, next, stopOnComma); } void IESLoadHelper::getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace, bool stopOnComma) { std::string line; getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma); assert(!line.empty()); ret = (float)std::atof(line.c_str()); } void IESLoadHelper::getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace, bool stopOnComma) { std::string line; getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma); assert(!line.empty()); ret = std::atoi(line.c_str()); }