123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581 |
- // +----------------------------------------------------------------------
- // | 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 <assert.h>
- #include <algorithm>
- #include <functional>
- #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<float> 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<float>& 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<float>& 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());
- }
|