| 1 | greg | 2.1 | #ifndef lint | 
| 2 | greg | 2.29 | static const char RCSid[] = "$Id: bsdf2klems.c,v 2.28 2019/12/28 18:05:14 greg Exp $"; | 
| 3 | greg | 2.1 | #endif | 
| 4 |  |  | /* | 
| 5 |  |  | * Load measured BSDF interpolant and write out as XML file with Klems matrix. | 
| 6 |  |  | * | 
| 7 |  |  | *      G. Ward | 
| 8 |  |  | */ | 
| 9 |  |  |  | 
| 10 |  |  | #define _USE_MATH_DEFINES | 
| 11 |  |  | #include <stdlib.h> | 
| 12 |  |  | #include <math.h> | 
| 13 | greg | 2.29 | #include <ctype.h> | 
| 14 | greg | 2.1 | #include "random.h" | 
| 15 |  |  | #include "platform.h" | 
| 16 | greg | 2.20 | #include "paths.h" | 
| 17 |  |  | #include "rtio.h" | 
| 18 | greg | 2.1 | #include "calcomp.h" | 
| 19 |  |  | #include "bsdfrep.h" | 
| 20 |  |  | #include "bsdf_m.h" | 
| 21 | greg | 2.20 | /* tristimulus components */ | 
| 22 |  |  | enum {CIE_X, CIE_Y, CIE_Z}; | 
| 23 | greg | 2.4 | /* assumed maximum # Klems patches */ | 
| 24 |  |  | #define MAXPATCHES      145 | 
| 25 | greg | 2.1 | /* global argv[0] */ | 
| 26 |  |  | char                    *progname; | 
| 27 |  |  | /* selected basis function name */ | 
| 28 | greg | 2.20 | static const char       klems_full[] = "LBNL/Klems Full"; | 
| 29 |  |  | static const char       klems_half[] = "LBNL/Klems Half"; | 
| 30 |  |  | static const char       klems_quarter[] = "LBNL/Klems Quarter"; | 
| 31 |  |  | static const char       *kbasis = klems_full; | 
| 32 | greg | 2.1 | /* number of BSDF samples per patch */ | 
| 33 | greg | 2.26 | static int              npsamps = 1024; | 
| 34 | greg | 2.10 | /* limit on number of RBF lobes */ | 
| 35 |  |  | static int              lobe_lim = 15000; | 
| 36 | greg | 2.13 | /* progress bar length */ | 
| 37 |  |  | static int              do_prog = 79; | 
| 38 |  |  |  | 
| 39 | greg | 2.20 | #define MAXCARG         512     /* wrapBSDF command */ | 
| 40 |  |  | static char             *wrapBSDF[MAXCARG] = {"wrapBSDF", "-W", "-UU"}; | 
| 41 |  |  | static int              wbsdfac = 3; | 
| 42 |  |  |  | 
| 43 | greg | 2.23 | /* Add argument to wrapBSDF, allocating space if !isstatic */ | 
| 44 | greg | 2.20 | static void | 
| 45 |  |  | add_wbsdf(const char *arg, int isstatic) | 
| 46 |  |  | { | 
| 47 |  |  | if (arg == NULL) | 
| 48 |  |  | return; | 
| 49 |  |  | if (wbsdfac >= MAXCARG-1) { | 
| 50 |  |  | fputs(progname, stderr); | 
| 51 |  |  | fputs(": too many command arguments to wrapBSDF\n", stderr); | 
| 52 |  |  | exit(1); | 
| 53 |  |  | } | 
| 54 |  |  | if (!*arg) | 
| 55 |  |  | arg = ""; | 
| 56 |  |  | else if (!isstatic) | 
| 57 |  |  | arg = savqstr((char *)arg); | 
| 58 |  |  |  | 
| 59 |  |  | wrapBSDF[wbsdfac++] = (char *)arg; | 
| 60 |  |  | } | 
| 61 | greg | 2.13 |  | 
| 62 |  |  | /* Start new progress bar */ | 
| 63 |  |  | #define prog_start(s)   if (do_prog) fprintf(stderr, "%s: %s...\n", progname, s); else | 
| 64 |  |  |  | 
| 65 |  |  | /* Draw progress bar of the appropriate length */ | 
| 66 |  |  | static void | 
| 67 |  |  | prog_show(double frac) | 
| 68 |  |  | { | 
| 69 | greg | 2.17 | static unsigned call_cnt = 0; | 
| 70 |  |  | static char     lastc[] = "-\\|/"; | 
| 71 |  |  | char            pbar[256]; | 
| 72 |  |  | int             nchars; | 
| 73 | greg | 2.13 |  | 
| 74 | greg | 2.14 | if (do_prog <= 1) return; | 
| 75 | greg | 2.13 | if (do_prog > sizeof(pbar)-2) | 
| 76 |  |  | do_prog = sizeof(pbar)-2; | 
| 77 |  |  | if (frac < 0) frac = 0; | 
| 78 | greg | 2.17 | else if (frac >= 1) frac = .9999; | 
| 79 |  |  | nchars = do_prog*frac; | 
| 80 | greg | 2.13 | pbar[0] = '\r'; | 
| 81 |  |  | memset(pbar+1, '*', nchars); | 
| 82 | greg | 2.17 | pbar[nchars+1] = lastc[call_cnt++ & 3]; | 
| 83 |  |  | memset(pbar+2+nchars, '-', do_prog-nchars-1); | 
| 84 | greg | 2.13 | pbar[do_prog+1] = '\0'; | 
| 85 |  |  | fputs(pbar, stderr); | 
| 86 |  |  | } | 
| 87 |  |  |  | 
| 88 |  |  | /* Finish progress bar */ | 
| 89 | greg | 2.14 | static void | 
| 90 |  |  | prog_done(void) | 
| 91 |  |  | { | 
| 92 |  |  | int     n = do_prog; | 
| 93 |  |  |  | 
| 94 |  |  | if (n <= 1) return; | 
| 95 |  |  | fputc('\r', stderr); | 
| 96 |  |  | while (n--) | 
| 97 |  |  | fputc(' ', stderr); | 
| 98 |  |  | fputc('\r', stderr); | 
| 99 |  |  | } | 
| 100 | greg | 2.1 |  | 
| 101 |  |  | /* Return angle basis corresponding to the given name */ | 
| 102 | greg | 2.13 | static ANGLE_BASIS * | 
| 103 | greg | 2.1 | get_basis(const char *bn) | 
| 104 |  |  | { | 
| 105 |  |  | int     n = nabases; | 
| 106 |  |  |  | 
| 107 |  |  | while (n-- > 0) | 
| 108 |  |  | if (!strcasecmp(bn, abase_list[n].name)) | 
| 109 |  |  | return &abase_list[n]; | 
| 110 |  |  | return NULL; | 
| 111 |  |  | } | 
| 112 |  |  |  | 
| 113 | greg | 2.20 | /* Copy geometry string to file for wrapBSDF */ | 
| 114 |  |  | static char * | 
| 115 |  |  | save_geom(const char *mgf) | 
| 116 |  |  | { | 
| 117 |  |  | char    *tfname = mktemp(savqstr(TEMPLATE)); | 
| 118 |  |  | int     fd = open(tfname, O_CREAT|O_WRONLY, 0600); | 
| 119 |  |  |  | 
| 120 |  |  | if (fd < 0) | 
| 121 |  |  | return(NULL); | 
| 122 |  |  | write(fd, mgf, strlen(mgf)); | 
| 123 |  |  | close(fd); | 
| 124 |  |  | add_wbsdf("-g", 1); | 
| 125 |  |  | add_wbsdf(tfname, 1); | 
| 126 |  |  | return(tfname); | 
| 127 |  |  | } | 
| 128 |  |  |  | 
| 129 |  |  | /* Open XYZ component file for output and add appropriate arguments */ | 
| 130 |  |  | static FILE * | 
| 131 |  |  | open_component_file(int c) | 
| 132 |  |  | { | 
| 133 |  |  | static const char       sname[3][6] = {"CIE-X", "CIE-Y", "CIE-Z"}; | 
| 134 |  |  | static const char       cname[4][4] = {"-rf", "-tf", "-tb", "-rb"}; | 
| 135 |  |  | char                    *tfname = mktemp(savqstr(TEMPLATE)); | 
| 136 |  |  | FILE                    *fp = fopen(tfname, "w"); | 
| 137 |  |  |  | 
| 138 |  |  | if (fp == NULL) { | 
| 139 |  |  | fprintf(stderr, "%s: cannot open '%s' for writing\n", | 
| 140 |  |  | progname, tfname); | 
| 141 | greg | 2.1 | exit(1); | 
| 142 |  |  | } | 
| 143 | greg | 2.20 | add_wbsdf("-s", 1); add_wbsdf(sname[c], 1); | 
| 144 |  |  | add_wbsdf(cname[(input_orient>0)<<1 | (output_orient>0)], 1); | 
| 145 |  |  | add_wbsdf(tfname, 1); | 
| 146 |  |  | return(fp); | 
| 147 | greg | 2.1 | } | 
| 148 |  |  |  | 
| 149 | greg | 2.2 | /* Load and resample XML BSDF description using Klems basis */ | 
| 150 | greg | 2.1 | static void | 
| 151 |  |  | eval_bsdf(const char *fname) | 
| 152 |  |  | { | 
| 153 |  |  | ANGLE_BASIS     *abp = get_basis(kbasis); | 
| 154 | greg | 2.20 | FILE            *cfp[3]; | 
| 155 | greg | 2.1 | SDData          bsd; | 
| 156 |  |  | SDError         ec; | 
| 157 |  |  | FVECT           vin, vout; | 
| 158 | greg | 2.20 | SDValue         sdv; | 
| 159 |  |  | double          sum, xsum, ysum; | 
| 160 | greg | 2.1 | int             i, j, n; | 
| 161 |  |  |  | 
| 162 | greg | 2.18 | initurand(npsamps); | 
| 163 | greg | 2.1 | SDclearBSDF(&bsd, fname);               /* load BSDF file */ | 
| 164 |  |  | if ((ec = SDloadFile(&bsd, fname)) != SDEnone) | 
| 165 |  |  | goto err; | 
| 166 | greg | 2.20 | if (bsd.mgf != NULL)                    /* save geometry */ | 
| 167 |  |  | save_geom(bsd.mgf); | 
| 168 |  |  | if (bsd.matn[0])                        /* save identifier(s) */ | 
| 169 |  |  | strcpy(bsdf_name, bsd.matn); | 
| 170 |  |  | if (bsd.makr[0]) | 
| 171 |  |  | strcpy(bsdf_manuf, bsd.makr); | 
| 172 |  |  | if (bsd.dim[2] > 0) {                   /* save dimension(s) */ | 
| 173 |  |  | char    buf[64]; | 
| 174 |  |  | if ((bsd.dim[0] > 0) & (bsd.dim[1] > 0)) | 
| 175 |  |  | sprintf(buf, "w=%g;h=%g;t=%g", | 
| 176 |  |  | bsd.dim[0], bsd.dim[1], bsd.dim[2]); | 
| 177 |  |  | else | 
| 178 |  |  | sprintf(buf, "t=%g", bsd.dim[2]); | 
| 179 |  |  | add_wbsdf("-f", 1); | 
| 180 |  |  | add_wbsdf(buf, 0); | 
| 181 |  |  | } | 
| 182 | greg | 2.1 | /* front reflection */ | 
| 183 |  |  | if (bsd.rf != NULL || bsd.rLambFront.cieY > .002) { | 
| 184 |  |  | input_orient = 1; output_orient = 1; | 
| 185 | greg | 2.20 | cfp[CIE_Y] = open_component_file(CIE_Y); | 
| 186 |  |  | if (bsd.rf != NULL && bsd.rf->comp[0].cspec[2].flags) { | 
| 187 |  |  | rbf_colorimetry = RBCtristimulus; | 
| 188 |  |  | cfp[CIE_X] = open_component_file(CIE_X); | 
| 189 |  |  | cfp[CIE_Z] = open_component_file(CIE_Z); | 
| 190 |  |  | } else | 
| 191 |  |  | rbf_colorimetry = RBCphotopic; | 
| 192 | greg | 2.1 | for (j = 0; j < abp->nangles; j++) { | 
| 193 |  |  | for (i = 0; i < abp->nangles; i++) { | 
| 194 |  |  | sum = 0;                    /* average over patches */ | 
| 195 | greg | 2.20 | xsum = ysum = 0; | 
| 196 | greg | 2.1 | for (n = npsamps; n-- > 0; ) { | 
| 197 |  |  | fo_getvec(vout, j+(n+frandom())/npsamps, abp); | 
| 198 | greg | 2.5 | fi_getvec(vin, i+urand(n), abp); | 
| 199 | greg | 2.20 | ec = SDevalBSDF(&sdv, vout, vin, &bsd); | 
| 200 | greg | 2.1 | if (ec != SDEnone) | 
| 201 |  |  | goto err; | 
| 202 | greg | 2.20 | sum += sdv.cieY; | 
| 203 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 204 | greg | 2.21 | xsum += sdv.cieY * sdv.spec.cx; | 
| 205 |  |  | ysum += sdv.cieY * sdv.spec.cy; | 
| 206 | greg | 2.20 | } | 
| 207 |  |  | } | 
| 208 |  |  | fprintf(cfp[CIE_Y], "\t%.3e\n", sum/npsamps); | 
| 209 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 210 | greg | 2.21 | fprintf(cfp[CIE_X], "\t%.3e\n", xsum*sum/(npsamps*ysum)); | 
| 211 |  |  | fprintf(cfp[CIE_Z], "\t%.3e\n", | 
| 212 | greg | 2.20 | (sum - xsum - ysum)*sum/(npsamps*ysum)); | 
| 213 | greg | 2.1 | } | 
| 214 |  |  | } | 
| 215 | greg | 2.20 | fputc('\n', cfp[CIE_Y]);        /* extra space between rows */ | 
| 216 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 217 |  |  | fputc('\n', cfp[CIE_X]); | 
| 218 |  |  | fputc('\n', cfp[CIE_Z]); | 
| 219 |  |  | } | 
| 220 |  |  | } | 
| 221 |  |  | if (fclose(cfp[CIE_Y])) { | 
| 222 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 223 |  |  | exit(1); | 
| 224 |  |  | } | 
| 225 |  |  | if (rbf_colorimetry == RBCtristimulus && | 
| 226 |  |  | (fclose(cfp[CIE_X]) || fclose(cfp[CIE_Z]))) { | 
| 227 |  |  | fprintf(stderr, "%s: error writing X/Z output\n", progname); | 
| 228 |  |  | exit(1); | 
| 229 | greg | 2.1 | } | 
| 230 |  |  | } | 
| 231 |  |  | /* back reflection */ | 
| 232 |  |  | if (bsd.rb != NULL || bsd.rLambBack.cieY > .002) { | 
| 233 |  |  | input_orient = -1; output_orient = -1; | 
| 234 | greg | 2.20 | cfp[CIE_Y] = open_component_file(CIE_Y); | 
| 235 |  |  | if (bsd.rb != NULL && bsd.rb->comp[0].cspec[2].flags) { | 
| 236 |  |  | rbf_colorimetry = RBCtristimulus; | 
| 237 |  |  | cfp[CIE_X] = open_component_file(CIE_X); | 
| 238 |  |  | cfp[CIE_Z] = open_component_file(CIE_Z); | 
| 239 |  |  | } else | 
| 240 |  |  | rbf_colorimetry = RBCphotopic; | 
| 241 | greg | 2.1 | for (j = 0; j < abp->nangles; j++) { | 
| 242 |  |  | for (i = 0; i < abp->nangles; i++) { | 
| 243 |  |  | sum = 0;                    /* average over patches */ | 
| 244 | greg | 2.20 | xsum = ysum = 0; | 
| 245 | greg | 2.1 | for (n = npsamps; n-- > 0; ) { | 
| 246 |  |  | bo_getvec(vout, j+(n+frandom())/npsamps, abp); | 
| 247 | greg | 2.5 | bi_getvec(vin, i+urand(n), abp); | 
| 248 | greg | 2.20 | ec = SDevalBSDF(&sdv, vout, vin, &bsd); | 
| 249 | greg | 2.1 | if (ec != SDEnone) | 
| 250 |  |  | goto err; | 
| 251 | greg | 2.20 | sum += sdv.cieY; | 
| 252 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 253 | greg | 2.21 | xsum += sdv.cieY * sdv.spec.cx; | 
| 254 |  |  | ysum += sdv.cieY * sdv.spec.cy; | 
| 255 | greg | 2.20 | } | 
| 256 |  |  | } | 
| 257 |  |  | fprintf(cfp[CIE_Y], "\t%.3e\n", sum/npsamps); | 
| 258 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 259 | greg | 2.21 | fprintf(cfp[CIE_X], "\t%.3e\n", xsum*sum/(npsamps*ysum)); | 
| 260 |  |  | fprintf(cfp[CIE_Z], "\t%.3e\n", | 
| 261 | greg | 2.20 | (sum - xsum - ysum)*sum/(npsamps*ysum)); | 
| 262 | greg | 2.1 | } | 
| 263 |  |  | } | 
| 264 | greg | 2.20 | if (rbf_colorimetry == RBCtristimulus) { | 
| 265 |  |  | fputc('\n', cfp[CIE_X]); | 
| 266 |  |  | fputc('\n', cfp[CIE_Z]); | 
| 267 |  |  | } | 
| 268 |  |  | } | 
| 269 |  |  | if (fclose(cfp[CIE_Y])) { | 
| 270 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 271 |  |  | exit(1); | 
| 272 |  |  | } | 
| 273 |  |  | if (rbf_colorimetry == RBCtristimulus && | 
| 274 |  |  | (fclose(cfp[CIE_X]) || fclose(cfp[CIE_Z]))) { | 
| 275 |  |  | fprintf(stderr, "%s: error writing X/Z output\n", progname); | 
| 276 |  |  | exit(1); | 
| 277 | greg | 2.1 | } | 
| 278 |  |  | } | 
| 279 |  |  | /* front transmission */ | 
| 280 |  |  | if (bsd.tf != NULL || bsd.tLamb.cieY > .002) { | 
| 281 |  |  | input_orient = 1; output_orient = -1; | 
| 282 | greg | 2.20 | cfp[CIE_Y] = open_component_file(CIE_Y); | 
| 283 |  |  | if (bsd.tf != NULL && bsd.tf->comp[0].cspec[2].flags) { | 
| 284 |  |  | rbf_colorimetry = RBCtristimulus; | 
| 285 |  |  | cfp[CIE_X] = open_component_file(CIE_X); | 
| 286 |  |  | cfp[CIE_Z] = open_component_file(CIE_Z); | 
| 287 |  |  | } else | 
| 288 |  |  | rbf_colorimetry = RBCphotopic; | 
| 289 | greg | 2.1 | for (j = 0; j < abp->nangles; j++) { | 
| 290 |  |  | for (i = 0; i < abp->nangles; i++) { | 
| 291 |  |  | sum = 0;                    /* average over patches */ | 
| 292 | greg | 2.20 | xsum = ysum = 0; | 
| 293 | greg | 2.1 | for (n = npsamps; n-- > 0; ) { | 
| 294 |  |  | bo_getvec(vout, j+(n+frandom())/npsamps, abp); | 
| 295 | greg | 2.5 | fi_getvec(vin, i+urand(n), abp); | 
| 296 | greg | 2.20 | ec = SDevalBSDF(&sdv, vout, vin, &bsd); | 
| 297 | greg | 2.1 | if (ec != SDEnone) | 
| 298 |  |  | goto err; | 
| 299 | greg | 2.20 | sum += sdv.cieY; | 
| 300 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 301 | greg | 2.21 | xsum += sdv.cieY * sdv.spec.cx; | 
| 302 |  |  | ysum += sdv.cieY * sdv.spec.cy; | 
| 303 | greg | 2.20 | } | 
| 304 | greg | 2.1 | } | 
| 305 | greg | 2.20 | fprintf(cfp[CIE_Y], "\t%.3e\n", sum/npsamps); | 
| 306 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 307 | greg | 2.21 | fprintf(cfp[CIE_X], "\t%.3e\n", xsum*sum/(npsamps*ysum)); | 
| 308 |  |  | fprintf(cfp[CIE_Z], "\t%.3e\n", | 
| 309 | greg | 2.20 | (sum - xsum - ysum)*sum/(npsamps*ysum)); | 
| 310 |  |  | } | 
| 311 |  |  | } | 
| 312 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 313 |  |  | fputc('\n', cfp[CIE_X]); | 
| 314 |  |  | fputc('\n', cfp[CIE_Z]); | 
| 315 | greg | 2.1 | } | 
| 316 |  |  | } | 
| 317 | greg | 2.20 | if (fclose(cfp[CIE_Y])) { | 
| 318 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 319 |  |  | exit(1); | 
| 320 |  |  | } | 
| 321 |  |  | if (rbf_colorimetry == RBCtristimulus && | 
| 322 |  |  | (fclose(cfp[CIE_X]) || fclose(cfp[CIE_Z]))) { | 
| 323 |  |  | fprintf(stderr, "%s: error writing X/Z output\n", progname); | 
| 324 |  |  | exit(1); | 
| 325 |  |  | } | 
| 326 | greg | 2.1 | } | 
| 327 |  |  | /* back transmission */ | 
| 328 | greg | 2.9 | if ((bsd.tb != NULL) | (bsd.tf != NULL)) { | 
| 329 | greg | 2.1 | input_orient = -1; output_orient = 1; | 
| 330 | greg | 2.20 | cfp[CIE_Y] = open_component_file(CIE_Y); | 
| 331 | greg | 2.21 | if (bsd.tb != NULL) | 
| 332 |  |  | rbf_colorimetry = bsd.tb->comp[0].cspec[2].flags | 
| 333 |  |  | ? RBCtristimulus : RBCphotopic ; | 
| 334 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 335 | greg | 2.20 | cfp[CIE_X] = open_component_file(CIE_X); | 
| 336 |  |  | cfp[CIE_Z] = open_component_file(CIE_Z); | 
| 337 | greg | 2.21 | } | 
| 338 | greg | 2.1 | for (j = 0; j < abp->nangles; j++) { | 
| 339 | greg | 2.9 | for (i = 0; i < abp->nangles; i++) { | 
| 340 |  |  | sum = 0;            /* average over patches */ | 
| 341 | greg | 2.20 | xsum = ysum = 0; | 
| 342 | greg | 2.9 | for (n = npsamps; n-- > 0; ) { | 
| 343 |  |  | fo_getvec(vout, j+(n+frandom())/npsamps, abp); | 
| 344 |  |  | bi_getvec(vin, i+urand(n), abp); | 
| 345 | greg | 2.20 | ec = SDevalBSDF(&sdv, vout, vin, &bsd); | 
| 346 | greg | 2.9 | if (ec != SDEnone) | 
| 347 | greg | 2.1 | goto err; | 
| 348 | greg | 2.20 | sum += sdv.cieY; | 
| 349 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 350 | greg | 2.21 | xsum += sdv.cieY * sdv.spec.cx; | 
| 351 |  |  | ysum += sdv.cieY * sdv.spec.cy; | 
| 352 | greg | 2.20 | } | 
| 353 |  |  | } | 
| 354 |  |  | fprintf(cfp[CIE_Y], "\t%.3e\n", sum/npsamps); | 
| 355 |  |  | if (rbf_colorimetry == RBCtristimulus) { | 
| 356 | greg | 2.21 | fprintf(cfp[CIE_X], "\t%.3e\n", xsum*sum/(npsamps*ysum)); | 
| 357 |  |  | fprintf(cfp[CIE_Z], "\t%.3e\n", | 
| 358 | greg | 2.20 | (sum - xsum - ysum)*sum/(npsamps*ysum)); | 
| 359 | greg | 2.1 | } | 
| 360 | greg | 2.9 | } | 
| 361 | greg | 2.20 | if (rbf_colorimetry == RBCtristimulus) { | 
| 362 |  |  | fputc('\n', cfp[CIE_X]); | 
| 363 |  |  | fputc('\n', cfp[CIE_Z]); | 
| 364 |  |  | } | 
| 365 |  |  | } | 
| 366 |  |  | if (fclose(cfp[CIE_Y])) { | 
| 367 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 368 |  |  | exit(1); | 
| 369 |  |  | } | 
| 370 |  |  | if (rbf_colorimetry == RBCtristimulus && | 
| 371 |  |  | (fclose(cfp[CIE_X]) || fclose(cfp[CIE_Z]))) { | 
| 372 |  |  | fprintf(stderr, "%s: error writing X/Z output\n", progname); | 
| 373 |  |  | exit(1); | 
| 374 | greg | 2.1 | } | 
| 375 |  |  | } | 
| 376 |  |  | SDfreeBSDF(&bsd);                       /* all done */ | 
| 377 |  |  | return; | 
| 378 |  |  | err: | 
| 379 |  |  | SDreportError(ec, stderr); | 
| 380 |  |  | exit(1); | 
| 381 |  |  | } | 
| 382 |  |  |  | 
| 383 | greg | 2.2 | /* Interpolate and output a BSDF function using Klems basis */ | 
| 384 | greg | 2.1 | static void | 
| 385 |  |  | eval_function(char *funame) | 
| 386 |  |  | { | 
| 387 |  |  | ANGLE_BASIS     *abp = get_basis(kbasis); | 
| 388 | greg | 2.8 | int             assignD = (fundefined(funame) < 6); | 
| 389 | greg | 2.20 | FILE            *ofp = open_component_file(CIE_Y); | 
| 390 | greg | 2.1 | double          iovec[6]; | 
| 391 |  |  | double          sum; | 
| 392 |  |  | int             i, j, n; | 
| 393 |  |  |  | 
| 394 | greg | 2.4 | initurand(npsamps); | 
| 395 | greg | 2.1 | for (j = 0; j < abp->nangles; j++) {    /* run through directions */ | 
| 396 |  |  | for (i = 0; i < abp->nangles; i++) { | 
| 397 |  |  | sum = 0; | 
| 398 |  |  | for (n = npsamps; n--; ) {      /* average over patches */ | 
| 399 |  |  | if (output_orient > 0) | 
| 400 |  |  | fo_getvec(iovec+3, j+(n+frandom())/npsamps, abp); | 
| 401 |  |  | else | 
| 402 |  |  | bo_getvec(iovec+3, j+(n+frandom())/npsamps, abp); | 
| 403 |  |  |  | 
| 404 |  |  | if (input_orient > 0) | 
| 405 | greg | 2.4 | fi_getvec(iovec, i+urand(n), abp); | 
| 406 | greg | 2.1 | else | 
| 407 | greg | 2.4 | bi_getvec(iovec, i+urand(n), abp); | 
| 408 | greg | 2.1 |  | 
| 409 | greg | 2.8 | if (assignD) { | 
| 410 |  |  | varset("Dx", '=', -iovec[3]); | 
| 411 |  |  | varset("Dy", '=', -iovec[4]); | 
| 412 |  |  | varset("Dz", '=', -iovec[5]); | 
| 413 |  |  | ++eclock; | 
| 414 |  |  | } | 
| 415 | greg | 2.1 | sum += funvalue(funame, 6, iovec); | 
| 416 |  |  | } | 
| 417 | greg | 2.20 | fprintf(ofp, "\t%.3e\n", sum/npsamps); | 
| 418 | greg | 2.1 | } | 
| 419 | greg | 2.20 | fputc('\n', ofp); | 
| 420 | greg | 2.13 | prog_show((j+1.)/abp->nangles); | 
| 421 | greg | 2.1 | } | 
| 422 | greg | 2.13 | prog_done(); | 
| 423 | greg | 2.20 | if (fclose(ofp)) { | 
| 424 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 425 |  |  | exit(1); | 
| 426 |  |  | } | 
| 427 | greg | 2.1 | } | 
| 428 |  |  |  | 
| 429 |  |  | /* Interpolate and output a radial basis function BSDF representation */ | 
| 430 |  |  | static void | 
| 431 |  |  | eval_rbf(void) | 
| 432 |  |  | { | 
| 433 | greg | 2.27 | ANGLE_BASIS *abp = get_basis(kbasis); | 
| 434 |  |  | float       (*XZarr)[2] = NULL; | 
| 435 |  |  | float       bsdfarr[MAXPATCHES*MAXPATCHES]; | 
| 436 |  |  | FILE        *cfp[3]; | 
| 437 |  |  | FVECT       vin, vout; | 
| 438 |  |  | double      sum, xsum, ysum, normf; | 
| 439 |  |  | int         i, j, ni, no, nisamps, nosamps; | 
| 440 |  |  | /* sanity check */ | 
| 441 |  |  | if (abp->nangles > MAXPATCHES) { | 
| 442 |  |  | fprintf(stderr, "%s: too many patches!\n", progname); | 
| 443 |  |  | exit(1); | 
| 444 |  |  | } | 
| 445 |  |  | memset(bsdfarr, 0, sizeof(bsdfarr)); | 
| 446 |  |  | if (rbf_colorimetry == RBCtristimulus) | 
| 447 |  |  | XZarr = (float (*)[2])calloc(abp->nangles*abp->nangles, 2*sizeof(float)); | 
| 448 |  |  | nosamps = (int)(pow((double)npsamps, 0.67) + .5); | 
| 449 |  |  | nisamps = (npsamps + (nosamps>>1)) / nosamps; | 
| 450 |  |  | normf = 1./(double)(nisamps*nosamps); | 
| 451 |  |  | for (i = 0; i < abp->nangles; i++) { | 
| 452 |  |  | for (ni = nisamps; ni--; ) {            /* sample over incident patch */ | 
| 453 |  |  | RBFNODE     *rbf; | 
| 454 |  |  | if (input_orient > 0)               /* vary incident patch loc. */ | 
| 455 |  |  | fi_getvec(vin, i+urand(ni), abp); | 
| 456 |  |  | else | 
| 457 |  |  | bi_getvec(vin, i+urand(ni), abp); | 
| 458 | greg | 2.2 |  | 
| 459 | greg | 2.27 | rbf = advect_rbf(vin, lobe_lim);    /* compute radial basis func */ | 
| 460 | greg | 2.2 |  | 
| 461 | greg | 2.27 | for (j = 0; j < abp->nangles; j++) { | 
| 462 |  |  | sum = 0;                        /* sample over exiting patch */ | 
| 463 | greg | 2.20 | xsum = ysum = 0; | 
| 464 | greg | 2.27 | for (no = nosamps; no--; ) { | 
| 465 | greg | 2.20 | SDValue     sdv; | 
| 466 | greg | 2.2 | if (output_orient > 0) | 
| 467 | greg | 2.27 | fo_getvec(vout, j+(no+frandom())/nosamps, abp); | 
| 468 | greg | 2.2 | else | 
| 469 | greg | 2.27 | bo_getvec(vout, j+(no+frandom())/nosamps, abp); | 
| 470 | greg | 2.1 |  | 
| 471 | greg | 2.20 | eval_rbfcol(&sdv, rbf, vout); | 
| 472 |  |  | sum += sdv.cieY; | 
| 473 | greg | 2.23 | if (rbf_colorimetry == RBCtristimulus) { | 
| 474 | greg | 2.21 | xsum += sdv.cieY * sdv.spec.cx; | 
| 475 |  |  | ysum += sdv.cieY * sdv.spec.cy; | 
| 476 | greg | 2.27 | } | 
| 477 | greg | 2.20 | } | 
| 478 | greg | 2.27 | no = j*abp->nangles + i; | 
| 479 |  |  | bsdfarr[no] += sum * normf; | 
| 480 | greg | 2.23 | if (rbf_colorimetry == RBCtristimulus) { | 
| 481 | greg | 2.27 | XZarr[no][0] += xsum*sum*normf/ysum; | 
| 482 |  |  | XZarr[no][1] += (sum - xsum - ysum)*sum*normf/ysum; | 
| 483 | greg | 2.2 | } | 
| 484 |  |  | } | 
| 485 | greg | 2.27 | if (rbf != NULL) | 
| 486 | greg | 2.6 | free(rbf); | 
| 487 | greg | 2.20 | } | 
| 488 | greg | 2.27 | prog_show((i+1.)/abp->nangles); | 
| 489 |  |  | } | 
| 490 |  |  | /* write out our matrix */ | 
| 491 |  |  | cfp[CIE_Y] = open_component_file(CIE_Y); | 
| 492 |  |  | no = 0; | 
| 493 |  |  | for (j = 0; j < abp->nangles; j++) { | 
| 494 |  |  | for (i = 0; i < abp->nangles; i++, no++) | 
| 495 |  |  | fprintf(cfp[CIE_Y], "\t%.3e\n", bsdfarr[no]); | 
| 496 |  |  | fputc('\n', cfp[CIE_Y]); | 
| 497 |  |  | } | 
| 498 |  |  | prog_done(); | 
| 499 |  |  | if (fclose(cfp[CIE_Y])) { | 
| 500 |  |  | fprintf(stderr, "%s: error writing Y output\n", progname); | 
| 501 |  |  | exit(1); | 
| 502 |  |  | } | 
| 503 |  |  | if (XZarr == NULL)                  /* no color? */ | 
| 504 |  |  | return; | 
| 505 |  |  | cfp[CIE_X] = open_component_file(CIE_X); | 
| 506 |  |  | cfp[CIE_Z] = open_component_file(CIE_Z); | 
| 507 |  |  | no = 0; | 
| 508 |  |  | for (j = 0; j < abp->nangles; j++) { | 
| 509 |  |  | for (i = 0; i < abp->nangles; i++, no++) { | 
| 510 |  |  | fprintf(cfp[CIE_X], "\t%.3e\n", XZarr[no][0]); | 
| 511 |  |  | fprintf(cfp[CIE_Z], "\t%.3e\n", XZarr[no][1]); | 
| 512 |  |  | } | 
| 513 |  |  | fputc('\n', cfp[CIE_X]); | 
| 514 |  |  | fputc('\n', cfp[CIE_Z]); | 
| 515 |  |  | } | 
| 516 |  |  | free(XZarr); | 
| 517 |  |  | if (fclose(cfp[CIE_X]) || fclose(cfp[CIE_Z])) { | 
| 518 |  |  | fprintf(stderr, "%s: error writing X/Z output\n", progname); | 
| 519 |  |  | exit(1); | 
| 520 |  |  | } | 
| 521 | greg | 2.1 | } | 
| 522 |  |  |  | 
| 523 | schorsch | 2.22 | #if defined(_WIN32) || defined(_WIN64) | 
| 524 | greg | 2.20 | /* Execute wrapBSDF command (may never return) */ | 
| 525 |  |  | static int | 
| 526 |  |  | wrap_up(void) | 
| 527 |  |  | { | 
| 528 |  |  | char    cmd[8192]; | 
| 529 |  |  |  | 
| 530 |  |  | if (bsdf_manuf[0]) { | 
| 531 |  |  | add_wbsdf("-f", 1); | 
| 532 |  |  | strcpy(cmd, "m="); | 
| 533 |  |  | strcpy(cmd+2, bsdf_manuf); | 
| 534 |  |  | add_wbsdf(cmd, 0); | 
| 535 |  |  | } | 
| 536 |  |  | if (bsdf_name[0]) { | 
| 537 |  |  | add_wbsdf("-f", 1); | 
| 538 |  |  | strcpy(cmd, "n="); | 
| 539 |  |  | strcpy(cmd+2, bsdf_name); | 
| 540 |  |  | add_wbsdf(cmd, 0); | 
| 541 |  |  | } | 
| 542 |  |  | if (!convert_commandline(cmd, sizeof(cmd), wrapBSDF)) { | 
| 543 |  |  | fputs(progname, stderr); | 
| 544 |  |  | fputs(": command line too long in wrap_up()\n", stderr); | 
| 545 |  |  | return(1); | 
| 546 |  |  | } | 
| 547 |  |  | return(system(cmd)); | 
| 548 |  |  | } | 
| 549 |  |  | #else | 
| 550 |  |  | /* Execute wrapBSDF command (may never return) */ | 
| 551 |  |  | static int | 
| 552 |  |  | wrap_up(void) | 
| 553 |  |  | { | 
| 554 |  |  | char    buf[256]; | 
| 555 |  |  | char    *compath = getpath((char *)wrapBSDF[0], getenv("PATH"), X_OK); | 
| 556 |  |  |  | 
| 557 |  |  | if (compath == NULL) { | 
| 558 |  |  | fprintf(stderr, "%s: cannot locate %s\n", progname, wrapBSDF[0]); | 
| 559 |  |  | return(1); | 
| 560 |  |  | } | 
| 561 |  |  | if (bsdf_manuf[0]) { | 
| 562 |  |  | add_wbsdf("-f", 1); | 
| 563 |  |  | strcpy(buf, "m="); | 
| 564 |  |  | strcpy(buf+2, bsdf_manuf); | 
| 565 |  |  | add_wbsdf(buf, 0); | 
| 566 |  |  | } | 
| 567 |  |  | if (bsdf_name[0]) { | 
| 568 |  |  | add_wbsdf("-f", 1); | 
| 569 |  |  | strcpy(buf, "n="); | 
| 570 |  |  | strcpy(buf+2, bsdf_name); | 
| 571 |  |  | add_wbsdf(buf, 0); | 
| 572 |  |  | } | 
| 573 |  |  | execv(compath, wrapBSDF);       /* successful call never returns */ | 
| 574 |  |  | perror(compath); | 
| 575 |  |  | return(1); | 
| 576 |  |  | } | 
| 577 |  |  | #endif | 
| 578 |  |  |  | 
| 579 | greg | 2.29 | #define HEAD_BUFLEN     8192 | 
| 580 |  |  | static char     head_buf[HEAD_BUFLEN]; | 
| 581 |  |  | static int      cur_headlen = 0; | 
| 582 |  |  |  | 
| 583 |  |  | /* Record header line as comment associated with this SIR input */ | 
| 584 |  |  | static int | 
| 585 |  |  | record2header(char *s) | 
| 586 |  |  | { | 
| 587 |  |  | int     len = strlen(s); | 
| 588 |  |  |  | 
| 589 |  |  | if (cur_headlen+len >= HEAD_BUFLEN-6) | 
| 590 |  |  | return(0); | 
| 591 |  |  | /* includes EOL */ | 
| 592 |  |  | strcpy(head_buf+cur_headlen, s); | 
| 593 |  |  | cur_headlen += len; | 
| 594 |  |  |  | 
| 595 |  |  | return(1); | 
| 596 |  |  | } | 
| 597 |  |  |  | 
| 598 |  |  | /* Finish off header for this file */ | 
| 599 |  |  | static void | 
| 600 |  |  | done_header(void) | 
| 601 |  |  | { | 
| 602 |  |  | while (cur_headlen > 0 && isspace(head_buf[cur_headlen-1])) | 
| 603 |  |  | --cur_headlen; | 
| 604 |  |  | head_buf[cur_headlen] = '\0'; | 
| 605 |  |  | if (!cur_headlen) | 
| 606 |  |  | return; | 
| 607 |  |  | add_wbsdf("-C", 1); | 
| 608 |  |  | add_wbsdf(head_buf, 0); | 
| 609 |  |  | head_buf[cur_headlen=0] = '\0'; | 
| 610 |  |  | } | 
| 611 |  |  |  | 
| 612 | greg | 2.1 | /* Read in BSDF and interpolate as Klems matrix representation */ | 
| 613 |  |  | int | 
| 614 |  |  | main(int argc, char *argv[]) | 
| 615 |  |  | { | 
| 616 |  |  | int     dofwd = 0, dobwd = 1; | 
| 617 | greg | 2.20 | char    buf[2048]; | 
| 618 | greg | 2.1 | char    *cp; | 
| 619 |  |  | int     i, na; | 
| 620 |  |  |  | 
| 621 |  |  | progname = argv[0]; | 
| 622 |  |  | esupport |= E_VARIABLE|E_FUNCTION|E_RCONST; | 
| 623 |  |  | esupport &= ~(E_INCHAN|E_OUTCHAN); | 
| 624 |  |  | scompile("PI:3.14159265358979323846", NULL, 0); | 
| 625 |  |  | biggerlib(); | 
| 626 | greg | 2.2 | for (i = 1; i < argc && (argv[i][0] == '-') | (argv[i][0] == '+'); i++) | 
| 627 | greg | 2.1 | switch (argv[i][1]) {           /* get options */ | 
| 628 |  |  | case 'n': | 
| 629 |  |  | npsamps = atoi(argv[++i]); | 
| 630 |  |  | if (npsamps <= 0) | 
| 631 |  |  | goto userr; | 
| 632 |  |  | break; | 
| 633 |  |  | case 'e': | 
| 634 |  |  | scompile(argv[++i], NULL, 0); | 
| 635 |  |  | single_plane_incident = 0; | 
| 636 |  |  | break; | 
| 637 |  |  | case 'f': | 
| 638 |  |  | if (!argv[i][2]) { | 
| 639 | greg | 2.20 | if (strchr(argv[++i], '=') != NULL) { | 
| 640 |  |  | add_wbsdf("-f", 1); | 
| 641 |  |  | add_wbsdf(argv[i], 1); | 
| 642 |  |  | } else { | 
| 643 | greg | 2.25 | char    *fpath = getpath(argv[i], | 
| 644 |  |  | getrlibpath(), 0); | 
| 645 |  |  | if (fpath == NULL) { | 
| 646 |  |  | fprintf(stderr, | 
| 647 |  |  | "%s: cannot find file '%s'\n", | 
| 648 |  |  | argv[0], argv[i]); | 
| 649 |  |  | return(1); | 
| 650 |  |  | } | 
| 651 |  |  | fcompile(fpath); | 
| 652 | greg | 2.20 | single_plane_incident = 0; | 
| 653 |  |  | } | 
| 654 | greg | 2.1 | } else | 
| 655 |  |  | dofwd = (argv[i][0] == '+'); | 
| 656 |  |  | break; | 
| 657 |  |  | case 'b': | 
| 658 |  |  | dobwd = (argv[i][0] == '+'); | 
| 659 |  |  | break; | 
| 660 |  |  | case 'h': | 
| 661 | greg | 2.20 | kbasis = klems_half; | 
| 662 |  |  | add_wbsdf("-a", 1); | 
| 663 |  |  | add_wbsdf("kh", 1); | 
| 664 | greg | 2.1 | break; | 
| 665 |  |  | case 'q': | 
| 666 | greg | 2.20 | kbasis = klems_quarter; | 
| 667 |  |  | add_wbsdf("-a", 1); | 
| 668 |  |  | add_wbsdf("kq", 1); | 
| 669 | greg | 2.1 | break; | 
| 670 | greg | 2.10 | case 'l': | 
| 671 |  |  | lobe_lim = atoi(argv[++i]); | 
| 672 |  |  | break; | 
| 673 | greg | 2.13 | case 'p': | 
| 674 |  |  | do_prog = atoi(argv[i]+2); | 
| 675 |  |  | break; | 
| 676 | greg | 2.20 | case 'C': | 
| 677 |  |  | add_wbsdf(argv[i], 1); | 
| 678 |  |  | add_wbsdf(argv[++i], 1); | 
| 679 |  |  | break; | 
| 680 | greg | 2.1 | default: | 
| 681 |  |  | goto userr; | 
| 682 |  |  | } | 
| 683 | greg | 2.20 | if (kbasis == klems_full) {             /* default (full) basis? */ | 
| 684 |  |  | add_wbsdf("-a", 1); | 
| 685 |  |  | add_wbsdf("kf", 1); | 
| 686 |  |  | } | 
| 687 |  |  | strcpy(buf, "File produced by: "); | 
| 688 |  |  | if (convert_commandline(buf+18, sizeof(buf)-18, argv) != NULL) { | 
| 689 |  |  | add_wbsdf("-C", 1); add_wbsdf(buf, 0); | 
| 690 |  |  | } | 
| 691 | greg | 2.1 | if (single_plane_incident >= 0) {       /* function-based BSDF? */ | 
| 692 | greg | 2.19 | if (i != argc-1 || fundefined(argv[i]) < 3) { | 
| 693 | greg | 2.1 | fprintf(stderr, | 
| 694 |  |  | "%s: need single function with 6 arguments: bsdf(ix,iy,iz,ox,oy,oz)\n", | 
| 695 |  |  | progname); | 
| 696 | greg | 2.13 | fprintf(stderr, "\tor 3 arguments using Dx,Dy,Dz: bsdf(ix,iy,iz)\n"); | 
| 697 | greg | 2.1 | goto userr; | 
| 698 |  |  | } | 
| 699 | greg | 2.8 | ++eclock; | 
| 700 | greg | 2.1 | if (dofwd) { | 
| 701 |  |  | input_orient = -1; | 
| 702 |  |  | output_orient = -1; | 
| 703 | greg | 2.13 | prog_start("Evaluating outside reflectance"); | 
| 704 |  |  | eval_function(argv[i]); | 
| 705 | greg | 2.1 | output_orient = 1; | 
| 706 | greg | 2.13 | prog_start("Evaluating outside->inside transmission"); | 
| 707 |  |  | eval_function(argv[i]); | 
| 708 | greg | 2.1 | } | 
| 709 |  |  | if (dobwd) { | 
| 710 |  |  | input_orient = 1; | 
| 711 |  |  | output_orient = 1; | 
| 712 | greg | 2.13 | prog_start("Evaluating inside reflectance"); | 
| 713 |  |  | eval_function(argv[i]); | 
| 714 | greg | 2.1 | output_orient = -1; | 
| 715 | greg | 2.13 | prog_start("Evaluating inside->outside transmission"); | 
| 716 |  |  | eval_function(argv[i]); | 
| 717 | greg | 2.1 | } | 
| 718 | greg | 2.20 | return(wrap_up()); | 
| 719 | greg | 2.1 | } | 
| 720 | greg | 2.2 | /* XML input? */ | 
| 721 |  |  | if (i == argc-1 && (cp = argv[i]+strlen(argv[i])-4) > argv[i] && | 
| 722 |  |  | !strcasecmp(cp, ".xml")) { | 
| 723 | greg | 2.1 | eval_bsdf(argv[i]);             /* load & resample BSDF */ | 
| 724 | greg | 2.20 | return(wrap_up()); | 
| 725 | greg | 2.1 | } | 
| 726 |  |  | if (i < argc) {                         /* open input files if given */ | 
| 727 |  |  | int     nbsdf = 0; | 
| 728 |  |  | for ( ; i < argc; i++) {        /* interpolate each component */ | 
| 729 | greg | 2.13 | char    pbuf[256]; | 
| 730 | greg | 2.1 | FILE    *fpin = fopen(argv[i], "rb"); | 
| 731 |  |  | if (fpin == NULL) { | 
| 732 |  |  | fprintf(stderr, "%s: cannot open BSDF interpolant '%s'\n", | 
| 733 |  |  | progname, argv[i]); | 
| 734 |  |  | return(1); | 
| 735 |  |  | } | 
| 736 | greg | 2.29 | sprintf(pbuf, "%s:\n", argv[i]); | 
| 737 |  |  | record2header(pbuf); | 
| 738 |  |  | sir_headshare = &record2header; | 
| 739 | greg | 2.1 | if (!load_bsdf_rep(fpin)) | 
| 740 |  |  | return(1); | 
| 741 |  |  | fclose(fpin); | 
| 742 | greg | 2.29 | done_header(); | 
| 743 | greg | 2.13 | sprintf(pbuf, "Interpolating component '%s'", argv[i]); | 
| 744 |  |  | prog_start(pbuf); | 
| 745 | greg | 2.1 | eval_rbf(); | 
| 746 |  |  | } | 
| 747 | greg | 2.20 | return(wrap_up()); | 
| 748 | greg | 2.1 | } | 
| 749 |  |  | SET_FILE_BINARY(stdin);                 /* load from stdin */ | 
| 750 |  |  | if (!load_bsdf_rep(stdin)) | 
| 751 |  |  | return(1); | 
| 752 | greg | 2.13 | prog_start("Interpolating from standard input"); | 
| 753 | greg | 2.1 | eval_rbf();                             /* resample dist. */ | 
| 754 | greg | 2.20 | return(wrap_up()); | 
| 755 | greg | 2.1 | userr: | 
| 756 |  |  | fprintf(stderr, | 
| 757 | greg | 2.10 | "Usage: %s [-n spp][-h|-q][-l maxlobes] [bsdf.sir ..] > bsdf.xml\n", progname); | 
| 758 | greg | 2.1 | fprintf(stderr, | 
| 759 | greg | 2.3 | "   or: %s [-n spp][-h|-q] bsdf_in.xml > bsdf_out.xml\n", progname); | 
| 760 | greg | 2.1 | fprintf(stderr, | 
| 761 |  |  | "   or: %s [-n spp][-h|-q][{+|-}for[ward]][{+|-}b[ackward]][-e expr][-f file] bsdf_func > bsdf.xml\n", | 
| 762 |  |  | progname); | 
| 763 |  |  | return(1); | 
| 764 |  |  | } |