| 1 | greg | 2.1 | #ifndef lint | 
| 2 | greg | 2.17 | static const char RCSid[] = "$Id: bsdfrbf.c,v 2.16 2013/11/08 23:49:07 greg Exp $"; | 
| 3 | greg | 2.1 | #endif | 
| 4 |  |  | /* | 
| 5 |  |  | * Radial basis function representation for BSDF data. | 
| 6 |  |  | * | 
| 7 |  |  | *      G. Ward | 
| 8 |  |  | */ | 
| 9 |  |  |  | 
| 10 | greg | 2.13 | /**************************************************************** | 
| 11 |  |  | 1) Collect samples into a grid using the Shirley-Chiu | 
| 12 |  |  | angular mapping from a hemisphere to a square. | 
| 13 |  |  |  | 
| 14 |  |  | 2) Compute an adaptive quadtree by subdividing the grid so that | 
| 15 |  |  | each leaf node has at least one sample up to as many | 
| 16 |  |  | samples as fit nicely on a plane to within a certain | 
| 17 |  |  | MSE tolerance. | 
| 18 |  |  |  | 
| 19 |  |  | 3) Place one Gaussian lobe at each leaf node in the quadtree, | 
| 20 |  |  | sizing it to have a radius equal to the leaf size and | 
| 21 |  |  | a volume equal to the energy in that node. | 
| 22 |  |  | *****************************************************************/ | 
| 23 |  |  |  | 
| 24 | greg | 2.1 | #define _USE_MATH_DEFINES | 
| 25 |  |  | #include <stdio.h> | 
| 26 |  |  | #include <stdlib.h> | 
| 27 |  |  | #include <string.h> | 
| 28 |  |  | #include <math.h> | 
| 29 |  |  | #include "bsdfrep.h" | 
| 30 |  |  |  | 
| 31 | greg | 2.12 | #ifndef RSCA | 
| 32 |  |  | #define RSCA            2.2             /* radius scaling factor (empirical) */ | 
| 33 | greg | 2.9 | #endif | 
| 34 | greg | 2.12 | #ifndef SMOOTH_MSE | 
| 35 |  |  | #define SMOOTH_MSE      5e-5            /* acceptable mean squared error */ | 
| 36 | greg | 2.1 | #endif | 
| 37 | greg | 2.12 | #ifndef SMOOTH_MSER | 
| 38 |  |  | #define SMOOTH_MSER     0.07            /* acceptable relative MSE */ | 
| 39 | greg | 2.7 | #endif | 
| 40 | greg | 2.12 | #define MAX_RAD         (GRIDRES/8)     /* maximum lobe radius */ | 
| 41 |  |  |  | 
| 42 |  |  | #define RBFALLOCB       10              /* RBF allocation block size */ | 
| 43 |  |  |  | 
| 44 | greg | 2.1 | /* our loaded grid for this incident angle */ | 
| 45 |  |  | GRIDVAL                 dsf_grid[GRIDRES][GRIDRES]; | 
| 46 |  |  |  | 
| 47 |  |  | /* Start new DSF input grid */ | 
| 48 |  |  | void | 
| 49 |  |  | new_bsdf_data(double new_theta, double new_phi) | 
| 50 |  |  | { | 
| 51 |  |  | if (!new_input_direction(new_theta, new_phi)) | 
| 52 |  |  | exit(1); | 
| 53 |  |  | memset(dsf_grid, 0, sizeof(dsf_grid)); | 
| 54 |  |  | } | 
| 55 |  |  |  | 
| 56 |  |  | /* Add BSDF data point */ | 
| 57 |  |  | void | 
| 58 |  |  | add_bsdf_data(double theta_out, double phi_out, double val, int isDSF) | 
| 59 |  |  | { | 
| 60 |  |  | FVECT   ovec; | 
| 61 |  |  | int     pos[2]; | 
| 62 |  |  |  | 
| 63 |  |  | if (!output_orient)             /* check output orientation */ | 
| 64 |  |  | output_orient = 1 - 2*(theta_out > 90.); | 
| 65 |  |  | else if (output_orient > 0 ^ theta_out < 90.) { | 
| 66 |  |  | fputs("Cannot handle output angles on both sides of surface\n", | 
| 67 |  |  | stderr); | 
| 68 |  |  | exit(1); | 
| 69 |  |  | } | 
| 70 |  |  | ovec[2] = sin((M_PI/180.)*theta_out); | 
| 71 |  |  | ovec[0] = cos((M_PI/180.)*phi_out) * ovec[2]; | 
| 72 |  |  | ovec[1] = sin((M_PI/180.)*phi_out) * ovec[2]; | 
| 73 |  |  | ovec[2] = sqrt(1. - ovec[2]*ovec[2]); | 
| 74 |  |  |  | 
| 75 | greg | 2.8 | if (val <= 0)                   /* truncate to zero */ | 
| 76 |  |  | val = 0; | 
| 77 |  |  | else if (!isDSF) | 
| 78 | greg | 2.1 | val *= ovec[2];         /* convert from BSDF to DSF */ | 
| 79 |  |  |  | 
| 80 | greg | 2.4 | /* update BSDF histogram */ | 
| 81 |  |  | if (val < BSDF2BIG*ovec[2] && val > BSDF2SML*ovec[2]) | 
| 82 |  |  | ++bsdf_hist[histndx(val/ovec[2])]; | 
| 83 |  |  |  | 
| 84 | greg | 2.1 | pos_from_vec(pos, ovec); | 
| 85 |  |  |  | 
| 86 |  |  | dsf_grid[pos[0]][pos[1]].vsum += val; | 
| 87 |  |  | dsf_grid[pos[0]][pos[1]].nval++; | 
| 88 |  |  | } | 
| 89 |  |  |  | 
| 90 | greg | 2.11 | /* Compute minimum BSDF from histogram (does not clear) */ | 
| 91 | greg | 2.5 | static void | 
| 92 |  |  | comp_bsdf_min() | 
| 93 |  |  | { | 
| 94 | greg | 2.17 | unsigned long   cnt, target; | 
| 95 |  |  | int             i; | 
| 96 | greg | 2.5 |  | 
| 97 |  |  | cnt = 0; | 
| 98 |  |  | for (i = HISTLEN; i--; ) | 
| 99 |  |  | cnt += bsdf_hist[i]; | 
| 100 |  |  | if (!cnt) {                             /* shouldn't happen */ | 
| 101 |  |  | bsdf_min = 0; | 
| 102 |  |  | return; | 
| 103 |  |  | } | 
| 104 |  |  | target = cnt/100;                       /* ignore bottom 1% */ | 
| 105 |  |  | cnt = 0; | 
| 106 |  |  | for (i = 0; cnt <= target; i++) | 
| 107 |  |  | cnt += bsdf_hist[i]; | 
| 108 |  |  | bsdf_min = histval(i-1); | 
| 109 |  |  | } | 
| 110 |  |  |  | 
| 111 | greg | 2.12 | /* Determine if the given region is empty of grid samples */ | 
| 112 | greg | 2.6 | static int | 
| 113 | greg | 2.12 | empty_region(int x0, int x1, int y0, int y1) | 
| 114 | greg | 2.6 | { | 
| 115 | greg | 2.12 | int     x, y; | 
| 116 |  |  |  | 
| 117 |  |  | for (x = x0; x < x1; x++) | 
| 118 |  |  | for (y = y0; y < y1; y++) | 
| 119 |  |  | if (dsf_grid[x][y].nval) | 
| 120 |  |  | return(0); | 
| 121 |  |  | return(1); | 
| 122 |  |  | } | 
| 123 |  |  |  | 
| 124 |  |  | /* Determine if the given region is smooth enough to be a single lobe */ | 
| 125 |  |  | static int | 
| 126 |  |  | smooth_region(int x0, int x1, int y0, int y1) | 
| 127 |  |  | { | 
| 128 |  |  | RREAL   rMtx[3][3]; | 
| 129 |  |  | FVECT   xvec; | 
| 130 |  |  | double  A, B, C, nvs, sqerr; | 
| 131 |  |  | int     x, y, n; | 
| 132 |  |  | /* compute planar regression */ | 
| 133 |  |  | memset(rMtx, 0, sizeof(rMtx)); | 
| 134 |  |  | memset(xvec, 0, sizeof(xvec)); | 
| 135 |  |  | for (x = x0; x < x1; x++) | 
| 136 |  |  | for (y = y0; y < y1; y++) | 
| 137 |  |  | if ((n = dsf_grid[x][y].nval) > 0) { | 
| 138 |  |  | double  z = dsf_grid[x][y].vsum; | 
| 139 | greg | 2.13 | rMtx[0][0] += x*x*(double)n; | 
| 140 |  |  | rMtx[0][1] += x*y*(double)n; | 
| 141 |  |  | rMtx[0][2] += x*(double)n; | 
| 142 |  |  | rMtx[1][1] += y*y*(double)n; | 
| 143 |  |  | rMtx[1][2] += y*(double)n; | 
| 144 |  |  | rMtx[2][2] += (double)n; | 
| 145 | greg | 2.12 | xvec[0] += x*z; | 
| 146 |  |  | xvec[1] += y*z; | 
| 147 |  |  | xvec[2] += z; | 
| 148 |  |  | } | 
| 149 |  |  | rMtx[1][0] = rMtx[0][1]; | 
| 150 | greg | 2.15 | rMtx[2][0] = rMtx[0][2]; | 
| 151 | greg | 2.12 | rMtx[2][1] = rMtx[1][2]; | 
| 152 |  |  | nvs = rMtx[2][2]; | 
| 153 |  |  | if (SDinvXform(rMtx, rMtx) != SDEnone) | 
| 154 | greg | 2.16 | return(1);              /* colinear values */ | 
| 155 | greg | 2.12 | A = DOT(rMtx[0], xvec); | 
| 156 |  |  | B = DOT(rMtx[1], xvec); | 
| 157 |  |  | C = DOT(rMtx[2], xvec); | 
| 158 |  |  | sqerr = 0.0;                    /* compute mean squared error */ | 
| 159 |  |  | for (x = x0; x < x1; x++) | 
| 160 |  |  | for (y = y0; y < y1; y++) | 
| 161 |  |  | if ((n = dsf_grid[x][y].nval) > 0) { | 
| 162 |  |  | double  d = A*x + B*y + C - dsf_grid[x][y].vsum/n; | 
| 163 |  |  | sqerr += n*d*d; | 
| 164 | greg | 2.6 | } | 
| 165 | greg | 2.12 | if (sqerr <= nvs*SMOOTH_MSE)    /* below absolute MSE threshold? */ | 
| 166 |  |  | return(1); | 
| 167 | greg | 2.13 | /* OR below relative MSE threshold? */ | 
| 168 | greg | 2.12 | return(sqerr*nvs <= xvec[2]*xvec[2]*SMOOTH_MSER); | 
| 169 |  |  | } | 
| 170 |  |  |  | 
| 171 |  |  | /* Create new lobe based on integrated samples in region */ | 
| 172 |  |  | static void | 
| 173 |  |  | create_lobe(RBFVAL *rvp, int x0, int x1, int y0, int y1) | 
| 174 |  |  | { | 
| 175 |  |  | double  vtot = 0.0; | 
| 176 |  |  | int     nv = 0; | 
| 177 |  |  | double  rad; | 
| 178 |  |  | int     x, y; | 
| 179 |  |  | /* compute average for region */ | 
| 180 |  |  | for (x = x0; x < x1; x++) | 
| 181 |  |  | for (y = y0; y < y1; y++) { | 
| 182 |  |  | vtot += dsf_grid[x][y].vsum; | 
| 183 |  |  | nv += dsf_grid[x][y].nval; | 
| 184 |  |  | } | 
| 185 |  |  | if (!nv) { | 
| 186 |  |  | fprintf(stderr, "%s: internal - missing samples in create_lobe\n", | 
| 187 |  |  | progname); | 
| 188 |  |  | exit(1); | 
| 189 | greg | 2.6 | } | 
| 190 | greg | 2.12 | /* peak value based on integral */ | 
| 191 |  |  | vtot *= (x1-x0)*(y1-y0)*(2.*M_PI/GRIDRES/GRIDRES)/(double)nv; | 
| 192 |  |  | rad = (RSCA/(double)GRIDRES)*(x1-x0); | 
| 193 |  |  | rvp->peak =  vtot / ((2.*M_PI) * rad*rad); | 
| 194 |  |  | rvp->crad = ANG2R(rad); | 
| 195 |  |  | rvp->gx = (x0+x1)>>1; | 
| 196 |  |  | rvp->gy = (y0+y1)>>1; | 
| 197 | greg | 2.6 | } | 
| 198 |  |  |  | 
| 199 | greg | 2.12 | /* Recursive function to build radial basis function representation */ | 
| 200 | greg | 2.6 | static int | 
| 201 | greg | 2.12 | build_rbfrep(RBFVAL **arp, int *np, int x0, int x1, int y0, int y1) | 
| 202 | greg | 2.6 | { | 
| 203 | greg | 2.12 | int     xmid = (x0+x1)>>1; | 
| 204 |  |  | int     ymid = (y0+y1)>>1; | 
| 205 |  |  | int     branched[4]; | 
| 206 |  |  | int     nadded, nleaves; | 
| 207 |  |  | /* need to make this a leaf? */ | 
| 208 |  |  | if (empty_region(x0, xmid, y0, ymid) || | 
| 209 |  |  | empty_region(xmid, x1, y0, ymid) || | 
| 210 |  |  | empty_region(x0, xmid, ymid, y1) || | 
| 211 |  |  | empty_region(xmid, x1, ymid, y1)) | 
| 212 |  |  | return(0); | 
| 213 |  |  | /* add children (branches+leaves) */ | 
| 214 |  |  | if ((branched[0] = build_rbfrep(arp, np, x0, xmid, y0, ymid)) < 0) | 
| 215 |  |  | return(-1); | 
| 216 |  |  | if ((branched[1] = build_rbfrep(arp, np, xmid, x1, y0, ymid)) < 0) | 
| 217 |  |  | return(-1); | 
| 218 |  |  | if ((branched[2] = build_rbfrep(arp, np, x0, xmid, ymid, y1)) < 0) | 
| 219 |  |  | return(-1); | 
| 220 |  |  | if ((branched[3] = build_rbfrep(arp, np, xmid, x1, ymid, y1)) < 0) | 
| 221 |  |  | return(-1); | 
| 222 |  |  | nadded = branched[0] + branched[1] + branched[2] + branched[3]; | 
| 223 |  |  | nleaves = !branched[0] + !branched[1] + !branched[2] + !branched[3]; | 
| 224 |  |  | if (!nleaves)                   /* nothing but branches? */ | 
| 225 |  |  | return(nadded); | 
| 226 |  |  | /* combine 4 leaves into 1? */ | 
| 227 | greg | 2.14 | if ((nleaves == 4) & (x1-x0 <= MAX_RAD) && | 
| 228 |  |  | smooth_region(x0, x1, y0, y1)) | 
| 229 | greg | 2.12 | return(0); | 
| 230 |  |  | /* need more array space? */ | 
| 231 |  |  | if ((*np+nleaves-1)>>RBFALLOCB != (*np-1)>>RBFALLOCB) { | 
| 232 |  |  | *arp = (RBFVAL *)realloc(*arp, | 
| 233 |  |  | sizeof(RBFVAL)*(*np+nleaves-1+(1<<RBFALLOCB))); | 
| 234 |  |  | if (*arp == NULL) | 
| 235 |  |  | return(-1); | 
| 236 | greg | 2.6 | } | 
| 237 | greg | 2.12 | /* create lobes for leaves */ | 
| 238 |  |  | if (!branched[0]) | 
| 239 |  |  | create_lobe(*arp+(*np)++, x0, xmid, y0, ymid); | 
| 240 |  |  | if (!branched[1]) | 
| 241 |  |  | create_lobe(*arp+(*np)++, xmid, x1, y0, ymid); | 
| 242 |  |  | if (!branched[2]) | 
| 243 |  |  | create_lobe(*arp+(*np)++, x0, xmid, ymid, y1); | 
| 244 |  |  | if (!branched[3]) | 
| 245 |  |  | create_lobe(*arp+(*np)++, xmid, x1, ymid, y1); | 
| 246 |  |  | nadded += nleaves; | 
| 247 |  |  | return(nadded); | 
| 248 | greg | 2.6 | } | 
| 249 |  |  |  | 
| 250 | greg | 2.1 | /* Count up filled nodes and build RBF representation from current grid */ | 
| 251 |  |  | RBFNODE * | 
| 252 | greg | 2.12 | make_rbfrep() | 
| 253 | greg | 2.1 | { | 
| 254 | greg | 2.12 | RBFNODE *newnode; | 
| 255 |  |  | RBFVAL  *rbfarr; | 
| 256 | greg | 2.1 | int     nn; | 
| 257 | greg | 2.5 | /* compute minimum BSDF */ | 
| 258 |  |  | comp_bsdf_min(); | 
| 259 | greg | 2.12 | /* create RBF node list */ | 
| 260 |  |  | rbfarr = NULL; nn = 0; | 
| 261 |  |  | if (build_rbfrep(&rbfarr, &nn, 0, GRIDRES, 0, GRIDRES) <= 0) | 
| 262 |  |  | goto memerr; | 
| 263 |  |  | /* (re)allocate RBF array */ | 
| 264 |  |  | newnode = (RBFNODE *)realloc(rbfarr, | 
| 265 |  |  | sizeof(RBFNODE) + sizeof(RBFVAL)*(nn-1)); | 
| 266 | greg | 2.2 | if (newnode == NULL) | 
| 267 |  |  | goto memerr; | 
| 268 | greg | 2.12 | /* copy computed lobes into RBF node */ | 
| 269 |  |  | memmove(newnode->rbfa, newnode, sizeof(RBFVAL)*nn); | 
| 270 | greg | 2.1 | newnode->ord = -1; | 
| 271 |  |  | newnode->next = NULL; | 
| 272 |  |  | newnode->ejl = NULL; | 
| 273 |  |  | newnode->invec[2] = sin((M_PI/180.)*theta_in_deg); | 
| 274 |  |  | newnode->invec[0] = cos((M_PI/180.)*phi_in_deg)*newnode->invec[2]; | 
| 275 |  |  | newnode->invec[1] = sin((M_PI/180.)*phi_in_deg)*newnode->invec[2]; | 
| 276 |  |  | newnode->invec[2] = input_orient*sqrt(1. - newnode->invec[2]*newnode->invec[2]); | 
| 277 | greg | 2.12 | newnode->vtotal = .0; | 
| 278 | greg | 2.1 | newnode->nrbf = nn; | 
| 279 | greg | 2.12 | /* compute sum for normalization */ | 
| 280 |  |  | while (nn-- > 0) | 
| 281 |  |  | newnode->vtotal += rbf_volume(&newnode->rbfa[nn]); | 
| 282 | greg | 2.3 | #ifdef DEBUG | 
| 283 | greg | 2.12 | fprintf(stderr, "Built RBF with %d lobes\n", newnode->nrbf); | 
| 284 | greg | 2.3 | fprintf(stderr, "Integrated DSF at (%.1f,%.1f) deg. is %.2f\n", | 
| 285 |  |  | get_theta180(newnode->invec), get_phi360(newnode->invec), | 
| 286 |  |  | newnode->vtotal); | 
| 287 |  |  | #endif | 
| 288 | greg | 2.1 | insert_dsf(newnode); | 
| 289 |  |  |  | 
| 290 |  |  | return(newnode); | 
| 291 | greg | 2.2 | memerr: | 
| 292 |  |  | fprintf(stderr, "%s: Out of memory in make_rbfrep()\n", progname); | 
| 293 |  |  | exit(1); | 
| 294 | greg | 2.1 | } |