ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
Revision: 2.30
Committed: Mon Jun 4 23:05:34 2018 UTC (6 years ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.29: +9 -6 lines
Log Message:
Fixed issue with newer IES file version strings

File Contents

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