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