Blender V5.0
ies.cpp
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2011-2022 Blender Foundation
2 *
3 * SPDX-License-Identifier: Apache-2.0 */
4
5#include <algorithm>
6
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.
19
20bool 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.empty() && !h_angles.empty()) {
40 return 2 + h_angles.size() + v_angles.size() + h_angles.size() * v_angles.size();
41 }
42 return 0;
43}
44
45void IESFile::pack(float *data)
46{
47 if (!v_angles.empty() && !h_angles.empty()) {
48 *(data++) = __int_as_float(h_angles.size());
49 *(data++) = __int_as_float(v_angles.size());
50
51 memcpy(data, h_angles.data(), h_angles.size() * sizeof(float));
52 data += h_angles.size();
53 memcpy(data, v_angles.data(), 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].data(), v_angles.size() * sizeof(float));
58 data += v_angles.size();
59 }
60 }
61}
62
64 public:
65 string text;
66 char *data;
67 bool error = false;
68
69 IESTextParser(const string &str) : text(str)
70 {
71 std::replace(text.begin(), text.end(), ',', ' ');
72 data = strstr(text.data(), "\nTILT=");
73 }
74
75 bool eof()
76 {
77 return (data == nullptr) || (data[0] == '\0');
78 }
79
80 bool has_error()
81 {
82 return error;
83 }
84
85 double get_double()
86 {
87 if (eof()) {
88 error = true;
89 return 0.0;
90 }
91 char *old_data = data;
92 const double val = strtod(data, &data);
93 if (data == old_data) {
94 data = nullptr;
95 error = true;
96 return 0.0;
97 }
98 return val;
99 }
100
101 long get_long()
102 {
103 if (eof()) {
104 error = true;
105 return 0;
106 }
107 char *old_data = data;
108 const long val = strtol(data, &data, 10);
109 if (data == old_data) {
110 data = nullptr;
111 error = true;
112 return 0;
113 }
114 return val;
115 }
116};
117
118bool IESFile::parse(const string &ies)
119{
120 if (ies.empty()) {
121 return false;
122 }
123
124 IESTextParser parser(ies);
125 if (parser.eof()) {
126 return false;
127 }
128
129 /* Handle the tilt data block. */
130 if (strncmp(parser.data, "\nTILT=INCLUDE", 13) == 0) {
131 parser.data += 13;
132 parser.get_double(); /* Lamp to Luminaire geometry */
133 const int num_tilt = parser.get_long(); /* Amount of tilt angles and factors */
134 /* Skip over angles and factors. */
135 for (int i = 0; i < 2 * num_tilt; i++) {
136 parser.get_double();
137 }
138 }
139 else {
140 /* Skip to next line. */
141 parser.data = strstr(parser.data + 1, "\n");
142 }
143
144 if (parser.eof()) {
145 return false;
146 }
147 parser.data++;
148
149 parser.get_long(); /* Number of lamps */
150 parser.get_double(); /* Lumens per lamp */
151 double factor = parser.get_double(); /* Candela multiplier */
152 const int v_angles_num = parser.get_long(); /* Number of vertical angles */
153 const int h_angles_num = parser.get_long(); /* Number of horizontal angles */
154 type = (IESType)parser.get_long(); /* Photometric type */
155
156 if (type != TYPE_A && type != TYPE_B && type != TYPE_C) {
157 return false;
158 }
159
160 parser.get_long(); /* Unit of the geometry data */
161 parser.get_double(); /* Width */
162 parser.get_double(); /* Length */
163 parser.get_double(); /* Height */
164 factor *= parser.get_double(); /* Ballast factor */
165 factor *= parser.get_double(); /* Ballast-Lamp Photometric factor */
166 parser.get_double(); /* Input Watts */
167
168 /* Intensity values in IES files are specified in candela (lumen/sr), a photometric quantity.
169 * Cycles expects radiometric quantities, though, which requires a conversion.
170 * However, the Luminous efficacy (ratio of lumens per Watt) depends on the spectral distribution
171 * of the light source since lumens take human perception into account.
172 * Since this spectral distribution is not known from the IES file, a typical one must be
173 * assumed. The D65 standard illuminant has a Luminous efficacy of 177.83, which is used here to
174 * convert to Watt/sr. A more advanced approach would be to add a Blackbody Temperature input to
175 * the node and numerically integrate the Luminous efficacy from the resulting spectral
176 * distribution. Also, the Watt/sr value must be multiplied by 4*pi to get the Watt value that
177 * Cycles expects for lamp strength. Therefore, the conversion here uses 4*pi/177.83 as a Candela
178 * to Watt factor.
179 */
180 factor *= 0.0706650768394;
181
182 v_angles.reserve(v_angles_num);
183 for (int i = 0; i < v_angles_num; i++) {
184 v_angles.push_back((float)parser.get_double());
185 }
186
187 h_angles.reserve(h_angles_num);
188 for (int i = 0; i < h_angles_num; i++) {
189 h_angles.push_back((float)parser.get_double());
190 }
191
192 intensity.resize(h_angles_num);
193 for (int i = 0; i < h_angles_num; i++) {
194 intensity[i].reserve(v_angles_num);
195 for (int j = 0; j < v_angles_num; j++) {
196 intensity[i].push_back((float)(factor * parser.get_double()));
197 }
198 }
199
200 return !parser.has_error();
201}
202
203static bool angle_close(const float a, const float b)
204{
205 return fabsf(a - b) < 1e-4f;
206}
207
208/* Processing functions to turn file contents into the format that Cycles expects.
209 * Handles type conversion (the output format is based on Type C), symmetry/mirroring,
210 * value shifting etc.
211 * Note that this code is much more forgiving than the spec. For example, in type A and B,
212 * the range of vertical angles officially must be either exactly 0°-90° or -90°-90°.
213 * However, in practice, IES files are all over the place. Therefore, the handling is as
214 * flexible as possible, and tries to turn any input into something useful. */
215
217{
218 /* According to the standard, Type B defines a different coordinate system where the polar axis
219 * is horizontal, not vertical.
220 * To avoid over complicating the conversion logic, we just transpose the angles and use the
221 * regular Type A/C coordinate system. Users can just rotate the light to get the "proper"
222 * orientation. */
223 vector<vector<float>> newintensity;
224 newintensity.resize(v_angles.size());
225 for (int i = 0; i < v_angles.size(); i++) {
226 newintensity[i].reserve(h_angles.size());
227 for (int j = 0; j < h_angles.size(); j++) {
228 newintensity[i].push_back(intensity[j][i]);
229 }
230 }
231 intensity.swap(newintensity);
232 h_angles.swap(v_angles);
233
234 if (angle_close(h_angles[0], 0.0f)) {
235 /* File angles cover 0°-90°. Mirror that to -90°-90°, and shift to 0°-180° to match Cycles. */
236 vector<float> new_h_angles;
237 vector<vector<float>> new_intensity;
238 const int hnum = h_angles.size();
239 new_h_angles.reserve(2 * hnum - 1);
240 new_intensity.reserve(2 * hnum - 1);
241 for (int i = hnum - 1; i > 0; i--) {
242 new_h_angles.push_back(90.0f - h_angles[i]);
243 new_intensity.push_back(intensity[i]);
244 }
245 for (int i = 0; i < hnum; i++) {
246 new_h_angles.push_back(90.0f + h_angles[i]);
247 new_intensity.push_back(intensity[i]);
248 }
249 h_angles.swap(new_h_angles);
250 intensity.swap(new_intensity);
251 }
252 else {
253 /* File angles cover -90°-90°. Shift to 0°-180° to match Cycles. */
254 for (int i = 0; i < h_angles.size(); i++) {
255 h_angles[i] += 90.0f;
256 }
257 }
258
259 if (angle_close(v_angles[0], 0.0f)) {
260 /* File angles cover 0°-90°. Mirror that to -90°-90°, and shift to 0°-180° to match Cycles. */
261 vector<float> new_v_angles;
262 const int hnum = h_angles.size();
263 const int vnum = v_angles.size();
264 new_v_angles.reserve(2 * vnum - 1);
265 for (int i = vnum - 1; i > 0; i--) {
266 new_v_angles.push_back(90.0f - v_angles[i]);
267 }
268 for (int i = 0; i < vnum; i++) {
269 new_v_angles.push_back(90.0f + v_angles[i]);
270 }
271 for (int i = 0; i < hnum; i++) {
272 vector<float> new_intensity;
273 new_intensity.reserve(2 * vnum - 1);
274 for (int j = vnum - 1; j > 0; j--) {
275 new_intensity.push_back(intensity[i][j]);
276 }
277 new_intensity.insert(new_intensity.end(), intensity[i].begin(), intensity[i].end());
278 intensity[i].swap(new_intensity);
279 }
280 v_angles.swap(new_v_angles);
281 }
282 else {
283 /* File angles cover -90°-90°. Shift to 0°-180° to match Cycles. */
284 for (int i = 0; i < v_angles.size(); i++) {
285 v_angles[i] += 90.0f;
286 }
287 }
288}
289
291{
292 /* Convert vertical angles - just a simple offset. */
293 for (int i = 0; i < v_angles.size(); i++) {
294 v_angles[i] += 90.0f;
295 }
296
297 vector<float> new_h_angles;
298 new_h_angles.reserve(h_angles.size());
299 vector<vector<float>> new_intensity;
300 new_intensity.reserve(h_angles.size());
301
302 /* Type A goes from -90° to 90°, which is mapped to 270° to 90° respectively in Type C. */
303 for (int i = h_angles.size() - 1; i >= 0; i--) {
304 new_h_angles.push_back(180.0f - h_angles[i]);
305 new_intensity.push_back(intensity[i]);
306 }
307
308 /* If the file angles start at 0°, we need to mirror around that.
309 * Since the negative input range (which we generate here) maps to 180° to 270°,
310 * it comes after the original entries in the output. */
311 if (angle_close(h_angles[0], 0.0f)) {
312 new_h_angles.reserve(2 * h_angles.size() - 1);
313 new_intensity.reserve(2 * h_angles.size() - 1);
314 for (int i = 1; i < h_angles.size(); i++) {
315 new_h_angles.push_back(180.0f + h_angles[i]);
316 new_intensity.push_back(intensity[i]);
317 }
318 }
319
320 h_angles.swap(new_h_angles);
321 intensity.swap(new_intensity);
322}
323
325{
326 if (angle_close(h_angles[0], 90.0f)) {
327 /* Some files are stored from 90° to 270°, so rotate them to the regular 0°-180° range. */
328 for (int i = 0; i < h_angles.size(); i++) {
329 h_angles[i] -= 90.0f;
330 }
331 }
332
333 if (h_angles.size() == 1) {
334 h_angles[0] = 0.0f;
335 h_angles.push_back(360.0f);
336 intensity.push_back(intensity[0]);
337 }
338
339 if (angle_close(h_angles[h_angles.size() - 1], 90.0f)) {
340 /* Only one quadrant is defined, so we need to mirror twice (from one to two, then to four).
341 * Since the two->four mirroring step might also be required if we get an input of two
342 * quadrants, we only do the first mirror here and later do the second mirror in either case.
343 */
344 const int hnum = h_angles.size();
345 for (int i = hnum - 2; i >= 0; i--) {
346 h_angles.push_back(180.0f - h_angles[i]);
347 intensity.push_back(intensity[i]);
348 }
349 }
350
351 if (angle_close(h_angles[h_angles.size() - 1], 180.0f)) {
352 /* Mirror half to the full range. */
353 const int hnum = h_angles.size();
354 for (int i = hnum - 2; i >= 0; i--) {
355 h_angles.push_back(360.0f - h_angles[i]);
356 intensity.push_back(intensity[i]);
357 }
358 }
359
360 /* Some files skip the 360° entry (contrary to standard) because it's supposed to be identical to
361 * the 0° entry. If the file has a discernible order in its spacing, just fix this. */
362 if (angle_close(h_angles[0], 0.0f) && !angle_close(h_angles[h_angles.size() - 1], 360.0f)) {
363 const int hnum = h_angles.size();
364 const float last_step = h_angles[hnum - 1] - h_angles[hnum - 2];
365 const float first_step = h_angles[1] - h_angles[0];
366 const float gap_step = 360.0f - h_angles[hnum - 1];
367 if (angle_close(last_step, gap_step) || angle_close(first_step, gap_step)) {
368 h_angles.push_back(360.0f);
369 intensity.push_back(intensity[0]);
370 }
371 }
372}
373
375{
376 if (h_angles.empty() || v_angles.empty()) {
377 return false;
378 }
379
380 if (type == TYPE_A) {
382 }
383 else if (type == TYPE_B) {
385 }
386 else if (type == TYPE_C) {
388 }
389 else {
390 return false;
391 }
392
393 /* Convert from deg to rad. */
394 for (int i = 0; i < v_angles.size(); i++) {
395 v_angles[i] *= M_PI_F / 180.f;
396 }
397 for (int i = 0; i < h_angles.size(); i++) {
398 h_angles[i] *= M_PI_F / 180.f;
399 }
400
401 return true;
402}
403
405{
406 clear();
407}
408
BMesh const char void * data
void pack(float *data)
Definition ies.cpp:45
bool process()
Definition ies.cpp:374
enum IESFile::IESType type
vector< vector< float > > intensity
Definition util/ies.h:37
void process_type_c()
Definition ies.cpp:324
void process_type_b()
Definition ies.cpp:216
vector< float > v_angles
Definition util/ies.h:34
vector< float > h_angles
Definition util/ies.h:34
void process_type_a()
Definition ies.cpp:290
bool load(const string &ies)
Definition ies.cpp:20
~IESFile()
Definition ies.cpp:404
bool parse(const string &ies)
Definition ies.cpp:118
int packed_size()
Definition ies.cpp:37
@ TYPE_A
Definition util/ies.h:40
@ TYPE_C
Definition util/ies.h:40
@ TYPE_B
Definition util/ies.h:40
void clear()
Definition ies.cpp:30
char * data
Definition ies.cpp:66
bool has_error()
Definition ies.cpp:80
double get_double()
Definition ies.cpp:85
long get_long()
Definition ies.cpp:101
bool error
Definition ies.cpp:67
IESTextParser(const string &str)
Definition ies.cpp:69
bool eof()
Definition ies.cpp:75
string text
Definition ies.cpp:65
#define CCL_NAMESPACE_END
#define __int_as_float(x)
#define str(s)
static bool angle_close(const float a, const float b)
Definition ies.cpp:203
#define fabsf
#define M_PI_F
i
Definition text_draw.cc:230