ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/common/bsdf_t.c
Revision: 3.6
Committed: Sun Apr 24 19:39:21 2011 UTC (13 years ago) by greg
Content type: text/plain
Branch: MAIN
Changes since 3.5: +437 -31 lines
Log Message:
Partial implementation of variable-resolution BSDFs

File Contents

# User Rev Content
1 greg 3.2 #ifndef lint
2 greg 3.6 static const char RCSid[] = "$Id: bsdf_t.c,v 3.5 2011/04/19 21:31:22 greg Exp $";
3 greg 3.2 #endif
4 greg 3.1 /*
5     * bsdf_t.c
6     *
7     * Definitions for variable-resolution BSDF trees
8     *
9     * Created by Greg Ward on 2/2/11.
10     *
11     */
12    
13 greg 3.3 #include "rtio.h"
14 greg 3.1 #include <stdlib.h>
15 greg 3.3 #include <math.h>
16     #include <ctype.h>
17 greg 3.1 #include "ezxml.h"
18     #include "bsdf.h"
19     #include "bsdf_t.h"
20 greg 3.6 #include "hilbert.h"
21    
22     /* Callback function type for SDtraverseTre() */
23     typedef int SDtreCallback(float val, const double *cmin,
24     double csiz, void *cptr);
25    
26     /* reference width maximum (1.0) */
27     static const unsigned iwmax = (1<<(sizeof(unsigned)*4))-1;
28    
29     /* Struct used for our distribution-building callback */
30     typedef struct {
31     int wmin; /* minimum square size so far */
32     int wmax; /* maximum square size */
33     int nic; /* number of input coordinates */
34     int alen; /* current array length */
35     int nall; /* number of allocated entries */
36     struct outdir_s {
37     unsigned hent; /* entering Hilbert index */
38     int wid; /* this square size */
39     float bsdf; /* BSDF for this square */
40     } *darr; /* output direction array */
41     } SDdistScaffold;
42 greg 3.1
43     /* Allocate a new scattering distribution node */
44     static SDNode *
45     SDnewNode(int nd, int lg)
46     {
47     SDNode *st;
48    
49     if (nd <= 0) {
50     strcpy(SDerrorDetail, "Zero dimension BSDF node request");
51     return NULL;
52     }
53     if (nd > SD_MAXDIM) {
54     sprintf(SDerrorDetail, "Illegal BSDF dimension (%d > %d)",
55     nd, SD_MAXDIM);
56     return NULL;
57     }
58     if (lg < 0) {
59     st = (SDNode *)malloc(sizeof(SDNode) +
60     ((1<<nd) - 1)*sizeof(st->u.t[0]));
61     if (st != NULL)
62 greg 3.5 memset(st->u.t, 0, sizeof(st->u.t[0])<<nd);
63 greg 3.1 } else
64     st = (SDNode *)malloc(sizeof(SDNode) +
65     ((1 << nd*lg) - 1)*sizeof(st->u.v[0]));
66    
67     if (st == NULL) {
68     if (lg < 0)
69     sprintf(SDerrorDetail,
70 greg 3.6 "Cannot allocate %d branch BSDF tree", 1<<nd);
71 greg 3.1 else
72     sprintf(SDerrorDetail,
73     "Cannot allocate %d BSDF leaves", 1 << nd*lg);
74     return NULL;
75     }
76     st->ndim = nd;
77     st->log2GR = lg;
78     return st;
79     }
80    
81     /* Free an SD tree */
82     static void
83 greg 3.6 SDfreeTre(SDNode *st)
84 greg 3.1 {
85     int i;
86    
87     if (st == NULL)
88     return;
89     for (i = (st->log2GR < 0) << st->ndim; i--; )
90 greg 3.5 SDfreeTre(st->u.t[i]);
91 greg 3.1 free((void *)st);
92     }
93    
94 greg 3.6 /* Free a variable-resolution BSDF */
95     static void
96     SDFreeBTre(void *p)
97     {
98     SDTre *sdt = (SDTre *)p;
99    
100     if (sdt == NULL)
101     return;
102     SDfreeTre(sdt->st);
103     free(sdt);
104     }
105 greg 3.5
106 greg 3.1 /* Add up N-dimensional hypercube array values over the given box */
107     static double
108     SDiterSum(const float *va, int nd, int siz, const int *imin, const int *imax)
109     {
110     double sum = .0;
111     unsigned skipsiz = 1;
112     int i;
113    
114     for (i = nd; --i > 0; )
115     skipsiz *= siz;
116     if (skipsiz == 1)
117     for (i = *imin; i < *imax; i++)
118     sum += va[i];
119     else
120     for (i = *imin; i < *imax; i++)
121     sum += SDiterSum(va + i*skipsiz,
122     nd-1, siz, imin+1, imax+1);
123     return sum;
124     }
125    
126     /* Average BSDF leaves over an orthotope defined by the unit hypercube */
127     static double
128 greg 3.6 SDavgTreBox(const SDNode *st, const double *bmin, const double *bmax)
129 greg 3.1 {
130     int imin[SD_MAXDIM], imax[SD_MAXDIM];
131     unsigned n;
132     int i;
133    
134     if (!st)
135     return .0;
136     /* check box limits */
137     for (i = st->ndim; i--; ) {
138     if (bmin[i] >= 1.)
139     return .0;
140     if (bmax[i] <= .0)
141     return .0;
142     if (bmin[i] >= bmax[i])
143     return .0;
144     }
145     if (st->log2GR < 0) { /* iterate on subtree */
146     double sum = .0, wsum = 1e-20;
147     double sbmin[SD_MAXDIM], sbmax[SD_MAXDIM], w;
148    
149     for (n = 1 << st->ndim; n--; ) {
150     w = 1.;
151     for (i = st->ndim; i--; ) {
152     sbmin[i] = 2.*bmin[i];
153     sbmax[i] = 2.*bmax[i];
154     if (n & 1<<i) {
155     sbmin[i] -= 1.;
156     sbmax[i] -= 1.;
157     }
158     if (sbmin[i] < .0) sbmin[i] = .0;
159     if (sbmax[i] > 1.) sbmax[i] = 1.;
160     w *= sbmax[i] - sbmin[i];
161     }
162     if (w > 1e-10) {
163 greg 3.6 sum += w * SDavgTreBox(st->u.t[n], sbmin, sbmax);
164 greg 3.1 wsum += w;
165     }
166     }
167     return sum / wsum;
168     }
169     n = 1; /* iterate over leaves */
170     for (i = st->ndim; i--; ) {
171     imin[i] = (bmin[i] <= .0) ? 0
172     : (int)((1 << st->log2GR)*bmin[i]);
173     imax[i] = (bmax[i] >= 1.) ? (1 << st->log2GR)
174     : (int)((1 << st->log2GR)*bmax[i] + .999999);
175     n *= imax[i] - imin[i];
176     }
177     if (!n)
178     return .0;
179    
180     return SDiterSum(st->u.v, st->ndim, 1 << st->log2GR, imin, imax) /
181     (double)n;
182     }
183    
184 greg 3.6 /* Recursive call for SDtraverseTre() */
185     static int
186     SDdotravTre(const SDNode *st, const double *pos, int cmask,
187     SDtreCallback *cf, void *cptr,
188     const double *cmin, double csiz)
189     {
190     int rv, rval = 0;
191     double bmin[SD_MAXDIM];
192     int i, n;
193     /* in branches? */
194     if (st->log2GR < 0) {
195     unsigned skipmask = 0;
196    
197     csiz *= .5;
198     for (i = st->ndim; i--; )
199     if (1<<i & cmask)
200     if (pos[i] < cmin[i] + csiz)
201     for (n = 1 << st->ndim; n--; )
202     if (n & 1<<i)
203     skipmask |= 1<<n;
204     else
205     for (n = 1 << st->ndim; n--; )
206     if (!(n & 1<<i))
207     skipmask |= 1<<n;
208     for (n = 1 << st->ndim; n--; ) {
209     if (1<<n & skipmask)
210     continue;
211     for (i = st->ndim; i--; )
212     if (1<<i & n)
213     bmin[i] = cmin[i] + csiz;
214     else
215     bmin[i] = cmin[i];
216    
217     rval += rv = SDdotravTre(st->u.t[n], pos, cmask,
218     cf, cptr, bmin, csiz);
219     if (rv < 0)
220     return rv;
221     }
222     } else { /* else traverse leaves */
223     int clim[SD_MAXDIM][2];
224     int cpos[SD_MAXDIM];
225    
226     if (st->log2GR == 0) /* short cut */
227     return (*cf)(st->u.v[0], cmin, csiz, cptr);
228    
229     csiz /= (double)(1 << st->log2GR);
230     /* assign coord. ranges */
231     for (i = st->ndim; i--; )
232     if (1<<i & cmask) {
233     clim[i][0] = (pos[i] - cmin[i])/csiz;
234     /* check overflow from f.p. error */
235     clim[i][0] -= clim[i][0] >> st->log2GR;
236     clim[i][1] = clim[i][0] + 1;
237     } else {
238     clim[i][0] = 0;
239     clim[i][1] = 1 << st->log2GR;
240     }
241     /* fill in unused dimensions */
242     for (i = SD_MAXDIM; i-- > st->ndim; ) {
243     clim[i][0] = 0; clim[i][1] = 1;
244     }
245     #if (SD_MAXDIM == 4)
246     bmin[0] = cmin[0] + csiz*clim[0][0];
247     for (cpos[0] = clim[0][0]; cpos[0] < clim[0][1]; cpos[0]++) {
248     bmin[1] = cmin[1] + csiz*clim[1][0];
249     for (cpos[1] = clim[1][0]; cpos[1] < clim[1][1]; cpos[1]++) {
250     bmin[2] = cmin[2] + csiz*clim[2][0];
251     for (cpos[2] = clim[2][0]; cpos[2] < clim[2][1]; cpos[2]++) {
252     bmin[3] = cmin[3] + csiz*(cpos[3] = clim[3][0]);
253     n = cpos[0];
254     for (i = 1; i < st->ndim; i++)
255     n = (n << st->log2GR) + cpos[i];
256     for ( ; cpos[3] < clim[3][1]; cpos[3]++) {
257     rval += rv = (*cf)(st->u.v[n++], bmin, csiz, cptr);
258     if (rv < 0)
259     return rv;
260     bmin[3] += csiz;
261     }
262     bmin[2] += csiz;
263     }
264     bmin[1] += csiz;
265     }
266     bmin[0] += csiz;
267     }
268     #else
269     _!_ "broken code segment!"
270     #endif
271     }
272     return rval;
273     }
274    
275     /* Traverse a tree, visiting nodes in a slice that fits partial position */
276     static int
277     SDtraverseTre(const SDNode *st, const double *pos, int cmask,
278     SDtreCallback *cf, void *cptr)
279     {
280     static double czero[SD_MAXDIM];
281     int i;
282     /* check arguments */
283     if ((st == NULL) | (cf == NULL))
284     return -1;
285     for (i = st->ndim; i--; )
286     if (1<<i & cmask && (pos[i] < 0) | (pos[i] >= 1.))
287     return -1;
288    
289     return SDdotravTre(st, pos, cmask, cf, cptr, czero, 1.);
290     }
291 greg 3.5
292     /* Look up tree value at the given grid position */
293     static float
294 greg 3.6 SDlookupTre(const SDNode *st, const double *pos, double *hcube)
295 greg 3.5 {
296     double spos[SD_MAXDIM];
297     int i, n, t;
298 greg 3.6 /* initialize voxel return */
299     if (hcube) {
300     hcube[i = st->ndim] = 1.;
301     while (i--)
302     hcube[i] = .0;
303     }
304 greg 3.5 /* climb the tree */
305     while (st->log2GR < 0) {
306     n = 0; /* move to appropriate branch */
307 greg 3.6 if (hcube) hcube[st->ndim] *= .5;
308 greg 3.5 for (i = st->ndim; i--; ) {
309     spos[i] = 2.*pos[i];
310     t = (spos[i] >= 1.);
311     n |= t<<i;
312     spos[i] -= (double)t;
313 greg 3.6 if (hcube) hcube[i] += (double)t * hcube[st->ndim];
314 greg 3.5 }
315     st = st->u.t[n]; /* avoids tail recursion */
316     pos = spos;
317     }
318 greg 3.6 if (st->log2GR == 0) /* short cut */
319     return st->u.v[0];
320 greg 3.5 n = t = 0; /* find grid array index */
321     for (i = st->ndim; i--; ) {
322     n += (int)((1<<st->log2GR)*pos[i]) << t;
323     t += st->log2GR;
324     }
325 greg 3.6 if (hcube) { /* compute final hypercube */
326     hcube[st->ndim] /= (double)(1<<st->log2GR);
327     for (i = st->ndim; i--; )
328     hcube[i] += floor((1<<st->log2GR)*pos[i])*hcube[st->ndim];
329     }
330     return st->u.v[n]; /* no interpolation */
331     }
332    
333     /* Query BSDF value and sample hypercube for the given vectors */
334     static float
335     SDqueryTre(const SDTre *sdt, const FVECT outVec, const FVECT inVec, double *hc)
336     {
337     static const FVECT zvec = {.0, .0, 1.};
338     FVECT rOutVec;
339     double gridPos[4];
340     /* check transmission */
341     if (!sdt->isxmit ^ outVec[2] > 0 ^ inVec[2] > 0)
342     return -1.;
343     /* convert vector coordinates */
344     if (sdt->st->ndim == 3) {
345     spinvector(rOutVec, outVec, zvec, -atan2(inVec[1],inVec[0]));
346     gridPos[0] = .5 - .5*sqrt(inVec[0]*inVec[0] + inVec[1]*inVec[1]);
347     SDdisk2square(gridPos+1, rOutVec[0], rOutVec[1]);
348     } else if (sdt->st->ndim == 4) {
349     SDdisk2square(gridPos, -inVec[0], -inVec[1]);
350     SDdisk2square(gridPos+2, outVec[0], outVec[1]);
351     } else
352     return -1.; /* should be internal error */
353    
354     return SDlookupTre(sdt->st, gridPos, hc);
355 greg 3.5 }
356    
357     /* Compute non-diffuse component for variable-resolution BSDF */
358     static int
359     SDgetTreBSDF(float coef[SDmaxCh], const FVECT outVec,
360 greg 3.6 const FVECT inVec, SDComponent *sdc)
361 greg 3.5 {
362 greg 3.6 /* check arguments */
363     if ((coef == NULL) | (outVec == NULL) | (inVec == NULL) | (sdc == NULL)
364     || sdc->dist == NULL)
365     return 0;
366 greg 3.5 /* get nearest BSDF value */
367 greg 3.6 coef[0] = SDqueryTre((SDTre *)sdc->dist, outVec, inVec, NULL);
368     return (coef[0] >= 0); /* monochromatic for now */
369     }
370    
371     /* Callback to build cumulative distribution using SDtraverseTre() */
372     static int
373     build_scaffold(float val, const double *cmin, double csiz, void *cptr)
374     {
375     SDdistScaffold *sp = (SDdistScaffold *)cptr;
376     int wid = csiz*(double)iwmax + .5;
377     bitmask_t bmin[2], bmax[2];
378    
379     cmin += sp->nic; /* skip to output coords */
380     if (wid < sp->wmin) /* new minimum width? */
381     sp->wmin = wid;
382     if (wid > sp->wmax) /* new maximum? */
383     sp->wmax = wid;
384     if (sp->alen >= sp->nall) { /* need more space? */
385     struct outdir_s *ndarr;
386     sp->nall += 8192;
387     ndarr = (struct outdir_s *)realloc(sp->darr,
388     sizeof(struct outdir_s)*sp->nall);
389     if (ndarr == NULL)
390     return -1; /* abort build */
391     sp->darr = ndarr;
392     }
393     /* find Hilbert entry index */
394     bmin[0] = cmin[0]*(double)iwmax + .5;
395     bmin[1] = cmin[1]*(double)iwmax + .5;
396     bmax[0] = bmin[0] + wid;
397     bmax[1] = bmin[1] + wid;
398     hilbert_box_vtx(2, sizeof(bitmask_t), sizeof(unsigned)*4,
399     1, bmin, bmax);
400     sp->darr[sp->alen].hent = hilbert_c2i(2, sizeof(unsigned)*4, bmin);
401     sp->darr[sp->alen].wid = wid;
402     sp->darr[sp->alen].bsdf = val;
403     sp->alen++; /* on to the next entry */
404     return 0;
405     }
406    
407     /* Scaffold comparison function for qsort -- ascending Hilbert index */
408     static int
409     sscmp(const void *p1, const void *p2)
410     {
411     return (int)((*(const struct outdir_s *)p1).hent -
412     (*(const struct outdir_s *)p2).hent);
413     }
414    
415     /* Create a new cumulative distribution for the given input direction */
416     static SDTreCDst *
417     make_cdist(const SDTre *sdt, const double *pos)
418     {
419     const unsigned cumlmax = ~0;
420     SDdistScaffold myScaffold;
421     SDTreCDst *cd;
422     struct outdir_s *sp;
423     double scale, cursum;
424     int i;
425     /* initialize scaffold */
426     myScaffold.wmin = iwmax;
427     myScaffold.wmax = 0;
428     myScaffold.nic = sdt->st->ndim - 2;
429     myScaffold.alen = 0;
430     myScaffold.nall = 8192;
431     myScaffold.darr = (struct outdir_s *)malloc(sizeof(struct outdir_s) *
432     myScaffold.nall);
433     if (myScaffold.darr == NULL)
434     return NULL;
435     /* grow the distribution */
436     if (SDtraverseTre(sdt->st, pos, (1<<myScaffold.nic)-1,
437     &build_scaffold, &myScaffold) < 0) {
438     free(myScaffold.darr);
439     return NULL;
440     }
441     /* allocate result holder */
442     cd = (SDTreCDst *)malloc(sizeof(SDTreCDst) +
443     sizeof(cd->carr[0])*myScaffold.alen);
444     if (cd == NULL) {
445     free(myScaffold.darr);
446     return NULL;
447     }
448     /* sort the distribution */
449     qsort(myScaffold.darr, cd->calen = myScaffold.alen,
450     sizeof(struct outdir_s), &sscmp);
451    
452     /* record input range */
453     scale = (double)myScaffold.wmin / iwmax;
454     for (i = myScaffold.nic; i--; ) {
455     cd->clim[i][0] = floor(pos[i]/scale + .5) * scale;
456     cd->clim[i][1] = cd->clim[i][0] + scale;
457     }
458     cd->max_psa = myScaffold.wmax / (double)iwmax;
459     cd->max_psa *= cd->max_psa * M_PI;
460     cd->isxmit = sdt->isxmit;
461     cd->cTotal = 1e-20; /* compute directional total */
462     sp = myScaffold.darr;
463     for (i = myScaffold.alen; i--; sp++)
464     cd->cTotal += sp->bsdf * (double)sp->wid * sp->wid;
465     cursum = .0; /* go back and get cumulative values */
466     scale = (double)cumlmax / cd->cTotal;
467     sp = myScaffold.darr;
468     for (i = 0; i < cd->calen; i++, sp++) {
469     cd->carr[i].cuml = scale*cursum + .5;
470     cursum += sp->bsdf * (double)sp->wid * sp->wid;
471     }
472     cd->carr[i].hndx = ~0; /* make final entry */
473     cd->carr[i].cuml = cumlmax;
474     cd->cTotal *= M_PI/(double)iwmax/iwmax;
475     /* all done, clean up and return */
476     free(myScaffold.darr);
477     return cd;
478     }
479    
480     /* Find or allocate a cumulative distribution for the given incoming vector */
481     const SDCDst *
482     SDgetTreCDist(const FVECT inVec, SDComponent *sdc)
483     {
484     const SDTre *sdt;
485     double inCoord[2];
486     int vflags;
487     int i;
488     SDTreCDst *cd, *cdlast;
489     /* check arguments */
490     if ((inVec == NULL) | (sdc == NULL) ||
491     (sdt = (SDTre *)sdc->dist) == NULL)
492     return NULL;
493     if (sdt->st->ndim == 3) /* isotropic BSDF? */
494     inCoord[0] = .5 - .5*sqrt(inVec[0]*inVec[0] + inVec[1]*inVec[1]);
495     else if (sdt->st->ndim == 4)
496     SDdisk2square(inCoord, -inVec[0], -inVec[1]);
497     else
498     return NULL; /* should be internal error */
499     cdlast = NULL; /* check for direction in cache list */
500     for (cd = (SDTreCDst *)sdc->cdList; cd != NULL;
501     cdlast = cd, cd = (SDTreCDst *)cd->next) {
502     for (i = sdt->st->ndim - 2; i--; )
503     if ((cd->clim[i][0] > inCoord[i]) |
504     (inCoord[i] >= cd->clim[i][1]))
505     break;
506     if (i < 0)
507     break; /* means we have a match */
508     }
509     if (cd == NULL) /* need to create new entry? */
510     cdlast = cd = make_cdist(sdt, inCoord);
511     if (cdlast != NULL) { /* move entry to head of cache list */
512     cdlast->next = cd->next;
513     cd->next = sdc->cdList;
514     sdc->cdList = (SDCDst *)cd;
515     }
516     return (SDCDst *)cd; /* ready to go */
517     }
518    
519     /* Query solid angle for vector(s) */
520     static SDError
521     SDqueryTreProjSA(double *psa, const FVECT v1, const RREAL *v2,
522     int qflags, SDComponent *sdc)
523     {
524     double myPSA[2];
525     /* check arguments */
526     if ((psa == NULL) | (v1 == NULL) | (sdc == NULL) ||
527     sdc->dist == NULL)
528     return SDEargument;
529     /* get projected solid angle(s) */
530     if (v2 != NULL) {
531     const SDTre *sdt = (SDTre *)sdc->dist;
532     double hcube[SD_MAXDIM];
533     if (SDqueryTre(sdt, v1, v2, hcube) < 0) {
534     if (qflags == SDqueryVal)
535     *psa = M_PI;
536     return SDEnone;
537     }
538     myPSA[0] = hcube[sdt->st->ndim];
539     myPSA[1] = myPSA[0] *= myPSA[0] * M_PI;
540     } else {
541     const SDTreCDst *cd = (const SDTreCDst *)SDgetTreCDist(v1, sdc);
542     if (cd == NULL)
543     return SDEmemory;
544     myPSA[0] = M_PI * (cd->clim[0][1] - cd->clim[0][0]) *
545     (cd->clim[1][1] - cd->clim[1][0]);
546     myPSA[1] = cd->max_psa;
547     }
548     switch (qflags) { /* record based on flag settings */
549     case SDqueryVal:
550     *psa = myPSA[0];
551     break;
552     case SDqueryMax:
553     if (myPSA[1] > *psa)
554     *psa = myPSA[1];
555     break;
556     case SDqueryMin+SDqueryMax:
557     if (myPSA[1] > psa[1])
558     psa[1] = myPSA[1];
559     /* fall through */
560     case SDqueryMin:
561     if (myPSA[0] < psa[0])
562     psa[0] = myPSA[0];
563     break;
564     }
565     return SDEnone;
566     }
567    
568     /* Sample cumulative distribution */
569     static SDError
570     SDsampTreCDist(FVECT ioVec, double randX, const SDCDst *cdp)
571     {
572     const unsigned nBitsC = 4*sizeof(bitmask_t);
573     const unsigned nExtraBits = 8*(sizeof(bitmask_t)-sizeof(unsigned));
574     const unsigned maxval = ~0;
575     const SDTreCDst *cd = (const SDTreCDst *)cdp;
576     const unsigned target = randX*maxval;
577     bitmask_t hndx, hcoord[2];
578     double gpos[3];
579     int i, iupper, ilower;
580     /* check arguments */
581     if ((ioVec == NULL) | (cd == NULL))
582     return SDEargument;
583     /* binary search to find position */
584     ilower = 0; iupper = cd->calen;
585     while ((i = (iupper + ilower) >> 1) != ilower)
586     if ((long)target >= (long)cd->carr[i].cuml)
587     ilower = i;
588     else
589     iupper = i;
590     /* localize random position */
591     randX = (randX*maxval - cd->carr[ilower].cuml) /
592     (double)(cd->carr[iupper].cuml - cd->carr[ilower].cuml);
593     /* index in longer Hilbert curve */
594     hndx = (randX*cd->carr[iupper].hndx + (1.-randX)*cd->carr[ilower].hndx)
595     * (double)((bitmask_t)1 << nExtraBits);
596     /* convert Hilbert index to vector */
597     hilbert_i2c(2, nBitsC, hndx, hcoord);
598     for (i = 2; i--; )
599     gpos[i] = ((double)hcoord[i] + rand()*(1./(RAND_MAX+.5))) /
600     (double)((bitmask_t)1 << nBitsC);
601     SDsquare2disk(gpos, gpos[0], gpos[1]);
602     gpos[2] = 1. - gpos[0]*gpos[0] - gpos[1]*gpos[1];
603     if (gpos[2] > 0) /* paranoia, I hope */
604     gpos[2] = sqrt(gpos[2]);
605     if (ioVec[2] > 0 ^ !cd->isxmit)
606     gpos[2] = -gpos[2];
607     VCOPY(ioVec, gpos);
608     return SDEnone;
609 greg 3.5 }
610    
611 greg 3.1 /* Load a variable-resolution BSDF tree from an open XML file */
612     SDError
613 greg 3.4 SDloadTre(SDData *sd, ezxml_t wtl)
614 greg 3.1 {
615     return SDEsupport;
616     }
617    
618     /* Variable resolution BSDF methods */
619 greg 3.5 SDFunc SDhandleTre = {
620     &SDgetTreBSDF,
621 greg 3.6 &SDqueryTreProjSA,
622     &SDgetTreCDist,
623     &SDsampTreCDist,
624     &SDFreeBTre,
625 greg 3.1 };