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

Comparing ray/src/rt/pmapdata.c (file contents):
Revision 2.7 by rschregle, Tue May 26 11:26:27 2015 UTC vs.
Revision 2.18 by rschregle, Mon Aug 14 21:12:10 2017 UTC

# Line 1 | Line 1
1 + #ifndef lint
2 + static const char RCSid[] = "$Id$";
3 + #endif
4 +
5   /*
6 <   ==================================================================
7 <   Photon map data structures and kd-tree handling
6 >   =========================================================================
7 >   Photon map types and interface to nearest neighbour lookups in underlying
8 >   point cloud data structure.
9 >  
10 >   The default data structure is an in-core kd-tree (see pmapkdt.{h,c}).
11 >   This can be overriden with the PMAP_OOC compiletime switch, which enables
12 >   an out-of-core octree (see oococt.{h,c}).
13  
14     Roland Schregle (roland.schregle@{hslu.ch, gmail.com})
15     (c) Fraunhofer Institute for Solar Energy Systems,
16     (c) Lucerne University of Applied Sciences and Arts,
17 <   supported by the Swiss National Science Foundation (SNSF, #147053)
18 <   ==================================================================  
17 >       supported by the Swiss National Science Foundation (SNSF, #147053)
18 >   ==========================================================================
19    
20     $Id$
21   */
22  
23  
24  
25 < #include "pmap.h"
25 > #include "pmapdata.h"
26   #include "pmaprand.h"
27   #include "pmapmat.h"
28   #include "otypes.h"
# Line 29 | Line 38 | PhotonMap *photonMaps [NUM_PMAP_TYPES] = {
38  
39  
40  
41 + /* Include routines to handle underlying point cloud data structure */
42 + #ifdef PMAP_OOC
43 +   #include "pmapooc.c"
44 + #else
45 +   #include "pmapkdt.c"
46 + #endif
47 +
48 +
49 +
50   void initPhotonMap (PhotonMap *pmap, PhotonMapType t)
51   /* Init photon map 'n' stuff... */
52   {
53     if (!pmap)
54        return;
55        
56 <   pmap -> heapSize = pmap -> heapEnd = 0;
39 <   pmap -> heap = NULL;
40 <   pmap -> squeue = NULL;
56 >   pmap -> numPhotons = 0;
57     pmap -> biasCompHist = NULL;
58     pmap -> maxPos [0] = pmap -> maxPos [1] = pmap -> maxPos [2] = -FHUGE;
59     pmap -> minPos [0] = pmap -> minPos [1] = pmap -> minPos [2] = FHUGE;
# Line 47 | Line 63 | void initPhotonMap (PhotonMap *pmap, PhotonMapType t)
63     pmap -> numDensity = 0;
64     pmap -> distribRatio = 1;
65     pmap -> type = t;
66 +   pmap -> squeue.node = NULL;
67 +   pmap -> squeue.len = 0;  
68  
69     /* Init local RNG state */
70     pmap -> randState [0] = 10243;
71     pmap -> randState [1] = 39829;
72     pmap -> randState [2] = 9433;
55   /* pmapSeed(25999, pmap -> randState); */
73     pmapSeed(randSeed, pmap -> randState);
74    
75     /* Set up type-specific photon lookup callback */
76     pmap -> lookup = pmapLookup [t];
77  
78 <   pmap -> primary = NULL;
79 <   pmap -> primarySize = pmap -> primaryEnd = 0;
78 >   /* Mark primary photon ray as unused */
79 >   pmap -> lastPrimary.srcIdx = -1;
80 >   pmap -> numPrimary = 0;  
81 >   pmap -> primaries = NULL;
82 >  
83 >   /* Init storage */
84 >   pmap -> heap = NULL;
85 >   pmap -> heapBuf = NULL;
86 >   pmap -> heapBufLen = 0;
87 > #ifdef PMAP_OOC
88 >   OOC_Null(&pmap -> store);
89 > #else
90 >   kdT_Null(&pmap -> store);
91 > #endif
92   }
93  
94  
95  
96 < const PhotonPrimary* addPhotonPrimary (PhotonMap *pmap, const RAY *ray)
96 > void initPhotonHeap (PhotonMap *pmap)
97   {
98 <   PhotonPrimary *prim = NULL;
70 <   FVECT dvec;
98 >   int fdFlags;
99    
100 <   if (!pmap || !ray)
101 <      return NULL;
100 >   if (!pmap)
101 >      error(INTERNAL, "undefined photon map in initPhotonHeap");
102        
103 <   /* Check if last primary ray has spawned photons (srcIdx >= 0, see
104 <    * addPhoton()), in which case we keep it and allocate a new one;
105 <    * otherwise we overwrite the unused entry */
106 <   if (pmap -> primary && pmap -> primary [pmap -> primaryEnd].srcIdx >= 0)
107 <      pmap -> primaryEnd++;
108 <      
109 <   if (!pmap -> primarySize || pmap -> primaryEnd >= pmap -> primarySize) {
110 <      /* Allocate/enlarge array */
111 <      pmap -> primarySize += pmap -> heapSizeInc;
112 <      
85 <      /* Counter wraparound? */
86 <      if (pmap -> primarySize < pmap -> heapSizeInc)
87 <         error(INTERNAL, "photon primary overflow");
88 <        
89 <      pmap -> primary = (PhotonPrimary *)realloc(pmap -> primary,
90 <                                                 sizeof(PhotonPrimary) *
91 <                                                 pmap -> primarySize);
92 <      if (!pmap -> primary)
93 <         error(USER, "can't allocate photon primaries");
103 >   if (!pmap -> heap) {
104 >      /* Open heap file */
105 >      mktemp(strcpy(pmap -> heapFname, PMAP_TMPFNAME));
106 >      if (!(pmap -> heap = fopen(pmap -> heapFname, "w+b")))
107 >         error(SYSTEM, "failed opening heap file in initPhotonHeap");
108 >
109 > #ifdef F_SETFL  /* XXX is there an alternate needed for Windows? */
110 >      fdFlags = fcntl(fileno(pmap -> heap), F_GETFL);
111 >      fcntl(fileno(pmap -> heap), F_SETFL, fdFlags | O_APPEND);
112 > #endif/*      ftruncate(fileno(pmap -> heap), 0); */
113     }
114 + }
115 +
116 +
117 +
118 + void flushPhotonHeap (PhotonMap *pmap)
119 + {
120 +   int                  fd;
121 +   const unsigned long  len = pmap -> heapBufLen * sizeof(Photon);
122    
123 <   prim = pmap -> primary + pmap -> primaryEnd;
123 >   if (!pmap)
124 >      error(INTERNAL, "undefined photon map in flushPhotonHeap");
125 >
126 >   if (!pmap -> heap || !pmap -> heapBuf) {
127 >      /* Silently ignore undefined heap
128 >      error(INTERNAL, "undefined heap in flushPhotonHeap"); */
129 >      return;
130 >   }
131 >
132 >   /* Atomically seek and write block to heap */
133 >   /* !!! Unbuffered I/O via pwrite() avoids potential race conditions
134 >    * !!! and buffer corruption which can occur with lseek()/fseek()
135 >    * !!! followed by write()/fwrite(). */  
136 >   fd = fileno(pmap -> heap);
137    
138 <   /* Mark unused with negative source index until path spawns a photon (see
139 <    * addPhoton()) */
140 <   prim -> srcIdx = -1;
141 <      
142 <   /* Reverse incident direction to point to light source */
103 <   dvec [0] = -ray -> rdir [0];
104 <   dvec [1] = -ray -> rdir [1];
105 <   dvec [2] = -ray -> rdir [2];
106 <   prim -> dir = encodedir(dvec);
138 > #ifdef DEBUG_PMAP
139 >   sprintf(errmsg, "Proc %d: flushing %ld photons from pos %ld\n", getpid(),
140 >           pmap -> heapBufLen, lseek(fd, 0, SEEK_END) / sizeof(Photon));
141 >   eputs(errmsg);
142 > #endif
143  
144 <   VCOPY(prim -> pos, ray -> rop);
144 >   /*if (pwrite(fd, pmap -> heapBuf, len, lseek(fd, 0, SEEK_END)) != len) */
145 >   if (write(fd, pmap -> heapBuf, len) != len)
146 >      error(SYSTEM, "failed append to heap file in flushPhotonHeap");
147 >
148 > #if NIX
149 >   if (fsync(fd))
150 >      error(SYSTEM, "failed fsync in flushPhotonHeap");
151 > #endif
152 >
153 >   pmap -> heapBufLen = 0;
154 > }
155 >
156 >
157 >
158 > #ifdef DEBUG_PMAP
159 > static int checkPhotonHeap (FILE *file)
160 > /* Check heap for nonsensical or duplicate photons */
161 > {
162 >   Photon   p, lastp;
163 >   int      i, dup;
164    
165 <   return prim;
165 >   rewind(file);
166 >   memset(&lastp, 0, sizeof(lastp));
167 >  
168 >   while (fread(&p, sizeof(p), 1, file)) {
169 >      dup = 1;
170 >      
171 >      for (i = 0; i <= 2; i++) {
172 >         if (p.pos [i] < thescene.cuorg [i] ||
173 >             p.pos [i] > thescene.cuorg [i] + thescene.cusize) {
174 >            
175 >            sprintf(errmsg, "corrupt photon in heap at [%f, %f, %f]\n",
176 >                    p.pos [0], p.pos [1], p.pos [2]);
177 >            error(WARNING, errmsg);
178 >         }
179 >        
180 >         dup &= p.pos [i] == lastp.pos [i];
181 >      }
182 >      
183 >      if (dup) {
184 >         sprintf(errmsg,
185 >                 "consecutive duplicate photon in heap at [%f, %f, %f]\n",
186 >                 p.pos [0], p.pos [1], p.pos [2]);
187 >         error(WARNING, errmsg);
188 >      }
189 >   }
190 >
191 >   return 0;
192   }
193 + #endif
194  
195  
196  
197 < const Photon* addPhoton (PhotonMap* pmap, const RAY* ray)
197 > int newPhoton (PhotonMap* pmap, const RAY* ray)
198   {
199 <   unsigned i;
200 <   Photon* photon = NULL;
199 >   unsigned i, inROI = 0;
200 >   Photon photon;
201     COLOR photonFlux;
202    
203     /* Account for distribution ratio */
204     if (!pmap || pmapRandom(pmap -> randState) > pmap -> distribRatio)
205 <      return NULL;
205 >      return -1;
206        
207     /* Don't store on sources */
208     if (ray -> robj > -1 && islight(objptr(ray -> ro -> omod) -> otype))
209 <      return NULL;
209 >      return -1;
210  
211 < #if 0
212 <   if (inContribPmap(pmap)) {
213 <      /* Adding contribution photon */
214 <      if (ray -> parent && ray -> parent -> rtype & PRIMARY)
215 <         /* Add primary photon ray if parent is primary; by putting this
216 <          * here and checking the ray's immediate parent, we only add
217 <          * primaries that actually contribute photons, and we only add them
218 <          * once.  */
219 <         addPhotonPrimary(pmap, ray -> parent);
220 <      
221 <      /* Save index to primary ray (remains unchanged if primary already in
140 <       * array) */
141 <      primary = pmap -> primaryEnd;
211 >   /* Store photon if within a region of interest (for ze Ecksperts!) */
212 >   if (!pmapNumROI || !pmapROI)
213 >      inROI = 1;
214 >   else {
215 >      for (i = 0; !inROI && i < pmapNumROI; i++)
216 >         inROI = (ray -> rop [0] >= pmapROI [i].min [0] &&
217 >                  ray -> rop [0] <= pmapROI [i].max [0] &&
218 >                  ray -> rop [1] >= pmapROI [i].min [1] &&
219 >                  ray -> rop [1] <= pmapROI [i].max [1] &&
220 >                  ray -> rop [2] >= pmapROI [i].min [2] &&
221 >                  ray -> rop [2] <= pmapROI [i].max [2]);
222     }
223 < #endif
224 <  
145 < #ifdef PMAP_ROI
146 <   /* Store photon if within region of interest -- for egg-spurtz only! */
147 <   if (ray -> rop [0] >= pmapROI [0] && ray -> rop [0] <= pmapROI [1] &&
148 <       ray -> rop [1] >= pmapROI [2] && ray -> rop [1] <= pmapROI [3] &&
149 <       ray -> rop [2] >= pmapROI [4] && ray -> rop [2] <= pmapROI [5])
150 < #endif
151 <   {      
152 <      if (pmap -> heapEnd >= pmap -> heapSize) {
153 <         /* Enlarge heap */
154 <         pmap -> heapSize += pmap -> heapSizeInc;
155 <        
156 <         /* Counter wraparound? */
157 <         if (pmap -> heapSize < pmap -> heapSizeInc)
158 <            error(INTERNAL, "photon heap overflow");
159 <        
160 <         pmap -> heap = (Photon *)realloc(pmap -> heap,
161 <                                          sizeof(Photon) * pmap -> heapSize);
162 <         if (!pmap -> heap)
163 <            error(USER, "can't allocate photon heap");
164 <      }
165 <
166 <      photon = pmap -> heap + pmap -> heapEnd++;
167 <
223 >    
224 >   if (inROI) {      
225        /* Adjust flux according to distribution ratio and ray weight */
226        copycolor(photonFlux, ray -> rcol);  
227        scalecolor(photonFlux,
228                   ray -> rweight / (pmap -> distribRatio ? pmap -> distribRatio
229                                                          : 1));
230 <      setPhotonFlux(photon, photonFlux);
230 >      setPhotonFlux(&photon, photonFlux);
231                
232        /* Set photon position and flags */
233 <      VCOPY(photon -> pos, ray -> rop);
234 <      photon -> flags = PMAP_CAUSTICRAY(ray) ? PMAP_CAUST_BIT : 0;
233 >      VCOPY(photon.pos, ray -> rop);
234 >      photon.flags = 0;
235 >      photon.caustic = PMAP_CAUSTICRAY(ray);
236  
237 <      /* Set primary ray index and mark as used for contrib photons */
237 >      /* Set contrib photon's primary ray and subprocess index (the latter
238 >       * to linearise the primary ray indices after photon distribution is
239 >       * complete). Also set primary ray's source index, thereby marking it
240 >       * as used. */
241        if (isContribPmap(pmap)) {
242 <         photon -> primary = pmap -> primaryEnd;
243 <         pmap -> primary [pmap -> primaryEnd].srcIdx = ray -> rsrc;
242 >         photon.primary = pmap -> numPrimary;
243 >         photon.proc = PMAP_GETRAYPROC(ray);
244 >         pmap -> lastPrimary.srcIdx = ray -> rsrc;
245        }
246 <      else photon -> primary = 0;
246 >      else photon.primary = 0;
247        
248 <      /* Update min and max positions & set normal */
249 <      for (i = 0; i <= 2; i++) {
250 <         if (photon -> pos [i] < pmap -> minPos [i])
251 <            pmap -> minPos [i] = photon -> pos [i];
252 <         if (photon -> pos [i] > pmap -> maxPos [i])
253 <            pmap -> maxPos [i] = photon -> pos [i];
254 <         photon -> norm [i] = 127.0 * (isVolumePmap(pmap) ? ray -> rdir [i]
255 <                                                          : ray -> ron [i]);
248 >      /* Set normal */
249 >      for (i = 0; i <= 2; i++)
250 >         photon.norm [i] = 127.0 * (isVolumePmap(pmap) ? ray -> rdir [i]
251 >                                                       : ray -> ron [i]);
252 >
253 >      if (!pmap -> heapBuf) {
254 >         /* Lazily allocate heap buffa */
255 > #if NIX
256 >         /* Randomise buffa size to temporally decorellate flushes in
257 >          * multiprocessing mode */
258 >         srandom(randSeed + getpid());
259 >         pmap -> heapBufSize = PMAP_HEAPBUFSIZE * (0.5 + frandom());
260 > #else
261 >         /* Randomisation disabled for single processes on WIN; also useful
262 >          * for reproducability during debugging */        
263 >         pmap -> heapBufSize = PMAP_HEAPBUFSIZE;
264 > #endif        
265 >         if (!(pmap -> heapBuf = calloc(pmap -> heapBufSize, sizeof(Photon))))
266 >            error(SYSTEM, "failed heap buffer allocation in newPhoton");
267 >         pmap -> heapBufLen = 0;      
268        }
269 +
270 +      /* Photon initialised; now append to heap buffa */
271 +      memcpy(pmap -> heapBuf + pmap -> heapBufLen, &photon, sizeof(Photon));
272 +                  
273 +      if (++pmap -> heapBufLen >= pmap -> heapBufSize)
274 +         /* Heap buffa full, flush to heap file */
275 +         flushPhotonHeap(pmap);
276 +
277 +      pmap -> numPhotons++;
278     }
279              
280 <   return photon;
280 >   return 0;
281   }
282  
283  
284  
285 < static void nearestNeighbours (PhotonMap* pmap, const float pos [3],
286 <                               const float norm [3], unsigned long node)
204 < /* Recursive part of findPhotons(..).
205 <   Note that all heap and priority queue index handling is 1-based, but
206 <   accesses to the arrays are 0-based! */
285 > void buildPhotonMap (PhotonMap *pmap, double *photonFlux,
286 >                     PhotonPrimaryIdx *primaryOfs, unsigned nproc)
287   {
288 <   Photon* p = &pmap -> heap [node - 1];
289 <   unsigned i, j;
290 <   /* Signed distance to current photon's splitting plane */
291 <   float d = pos [photonDiscr(*p)] - p -> pos [photonDiscr(*p)],
292 <         d2 = d * d;
293 <   PhotonSQNode* sq = pmap -> squeue;
294 <   const unsigned sqSize = pmap -> squeueSize;
295 <   float dv [3];
288 >   unsigned long  n, nCheck = 0;
289 >   unsigned       i;
290 >   Photon         *p;
291 >   COLOR          flux;
292 >   char           nuHeapFname [sizeof(PMAP_TMPFNAME)];
293 >   FILE           *nuHeap;
294 >   /* Need double here to reduce summation errors */
295 >   double         avgFlux [3] = {0, 0, 0}, CoG [3] = {0, 0, 0}, CoGdist = 0;
296 >   FVECT          d;
297    
298 <   /* Search subtree closer to pos first; exclude other subtree if the
299 <      distance to the splitting plane is greater than maxDist */
219 <   if (d < 0) {
220 <      if (node << 1 <= pmap -> heapSize)
221 <         nearestNeighbours(pmap, pos, norm, node << 1);
222 <      if (d2 < pmap -> maxDist && node << 1 < pmap -> heapSize)
223 <         nearestNeighbours(pmap, pos, norm, (node << 1) + 1);
224 <   }
225 <   else {
226 <      if (node << 1 < pmap -> heapSize)
227 <         nearestNeighbours(pmap, pos, norm, (node << 1) + 1);
228 <      if (d2 < pmap -> maxDist && node << 1 <= pmap -> heapSize)
229 <         nearestNeighbours(pmap, pos, norm, node << 1);
230 <   }
231 <
232 <   /* Reject photon if normal faces away (ignored for volume photons) */
233 <   if (norm && DOT(norm, p -> norm) <= 0.5 * frandom())
234 <      return;
298 >   if (!pmap)
299 >      error(INTERNAL, "undefined photon map in buildPhotonMap");
300        
301 <   if (isContribPmap(pmap) && pmap -> srcContrib) {
302 <      /* Lookup in contribution photon map */
303 <      OBJREC *srcMod;
304 <      const int srcIdx = photonSrcIdx(pmap, p);
305 <      
306 <      if (srcIdx < 0 || srcIdx >= nsources)
307 <         error(INTERNAL, "invalid light source index in photon map");
243 <      
244 <      srcMod = findmaterial(source [srcIdx].so);
301 >   /* Get number of photons from heapfile size */
302 >   if (fseek(pmap -> heap, 0, SEEK_END) < 0)
303 >      error(SYSTEM, "failed seek to end of photon heap in buildPhotonMap");
304 >   pmap -> numPhotons = ftell(pmap -> heap) / sizeof(Photon);
305 >  
306 >   if (!pmap -> numPhotons)
307 >      error(INTERNAL, "empty photon map in buildPhotonMap");  
308  
309 <      /* Reject photon if contributions from light source which emitted it
310 <       * are not sought */
248 <      if (!lu_find(pmap -> srcContrib, srcMod -> oname) -> data)
249 <         return;
309 >   if (!pmap -> heap)
310 >      error(INTERNAL, "no heap in buildPhotonMap");
311  
312 <      /* Reject non-caustic photon if lookup for caustic contribs */
313 <      if (pmap -> lookupFlags & PMAP_CAUST_BIT & ~p -> flags)
314 <         return;
312 > #ifdef DEBUG_PMAP
313 >   eputs("Checking photon heap consistency...\n");
314 >   checkPhotonHeap(pmap -> heap);
315 >  
316 >   sprintf(errmsg, "Heap contains %ld photons\n", pmap -> numPhotons);
317 >   eputs(errmsg);
318 > #endif
319 >
320 >   /* Allocate heap buffa */
321 >   if (!pmap -> heapBuf) {
322 >      pmap -> heapBufSize = PMAP_HEAPBUFSIZE;
323 >      pmap -> heapBuf = calloc(pmap -> heapBufSize, sizeof(Photon));
324 >      if (!pmap -> heapBuf)
325 >         error(SYSTEM, "failed to allocate postprocessed photon heap in"
326 >               "buildPhotonMap");
327     }
328 +
329 +   /* We REALLY don't need yet another @%&*! heap just to hold the scaled
330 +    * photons, but can't think of a quicker fix... */
331 +   mktemp(strcpy(nuHeapFname, PMAP_TMPFNAME));
332 +   if (!(nuHeap = fopen(nuHeapFname, "w+b")))
333 +      error(SYSTEM, "failed to open postprocessed photon heap in "
334 +            "buildPhotonMap");
335 +            
336 +   rewind(pmap -> heap);
337 +
338 + #ifdef DEBUG_PMAP
339 +   eputs("Postprocessing photons...\n");
340 + #endif
341    
342 <   /* Squared distance to current photon */
343 <   dv [0] = pos [0] - p -> pos [0];
344 <   dv [1] = pos [1] - p -> pos [1];
345 <   dv [2] = pos [2] - p -> pos [2];
346 <   d2 = DOT(dv, dv);
342 >   while (!feof(pmap -> heap)) {  
343 > #ifdef DEBUG_PMAP
344 >      printf("Reading %lu at %lu... ", pmap -> heapBufSize, ftell(pmap->heap));
345 > #endif      
346 >      pmap -> heapBufLen = fread(pmap -> heapBuf, sizeof(Photon),
347 >                                 pmap -> heapBufSize, pmap -> heap);
348 > #ifdef DEBUG_PMAP                                
349 >      printf("Got %lu\n", pmap -> heapBufLen);
350 > #endif      
351  
352 <   /* Accept photon if closer than current max dist & add to priority queue */
353 <   if (d2 < pmap -> maxDist) {
354 <      if (pmap -> squeueEnd < sqSize) {
355 <         /* Priority queue not full; append photon and restore heap */
356 <         i = ++pmap -> squeueEnd;
352 >      if (ferror(pmap -> heap))
353 >         error(SYSTEM, "failed to read photon heap in buildPhotonMap");
354 >
355 >      for (n = pmap -> heapBufLen, p = pmap -> heapBuf; n; n--, p++) {
356 >         /* Update min and max pos and set photon flux */
357 >         for (i = 0; i <= 2; i++) {
358 >            if (p -> pos [i] < pmap -> minPos [i])
359 >               pmap -> minPos [i] = p -> pos [i];
360 >            else if (p -> pos [i] > pmap -> maxPos [i])
361 >               pmap -> maxPos [i] = p -> pos [i];  
362 >
363 >            /* Update centre of gravity with photon position */                
364 >            CoG [i] += p -> pos [i];                  
365 >         }  
366          
367 <         while (i > 1 && sq [(i >> 1) - 1].dist <= d2) {
368 <            sq [i - 1].photon = sq [(i >> 1) - 1].photon;
369 <            sq [i - 1].dist = sq [(i >> 1) - 1].dist;
370 <            i >>= 1;
272 <         }
367 >         if (primaryOfs)
368 >            /* Linearise photon primary index from subprocess index using the
369 >             * per-subprocess offsets in primaryOfs */
370 >            p -> primary += primaryOfs [p -> proc];
371          
372 <         sq [--i].photon = p;
373 <         sq [i].dist = d2;
374 <         /* Update maxDist if we've just filled the queue */
375 <         if (pmap -> squeueEnd >= pmap -> squeueSize)
376 <            pmap -> maxDist = sq [0].dist;
377 <      }
378 <      else {
379 <         /* Priority queue full; replace maximum, restore heap, and
380 <            update maxDist */
283 <         i = 1;
284 <        
285 <         while (i <= sqSize >> 1) {
286 <            j = i << 1;
287 <            if (j < sqSize && sq [j - 1].dist < sq [j].dist)
288 <               j++;
289 <            if (d2 >= sq [j - 1].dist)
290 <               break;
291 <            sq [i - 1].photon = sq [j - 1].photon;
292 <            sq [i - 1].dist = sq [j - 1].dist;
293 <            i = j;
372 >         /* Scale photon's flux (hitherto normalised to 1 over RGB); in
373 >          * case of a contrib photon map, this is done per light source,
374 >          * and photonFlux is assumed to be an array */
375 >         getPhotonFlux(p, flux);            
376 >
377 >         if (photonFlux) {
378 >            scalecolor(flux, photonFlux [isContribPmap(pmap) ?
379 >                                            photonSrcIdx(pmap, p) : 0]);
380 >            setPhotonFlux(p, flux);
381           }
382 <        
383 <         sq [--i].photon = p;
384 <         sq [i].dist = d2;
298 <         pmap -> maxDist = sq [0].dist;
382 >
383 >         /* Update average photon flux; need a double here */
384 >         addcolor(avgFlux, flux);
385        }
386 +        
387 +      /* Write modified photons to new heap */
388 +      fwrite(pmap -> heapBuf, sizeof(Photon), pmap -> heapBufLen, nuHeap);
389 +                
390 +      if (ferror(nuHeap))
391 +         error(SYSTEM, "failed postprocessing photon flux in "
392 +               "buildPhotonMap");
393 +      
394 +      nCheck += pmap -> heapBufLen;
395     }
396 +
397 + #ifdef DEBUG_PMAP
398 +   if (nCheck < pmap -> numPhotons)
399 +      error(INTERNAL, "truncated photon heap in buildPhotonMap");
400 + #endif
401 +  
402 +   /* Finalise average photon flux */
403 +   scalecolor(avgFlux, 1.0 / pmap -> numPhotons);
404 +   copycolor(pmap -> photonFlux, avgFlux);
405 +
406 +   /* Average photon positions to get centre of gravity */
407 +   for (i = 0; i < 3; i++)
408 +      pmap -> CoG [i] = CoG [i] /= pmap -> numPhotons;
409 +      
410 +   rewind(pmap -> heap);
411 +  
412 +   /* Compute average photon distance to centre of gravity */
413 +   while (!feof(pmap -> heap)) {
414 +      pmap -> heapBufLen = fread(pmap -> heapBuf, sizeof(Photon),
415 +                                 pmap -> heapBufSize, pmap -> heap);
416 +      
417 +      for (n = pmap -> heapBufLen, p = pmap -> heapBuf; n; n--, p++) {
418 +         VSUB(d, p -> pos, CoG);
419 +         CoGdist += DOT(d, d);
420 +      }
421 +   }  
422 +
423 +   pmap -> CoGdist = CoGdist /= pmap -> numPhotons;
424 +
425 +   /* Swap heaps, discarding unscaled photons */
426 +   fclose(pmap -> heap);
427 +   unlink(pmap -> heapFname);
428 +   pmap -> heap = nuHeap;
429 +   strcpy(pmap -> heapFname, nuHeapFname);
430 +  
431 + #ifdef PMAP_OOC
432 +   OOC_BuildPhotonMap(pmap, nproc);
433 + #else
434 +   kdT_BuildPhotonMap(pmap);
435 + #endif
436 +
437 +   /* Trash heap and its buffa */
438 +   free(pmap -> heapBuf);
439 +   fclose(pmap -> heap);
440 +   unlink(pmap -> heapFname);
441 +   pmap -> heap = NULL;
442 +   pmap -> heapBuf = NULL;
443   }
444  
445  
# Line 312 | Line 454 | static void nearestNeighbours (PhotonMap* pmap, const
454   /* Threshold below which we assume increasing max radius won't help */
455   #define PMAP_SHORT_LOOKUP_THRESH 1
456  
457 + /* Coefficient for adaptive maximum search radius */
458 + #define PMAP_MAXDIST_COEFF 100
459 +
460   void findPhotons (PhotonMap* pmap, const RAY* ray)
461   {
317   float pos [3], norm [3];
462     int redo = 0;
463    
464 <   if (!pmap -> squeue) {
464 >   if (!pmap -> squeue.len) {
465        /* Lazy init priority queue */
466 <      pmap -> squeueSize = pmap -> maxGather + 1;
467 <      pmap -> squeue = (PhotonSQNode*)malloc(pmap -> squeueSize *
468 <                                             sizeof(PhotonSQNode));
469 <      if (!pmap -> squeue)
470 <         error(USER, "can't allocate photon priority queue");
327 <        
466 > #ifdef PMAP_OOC
467 >      OOC_InitFindPhotons(pmap);
468 > #else
469 >      kdT_InitFindPhotons(pmap);      
470 > #endif      
471        pmap -> minGathered = pmap -> maxGather;
472        pmap -> maxGathered = pmap -> minGather;
473        pmap -> totalGathered = 0;
# Line 333 | Line 476 | void findPhotons (PhotonMap* pmap, const RAY* ray)
476        pmap -> minError = FHUGE;
477        pmap -> maxError = -FHUGE;
478        pmap -> rmsError = 0;
479 < #ifdef PMAP_MAXDIST_ABS
480 <      /* Treat maxDistCoeff as an *absolute* and *fixed* max search radius.
481 <         Primarily intended for debugging; FOR ZE ECKSPURTZ ONLY! */
482 <      pmap -> maxDist0 = pmap -> maxDistLimit = maxDistCoeff;
483 < #else
484 <      /* Maximum search radius limit based on avg photon distance to
342 <       * centre of gravity */
343 <      pmap -> maxDist0 = pmap -> maxDistLimit =
344 <         maxDistCoeff * pmap -> squeueSize * pmap -> CoGdist /
345 <         pmap -> heapSize;
346 < #endif      
479 >      /* SQUARED max search radius limit is based on avg photon distance to
480 >       * centre of gravity, unless fixed by user (maxDistFix > 0) */
481 >      pmap -> maxDist0 = pmap -> maxDist2Limit =
482 >         maxDistFix > 0 ? maxDistFix * maxDistFix
483 >                        : PMAP_MAXDIST_COEFF * pmap -> squeue.len *
484 >                          pmap -> CoGdist / pmap -> numPhotons;
485     }
486  
487     do {
488 <      pmap -> squeueEnd = 0;
489 <      pmap -> maxDist = pmap -> maxDist0;
488 >      pmap -> squeue.tail = 0;
489 >      pmap -> maxDist2 = pmap -> maxDist0;
490          
491        /* Search position is ray -> rorg for volume photons, since we have no
492           intersection point. Normals are ignored -- these are incident
493           directions). */  
494        if (isVolumePmap(pmap)) {
495 <         VCOPY(pos, ray -> rorg);
496 <         nearestNeighbours(pmap, pos, NULL, 1);
495 > #ifdef PMAP_OOC
496 >         OOC_FindPhotons(pmap, ray -> rorg, NULL);
497 > #else
498 >         kdT_FindPhotons(pmap, ray -> rorg, NULL);
499 > #endif                  
500        }
501        else {
502 <         VCOPY(pos, ray -> rop);
503 <         VCOPY(norm, ray -> ron);
504 <         nearestNeighbours(pmap, pos, norm, 1);
502 > #ifdef PMAP_OOC
503 >         OOC_FindPhotons(pmap, ray -> rop, ray -> ron);        
504 > #else
505 >         kdT_FindPhotons(pmap, ray -> rop, ray -> ron);
506 > #endif
507        }
508  
509 < #ifndef PMAP_MAXDIST_ABS      
510 <      if (pmap -> squeueEnd < pmap -> squeueSize * pmap -> gatherTolerance) {
509 > #ifdef PMAP_LOOKUP_INFO
510 >      fprintf(stderr, "%d/%d %s photons found within radius %.3f "
511 >              "at (%.2f,%.2f,%.2f) on %s\n", pmap -> squeue.tail,
512 >              pmap -> squeue.len, pmapName [pmap -> type], sqrt(pmap -> maxDist2),
513 >              ray -> rop [0], ray -> rop [1], ray -> rop [2],
514 >              ray -> ro ? ray -> ro -> oname : "<null>");
515 > #endif      
516 >            
517 >      if (pmap -> squeue.tail < pmap -> squeue.len * pmap -> gatherTolerance) {
518           /* Short lookup; too few photons found */
519 <         if (pmap -> squeueEnd > PMAP_SHORT_LOOKUP_THRESH) {
519 >         if (pmap -> squeue.tail > PMAP_SHORT_LOOKUP_THRESH) {
520              /* Ignore short lookups which return fewer than
521               * PMAP_SHORT_LOOKUP_THRESH photons under the assumption there
522               * really are no photons in the vicinity, and increasing the max
523               * search radius therefore won't help */
524 <   #ifdef PMAP_LOOKUP_WARN
524 > #ifdef PMAP_LOOKUP_WARN
525              sprintf(errmsg,
526                      "%d/%d %s photons found at (%.2f,%.2f,%.2f) on %s",
527 <                    pmap -> squeueEnd, pmap -> squeueSize,
528 <                    pmapName [pmap -> type], pos [0], pos [1], pos [2],
527 >                    pmap -> squeue.tail, pmap -> squeue.len,
528 >                    pmapName [pmap -> type],
529 >                    ray -> rop [0], ray -> rop [1], ray -> rop [2],
530                      ray -> ro ? ray -> ro -> oname : "<null>");
531              error(WARNING, errmsg);        
532 <   #endif            
532 > #endif            
533  
534 <            if (pmap -> maxDist0 < pmap -> maxDistLimit) {
534 >            /* Bail out after warning if maxDist is fixed */
535 >            if (maxDistFix > 0)
536 >               return;
537 >            
538 >            if (pmap -> maxDist0 < pmap -> maxDist2Limit) {
539                 /* Increase max search radius if below limit & redo search */
540                 pmap -> maxDist0 *= PMAP_MAXDIST_INC;
541 <   #ifdef PMAP_LOOKUP_REDO
541 > #ifdef PMAP_LOOKUP_REDO
542                 redo = 1;
543 <   #endif              
544 <
390 <   #ifdef PMAP_LOOKUP_WARN
543 > #endif              
544 > #ifdef PMAP_LOOKUP_WARN
545                 sprintf(errmsg,
546                         redo ? "restarting photon lookup with max radius %.1e"
547                              : "max photon lookup radius adjusted to %.1e",
548                         pmap -> maxDist0);
549                 error(WARNING, errmsg);
550 <   #endif
550 > #endif
551              }
552 <   #ifdef PMAP_LOOKUP_REDO
552 > #ifdef PMAP_LOOKUP_REDO
553              else {
554                 sprintf(errmsg, "max photon lookup radius clamped to %.1e",
555                         pmap -> maxDist0);
556                 error(WARNING, errmsg);
557              }
558 <   #endif
558 > #endif
559           }
560          
561           /* Reset successful lookup counter */
562           pmap -> numLookups = 0;
563        }
564        else {
565 +         /* Bail out after warning if maxDist is fixed */
566 +         if (maxDistFix > 0)
567 +            return;
568 +            
569           /* Increment successful lookup counter and reduce max search radius if
570            * wraparound */
571           pmap -> numLookups = (pmap -> numLookups + 1) % PMAP_MAXDIST_CNT;
# Line 416 | Line 574 | void findPhotons (PhotonMap* pmap, const RAY* ray)
574              
575           redo = 0;
576        }
577 < #endif
577 >      
578     } while (redo);
579   }
580  
581  
582  
583 < static void nearest1Neighbour (PhotonMap *pmap, const float pos [3],
426 <                               const float norm [3], Photon **photon,
427 <                               unsigned long node)
428 < /* Recursive part of find1Photon(..).
429 <   Note that all heap index handling is 1-based, but accesses to the
430 <   arrays are 0-based! */
583 > void find1Photon (PhotonMap *pmap, const RAY* ray, Photon *photon)
584   {
585 <   Photon *p = pmap -> heap + node - 1;
433 <   /* Signed distance to current photon's splitting plane */
434 <   float d = pos [photonDiscr(*p)] - p -> pos [photonDiscr(*p)],
435 <         d2 = d * d;
436 <   float dv [3];
585 >   pmap -> maxDist2 = thescene.cusize;  /* ? */
586    
587 <   /* Search subtree closer to pos first; exclude other subtree if the
588 <      distance to the splitting plane is greater than maxDist */
589 <   if (d < 0) {
590 <      if (node << 1 <= pmap -> heapSize)
591 <         nearest1Neighbour(pmap, pos, norm, photon, node << 1);
443 <      if (d2 < pmap -> maxDist && node << 1 < pmap -> heapSize)
444 <         nearest1Neighbour(pmap, pos, norm, photon, (node << 1) + 1);
445 <   }
446 <   else {
447 <      if (node << 1 < pmap -> heapSize)
448 <         nearest1Neighbour(pmap, pos, norm, photon, (node << 1) + 1);
449 <      if (d2 < pmap -> maxDist && node << 1 <= pmap -> heapSize)
450 <         nearest1Neighbour(pmap, pos, norm, photon, node << 1);
451 <   }
452 <  
453 <   /* Squared distance to current photon */
454 <   dv [0] = pos [0] - p -> pos [0];
455 <   dv [1] = pos [1] - p -> pos [1];
456 <   dv [2] = pos [2] - p -> pos [2];
457 <   d2 = DOT(dv, dv);
458 <  
459 <   if (d2 < pmap -> maxDist && DOT(norm, p -> norm) > 0.5 * frandom()) {
460 <      /* Closest photon so far with similar normal */
461 <      pmap -> maxDist = d2;
462 <      *photon = p;
463 <   }
587 > #ifdef PMAP_OOC
588 >   OOC_Find1Photon(pmap, ray -> rop, ray -> ron, photon);
589 > #else
590 >   kdT_Find1Photon(pmap, ray -> rop, ray -> ron, photon);
591 > #endif                  
592   }
593  
594  
595  
596 < Photon* find1Photon (PhotonMap *pmap, const RAY* ray)
596 > void getPhoton (PhotonMap *pmap, PhotonIdx idx, Photon *photon)
597   {
598 <   float fpos [3], norm [3];
599 <   Photon* photon = NULL;
600 <
601 <   VCOPY(fpos, ray -> rop);
602 <   VCOPY(norm, ray -> ron);
603 <   pmap -> maxDist = thescene.cusize;
476 <   nearest1Neighbour(pmap, fpos, norm, &photon, 1);
477 <  
478 <   return photon;
598 > #ifdef PMAP_OOC
599 >   if (OOC_GetPhoton(pmap, idx, photon))      
600 > #else
601 >   if (kdT_GetPhoton(pmap, idx, photon))
602 > #endif
603 >      error(INTERNAL, "failed photon lookup");
604   }
605  
606  
607  
608 < static unsigned long medianPartition (const Photon* heap,
484 <                                      unsigned long* heapIdx,
485 <                                      unsigned long* heapXdi,
486 <                                      unsigned long left,
487 <                                      unsigned long right, unsigned dim)
488 < /* Returns index to median in heap from indices left to right
489 <   (inclusive) in dimension dim. The heap is partitioned relative to
490 <   median using a quicksort algorithm. The heap indices in heapIdx are
491 <   sorted rather than the heap itself. */
608 > Photon *getNearestPhoton (const PhotonSearchQueue *squeue, PhotonIdx idx)
609   {
610 <   register const float* p;
611 <   const unsigned long n = right - left + 1;
612 <   register unsigned long l, r, lg2, n2, m;
613 <   register unsigned d;
614 <  
498 <   /* Round down n to nearest power of 2 */
499 <   for (lg2 = 0, n2 = n; n2 > 1; n2 >>= 1, ++lg2);
500 <   n2 = 1 << lg2;
501 <  
502 <   /* Determine median position; this takes into account the fact that
503 <      only the last level in the heap can be partially empty, and that
504 <      it fills from left to right */
505 <   m = left + ((n - n2) > (n2 >> 1) - 1 ? n2 - 1 : n - (n2 >> 1));
506 <  
507 <   while (right > left) {
508 <      /* Pivot node */
509 <      p = heap [heapIdx [right]].pos;
510 <      l = left;
511 <      r = right - 1;
512 <      
513 <      /* l & r converge, swapping elements out of order with respect to
514 <         pivot node. Identical keys are resolved by cycling through
515 <         dim. The convergence point is then the pivot's position. */
516 <      do {
517 <         while (l <= r) {
518 <            d = dim;
519 <            
520 <            while (heap [heapIdx [l]].pos [d] == p [d]) {
521 <               d = (d + 1) % 3;
522 <              
523 <               if (d == dim) {
524 <                  /* Ignore dupes? */
525 <                  error(WARNING, "duplicate keys in photon heap");
526 <                  l++;
527 <                  break;
528 <               }
529 <            }
530 <            
531 <            if (heap [heapIdx [l]].pos [d] < p [d])
532 <               l++;
533 <            else break;
534 <         }
535 <        
536 <         while (r > l) {
537 <            d = dim;
538 <            
539 <            while (heap [heapIdx [r]].pos [d] == p [d]) {
540 <               d = (d + 1) % 3;
541 <              
542 <               if (d == dim) {
543 <                  /* Ignore dupes? */
544 <                  error(WARNING, "duplicate keys in photon heap");
545 <                  r--;
546 <                  break;
547 <               }
548 <            }
549 <            
550 <            if (heap [heapIdx [r]].pos [d] > p [d])
551 <               r--;
552 <            else break;
553 <         }
554 <        
555 <         /* Swap indices (not the nodes they point to) */
556 <         n2 = heapIdx [l];
557 <         heapIdx [l] = heapIdx [r];
558 <         heapIdx [r] = n2;
559 <         /* Update reverse indices */
560 <         heapXdi [heapIdx [l]] = l;
561 <         heapXdi [n2] = r;
562 <      } while (l < r);
563 <      
564 <      /* Swap indices of convergence and pivot nodes */
565 <      heapIdx [r] = heapIdx [l];
566 <      heapIdx [l] = heapIdx [right];
567 <      heapIdx [right] = n2;
568 <      /* Update reverse indices */
569 <      heapXdi [heapIdx [r]] = r;
570 <      heapXdi [heapIdx [l]] = l;
571 <      heapXdi [n2] = right;
572 <      if (l >= m) right = l - 1;
573 <      if (l <= m) left = l + 1;
574 <   }
575 <  
576 <   /* Once left & right have converged at m, we have found the median */
577 <   return m;
610 > #ifdef PMAP_OOC
611 >   return OOC_GetNearestPhoton(squeue, idx);
612 > #else
613 >   return kdT_GetNearestPhoton(squeue, idx);
614 > #endif
615   }
616  
617  
618  
619 < void buildHeap (Photon* heap, unsigned long* heapIdx,
583 <                unsigned long* heapXdi, const float min [3],
584 <                const float max [3], unsigned long left,
585 <                unsigned long right, unsigned long root)
586 < /* Recursive part of balancePhotons(..). Builds heap from subarray
587 <   defined by indices left and right. min and max are the minimum resp.
588 <   maximum photon positions in the array. root is the index of the
589 <   current subtree's root, which corresponds to the median's 1-based
590 <   index in the heap. heapIdx are the balanced heap indices. The heap
591 <   is accessed indirectly through these. heapXdi are the reverse indices
592 <   from the heap to heapIdx so that heapXdi [heapIdx [i]] = i. */
619 > PhotonIdx firstPhoton (const PhotonMap *pmap)
620   {
621 <   float maxLeft [3], minRight [3];
622 <   Photon rootNode;
623 <   unsigned d;
624 <  
625 <   /* Choose median for dimension with largest spread and partition
599 <      accordingly */
600 <   const float d0 = max [0] - min [0],
601 <               d1 = max [1] - min [1],
602 <               d2 = max [2] - min [2];
603 <   const unsigned char dim = d0 > d1 ? d0 > d2 ? 0 : 2
604 <                                     : d1 > d2 ? 1 : 2;
605 <   const unsigned long median =
606 <      left == right ? left
607 <                    : medianPartition(heap, heapIdx, heapXdi, left, right, dim);
608 <  
609 <   /* Place median at root of current subtree. This consists of swapping
610 <      the median and the root nodes and updating the heap indices */
611 <   memcpy(&rootNode, heap + heapIdx [median], sizeof(Photon));
612 <   memcpy(heap + heapIdx [median], heap + root - 1, sizeof(Photon));
613 <   setPhotonDiscr(rootNode, dim);
614 <   memcpy(heap + root - 1, &rootNode, sizeof(Photon));
615 <   heapIdx [heapXdi [root - 1]] = heapIdx [median];
616 <   heapXdi [heapIdx [median]] = heapXdi [root - 1];
617 <   heapIdx [median] = root - 1;
618 <   heapXdi [root - 1] = median;
619 <  
620 <   /* Update bounds for left and right subtrees and recurse on them */
621 <   for (d = 0; d <= 2; d++)
622 <      if (d == dim)
623 <         maxLeft [d] = minRight [d] = rootNode.pos [d];
624 <      else {
625 <         maxLeft [d] = max [d];
626 <         minRight [d] = min [d];
627 <      }
628 <      
629 <   if (left < median)
630 <      buildHeap(heap, heapIdx, heapXdi, min, maxLeft,
631 <                left, median - 1, root << 1);
632 <                
633 <   if (right > median)
634 <      buildHeap(heap, heapIdx, heapXdi, minRight, max,
635 <                median + 1, right, (root << 1) + 1);
621 > #ifdef PMAP_OOC
622 >   return OOC_FirstPhoton(pmap);
623 > #else
624 >   return kdT_FirstPhoton(pmap);
625 > #endif
626   }
627  
628  
629  
640 void balancePhotons (PhotonMap* pmap, double *photonFlux)
641 {
642   Photon *heap = pmap -> heap;
643   unsigned long i;
644   unsigned long *heapIdx;        /* Photon index array */
645   unsigned long *heapXdi;        /* Reverse index to heapIdx */
646   unsigned j;
647   COLOR flux;
648   /* Need doubles here to reduce errors from increment */
649   double avgFlux [3] = {0, 0, 0}, CoG [3] = {0, 0, 0}, CoGdist = 0;
650   FVECT d;
651  
652   if (pmap -> heapEnd) {
653      pmap -> heapSize = pmap -> heapEnd;
654      heapIdx = (unsigned long*)malloc(pmap -> heapSize *
655                                       sizeof(unsigned long));
656      heapXdi = (unsigned long*)malloc(pmap -> heapSize *
657                                       sizeof(unsigned long));
658      if (!heapIdx || !heapXdi)
659         error(USER, "can't allocate heap index");
660        
661      for (i = 0; i < pmap -> heapSize; i++) {
662         /* Initialize index arrays */
663         heapXdi [i] = heapIdx [i] = i;
664         getPhotonFlux(heap + i, flux);
665
666         /* Scale photon's flux (hitherto normalised to 1 over RGB); in case
667          * of a contrib photon map, this is done per light source, and
668          * photonFlux is assumed to be an array */
669         if (photonFlux) {
670            scalecolor(flux, photonFlux [isContribPmap(pmap) ?
671                                         photonSrcIdx(pmap, heap + i) : 0]);
672            setPhotonFlux(heap + i, flux);
673         }
674
675         /* Need a double here */
676         addcolor(avgFlux, flux);
677        
678         /* Add photon position to centre of gravity */
679         for (j = 0; j < 3; j++)
680            CoG [j] += heap [i].pos [j];
681      }
682      
683      /* Average photon positions to get centre of gravity */
684      for (j = 0; j < 3; j++)
685         pmap -> CoG [j] = CoG [j] /= pmap -> heapSize;
686      
687      /* Compute average photon distance to CoG */
688      for (i = 0; i < pmap -> heapSize; i++) {
689         VSUB(d, heap [i].pos, CoG);
690         CoGdist += DOT(d, d);
691      }
692      
693      pmap -> CoGdist = CoGdist /= pmap -> heapSize;
694      
695      /* Average photon flux based on RGBE representation */
696      scalecolor(avgFlux, 1.0 / pmap -> heapSize);
697      copycolor(pmap -> photonFlux, avgFlux);
698      
699      /* Build kd-tree */
700      buildHeap(pmap -> heap, heapIdx, heapXdi, pmap -> minPos,
701                pmap -> maxPos, 0, pmap -> heapSize - 1, 1);
702                
703      free(heapIdx);
704      free(heapXdi);
705   }
706 }
707
708
709
630   void deletePhotons (PhotonMap* pmap)
631   {
632 <   free(pmap -> heap);
633 <   free(pmap -> squeue);
632 > #ifdef PMAP_OOC
633 >   OOC_Delete(&pmap -> store);
634 > #else
635 >   kdT_Delete(&pmap -> store);
636 > #endif
637 >
638 >   free(pmap -> squeue.node);
639     free(pmap -> biasCompHist);
640    
641 <   pmap -> heapSize = 0;
642 <   pmap -> minGather = pmap -> maxGather =
718 <      pmap -> squeueSize = pmap -> squeueEnd = 0;
641 >   pmap -> numPhotons = pmap -> minGather = pmap -> maxGather =
642 >      pmap -> squeue.len = pmap -> squeue.tail = 0;
643   }

Diff Legend

Removed lines
+ Added lines
< Changed lines
> Changed lines