ClangFormat: apply to source, most of intern
[blender.git] / intern / cycles / util / util_ies.cpp
1 /*
2  * Copyright 2011-2018 Blender Foundation
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 #include "util/util_foreach.h"
18 #include "util/util_ies.h"
19 #include "util/util_math.h"
20 #include "util/util_string.h"
21
22 CCL_NAMESPACE_BEGIN
23
24 // NOTE: For some reason gcc-7.2 does not instantiate this versio of allocator
25 // gere (used in IESTextParser). Works fine for gcc-6, gcc-7.3 and gcc-8.
26 //
27 // TODO(sergey): Get to the root of this issue, or confirm this i a compiler
28 // issue.
29 template class GuardedAllocator<char>;
30
31 bool IESFile::load(ustring ies)
32 {
33   clear();
34   if (!parse(ies) || !process()) {
35     clear();
36     return false;
37   }
38   return true;
39 }
40
41 void IESFile::clear()
42 {
43   intensity.clear();
44   v_angles.clear();
45   h_angles.clear();
46 }
47
48 int IESFile::packed_size()
49 {
50   if (v_angles.size() && h_angles.size() > 0) {
51     return 2 + h_angles.size() + v_angles.size() + h_angles.size() * v_angles.size();
52   }
53   return 0;
54 }
55
56 void IESFile::pack(float *data)
57 {
58   if (v_angles.size() && h_angles.size()) {
59     *(data++) = __int_as_float(h_angles.size());
60     *(data++) = __int_as_float(v_angles.size());
61
62     memcpy(data, &h_angles[0], h_angles.size() * sizeof(float));
63     data += h_angles.size();
64     memcpy(data, &v_angles[0], v_angles.size() * sizeof(float));
65     data += v_angles.size();
66
67     for (int h = 0; h < intensity.size(); h++) {
68       memcpy(data, &intensity[h][0], v_angles.size() * sizeof(float));
69       data += v_angles.size();
70     }
71   }
72 }
73
74 class IESTextParser {
75  public:
76   vector<char> text;
77   char *data;
78
79   IESTextParser(ustring str) : text(str.begin(), str.end())
80   {
81     std::replace(text.begin(), text.end(), ',', ' ');
82     data = strstr(&text[0], "\nTILT=");
83   }
84
85   bool eof()
86   {
87     return (data == NULL) || (data[0] == '\0');
88   }
89
90   double get_double()
91   {
92     if (eof()) {
93       return 0.0;
94     }
95     char *old_data = data;
96     double val = strtod(data, &data);
97     if (data == old_data) {
98       data = NULL;
99       return 0.0;
100     }
101     return val;
102   }
103
104   long get_long()
105   {
106     if (eof()) {
107       return 0;
108     }
109     char *old_data = data;
110     long val = strtol(data, &data, 10);
111     if (data == old_data) {
112       data = NULL;
113       return 0;
114     }
115     return val;
116   }
117 };
118
119 bool IESFile::parse(ustring ies)
120 {
121   if (ies.empty()) {
122     return false;
123   }
124
125   IESTextParser parser(ies);
126   if (parser.eof()) {
127     return false;
128   }
129
130   /* Handle the tilt data block. */
131   if (strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) {
132     parser.data += 13;
133     parser.get_double();              /* Lamp to Luminaire geometry */
134     int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */
135     /* Skip over angles and factors. */
136     for (int i = 0; i < 2 * num_tilt; i++) {
137       parser.get_double();
138     }
139   }
140   else {
141     /* Skip to next line. */
142     parser.data = strstr(parser.data + 1, "\n");
143   }
144
145   if (parser.eof()) {
146     return false;
147   }
148   parser.data++;
149
150   parser.get_long();                    /* Number of lamps */
151   parser.get_double();                  /* Lumens per lamp */
152   double factor = parser.get_double();  /* Candela multiplier */
153   int v_angles_num = parser.get_long(); /* Number of vertical angles */
154   int h_angles_num = parser.get_long(); /* Number of horizontal angles */
155   type = (IESType)parser.get_long();    /* Photometric type */
156
157   /* TODO(lukas): Test whether the current type B processing can also deal with type A files.
158    * In theory the only difference should be orientation which we ignore anyways, but with IES you never know...
159    */
160   if (type != TYPE_B && type != TYPE_C) {
161     return false;
162   }
163
164   parser.get_long();             /* Unit of the geometry data */
165   parser.get_double();           /* Width */
166   parser.get_double();           /* Length */
167   parser.get_double();           /* Height */
168   factor *= parser.get_double(); /* Ballast factor */
169   factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */
170   parser.get_double();           /* Input Watts */
171
172   /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity.
173    * Cycles expects radiometric quantities, though, which requires a conversion.
174    * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution
175    * of the light source since lumens take human perception into account.
176    * Since this spectral distribution is not known from the IES file, a typical one must be assumed.
177    * The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to convert to Watt/sr.
178    * A more advanced approach would be to add a Blackbody Temperature input to the node and numerically
179    * integrate the Luminous efficacy from the resulting spectral distribution.
180    * Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that Cycles expects
181    * for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela to Watt factor.
182    */
183   factor *= 0.0706650768394;
184
185   v_angles.reserve(v_angles_num);
186   for (int i = 0; i < v_angles_num; i++) {
187     v_angles.push_back((float)parser.get_double());
188   }
189
190   h_angles.reserve(h_angles_num);
191   for (int i = 0; i < h_angles_num; i++) {
192     h_angles.push_back((float)parser.get_double());
193   }
194
195   intensity.resize(h_angles_num);
196   for (int i = 0; i < h_angles_num; i++) {
197     intensity[i].reserve(v_angles_num);
198     for (int j = 0; j < v_angles_num; j++) {
199       intensity[i].push_back((float)(factor * parser.get_double()));
200     }
201   }
202
203   return !parser.eof();
204 }
205
206 bool IESFile::process_type_b()
207 {
208   vector<vector<float>> newintensity;
209   newintensity.resize(v_angles.size());
210   for (int i = 0; i < v_angles.size(); i++) {
211     newintensity[i].reserve(h_angles.size());
212     for (int j = 0; j < h_angles.size(); j++) {
213       newintensity[i].push_back(intensity[j][i]);
214     }
215   }
216   intensity.swap(newintensity);
217   h_angles.swap(v_angles);
218
219   float h_first = h_angles[0], h_last = h_angles[h_angles.size() - 1];
220   if (h_last != 90.0f) {
221     return false;
222   }
223
224   if (h_first == 0.0f) {
225     /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
226      * full 180° range. */
227     vector<float> new_h_angles;
228     vector<vector<float>> new_intensity;
229     int hnum = h_angles.size();
230     new_h_angles.reserve(2 * hnum - 1);
231     new_intensity.reserve(2 * hnum - 1);
232     for (int i = hnum - 1; i > 0; i--) {
233       new_h_angles.push_back(90.0f - h_angles[i]);
234       new_intensity.push_back(intensity[i]);
235     }
236     for (int i = 0; i < hnum; i++) {
237       new_h_angles.push_back(90.0f + h_angles[i]);
238       new_intensity.push_back(intensity[i]);
239     }
240     h_angles.swap(new_h_angles);
241     intensity.swap(new_intensity);
242   }
243   else if (h_first == -90.0f) {
244     /* We have full 180° coverage, so just shift to match the angle range convention. */
245     for (int i = 0; i < h_angles.size(); i++) {
246       h_angles[i] += 90.0f;
247     }
248   }
249   /* To get correct results with the cubic interpolation in the kernel, the horizontal range
250    * has to cover all 360°. Therefore, we copy the 0° entry to 360° to ensure full coverage
251    * and seamless interpolation. */
252   h_angles.push_back(360.0f);
253   intensity.push_back(intensity[0]);
254
255   float v_first = v_angles[0], v_last = v_angles[v_angles.size() - 1];
256   if (v_last != 90.0f) {
257     return false;
258   }
259
260   if (v_first == 0.0f) {
261     /* The range in the file corresponds to 90°-180°, we need to mirror that to get the
262      * full 180° range. */
263     vector<float> new_v_angles;
264     int hnum = h_angles.size();
265     int vnum = v_angles.size();
266     new_v_angles.reserve(2 * vnum - 1);
267     for (int i = vnum - 1; i > 0; i--) {
268       new_v_angles.push_back(90.0f - v_angles[i]);
269     }
270     for (int i = 0; i < vnum; i++) {
271       new_v_angles.push_back(90.0f + v_angles[i]);
272     }
273     for (int i = 0; i < hnum; i++) {
274       vector<float> new_intensity;
275       new_intensity.reserve(2 * vnum - 1);
276       for (int j = vnum - 2; j >= 0; j--) {
277         new_intensity.push_back(intensity[i][j]);
278       }
279       new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end());
280       intensity[i].swap(new_intensity);
281     }
282     v_angles.swap(new_v_angles);
283   }
284   else if (v_first == -90.0f) {
285     /* We have full 180° coverage, so just shift to match the angle range convention. */
286     for (int i = 0; i < v_angles.size(); i++) {
287       v_angles[i] += 90.0f;
288     }
289   }
290
291   return true;
292 }
293
294 bool IESFile::process_type_c()
295 {
296   if (h_angles[0] == 90.0f) {
297     /* Some files are stored from 90° to 270°, so we just rotate them to the regular 0°-180° range here. */
298     for (int i = 0; i < h_angles.size(); i++) {
299       h_angles[i] -= 90.0f;
300     }
301   }
302
303   if (h_angles[0] != 0.0f) {
304     return false;
305   }
306
307   if (h_angles.size() == 1) {
308     h_angles.push_back(360.0f);
309     intensity.push_back(intensity[0]);
310   }
311
312   if (h_angles[h_angles.size() - 1] == 90.0f) {
313     /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four).
314      * Since the two->four mirroring step might also be required if we get an input of two quadrants,
315      * we only do the first mirror here and later do the second mirror in either case. */
316     int hnum = h_angles.size();
317     for (int i = hnum - 2; i >= 0; i--) {
318       h_angles.push_back(180.0f - h_angles[i]);
319       intensity.push_back(intensity[i]);
320     }
321   }
322
323   if (h_angles[h_angles.size() - 1] == 180.0f) {
324     /* Mirror half to the full range. */
325     int hnum = h_angles.size();
326     for (int i = hnum - 2; i >= 0; i--) {
327       h_angles.push_back(360.0f - h_angles[i]);
328       intensity.push_back(intensity[i]);
329     }
330   }
331
332   /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to the 0° entry.
333    * If the file has a discernible order in its spacing, just fix this. */
334   if (h_angles[h_angles.size() - 1] != 360.0f) {
335     int hnum = h_angles.size();
336     float last_step = h_angles[hnum - 1] - h_angles[hnum - 2];
337     float first_step = h_angles[1] - h_angles[0];
338     float difference = 360.0f - h_angles[hnum - 1];
339     if (last_step == difference || first_step == difference) {
340       h_angles.push_back(360.0f);
341       intensity.push_back(intensity[0]);
342     }
343     else {
344       return false;
345     }
346   }
347
348   float v_first = v_angles[0], v_last = v_angles[v_angles.size() - 1];
349   if (v_first == 90.0f) {
350     if (v_last == 180.0f) {
351       /* Flip to ensure that vertical angles always start at 0°. */
352       for (int i = 0; i < v_angles.size(); i++) {
353         v_angles[i] = 180.0f - v_angles[i];
354       }
355     }
356     else {
357       return false;
358     }
359   }
360   else if (v_first != 0.0f) {
361     return false;
362   }
363
364   return true;
365 }
366
367 bool IESFile::process()
368 {
369   if (h_angles.size() == 0 || v_angles.size() == 0) {
370     return false;
371   }
372
373   if (type == TYPE_B) {
374     if (!process_type_b()) {
375       return false;
376     }
377   }
378   else {
379     assert(type == TYPE_C);
380     if (!process_type_c()) {
381       return false;
382     }
383   }
384
385   assert(v_angles[0] == 0.0f);
386   assert(h_angles[0] == 0.0f);
387   assert(h_angles[h_angles.size() - 1] == 360.0f);
388
389   /* Convert from deg to rad. */
390   for (int i = 0; i < v_angles.size(); i++) {
391     v_angles[i] *= M_PI_F / 180.f;
392   }
393   for (int i = 0; i < h_angles.size(); i++) {
394     h_angles[i] *= M_PI_F / 180.f;
395   }
396
397   return true;
398 }
399
400 IESFile::~IESFile()
401 {
402   clear();
403 }
404
405 CCL_NAMESPACE_END