ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf.c
Revision: 2.3
Committed: Sat Jul 3 02:50:22 2010 UTC (13 years, 10 months ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 2.2: +72 -6 lines
Log Message:
Added support for custom angle bases

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bsdf.c,v 2.2 2009/06/19 06:49:25 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 5 /* 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 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 == NULL || !*abname)
212 return;
213 for (i = nabases; i--; )
214 if (!strcmp(abname, abase_list[i].name))
215 return; /* XXX 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 void
243 load_bsdf_data( /* load BSDF distribution for this wavelength */
244 struct BSDF_data *dp,
245 ezxml_t wdb
246 )
247 {
248 char *cbasis = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
249 char *rbasis = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
250 char *sdata;
251 int i;
252
253 if ((cbasis == NULL || !*cbasis) | (rbasis == NULL || !*rbasis)) {
254 error(WARNING, "missing column/row basis for BSDF");
255 return;
256 }
257 /* XXX need to add routines for loading in foreign bases */
258 for (i = nabases; i--; )
259 if (!strcmp(cbasis, abase_list[i].name)) {
260 dp->ninc = abase_list[i].nangles;
261 dp->ib_priv = (void *)&abase_list[i];
262 dp->ib_vec = ab_getvecR;
263 dp->ib_ndx = ab_getndxR;
264 dp->ib_ohm = ab_getohm;
265 break;
266 }
267 if (i < 0) {
268 sprintf(errmsg, "unsupported ColumnAngleBasis '%s'", cbasis);
269 error(WARNING, errmsg);
270 return;
271 }
272 for (i = nabases; i--; )
273 if (!strcmp(rbasis, abase_list[i].name)) {
274 dp->nout = abase_list[i].nangles;
275 dp->ob_priv = (void *)&abase_list[i];
276 dp->ob_vec = ab_getvec;
277 dp->ob_ndx = ab_getndx;
278 dp->ob_ohm = ab_getohm;
279 break;
280 }
281 if (i < 0) {
282 sprintf(errmsg, "unsupported RowAngleBasis '%s'", cbasis);
283 error(WARNING, errmsg);
284 return;
285 }
286 /* read BSDF data */
287 sdata = ezxml_txt(ezxml_child(wdb,"ScatteringData"));
288 if (sdata == NULL || !*sdata) {
289 error(WARNING, "missing BSDF ScatteringData");
290 return;
291 }
292 dp->bsdf = (float *)malloc(sizeof(float)*dp->ninc*dp->nout);
293 if (dp->bsdf == NULL)
294 error(SYSTEM, "out of memory in load_bsdf_data");
295 for (i = 0; i < dp->ninc*dp->nout; i++) {
296 char *sdnext = fskip(sdata);
297 if (sdnext == NULL) {
298 error(WARNING, "bad/missing BSDF ScatteringData");
299 free(dp->bsdf); dp->bsdf = NULL;
300 return;
301 }
302 while (*sdnext && isspace(*sdnext))
303 sdnext++;
304 if (*sdnext == ',') sdnext++;
305 dp->bsdf[i] = atof(sdata);
306 sdata = sdnext;
307 }
308 while (isspace(*sdata))
309 sdata++;
310 if (*sdata) {
311 sprintf(errmsg, "%d extra characters after BSDF ScatteringData",
312 strlen(sdata));
313 error(WARNING, errmsg);
314 }
315 }
316
317
318 static int
319 check_bsdf_data( /* check that BSDF data is sane */
320 struct BSDF_data *dp
321 )
322 {
323 double *omega_iarr, *omega_oarr;
324 double dom, contrib, hemi_total;
325 int nneg;
326 FVECT v;
327 int i, o;
328
329 if (dp == NULL || dp->bsdf == NULL)
330 return(0);
331 omega_iarr = (double *)calloc(dp->ninc, sizeof(double));
332 omega_oarr = (double *)calloc(dp->nout, sizeof(double));
333 if ((omega_iarr == NULL) | (omega_oarr == NULL))
334 error(SYSTEM, "out of memory in check_bsdf_data");
335 /* incoming projected solid angles */
336 hemi_total = .0;
337 for (i = dp->ninc; i--; ) {
338 dom = getBSDF_incohm(dp,i);
339 if (dom <= .0) {
340 error(WARNING, "zero/negative incoming solid angle");
341 continue;
342 }
343 if (!getBSDF_incvec(v,dp,i) || v[2] > FTINY) {
344 error(WARNING, "illegal incoming BSDF direction");
345 free(omega_iarr); free(omega_oarr);
346 return(0);
347 }
348 hemi_total += omega_iarr[i] = dom * -v[2];
349 }
350 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
351 sprintf(errmsg, "incoming BSDF hemisphere off by %.1f%%",
352 100.*(hemi_total/PI - 1.));
353 error(WARNING, errmsg);
354 }
355 dom = PI / hemi_total; /* fix normalization */
356 for (i = dp->ninc; i--; )
357 omega_iarr[i] *= dom;
358 /* outgoing projected solid angles */
359 hemi_total = .0;
360 for (o = dp->nout; o--; ) {
361 dom = getBSDF_outohm(dp,o);
362 if (dom <= .0) {
363 error(WARNING, "zero/negative outgoing solid angle");
364 continue;
365 }
366 if (!getBSDF_outvec(v,dp,o) || v[2] < -FTINY) {
367 error(WARNING, "illegal outgoing BSDF direction");
368 free(omega_iarr); free(omega_oarr);
369 return(0);
370 }
371 hemi_total += omega_oarr[o] = dom * v[2];
372 }
373 if ((hemi_total > 1.02*PI) | (hemi_total < 0.98*PI)) {
374 sprintf(errmsg, "outgoing BSDF hemisphere off by %.1f%%",
375 100.*(hemi_total/PI - 1.));
376 error(WARNING, errmsg);
377 }
378 dom = PI / hemi_total; /* fix normalization */
379 for (o = dp->nout; o--; )
380 omega_oarr[o] *= dom;
381 nneg = 0; /* check outgoing totals */
382 for (i = 0; i < dp->ninc; i++) {
383 hemi_total = .0;
384 for (o = dp->nout; o--; ) {
385 double f = BSDF_value(dp,i,o);
386 if (f >= .0)
387 hemi_total += f*omega_oarr[o];
388 else {
389 nneg += (f < -FTINY);
390 BSDF_value(dp,i,o) = .0f;
391 }
392 }
393 if (hemi_total > 1.02) {
394 sprintf(errmsg,
395 "incoming BSDF direction %d passes %.1f%% of light",
396 i, 100.*hemi_total);
397 error(WARNING, errmsg);
398 }
399 }
400 if (nneg) {
401 sprintf(errmsg, "%d negative BSDF values (ignored)", nneg);
402 error(WARNING, errmsg);
403 }
404 /* reverse roles and check again */
405 for (o = 0; o < dp->nout; o++) {
406 hemi_total = .0;
407 for (i = dp->ninc; i--; )
408 hemi_total += BSDF_value(dp,i,o) * omega_iarr[i];
409
410 if (hemi_total > 1.02) {
411 sprintf(errmsg,
412 "outgoing BSDF direction %d collects %.1f%% of light",
413 o, 100.*hemi_total);
414 error(WARNING, errmsg);
415 }
416 }
417 free(omega_iarr); free(omega_oarr);
418 return(1);
419 }
420
421 struct BSDF_data *
422 load_BSDF( /* load BSDF data from file */
423 char *fname
424 )
425 {
426 char *path;
427 ezxml_t fl, wtl, wld, wdb;
428 struct BSDF_data *dp;
429
430 path = getpath(fname, getrlibpath(), R_OK);
431 if (path == NULL) {
432 sprintf(errmsg, "cannot find BSDF file \"%s\"", fname);
433 error(WARNING, errmsg);
434 return(NULL);
435 }
436 fl = ezxml_parse_file(path);
437 if (fl == NULL) {
438 sprintf(errmsg, "cannot open BSDF \"%s\"", path);
439 error(WARNING, errmsg);
440 return(NULL);
441 }
442 if (ezxml_error(fl)[0]) {
443 sprintf(errmsg, "BSDF \"%s\" %s", path, ezxml_error(fl));
444 error(WARNING, errmsg);
445 ezxml_free(fl);
446 return(NULL);
447 }
448 if (strcmp(ezxml_name(fl), "WindowElement")) {
449 sprintf(errmsg,
450 "BSDF \"%s\": top level node not 'WindowElement'",
451 path);
452 error(WARNING, errmsg);
453 ezxml_free(fl);
454 return(NULL);
455 }
456 wtl = ezxml_child(ezxml_child(fl, "Optical"), "Layer");
457 load_angle_basis(ezxml_child(ezxml_child(wtl,
458 "DataDefinition"), "AngleBasis"));
459 dp = (struct BSDF_data *)calloc(1, sizeof(struct BSDF_data));
460 for (wld = ezxml_child(wtl, "WavelengthData");
461 wld != NULL; wld = wld->next) {
462 if (strcmp(ezxml_txt(ezxml_child(wld,"Wavelength")), "Visible"))
463 continue;
464 wdb = ezxml_child(wld, "WavelengthDataBlock");
465 if (wdb == NULL) continue;
466 if (strcmp(ezxml_txt(ezxml_child(wdb,"WavelengthDataDirection")),
467 "Transmission Front"))
468 continue;
469 load_bsdf_data(dp, wdb); /* load front BTDF */
470 break; /* ignore the rest */
471 }
472 ezxml_free(fl); /* done with XML file */
473 if (!check_bsdf_data(dp)) {
474 sprintf(errmsg, "bad/missing BTDF data in \"%s\"", path);
475 error(WARNING, errmsg);
476 free_BSDF(dp);
477 dp = NULL;
478 }
479 return(dp);
480 }
481
482
483 void
484 free_BSDF( /* free BSDF data structure */
485 struct BSDF_data *b
486 )
487 {
488 if (b == NULL)
489 return;
490 if (b->bsdf != NULL)
491 free(b->bsdf);
492 free(b);
493 }
494
495
496 int
497 r_BSDF_incvec( /* compute random input vector at given location */
498 FVECT v,
499 struct BSDF_data *b,
500 int i,
501 double rv,
502 MAT4 xm
503 )
504 {
505 FVECT pert;
506 double rad;
507 int j;
508
509 if (!getBSDF_incvec(v, b, i))
510 return(0);
511 rad = sqrt(getBSDF_incohm(b, i) / PI);
512 multisamp(pert, 3, rv);
513 for (j = 0; j < 3; j++)
514 v[j] += rad*(2.*pert[j] - 1.);
515 if (xm != NULL)
516 multv3(v, v, xm);
517 return(normalize(v) != 0.0);
518 }
519
520
521 int
522 r_BSDF_outvec( /* compute random output vector at given location */
523 FVECT v,
524 struct BSDF_data *b,
525 int o,
526 double rv,
527 MAT4 xm
528 )
529 {
530 FVECT pert;
531 double rad;
532 int j;
533
534 if (!getBSDF_outvec(v, b, o))
535 return(0);
536 rad = sqrt(getBSDF_outohm(b, o) / PI);
537 multisamp(pert, 3, rv);
538 for (j = 0; j < 3; j++)
539 v[j] += rad*(2.*pert[j] - 1.);
540 if (xm != NULL)
541 multv3(v, v, xm);
542 return(normalize(v) != 0.0);
543 }
544
545
546 static int
547 addrot( /* compute rotation (x,y,z) => (xp,yp,zp) */
548 char *xfarg[],
549 FVECT xp,
550 FVECT yp,
551 FVECT zp
552 )
553 {
554 static char bufs[3][16];
555 int bn = 0;
556 char **xfp = xfarg;
557 double theta;
558
559 if (yp[2]*yp[2] + zp[2]*zp[2] < 2.*FTINY*FTINY) {
560 /* Special case for X' along Z-axis */
561 theta = -atan2(yp[0], yp[1]);
562 *xfp++ = "-ry";
563 *xfp++ = xp[2] < 0.0 ? "90" : "-90";
564 *xfp++ = "-rz";
565 sprintf(bufs[bn], "%f", theta*(180./PI));
566 *xfp++ = bufs[bn++];
567 return(xfp - xfarg);
568 }
569 theta = atan2(yp[2], zp[2]);
570 if (!FEQ(theta,0.0)) {
571 *xfp++ = "-rx";
572 sprintf(bufs[bn], "%f", theta*(180./PI));
573 *xfp++ = bufs[bn++];
574 }
575 theta = asin(-xp[2]);
576 if (!FEQ(theta,0.0)) {
577 *xfp++ = "-ry";
578 sprintf(bufs[bn], " %f", theta*(180./PI));
579 *xfp++ = bufs[bn++];
580 }
581 theta = atan2(xp[1], xp[0]);
582 if (!FEQ(theta,0.0)) {
583 *xfp++ = "-rz";
584 sprintf(bufs[bn], "%f", theta*(180./PI));
585 *xfp++ = bufs[bn++];
586 }
587 *xfp = NULL;
588 return(xfp - xfarg);
589 }
590
591
592 int
593 getBSDF_xfm( /* compute BSDF orient. -> world orient. transform */
594 MAT4 xm,
595 FVECT nrm,
596 UpDir ud
597 )
598 {
599 char *xfargs[7];
600 XF myxf;
601 FVECT updir, xdest, ydest;
602
603 updir[0] = updir[1] = updir[2] = 0.;
604 switch (ud) {
605 case UDzneg:
606 updir[2] = -1.;
607 break;
608 case UDyneg:
609 updir[1] = -1.;
610 break;
611 case UDxneg:
612 updir[0] = -1.;
613 break;
614 case UDxpos:
615 updir[0] = 1.;
616 break;
617 case UDypos:
618 updir[1] = 1.;
619 break;
620 case UDzpos:
621 updir[2] = 1.;
622 break;
623 case UDunknown:
624 return(0);
625 }
626 fcross(xdest, updir, nrm);
627 if (normalize(xdest) == 0.0)
628 return(0);
629 fcross(ydest, nrm, xdest);
630 xf(&myxf, addrot(xfargs, xdest, ydest, nrm), xfargs);
631 copymat4(xm, myxf.xfm);
632 return(1);
633 }