ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/ies2rad.c
(Generate patch)

Comparing ray/src/cv/ies2rad.c (file contents):
Revision 2.23 by schorsch, Sat Nov 15 13:29:23 2003 UTC vs.
Revision 2.35 by greg, Mon Nov 29 16:07:36 2021 UTC

# Line 2 | Line 2
2   static const char       RCSid[] = "$Id$";
3   #endif
4   /*
5 < * Convert IES luminaire data to Radiance description
5 > * ies2rad -- Convert IES luminaire data to Radiance description
6   *
7 + * ies2rad converts an IES LM-63 luminare description to a Radiance
8 + * luminaire description.  In addition, ies2rad manages a local
9 + * database of Radiance luminaire files.
10 + *
11 + * Ies2rad generates two or three files for each luminaire. For a
12 + * luminaire named LUM, ies2rad will generate LUM.rad, a Radiance
13 + * scene description file which describes the light source, LUM.dat,
14 + * which contains the photometric data from the IES LM-63 file, and
15 + * (if tilt data is provided) LUM%.dat, which contains the tilt data
16 + * from the IES file.
17 + *
18 + * Ies2rad is supported by the Radiance function files source.cal and
19 + * tilt.cal, which transform the coordinates in the IES data into
20 + * Radiance (θ,φ) luminaire coordinates and then apply photometric and
21 + * tilt data to generate Radiance light. θ is altitude from the
22 + * negative z-axis and φ is azimuth from the positive x-axis,
23 + * increasing towards the positive y-axis. This system matches none of
24 + * the usual goniophotometric conventions, but it is closest to IES
25 + * type C; V in type C photometry is θ in Radiance and L is -φ.
26 + *
27 + * The ies2rad scene description for a luminaire LUM, with tilt data,
28 + * uses the following Radiance scene description primitives:
29 + *
30 + *     void brightdata LUM_tilt
31 + *     …
32 + *     LUM_tilt brightdata LUM_dist
33 + *     …
34 + *     LUM_dist light LUM_light
35 + *     …
36 + *     LUM_light surface1 name1
37 + *     …
38 + *     LUM_light surface2 name2
39 + *     …
40 + *     LUM_light surface_n name_n
41 + *
42 + * Without tilt data, the primitives are:
43 + *
44 + *     void brightdata LUM_dist
45 + *     …
46 + *     LUM_dist light LUM_light
47 + *     …
48 + *     LUM_light surface1 name1
49 + *     …
50 + *     LUM_light surface2 name2
51 + *     …
52 + *     LUM_light surface_n name_n
53 + *
54 + * As many surfaces are given as required to describe the light
55 + * source. Illum may be used rather than light so that a visible form
56 + * (impostor) may be given to the luminaire, rather than a simple
57 + * glowing shape. If an impostor is provided, it must be wholly
58 + * contained within the illum and if it provides impostor light
59 + * sources, those must be given with glow, so that they do not
60 + * themselves illuminate the scene, providing incorrect results.
61 + *
62 + * 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 + * 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 + *
139 + * History
140 + * =======
141 + *
142   *      07Apr90         Greg Ward
143   *
144   *  Fixed correction factor for flat sources 29Oct2001 GW
145 + *  Extensive comments added by Randolph Fritz May2018
146   */
147  
12 #include <stdio.h>
13 #include <string.h>
148   #include <math.h>
15 #include <sys/types.h>
149   #include <ctype.h>
150  
151   #include "rtio.h"
# Line 20 | Line 153 | static const char      RCSid[] = "$Id$";
153   #include "paths.h"
154  
155   #define PI              3.14159265358979323846
156 <                                        /* floating comparisons */
156 >
157 > #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   #define FTINY           1e-6
163   #define FEQ(a,b)        ((a)<=(b)+FTINY&&(a)>=(b)-FTINY)
26                                        /* keywords */
27 #define MAGICID         "IESNA"
28 #define LMAGICID        5
29 #define FIRSTREV        86
30 #define LASTREV         95
164  
165 < #define D86             0               /* keywords defined in LM-63-1986 */
165 > #define IESFIRSTVER 1986
166 > #define IESLASTVER 2019
167  
168 < #define K_TST           0
169 < #define K_MAN           1
170 < #define K_LMC           2
171 < #define K_LMN           3
172 < #define K_LPC           4
173 < #define K_LMP           5
174 < #define K_BAL           6
175 < #define K_MTC           7
42 < #define K_OTH           8
43 < #define K_SCH           9
44 < #define K_MOR           10
45 < #define K_BLK           11
46 < #define K_EBK           12
168 > /* 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  
48 #define D91             ((1L<<13)-1)    /* keywords defined in LM-63-1991 */
49
50 #define K_LMG           13
51
52 #define D95             ((1L<<14)-1)    /* keywords defined in LM-63-1995 */
53
54 char    k_kwd[][20] = {"TEST", "MANUFAC", "LUMCAT", "LUMINAIRE", "LAMPCAT",
55                        "LAMP", "BALLAST", "MAINTCAT", "OTHER", "SEARCH",
56                        "MORE", "BLOCK", "ENDBLOCK", "LUMINOUSGEOMETRY"};
57
58 long k_defined[] = {D86, D86, D86, D86, D86, D91, D91, D91, D91, D95};
59
60 int     filerev = FIRSTREV;
61
62 #define keymatch(i,s)   (k_defined[filerev-FIRSTREV]&1L<<(i) &&\
63                                k_match(k_kwd[i],s))
64
65 #define checklamp(s)    (!(k_defined[filerev-FIRSTREV]&(1<<K_LMP|1<<K_LPC)) ||\
66                                keymatch(K_LMP,s) || keymatch(K_LPC,s))
67
68                                        /* tilt specs */
177   #define TLTSTR          "TILT="
178   #define TLTSTRLEN       5
179   #define TLTNONE         "NONE"
# Line 73 | Line 181 | int    filerev = FIRSTREV;
181   #define TLT_VERT        1
182   #define TLT_H0          2
183   #define TLT_H90         3
184 <                                        /* photometric types */
184 >
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   #define PM_C            1
203   #define PM_B            2
204 <                                        /* unit types */
204 > #define PM_A            3
205 >
206 > /* unit types */
207   #define U_FEET          1
208   #define U_METERS        2
209 <                                        /* string lengths */
210 < #define MAXLINE         132
209 >
210 > /* string lengths */
211 > /* 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 > #define MAXLINE         257
215 > #define MAXUNITNAME     64
216   #define RMAXWORD        76
217 <                                        /* file types */
217 >
218 > /* 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 >
249 > /* file extensions */
250   #define T_RAD           ".rad"
251   #define T_DST           ".dat"
252   #define T_TLT           "%.dat"
253   #define T_OCT           ".oct"
254 <                                        /* shape types */
254 >
255 > /* Radiance shape types
256 > * These #defines enumerate the shapes of the Radiance objects which
257 > * emit the light.
258 > */
259   #define RECT            1
260   #define DISK            2
261   #define SPHERE          3
262  
263 < #define MINDIM          .001            /* minimum dimension (point source) */
263 > /* 1mm.  The diameter of a point source luminaire model. Also the minimum
264 > * 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 < #define F_M             .3048           /* feet to meters */
268 > /* feet to meters */
269 > /* length_in_meters = length_in_feet * F_M */
270 > #define F_M             .3048
271  
272 + /* abspath - return true if a path begins with a directory separator
273 + * or a '.' (current directory) */
274   #define abspath(p)      (ISDIRSEP((p)[0]) || (p)[0] == '.')
275  
276 + /* 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 + /* Global variables.
305 + *
306 + * Mostly, these are a way of communicating command line parameters to
307 + * the rest of the program.
308 + */
309   static char     default_name[] = "default";
310  
311   char    *libdir = NULL;                 /* library directory location */
# Line 110 | Line 318 | char   *deflamp = NULL;                /* default lamp type */
318   float   defcolor[3] = {1.,1.,1.};       /* default lamp color */
319   float   *lampcolor = defcolor;          /* pointer to current lamp color */
320   double  multiplier = 1.0;               /* multiplier for all light sources */
321 < char    units[64] = "meters";           /* output units */
321 > char    units[MAXUNITNAME] = "meters";  /* output units */
322   int     out2stdout = 0;                 /* put out to stdout r.t. file */
323   int     instantiate = 0;                /* instantiate geometry */
324   double  illumrad = 0.0;                 /* radius for illum sphere */
325  
326 + /* This struct describes the Radiance source object */
327   typedef struct {
328          int     isillum;                        /* do as illum */
329          int     type;                           /* RECT, DISK, SPHERE */
330          double  mult;                           /* candela multiplier */
331          double  w, l, h;                        /* width, length, height */
332          double  area;                           /* max. projected area */
333 +        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   } SRCINFO;                              /* a source shape (units=meters) */
339  
340 < int     gargc;                          /* global argc (minus filenames) */
340 > /* A count and pointer to the list of input file names */
341 > int     gargc;                          /* global argc */
342   char    **gargv;                        /* global argv */
343  
344 <
344 > /* 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   #define scnint(fp,ip)   cvtint(ip,getword(fp))
352   #define scnflt(fp,rp)   cvtflt(rp,getword(fp))
132 #define isint           isflt                   /* IES allows real as integer */
353  
354 + /* 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  
360 + /* IES file conversion functions */
361   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);
# Line 142 | Line 368 | 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 + static int makeiesshape(SRCINFO *shp, double length, double width, double height);
372 + static int makeillumsphere(SRCINFO *shp);
373   static int makeshape(SRCINFO *shp, double width, double length, double height);
374 + 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   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);
# Line 153 | Line 387 | static void putrect(SRCINFO *shp, FILE *fp, char *mod,
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 + 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   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 + /* output function */
402 + static void fpcomment(FILE *fp, char *prefix, char *s);
403  
404 + /* 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   int
429   main(
430          int     argc,
# Line 171 | Line 436 | main(
436          char    outname[RMAXWORD];
437          double  d1;
438          int     i;
439 <        
439 >
440 >        /* Scan the options */
441          for (i = 1; i < argc && argv[i][0] == '-'; i++)
442                  switch (argv[i][1]) {
443                  case 'd':               /* dimensions */
# Line 242 | Line 508 | main(
508                  case 'i':               /* illum */
509                          illumrad = atof(argv[++i]);
510                          break;
511 <                case 'g':               /* instatiate geometry? */
511 >                case 'g':               /* instantiate geometry? */
512                          instantiate = !instantiate;
513                          break;
514                  case 't':               /* override lamp type */
# Line 265 | Line 531 | main(
531                                          argv[0], argv[i]);
532                          exit(1);
533                  }
534 +        /* Save pointers to the list of input file names */
535          gargc = i;
536          gargv = argv;
537 <        initlamps();                    /* get lamp data (if needed) */
538 <                                        /* convert ies file(s) */
537 >
538 >        /* get lamp data (if needed) */
539 >        initlamps();
540 >
541 >        /* convert ies file(s) */
542 >        /* If an output file name is specified */
543          if (outfile != NULL) {
544                  if (i == argc)
545 +                        /* If no input filename is given, use stdin as
546 +                         * the source for the IES file */
547                          exit(ies2rad(NULL, outfile) == 0 ? 0 : 1);
548                  else if (i == argc-1)
549 +                        /* If exactly one input file name is given, use it. */
550                          exit(ies2rad(argv[i], outfile) == 0 ? 0 : 1);
551                  else
552 <                        goto needsingle;
552 >                        goto needsingle; /* Otherwise, error. */
553          } else if (i >= argc) {
554 +                /* If an output file and an input file are not give, error. */
555                  fprintf(stderr, "%s: missing output file specification\n",
556                                  argv[0]);
557                  exit(1);
558          }
559 +        /* If no input or output file is given, error. */
560          if (out2stdout && i != argc-1)
561                  goto needsingle;
562 +        /* Otherwise, process each input file in turn. */
563          status = 0;
564          for ( ; i < argc; i++) {
565                  tailtrunc(strcpy(outname,filename(argv[i])));
# Line 295 | Line 572 | needsingle:
572          exit(1);
573   }
574  
575 + /* Initlamps -- If necessary, read lamp data table */
576   void
577   initlamps(void)                         /* set up lamps */
578   {
579          float   *lcol;
580          int     status;
581  
582 +        /* If the lamp name is set to default, don't bother to read
583 +         * the lamp data table. */
584          if (lamptype != NULL && !strcmp(lamptype, default_name) &&
585                          deflamp == NULL)
586 <                return;                         /* no need for data */
587 <                                                /* else load file */
588 <        if ((status = loadlamps(lampdat)) < 0)
589 <                exit(1);
586 >                return;
587 >
588 >        if ((status = loadlamps(lampdat)) < 0) /* Load the lamp data table */
589 >                exit(1);                       /* Exit if problems
590 >                                                * with the file. */
591          if (status == 0) {
592 +                /* If can't open the file, just use the standard default lamp */
593                  fprintf(stderr, "%s: warning - no lamp data\n", lampdat);
594                  lamptype = default_name;
595                  return;
596          }
597 <        if (deflamp != NULL) {                  /* match default type */
597 >        if (deflamp != NULL) {
598 >                /* Look up the specified default lamp type */
599                  if ((lcol = matchlamp(deflamp)) == NULL)
600 +                        /* If it can't be found, use the default */
601                          fprintf(stderr,
602                                  "%s: warning - unknown default lamp type\n",
603                                          deflamp);
604                  else
605 +                        /* Use the selected default lamp color */
606                          copycolor(defcolor, lcol);
607          }
608 <        if (lamptype != NULL) {                 /* match selected type */
608 >        /* 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                  if (strcmp(lamptype, default_name)) {
612                          if ((lcol = matchlamp(lamptype)) == NULL) {
613                                  fprintf(stderr,
# Line 332 | Line 619 | initlamps(void)                                /* set up lamps */
619                  }
620                  freelamps();                    /* all done with data */
621          }
622 <                                                /* else keep lamp data */
622 >        /* else keep lamp data */
623   }
624  
625 + /*
626 + * 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 + * 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 +
764 + /*
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 + char *
773   stradd(                 /* add a string at dst */
774 <        register char   *dst,
775 <        register char   *src,
774 >        char    *dst,
775 >        char    *src,
776          int     sep
777   )
778   {
# Line 354 | Line 787 | stradd(                        /* add a string at dst */
787          return(dst);
788   }
789  
790 <
790 > /*
791 > * fullnam () - return a usable path name for an output file
792 > */
793   char *
794 < fullnam(                /* return full path name */
795 <        char    *path,
796 <        char    *fname,
797 <        char    *suffix
794 > 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   )
800   {
801 +        extern char *prefdir;
802 +        extern char *libdir;
803 +
804          if (prefdir != NULL && abspath(prefdir))
805 +                /* If the subdirectory path is absolute or '.', just
806 +                 * concatenate the names together */
807                  libname(path, fname, suffix);
808          else if (abspath(fname))
809 +                /* If there is no subdirectory, and the file name is
810 +                 * an absolute path or '.', concatenate the path,
811 +                 * filename, and suffix. */
812                  strcpy(stradd(path, fname, 0), suffix);
813          else
814 +                /* If the file name is relative, concatenate path,
815 +                 * library directory, directory separator, file name,
816 +                 * and suffix.  */
817                  libname(stradd(path, libdir, DIRSEP), fname, suffix);
818  
819          return(path);
820   }
821  
822  
823 + /*
824 + * libname - convert a file name to a path
825 + */
826   char *
827 < libname(                /* return library relative name */
828 <        char    *path,
829 <        char    *fname,
830 <        char    *suffix
827 > 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   )
833   {
834 +        extern char *prefdir;   /* The subdirectory where the file
835 +                                 * name is stored. */
836 +
837          if (abspath(fname))
838 +                /* If the file name begins with '/' or '.', combine
839 +                 * it with the path and attach the suffix */
840                  strcpy(stradd(path, fname, 0), suffix);
841          else
842 +                /* If the file name is relative, attach it to the
843 +                 * path, include the subdirectory, and append the suffix. */
844                  strcpy(stradd(stradd(path, prefdir, DIRSEP), fname, 0), suffix);
845  
846          return(path);
847   }
848  
849 <
849 > /* filename - pointer to filename in buffer containing path
850 > *
851 > * 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 > */
855   char *
856 < filename(                       /* get final component of pathname */
857 <        register char   *path
856 > filename(
857 >        char    *path
858   )
859   {
860 <        register char   *cp;
860 >        char    *cp = path;
861  
862 <        for (cp = path; *path; path++)
862 >        for (; *path; path++)
863                  if (ISDIRSEP(*path))
864                          cp = path+1;
865          return(cp);
866   }
867  
868  
869 + /* 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   char *
876 < filetrunc(                              /* truncate filename at end of path */
876 > filetrunc(
877          char    *path
878   )
879   {
880 <        register char   *p1, *p2;
880 >        char    *p1, *p2;
881  
882          for (p1 = p2 = path; *p2; p2++)
883                  if (ISDIRSEP(*p2))
# Line 419 | Line 888 | filetrunc(                             /* truncate filename at end of path */
888          return(path);
889   }
890  
891 <
891 > /* 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   char *
897 < tailtrunc(                              /* truncate tail of filename */
897 > tailtrunc(
898          char    *name
899   )
900   {
901 <        register char   *p1, *p2;
901 >        char    *p1, *p2;
902  
903 +        /* Skip leading periods */
904          for (p1 = filename(name); *p1 == '.'; p1++)
905                  ;
906 +        /* Find the last period in a file name */
907          p2 = NULL;
908          for ( ; *p1; p1++)
909                  if (*p1 == '.')
910                          p2 = p1;
911 +        /* If present, trim the filename at that period */
912          if (p2 != NULL)
913                  *p2 = '\0';
914          return(name);
915   }
916  
917 <
917 > /* blanktrunc() - trim spaces at the end of a string
918 > *
919 > * the string is passed in a character array, which is modified
920 > */
921   void
922 < blanktrunc(                             /* truncate spaces at end of line */
922 > blanktrunc(
923          char    *s
924   )
925   {
926 <        register char   *cp;
926 >        char    *cp;
927  
928          for (cp = s; *cp; cp++)
929                  ;
# Line 453 | Line 932 | blanktrunc(                            /* truncate spaces at end of line */
932          *++cp = '\0';
933   }
934  
935 <
936 < int
937 < k_match(                        /* header line matches keyword? */
938 <        register char   *kwd,
939 <        register char   *hdl
940 < )
941 < {
942 <        if (!*hdl++ == '[')
943 <                return(0);
944 <        while (islower(*hdl) ? toupper(*hdl) == *kwd++ : *hdl == *kwd++)
945 <                if (!*hdl++)
946 <                        return(0);
947 <        return((!*kwd) & (*hdl == ']'));
935 > /* fpcomment - output a multi-line comment
936 > *
937 > * 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 > */
941 > 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   }
953  
954 <
955 < char *
956 < keyargs(                                /* return keyword arguments */
957 <        register char   *hdl
958 < )
959 < {
960 <        while (*hdl && *hdl++ != ']')
961 <                ;
962 <        while (isspace(*hdl))
480 <                hdl++;
481 <        return(hdl);
482 < }
483 <
484 <
954 > /* 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   void
964 < putheader(                              /* print header */
964 >
965 > putheader(
966          FILE    *out
967   )
968   {
969 <        register int    i;
970 <        
969 >        int     i;
970 >
971          putc('#', out);
972          for (i = 0; i < gargc; i++) {
973                  putc(' ', out);
# Line 499 | Line 978 | putheader(                             /* print header */
978          putc('\n', out);
979   }
980  
981 <
981 > /* 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   int
987   ies2rad(                /* convert IES file */
988          char    *inpname,
# Line 508 | Line 991 | ies2rad(               /* convert IES file */
991   {
992          SRCINFO srcinfo;
993          char    buf[MAXLINE], tltid[RMAXWORD];
994 <        char    geomfile[128];
994 >        char    geomfile[MAXLINE];
995          FILE    *inpfp, *outfp;
996          int     lineno = 0;
997  
998 <        geomfile[0] = '\0';
998 >
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 +        geomfile[0] = '\0';
1007          if (inpname == NULL) {
1008                  inpname = "<stdin>";
1009                  inpfp = stdin;
# Line 528 | Line 1018 | ies2rad(               /* convert IES file */
1018                  fclose(inpfp);
1019                  return(-1);
1020          }
1021 +
1022 +        /* Output the output file header */
1023          putheader(outfp);
1024 +
1025 +        /* If the lamp type wasn't given on the command line, mark
1026 +         * the lamp color as missing */
1027          if (lamptype == NULL)
1028                  lampcolor = NULL;
1029 +
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          while (fgets(buf,sizeof(buf),inpfp) != NULL
1034                          && strncmp(buf,TLTSTR,TLTSTRLEN)) {
1035 <                blanktrunc(buf);
1036 <                if (!buf[0])
1035 >                blanktrunc(buf); /* Trim trailing spaces, CR, LF. */
1036 >                if (!buf[0])     /* Skip blank lines */
1037                          continue;
1038 <                if (!lineno++ && !strncmp(buf, MAGICID, LMAGICID)) {
1039 <                        filerev = atoi(buf+LMAGICID);
1040 <                        if (filerev < FIRSTREV)
1041 <                                filerev = FIRSTREV;
1042 <                        else if (filerev > LASTREV)
1043 <                                filerev = LASTREV;
1044 <                }
1038 >                /* 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 >                /* Output the header line as a comment in the .rad file. */
1045                  fputs("#<", outfp);
1046                  fputs(buf, outfp);
1047                  putc('\n', outfp);
1048 <                if (lampcolor == NULL && checklamp(buf))
1049 <                        lampcolor = matchlamp( buf[0] == '[' ?
1050 <                                                keyargs(buf) : buf );
1051 <                if (keymatch(K_LMG, buf)) {             /* geometry file */
1052 <                        strcpy(geomfile, inpname);
1053 <                        strcpy(filename(geomfile), keyargs(buf));
1054 <                        srcinfo.isillum = 1;
1055 <                }
1048 >
1049 >                /* 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          }
1057 +
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          if (lampcolor == NULL) {
1063                  fprintf(stderr, "%s: warning - no lamp type\n", inpname);
1064                  fputs("# Unknown lamp type (used default)\n", outfp);
# Line 562 | Line 1066 | ies2rad(               /* convert IES file */
1066          } 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 +
1070 +        /* If the file ended before a "TILT=" line, that's an error. */
1071          if (feof(inpfp)) {
1072                  fprintf(stderr, "%s: not in IES format\n", inpname);
1073                  goto readerr;
1074          }
1075 +
1076 +        /* Process the tilt section of the file. */
1077 +        /* Get the tilt file name, or the keyword "INCLUDE". */
1078          atos(tltid, RMAXWORD, buf+TLTSTRLEN);
1079          if (inpfp == stdin)
1080                  buf[0] = '\0';
1081          else
1082                  filetrunc(strcpy(buf, inpname));
1083 +        /* Process the tilt data. */
1084          if (dotilt(inpfp, outfp, buf, tltid, outname, tltid) != 0) {
1085                  fprintf(stderr, "%s: bad tilt data\n", inpname);
1086                  goto readerr;
1087          }
1088 +
1089 +        /* Process the luminaire data. */
1090          if (dosource(&srcinfo, inpfp, outfp, tltid, outname) != 0) {
1091                  fprintf(stderr, "%s: bad luminaire data\n", inpname);
1092                  goto readerr;
1093          }
1094 +
1095 +        /* Close the input file */
1096          fclose(inpfp);
1097 <                                        /* cvgeometry closes outfp */
1097 >
1098 >        /* Process an MGF file, if present. cvgeometry() closes outfp. */
1099          if (cvgeometry(geomfile, &srcinfo, outname, outfp) != 0) {
1100                  fprintf(stderr, "%s: bad geometry file\n", geomfile);
1101                  return(-1);
1102          }
1103          return(0);
1104 +
1105   readerr:
1106 +        /* If there is an error reading the file, close the input and
1107 +         * .rad output files, and delete the .rad file, returning -1. */
1108          fclose(inpfp);
1109          fclose(outfp);
1110          unlink(fullnam(buf,outname,T_RAD));
1111          return(-1);
1112   }
1113  
1114 <
1114 > /* 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   int
1127 < dotilt( /* convert tilt data */
1127 > dotilt(
1128          FILE    *in,
1129          FILE    *out,
1130          char    *dir,
# Line 609 | Line 1138 | dotilt(        /* convert tilt data */
1138          char    buf[PATH_MAX], tltname[RMAXWORD];
1139          FILE    *datin, *datout;
1140  
1141 +        /* Decide where the tilt data is; if the luminaire description
1142 +         * doesn't have a tilt section, set the identifier to "void". */
1143          if (!strcmp(tltspec, TLTNONE)) {
1144 +                /* If the line is "TILT=NONE", set the input file
1145 +                 * pointer to NULL and the identifier to "void". */
1146                  datin = NULL;
1147                  strcpy(tltid, "void");
1148          } else if (!strcmp(tltspec, TLTINCL)) {
1149 +                /* If the line is "TILT=INCLUDE" use the main IES
1150 +                 * file as the source of tilt data. */
1151                  datin = in;
1152                  strcpy(tltname, dfltname);
1153          } else {
1154 +                /* If the line is "TILT=<filename>", use that file
1155 +                 * name as the source of tilt data. */
1156                  if (ISDIRSEP(tltspec[0]))
1157                          strcpy(buf, tltspec);
1158                  else
# Line 626 | Line 1163 | dotilt(        /* convert tilt data */
1163                  }
1164                  tailtrunc(strcpy(tltname,filename(tltspec)));
1165          }
1166 +        /* If tilt data is present, read, process, and output it. */
1167          if (datin != NULL) {
1168 +                /* Try to open the output file */
1169                  if ((datout = fopen(fullnam(buf,tltname,T_TLT),"w")) == NULL) {
1170                          perror(buf);
1171                          if (datin != in)
1172                                  fclose(datin);
1173                          return(-1);
1174                  }
1175 +                /* Try to copy the tilt data to the tilt data file */
1176                  if (!scnint(datin,&tlt_type) || !scnint(datin,&nangles)
1177                          || cvdata(datin,datout,1,&nangles,1.,minmax) != 0) {
1178                          fprintf(stderr, "%s: data format error\n", tltspec);
# Line 645 | Line 1185 | dotilt(        /* convert tilt data */
1185                  fclose(datout);
1186                  if (datin != in)
1187                          fclose(datin);
1188 +
1189 +                /* Generate the identifier of the brightdata; the filename
1190 +                 * with "_tilt" appended. */
1191                  strcat(strcpy(tltid, filename(tltname)), "_tilt");
1192 +                /* Write out the brightdata primitive */
1193                  fprintf(out, "\nvoid brightdata %s\n", tltid);
1194                  libname(buf,tltname,T_TLT);
1195 +                /* Generate the tilt description */
1196                  switch (tlt_type) {
1197 <                case TLT_VERT:                  /* vertical */
1197 >                case TLT_VERT:
1198 >                        /* The lamp is mounted vertically; either
1199 >                         * base up or base down. */
1200                          fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1201                                  minmax[0][1]>90.+FTINY ? "tilt_ang" : "tilt_ang2");
1202                          break;
1203 <                case TLT_H0:                    /* horiz. in 0 deg. plane */
1203 >                case TLT_H0:
1204 >                        /* The lamp is mounted horizontally and
1205 >                         * rotates but does not tilt when the
1206 >                         * luminaire is tilted. */
1207                          fprintf(out, "6 noop %s tilt.cal %s -rz 90\n", buf,
1208                          minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1209                          break;
1210                  case TLT_H90:
1211 +                        /* The lamp is mounted horizontally, and
1212 +                         * tilts when the luminaire is tilted. */
1213                          fprintf(out, "4 noop %s tilt.cal %s\n", buf,
1214                          minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2");
1215                          break;
1216                  default:
1217 +                        /* otherwise, this is a bad IES file */
1218                          fprintf(stderr,
1219                                  "%s: illegal lamp to luminaire geometry (%d)\n",
1220                                  tltspec, tlt_type);
1221                          return(-1);
1222                  }
1223 +                /* And finally output the numbers of integer and real
1224 +                 * arguments, of which there are none. */
1225                  fprintf(out, "0\n0\n");
1226          }
1227          return(0);
1228   }
1229  
1230 <
1230 > /* dosource -- create the source and distribution primitives */
1231   int
1232 < dosource(       /* create source and distribution */
1232 > dosource(
1233          SRCINFO *sinf,
1234          FILE    *in,
1235          FILE    *out,
# Line 688 | Line 1243 | dosource(      /* create source and distribution */
1243          double  bounds[2][2];
1244          int     nangles[2], pmtype, unitype;
1245          double  d1;
1246 <        int     doupper, dolower, dosides;
1246 >        int     doupper, dolower, dosides;
1247  
1248 +        /* Read in the luminaire description header */
1249          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)
# Line 699 | Line 1255 | dosource(      /* create source and distribution */
1255                  fprintf(stderr, "dosource: bad lamp specification\n");
1256                  return(-1);
1257          }
1258 +        
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 +        /* Type A photometry is not supported */
1267 +        if (pmtype != PM_C && pmtype != PM_B) {
1268 +                fprintf(stderr, "dosource: unsupported photometric type (%d)\n",
1269 +                                pmtype);
1270 +                return(-1);
1271 +        }
1272 +
1273 +        /* Multiplier = the multiplier from the -m option, times the
1274 +         * multiplier from the IES file, times the ballast factor,
1275 +         * 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          sinf->mult = multiplier*mult*bfactor*pfactor;
1282 +
1283 +        /* If the count of angles is wrong, raise an error and quit. */
1284          if (nangles[0] < 2 || nangles[1] < 1) {
1285                  fprintf(stderr, "dosource: too few measured angles\n");
1286                  return(-1);
1287          }
1288 +
1289 +        /* For internal computation, convert units to meters. */
1290          if (unitype == U_FEET) {
1291                  width *= F_M;
1292                  length *= F_M;
1293                  height *= F_M;
1294          }
1295 +
1296 +        /* Make decisions about the shape of the light source
1297 +         * geometry, and store them in sinf. */
1298          if (makeshape(sinf, width, length, height) != 0) {
1299 <                fprintf(stderr, "dosource: illegal source dimensions");
1299 >                fprintf(stderr, "dosource: illegal source dimensions\n");
1300                  return(-1);
1301          }
1302 +        /* If any warning messages were generated by makeshape(), output them */
1303 +        if ((sinf->warn) != NULL)
1304 +                fputs(sinf->warn, stderr);
1305 +
1306 +        /* Copy the candela values into a Radiance data file. */
1307          if ((datout = fopen(fullnam(buf,name,T_DST), "w")) == NULL) {
1308                  perror(buf);
1309                  return(-1);
# Line 724 | Line 1315 | dosource(      /* create source and distribution */
1315                  return(-1);
1316          }
1317          fclose(datout);
1318 <        fprintf(out, "# %g watt luminaire, lamp*ballast factor = %g\n",
1318 >
1319 >        /* Output explanatory comment */
1320 >        fprintf(out, "\n# %g watt luminaire, lamp*ballast factor = %g\n",
1321                          wattage, bfactor*pfactor);
1322 +        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 +        /* Output distribution "brightdata" primitive. Start handling
1333 +           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          strcat(strcpy(id, filename(name)), "_dist");
1338 <        fprintf(out, "\n%s brightdata %s\n", mod, id);
1338 >        fprintf(out, "\n'%s' brightdata '%s'\n", mod, id);
1339          if (nangles[1] < 2)
1340 +                 /* if it's a radially-symmetric type C distribution */
1341                  fprintf(out, "4 ");
1342          else if (pmtype == PM_B)
1343 +                /* Photometry type B */
1344                  fprintf(out, "5 ");
1345          else if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1346 +                /* Symmetric around the 90-270 degree plane */
1347                  fprintf(out, "7 ");
1348          else
1349 +                /* Just regular type C photometry */
1350                  fprintf(out, "5 ");
1351 <        dolower = (bounds[0][0] < 90.);
1352 <        doupper = (bounds[0][1] > 90.);
1353 <        dosides = (doupper & dolower && sinf->h > MINDIM);
1354 <        fprintf(out, "%s %s source.cal ",
1351 >
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 >        dolower = (bounds[0][0] < 90.-FTINY); /* Smallest vertical angle */
1356 >        doupper = (bounds[0][1] > 90.+FTINY); /* Largest vertical angle */
1357 >        dosides = (doupper & dolower && sinf->h > MINDIM); /* Sides */
1358 >
1359 >        /* Select the appropriate function and parameters from source.cal */
1360 >        fprintf(out, "%s '%s' source.cal ",
1361                          sinf->type==SPHERE ? "corr" :
1362                          !dosides ? "flatcorr" :
1363                          sinf->type==DISK ? "cylcorr" : "boxcorr",
1364                          libname(buf,name,T_DST));
1365          if (pmtype == PM_B) {
1366 +                /* Type B photometry */
1367                  if (FEQ(bounds[1][0],0.))
1368 +                        /* laterally symmetric around a vertical plane */
1369                          fprintf(out, "srcB_horiz2 ");
1370                  else
1371                          fprintf(out, "srcB_horiz ");
1372                  fprintf(out, "srcB_vert ");
1373 <        } else /* pmtype == PM_A */ {
1373 >        } else /* pmtype == PM_C */ {
1374                  if (nangles[1] >= 2) {
1375 +                        /* Not radially symmetric */
1376                          d1 = bounds[1][1] - bounds[1][0];
1377                          if (d1 <= 90.+FTINY)
1378 +                                /* Data for a quadrant */
1379                                  fprintf(out, "src_phi4 ");
1380                          else if (d1 <= 180.+FTINY) {
1381 +                                /* Data for a hemisphere */
1382                                  if (FEQ(bounds[1][0],90.))
1383                                          fprintf(out, "src_phi2+90 ");
1384                                  else
1385                                          fprintf(out, "src_phi2 ");
1386 <                        } else
1386 >                        } else  /* Data for a whole sphere */
1387                                  fprintf(out, "src_phi ");
1388                          fprintf(out, "src_theta ");
1389 +                        /* For the hemisphere around the 90-270 degree plane */
1390                          if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.))
1391                                  fprintf(out, "-rz -90 ");
1392 <                } else
1392 >                } else          /* Radially symmetric */
1393                          fprintf(out, "src_theta ");
1394          }
1395 +        /* finish the brightdata primitive with appropriate data */
1396          if (!dosides || sinf->type == SPHERE)
1397                  fprintf(out, "\n0\n1 %g\n", sinf->mult/sinf->area);
1398          else if (sinf->type == DISK)
# Line 776 | Line 1401 | dosource(      /* create source and distribution */
1401          else
1402                  fprintf(out, "\n0\n4 %g %g %g %g\n", sinf->mult,
1403                                  sinf->l, sinf->w, sinf->h);
1404 +        /* Brightdata primitive written out. */
1405 +
1406 +        /* Finally, output the descriptions of the actual radiant
1407 +         * surfaces. */
1408          if (putsource(sinf, out, id, filename(name),
1409                          dolower, doupper, dosides) != 0)
1410                  return(-1);
1411          return(0);
1412   }
1413  
1414 <
1414 > /* 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   int
1421 < putsource( /* put out source */
1421 > putsource(
1422          SRCINFO *shp,
1423          FILE    *fp,
1424          char    *mod,
1425          char    *name,
1426          int     dolower,
1427          int     doupper,
1428 <        int dosides
1428 >        int     dosides
1429   )
1430   {
1431          char    lname[RMAXWORD];
1432 <        
1432 >
1433 >        /* First, describe the light. If a materials and geometry
1434 >         * file is given, generate an illum instead. */
1435          strcat(strcpy(lname, name), "_light");
1436 <        fprintf(fp, "\n%s %s %s\n", mod,
1436 >        fprintf(fp, "\n'%s' %s '%s'\n", mod,
1437                          shp->isillum ? "illum" : "light", lname);
1438          fprintf(fp, "0\n0\n3 %g %g %g\n",
1439                          lampcolor[0], lampcolor[1], lampcolor[2]);
1440          switch (shp->type) {
1441          case RECT:
1442 +                /* Output at least one rectangle. If light is radiated
1443 +                 * from the sides of the luminaire, output rectangular
1444 +                 * sides as well. */
1445                  if (dolower)
1446                          putrectsrc(shp, fp, lname, name, 0);
1447                  if (doupper)
# Line 811 | Line 1450 | putsource( /* put out source */
1450                          putsides(shp, fp, lname, name);
1451                  break;
1452          case DISK:
1453 +                /* Output at least one disk. If light is radiated from
1454 +                 * the sides of luminaire, output a cylinder as well. */
1455                  if (dolower)
1456                          putdisksrc(shp, fp, lname, name, 0);
1457                  if (doupper)
# Line 819 | Line 1460 | putsource( /* put out source */
1460                          putcyl(shp, fp, lname, name);
1461                  break;
1462          case SPHERE:
1463 +                /* Output a sphere. */
1464                  putspheresrc(shp, fp, lname, name);
1465                  break;
1466          }
1467          return(0);
1468   }
1469  
1470 <
1470 > /* makeshape -- decide what shape will be used
1471 > *
1472 > * Makeshape decides what Radiance geometry will be used to represent
1473 > * the light source and stores information about it in shp.
1474 > *
1475 > * The height, width, and length parameters are values from the
1476 > * IES file, given in meters.
1477 > *
1478 > * 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 > *
1492 > * Makeshape() returns 0 if it succeeds in choosing a shape, and -1 if
1493 > * it fails.
1494 > *
1495 > */
1496   int
1497 < makeshape(              /* make source shape */
1498 <        register SRCINFO        *shp,
1497 > makeshape(
1498 >        SRCINFO *shp,
1499          double  width,
1500          double  length,
1501          double  height
1502   )
1503   {
1504 <        if (illumrad/meters2out >= MINDIM/2.) {
1505 <                shp->isillum = 1;
1504 >        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 = 2.*illumrad / meters2out;
1596 <        } else if (width < MINDIM) {
1597 <                width = -width;
1598 <                if (width < MINDIM) {
1599 <                        shp->type = SPHERE;
1600 <                        shp->w = shp->l = shp->h = MINDIM;
1601 <                } else if (height < .5*width) {
1602 <                        shp->type = DISK;
1603 <                        shp->w = shp->l = width;
1604 <                        if (height >= MINDIM)
1605 <                                shp->h = height;
1606 <                        else
1607 <                                shp->h = .5*MINDIM;
1608 <                } else {
1609 <                        shp->type = SPHERE;
1610 <                        shp->w = shp->l = shp->h = width;
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 <        } else {
1631 <                shp->type = RECT;
1632 <                shp->w = width;
1633 <                if (length >= MINDIM)
1634 <                        shp->l = length;
1630 >                shp->type = SPHERE;
1631 >                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 <                        shp->l = MINDIM;
1661 <                if (height >= MINDIM)
1662 <                        shp->h = height;
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 <                        shp->h = .5*MINDIM;
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 >                        shp->type = SPHERE;
1673 >                        shp->w = shp->l = shp->h = wp;
1674 >                }
1675 >                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 +        } else {
1779 +                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 +        }
1782 + }
1783 +
1784 + void
1785 + shapearea(SRCINFO *shp) {
1786          switch (shp->type) {
1787          case RECT:
1788                  shp->area = shp->w * shp->l;
# Line 875 | Line 1792 | makeshape(             /* make source shape */
1792                  shp->area = PI/4. * shp->w * shp->w;
1793                  break;
1794          }
1795 <        return(0);
879 < }
1795 > }      
1796  
1797 + /* 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  
1827 + /* 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   void
1838 < putrectsrc(             /* rectangular source */
1838 > putrectsrc(
1839          SRCINFO *shp,
1840          FILE    *fp,
1841          char    *mod,
# Line 894 | Line 1849 | putrectsrc(            /* rectangular source */
1849                  putrect(shp, fp, mod, name, ".d", 0, 2, 3, 1);
1850   }
1851  
1852 <
1852 > /* putsides - put out sides of box */
1853   void
1854 < putsides(                       /* put out sides of box */
1855 <        register SRCINFO        *shp,
1854 > putsides(
1855 >        SRCINFO *shp,
1856          FILE    *fp,
1857          char    *mod,
1858          char    *name
# Line 908 | Line 1863 | putsides(                      /* put out sides of box */
1863          putrect(shp, fp, mod, name, ".3", 3, 2, 6, 7);
1864          putrect(shp, fp, mod, name, ".4", 2, 0, 4, 6);
1865   }
911        
1866  
1867 + /* putrect - put out a rectangle
1868 + *
1869 + * putrect generates the "polygon" primitive which describes a
1870 + * rectangle.
1871 + */
1872   void
1873 < putrect(        /* put out a rectangle */
1873 > putrect(
1874          SRCINFO *shp,
1875          FILE    *fp,
1876          char    *mod,
# Line 923 | Line 1882 | putrect(       /* put out a rectangle */
1882          int d
1883   )
1884   {
1885 <        fprintf(fp, "\n%s polygon %s%s\n0\n0\n12\n", mod, name, suffix);
1885 >        fprintf(fp, "\n'%s' polygon '%s%s'\n0\n0\n12\n", mod, name, suffix);
1886          putpoint(shp, fp, a);
1887          putpoint(shp, fp, b);
1888          putpoint(shp, fp, c);
1889          putpoint(shp, fp, d);
1890   }
1891  
1892 <
1892 > /* putpoint -- output a the coordinates of a vertex
1893 > *
1894 > * putpoint maps vertex numbers to coordinates and outputs the
1895 > * coordinates.
1896 > */
1897   void
1898 < putpoint(                               /* put out a point */
1899 <        register SRCINFO        *shp,
1898 > putpoint(
1899 >        SRCINFO *shp,
1900          FILE    *fp,
1901          int     p
1902   )
# Line 946 | Line 1909 | putpoint(                              /* put out a point */
1909                          mult[p>>2]*shp->h*meters2out);
1910   }
1911  
1912 + /* End of routines to output a box-shaped light source */
1913  
1914 + /* 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   void
1921   putdisksrc(             /* put out a disk source */
1922 <        register SRCINFO        *shp,
1922 >        SRCINFO *shp,
1923          FILE    *fp,
1924          char    *mod,
1925          char    *name,
# Line 957 | Line 1927 | putdisksrc(            /* put out a disk source */
1927   )
1928   {
1929          if (up) {
1930 <                fprintf(fp, "\n%s ring %s.u\n", mod, name);
1930 >                fprintf(fp, "\n'%s' ring '%s.u'\n", mod, name);
1931                  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 <                fprintf(fp, "\n%s ring %s.d\n", mod, name);
1936 >                fprintf(fp, "\n'%s' ring '%s.d'\n", mod, name);
1937                  fprintf(fp, "0\n0\n8\n");
1938                  fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out);
1939                  fprintf(fp, "\t0 0 -1\n");
# Line 974 | Line 1944 | putdisksrc(            /* put out a disk source */
1944  
1945   void
1946   putcyl(                 /* put out a cylinder */
1947 <        register SRCINFO        *shp,
1947 >        SRCINFO *shp,
1948          FILE    *fp,
1949          char    *mod,
1950          char    *name
1951   )
1952   {
1953 <        fprintf(fp, "\n%s cylinder %s.c\n", mod, name);
1953 >        fprintf(fp, "\n'%s' cylinder '%s.c'\n", mod, name);
1954          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 + /* end of of routines to output cylinders and disks */
1961  
1962   void
1963   putspheresrc(           /* put out a sphere source */
# Line 996 | Line 1967 | putspheresrc(          /* put out a sphere source */
1967          char    *name
1968   )
1969   {
1970 <        fprintf(fp, "\n%s sphere %s.s\n", mod, name);
1970 >        fprintf(fp, "\n'%s' sphere '%s.s'\n", mod, name);
1971          fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out);
1972   }
1973  
1974 <
1974 > /* 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   int
1987 < cvdata(         /* convert data */
1988 <        FILE    *in,
1989 <        FILE    *out,
1990 <        int     ndim,
1991 <        int     npts[],
1992 <        double  mult,
1993 <        double  lim[][2]
1987 > 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   )
1999   {
2000 <        double  *pt[4];
2001 <        register int    i, j;
2000 >        double  *pt[4];         /* Four is the expected maximum of ndim. */
2001 >        int     i, j;
2002          double  val;
2003          int     total;
2004  
2005 +        /* Calculate and output the number of data values */
2006          total = 1; j = 0;
2007          for (i = 0; i < ndim; i++)
2008                  if (npts[i] > 1) {
# Line 1023 | Line 2010 | cvdata(                /* convert data */
2010                          j++;
2011                  }
2012          fprintf(out, "%d\n", j);
2013 <                                        /* get coordinates */
2013 >
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          for (i = 0; i < ndim; i++) {
2020 +                /* Allocate space for the angle values. */
2021                  pt[i] = (double *)malloc(npts[i]*sizeof(double));
2022                  for (j = 0; j < npts[i]; j++)
2023                          if (!scnflt(in, &pt[i][j]))
# Line 1034 | Line 2027 | cvdata(                /* convert data */
2027                          lim[i][1] = pt[i][npts[i]-1];
2028                  }
2029          }
2030 <                                        /* write out in reverse */
2030 >
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          for (i = ndim-1; i >= 0; i--) {
2043                  if (npts[i] > 1) {
2044 +                        /* Determine if the angles are evenly spaces */
2045                          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 +                        /* If they are, output the first angle, the
2050 +                         * last angle, and a count */
2051                          if (j == npts[i]-1)
2052                                  fprintf(out, "%g %g %d\n", pt[i][0], pt[i][j],
2053                                                  npts[i]);
2054                          else {
2055 +                                /* otherwise, output 0, 0, and a
2056 +                                 * count, followed by the list of
2057 +                                 * angles, one to a line. */
2058                                  fprintf(out, "0 0 %d", npts[i]);
2059                                  for (j = 0; j < npts[i]; j++) {
2060                                          if (j%4 == 0)
# Line 1054 | Line 2064 | cvdata(                /* convert data */
2064                                  putc('\n', out);
2065                          }
2066                  }
2067 +                /* Free the storage containing the angle values. */
2068                  free((void *)pt[i]);
2069          }
2070 +
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          for (i = 0; i < total; i++) {
2075                  if (i%4 == 0)
2076                          putc('\n', out);
# Line 1067 | Line 2082 | cvdata(                /* convert data */
2082          return(0);
2083   }
2084  
2085 <
2085 > /* 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   char *
2094   getword(                        /* scan a word from fp */
2095 <        register FILE   *fp
2095 >        FILE    *fp
2096   )
2097   {
2098          static char     wrd[RMAXWORD];
2099 <        register char   *cp;
2100 <        register int    c;
2099 >        char    *cp;
2100 >        int     c;
2101  
2102 +        /* Skip initial spaces */
2103          while (isspace(c=getc(fp)))
2104                  ;
2105 +        /* Get characters to a delimiter or until wrd is full */
2106          for (cp = wrd; c != EOF && cp < wrd+RMAXWORD-1;
2107                          *cp++ = c, c = getc(fp))
2108                  if (isspace(c) || c == ',') {
2109 +                        /* If we find a delimiter */
2110 +                        /* Gobble up whitespace */
2111                          while (isspace(c))
2112                                  c = getc(fp);
2113 +                        /* If it's not a comma, put the first
2114 +                         * character of the next data item back */
2115                          if ((c != EOF) & (c != ','))
2116                                  ungetc(c, fp);
2117 +                        /* Close out the strimg */
2118                          *cp = '\0';
2119 +                        /* return it */
2120                          return(wrd);
2121                  }
2122 +        /* If we ran out of space or are at the end of the file,
2123 +         * return either the word or NULL, as appropriate. */
2124          *cp = '\0';
2125          return(cp > wrd ? wrd : NULL);
2126   }
2127  
2128 <
2128 > /* 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   int
2137 < cvtint(                 /* convert a word to an integer */
2137 > cvtint(
2138          int     *ip,
2139          char    *wrd
2140   )
# Line 1107 | Line 2146 | cvtint(                        /* convert a word to an integer */
2146   }
2147  
2148  
2149 + /* 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   int
2157 < cvtflt(                 /* convert a word to a double */
2157 > cvtflt(
2158          double  *rp,
2159          char    *wrd
2160   )
# Line 1119 | Line 2165 | cvtflt(                        /* convert a word to a double */
2165          return(1);
2166   }
2167  
2168 <
2168 > /* 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   int
2189   cvgeometry(
2190          char    *inpname,
2191 <        register SRCINFO        *sinf,
2191 >        SRCINFO *sinf,
2192          char    *outname,
2193          FILE    *outfp                  /* close output file upon return */
2194   )
2195   {
2196          char    buf[256];
2197 <        register char   *cp;
2197 >        char    *cp;
2198  
2199          if (inpname == NULL || !inpname[0]) {   /* no geometry file */
2200                  fclose(outfp);
# Line 1139 | Line 2204 | cvgeometry(
2204          strcpy(buf, "mgf2rad ");                /* build mgf2rad command */
2205          cp = buf+8;
2206          if (!FEQ(sinf->mult, 1.0)) {
2207 <                sprintf(cp, "-m %f ", sinf->mult);
2207 >                /* if there's an output multiplier, include in the
2208 >                 * mgf2rad command */
2209 >                sprintf(cp, "-e %f ", sinf->mult);
2210                  cp += strlen(cp);
2211          }
2212 +        /* Include the glow distance for the geometry */
2213          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 +                /* If "-g" is given on the command line, include an
2219 +                 * "oconv" command in the pipe. */
2220                  strcpy(cp, "| oconv - > ");
2221                  cp += 12;
2222                  fullnam(cp,outname,T_OCT);
2223 +                /* Only update if the input file is newer than the
2224 +                 * output file */
2225                  if (fdate(inpname) > fdate(outname) &&
2226                                  system(buf)) {          /* create octree */
2227                          fclose(outfp);
2228                          return(-1);
2229                  }
2230 +                /* Reference the instance file in the scene description */
2231                  fprintf(outfp, "void instance %s_inst\n", outname);
2232 +                /* If the geometry isn't in meters, scale it appropriately. */
2233                  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 +                /* Close off the "instance" primitive. */
2240                  fprintf(outfp, "0\n0\n");
2241 +                /* And the Radiance scene description. */
2242                  fclose(outfp);
2243          } else {                        /* else append to luminaire file */
2244                  if (!FEQ(meters2out, 1.0)) {    /* apply scalefactor */
# Line 1180 | Line 2256 | cvgeometry(
2256          }
2257          return(0);
2258   }
2259 +
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" */

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines