ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf_m.c
Revision: 3.46
Committed: Thu Jul 14 02:52:02 2022 UTC (21 months, 2 weeks ago) by greg
Content type: text/plain
Branch: MAIN
CVS Tags: rad5R4, HEAD
Changes since 3.45: +7 -7 lines
Log Message:
fix: updated and corrected Klems half and quarter bases to match WINDOW

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: bsdf_m.c,v 3.45 2022/01/25 01:34:20 greg Exp $";
3 #endif
4 /*
5 * bsdf_m.c
6 *
7 * Definitions supporting BSDF matrices
8 *
9 * Created by Greg Ward on 2/2/11.
10 * Copyright 2011 Anyhere Software. All rights reserved.
11 *
12 */
13
14 #define _USE_MATH_DEFINES
15 #include "rtio.h"
16 #include <math.h>
17 #include <ctype.h>
18 #include "ezxml.h"
19 #include "bsdf.h"
20 #include "bsdf_m.h"
21
22 /* Function return codes */
23 #define RC_GOOD 1
24 #define RC_FAIL 0
25 #define RC_FORMERR (-1)
26 #define RC_DATERR (-2)
27 #define RC_UNSUPP (-3)
28 #define RC_INTERR (-4)
29 #define RC_MEMERR (-5)
30
31 ANGLE_BASIS abase_list[MAXABASES] = {
32 {
33 "LBNL/Klems Full", 145,
34 { {0., 1},
35 {5., 8},
36 {15., 16},
37 {25., 20},
38 {35., 24},
39 {45., 24},
40 {55., 24},
41 {65., 16},
42 {75., 12},
43 {90., 0} }
44 }, {
45 "LBNL/Klems Half", 77,
46 { {0., 1},
47 {6.5, 8},
48 {19.5, 12},
49 {32.5, 16},
50 {45.5, 20},
51 {58.5, 12},
52 {71.5, 8},
53 {90., 0} }
54 }, {
55 "LBNL/Klems Quarter", 41,
56 { {0., 1},
57 {9., 8},
58 {27., 12},
59 {45., 12},
60 {63., 8},
61 {90., 0} }
62 }
63 };
64
65 int nabases = 3; /* current number of defined bases */
66
67 C_COLOR mtx_RGB_prim[3]; /* our RGB primaries */
68 float mtx_RGB_coef[3]; /* corresponding Y coefficients */
69
70 enum {mtx_Y, mtx_X, mtx_Z}; /* matrix components (mtx_Y==0) */
71
72 /* check if two real values are near enough to equal */
73 static int
74 fequal(double a, double b)
75 {
76 if (b != 0)
77 a = a/b - 1.;
78 return (a <= 1e-6) & (a >= -1e-6);
79 }
80
81 /* convert error to standard BSDF code */
82 static SDError
83 convert_errcode(int ec)
84 {
85 switch (ec) {
86 case RC_GOOD:
87 return SDEnone;
88 case RC_FORMERR:
89 return SDEformat;
90 case RC_DATERR:
91 return SDEdata;
92 case RC_UNSUPP:
93 return SDEsupport;
94 case RC_INTERR:
95 return SDEinternal;
96 case RC_MEMERR:
97 return SDEmemory;
98 }
99 return SDEunknown;
100 }
101
102 /* allocate a BSDF matrix of the given size */
103 static SDMat *
104 SDnewMatrix(int ni, int no)
105 {
106 SDMat *sm;
107
108 if ((ni <= 0) | (no <= 0)) {
109 strcpy(SDerrorDetail, "Empty BSDF matrix request");
110 return NULL;
111 }
112 sm = (SDMat *)malloc(sizeof(SDMat) + (ni*no - 1)*sizeof(float));
113 if (sm == NULL) {
114 sprintf(SDerrorDetail, "Cannot allocate %dx%d BSDF matrix",
115 ni, no);
116 return NULL;
117 }
118 memset(sm, 0, sizeof(SDMat)-sizeof(float));
119 sm->ninc = ni;
120 sm->nout = no;
121
122 return sm;
123 }
124
125 /* Free a BSDF matrix */
126 void
127 SDfreeMatrix(void *ptr)
128 {
129 SDMat *mp = (SDMat *)ptr;
130
131 if (mp->chroma != NULL) free(mp->chroma);
132 free(ptr);
133 }
134
135 /* compute square of real value */
136 static double sq(double x) { return x*x; }
137
138 /* Get vector for this angle basis index (front exiting) */
139 int
140 fo_getvec(FVECT v, double ndxr, void *p)
141 {
142 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
143 int ndx = (int)ndxr;
144 double randX = ndxr - ndx;
145 double rx[2];
146 int li;
147 double azi, d;
148
149 if ((ndxr < 0) | (ndx >= ab->nangles))
150 return RC_FAIL;
151 for (li = 0; ndx >= ab->lat[li].nphis; li++)
152 ndx -= ab->lat[li].nphis;
153 SDmultiSamp(rx, 2, randX);
154 d = (1. - rx[0])*sq(cos(M_PI/180.*ab->lat[li].tmin)) +
155 rx[0]*sq(cos(M_PI/180.*ab->lat[li+1].tmin));
156 v[2] = d = sqrt(d); /* cos(pol) */
157 azi = 2.*M_PI*(ndx + rx[1] - .5)/ab->lat[li].nphis;
158 d = sqrt(1. - d*d); /* sin(pol) */
159 v[0] = cos(azi)*d;
160 v[1] = sin(azi)*d;
161 return RC_GOOD;
162 }
163
164 /* Get index corresponding to the given vector (front exiting) */
165 int
166 fo_getndx(const FVECT v, void *p)
167 {
168 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
169 int li, ndx;
170 double pol, azi;
171
172 if (v == NULL)
173 return -1;
174 if ((v[2] < 0) | (v[2] > 1.00001))
175 return -1;
176 pol = 180.0/M_PI*Acos(v[2]);
177 azi = 180.0/M_PI*atan2(v[1], v[0]);
178 if (azi < 0.0) azi += 360.0;
179 for (li = 1; ab->lat[li].tmin <= pol; li++)
180 if (!ab->lat[li].nphis)
181 return -1;
182 --li;
183 ndx = (int)((1./360.)*azi*ab->lat[li].nphis + 0.5);
184 if (ndx >= ab->lat[li].nphis) ndx = 0;
185 while (li--)
186 ndx += ab->lat[li].nphis;
187 return ndx;
188 }
189
190 /* Get projected solid angle for this angle basis index (universal) */
191 double
192 io_getohm(int ndx, void *p)
193 {
194 static void *last_p = NULL;
195 static int last_li = -1;
196 static double last_ohm;
197 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
198 int li;
199 double theta, theta1;
200
201 if ((ndx < 0) | (ndx >= ab->nangles))
202 return -1.;
203 for (li = 0; ndx >= ab->lat[li].nphis; li++)
204 ndx -= ab->lat[li].nphis;
205 if ((p == last_p) & (li == last_li)) /* cached latitude? */
206 return last_ohm;
207 last_p = p;
208 last_li = li;
209 theta = M_PI/180. * ab->lat[li].tmin;
210 theta1 = M_PI/180. * ab->lat[li+1].tmin;
211 return last_ohm = M_PI*(sq(cos(theta)) - sq(cos(theta1))) /
212 (double)ab->lat[li].nphis;
213 }
214
215 /* Get vector for this angle basis index (back incident) */
216 int
217 bi_getvec(FVECT v, double ndxr, void *p)
218 {
219 if (!fo_getvec(v, ndxr, p))
220 return RC_FAIL;
221
222 v[0] = -v[0];
223 v[1] = -v[1];
224 v[2] = -v[2];
225
226 return RC_GOOD;
227 }
228
229 /* Get index corresponding to the vector (back incident) */
230 int
231 bi_getndx(const FVECT v, void *p)
232 {
233 FVECT v2;
234
235 v2[0] = -v[0];
236 v2[1] = -v[1];
237 v2[2] = -v[2];
238
239 return fo_getndx(v2, p);
240 }
241
242 /* Get vector for this angle basis index (back exiting) */
243 int
244 bo_getvec(FVECT v, double ndxr, void *p)
245 {
246 if (!fo_getvec(v, ndxr, p))
247 return RC_FAIL;
248
249 v[2] = -v[2];
250
251 return RC_GOOD;
252 }
253
254 /* Get index corresponding to the vector (back exiting) */
255 int
256 bo_getndx(const FVECT v, void *p)
257 {
258 FVECT v2;
259
260 v2[0] = v[0];
261 v2[1] = v[1];
262 v2[2] = -v[2];
263
264 return fo_getndx(v2, p);
265 }
266
267 /* Get vector for this angle basis index (front incident) */
268 int
269 fi_getvec(FVECT v, double ndxr, void *p)
270 {
271 if (!fo_getvec(v, ndxr, p))
272 return RC_FAIL;
273
274 v[0] = -v[0];
275 v[1] = -v[1];
276
277 return RC_GOOD;
278 }
279
280 /* Get index corresponding to the vector (front incident) */
281 int
282 fi_getndx(const FVECT v, void *p)
283 {
284 FVECT v2;
285
286 v2[0] = -v[0];
287 v2[1] = -v[1];
288 v2[2] = v[2];
289
290 return fo_getndx(v2, p);
291 }
292
293 /* Get color or grayscale value for BSDF for the given direction pair */
294 int
295 mBSDF_color(float coef[], const SDMat *dp, int i, int o)
296 {
297 C_COLOR cxy;
298 double d;
299
300 coef[0] = mBSDF_value(dp, o, i);
301 /* position-specific perturbation */
302 d = 2*dp->ninc/(i + .22545) + 4*dp->nout/(o + .70281);
303 d -= (int)d;
304 coef[0] *= 1. + 6e-4*(d - .5);
305 if (dp->chroma == NULL)
306 return 1; /* grayscale */
307
308 c_decodeChroma(&cxy, mBSDF_chroma(dp,o,i));
309 c_toSharpRGB(&cxy, coef[0], coef);
310 coef[0] *= mtx_RGB_coef[0];
311 coef[1] *= mtx_RGB_coef[1];
312 coef[2] *= mtx_RGB_coef[2];
313 return 3; /* RGB color */
314 }
315
316 /* load custom BSDF angle basis */
317 static int
318 load_angle_basis(ezxml_t wab)
319 {
320 char *abname = ezxml_txt(ezxml_child(wab, "AngleBasisName"));
321 ezxml_t wbb;
322 int i;
323
324 if (!abname || !*abname)
325 return RC_FAIL;
326 for (i = nabases; i--; )
327 if (!strcasecmp(abname, abase_list[i].name))
328 return RC_GOOD; /* assume it's the same */
329 if (nabases >= MAXABASES) {
330 sprintf(SDerrorDetail, "Out of angle bases reading '%s'",
331 abname);
332 return RC_INTERR;
333 }
334 strcpy(abase_list[nabases].name, abname);
335 abase_list[nabases].nangles = 0;
336 for (i = 0, wbb = ezxml_child(wab, "AngleBasisBlock");
337 wbb != NULL; i++, wbb = wbb->next) {
338 if (i >= MAXLATS) {
339 sprintf(SDerrorDetail, "Too many latitudes for '%s'",
340 abname);
341 return RC_INTERR;
342 }
343 abase_list[nabases].lat[i+1].tmin = atof(ezxml_txt(
344 ezxml_child(ezxml_child(wbb,
345 "ThetaBounds"), "UpperTheta")));
346 if (!i)
347 abase_list[nabases].lat[0].tmin = 0;
348 else if (!fequal(atof(ezxml_txt(ezxml_child(ezxml_child(wbb,
349 "ThetaBounds"), "LowerTheta"))),
350 abase_list[nabases].lat[i].tmin)) {
351 sprintf(SDerrorDetail, "Theta values disagree in '%s'",
352 abname);
353 return RC_DATERR;
354 }
355 abase_list[nabases].nangles +=
356 abase_list[nabases].lat[i].nphis =
357 atoi(ezxml_txt(ezxml_child(wbb, "nPhis")));
358 if (abase_list[nabases].lat[i].nphis <= 0 ||
359 (abase_list[nabases].lat[i].nphis == 1 &&
360 abase_list[nabases].lat[i].tmin > FTINY)) {
361 sprintf(SDerrorDetail, "Illegal phi count in '%s'",
362 abname);
363 return RC_DATERR;
364 }
365 }
366 abase_list[nabases++].lat[i].nphis = 0;
367 return RC_GOOD;
368 }
369
370 /* compute min. proj. solid angle and max. direct hemispherical scattering */
371 static int
372 get_extrema(SDSpectralDF *df)
373 {
374 SDMat *dp = (SDMat *)df->comp[0].dist;
375 double *ohma;
376 int i, o;
377 /* initialize extrema */
378 df->minProjSA = M_PI;
379 df->maxHemi = .0;
380 ohma = (double *)malloc(dp->nout*sizeof(double));
381 if (ohma == NULL)
382 return RC_MEMERR;
383 /* get outgoing solid angles */
384 for (o = dp->nout; o--; )
385 if ((ohma[o] = mBSDF_outohm(dp,o)) < df->minProjSA)
386 df->minProjSA = ohma[o];
387 /* compute hemispherical sums */
388 for (i = dp->ninc; i--; ) {
389 double hemi = .0;
390 for (o = dp->nout; o--; )
391 hemi += ohma[o] * mBSDF_value(dp, o, i);
392 if (hemi > df->maxHemi)
393 df->maxHemi = hemi;
394 }
395 free(ohma);
396 /* need incoming solid angles, too? */
397 if ((dp->ib_ohm != dp->ob_ohm) | (dp->ib_priv != dp->ob_priv)) {
398 double ohm;
399 for (i = dp->ninc; i--; )
400 if ((ohm = mBSDF_incohm(dp,i)) < df->minProjSA)
401 df->minProjSA = ohm;
402 }
403 return (df->maxHemi <= 1.01);
404 }
405
406 /* load BSDF distribution for this wavelength */
407 static int
408 load_bsdf_data(SDData *sd, ezxml_t wdb, int ct, int rowinc)
409 {
410 SDSpectralDF *df;
411 SDMat *dp;
412 char *sdata;
413 int inbi, outbi;
414 int i;
415 /* allocate BSDF component */
416 sdata = ezxml_txt(ezxml_child(wdb, "WavelengthDataDirection"));
417 if (!sdata)
418 return RC_FAIL;
419 /*
420 * Remember that front and back are reversed from WINDOW 6 orientations
421 */
422 if (!strcasecmp(sdata, "Transmission Front")) {
423 if (sd->tb == NULL && (sd->tb = SDnewSpectralDF(3)) == NULL)
424 return RC_MEMERR;
425 df = sd->tb;
426 } else if (!strcasecmp(sdata, "Transmission Back")) {
427 if (sd->tf == NULL && (sd->tf = SDnewSpectralDF(3)) == NULL)
428 return RC_MEMERR;
429 df = sd->tf;
430 } else if (!strcasecmp(sdata, "Reflection Front")) {
431 if (sd->rb == NULL && (sd->rb = SDnewSpectralDF(3)) == NULL)
432 return RC_MEMERR;
433 df = sd->rb;
434 } else if (!strcasecmp(sdata, "Reflection Back")) {
435 if (sd->rf == NULL && (sd->rf = SDnewSpectralDF(3)) == NULL)
436 return RC_MEMERR;
437 df = sd->rf;
438 } else
439 return RC_FAIL;
440 /* free previous matrix if any */
441 if (df->comp[ct].dist != NULL) {
442 SDfreeMatrix(df->comp[ct].dist);
443 df->comp[ct].dist = NULL;
444 }
445 /* get angle bases */
446 sdata = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
447 if (!sdata || !*sdata) {
448 sprintf(SDerrorDetail, "Missing column basis for BSDF '%s'",
449 sd->name);
450 return RC_FORMERR;
451 }
452 for (inbi = nabases; inbi--; )
453 if (!strcasecmp(sdata, abase_list[inbi].name))
454 break;
455 if (inbi < 0) {
456 sprintf(SDerrorDetail, "Undefined ColumnAngleBasis '%s'", sdata);
457 return RC_FORMERR;
458 }
459 sdata = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
460 if (!sdata || !*sdata) {
461 sprintf(SDerrorDetail, "Missing row basis for BSDF '%s'",
462 sd->name);
463 return RC_FORMERR;
464 }
465 for (outbi = nabases; outbi--; )
466 if (!strcasecmp(sdata, abase_list[outbi].name))
467 break;
468 if (outbi < 0) {
469 sprintf(SDerrorDetail, "Undefined RowAngleBasis '%s'", sdata);
470 return RC_FORMERR;
471 }
472 /* allocate BSDF matrix */
473 dp = SDnewMatrix(abase_list[inbi].nangles, abase_list[outbi].nangles);
474 if (dp == NULL)
475 return RC_MEMERR;
476 dp->ib_priv = &abase_list[inbi];
477 dp->ob_priv = &abase_list[outbi];
478 if (df == sd->tf) {
479 dp->ib_vec = &fi_getvec;
480 dp->ib_ndx = &fi_getndx;
481 dp->ob_vec = &bo_getvec;
482 dp->ob_ndx = &bo_getndx;
483 } else if (df == sd->tb) {
484 dp->ib_vec = &bi_getvec;
485 dp->ib_ndx = &bi_getndx;
486 dp->ob_vec = &fo_getvec;
487 dp->ob_ndx = &fo_getndx;
488 } else if (df == sd->rf) {
489 dp->ib_vec = &fi_getvec;
490 dp->ib_ndx = &fi_getndx;
491 dp->ob_vec = &fo_getvec;
492 dp->ob_ndx = &fo_getndx;
493 } else /* df == sd->rb */ {
494 dp->ib_vec = &bi_getvec;
495 dp->ib_ndx = &bi_getndx;
496 dp->ob_vec = &bo_getvec;
497 dp->ob_ndx = &bo_getndx;
498 }
499 dp->ib_ohm = &io_getohm;
500 dp->ob_ohm = &io_getohm;
501 df->comp[ct].dist = dp;
502 df->comp[ct].func = &SDhandleMtx;
503 /* read BSDF data */
504 sdata = ezxml_txt(ezxml_child(wdb, "ScatteringData"));
505 if (!sdata || !*sdata) {
506 sprintf(SDerrorDetail, "Missing BSDF ScatteringData in '%s'",
507 sd->name);
508 return RC_FORMERR;
509 }
510 for (i = 0; i < dp->ninc*dp->nout; i++) {
511 char *sdnext = fskip(sdata);
512 double val;
513 if (sdnext == NULL) {
514 sprintf(SDerrorDetail,
515 "Bad/missing BSDF ScatteringData in '%s'",
516 sd->name);
517 return RC_FORMERR;
518 }
519 while (isspace(*sdnext))
520 sdnext++;
521 if (*sdnext == ',') sdnext++;
522 if ((val = atof(sdata)) < 0)
523 val = 0; /* don't allow negative values */
524 if (rowinc) {
525 int r = i/dp->nout;
526 int c = i - r*dp->nout;
527 mBSDF_value(dp,c,r) = val;
528 } else
529 dp->bsdf[i] = val;
530 sdata = sdnext;
531 }
532 return (ct == mtx_Y) ? get_extrema(df) : RC_GOOD;
533 }
534
535 /* copy our RGB (x,y) primary chromaticities */
536 static void
537 copy_RGB_prims(C_COLOR cspec[])
538 {
539 if (mtx_RGB_coef[1] < .001) { /* need to initialize */
540 int i = 3;
541 while (i--) {
542 float rgb[3];
543 rgb[0] = rgb[1] = rgb[2] = .0f;
544 rgb[i] = 1.f;
545 mtx_RGB_coef[i] = c_fromSharpRGB(rgb, &mtx_RGB_prim[i]);
546 }
547 }
548 memcpy(cspec, mtx_RGB_prim, sizeof(mtx_RGB_prim));
549 }
550
551 /* encode chromaticity if XYZ -- reduce to one channel in any case */
552 static SDSpectralDF *
553 encode_chroma(SDSpectralDF *df)
554 {
555 SDMat *mpx, *mpy, *mpz;
556 int n;
557
558 if (df == NULL || df->ncomp != 3)
559 return df;
560
561 mpy = (SDMat *)df->comp[mtx_Y].dist;
562 if (mpy == NULL) {
563 free(df);
564 return NULL;
565 }
566 mpx = (SDMat *)df->comp[mtx_X].dist;
567 mpz = (SDMat *)df->comp[mtx_Z].dist;
568 if (mpx == NULL || (mpx->ninc != mpy->ninc) | (mpx->nout != mpy->nout))
569 goto done;
570 if (mpz == NULL || (mpz->ninc != mpy->ninc) | (mpz->nout != mpy->nout))
571 goto done;
572 mpy->chroma = (C_CHROMA *)malloc(sizeof(C_CHROMA)*mpy->ninc*mpy->nout);
573 if (mpy->chroma == NULL)
574 goto done; /* XXX punt */
575 /* encode chroma values */
576 for (n = mpy->ninc*mpy->nout; n--; ) {
577 const double sum = mpx->bsdf[n] + mpy->bsdf[n] + mpz->bsdf[n];
578 C_COLOR cxy;
579 if (sum > .0)
580 c_cset(&cxy, mpx->bsdf[n]/sum, mpy->bsdf[n]/sum);
581 else
582 c_cset(&cxy, 1./3., 1./3.);
583 mpy->chroma[n] = c_encodeChroma(&cxy);
584 }
585 done: /* free X & Z channels */
586 if (mpx != NULL) SDfreeMatrix(mpx);
587 if (mpz != NULL) SDfreeMatrix(mpz);
588 if (mpy->chroma == NULL) /* grayscale after all? */
589 df->comp[0].cspec[0] = c_dfcolor;
590 else /* else copy RGB primaries */
591 copy_RGB_prims(df->comp[0].cspec);
592 df->ncomp = 1; /* return resized struct */
593 return (SDSpectralDF *)realloc(df, sizeof(SDSpectralDF));
594 }
595
596 /* subtract minimum (diffuse) scattering amount from BSDF */
597 static double
598 subtract_min(C_COLOR *cs, SDMat *sm)
599 {
600 const int ncomp = 1 + 2*(sm->chroma != NULL);
601 float min_coef[3], ymin, coef[3];
602 int i, o, c;
603
604 min_coef[0] = min_coef[1] = min_coef[2] = FHUGE;
605 for (i = 0; i < sm->ninc; i++)
606 for (o = 0; o < sm->nout; o++) {
607 c = mBSDF_color(coef, sm, i, o);
608 while (c--)
609 if (coef[c] < min_coef[c])
610 min_coef[c] = coef[c];
611 }
612 ymin = 0;
613 for (c = ncomp; c--; )
614 ymin += min_coef[c];
615 if (ymin <= .01/M_PI) /* not worth bothering about? */
616 return .0;
617 if (ncomp == 1) { /* subtract grayscale minimum */
618 for (i = sm->ninc*sm->nout; i--; )
619 sm->bsdf[i] -= ymin;
620 *cs = c_dfcolor;
621 return M_PI*ymin;
622 }
623 /* else subtract colored minimum */
624 for (i = 0; i < sm->ninc; i++)
625 for (o = 0; o < sm->nout; o++) {
626 C_COLOR cxy;
627 c = mBSDF_color(coef, sm, i, o);
628 while (c--)
629 coef[c] = (coef[c] - min_coef[c]) /
630 mtx_RGB_coef[c];
631 if (c_fromSharpRGB(coef, &cxy) > 1e-5)
632 mBSDF_chroma(sm,o,i) = c_encodeChroma(&cxy);
633 mBSDF_value(sm,o,i) -= ymin;
634 }
635 /* return colored minimum */
636 for (i = 3; i--; )
637 coef[i] = min_coef[i]/mtx_RGB_coef[i];
638 c_fromSharpRGB(coef, cs);
639
640 return M_PI*ymin;
641 }
642
643 /* Extract and separate diffuse portion of BSDF & convert color */
644 static SDSpectralDF *
645 extract_diffuse(SDValue *dv, SDSpectralDF *df)
646 {
647
648 df = encode_chroma(df); /* reduce XYZ to Y + chroma */
649 if (df == NULL || df->ncomp <= 0) {
650 dv->spec = c_dfcolor;
651 dv->cieY = .0;
652 return df;
653 }
654 /* subtract minimum value */
655 dv->cieY = subtract_min(&dv->spec, (SDMat *)df->comp[0].dist);
656 df->maxHemi -= dv->cieY; /* adjust maximum hemispherical */
657
658 c_ccvt(&dv->spec, C_CSXY); /* make sure (x,y) is set */
659 return df;
660 }
661
662 /* Load a BSDF matrix from an open XML file */
663 SDError
664 SDloadMtx(SDData *sd, ezxml_t wtl)
665 {
666 ezxml_t wld, wdb;
667 int rowIn;
668 char *txt;
669 int rval;
670 /* basic checks and data ordering */
671 txt = ezxml_txt(ezxml_child(ezxml_child(wtl,
672 "DataDefinition"), "IncidentDataStructure"));
673 if (txt == NULL || !*txt) {
674 sprintf(SDerrorDetail,
675 "BSDF \"%s\": missing IncidentDataStructure",
676 sd->name);
677 return SDEformat;
678 }
679 if (!strcasecmp(txt, "Rows"))
680 rowIn = 1;
681 else if (!strcasecmp(txt, "Columns"))
682 rowIn = 0;
683 else {
684 sprintf(SDerrorDetail,
685 "BSDF \"%s\": unsupported IncidentDataStructure",
686 sd->name);
687 return SDEsupport;
688 }
689 /* get angle bases */
690 for (wld = ezxml_child(ezxml_child(wtl, "DataDefinition"), "AngleBasis");
691 wld != NULL; wld = wld->next) {
692 rval = load_angle_basis(wld);
693 if (rval < 0)
694 return convert_errcode(rval);
695 }
696 /* load BSDF components */
697 for (wld = ezxml_child(wtl, "WavelengthData");
698 wld != NULL; wld = wld->next) {
699 const char *cnm = ezxml_txt(ezxml_child(wld,"Wavelength"));
700 int ct = -1;
701 if (!strcasecmp(cnm, "Visible"))
702 ct = mtx_Y;
703 else if (!strcasecmp(cnm, "CIE-X"))
704 ct = mtx_X;
705 else if (!strcasecmp(cnm, "CIE-Z"))
706 ct = mtx_Z;
707 else
708 continue;
709 for (wdb = ezxml_child(wld, "WavelengthDataBlock");
710 wdb != NULL; wdb = wdb->next)
711 if ((rval = load_bsdf_data(sd, wdb, ct, rowIn)) < 0)
712 return convert_errcode(rval);
713 }
714 /* separate diffuse components */
715 sd->rf = extract_diffuse(&sd->rLambFront, sd->rf);
716 sd->rb = extract_diffuse(&sd->rLambBack, sd->rb);
717 sd->tf = extract_diffuse(&sd->tLambFront, sd->tf);
718 if (sd->tb != NULL) {
719 sd->tb = extract_diffuse(&sd->tLambBack, sd->tb);
720 if (sd->tf == NULL)
721 sd->tLambFront = sd->tLambBack;
722 } else if (sd->tf != NULL)
723 sd->tLambBack = sd->tLambFront;
724 /* return success */
725 return SDEnone;
726 }
727
728 /* Get Matrix BSDF value */
729 static int
730 SDgetMtxBSDF(float coef[SDmaxCh], const FVECT inVec,
731 const FVECT outVec, SDComponent *sdc)
732 {
733 const SDMat *dp;
734 int i_ndx, o_ndx;
735 /* check arguments */
736 if ((coef == NULL) | (outVec == NULL) | (inVec == NULL) | (sdc == NULL)
737 || (dp = (SDMat *)sdc->dist) == NULL)
738 return 0;
739 /* get angle indices */
740 i_ndx = mBSDF_incndx(dp, inVec);
741 o_ndx = mBSDF_outndx(dp, outVec);
742 /* try reciprocity if necessary */
743 if ((i_ndx < 0) & (o_ndx < 0)) {
744 i_ndx = mBSDF_incndx(dp, outVec);
745 o_ndx = mBSDF_outndx(dp, inVec);
746 }
747 if ((i_ndx < 0) | (o_ndx < 0))
748 return 0; /* nothing from this component */
749
750 return mBSDF_color(coef, dp, i_ndx, o_ndx);
751 }
752
753 /* Query solid angle for vector(s) */
754 static SDError
755 SDqueryMtxProjSA(double *psa, const FVECT v1, const RREAL *v2,
756 int qflags, SDComponent *sdc)
757 {
758 const SDMat *dp;
759 double inc_psa, out_psa;
760 /* check arguments */
761 if ((psa == NULL) | (v1 == NULL) | (sdc == NULL) ||
762 (dp = (SDMat *)sdc->dist) == NULL)
763 return SDEargument;
764 if (v2 == NULL)
765 v2 = v1;
766 /* get projected solid angles */
767 out_psa = mBSDF_outohm(dp, mBSDF_outndx(dp, v1));
768 inc_psa = mBSDF_incohm(dp, mBSDF_incndx(dp, v2));
769 if ((v1 != v2) & (out_psa <= 0) & (inc_psa <= 0)) {
770 inc_psa = mBSDF_outohm(dp, mBSDF_outndx(dp, v2));
771 out_psa = mBSDF_incohm(dp, mBSDF_incndx(dp, v1));
772 }
773
774 switch (qflags) { /* record based on flag settings */
775 case SDqueryMax:
776 if (inc_psa > psa[0])
777 psa[0] = inc_psa;
778 if (out_psa > psa[0])
779 psa[0] = out_psa;
780 break;
781 case SDqueryMin+SDqueryMax:
782 if (inc_psa > psa[1])
783 psa[1] = inc_psa;
784 if (out_psa > psa[1])
785 psa[1] = out_psa;
786 /* fall through */
787 case SDqueryVal:
788 if (qflags == SDqueryVal)
789 psa[0] = M_PI;
790 /* fall through */
791 case SDqueryMin:
792 if ((inc_psa > 0) & (inc_psa < psa[0]))
793 psa[0] = inc_psa;
794 if ((out_psa > 0) & (out_psa < psa[0]))
795 psa[0] = out_psa;
796 break;
797 }
798 /* make sure it's legal */
799 return (psa[0] <= 0) ? SDEinternal : SDEnone;
800 }
801
802 /* Compute new cumulative distribution from BSDF */
803 static int
804 make_cdist(SDMatCDst *cd, const FVECT inVec, SDMat *dp, int rev)
805 {
806 const unsigned maxval = ~0;
807 double *cmtab, scale;
808 int o;
809
810 cmtab = (double *)malloc((cd->calen+1)*sizeof(double));
811 if (cmtab == NULL)
812 return 0;
813 cmtab[0] = .0;
814 for (o = 0; o < cd->calen; o++) {
815 if (rev)
816 cmtab[o+1] = mBSDF_value(dp, cd->indx, o) *
817 (*dp->ib_ohm)(o, dp->ib_priv);
818 else
819 cmtab[o+1] = mBSDF_value(dp, o, cd->indx) *
820 (*dp->ob_ohm)(o, dp->ob_priv);
821 cmtab[o+1] += cmtab[o];
822 }
823 cd->cTotal = cmtab[cd->calen];
824 scale = (double)maxval / cd->cTotal;
825 cd->carr[0] = 0;
826 for (o = 1; o < cd->calen; o++)
827 cd->carr[o] = scale*cmtab[o] + .5;
828 cd->carr[cd->calen] = maxval;
829 free(cmtab);
830 return 1;
831 }
832
833 /* Get cumulative distribution for matrix BSDF */
834 static const SDCDst *
835 SDgetMtxCDist(const FVECT inVec, SDComponent *sdc)
836 {
837 SDMat *dp;
838 int reverse;
839 SDMatCDst myCD;
840 SDMatCDst *cd, *cdlast;
841 /* check arguments */
842 if ((inVec == NULL) | (sdc == NULL) ||
843 (dp = (SDMat *)sdc->dist) == NULL)
844 return NULL;
845 memset(&myCD, 0, sizeof(myCD));
846 myCD.indx = mBSDF_incndx(dp, inVec);
847 if (myCD.indx >= 0) {
848 myCD.ob_priv = dp->ob_priv;
849 myCD.ob_vec = dp->ob_vec;
850 myCD.calen = dp->nout;
851 reverse = 0;
852 } else { /* try reciprocity */
853 myCD.indx = mBSDF_outndx(dp, inVec);
854 if (myCD.indx < 0)
855 return NULL;
856 myCD.ob_priv = dp->ib_priv;
857 myCD.ob_vec = dp->ib_vec;
858 myCD.calen = dp->ninc;
859 reverse = 1;
860 }
861 cdlast = NULL; /* check for it in cache list */
862 /* PLACE MUTEX LOCK HERE FOR THREAD-SAFE */
863 for (cd = (SDMatCDst *)sdc->cdList; cd != NULL;
864 cdlast = cd, cd = cd->next)
865 if (cd->indx == myCD.indx && (cd->calen == myCD.calen) &
866 (cd->ob_priv == myCD.ob_priv) &
867 (cd->ob_vec == myCD.ob_vec))
868 break;
869 if (cd == NULL) { /* need to allocate new entry */
870 cd = (SDMatCDst *)malloc(sizeof(SDMatCDst) +
871 sizeof(myCD.carr[0])*myCD.calen);
872 if (cd == NULL)
873 return NULL;
874 *cd = myCD; /* compute cumulative distribution */
875 if (!make_cdist(cd, inVec, dp, reverse)) {
876 free(cd);
877 return NULL;
878 }
879 cdlast = cd;
880 }
881 if (cdlast != NULL) { /* move entry to head of cache list */
882 cdlast->next = cd->next;
883 cd->next = (SDMatCDst *)sdc->cdList;
884 sdc->cdList = (SDCDst *)cd;
885 }
886 /* END MUTEX LOCK */
887 return (SDCDst *)cd; /* ready to go */
888 }
889
890 /* Sample cumulative distribution */
891 static SDError
892 SDsampMtxCDist(FVECT ioVec, double randX, const SDCDst *cdp)
893 {
894 const unsigned maxval = ~0;
895 const SDMatCDst *mcd = (const SDMatCDst *)cdp;
896 const unsigned target = randX*maxval;
897 int i, iupper, ilower;
898 /* check arguments */
899 if ((ioVec == NULL) | (mcd == NULL))
900 return SDEargument;
901 /* binary search to find index */
902 ilower = 0; iupper = mcd->calen;
903 while ((i = (iupper + ilower) >> 1) != ilower)
904 if (target >= mcd->carr[i])
905 ilower = i;
906 else
907 iupper = i;
908 /* localize random position */
909 randX = (randX*maxval - mcd->carr[ilower]) /
910 (double)(mcd->carr[iupper] - mcd->carr[ilower]);
911 /* convert index to vector */
912 if ((*mcd->ob_vec)(ioVec, i+randX, mcd->ob_priv))
913 return SDEnone;
914 strcpy(SDerrorDetail, "Matrix BSDF sampling fault");
915 return SDEinternal;
916 }
917
918 /* Fixed resolution BSDF methods */
919 const SDFunc SDhandleMtx = {
920 &SDgetMtxBSDF,
921 &SDqueryMtxProjSA,
922 &SDgetMtxCDist,
923 &SDsampMtxCDist,
924 &SDfreeMatrix,
925 };