ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
Revision: 2.33
Committed: Mon Sep 20 02:00:05 2021 UTC (2 years, 7 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.32: +32 -15 lines
Log Message:
docs(ies2rad): Randolph Fritz updated some of the comments for us

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: ies2rad.c,v 2.32 2019/12/28 18:05:14 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 * The ies2rad code uses the "bsd" style. For emacs, this is set up
63 * automatically in the "Local Variables" section at the end of the
64 * file. For vim, use ":set tabstop=8 shiftwidth=8".
65 *
66 * 07Apr90 Greg Ward
67 *
68 * Fixed correction factor for flat sources 29Oct2001 GW
69 * Extensive comments added by Randolph Fritz May2018
70 */
71
72 #include <math.h>
73 #include <ctype.h>
74
75 #include "rtio.h"
76 #include "color.h"
77 #include "paths.h"
78
79 #define PI 3.14159265358979323846
80
81 /* floating comparisons -- floating point numbers within FTINY of each
82 * other are considered equal */
83 #define FTINY 1e-6
84 #define FEQ(a,b) ((a)<=(b)+FTINY&&(a)>=(b)-FTINY)
85
86
87 /* IESNA LM-63 keywords and constants */
88 /* Since 1991, LM-63 files have begun with the magic keyword IESNA */
89 #define MAGICID "IESNA"
90 #define LMAGICID 5
91 /* But newer files start with IESNA:LM-63- */
92 #define MAGICID2 "IESNA:LM-63-"
93 #define LMAGICID2 12
94 /* ies2rad supports the 1986, 1991, and 1995 versions of
95 * LM-63. FIRSTREV describes the first version; LASTREV describes the
96 * 1995 version. */
97 #define FIRSTREV 86
98 #define LASTREV 95
99
100 /* The following definitions support LM-63 file keyword reading and
101 * analysis.
102 *
103 * This section defines two function-like macros: keymatch(i,s), which
104 * checks to see if keyword i matches string s, and checklamp(s),
105 * which checks to see if a string matches the keywords "LAMP" or
106 * "LAMPCAT".
107 *
108 * LM-63-1986 files begin with a list of free-form label lines.
109 * LM-63-1991 files begin with the identifying line "IESNA91" followed
110 * by a list of formatted keywords. LM-63-1995 files begin with the
111 * identifying line "IESNA:LM-63-1995" followed by a list of formatted
112 * keywords.
113 *
114 * The K_* #defines enumerate the keywords used in the different
115 * versions of the file and give them symbolic names.
116 *
117 * The D86, D91, and D95 #defines validate the keywords in the 1986,
118 * 1991, and 1995 versions of the standard, one bit per keyword.
119 * Since the 1986 standard does not use keywords, D86 is zero. The
120 * 1991 standard has 13 keywords, and D91 has the lower 13 bits set.
121 * The 1995 standard has 14 keywords, and D95 has the lower 14 bits
122 * set.
123 *
124 */
125 #define D86 0
126
127 #define K_TST 0
128 #define K_MAN 1
129 #define K_LMC 2
130 #define K_LMN 3
131 #define K_LPC 4
132 #define K_LMP 5
133 #define K_BAL 6
134 #define K_MTC 7
135 #define K_OTH 8
136 #define K_SCH 9
137 #define K_MOR 10
138 #define K_BLK 11
139 #define K_EBK 12
140
141 /* keywords defined in LM-63-1991 */
142 #define D91 ((1L<<13)-1)
143
144 #define K_LMG 13
145
146 /* keywords defined in LM-63-1995 */
147 #define D95 ((1L<<14)-1)
148
149 char k_kwd[][20] = {"TEST", "MANUFAC", "LUMCAT", "LUMINAIRE", "LAMPCAT",
150 "LAMP", "BALLAST", "MAINTCAT", "OTHER", "SEARCH",
151 "MORE", "BLOCK", "ENDBLOCK", "LUMINOUSGEOMETRY"};
152
153 long k_defined[] = {D86, D86, D86, D86, D86, D91, D91, D91, D91, D95};
154
155 int filerev = FIRSTREV;
156
157 #define keymatch(i,s) (k_defined[filerev-FIRSTREV]&1L<<(i) &&\
158 k_match(k_kwd[i],s))
159
160 #define checklamp(s) (!(k_defined[filerev-FIRSTREV]&(1<<K_LMP|1<<K_LPC)) ||\
161 keymatch(K_LMP,s) || keymatch(K_LPC,s))
162
163 /* tilt specs
164 *
165 * This next series of definitions address metal-halide lamps, which
166 * change their brightness depending on the angle at which they are
167 * mounted. The section begins with "TILT=". The constants in this
168 * section are all defined in LM-63.
169 *
170 */
171
172 #define TLTSTR "TILT="
173 #define TLTSTRLEN 5
174 #define TLTNONE "NONE"
175 #define TLTINCL "INCLUDE"
176 #define TLT_VERT 1
177 #define TLT_H0 2
178 #define TLT_H90 3
179
180 /* Constants from LM-63 files */
181
182 /* photometric types
183 *
184 * This enumeration reflects three different methods of measuring the
185 * distribution of light from a luminaire -- "goniophotometry" -- and
186 * the different coordinate systems related to these
187 * goniophotometers. All are described in IES standard LM-75-01.
188 * Earlier and shorter descriptions may be found the LM-63 standards
189 * from 1986, 1991, and 1995.
190 *
191 * ies2rad does not support type A photometry.
192 *
193 * In the 1986 file format, LM-63-86, 1 is used for type C and type A
194 * photometric data.
195 *
196 */
197 #define PM_C 1
198 #define PM_B 2
199 #define PM_A 3
200
201 /* unit types */
202 #define U_FEET 1
203 #define U_METERS 2
204
205 /* string lengths */
206 /* Maximum input line is 256 characters including CR LF at end. */
207 #define MAXLINE 257
208 #define RMAXWORD 76
209
210 /* End of LM-63-related #defines */
211
212 /* file extensions */
213 #define T_RAD ".rad"
214 #define T_DST ".dat"
215 #define T_TLT "%.dat"
216 #define T_OCT ".oct"
217
218 /* shape types
219 * These #defines enumerate the shapes of the Radiance objects which
220 * emit the light.
221 */
222 #define RECT 1
223 #define DISK 2
224 #define SPHERE 3
225
226 /* 1mm. The diameter of a point source luminaire model. Also the minimum
227 * size (in meters) that the luminous opening of a luminaire must have
228 * to be treated as other than a point source. */
229 #define MINDIM .001
230
231 /* feet to meters */
232 /* length_in_meters = length_in_feet * F_M */
233 #define F_M .3048
234
235 /* abspath - return true if a path begins with a directory separator
236 * or a '.' (current directory) */
237 #define abspath(p) (ISDIRSEP((p)[0]) || (p)[0] == '.')
238
239 /* Global variables.
240 *
241 * Mostly, these are a way of communicating command line parameters to
242 * the rest of the program.
243 */
244 static char default_name[] = "default";
245
246 char *libdir = NULL; /* library directory location */
247 char *prefdir = NULL; /* subdirectory */
248 char *lampdat = "lamp.tab"; /* lamp data file */
249
250 double meters2out = 1.0; /* conversion from meters to output */
251 char *lamptype = NULL; /* selected lamp type */
252 char *deflamp = NULL; /* default lamp type */
253 float defcolor[3] = {1.,1.,1.}; /* default lamp color */
254 float *lampcolor = defcolor; /* pointer to current lamp color */
255 double multiplier = 1.0; /* multiplier for all light sources */
256 char units[64] = "meters"; /* output units */
257 int out2stdout = 0; /* put out to stdout r.t. file */
258 int instantiate = 0; /* instantiate geometry */
259 double illumrad = 0.0; /* radius for illum sphere */
260
261 /* This struct describes the Radiance source object */
262 typedef struct {
263 int isillum; /* do as illum */
264 int type; /* RECT, DISK, SPHERE */
265 double mult; /* candela multiplier */
266 double w, l, h; /* width, length, height */
267 double area; /* max. projected area */
268 } SRCINFO; /* a source shape (units=meters) */
269
270 /* A count and pointer to the list of input file names */
271 int gargc; /* global argc */
272 char **gargv; /* global argv */
273
274 /* macros to scan numbers out of IES files
275 *
276 * fp is a file pointer. scnint() places the number in the integer
277 * indicated by ip; scnflt() places the number in the double indicated
278 * by rp. The macros return 1 if successful, 0 if not.
279 *
280 */
281 #define scnint(fp,ip) cvtint(ip,getword(fp))
282 #define scnflt(fp,rp) cvtflt(rp,getword(fp))
283
284 /* The original (1986) version of LM-63 allows decimals points in
285 * integers, so that, for instance, the number of lamps may be written
286 * 3.0 (the number, obviously, must still be an integer.) This
287 * confusing define accommodates that. */
288 #define isint isflt
289
290 /* Function declarations */
291 static int ies2rad(char *inpname, char *outname);
292 static void initlamps(void);
293 static int dosource(SRCINFO *sinf, FILE *in, FILE *out, char *mod, char *name);
294 static int dotilt(FILE *in, FILE *out, char *dir, char *tltspec,
295 char *dfltname, char *tltid);
296 static int cvgeometry(char *inpname, SRCINFO *sinf, char *outname, FILE *outfp);
297 static int cvtint(int *ip, char *wrd);
298 static int cvdata(FILE *in, FILE *out, int ndim, int npts[], double mult,
299 double lim[][2]);
300 static int cvtflt(double *rp, char *wrd);
301 static int makeshape(SRCINFO *shp, double width, double length, double height);
302 static int putsource(SRCINFO *shp, FILE *fp, char *mod, char *name,
303 int dolower, int doupper, int dosides);
304 static void putrectsrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up);
305 static void putsides(SRCINFO *shp, FILE *fp, char *mod, char *name);
306 static void putdisksrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up);
307 static void putspheresrc(SRCINFO *shp, FILE *fp, char *mod, char *name);
308 static void putrect(SRCINFO *shp, FILE *fp, char *mod, char *name, char *suffix,
309 int a, int b, int c, int d);
310 static void putpoint(SRCINFO *shp, FILE *fp, int p);
311 static void putcyl(SRCINFO *shp, FILE *fp, char *mod, char *name);
312 static char * tailtrunc(char *name);
313 static char * filename(char *path);
314 static char * libname(char *path, char *fname, char *suffix);
315 static char * getword(FILE *fp);
316 static char * fullnam(char *path, char *fname, char *suffix);
317
318 /* main - process arguments and run the conversion
319 *
320 * Refer to the man page for details of the arguments.
321 *
322 * Following Unix environment conventions, main() exits with 0 on
323 * success and 1 on failure.
324 *
325 * ies2rad outputs either two or three files for a given IES
326 * file. There is always a .rad file containing Radiance scene
327 * description primitives and a .dat file for the photometric data. If
328 * tilt data is given, that is placed in a separate .dat file. So
329 * ies2rad must have a filename to operate. Sometimes this name is the
330 * input file name, shorn of its extension; sometimes it is given in
331 * the -o option. But an output file name is required for ies2rad to
332 * do its work.
333 *
334 * Older versions of the LM-63 standard allowed inclusion of multiple
335 * luminaires in one IES file; this is not supported by ies2rad.
336 *
337 * This code sometimes does not check to make sure it has not run out
338 * of arguments; this can lead to segmentation faults and perhaps
339 * other errors.
340 *
341 */
342 int
343 main(
344 int argc,
345 char *argv[]
346 )
347 {
348 char *outfile = NULL;
349 int status;
350 char outname[RMAXWORD];
351 double d1;
352 int i;
353
354 /* Scan the options */
355 for (i = 1; i < argc && argv[i][0] == '-'; i++)
356 switch (argv[i][1]) {
357 case 'd': /* dimensions */
358 if (argv[i][2] == '\0')
359 goto badopt;
360 if (argv[i][3] == '\0')
361 d1 = 1.0;
362 else if (argv[i][3] == '/') {
363 d1 = atof(argv[i]+4);
364 if (d1 <= FTINY)
365 goto badopt;
366 } else
367 goto badopt;
368 switch (argv[i][2]) {
369 case 'c': /* centimeters */
370 if (FEQ(d1,10.))
371 strcpy(units,"millimeters");
372 else {
373 strcpy(units,"centimeters");
374 strcat(units,argv[i]+3);
375 }
376 meters2out = 100.*d1;
377 break;
378 case 'm': /* meters */
379 if (FEQ(d1,1000.))
380 strcpy(units,"millimeters");
381 else if (FEQ(d1,100.))
382 strcpy(units,"centimeters");
383 else {
384 strcpy(units,"meters");
385 strcat(units,argv[i]+3);
386 }
387 meters2out = d1;
388 break;
389 case 'i': /* inches */
390 strcpy(units,"inches");
391 strcat(units,argv[i]+3);
392 meters2out = d1*(12./F_M);
393 break;
394 case 'f': /* feet */
395 if (FEQ(d1,12.))
396 strcpy(units,"inches");
397 else {
398 strcpy(units,"feet");
399 strcat(units,argv[i]+3);
400 }
401 meters2out = d1/F_M;
402 break;
403 default:
404 goto badopt;
405 }
406 break;
407 case 'l': /* library directory */
408 libdir = argv[++i];
409 break;
410 case 'p': /* prefix subdirectory */
411 prefdir = argv[++i];
412 break;
413 case 'f': /* lamp data file */
414 lampdat = argv[++i];
415 break;
416 case 'o': /* output file root name */
417 outfile = argv[++i];
418 break;
419 case 's': /* output to stdout */
420 out2stdout = !out2stdout;
421 break;
422 case 'i': /* illum */
423 illumrad = atof(argv[++i]);
424 break;
425 case 'g': /* instantiate geometry? */
426 instantiate = !instantiate;
427 break;
428 case 't': /* override lamp type */
429 lamptype = argv[++i];
430 break;
431 case 'u': /* default lamp type */
432 deflamp = argv[++i];
433 break;
434 case 'c': /* default lamp color */
435 defcolor[0] = atof(argv[++i]);
436 defcolor[1] = atof(argv[++i]);
437 defcolor[2] = atof(argv[++i]);
438 break;
439 case 'm': /* multiplier */
440 multiplier = atof(argv[++i]);
441 break;
442 default:
443 badopt:
444 fprintf(stderr, "%s: bad option: %s\n",
445 argv[0], argv[i]);
446 exit(1);
447 }
448 /* Save pointers to the list of input file names */
449 gargc = i;
450 gargv = argv;
451
452 /* get lamp data (if needed) */
453 initlamps();
454
455 /* convert ies file(s) */
456 /* If an output file name is specified */
457 if (outfile != NULL) {
458 if (i == argc)
459 /* If no input filename is given, use stdin as
460 * the source for the IES file */
461 exit(ies2rad(NULL, outfile) == 0 ? 0 : 1);
462 else if (i == argc-1)
463 /* If exactly one input file name is given, use it. */
464 exit(ies2rad(argv[i], outfile) == 0 ? 0 : 1);
465 else
466 goto needsingle; /* Otherwise, error. */
467 } else if (i >= argc) {
468 /* If an output file and an input file are not give, error. */
469 fprintf(stderr, "%s: missing output file specification\n",
470 argv[0]);
471 exit(1);
472 }
473 /* If no input or output file is given, error. */
474 if (out2stdout && i != argc-1)
475 goto needsingle;
476 /* Otherwise, process each input file in turn. */
477 status = 0;
478 for ( ; i < argc; i++) {
479 tailtrunc(strcpy(outname,filename(argv[i])));
480 if (ies2rad(argv[i], outname) != 0)
481 status = 1;
482 }
483 exit(status);
484 needsingle:
485 fprintf(stderr, "%s: single input file required\n", argv[0]);
486 exit(1);
487 }
488
489 /* Initlamps -- If necessary, read lamp data table */
490 void
491 initlamps(void) /* set up lamps */
492 {
493 float *lcol;
494 int status;
495
496 /* If the lamp name is set to default, don't bother to read
497 * the lamp data table. */
498 if (lamptype != NULL && !strcmp(lamptype, default_name) &&
499 deflamp == NULL)
500 return;
501
502 if ((status = loadlamps(lampdat)) < 0) /* Load the lamp data table */
503 exit(1); /* Exit if problems
504 * with the file. */
505 if (status == 0) {
506 /* If can't open the file, just use the standard default lamp */
507 fprintf(stderr, "%s: warning - no lamp data\n", lampdat);
508 lamptype = default_name;
509 return;
510 }
511 if (deflamp != NULL) {
512 /* Look up the specified default lamp type */
513 if ((lcol = matchlamp(deflamp)) == NULL)
514 /* If it can't be found, use the default */
515 fprintf(stderr,
516 "%s: warning - unknown default lamp type\n",
517 deflamp);
518 else
519 /* Use the selected default lamp color */
520 copycolor(defcolor, lcol);
521 }
522 /* If a lamp type is specified and can be found, use it, and
523 * release the lamp data table memory; it won't be needed any more. */
524 if (lamptype != NULL) {
525 if (strcmp(lamptype, default_name)) {
526 if ((lcol = matchlamp(lamptype)) == NULL) {
527 fprintf(stderr,
528 "%s: warning - unknown lamp type\n",
529 lamptype);
530 lamptype = default_name;
531 } else
532 copycolor(defcolor, lcol);
533 }
534 freelamps(); /* all done with data */
535 }
536 /* else keep lamp data */
537 }
538
539 /*
540 * File path operations
541 *
542 * These provide file path operations that operate on both MS-Windows
543 * and *nix. They will ignore and pass, but will not necessarily
544 * process correctly, Windows drive letters. Paths including Windows
545 * UNC network names (\\server\folder\file) may also cause problems.
546 *
547 */
548
549 /*
550 * stradd()
551 *
552 * Add a string to the end of a string, optionally concatenating a
553 * file path separator character. If the path already ends with a
554 * path separator, no additional separator is appended.
555 *
556 */
557 char *
558 stradd( /* add a string at dst */
559 char *dst,
560 char *src,
561 int sep
562 )
563 {
564 if (src && *src) {
565 do
566 *dst++ = *src++;
567 while (*src);
568 if (sep && dst[-1] != sep)
569 *dst++ = sep;
570 }
571 *dst = '\0';
572 return(dst);
573 }
574
575 /*
576 * fullnam () - return a usable path name for an output file
577 */
578 char *
579 fullnam(
580 char *path, /* The base directory path */
581 char *fname, /* The file name */
582 char *suffix /* A suffix, which usually contains
583 * a file name extension. */
584 )
585 {
586 extern char *prefdir;
587 extern char *libdir;
588
589 if (prefdir != NULL && abspath(prefdir))
590 /* If the subdirectory path is absolute or '.', just
591 * concatenate the names together */
592 libname(path, fname, suffix);
593 else if (abspath(fname))
594 /* If there is no subdirectory, and the file name is
595 * an absolute path or '.', concatenate the path,
596 * filename, and suffix. */
597 strcpy(stradd(path, fname, 0), suffix);
598 else
599 /* If the file name is relative, concatenate path,
600 * library directory, directory separator, file name,
601 * and suffix. */
602 libname(stradd(path, libdir, DIRSEP), fname, suffix);
603
604 return(path);
605 }
606
607
608 /*
609 * libname - convert a file name to a path
610 */
611 char *
612 libname(
613 char *path, /* The base directory path */
614 char *fname, /* The file name */
615 char *suffix /* A suffix, which usually contains
616 * a file name extension. */
617 )
618 {
619 extern char *prefdir; /* The subdirectory where the file
620 * name is stored. */
621
622 if (abspath(fname))
623 /* If the file name begins with '/' or '.', combine
624 * it with the path and attach the suffix */
625 strcpy(stradd(path, fname, 0), suffix);
626 else
627 /* If the file name is relative, attach it to the
628 * path, include the subdirectory, and append the suffix. */
629 strcpy(stradd(stradd(path, prefdir, DIRSEP), fname, 0), suffix);
630
631 return(path);
632 }
633
634 /* filename - find the base file name in a buffer containing a path
635 *
636 * The pointer is to a character within the buffer, not a string in itself;
637 * it will become invalid when the buffer is freed.
638 *
639 */
640 char *
641 filename(
642 char *path
643 )
644 {
645 char *cp;
646
647 for (cp = path; *path; path++)
648 if (ISDIRSEP(*path))
649 cp = path+1;
650 return(cp);
651 }
652
653
654 /* filetrunc() - return the directory portion of a path
655 *
656 * The path is passed in in a pointer to a buffer; a null character is
657 * inserted in the buffer after the last directory separator
658 *
659 */
660 char *
661 filetrunc(
662 char *path
663 )
664 {
665 char *p1, *p2;
666
667 for (p1 = p2 = path; *p2; p2++)
668 if (ISDIRSEP(*p2))
669 p1 = p2;
670 if (p1 == path && ISDIRSEP(*p1))
671 p1++;
672 *p1 = '\0';
673 return(path);
674 }
675
676 /* tailtrunc() - trim a file name extension, if any.
677 *
678 * The file name is passed in in a buffer indicated by *name; the
679 * period which begins the extension is replaced with a 0 byte.
680 */
681 char *
682 tailtrunc(
683 char *name
684 )
685 {
686 char *p1, *p2;
687
688 /* Skip leading periods */
689 for (p1 = filename(name); *p1 == '.'; p1++)
690 ;
691 /* Find the last period in a file name */
692 p2 = NULL;
693 for ( ; *p1; p1++)
694 if (*p1 == '.')
695 p2 = p1;
696 /* If present, trim the filename at that period */
697 if (p2 != NULL)
698 *p2 = '\0';
699 return(name);
700 }
701
702 /* blanktrunc() - trim spaces at the end of a string
703 *
704 * the string is passed in a character array, which is modified
705 */
706 void
707 blanktrunc(
708 char *s
709 )
710 {
711 char *cp;
712
713 for (cp = s; *cp; cp++)
714 ;
715 while (cp-- > s && isspace(*cp))
716 ;
717 *++cp = '\0';
718 }
719
720 /* k_match - return true if keyword matches header line */
721 int
722 k_match(
723 char *kwd, /* keyword */
724 char *hdl /* header line */
725 )
726 {
727 /* Skip leading spaces */
728 while (isspace(*hdl))
729 hdl++;
730 /* The line has to begin with '[' */
731 if (*hdl++ != '[')
732 return(0);
733 /* case-independent keyword match */
734 while (toupper(*hdl) == *kwd++)
735 if (!*hdl++)
736 return(0);
737 /* If we have come to the end of the keyword, and the keyword
738 * at the beginning of the matched line is terminated with
739 * ']', return 1 */
740 return(!kwd[-1] & (*hdl == ']'));
741 }
742
743 /* keyargs - return the argument of a keyword, without leading spaces
744 *
745 * keyargs is passed a pointer to a buffer; it returns a pointer to
746 * where the argument starts in the buffer
747 *
748 */
749 char *
750 keyargs(
751 char *hdl /* header line */
752 )
753 {
754 while (*hdl && *hdl++ != ']')
755 ;
756 while (isspace(*hdl))
757 hdl++;
758 return(hdl);
759 }
760
761
762 /* putheader - output the header of the .rad file
763 *
764 * Header is:
765 * # <file> <file> <file> (all files from input line)
766 * # Dimensions in [feet,meters,etc.]
767 *
768 * ??? Is listing all the input file names correct behavior?
769 *
770 */
771 void
772
773 putheader(
774 FILE *out
775 )
776 {
777 int i;
778
779 putc('#', out);
780 for (i = 0; i < gargc; i++) {
781 putc(' ', out);
782 fputs(gargv[i], out);
783 }
784 fputs("\n# Dimensions in ", out);
785 fputs(units, out);
786 putc('\n', out);
787 }
788
789 /* ies2rad - convert an IES LM-63 file to a Radiance light source desc.
790 *
791 * Return -1 in case of failure, 0 in case of success.
792 *
793 * The file version recognition is confused and will treat 1995 and
794 * 2002 version files as 1986 version files.
795 *
796 */
797 int
798 ies2rad( /* convert IES file */
799 char *inpname,
800 char *outname
801 )
802 {
803 SRCINFO srcinfo;
804 char buf[MAXLINE], tltid[RMAXWORD];
805 char geomfile[128];
806 FILE *inpfp, *outfp;
807 int lineno = 0;
808
809 /* Open input and output files */
810 geomfile[0] = '\0';
811 srcinfo.isillum = 0;
812 if (inpname == NULL) {
813 inpname = "<stdin>";
814 inpfp = stdin;
815 } else if ((inpfp = fopen(inpname, "r")) == NULL) {
816 perror(inpname);
817 return(-1);
818 }
819 if (out2stdout)
820 outfp = stdout;
821 else if ((outfp = fopen(fullnam(buf,outname,T_RAD), "w")) == NULL) {
822 perror(buf);
823 fclose(inpfp);
824 return(-1);
825 }
826
827 /* Output the output file header */
828 putheader(outfp);
829
830 /* If the lamp type wasn't given on the command line, mark
831 * the lamp color as missing */
832 if (lamptype == NULL)
833 lampcolor = NULL;
834
835 /* Read the input file header, copying lines to the .rad file
836 * and looking for a lamp type. Stop at EOF or a line
837 * beginning with "TILT=". */
838 while (fgets(buf,sizeof(buf),inpfp) != NULL
839 && strncmp(buf,TLTSTR,TLTSTRLEN)) {
840 blanktrunc(buf); /* Trim trailing spaces, CR, LF. */
841 if (!buf[0]) /* Skip blank lines */
842 continue;
843 /* increment the header line count, and check for the
844 * "TILT=" line that terminates the header */
845 if (!lineno++) { /* first line may be magic */
846 if (!strncmp(buf, MAGICID2, LMAGICID2))
847 filerev = atoi(buf+LMAGICID2) - 1900;
848 else if (!strncmp(buf, MAGICID, LMAGICID))
849 filerev = atoi(buf+LMAGICID);
850 if (filerev < FIRSTREV)
851 filerev = FIRSTREV;
852 else if (filerev > LASTREV)
853 filerev = LASTREV;
854 }
855 /* Output the header line as a comment in the .rad file. */
856 fputs("#<", outfp);
857 fputs(buf, outfp);
858 putc('\n', outfp);
859
860 /* If the header line is a keyword line (file version
861 * later than 1986 and begins with '['), check a lamp
862 * in the "[LAMP]" and "[LAMPCAT]" keyword lines;
863 * otherwise check all lines. */
864 if (lampcolor == NULL && checklamp(buf))
865 lampcolor = matchlamp(*sskip2(buf,0) == '[' ?
866 keyargs(buf) : buf );
867 /* Look for a materials and geometry file in the keywords. */
868 if (keymatch(K_LMG, buf)) {
869 strcpy(geomfile, inpname);
870 strcpy(filename(geomfile), keyargs(buf));
871 srcinfo.isillum = 1;
872 }
873 }
874
875 /* Done reading header information. If a lamp color still
876 * hasn't been found, print a warning and use the default
877 * color; if a lamp type hasn't been found, but a color has
878 * been specified, used the specified color. */
879 if (lampcolor == NULL) {
880 fprintf(stderr, "%s: warning - no lamp type\n", inpname);
881 fputs("# Unknown lamp type (used default)\n", outfp);
882 lampcolor = defcolor;
883 } else if (lamptype == NULL)
884 fprintf(outfp,"# CIE(x,y) = (%f,%f)\n# Depreciation = %.1f%%\n",
885 lampcolor[3], lampcolor[4], 100.*lampcolor[5]);
886
887 /* If the file ended before a "TILT=" line, that's an error. */
888 if (feof(inpfp)) {
889 fprintf(stderr, "%s: not in IES format\n", inpname);
890 goto readerr;
891 }
892
893 /* Process the tilt section of the file. */
894 /* Get the tilt file name, or the keyword "INCLUDE". */
895 atos(tltid, RMAXWORD, buf+TLTSTRLEN);
896 if (inpfp == stdin)
897 buf[0] = '\0';
898 else
899 filetrunc(strcpy(buf, inpname));
900 /* Process the tilt data. */
901 if (dotilt(inpfp, outfp, buf, tltid, outname, tltid) != 0) {
902 fprintf(stderr, "%s: bad tilt data\n", inpname);
903 goto readerr;
904 }
905
906 /* Process the luminaire data. */
907 if (dosource(&srcinfo, inpfp, outfp, tltid, outname) != 0) {
908 fprintf(stderr, "%s: bad luminaire data\n", inpname);
909 goto readerr;
910 }
911
912 /* Close the input file */
913 fclose(inpfp);
914
915 /* Process an MGF file, if present. cvgeometry() closes outfp. */
916 if (cvgeometry(geomfile, &srcinfo, outname, outfp) != 0) {
917 fprintf(stderr, "%s: bad geometry file\n", geomfile);
918 return(-1);
919 }
920 return(0);
921
922 readerr:
923 /* If there is an error reading the file, close the input and
924 * .rad output files, and delete the .rad file, returning -1. */
925 fclose(inpfp);
926 fclose(outfp);
927 unlink(fullnam(buf,outname,T_RAD));
928 return(-1);
929 }
930
931 /* dotilt -- process tilt data
932 *
933 * Generate a brightdata primitive which describes the effect of
934 * luminaire tilt on luminaire output and return its identifier in tltid.
935 *
936 * Tilt data (if present) is given as a number 1, 2, or 3, which
937 * specifies the orientation of the lamp within the luminaire, a
938 * number, n, of (angle, multiplier) pairs, followed by n angles and n
939 * multipliers.
940 *
941 * returns 0 for success, -1 for error
942 */
943 int
944 dotilt(
945 FILE *in,
946 FILE *out,
947 char *dir,
948 char *tltspec,
949 char *dfltname,
950 char *tltid
951 )
952 {
953 int nangles, tlt_type;
954 double minmax[1][2];
955 char buf[PATH_MAX], tltname[RMAXWORD];
956 FILE *datin, *datout;
957
958 /* Decide where the tilt data is; if the luminaire description
959 * doesn't have a tilt section, set the identifier to "void". */
960 if (!strcmp(tltspec, TLTNONE)) {
961 /* If the line is "TILT=NONE", set the input file
962 * pointer to NULL and the identifier to "void". */
963 datin = NULL;
964 strcpy(tltid, "void");
965 } else if (!strcmp(tltspec, TLTINCL)) {
966 /* If the line is "TILT=INCLUDE" use the main IES
967 * file as the source of tilt data. */
968 datin = in;
969 strcpy(tltname, dfltname);
970 } else {
971 /* If the line is "TILE=<filename>", use that file
972 * name as the source of tilt data. */
973 if (ISDIRSEP(tltspec[0]))
974 strcpy(buf, tltspec);
975 else
976 strcpy(stradd(buf, dir, DIRSEP), tltspec);
977 if ((datin = fopen(buf, "r")) == NULL) {
978 perror(buf);
979 return(-1);
980 }
981 tailtrunc(strcpy(tltname,filename(tltspec)));
982 }
983 /* If tilt data is present, read, process, and output it. */
984 if (datin != NULL) {
985 /* Try to open the output file */
986 if ((datout = fopen(fullnam(buf,tltname,T_TLT),"w")) == NULL) {
987 perror(buf);
988 if (datin != in)
989 fclose(datin);
990 return(-1);
991 }
992 /* Try to copy the tilt data to the tilt data file */
993 if (!scnint(datin,&tlt_type) || !scnint(datin,&nangles)
994 || cvdata(datin,datout,1,&nangles,1.,minmax) != 0) {
995 fprintf(stderr, "%s: data format error\n", tltspec);
996 fclose(datout);
997 if (datin != in)
998 fclose(datin);
999 unlink(fullnam(buf,tltname,T_TLT));
1000 return(-1);
1001 }
1002 fclose(datout);
1003 if (datin != in)
1004 fclose(datin);
1005
1006 /* Generate the identifier of the brightdata; the filename
1007 * with "_tilt" appended. */
1008 strcat(strcpy(tltid, filename(tltname)), "_tilt");
1009 /* Write out the brightdata primitive */
1010 fprintf(out, "\nvoid brightdata %s\n", tltid);
1011 libname(buf,tltname,T_TLT);
1012 /* Generate the tilt description */
1013 switch (tlt_type) {
1014 case TLT_VERT:
1015 /* The lamp is mounted vertically; either
1016 * base up or base down. */
1017 fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1018 minmax[0][1]>90.+FTINY ? "tilt_ang" : "tilt_ang2");
1019 break;
1020 case TLT_H0:
1021 /* The lamp is mounted horizontally and
1022 * rotates but does not tilt when the
1023 * luminaire is tilted. */
1024 fprintf(out, "6 noop %s tilt.cal %s -rz 90\n", buf,
1025 minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1026 break;
1027 case TLT_H90:
1028 /* The lamp is mounted horizontally, and
1029 * tilts when the luminaire is tilted. */
1030 fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1031 minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1032 break;
1033 default:
1034 /* otherwise, this is a bad IES file */
1035 fprintf(stderr,
1036 "%s: illegal lamp to luminaire geometry (%d)\n",
1037 tltspec, tlt_type);
1038 return(-1);
1039 }
1040 /* And finally output the numbers of integer and real
1041 * arguments, of which there are none. */
1042 fprintf(out, "0\n0\n");
1043 }
1044 return(0);
1045 }
1046
1047 /* dosource -- create the source and distribution primitives */
1048 int
1049 dosource(
1050 SRCINFO *sinf,
1051 FILE *in,
1052 FILE *out,
1053 char *mod,
1054 char *name
1055 )
1056 {
1057 char buf[PATH_MAX], id[RMAXWORD];
1058 FILE *datout;
1059 double mult, bfactor, pfactor, width, length, height, wattage;
1060 double bounds[2][2];
1061 int nangles[2], pmtype, unitype;
1062 double d1;
1063 int doupper, dolower, dosides;
1064
1065 /* Read in the luminaire description header */
1066 if (!isint(getword(in)) || !isflt(getword(in)) || !scnflt(in,&mult)
1067 || !scnint(in,&nangles[0]) || !scnint(in,&nangles[1])
1068 || !scnint(in,&pmtype) || !scnint(in,&unitype)
1069 || !scnflt(in,&width) || !scnflt(in,&length)
1070 || !scnflt(in,&height) || !scnflt(in,&bfactor)
1071 || !scnflt(in,&pfactor) || !scnflt(in,&wattage)) {
1072 fprintf(stderr, "dosource: bad lamp specification\n");
1073 return(-1);
1074 }
1075 /* Type A photometry is not supported */
1076 if (pmtype != PM_C && pmtype != PM_B) {
1077 fprintf(stderr, "dosource: unsupported photometric type (%d)\n",
1078 pmtype);
1079 return(-1);
1080 }
1081
1082 /* Multiplier = the multiplier from the -m option, times the
1083 * multiplier from the IES file, times the ballast factor,
1084 * times the "ballast lamp photometric factor," which was part
1085 * of the 1986 and 1991 standards. In the 1995 standard, it is
1086 * always supposed to be 1. */
1087 sinf->mult = multiplier*mult*bfactor*pfactor;
1088
1089 /* If the count of angles is wrong, raise an error and quit. */
1090 if (nangles[0] < 2 || nangles[1] < 1) {
1091 fprintf(stderr, "dosource: too few measured angles\n");
1092 return(-1);
1093 }
1094
1095 /* For internal computation, convert units to meters. */
1096 if (unitype == U_FEET) {
1097 width *= F_M;
1098 length *= F_M;
1099 height *= F_M;
1100 }
1101
1102 /* Make decisions about the shape of the light source
1103 * geometry, and store them in sinf. */
1104 if (makeshape(sinf, width, length, height) != 0) {
1105 fprintf(stderr, "dosource: illegal source dimensions");
1106 return(-1);
1107 }
1108
1109 /* Copy the candela values into a Radiance data file. */
1110 if ((datout = fopen(fullnam(buf,name,T_DST), "w")) == NULL) {
1111 perror(buf);
1112 return(-1);
1113 }
1114 if (cvdata(in, datout, 2, nangles, 1./WHTEFFICACY, bounds) != 0) {
1115 fprintf(stderr, "dosource: bad distribution data\n");
1116 fclose(datout);
1117 unlink(fullnam(buf,name,T_DST));
1118 return(-1);
1119 }
1120 fclose(datout);
1121
1122 /* Output explanatory comment */
1123 fprintf(out, "# %g watt luminaire, lamp*ballast factor = %g\n",
1124 wattage, bfactor*pfactor);
1125 /* Output distribution "brightdata" primitive. Start handling
1126 the various cases of symmetry of the distribution. */
1127 strcat(strcpy(id, filename(name)), "_dist");
1128 fprintf(out, "\n%s brightdata %s\n", mod, id);
1129 if (nangles[1] < 2)
1130 fprintf(out, "4 ");
1131 else if (pmtype == PM_B)
1132 fprintf(out, "5 ");
1133 else if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1134 fprintf(out, "7 ");
1135 else
1136 fprintf(out, "5 ");
1137
1138 /* If the generated source geometry will be a box, a flat
1139 * rectangle, or a disk figure out if it needs a top, a
1140 * bottom, and/or sides. */
1141 dolower = (bounds[0][0] < 90.-FTINY); /* Bottom */
1142 doupper = (bounds[0][1] > 90.+FTINY); /* Top */
1143 dosides = (doupper & dolower && sinf->h > MINDIM); /* Sides */
1144
1145 /* Select the appropriate function and parameters from source.cal */
1146 fprintf(out, "%s %s source.cal ",
1147 sinf->type==SPHERE ? "corr" :
1148 !dosides ? "flatcorr" :
1149 sinf->type==DISK ? "cylcorr" : "boxcorr",
1150 libname(buf,name,T_DST));
1151 if (pmtype == PM_B) {
1152 if (FEQ(bounds[1][0],0.))
1153 fprintf(out, "srcB_horiz2 ");
1154 else
1155 fprintf(out, "srcB_horiz ");
1156 fprintf(out, "srcB_vert ");
1157 } else /* pmtype == PM_C */ {
1158 if (nangles[1] >= 2) {
1159 d1 = bounds[1][1] - bounds[1][0];
1160 if (d1 <= 90.+FTINY)
1161 fprintf(out, "src_phi4 ");
1162 else if (d1 <= 180.+FTINY) {
1163 if (FEQ(bounds[1][0],90.))
1164 fprintf(out, "src_phi2+90 ");
1165 else
1166 fprintf(out, "src_phi2 ");
1167 } else
1168 fprintf(out, "src_phi ");
1169 fprintf(out, "src_theta ");
1170 if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1171 fprintf(out, "-rz -90 ");
1172 } else
1173 fprintf(out, "src_theta ");
1174 }
1175 /* finish the brightdata primitive with appropriate data */
1176 if (!dosides || sinf->type == SPHERE)
1177 fprintf(out, "\n0\n1 %g\n", sinf->mult/sinf->area);
1178 else if (sinf->type == DISK)
1179 fprintf(out, "\n0\n3 %g %g %g\n", sinf->mult,
1180 sinf->w, sinf->h);
1181 else
1182 fprintf(out, "\n0\n4 %g %g %g %g\n", sinf->mult,
1183 sinf->l, sinf->w, sinf->h);
1184 /* Brightdata primitive written out. */
1185
1186 /* Finally, output the descriptions of the actual radiant
1187 * surfaces. */
1188 if (putsource(sinf, out, id, filename(name),
1189 dolower, doupper, dosides) != 0)
1190 return(-1);
1191 return(0);
1192 }
1193
1194 /* putsource - output the actual light emitting geometry
1195 *
1196 * Three kinds of geometry are produced: rectangles and boxes, disks
1197 * ("ring" primitive, but the radius of the hole is always zero) and
1198 * cylinders, and spheres.
1199 */
1200 int
1201 putsource(
1202 SRCINFO *shp,
1203 FILE *fp,
1204 char *mod,
1205 char *name,
1206 int dolower,
1207 int doupper,
1208 int dosides
1209 )
1210 {
1211 char lname[RMAXWORD];
1212
1213 /* First, describe the light. If a materials and geometry
1214 * file is given, generate an illum instead. */
1215 strcat(strcpy(lname, name), "_light");
1216 fprintf(fp, "\n%s %s %s\n", mod,
1217 shp->isillum ? "illum" : "light", lname);
1218 fprintf(fp, "0\n0\n3 %g %g %g\n",
1219 lampcolor[0], lampcolor[1], lampcolor[2]);
1220 switch (shp->type) {
1221 case RECT:
1222 /* Output at least one rectangle. If light is radiated
1223 * from the sides of the luminaire, output rectangular
1224 * sides as well. */
1225 if (dolower)
1226 putrectsrc(shp, fp, lname, name, 0);
1227 if (doupper)
1228 putrectsrc(shp, fp, lname, name, 1);
1229 if (dosides)
1230 putsides(shp, fp, lname, name);
1231 break;
1232 case DISK:
1233 /* Output at least one disk. If light is radiated from
1234 * the sides of luminaire, output a cylinder as well. */
1235 if (dolower)
1236 putdisksrc(shp, fp, lname, name, 0);
1237 if (doupper)
1238 putdisksrc(shp, fp, lname, name, 1);
1239 if (dosides)
1240 putcyl(shp, fp, lname, name);
1241 break;
1242 case SPHERE:
1243 /* Output a sphere. */
1244 putspheresrc(shp, fp, lname, name);
1245 break;
1246 }
1247 return(0);
1248 }
1249
1250 /* makeshape -- decide what shape will be used
1251 *
1252 * Makeshape decides what Radiance geometry will be used to represent
1253 * the light source and stores information about it in shp.
1254 *
1255 * The various versions of the IES LM-63 standard give a "luminous
1256 * opening" (really a crude shape) a width, a length (or depth), and a
1257 * height. If all three values are positive, they describe a box. If
1258 * they are all zero, they describe a point. Various combinations of
1259 * negative values are used to denote disks, circular or elliptical
1260 * cylinders, spheres, and ellipsoids. This encoding differs from
1261 * version to version of LM-63.
1262 *
1263 * Ies2rad simplifies this, reducing the geometry of LM-63 files to
1264 * three forms which can be easily represented by Radiance primitives:
1265 * boxes (RECT), cylinders or disks (DISK), and spheres (SPHERE.) A
1266 * point is necessarily represented by a small sphere, since a point
1267 * is not a Radiance object.
1268 */
1269 int
1270 makeshape(
1271 SRCINFO *shp,
1272 double width,
1273 double length,
1274 double height
1275 )
1276 {
1277 /* Categorize the shape */
1278 if (illumrad/meters2out >= MINDIM/2.) {
1279 /* If the -i command line option is used, output an
1280 * "illum" sphere whose radius is given by the
1281 * argument to -i. */
1282 shp->isillum = 1;
1283 shp->type = SPHERE;
1284 shp->w = shp->l = shp->h = 2.*illumrad / meters2out;
1285 /* Otherwise, use the dimensions in the IES file */
1286 } else if (width < MINDIM) {
1287 width = -width;
1288 if (width < MINDIM) {
1289 /* If the LM-63 width is zero, assume a point
1290 * source is described. Output a small
1291 * sphere. */
1292 shp->type = SPHERE;
1293 shp->w = shp->l = shp->h = MINDIM;
1294 } else if (height < .5*width) {
1295 /* The width is negative and the height is
1296 * less than half the width. Treat the
1297 * luminous opening as a disk or short
1298 * vertical cylinder. Disks will be
1299 * represented as nearly flat cylinders of
1300 * MINDIM/2 height. */
1301 shp->type = DISK;
1302 shp->w = shp->l = width;
1303 if (height >= MINDIM)
1304 shp->h = height;
1305 else
1306 shp->h = .5*MINDIM;
1307 } else {
1308 /* Treat a tall cylinder as a sphere. */
1309 shp->type = SPHERE;
1310 shp->w = shp->l = shp->h = width;
1311 }
1312 } else {
1313 /* The width is positive. The luminous opening is a
1314 box or simple rectangle. */
1315 shp->type = RECT;
1316 shp->w = width;
1317 if (length >= MINDIM)
1318 shp->l = length;
1319 else
1320 shp->l = MINDIM;
1321 if (height >= MINDIM)
1322 shp->h = height;
1323 else
1324 shp->h = .5*MINDIM;
1325 }
1326
1327 /* Done choosing the shape; calculate its area in the x-y plane. */
1328 switch (shp->type) {
1329 case RECT:
1330 shp->area = shp->w * shp->l;
1331 break;
1332 case DISK:
1333 case SPHERE:
1334 shp->area = PI/4. * shp->w * shp->w;
1335 break;
1336 }
1337 return(0);
1338 }
1339
1340 /* Rectangular or box-shaped light source.
1341 *
1342 * putrectsrc, putsides, putrect, and putpoint are used to output the
1343 * Radiance description of a box. The box is centered on the origin
1344 * and has the dimensions given in the IES file. The coordinates
1345 * range from [-1/2*length, -1/2*width, -1/2*height] to [1/2*length,
1346 * 1/2*width, 1/2*height].
1347 *
1348 * The location of the point is encoded in the low-order three bits of
1349 * an integer. If the integer is p, then: bit 0 is (p & 1),
1350 * representing length (x), bit 1 is (p & 2) representing width (y),
1351 * and bit 2 is (p & 4), representing height (z).
1352 *
1353 * Looking down from above (towards -z), the vertices of the box or
1354 * rectangle are numbered so:
1355 *
1356 * 2,6 3,7
1357 * +--------------------------------------+
1358 * | |
1359 * | |
1360 * | |
1361 * | |
1362 * +--------------------------------------+
1363 * 0,4 1,5
1364 *
1365 * The higher number of each pair is above the x-y plane (positive z),
1366 * the lower number is below the x-y plane (negative z.)
1367 *
1368 */
1369
1370 /* putrecsrc - output a rectangle parallel to the x-y plane
1371 *
1372 * Putrecsrc calls out the vertices of a rectangle parallel to the x-y
1373 * plane. The order of the vertices is different for the upper and
1374 * lower rectangles of a box, since a right-hand rule based on the
1375 * order of the vertices is used to determine the surface normal of
1376 * the rectangle, and the surface normal determines the direction the
1377 * light radiated by the rectangle.
1378 *
1379 */
1380 void
1381 putrectsrc(
1382 SRCINFO *shp,
1383 FILE *fp,
1384 char *mod,
1385 char *name,
1386 int up
1387 )
1388 {
1389 if (up)
1390 putrect(shp, fp, mod, name, ".u", 4, 5, 7, 6);
1391 else
1392 putrect(shp, fp, mod, name, ".d", 0, 2, 3, 1);
1393 }
1394
1395 /* putsides - put out sides of box */
1396 void
1397 putsides(
1398 SRCINFO *shp,
1399 FILE *fp,
1400 char *mod,
1401 char *name
1402 )
1403 {
1404 putrect(shp, fp, mod, name, ".1", 0, 1, 5, 4);
1405 putrect(shp, fp, mod, name, ".2", 1, 3, 7, 5);
1406 putrect(shp, fp, mod, name, ".3", 3, 2, 6, 7);
1407 putrect(shp, fp, mod, name, ".4", 2, 0, 4, 6);
1408 }
1409
1410 /* putrect - put out a rectangle
1411 *
1412 * putrect generates the "polygon" primitive which describes a
1413 * rectangle.
1414 */
1415 void
1416 putrect(
1417 SRCINFO *shp,
1418 FILE *fp,
1419 char *mod,
1420 char *name,
1421 char *suffix,
1422 int a,
1423 int b,
1424 int c,
1425 int d
1426 )
1427 {
1428 fprintf(fp, "\n%s polygon %s%s\n0\n0\n12\n", mod, name, suffix);
1429 putpoint(shp, fp, a);
1430 putpoint(shp, fp, b);
1431 putpoint(shp, fp, c);
1432 putpoint(shp, fp, d);
1433 }
1434
1435 /* putpoint -- output a the coordinates of a vertex
1436 *
1437 * putpoint maps vertex numbers to coordinates and outputs the
1438 * coordinates.
1439 */
1440 void
1441 putpoint(
1442 SRCINFO *shp,
1443 FILE *fp,
1444 int p
1445 )
1446 {
1447 static double mult[2] = {-.5, .5};
1448
1449 fprintf(fp, "\t%g\t%g\t%g\n",
1450 mult[p&1]*shp->l*meters2out,
1451 mult[p>>1&1]*shp->w*meters2out,
1452 mult[p>>2]*shp->h*meters2out);
1453 }
1454
1455 /* End of routines to output a box-shaped light source */
1456
1457 /* Routines to output a cylindrical or disk shaped light source
1458 *
1459 * As with other shapes, the light source is centered on the origin.
1460 * The "ring" and "cylinder" primitives are used.
1461 *
1462 */
1463 void
1464 putdisksrc( /* put out a disk source */
1465 SRCINFO *shp,
1466 FILE *fp,
1467 char *mod,
1468 char *name,
1469 int up
1470 )
1471 {
1472 if (up) {
1473 fprintf(fp, "\n%s ring %s.u\n", mod, name);
1474 fprintf(fp, "0\n0\n8\n");
1475 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1476 fprintf(fp, "\t0 0 1\n");
1477 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1478 } else {
1479 fprintf(fp, "\n%s ring %s.d\n", mod, name);
1480 fprintf(fp, "0\n0\n8\n");
1481 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1482 fprintf(fp, "\t0 0 -1\n");
1483 fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out);
1484 }
1485 }
1486
1487
1488 void
1489 putcyl( /* put out a cylinder */
1490 SRCINFO *shp,
1491 FILE *fp,
1492 char *mod,
1493 char *name
1494 )
1495 {
1496 fprintf(fp, "\n%s cylinder %s.c\n", mod, name);
1497 fprintf(fp, "0\n0\n7\n");
1498 fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out);
1499 fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1500 fprintf(fp, "\t%g\n", .5*shp->w*meters2out);
1501 }
1502
1503 /* end of of routines to output cylinders and disks */
1504
1505 void
1506 putspheresrc( /* put out a sphere source */
1507 SRCINFO *shp,
1508 FILE *fp,
1509 char *mod,
1510 char *name
1511 )
1512 {
1513 fprintf(fp, "\n%s sphere %s.s\n", mod, name);
1514 fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out);
1515 }
1516
1517 /* cvdata - convert LM-63 tilt and candela data to Radiance brightdata format
1518 *
1519 * The files created by this routine are intended for use with the Radiance
1520 * "brightdata" material type.
1521 *
1522 * Two types of data are converted; one-dimensional tilt data, which
1523 * is given in polar coordinates, and two-dimensional candela data,
1524 * which is given in spherical co-ordinates.
1525 *
1526 * Return 0 for success, -1 for failure.
1527 *
1528 */
1529 int
1530 cvdata(
1531 FILE *in, /* Input file */
1532 FILE *out, /* Output file */
1533 int ndim, /* Number of dimensions; 1 for
1534 * tilt data, 2 for photometric data. */
1535 int npts[], /* Number of points in each dimension */
1536 double mult, /* Multiple each value by this
1537 * number. For tilt data, always
1538 * 1. For candela values, the
1539 * efficacy of white Radiance light. */
1540 double lim[][2] /* The range of angles in each dimension. */
1541 )
1542 {
1543 double *pt[4]; /* Four is the expected maximum of ndim. */
1544 int i, j;
1545 double val;
1546 int total;
1547
1548 /* Calculate and output the number of data values */
1549 total = 1; j = 0;
1550 for (i = 0; i < ndim; i++)
1551 if (npts[i] > 1) {
1552 total *= npts[i];
1553 j++;
1554 }
1555 fprintf(out, "%d\n", j);
1556
1557 /* Read in the angle values, and note the first and last in
1558 * each dimension, if there is a place to store them. In the
1559 * case of tilt data, there is only one list of angles. In the
1560 * case of candela values, vertical angles appear first, and
1561 * horizontal angles occur second. */
1562 for (i = 0; i < ndim; i++) {
1563 /* Allocate space for the angle values. */
1564 pt[i] = (double *)malloc(npts[i]*sizeof(double));
1565 for (j = 0; j < npts[i]; j++)
1566 if (!scnflt(in, &pt[i][j]))
1567 return(-1);
1568 if (lim != NULL) {
1569 lim[i][0] = pt[i][0];
1570 lim[i][1] = pt[i][npts[i]-1];
1571 }
1572 }
1573
1574 /* Output the angles. If this is candela data, horizontal
1575 * angles output first. There are two cases: the first where
1576 * the angles are evenly spaced, the second where they are
1577 * not.
1578 *
1579 * When the angles are evenly spaced, three numbers are
1580 * output: the first angle, the last angle, and the number of
1581 * angles. When the angles are not evenly spaced, instead
1582 * zero, zero, and the count of angles is given, followed by a
1583 * list of angles. In this case, angles are output four to a line.
1584 */
1585 for (i = ndim-1; i >= 0; i--) {
1586 if (npts[i] > 1) {
1587 /* Determine if the angles are evenly spaces */
1588 for (j = 1; j < npts[i]-1; j++)
1589 if (!FEQ(pt[i][j]-pt[i][j-1],
1590 pt[i][j+1]-pt[i][j]))
1591 break;
1592 /* If they are, output the first angle, the
1593 * last angle, and a count */
1594 if (j == npts[i]-1)
1595 fprintf(out, "%g %g %d\n", pt[i][0], pt[i][j],
1596 npts[i]);
1597 else {
1598 /* otherwise, output 0, 0, and a
1599 * count, followed by the list of
1600 * angles, one to a line. */
1601 fprintf(out, "0 0 %d", npts[i]);
1602 for (j = 0; j < npts[i]; j++) {
1603 if (j%4 == 0)
1604 putc('\n', out);
1605 fprintf(out, "\t%g", pt[i][j]);
1606 }
1607 putc('\n', out);
1608 }
1609 }
1610 /* Free the storage containing the angle values. */
1611 free((void *)pt[i]);
1612 }
1613
1614 /* Finally, read in the data values (candela or multiplier values,
1615 * depending on the part of the file) and output them four to
1616 * a line. */
1617 for (i = 0; i < total; i++) {
1618 if (i%4 == 0)
1619 putc('\n', out);
1620 if (!scnflt(in, &val))
1621 return(-1);
1622 fprintf(out, "\t%g", val*mult);
1623 }
1624 putc('\n', out);
1625 return(0);
1626 }
1627
1628 /* getword - get an LM-63 delimited word from fp
1629 *
1630 * Getword gets a word from an IES file delimited by either white
1631 * space or a comma surrounded by white space. A pointer to the word
1632 * is returned, which will persist only until getword is called again.
1633 * At EOF, return NULL instead.
1634 *
1635 */
1636 char *
1637 getword( /* scan a word from fp */
1638 FILE *fp
1639 )
1640 {
1641 static char wrd[RMAXWORD];
1642 char *cp;
1643 int c;
1644
1645 /* Skip initial spaces */
1646 while (isspace(c=getc(fp)))
1647 ;
1648 /* Get characters to a delimiter or until wrd is full */
1649 for (cp = wrd; c != EOF && cp < wrd+RMAXWORD-1;
1650 *cp++ = c, c = getc(fp))
1651 if (isspace(c) || c == ',') {
1652 /* If we find a delimiter */
1653 /* Gobble up whitespace */
1654 while (isspace(c))
1655 c = getc(fp);
1656 /* If it's not a comma, put the first
1657 * character of the next data item back */
1658 if ((c != EOF) & (c != ','))
1659 ungetc(c, fp);
1660 /* Close out the strimg */
1661 *cp = '\0';
1662 /* return it */
1663 return(wrd);
1664 }
1665 /* If we ran out of space or are at the end of the file,
1666 * return either the word or NULL, as appropriate. */
1667 *cp = '\0';
1668 return(cp > wrd ? wrd : NULL);
1669 }
1670
1671 /* cvtint - convert an IES word to an integer
1672 *
1673 * A pointer to the word is passed in wrd; ip is expected to point to
1674 * an integer. cvtint() will silently truncate a floating point value
1675 * to an integer; "1", "1.0", and "1.5" will all return 1.
1676 *
1677 * cvtint() returns 0 if it fails, 1 if it succeeds.
1678 */
1679 int
1680 cvtint(
1681 int *ip,
1682 char *wrd
1683 )
1684 {
1685 if (wrd == NULL || !isint(wrd))
1686 return(0);
1687 *ip = atoi(wrd);
1688 return(1);
1689 }
1690
1691
1692 /* cvtflt - convert an IES word to a double precision floating-point number
1693 *
1694 * A pointer to the word is passed in wrd; rp is expected to point to
1695 * a double.
1696 *
1697 * cvtflt returns 0 if it fails, 1 if it succeeds.
1698 */
1699 int
1700 cvtflt(
1701 double *rp,
1702 char *wrd
1703 )
1704 {
1705 if (wrd == NULL || !isflt(wrd))
1706 return(0);
1707 *rp = atof(wrd);
1708 return(1);
1709 }
1710
1711 /* cvgeometry - process materials and geometry format luminaire data
1712 *
1713 * The materials and geometry format (MGF) for describing luminaires
1714 * was a part of Radiance that was first adopted and then retracted by
1715 * the IES as part of LM-63. It provides a way of describing
1716 * luminaire geometry similar to the Radiance scene description
1717 * format.
1718 *
1719 * cvgeometry() generates an mgf2rad command and then, if "-g" is given
1720 * on the command line, an oconv command, both of which are then
1721 * executed with the system() function.
1722 *
1723 * The generated commands are:
1724 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
1725 * | xform -s <scale_factor> \
1726 * >> <luminare_scene_description_file
1727 * or:
1728 * mgf2rad -e <multiplier> -g <size> <mgf_filename> \
1729 * oconv - > <instance_filename>
1730 */
1731 int
1732 cvgeometry(
1733 char *inpname,
1734 SRCINFO *sinf,
1735 char *outname,
1736 FILE *outfp /* close output file upon return */
1737 )
1738 {
1739 char buf[256];
1740 char *cp;
1741
1742 if (inpname == NULL || !inpname[0]) { /* no geometry file */
1743 fclose(outfp);
1744 return(0);
1745 }
1746 putc('\n', outfp);
1747 strcpy(buf, "mgf2rad "); /* build mgf2rad command */
1748 cp = buf+8;
1749 if (!FEQ(sinf->mult, 1.0)) {
1750 /* if there's an output multiplier, include in the
1751 * mgf2rad command */
1752 sprintf(cp, "-e %f ", sinf->mult);
1753 cp += strlen(cp);
1754 }
1755 /* Include the glow distance for the geometry */
1756 sprintf(cp, "-g %f %s ",
1757 sqrt(sinf->w*sinf->w + sinf->h*sinf->h + sinf->l*sinf->l),
1758 inpname);
1759 cp += strlen(cp);
1760 if (instantiate) { /* instantiate octree */
1761 /* If "-g" is given on the command line, include an
1762 * "oconv" command in the pipe. */
1763 strcpy(cp, "| oconv - > ");
1764 cp += 12;
1765 fullnam(cp,outname,T_OCT);
1766 /* Only update if the input file is newer than the
1767 * output file */
1768 if (fdate(inpname) > fdate(outname) &&
1769 system(buf)) { /* create octree */
1770 fclose(outfp);
1771 return(-1);
1772 }
1773 /* Reference the instance file in the scene description */
1774 fprintf(outfp, "void instance %s_inst\n", outname);
1775 /* If the geometry isn't in meters, scale it appropriately. */
1776 if (!FEQ(meters2out, 1.0))
1777 fprintf(outfp, "3 %s -s %f\n",
1778 libname(buf,outname,T_OCT),
1779 meters2out);
1780 else
1781 fprintf(outfp, "1 %s\n", libname(buf,outname,T_OCT));
1782 /* Close off the "instance" primitive. */
1783 fprintf(outfp, "0\n0\n");
1784 /* And the Radiance scene description. */
1785 fclose(outfp);
1786 } else { /* else append to luminaire file */
1787 if (!FEQ(meters2out, 1.0)) { /* apply scalefactor */
1788 sprintf(cp, "| xform -s %f ", meters2out);
1789 cp += strlen(cp);
1790 }
1791 if (!out2stdout) {
1792 fclose(outfp);
1793 strcpy(cp, ">> "); /* append works for DOS? */
1794 cp += 3;
1795 fullnam(cp,outname,T_RAD);
1796 }
1797 if (system(buf))
1798 return(-1);
1799 }
1800 return(0);
1801 }
1802
1803 /* Set up emacs indentation */
1804 /* Local Variables: */
1805 /* c-file-style: "bsd" */
1806 /* End: */
1807
1808 /* For vim, use ":set tabstop=8 shiftwidth=8" */