ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
Revision: 2.35
Committed: Mon Nov 29 16:07:36 2021 UTC (2 years, 5 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.34: +673 -216 lines
Log Message:
feat(ies2rad): changes and enhancements to ies2rad by Randolph Fritz

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: ies2rad.c,v 2.34 2021/09/30 20:05:09 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 /* makeiesshape - convert IES shape to Radiance shape
1523 *
1524 * Some 34 cases in the various versions of the IES LM-63 standard are
1525 * handled, though some only by approximation. For each case which is
1526 * processed a Radiance box, cylinder, or sphere is selected.
1527 *
1528 * Shapes are categorized by version year of the standard and the
1529 * signs of the LM-63 length, width (depth), and height fields. These
1530 * are combined and converted to an integer, which is then used as the
1531 * argument to switch(). The last two digits of the IES file version
1532 * year are used and the signs of length, width, and height are
1533 * encoded, in that order, as 1 for negative, 2 for zero, and 3 for
1534 * positive. These are then combined into a numeric key by the
1535 * following formula:
1536 *
1537 * version * 1000 + sgn(length) * 100 + sgn(width) * 10 + sgn(height).
1538 *
1539 * Since the 1991 version uses the same encoding as the 1986 version,
1540 * and the 2019 version uses the same encoding as the 2002 version,
1541 * these are collapsed into the earlier years.
1542 *
1543 * In the cases of the switch() statement, further processing takes
1544 * place. Circles and ellipses are distinguished by comparisons. Then
1545 * routines are called to fill out the fields of the shp structure.
1546 *
1547 * As per the conventions of the rest of ies2rad, makeiesshape()
1548 * returns 0 on success and -1 on failure. -1 reflects an error in
1549 * the IES file and is unusual.
1550 *
1551 * By convention, the shape generating routines are always given
1552 * positive values for dimensions and always succeed; all errors are
1553 * caught before they are called. The absolute values of all three
1554 * dimensions are calculated at the beginning of makeiesshape() and
1555 * used throughout the function, this has a low cost and eliminates
1556 * the chance of sign errors.
1557 *
1558 * There is one extension to the ies standard here, devised to
1559 * accomdate wall-mounted fixtures; vertical rectangles, not formally
1560 * supported by any version of LM-63, are treated as boxes.
1561 *
1562 * The code is complicated by the way that earlier versions of the
1563 * standard (1986 and 1991) prioritize width in their discussions, and
1564 * later versions prioritize length. It is not always clear which to
1565 * write first and there is hesitation between the older code which
1566 * invokes makeiesshape() and makeiesshape() itself.
1567 */
1568 int
1569 makeiesshape(SRCINFO *shp, double l, double w, double h) {
1570 int rc = SUCCESS;
1571 int shape = IESNONE;
1572 /* Get the last two digits of the standard year */
1573 int ver = shp->filerev % 100;
1574 /* Make positive versions of all dimensions, for clarity in
1575 * function calls. If you like, read this as l', w', and h'. */
1576 double lp = fabs(l), wp = fabs(w), hp = fabs(h);
1577 int thumbprint;
1578
1579 /* Change 1991 into 1986 and 2019 in 2002 */
1580 switch (ver) {
1581 case 91:
1582 ver = 86;
1583 break;
1584 case 19:
1585 ver = 02;
1586 break;
1587 }
1588
1589 thumbprint =
1590 ver * 1000 + CONVSGN(l) * 100 + CONVSGN(w) * 10 + CONVSGN(h);
1591 switch(thumbprint) {
1592 case 86222: case 95222: case 2222:
1593 shp->iesshape = IESPT;
1594 shp->type = SPHERE;
1595 shp->w = shp->l = shp->h = MINDIM;
1596 break;
1597 case 86332: case 95332: case 2332:
1598 shp->iesshape = IESRECT;
1599 makeboxshape(shp, lp, wp, hp);
1600 break;
1601 case 86333: case 86233: case 86323:
1602 case 95333: case 95233: case 95323:
1603 case 2333: case 2233: case 2323:
1604 shp->iesshape = IESBOX;
1605 makeboxshape(shp, lp, wp, hp);
1606 break;
1607 case 86212: case 95212:
1608 shp->iesshape = IESDISK;
1609 makecylshape(shp, wp, hp);
1610 break;
1611 case 86213:
1612 shp->iesshape = IESVCYL;
1613 makecylshape(shp, wp, hp);
1614 break;
1615 case 86312:
1616 shp->iesshape = IESELLIPSE;
1617 makeecylshape(shp, lp, wp, 0);
1618 break;
1619 case 86313:
1620 shp->iesshape = IESELLIPSOID;
1621 makeelshape(shp, wp, lp, hp);
1622 break;
1623 case 95211:
1624 shp->iesshape = FEQ(lp,hp) ? IESSPHERE : IESNONE;
1625 if (shp->iesshape == IESNONE) {
1626 shp->warn = "makeshape: cannot determine shape\n";
1627 rc = FAIL;
1628 break;
1629 }
1630 shp->type = SPHERE;
1631 shp->w = shp->l = shp->h = wp;
1632 break;
1633 case 95213:
1634 shp->iesshape = IESVCYL;
1635 makecylshape(shp, wp, hp);
1636 break;
1637 case 95321:
1638 shp->iesshape = IESHCYL_PH;
1639 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1640 makeboxshape(shp, lp, wp, hp);
1641 break;
1642 case 95231:
1643 shp->iesshape = IESHCYL_PPH;
1644 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1645 makeboxshape(shp, lp, wp, hp);
1646 break;
1647 case 95133: case 95313:
1648 shp->iesshape = IESVECYL;
1649 makeecylshape(shp, lp, wp, hp);
1650 break;
1651 case 95131: case 95311:
1652 shp->iesshape = IESELLIPSOID;
1653 makeelshape(shp, lp, wp, hp);
1654 break;
1655 case 2112:
1656 shp->iesshape = FEQ(l,w) ? IESDISK : IESELLIPSE;
1657 if (shp->iesshape == IESDISK)
1658 makecylshape(shp, wp, hp);
1659 else
1660 makeecylshape(shp, wp, lp, hp);
1661 break;
1662 case 2113:
1663 shp->iesshape = FEQ(l,w) ? IESVCYL : IESVECYL;
1664 if (shp->iesshape == IESVCYL)
1665 makecylshape(shp, wp, hp);
1666 else
1667 makeecylshape(shp, wp, lp, hp);
1668 break;
1669 case 2111:
1670 shp->iesshape = FEQ(l,w) && FEQ(l,h) ? IESSPHERE : IESELLIPSOID;
1671 if (shp->iesshape == IESSPHERE) {
1672 shp->type = SPHERE;
1673 shp->w = shp->l = shp->h = wp;
1674 }
1675 else
1676 makeelshape(shp, lp, wp, hp);
1677 break;
1678 case 2311:
1679 shp->iesshape = FEQ(w,h) ? IESHCYL_PH : IESHECYL_PH;
1680 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1681 makeboxshape(shp, lp, wp, hp);
1682 break;
1683 case 2131:
1684 shp->iesshape = FEQ(l,h) ? IESHCYL_PPH : IESHECYL_PPH;
1685 shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n";
1686 makeboxshape(shp, lp, wp, hp);
1687 break;
1688 case 2121:
1689 shp->iesshape = FEQ(w,h) ? IESVDISK_PH : IESVEL_PH;
1690 shp->warn = "makeshape: shape is a vertical ellipse, which is not supported.\nmakeshape: replaced with rectangle\n";
1691 makeboxshape(shp, lp, wp, hp);
1692 break;
1693 default:
1694 /* We don't recognize the shape - report an error. */
1695 rc = FAIL;
1696 }
1697 return rc;
1698 }
1699
1700 /* makeillumsphere - create an illum sphere */
1701 int
1702 makeillumsphere(SRCINFO *shp) {
1703 /* If the size is too small or negative, error. */
1704 if (illumrad/meters2out < MINDIM/2.) {
1705 fprintf(stderr, "makeillumsphere: -i argument is too small or negative\n");
1706 return FAIL;
1707 }
1708 shp->isillum = 1;
1709 shp->type = SPHERE;
1710 shp->w = shp->l = shp->h = 2.*illumrad / meters2out;
1711 return SUCCESS;
1712 }
1713
1714 /* makeboxshape - create a box */
1715 void
1716 makeboxshape(SRCINFO *shp, double l, double w, double h) {
1717 shp->type = RECT;
1718 shp->l = fmax(l, MINDIM);
1719 shp->w = fmax(w, MINDIM);
1720 shp->h = fmax(h, .5*MINDIM);
1721 }
1722
1723 /* makecylshape - output a vertical cylinder or disk
1724 *
1725 * If the shape has no height, make it a half-millimeter.
1726 */
1727 void
1728 makecylshape(SRCINFO *shp, double diam, double height) {
1729 shp->type = DISK;
1730 shp->w = shp->l = diam;
1731 shp->h = fmax(height, .5*MINDIM);
1732 }
1733
1734 /* makeelshape - create a substitute for an ellipsoid
1735 *
1736 * Because we don't actually support ellipsoids, and they don't seem
1737 * to be common in actual IES files.
1738 */
1739 void
1740 makeelshape(SRCINFO *shp, double w, double l, double h) {
1741 float avg = (w + l + h) / 3;
1742 float bot = .5 * avg;
1743 float top = 1.5 * avg;
1744
1745 if (bot < w && w < top
1746 && bot < l && l < top
1747 && bot < h && h > top) {
1748 /* it's sort of spherical, replace it with a sphere */
1749 shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with sphere\n";
1750 shp->type = SPHERE;
1751 shp->w = shp->l = shp->h = avg;
1752 } else if (bot < w && w < top
1753 && bot < l && l < top
1754 && h <= .5*MINDIM) {
1755 /* It's flat and sort of circular, replace it
1756 * with a disk. */
1757 shp->warn = "makeshape: shape is an ellipse, which is not supported.\nmakeshape: replaced with disk\n";
1758 makecylshape(shp, w, 0);
1759 } else {
1760 shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with box\n";
1761 makeboxshape(shp, w, l, h);
1762 }
1763 }
1764
1765 /* makeecylshape - create a substitute for an elliptical cylinder or disk */
1766 void
1767 makeecylshape(SRCINFO *shp, double l, double w, double h) {
1768 float avg = (w + l) / 2;
1769 float bot = .5 * avg;
1770 float top = 1.5 * avg;
1771
1772 if (bot < w && w < top
1773 && bot < l && l < top) {
1774 /* It's sort of circular, replace it
1775 * with a circular cylinder. */
1776 shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with circular cylinder\n";
1777 makecylshape(shp, w, h);
1778 } else {
1779 shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with box\n";
1780 makeboxshape(shp, w, l, h);
1781 }
1782 }
1783
1784 void
1785 shapearea(SRCINFO *shp) {
1786 switch (shp->type) {
1787 case RECT:
1788 shp->area = shp->w * shp->l;
1789 break;
1790 case DISK:
1791 case SPHERE:
1792 shp->area = PI/4. * shp->w * shp->w;
1793 break;
1794 }
1795 }
1796
1797 /* Rectangular or box-shaped light source.
1798 *
1799 * putrectsrc, putsides, putrect, and putpoint are used to output the
1800 * Radiance description of a box. The box is centered on the origin
1801 * and has the dimensions given in the IES file. The coordinates
1802 * range from [-1/2*length, -1/2*width, -1/2*height] to [1/2*length,
1803 * 1/2*width, 1/2*height].
1804 *
1805 * The location of the point is encoded in the low-order three bits of
1806 * an integer. If the integer is p, then: bit 0 is (p & 1),
1807 * representing length (x), bit 1 is (p & 2) representing width (y),
1808 * and bit 2 is (p & 4), representing height (z).
1809 *
1810 * Looking down from above (towards -z), the vertices of the box or
1811 * rectangle are numbered so:
1812 *
1813 * 2,6 3,7
1814 * +--------------------------------------+
1815 * | |
1816 * | |
1817 * | |
1818 * | |
1819 * +--------------------------------------+
1820 * 0,4 1,5
1821 *
1822 * The higher number of each pair is above the x-y plane (positive z),
1823 * the lower number is below the x-y plane (negative z.)
1824 *
1825 */
1826
1827 /* putrecsrc - output a rectangle parallel to the x-y plane
1828 *
1829 * Putrecsrc calls out the vertices of a rectangle parallel to the x-y
1830 * plane. The order of the vertices is different for the upper and
1831 * lower rectangles of a box, since a right-hand rule based on the
1832 * order of the vertices is used to determine the surface normal of
1833 * the rectangle, and the surface normal determines the direction the
1834 * light radiated by the rectangle.
1835 *
1836 */
1837 void
1838 putrectsrc(
1839 SRCINFO *shp,
1840 FILE *fp,
1841 char *mod,
1842 char *name,
1843 int up
1844 )
1845 {
1846 if (up)
1847 putrect(shp, fp, mod, name, ".u", 4, 5, 7, 6);
1848 else
1849 putrect(shp, fp, mod, name, ".d", 0, 2, 3, 1);
1850 }
1851
1852 /* putsides - put out sides of box */
1853 void
1854 putsides(
1855 SRCINFO *shp,
1856 FILE *fp,
1857 char *mod,
1858 char *name
1859 )
1860 {
1861 putrect(shp, fp, mod, name, ".1", 0, 1, 5, 4);
1862 putrect(shp, fp, mod, name, ".2", 1, 3, 7, 5);
1863 putrect(shp, fp, mod, name, ".3", 3, 2, 6, 7);
1864 putrect(shp, fp, mod, name, ".4", 2, 0, 4, 6);
1865 }
1866
1867 /* putrect - put out a rectangle
1868 *
1869 * putrect generates the "polygon" primitive which describes a
1870 * rectangle.
1871 */
1872 void
1873 putrect(
1874 SRCINFO *shp,
1875 FILE *fp,
1876 char *mod,
1877 char *name,
1878 char *suffix,
1879 int a,
1880 int b,
1881 int c,
1882 int d
1883 )
1884 {
1885 fprintf(fp, "\n'%s' polygon '%s%s'\n0\n0\n12\n", mod, name, suffix);
1886 putpoint(shp, fp, a);
1887 putpoint(shp, fp, b);
1888 putpoint(shp, fp, c);
1889 putpoint(shp, fp, d);
1890 }
1891
1892 /* putpoint -- output a the coordinates of a vertex
1893 *
1894 * putpoint maps vertex numbers to coordinates and outputs the
1895 * coordinates.
1896 */
1897 void
1898 putpoint(
1899 SRCINFO *shp,
1900 FILE *fp,
1901 int p
1902 )
1903 {
1904 static double mult[2] = {-.5, .5};
1905
1906 fprintf(fp, "\t%g\t%g\t%g\n",
1907 mult[p&1]*shp->l*meters2out,
1908 mult[p>>1&1]*shp->w*meters2out,
1909 mult[p>>2]*shp->h*meters2out);
1910 }
1911
1912 /* End of routines to output a box-shaped light source */
1913
1914 /* Routines to output a cylindrical or disk shaped light source
1915 *
1916 * As with other shapes, the light source is centered on the origin.
1917 * The "ring" and "cylinder" primitives are used.
1918 *
1919 */
1920 void
1921 putdisksrc( /* put out a disk source */
1922 SRCINFO *shp,
1923 FILE *fp,
1924 char *mod,
1925 char *name,
1926 int up
1927 )
1928 {
1929 if (up) {
1930 fprintf(fp, "\n'%s' ring '%s.u'\n", mod, name);
1931 fprintf(fp, "0\n0\n8\n");
1932 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1933 fprintf(fp, "\t0 0 1\n");
1934 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1935 } else {
1936 fprintf(fp, "\n'%s' ring '%s.d'\n", mod, name);
1937 fprintf(fp, "0\n0\n8\n");
1938 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1939 fprintf(fp, "\t0 0 -1\n");
1940 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1941 }
1942 }
1943
1944
1945 void
1946 putcyl( /* put out a cylinder */
1947 SRCINFO *shp,
1948 FILE *fp,
1949 char *mod,
1950 char *name
1951 )
1952 {
1953 fprintf(fp, "\n'%s' cylinder '%s.c'\n", mod, name);
1954 fprintf(fp, "0\n0\n7\n");
1955 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1956 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1957 fprintf(fp, "\t%g\n", .5*shp->w*meters2out);
1958 }
1959
1960 /* end of of routines to output cylinders and disks */
1961
1962 void
1963 putspheresrc( /* put out a sphere source */
1964 SRCINFO *shp,
1965 FILE *fp,
1966 char *mod,
1967 char *name
1968 )
1969 {
1970 fprintf(fp, "\n'%s' sphere '%s.s'\n", mod, name);
1971 fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out);
1972 }
1973
1974 /* cvdata - convert LM-63 tilt and candela data to Radiance brightdata format
1975 *
1976 * The files created by this routine are intended for use with the Radiance
1977 * "brightdata" material type.
1978 *
1979 * Two types of data are converted; one-dimensional tilt data, which
1980 * is given in polar coordinates, and two-dimensional candela data,
1981 * which is given in spherical co-ordinates.
1982 *
1983 * Return 0 for success, -1 for failure.
1984 *
1985 */
1986 int
1987 cvdata(
1988 FILE *in, /* Input file */
1989 FILE *out, /* Output file */
1990 int ndim, /* Number of dimensions; 1 for
1991 * tilt data, 2 for photometric data. */
1992 int npts[], /* Number of points in each dimension */
1993 double mult, /* Multiple each value by this
1994 * number. For tilt data, always
1995 * 1. For candela values, the
1996 * efficacy of white Radiance light. */
1997 double lim[][2] /* The range of angles in each dimension. */
1998 )
1999 {
2000 double *pt[4]; /* Four is the expected maximum of ndim. */
2001 int i, j;
2002 double val;
2003 int total;
2004
2005 /* Calculate and output the number of data values */
2006 total = 1; j = 0;
2007 for (i = 0; i < ndim; i++)
2008 if (npts[i] > 1) {
2009 total *= npts[i];
2010 j++;
2011 }
2012 fprintf(out, "%d\n", j);
2013
2014 /* Read in the angle values, and note the first and last in
2015 * each dimension, if there is a place to store them. In the
2016 * case of tilt data, there is only one list of angles. In the
2017 * case of candela values, vertical angles appear first, and
2018 * horizontal angles occur second. */
2019 for (i = 0; i < ndim; i++) {
2020 /* Allocate space for the angle values. */
2021 pt[i] = (double *)malloc(npts[i]*sizeof(double));
2022 for (j = 0; j < npts[i]; j++)
2023 if (!scnflt(in, &pt[i][j]))
2024 return(-1);
2025 if (lim != NULL) {
2026 lim[i][0] = pt[i][0];
2027 lim[i][1] = pt[i][npts[i]-1];
2028 }
2029 }
2030
2031 /* Output the angles. If this is candela data, horizontal
2032 * angles output first. There are two cases: the first where
2033 * the angles are evenly spaced, the second where they are
2034 * not.
2035 *
2036 * When the angles are evenly spaced, three numbers are
2037 * output: the first angle, the last angle, and the number of
2038 * angles. When the angles are not evenly spaced, instead
2039 * zero, zero, and the count of angles is given, followed by a
2040 * list of angles. In this case, angles are output four to a line.
2041 */
2042 for (i = ndim-1; i >= 0; i--) {
2043 if (npts[i] > 1) {
2044 /* Determine if the angles are evenly spaces */
2045 for (j = 1; j < npts[i]-1; j++)
2046 if (!FEQ(pt[i][j]-pt[i][j-1],
2047 pt[i][j+1]-pt[i][j]))
2048 break;
2049 /* If they are, output the first angle, the
2050 * last angle, and a count */
2051 if (j == npts[i]-1)
2052 fprintf(out, "%g %g %d\n", pt[i][0], pt[i][j],
2053 npts[i]);
2054 else {
2055 /* otherwise, output 0, 0, and a
2056 * count, followed by the list of
2057 * angles, one to a line. */
2058 fprintf(out, "0 0 %d", npts[i]);
2059 for (j = 0; j < npts[i]; j++) {
2060 if (j%4 == 0)
2061 putc('\n', out);
2062 fprintf(out, "\t%g", pt[i][j]);
2063 }
2064 putc('\n', out);
2065 }
2066 }
2067 /* Free the storage containing the angle values. */
2068 free((void *)pt[i]);
2069 }
2070
2071 /* Finally, read in the data values (candela or multiplier values,
2072 * depending on the part of the file) and output them four to
2073 * a line. */
2074 for (i = 0; i < total; i++) {
2075 if (i%4 == 0)
2076 putc('\n', out);
2077 if (!scnflt(in, &val))
2078 return(-1);
2079 fprintf(out, "\t%g", val*mult);
2080 }
2081 putc('\n', out);
2082 return(0);
2083 }
2084
2085 /* getword - get an LM-63 delimited word from fp
2086 *
2087 * Getword gets a word from an IES file delimited by either white
2088 * space or a comma surrounded by white space. A pointer to the word
2089 * is returned, which will persist only until getword is called again.
2090 * At EOF, return NULL instead.
2091 *
2092 */
2093 char *
2094 getword( /* scan a word from fp */
2095 FILE *fp
2096 )
2097 {
2098 static char wrd[RMAXWORD];
2099 char *cp;
2100 int c;
2101
2102 /* Skip initial spaces */
2103 while (isspace(c=getc(fp)))
2104 ;
2105 /* Get characters to a delimiter or until wrd is full */
2106 for (cp = wrd; c != EOF && cp < wrd+RMAXWORD-1;
2107 *cp++ = c, c = getc(fp))
2108 if (isspace(c) || c == ',') {
2109 /* If we find a delimiter */
2110 /* Gobble up whitespace */
2111 while (isspace(c))
2112 c = getc(fp);
2113 /* If it's not a comma, put the first
2114 * character of the next data item back */
2115 if ((c != EOF) & (c != ','))
2116 ungetc(c, fp);
2117 /* Close out the strimg */
2118 *cp = '\0';
2119 /* return it */
2120 return(wrd);
2121 }
2122 /* If we ran out of space or are at the end of the file,
2123 * return either the word or NULL, as appropriate. */
2124 *cp = '\0';
2125 return(cp > wrd ? wrd : NULL);
2126 }
2127
2128 /* cvtint - convert an IES word to an integer
2129 *
2130 * A pointer to the word is passed in wrd; ip is expected to point to
2131 * an integer. cvtint() will silently truncate a floating point value
2132 * to an integer; "1", "1.0", and "1.5" will all return 1.
2133 *
2134 * cvtint() returns 0 if it fails, 1 if it succeeds.
2135 */
2136 int
2137 cvtint(
2138 int *ip,
2139 char *wrd
2140 )
2141 {
2142 if (wrd == NULL || !isint(wrd))
2143 return(0);
2144 *ip = atoi(wrd);
2145 return(1);
2146 }
2147
2148
2149 /* cvtflt - convert an IES word to a double precision floating-point number
2150 *
2151 * A pointer to the word is passed in wrd; rp is expected to point to
2152 * a double.
2153 *
2154 * cvtflt returns 0 if it fails, 1 if it succeeds.
2155 */
2156 int
2157 cvtflt(
2158 double *rp,
2159 char *wrd
2160 )
2161 {
2162 if (wrd == NULL || !isflt(wrd))
2163 return(0);
2164 *rp = atof(wrd);
2165 return(1);
2166 }
2167
2168 /* cvgeometry - process materials and geometry format luminaire data
2169 *
2170 * The materials and geometry format (MGF) for describing luminaires
2171 * was a part of Radiance that was first adopted and then retracted by
2172 * the IES as part of LM-63. It provides a way of describing
2173 * luminaire geometry similar to the Radiance scene description
2174 * format.
2175 *
2176 * cvgeometry() generates an mgf2rad command and then, if "-g" is given
2177 * on the command line, an oconv command, both of which are then
2178 * executed with the system() function.
2179 *
2180 * The generated commands are:
2181 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
2182 * | xform -s <scale_factor> \
2183 * >> <luminare_scene_description_file
2184 * or:
2185 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
2186 * oconv - > <instance_filename>
2187 */
2188 int
2189 cvgeometry(
2190 char *inpname,
2191 SRCINFO *sinf,
2192 char *outname,
2193 FILE *outfp /* close output file upon return */
2194 )
2195 {
2196 char buf[256];
2197 char *cp;
2198
2199 if (inpname == NULL || !inpname[0]) { /* no geometry file */
2200 fclose(outfp);
2201 return(0);
2202 }
2203 putc('\n', outfp);
2204 strcpy(buf, "mgf2rad "); /* build mgf2rad command */
2205 cp = buf+8;
2206 if (!FEQ(sinf->mult, 1.0)) {
2207 /* if there's an output multiplier, include in the
2208 * mgf2rad command */
2209 sprintf(cp, "-e %f ", sinf->mult);
2210 cp += strlen(cp);
2211 }
2212 /* Include the glow distance for the geometry */
2213 sprintf(cp, "-g %f %s ",
2214 sqrt(sinf->w*sinf->w + sinf->h*sinf->h + sinf->l*sinf->l),
2215 inpname);
2216 cp += strlen(cp);
2217 if (instantiate) { /* instantiate octree */
2218 /* If "-g" is given on the command line, include an
2219 * "oconv" command in the pipe. */
2220 strcpy(cp, "| oconv - > ");
2221 cp += 12;
2222 fullnam(cp,outname,T_OCT);
2223 /* Only update if the input file is newer than the
2224 * output file */
2225 if (fdate(inpname) > fdate(outname) &&
2226 system(buf)) { /* create octree */
2227 fclose(outfp);
2228 return(-1);
2229 }
2230 /* Reference the instance file in the scene description */
2231 fprintf(outfp, "void instance %s_inst\n", outname);
2232 /* If the geometry isn't in meters, scale it appropriately. */
2233 if (!FEQ(meters2out, 1.0))
2234 fprintf(outfp, "3 %s -s %f\n",
2235 libname(buf,outname,T_OCT),
2236 meters2out);
2237 else
2238 fprintf(outfp, "1 %s\n", libname(buf,outname,T_OCT));
2239 /* Close off the "instance" primitive. */
2240 fprintf(outfp, "0\n0\n");
2241 /* And the Radiance scene description. */
2242 fclose(outfp);
2243 } else { /* else append to luminaire file */
2244 if (!FEQ(meters2out, 1.0)) { /* apply scalefactor */
2245 sprintf(cp, "| xform -s %f ", meters2out);
2246 cp += strlen(cp);
2247 }
2248 if (!out2stdout) {
2249 fclose(outfp);
2250 strcpy(cp, ">> "); /* append works for DOS? */
2251 cp += 3;
2252 fullnam(cp,outname,T_RAD);
2253 }
2254 if (system(buf))
2255 return(-1);
2256 }
2257 return(0);
2258 }
2259
2260 /* Set up emacs indentation */
2261 /* Local Variables: */
2262 /* c-file-style: "bsd" */
2263 /* End: */
2264
2265 /* For vim, use ":set tabstop=8 shiftwidth=8" */