--- ray/src/cv/ies2rad.c 1993/08/23 10:22:42 2.9 +++ ray/src/cv/ies2rad.c 2022/11/16 01:59:43 2.36 @@ -1,26 +1,179 @@ -/* 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.36 2022/11/16 01:59:43 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. + * + * Overview of the LM-63 file format + * ================================= + * Here we offer a summary of the IESNA LM-63 photometry file format + * for the perplexed reader. Dear reader, do remember that this is + * our interpretation of the five different versions of the standard. + * When our interpretation of the standard conflicts with the official + * standard, the official document is to be respected. In conflicts + * with practice, do take into account the robustness principle and be + * permissive, accepting reasonable deviations from the standard. + * + * LM-63 files are organized as a version tag, followed by a series of + * luminaire data sets. The luminaire data sets, in turn, are + * organized into a label, a tilt data section, and a photometric data + * section. Finally, the data sections are organized into records, + * which are made up of lines of numeric values delimited by spaces or + * commas. Lines are delimited by CR LF sequences. Records are made + * up of one or more lines, and every record must be made up of some + * number of complete lines, but there is no delimiter which makes the + * end of a record. The first records of the tilt and photometric + * data sections have fixed numbers of numeric values; the initial + * records contain counts that describe the remaining records. + * + * Ies2rad allows only one luminaire data set per file. + * + * The tilt section is made up of exactly four records; the second gives + * the number of values in the third and fourth records. + * + * The photometric section begins with two records, which give both the + * number of records following and the number of values in each of the + * following records. + * + * The original 1986 version of LM-63 does not have a version tag. + * + * The 1986, 1991, and 1995 versions allow 80 characters for the label + * lines and the "TILT=" line which begins the tilt data section, and + * 132 characters thereafter. (Those counts do not include the CR LF + * line terminator.) The 2002 version dispenses with those limits, + * allowing 256 characters per line, including the CR LF line + * terminator. The 2019 version does not specify a line length at + * all. Ies2rad allows lines of up to 256 characters and will accept + * CR LF or LF alone as line terminators. + * + * In the 1986 version, the label is a series of free-form lines of up + * to 80 characters. In later versions, the label is a series of + * lines of beginning with keywords in brackets with interpretation + * rules which differ between versions. + * + * The tilt data section begins with a line beginning with "TILT=", + * optionally followed by either a file name or four records of + * numerical data. The 2019 version no longer allows a file name to + * be given. + * + * The main photometric data section contains two header records + * followed by a record of vertical angles, a record of horizontal + * angles, and one record of candela values for each horizontal angle. + * Each record of candela values contains exactly one value for each + * vertical angle. Data values in records are separated by spaces or + * commas. In keeping with the robustness principle, commas + * surrounded by spaces will also be accepted as separators. + * + * The first header record of the photometric data section contains + * exactly 10 values. The second contains exactly 3 values. Most of + * the data values are floating point numbers; the exceptions are + * various counts and enumerators, which are integers: the number of + * lamps, the numbers of vertical and horizontal angles, the + * photometric type identifier, and the units type identifier. In the + * 2019 version, a field with information about how the file was + * generated has replaced a field unused since 1995; it is a textual + * representation of a bit string, but may - we hope! - safely be + * interpreted as a floating point number and decoded later. + * + * Style Note + * ========== + * 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". + * + * History + * ======= + * * 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 */ + +#define FAIL (-1) +#define SUCCESS 0 + +/* floating point 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 */ + +#define IESFIRSTVER 1986 +#define IESLASTVER 2019 + +/* tilt specs + * + * This next series of definitions address metal-halide lamps, which + * change their brightness depending on the angle at which they are + * mounted. The section begins with "TILT=". The constants in this + * section are all defined in LM-63. + * + */ + #define TLTSTR "TILT=" #define TLTSTRLEN 5 #define TLTNONE "NONE" @@ -28,30 +181,131 @@ static char SCCSid[] = "$SunId$ LBL"; #define TLT_VERT 1 #define TLT_H0 2 #define TLT_H90 3 - /* photometric types */ + +/* Constants from LM-63 files */ + +/* photometric types + * + * This enumeration reflects three different methods of measuring the + * distribution of light from a luminaire -- "goniophotometry" -- and + * the different coordinate systems related to these + * goniophotometers. All are described in IES standard LM-75-01. + * Earlier and shorter descriptions may be found the LM-63 standards + * from 1986, 1991, and 1995. + * + * ies2rad does not support type A photometry. + * + * In the 1986 file format, LM-63-86, 1 is used for type C and type A + * photometric data. + * + */ #define PM_C 1 #define PM_B 2 - /* unit types */ +#define PM_A 3 + +/* unit types */ #define U_FEET 1 #define U_METERS 2 - /* string lengths */ -#define MAXLINE 132 -#define MAXWORD 76 - /* file types */ + +/* string lengths */ +/* Maximum length of a keyword, including brackets and NUL */ +#define MAXKW 21 +/* Maximum input line is 256 characters including CR LF and NUL at end. */ +#define MAXLINE 257 +#define MAXUNITNAME 64 +#define RMAXWORD 76 + +/* Shapes defined in the IES LM-63 standards + * + * PH stands for photometric horizontal + * PPH stands for perpendicular to photometric horizontal + * Cylinders are vertical and circular unless otherwise stated + * + * The numbers assigned here are not part of any LM-63 standard; they + * are for programming convenience. + */ +/* Error and not-yet-assigned constants */ +#define IESERROR -2 +#define IESNONE -1 +/* Shapes */ +#define IESPT 0 +#define IESRECT 1 +#define IESBOX 2 +#define IESDISK 3 +#define IESELLIPSE 4 +#define IESVCYL 5 +#define IESVECYL 6 +#define IESSPHERE 7 +#define IESELLIPSOID 8 +#define IESHCYL_PH 9 +#define IESHECYL_PH 10 +#define IESHCYL_PPH 11 +#define IESHECYL_PPH 12 +#define IESVDISK_PH 13 +#define IESVEL_PH 14 + +/* End of LM-63 related #defines */ + +/* file extensions */ #define T_RAD ".rad" #define T_DST ".dat" -#define T_TLT "+.dat" - /* shape types */ +#define T_TLT "%.dat" +#define T_OCT ".oct" + +/* Radiance shape types + * These #defines enumerate the shapes of the Radiance objects which + * emit the light. + */ #define RECT 1 #define DISK 2 #define SPHERE 3 -#define MINDIM .001 /* minimum dimension (point source) */ +/* 1mm. The diameter of a point source luminaire model. Also the minimum + * size (in meters) that the luminous opening of a luminaire must have + * to be treated as other than a point source. */ +#define MINDIM .001 -#define F_M .3048 /* feet to meters */ +/* feet to meters */ +/* length_in_meters = length_in_feet * F_M */ +#define F_M .3048 +/* abspath - return true if a path begins with a directory separator + * or a '.' (current directory) */ #define abspath(p) (ISDIRSEP((p)[0]) || (p)[0] == '.') +/* LM-63 related constants */ +typedef struct { + char *tag; + int yr; } IESversions; + +IESversions IESFILEVERSIONS[] = { + { "IESNA91", 1991 }, + { "IESNA:LM-63-1995", 1995 }, + { "IESNA:LM-63-2002", 2002 }, + { "IES:LM-63-2019", 2019 }, + { NULL, 1986 } +}; + +char *IESHAPENAMES[] = { + "point", "rectangle", "box", "disk", "ellipse", "vertical cylinder", + "vertical elliptical cylinder", "sphere", "ellipsoid", + "horizontal cylinder along photometric horizontal", + "horizontal elliptical cylinder along photometric horizontal", + "horizontal cylinder perpendicular to photometric horizontal", + "horizontal elliptical cylinder perpendicular to photometric horizontal", + "vertical disk facing photometric horizontal", + "vertical ellipse facing photometric horizontal" }; + +/* end of LM-63 related constants */ + +/* Radiance shape names */ +char *RADSHAPENAMES[] = { "rectangle or box", "disk or cylinder", "sphere" }; + +/* Global variables. + * + * Mostly, these are a way of communicating command line parameters to + * the rest of the program. + */ static char default_name[] = "default"; char *libdir = NULL; /* library directory location */ @@ -64,38 +318,126 @@ char *deflamp = NULL; /* default lamp type */ float defcolor[3] = {1.,1.,1.}; /* default lamp color */ float *lampcolor = defcolor; /* pointer to current lamp color */ double multiplier = 1.0; /* multiplier for all light sources */ -char units[64] = "meters"; /* output units */ +char units[MAXUNITNAME] = "meters"; /* output units */ +int out2stdout = 0; /* put out to stdout r.t. file */ +int instantiate = 0; /* instantiate geometry */ double illumrad = 0.0; /* radius for illum sphere */ +/* This struct describes the Radiance source object */ typedef struct { + int isillum; /* do as illum */ int type; /* RECT, DISK, SPHERE */ + double mult; /* candela multiplier */ double w, l, h; /* width, length, height */ double area; /* max. projected area */ -} SHAPE; /* a source shape */ + int filerev; /* IES file version */ + int havelamppos; /* Lamp position was given */ + float lamppos[2]; /* Lamp position */ + int iesshape; /* Shape number */ + char *warn; /* Warning message */ +} SRCINFO; /* a source shape (units=meters) */ -int gargc; /* global argc (minus filenames) */ +/* A count and pointer to the list of input file names */ +int gargc; /* global argc */ char **gargv; /* global argv */ -extern char *strcpy(), *strcat(), *stradd(), *tailtrunc(), *filetrunc(), - *filename(), *libname(), *fullname(), *malloc(), - *getword(), *atos(); -extern float *matchlamp(); - +/* macros to scan numbers out of IES files + * + * fp is a file pointer. scnint() places the number in the integer + * indicated by ip; scnflt() places the number in the double indicated + * by rp. The macros return 1 if successful, 0 if not. + * + */ #define scnint(fp,ip) cvtint(ip,getword(fp)) #define scnflt(fp,rp) cvtflt(rp,getword(fp)) -#define isint isflt /* IES allows real as integer */ +/* The original (1986) version of LM-63 allows decimals points in + * integers, so that, for instance, the number of lamps may be written + * 3.0 (the number, obviously, must still be an integer.) This + * confusing define accommodates that. */ +#define isint isflt -main(argc, argv) -int argc; -char *argv[]; +/* IES file conversion functions */ +static int ies2rad(char *inpname, char *outname); +static void initlamps(void); +static int dosource(SRCINFO *sinf, FILE *in, FILE *out, char *mod, char *name); +static int dotilt(FILE *in, FILE *out, char *dir, char *tltspec, + char *dfltname, char *tltid); +static int cvgeometry(char *inpname, SRCINFO *sinf, char *outname, FILE *outfp); +static int cvtint(int *ip, char *wrd); +static int cvdata(FILE *in, FILE *out, int ndim, int npts[], double mult, + double lim[][2]); +static int cvtflt(double *rp, char *wrd); +static int makeiesshape(SRCINFO *shp, double length, double width, double height); +static int makeillumsphere(SRCINFO *shp); +static int makeshape(SRCINFO *shp, double width, double length, double height); +static void makecylshape(SRCINFO *shp, double diam, double height); +static void makeelshape(SRCINFO *shp, double width, double length, double height); +static void makeecylshape(SRCINFO *shp, double width, double length, double height); +static void makeelshape(SRCINFO *shp, double width, double length, double height); +static void makeboxshape(SRCINFO *shp, double length, double width, double height); +static int makepointshape(SRCINFO *shp); +static int putsource(SRCINFO *shp, FILE *fp, char *mod, char *name, + int dolower, int doupper, int dosides); +static void putrectsrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up); +static void putsides(SRCINFO *shp, FILE *fp, char *mod, char *name); +static void putdisksrc(SRCINFO *shp, FILE *fp, char *mod, char *name, int up); +static void putspheresrc(SRCINFO *shp, FILE *fp, char *mod, char *name); +static void putrect(SRCINFO *shp, FILE *fp, char *mod, char *name, char *suffix, + int a, int b, int c, int d); +static void putpoint(SRCINFO *shp, FILE *fp, int p); +static void putcyl(SRCINFO *shp, FILE *fp, char *mod, char *name); +static void shapearea(SRCINFO *shp); + +/* string and filename functions */ +static int isprefix(char *p, char *s); +static char * matchprefix(char *p, char *s); +static char * tailtrunc(char *name); +static char * filename(char *path); +static char * libname(char *path, char *fname, char *suffix); +static char * getword(FILE *fp); +static char * fullnam(char *path, char *fname, char *suffix); + +/* output function */ +static void fpcomment(FILE *fp, char *prefix, char *s); + +/* main - process arguments and run the conversion + * + * Refer to the man page for details of the arguments. + * + * Following Unix environment conventions, main() exits with 0 on + * success and 1 on failure. + * + * ies2rad outputs either two or three files for a given IES + * file. There is always a .rad file containing Radiance scene + * description primitives and a .dat file for the photometric data. If + * tilt data is given, that is placed in a separate .dat file. So + * ies2rad must have a filename to operate. Sometimes this name is the + * input file name, shorn of its extension; sometimes it is given in + * the -o option. But an output file name is required for ies2rad to + * do its work. + * + * Older versions of the LM-63 standard allowed inclusion of multiple + * luminaires in one IES file; this is not supported by ies2rad. + * + * This code sometimes does not check to make sure it has not run out + * of arguments; this can lead to segmentation faults and perhaps + * other errors. + * + */ +int +main( + int argc, + char *argv[] +) { char *outfile = NULL; int status; - char outname[MAXWORD]; + char outname[RMAXWORD]; double d1; int i; - + + /* Scan the options */ for (i = 1; i < argc && argv[i][0] == '-'; i++) switch (argv[i][1]) { case 'd': /* dimensions */ @@ -157,14 +499,18 @@ char *argv[]; case 'f': /* lamp data file */ lampdat = argv[++i]; break; - case 'o': /* output file name */ + case 'o': /* output file root name */ outfile = argv[++i]; break; + case 's': /* output to stdout */ + out2stdout = !out2stdout; + break; case 'i': /* illum */ illumrad = atof(argv[++i]); - if (illumrad < MINDIM) - illumrad = MINDIM; break; + case 'g': /* instantiate geometry? */ + instantiate = !instantiate; + break; case 't': /* override lamp type */ lamptype = argv[++i]; break; @@ -185,25 +531,35 @@ char *argv[]; argv[0], argv[i]); exit(1); } + /* Save pointers to the list of input file names */ gargc = i; gargv = argv; - initlamps(); /* get lamp data (if needed) */ - /* convert ies file(s) */ + + /* get lamp data (if needed) */ + initlamps(); + + /* convert ies file(s) */ + /* If an output file name is specified */ if (outfile != NULL) { if (i == argc) + /* If no input filename is given, use stdin as + * the source for the IES file */ exit(ies2rad(NULL, outfile) == 0 ? 0 : 1); else if (i == argc-1) + /* If exactly one input file name is given, use it. */ exit(ies2rad(argv[i], outfile) == 0 ? 0 : 1); - else { - fprintf(stderr, "%s: single input file required\n", - argv[0]); - exit(1); - } + else + goto needsingle; /* Otherwise, error. */ } else if (i >= 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]))); @@ -211,34 +567,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, @@ -250,14 +619,162 @@ initlamps() /* set up lamps */ } freelamps(); /* all done with data */ } - /* else keep lamp data */ + /* else keep lamp data */ } +/* + * String functions + */ +/* + * isprefix - return 1 (true) if p is a prefix of s, 0 otherwise + * + * For this to work properly, s must be as long or longer than p. + */ +int +isprefix(char *p, char *s) { + return matchprefix(p,s) != NULL; +} + +/* + * matchprefix - match p against s + * + * If p is a prefix of s, return a pointer to the character of s just + * past p. + * + * For this to work properly, s must be as long or longer than p. + */ char * -stradd(dst, src, sep) /* add a string at dst */ -register char *dst, *src; -int sep; +matchprefix(char *p, char *s) { + int c; + + while ((c = *p++)) { + if (c != *s++) + return NULL; + } + return s; +} + +/* + * skipws - skip whitespace + */ +char * +skipws(char *s) { + while (isspace(*s)) + s++; + return s; +} + +/* + * streq - test strings for equality + */ +int +streq(char *s1, char *s2) { + return strcmp(s1,s2) == 0; +} + +/* + * strneq - test strings for equality, with a length limit + */ +int +strneq(char *s1, char *s2, int n) { + return strncmp(s1,s2,n) == 0; +} + +/* + * IES (LM-63) file functions + */ + +/* + * prockwd - process keywords on a label line + * + * We're looking for four keywords: LAMP, LAMPCAT, LAMPPOSITION, and + * LUMINOUSGEOMETRY. Any other keywords are ignored. + * + * LAMP and LAMPCAT are searched for a known lamp type name. + * LAMPPOSITION is stored. + * LUMINOUSGEOMETRY contains the name of an MGF file, which is stored. + */ +void +prockwd(char *bp, char *geomfile, char *inpname, SRCINFO *srcinfo) { + char *kwbegin; + int kwlen; + + bp = skipws(bp); /* Skip leading whitespace. */ + if (*bp != '[') + return; /* If there's no keyword on this line, + * do nothing */ + kwbegin = bp; + while (*bp && *bp != ']') /* Skip to the end of the keyword or + * end of the buffer. */ + bp++; + if (!(*bp)) /* If the keyword doesn't have a + * terminating ']', return. */ + return; + kwlen = bp - kwbegin + 1; + bp++; + if (lampcolor == NULL && strneq("[LAMP]", kwbegin, kwlen)) + lampcolor = matchlamp(bp); + else if (lampcolor == NULL && strneq("[LAMPCAT]", kwbegin, kwlen)) + lampcolor = matchlamp(bp); + else if (strneq("[LUMINOUSGEOMETRY]", kwbegin, kwlen)) { + bp = skipws(bp); /* Skip leading whitespace. */ + strcpy(geomfile, inpname); /* Copy the input file path */ + /* Replace the filename in the input file path with + * the name of the MGF file. Trailing spaces were + * trimmed before this routine was called. */ + strcpy(filename(geomfile), bp); + srcinfo->isillum = 1; + } + else if (strneq("[LAMPPOSITION]", kwbegin, kwlen)) { + srcinfo->havelamppos = 1; + sscanf(bp,"%f%f", &(srcinfo->lamppos[0]), + &(srcinfo->lamppos[1])); + } +} + +/* + * iesversion - examine the first line of an IES file and return the version + * + * Returns the year of the version. If the version is unknown, + * returns 1986, since the first line of a 1986-format IES file can be + * anything. + */ +int +iesversion(char *buf) { + IESversions *v; + + for(v = IESFILEVERSIONS; v != NULL; v++) + if (streq(v->tag,buf)) + return v->yr; + return v->yr; +} + + +/* + * 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( /* add a string at dst */ + char *dst, + char *src, + int sep +) { if (src && *src) { do @@ -270,84 +787,143 @@ 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 - pointer to filename in buffer containing path + * + * Scan the path, recording directory separators. Return the location + * of the character past the last one. If no directory separators are + * found, returns a pointer to beginning of the path. + */ char * -filename(path) /* get final component of pathname */ -register char *path; +filename( + char *path +) { - register char *cp; + char *cp = path; - for (cp = path; *path; path++) + for (; *path; path++) if (ISDIRSEP(*path)) cp = path+1; return(cp); } +/* 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++) ; @@ -356,12 +932,42 @@ char *s; *++cp = '\0'; } +/* fpcomment - output a multi-line comment + * + * The comment may be multiple lines, with each line separated by a + * newline. Each line is prefixed by prefix. If the last line isn't + * terminated by a newline, no newline will be output. + */ +void +fpcomment(FILE *fp, char *prefix, char *s) { + while (*s) { /* While there are characters left to output */ + fprintf(fp, "%s", prefix); /* Output the prefix */ + for (; *s && *s != '\n'; s++) /* Output a line */ + putc(*s, fp); + if (*s == '\n') { /* Including the newline, if any */ + putc(*s, fp); + s++; + } + } +} -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); @@ -372,13 +978,32 @@ 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. + * + */ +int +ies2rad( /* convert IES file */ + char *inpname, + char *outname +) { - char buf[MAXLINE], tltid[MAXWORD]; + SRCINFO srcinfo; + char buf[MAXLINE], tltid[RMAXWORD]; + char geomfile[MAXLINE]; FILE *inpfp, *outfp; + int lineno = 0; + + /* Initialize srcinfo */ + srcinfo.filerev = IESFIRSTVER; + srcinfo.iesshape = IESNONE; + srcinfo.warn = NULL; + srcinfo.isillum = 0; + srcinfo.havelamppos = 0; + /* Open input and output files */ + geomfile[0] = '\0'; if (inpname == NULL) { inpname = ""; inpfp = stdin; @@ -386,25 +1011,54 @@ 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. If we are on the + * first line of the file, check for a version tag. If + * one is not found, assume the first version of the + * file. */ + if (!lineno++) + srcinfo.filerev = iesversion(buf); + /* Output the header line as a comment in the .rad file. */ fputs("#<", outfp); fputs(buf, outfp); putc('\n', outfp); - if (lampcolor == NULL) + + /* For post-1986 version files, process a keyword + * line. Otherwise, just scan the line for a lamp + * name */ + if (srcinfo.filerev != 1986) + prockwd(buf, geomfile, inpname, &srcinfo); + else if (lampcolor == NULL) lampcolor = matchlamp(buf); } + + /* 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); @@ -412,50 +1066,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; } - 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 "TILT=", use that file + * name as the source of tilt data. */ if (ISDIRSEP(tltspec[0])) strcpy(buf, tltspec); else @@ -466,65 +1163,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) @@ -534,163 +1255,549 @@ char *mod, *name; fprintf(stderr, "dosource: bad lamp specification\n"); return(-1); } + + /* pfactor is only provided in 1986 and 1991 format files, and + * is something completely different in 2019 files. If the + * file version is 1995 or later, set it to 1.0 to avoid + * error. */ + if (sinf->filerev >= 1995) + pfactor = 1.0; + + /* 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," (pfactor) + * which was part of the 1986 and 1991 standards. In the 1995 + * and 2002 standards, it is always supposed to be 1 and in + * the 2019 standard it encodes information about the source + * of the file. For those files, pfactor is set to 1.0, + * above. */ + 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) { - fprintf(stderr, "dosource: illegal source dimensions"); + + /* 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\n"); return(-1); } - if ((datout = fopen(fullname(buf,name,T_DST), "w")) == NULL) { + /* If any warning messages were generated by makeshape(), output them */ + if ((sinf->warn) != NULL) + fputs(sinf->warn, stderr); + + /* 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); - fprintf(out, "# %g watt luminaire, lamp*ballast factor = %g\n", + + /* Output explanatory comment */ + fprintf(out, "\n# %g watt luminaire, lamp*ballast factor = %g\n", wattage, bfactor*pfactor); + if (sinf->iesshape >= 0) + fprintf(out, "# IES file shape = %s\n", + IESHAPENAMES[sinf->iesshape]); + else + fprintf(out, "# IES file shape overridden\n"); + fprintf(out, "# Radiance geometry shape = %s\n", + RADSHAPENAMES[sinf->type - 1]); + if (sinf->warn != NULL) + fpcomment(out, "# ", sinf->warn); + + /* Output distribution "brightdata" primitive. Start handling + the various cases of symmetry of the distribution. This + code reflects the complexity of the LM-63 format, as + described under "" in the various + versions of the standard. */ strcat(strcpy(id, filename(name)), "_dist"); - fprintf(out, "\n%s brightdata %s\n", mod, id); + fprintf(out, "\n'%s' brightdata '%s'\n", mod, id); if (nangles[1] < 2) + /* if it's a radially-symmetric type C distribution */ fprintf(out, "4 "); else if (pmtype == PM_B) + /* Photometry type B */ fprintf(out, "5 "); else if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.)) + /* Symmetric around the 90-270 degree plane */ fprintf(out, "7 "); else + /* Just regular type C photometry */ fprintf(out, "5 "); - fprintf(out, "%s %s source.cal ", - srcshape.type==SPHERE ? "corr" : "flatcorr", + + /* 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); /* Smallest vertical angle */ + doupper = (bounds[0][1] > 90.+FTINY); /* Largest vertical angle */ + 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) { + /* Type B photometry */ if (FEQ(bounds[1][0],0.)) + /* laterally symmetric around a vertical plane */ fprintf(out, "srcB_horiz2 "); else fprintf(out, "srcB_horiz "); fprintf(out, "srcB_vert "); - } else { + } else /* pmtype == PM_C */ { if (nangles[1] >= 2) { + /* Not radially symmetric */ d1 = bounds[1][1] - bounds[1][0]; if (d1 <= 90.+FTINY) + /* Data for a quadrant */ fprintf(out, "src_phi4 "); - else if (d1 <= 180.+FTINY) - fprintf(out, "src_phi2 "); - else + else if (d1 <= 180.+FTINY) { + /* Data for a hemisphere */ + if (FEQ(bounds[1][0],90.)) + fprintf(out, "src_phi2+90 "); + else + fprintf(out, "src_phi2 "); + } else /* Data for a whole sphere */ fprintf(out, "src_phi "); fprintf(out, "src_theta "); + /* For the hemisphere around the 90-270 degree plane */ if (FEQ(bounds[1][0],90.) && FEQ(bounds[1][1],270.)) fprintf(out, "-rz -90 "); - } else + } else /* Radially symmetric */ 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 height, width, and length parameters are values from the + * IES file, given in meters. + * + * 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. + * + * Makeshape() returns 0 if it succeeds in choosing a shape, and -1 if + * it fails. + * + */ +int +makeshape( + SRCINFO *shp, + double width, + double length, + double height +) { - if (illumrad >= MINDIM/2.) { + int rc; + + if (illumrad != 0.0) + rc = makeillumsphere(shp); + else + rc = makeiesshape(shp, length, width, height); + if (rc == SUCCESS) + shapearea(shp); + return rc; +} + +/* + * Return 1 if d < 0, 2 if d == 0, 3 if d > 0. This is used to encode + * the signs of IES file dimensions for quick lookup. As usual with + * macros, don't use an expression with side effects as an argument. + */ +#define CONVSGN(d) ((d) < 0 ? 1 : ((d) == 0 ? 2 : 3)) + +/* + * Generate the numeric key, the "thumbprint" for the various + * combinations of IES LM-63 version year, length, width, and height. + * This must be an integer constant expression so that it can be used + * in a case label. See the header comments of makeiesshape() for + * additional information. + */ +#define TBPR(ver,l,w,h) ((ver) * 1000 + CONVSGN(l) * 100 + CONVSGN(w) * 10 + CONVSGN(h)) + +/* makeiesshape - convert IES shape to Radiance shape + * + * Some 34 cases in the various versions of the IES LM-63 standard are + * handled, though some only by approximation. For each case which is + * processed a Radiance box, cylinder, or sphere is selected. + * + * Shapes are categorized by version year of the standard and the + * signs of the LM-63 length, width (depth), and height fields. These + * are combined and converted to an integer, which is then used as the + * argument to switch(). The last two digits of the IES file version + * year are used and the signs of length, width, and height are + * encoded, in that order, as 1 for negative, 2 for zero, and 3 for + * positive. These are then combined into a numeric key by the + * following formula: + * + * version * 1000 + sgn(length) * 100 + sgn(width) * 10 + sgn(height) + * + * The macro TBPR implements this formula. + * + * Since the 1991 version uses the same encoding as the 1986 version, + * and the 2019 version uses the same encoding as the 2002 version, + * these are collapsed into the earlier years. + * + * In the cases of the switch() statement, further processing takes + * place. Circles and ellipses are distinguished by comparisons. Then + * routines are called to fill out the fields of the shp structure. + * + * As per the conventions of the rest of ies2rad, makeiesshape() + * returns 0 on success and -1 on failure. -1 reflects an error in + * the IES file and is unusual. + * + * By convention, the shape generating routines are always given + * positive values for dimensions and always succeed; all errors are + * caught before they are called. The absolute values of all three + * dimensions are calculated at the beginning of makeiesshape() and + * used throughout the function, this has a low cost and eliminates + * the chance of sign errors. + * + * There are two extensions to the ies standard here: + * + * 1. devised to accomdate wall-mounted fixtures; vertical rectangles, + * not formally supported by any version of LM-63, are treated as + * boxes. + * + * 2. A 2002-flagged file with only a negative width will be + * recognized as a disk. + * + * The code is complicated by the way that earlier versions of the + * standard (1986 and 1991) prioritize width in their discussions, and + * later versions prioritize length. It is not always clear which to + * write first and there is hesitation between the older code which + * invokes makeiesshape() and makeiesshape() itself. + */ +int +makeiesshape(SRCINFO *shp, double l, double w, double h) { + int rc = SUCCESS; + int shape = IESNONE; + /* Get the last two digits of the standard year */ + int ver = shp->filerev % 100; + /* Make positive versions of all dimensions, for clarity in + * function calls. If you like, read this as l', w', and h'. */ + double lp = fabs(l), wp = fabs(w), hp = fabs(h); + int thumbprint; + + /* Change 1991 into 1986 and 2019 in 2002 */ + switch (ver) { + case 91: + ver = 86; + break; + case 19: + ver = 02; + break; + } + + thumbprint = TBPR(ver, l, w, h); + switch(thumbprint) { + case TBPR(86,0,0,0): case TBPR(95, 0, 0, 0): case TBPR(02, 0, 0, 0): + shp->iesshape = IESPT; shp->type = SPHERE; - shp->w = shp->l = shp->h = 2.*illumrad; - } else if (width < MINDIM) { - width = -width; - if (width < MINDIM) { - shp->type = SPHERE; - shp->w = shp->l = shp->h = MINDIM; - } else if (height < .5*width) { - shp->type = DISK; - shp->w = shp->l = width; - if (height >= MINDIM) - shp->h = height; - else - shp->h = .5*MINDIM; - } else { - shp->type = SPHERE; - shp->w = shp->l = shp->h = width; + shp->w = shp->l = shp->h = MINDIM; + break; + case TBPR(86, 1, 1, 0): case TBPR(95, 1, 1, 0): case TBPR(02, 1, 1, 0): + shp->iesshape = IESRECT; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(86, 1, 1, 1): case TBPR(86, 0, 1, 1): case TBPR(86, 1, 0, 1): + case TBPR(95, 1, 1, 1): case TBPR(95, 0, 1, 1): case TBPR(95, 1, 0, 1): + case TBPR(02, 1, 1, 1): case TBPR(02, 0, 1, 1): case TBPR(02, 1, 0, 1): + shp->iesshape = IESBOX; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(86,0,-1,0): case TBPR(95,0,-1,0): case TBPR(02,0,-1,0): + shp->iesshape = IESDISK; + makecylshape(shp, wp, hp); + break; + case TBPR(86, 0, -1, 1): + shp->iesshape = IESVCYL; + makecylshape(shp, wp, hp); + break; + case TBPR(86, 1, -1, 0): + shp->iesshape = IESELLIPSE; + makeecylshape(shp, lp, wp, 0); + break; + case TBPR(86, 1, -1, 1): + shp->iesshape = IESELLIPSOID; + makeelshape(shp, wp, lp, hp); + break; + case TBPR(95, 0, -1, -1): + shp->iesshape = FEQ(lp,hp) ? IESSPHERE : IESNONE; + if (shp->iesshape == IESNONE) { + shp->warn = "makeshape: cannot determine shape\n"; + rc = FAIL; + break; } - } else { - shp->type = RECT; - shp->w = width; - if (length >= MINDIM) - shp->l = length; + shp->type = SPHERE; + shp->w = shp->l = shp->h = wp; + break; + case TBPR(95, 0, -1, 1): + shp->iesshape = IESVCYL; + makecylshape(shp, wp, hp); + break; + case TBPR(95, 1, 0, -1): + shp->iesshape = IESHCYL_PH; + shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(95, 0, 1, -1): + shp->iesshape = IESHCYL_PPH; + shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(95, -1, 1, 1): case TBPR(95, 1, -1, 1): + shp->iesshape = IESVECYL; + makeecylshape(shp, lp, wp, hp); + break; + case TBPR(95, -1, 1, -1): case TBPR(95, 1, -1, -1): + shp->iesshape = IESELLIPSOID; + makeelshape(shp, lp, wp, hp); + break; + case TBPR(02, -1, -1, 0): + shp->iesshape = FEQ(l,w) ? IESDISK : IESELLIPSE; + if (shp->iesshape == IESDISK) + makecylshape(shp, wp, hp); else - shp->l = MINDIM; - if (height >= MINDIM) - shp->h = height; + makeecylshape(shp, wp, lp, hp); + break; + case TBPR(02, -1, -1, 1): + shp->iesshape = FEQ(l,w) ? IESVCYL : IESVECYL; + if (shp->iesshape == IESVCYL) + makecylshape(shp, wp, hp); else - shp->h = .5*MINDIM; + makeecylshape(shp, wp, lp, hp); + break; + case TBPR(02, -1, -1, -1): + shp->iesshape = FEQ(l,w) && FEQ(l,h) ? IESSPHERE : IESELLIPSOID; + if (shp->iesshape == IESSPHERE) { + shp->type = SPHERE; + shp->w = shp->l = shp->h = wp; + } + else + makeelshape(shp, lp, wp, hp); + break; + case TBPR(02, 1, -1, -1): + shp->iesshape = FEQ(w,h) ? IESHCYL_PH : IESHECYL_PH; + shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(02, -1, 1, -1): + shp->iesshape = FEQ(l,h) ? IESHCYL_PPH : IESHECYL_PPH; + shp->warn = "makeshape: shape is a horizontal cylinder, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, lp, wp, hp); + break; + case TBPR(02, -1, 0, -1): + shp->iesshape = FEQ(w,h) ? IESVDISK_PH : IESVEL_PH; + shp->warn = "makeshape: shape is a vertical ellipse, which is not supported.\nmakeshape: replaced with rectangle\n"; + makeboxshape(shp, lp, wp, hp); + break; + default: + /* We don't recognize the shape - report an error. */ + rc = FAIL; } + return rc; +} + +/* makeillumsphere - create an illum sphere */ +int +makeillumsphere(SRCINFO *shp) { + /* If the size is too small or negative, error. */ + if (illumrad/meters2out < MINDIM/2.) { + fprintf(stderr, "makeillumsphere: -i argument is too small or negative\n"); + return FAIL; + } + shp->isillum = 1; + shp->type = SPHERE; + shp->w = shp->l = shp->h = 2.*illumrad / meters2out; + return SUCCESS; +} + +/* makeboxshape - create a box */ +void +makeboxshape(SRCINFO *shp, double l, double w, double h) { + shp->type = RECT; + shp->l = fmax(l, MINDIM); + shp->w = fmax(w, MINDIM); + shp->h = fmax(h, .5*MINDIM); +} + +/* makecylshape - output a vertical cylinder or disk + * + * If the shape has no height, make it a half-millimeter. + */ +void +makecylshape(SRCINFO *shp, double diam, double height) { + shp->type = DISK; + shp->w = shp->l = diam; + shp->h = fmax(height, .5*MINDIM); +} + +/* makeelshape - create a substitute for an ellipsoid + * + * Because we don't actually support ellipsoids, and they don't seem + * to be common in actual IES files. + */ +void +makeelshape(SRCINFO *shp, double w, double l, double h) { + float avg = (w + l + h) / 3; + float bot = .5 * avg; + float top = 1.5 * avg; + + if (bot < w && w < top + && bot < l && l < top + && bot < h && h > top) { + /* it's sort of spherical, replace it with a sphere */ + shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with sphere\n"; + shp->type = SPHERE; + shp->w = shp->l = shp->h = avg; + } else if (bot < w && w < top + && bot < l && l < top + && h <= .5*MINDIM) { + /* It's flat and sort of circular, replace it + * with a disk. */ + shp->warn = "makeshape: shape is an ellipse, which is not supported.\nmakeshape: replaced with disk\n"; + makecylshape(shp, w, 0); + } else { + shp->warn = "makeshape: shape is an ellipsoid, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, w, l, h); + } +} + +/* makeecylshape - create a substitute for an elliptical cylinder or disk */ +void +makeecylshape(SRCINFO *shp, double l, double w, double h) { + float avg = (w + l) / 2; + float bot = .5 * avg; + float top = 1.5 * avg; + + if (bot < w && w < top + && bot < l && l < top) { + /* It's sort of circular, replace it + * with a circular cylinder. */ + shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with circular cylinder\n"; + makecylshape(shp, w, h); + } else { + shp->warn = "makeshape: shape is a vertical elliptical cylinder, which is not supported.\nmakeshape: replaced with box\n"; + makeboxshape(shp, w, l, h); + } +} + +void +shapearea(SRCINFO *shp) { switch (shp->type) { case RECT: shp->area = shp->w * shp->l; @@ -700,15 +1807,56 @@ double width, length, height; shp->area = PI/4. * shp->w * shp->w; break; } - 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); @@ -716,37 +1864,57 @@ 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); + fprintf(fp, "\n'%s' polygon '%s%s'\n0\n0\n12\n", mod, name, suffix); putpoint(shp, fp, a); putpoint(shp, fp, b); putpoint(shp, fp, c); 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}; @@ -756,21 +1924,31 @@ 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); + fprintf(fp, "\n'%s' ring '%s.u'\n", mod, name); fprintf(fp, "0\n0\n8\n"); fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out); fprintf(fp, "\t0 0 1\n"); fprintf(fp, "\t0 %g\n", .5*shp->w*meters2out); } else { - fprintf(fp, "\n%s ring %s.d\n", mod, name); + fprintf(fp, "\n'%s' ring '%s.d'\n", mod, name); fprintf(fp, "0\n0\n8\n"); fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out); fprintf(fp, "\t0 0 -1\n"); @@ -779,39 +1957,67 @@ 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, "\n'%s' cylinder '%s.c'\n", mod, name); fprintf(fp, "0\n0\n7\n"); fprintf(fp, "\t0 0 %g\n", .5*shp->h*meters2out); fprintf(fp, "\t0 0 %g\n", -.5*shp->h*meters2out); 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, "\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) { @@ -819,8 +2025,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])) @@ -830,17 +2042,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) @@ -850,8 +2079,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); @@ -863,49 +2097,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" */