ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
Revision: 2.36
Committed: Wed Nov 16 01:59:43 2022 UTC (17 months, 1 week ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: rad5R4, HEAD
Changes since 2.35: +43 -28 lines
Log Message:
fix(ies2rad): Randolph Fritz fixed issue with some luminaire geometries and improved code readability

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: ies2rad.c,v 2.35 2021/11/29 16:07:36 greg Exp $";
3 #endif
4 /*
5 * ies2rad -- Convert IES luminaire data to Radiance description
6 *
7 * ies2rad converts an IES LM-63 luminare description to a Radiance
8 * luminaire description. In addition, ies2rad manages a local
9 * database of Radiance luminaire files.
10 *
11 * Ies2rad generates two or three files for each luminaire. For a
12 * luminaire named LUM, ies2rad will generate LUM.rad, a Radiance
13 * scene description file which describes the light source, LUM.dat,
14 * which contains the photometric data from the IES LM-63 file, and
15 * (if tilt data is provided) LUM%.dat, which contains the tilt data
16 * from the IES file.
17 *
18 * Ies2rad is supported by the Radiance function files source.cal and
19 * tilt.cal, which transform the coordinates in the IES data into
20 * Radiance (θ,φ) luminaire coordinates and then apply photometric and
21 * tilt data to generate Radiance light. θ is altitude from the
22 * negative z-axis and φ is azimuth from the positive x-axis,
23 * increasing towards the positive y-axis. This system matches none of
24 * the usual goniophotometric conventions, but it is closest to IES
25 * type C; V in type C photometry is θ in Radiance and L is -φ.
26 *
27 * The ies2rad scene description for a luminaire LUM, with tilt data,
28 * uses the following Radiance scene description primitives:
29 *
30 * void brightdata LUM_tilt
31 * …
32 * LUM_tilt brightdata LUM_dist
33 * …
34 * LUM_dist light LUM_light
35 * …
36 * LUM_light surface1 name1
37 * …
38 * LUM_light surface2 name2
39 * …
40 * LUM_light surface_n name_n
41 *
42 * Without tilt data, the primitives are:
43 *
44 * void brightdata LUM_dist
45 * …
46 * LUM_dist light LUM_light
47 * …
48 * LUM_light surface1 name1
49 * …
50 * LUM_light surface2 name2
51 * …
52 * LUM_light surface_n name_n
53 *
54 * As many surfaces are given as required to describe the light
55 * source. Illum may be used rather than light so that a visible form
56 * (impostor) may be given to the luminaire, rather than a simple
57 * glowing shape. If an impostor is provided, it must be wholly
58 * contained within the illum and if it provides impostor light
59 * sources, those must be given with glow, so that they do not
60 * themselves illuminate the scene, providing incorrect results.
61 *
62 * Overview of the LM-63 file format
63 * =================================
64 * Here we offer a summary of the IESNA LM-63 photometry file format
65 * for the perplexed reader. Dear reader, do remember that this is
66 * our interpretation of the five different versions of the standard.
67 * When our interpretation of the standard conflicts with the official
68 * standard, the official document is to be respected. In conflicts
69 * with practice, do take into account the robustness principle and be
70 * permissive, accepting reasonable deviations from the standard.
71 *
72 * LM-63 files are organized as a version tag, followed by a series of
73 * luminaire data sets. The luminaire data sets, in turn, are
74 * organized into a label, a tilt data section, and a photometric data
75 * section. Finally, the data sections are organized into records,
76 * which are made up of lines of numeric values delimited by spaces or
77 * commas. Lines are delimited by CR LF sequences. Records are made
78 * up of one or more lines, and every record must be made up of some
79 * number of complete lines, but there is no delimiter which makes the
80 * end of a record. The first records of the tilt and photometric
81 * data sections have fixed numbers of numeric values; the initial
82 * records contain counts that describe the remaining records.
83 *
84 * Ies2rad allows only one luminaire data set per file.
85 *
86 * The tilt section is made up of exactly four records; the second gives
87 * the number of values in the third and fourth records.
88 *
89 * The photometric section begins with two records, which give both the
90 * number of records following and the number of values in each of the
91 * following records.
92 *
93 * The original 1986 version of LM-63 does not have a version tag.
94 *
95 * The 1986, 1991, and 1995 versions allow 80 characters for the label
96 * lines and the "TILT=" line which begins the tilt data section, and
97 * 132 characters thereafter. (Those counts do not include the CR LF
98 * line terminator.) The 2002 version dispenses with those limits,
99 * allowing 256 characters per line, including the CR LF line
100 * terminator. The 2019 version does not specify a line length at
101 * all. Ies2rad allows lines of up to 256 characters and will accept
102 * CR LF or LF alone as line terminators.
103 *
104 * In the 1986 version, the label is a series of free-form lines of up
105 * to 80 characters. In later versions, the label is a series of
106 * lines of beginning with keywords in brackets with interpretation
107 * rules which differ between versions.
108 *
109 * The tilt data section begins with a line beginning with "TILT=",
110 * optionally followed by either a file name or four records of
111 * numerical data. The 2019 version no longer allows a file name to
112 * be given.
113 *
114 * The main photometric data section contains two header records
115 * followed by a record of vertical angles, a record of horizontal
116 * angles, and one record of candela values for each horizontal angle.
117 * Each record of candela values contains exactly one value for each
118 * vertical angle. Data values in records are separated by spaces or
119 * commas. In keeping with the robustness principle, commas
120 * surrounded by spaces will also be accepted as separators.
121 *
122 * The first header record of the photometric data section contains
123 * exactly 10 values. The second contains exactly 3 values. Most of
124 * the data values are floating point numbers; the exceptions are
125 * various counts and enumerators, which are integers: the number of
126 * lamps, the numbers of vertical and horizontal angles, the
127 * photometric type identifier, and the units type identifier. In the
128 * 2019 version, a field with information about how the file was
129 * generated has replaced a field unused since 1995; it is a textual
130 * representation of a bit string, but may - we hope! - safely be
131 * interpreted as a floating point number and decoded later.
132 *
133 * Style Note
134 * ==========
135 * The ies2rad code uses the "bsd" style. For emacs, this is set up
136 * automatically in the "Local Variables" section at the end of the
137 * file. For vim, use ":set tabstop=8 shiftwidth=8".
138 *
139 * History
140 * =======
141 *
142 * 07Apr90 Greg Ward
143 *
144 * Fixed correction factor for flat sources 29Oct2001 GW
145 * Extensive comments added by Randolph Fritz May2018
146 */
147
148 #include <math.h>
149 #include <ctype.h>
150
151 #include "rtio.h"
152 #include "color.h"
153 #include "paths.h"
154
155 #define PI 3.14159265358979323846
156
157 #define FAIL (-1)
158 #define SUCCESS 0
159
160 /* floating point comparisons -- floating point numbers within FTINY
161 * of each other are considered equal */
162 #define FTINY 1e-6
163 #define FEQ(a,b) ((a)<=(b)+FTINY&&(a)>=(b)-FTINY)
164
165 #define IESFIRSTVER 1986
166 #define IESLASTVER 2019
167
168 /* tilt specs
169 *
170 * This next series of definitions address metal-halide lamps, which
171 * change their brightness depending on the angle at which they are
172 * mounted. The section begins with "TILT=". The constants in this
173 * section are all defined in LM-63.
174 *
175 */
176
177 #define TLTSTR "TILT="
178 #define TLTSTRLEN 5
179 #define TLTNONE "NONE"
180 #define TLTINCL "INCLUDE"
181 #define TLT_VERT 1
182 #define TLT_H0 2
183 #define TLT_H90 3
184
185 /* Constants from LM-63 files */
186
187 /* photometric types
188 *
189 * This enumeration reflects three different methods of measuring the
190 * distribution of light from a luminaire -- "goniophotometry" -- and
191 * the different coordinate systems related to these
192 * goniophotometers. All are described in IES standard LM-75-01.
193 * Earlier and shorter descriptions may be found the LM-63 standards
194 * from 1986, 1991, and 1995.
195 *
196 * ies2rad does not support type A photometry.
197 *
198 * In the 1986 file format, LM-63-86, 1 is used for type C and type A
199 * photometric data.
200 *
201 */
202 #define PM_C 1
203 #define PM_B 2
204 #define PM_A 3
205
206 /* unit types */
207 #define U_FEET 1
208 #define U_METERS 2
209
210 /* string lengths */
211 /* Maximum length of a keyword, including brackets and NUL */
212 #define MAXKW 21
213 /* Maximum input line is 256 characters including CR LF and NUL at end. */
214 #define MAXLINE 257
215 #define MAXUNITNAME 64
216 #define RMAXWORD 76
217
218 /* Shapes defined in the IES LM-63 standards
219 *
220 * PH stands for photometric horizontal
221 * PPH stands for perpendicular to photometric horizontal
222 * Cylinders are vertical and circular unless otherwise stated
223 *
224 * The numbers assigned here are not part of any LM-63 standard; they
225 * are for programming convenience.
226 */
227 /* Error and not-yet-assigned constants */
228 #define IESERROR -2
229 #define IESNONE -1
230 /* Shapes */
231 #define IESPT 0
232 #define IESRECT 1
233 #define IESBOX 2
234 #define IESDISK 3
235 #define IESELLIPSE 4
236 #define IESVCYL 5
237 #define IESVECYL 6
238 #define IESSPHERE 7
239 #define IESELLIPSOID 8
240 #define IESHCYL_PH 9
241 #define IESHECYL_PH 10
242 #define IESHCYL_PPH 11
243 #define IESHECYL_PPH 12
244 #define IESVDISK_PH 13
245 #define IESVEL_PH 14
246
247 /* End of LM-63 related #defines */
248
249 /* file extensions */
250 #define T_RAD ".rad"
251 #define T_DST ".dat"
252 #define T_TLT "%.dat"
253 #define T_OCT ".oct"
254
255 /* Radiance shape types
256 * These #defines enumerate the shapes of the Radiance objects which
257 * emit the light.
258 */
259 #define RECT 1
260 #define DISK 2
261 #define SPHERE 3
262
263 /* 1mm. The diameter of a point source luminaire model. Also the minimum
264 * size (in meters) that the luminous opening of a luminaire must have
265 * to be treated as other than a point source. */
266 #define MINDIM .001
267
268 /* feet to meters */
269 /* length_in_meters = length_in_feet * F_M */
270 #define F_M .3048
271
272 /* abspath - return true if a path begins with a directory separator
273 * or a '.' (current directory) */
274 #define abspath(p) (ISDIRSEP((p)[0]) || (p)[0] == '.')
275
276 /* LM-63 related constants */
277 typedef struct {
278 char *tag;
279 int yr; } IESversions;
280
281 IESversions IESFILEVERSIONS[] = {
282 { "IESNA91", 1991 },
283 { "IESNA:LM-63-1995", 1995 },
284 { "IESNA:LM-63-2002", 2002 },
285 { "IES:LM-63-2019", 2019 },
286 { NULL, 1986 }
287 };
288
289 char *IESHAPENAMES[] = {
290 "point", "rectangle", "box", "disk", "ellipse", "vertical cylinder",
291 "vertical elliptical cylinder", "sphere", "ellipsoid",
292 "horizontal cylinder along photometric horizontal",
293 "horizontal elliptical cylinder along photometric horizontal",
294 "horizontal cylinder perpendicular to photometric horizontal",
295 "horizontal elliptical cylinder perpendicular to photometric horizontal",
296 "vertical disk facing photometric horizontal",
297 "vertical ellipse facing photometric horizontal" };
298
299 /* end of LM-63 related constants */
300
301 /* Radiance shape names */
302 char *RADSHAPENAMES[] = { "rectangle or box", "disk or cylinder", "sphere" };
303
304 /* Global variables.
305 *
306 * Mostly, these are a way of communicating command line parameters to
307 * the rest of the program.
308 */
309 static char default_name[] = "default";
310
311 char *libdir = NULL; /* library directory location */
312 char *prefdir = NULL; /* subdirectory */
313 char *lampdat = "lamp.tab"; /* lamp data file */
314
315 double meters2out = 1.0; /* conversion from meters to output */
316 char *lamptype = NULL; /* selected lamp type */
317 char *deflamp = NULL; /* default lamp type */
318 float defcolor[3] = {1.,1.,1.}; /* default lamp color */
319 float *lampcolor = defcolor; /* pointer to current lamp color */
320 double multiplier = 1.0; /* multiplier for all light sources */
321 char units[MAXUNITNAME] = "meters"; /* output units */
322 int out2stdout = 0; /* put out to stdout r.t. file */
323 int instantiate = 0; /* instantiate geometry */
324 double illumrad = 0.0; /* radius for illum sphere */
325
326 /* This struct describes the Radiance source object */
327 typedef struct {
328 int isillum; /* do as illum */
329 int type; /* RECT, DISK, SPHERE */
330 double mult; /* candela multiplier */
331 double w, l, h; /* width, length, height */
332 double area; /* max. projected area */
333 int filerev; /* IES file version */
334 int havelamppos; /* Lamp position was given */
335 float lamppos[2]; /* Lamp position */
336 int iesshape; /* Shape number */
337 char *warn; /* Warning message */
338 } SRCINFO; /* a source shape (units=meters) */
339
340 /* A count and pointer to the list of input file names */
341 int gargc; /* global argc */
342 char **gargv; /* global argv */
343
344 /* macros to scan numbers out of IES files
345 *
346 * fp is a file pointer. scnint() places the number in the integer
347 * indicated by ip; scnflt() places the number in the double indicated
348 * by rp. The macros return 1 if successful, 0 if not.
349 *
350 */
351 #define scnint(fp,ip) cvtint(ip,getword(fp))
352 #define scnflt(fp,rp) cvtflt(rp,getword(fp))
353
354 /* The original (1986) version of LM-63 allows decimals points in
355 * integers, so that, for instance, the number of lamps may be written
356 * 3.0 (the number, obviously, must still be an integer.) This
357 * confusing define accommodates that. */
358 #define isint isflt
359
360 /* IES file conversion functions */
361 static int ies2rad(char *inpname, char *outname);
362 static void initlamps(void);
363 static int dosource(SRCINFO *sinf, FILE *in, FILE *out, char *mod, char *name);
364 static int dotilt(FILE *in, FILE *out, char *dir, char *tltspec,
365 char *dfltname, char *tltid);
366 static int cvgeometry(char *inpname, SRCINFO *sinf, char *outname, FILE *outfp);
367 static int cvtint(int *ip, char *wrd);
368 static int cvdata(FILE *in, FILE *out, int ndim, int npts[], double mult,
369 double lim[][2]);
370 static int cvtflt(double *rp, char *wrd);
371 static int makeiesshape(SRCINFO *shp, double length, double width, double height);
372 static int makeillumsphere(SRCINFO *shp);
373 static int makeshape(SRCINFO *shp, double width, double length, double height);
374 static void makecylshape(SRCINFO *shp, double diam, double height);
375 static void makeelshape(SRCINFO *shp, double width, double length, double height);
376 static void makeecylshape(SRCINFO *shp, double width, double length, double height);
377 static void makeelshape(SRCINFO *shp, double width, double length, double height);
378 static void makeboxshape(SRCINFO *shp, double length, double width, double height);
379 static int makepointshape(SRCINFO *shp);
380 static int putsource(SRCINFO *shp, FILE *fp, char *mod, char *name,
381 int dolower, int doupper, int dosides);
382 static void putrectsrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up);
383 static void putsides(SRCINFO *shp, FILE *fp, char *mod, char *name);
384 static void putdisksrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up);
385 static void putspheresrc(SRCINFO *shp, FILE *fp, char *mod, char *name);
386 static void putrect(SRCINFO *shp, FILE *fp, char *mod, char *name, char *suffix,
387 int a, int b, int c, int d);
388 static void putpoint(SRCINFO *shp, FILE *fp, int p);
389 static void putcyl(SRCINFO *shp, FILE *fp, char *mod, char *name);
390 static void shapearea(SRCINFO *shp);
391
392 /* string and filename functions */
393 static int isprefix(char *p, char *s);
394 static char * matchprefix(char *p, char *s);
395 static char * tailtrunc(char *name);
396 static char * filename(char *path);
397 static char * libname(char *path, char *fname, char *suffix);
398 static char * getword(FILE *fp);
399 static char * fullnam(char *path, char *fname, char *suffix);
400
401 /* output function */
402 static void fpcomment(FILE *fp, char *prefix, char *s);
403
404 /* main - process arguments and run the conversion
405 *
406 * Refer to the man page for details of the arguments.
407 *
408 * Following Unix environment conventions, main() exits with 0 on
409 * success and 1 on failure.
410 *
411 * ies2rad outputs either two or three files for a given IES
412 * file. There is always a .rad file containing Radiance scene
413 * description primitives and a .dat file for the photometric data. If
414 * tilt data is given, that is placed in a separate .dat file. So
415 * ies2rad must have a filename to operate. Sometimes this name is the
416 * input file name, shorn of its extension; sometimes it is given in
417 * the -o option. But an output file name is required for ies2rad to
418 * do its work.
419 *
420 * Older versions of the LM-63 standard allowed inclusion of multiple
421 * luminaires in one IES file; this is not supported by ies2rad.
422 *
423 * This code sometimes does not check to make sure it has not run out
424 * of arguments; this can lead to segmentation faults and perhaps
425 * other errors.
426 *
427 */
428 int
429 main(
430 int argc,
431 char *argv[]
432 )
433 {
434 char *outfile = NULL;
435 int status;
436 char outname[RMAXWORD];
437 double d1;
438 int i;
439
440 /* Scan the options */
441 for (i = 1; i < argc && argv[i][0] == '-'; i++)
442 switch (argv[i][1]) {
443 case 'd': /* dimensions */
444 if (argv[i][2] == '\0')
445 goto badopt;
446 if (argv[i][3] == '\0')
447 d1 = 1.0;
448 else if (argv[i][3] == '/') {
449 d1 = atof(argv[i]+4);
450 if (d1 <= FTINY)
451 goto badopt;
452 } else
453 goto badopt;
454 switch (argv[i][2]) {
455 case 'c': /* centimeters */
456 if (FEQ(d1,10.))
457 strcpy(units,"millimeters");
458 else {
459 strcpy(units,"centimeters");
460 strcat(units,argv[i]+3);
461 }
462 meters2out = 100.*d1;
463 break;
464 case 'm': /* meters */
465 if (FEQ(d1,1000.))
466 strcpy(units,"millimeters");
467 else if (FEQ(d1,100.))
468 strcpy(units,"centimeters");
469 else {
470 strcpy(units,"meters");
471 strcat(units,argv[i]+3);
472 }
473 meters2out = d1;
474 break;
475 case 'i': /* inches */
476 strcpy(units,"inches");
477 strcat(units,argv[i]+3);
478 meters2out = d1*(12./F_M);
479 break;
480 case 'f': /* feet */
481 if (FEQ(d1,12.))
482 strcpy(units,"inches");
483 else {
484 strcpy(units,"feet");
485 strcat(units,argv[i]+3);
486 }
487 meters2out = d1/F_M;
488 break;
489 default:
490 goto badopt;
491 }
492 break;
493 case 'l': /* library directory */
494 libdir = argv[++i];
495 break;
496 case 'p': /* prefix subdirectory */
497 prefdir = argv[++i];
498 break;
499 case 'f': /* lamp data file */
500 lampdat = argv[++i];
501 break;
502 case 'o': /* output file root name */
503 outfile = argv[++i];
504 break;
505 case 's': /* output to stdout */
506 out2stdout = !out2stdout;
507 break;
508 case 'i': /* illum */
509 illumrad = atof(argv[++i]);
510 break;
511 case 'g': /* instantiate geometry? */
512 instantiate = !instantiate;
513 break;
514 case 't': /* override lamp type */
515 lamptype = argv[++i];
516 break;
517 case 'u': /* default lamp type */
518 deflamp = argv[++i];
519 break;
520 case 'c': /* default lamp color */
521 defcolor[0] = atof(argv[++i]);
522 defcolor[1] = atof(argv[++i]);
523 defcolor[2] = atof(argv[++i]);
524 break;
525 case 'm': /* multiplier */
526 multiplier = atof(argv[++i]);
527 break;
528 default:
529 badopt:
530 fprintf(stderr, "%s: bad option: %s\n",
531 argv[0], argv[i]);
532 exit(1);
533 }
534 /* Save pointers to the list of input file names */
535 gargc = i;
536 gargv = argv;
537
538 /* get lamp data (if needed) */
539 initlamps();
540
541 /* convert ies file(s) */
542 /* If an output file name is specified */
543 if (outfile != NULL) {
544 if (i == argc)
545 /* If no input filename is given, use stdin as
546 * the source for the IES file */
547 exit(ies2rad(NULL, outfile) == 0 ? 0 : 1);
548 else if (i == argc-1)
549 /* If exactly one input file name is given, use it. */
550 exit(ies2rad(argv[i], outfile) == 0 ? 0 : 1);
551 else
552 goto needsingle; /* Otherwise, error. */
553 } else if (i >= argc) {
554 /* If an output file and an input file are not give, error. */
555 fprintf(stderr, "%s: missing output file specification\n",
556 argv[0]);
557 exit(1);
558 }
559 /* If no input or output file is given, error. */
560 if (out2stdout && i != argc-1)
561 goto needsingle;
562 /* Otherwise, process each input file in turn. */
563 status = 0;
564 for ( ; i < argc; i++) {
565 tailtrunc(strcpy(outname,filename(argv[i])));
566 if (ies2rad(argv[i], outname) != 0)
567 status = 1;
568 }
569 exit(status);
570 needsingle:
571 fprintf(stderr, "%s: single input file required\n", argv[0]);
572 exit(1);
573 }
574
575 /* Initlamps -- If necessary, read lamp data table */
576 void
577 initlamps(void) /* set up lamps */
578 {
579 float *lcol;
580 int status;
581
582 /* If the lamp name is set to default, don't bother to read
583 * the lamp data table. */
584 if (lamptype != NULL && !strcmp(lamptype, default_name) &&
585 deflamp == NULL)
586 return;
587
588 if ((status = loadlamps(lampdat)) < 0) /* Load the lamp data table */
589 exit(1); /* Exit if problems
590 * with the file. */
591 if (status == 0) {
592 /* If can't open the file, just use the standard default lamp */
593 fprintf(stderr, "%s: warning - no lamp data\n", lampdat);
594 lamptype = default_name;
595 return;
596 }
597 if (deflamp != NULL) {
598 /* Look up the specified default lamp type */
599 if ((lcol = matchlamp(deflamp)) == NULL)
600 /* If it can't be found, use the default */
601 fprintf(stderr,
602 "%s: warning - unknown default lamp type\n",
603 deflamp);
604 else
605 /* Use the selected default lamp color */
606 copycolor(defcolor, lcol);
607 }
608 /* If a lamp type is specified and can be found, use it, and
609 * release the lamp data table memory; it won't be needed any more. */
610 if (lamptype != NULL) {
611 if (strcmp(lamptype, default_name)) {
612 if ((lcol = matchlamp(lamptype)) == NULL) {
613 fprintf(stderr,
614 "%s: warning - unknown lamp type\n",
615 lamptype);
616 lamptype = default_name;
617 } else
618 copycolor(defcolor, lcol);
619 }
620 freelamps(); /* all done with data */
621 }
622 /* else keep lamp data */
623 }
624
625 /*
626 * String functions
627 */
628
629 /*
630 * isprefix - return 1 (true) if p is a prefix of s, 0 otherwise
631 *
632 * For this to work properly, s must be as long or longer than p.
633 */
634 int
635 isprefix(char *p, char *s) {
636 return matchprefix(p,s) != NULL;
637 }
638
639 /*
640 * matchprefix - match p against s
641 *
642 * If p is a prefix of s, return a pointer to the character of s just
643 * past p.
644 *
645 * For this to work properly, s must be as long or longer than p.
646 */
647 char *
648 matchprefix(char *p, char *s) {
649 int c;
650
651 while ((c = *p++)) {
652 if (c != *s++)
653 return NULL;
654 }
655 return s;
656 }
657
658 /*
659 * skipws - skip whitespace
660 */
661 char *
662 skipws(char *s) {
663 while (isspace(*s))
664 s++;
665 return s;
666 }
667
668 /*
669 * streq - test strings for equality
670 */
671 int
672 streq(char *s1, char *s2) {
673 return strcmp(s1,s2) == 0;
674 }
675
676 /*
677 * strneq - test strings for equality, with a length limit
678 */
679 int
680 strneq(char *s1, char *s2, int n) {
681 return strncmp(s1,s2,n) == 0;
682 }
683
684 /*
685 * IES (LM-63) file functions
686 */
687
688 /*
689 * prockwd - process keywords on a label line
690 *
691 * We're looking for four keywords: LAMP, LAMPCAT, LAMPPOSITION, and
692 * LUMINOUSGEOMETRY. Any other keywords are ignored.
693 *
694 * LAMP and LAMPCAT are searched for a known lamp type name.
695 * LAMPPOSITION is stored.
696 * LUMINOUSGEOMETRY contains the name of an MGF file, which is stored.
697 */
698 void
699 prockwd(char *bp, char *geomfile, char *inpname, SRCINFO *srcinfo) {
700 char *kwbegin;
701 int kwlen;
702
703 bp = skipws(bp); /* Skip leading whitespace. */
704 if (*bp != '[')
705 return; /* If there's no keyword on this line,
706 * do nothing */
707 kwbegin = bp;
708 while (*bp && *bp != ']') /* Skip to the end of the keyword or
709 * end of the buffer. */
710 bp++;
711 if (!(*bp)) /* If the keyword doesn't have a
712 * terminating ']', return. */
713 return;
714 kwlen = bp - kwbegin + 1;
715 bp++;
716 if (lampcolor == NULL && strneq("[LAMP]", kwbegin, kwlen))
717 lampcolor = matchlamp(bp);
718 else if (lampcolor == NULL && strneq("[LAMPCAT]", kwbegin, kwlen))
719 lampcolor = matchlamp(bp);
720 else if (strneq("[LUMINOUSGEOMETRY]", kwbegin, kwlen)) {
721 bp = skipws(bp); /* Skip leading whitespace. */
722 strcpy(geomfile, inpname); /* Copy the input file path */
723 /* Replace the filename in the input file path with
724 * the name of the MGF file. Trailing spaces were
725 * trimmed before this routine was called. */
726 strcpy(filename(geomfile), bp);
727 srcinfo->isillum = 1;
728 }
729 else if (strneq("[LAMPPOSITION]", kwbegin, kwlen)) {
730 srcinfo->havelamppos = 1;
731 sscanf(bp,"%f%f", &(srcinfo->lamppos[0]),
732 &(srcinfo->lamppos[1]));
733 }
734 }
735
736 /*
737 * iesversion - examine the first line of an IES file and return the version
738 *
739 * Returns the year of the version. If the version is unknown,
740 * returns 1986, since the first line of a 1986-format IES file can be
741 * anything.
742 */
743 int
744 iesversion(char *buf) {
745 IESversions *v;
746
747 for(v = IESFILEVERSIONS; v != NULL; v++)
748 if (streq(v->tag,buf))
749 return v->yr;
750 return v->yr;
751 }
752
753
754 /*
755 * File path operations
756 *
757 * These provide file path operations that operate on both MS-Windows
758 * and *nix. They will ignore and pass, but will not necessarily
759 * process correctly, Windows drive letters. Paths including Windows
760 * UNC network names (\\server\folder\file) may also cause problems.
761 *
762 */
763
764 /*
765 * stradd()
766 *
767 * Add a string to the end of a string, optionally concatenating a
768 * file path separator character. If the path already ends with a
769 * path separator, no additional separator is appended.
770 *
771 */
772 char *
773 stradd( /* add a string at dst */
774 char *dst,
775 char *src,
776 int sep
777 )
778 {
779 if (src && *src) {
780 do
781 *dst++ = *src++;
782 while (*src);
783 if (sep && dst[-1] != sep)
784 *dst++ = sep;
785 }
786 *dst = '\0';
787 return(dst);
788 }
789
790 /*
791 * fullnam () - return a usable path name for an output file
792 */
793 char *
794 fullnam(
795 char *path, /* The base directory path */
796 char *fname, /* The file name */
797 char *suffix /* A suffix, which usually contains
798 * a file name extension. */
799 )
800 {
801 extern char *prefdir;
802 extern char *libdir;
803
804 if (prefdir != NULL && abspath(prefdir))
805 /* If the subdirectory path is absolute or '.', just
806 * concatenate the names together */
807 libname(path, fname, suffix);
808 else if (abspath(fname))
809 /* If there is no subdirectory, and the file name is
810 * an absolute path or '.', concatenate the path,
811 * filename, and suffix. */
812 strcpy(stradd(path, fname, 0), suffix);
813 else
814 /* If the file name is relative, concatenate path,
815 * library directory, directory separator, file name,
816 * and suffix. */
817 libname(stradd(path, libdir, DIRSEP), fname, suffix);
818
819 return(path);
820 }
821
822
823 /*
824 * libname - convert a file name to a path
825 */
826 char *
827 libname(
828 char *path, /* The base directory path */
829 char *fname, /* The file name */
830 char *suffix /* A suffix, which usually contains
831 * a file name extension. */
832 )
833 {
834 extern char *prefdir; /* The subdirectory where the file
835 * name is stored. */
836
837 if (abspath(fname))
838 /* If the file name begins with '/' or '.', combine
839 * it with the path and attach the suffix */
840 strcpy(stradd(path, fname, 0), suffix);
841 else
842 /* If the file name is relative, attach it to the
843 * path, include the subdirectory, and append the suffix. */
844 strcpy(stradd(stradd(path, prefdir, DIRSEP), fname, 0), suffix);
845
846 return(path);
847 }
848
849 /* filename - pointer to filename in buffer containing path
850 *
851 * Scan the path, recording directory separators. Return the location
852 * of the character past the last one. If no directory separators are
853 * found, returns a pointer to beginning of the path.
854 */
855 char *
856 filename(
857 char *path
858 )
859 {
860 char *cp = path;
861
862 for (; *path; path++)
863 if (ISDIRSEP(*path))
864 cp = path+1;
865 return(cp);
866 }
867
868
869 /* filetrunc() - return the directory portion of a path
870 *
871 * The path is passed in in a pointer to a buffer; a null character is
872 * inserted in the buffer after the last directory separator
873 *
874 */
875 char *
876 filetrunc(
877 char *path
878 )
879 {
880 char *p1, *p2;
881
882 for (p1 = p2 = path; *p2; p2++)
883 if (ISDIRSEP(*p2))
884 p1 = p2;
885 if (p1 == path && ISDIRSEP(*p1))
886 p1++;
887 *p1 = '\0';
888 return(path);
889 }
890
891 /* tailtrunc() - trim a file name extension, if any.
892 *
893 * The file name is passed in in a buffer indicated by *name; the
894 * period which begins the extension is replaced with a 0 byte.
895 */
896 char *
897 tailtrunc(
898 char *name
899 )
900 {
901 char *p1, *p2;
902
903 /* Skip leading periods */
904 for (p1 = filename(name); *p1 == '.'; p1++)
905 ;
906 /* Find the last period in a file name */
907 p2 = NULL;
908 for ( ; *p1; p1++)
909 if (*p1 == '.')
910 p2 = p1;
911 /* If present, trim the filename at that period */
912 if (p2 != NULL)
913 *p2 = '\0';
914 return(name);
915 }
916
917 /* blanktrunc() - trim spaces at the end of a string
918 *
919 * the string is passed in a character array, which is modified
920 */
921 void
922 blanktrunc(
923 char *s
924 )
925 {
926 char *cp;
927
928 for (cp = s; *cp; cp++)
929 ;
930 while (cp-- > s && isspace(*cp))
931 ;
932 *++cp = '\0';
933 }
934
935 /* fpcomment - output a multi-line comment
936 *
937 * The comment may be multiple lines, with each line separated by a
938 * newline. Each line is prefixed by prefix. If the last line isn't
939 * terminated by a newline, no newline will be output.
940 */
941 void
942 fpcomment(FILE *fp, char *prefix, char *s) {
943 while (*s) { /* While there are characters left to output */
944 fprintf(fp, "%s", prefix); /* Output the prefix */
945 for (; *s && *s != '\n'; s++) /* Output a line */
946 putc(*s, fp);
947 if (*s == '\n') { /* Including the newline, if any */
948 putc(*s, fp);
949 s++;
950 }
951 }
952 }
953
954 /* putheader - output the header of the .rad file
955 *
956 * Header is:
957 * # <file> <file> <file> (all files from input line)
958 * # Dimensions in [feet,meters,etc.]
959 *
960 * ??? Is listing all the input file names correct behavior?
961 *
962 */
963 void
964
965 putheader(
966 FILE *out
967 )
968 {
969 int i;
970
971 putc('#', out);
972 for (i = 0; i < gargc; i++) {
973 putc(' ', out);
974 fputs(gargv[i], out);
975 }
976 fputs("\n# Dimensions in ", out);
977 fputs(units, out);
978 putc('\n', out);
979 }
980
981 /* ies2rad - convert an IES LM-63 file to a Radiance light source desc.
982 *
983 * Return -1 in case of failure, 0 in case of success.
984 *
985 */
986 int
987 ies2rad( /* convert IES file */
988 char *inpname,
989 char *outname
990 )
991 {
992 SRCINFO srcinfo;
993 char buf[MAXLINE], tltid[RMAXWORD];
994 char geomfile[MAXLINE];
995 FILE *inpfp, *outfp;
996 int lineno = 0;
997
998
999 /* Initialize srcinfo */
1000 srcinfo.filerev = IESFIRSTVER;
1001 srcinfo.iesshape = IESNONE;
1002 srcinfo.warn = NULL;
1003 srcinfo.isillum = 0;
1004 srcinfo.havelamppos = 0;
1005 /* Open input and output files */
1006 geomfile[0] = '\0';
1007 if (inpname == NULL) {
1008 inpname = "<stdin>";
1009 inpfp = stdin;
1010 } else if ((inpfp = fopen(inpname, "r")) == NULL) {
1011 perror(inpname);
1012 return(-1);
1013 }
1014 if (out2stdout)
1015 outfp = stdout;
1016 else if ((outfp = fopen(fullnam(buf,outname,T_RAD), "w")) == NULL) {
1017 perror(buf);
1018 fclose(inpfp);
1019 return(-1);
1020 }
1021
1022 /* Output the output file header */
1023 putheader(outfp);
1024
1025 /* If the lamp type wasn't given on the command line, mark
1026 * the lamp color as missing */
1027 if (lamptype == NULL)
1028 lampcolor = NULL;
1029
1030 /* Read the input file header, copying lines to the .rad file
1031 * and looking for a lamp type. Stop at EOF or a line
1032 * beginning with "TILT=". */
1033 while (fgets(buf,sizeof(buf),inpfp) != NULL
1034 && strncmp(buf,TLTSTR,TLTSTRLEN)) {
1035 blanktrunc(buf); /* Trim trailing spaces, CR, LF. */
1036 if (!buf[0]) /* Skip blank lines */
1037 continue;
1038 /* increment the header line count. If we are on the
1039 * first line of the file, check for a version tag. If
1040 * one is not found, assume the first version of the
1041 * file. */
1042 if (!lineno++)
1043 srcinfo.filerev = iesversion(buf);
1044 /* Output the header line as a comment in the .rad file. */
1045 fputs("#<", outfp);
1046 fputs(buf, outfp);
1047 putc('\n', outfp);
1048
1049 /* For post-1986 version files, process a keyword
1050 * line. Otherwise, just scan the line for a lamp
1051 * name */
1052 if (srcinfo.filerev != 1986)
1053 prockwd(buf, geomfile, inpname, &srcinfo);
1054 else if (lampcolor == NULL)
1055 lampcolor = matchlamp(buf);
1056 }
1057
1058 /* Done reading header information. If a lamp color still
1059 * hasn't been found, print a warning and use the default
1060 * color; if a lamp type hasn't been found, but a color has
1061 * been specified, used the specified color. */
1062 if (lampcolor == NULL) {
1063 fprintf(stderr, "%s: warning - no lamp type\n", inpname);
1064 fputs("# Unknown lamp type (used default)\n", outfp);
1065 lampcolor = defcolor;
1066 } else if (lamptype == NULL)
1067 fprintf(outfp,"# CIE(x,y) = (%f,%f)\n# Depreciation = %.1f%%\n",
1068 lampcolor[3], lampcolor[4], 100.*lampcolor[5]);
1069
1070 /* If the file ended before a "TILT=" line, that's an error. */
1071 if (feof(inpfp)) {
1072 fprintf(stderr, "%s: not in IES format\n", inpname);
1073 goto readerr;
1074 }
1075
1076 /* Process the tilt section of the file. */
1077 /* Get the tilt file name, or the keyword "INCLUDE". */
1078 atos(tltid, RMAXWORD, buf+TLTSTRLEN);
1079 if (inpfp == stdin)
1080 buf[0] = '\0';
1081 else
1082 filetrunc(strcpy(buf, inpname));
1083 /* Process the tilt data. */
1084 if (dotilt(inpfp, outfp, buf, tltid, outname, tltid) != 0) {
1085 fprintf(stderr, "%s: bad tilt data\n", inpname);
1086 goto readerr;
1087 }
1088
1089 /* Process the luminaire data. */
1090 if (dosource(&srcinfo, inpfp, outfp, tltid, outname) != 0) {
1091 fprintf(stderr, "%s: bad luminaire data\n", inpname);
1092 goto readerr;
1093 }
1094
1095 /* Close the input file */
1096 fclose(inpfp);
1097
1098 /* Process an MGF file, if present. cvgeometry() closes outfp. */
1099 if (cvgeometry(geomfile, &srcinfo, outname, outfp) != 0) {
1100 fprintf(stderr, "%s: bad geometry file\n", geomfile);
1101 return(-1);
1102 }
1103 return(0);
1104
1105 readerr:
1106 /* If there is an error reading the file, close the input and
1107 * .rad output files, and delete the .rad file, returning -1. */
1108 fclose(inpfp);
1109 fclose(outfp);
1110 unlink(fullnam(buf,outname,T_RAD));
1111 return(-1);
1112 }
1113
1114 /* dotilt -- process tilt data
1115 *
1116 * Generate a brightdata primitive which describes the effect of
1117 * luminaire tilt on luminaire output and return its identifier in tltid.
1118 *
1119 * Tilt data (if present) is given as a number 1, 2, or 3, which
1120 * specifies the orientation of the lamp within the luminaire, a
1121 * number, n, of (angle, multiplier) pairs, followed by n angles and n
1122 * multipliers.
1123 *
1124 * returns 0 for success, -1 for error
1125 */
1126 int
1127 dotilt(
1128 FILE *in,
1129 FILE *out,
1130 char *dir,
1131 char *tltspec,
1132 char *dfltname,
1133 char *tltid
1134 )
1135 {
1136 int nangles, tlt_type;
1137 double minmax[1][2];
1138 char buf[PATH_MAX], tltname[RMAXWORD];
1139 FILE *datin, *datout;
1140
1141 /* Decide where the tilt data is; if the luminaire description
1142 * doesn't have a tilt section, set the identifier to "void". */
1143 if (!strcmp(tltspec, TLTNONE)) {
1144 /* If the line is "TILT=NONE", set the input file
1145 * pointer to NULL and the identifier to "void". */
1146 datin = NULL;
1147 strcpy(tltid, "void");
1148 } else if (!strcmp(tltspec, TLTINCL)) {
1149 /* If the line is "TILT=INCLUDE" use the main IES
1150 * file as the source of tilt data. */
1151 datin = in;
1152 strcpy(tltname, dfltname);
1153 } else {
1154 /* If the line is "TILT=<filename>", use that file
1155 * name as the source of tilt data. */
1156 if (ISDIRSEP(tltspec[0]))
1157 strcpy(buf, tltspec);
1158 else
1159 strcpy(stradd(buf, dir, DIRSEP), tltspec);
1160 if ((datin = fopen(buf, "r")) == NULL) {
1161 perror(buf);
1162 return(-1);
1163 }
1164 tailtrunc(strcpy(tltname,filename(tltspec)));
1165 }
1166 /* If tilt data is present, read, process, and output it. */
1167 if (datin != NULL) {
1168 /* Try to open the output file */
1169 if ((datout = fopen(fullnam(buf,tltname,T_TLT),"w")) == NULL) {
1170 perror(buf);
1171 if (datin != in)
1172 fclose(datin);
1173 return(-1);
1174 }
1175 /* Try to copy the tilt data to the tilt data file */
1176 if (!scnint(datin,&tlt_type) || !scnint(datin,&nangles)
1177 || cvdata(datin,datout,1,&nangles,1.,minmax) != 0) {
1178 fprintf(stderr, "%s: data format error\n", tltspec);
1179 fclose(datout);
1180 if (datin != in)
1181 fclose(datin);
1182 unlink(fullnam(buf,tltname,T_TLT));
1183 return(-1);
1184 }
1185 fclose(datout);
1186 if (datin != in)
1187 fclose(datin);
1188
1189 /* Generate the identifier of the brightdata; the filename
1190 * with "_tilt" appended. */
1191 strcat(strcpy(tltid, filename(tltname)), "_tilt");
1192 /* Write out the brightdata primitive */
1193 fprintf(out, "\nvoid brightdata %s\n", tltid);
1194 libname(buf,tltname,T_TLT);
1195 /* Generate the tilt description */
1196 switch (tlt_type) {
1197 case TLT_VERT:
1198 /* The lamp is mounted vertically; either
1199 * base up or base down. */
1200 fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1201 minmax[0][1]>90.+FTINY ? "tilt_ang" : "tilt_ang2");
1202 break;
1203 case TLT_H0:
1204 /* The lamp is mounted horizontally and
1205 * rotates but does not tilt when the
1206 * luminaire is tilted. */
1207 fprintf(out, "6 noop %s tilt.cal %s -rz 90\n", buf,
1208 minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1209 break;
1210 case TLT_H90:
1211 /* The lamp is mounted horizontally, and
1212 * tilts when the luminaire is tilted. */
1213 fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1214 minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1215 break;
1216 default:
1217 /* otherwise, this is a bad IES file */
1218 fprintf(stderr,
1219 "%s: illegal lamp to luminaire geometry (%d)\n",
1220 tltspec, tlt_type);
1221 return(-1);
1222 }
1223 /* And finally output the numbers of integer and real
1224 * arguments, of which there are none. */
1225 fprintf(out, "0\n0\n");
1226 }
1227 return(0);
1228 }
1229
1230 /* dosource -- create the source and distribution primitives */
1231 int
1232 dosource(
1233 SRCINFO *sinf,
1234 FILE *in,
1235 FILE *out,
1236 char *mod,
1237 char *name
1238 )
1239 {
1240 char buf[PATH_MAX], id[RMAXWORD];
1241 FILE *datout;
1242 double mult, bfactor, pfactor, width, length, height, wattage;
1243 double bounds[2][2];
1244 int nangles[2], pmtype, unitype;
1245 double d1;
1246 int doupper, dolower, dosides;
1247
1248 /* Read in the luminaire description header */
1249 if (!isint(getword(in)) || !isflt(getword(in)) || !scnflt(in,&mult)
1250 || !scnint(in,&nangles[0]) || !scnint(in,&nangles[1])
1251 || !scnint(in,&pmtype) || !scnint(in,&unitype)
1252 || !scnflt(in,&width) || !scnflt(in,&length)
1253 || !scnflt(in,&height) || !scnflt(in,&bfactor)
1254 || !scnflt(in,&pfactor) || !scnflt(in,&wattage)) {
1255 fprintf(stderr, "dosource: bad lamp specification\n");
1256 return(-1);
1257 }
1258
1259 /* pfactor is only provided in 1986 and 1991 format files, and
1260 * is something completely different in 2019 files. If the
1261 * file version is 1995 or later, set it to 1.0 to avoid
1262 * error. */
1263 if (sinf->filerev >= 1995)
1264 pfactor = 1.0;
1265
1266 /* Type A photometry is not supported */
1267 if (pmtype != PM_C && pmtype != PM_B) {
1268 fprintf(stderr, "dosource: unsupported photometric type (%d)\n",
1269 pmtype);
1270 return(-1);
1271 }
1272
1273 /* Multiplier = the multiplier from the -m option, times the
1274 * multiplier from the IES file, times the ballast factor,
1275 * times the "ballast lamp photometric factor," (pfactor)
1276 * which was part of the 1986 and 1991 standards. In the 1995
1277 * and 2002 standards, it is always supposed to be 1 and in
1278 * the 2019 standard it encodes information about the source
1279 * of the file. For those files, pfactor is set to 1.0,
1280 * above. */
1281 sinf->mult = multiplier*mult*bfactor*pfactor;
1282
1283 /* If the count of angles is wrong, raise an error and quit. */
1284 if (nangles[0] < 2 || nangles[1] < 1) {
1285 fprintf(stderr, "dosource: too few measured angles\n");
1286 return(-1);
1287 }
1288
1289 /* For internal computation, convert units to meters. */
1290 if (unitype == U_FEET) {
1291 width *= F_M;
1292 length *= F_M;
1293 height *= F_M;
1294 }
1295
1296 /* Make decisions about the shape of the light source
1297 * geometry, and store them in sinf. */
1298 if (makeshape(sinf, width, length, height) != 0) {
1299 fprintf(stderr, "dosource: illegal source dimensions\n");
1300 return(-1);
1301 }
1302 /* If any warning messages were generated by makeshape(), output them */
1303 if ((sinf->warn) != NULL)
1304 fputs(sinf->warn, stderr);
1305
1306 /* Copy the candela values into a Radiance data file. */
1307 if ((datout = fopen(fullnam(buf,name,T_DST), "w")) == NULL) {
1308 perror(buf);
1309 return(-1);
1310 }
1311 if (cvdata(in, datout, 2, nangles, 1./WHTEFFICACY, bounds) != 0) {
1312 fprintf(stderr, "dosource: bad distribution data\n");
1313 fclose(datout);
1314 unlink(fullnam(buf,name,T_DST));
1315 return(-1);
1316 }
1317 fclose(datout);
1318
1319 /* Output explanatory comment */
1320 fprintf(out, "\n# %g watt luminaire, lamp*ballast factor = %g\n",
1321 wattage, bfactor*pfactor);
1322 if (sinf->iesshape >= 0)
1323 fprintf(out, "# IES file shape = %s\n",
1324 IESHAPENAMES[sinf->iesshape]);
1325 else
1326 fprintf(out, "# IES file shape overridden\n");
1327 fprintf(out, "# Radiance geometry shape = %s\n",
1328 RADSHAPENAMES[sinf->type - 1]);
1329 if (sinf->warn != NULL)
1330 fpcomment(out, "# ", sinf->warn);
1331
1332 /* Output distribution "brightdata" primitive. Start handling
1333 the various cases of symmetry of the distribution. This
1334 code reflects the complexity of the LM-63 format, as
1335 described under "<horizontal angles>" in the various
1336 versions of the standard. */
1337 strcat(strcpy(id, filename(name)), "_dist");
1338 fprintf(out, "\n'%s' brightdata '%s'\n", mod, id);
1339 if (nangles[1] < 2)
1340 /* if it's a radially-symmetric type C distribution */
1341 fprintf(out, "4 ");
1342 else if (pmtype == PM_B)
1343 /* Photometry type B */
1344 fprintf(out, "5 ");
1345 else if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1346 /* Symmetric around the 90-270 degree plane */
1347 fprintf(out, "7 ");
1348 else
1349 /* Just regular type C photometry */
1350 fprintf(out, "5 ");
1351
1352 /* If the generated source geometry will be a box, a flat
1353 * rectangle, or a disk figure out if it needs a top, a
1354 * bottom, and/or sides. */
1355 dolower = (bounds[0][0] < 90.-FTINY); /* Smallest vertical angle */
1356 doupper = (bounds[0][1] > 90.+FTINY); /* Largest vertical angle */
1357 dosides = (doupper & dolower && sinf->h > MINDIM); /* Sides */
1358
1359 /* Select the appropriate function and parameters from source.cal */
1360 fprintf(out, "%s '%s' source.cal ",
1361 sinf->type==SPHERE ? "corr" :
1362 !dosides ? "flatcorr" :
1363 sinf->type==DISK ? "cylcorr" : "boxcorr",
1364 libname(buf,name,T_DST));
1365 if (pmtype == PM_B) {
1366 /* Type B photometry */
1367 if (FEQ(bounds[1][0],0.))
1368 /* laterally symmetric around a vertical plane */
1369 fprintf(out, "srcB_horiz2 ");
1370 else
1371 fprintf(out, "srcB_horiz ");
1372 fprintf(out, "srcB_vert ");
1373 } else /* pmtype == PM_C */ {
1374 if (nangles[1] >= 2) {
1375 /* Not radially symmetric */
1376 d1 = bounds[1][1] - bounds[1][0];
1377 if (d1 <= 90.+FTINY)
1378 /* Data for a quadrant */
1379 fprintf(out, "src_phi4 ");
1380 else if (d1 <= 180.+FTINY) {
1381 /* Data for a hemisphere */
1382 if (FEQ(bounds[1][0],90.))
1383 fprintf(out, "src_phi2+90 ");
1384 else
1385 fprintf(out, "src_phi2 ");
1386 } else /* Data for a whole sphere */
1387 fprintf(out, "src_phi ");
1388 fprintf(out, "src_theta ");
1389 /* For the hemisphere around the 90-270 degree plane */
1390 if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1391 fprintf(out, "-rz -90 ");
1392 } else /* Radially symmetric */
1393 fprintf(out, "src_theta ");
1394 }
1395 /* finish the brightdata primitive with appropriate data */
1396 if (!dosides || sinf->type == SPHERE)
1397 fprintf(out, "\n0\n1 %g\n", sinf->mult/sinf->area);
1398 else if (sinf->type == DISK)
1399 fprintf(out, "\n0\n3 %g %g %g\n", sinf->mult,
1400 sinf->w, sinf->h);
1401 else
1402 fprintf(out, "\n0\n4 %g %g %g %g\n", sinf->mult,
1403 sinf->l, sinf->w, sinf->h);
1404 /* Brightdata primitive written out. */
1405
1406 /* Finally, output the descriptions of the actual radiant
1407 * surfaces. */
1408 if (putsource(sinf, out, id, filename(name),
1409 dolower, doupper, dosides) != 0)
1410 return(-1);
1411 return(0);
1412 }
1413
1414 /* putsource - output the actual light emitting geometry
1415 *
1416 * Three kinds of geometry are produced: rectangles and boxes, disks
1417 * ("ring" primitive, but the radius of the hole is always zero) and
1418 * cylinders, and spheres.
1419 */
1420 int
1421 putsource(
1422 SRCINFO *shp,
1423 FILE *fp,
1424 char *mod,
1425 char *name,
1426 int dolower,
1427 int doupper,
1428 int dosides
1429 )
1430 {
1431 char lname[RMAXWORD];
1432
1433 /* First, describe the light. If a materials and geometry
1434 * file is given, generate an illum instead. */
1435 strcat(strcpy(lname, name), "_light");
1436 fprintf(fp, "\n'%s' %s '%s'\n", mod,
1437 shp->isillum ? "illum" : "light", lname);
1438 fprintf(fp, "0\n0\n3 %g %g %g\n",
1439 lampcolor[0], lampcolor[1], lampcolor[2]);
1440 switch (shp->type) {
1441 case RECT:
1442 /* Output at least one rectangle. If light is radiated
1443 * from the sides of the luminaire, output rectangular
1444 * sides as well. */
1445 if (dolower)
1446 putrectsrc(shp, fp, lname, name, 0);
1447 if (doupper)
1448 putrectsrc(shp, fp, lname, name, 1);
1449 if (dosides)
1450 putsides(shp, fp, lname, name);
1451 break;
1452 case DISK:
1453 /* Output at least one disk. If light is radiated from
1454 * the sides of luminaire, output a cylinder as well. */
1455 if (dolower)
1456 putdisksrc(shp, fp, lname, name, 0);
1457 if (doupper)
1458 putdisksrc(shp, fp, lname, name, 1);
1459 if (dosides)
1460 putcyl(shp, fp, lname, name);
1461 break;
1462 case SPHERE:
1463 /* Output a sphere. */
1464 putspheresrc(shp, fp, lname, name);
1465 break;
1466 }
1467 return(0);
1468 }
1469
1470 /* makeshape -- decide what shape will be used
1471 *
1472 * Makeshape decides what Radiance geometry will be used to represent
1473 * the light source and stores information about it in shp.
1474 *
1475 * The height, width, and length parameters are values from the
1476 * IES file, given in meters.
1477 *
1478 * The various versions of the IES LM-63 standard give a "luminous
1479 * opening" (really a crude shape) a width, a length (or depth), and a
1480 * height. If all three values are positive, they describe a box. If
1481 * they are all zero, they describe a point. Various combinations of
1482 * negative values are used to denote disks, circular or elliptical
1483 * cylinders, spheres, and ellipsoids. This encoding differs from
1484 * version to version of LM-63.
1485 *
1486 * Ies2rad simplifies this, reducing the geometry of LM-63 files to
1487 * three forms which can be easily represented by Radiance primitives:
1488 * boxes (RECT), cylinders or disks (DISK), and spheres (SPHERE.) A
1489 * point is necessarily represented by a small sphere, since a point
1490 * is not a Radiance object.
1491 *
1492 * Makeshape() returns 0 if it succeeds in choosing a shape, and -1 if
1493 * it fails.
1494 *
1495 */
1496 int
1497 makeshape(
1498 SRCINFO *shp,
1499 double width,
1500 double length,
1501 double height
1502 )
1503 {
1504 int rc;
1505
1506 if (illumrad != 0.0)
1507 rc = makeillumsphere(shp);
1508 else
1509 rc = makeiesshape(shp, length, width, height);
1510 if (rc == SUCCESS)
1511 shapearea(shp);
1512 return rc;
1513 }
1514
1515 /*
1516 * Return 1 if d < 0, 2 if d == 0, 3 if d > 0. This is used to encode
1517 * the signs of IES file dimensions for quick lookup. As usual with
1518 * macros, don't use an expression with side effects as an argument.
1519 */
1520 #define CONVSGN(d) ((d) < 0 ? 1 : ((d) == 0 ? 2 : 3))
1521
1522 /*
1523 * Generate the numeric key, the "thumbprint" for the various
1524 * combinations of IES LM-63 version year, length, width, and height.
1525 * This must be an integer constant expression so that it can be used
1526 * in a case label. See the header comments of makeiesshape() for
1527 * additional information.
1528 */
1529 #define TBPR(ver,l,w,h) ((ver) * 1000 + CONVSGN(l) * 100 + CONVSGN(w) * 10 + CONVSGN(h))
1530
1531 /* makeiesshape - convert IES shape to Radiance shape
1532 *
1533 * Some 34 cases in the various versions of the IES LM-63 standard are
1534 * handled, though some only by approximation. For each case which is
1535 * processed a Radiance box, cylinder, or sphere is selected.
1536 *
1537 * Shapes are categorized by version year of the standard and the
1538 * signs of the LM-63 length, width (depth), and height fields. These
1539 * are combined and converted to an integer, which is then used as the
1540 * argument to switch(). The last two digits of the IES file version
1541 * year are used and the signs of length, width, and height are
1542 * encoded, in that order, as 1 for negative, 2 for zero, and 3 for
1543 * positive. These are then combined into a numeric key by the
1544 * following formula:
1545 *
1546 * version * 1000 + sgn(length) * 100 + sgn(width) * 10 + sgn(height)
1547 *
1548 * The macro TBPR implements this formula.
1549 *
1550 * Since the 1991 version uses the same encoding as the 1986 version,
1551 * and the 2019 version uses the same encoding as the 2002 version,
1552 * these are collapsed into the earlier years.
1553 *
1554 * In the cases of the switch() statement, further processing takes
1555 * place. Circles and ellipses are distinguished by comparisons. Then
1556 * routines are called to fill out the fields of the shp structure.
1557 *
1558 * As per the conventions of the rest of ies2rad, makeiesshape()
1559 * returns 0 on success and -1 on failure. -1 reflects an error in
1560 * the IES file and is unusual.
1561 *
1562 * By convention, the shape generating routines are always given
1563 * positive values for dimensions and always succeed; all errors are
1564 * caught before they are called. The absolute values of all three
1565 * dimensions are calculated at the beginning of makeiesshape() and
1566 * used throughout the function, this has a low cost and eliminates
1567 * the chance of sign errors.
1568 *
1569 * There are two extensions to the ies standard here:
1570 *
1571 * 1. devised to accomdate wall-mounted fixtures; vertical rectangles,
1572 * not formally supported by any version of LM-63, are treated as
1573 * boxes.
1574 *
1575 * 2. A 2002-flagged file with only a negative width will be
1576 * recognized as a disk.
1577 *
1578 * The code is complicated by the way that earlier versions of the
1579 * standard (1986 and 1991) prioritize width in their discussions, and
1580 * later versions prioritize length. It is not always clear which to
1581 * write first and there is hesitation between the older code which
1582 * invokes makeiesshape() and makeiesshape() itself.
1583 */
1584 int
1585 makeiesshape(SRCINFO *shp, double l, double w, double h) {
1586 int rc = SUCCESS;
1587 int shape = IESNONE;
1588 /* Get the last two digits of the standard year */
1589 int ver = shp->filerev % 100;
1590 /* Make positive versions of all dimensions, for clarity in
1591 * function calls. If you like, read this as l', w', and h'. */
1592 double lp = fabs(l), wp = fabs(w), hp = fabs(h);
1593 int thumbprint;
1594
1595 /* Change 1991 into 1986 and 2019 in 2002 */
1596 switch (ver) {
1597 case 91:
1598 ver = 86;
1599 break;
1600 case 19:
1601 ver = 02;
1602 break;
1603 }
1604
1605 thumbprint = TBPR(ver, l, w, h);
1606 switch(thumbprint) {
1607 case TBPR(86,0,0,0): case TBPR(95, 0, 0, 0): case TBPR(02, 0, 0, 0):
1608 shp->iesshape = IESPT;
1609 shp->type = SPHERE;
1610 shp->w = shp->l = shp->h = MINDIM;
1611 break;
1612 case TBPR(86, 1, 1, 0): case TBPR(95, 1, 1, 0): case TBPR(02, 1, 1, 0):
1613 shp->iesshape = IESRECT;
1614 makeboxshape(shp, lp, wp, hp);
1615 break;
1616 case TBPR(86, 1, 1, 1): case TBPR(86, 0, 1, 1): case TBPR(86, 1, 0, 1):
1617 case TBPR(95, 1, 1, 1): case TBPR(95, 0, 1, 1): case TBPR(95, 1, 0, 1):
1618 case TBPR(02, 1, 1, 1): case TBPR(02, 0, 1, 1): case TBPR(02, 1, 0, 1):
1619 shp->iesshape = IESBOX;
1620 makeboxshape(shp, lp, wp, hp);
1621 break;
1622 case TBPR(86,0,-1,0): case TBPR(95,0,-1,0): case TBPR(02,0,-1,0):
1623 shp->iesshape = IESDISK;
1624 makecylshape(shp, wp, hp);
1625 break;
1626 case TBPR(86, 0, -1, 1):
1627 shp->iesshape = IESVCYL;
1628 makecylshape(shp, wp, hp);
1629 break;
1630 case TBPR(86, 1, -1, 0):
1631 shp->iesshape = IESELLIPSE;
1632 makeecylshape(shp, lp, wp, 0);
1633 break;
1634 case TBPR(86, 1, -1, 1):
1635 shp->iesshape = IESELLIPSOID;
1636 makeelshape(shp, wp, lp, hp);
1637 break;
1638 case TBPR(95, 0, -1, -1):
1639 shp->iesshape = FEQ(lp,hp) ? IESSPHERE : IESNONE;
1640 if (shp->iesshape == IESNONE) {
1641 shp->warn = "makeshape: cannot determine shape\n";
1642 rc = FAIL;
1643 break;
1644 }
1645 shp->type = SPHERE;
1646 shp->w = shp->l = shp->h = wp;
1647 break;
1648 case TBPR(95, 0, -1, 1):
1649 shp->iesshape = IESVCYL;
1650 makecylshape(shp, wp, hp);
1651 break;
1652 case TBPR(95, 1, 0, -1):
1653 shp->iesshape = IESHCYL_PH;
1654 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1655 makeboxshape(shp, lp, wp, hp);
1656 break;
1657 case TBPR(95, 0, 1, -1):
1658 shp->iesshape = IESHCYL_PPH;
1659 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1660 makeboxshape(shp, lp, wp, hp);
1661 break;
1662 case TBPR(95, -1, 1, 1): case TBPR(95, 1, -1, 1):
1663 shp->iesshape = IESVECYL;
1664 makeecylshape(shp, lp, wp, hp);
1665 break;
1666 case TBPR(95, -1, 1, -1): case TBPR(95, 1, -1, -1):
1667 shp->iesshape = IESELLIPSOID;
1668 makeelshape(shp, lp, wp, hp);
1669 break;
1670 case TBPR(02, -1, -1, 0):
1671 shp->iesshape = FEQ(l,w) ? IESDISK : IESELLIPSE;
1672 if (shp->iesshape == IESDISK)
1673 makecylshape(shp, wp, hp);
1674 else
1675 makeecylshape(shp, wp, lp, hp);
1676 break;
1677 case TBPR(02, -1, -1, 1):
1678 shp->iesshape = FEQ(l,w) ? IESVCYL : IESVECYL;
1679 if (shp->iesshape == IESVCYL)
1680 makecylshape(shp, wp, hp);
1681 else
1682 makeecylshape(shp, wp, lp, hp);
1683 break;
1684 case TBPR(02, -1, -1, -1):
1685 shp->iesshape = FEQ(l,w) && FEQ(l,h) ? IESSPHERE : IESELLIPSOID;
1686 if (shp->iesshape == IESSPHERE) {
1687 shp->type = SPHERE;
1688 shp->w = shp->l = shp->h = wp;
1689 }
1690 else
1691 makeelshape(shp, lp, wp, hp);
1692 break;
1693 case TBPR(02, 1, -1, -1):
1694 shp->iesshape = FEQ(w,h) ? IESHCYL_PH : IESHECYL_PH;
1695 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1696 makeboxshape(shp, lp, wp, hp);
1697 break;
1698 case TBPR(02, -1, 1, -1):
1699 shp->iesshape = FEQ(l,h) ? IESHCYL_PPH : IESHECYL_PPH;
1700 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1701 makeboxshape(shp, lp, wp, hp);
1702 break;
1703 case TBPR(02, -1, 0, -1):
1704 shp->iesshape = FEQ(w,h) ? IESVDISK_PH : IESVEL_PH;
1705 shp->warn = "makeshape: shape is a vertical ellipse, which is not supported.\nmakeshape: replaced with rectangle\n";
1706 makeboxshape(shp, lp, wp, hp);
1707 break;
1708 default:
1709 /* We don't recognize the shape - report an error. */
1710 rc = FAIL;
1711 }
1712 return rc;
1713 }
1714
1715 /* makeillumsphere - create an illum sphere */
1716 int
1717 makeillumsphere(SRCINFO *shp) {
1718 /* If the size is too small or negative, error. */
1719 if (illumrad/meters2out < MINDIM/2.) {
1720 fprintf(stderr, "makeillumsphere: -i argument is too small or negative\n");
1721 return FAIL;
1722 }
1723 shp->isillum = 1;
1724 shp->type = SPHERE;
1725 shp->w = shp->l = shp->h = 2.*illumrad / meters2out;
1726 return SUCCESS;
1727 }
1728
1729 /* makeboxshape - create a box */
1730 void
1731 makeboxshape(SRCINFO *shp, double l, double w, double h) {
1732 shp->type = RECT;
1733 shp->l = fmax(l, MINDIM);
1734 shp->w = fmax(w, MINDIM);
1735 shp->h = fmax(h, .5*MINDIM);
1736 }
1737
1738 /* makecylshape - output a vertical cylinder or disk
1739 *
1740 * If the shape has no height, make it a half-millimeter.
1741 */
1742 void
1743 makecylshape(SRCINFO *shp, double diam, double height) {
1744 shp->type = DISK;
1745 shp->w = shp->l = diam;
1746 shp->h = fmax(height, .5*MINDIM);
1747 }
1748
1749 /* makeelshape - create a substitute for an ellipsoid
1750 *
1751 * Because we don't actually support ellipsoids, and they don't seem
1752 * to be common in actual IES files.
1753 */
1754 void
1755 makeelshape(SRCINFO *shp, double w, double l, double h) {
1756 float avg = (w + l + h) / 3;
1757 float bot = .5 * avg;
1758 float top = 1.5 * avg;
1759
1760 if (bot < w && w < top
1761 && bot < l && l < top
1762 && bot < h && h > top) {
1763 /* it's sort of spherical, replace it with a sphere */
1764 shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with sphere\n";
1765 shp->type = SPHERE;
1766 shp->w = shp->l = shp->h = avg;
1767 } else if (bot < w && w < top
1768 && bot < l && l < top
1769 && h <= .5*MINDIM) {
1770 /* It's flat and sort of circular, replace it
1771 * with a disk. */
1772 shp->warn = "makeshape: shape is an ellipse, which is not supported.\nmakeshape: replaced with disk\n";
1773 makecylshape(shp, w, 0);
1774 } else {
1775 shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with box\n";
1776 makeboxshape(shp, w, l, h);
1777 }
1778 }
1779
1780 /* makeecylshape - create a substitute for an elliptical cylinder or disk */
1781 void
1782 makeecylshape(SRCINFO *shp, double l, double w, double h) {
1783 float avg = (w + l) / 2;
1784 float bot = .5 * avg;
1785 float top = 1.5 * avg;
1786
1787 if (bot < w && w < top
1788 && bot < l && l < top) {
1789 /* It's sort of circular, replace it
1790 * with a circular cylinder. */
1791 shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with circular cylinder\n";
1792 makecylshape(shp, w, h);
1793 } else {
1794 shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with box\n";
1795 makeboxshape(shp, w, l, h);
1796 }
1797 }
1798
1799 void
1800 shapearea(SRCINFO *shp) {
1801 switch (shp->type) {
1802 case RECT:
1803 shp->area = shp->w * shp->l;
1804 break;
1805 case DISK:
1806 case SPHERE:
1807 shp->area = PI/4. * shp->w * shp->w;
1808 break;
1809 }
1810 }
1811
1812 /* Rectangular or box-shaped light source.
1813 *
1814 * putrectsrc, putsides, putrect, and putpoint are used to output the
1815 * Radiance description of a box. The box is centered on the origin
1816 * and has the dimensions given in the IES file. The coordinates
1817 * range from [-1/2*length, -1/2*width, -1/2*height] to [1/2*length,
1818 * 1/2*width, 1/2*height].
1819 *
1820 * The location of the point is encoded in the low-order three bits of
1821 * an integer. If the integer is p, then: bit 0 is (p & 1),
1822 * representing length (x), bit 1 is (p & 2) representing width (y),
1823 * and bit 2 is (p & 4), representing height (z).
1824 *
1825 * Looking down from above (towards -z), the vertices of the box or
1826 * rectangle are numbered so:
1827 *
1828 * 2,6 3,7
1829 * +--------------------------------------+
1830 * | |
1831 * | |
1832 * | |
1833 * | |
1834 * +--------------------------------------+
1835 * 0,4 1,5
1836 *
1837 * The higher number of each pair is above the x-y plane (positive z),
1838 * the lower number is below the x-y plane (negative z.)
1839 *
1840 */
1841
1842 /* putrecsrc - output a rectangle parallel to the x-y plane
1843 *
1844 * Putrecsrc calls out the vertices of a rectangle parallel to the x-y
1845 * plane. The order of the vertices is different for the upper and
1846 * lower rectangles of a box, since a right-hand rule based on the
1847 * order of the vertices is used to determine the surface normal of
1848 * the rectangle, and the surface normal determines the direction the
1849 * light radiated by the rectangle.
1850 *
1851 */
1852 void
1853 putrectsrc(
1854 SRCINFO *shp,
1855 FILE *fp,
1856 char *mod,
1857 char *name,
1858 int up
1859 )
1860 {
1861 if (up)
1862 putrect(shp, fp, mod, name, ".u", 4, 5, 7, 6);
1863 else
1864 putrect(shp, fp, mod, name, ".d", 0, 2, 3, 1);
1865 }
1866
1867 /* putsides - put out sides of box */
1868 void
1869 putsides(
1870 SRCINFO *shp,
1871 FILE *fp,
1872 char *mod,
1873 char *name
1874 )
1875 {
1876 putrect(shp, fp, mod, name, ".1", 0, 1, 5, 4);
1877 putrect(shp, fp, mod, name, ".2", 1, 3, 7, 5);
1878 putrect(shp, fp, mod, name, ".3", 3, 2, 6, 7);
1879 putrect(shp, fp, mod, name, ".4", 2, 0, 4, 6);
1880 }
1881
1882 /* putrect - put out a rectangle
1883 *
1884 * putrect generates the "polygon" primitive which describes a
1885 * rectangle.
1886 */
1887 void
1888 putrect(
1889 SRCINFO *shp,
1890 FILE *fp,
1891 char *mod,
1892 char *name,
1893 char *suffix,
1894 int a,
1895 int b,
1896 int c,
1897 int d
1898 )
1899 {
1900 fprintf(fp, "\n'%s' polygon '%s%s'\n0\n0\n12\n", mod, name, suffix);
1901 putpoint(shp, fp, a);
1902 putpoint(shp, fp, b);
1903 putpoint(shp, fp, c);
1904 putpoint(shp, fp, d);
1905 }
1906
1907 /* putpoint -- output a the coordinates of a vertex
1908 *
1909 * putpoint maps vertex numbers to coordinates and outputs the
1910 * coordinates.
1911 */
1912 void
1913 putpoint(
1914 SRCINFO *shp,
1915 FILE *fp,
1916 int p
1917 )
1918 {
1919 static double mult[2] = {-.5, .5};
1920
1921 fprintf(fp, "\t%g\t%g\t%g\n",
1922 mult[p&1]*shp->l*meters2out,
1923 mult[p>>1&1]*shp->w*meters2out,
1924 mult[p>>2]*shp->h*meters2out);
1925 }
1926
1927 /* End of routines to output a box-shaped light source */
1928
1929 /* Routines to output a cylindrical or disk shaped light source
1930 *
1931 * As with other shapes, the light source is centered on the origin.
1932 * The "ring" and "cylinder" primitives are used.
1933 *
1934 */
1935 void
1936 putdisksrc( /* put out a disk source */
1937 SRCINFO *shp,
1938 FILE *fp,
1939 char *mod,
1940 char *name,
1941 int up
1942 )
1943 {
1944 if (up) {
1945 fprintf(fp, "\n'%s' ring '%s.u'\n", mod, name);
1946 fprintf(fp, "0\n0\n8\n");
1947 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1948 fprintf(fp, "\t0 0 1\n");
1949 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1950 } else {
1951 fprintf(fp, "\n'%s' ring '%s.d'\n", mod, name);
1952 fprintf(fp, "0\n0\n8\n");
1953 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1954 fprintf(fp, "\t0 0 -1\n");
1955 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1956 }
1957 }
1958
1959
1960 void
1961 putcyl( /* put out a cylinder */
1962 SRCINFO *shp,
1963 FILE *fp,
1964 char *mod,
1965 char *name
1966 )
1967 {
1968 fprintf(fp, "\n'%s' cylinder '%s.c'\n", mod, name);
1969 fprintf(fp, "0\n0\n7\n");
1970 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1971 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1972 fprintf(fp, "\t%g\n", .5*shp->w*meters2out);
1973 }
1974
1975 /* end of of routines to output cylinders and disks */
1976
1977 void
1978 putspheresrc( /* put out a sphere source */
1979 SRCINFO *shp,
1980 FILE *fp,
1981 char *mod,
1982 char *name
1983 )
1984 {
1985 fprintf(fp, "\n'%s' sphere '%s.s'\n", mod, name);
1986 fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out);
1987 }
1988
1989 /* cvdata - convert LM-63 tilt and candela data to Radiance brightdata format
1990 *
1991 * The files created by this routine are intended for use with the Radiance
1992 * "brightdata" material type.
1993 *
1994 * Two types of data are converted; one-dimensional tilt data, which
1995 * is given in polar coordinates, and two-dimensional candela data,
1996 * which is given in spherical co-ordinates.
1997 *
1998 * Return 0 for success, -1 for failure.
1999 *
2000 */
2001 int
2002 cvdata(
2003 FILE *in, /* Input file */
2004 FILE *out, /* Output file */
2005 int ndim, /* Number of dimensions; 1 for
2006 * tilt data, 2 for photometric data. */
2007 int npts[], /* Number of points in each dimension */
2008 double mult, /* Multiple each value by this
2009 * number. For tilt data, always
2010 * 1. For candela values, the
2011 * efficacy of white Radiance light. */
2012 double lim[][2] /* The range of angles in each dimension. */
2013 )
2014 {
2015 double *pt[4]; /* Four is the expected maximum of ndim. */
2016 int i, j;
2017 double val;
2018 int total;
2019
2020 /* Calculate and output the number of data values */
2021 total = 1; j = 0;
2022 for (i = 0; i < ndim; i++)
2023 if (npts[i] > 1) {
2024 total *= npts[i];
2025 j++;
2026 }
2027 fprintf(out, "%d\n", j);
2028
2029 /* Read in the angle values, and note the first and last in
2030 * each dimension, if there is a place to store them. In the
2031 * case of tilt data, there is only one list of angles. In the
2032 * case of candela values, vertical angles appear first, and
2033 * horizontal angles occur second. */
2034 for (i = 0; i < ndim; i++) {
2035 /* Allocate space for the angle values. */
2036 pt[i] = (double *)malloc(npts[i]*sizeof(double));
2037 for (j = 0; j < npts[i]; j++)
2038 if (!scnflt(in, &pt[i][j]))
2039 return(-1);
2040 if (lim != NULL) {
2041 lim[i][0] = pt[i][0];
2042 lim[i][1] = pt[i][npts[i]-1];
2043 }
2044 }
2045
2046 /* Output the angles. If this is candela data, horizontal
2047 * angles output first. There are two cases: the first where
2048 * the angles are evenly spaced, the second where they are
2049 * not.
2050 *
2051 * When the angles are evenly spaced, three numbers are
2052 * output: the first angle, the last angle, and the number of
2053 * angles. When the angles are not evenly spaced, instead
2054 * zero, zero, and the count of angles is given, followed by a
2055 * list of angles. In this case, angles are output four to a line.
2056 */
2057 for (i = ndim-1; i >= 0; i--) {
2058 if (npts[i] > 1) {
2059 /* Determine if the angles are evenly spaces */
2060 for (j = 1; j < npts[i]-1; j++)
2061 if (!FEQ(pt[i][j]-pt[i][j-1],
2062 pt[i][j+1]-pt[i][j]))
2063 break;
2064 /* If they are, output the first angle, the
2065 * last angle, and a count */
2066 if (j == npts[i]-1)
2067 fprintf(out, "%g %g %d\n", pt[i][0], pt[i][j],
2068 npts[i]);
2069 else {
2070 /* otherwise, output 0, 0, and a
2071 * count, followed by the list of
2072 * angles, one to a line. */
2073 fprintf(out, "0 0 %d", npts[i]);
2074 for (j = 0; j < npts[i]; j++) {
2075 if (j%4 == 0)
2076 putc('\n', out);
2077 fprintf(out, "\t%g", pt[i][j]);
2078 }
2079 putc('\n', out);
2080 }
2081 }
2082 /* Free the storage containing the angle values. */
2083 free((void *)pt[i]);
2084 }
2085
2086 /* Finally, read in the data values (candela or multiplier values,
2087 * depending on the part of the file) and output them four to
2088 * a line. */
2089 for (i = 0; i < total; i++) {
2090 if (i%4 == 0)
2091 putc('\n', out);
2092 if (!scnflt(in, &val))
2093 return(-1);
2094 fprintf(out, "\t%g", val*mult);
2095 }
2096 putc('\n', out);
2097 return(0);
2098 }
2099
2100 /* getword - get an LM-63 delimited word from fp
2101 *
2102 * Getword gets a word from an IES file delimited by either white
2103 * space or a comma surrounded by white space. A pointer to the word
2104 * is returned, which will persist only until getword is called again.
2105 * At EOF, return NULL instead.
2106 *
2107 */
2108 char *
2109 getword( /* scan a word from fp */
2110 FILE *fp
2111 )
2112 {
2113 static char wrd[RMAXWORD];
2114 char *cp;
2115 int c;
2116
2117 /* Skip initial spaces */
2118 while (isspace(c=getc(fp)))
2119 ;
2120 /* Get characters to a delimiter or until wrd is full */
2121 for (cp = wrd; c != EOF && cp < wrd+RMAXWORD-1;
2122 *cp++ = c, c = getc(fp))
2123 if (isspace(c) || c == ',') {
2124 /* If we find a delimiter */
2125 /* Gobble up whitespace */
2126 while (isspace(c))
2127 c = getc(fp);
2128 /* If it's not a comma, put the first
2129 * character of the next data item back */
2130 if ((c != EOF) & (c != ','))
2131 ungetc(c, fp);
2132 /* Close out the strimg */
2133 *cp = '\0';
2134 /* return it */
2135 return(wrd);
2136 }
2137 /* If we ran out of space or are at the end of the file,
2138 * return either the word or NULL, as appropriate. */
2139 *cp = '\0';
2140 return(cp > wrd ? wrd : NULL);
2141 }
2142
2143 /* cvtint - convert an IES word to an integer
2144 *
2145 * A pointer to the word is passed in wrd; ip is expected to point to
2146 * an integer. cvtint() will silently truncate a floating point value
2147 * to an integer; "1", "1.0", and "1.5" will all return 1.
2148 *
2149 * cvtint() returns 0 if it fails, 1 if it succeeds.
2150 */
2151 int
2152 cvtint(
2153 int *ip,
2154 char *wrd
2155 )
2156 {
2157 if (wrd == NULL || !isint(wrd))
2158 return(0);
2159 *ip = atoi(wrd);
2160 return(1);
2161 }
2162
2163
2164 /* cvtflt - convert an IES word to a double precision floating-point number
2165 *
2166 * A pointer to the word is passed in wrd; rp is expected to point to
2167 * a double.
2168 *
2169 * cvtflt returns 0 if it fails, 1 if it succeeds.
2170 */
2171 int
2172 cvtflt(
2173 double *rp,
2174 char *wrd
2175 )
2176 {
2177 if (wrd == NULL || !isflt(wrd))
2178 return(0);
2179 *rp = atof(wrd);
2180 return(1);
2181 }
2182
2183 /* cvgeometry - process materials and geometry format luminaire data
2184 *
2185 * The materials and geometry format (MGF) for describing luminaires
2186 * was a part of Radiance that was first adopted and then retracted by
2187 * the IES as part of LM-63. It provides a way of describing
2188 * luminaire geometry similar to the Radiance scene description
2189 * format.
2190 *
2191 * cvgeometry() generates an mgf2rad command and then, if "-g" is given
2192 * on the command line, an oconv command, both of which are then
2193 * executed with the system() function.
2194 *
2195 * The generated commands are:
2196 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
2197 * | xform -s <scale_factor> \
2198 * >> <luminare_scene_description_file
2199 * or:
2200 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
2201 * oconv - > <instance_filename>
2202 */
2203 int
2204 cvgeometry(
2205 char *inpname,
2206 SRCINFO *sinf,
2207 char *outname,
2208 FILE *outfp /* close output file upon return */
2209 )
2210 {
2211 char buf[256];
2212 char *cp;
2213
2214 if (inpname == NULL || !inpname[0]) { /* no geometry file */
2215 fclose(outfp);
2216 return(0);
2217 }
2218 putc('\n', outfp);
2219 strcpy(buf, "mgf2rad "); /* build mgf2rad command */
2220 cp = buf+8;
2221 if (!FEQ(sinf->mult, 1.0)) {
2222 /* if there's an output multiplier, include in the
2223 * mgf2rad command */
2224 sprintf(cp, "-e %f ", sinf->mult);
2225 cp += strlen(cp);
2226 }
2227 /* Include the glow distance for the geometry */
2228 sprintf(cp, "-g %f %s ",
2229 sqrt(sinf->w*sinf->w + sinf->h*sinf->h + sinf->l*sinf->l),
2230 inpname);
2231 cp += strlen(cp);
2232 if (instantiate) { /* instantiate octree */
2233 /* If "-g" is given on the command line, include an
2234 * "oconv" command in the pipe. */
2235 strcpy(cp, "| oconv - > ");
2236 cp += 12;
2237 fullnam(cp,outname,T_OCT);
2238 /* Only update if the input file is newer than the
2239 * output file */
2240 if (fdate(inpname) > fdate(outname) &&
2241 system(buf)) { /* create octree */
2242 fclose(outfp);
2243 return(-1);
2244 }
2245 /* Reference the instance file in the scene description */
2246 fprintf(outfp, "void instance %s_inst\n", outname);
2247 /* If the geometry isn't in meters, scale it appropriately. */
2248 if (!FEQ(meters2out, 1.0))
2249 fprintf(outfp, "3 %s -s %f\n",
2250 libname(buf,outname,T_OCT),
2251 meters2out);
2252 else
2253 fprintf(outfp, "1 %s\n", libname(buf,outname,T_OCT));
2254 /* Close off the "instance" primitive. */
2255 fprintf(outfp, "0\n0\n");
2256 /* And the Radiance scene description. */
2257 fclose(outfp);
2258 } else { /* else append to luminaire file */
2259 if (!FEQ(meters2out, 1.0)) { /* apply scalefactor */
2260 sprintf(cp, "| xform -s %f ", meters2out);
2261 cp += strlen(cp);
2262 }
2263 if (!out2stdout) {
2264 fclose(outfp);
2265 strcpy(cp, ">> "); /* append works for DOS? */
2266 cp += 3;
2267 fullnam(cp,outname,T_RAD);
2268 }
2269 if (system(buf))
2270 return(-1);
2271 }
2272 return(0);
2273 }
2274
2275 /* Set up emacs indentation */
2276 /* Local Variables: */
2277 /* c-file-style: "bsd" */
2278 /* End: */
2279
2280 /* For vim, use ":set tabstop=8 shiftwidth=8" */