1 /*
2   Teem: Tools to process and visualize scientific data and images             .
3   Copyright (C) 2012, 2011, 2010, 2009  University of Chicago
4   Copyright (C) 2008, 2007, 2006, 2005  Gordon Kindlmann
5   Copyright (C) 2004, 2003, 2002, 2001, 2000, 1999, 1998  University of Utah
6 
7   This library is free software; you can redistribute it and/or
8   modify it under the terms of the GNU Lesser General Public License
9   (LGPL) as published by the Free Software Foundation; either
10   version 2.1 of the License, or (at your option) any later version.
11   The terms of redistributing and/or modifying this software also
12   include exceptions to the LGPL that facilitate static linking.
13 
14   This library is distributed in the hope that it will be useful,
15   but WITHOUT ANY WARRANTY; without even the implied warranty of
16   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17   Lesser General Public License for more details.
18 
19   You should have received a copy of the GNU Lesser General Public License
20   along with this library; if not, write to Free Software Foundation, Inc.,
21   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 */
23 
24 #include "echo.h"
25 #include "privateEcho.h"
26 
27 int
echoThreadStateInit(int threadIdx,echoThreadState * tstate,echoRTParm * parm,echoGlobalState * gstate)28 echoThreadStateInit(int threadIdx, echoThreadState *tstate,
29                     echoRTParm *parm, echoGlobalState *gstate) {
30   static const char me[]="echoThreadStateInit";
31 
32   if (!(tstate && parm && gstate)) {
33     biffAddf(ECHO, "%s: got NULL pointer", me);
34     return 1;
35   }
36   /* tstate->thread set by echoThreadStateNew() */
37   tstate->gstate = gstate;
38   /* this will probably be over-written */
39   tstate->verbose = gstate->verbose;
40   tstate->threadIdx = threadIdx;
41   if (nrrdMaybeAlloc_va(tstate->nperm, nrrdTypeInt, 2,
42                         AIR_CAST(size_t, ECHO_JITTABLE_NUM),
43                         AIR_CAST(size_t, parm->numSamples))) {
44     biffMovef(ECHO, NRRD,
45               "%s: couldn't allocate jitter permutation array", me);
46     return 1;
47   }
48   nrrdAxisInfoSet_va(tstate->nperm, nrrdAxisInfoLabel,
49                      "jittable", "sample");
50 
51   if (nrrdMaybeAlloc_va(tstate->njitt, echoPos_nt, 3,
52                         AIR_CAST(size_t, 2),
53                         AIR_CAST(size_t, ECHO_JITTABLE_NUM),
54                         AIR_CAST(size_t, parm->numSamples))) {
55     biffMovef(ECHO, NRRD, "%s: couldn't allocate jitter array", me);
56     return 1;
57   }
58   nrrdAxisInfoSet_va(tstate->njitt, nrrdAxisInfoLabel,
59                      "x,y", "jittable", "sample");
60 
61   tstate->permBuff = AIR_CAST(unsigned int *, airFree(tstate->permBuff));
62   if (!(tstate->permBuff = AIR_CAST(unsigned int *,
63                                     calloc(parm->numSamples, sizeof(int))))) {
64     biffAddf(ECHO, "%s: couldn't allocate permutation buffer", me);
65     return 1;
66   }
67   tstate->chanBuff = (echoCol_t *)airFree(tstate->chanBuff);
68   if (!( tstate->chanBuff =
69          (echoCol_t*)calloc(ECHO_IMG_CHANNELS * parm->numSamples,
70                             sizeof(echoCol_t)) )) {
71     biffAddf(ECHO, "%s: couldn't allocate img channel sample buffer", me);
72     return 1;
73   }
74 
75   airSrandMT_r(tstate->rst, AIR_CAST(unsigned int, (parm->seedRand
76                                                     ? airTime()
77                                                     : threadIdx)));
78   tstate->returnPtr = NULL;
79 
80   return 0;
81 }
82 
83 /*
84 ******** echoJitterCompute()
85 **
86 **
87 */
88 void
echoJitterCompute(echoRTParm * parm,echoThreadState * tstate)89 echoJitterCompute(echoRTParm *parm, echoThreadState *tstate) {
90   echoPos_t *jitt, w;
91   int s, i, j, xi, yi, n, N, *perm;
92 
93   N = parm->numSamples;
94   n = (int)sqrt(N);
95   w = 1.0/n;
96   /* each row in perm[] is for one sample, for going through all jittables;
97      each column is a different permutation of [0..parm->numSamples-1] */
98   perm = (int *)tstate->nperm->data;
99   for (j=0; j<ECHO_JITTABLE_NUM; j++) {
100     airShuffle_r(tstate->rst, tstate->permBuff,
101                  parm->numSamples, parm->permuteJitter);
102     for (s=0; s<N; s++) {
103       perm[j + ECHO_JITTABLE_NUM*s] = tstate->permBuff[s];
104     }
105   }
106   jitt = (echoPos_t *)tstate->njitt->data;
107   for (s=0; s<N; s++) {
108     for (j=0; j<ECHO_JITTABLE_NUM; j++) {
109       i = perm[j + ECHO_JITTABLE_NUM*s];
110       xi = i % n;
111       yi = i / n;
112       switch(parm->jitterType) {
113       case echoJitterNone:
114         jitt[0 + 2*j] = 0.0;
115         jitt[1 + 2*j] = 0.0;
116         break;
117       case echoJitterGrid:
118         jitt[0 + 2*j] = NRRD_POS(nrrdCenterCell, -0.5, 0.5, n, xi);
119         jitt[1 + 2*j] = NRRD_POS(nrrdCenterCell, -0.5, 0.5, n, yi);
120         break;
121       case echoJitterJitter:
122         jitt[0 + 2*j] = (NRRD_POS(nrrdCenterCell, -0.5, 0.5, n, xi)
123                          + w*(airDrandMT_r(tstate->rst) - 0.5));
124         jitt[1 + 2*j] = (NRRD_POS(nrrdCenterCell, -0.5, 0.5, n, yi)
125                          + w*(airDrandMT_r(tstate->rst) - 0.5));
126         break;
127       case echoJitterRandom:
128         jitt[0 + 2*j] = airDrandMT_r(tstate->rst) - 0.5;
129         jitt[1 + 2*j] = airDrandMT_r(tstate->rst) - 0.5;
130         break;
131       }
132     }
133     jitt += 2*ECHO_JITTABLE_NUM;
134   }
135 
136   return;
137 }
138 
139 /*
140 ******** echoRTRenderCheck
141 **
142 ** does all the error checking required of echoRTRender and
143 ** everything that it calls
144 */
145 int
echoRTRenderCheck(Nrrd * nraw,limnCamera * cam,echoScene * scene,echoRTParm * parm,echoGlobalState * gstate)146 echoRTRenderCheck(Nrrd *nraw, limnCamera *cam, echoScene *scene,
147                   echoRTParm *parm, echoGlobalState *gstate) {
148   static const char me[]="echoRTRenderCheck";
149   int tmp;
150 
151   if (!(nraw && cam && scene && parm && gstate)) {
152     biffAddf(ECHO, "%s: got NULL pointer", me);
153     return 1;
154   }
155   if (limnCameraUpdate(cam)) {
156     biffMovef(ECHO, LIMN, "%s: camera trouble", me);
157     return 1;
158   }
159   if (scene->envmap) {
160     if (limnEnvMapCheck(scene->envmap)) {
161       biffMovef(ECHO, LIMN, "%s: environment map not valid", me);
162       return 1;
163     }
164   }
165   if (airEnumValCheck(echoJitter, parm->jitterType)) {
166     biffAddf(ECHO, "%s: jitter method (%d) invalid", me, parm->jitterType);
167     return 1;
168   }
169   if (!(parm->numSamples > 0)) {
170     biffAddf(ECHO, "%s: # samples (%d) invalid", me, parm->numSamples);
171     return 1;
172   }
173   if (!(parm->imgResU > 0 && parm->imgResV)) {
174     biffAddf(ECHO, "%s: image dimensions (%dx%d) invalid", me,
175              parm->imgResU, parm->imgResV);
176     return 1;
177   }
178   if (!AIR_EXISTS(parm->aperture)) {
179     biffAddf(ECHO, "%s: aperture doesn't exist", me);
180     return 1;
181   }
182 
183   switch (parm->jitterType) {
184   case echoJitterNone:
185   case echoJitterRandom:
186     break;
187   case echoJitterGrid:
188   case echoJitterJitter:
189     tmp = (int)sqrt(parm->numSamples);
190     if (tmp*tmp != parm->numSamples) {
191       biffAddf(ECHO,
192                "%s: need a square # samples for %s jitter method (not %d)",
193                me, airEnumStr(echoJitter, parm->jitterType), parm->numSamples);
194       return 1;
195     }
196     break;
197   }
198 
199   /* for the time being things are hard-coded to be r,g,b,a,time */
200   if (ECHO_IMG_CHANNELS != 5) {
201     biffAddf(ECHO, "%s: ECHO_IMG_CHANNELS != 5", me);
202     return 1;
203   }
204 
205   /* all is well */
206   return 0;
207 }
208 
209 void
echoChannelAverage(echoCol_t * img,echoRTParm * parm,echoThreadState * tstate)210 echoChannelAverage(echoCol_t *img,
211                    echoRTParm *parm, echoThreadState *tstate) {
212   int s;
213   echoCol_t R, G, B, A, T;
214 
215   R = G = B = A = T = 0;
216   for (s=0; s<parm->numSamples; s++) {
217     R += tstate->chanBuff[0 + ECHO_IMG_CHANNELS*s];
218     G += tstate->chanBuff[1 + ECHO_IMG_CHANNELS*s];
219     B += tstate->chanBuff[2 + ECHO_IMG_CHANNELS*s];
220     A += tstate->chanBuff[3 + ECHO_IMG_CHANNELS*s];
221     T += tstate->chanBuff[4 + ECHO_IMG_CHANNELS*s];
222   }
223   img[0] = R / parm->numSamples;
224   img[1] = G / parm->numSamples;
225   img[2] = B / parm->numSamples;
226   img[3] = A / parm->numSamples;
227   img[4] = T;
228 
229   return;
230 }
231 
232 /*
233 ******** echoRayColor
234 **
235 ** This is called by echoRTRender and by the various color routines,
236 ** following an intersection with non-phong non-light material (the
237 ** things that require reflection or refraction rays).  As such, it is
238 ** never called on shadow rays.
239 */
240 void
echoRayColor(echoCol_t * chan,echoRay * ray,echoScene * scene,echoRTParm * parm,echoThreadState * tstate)241 echoRayColor(echoCol_t *chan, echoRay *ray,
242              echoScene *scene, echoRTParm *parm, echoThreadState *tstate) {
243   static const char me[]="echoRayColor";
244   echoCol_t rgba[4];
245   echoIntx intx;
246 
247   tstate->depth++;
248   if (tstate->depth > parm->maxRecDepth) {
249     /* we've exceeded the recursion depth, so no more rays for you */
250     ELL_4V_SET(chan, parm->maxRecCol[0], parm->maxRecCol[1],
251                parm->maxRecCol[2], 1.0);
252     goto done;
253   }
254 
255   intx.boxhits = 0;
256   if (!echoRayIntx(&intx, ray, scene, parm, tstate)) {
257     if (tstate->verbose) {
258       fprintf(stderr, "%s%s: (nothing was hit)\n",_echoDot(tstate->depth), me);
259     }
260     /* ray hits nothing in scene */
261     ELL_4V_SET_TT(chan, echoCol_t,
262                   scene->bkgr[0], scene->bkgr[1], scene->bkgr[2],
263                   (parm->renderBoxes
264                    ? 1.0 - pow(1.0 - parm->boxOpac, intx.boxhits)
265                    : 0.0));
266     goto done;
267   }
268 
269   if (tstate->verbose) {
270     fprintf(stderr, "%s%s: hit a %d (%p) at (%g,%g,%g)\n"
271             "%s    = %g along (%g,%g,%g)\n", _echoDot(tstate->depth), me,
272             intx.obj->type, AIR_CAST(void*, intx.obj),
273             intx.pos[0], intx.pos[1], intx.pos[2], _echoDot(tstate->depth),
274             intx.t, ray->dir[0], ray->dir[1], ray->dir[2]);
275   }
276   echoIntxColor(rgba, &intx, scene, parm, tstate);
277   ELL_4V_COPY(chan, rgba);
278  done:
279   tstate->depth--;
280   return;
281 }
282 
283 void *
_echoRTRenderThreadBody(void * _arg)284 _echoRTRenderThreadBody(void *_arg) {
285   char done[20];
286   int imgUi, imgVi,         /* integral pixel indices */
287     samp;                   /* which sample are we doing */
288   echoPos_t tmp0, tmp1,
289     pixUsz, pixVsz,         /* U and V dimensions of a pixel */
290     U[4], V[4], N[4],       /* view space basis (only first 3 elements used) */
291     imgU, imgV,             /* floating point pixel center locations */
292     eye[3],                 /* eye center before jittering */
293     at[3],                  /* ray destination (pixel center post-jittering) */
294     imgOrig[3];             /* image origin */
295   double time0;
296   echoRay ray;              /* (not a pointer) */
297   echoThreadState *arg;
298   echoCol_t *img, *chan;    /* current scanline of channel buffer array */
299   Nrrd *nraw;               /* copies of arguments to echoRTRender . . . */
300   limnCamera *cam;
301   echoScene *scene;
302   echoRTParm *parm;
303 
304   arg = (echoThreadState *)_arg;
305   nraw = arg->gstate->nraw;
306   cam = arg->gstate->cam;
307   scene = arg->gstate->scene;
308   parm = arg->gstate->parm;
309 
310   echoJitterCompute(arg->gstate->parm, arg);
311   if (arg->gstate->verbose > 2) {
312     nrrdSave("jitt.nrrd", arg->njitt, NULL);
313   }
314 
315   /* set eye, U, V, N, imgOrig */
316   ELL_3V_COPY(eye, arg->gstate->cam->from);
317   ELL_4MV_ROW0_GET(U, cam->W2V);
318   ELL_4MV_ROW1_GET(V, cam->W2V);
319   ELL_4MV_ROW2_GET(N, cam->W2V);
320   ELL_3V_SCALE_ADD2(imgOrig, 1.0, eye, cam->vspDist, N);
321 
322   /* determine size of a single pixel (based on cell-centering) */
323   pixUsz = (cam->uRange[1] - cam->uRange[0])/(parm->imgResU);
324   pixVsz = (cam->vRange[1] - cam->vRange[0])/(parm->imgResV);
325 
326   arg->depth = 0;
327   ray.shadow = AIR_FALSE;
328   arg->verbose = AIR_FALSE;
329 
330   while (1) {
331     if (arg->gstate->workMutex) {
332       airThreadMutexLock(arg->gstate->workMutex);
333     }
334     imgVi = arg->gstate->workIdx;
335     if (arg->gstate->workIdx < parm->imgResV) {
336       arg->gstate->workIdx += 1;
337     }
338     if (!(imgVi % 5)) {
339       fprintf(stderr, "%s", airDoneStr(0, imgVi, parm->imgResV-1, done));
340       fflush(stderr);
341     }
342     if (arg->gstate->workMutex) {
343       airThreadMutexUnlock(arg->gstate->workMutex);
344     }
345     if (imgVi == parm->imgResV) {
346       /* we're done! */
347       break;
348     }
349 
350     imgV = NRRD_POS(nrrdCenterCell, cam->vRange[0], cam->vRange[1],
351                     parm->imgResV, imgVi);
352     for (imgUi=0; imgUi<parm->imgResU; imgUi++) {
353       imgU = NRRD_POS(nrrdCenterCell, cam->uRange[0], cam->uRange[1],
354                       parm->imgResU, imgUi);
355       img = ((echoCol_t *)nraw->data
356              + ECHO_IMG_CHANNELS*(imgUi + parm->imgResU*imgVi));
357 
358       /* initialize things on first "scanline" */
359       arg->jitt = (echoPos_t *)arg->njitt->data;
360       chan = arg->chanBuff;
361 
362       /*
363       arg->verbose = ( (48 == imgUi && 13 == imgVi)
364                        || (49 == imgUi && 13 == imgVi) );
365       */
366 
367       if (arg->verbose) {
368         fprintf(stderr, "\n");
369         fprintf(stderr, "-----------------------------------------------\n");
370         fprintf(stderr, "----------------- (%3d, %3d) ------------------\n",
371                 imgUi, imgVi);
372         fprintf(stderr, "-----------------------------------------------\n\n");
373       }
374 
375       /* go through samples */
376       for (samp=0; samp<parm->numSamples; samp++) {
377         /* set ray.from[] */
378         ELL_3V_COPY(ray.from, eye);
379         if (parm->aperture) {
380           tmp0 = parm->aperture*(arg->jitt[0 + 2*echoJittableLens]);
381           tmp1 = parm->aperture*(arg->jitt[1 + 2*echoJittableLens]);
382           ELL_3V_SCALE_ADD3(ray.from, 1, ray.from, tmp0, U, tmp1, V);
383         }
384 
385         /* set at[] */
386         tmp0 = imgU + pixUsz*(arg->jitt[0 + 2*echoJittablePixel]);
387         tmp1 = imgV + pixVsz*(arg->jitt[1 + 2*echoJittablePixel]);
388         ELL_3V_SCALE_ADD3(at, 1, imgOrig, tmp0, U, tmp1, V);
389 
390         /* do it! */
391         ELL_3V_SUB(ray.dir, at, ray.from);
392         ELL_3V_NORM(ray.dir, ray.dir, tmp0);
393         ray.neer = 0.0;
394         ray.faar = ECHO_POS_MAX;
395         time0 = airTime();
396         if (0) {
397           memset(chan, 0, ECHO_IMG_CHANNELS*sizeof(echoCol_t));
398         } else {
399           echoRayColor(chan, &ray, scene, parm, arg);
400         }
401         chan[4] = AIR_CAST(echoCol_t, airTime() - time0);
402 
403         /* move to next "scanline" */
404         arg->jitt += 2*ECHO_JITTABLE_NUM;
405         chan += ECHO_IMG_CHANNELS;
406       }
407       echoChannelAverage(img, parm, arg);
408       img += ECHO_IMG_CHANNELS;
409       if (!parm->reuseJitter) {
410         echoJitterCompute(parm, arg);
411       }
412     }
413   }
414 
415   return _arg;
416 }
417 
418 
419 /*
420 ******** echoRTRender
421 **
422 ** top-level call to accomplish all (ray-tracing) rendering.  As much
423 ** error checking as possible should be done here and not in the
424 ** lower-level functions.
425 */
426 int
echoRTRender(Nrrd * nraw,limnCamera * cam,echoScene * scene,echoRTParm * parm,echoGlobalState * gstate)427 echoRTRender(Nrrd *nraw, limnCamera *cam, echoScene *scene,
428              echoRTParm *parm, echoGlobalState *gstate) {
429   static const char me[]="echoRTRender";
430   int tid, ret;
431   airArray *mop;
432   echoThreadState *tstate[ECHO_THREAD_MAX];
433 
434   if (echoRTRenderCheck(nraw, cam, scene, parm, gstate)) {
435     biffAddf(ECHO, "%s: problem with input", me);
436     return 1;
437   }
438   gstate->nraw = nraw;
439   gstate->cam = cam;
440   gstate->scene = scene;
441   gstate->parm = parm;
442   mop = airMopNew();
443   if (nrrdMaybeAlloc_va(nraw, echoCol_nt, 3,
444                         AIR_CAST(size_t, ECHO_IMG_CHANNELS),
445                         AIR_CAST(size_t, parm->imgResU),
446                         AIR_CAST(size_t, parm->imgResV))) {
447     biffMovef(ECHO, NRRD, "%s: couldn't allocate output image", me);
448     airMopError(mop); return 1;
449   }
450   airMopAdd(mop, nraw, (airMopper)nrrdNix, airMopOnError);
451   nrrdAxisInfoSet_va(nraw, nrrdAxisInfoLabel,
452                      "r,g,b,a,t", "x", "y");
453   nrrdAxisInfoSet_va(nraw, nrrdAxisInfoMin,
454                      AIR_NAN, cam->uRange[0], cam->vRange[0]);
455   nrrdAxisInfoSet_va(nraw, nrrdAxisInfoMax,
456                      AIR_NAN, cam->uRange[1], cam->vRange[1]);
457   gstate->time = airTime();
458 
459   if (parm->numThreads > 1) {
460     gstate->workMutex = airThreadMutexNew();
461     airMopAdd(mop, gstate->workMutex,
462               (airMopper)airThreadMutexNix, airMopAlways);
463   } else {
464     gstate->workMutex = NULL;
465   }
466   for (tid=0; tid<parm->numThreads; tid++) {
467     if (!( tstate[tid] = echoThreadStateNew() )) {
468       biffAddf(ECHO, "%s: failed to create thread state %d", me, tid);
469       airMopError(mop); return 1;
470     }
471     if (echoThreadStateInit(tid, tstate[tid], parm, gstate)) {
472       biffAddf(ECHO, "%s: failed to initialized thread state %d", me, tid);
473       airMopError(mop); return 1;
474     }
475     airMopAdd(mop, tstate[tid], (airMopper)echoThreadStateNix, airMopAlways);
476   }
477   fprintf(stderr, "%s:       ", me);  /* prep for printing airDoneStr */
478   gstate->workIdx = 0;
479   for (tid=0; tid<parm->numThreads; tid++) {
480     if (( ret = airThreadStart(tstate[tid]->thread, _echoRTRenderThreadBody,
481                                (void *)(tstate[tid])) )) {
482       biffAddf(ECHO, "%s: thread[%d] failed to start: %d", me, tid, ret);
483       airMopError(mop); return 1;
484     }
485   }
486   for (tid=0; tid<parm->numThreads; tid++) {
487     if (( ret = airThreadJoin(tstate[tid]->thread,
488                               (void **)(&(tstate[tid]->returnPtr))) )) {
489       biffAddf(ECHO, "%s: thread[%d] failed to join: %d", me, tid, ret);
490       airMopError(mop); return 1;
491     }
492   }
493 
494   gstate->time = airTime() - gstate->time;
495   fprintf(stderr, "\n%s: time = %g\n", me, gstate->time);
496 
497   airMopOkay(mop);
498   return 0;
499 }
500