--- ray/src/cv/ies2rad.c 1993/04/05 16:40:07 2.7 +++ ray/src/cv/ies2rad.c 2021/09/20 02:00:05 2.33 @@ -1,25 +1,174 @@ -/* Copyright (c) 1992 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 "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) - /* tilt specs */ + + +/* 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 + +/* 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 +#define K_LMC 2 +#define K_LMN 3 +#define K_LPC 4 +#define K_LMP 5 +#define K_BAL 6 +#define K_MTC 7 +#define K_OTH 8 +#define K_SCH 9 +#define K_MOR 10 +#define K_BLK 11 +#define K_EBK 12 + +/* keywords defined in LM-63-1991 */ +#define D91 ((1L<<13)-1) + +#define K_LMG 13 + +/* 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", + "MORE", "BLOCK", "ENDBLOCK", "LUMINOUSGEOMETRY"}; + +long k_defined[] = {D86, D86, D86, D86, D86, D91, D91, D91, D91, D95}; + +int filerev = FIRSTREV; + +#define keymatch(i,s) (k_defined[filerev-FIRSTREV]&1L<<(i) &&\ + k_match(k_kwd[i],s)) + +#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]))); @@ -210,34 +481,47 @@ char *argv[]; status = 1; } exit(status); +needsingle: + fprintf(stderr, "%s: single input file required\n", argv[0]); + 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, @@ -249,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 @@ -269,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)) @@ -311,42 +651,64 @@ 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)) p1 = p2; + if (p1 == path && ISDIRSEP(*p1)) + p1++; *p1 = '\0'; 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++) ; @@ -355,12 +717,65 @@ char *s; *++cp = '\0'; } +/* k_match - return true if keyword matches header line */ +int +k_match( + char *kwd, /* keyword */ + char *hdl /* header line */ +) +{ + /* Skip leading spaces */ + while (isspace(*hdl)) + hdl++; + /* The line has to begin with '[' */ + if (*hdl++ != '[') + return(0); + /* case-independent keyword match */ + while (toupper(*hdl) == *kwd++) + if (!*hdl++) + return(0); + /* 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 == ']')); +} -putheader(out) /* print header */ -FILE *out; +/* 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( + char *hdl /* header line */ +) { - register int i; - + while (*hdl && *hdl++ != ']') + ; + while (isspace(*hdl)) + hdl++; + return(hdl); +} + + +/* 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 +) +{ + int i; + putc('#', out); for (i = 0; i < gargc; i++) { putc(' ', out); @@ -371,13 +786,29 @@ 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 +) { - char buf[MAXLINE], tltid[MAXWORD]; + SRCINFO srcinfo; + 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) { inpname = ""; inpfp = stdin; @@ -385,73 +816,160 @@ char *inpname, *outname; perror(inpname); return(-1); } - if ((outfp = fopen(fullname(buf,outname,T_RAD), "w")) == NULL) { + if (out2stdout) + outfp = stdout; + 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; + /* 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 (lampcolor == NULL) - lampcolor = matchlamp(buf); + + /* 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(*sskip2(buf,0) == '[' ? + keyargs(buf) : buf ); + /* 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); lampcolor = defcolor; - } + } 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; } - if (dosource(inpfp, outfp, tltid, outname) != 0) { + + /* Process the luminaire data. */ + if (dosource(&srcinfo, inpfp, outfp, tltid, outname) != 0) { fprintf(stderr, "%s: bad luminaire data\n", inpname); goto readerr; } - fclose(outfp); + + /* Close the input file */ fclose(inpfp); + + /* 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: - fclose(outfp); + /* If there is an error reading the file, close the input and + * .rad output files, and delete the .rad file, returning -1. */ fclose(inpfp); - unlink(fullname(buf,outname,T_RAD)); + fclose(outfp); + 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 @@ -462,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(in, out, mod, name) /* create source and distribution */ -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 +) { - SHAPE srcshape; - 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) @@ -530,32 +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; } - if (makeshape(&srcshape, width, length, height) != 0) { + + /* 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) @@ -566,8 +1134,19 @@ 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 ", - srcshape.type==SPHERE ? "corr" : "flatcorr", + sinf->type==SPHERE ? "corr" : + !dosides ? "flatcorr" : + sinf->type==DISK ? "cylcorr" : "boxcorr", libname(buf,name,T_DST)); if (pmtype == PM_B) { if (FEQ(bounds[1][0],0.)) @@ -575,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.)) @@ -590,81 +1172,132 @@ char *mod, *name; } else fprintf(out, "src_theta "); } - fprintf(out, "\n0\n1 %g\n", multiplier*mult*bfactor*pfactor); - if (putsource(&srcshape, out, id, filename(name), - bounds[0][0]<90., bounds[0][1]>90.) != 0) + /* 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->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), + dolower, doupper, dosides) != 0) return(-1); return(0); } - -putsource(shp, fp, mod, name, dolower, doupper) /* put out source */ -SHAPE *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 +) { - char buf[MAXWORD]; - - fprintf(fp, "\n%s %s %s_light\n", mod, - illumrad>=MINDIM/2. ? "illum" : "light", - name); + 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); fprintf(fp, "0\n0\n3 %g %g %g\n", - lampcolor[0]/shp->area, - lampcolor[1]/shp->area, - lampcolor[2]/shp->area); - if (doupper && dolower && shp->type != SPHERE && shp->h > MINDIM) { - fprintf(fp, "\n%s glow %s_glow\n", mod, name); - fprintf(fp, "0\n0\n4 %g %g %g -1\n", - lampcolor[0]/shp->area, - lampcolor[1]/shp->area, - lampcolor[2]/shp->area); - } + lampcolor[0], lampcolor[1], lampcolor[2]); switch (shp->type) { case RECT: - strcat(strcpy(buf, name), "_light"); + /* 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, buf, name, 0); + putrectsrc(shp, fp, lname, name, 0); if (doupper) - putrectsrc(shp, fp, buf, name, 1); - if (doupper && dolower && shp->h > MINDIM) { - strcat(strcpy(buf, name), "_glow"); - putsides(shp, fp, buf, name); - } + putrectsrc(shp, fp, lname, name, 1); + if (dosides) + putsides(shp, fp, lname, name); break; case DISK: - strcat(strcpy(buf, name), "_light"); + /* Output at least one disk. If light is radiated from + * the sides of luminaire, output a cylinder as well. */ if (dolower) - putdisksrc(shp, fp, buf, name, 0); + putdisksrc(shp, fp, lname, name, 0); if (doupper) - putdisksrc(shp, fp, buf, name, 1); - if (doupper && dolower && shp->h > MINDIM) { - strcat(strcpy(buf, name), "_glow"); - putcyl(shp, fp, buf, name); - } + putdisksrc(shp, fp, lname, name, 1); + if (dosides) + putcyl(shp, fp, lname, name); break; case SPHERE: - strcat(strcpy(buf, name), "_light"); - putspheresrc(shp, fp, buf, name); + /* Output a sphere. */ + putspheresrc(shp, fp, lname, name); break; } return(0); } - -makeshape(shp, width, length, height) /* make source shape */ -register SHAPE *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 +) { - if (illumrad >= MINDIM/2.) { + /* 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; + 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) @@ -672,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) @@ -687,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; @@ -699,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 */ -SHAPE *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); @@ -712,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 SHAPE *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 */ -SHAPE *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); @@ -738,11 +1432,17 @@ int a, b, c, d; putpoint(shp, fp, d); } - -putpoint(shp, fp, p) /* put out a point */ -register SHAPE *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}; @@ -752,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 SHAPE *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); @@ -775,10 +1485,13 @@ int up; } -putcyl(shp, fp, mod, name) /* put out a cylinder */ -register SHAPE *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"); @@ -787,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 */ -SHAPE *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) { @@ -815,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])) @@ -826,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) @@ -846,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); @@ -859,49 +1625,184 @@ 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 word[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 = word; c != EOF && cp < word+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(word); + /* 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 > word ? word : NULL); + return(cp > wrd ? wrd : NULL); } - -cvtint(ip, word) /* convert a word to an integer */ -int *ip; -char *word; +/* 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 (word == NULL || !isint(word)) + if (wrd == NULL || !isint(wrd)) return(0); - *ip = atoi(word); + *ip = atoi(wrd); return(1); } -cvtflt(rp, word) /* convert a word to a double */ -double *rp; -char *word; +/* 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 (word == NULL || !isflt(word)) + if (wrd == NULL || !isflt(wrd)) return(0); - *rp = atof(word); + *rp = atof(wrd); return(1); } + +/* 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]; + char *cp; + + if (inpname == NULL || !inpname[0]) { /* no geometry file */ + fclose(outfp); + return(0); + } + putc('\n', outfp); + strcpy(buf, "mgf2rad "); /* build mgf2rad command */ + cp = buf+8; + if (!FEQ(sinf->mult, 1.0)) { + /* 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; + 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 */ + sprintf(cp, "| xform -s %f ", meters2out); + cp += strlen(cp); + } + if (!out2stdout) { + fclose(outfp); + strcpy(cp, ">> "); /* append works for DOS? */ + cp += 3; + 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" */