ies_loader.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581
  1. // +----------------------------------------------------------------------
  2. // | Project : ray.
  3. // | All rights reserved.
  4. // +----------------------------------------------------------------------
  5. // | Copyright (c) 2013-2017.
  6. // +----------------------------------------------------------------------
  7. // | * Redistribution and use of this software in source and binary forms,
  8. // | with or without modification, are permitted provided that the following
  9. // | conditions are met:
  10. // |
  11. // | * Redistributions of source code must retain the above
  12. // | copyright notice, this list of conditions and the
  13. // | following disclaimer.
  14. // |
  15. // | * Redistributions in binary form must reproduce the above
  16. // | copyright notice, this list of conditions and the
  17. // | following disclaimer in the documentation and/or other
  18. // | materials provided with the distribution.
  19. // |
  20. // | * Neither the name of the ray team, nor the names of its
  21. // | contributors may be used to endorse or promote products
  22. // | derived from this software without specific prior
  23. // | written permission of the ray team.
  24. // |
  25. // | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  26. // | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  27. // | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
  28. // | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
  29. // | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  30. // | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
  31. // | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
  32. // | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
  33. // | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
  34. // | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  35. // | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  36. // +----------------------------------------------------------------------
  37. #include "ies_loader.h"
  38. #include <assert.h>
  39. #include <algorithm>
  40. #include <functional>
  41. #include "math/mMathFn.h"
  42. IESFileInfo::IESFileInfo()
  43. : _cachedIntegral(F32_MAX)
  44. , _error("No data loaded")
  45. {
  46. }
  47. bool
  48. IESFileInfo::valid() const
  49. {
  50. return _error.empty();
  51. }
  52. const std::string&
  53. IESFileInfo::error() const
  54. {
  55. return _error;
  56. }
  57. IESLoadHelper::IESLoadHelper()
  58. {
  59. }
  60. IESLoadHelper::~IESLoadHelper()
  61. {
  62. }
  63. bool
  64. IESLoadHelper::load(const char* data, std::size_t dataLength, IESFileInfo& info)
  65. {
  66. assert(!info.valid());
  67. return this->load(std::string(data, dataLength), info);
  68. }
  69. bool
  70. IESLoadHelper::load(const std::string& data, IESFileInfo& info)
  71. {
  72. assert(!info.valid());
  73. std::string dataPos;
  74. std::string version;
  75. this->getLineContent(data, dataPos, version, false, false);
  76. if (version.empty())
  77. {
  78. info._error = "Unknown IES version";
  79. return false;
  80. }
  81. else if (version == "IESNA:LM-63-1995")
  82. info._version = "IESNA:LM-63-1995";
  83. else if (version == "IESNA91")
  84. info._version = "IESNA91";
  85. else if (version == "IESNA:LM-63-2002")
  86. info._version = "IESNA:LM-63-2002";
  87. else
  88. info._version = version;
  89. while (!dataPos.empty())
  90. {
  91. std::string line;
  92. this->getLineContent(dataPos, dataPos, line, false, false);
  93. if (line.compare(0, 9, "TILT=NONE", 9) == 0 ||
  94. line.compare(0, 10, "TILT= NONE", 10) == 0 ||
  95. line.compare(0, 10, "TILT =NONE", 10) == 0 ||
  96. line.compare(0, 11, "TILT = NONE", 11) == 0)
  97. {
  98. break;
  99. }
  100. else if (line.compare(0, 5, "TILT=", 5) == 0 ||
  101. line.compare(0, 5, "TILT =", 5) == 0)
  102. {
  103. info._error = "Not supported yet.";
  104. return false;
  105. }
  106. }
  107. this->getFloat(dataPos, dataPos, info.totalLights);
  108. if (info.totalLights < 0 || info.totalLights > F32_MAX)
  109. {
  110. info._error = "Light Count is not valid";
  111. return false;
  112. }
  113. this->getFloat(dataPos, dataPos, info.totalLumens);
  114. if (info.totalLumens < 0)
  115. {
  116. info._error = "TotalLumens is not positive number";
  117. return false;
  118. }
  119. this->getFloat(dataPos, dataPos, info.candalaMult);
  120. if (info.candalaMult < 0)
  121. {
  122. info._error = "CandalaMult is not positive number";
  123. return false;
  124. }
  125. this->getInt(dataPos, dataPos, info.anglesNumV);
  126. if (info.anglesNumV < 0 || info.anglesNumV > F32_MAX)
  127. {
  128. info._error = "VAnglesNum is not valid";
  129. return false;
  130. }
  131. this->getInt(dataPos, dataPos, info.anglesNumH);
  132. if (info.anglesNumH < 0 || info.anglesNumH > F32_MAX)
  133. {
  134. info._error = "HAnglesNum is not valid";
  135. return false;
  136. }
  137. this->getInt(dataPos, dataPos, info.typeOfPhotometric);
  138. this->getInt(dataPos, dataPos, info.typeOfUnit);
  139. this->getFloat(dataPos, dataPos, info.width);
  140. this->getFloat(dataPos, dataPos, info.length);
  141. this->getFloat(dataPos, dataPos, info.height);
  142. this->getFloat(dataPos, dataPos, info.ballastFactor);
  143. this->getFloat(dataPos, dataPos, info.futureUse);
  144. this->getFloat(dataPos, dataPos, info.inputWatts);
  145. float minSoFarV = F32_MIN_EX;
  146. float minSoFarH = F32_MIN_EX;
  147. info._anglesV.reserve(info.anglesNumV);
  148. info._anglesH.reserve(info.anglesNumH);
  149. for (std::int32_t y = 0; y < info.anglesNumV; ++y)
  150. {
  151. float value;
  152. this->getFloat(dataPos, dataPos, value, true, true);
  153. if (value < minSoFarV)
  154. {
  155. info._error = "V Values is not valid";
  156. return false;
  157. }
  158. minSoFarV = value;
  159. info._anglesV.push_back(value);
  160. }
  161. for (std::int32_t x = 0; x < info.anglesNumH; ++x)
  162. {
  163. float value;
  164. this->getFloat(dataPos, dataPos, value, true, true);
  165. if (value < minSoFarH)
  166. {
  167. info._error = "H Values is not valid";
  168. return false;
  169. }
  170. minSoFarH = value;
  171. info._anglesH.push_back(value);
  172. }
  173. info._candalaValues.reserve(info.anglesNumH * info.anglesNumV);
  174. for (std::int32_t y = 0; y < info.anglesNumH; ++y)
  175. {
  176. for (std::int32_t x = 0; x < info.anglesNumV; ++x)
  177. {
  178. float value;
  179. this->getFloat(dataPos, dataPos, value, true, true);
  180. info._candalaValues.push_back(value * info.candalaMult);
  181. }
  182. }
  183. skipSpaceAndLineEnd(dataPos, dataPos);
  184. if (!dataPos.empty())
  185. {
  186. std::string line;
  187. this->getLineContent(dataPos, dataPos, line, true, false);
  188. if (line == "END")
  189. skipSpaceAndLineEnd(dataPos, dataPos);
  190. if (!dataPos.empty())
  191. {
  192. info._error = "Unexpected content after END.";
  193. return false;
  194. }
  195. }
  196. info._error.clear();
  197. return true;
  198. }
  199. bool
  200. IESLoadHelper::saveAs1D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint8_t channel) noexcept
  201. {
  202. assert(data);
  203. assert(width > 0);
  204. assert(channel == 1 || channel == 3 || channel == 4);
  205. assert(info.valid());
  206. float invW = 1.0f / width;
  207. float invMaxValue = this->computeInvMax(info._candalaValues);
  208. for (std::uint32_t x = 0; x < width; ++x)
  209. {
  210. float fraction = x * invW;
  211. float value = invMaxValue * interpolate1D(info, fraction * 180.0f);
  212. switch (channel)
  213. {
  214. case 1:
  215. *data++ = value;
  216. break;
  217. case 3:
  218. *data++ = value;
  219. *data++ = value;
  220. *data++ = value;
  221. break;
  222. case 4:
  223. *data++ = value;
  224. *data++ = value;
  225. *data++ = value;
  226. *data++ = 1.0f;
  227. break;
  228. default:
  229. return false;
  230. }
  231. }
  232. return true;
  233. }
  234. bool
  235. IESLoadHelper::saveAs2D(const IESFileInfo& info, float* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept
  236. {
  237. assert(data);
  238. assert(width > 0 && height > 0);
  239. assert(channel == 1 || channel == 3 || channel == 4);
  240. assert(info.valid());
  241. float invW = 1.0f / width;
  242. float invH = 1.0f / height;
  243. float invMaxValue = this->computeInvMax(info._candalaValues);
  244. for (std::uint32_t y = 0; y < height; ++y)
  245. {
  246. for (std::uint32_t x = 0; x < width; ++x)
  247. {
  248. float fractionV = x * invW * 180.0f;
  249. float fractionH = y * invH * 180.0f;
  250. float value = invMaxValue * interpolate2D(info, fractionV, fractionH);
  251. switch (channel)
  252. {
  253. case 1:
  254. *data++ = value;
  255. break;
  256. case 3:
  257. *data++ = value;
  258. *data++ = value;
  259. *data++ = value;
  260. break;
  261. case 4:
  262. *data++ = value;
  263. *data++ = value;
  264. *data++ = value;
  265. *data++ = 1.0;
  266. break;
  267. default:
  268. return false;
  269. }
  270. }
  271. }
  272. return true;
  273. }
  274. bool
  275. IESLoadHelper::saveAsPreview(const IESFileInfo& info, std::uint8_t* data, std::uint32_t width, std::uint32_t height, std::uint8_t channel) noexcept
  276. {
  277. assert(data);
  278. assert(width > 0 && height > 0);
  279. assert(channel == 1 || channel == 3 || channel == 4);
  280. assert(info.valid());
  281. std::vector<float> ies(256);
  282. if (!this->saveAs1D(info, ies.data(), ies.size(), 1))
  283. return false;
  284. float maxValue = this->computeInvMax(info._candalaValues);
  285. auto TonemapHable = [](float x)
  286. {
  287. const float A = 0.22f;
  288. const float B = 0.30f;
  289. const float C = 0.10f;
  290. const float D = 0.20f;
  291. const float E = 0.01f;
  292. const float F = 0.30f;
  293. return ((x*(A*x + C*B) + D*E) / (x*(A*x + B) + D*F)) - E / F;
  294. };
  295. for (int y = 0; y < height; y++)
  296. {
  297. for (int x = 0; x < width; x++)
  298. {
  299. float u = ((float)x / width) * 2.0f - 1.0f;
  300. float v = 1.0f - ((float)y / height) * 2.0f - 1.0f;
  301. u *= 2.2f;
  302. v *= 2.4f;
  303. // float3(0.0f, 0.0f, -0.5f) - ray::float3(u, v, 0.0f)
  304. float lx = +0.0f - u;
  305. float ly = +0.0f - v;
  306. float lz = -0.5f - 0.0f;
  307. // normalize
  308. float length = mSqrt(lx * lx + ly * ly + lz * lz);
  309. lx /= length;
  310. ly /= length;
  311. lz /= length;
  312. float angle = 1.0 - mAcos(lx * 0.0 + ly * -1.0 + lz * 0.0f) / 3.141592654;
  313. float intensity = ies[angle * 255] * maxValue / length;
  314. std::uint8_t value = std::min(std::max((int)mFloor(TonemapHable(intensity) / TonemapHable(maxValue) * 255.0f), 0), 255);
  315. switch (channel)
  316. {
  317. case 1:
  318. *data++ = value;
  319. break;
  320. case 3:
  321. *data++ = value;
  322. *data++ = value;
  323. *data++ = value;
  324. break;
  325. case 4:
  326. *data++ = value;
  327. *data++ = value;
  328. *data++ = value;
  329. *data++ = 1.0;
  330. break;
  331. default:
  332. return false;
  333. }
  334. }
  335. }
  336. return true;
  337. }
  338. float
  339. IESLoadHelper::computeInvMax(const std::vector<float>& candalaValues) const
  340. {
  341. assert(candalaValues.size());
  342. float candala = *std::max_element(candalaValues.begin(), candalaValues.end());
  343. return 1.0f / candala;
  344. }
  345. float
  346. IESLoadHelper::computeFilterPos(float value, const std::vector<float>& angles) const
  347. {
  348. assert(angles.size());
  349. std::size_t start = 0;
  350. std::size_t end = angles.size() - 1;
  351. if (value < angles[start]) return 0.0f;
  352. if (value > angles[end]) return (float)end;
  353. while (start < end)
  354. {
  355. std::size_t index = (start + end + 1) / 2;
  356. float angle = angles[index];
  357. if (value >= angle)
  358. {
  359. assert(start != index);
  360. start = index;
  361. }
  362. else
  363. {
  364. assert(end != index - 1);
  365. end = index - 1;
  366. }
  367. }
  368. float leftValue = angles[start];
  369. float fraction = 0.0f;
  370. if (start + 1 < (std::uint32_t)angles.size())
  371. {
  372. float rightValue = angles[start + 1];
  373. float deltaValue = rightValue - leftValue;
  374. if (deltaValue > 0.0001f)
  375. {
  376. fraction = (value - leftValue) / deltaValue;
  377. }
  378. }
  379. return start + fraction;
  380. }
  381. float
  382. IESLoadHelper::interpolate1D(const IESFileInfo& info, float angle) const
  383. {
  384. float angleV = this->computeFilterPos(angle, info._anglesV);
  385. float anglesNum = (float)info._anglesH.size();
  386. float angleTotal = 0.0f;
  387. for (float x = 0; x < anglesNum; x++)
  388. angleTotal += this->interpolateBilinear(info, x, angleV);
  389. return angleTotal / anglesNum;
  390. }
  391. float
  392. IESLoadHelper::interpolate2D(const IESFileInfo& info, float angleV, float angleH) const
  393. {
  394. float u = this->computeFilterPos(angleH, info._anglesH);
  395. float v = this->computeFilterPos(angleV, info._anglesV);
  396. return this->interpolateBilinear(info, u, v);
  397. }
  398. float
  399. IESLoadHelper::interpolatePoint(const IESFileInfo& info, std::uint32_t x, std::uint32_t y) const
  400. {
  401. assert(x >= 0);
  402. assert(y >= 0);
  403. std::size_t anglesNumH = info._anglesH.size();
  404. std::size_t anglesNumV = info._anglesV.size();
  405. x %= anglesNumH;
  406. y %= anglesNumV;
  407. assert(x < anglesNumH);
  408. assert(y < anglesNumV);
  409. return info._candalaValues[y + anglesNumV * x];
  410. }
  411. float
  412. IESLoadHelper::interpolateBilinear(const IESFileInfo& info, float x, float y) const
  413. {
  414. int ix = (int)mFloor(x);
  415. int iy = (int)mFloor(y);
  416. float fracX = x - ix;
  417. float fracY = y - iy;
  418. float p00 = this->interpolatePoint(info, ix + 0, iy + 0);
  419. float p10 = this->interpolatePoint(info, ix + 1, iy + 0);
  420. float p01 = this->interpolatePoint(info, ix + 0, iy + 1);
  421. float p11 = this->interpolatePoint(info, ix + 1, iy + 1);
  422. auto lerp = [](float t1, float t2, float t3) -> float { return t1 + (t2 - t1) * t3; };
  423. float p0 = lerp(p00, p01, fracY);
  424. float p1 = lerp(p10, p11, fracY);
  425. return lerp(p0, p1, fracX);
  426. }
  427. void
  428. IESLoadHelper::skipSpaceAndLineEnd(const std::string& data, std::string& out, bool stopOnComma)
  429. {
  430. std::size_t dataBegin = 0;
  431. std::size_t dataEnd = data.size();
  432. while (dataBegin < dataEnd)
  433. {
  434. if (data[dataBegin] != '\r' && data[dataBegin] != '\n' && data[dataBegin] > ' ')
  435. break;
  436. dataBegin++;
  437. }
  438. if (stopOnComma)
  439. {
  440. while (dataBegin < dataEnd)
  441. {
  442. if (data[dataBegin] != ',')
  443. break;
  444. dataBegin++;
  445. }
  446. }
  447. out = data.substr(dataBegin, data.size() - dataBegin);
  448. }
  449. void
  450. IESLoadHelper::getLineContent(const std::string& data, std::string& next, std::string& line, bool stopOnWhiteSpace, bool stopOnComma)
  451. {
  452. skipSpaceAndLineEnd(data, next);
  453. auto it = data.begin();
  454. auto end = data.end();
  455. for (; it < end; ++it)
  456. {
  457. if ((*it == '\r') ||
  458. (*it == '\n') ||
  459. (*it <= ' ' && stopOnWhiteSpace) ||
  460. (*it == ',' && stopOnComma))
  461. {
  462. break;
  463. }
  464. }
  465. line.assign(data, 0, it - data.begin());
  466. next.assign(data, it - data.begin(), end - it);
  467. skipSpaceAndLineEnd(next, next, stopOnComma);
  468. }
  469. void
  470. IESLoadHelper::getFloat(const std::string& data, std::string& next, float& ret, bool stopOnWhiteSpace, bool stopOnComma)
  471. {
  472. std::string line;
  473. getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma);
  474. assert(!line.empty());
  475. ret = (float)std::atof(line.c_str());
  476. }
  477. void
  478. IESLoadHelper::getInt(const std::string& data, std::string& next, std::int32_t& ret, bool stopOnWhiteSpace, bool stopOnComma)
  479. {
  480. std::string line;
  481. getLineContent(data, next, line, stopOnWhiteSpace, stopOnComma);
  482. assert(!line.empty());
  483. ret = std::atoi(line.c_str());
  484. }