ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf_m.c
Revision: 3.1
Committed: Fri Feb 18 00:40:25 2011 UTC (13 years, 2 months ago) by greg
Content type: text/plain
Branch: MAIN
Log Message:
Major code reorg, moving mgflib to common and introducing BSDF material

File Contents

# User Rev Content
1 greg 3.1 /*
2     * bsdf_m.c
3     *
4     * Definitions supporting BSDF matrices
5     *
6     * Created by Greg Ward on 2/2/11.
7     * Copyright 2011 Anyhere Software. All rights reserved.
8     *
9     */
10    
11     #include <stdio.h>
12     #include <stdlib.h>
13     #include <math.h>
14     #include <strings.h>
15     #include <ctype.h>
16     #include "ezxml.h"
17     #include "bsdf.h"
18     #include "bsdf_m.h"
19    
20     #ifndef FTINY
21     #define FTINY 1e-6
22     #endif
23    
24     /* Function return codes */
25     #define RC_GOOD 1
26     #define RC_FAIL 0
27     #define RC_FORMERR (-1)
28     #define RC_DATERR (-2)
29     #define RC_UNSUPP (-3)
30     #define RC_INTERR (-4)
31     #define RC_MEMERR (-5)
32    
33     #define MAXLATS 46 /* maximum number of latitudes */
34    
35     /* BSDF angle specification */
36     typedef struct {
37     char name[64]; /* basis name */
38     int nangles; /* total number of directions */
39     struct {
40     float tmin; /* starting theta */
41     int nphis; /* number of phis (0 term) */
42     } lat[MAXLATS+1]; /* latitudes */
43     } ANGLE_BASIS;
44    
45     #define MAXABASES 7 /* limit on defined bases */
46    
47     static ANGLE_BASIS abase_list[MAXABASES] = {
48     {
49     "LBNL/Klems Full", 145,
50     { {-5., 1},
51     {5., 8},
52     {15., 16},
53     {25., 20},
54     {35., 24},
55     {45., 24},
56     {55., 24},
57     {65., 16},
58     {75., 12},
59     {90., 0} }
60     }, {
61     "LBNL/Klems Half", 73,
62     { {-6.5, 1},
63     {6.5, 8},
64     {19.5, 12},
65     {32.5, 16},
66     {46.5, 20},
67     {61.5, 12},
68     {76.5, 4},
69     {90., 0} }
70     }, {
71     "LBNL/Klems Quarter", 41,
72     { {-9., 1},
73     {9., 8},
74     {27., 12},
75     {46., 12},
76     {66., 8},
77     {90., 0} }
78     }
79     };
80    
81     static int nabases = 3; /* current number of defined bases */
82    
83     static int
84     fequal(double a, double b)
85     {
86     if (b != .0)
87     a = a/b - 1.;
88     return (a <= 1e-6) & (a >= -1e-6);
89     }
90    
91     /* returns the name of the given tag */
92     #ifdef ezxml_name
93     #undef ezxml_name
94     static char *
95     ezxml_name(ezxml_t xml)
96     {
97     if (xml == NULL)
98     return NULL;
99     return xml->name;
100     }
101     #endif
102    
103     /* returns the given tag's character content or empty string if none */
104     #ifdef ezxml_txt
105     #undef ezxml_txt
106     static char *
107     ezxml_txt(ezxml_t xml)
108     {
109     if (xml == NULL)
110     return "";
111     return xml->txt;
112     }
113     #endif
114    
115     /* Convert error to standard BSDF code */
116     static SDError
117     convert_errcode(int ec)
118     {
119     switch (ec) {
120     case RC_GOOD:
121     return SDEnone;
122     case RC_FORMERR:
123     return SDEformat;
124     case RC_DATERR:
125     return SDEdata;
126     case RC_UNSUPP:
127     return SDEsupport;
128     case RC_INTERR:
129     return SDEinternal;
130     case RC_MEMERR:
131     return SDEmemory;
132     }
133     return SDEunknown;
134     }
135    
136     /* Allocate a BSDF matrix of the given size */
137     static SDMat *
138     SDnewMatrix(int ni, int no)
139     {
140     SDMat *sm;
141    
142     if ((ni <= 0) | (no <= 0)) {
143     strcpy(SDerrorDetail, "Empty BSDF matrix request");
144     return NULL;
145     }
146     sm = (SDMat *)malloc(sizeof(SDMat) + (ni*no - 1)*sizeof(float));
147     if (sm == NULL) {
148     sprintf(SDerrorDetail, "Cannot allocate %dx%d BSDF matrix",
149     ni, no);
150     return NULL;
151     }
152     memset(sm, 0, sizeof(SDMat)-sizeof(float));
153     sm->ninc = ni;
154     sm->nout = no;
155    
156     return sm;
157     }
158    
159     /* Free a BSDF matrix */
160     #define SDfreeMatrix free
161    
162     /* get vector for this angle basis index */
163     static int
164     ab_getvec(FVECT v, int ndx, double randX, void *p)
165     {
166     ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
167     double rx[2];
168     int li;
169     double pol, azi, d;
170    
171     if ((ndx < 0) | (ndx >= ab->nangles))
172     return RC_FAIL;
173     for (li = 0; ndx >= ab->lat[li].nphis; li++)
174     ndx -= ab->lat[li].nphis;
175     SDmultiSamp(rx, 2, randX);
176     pol = M_PI/180.*( (1.-rx[0])*ab->lat[li].tmin +
177     rx[0]*ab->lat[li+1].tmin );
178     azi = 2.*M_PI*(ndx + rx[1] - .5)/ab->lat[li].nphis;
179     v[2] = d = cos(pol);
180     d = sqrt(1. - d*d); /* sin(pol) */
181     v[0] = cos(azi)*d;
182     v[1] = sin(azi)*d;
183     return RC_GOOD;
184     }
185    
186     /* get index corresponding to the given vector */
187     static int
188     ab_getndx(const FVECT v, void *p)
189     {
190     ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
191     int li, ndx;
192     double pol, azi, d;
193    
194     if (v == NULL)
195     return -1;
196     if ((v[2] < .0) | (v[2] > 1.0))
197     return -1;
198     pol = 180.0/M_PI*acos(v[2]);
199     azi = 180.0/M_PI*atan2(v[1], v[0]);
200     if (azi < 0.0) azi += 360.0;
201     for (li = 1; ab->lat[li].tmin <= pol; li++)
202     if (!ab->lat[li].nphis)
203     return -1;
204     --li;
205     ndx = (int)((1./360.)*azi*ab->lat[li].nphis + 0.5);
206     if (ndx >= ab->lat[li].nphis) ndx = 0;
207     while (li--)
208     ndx += ab->lat[li].nphis;
209     return ndx;
210     }
211    
212     /* compute square of real value */
213     static double sq(double x) { return x*x; }
214    
215     /* get projected solid angle for this angle basis index */
216     static double
217     ab_getohm(int ndx, void *p)
218     {
219     static int last_li = -1;
220     static double last_ohm;
221     ANGLE_BASIS *ab = (ANGLE_BASIS *)p;
222     int li;
223     double theta, theta1;
224    
225     if ((ndx < 0) | (ndx >= ab->nangles))
226     return -1.;
227     for (li = 0; ndx >= ab->lat[li].nphis; li++)
228     ndx -= ab->lat[li].nphis;
229     if (li == last_li) /* cached latitude? */
230     return last_ohm;
231     last_li = li;
232     theta1 = M_PI/180. * ab->lat[li+1].tmin;
233     if (ab->lat[li].nphis == 1) /* special case */
234     return last_ohm = M_PI*(1. - sq(cos(theta1)));
235     theta = M_PI/180. * ab->lat[li].tmin;
236     return last_ohm = M_PI*(sq(cos(theta)) - sq(cos(theta1))) /
237     (double)ab->lat[li].nphis;
238     }
239    
240     /* get reverse vector for this angle basis index */
241     static int
242     ab_getvecR(FVECT v, int ndx, double randX, void *p)
243     {
244     int na = (*(ANGLE_BASIS *)p).nangles;
245    
246     if (!ab_getvec(v, ndx, randX, p))
247     return RC_FAIL;
248    
249     v[0] = -v[0];
250     v[1] = -v[1];
251     v[2] = -v[2];
252    
253     return RC_GOOD;
254     }
255    
256     /* get index corresponding to the reverse vector */
257     static int
258     ab_getndxR(const FVECT v, void *p)
259     {
260     FVECT v2;
261    
262     v2[0] = -v[0];
263     v2[1] = -v[1];
264     v2[2] = -v[2];
265    
266     return ab_getndx(v2, p);
267     }
268    
269     /* load custom BSDF angle basis */
270     static int
271     load_angle_basis(ezxml_t wab)
272     {
273     char *abname = ezxml_txt(ezxml_child(wab, "AngleBasisName"));
274     ezxml_t wbb;
275     int i;
276    
277     if (!abname || !*abname)
278     return RC_FAIL;
279     for (i = nabases; i--; )
280     if (!strcasecmp(abname, abase_list[i].name))
281     return RC_GOOD; /* assume it's the same */
282     if (nabases >= MAXABASES) {
283     sprintf(SDerrorDetail, "Out of angle bases reading '%s'",
284     abname);
285     return RC_INTERR;
286     }
287     strcpy(abase_list[nabases].name, abname);
288     abase_list[nabases].nangles = 0;
289     for (i = 0, wbb = ezxml_child(wab, "AngleBasisBlock");
290     wbb != NULL; i++, wbb = wbb->next) {
291     if (i >= MAXLATS) {
292     sprintf(SDerrorDetail, "Too many latitudes for '%s'",
293     abname);
294     return RC_INTERR;
295     }
296     abase_list[nabases].lat[i+1].tmin = atof(ezxml_txt(
297     ezxml_child(ezxml_child(wbb,
298     "ThetaBounds"), "UpperTheta")));
299     if (!i)
300     abase_list[nabases].lat[i].tmin =
301     -abase_list[nabases].lat[i+1].tmin;
302     else if (!fequal(atof(ezxml_txt(ezxml_child(ezxml_child(wbb,
303     "ThetaBounds"), "LowerTheta"))),
304     abase_list[nabases].lat[i].tmin)) {
305     sprintf(SDerrorDetail, "Theta values disagree in '%s'",
306     abname);
307     return RC_DATERR;
308     }
309     abase_list[nabases].nangles +=
310     abase_list[nabases].lat[i].nphis =
311     atoi(ezxml_txt(ezxml_child(wbb, "nPhis")));
312     if (abase_list[nabases].lat[i].nphis <= 0 ||
313     (abase_list[nabases].lat[i].nphis == 1 &&
314     abase_list[nabases].lat[i].tmin > FTINY)) {
315     sprintf(SDerrorDetail, "Illegal phi count in '%s'",
316     abname);
317     return RC_DATERR;
318     }
319     }
320     abase_list[nabases++].lat[i].nphis = 0;
321     return RC_GOOD;
322     }
323    
324     /* compute min. proj. solid angle and max. direct hemispherical scattering */
325     static int
326     get_extrema(SDSpectralDF *df)
327     {
328     SDMat *dp = (SDMat *)df->comp[0].dist;
329     double *ohma;
330     int i, o;
331     /* initialize extrema */
332     df->minProjSA = M_PI;
333     df->maxHemi = .0;
334     ohma = (double *)malloc(dp->nout*sizeof(double));
335     if (ohma == NULL)
336     return RC_MEMERR;
337     /* get outgoing solid angles */
338     for (o = dp->nout; o--; )
339     if ((ohma[o] = mBSDF_outohm(dp,o)) < df->minProjSA)
340     df->minProjSA = ohma[o];
341     /* compute hemispherical sums */
342     for (i = dp->ninc; i--; ) {
343     double hemi = .0;
344     for (o = dp->nout; o--; )
345     hemi += ohma[o] * mBSDF_value(dp, i, o);
346     if (hemi > df->maxHemi)
347     df->maxHemi = hemi;
348     }
349     free(ohma);
350     /* need incoming solid angles, too? */
351     if (dp->ninc < dp->nout || dp->ib_ohm != dp->ob_ohm ||
352     dp->ib_priv != dp->ob_priv) {
353     double ohm;
354     for (i = dp->ninc; i--; )
355     if ((ohm = mBSDF_incohm(dp,i)) < df->minProjSA)
356     df->minProjSA = ohm;
357     }
358     return (df->maxHemi <= 1.01);
359     }
360    
361     /* skip integer in string */
362     static char *
363     i_skip(char *s)
364     {
365     while (isspace(*s))
366     s++;
367     if (*s == '-' || *s == '+')
368     s++;
369     if (!isdigit(*s))
370     return(NULL);
371     do
372     s++;
373     while (isdigit(*s));
374     return(s);
375     }
376    
377     /* skip float in string */
378     static char *
379     f_skip(char *s)
380     {
381     register char *cp;
382    
383     while (isspace(*s))
384     s++;
385     if (*s == '-' || *s == '+')
386     s++;
387     cp = s;
388     while (isdigit(*cp))
389     cp++;
390     if (*cp == '.') {
391     cp++; s++;
392     while (isdigit(*cp))
393     cp++;
394     }
395     if (cp == s)
396     return(NULL);
397     if (*cp == 'e' || *cp == 'E')
398     return(i_skip(cp+1));
399     return(cp);
400     }
401    
402     /* load BSDF distribution for this wavelength */
403     static int
404     load_bsdf_data(SDData *sd, ezxml_t wdb, int rowinc)
405     {
406     char *cbasis = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
407     char *rbasis = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
408     SDSpectralDF *df;
409     SDMat *dp;
410     char *sdata;
411     int inbi, outbi;
412     int i;
413    
414     if ((!cbasis || !*cbasis) | (!rbasis || !*rbasis)) {
415     sprintf(SDerrorDetail, "Missing column/row basis for BSDF '%s'",
416     sd->name);
417     return RC_FORMERR;
418     }
419     /* allocate BSDF component */
420     sdata = ezxml_txt(ezxml_child(wdb, "WavelengthDataDirection"));
421     if (!strcasecmp(sdata, "Transmission Front")) {
422     if (sd->tf != NULL)
423     SDfreeSpectralDF(sd->tf);
424     if ((sd->tf = SDnewSpectralDF(1)) == NULL)
425     return RC_MEMERR;
426     df = sd->tf;
427     } else if (!strcasecmp(sdata, "Reflection Front")) {
428     if (sd->rf != NULL)
429     SDfreeSpectralDF(sd->rf);
430     if ((sd->rf = SDnewSpectralDF(1)) == NULL)
431     return RC_MEMERR;
432     df = sd->rf;
433     } else if (!strcasecmp(sdata, "Reflection Back")) {
434     if (sd->rb != NULL)
435     SDfreeSpectralDF(sd->rb);
436     if ((sd->rb = SDnewSpectralDF(1)) == NULL)
437     return RC_MEMERR;
438     df = sd->rb;
439     } else
440     return RC_FAIL;
441     /* get angle bases */
442     sdata = ezxml_txt(ezxml_child(wdb,"ColumnAngleBasis"));
443     if (!sdata || !*sdata) {
444     sprintf(SDerrorDetail, "Missing column basis for BSDF '%s'",
445     sd->name);
446     return RC_FORMERR;
447     }
448     for (inbi = nabases; inbi--; )
449     if (!strcasecmp(cbasis, abase_list[inbi].name))
450     break;
451     if (inbi < 0) {
452     sprintf(SDerrorDetail, "Undefined ColumnAngleBasis '%s'",
453     cbasis);
454     return RC_FORMERR;
455     }
456     sdata = ezxml_txt(ezxml_child(wdb,"RowAngleBasis"));
457     if (!sdata || !*sdata) {
458     sprintf(SDerrorDetail, "Missing row basis for BSDF '%s'",
459     sd->name);
460     return RC_FORMERR;
461     }
462     for (outbi = nabases; outbi--; )
463     if (!strcasecmp(rbasis, abase_list[outbi].name))
464     break;
465     if (outbi < 0) {
466     sprintf(SDerrorDetail, "Undefined RowAngleBasis '%s'", cbasis);
467     return RC_FORMERR;
468     }
469     /* allocate BSDF matrix */
470     dp = SDnewMatrix(abase_list[inbi].nangles, abase_list[outbi].nangles);
471     if (dp == NULL)
472     return RC_MEMERR;
473     dp->ib_priv = (void *)&abase_list[inbi];
474     dp->ob_priv = (void *)&abase_list[outbi];
475     if (df == sd->tf) {
476     dp->ib_vec = ab_getvecR;
477     dp->ib_ndx = ab_getndxR;
478     dp->ob_vec = ab_getvec;
479     dp->ob_ndx = ab_getndx;
480     } else if (df == sd->rf) {
481     dp->ib_vec = ab_getvec;
482     dp->ib_ndx = ab_getndx;
483     dp->ob_vec = ab_getvec;
484     dp->ob_ndx = ab_getndx;
485     } else /* df == sd->rb */ {
486     dp->ib_vec = ab_getvecR;
487     dp->ib_ndx = ab_getndxR;
488     dp->ob_vec = ab_getvecR;
489     dp->ob_ndx = ab_getndxR;
490     }
491     dp->ib_ohm = ab_getohm;
492     dp->ob_ohm = ab_getohm;
493     df->comp[0].cspec[0] = c_dfcolor; /* XXX monochrome for now */
494     df->comp[0].dist = dp;
495     df->comp[0].func = &SDhandleMtx;
496     /* read BSDF data */
497     sdata = ezxml_txt(ezxml_child(wdb,"ScatteringData"));
498     if (!sdata || !*sdata) {
499     sprintf(SDerrorDetail, "Missing BSDF ScatteringData in '%s'",
500     sd->name);
501     return RC_FORMERR;
502     }
503     for (i = 0; i < dp->ninc*dp->nout; i++) {
504     char *sdnext = f_skip(sdata);
505     if (sdnext == NULL) {
506     sprintf(SDerrorDetail,
507     "Bad/missing BSDF ScatteringData in '%s'",
508     sd->name);
509     return RC_FORMERR;
510     }
511     while (*sdnext && isspace(*sdnext))
512     sdnext++;
513     if (*sdnext == ',') sdnext++;
514     if (rowinc) {
515     int r = i/dp->nout;
516     int c = i - c*dp->nout;
517     mBSDF_value(dp,r,c) = atof(sdata);
518     } else
519     dp->bsdf[i] = atof(sdata);
520     sdata = sdnext;
521     }
522     return get_extrema(df);
523     }
524    
525     /* Subtract minimum (diffuse) scattering amount from BSDF */
526     static double
527     subtract_min(SDMat *sm)
528     {
529     float minv = sm->bsdf[0];
530     int n = sm->ninc*sm->nout;
531     int i;
532    
533     for (i = n; --i; )
534     if (sm->bsdf[i] < minv)
535     minv = sm->bsdf[i];
536     for (i = n; i--; )
537     sm->bsdf[i] -= minv;
538    
539     return minv*M_PI; /* be sure to include multiplier */
540     }
541    
542     /* Extract and separate diffuse portion of BSDF */
543     static void
544     extract_diffuse(SDValue *dv, SDSpectralDF *df)
545     {
546     int n;
547    
548     if (df == NULL || df->ncomp <= 0) {
549     dv->spec = c_dfcolor;
550     dv->cieY = .0;
551     return;
552     }
553     dv->spec = df->comp[0].cspec[0];
554     dv->cieY = subtract_min((SDMat *)df->comp[0].dist);
555     /* in case of multiple components */
556     for (n = df->ncomp; --n; ) {
557     double ymin = subtract_min((SDMat *)df->comp[n].dist);
558     c_cmix(&dv->spec, dv->cieY, &dv->spec, ymin, &df->comp[n].cspec[0]);
559     dv->cieY += ymin;
560     }
561     df->maxHemi -= dv->cieY; /* correct minimum hemispherical */
562     dv->spec.clock++; /* make sure everything is set */
563     c_ccvt(&dv->spec, C_CSXY+C_CSSPEC);
564     }
565    
566     /* Load a BSDF matrix from an open XML file */
567     SDError
568     SDloadMtx(SDData *sd, ezxml_t fl)
569     {
570     ezxml_t wtl, wld, wdb;
571     int rowIn;
572     struct BSDF_data *dp;
573     char *txt;
574     int rval;
575    
576     if (strcmp(ezxml_name(fl), "WindowElement")) {
577     sprintf(SDerrorDetail,
578     "BSDF \"%s\": top level node not 'WindowElement'",
579     sd->name);
580     return SDEformat;
581     }
582     wtl = ezxml_child(ezxml_child(fl, "Optical"), "Layer");
583     txt = ezxml_txt(ezxml_child(ezxml_child(wtl,
584     "DataDefinition"), "IncidentDataStructure"));
585     if (!strcasecmp(txt, "Rows"))
586     rowIn = 1;
587     else if (!strcasecmp(txt, "Columns"))
588     rowIn = 0;
589     else {
590     sprintf(SDerrorDetail,
591     "BSDF \"%s\": unsupported IncidentDataStructure",
592     sd->name);
593     return SDEsupport;
594     }
595     /* get angle basis */
596     rval = load_angle_basis(ezxml_child(ezxml_child(wtl,
597     "DataDefinition"), "AngleBasis"));
598     if (rval < 0)
599     goto err_return;
600     /* load BSDF components */
601     for (wld = ezxml_child(wtl, "WavelengthData");
602     wld != NULL; wld = wld->next) {
603     if (strcasecmp(ezxml_txt(ezxml_child(wld,"Wavelength")),
604     "Visible"))
605     continue; /* just visible for now */
606     for (wdb = ezxml_child(wld, "WavelengthDataBlock");
607     wdb != NULL; wdb = wdb->next)
608     if ((rval = load_bsdf_data(sd, wdb, rowIn)) < 0)
609     goto err_return;
610     }
611     /* separate diffuse components */
612     extract_diffuse(&sd->rLambFront, sd->rf);
613     extract_diffuse(&sd->rLambBack, sd->rb);
614     extract_diffuse(&sd->tLamb, sd->tf);
615     /* return success */
616     return SDEnone;
617     err_return: /* jump here on failure */
618     if (sd->rf != NULL) {
619     SDfreeSpectralDF(sd->rf);
620     sd->rf = NULL;
621     }
622     if (sd->rb != NULL) {
623     SDfreeSpectralDF(sd->rb);
624     sd->rb = NULL;
625     }
626     if (sd->tf != NULL) {
627     SDfreeSpectralDF(sd->tf);
628     sd->tf = NULL;
629     }
630     return convert_errcode(rval);
631     }
632    
633     /* Get Matrix BSDF value */
634     static int
635     SDgetMtxBSDF(float coef[SDmaxCh], const FVECT outVec,
636     const FVECT inVec, const void *dist)
637     {
638     const SDMat *dp = (const SDMat *)dist;
639     int i_ndx, o_ndx;
640     /* get angle indices */
641     i_ndx = mBSDF_incndx(dp, inVec);
642     o_ndx = mBSDF_outndx(dp, outVec);
643     /* try reciprocity if necessary */
644     if ((i_ndx < 0) & (o_ndx < 0)) {
645     i_ndx = mBSDF_incndx(dp, outVec);
646     o_ndx = mBSDF_outndx(dp, inVec);
647     }
648     if ((i_ndx < 0) | (o_ndx < 0))
649     return 0; /* nothing from this component */
650     coef[0] = mBSDF_value(dp, i_ndx, o_ndx);
651     return 1; /* XXX monochrome for now */
652     }
653    
654     /* Query solid angle for vector */
655     static SDError
656     SDqueryMtxProjSA(double *psa, const FVECT vec, int qflags, const void *dist)
657     {
658     const SDMat *dp = (const SDMat *)dist;
659    
660     if (!(qflags & SDqueryInc+SDqueryOut))
661     return SDEargument;
662     if (qflags & SDqueryInc) {
663     double inc_psa = mBSDF_incohm(dp, mBSDF_incndx(dp, vec));
664     if (inc_psa < .0)
665     return SDEinternal;
666     switch (qflags & SDqueryMin+SDqueryMax) {
667     case SDqueryMax:
668     if (inc_psa > psa[0])
669     psa[0] = inc_psa;
670     break;
671     case SDqueryMin+SDqueryMax:
672     if (inc_psa > psa[1])
673     psa[1] = inc_psa;
674     /* fall through */
675     case SDqueryMin:
676     if (inc_psa < psa[0])
677     psa[0] = inc_psa;
678     break;
679     case 0:
680     psa[0] = inc_psa;
681     break;
682     }
683     }
684     if (qflags & SDqueryOut) {
685     double out_psa = mBSDF_outohm(dp, mBSDF_outndx(dp, vec));
686     if (out_psa < .0)
687     return SDEinternal;
688     switch (qflags & SDqueryMin+SDqueryMax) {
689     case SDqueryMax:
690     if (out_psa > psa[0])
691     psa[0] = out_psa;
692     break;
693     case SDqueryMin+SDqueryMax:
694     if (out_psa > psa[1])
695     psa[1] = out_psa;
696     /* fall through */
697     case SDqueryMin:
698     if (out_psa < psa[0])
699     psa[0] = out_psa;
700     break;
701     case 0:
702     psa[(qflags&SDqueryInc)!=0] = out_psa;
703     break;
704     }
705     }
706     return SDEnone;
707     }
708    
709     /* Compute new cumulative distribution from BSDF */
710     static int
711     make_cdist(SDMatCDst *cd, const FVECT inVec, SDMat *dp, int rev)
712     {
713     const unsigned maxval = ~0;
714     double *cmtab, scale;
715     int o;
716    
717     cmtab = (double *)malloc((cd->calen+1)*sizeof(double));
718     if (cmtab == NULL)
719     return 0;
720     cmtab[0] = .0;
721     for (o = 0; o < cd->calen; o++) {
722     if (rev)
723     cmtab[o+1] = mBSDF_value(dp, o, cd->indx) *
724     (*dp->ib_ohm)(o, dp->ib_priv);
725     else
726     cmtab[o+1] = mBSDF_value(dp, cd->indx, o) *
727     (*dp->ob_ohm)(o, dp->ob_priv);
728     cmtab[o+1] += cmtab[o];
729     }
730     cd->cTotal = cmtab[cd->calen];
731     scale = (double)maxval / cd->cTotal;
732     cd->carr[0] = 0;
733     for (o = 1; o < cd->calen; o++)
734     cd->carr[o] = scale*cmtab[o] + .5;
735     cd->carr[cd->calen] = maxval;
736     free(cmtab);
737     return 1;
738     }
739    
740     /* Get cumulative distribution for matrix BSDF */
741     static const SDCDst *
742     SDgetMtxCDist(const FVECT inVec, SDComponent *sdc)
743     {
744     SDMat *dp = (SDMat *)sdc->dist;
745     int reverse;
746     SDMatCDst myCD;
747     SDMatCDst *cd, *cdlast;
748    
749     if (dp == NULL)
750     return NULL;
751     memset(&myCD, 0, sizeof(myCD));
752     myCD.indx = mBSDF_incndx(dp, inVec);
753     if (myCD.indx >= 0) {
754     myCD.ob_priv = dp->ob_priv;
755     myCD.ob_vec = dp->ob_vec;
756     myCD.calen = dp->nout;
757     reverse = 0;
758     } else { /* try reciprocity */
759     myCD.indx = mBSDF_outndx(dp, inVec);
760     if (myCD.indx < 0)
761     return NULL;
762     myCD.ob_priv = dp->ib_priv;
763     myCD.ob_vec = dp->ib_vec;
764     myCD.calen = dp->ninc;
765     reverse = 1;
766     }
767     cdlast = NULL; /* check for it in cache list */
768     for (cd = (SDMatCDst *)sdc->cdList;
769     cd != NULL; cd = (SDMatCDst *)cd->next) {
770     if (cd->indx == myCD.indx && (cd->calen == myCD.calen) &
771     (cd->ob_priv == myCD.ob_priv) &
772     (cd->ob_vec == myCD.ob_vec))
773     break;
774     cdlast = cd;
775     }
776     if (cd == NULL) { /* need to allocate new entry */
777     cd = (SDMatCDst *)malloc(sizeof(SDMatCDst) +
778     myCD.calen*sizeof(myCD.carr[0]));
779     if (cd == NULL)
780     return NULL;
781     *cd = myCD; /* compute cumulative distribution */
782     if (!make_cdist(cd, inVec, dp, reverse)) {
783     free(cd);
784     return NULL;
785     }
786     cdlast = cd;
787     }
788     if (cdlast != NULL) { /* move entry to head of cache list */
789     cdlast->next = cd->next;
790     cd->next = sdc->cdList;
791     sdc->cdList = (SDCDst *)cd;
792     }
793     return (SDCDst *)cd; /* ready to go */
794     }
795    
796     /* Sample cumulative distribution */
797     static SDError
798     SDsampMtxCDist(FVECT outVec, double randX, const SDCDst *cdp)
799     {
800     const unsigned maxval = ~0;
801     const SDMatCDst *mcd = (const SDMatCDst *)cdp;
802     const unsigned target = randX*maxval;
803     int i, iupper, ilower;
804     /* binary search to find index */
805     ilower = 0; iupper = mcd->calen;
806     while ((i = (iupper + ilower) >> 1) != ilower)
807     if ((long)target >= (long)mcd->carr[i])
808     ilower = i;
809     else
810     iupper = i;
811     /* localize random position */
812     randX = (randX*maxval - mcd->carr[ilower]) /
813     (double)(mcd->carr[iupper] - mcd->carr[ilower]);
814     /* convert index to vector */
815     if ((*mcd->ob_vec)(outVec, i, randX, mcd->ob_priv))
816     return SDEnone;
817     strcpy(SDerrorDetail, "BSDF sampling fault");
818     return SDEinternal;
819     }
820    
821     /* Fixed resolution BSDF methods */
822     SDFunc SDhandleMtx = {
823     &SDgetMtxBSDF,
824     &SDqueryMtxProjSA,
825     &SDgetMtxCDist,
826     &SDsampMtxCDist,
827     &SDfreeMatrix,
828     };