ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
Revision: 2.31
Committed: Tue Jun 5 16:04:00 2018 UTC (5 years, 11 months ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: rad5R2
Changes since 2.30: +11 -8 lines
Log Message:
Increased maximum line length and allow spaces before bracketed keywords

File Contents

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