ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/cv/bsdfrbf.c
(Generate patch)

Comparing ray/src/cv/bsdfrbf.c (file contents):
Revision 2.9 by greg, Thu Oct 17 19:09:11 2013 UTC vs.
Revision 2.16 by greg, Fri Nov 8 23:49:07 2013 UTC

# Line 7 | Line 7 | static const char RCSid[] = "$Id$";
7   *      G. Ward
8   */
9  
10 + /****************************************************************
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   #define _USE_MATH_DEFINES
25   #include <stdio.h>
26   #include <stdlib.h>
# Line 14 | Line 28 | static const char RCSid[] = "$Id$";
28   #include <math.h>
29   #include "bsdfrep.h"
30  
31 < #ifndef MINRSCA
32 < #define MINRSCA         0.15            /* minimum radius scaling factor */
31 > #ifndef RSCA
32 > #define RSCA            2.2             /* radius scaling factor (empirical) */
33   #endif
34 < #ifndef MAXRSCA
35 < #define MAXRSCA         2.7             /* maximum radius scaling factor */
34 > #ifndef SMOOTH_MSE
35 > #define SMOOTH_MSE      5e-5            /* acceptable mean squared error */
36   #endif
37 < #ifndef MAXFRAC
38 < #define MAXFRAC         0.5             /* maximum contribution to neighbor */
37 > #ifndef SMOOTH_MSER
38 > #define SMOOTH_MSER     0.07            /* acceptable relative MSE */
39   #endif
40 < #ifndef NNEIGH
41 < #define NNEIGH          10              /* number of neighbors to consider */
42 < #endif
40 > #define MAX_RAD         (GRIDRES/8)     /* maximum lobe radius */
41 >
42 > #define RBFALLOCB       10              /* RBF allocation block size */
43 >
44                                  /* our loaded grid for this incident angle */
45   GRIDVAL                 dsf_grid[GRIDRES][GRIDRES];
46  
# Line 72 | Line 87 | add_bsdf_data(double theta_out, double phi_out, double
87          dsf_grid[pos[0]][pos[1]].nval++;
88   }
89  
90 < /* Compute radii for non-empty bins */
76 < /* (distance to furthest empty bin for which non-empty test bin is closest) */
90 > /* Compute minimum BSDF from histogram (does not clear) */
91   static void
78 compute_radii(void)
79 {
80        const int       cradmin = ANG2R(.5*M_PI/GRIDRES);
81        unsigned int    fill_grid[GRIDRES][GRIDRES];
82        unsigned short  fill_cnt[GRIDRES][GRIDRES];
83        FVECT           ovec0, ovec1;
84        double          ang2, lastang2;
85        int             r, i, j, jn, ii, jj, inear, jnear;
86
87        for (i = 0; i < GRIDRES; i++)           /* initialize minimum radii */
88            for (j = 0; j < GRIDRES; j++)
89                if (dsf_grid[i][j].nval)
90                    dsf_grid[i][j].crad = cradmin;
91
92        r = GRIDRES/2;                          /* proceed in zig-zag */
93        for (i = 0; i < GRIDRES; i++)
94            for (jn = 0; jn < GRIDRES; jn++) {
95                j = (i&1) ? jn : GRIDRES-1-jn;
96                if (dsf_grid[i][j].nval)        /* find empty grid pos. */
97                        continue;
98                ovec_from_pos(ovec0, i, j);
99                inear = jnear = -1;             /* find nearest non-empty */
100                lastang2 = M_PI*M_PI;
101                for (ii = i-r; ii <= i+r; ii++) {
102                    if (ii < 0) continue;
103                    if (ii >= GRIDRES) break;
104                    for (jj = j-r; jj <= j+r; jj++) {
105                        if (jj < 0) continue;
106                        if (jj >= GRIDRES) break;
107                        if (!dsf_grid[ii][jj].nval)
108                                continue;
109                        ovec_from_pos(ovec1, ii, jj);
110                        ang2 = 2. - 2.*DOT(ovec0,ovec1);
111                        if (ang2 >= lastang2)
112                                continue;
113                        lastang2 = ang2;
114                        inear = ii; jnear = jj;
115                    }
116                }
117                if (inear < 0) {
118                        fprintf(stderr,
119                                "%s: Could not find non-empty neighbor!\n",
120                                        progname);
121                        exit(1);
122                }
123                ang2 = sqrt(lastang2);
124                r = ANG2R(ang2);                /* record if > previous */
125                if (r > dsf_grid[inear][jnear].crad)
126                        dsf_grid[inear][jnear].crad = r;
127                                                /* next search radius */
128                r = ang2*(2.*GRIDRES/M_PI) + 3;
129            }
130                                                /* blur radii over hemisphere */
131        memset(fill_grid, 0, sizeof(fill_grid));
132        memset(fill_cnt, 0, sizeof(fill_cnt));
133        for (i = 0; i < GRIDRES; i++)
134            for (j = 0; j < GRIDRES; j++) {
135                if (!dsf_grid[i][j].nval)
136                        continue;               /* not part of this */
137                r = R2ANG(dsf_grid[i][j].crad)*(2.*MAXRSCA*GRIDRES/M_PI);
138                for (ii = i-r; ii <= i+r; ii++) {
139                    if (ii < 0) continue;
140                    if (ii >= GRIDRES) break;
141                    for (jj = j-r; jj <= j+r; jj++) {
142                        if (jj < 0) continue;
143                        if (jj >= GRIDRES) break;
144                        if ((ii-i)*(ii-i) + (jj-j)*(jj-j) > r*r)
145                                continue;
146                        fill_grid[ii][jj] += dsf_grid[i][j].crad;
147                        fill_cnt[ii][jj]++;
148                    }
149                }
150            }
151                                                /* copy back blurred radii */
152        for (i = 0; i < GRIDRES; i++)
153            for (j = 0; j < GRIDRES; j++)
154                if (fill_cnt[i][j])
155                        dsf_grid[i][j].crad = fill_grid[i][j]/fill_cnt[i][j];
156 }
157
158 /* Cull points for more uniform distribution, leave all nval 0 or 1 */
159 static void
160 cull_values(void)
161 {
162        FVECT   ovec0, ovec1;
163        double  maxang, maxang2;
164        int     i, j, ii, jj, r;
165                                                /* simple greedy algorithm */
166        for (i = 0; i < GRIDRES; i++)
167            for (j = 0; j < GRIDRES; j++) {
168                if (!dsf_grid[i][j].nval)
169                        continue;
170                if (!dsf_grid[i][j].crad)
171                        continue;               /* shouldn't happen */
172                ovec_from_pos(ovec0, i, j);
173                maxang = 2.*R2ANG(dsf_grid[i][j].crad);
174                if (maxang > ovec0[2])          /* clamp near horizon */
175                        maxang = ovec0[2];
176                r = maxang*(2.*GRIDRES/M_PI) + 1;
177                maxang2 = maxang*maxang;
178                for (ii = i-r; ii <= i+r; ii++) {
179                    if (ii < 0) continue;
180                    if (ii >= GRIDRES) break;
181                    for (jj = j-r; jj <= j+r; jj++) {
182                        if ((ii == i) & (jj == j))
183                                continue;       /* don't get self-absorbed */
184                        if (jj < 0) continue;
185                        if (jj >= GRIDRES) break;
186                        if (!dsf_grid[ii][jj].nval)
187                                continue;
188                        ovec_from_pos(ovec1, ii, jj);
189                        if (2. - 2.*DOT(ovec0,ovec1) >= maxang2)
190                                continue;
191                                                /* absorb sum */
192                        dsf_grid[i][j].vsum += dsf_grid[ii][jj].vsum;
193                        dsf_grid[i][j].nval += dsf_grid[ii][jj].nval;
194                                                /* keep value, though */
195                        dsf_grid[ii][jj].vsum /= (float)dsf_grid[ii][jj].nval;
196                        dsf_grid[ii][jj].nval = 0;
197                    }
198                }
199            }
200                                                /* final averaging pass */
201        for (i = 0; i < GRIDRES; i++)
202            for (j = 0; j < GRIDRES; j++)
203                if (dsf_grid[i][j].nval > 1) {
204                        dsf_grid[i][j].vsum /= (float)dsf_grid[i][j].nval;
205                        dsf_grid[i][j].nval = 1;
206                }
207 }
208
209 /* Compute minimum BSDF from histogram and clear it */
210 static void
92   comp_bsdf_min()
93   {
94          int     cnt;
# Line 225 | Line 106 | comp_bsdf_min()
106          for (i = 0; cnt <= target; i++)
107                  cnt += bsdf_hist[i];
108          bsdf_min = histval(i-1);
228        memset(bsdf_hist, 0, sizeof(bsdf_hist));
109   }
110  
111 < /* Find n nearest sub-sampled neighbors to the given grid position */
111 > /* Determine if the given region is empty of grid samples */
112   static int
113 < get_neighbors(int neigh[][2], int n, const int i, const int j)
113 > empty_region(int x0, int x1, int y0, int y1)
114   {
115 <        int     k = 0;
116 <        int     r;
117 <                                                /* search concentric squares */
118 <        for (r = 1; r < GRIDRES; r++) {
119 <                int     ii, jj;
120 <                for (ii = i-r; ii <= i+r; ii++) {
121 <                        int     jstep = 1;
122 <                        if (ii < 0) continue;
123 <                        if (ii >= GRIDRES) break;
124 <                        if ((i-r < ii) & (ii < i+r))
125 <                                jstep = r<<1;
126 <                        for (jj = j-r; jj <= j+r; jj += jstep) {
127 <                                if (jj < 0) continue;
128 <                                if (jj >= GRIDRES) break;
129 <                                if (dsf_grid[ii][jj].nval) {
130 <                                        neigh[k][0] = ii;
131 <                                        neigh[k][1] = jj;
132 <                                        if (++k >= n)
133 <                                                return(n);
134 <                                }
135 <                        }
115 >        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 >                        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 >                        xvec[0] += x*z;
146 >                        xvec[1] += y*z;
147 >                        xvec[2] += z;
148                  }
149 +        rMtx[1][0] = rMtx[0][1];
150 +        rMtx[2][0] = rMtx[0][2];
151 +        rMtx[2][1] = rMtx[1][2];
152 +        nvs = rMtx[2][2];
153 +        if (SDinvXform(rMtx, rMtx) != SDEnone)
154 +                return(1);              /* colinear values */
155 +        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 +                }
165 +        if (sqerr <= nvs*SMOOTH_MSE)    /* below absolute MSE threshold? */
166 +                return(1);
167 +                                        /* OR below relative MSE threshold? */
168 +        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          }
190 <        return(k);
190 >                                        /* 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   }
198  
199 < /* Adjust coded radius for the given grid position based on neighborhood */
199 > /* Recursive function to build radial basis function representation */
200   static int
201 < adj_coded_radius(const int i, const int j)
201 > build_rbfrep(RBFVAL **arp, int *np, int x0, int x1, int y0, int y1)
202   {
203 <        const double    rad0 = R2ANG(dsf_grid[i][j].crad);
204 <        const double    minrad = MINRSCA * rad0;
205 <        double          currad = MAXRSCA * rad0;
206 <        int             neigh[NNEIGH][2];
207 <        int             n;
208 <        FVECT           our_dir;
209 <
210 <        ovec_from_pos(our_dir, i, j);
211 <        n = get_neighbors(neigh, NNEIGH, i, j);
212 <        while (n--) {
213 <                FVECT   their_dir;
214 <                double  max_ratio, rad_ok2;
215 <                                                /* check our value at neighbor */
216 <                ovec_from_pos(their_dir, neigh[n][0], neigh[n][1]);
217 <                max_ratio = MAXFRAC * dsf_grid[neigh[n][0]][neigh[n][1]].vsum
218 <                                / dsf_grid[i][j].vsum;
219 <                if (max_ratio >= 1)
220 <                        continue;
221 <                rad_ok2 = (DOT(their_dir,our_dir) - 1.)/log(max_ratio);
222 <                if (rad_ok2 >= currad*currad)
223 <                        continue;               /* value fraction OK */
224 <                currad = sqrt(rad_ok2);         /* else reduce lobe radius */
225 <                if (currad <= minrad)           /* limit how small we'll go */
226 <                        return(ANG2R(minrad));
203 >        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 >        if ((nleaves == 4) & (x1-x0 <= MAX_RAD) &&
228 >                        smooth_region(x0, x1, y0, y1))
229 >                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          }
237 <        return(ANG2R(currad));                  /* encode selected radius */
237 >                                        /* 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   }
249  
250   /* Count up filled nodes and build RBF representation from current grid */
251   RBFNODE *
252 < make_rbfrep(void)
252 > make_rbfrep()
253   {
297        long    cradsum = 0, ocradsum = 0;
298        int     niter = 16;
299        double  lastVar, thisVar = 100.;
300        int     nn;
254          RBFNODE *newnode;
255 <        RBFVAL  *itera;
256 <        int     i, j;
304 <
305 < #ifdef DEBUG
306 < {
307 <        int     maxcnt = 0, nempty = 0;
308 <        long    cntsum = 0;
309 <        for (i = 0; i < GRIDRES; i++)
310 <            for (j = 0; j < GRIDRES; j++)
311 <                if (!dsf_grid[i][j].nval) {
312 <                        ++nempty;
313 <                } else {
314 <                        if (dsf_grid[i][j].nval > maxcnt)
315 <                                maxcnt = dsf_grid[i][j].nval;
316 <                        cntsum += dsf_grid[i][j].nval;
317 <                }
318 <        fprintf(stderr, "Average, maximum bin count: %d, %d (%.1f%% empty)\n",
319 <                        (int)(cntsum/((GRIDRES*GRIDRES)-nempty)), maxcnt,
320 <                        100./(GRIDRES*GRIDRES)*nempty);
321 < }
322 < #endif
323 <                                /* compute RBF radii */
324 <        compute_radii();
325 <                                /* coagulate lobes */
326 <        cull_values();
327 <        nn = 0;                 /* count selected bins */
328 <        for (i = 0; i < GRIDRES; i++)
329 <            for (j = 0; j < GRIDRES; j++)
330 <                nn += dsf_grid[i][j].nval;
255 >        RBFVAL  *rbfarr;
256 >        int     nn;
257                                  /* compute minimum BSDF */
258          comp_bsdf_min();
259 <                                /* allocate RBF array */
260 <        newnode = (RBFNODE *)malloc(sizeof(RBFNODE) + sizeof(RBFVAL)*(nn-1));
259 >                                /* 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          if (newnode == NULL)
267                  goto memerr;
268 +                                /* copy computed lobes into RBF node */
269 +        memmove(newnode->rbfa, newnode, sizeof(RBFVAL)*nn);
270          newnode->ord = -1;
271          newnode->next = NULL;
272          newnode->ejl = NULL;
# Line 341 | Line 274 | make_rbfrep(void)
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 <        newnode->vtotal = 0;
277 >        newnode->vtotal = .0;
278          newnode->nrbf = nn;
279 <        nn = 0;                 /* fill RBF array */
280 <        for (i = 0; i < GRIDRES; i++)
281 <            for (j = 0; j < GRIDRES; j++)
349 <                if (dsf_grid[i][j].nval) {
350 <                        newnode->rbfa[nn].peak = dsf_grid[i][j].vsum;
351 <                        ocradsum += dsf_grid[i][j].crad;
352 <                        cradsum +=
353 <                        newnode->rbfa[nn].crad = adj_coded_radius(i, j);
354 <                        newnode->rbfa[nn].gx = i;
355 <                        newnode->rbfa[nn].gy = j;
356 <                        ++nn;
357 <                }
279 >                                /* compute sum for normalization */
280 >        while (nn-- > 0)
281 >                newnode->vtotal += rbf_volume(&newnode->rbfa[nn]);
282   #ifdef DEBUG
283 <        fprintf(stderr,
360 <        "Average radius reduced from %.2f to %.2f degrees for %d lobes\n",
361 <                        180./M_PI*MAXRSCA*R2ANG(ocradsum/newnode->nrbf),
362 <                        180./M_PI*R2ANG(cradsum/newnode->nrbf), newnode->nrbf);
363 < #endif
364 <                                /* iterate to improve interpolation accuracy */
365 <        itera = (RBFVAL *)malloc(sizeof(RBFVAL)*newnode->nrbf);
366 <        if (itera == NULL)
367 <                goto memerr;
368 <        memcpy(itera, newnode->rbfa, sizeof(RBFVAL)*newnode->nrbf);
369 <        do {
370 <                double  dsum = 0, dsum2 = 0;
371 <                nn = 0;
372 <                for (i = 0; i < GRIDRES; i++)
373 <                    for (j = 0; j < GRIDRES; j++)
374 <                        if (dsf_grid[i][j].nval) {
375 <                                FVECT   odir;
376 <                                double  corr;
377 <                                ovec_from_pos(odir, i, j);
378 <                                itera[nn++].peak *= corr =
379 <                                        dsf_grid[i][j].vsum /
380 <                                                eval_rbfrep(newnode, odir);
381 <                                dsum += 1. - corr;
382 <                                dsum2 += (1.-corr)*(1.-corr);
383 <                        }
384 <                memcpy(newnode->rbfa, itera, sizeof(RBFVAL)*newnode->nrbf);
385 <                lastVar = thisVar;
386 <                thisVar = dsum2/(double)nn;
387 < #ifdef DEBUG
388 <                fprintf(stderr, "Avg., RMS error: %.1f%%  %.1f%%\n",
389 <                                        100.*dsum/(double)nn,
390 <                                        100.*sqrt(thisVar));
391 < #endif
392 <        } while (--niter > 0 && lastVar-thisVar > 0.02*lastVar);
393 <
394 <        free(itera);
395 <        nn = 0;                 /* compute sum for normalization */
396 <        while (nn < newnode->nrbf)
397 <                newnode->vtotal += rbf_volume(&newnode->rbfa[nn++]);
398 < #ifdef DEBUG
283 >        fprintf(stderr, "Built RBF with %d lobes\n", newnode->nrbf);
284          fprintf(stderr, "Integrated DSF at (%.1f,%.1f) deg. is %.2f\n",
285                          get_theta180(newnode->invec), get_phi360(newnode->invec),
286                          newnode->vtotal);

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines