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

File Contents

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