ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf.c
Revision: 2.6
Committed: Fri Sep 3 23:53:50 2010 UTC (13 years, 8 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.5: +72 -2 lines
Log Message:
Working version of genBSDF with detail geometry support in mkillum

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bsdf.c,v 2.5 2010/08/01 22:26:37 greg Exp $";
3 #endif
4 /*
5 * Routines for handling BSDF data
6 */
7
8 #include "standard.h"
9 #include "bsdf.h"
10 #include "paths.h"
11 #include "ezxml.h"
12 #include <ctype.h>
13
14 #define MAXLATS 46 /* maximum number of latitudes */
15
16 /* BSDF angle specification */
17 typedef struct {
18 char name[64]; /* basis name */
19 int nangles; /* total number of directions */
20 struct {
21 float tmin; /* starting theta */
22 short nphis; /* number of phis (0 term) */
23 } lat[MAXLATS+1]; /* latitudes */
24 } ANGLE_BASIS;
25
26 #define MAXABASES 7 /* limit on defined bases */
27
28 static ANGLE_BASIS abase_list[MAXABASES] = {
29 {
30 "LBNL/Klems Full", 145,
31 { {-5., 1},
32 {5., 8},
33 {15., 16},
34 {25., 20},
35 {35., 24},
36 {45., 24},
37 {55., 24},
38 {65., 16},
39 {75., 12},
40 {90., 0} }
41 }, {
42 "LBNL/Klems Half", 73,
43 { {-6.5, 1},
44 {6.5, 8},
45 {19.5, 12},
46 {32.5, 16},
47 {46.5, 20},
48 {61.5, 12},
49 {76.5, 4},
50 {90., 0} }
51 }, {
52 "LBNL/Klems Quarter", 41,
53 { {-9., 1},
54 {9., 8},
55 {27., 12},
56 {46., 12},
57 {66., 8},
58 {90., 0} }
59 }
60 };
61
62 static int nabases = 3; /* current number of defined bases */
63
64 #define FEQ(a,b) ((a)-(b) <= 1e-7 && (b)-(a) <= 1e-7)
65
66 // returns the name of the given tag
67 #ifdef ezxml_name
68 #undef ezxml_name
69 static char *
70 ezxml_name(ezxml_t xml)
71 {
72 if (xml == NULL)
73 return(NULL);
74 return(xml->name);
75 }
76 #endif
77
78 // returns the given tag's character content or empty string if none
79 #ifdef ezxml_txt
80 #undef ezxml_txt
81 static char *
82 ezxml_txt(ezxml_t xml)
83 {
84 if (xml == NULL)
85 return("");
86 return(xml->txt);
87 }
88 #endif
89
90
91 static int
92 ab_getvec( /* get vector for this angle basis index */
93 FVECT v,
94 int ndx,
95 void *p
96 )
97 {
98 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
99 int li;
100 double pol, azi, d;
101
102 if ((ndx < 0) | (ndx >= ab->nangles))
103 return(0);
104 for (li = 0; ndx >= ab->lat[li].nphis; li++)
105 ndx -= ab->lat[li].nphis;
106 pol = PI/180.*0.5*(ab->lat[li].tmin + ab->lat[li+1].tmin);
107 azi = 2.*PI*ndx/ab->lat[li].nphis;
108 v[2] = d = cos(pol);
109 d = sqrt(1. - d*d); /* sin(pol) */
110 v[0] = cos(azi)*d;
111 v[1] = sin(azi)*d;
112 return(1);
113 }
114
115
116 static int
117 ab_getndx( /* get index corresponding to the given vector */
118 FVECT v,
119 void *p
120 )
121 {
122 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
123 int li, ndx;
124 double pol, azi, d;
125
126 if ((v[2] < -1.0) | (v[2] > 1.0))
127 return(-1);
128 pol = 180.0/PI*acos(v[2]);
129 azi = 180.0/PI*atan2(v[1], v[0]);
130 if (azi < 0.0) azi += 360.0;
131 for (li = 1; ab->lat[li].tmin <= pol; li++)
132 if (!ab->lat[li].nphis)
133 return(-1);
134 --li;
135 ndx = (int)((1./360.)*azi*ab->lat[li].nphis + 0.5);
136 if (ndx >= ab->lat[li].nphis) ndx = 0;
137 while (li--)
138 ndx += ab->lat[li].nphis;
139 return(ndx);
140 }
141
142
143 static double
144 ab_getohm( /* get solid angle for this angle basis index */
145 int ndx,
146 void *p
147 )
148 {
149 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
150 int li;
151 double theta, theta1;
152
153 if ((ndx < 0) | (ndx >= ab->nangles))
154 return(0);
155 for (li = 0; ndx >= ab->lat[li].nphis; li++)
156 ndx -= ab->lat[li].nphis;
157 theta1 = PI/180. * ab->lat[li+1].tmin;
158 if (ab->lat[li].nphis == 1) { /* special case */
159 if (ab->lat[li].tmin > FTINY)
160 error(USER, "unsupported BSDF coordinate system");
161 return(2.*PI*(1. - cos(theta1)));
162 }
163 theta = PI/180. * ab->lat[li].tmin;
164 return(2.*PI*(cos(theta) - cos(theta1))/(double)ab->lat[li].nphis);
165 }
166
167
168 static int
169 ab_getvecR( /* get reverse vector for this angle basis index */
170 FVECT v,
171 int ndx,
172 void *p
173 )
174 {
175 if (!ab_getvec(v, ndx, p))
176 return(0);
177
178 v[0] = -v[0];
179 v[1] = -v[1];
180 v[2] = -v[2];
181
182 return(1);
183 }
184
185
186 static int
187 ab_getndxR( /* get index corresponding to the reverse vector */
188 FVECT v,
189 void *p
190 )
191 {
192 FVECT v2;
193
194 v2[0] = -v[0];
195 v2[1] = -v[1];
196 v2[2] = -v[2];
197
198 return ab_getndx(v2, p);
199 }
200
201
202 static void
203 load_angle_basis( /* load custom BSDF angle basis */
204 ezxml_t wab
205 )
206 {
207 char *abname = ezxml_txt(ezxml_child(wab, "AngleBasisName"));
208 ezxml_t wbb;
209 int i;
210
211 if (!abname || !*abname)
212 return;
213 for (i = nabases; i--; )
214 if (!strcmp(abname, abase_list[i].name))
215 return; /* assume it's the same */
216 if (nabases >= MAXABASES)
217 error(INTERNAL, "too many angle bases");
218 strcpy(abase_list[nabases].name, abname);
219 abase_list[nabases].nangles = 0;
220 for (i = 0, wbb = ezxml_child(wab, "AngleBasisBlock");
221 wbb != NULL; i++, wbb = wbb->next) {
222 if (i >= MAXLATS)
223 error(INTERNAL, "too many latitudes in custom basis");
224 abase_list[nabases].lat[i+1].tmin = atof(ezxml_txt(
225 ezxml_child(ezxml_child(wbb,
226 "ThetaBounds"), "UpperTheta")));
227 if (!i)
228 abase_list[nabases].lat[i].tmin =
229 -abase_list[nabases].lat[i+1].tmin;
230 else if (!FEQ(atof(ezxml_txt(ezxml_child(ezxml_child(wbb,
231 "ThetaBounds"), "LowerTheta"))),
232 abase_list[nabases].lat[i].tmin))
233 error(WARNING, "theta values disagree in custom basis");
234 abase_list[nabases].nangles +=
235 abase_list[nabases].lat[i].nphis =
236 atoi(ezxml_txt(ezxml_child(wbb, "nPhis")));
237 }
238 abase_list[nabases++].lat[i].nphis = 0;
239 }
240
241
242 static double
243 to_meters( /* return factor to convert given unit to meters */
244 const char *unit
245 )
246 {
247 if (unit == NULL) return(1.); /* safe assumption? */
248 if (!strcasecmp(unit, "Meter")) return(1.);
249 if (!strcasecmp(unit, "Foot")) return(.3048);
250 if (!strcasecmp(unit, "Inch")) return(.0254);
251 if (!strcasecmp(unit, "Centimeter")) return(.01);
252 sprintf(errmsg, "unknown dimensional unit '%s'", unit);
253 error(USER, errmsg);
254 }
255
256
257 static void
258 load_geometry( /* load geometric dimensions and description (if any) */
259 struct BSDF_data *dp,
260 ezxml_t wdb
261 )
262 {
263 ezxml_t geom;
264 double cfact;
265 const char *fmt, *mgfstr;
266
267 dp->dim[0] = dp->dim[1] = dp->dim[2] = 0;
268 dp->mgf = NULL;
269 if ((geom = ezxml_child(wdb, "Width")) != NULL)
270 dp->dim[0] = atof(ezxml_txt(geom)) *
271 to_meters(ezxml_attr(geom, "unit"));
272 if ((geom = ezxml_child(wdb, "Height")) != NULL)
273 dp->dim[1] = atof(ezxml_txt(geom)) *
274 to_meters(ezxml_attr(geom, "unit"));
275 if ((geom = ezxml_child(wdb, "Thickness")) != NULL)
276 dp->dim[2] = atof(ezxml_txt(geom)) *
277 to_meters(ezxml_attr(geom, "unit"));
278 if ((geom = ezxml_child(wdb, "Geometry")) == NULL ||
279 (mgfstr = ezxml_txt(geom)) == NULL)
280 return;
281 if ((fmt = ezxml_attr(geom, "format")) != NULL &&
282 strcasecmp(fmt, "MGF")) {
283 sprintf(errmsg, "unrecognized geometry format '%s'", fmt);
284 error(WARNING, errmsg);
285 return;
286 }
287 cfact = to_meters(ezxml_attr(geom, "unit"));
288 dp->mgf = (char *)malloc(strlen(mgfstr)+32);
289 if (dp->mgf == NULL)
290 error(SYSTEM, "out of memory in load_geometry");
291 if (cfact < 0.99 || cfact > 1.01)
292 sprintf(dp->mgf, "xf -s %.5f\n%s\nxf\n", cfact, mgfstr);
293 else
294 strcpy(dp->mgf, mgfstr);
295 }
296
297
298 static void
299 load_bsdf_data( /* load BSDF distribution for this wavelength */
300 struct BSDF_data *dp,
301 ezxml_t wdb
302 )
303 {
304 char *cbasis = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
305 char *rbasis = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
306 char *sdata;
307 int i;
308
309 if ((!cbasis || !*cbasis) | (!rbasis || !*rbasis)) {
310 error(WARNING, "missing column/row basis for BSDF");
311 return;
312 }
313 for (i = nabases; i--; )
314 if (!strcmp(cbasis, abase_list[i].name)) {
315 dp->ninc = abase_list[i].nangles;
316 dp->ib_priv = (void *)&abase_list[i];
317 dp->ib_vec = ab_getvecR;
318 dp->ib_ndx = ab_getndxR;
319 dp->ib_ohm = ab_getohm;
320 break;
321 }
322 if (i < 0) {
323 sprintf(errmsg, "undefined ColumnAngleBasis '%s'", cbasis);
324 error(WARNING, errmsg);
325 return;
326 }
327 for (i = nabases; i--; )
328 if (!strcmp(rbasis, abase_list[i].name)) {
329 dp->nout = abase_list[i].nangles;
330 dp->ob_priv = (void *)&abase_list[i];
331 dp->ob_vec = ab_getvec;
332 dp->ob_ndx = ab_getndx;
333 dp->ob_ohm = ab_getohm;
334 break;
335 }
336 if (i < 0) {
337 sprintf(errmsg, "undefined RowAngleBasis '%s'", cbasis);
338 error(WARNING, errmsg);
339 return;
340 }
341 /* read BSDF data */
342 sdata = ezxml_txt(ezxml_child(wdb,"ScatteringData"));
343 if (!sdata || !*sdata) {
344 error(WARNING, "missing BSDF ScatteringData");
345 return;
346 }
347 dp->bsdf = (float *)malloc(sizeof(float)*dp->ninc*dp->nout);
348 if (dp->bsdf == NULL)
349 error(SYSTEM, "out of memory in load_bsdf_data");
350 for (i = 0; i < dp->ninc*dp->nout; i++) {
351 char *sdnext = fskip(sdata);
352 if (sdnext == NULL) {
353 error(WARNING, "bad/missing BSDF ScatteringData");
354 free(dp->bsdf); dp->bsdf = NULL;
355 return;
356 }
357 while (*sdnext && isspace(*sdnext))
358 sdnext++;
359 if (*sdnext == ',') sdnext++;
360 dp->bsdf[i] = atof(sdata);
361 sdata = sdnext;
362 }
363 while (isspace(*sdata))
364 sdata++;
365 if (*sdata) {
366 sprintf(errmsg, "%d extra characters after BSDF ScatteringData",
367 (int)strlen(sdata));
368 error(WARNING, errmsg);
369 }
370 }
371
372
373 static int
374 check_bsdf_data( /* check that BSDF data is sane */
375 struct BSDF_data *dp
376 )
377 {
378 double *omega_iarr, *omega_oarr;
379 double dom, contrib, hemi_total;
380 int nneg;
381 FVECT v;
382 int i, o;
383
384 if (dp == NULL || dp->bsdf == NULL)
385 return(0);
386 omega_iarr = (double *)calloc(dp->ninc, sizeof(double));
387 omega_oarr = (double *)calloc(dp->nout, sizeof(double));
388 if ((omega_iarr == NULL) | (omega_oarr == NULL))
389 error(SYSTEM, "out of memory in check_bsdf_data");
390 /* incoming projected solid angles */
391 hemi_total = .0;
392 for (i = dp->ninc; i--; ) {
393 dom = getBSDF_incohm(dp,i);
394 if (dom <= .0) {
395 error(WARNING, "zero/negative incoming solid angle");
396 continue;
397 }
398 if (!getBSDF_incvec(v,dp,i) || v[2] > FTINY) {
399 error(WARNING, "illegal incoming BSDF direction");
400 free(omega_iarr); free(omega_oarr);
401 return(0);
402 }
403 hemi_total += omega_iarr[i] = dom * -v[2];
404 }
405 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
406 sprintf(errmsg, "incoming BSDF hemisphere off by %.1f%%",
407 100.*(hemi_total/PI - 1.));
408 error(WARNING, errmsg);
409 }
410 dom = PI / hemi_total; /* fix normalization */
411 for (i = dp->ninc; i--; )
412 omega_iarr[i] *= dom;
413 /* outgoing projected solid angles */
414 hemi_total = .0;
415 for (o = dp->nout; o--; ) {
416 dom = getBSDF_outohm(dp,o);
417 if (dom <= .0) {
418 error(WARNING, "zero/negative outgoing solid angle");
419 continue;
420 }
421 if (!getBSDF_outvec(v,dp,o) || v[2] < -FTINY) {
422 error(WARNING, "illegal outgoing BSDF direction");
423 free(omega_iarr); free(omega_oarr);
424 return(0);
425 }
426 hemi_total += omega_oarr[o] = dom * v[2];
427 }
428 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
429 sprintf(errmsg, "outgoing BSDF hemisphere off by %.1f%%",
430 100.*(hemi_total/PI - 1.));
431 error(WARNING, errmsg);
432 }
433 dom = PI / hemi_total; /* fix normalization */
434 for (o = dp->nout; o--; )
435 omega_oarr[o] *= dom;
436 nneg = 0; /* check outgoing totals */
437 for (i = 0; i < dp->ninc; i++) {
438 hemi_total = .0;
439 for (o = dp->nout; o--; ) {
440 double f = BSDF_value(dp,i,o);
441 if (f >= .0)
442 hemi_total += f*omega_oarr[o];
443 else {
444 nneg += (f < -FTINY);
445 BSDF_value(dp,i,o) = .0f;
446 }
447 }
448 if (hemi_total > 1.02) {
449 sprintf(errmsg,
450 "incoming BSDF direction %d passes %.1f%% of light",
451 i, 100.*hemi_total);
452 error(WARNING, errmsg);
453 }
454 }
455 if (nneg) {
456 sprintf(errmsg, "%d negative BSDF values (ignored)", nneg);
457 error(WARNING, errmsg);
458 }
459 /* reverse roles and check again */
460 for (o = 0; o < dp->nout; o++) {
461 hemi_total = .0;
462 for (i = dp->ninc; i--; )
463 hemi_total += BSDF_value(dp,i,o) * omega_iarr[i];
464
465 if (hemi_total > 1.02) {
466 sprintf(errmsg,
467 "outgoing BSDF direction %d collects %.1f%% of light",
468 o, 100.*hemi_total);
469 error(WARNING, errmsg);
470 }
471 }
472 free(omega_iarr); free(omega_oarr);
473 return(1);
474 }
475
476
477 struct BSDF_data *
478 load_BSDF( /* load BSDF data from file */
479 char *fname
480 )
481 {
482 char *path;
483 ezxml_t fl, wtl, wld, wdb;
484 struct BSDF_data *dp;
485
486 path = getpath(fname, getrlibpath(), R_OK);
487 if (path == NULL) {
488 sprintf(errmsg, "cannot find BSDF file \"%s\"", fname);
489 error(WARNING, errmsg);
490 return(NULL);
491 }
492 fl = ezxml_parse_file(path);
493 if (fl == NULL) {
494 sprintf(errmsg, "cannot open BSDF \"%s\"", path);
495 error(WARNING, errmsg);
496 return(NULL);
497 }
498 if (ezxml_error(fl)[0]) {
499 sprintf(errmsg, "BSDF \"%s\" %s", path, ezxml_error(fl));
500 error(WARNING, errmsg);
501 ezxml_free(fl);
502 return(NULL);
503 }
504 if (strcmp(ezxml_name(fl), "WindowElement")) {
505 sprintf(errmsg,
506 "BSDF \"%s\": top level node not 'WindowElement'",
507 path);
508 error(WARNING, errmsg);
509 ezxml_free(fl);
510 return(NULL);
511 }
512 wtl = ezxml_child(ezxml_child(fl, "Optical"), "Layer");
513 load_angle_basis(ezxml_child(ezxml_child(wtl,
514 "DataDefinition"), "AngleBasis"));
515 dp = (struct BSDF_data *)calloc(1, sizeof(struct BSDF_data));
516 load_geometry(dp, ezxml_child(wtl, "Material"));
517 for (wld = ezxml_child(wtl, "WavelengthData");
518 wld != NULL; wld = wld->next) {
519 if (strcmp(ezxml_txt(ezxml_child(wld,"Wavelength")), "Visible"))
520 continue;
521 wdb = ezxml_child(wld, "WavelengthDataBlock");
522 if (wdb == NULL) continue;
523 if (strcmp(ezxml_txt(ezxml_child(wdb,"WavelengthDataDirection")),
524 "Transmission Front"))
525 continue;
526 load_bsdf_data(dp, wdb); /* load front BTDF */
527 break; /* ignore the rest */
528 }
529 ezxml_free(fl); /* done with XML file */
530 if (!check_bsdf_data(dp)) {
531 sprintf(errmsg, "bad/missing BTDF data in \"%s\"", path);
532 error(WARNING, errmsg);
533 free_BSDF(dp);
534 dp = NULL;
535 }
536 return(dp);
537 }
538
539
540 void
541 free_BSDF( /* free BSDF data structure */
542 struct BSDF_data *b
543 )
544 {
545 if (b == NULL)
546 return;
547 if (b->mgf != NULL)
548 free(b->mgf);
549 if (b->bsdf != NULL)
550 free(b->bsdf);
551 free(b);
552 }
553
554
555 int
556 r_BSDF_incvec( /* compute random input vector at given location */
557 FVECT v,
558 struct BSDF_data *b,
559 int i,
560 double rv,
561 MAT4 xm
562 )
563 {
564 FVECT pert;
565 double rad;
566 int j;
567
568 if (!getBSDF_incvec(v, b, i))
569 return(0);
570 rad = sqrt(getBSDF_incohm(b, i) / PI);
571 multisamp(pert, 3, rv);
572 for (j = 0; j < 3; j++)
573 v[j] += rad*(2.*pert[j] - 1.);
574 if (xm != NULL)
575 multv3(v, v, xm);
576 return(normalize(v) != 0.0);
577 }
578
579
580 int
581 r_BSDF_outvec( /* compute random output vector at given location */
582 FVECT v,
583 struct BSDF_data *b,
584 int o,
585 double rv,
586 MAT4 xm
587 )
588 {
589 FVECT pert;
590 double rad;
591 int j;
592
593 if (!getBSDF_outvec(v, b, o))
594 return(0);
595 rad = sqrt(getBSDF_outohm(b, o) / PI);
596 multisamp(pert, 3, rv);
597 for (j = 0; j < 3; j++)
598 v[j] += rad*(2.*pert[j] - 1.);
599 if (xm != NULL)
600 multv3(v, v, xm);
601 return(normalize(v) != 0.0);
602 }
603
604
605 static int
606 addrot( /* compute rotation (x,y,z) => (xp,yp,zp) */
607 char *xfarg[],
608 FVECT xp,
609 FVECT yp,
610 FVECT zp
611 )
612 {
613 static char bufs[3][16];
614 int bn = 0;
615 char **xfp = xfarg;
616 double theta;
617
618 if (yp[2]*yp[2] + zp[2]*zp[2] < 2.*FTINY*FTINY) {
619 /* Special case for X' along Z-axis */
620 theta = -atan2(yp[0], yp[1]);
621 *xfp++ = "-ry";
622 *xfp++ = xp[2] < 0.0 ? "90" : "-90";
623 *xfp++ = "-rz";
624 sprintf(bufs[bn], "%f", theta*(180./PI));
625 *xfp++ = bufs[bn++];
626 return(xfp - xfarg);
627 }
628 theta = atan2(yp[2], zp[2]);
629 if (!FEQ(theta,0.0)) {
630 *xfp++ = "-rx";
631 sprintf(bufs[bn], "%f", theta*(180./PI));
632 *xfp++ = bufs[bn++];
633 }
634 theta = asin(-xp[2]);
635 if (!FEQ(theta,0.0)) {
636 *xfp++ = "-ry";
637 sprintf(bufs[bn], " %f", theta*(180./PI));
638 *xfp++ = bufs[bn++];
639 }
640 theta = atan2(xp[1], xp[0]);
641 if (!FEQ(theta,0.0)) {
642 *xfp++ = "-rz";
643 sprintf(bufs[bn], "%f", theta*(180./PI));
644 *xfp++ = bufs[bn++];
645 }
646 *xfp = NULL;
647 return(xfp - xfarg);
648 }
649
650
651 int
652 getBSDF_xfm( /* compute BSDF orient. -> world orient. transform */
653 MAT4 xm,
654 FVECT nrm,
655 UpDir ud,
656 char *xfbuf
657 )
658 {
659 char *xfargs[7];
660 XF myxf;
661 FVECT updir, xdest, ydest;
662 int i;
663
664 updir[0] = updir[1] = updir[2] = 0.;
665 switch (ud) {
666 case UDzneg:
667 updir[2] = -1.;
668 break;
669 case UDyneg:
670 updir[1] = -1.;
671 break;
672 case UDxneg:
673 updir[0] = -1.;
674 break;
675 case UDxpos:
676 updir[0] = 1.;
677 break;
678 case UDypos:
679 updir[1] = 1.;
680 break;
681 case UDzpos:
682 updir[2] = 1.;
683 break;
684 case UDunknown:
685 return(0);
686 }
687 fcross(xdest, updir, nrm);
688 if (normalize(xdest) == 0.0)
689 return(0);
690 fcross(ydest, nrm, xdest);
691 xf(&myxf, addrot(xfargs, xdest, ydest, nrm), xfargs);
692 copymat4(xm, myxf.xfm);
693 if (xfbuf == NULL)
694 return(1);
695 /* return xf arguments as well */
696 for (i = 0; xfargs[i] != NULL; i++) {
697 *xfbuf++ = ' ';
698 strcpy(xfbuf, xfargs[i]);
699 while (*xfbuf) ++xfbuf;
700 }
701 return(1);
702 }