| 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 | 
| 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 | 
| 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 |  | */ | 
| 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 | 
| 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 */ | 
| 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 */ | 
| 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); | 
| 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); | 
| 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. | 
| 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 | 
| 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); | 
| 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: | 
| 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 */ | 
| 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; | 
| 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 | 
| 1151 |  | datin = in; | 
| 1152 |  | strcpy(tltname, dfltname); | 
| 1153 |  | } else { | 
| 1154 | < | /* If the line is "TILE=<filename>", use that file | 
| 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); | 
| 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", | 
| 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. */ | 
| 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) { | 
| 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); | 
| 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 */ | 
| 1360 | < | fprintf(out, "%s %s 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_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 */ | 
| 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]); | 
| 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 | 
| 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( | 
| 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 | > | /* | 
| 1523 | > | * Generate the numeric key, the "thumbprint" for the various | 
| 1524 | > | * combinations of IES LM-63 version year, length, width, and height. | 
| 1525 | > | * This must be an integer constant expression so that it can be used | 
| 1526 | > | * in a case label.  See the header comments of makeiesshape() for | 
| 1527 | > | * additional information. | 
| 1528 | > | */ | 
| 1529 | > | #define TBPR(ver,l,w,h) ((ver) * 1000 + CONVSGN(l) * 100 + CONVSGN(w) * 10 + CONVSGN(h)) | 
| 1530 | > |  | 
| 1531 | > | /* makeiesshape - convert IES shape to Radiance shape | 
| 1532 | > | * | 
| 1533 | > | * Some 34 cases in the various versions of the IES LM-63 standard are | 
| 1534 | > | * handled, though some only by approximation.  For each case which is | 
| 1535 | > | * processed a Radiance box, cylinder, or sphere is selected. | 
| 1536 | > | * | 
| 1537 | > | * Shapes are categorized by version year of the standard and the | 
| 1538 | > | * signs of the LM-63 length, width (depth), and height fields.  These | 
| 1539 | > | * are combined and converted to an integer, which is then used as the | 
| 1540 | > | * argument to switch().  The last two digits of the IES file version | 
| 1541 | > | * year are used and the signs of length, width, and height are | 
| 1542 | > | * encoded, in that order, as 1 for negative, 2 for zero, and 3 for | 
| 1543 | > | * positive.  These are then combined into a numeric key by the | 
| 1544 | > | * following formula: | 
| 1545 | > | * | 
| 1546 | > | *   version * 1000 + sgn(length) * 100 + sgn(width) * 10 + sgn(height) | 
| 1547 | > | * | 
| 1548 | > | * The macro TBPR implements this formula. | 
| 1549 | > | * | 
| 1550 | > | * Since the 1991 version uses the same encoding as the 1986 version, | 
| 1551 | > | * and the 2019 version uses the same encoding as the 2002 version, | 
| 1552 | > | * these are collapsed into the earlier years. | 
| 1553 | > | * | 
| 1554 | > | * In the cases of the switch() statement, further processing takes | 
| 1555 | > | * place. Circles and ellipses are distinguished by comparisons.  Then | 
| 1556 | > | * routines are called to fill out the fields of the shp structure. | 
| 1557 | > | * | 
| 1558 | > | * As per the conventions of the rest of ies2rad, makeiesshape() | 
| 1559 | > | * returns 0 on success and -1 on failure.  -1 reflects an error in | 
| 1560 | > | * the IES file and is unusual. | 
| 1561 | > | * | 
| 1562 | > | * By convention, the shape generating routines are always given | 
| 1563 | > | * positive values for dimensions and always succeed; all errors are | 
| 1564 | > | * caught before they are called.  The absolute values of all three | 
| 1565 | > | * dimensions are calculated at the beginning of makeiesshape() and | 
| 1566 | > | * used throughout the function, this has a low cost and eliminates | 
| 1567 | > | * the chance of sign errors. | 
| 1568 | > | * | 
| 1569 | > | * There are two extensions to the ies standard here: | 
| 1570 | > | * | 
| 1571 | > | *   1. devised to accomdate wall-mounted fixtures; vertical rectangles, | 
| 1572 | > | *   not formally supported by any version of LM-63, are treated as | 
| 1573 | > | *   boxes. | 
| 1574 | > | * | 
| 1575 | > | *   2. A 2002-flagged file with only a negative width will be | 
| 1576 | > | *   recognized as a disk. | 
| 1577 | > | * | 
| 1578 | > | * The code is complicated by the way that earlier versions of the | 
| 1579 | > | * standard (1986 and 1991) prioritize width in their discussions, and | 
| 1580 | > | * later versions prioritize length.  It is not always clear which to | 
| 1581 | > | * write first and there is hesitation between the older code which | 
| 1582 | > | * invokes makeiesshape() and makeiesshape() itself. | 
| 1583 | > | */ | 
| 1584 | > | int | 
| 1585 | > | makeiesshape(SRCINFO *shp, double l, double w, double h) { | 
| 1586 | > | int rc = SUCCESS; | 
| 1587 | > | int shape = IESNONE; | 
| 1588 | > | /* Get the last two digits of the standard year  */ | 
| 1589 | > | int ver = shp->filerev % 100; | 
| 1590 | > | /* Make positive versions of all dimensions, for clarity in | 
| 1591 | > | * function calls.  If you like, read this as l', w', and h'. */ | 
| 1592 | > | double lp = fabs(l), wp = fabs(w), hp = fabs(h); | 
| 1593 | > | int thumbprint; | 
| 1594 | > |  | 
| 1595 | > | /* Change 1991 into 1986 and 2019 in 2002 */ | 
| 1596 | > | switch (ver) { | 
| 1597 | > | case 91: | 
| 1598 | > | ver = 86; | 
| 1599 | > | break; | 
| 1600 | > | case 19: | 
| 1601 | > | ver = 02; | 
| 1602 | > | break; | 
| 1603 | > | } | 
| 1604 | > |  | 
| 1605 | > | thumbprint = TBPR(ver, l, w, h); | 
| 1606 | > | switch(thumbprint) { | 
| 1607 | > | case TBPR(86,0,0,0): case TBPR(95, 0, 0, 0): case TBPR(02, 0, 0, 0): | 
| 1608 | > | shp->iesshape = IESPT; | 
| 1609 |  | shp->type = SPHERE; | 
| 1610 | < | shp->w = shp->l = shp->h = 2.*illumrad / meters2out; | 
| 1611 | < | /* Otherwise, use the dimensions in the IES file */ | 
| 1612 | < | } else if (width < MINDIM) { | 
| 1613 | < | width = -width; | 
| 1614 | < | if (width < MINDIM) { | 
| 1615 | < | /* If the LM-63 width is zero, assume a point | 
| 1616 | < | * source is described.  Output a small | 
| 1617 | < | * sphere. */ | 
| 1618 | < | shp->type = SPHERE; | 
| 1619 | < | shp->w = shp->l = shp->h = MINDIM; | 
| 1620 | < | } else if (height < .5*width) { | 
| 1621 | < | /* The width is negative and the height is | 
| 1622 | < | * less than half the width.  Treat the | 
| 1623 | < | * luminous opening as a disk or short | 
| 1624 | < | * vertical cylinder. Disks will be | 
| 1625 | < | * represented as nearly flat cylinders of | 
| 1626 | < | * MINDIM/2 height. */ | 
| 1627 | < | shp->type = DISK; | 
| 1628 | < | shp->w = shp->l = width; | 
| 1629 | < | if (height >= MINDIM) | 
| 1630 | < | shp->h = height; | 
| 1631 | < | else | 
| 1632 | < | shp->h = .5*MINDIM; | 
| 1633 | < | } else { | 
| 1634 | < | /* Treat a tall cylinder as a sphere. */ | 
| 1635 | < | shp->type = SPHERE; | 
| 1636 | < | shp->w = shp->l = shp->h = width; | 
| 1610 | > | shp->w = shp->l = shp->h = MINDIM; | 
| 1611 | > | break; | 
| 1612 | > | case TBPR(86, 1, 1, 0): case TBPR(95, 1, 1, 0): case TBPR(02, 1, 1, 0): | 
| 1613 | > | shp->iesshape = IESRECT; | 
| 1614 | > | makeboxshape(shp, lp, wp, hp); | 
| 1615 | > | break; | 
| 1616 | > | case TBPR(86, 1, 1, 1): case TBPR(86, 0, 1, 1): case TBPR(86, 1, 0, 1): | 
| 1617 | > | case TBPR(95, 1, 1, 1): case TBPR(95, 0, 1, 1): case TBPR(95, 1, 0, 1): | 
| 1618 | > | case TBPR(02, 1, 1, 1): case TBPR(02, 0, 1, 1): case TBPR(02, 1, 0, 1): | 
| 1619 | > | shp->iesshape = IESBOX; | 
| 1620 | > | makeboxshape(shp, lp, wp, hp); | 
| 1621 | > | break; | 
| 1622 | > | case TBPR(86,0,-1,0): case TBPR(95,0,-1,0): case TBPR(02,0,-1,0): | 
| 1623 | > | shp->iesshape = IESDISK; | 
| 1624 | > | makecylshape(shp, wp, hp); | 
| 1625 | > | break; | 
| 1626 | > | case TBPR(86, 0, -1, 1): | 
| 1627 | > | shp->iesshape = IESVCYL; | 
| 1628 | > | makecylshape(shp, wp, hp); | 
| 1629 | > | break; | 
| 1630 | > | case TBPR(86, 1, -1, 0): | 
| 1631 | > | shp->iesshape = IESELLIPSE; | 
| 1632 | > | makeecylshape(shp, lp, wp, 0); | 
| 1633 | > | break; | 
| 1634 | > | case TBPR(86, 1, -1, 1): | 
| 1635 | > | shp->iesshape = IESELLIPSOID; | 
| 1636 | > | makeelshape(shp, wp, lp, hp); | 
| 1637 | > | break; | 
| 1638 | > | case TBPR(95, 0, -1, -1): | 
| 1639 | > | shp->iesshape = FEQ(lp,hp) ? IESSPHERE : IESNONE; | 
| 1640 | > | if (shp->iesshape == IESNONE) { | 
| 1641 | > | shp->warn = "makeshape: cannot determine shape\n"; | 
| 1642 | > | rc = FAIL; | 
| 1643 | > | break; | 
| 1644 |  | } | 
| 1645 | < | } else { | 
| 1646 | < | /* The width is positive. The luminous opening is a | 
| 1647 | < | box or simple rectangle. */ | 
| 1648 | < | shp->type = RECT; | 
| 1649 | < | shp->w = width; | 
| 1650 | < | if (length >= MINDIM) | 
| 1651 | < | shp->l = length; | 
| 1645 | > | shp->type = SPHERE; | 
| 1646 | > | shp->w = shp->l = shp->h = wp; | 
| 1647 | > | break; | 
| 1648 | > | case TBPR(95, 0, -1, 1): | 
| 1649 | > | shp->iesshape = IESVCYL; | 
| 1650 | > | makecylshape(shp, wp, hp); | 
| 1651 | > | break; | 
| 1652 | > | case TBPR(95, 1, 0, -1): | 
| 1653 | > | shp->iesshape = IESHCYL_PH; | 
| 1654 | > | shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1655 | > | makeboxshape(shp, lp, wp, hp); | 
| 1656 | > | break; | 
| 1657 | > | case TBPR(95, 0, 1, -1): | 
| 1658 | > | shp->iesshape = IESHCYL_PPH; | 
| 1659 | > | shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1660 | > | makeboxshape(shp, lp, wp, hp); | 
| 1661 | > | break; | 
| 1662 | > | case TBPR(95, -1, 1, 1): case TBPR(95, 1, -1, 1): | 
| 1663 | > | shp->iesshape = IESVECYL; | 
| 1664 | > | makeecylshape(shp, lp, wp, hp); | 
| 1665 | > | break; | 
| 1666 | > | case TBPR(95, -1, 1, -1): case TBPR(95, 1, -1, -1): | 
| 1667 | > | shp->iesshape = IESELLIPSOID; | 
| 1668 | > | makeelshape(shp, lp, wp, hp); | 
| 1669 | > | break; | 
| 1670 | > | case TBPR(02, -1, -1, 0): | 
| 1671 | > | shp->iesshape = FEQ(l,w) ? IESDISK : IESELLIPSE; | 
| 1672 | > | if (shp->iesshape == IESDISK) | 
| 1673 | > | makecylshape(shp, wp, hp); | 
| 1674 |  | else | 
| 1675 | < | shp->l = MINDIM; | 
| 1676 | < | if (height >= MINDIM) | 
| 1677 | < | shp->h = height; | 
| 1675 | > | makeecylshape(shp, wp, lp, hp); | 
| 1676 | > | break; | 
| 1677 | > | case TBPR(02, -1, -1, 1): | 
| 1678 | > | shp->iesshape = FEQ(l,w) ? IESVCYL : IESVECYL; | 
| 1679 | > | if (shp->iesshape == IESVCYL) | 
| 1680 | > | makecylshape(shp, wp, hp); | 
| 1681 |  | else | 
| 1682 | < | shp->h = .5*MINDIM; | 
| 1682 | > | makeecylshape(shp, wp, lp, hp); | 
| 1683 | > | break; | 
| 1684 | > | case TBPR(02, -1, -1, -1): | 
| 1685 | > | shp->iesshape = FEQ(l,w) && FEQ(l,h) ? IESSPHERE : IESELLIPSOID; | 
| 1686 | > | if (shp->iesshape == IESSPHERE) { | 
| 1687 | > | shp->type = SPHERE; | 
| 1688 | > | shp->w = shp->l = shp->h = wp; | 
| 1689 | > | } | 
| 1690 | > | else | 
| 1691 | > | makeelshape(shp, lp, wp, hp); | 
| 1692 | > | break; | 
| 1693 | > | case TBPR(02, 1, -1, -1): | 
| 1694 | > | shp->iesshape = FEQ(w,h) ? IESHCYL_PH : IESHECYL_PH; | 
| 1695 | > | shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1696 | > | makeboxshape(shp, lp, wp, hp); | 
| 1697 | > | break; | 
| 1698 | > | case TBPR(02, -1, 1, -1): | 
| 1699 | > | shp->iesshape = FEQ(l,h) ? IESHCYL_PPH : IESHECYL_PPH; | 
| 1700 | > | shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1701 | > | makeboxshape(shp, lp, wp, hp); | 
| 1702 | > | break; | 
| 1703 | > | case TBPR(02, -1, 0, -1): | 
| 1704 | > | shp->iesshape = FEQ(w,h) ? IESVDISK_PH : IESVEL_PH; | 
| 1705 | > | shp->warn = "makeshape: shape is a vertical ellipse, which is not supported.\nmakeshape: replaced with rectangle\n"; | 
| 1706 | > | makeboxshape(shp, lp, wp, hp); | 
| 1707 | > | break; | 
| 1708 | > | default: | 
| 1709 | > | /* We don't recognize the shape - report an error. */ | 
| 1710 | > | rc = FAIL; | 
| 1711 |  | } | 
| 1712 | + | return rc; | 
| 1713 | + | } | 
| 1714 |  |  | 
| 1715 | < | /* Done choosing the shape; calculate its area in the x-y plane. */ | 
| 1715 | > | /* makeillumsphere - create an illum sphere */ | 
| 1716 | > | int | 
| 1717 | > | makeillumsphere(SRCINFO *shp) { | 
| 1718 | > | /* If the size is too small or negative, error. */ | 
| 1719 | > | if (illumrad/meters2out < MINDIM/2.) { | 
| 1720 | > | fprintf(stderr, "makeillumsphere: -i argument is too small or negative\n"); | 
| 1721 | > | return FAIL; | 
| 1722 | > | } | 
| 1723 | > | shp->isillum = 1; | 
| 1724 | > | shp->type = SPHERE; | 
| 1725 | > | shp->w = shp->l = shp->h = 2.*illumrad / meters2out; | 
| 1726 | > | return SUCCESS; | 
| 1727 | > | } | 
| 1728 | > |  | 
| 1729 | > | /* makeboxshape - create a box */ | 
| 1730 | > | void | 
| 1731 | > | makeboxshape(SRCINFO *shp, double l, double w, double h) { | 
| 1732 | > | shp->type = RECT; | 
| 1733 | > | shp->l = fmax(l, MINDIM); | 
| 1734 | > | shp->w = fmax(w, MINDIM); | 
| 1735 | > | shp->h = fmax(h, .5*MINDIM); | 
| 1736 | > | } | 
| 1737 | > |  | 
| 1738 | > | /* makecylshape - output a vertical cylinder or disk | 
| 1739 | > | * | 
| 1740 | > | * If the shape has no height, make it a half-millimeter. | 
| 1741 | > | */ | 
| 1742 | > | void | 
| 1743 | > | makecylshape(SRCINFO *shp, double diam, double height) { | 
| 1744 | > | shp->type = DISK; | 
| 1745 | > | shp->w = shp->l = diam; | 
| 1746 | > | shp->h = fmax(height, .5*MINDIM); | 
| 1747 | > | } | 
| 1748 | > |  | 
| 1749 | > | /* makeelshape - create a substitute for an ellipsoid | 
| 1750 | > | * | 
| 1751 | > | * Because we don't actually support ellipsoids, and they don't seem | 
| 1752 | > | * to be common in actual IES files. | 
| 1753 | > | */ | 
| 1754 | > | void | 
| 1755 | > | makeelshape(SRCINFO *shp, double w, double l, double h) { | 
| 1756 | > | float avg = (w + l + h) / 3; | 
| 1757 | > | float bot = .5 * avg; | 
| 1758 | > | float top = 1.5 * avg; | 
| 1759 | > |  | 
| 1760 | > | if (bot < w && w < top | 
| 1761 | > | && bot < l && l < top | 
| 1762 | > | && bot < h && h > top) { | 
| 1763 | > | /* it's sort of spherical, replace it with a sphere */ | 
| 1764 | > | shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with sphere\n"; | 
| 1765 | > | shp->type = SPHERE; | 
| 1766 | > | shp->w = shp->l = shp->h = avg; | 
| 1767 | > | } else if (bot < w && w < top | 
| 1768 | > | && bot < l && l < top | 
| 1769 | > | && h <= .5*MINDIM) { | 
| 1770 | > | /* It's flat and sort of circular, replace it | 
| 1771 | > | * with a disk. */ | 
| 1772 | > | shp->warn = "makeshape: shape is an ellipse, which is not supported.\nmakeshape: replaced with disk\n"; | 
| 1773 | > | makecylshape(shp, w, 0); | 
| 1774 | > | } else { | 
| 1775 | > | shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1776 | > | makeboxshape(shp, w, l, h); | 
| 1777 | > | } | 
| 1778 | > | } | 
| 1779 | > |  | 
| 1780 | > | /* makeecylshape - create a substitute for an elliptical cylinder or disk */ | 
| 1781 | > | void | 
| 1782 | > | makeecylshape(SRCINFO *shp, double l, double w, double h) { | 
| 1783 | > | float avg = (w + l) / 2; | 
| 1784 | > | float bot = .5 * avg; | 
| 1785 | > | float top = 1.5 * avg; | 
| 1786 | > |  | 
| 1787 | > | if (bot < w && w < top | 
| 1788 | > | && bot < l && l < top) { | 
| 1789 | > | /* It's sort of circular, replace it | 
| 1790 | > | * with a circular cylinder. */ | 
| 1791 | > | shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with circular cylinder\n"; | 
| 1792 | > | makecylshape(shp, w, h); | 
| 1793 | > | } else { | 
| 1794 | > | shp->warn = "makeshape: shape is a  vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with box\n"; | 
| 1795 | > | makeboxshape(shp, w, l, h); | 
| 1796 | > | } | 
| 1797 | > | } | 
| 1798 | > |  | 
| 1799 | > | void | 
| 1800 | > | shapearea(SRCINFO *shp) { | 
| 1801 |  | switch (shp->type) { | 
| 1802 |  | case RECT: | 
| 1803 |  | shp->area = shp->w * shp->l; | 
| 1807 |  | shp->area = PI/4. * shp->w * shp->w; | 
| 1808 |  | break; | 
| 1809 |  | } | 
| 1810 | < | return(0); | 
| 1338 | < | } | 
| 1810 | > | } | 
| 1811 |  |  | 
| 1812 |  | /* Rectangular or box-shaped light source. | 
| 1813 |  | * | 
| 1897 |  | int d | 
| 1898 |  | ) | 
| 1899 |  | { | 
| 1900 | < | fprintf(fp, "\n%s polygon %s%s\n0\n0\n12\n", mod, name, suffix); | 
| 1900 | > | fprintf(fp, "\n'%s' polygon '%s%s'\n0\n0\n12\n", mod, name, suffix); | 
| 1901 |  | putpoint(shp, fp, a); | 
| 1902 |  | putpoint(shp, fp, b); | 
| 1903 |  | putpoint(shp, fp, c); | 
| 1942 |  | ) | 
| 1943 |  | { | 
| 1944 |  | if (up) { | 
| 1945 | < | fprintf(fp, "\n%s ring %s.u\n", mod, name); | 
| 1945 | > | fprintf(fp, "\n'%s' ring '%s.u'\n", mod, name); | 
| 1946 |  | fprintf(fp, "0\n0\n8\n"); | 
| 1947 |  | fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out); | 
| 1948 |  | fprintf(fp, "\t0 0 1\n"); | 
| 1949 |  | fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out); | 
| 1950 |  | } else { | 
| 1951 | < | fprintf(fp, "\n%s ring %s.d\n", mod, name); | 
| 1951 | > | fprintf(fp, "\n'%s' ring '%s.d'\n", mod, name); | 
| 1952 |  | fprintf(fp, "0\n0\n8\n"); | 
| 1953 |  | fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out); | 
| 1954 |  | fprintf(fp, "\t0 0 -1\n"); | 
| 1965 |  | char    *name | 
| 1966 |  | ) | 
| 1967 |  | { | 
| 1968 | < | fprintf(fp, "\n%s cylinder %s.c\n", mod, name); | 
| 1968 | > | fprintf(fp, "\n'%s' cylinder '%s.c'\n", mod, name); | 
| 1969 |  | fprintf(fp, "0\n0\n7\n"); | 
| 1970 |  | fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out); | 
| 1971 |  | fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out); | 
| 1982 |  | char    *name | 
| 1983 |  | ) | 
| 1984 |  | { | 
| 1985 | < | fprintf(fp, "\n%s sphere %s.s\n", mod, name); | 
| 1985 | > | fprintf(fp, "\n'%s' sphere '%s.s'\n", mod, name); | 
| 1986 |  | fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out); | 
| 1987 |  | } | 
| 1988 |  |  |