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