--- ray/src/cv/ies2rad.c 1997/11/10 10:13:58 2.17 +++ ray/src/cv/ies2rad.c 2021/09/20 02:00:05 2.33 @@ -1,33 +1,128 @@ -/* Copyright (c) 1996 Regents of the University of California */ - #ifndef lint -static char SCCSid[] = "$SunId$ LBL"; +static const char RCSid[] = "$Id: ies2rad.c,v 2.33 2021/09/20 02:00:05 greg Exp $"; #endif - /* - * Convert IES luminaire data to Radiance description + * ies2rad -- Convert IES luminaire data to Radiance description * + * ies2rad converts an IES LM-63 luminare description to a Radiance + * luminaire description. In addition, ies2rad manages a local + * database of Radiance luminaire files. + * + * Ies2rad generates two or three files for each luminaire. For a + * luminaire named LUM, ies2rad will generate LUM.rad, a Radiance + * scene description file which describes the light source, LUM.dat, + * which contains the photometric data from the IES LM-63 file, and + * (if tilt data is provided) LUM%.dat, which contains the tilt data + * from the IES file. + * + * Ies2rad is supported by the Radiance function files source.cal and + * tilt.cal, which transform the coordinates in the IES data into + * Radiance (θ,φ) luminaire coordinates and then apply photometric and + * tilt data to generate Radiance light. θ is altitude from the + * negative z-axis and φ is azimuth from the positive x-axis, + * increasing towards the positive y-axis. This system matches none of + * the usual goniophotometric conventions, but it is closest to IES + * type C; V in type C photometry is θ in Radiance and L is -φ. + * + * The ies2rad scene description for a luminaire LUM, with tilt data, + * uses the following Radiance scene description primitives: + * + * void brightdata LUM_tilt + * … + * LUM_tilt brightdata LUM_dist + * … + * LUM_dist light LUM_light + * … + * LUM_light surface1 name1 + * … + * LUM_light surface2 name2 + * … + * LUM_light surface_n name_n + * + * Without tilt data, the primitives are: + * + * void brightdata LUM_dist + * … + * LUM_dist light LUM_light + * … + * LUM_light surface1 name1 + * … + * LUM_light surface2 name2 + * … + * LUM_light surface_n name_n + * + * As many surfaces are given as required to describe the light + * source. Illum may be used rather than light so that a visible form + * (impostor) may be given to the luminaire, rather than a simple + * glowing shape. If an impostor is provided, it must be wholly + * contained within the illum and if it provides impostor light + * sources, those must be given with glow, so that they do not + * themselves illuminate the scene, providing incorrect results. + * + * The ies2rad code uses the "bsd" style. For emacs, this is set up + * automatically in the "Local Variables" section at the end of the + * file. For vim, use ":set tabstop=8 shiftwidth=8". + * * 07Apr90 Greg Ward + * + * Fixed correction factor for flat sources 29Oct2001 GW + * Extensive comments added by Randolph Fritz May2018 */ -#include #include -#include #include + +#include "rtio.h" #include "color.h" #include "paths.h" #define PI 3.14159265358979323846 - /* floating comparisons */ + +/* floating comparisons -- floating point numbers within FTINY of each + * other are considered equal */ #define FTINY 1e-6 #define FEQ(a,b) ((a)<=(b)+FTINY&&(a)>=(b)-FTINY) - /* keywords */ + + +/* IESNA LM-63 keywords and constants */ +/* Since 1991, LM-63 files have begun with the magic keyword IESNA */ #define MAGICID "IESNA" #define LMAGICID 5 +/* But newer files start with IESNA:LM-63- */ +#define MAGICID2 "IESNA:LM-63-" +#define LMAGICID2 12 +/* ies2rad supports the 1986, 1991, and 1995 versions of + * LM-63. FIRSTREV describes the first version; LASTREV describes the + * 1995 version. */ #define FIRSTREV 86 #define LASTREV 95 -#define D86 0 /* keywords defined in LM-63-1986 */ +/* The following definitions support LM-63 file keyword reading and + * analysis. + * + * This section defines two function-like macros: keymatch(i,s), which + * checks to see if keyword i matches string s, and checklamp(s), + * which checks to see if a string matches the keywords "LAMP" or + * "LAMPCAT". + * + * LM-63-1986 files begin with a list of free-form label lines. + * LM-63-1991 files begin with the identifying line "IESNA91" followed + * by a list of formatted keywords. LM-63-1995 files begin with the + * identifying line "IESNA:LM-63-1995" followed by a list of formatted + * keywords. + * + * The K_* #defines enumerate the keywords used in the different + * versions of the file and give them symbolic names. + * + * The D86, D91, and D95 #defines validate the keywords in the 1986, + * 1991, and 1995 versions of the standard, one bit per keyword. + * Since the 1986 standard does not use keywords, D86 is zero. The + * 1991 standard has 13 keywords, and D91 has the lower 13 bits set. + * The 1995 standard has 14 keywords, and D95 has the lower 14 bits + * set. + * + */ +#define D86 0 #define K_TST 0 #define K_MAN 1 @@ -43,11 +138,13 @@ static char SCCSid[] = "$SunId$ LBL"; #define K_BLK 11 #define K_EBK 12 -#define D91 ((1L<<13)-1) /* keywords defined in LM-63-1991 */ +/* keywords defined in LM-63-1991 */ +#define D91 ((1L<<13)-1) #define K_LMG 13 -#define D95 ((1L<<14)-1) /* keywords defined in LM-63-1995 */ +/* keywords defined in LM-63-1995 */ +#define D95 ((1L<<14)-1) char k_kwd[][20] = {"TEST", "MANUFAC", "LUMCAT", "LUMINAIRE", "LAMPCAT", "LAMP", "BALLAST", "MAINTCAT", "OTHER", "SEARCH", @@ -63,7 +160,15 @@ int filerev = FIRSTREV; #define checklamp(s) (!(k_defined[filerev-FIRSTREV]&(1<= argc) { + /* If an output file and an input file are not give, error. */ fprintf(stderr, "%s: missing output file specification\n", argv[0]); exit(1); } + /* If no input or output file is given, error. */ if (out2stdout && i != argc-1) goto needsingle; + /* Otherwise, process each input file in turn. */ status = 0; for ( ; i < argc; i++) { tailtrunc(strcpy(outname,filename(argv[i]))); @@ -268,32 +486,42 @@ needsingle: exit(1); } - -initlamps() /* set up lamps */ +/* Initlamps -- If necessary, read lamp data table */ +void +initlamps(void) /* set up lamps */ { float *lcol; int status; + /* If the lamp name is set to default, don't bother to read + * the lamp data table. */ if (lamptype != NULL && !strcmp(lamptype, default_name) && deflamp == NULL) - return; /* no need for data */ - /* else load file */ - if ((status = loadlamps(lampdat)) < 0) - exit(1); + return; + + if ((status = loadlamps(lampdat)) < 0) /* Load the lamp data table */ + exit(1); /* Exit if problems + * with the file. */ if (status == 0) { + /* If can't open the file, just use the standard default lamp */ fprintf(stderr, "%s: warning - no lamp data\n", lampdat); lamptype = default_name; return; } - if (deflamp != NULL) { /* match default type */ + if (deflamp != NULL) { + /* Look up the specified default lamp type */ if ((lcol = matchlamp(deflamp)) == NULL) + /* If it can't be found, use the default */ fprintf(stderr, "%s: warning - unknown default lamp type\n", deflamp); else + /* Use the selected default lamp color */ copycolor(defcolor, lcol); } - if (lamptype != NULL) { /* match selected type */ + /* If a lamp type is specified and can be found, use it, and + * release the lamp data table memory; it won't be needed any more. */ + if (lamptype != NULL) { if (strcmp(lamptype, default_name)) { if ((lcol = matchlamp(lamptype)) == NULL) { fprintf(stderr, @@ -305,14 +533,33 @@ initlamps() /* set up lamps */ } freelamps(); /* all done with data */ } - /* else keep lamp data */ + /* else keep lamp data */ } +/* + * File path operations + * + * These provide file path operations that operate on both MS-Windows + * and *nix. They will ignore and pass, but will not necessarily + * process correctly, Windows drive letters. Paths including Windows + * UNC network names (\\server\folder\file) may also cause problems. + * + */ +/* + * stradd() + * + * Add a string to the end of a string, optionally concatenating a + * file path separator character. If the path already ends with a + * path separator, no additional separator is appended. + * + */ char * -stradd(dst, src, sep) /* add a string at dst */ -register char *dst, *src; -int sep; +stradd( /* add a string at dst */ + char *dst, + char *src, + int sep +) { if (src && *src) { do @@ -325,40 +572,77 @@ int sep; return(dst); } - +/* + * fullnam () - return a usable path name for an output file + */ char * -fullname(path, fname, suffix) /* return full path name */ -char *path, *fname, *suffix; +fullnam( + char *path, /* The base directory path */ + char *fname, /* The file name */ + char *suffix /* A suffix, which usually contains + * a file name extension. */ +) { + extern char *prefdir; + extern char *libdir; + if (prefdir != NULL && abspath(prefdir)) + /* If the subdirectory path is absolute or '.', just + * concatenate the names together */ libname(path, fname, suffix); else if (abspath(fname)) + /* If there is no subdirectory, and the file name is + * an absolute path or '.', concatenate the path, + * filename, and suffix. */ strcpy(stradd(path, fname, 0), suffix); else + /* If the file name is relative, concatenate path, + * library directory, directory separator, file name, + * and suffix. */ libname(stradd(path, libdir, DIRSEP), fname, suffix); return(path); } +/* + * libname - convert a file name to a path + */ char * -libname(path, fname, suffix) /* return library relative name */ -char *path, *fname, *suffix; +libname( + char *path, /* The base directory path */ + char *fname, /* The file name */ + char *suffix /* A suffix, which usually contains + * a file name extension. */ +) { + extern char *prefdir; /* The subdirectory where the file + * name is stored. */ + if (abspath(fname)) + /* If the file name begins with '/' or '.', combine + * it with the path and attach the suffix */ strcpy(stradd(path, fname, 0), suffix); else + /* If the file name is relative, attach it to the + * path, include the subdirectory, and append the suffix. */ strcpy(stradd(stradd(path, prefdir, DIRSEP), fname, 0), suffix); return(path); } - +/* filename - find the base file name in a buffer containing a path + * + * The pointer is to a character within the buffer, not a string in itself; + * it will become invalid when the buffer is freed. + * + */ char * -filename(path) /* get final component of pathname */ -register char *path; +filename( + char *path +) { - register char *cp; + char *cp; for (cp = path; *path; path++) if (ISDIRSEP(*path)) @@ -367,11 +651,18 @@ register char *path; } +/* filetrunc() - return the directory portion of a path + * + * The path is passed in in a pointer to a buffer; a null character is + * inserted in the buffer after the last directory separator + * + */ char * -filetrunc(path) /* truncate filename at end of path */ -char *path; +filetrunc( + char *path +) { - register char *p1, *p2; + char *p1, *p2; for (p1 = p2 = path; *p2; p2++) if (ISDIRSEP(*p2)) @@ -382,29 +673,42 @@ char *path; return(path); } - +/* tailtrunc() - trim a file name extension, if any. + * + * The file name is passed in in a buffer indicated by *name; the + * period which begins the extension is replaced with a 0 byte. + */ char * -tailtrunc(name) /* truncate tail of filename */ -char *name; +tailtrunc( + char *name +) { - register char *p1, *p2; + char *p1, *p2; + /* Skip leading periods */ for (p1 = filename(name); *p1 == '.'; p1++) ; + /* Find the last period in a file name */ p2 = NULL; for ( ; *p1; p1++) if (*p1 == '.') p2 = p1; + /* If present, trim the filename at that period */ if (p2 != NULL) *p2 = '\0'; return(name); } - -blanktrunc(s) /* truncate spaces at end of line */ -char *s; +/* blanktrunc() - trim spaces at the end of a string + * + * the string is passed in a character array, which is modified + */ +void +blanktrunc( + char *s +) { - register char *cp; + char *cp; for (cp = s; *cp; cp++) ; @@ -413,22 +717,39 @@ char *s; *++cp = '\0'; } - -k_match(kwd, hdl) /* header line matches keyword? */ -register char *kwd, *hdl; +/* k_match - return true if keyword matches header line */ +int +k_match( + char *kwd, /* keyword */ + char *hdl /* header line */ +) { - if (!*hdl++ == '[') + /* Skip leading spaces */ + while (isspace(*hdl)) + hdl++; + /* The line has to begin with '[' */ + if (*hdl++ != '[') return(0); - while (islower(*hdl) ? toupper(*hdl) == *kwd++ : *hdl == *kwd++) + /* case-independent keyword match */ + while (toupper(*hdl) == *kwd++) if (!*hdl++) return(0); - return(!*kwd & *hdl == ']'); + /* If we have come to the end of the keyword, and the keyword + * at the beginning of the matched line is terminated with + * ']', return 1 */ + return(!kwd[-1] & (*hdl == ']')); } - +/* keyargs - return the argument of a keyword, without leading spaces + * + * keyargs is passed a pointer to a buffer; it returns a pointer to + * where the argument starts in the buffer + * + */ char * -keyargs(hdl) /* return keyword arguments */ -register char *hdl; +keyargs( + char *hdl /* header line */ +) { while (*hdl && *hdl++ != ']') ; @@ -438,11 +759,23 @@ register char *hdl; } -putheader(out) /* print header */ -FILE *out; +/* putheader - output the header of the .rad file + * + * Header is: + * # (all files from input line) + * # Dimensions in [feet,meters,etc.] + * + * ??? Is listing all the input file names correct behavior? + * + */ +void + +putheader( + FILE *out +) { - register int i; - + int i; + putc('#', out); for (i = 0; i < gargc; i++) { putc(' ', out); @@ -453,16 +786,27 @@ FILE *out; putc('\n', out); } - -ies2rad(inpname, outname) /* convert IES file */ -char *inpname, *outname; +/* ies2rad - convert an IES LM-63 file to a Radiance light source desc. + * + * Return -1 in case of failure, 0 in case of success. + * + * The file version recognition is confused and will treat 1995 and + * 2002 version files as 1986 version files. + * + */ +int +ies2rad( /* convert IES file */ + char *inpname, + char *outname +) { SRCINFO srcinfo; - char buf[MAXLINE], tltid[MAXWORD]; + char buf[MAXLINE], tltid[RMAXWORD]; char geomfile[128]; FILE *inpfp, *outfp; int lineno = 0; + /* Open input and output files */ geomfile[0] = '\0'; srcinfo.isillum = 0; if (inpname == NULL) { @@ -474,38 +818,64 @@ char *inpname, *outname; } if (out2stdout) outfp = stdout; - else if ((outfp = fopen(fullname(buf,outname,T_RAD), "w")) == NULL) { + else if ((outfp = fopen(fullnam(buf,outname,T_RAD), "w")) == NULL) { perror(buf); fclose(inpfp); return(-1); } + + /* Output the output file header */ putheader(outfp); + + /* If the lamp type wasn't given on the command line, mark + * the lamp color as missing */ if (lamptype == NULL) lampcolor = NULL; + + /* Read the input file header, copying lines to the .rad file + * and looking for a lamp type. Stop at EOF or a line + * beginning with "TILT=". */ while (fgets(buf,sizeof(buf),inpfp) != NULL && strncmp(buf,TLTSTR,TLTSTRLEN)) { - blanktrunc(buf); - if (!buf[0]) + blanktrunc(buf); /* Trim trailing spaces, CR, LF. */ + if (!buf[0]) /* Skip blank lines */ continue; - if (!lineno++ && !strncmp(buf, MAGICID, LMAGICID)) { - filerev = atoi(buf+LMAGICID); + /* increment the header line count, and check for the + * "TILT=" line that terminates the header */ + if (!lineno++) { /* first line may be magic */ + if (!strncmp(buf, MAGICID2, LMAGICID2)) + filerev = atoi(buf+LMAGICID2) - 1900; + else if (!strncmp(buf, MAGICID, LMAGICID)) + filerev = atoi(buf+LMAGICID); if (filerev < FIRSTREV) filerev = FIRSTREV; else if (filerev > LASTREV) filerev = LASTREV; } + /* Output the header line as a comment in the .rad file. */ fputs("#<", outfp); fputs(buf, outfp); putc('\n', outfp); + + /* If the header line is a keyword line (file version + * later than 1986 and begins with '['), check a lamp + * in the "[LAMP]" and "[LAMPCAT]" keyword lines; + * otherwise check all lines. */ if (lampcolor == NULL && checklamp(buf)) - lampcolor = matchlamp( buf[0] == '[' ? + lampcolor = matchlamp(*sskip2(buf,0) == '[' ? keyargs(buf) : buf ); - if (keymatch(K_LMG, buf)) { /* geometry file */ + /* Look for a materials and geometry file in the keywords. */ + if (keymatch(K_LMG, buf)) { strcpy(geomfile, inpname); strcpy(filename(geomfile), keyargs(buf)); srcinfo.isillum = 1; } } + + /* Done reading header information. If a lamp color still + * hasn't been found, print a warning and use the default + * color; if a lamp type hasn't been found, but a color has + * been specified, used the specified color. */ if (lampcolor == NULL) { fprintf(stderr, "%s: warning - no lamp type\n", inpname); fputs("# Unknown lamp type (used default)\n", outfp); @@ -513,54 +883,93 @@ char *inpname, *outname; } else if (lamptype == NULL) fprintf(outfp,"# CIE(x,y) = (%f,%f)\n# Depreciation = %.1f%%\n", lampcolor[3], lampcolor[4], 100.*lampcolor[5]); + + /* If the file ended before a "TILT=" line, that's an error. */ if (feof(inpfp)) { fprintf(stderr, "%s: not in IES format\n", inpname); goto readerr; } - atos(tltid, MAXWORD, buf+TLTSTRLEN); + + /* Process the tilt section of the file. */ + /* Get the tilt file name, or the keyword "INCLUDE". */ + atos(tltid, RMAXWORD, buf+TLTSTRLEN); if (inpfp == stdin) buf[0] = '\0'; else filetrunc(strcpy(buf, inpname)); + /* Process the tilt data. */ if (dotilt(inpfp, outfp, buf, tltid, outname, tltid) != 0) { fprintf(stderr, "%s: bad tilt data\n", inpname); goto readerr; } + + /* Process the luminaire data. */ if (dosource(&srcinfo, inpfp, outfp, tltid, outname) != 0) { fprintf(stderr, "%s: bad luminaire data\n", inpname); goto readerr; } + + /* Close the input file */ fclose(inpfp); - /* cvgeometry closes outfp */ + + /* Process an MGF file, if present. cvgeometry() closes outfp. */ if (cvgeometry(geomfile, &srcinfo, outname, outfp) != 0) { fprintf(stderr, "%s: bad geometry file\n", geomfile); return(-1); } return(0); + readerr: + /* If there is an error reading the file, close the input and + * .rad output files, and delete the .rad file, returning -1. */ fclose(inpfp); fclose(outfp); - unlink(fullname(buf,outname,T_RAD)); + unlink(fullnam(buf,outname,T_RAD)); return(-1); } - -dotilt(in, out, dir, tltspec, dfltname, tltid) /* convert tilt data */ -FILE *in, *out; -char *dir, *tltspec, *dfltname, *tltid; +/* dotilt -- process tilt data + * + * Generate a brightdata primitive which describes the effect of + * luminaire tilt on luminaire output and return its identifier in tltid. + * + * Tilt data (if present) is given as a number 1, 2, or 3, which + * specifies the orientation of the lamp within the luminaire, a + * number, n, of (angle, multiplier) pairs, followed by n angles and n + * multipliers. + * + * returns 0 for success, -1 for error + */ +int +dotilt( + FILE *in, + FILE *out, + char *dir, + char *tltspec, + char *dfltname, + char *tltid +) { int nangles, tlt_type; - double minmax[2]; - char buf[MAXPATH], tltname[MAXWORD]; + double minmax[1][2]; + char buf[PATH_MAX], tltname[RMAXWORD]; FILE *datin, *datout; + /* Decide where the tilt data is; if the luminaire description + * doesn't have a tilt section, set the identifier to "void". */ if (!strcmp(tltspec, TLTNONE)) { + /* If the line is "TILT=NONE", set the input file + * pointer to NULL and the identifier to "void". */ datin = NULL; strcpy(tltid, "void"); } else if (!strcmp(tltspec, TLTINCL)) { + /* If the line is "TILT=INCLUDE" use the main IES + * file as the source of tilt data. */ datin = in; strcpy(tltname, dfltname); } else { + /* If the line is "TILE=", use that file + * name as the source of tilt data. */ if (ISDIRSEP(tltspec[0])) strcpy(buf, tltspec); else @@ -571,65 +980,89 @@ char *dir, *tltspec, *dfltname, *tltid; } tailtrunc(strcpy(tltname,filename(tltspec))); } + /* If tilt data is present, read, process, and output it. */ if (datin != NULL) { - if ((datout = fopen(fullname(buf,tltname,T_TLT),"w")) == NULL) { + /* Try to open the output file */ + if ((datout = fopen(fullnam(buf,tltname,T_TLT),"w")) == NULL) { perror(buf); if (datin != in) fclose(datin); return(-1); } + /* Try to copy the tilt data to the tilt data file */ if (!scnint(datin,&tlt_type) || !scnint(datin,&nangles) || cvdata(datin,datout,1,&nangles,1.,minmax) != 0) { fprintf(stderr, "%s: data format error\n", tltspec); fclose(datout); if (datin != in) fclose(datin); - unlink(fullname(buf,tltname,T_TLT)); + unlink(fullnam(buf,tltname,T_TLT)); return(-1); } fclose(datout); if (datin != in) fclose(datin); + + /* Generate the identifier of the brightdata; the filename + * with "_tilt" appended. */ strcat(strcpy(tltid, filename(tltname)), "_tilt"); + /* Write out the brightdata primitive */ fprintf(out, "\nvoid brightdata %s\n", tltid); libname(buf,tltname,T_TLT); + /* Generate the tilt description */ switch (tlt_type) { - case TLT_VERT: /* vertical */ + case TLT_VERT: + /* The lamp is mounted vertically; either + * base up or base down. */ fprintf(out, "4 noop %s tilt.cal %s\n", buf, - minmax[1]>90.+FTINY ? "tilt_ang" : "tilt_ang2"); + minmax[0][1]>90.+FTINY ? "tilt_ang" : "tilt_ang2"); break; - case TLT_H0: /* horiz. in 0 deg. plane */ + case TLT_H0: + /* The lamp is mounted horizontally and + * rotates but does not tilt when the + * luminaire is tilted. */ fprintf(out, "6 noop %s tilt.cal %s -rz 90\n", buf, - minmax[1]>90.+FTINY ? "tilt_xang" : "tilt_xang2"); + minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2"); break; case TLT_H90: + /* The lamp is mounted horizontally, and + * tilts when the luminaire is tilted. */ fprintf(out, "4 noop %s tilt.cal %s\n", buf, - minmax[1]>90.+FTINY ? "tilt_xang" : "tilt_xang2"); + minmax[0][1]>90.+FTINY ? "tilt_xang" : "tilt_xang2"); break; default: + /* otherwise, this is a bad IES file */ fprintf(stderr, "%s: illegal lamp to luminaire geometry (%d)\n", tltspec, tlt_type); return(-1); } + /* And finally output the numbers of integer and real + * arguments, of which there are none. */ fprintf(out, "0\n0\n"); } return(0); } - -dosource(sinf, in, out, mod, name) /* create source and distribution */ -SRCINFO *sinf; -FILE *in, *out; -char *mod, *name; +/* dosource -- create the source and distribution primitives */ +int +dosource( + SRCINFO *sinf, + FILE *in, + FILE *out, + char *mod, + char *name +) { - char buf[MAXPATH], id[MAXWORD]; + char buf[PATH_MAX], id[RMAXWORD]; FILE *datout; double mult, bfactor, pfactor, width, length, height, wattage; double bounds[2][2]; int nangles[2], pmtype, unitype; double d1; + int doupper, dolower, dosides; + /* Read in the luminaire description header */ if (!isint(getword(in)) || !isflt(getword(in)) || !scnflt(in,&mult) || !scnint(in,&nangles[0]) || !scnint(in,&nangles[1]) || !scnint(in,&pmtype) || !scnint(in,&unitype) @@ -639,33 +1072,58 @@ char *mod, *name; fprintf(stderr, "dosource: bad lamp specification\n"); return(-1); } + /* Type A photometry is not supported */ + if (pmtype != PM_C && pmtype != PM_B) { + fprintf(stderr, "dosource: unsupported photometric type (%d)\n", + pmtype); + return(-1); + } + + /* Multiplier = the multiplier from the -m option, times the + * multiplier from the IES file, times the ballast factor, + * times the "ballast lamp photometric factor," which was part + * of the 1986 and 1991 standards. In the 1995 standard, it is + * always supposed to be 1. */ sinf->mult = multiplier*mult*bfactor*pfactor; + + /* If the count of angles is wrong, raise an error and quit. */ if (nangles[0] < 2 || nangles[1] < 1) { fprintf(stderr, "dosource: too few measured angles\n"); return(-1); } + + /* For internal computation, convert units to meters. */ if (unitype == U_FEET) { width *= F_M; length *= F_M; height *= F_M; } + + /* Make decisions about the shape of the light source + * geometry, and store them in sinf. */ if (makeshape(sinf, width, length, height) != 0) { fprintf(stderr, "dosource: illegal source dimensions"); return(-1); } - if ((datout = fopen(fullname(buf,name,T_DST), "w")) == NULL) { + + /* Copy the candela values into a Radiance data file. */ + if ((datout = fopen(fullnam(buf,name,T_DST), "w")) == NULL) { perror(buf); return(-1); } if (cvdata(in, datout, 2, nangles, 1./WHTEFFICACY, bounds) != 0) { fprintf(stderr, "dosource: bad distribution data\n"); fclose(datout); - unlink(fullname(buf,name,T_DST)); + unlink(fullnam(buf,name,T_DST)); return(-1); } fclose(datout); + + /* Output explanatory comment */ fprintf(out, "# %g watt luminaire, lamp*ballast factor = %g\n", wattage, bfactor*pfactor); + /* Output distribution "brightdata" primitive. Start handling + the various cases of symmetry of the distribution. */ strcat(strcpy(id, filename(name)), "_dist"); fprintf(out, "\n%s brightdata %s\n", mod, id); if (nangles[1] < 2) @@ -676,8 +1134,18 @@ char *mod, *name; fprintf(out, "7 "); else fprintf(out, "5 "); + + /* If the generated source geometry will be a box, a flat + * rectangle, or a disk figure out if it needs a top, a + * bottom, and/or sides. */ + dolower = (bounds[0][0] < 90.-FTINY); /* Bottom */ + doupper = (bounds[0][1] > 90.+FTINY); /* Top */ + dosides = (doupper & dolower && sinf->h > MINDIM); /* Sides */ + + /* Select the appropriate function and parameters from source.cal */ fprintf(out, "%s %s source.cal ", sinf->type==SPHERE ? "corr" : + !dosides ? "flatcorr" : sinf->type==DISK ? "cylcorr" : "boxcorr", libname(buf,name,T_DST)); if (pmtype == PM_B) { @@ -686,14 +1154,17 @@ char *mod, *name; else fprintf(out, "srcB_horiz "); fprintf(out, "srcB_vert "); - } else { + } else /* pmtype == PM_C */ { if (nangles[1] >= 2) { d1 = bounds[1][1] - bounds[1][0]; if (d1 <= 90.+FTINY) fprintf(out, "src_phi4 "); - else if (d1 <= 180.+FTINY) - fprintf(out, "src_phi2 "); - else + else if (d1 <= 180.+FTINY) { + if (FEQ(bounds[1][0],90.)) + fprintf(out, "src_phi2+90 "); + else + fprintf(out, "src_phi2 "); + } else fprintf(out, "src_phi "); fprintf(out, "src_theta "); if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.)) @@ -701,30 +1172,46 @@ char *mod, *name; } else fprintf(out, "src_theta "); } - if (sinf->type == SPHERE) + /* finish the brightdata primitive with appropriate data */ + if (!dosides || sinf->type == SPHERE) fprintf(out, "\n0\n1 %g\n", sinf->mult/sinf->area); else if (sinf->type == DISK) fprintf(out, "\n0\n3 %g %g %g\n", sinf->mult, - sinf->l, sinf->h); + sinf->w, sinf->h); else fprintf(out, "\n0\n4 %g %g %g %g\n", sinf->mult, sinf->l, sinf->w, sinf->h); + /* Brightdata primitive written out. */ + + /* Finally, output the descriptions of the actual radiant + * surfaces. */ if (putsource(sinf, out, id, filename(name), - bounds[0][0]<90., bounds[0][1]>90.) != 0) + dolower, doupper, dosides) != 0) return(-1); return(0); } - -putsource(shp, fp, mod, name, dolower, doupper) /* put out source */ -SRCINFO *shp; -FILE *fp; -char *mod, *name; -int dolower, doupper; +/* putsource - output the actual light emitting geometry + * + * Three kinds of geometry are produced: rectangles and boxes, disks + * ("ring" primitive, but the radius of the hole is always zero) and + * cylinders, and spheres. + */ +int +putsource( + SRCINFO *shp, + FILE *fp, + char *mod, + char *name, + int dolower, + int doupper, + int dosides +) { - int dosides = doupper && dolower && shp->h > MINDIM; - char lname[MAXWORD]; - + char lname[RMAXWORD]; + + /* First, describe the light. If a materials and geometry + * file is given, generate an illum instead. */ strcat(strcpy(lname, name), "_light"); fprintf(fp, "\n%s %s %s\n", mod, shp->isillum ? "illum" : "light", lname); @@ -732,6 +1219,9 @@ int dolower, doupper; lampcolor[0], lampcolor[1], lampcolor[2]); switch (shp->type) { case RECT: + /* Output at least one rectangle. If light is radiated + * from the sides of the luminaire, output rectangular + * sides as well. */ if (dolower) putrectsrc(shp, fp, lname, name, 0); if (doupper) @@ -740,6 +1230,8 @@ int dolower, doupper; putsides(shp, fp, lname, name); break; case DISK: + /* Output at least one disk. If light is radiated from + * the sides of luminaire, output a cylinder as well. */ if (dolower) putdisksrc(shp, fp, lname, name, 0); if (doupper) @@ -748,27 +1240,64 @@ int dolower, doupper; putcyl(shp, fp, lname, name); break; case SPHERE: + /* Output a sphere. */ putspheresrc(shp, fp, lname, name); break; } return(0); } - -makeshape(shp, width, length, height) /* make source shape */ -register SRCINFO *shp; -double width, length, height; +/* makeshape -- decide what shape will be used + * + * Makeshape decides what Radiance geometry will be used to represent + * the light source and stores information about it in shp. + * + * The various versions of the IES LM-63 standard give a "luminous + * opening" (really a crude shape) a width, a length (or depth), and a + * height. If all three values are positive, they describe a box. If + * they are all zero, they describe a point. Various combinations of + * negative values are used to denote disks, circular or elliptical + * cylinders, spheres, and ellipsoids. This encoding differs from + * version to version of LM-63. + * + * Ies2rad simplifies this, reducing the geometry of LM-63 files to + * three forms which can be easily represented by Radiance primitives: + * boxes (RECT), cylinders or disks (DISK), and spheres (SPHERE.) A + * point is necessarily represented by a small sphere, since a point + * is not a Radiance object. + */ +int +makeshape( + SRCINFO *shp, + double width, + double length, + double height +) { + /* Categorize the shape */ if (illumrad/meters2out >= MINDIM/2.) { + /* If the -i command line option is used, output an + * "illum" sphere whose radius is given by the + * argument to -i. */ shp->isillum = 1; shp->type = SPHERE; shp->w = shp->l = shp->h = 2.*illumrad / meters2out; + /* Otherwise, use the dimensions in the IES file */ } else if (width < MINDIM) { width = -width; if (width < MINDIM) { + /* If the LM-63 width is zero, assume a point + * source is described. Output a small + * sphere. */ shp->type = SPHERE; shp->w = shp->l = shp->h = MINDIM; } else if (height < .5*width) { + /* The width is negative and the height is + * less than half the width. Treat the + * luminous opening as a disk or short + * vertical cylinder. Disks will be + * represented as nearly flat cylinders of + * MINDIM/2 height. */ shp->type = DISK; shp->w = shp->l = width; if (height >= MINDIM) @@ -776,10 +1305,13 @@ double width, length, height; else shp->h = .5*MINDIM; } else { + /* Treat a tall cylinder as a sphere. */ shp->type = SPHERE; shp->w = shp->l = shp->h = width; } } else { + /* The width is positive. The luminous opening is a + box or simple rectangle. */ shp->type = RECT; shp->w = width; if (length >= MINDIM) @@ -791,6 +1323,8 @@ double width, length, height; else shp->h = .5*MINDIM; } + + /* Done choosing the shape; calculate its area in the x-y plane. */ switch (shp->type) { case RECT: shp->area = shp->w * shp->l; @@ -803,12 +1337,54 @@ double width, length, height; return(0); } +/* Rectangular or box-shaped light source. + * + * putrectsrc, putsides, putrect, and putpoint are used to output the + * Radiance description of a box. The box is centered on the origin + * and has the dimensions given in the IES file. The coordinates + * range from [-1/2*length, -1/2*width, -1/2*height] to [1/2*length, + * 1/2*width, 1/2*height]. + * + * The location of the point is encoded in the low-order three bits of + * an integer. If the integer is p, then: bit 0 is (p & 1), + * representing length (x), bit 1 is (p & 2) representing width (y), + * and bit 2 is (p & 4), representing height (z). + * + * Looking down from above (towards -z), the vertices of the box or + * rectangle are numbered so: + * + * 2,6 3,7 + * +--------------------------------------+ + * | | + * | | + * | | + * | | + * +--------------------------------------+ + * 0,4 1,5 + * + * The higher number of each pair is above the x-y plane (positive z), + * the lower number is below the x-y plane (negative z.) + * + */ -putrectsrc(shp, fp, mod, name, up) /* rectangular source */ -SRCINFO *shp; -FILE *fp; -char *mod, *name; -int up; +/* putrecsrc - output a rectangle parallel to the x-y plane + * + * Putrecsrc calls out the vertices of a rectangle parallel to the x-y + * plane. The order of the vertices is different for the upper and + * lower rectangles of a box, since a right-hand rule based on the + * order of the vertices is used to determine the surface normal of + * the rectangle, and the surface normal determines the direction the + * light radiated by the rectangle. + * + */ +void +putrectsrc( + SRCINFO *shp, + FILE *fp, + char *mod, + char *name, + int up +) { if (up) putrect(shp, fp, mod, name, ".u", 4, 5, 7, 6); @@ -816,24 +1392,38 @@ int up; putrect(shp, fp, mod, name, ".d", 0, 2, 3, 1); } - -putsides(shp, fp, mod, name) /* put out sides of box */ -register SRCINFO *shp; -FILE *fp; -char *mod, *name; +/* putsides - put out sides of box */ +void +putsides( + SRCINFO *shp, + FILE *fp, + char *mod, + char *name +) { putrect(shp, fp, mod, name, ".1", 0, 1, 5, 4); putrect(shp, fp, mod, name, ".2", 1, 3, 7, 5); putrect(shp, fp, mod, name, ".3", 3, 2, 6, 7); putrect(shp, fp, mod, name, ".4", 2, 0, 4, 6); } - -putrect(shp, fp, mod, name, suffix, a, b, c, d) /* put out a rectangle */ -SRCINFO *shp; -FILE *fp; -char *mod, *name, *suffix; -int a, b, c, d; +/* putrect - put out a rectangle + * + * putrect generates the "polygon" primitive which describes a + * rectangle. + */ +void +putrect( + SRCINFO *shp, + FILE *fp, + char *mod, + char *name, + char *suffix, + int a, + int b, + int c, + int d +) { fprintf(fp, "\n%s polygon %s%s\n0\n0\n12\n", mod, name, suffix); putpoint(shp, fp, a); @@ -842,11 +1432,17 @@ int a, b, c, d; putpoint(shp, fp, d); } - -putpoint(shp, fp, p) /* put out a point */ -register SRCINFO *shp; -FILE *fp; -int p; +/* putpoint -- output a the coordinates of a vertex + * + * putpoint maps vertex numbers to coordinates and outputs the + * coordinates. + */ +void +putpoint( + SRCINFO *shp, + FILE *fp, + int p +) { static double mult[2] = {-.5, .5}; @@ -856,12 +1452,22 @@ int p; mult[p>>2]*shp->h*meters2out); } +/* End of routines to output a box-shaped light source */ -putdisksrc(shp, fp, mod, name, up) /* put out a disk source */ -register SRCINFO *shp; -FILE *fp; -char *mod, *name; -int up; +/* Routines to output a cylindrical or disk shaped light source + * + * As with other shapes, the light source is centered on the origin. + * The "ring" and "cylinder" primitives are used. + * + */ +void +putdisksrc( /* put out a disk source */ + SRCINFO *shp, + FILE *fp, + char *mod, + char *name, + int up +) { if (up) { fprintf(fp, "\n%s ring %s.u\n", mod, name); @@ -879,10 +1485,13 @@ int up; } -putcyl(shp, fp, mod, name) /* put out a cylinder */ -register SRCINFO *shp; -FILE *fp; -char *mod, *name; +void +putcyl( /* put out a cylinder */ + SRCINFO *shp, + FILE *fp, + char *mod, + char *name +) { fprintf(fp, "\n%s cylinder %s.c\n", mod, name); fprintf(fp, "0\n0\n7\n"); @@ -891,27 +1500,52 @@ char *mod, *name; fprintf(fp, "\t%g\n", .5*shp->w*meters2out); } +/* end of of routines to output cylinders and disks */ -putspheresrc(shp, fp, mod, name) /* put out a sphere source */ -SRCINFO *shp; -FILE *fp; -char *mod, *name; +void +putspheresrc( /* put out a sphere source */ + SRCINFO *shp, + FILE *fp, + char *mod, + char *name +) { fprintf(fp, "\n%s sphere %s.s\n", mod, name); fprintf(fp, "0\n0\n4 0 0 0 %g\n", .5*shp->w*meters2out); } - -cvdata(in, out, ndim, npts, mult, lim) /* convert data */ -FILE *in, *out; -int ndim, npts[]; -double mult, lim[][2]; +/* cvdata - convert LM-63 tilt and candela data to Radiance brightdata format + * + * The files created by this routine are intended for use with the Radiance + * "brightdata" material type. + * + * Two types of data are converted; one-dimensional tilt data, which + * is given in polar coordinates, and two-dimensional candela data, + * which is given in spherical co-ordinates. + * + * Return 0 for success, -1 for failure. + * + */ +int +cvdata( + FILE *in, /* Input file */ + FILE *out, /* Output file */ + int ndim, /* Number of dimensions; 1 for + * tilt data, 2 for photometric data. */ + int npts[], /* Number of points in each dimension */ + double mult, /* Multiple each value by this + * number. For tilt data, always + * 1. For candela values, the + * efficacy of white Radiance light. */ + double lim[][2] /* The range of angles in each dimension. */ +) { - double *pt[4]; - register int i, j; + double *pt[4]; /* Four is the expected maximum of ndim. */ + int i, j; double val; int total; + /* Calculate and output the number of data values */ total = 1; j = 0; for (i = 0; i < ndim; i++) if (npts[i] > 1) { @@ -919,8 +1553,14 @@ double mult, lim[][2]; j++; } fprintf(out, "%d\n", j); - /* get coordinates */ + + /* Read in the angle values, and note the first and last in + * each dimension, if there is a place to store them. In the + * case of tilt data, there is only one list of angles. In the + * case of candela values, vertical angles appear first, and + * horizontal angles occur second. */ for (i = 0; i < ndim; i++) { + /* Allocate space for the angle values. */ pt[i] = (double *)malloc(npts[i]*sizeof(double)); for (j = 0; j < npts[i]; j++) if (!scnflt(in, &pt[i][j])) @@ -930,17 +1570,34 @@ double mult, lim[][2]; lim[i][1] = pt[i][npts[i]-1]; } } - /* write out in reverse */ + + /* Output the angles. If this is candela data, horizontal + * angles output first. There are two cases: the first where + * the angles are evenly spaced, the second where they are + * not. + * + * When the angles are evenly spaced, three numbers are + * output: the first angle, the last angle, and the number of + * angles. When the angles are not evenly spaced, instead + * zero, zero, and the count of angles is given, followed by a + * list of angles. In this case, angles are output four to a line. + */ for (i = ndim-1; i >= 0; i--) { if (npts[i] > 1) { + /* Determine if the angles are evenly spaces */ for (j = 1; j < npts[i]-1; j++) if (!FEQ(pt[i][j]-pt[i][j-1], pt[i][j+1]-pt[i][j])) break; + /* If they are, output the first angle, the + * last angle, and a count */ if (j == npts[i]-1) fprintf(out, "%g %g %d\n", pt[i][0], pt[i][j], npts[i]); else { + /* otherwise, output 0, 0, and a + * count, followed by the list of + * angles, one to a line. */ fprintf(out, "0 0 %d", npts[i]); for (j = 0; j < npts[i]; j++) { if (j%4 == 0) @@ -950,8 +1607,13 @@ double mult, lim[][2]; putc('\n', out); } } - free((char *)pt[i]); + /* Free the storage containing the angle values. */ + free((void *)pt[i]); } + + /* Finally, read in the data values (candela or multiplier values, + * depending on the part of the file) and output them four to + * a line. */ for (i = 0; i < total; i++) { if (i%4 == 0) putc('\n', out); @@ -963,35 +1625,62 @@ double mult, lim[][2]; return(0); } - +/* getword - get an LM-63 delimited word from fp + * + * Getword gets a word from an IES file delimited by either white + * space or a comma surrounded by white space. A pointer to the word + * is returned, which will persist only until getword is called again. + * At EOF, return NULL instead. + * + */ char * -getword(fp) /* scan a word from fp */ -register FILE *fp; +getword( /* scan a word from fp */ + FILE *fp +) { - static char wrd[MAXWORD]; - register char *cp; - register int c; + static char wrd[RMAXWORD]; + char *cp; + int c; + /* Skip initial spaces */ while (isspace(c=getc(fp))) ; - for (cp = wrd; c != EOF && cp < wrd+MAXWORD-1; + /* Get characters to a delimiter or until wrd is full */ + for (cp = wrd; c != EOF && cp < wrd+RMAXWORD-1; *cp++ = c, c = getc(fp)) if (isspace(c) || c == ',') { + /* If we find a delimiter */ + /* Gobble up whitespace */ while (isspace(c)) c = getc(fp); - if (c != EOF & c != ',') + /* If it's not a comma, put the first + * character of the next data item back */ + if ((c != EOF) & (c != ',')) ungetc(c, fp); + /* Close out the strimg */ *cp = '\0'; + /* return it */ return(wrd); } + /* If we ran out of space or are at the end of the file, + * return either the word or NULL, as appropriate. */ *cp = '\0'; return(cp > wrd ? wrd : NULL); } - -cvtint(ip, wrd) /* convert a word to an integer */ -int *ip; -char *wrd; +/* cvtint - convert an IES word to an integer + * + * A pointer to the word is passed in wrd; ip is expected to point to + * an integer. cvtint() will silently truncate a floating point value + * to an integer; "1", "1.0", and "1.5" will all return 1. + * + * cvtint() returns 0 if it fails, 1 if it succeeds. + */ +int +cvtint( + int *ip, + char *wrd +) { if (wrd == NULL || !isint(wrd)) return(0); @@ -1000,9 +1689,18 @@ char *wrd; } -cvtflt(rp, wrd) /* convert a word to a double */ -double *rp; -char *wrd; +/* cvtflt - convert an IES word to a double precision floating-point number + * + * A pointer to the word is passed in wrd; rp is expected to point to + * a double. + * + * cvtflt returns 0 if it fails, 1 if it succeeds. + */ +int +cvtflt( + double *rp, + char *wrd +) { if (wrd == NULL || !isflt(wrd)) return(0); @@ -1010,15 +1708,36 @@ char *wrd; return(1); } - -cvgeometry(inpname, sinf, outname, outfp) -char *inpname; -register SRCINFO *sinf; -char *outname; -FILE *outfp; /* close output file upon return */ +/* cvgeometry - process materials and geometry format luminaire data + * + * The materials and geometry format (MGF) for describing luminaires + * was a part of Radiance that was first adopted and then retracted by + * the IES as part of LM-63. It provides a way of describing + * luminaire geometry similar to the Radiance scene description + * format. + * + * cvgeometry() generates an mgf2rad command and then, if "-g" is given + * on the command line, an oconv command, both of which are then + * executed with the system() function. + * + * The generated commands are: + * mgf2rad -e -g \ + * | xform -s \ + * >> -g \ + * oconv - > + */ +int +cvgeometry( + char *inpname, + SRCINFO *sinf, + char *outname, + FILE *outfp /* close output file upon return */ +) { char buf[256]; - register char *cp; + char *cp; if (inpname == NULL || !inpname[0]) { /* no geometry file */ fclose(outfp); @@ -1028,30 +1747,41 @@ FILE *outfp; /* close output file upon return */ strcpy(buf, "mgf2rad "); /* build mgf2rad command */ cp = buf+8; if (!FEQ(sinf->mult, 1.0)) { - sprintf(cp, "-m %f ", sinf->mult); + /* if there's an output multiplier, include in the + * mgf2rad command */ + sprintf(cp, "-e %f ", sinf->mult); cp += strlen(cp); } + /* Include the glow distance for the geometry */ sprintf(cp, "-g %f %s ", sqrt(sinf->w*sinf->w + sinf->h*sinf->h + sinf->l*sinf->l), inpname); cp += strlen(cp); if (instantiate) { /* instantiate octree */ + /* If "-g" is given on the command line, include an + * "oconv" command in the pipe. */ strcpy(cp, "| oconv - > "); cp += 12; - fullname(cp,outname,T_OCT); + fullnam(cp,outname,T_OCT); + /* Only update if the input file is newer than the + * output file */ if (fdate(inpname) > fdate(outname) && system(buf)) { /* create octree */ fclose(outfp); return(-1); } + /* Reference the instance file in the scene description */ fprintf(outfp, "void instance %s_inst\n", outname); + /* If the geometry isn't in meters, scale it appropriately. */ if (!FEQ(meters2out, 1.0)) fprintf(outfp, "3 %s -s %f\n", libname(buf,outname,T_OCT), meters2out); else fprintf(outfp, "1 %s\n", libname(buf,outname,T_OCT)); + /* Close off the "instance" primitive. */ fprintf(outfp, "0\n0\n"); + /* And the Radiance scene description. */ fclose(outfp); } else { /* else append to luminaire file */ if (!FEQ(meters2out, 1.0)) { /* apply scalefactor */ @@ -1062,10 +1792,17 @@ FILE *outfp; /* close output file upon return */ fclose(outfp); strcpy(cp, ">> "); /* append works for DOS? */ cp += 3; - fullname(cp,outname,T_RAD); + fullnam(cp,outname,T_RAD); } if (system(buf)) return(-1); } return(0); } + +/* Set up emacs indentation */ +/* Local Variables: */ +/* c-file-style: "bsd" */ +/* End: */ + +/* For vim, use ":set tabstop=8 shiftwidth=8" */