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

File Contents

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