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.34 by greg, Thu Sep 30 20:05:09 2021 UTC vs.
Revision 2.35 by greg, Mon Nov 29 16:07:36 2021 UTC

# Line 59 | Line 59 | static const char      RCSid[] = "$Id$";
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
# Line 78 | Line 154 | static const char      RCSid[] = "$Id$";
154  
155   #define PI              3.14159265358979323846
156  
157 < /* floating comparisons -- floating point numbers within FTINY of each
158 < * other are considered equal */
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)
164  
165 + #define IESFIRSTVER 1986
166 + #define IESLASTVER 2019
167  
87 /* IESNA LM-63 keywords and constants */
88 /* Since 1991, LM-63 files have begun with the magic keyword IESNA */
89 #define MAGICID         "IESNA"
90 #define LMAGICID        5
91 /* But newer files start with IESNA:LM-63- */
92 #define MAGICID2        "IESNA:LM-63-"
93 #define LMAGICID2       12
94 /* ies2rad supports the 1986, 1991, and 1995 versions of
95 * LM-63. FIRSTREV describes the first version; LASTREV describes the
96 * 1995 version. */
97 #define FIRSTREV        86
98 #define LASTREV         95
99
100 /* The following definitions support LM-63 file keyword reading and
101 * analysis.
102 *
103 * This section defines two function-like macros: keymatch(i,s), which
104 * checks to see if keyword i matches string s, and checklamp(s),
105 * which checks to see if a string matches the keywords "LAMP" or
106 * "LAMPCAT".
107 *
108 * LM-63-1986 files begin with a list of free-form label lines.
109 * LM-63-1991 files begin with the identifying line "IESNA91" followed
110 * by a list of formatted keywords.  LM-63-1995 files begin with the
111 * identifying line "IESNA:LM-63-1995" followed by a list of formatted
112 * keywords.
113 *
114 * The K_* #defines enumerate the keywords used in the different
115 * versions of the file and give them symbolic names.
116 *
117 * The D86, D91, and D95 #defines validate the keywords in the 1986,
118 * 1991, and 1995 versions of the standard, one bit per keyword.
119 * Since the 1986 standard does not use keywords, D86 is zero.  The
120 * 1991 standard has 13 keywords, and D91 has the lower 13 bits set.
121 * The 1995 standard has 14 keywords, and D95 has the lower 14 bits
122 * set.
123 *
124 */
125 #define D86             0
126
127 #define K_TST           0
128 #define K_MAN           1
129 #define K_LMC           2
130 #define K_LMN           3
131 #define K_LPC           4
132 #define K_LMP           5
133 #define K_BAL           6
134 #define K_MTC           7
135 #define K_OTH           8
136 #define K_SCH           9
137 #define K_MOR           10
138 #define K_BLK           11
139 #define K_EBK           12
140
141 /* keywords defined in LM-63-1991 */
142 #define D91             ((1L<<13)-1)
143
144 #define K_LMG           13
145
146 /* keywords defined in LM-63-1995 */
147 #define D95             ((1L<<14)-1)
148
149 char    k_kwd[][20] = {"TEST", "MANUFAC", "LUMCAT", "LUMINAIRE", "LAMPCAT",
150                        "LAMP", "BALLAST", "MAINTCAT", "OTHER", "SEARCH",
151                        "MORE", "BLOCK", "ENDBLOCK", "LUMINOUSGEOMETRY"};
152
153 long k_defined[] = {D86, D86, D86, D86, D86, D91, D91, D91, D91, D95};
154
155 int     filerev = FIRSTREV;
156
157 #define keymatch(i,s)   (k_defined[filerev-FIRSTREV]&1L<<(i) &&\
158                                k_match(k_kwd[i],s))
159
160 #define checklamp(s)    (!(k_defined[filerev-FIRSTREV]&(1<<K_LMP|1<<K_LPC)) ||\
161                                keymatch(K_LMP,s) || keymatch(K_LPC,s))
162
168   /* tilt specs
169   *
170   * This next series of definitions address metal-halide lamps, which
# Line 203 | Line 208 | int    filerev = FIRSTREV;
208   #define U_METERS        2
209  
210   /* string lengths */
211 < /* Maximum input line is 256 characters including CR LF at end. */
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  
218 < /* End of LM-63-related #defines */
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  
255 < /* shape types
255 > /* Radiance shape types
256   * These #defines enumerate the shapes of the Radiance objects which
257   * emit the light.
258   */
# Line 236 | Line 273 | int    filerev = FIRSTREV;
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
# Line 253 | 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 */
# Line 265 | Line 330 | typedef struct {
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   /* A count and pointer to the list of input file names */
# Line 287 | Line 357 | char   **gargv;                        /* global argv */
357   * confusing define accommodates that.  */
358   #define isint           isflt
359  
360 < /* Function declarations */
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 298 | 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 309 | 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.
# Line 537 | Line 623 | initlamps(void)                                /* set up lamps */
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
# Line 631 | Line 846 | libname(
846          return(path);
847   }
848  
849 < /* filename - find the base file name in a buffer containing a path
849 > /* filename - pointer to filename in buffer containing path
850   *
851 < * The pointer is to a character within the buffer, not a string in itself;
852 < * it will become invalid when the buffer is freed.
853 < *
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(
857          char    *path
858   )
859   {
860 <        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);
# Line 717 | Line 932 | blanktrunc(
932          *++cp = '\0';
933   }
934  
935 < /* k_match - return true if keyword matches header line */
721 < int
722 < k_match(
723 <        char    *kwd,           /* keyword */
724 <        char    *hdl            /* header line */
725 < )
726 < {
727 <        /* Skip leading spaces */
728 <        while (isspace(*hdl))
729 <                hdl++;
730 <        /* The line has to begin with '[' */
731 <        if (*hdl++ != '[')
732 <                return(0);
733 <        /* case-independent keyword match */
734 <        while (toupper(*hdl) == *kwd++)
735 <                if (!*hdl++)
736 <                        return(0);
737 <        /* If we have come to the end of the keyword, and the keyword
738 <         * at the beginning of the matched line is terminated with
739 <         * ']', return 1 */
740 <        return(!kwd[-1] & (*hdl == ']'));
741 < }
742 <
743 < /* keyargs - return the argument of a keyword, without leading spaces
935 > /* fpcomment - output a multi-line comment
936   *
937 < * keyargs is passed a pointer to a buffer; it returns a pointer to
938 < * where the argument starts in the buffer
939 < *
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 < char *
942 < keyargs(
943 <        char    *hdl /* header line */
944 < )
945 < {
946 <        while (*hdl && *hdl++ != ']')
947 <                ;
948 <        while (isspace(*hdl))
949 <                hdl++;
950 <        return(hdl);
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  
761
954   /* putheader - output the header of the .rad file
955   *
956   * Header is:
# Line 790 | Line 982 | putheader(
982   *
983   * Return -1 in case of failure, 0 in case of success.
984   *
793 * The file version recognition is confused and will treat 1995 and
794 * 2002 version files as 1986 version files.
795 *
985   */
986   int
987   ies2rad(                /* convert IES file */
# Line 802 | 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 <        /* Open input and output files */
999 <        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 840 | Line 1035 | ies2rad(               /* convert IES file */
1035                  blanktrunc(buf); /* Trim trailing spaces, CR, LF. */
1036                  if (!buf[0])     /* Skip blank lines */
1037                          continue;
1038 <                /* increment the header line count, and check for the
1039 <                 * "TILT=" line that terminates the header */
1040 <                if (!lineno++) {        /* first line may be magic */
1041 <                        if (!strncmp(buf, MAGICID2, LMAGICID2))
1042 <                                filerev = atoi(buf+LMAGICID2) - 1900;
1043 <                        else if (!strncmp(buf, MAGICID, LMAGICID))
849 <                                filerev = atoi(buf+LMAGICID);
850 <                        if (filerev < FIRSTREV)
851 <                                filerev = FIRSTREV;
852 <                        else if (filerev > LASTREV)
853 <                                filerev = LASTREV;
854 <                }
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  
1049 <                /* If the header line is a keyword line (file version
1050 <                 * later than 1986 and begins with '['), check a lamp
1051 <                 * in the "[LAMP]" and "[LAMPCAT]" keyword lines;
1052 <                 * otherwise check all lines.  */
1053 <                if (lampcolor == NULL && checklamp(buf))
1054 <                        lampcolor = matchlamp(*sskip2(buf,0) == '[' ?
1055 <                                                keyargs(buf) : buf );
867 <                /* Look for a materials and geometry file in the keywords. */
868 <                if (keymatch(K_LMG, buf)) {
869 <                        strcpy(geomfile, inpname);
870 <                        strcpy(filename(geomfile), keyargs(buf));
871 <                        srcinfo.isillum = 1;
872 <                }
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
# Line 1072 | Line 1255 | dosource(
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",
# Line 1081 | Line 1272 | dosource(
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," which was part
1276 <         * of the 1986 and 1991 standards. In the 1995 standard, it is
1277 <         * always supposed to be 1. */
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. */
# Line 1102 | Line 1296 | dosource(
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) {
# Line 1120 | Line 1317 | dosource(
1317          fclose(datout);
1318  
1319          /* Output explanatory comment */
1320 <        fprintf(out, "# %g watt luminaire, lamp*ballast factor = %g\n",
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. */
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);
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  
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); /* Bottom */
1356 <        doupper = (bounds[0][1] > 90.+FTINY); /* Top */
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 */
# Line 1149 | Line 1363 | dosource(
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_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 */
# Line 1252 | Line 1472 | putsource(
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
# Line 1265 | Line 1488 | putsource(
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(
# Line 1274 | Line 1501 | makeshape(
1501          double  height
1502   )
1503   {
1504 <        /* Categorize the shape */
1505 <        if (illumrad/meters2out >= MINDIM/2.) {
1506 <                /* If the -i command line option is used, output an
1507 <                 * "illum" sphere whose radius is given by the
1508 <                 * argument to -i. */
1509 <                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 <                /* Otherwise, use the dimensions in the IES file */
1597 <        } else if (width < MINDIM) {
1598 <                width = -width;
1599 <                if (width < MINDIM) {
1600 <                        /* If the LM-63 width is zero, assume a point
1601 <                         * source is described.  Output a small
1602 <                         * sphere. */
1603 <                        shp->type = SPHERE;
1604 <                        shp->w = shp->l = shp->h = MINDIM;
1605 <                } else if (height < .5*width) {
1606 <                        /* The width is negative and the height is
1607 <                         * less than half the width.  Treat the
1608 <                         * luminous opening as a disk or short
1609 <                         * vertical cylinder. Disks will be
1610 <                         * represented as nearly flat cylinders of
1611 <                         * MINDIM/2 height. */
1612 <                        shp->type = DISK;
1613 <                        shp->w = shp->l = width;
1614 <                        if (height >= MINDIM)
1615 <                                shp->h = height;
1616 <                        else
1617 <                                shp->h = .5*MINDIM;
1618 <                } else {
1619 <                        /* Treat a tall cylinder as a sphere. */
1620 <                        shp->type = SPHERE;
1621 <                        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 <                /* The width is positive. The luminous opening is a
1632 <                   box or simple rectangle. */
1633 <                shp->type = RECT;
1634 <                shp->w = width;
1635 <                if (length >= MINDIM)
1636 <                        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 <        /* Done choosing the shape; calculate its area in the x-y plane. */
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 1334 | Line 1792 | makeshape(
1792                  shp->area = PI/4. * shp->w * shp->w;
1793                  break;
1794          }
1795 <        return(0);
1338 < }
1795 > }      
1796  
1797   /* Rectangular or box-shaped light source.
1798   *

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines