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 (22 months 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

# User Rev Content
1 greg 3.2 #ifndef lint
2 greg 3.46 static const char RCSid[] = "$Id: bsdf_m.c,v 3.45 2022/01/25 01:34:20 greg Exp $";
3 greg 3.2 #endif
4 greg 3.1 /*
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 greg 3.16 #define _USE_MATH_DEFINES
15 greg 3.3 #include "rtio.h"
16 greg 3.1 #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 greg 3.25 ANGLE_BASIS abase_list[MAXABASES] = {
32 greg 3.1 {
33     "LBNL/Klems Full", 145,
34 greg 3.22 { {0., 1},
35 greg 3.1 {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 greg 3.46 "LBNL/Klems Half", 77,
46 greg 3.22 { {0., 1},
47 greg 3.1 {6.5, 8},
48     {19.5, 12},
49     {32.5, 16},
50 greg 3.46 {45.5, 20},
51     {58.5, 12},
52     {71.5, 8},
53 greg 3.1 {90., 0} }
54     }, {
55     "LBNL/Klems Quarter", 41,
56 greg 3.22 { {0., 1},
57 greg 3.1 {9., 8},
58     {27., 12},
59 greg 3.46 {45., 12},
60     {63., 8},
61 greg 3.1 {90., 0} }
62     }
63     };
64    
65 greg 3.25 int nabases = 3; /* current number of defined bases */
66 greg 3.1
67 greg 3.30 C_COLOR mtx_RGB_prim[3]; /* our RGB primaries */
68     float mtx_RGB_coef[3]; /* corresponding Y coefficients */
69 greg 3.29
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 greg 3.1 static int
74     fequal(double a, double b)
75     {
76 greg 3.10 if (b != 0)
77 greg 3.1 a = a/b - 1.;
78     return (a <= 1e-6) & (a >= -1e-6);
79     }
80    
81 greg 3.29 /* convert error to standard BSDF code */
82 greg 3.1 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 greg 3.29 /* allocate a BSDF matrix of the given size */
103 greg 3.1 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 greg 3.29 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 greg 3.1
135 greg 3.32 /* compute square of real value */
136     static double sq(double x) { return x*x; }
137    
138 greg 3.25 /* Get vector for this angle basis index (front exiting) */
139     int
140 greg 3.9 fo_getvec(FVECT v, double ndxr, void *p)
141 greg 3.1 {
142 greg 3.9 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
143     int ndx = (int)ndxr;
144     double randX = ndxr - ndx;
145 greg 3.1 double rx[2];
146     int li;
147 greg 3.32 double azi, d;
148 greg 3.1
149 greg 3.9 if ((ndxr < 0) | (ndx >= ab->nangles))
150 greg 3.1 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 greg 3.33 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 greg 3.32 v[2] = d = sqrt(d); /* cos(pol) */
157 greg 3.1 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 greg 3.25 /* Get index corresponding to the given vector (front exiting) */
165     int
166 greg 3.8 fo_getndx(const FVECT v, void *p)
167 greg 3.1 {
168 greg 3.9 ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
169 greg 3.1 int li, ndx;
170 greg 3.17 double pol, azi;
171 greg 3.1
172     if (v == NULL)
173     return -1;
174 greg 3.44 if ((v[2] < 0) | (v[2] > 1.00001))
175 greg 3.1 return -1;
176 greg 3.27 pol = 180.0/M_PI*Acos(v[2]);
177 greg 3.1 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 greg 3.25 /* Get projected solid angle for this angle basis index (universal) */
191     double
192 greg 3.8 io_getohm(int ndx, void *p)
193 greg 3.1 {
194 greg 3.36 static void *last_p = NULL;
195 greg 3.1 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 greg 3.36 if ((p == last_p) & (li == last_li)) /* cached latitude? */
206 greg 3.1 return last_ohm;
207 greg 3.36 last_p = p;
208 greg 3.1 last_li = li;
209 greg 3.21 theta = M_PI/180. * ab->lat[li].tmin;
210 greg 3.1 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 greg 3.25 /* Get vector for this angle basis index (back incident) */
216     int
217 greg 3.9 bi_getvec(FVECT v, double ndxr, void *p)
218 greg 3.1 {
219 greg 3.9 if (!fo_getvec(v, ndxr, p))
220 greg 3.1 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 greg 3.25 /* Get index corresponding to the vector (back incident) */
230     int
231 greg 3.8 bi_getndx(const FVECT v, void *p)
232 greg 3.1 {
233     FVECT v2;
234    
235     v2[0] = -v[0];
236     v2[1] = -v[1];
237     v2[2] = -v[2];
238    
239 greg 3.8 return fo_getndx(v2, p);
240     }
241    
242 greg 3.25 /* Get vector for this angle basis index (back exiting) */
243     int
244 greg 3.9 bo_getvec(FVECT v, double ndxr, void *p)
245 greg 3.8 {
246 greg 3.9 if (!fo_getvec(v, ndxr, p))
247 greg 3.8 return RC_FAIL;
248    
249     v[2] = -v[2];
250    
251     return RC_GOOD;
252     }
253    
254 greg 3.25 /* Get index corresponding to the vector (back exiting) */
255     int
256 greg 3.8 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 greg 3.25 /* Get vector for this angle basis index (front incident) */
268     int
269 greg 3.9 fi_getvec(FVECT v, double ndxr, void *p)
270 greg 3.8 {
271 greg 3.9 if (!fo_getvec(v, ndxr, p))
272 greg 3.8 return RC_FAIL;
273    
274     v[0] = -v[0];
275     v[1] = -v[1];
276    
277     return RC_GOOD;
278     }
279    
280 greg 3.25 /* Get index corresponding to the vector (front incident) */
281     int
282 greg 3.8 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 greg 3.1 }
292    
293 greg 3.29 /* 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 greg 3.45 double d;
299 greg 3.29
300 greg 3.39 coef[0] = mBSDF_value(dp, o, i);
301 greg 3.45 /* 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 greg 3.29 if (dp->chroma == NULL)
306     return 1; /* grayscale */
307    
308 greg 3.39 c_decodeChroma(&cxy, mBSDF_chroma(dp,o,i));
309 greg 3.29 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 greg 3.1 /* 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 greg 3.21 abase_list[nabases].lat[0].tmin = 0;
348 greg 3.1 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 greg 3.12 abname);
353 greg 3.1 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 greg 3.12 abname);
363 greg 3.1 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 greg 3.39 hemi += ohma[o] * mBSDF_value(dp, o, i);
392 greg 3.1 if (hemi > df->maxHemi)
393     df->maxHemi = hemi;
394     }
395     free(ohma);
396     /* need incoming solid angles, too? */
397 greg 3.5 if ((dp->ib_ohm != dp->ob_ohm) | (dp->ib_priv != dp->ob_priv)) {
398 greg 3.1 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 greg 3.29 load_bsdf_data(SDData *sd, ezxml_t wdb, int ct, int rowinc)
409 greg 3.1 {
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 greg 3.15 if (!sdata)
418     return RC_FAIL;
419 greg 3.8 /*
420     * Remember that front and back are reversed from WINDOW 6 orientations
421     */
422 greg 3.23 if (!strcasecmp(sdata, "Transmission Front")) {
423 greg 3.29 if (sd->tb == NULL && (sd->tb = SDnewSpectralDF(3)) == NULL)
424 greg 3.24 return RC_MEMERR;
425     df = sd->tb;
426     } else if (!strcasecmp(sdata, "Transmission Back")) {
427 greg 3.29 if (sd->tf == NULL && (sd->tf = SDnewSpectralDF(3)) == NULL)
428 greg 3.1 return RC_MEMERR;
429     df = sd->tf;
430     } else if (!strcasecmp(sdata, "Reflection Front")) {
431 greg 3.29 if (sd->rb == NULL && (sd->rb = SDnewSpectralDF(3)) == NULL)
432 greg 3.6 return RC_MEMERR;
433     df = sd->rb;
434     } else if (!strcasecmp(sdata, "Reflection Back")) {
435 greg 3.29 if (sd->rf == NULL && (sd->rf = SDnewSpectralDF(3)) == NULL)
436 greg 3.1 return RC_MEMERR;
437     df = sd->rf;
438     } else
439     return RC_FAIL;
440 greg 3.29 /* 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 greg 3.1 /* 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 greg 3.4 if (!strcasecmp(sdata, abase_list[inbi].name))
454 greg 3.1 break;
455     if (inbi < 0) {
456 greg 3.4 sprintf(SDerrorDetail, "Undefined ColumnAngleBasis '%s'", sdata);
457 greg 3.1 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 greg 3.4 if (!strcasecmp(sdata, abase_list[outbi].name))
467 greg 3.1 break;
468     if (outbi < 0) {
469 greg 3.4 sprintf(SDerrorDetail, "Undefined RowAngleBasis '%s'", sdata);
470 greg 3.1 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 greg 3.5 dp->ib_priv = &abase_list[inbi];
477     dp->ob_priv = &abase_list[outbi];
478 greg 3.1 if (df == sd->tf) {
479 greg 3.23 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 greg 3.1 } else if (df == sd->rf) {
489 greg 3.8 dp->ib_vec = &fi_getvec;
490     dp->ib_ndx = &fi_getndx;
491     dp->ob_vec = &fo_getvec;
492     dp->ob_ndx = &fo_getndx;
493 greg 3.1 } else /* df == sd->rb */ {
494 greg 3.8 dp->ib_vec = &bi_getvec;
495     dp->ib_ndx = &bi_getndx;
496     dp->ob_vec = &bo_getvec;
497     dp->ob_ndx = &bo_getndx;
498 greg 3.1 }
499 greg 3.8 dp->ib_ohm = &io_getohm;
500     dp->ob_ohm = &io_getohm;
501 greg 3.29 df->comp[ct].dist = dp;
502     df->comp[ct].func = &SDhandleMtx;
503 greg 3.1 /* read BSDF data */
504 greg 3.15 sdata = ezxml_txt(ezxml_child(wdb, "ScatteringData"));
505 greg 3.1 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 greg 3.28 char *sdnext = fskip(sdata);
512     double val;
513 greg 3.1 if (sdnext == NULL) {
514     sprintf(SDerrorDetail,
515     "Bad/missing BSDF ScatteringData in '%s'",
516     sd->name);
517     return RC_FORMERR;
518     }
519 greg 3.15 while (isspace(*sdnext))
520 greg 3.1 sdnext++;
521     if (*sdnext == ',') sdnext++;
522 greg 3.28 if ((val = atof(sdata)) < 0)
523     val = 0; /* don't allow negative values */
524 greg 3.1 if (rowinc) {
525     int r = i/dp->nout;
526 greg 3.16 int c = i - r*dp->nout;
527 greg 3.39 mBSDF_value(dp,c,r) = val;
528 greg 3.1 } else
529 greg 3.28 dp->bsdf[i] = val;
530 greg 3.1 sdata = sdnext;
531     }
532 greg 3.29 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 greg 3.1 }
550    
551 greg 3.29 /* 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 greg 3.1 static double
598 greg 3.29 subtract_min(C_COLOR *cs, SDMat *sm)
599 greg 3.1 {
600 greg 3.29 const int ncomp = 1 + 2*(sm->chroma != NULL);
601 greg 3.31 float min_coef[3], ymin, coef[3];
602 greg 3.29 int i, o, c;
603 greg 3.1
604 greg 3.29 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 greg 3.31 ymin = 0;
613 greg 3.29 for (c = ncomp; c--; )
614 greg 3.31 ymin += min_coef[c];
615     if (ymin <= .01/M_PI) /* not worth bothering about? */
616 greg 3.15 return .0;
617 greg 3.29 if (ncomp == 1) { /* subtract grayscale minimum */
618     for (i = sm->ninc*sm->nout; i--; )
619 greg 3.31 sm->bsdf[i] -= ymin;
620 greg 3.29 *cs = c_dfcolor;
621 greg 3.31 return M_PI*ymin;
622 greg 3.29 }
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 greg 3.30 if (c_fromSharpRGB(coef, &cxy) > 1e-5)
632 greg 3.39 mBSDF_chroma(sm,o,i) = c_encodeChroma(&cxy);
633     mBSDF_value(sm,o,i) -= ymin;
634 greg 3.29 }
635     /* return colored minimum */
636 greg 3.30 for (i = 3; i--; )
637     coef[i] = min_coef[i]/mtx_RGB_coef[i];
638     c_fromSharpRGB(coef, cs);
639 greg 3.1
640 greg 3.31 return M_PI*ymin;
641 greg 3.1 }
642    
643 greg 3.29 /* Extract and separate diffuse portion of BSDF & convert color */
644     static SDSpectralDF *
645 greg 3.1 extract_diffuse(SDValue *dv, SDSpectralDF *df)
646     {
647    
648 greg 3.29 df = encode_chroma(df); /* reduce XYZ to Y + chroma */
649 greg 3.1 if (df == NULL || df->ncomp <= 0) {
650     dv->spec = c_dfcolor;
651     dv->cieY = .0;
652 greg 3.29 return df;
653 greg 3.1 }
654 greg 3.29 /* subtract minimum value */
655     dv->cieY = subtract_min(&dv->spec, (SDMat *)df->comp[0].dist);
656 greg 3.15 df->maxHemi -= dv->cieY; /* adjust maximum hemispherical */
657 greg 3.38
658     c_ccvt(&dv->spec, C_CSXY); /* make sure (x,y) is set */
659 greg 3.29 return df;
660 greg 3.1 }
661    
662     /* Load a BSDF matrix from an open XML file */
663     SDError
664 greg 3.4 SDloadMtx(SDData *sd, ezxml_t wtl)
665 greg 3.1 {
666 greg 3.15 ezxml_t wld, wdb;
667     int rowIn;
668     char *txt;
669     int rval;
670     /* basic checks and data ordering */
671 greg 3.4 txt = ezxml_txt(ezxml_child(ezxml_child(wtl,
672     "DataDefinition"), "IncidentDataStructure"));
673     if (txt == NULL || !*txt) {
674 greg 3.1 sprintf(SDerrorDetail,
675 greg 3.4 "BSDF \"%s\": missing IncidentDataStructure",
676 greg 3.1 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 greg 3.18 /* 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 greg 3.15 /* load BSDF components */
697 greg 3.1 for (wld = ezxml_child(wtl, "WavelengthData");
698     wld != NULL; wld = wld->next) {
699 greg 3.29 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 greg 3.1 for (wdb = ezxml_child(wld, "WavelengthDataBlock");
710     wdb != NULL; wdb = wdb->next)
711 greg 3.29 if ((rval = load_bsdf_data(sd, wdb, ct, rowIn)) < 0)
712 greg 3.4 return convert_errcode(rval);
713 greg 3.1 }
714 greg 3.15 /* separate diffuse components */
715 greg 3.29 sd->rf = extract_diffuse(&sd->rLambFront, sd->rf);
716     sd->rb = extract_diffuse(&sd->rLambBack, sd->rb);
717 greg 3.41 sd->tf = extract_diffuse(&sd->tLambFront, sd->tf);
718 greg 3.42 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 greg 3.15 /* return success */
725 greg 3.1 return SDEnone;
726     }
727    
728     /* Get Matrix BSDF value */
729     static int
730 greg 3.43 SDgetMtxBSDF(float coef[SDmaxCh], const FVECT inVec,
731     const FVECT outVec, SDComponent *sdc)
732 greg 3.1 {
733 greg 3.12 const SDMat *dp;
734 greg 3.1 int i_ndx, o_ndx;
735 greg 3.12 /* check arguments */
736     if ((coef == NULL) | (outVec == NULL) | (inVec == NULL) | (sdc == NULL)
737     || (dp = (SDMat *)sdc->dist) == NULL)
738     return 0;
739 greg 3.1 /* get angle indices */
740     i_ndx = mBSDF_incndx(dp, inVec);
741     o_ndx = mBSDF_outndx(dp, outVec);
742     /* try reciprocity if necessary */
743 greg 3.19 if ((i_ndx < 0) & (o_ndx < 0)) {
744 greg 3.1 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 greg 3.29
750     return mBSDF_color(coef, dp, i_ndx, o_ndx);
751 greg 3.1 }
752    
753 greg 3.12 /* Query solid angle for vector(s) */
754 greg 3.1 static SDError
755 greg 3.10 SDqueryMtxProjSA(double *psa, const FVECT v1, const RREAL *v2,
756 greg 3.12 int qflags, SDComponent *sdc)
757 greg 3.1 {
758 greg 3.12 const SDMat *dp;
759 greg 3.5 double inc_psa, out_psa;
760     /* check arguments */
761 greg 3.12 if ((psa == NULL) | (v1 == NULL) | (sdc == NULL) ||
762     (dp = (SDMat *)sdc->dist) == NULL)
763 greg 3.1 return SDEargument;
764 greg 3.10 if (v2 == NULL)
765     v2 = v1;
766 greg 3.5 /* get projected solid angles */
767 greg 3.10 out_psa = mBSDF_outohm(dp, mBSDF_outndx(dp, v1));
768     inc_psa = mBSDF_incohm(dp, mBSDF_incndx(dp, v2));
769 greg 3.12 if ((v1 != v2) & (out_psa <= 0) & (inc_psa <= 0)) {
770 greg 3.11 inc_psa = mBSDF_outohm(dp, mBSDF_outndx(dp, v2));
771     out_psa = mBSDF_incohm(dp, mBSDF_incndx(dp, v1));
772     }
773 greg 3.5
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 greg 3.13 if (inc_psa > psa[1])
783 greg 3.5 psa[1] = inc_psa;
784 greg 3.13 if (out_psa > psa[1])
785 greg 3.5 psa[1] = out_psa;
786     /* fall through */
787 greg 3.12 case SDqueryVal:
788     if (qflags == SDqueryVal)
789     psa[0] = M_PI;
790 greg 3.14 /* fall through */
791     case SDqueryMin:
792 greg 3.10 if ((inc_psa > 0) & (inc_psa < psa[0]))
793 greg 3.1 psa[0] = inc_psa;
794 greg 3.10 if ((out_psa > 0) & (out_psa < psa[0]))
795 greg 3.5 psa[0] = out_psa;
796     break;
797 greg 3.1 }
798 greg 3.5 /* make sure it's legal */
799 greg 3.10 return (psa[0] <= 0) ? SDEinternal : SDEnone;
800 greg 3.1 }
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 greg 3.39 cmtab[o+1] = mBSDF_value(dp, cd->indx, o) *
817 greg 3.1 (*dp->ib_ohm)(o, dp->ib_priv);
818     else
819 greg 3.39 cmtab[o+1] = mBSDF_value(dp, o, cd->indx) *
820 greg 3.1 (*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 greg 3.12 SDMat *dp;
838 greg 3.1 int reverse;
839     SDMatCDst myCD;
840     SDMatCDst *cd, *cdlast;
841 greg 3.5 /* check arguments */
842 greg 3.12 if ((inVec == NULL) | (sdc == NULL) ||
843     (dp = (SDMat *)sdc->dist) == NULL)
844 greg 3.1 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 greg 3.19 } else { /* try reciprocity */
853 greg 3.1 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 greg 3.34 /* PLACE MUTEX LOCK HERE FOR THREAD-SAFE */
863 greg 3.14 for (cd = (SDMatCDst *)sdc->cdList; cd != NULL;
864 greg 3.20 cdlast = cd, cd = cd->next)
865 greg 3.1 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 greg 3.14 sizeof(myCD.carr[0])*myCD.calen);
872 greg 3.1 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 greg 3.20 cd->next = (SDMatCDst *)sdc->cdList;
884 greg 3.1 sdc->cdList = (SDCDst *)cd;
885     }
886 greg 3.34 /* END MUTEX LOCK */
887 greg 3.1 return (SDCDst *)cd; /* ready to go */
888     }
889    
890     /* Sample cumulative distribution */
891     static SDError
892 greg 3.12 SDsampMtxCDist(FVECT ioVec, double randX, const SDCDst *cdp)
893 greg 3.1 {
894     const unsigned maxval = ~0;
895     const SDMatCDst *mcd = (const SDMatCDst *)cdp;
896     const unsigned target = randX*maxval;
897     int i, iupper, ilower;
898 greg 3.5 /* check arguments */
899 greg 3.12 if ((ioVec == NULL) | (mcd == NULL))
900 greg 3.5 return SDEargument;
901 greg 3.1 /* binary search to find index */
902     ilower = 0; iupper = mcd->calen;
903     while ((i = (iupper + ilower) >> 1) != ilower)
904 greg 3.18 if (target >= mcd->carr[i])
905 greg 3.1 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 greg 3.12 if ((*mcd->ob_vec)(ioVec, i+randX, mcd->ob_priv))
913 greg 3.1 return SDEnone;
914 greg 3.12 strcpy(SDerrorDetail, "Matrix BSDF sampling fault");
915 greg 3.1 return SDEinternal;
916     }
917    
918     /* Fixed resolution BSDF methods */
919 greg 3.37 const SDFunc SDhandleMtx = {
920 greg 3.1 &SDgetMtxBSDF,
921     &SDqueryMtxProjSA,
922     &SDgetMtxCDist,
923     &SDsampMtxCDist,
924     &SDfreeMatrix,
925     };