Page MenuHomec4science

pmap.c
No OneTemporary

File Metadata

Created
Fri, Mar 29, 10:47
#ifndef lint
static const char RCSid[] = "$Id: pmap.c,v 2.17 2018/12/18 22:14:04 rschregle Exp $";
#endif
/*
======================================================================
Photon map main module
Roland Schregle (roland.schregle@{hslu.ch, gmail.com})
(c) Fraunhofer Institute for Solar Energy Systems,
(c) Lucerne University of Applied Sciences and Arts,
supported by the Swiss National Science Foundation (SNSF, #147053)
======================================================================
$Id: pmap.c,v 2.17 2018/12/18 22:14:04 rschregle Exp $
*/
#include "pmap.h"
#include "pmapmat.h"
#include "pmapsrc.h"
#include "pmaprand.h"
#include "pmapio.h"
#include "pmapbias.h"
#include "pmapdiag.h"
#include "otypes.h"
#include <time.h>
#if NIX
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/wait.h>
#endif
void savePmaps (const PhotonMap **pmaps, int argc, char **argv)
{
unsigned t;
for (t = 0; t < NUM_PMAP_TYPES; t++) {
if (pmaps [t])
savePhotonMap(pmaps [t], pmaps [t] -> fileName, argc, argv);
}
}
static int photonParticipate (RAY *ray)
/* Trace photon through participating medium. Returns 1 if passed through,
or 0 if absorbed and $*%&ed. Analogon to rayparticipate(). */
{
int i;
RREAL xi1, cosTheta, phi, du, dv;
const float cext = colorAvg(ray -> cext),
albedo = colorAvg(ray -> albedo),
gecc = ray -> gecc, gecc2 = sqr(gecc);
FVECT u, v;
COLOR cvext;
/* Mean free distance until interaction with medium */
ray -> rmax = -log(pmapRandom(mediumState)) / cext;
while (!localhit(ray, &thescene)) {
if (!incube(&thescene, ray -> rop)) {
/* Terminate photon if it has leaked from the scene */
#ifdef DEBUG_PMAP
fprintf(stderr,
"Volume photon leaked from scene at [%.3f %.3f %.3f]\n",
ray -> rop [0], ray -> rop [1], ray -> rop [2]);
#endif
return 0;
}
setcolor(cvext, exp(-ray -> rmax * ray -> cext [0]),
exp(-ray -> rmax * ray -> cext [1]),
exp(-ray -> rmax * ray -> cext [2]));
/* Modify ray color and normalise */
multcolor(ray -> rcol, cvext);
colorNorm(ray -> rcol);
VCOPY(ray -> rorg, ray -> rop);
#if 0
if (albedo > FTINY && ray -> rlvl > 0)
#else
/* Store volume photons unconditionally in mist to also account for
direct inscattering from sources */
if (albedo > FTINY)
#endif
/* Add to volume photon map */
newPhoton(volumePmap, ray);
/* Absorbed? */
if (pmapRandom(rouletteState) > albedo)
return 0;
/* Colour bleeding without attenuation (?) */
multcolor(ray -> rcol, ray -> albedo);
scalecolor(ray -> rcol, 1 / albedo);
/* Scatter photon */
xi1 = pmapRandom(scatterState);
cosTheta = ray -> gecc <= FTINY
? 2 * xi1 - 1
: 0.5 / gecc *
(1 + gecc2 - sqr((1 - gecc2) /
(1 + gecc * (2 * xi1 - 1))));
phi = 2 * PI * pmapRandom(scatterState);
du = dv = sqrt(1 - sqr(cosTheta)); /* sin(theta) */
du *= cos(phi);
dv *= sin(phi);
/* Get axes u & v perpendicular to photon direction */
i = 0;
do {
v [0] = v [1] = v [2] = 0;
v [i++] = 1;
fcross(u, v, ray -> rdir);
} while (normalize(u) < FTINY);
fcross(v, ray -> rdir, u);
for (i = 0; i < 3; i++)
ray -> rdir [i] = du * u [i] + dv * v [i] +
cosTheta * ray -> rdir [i];
ray -> rlvl++;
ray -> rmax = -log(pmapRandom(mediumState)) / cext;
}
/* Passed through medium until intersecting local object */
setcolor(cvext, exp(-ray -> rot * ray -> cext [0]),
exp(-ray -> rot * ray -> cext [1]),
exp(-ray -> rot * ray -> cext [2]));
/* Modify ray color and normalise */
multcolor(ray -> rcol, cvext);
colorNorm(ray -> rcol);
return 1;
}
void tracePhoton (RAY *ray)
/* Follow photon as it bounces around... */
{
long mod;
OBJREC *mat, *port = NULL;
if (!ray -> parent) {
/* !!! PHOTON PORT REJECTION SAMPLING HACK: get photon port for
* !!! primary ray from ray -> ro, then reset the latter to NULL so
* !!! as not to interfere with localhit() */
port = ray -> ro;
ray -> ro = NULL;
}
if (ray -> rlvl > photonMaxBounce) {
#ifdef PMAP_RUNAWAY_WARN
error(WARNING, "runaway photon!");
#endif
return;
}
if (colorAvg(ray -> cext) > FTINY && !photonParticipate(ray))
return;
if (localhit(ray, &thescene)) {
mod = ray -> ro -> omod;
if (port && ray -> ro != port) {
/* !!! PHOTON PORT REJECTION SAMPLING HACK !!!
* Terminate photon if emitted from port without intersecting it;
* this can happen when the port's partitions extend beyond its
* actual geometry, e.g. with polygons. Since the total flux
* relayed by the port is based on the (in this case) larger
* partition area, it is overestimated; terminating these photons
* constitutes rejection sampling and thereby compensates any bias
* incurred by the overestimated flux. */
#ifdef PMAP_PORTREJECT_WARN
sprintf(errmsg, "photon outside port %s", ray -> ro -> oname);
error(WARNING, errmsg);
#endif
return;
}
if ((ray -> clipset && inset(ray -> clipset, mod)) || mod == OVOID) {
/* Transfer ray if modifier is VOID or clipped within antimatta */
RAY tray;
photonRay(ray, &tray, PMAP_XFER, NULL);
tracePhoton(&tray);
}
else {
/* Scatter for modifier material */
mat = objptr(mod);
photonScatter [mat -> otype] (mat, ray);
}
}
}
static void preComputeGlobal (PhotonMap *pmap)
/* Precompute irradiance from global photons for final gathering for
a random subset of finalGather * pmap -> numPhotons photons, and builds
the photon map, discarding the original photons. */
/* !!! NOTE: PRECOMPUTATION WITH OOC CURRENTLY WITHOUT CACHE !!! */
{
unsigned long i, numPreComp;
unsigned j;
PhotonIdx pIdx;
Photon photon;
RAY ray;
PhotonMap nuPmap;
repComplete = numPreComp = finalGather * pmap -> numPhotons;
if (verbose) {
sprintf(errmsg,
"\nPrecomputing irradiance for %ld global photons\n",
numPreComp);
eputs(errmsg);
#if NIX
fflush(stderr);
#endif
}
/* Copy photon map for precomputed photons */
memcpy(&nuPmap, pmap, sizeof(PhotonMap));
/* Zero counters, init new heap and extents */
nuPmap.numPhotons = 0;
initPhotonHeap(&nuPmap);
for (j = 0; j < 3; j++) {
nuPmap.minPos [j] = FHUGE;
nuPmap.maxPos [j] = -FHUGE;
}
/* Record start time, baby */
repStartTime = time(NULL);
#ifdef SIGCONT
signal(SIGCONT, pmapPreCompReport);
#endif
repProgress = 0;
photonRay(NULL, &ray, PRIMARY, NULL);
ray.ro = NULL;
for (i = 0; i < numPreComp; i++) {
/* Get random photon from stratified distribution in source heap to
* avoid duplicates and clustering */
pIdx = firstPhoton(pmap) +
(unsigned long)((i + pmapRandom(pmap -> randState)) /
finalGather);
getPhoton(pmap, pIdx, &photon);
/* Init dummy photon ray with intersection at photon position */
VCOPY(ray.rop, photon.pos);
for (j = 0; j < 3; j++)
ray.ron [j] = photon.norm [j] / 127.0;
/* Get density estimate at photon position */
photonDensity(pmap, &ray, ray.rcol);
/* Append photon to new heap from ray */
newPhoton(&nuPmap, &ray);
/* Update progress */
repProgress++;
if (photonRepTime > 0 && time(NULL) >= repLastTime + photonRepTime)
pmapPreCompReport();
#ifdef SIGCONT
else signal(SIGCONT, pmapPreCompReport);
#endif
}
/* Flush heap */
flushPhotonHeap(&nuPmap);
#ifdef SIGCONT
signal(SIGCONT, SIG_DFL);
#endif
/* Trash original pmap, replace with precomputed one */
deletePhotons(pmap);
memcpy(pmap, &nuPmap, sizeof(PhotonMap));
if (verbose) {
eputs("\nRebuilding precomputed photon map\n");
#if NIX
fflush(stderr);
#endif
}
/* Rebuild underlying data structure, destroying heap */
buildPhotonMap(pmap, NULL, NULL, 1);
}
typedef struct {
unsigned long numPhotons [NUM_PMAP_TYPES],
numEmitted, numComplete;
} PhotonCnt;
void distribPhotons (PhotonMap **pmaps, unsigned numProc)
{
EmissionMap emap;
char errmsg2 [128], shmFname [PMAP_TMPFNLEN];
unsigned t, srcIdx, proc;
double totalFlux = 0;
int shmFile, stat, pid;
PhotonMap *pm;
PhotonCnt *photonCnt;
for (t = 0; t < NUM_PMAP_TYPES && !pmaps [t]; t++);
if (t >= NUM_PMAP_TYPES)
error(USER, "no photon maps defined in distribPhotons");
if (!nsources)
error(USER, "no light sources in distribPhotons");
/* ===================================================================
* INITIALISATION - Set up emission and scattering funcs
* =================================================================== */
emap.samples = NULL;
emap.maxPartitions = MAXSPART;
emap.partitions = (unsigned char*)malloc(emap.maxPartitions >> 1);
if (!emap.partitions)
error(INTERNAL, "can't allocate source partitions in distribPhotons");
/* Initialise all defined photon maps */
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t]) {
initPhotonMap(pmaps [t], t);
/* Open photon heapfile */
initPhotonHeap(pmaps [t]);
/* Per-subprocess target count */
pmaps [t] -> distribTarget /= numProc;
if (!pmaps [t] -> distribTarget)
error(INTERNAL, "no photons to distribute in distribPhotons");
}
initPhotonEmissionFuncs();
initPhotonScatterFuncs();
/* Get photon ports from modifier list */
getPhotonPorts(photonPortList);
/* Get photon sensor modifiers */
getPhotonSensors(photonSensorList);
#if NIX
/* Set up shared mem for photon counters (zeroed by ftruncate) */
strcpy(shmFname, PMAP_TMPFNAME);
shmFile = mkstemp(shmFname);
if (shmFile < 0 || ftruncate(shmFile, sizeof(*photonCnt)) < 0)
error(SYSTEM, "failed shared mem init in distribPhotons");
photonCnt = mmap(NULL, sizeof(*photonCnt), PROT_READ | PROT_WRITE,
MAP_SHARED, shmFile, 0);
if (photonCnt == MAP_FAILED)
error(SYSTEM, "failed mapping shared memory in distribPhotons");
#else
/* Allocate photon counters statically on Windoze */
if (!(photonCnt = malloc(sizeof(PhotonCnt))))
error(SYSTEM, "failed trivial malloc in distribPhotons");
photonCnt -> numEmitted = photonCnt -> numComplete = 0;
#endif /* NIX */
if (verbose) {
sprintf(errmsg, "\nIntegrating flux from %d sources", nsources);
if (photonPorts) {
sprintf(errmsg2, " via %d ports", numPhotonPorts);
strcat(errmsg, errmsg2);
}
strcat(errmsg, "\n");
eputs(errmsg);
}
/* ===================================================================
* FLUX INTEGRATION - Get total photon flux from light sources
* =================================================================== */
for (srcIdx = 0; srcIdx < nsources; srcIdx++) {
unsigned portCnt = 0;
emap.src = source + srcIdx;
do { /* Need at least one iteration if no ports! */
emap.port = emap.src -> sflags & SDISTANT ? photonPorts + portCnt
: NULL;
photonPartition [emap.src -> so -> otype] (&emap);
if (verbose) {
sprintf(errmsg, "\tIntegrating flux from source %s ",
source [srcIdx].so -> oname);
if (emap.port) {
sprintf(errmsg2, "via port %s ",
photonPorts [portCnt].so -> oname);
strcat(errmsg, errmsg2);
}
sprintf(errmsg2, "(%lu partitions)\n", emap.numPartitions);
strcat(errmsg, errmsg2);
eputs(errmsg);
#if NIX
fflush(stderr);
#endif
}
for (emap.partitionCnt = 0; emap.partitionCnt < emap.numPartitions;
emap.partitionCnt++) {
initPhotonEmission(&emap, pdfSamples);
totalFlux += colorAvg(emap.partFlux);
}
portCnt++;
} while (portCnt < numPhotonPorts);
}
if (totalFlux < FTINY)
error(USER, "zero flux from light sources");
/* Record start time for progress reports */
repStartTime = time(NULL);
if (verbose) {
sprintf(errmsg, "\nPhoton distribution @ %d procs\n", numProc);
eputs(errmsg);
}
/* MAIN LOOP */
for (proc = 0; proc < numProc; proc++) {
#if NIX
if (!(pid = fork())) {
/* SUBPROCESS ENTERS HERE; open and mmapped files inherited */
#else
if (1) {
/* No subprocess under Windoze */
#endif
/* Local photon counters for this subprocess */
unsigned passCnt = 0, prePassCnt = 0;
unsigned long lastNumPhotons [NUM_PMAP_TYPES];
unsigned long localNumEmitted = 0; /* Num photons emitted by this
subprocess alone */
/* Seed RNGs from PID for decorellated photon distribution */
pmapSeed(randSeed + proc, partState);
pmapSeed(randSeed + (proc + 1) % numProc, emitState);
pmapSeed(randSeed + (proc + 2) % numProc, cntState);
pmapSeed(randSeed + (proc + 3) % numProc, mediumState);
pmapSeed(randSeed + (proc + 4) % numProc, scatterState);
pmapSeed(randSeed + (proc + 5) % numProc, rouletteState);
#ifdef DEBUG_PMAP
/* Output child process PID after random delay to prevent corrupted
* console output due to race condition */
usleep(1e6 * pmapRandom(rouletteState));
fprintf(stderr, "Proc %d: PID = %d "
"(waiting 10 sec to attach debugger...)\n",
proc, getpid());
/* Allow time for debugger to attach to child process */
sleep(10);
#endif
for (t = 0; t < NUM_PMAP_TYPES; t++)
lastNumPhotons [t] = 0;
/* =============================================================
* 2-PASS PHOTON DISTRIBUTION
* Pass 1 (pre): emit fraction of target photon count
* Pass 2 (main): based on outcome of pass 1, estimate remaining
* number of photons to emit to approximate target
* count
* ============================================================= */
do {
double numEmit;
if (!passCnt) {
/* INIT PASS 1 */
/* Skip if no photons contributed after sufficient
* iterations; make it clear to user which photon maps are
* missing so (s)he can check geometry and materials */
if (++prePassCnt > maxPreDistrib) {
sprintf(errmsg, "proc %d: too many prepasses", proc);
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t] && !pmaps [t] -> numPhotons) {
sprintf(errmsg2, ", no %s photons stored",
pmapName [t]);
strcat(errmsg, errmsg2);
}
error(USER, errmsg);
break;
}
/* Num to emit is fraction of minimum target count */
numEmit = FHUGE;
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t])
numEmit = min(pmaps [t] -> distribTarget, numEmit);
numEmit *= preDistrib;
}
else {
/* INIT PASS 2 */
/* Based on the outcome of the predistribution we can now
* estimate how many more photons we have to emit for each
* photon map to meet its respective target count. This
* value is clamped to 0 in case the target has already been
* exceeded in the pass 1. */
double maxDistribRatio = 0;
/* Set the distribution ratio for each map; this indicates
* how many photons of each respective type are stored per
* emitted photon, and is used as probability for storing a
* photon by newPhoton(). Since this biases the photon
* density, newPhoton() promotes the flux of stored photons
* to compensate. */
for (t = 0; t < NUM_PMAP_TYPES; t++)
if ((pm = pmaps [t])) {
pm -> distribRatio = (double)pm -> distribTarget /
pm -> numPhotons - 1;
/* Check if photon map "overflowed", i.e. exceeded its
* target count in the prepass; correcting the photon
* flux via the distribution ratio is no longer
* possible, as no more photons of this type will be
* stored, so notify the user rather than deliver
* incorrect results. In future we should handle this
* more intelligently by using the photonFlux in each
* photon map to individually correct the flux after
* distribution. */
if (pm -> distribRatio <= FTINY) {
sprintf(errmsg, "%s photon map overflow in "
"prepass, reduce -apD", pmapName [t]);
error(INTERNAL, errmsg);
}
maxDistribRatio = max(pm -> distribRatio,
maxDistribRatio);
}
/* Normalise distribution ratios and calculate number of
* photons to emit in main pass */
for (t = 0; t < NUM_PMAP_TYPES; t++)
if ((pm = pmaps [t]))
pm -> distribRatio /= maxDistribRatio;
if ((numEmit = localNumEmitted * maxDistribRatio) < FTINY)
/* No photons left to distribute in main pass */
break;
}
/* Update shared completion counter for progreport by parent */
photonCnt -> numComplete += numEmit;
/* PHOTON DISTRIBUTION LOOP */
for (srcIdx = 0; srcIdx < nsources; srcIdx++) {
unsigned portCnt = 0;
emap.src = source + srcIdx;
do { /* Need at least one iteration if no ports! */
emap.port = emap.src -> sflags & SDISTANT
? photonPorts + portCnt : NULL;
photonPartition [emap.src -> so -> otype] (&emap);
if (verbose && !proc) {
/* Output from subproc 0 only to avoid race condition
* on console I/O */
if (!passCnt)
sprintf(errmsg, "\tPREPASS %d on source %s ",
prePassCnt, source [srcIdx].so -> oname);
else
sprintf(errmsg, "\tMAIN PASS on source %s ",
source [srcIdx].so -> oname);
if (emap.port) {
sprintf(errmsg2, "via port %s ",
photonPorts [portCnt].so -> oname);
strcat(errmsg, errmsg2);
}
sprintf(errmsg2, "(%lu partitions)\n",
emap.numPartitions);
strcat(errmsg, errmsg2);
eputs(errmsg);
#if NIX
fflush(stderr);
#endif
}
for (emap.partitionCnt = 0; emap.partitionCnt < emap.numPartitions;
emap.partitionCnt++) {
double partNumEmit;
unsigned long partEmitCnt;
/* Get photon origin within current source partishunn
* and build emission map */
photonOrigin [emap.src -> so -> otype] (&emap);
initPhotonEmission(&emap, pdfSamples);
/* Number of photons to emit from ziss partishunn --
* proportional to flux; photon ray weight and scalar
* flux are uniform (latter only varying in RGB). */
partNumEmit = numEmit * colorAvg(emap.partFlux) /
totalFlux;
partEmitCnt = (unsigned long)partNumEmit;
/* Probabilistically account for fractional photons */
if (pmapRandom(cntState) < partNumEmit - partEmitCnt)
partEmitCnt++;
/* Update local and shared (global) emission counter */
photonCnt -> numEmitted += partEmitCnt;
localNumEmitted += partEmitCnt;
/* Integer counter avoids FP rounding errors during
* iteration */
while (partEmitCnt--) {
RAY photonRay;
/* Emit photon based on PDF and trace through scene
* until absorbed/leaked */
emitPhoton(&emap, &photonRay);
#if 1
if (emap.port)
/* !!! PHOTON PORT REJECTION SAMPLING HACK: set
* !!! photon port as fake hit object for
* !!! primary ray to check for intersection in
* !!! tracePhoton() */
photonRay.ro = emap.port -> so;
#endif
tracePhoton(&photonRay);
}
/* Update shared global photon count for each pmap */
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t]) {
photonCnt -> numPhotons [t] +=
pmaps [t] -> numPhotons - lastNumPhotons [t];
lastNumPhotons [t] = pmaps [t] -> numPhotons;
}
#if !NIX
/* Synchronous progress report on Windoze */
if (!proc && photonRepTime > 0 &&
time(NULL) >= repLastTime + photonRepTime) {
repEmitted = repProgress = photonCnt -> numEmitted;
repComplete = photonCnt -> numComplete;
pmapDistribReport();
}
#endif
}
portCnt++;
} while (portCnt < numPhotonPorts);
}
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t] && !pmaps [t] -> numPhotons) {
/* Double preDistrib in case a photon map is empty and
* redo pass 1 --> possibility of infinite loop for
* pathological scenes (e.g. absorbing materials) */
preDistrib *= 2;
break;
}
if (t >= NUM_PMAP_TYPES)
/* No empty photon maps found; now do pass 2 */
passCnt++;
} while (passCnt < 2);
/* Flush heap buffa for every pmap one final time;
* avoids potential data corruption! */
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t]) {
flushPhotonHeap(pmaps [t]);
/* Heap file closed automatically on exit
fclose(pmaps [t] -> heap); */
#ifdef DEBUG_PMAP
sprintf(errmsg, "Proc %d: total %ld photons\n", proc,
pmaps [t] -> numPhotons);
eputs(errmsg);
#endif
}
#if NIX
/* Terminate subprocess */
exit(0);
#endif
}
else if (pid < 0)
error(SYSTEM, "failed to fork subprocess in distribPhotons");
}
#if NIX
/* PARENT PROCESS CONTINUES HERE */
#ifdef SIGCONT
/* Enable progress report signal handler */
signal(SIGCONT, pmapDistribReport);
#endif
/* Wait for subprocesses complete while reporting progress */
proc = numProc;
while (proc) {
while (waitpid(-1, &stat, WNOHANG) > 0) {
/* Subprocess exited; check status */
if (!WIFEXITED(stat) || WEXITSTATUS(stat))
error(USER, "failed photon distribution");
--proc;
}
/* Nod off for a bit and update progress */
sleep(1);
/* Asynchronous progress report from shared subprocess counters */
repEmitted = repProgress = photonCnt -> numEmitted;
repComplete = photonCnt -> numComplete;
repProgress = repComplete = 0;
for (t = 0; t < NUM_PMAP_TYPES; t++)
if ((pm = pmaps [t])) {
/* Get global photon count from shmem updated by subprocs */
repProgress += pm -> numPhotons = photonCnt -> numPhotons [t];
repComplete += pm -> distribTarget;
}
repComplete *= numProc;
if (photonRepTime > 0 && time(NULL) >= repLastTime + photonRepTime)
pmapDistribReport();
#ifdef SIGCONT
else signal(SIGCONT, pmapDistribReport);
#endif
}
#endif /* NIX */
/* ===================================================================
* POST-DISTRIBUTION - Set photon flux and build data struct for photon
* storage, etc.
* =================================================================== */
#ifdef SIGCONT
/* Reset signal handler */
signal(SIGCONT, SIG_DFL);
#endif
free(emap.samples);
/* Set photon flux */
totalFlux /= photonCnt -> numEmitted;
#if NIX
/* Photon counters no longer needed, unmap shared memory */
munmap(photonCnt, sizeof(*photonCnt));
close(shmFile);
unlink(shmFname);
#else
free(photonCnt);
#endif
if (verbose)
eputs("\n");
for (t = 0; t < NUM_PMAP_TYPES; t++)
if (pmaps [t]) {
if (verbose) {
sprintf(errmsg, "Building %s photon map\n", pmapName [t]);
eputs(errmsg);
#if NIX
fflush(stderr);
#endif
}
/* Build underlying data structure; heap is destroyed */
buildPhotonMap(pmaps [t], &totalFlux, NULL, numProc);
}
/* Precompute photon irradiance if necessary */
if (preCompPmap) {
if (verbose)
eputs("\n");
preComputeGlobal(preCompPmap);
}
if (verbose)
eputs("\n");
}

Event Timeline