diff --git a/mkpmap.c b/mkpmap.c index 76661c9..b210ae3 100644 --- a/mkpmap.c +++ b/mkpmap.c @@ -1,876 +1,879 @@ #ifndef lint static const char RCSid[] = "$Id: mkpmap.c,v 2.11 2021/04/14 11:26:25 rschregle Exp $"; #endif /* ========================================================================= Photon map generator Roland Schregle (roland.schregle@{hslu.ch, gmail.com}) (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components", SNSF #179067, "Light Fields for Spatio-Temporal Glare Assessment") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ========================================================================= $Id: mkpmap.c,v 2.11 2021/04/14 11:26:25 rschregle Exp $ */ #include "pmap.h" #include "pmapmat.h" #include "pmapsrc.h" #include "pmapcontrib.h" #include "pmaprand.h" #include "paths.h" #include "ambient.h" #include "resolu.h" #include "source.h" #include <ctype.h> #include <string.h> #include <sys/stat.h> /* Enable options for Ze Ekspertz only! */ #define PMAP_EKSPERTZ extern char VersionID []; char* progname; /* argv[0] */ int dimlist [MAXDIM]; /* sampling dimensions */ int ndims = 0; /* number of sampling dimensions */ char* octname = NULL; /* octree name */ CUBE thescene; /* scene top-level octree */ OBJECT nsceneobjs; /* number of objects in scene */ double srcsizerat = 0.01; /* source partition size ratio */ int backvis = 1; /* back face visibility */ int clobber = 0; /* overwrite output */ COLOR cextinction = BLKCOLOR; /* global extinction coefficient */ COLOR salbedo = BLKCOLOR; /* global scattering albedo */ double seccg = 0; /* global scattering eccentricity */ char *amblist [AMBLLEN + 1]; /* ambient include/exclude list */ int ambincl = -1; /* include == 1, exclude == 0 */ char *diagFile = NULL; /* diagnostics output file */ int rand_samp = 1; /* uncorrelated random sampling */ int nproc = 1; /* number of parallel processes */ #ifdef EVALDRC_HACK char *angsrcfile = NULL; /* angular source file for EvalDRC */ #endif /* Contribution photon map stuff: light source modifier lookup table and associated cleanup func */ static void chickenMcFreeNuggetz(void *contrib) { epfree((*(MODCONT *)contrib).binv); free(contrib); } LUTAB srcContrib = LU_SINIT(NULL, chickenMcFreeNuggetz); #if 0 char srcMod [MAXMODLIST]; #endif unsigned numSrcContrib = 0; /* The variables below interface with the RADIANCE suite merely as dummies to resolve external references and make the linker happy; most of them are unused. */ COLOR ambval = BLKCOLOR; double shadthresh = .05, ambacc = 0.2, shadcert = .5, minweight = 5e-3, ssampdist = 0, dstrsrc = 0.0, specthresh = 0.15, specjitter = 1.0, avgrefl = 0.5; int ambvwt = 0, ambssamp = 0, ambres = 32, ambounce = 0, directrelay = 1, directvis = 1, samplendx, do_irrad = 0, ambdiv = 128, vspretest = 512, maxdepth = 6; char *shm_boundary = NULL, *ambfile = NULL, RCCONTEXT [] = "RC."; /* Evaluation context for contrib pmap */ void (*trace)() = NULL, (*addobjnotify [])() = {ambnotify, NULL}; void printdefaults() /* print default values to stdout */ { #ifdef EVALDRC_HACK /* EvalDRC support */ puts("-A\t\t\t\t# angular source file"); #endif puts("-ae mod\t\t\t\t# exclude modifier"); puts("-aE file\t\t\t\t# exclude modifiers from file"); puts("-ai mod\t\t\t\t# include modifier"); puts("-aI file\t\t\t\t# include modifiers from file"); #ifdef PMAP_EKSPERTZ puts("-api xmin ymin zmin xmax ymax zmax" "\t# rectangular region of interest" ); puts("-apI xpos ypos zpos radius\t\t# spherical region of interest"); #endif puts("-apg file nPhotons\t\t\t# global photon map"); puts("-apc file nPhotons\t\t\t# caustic photon map"); puts("-apd file nPhotons\t\t\t# direct photon map"); puts("-app file nPhotons bwidth\t\t# precomputed global photon map"); puts("-apv file nPhotons\t\t\t# volume photon map"); puts("-apC file nPhotons bwidth compRatio" "\t# precomputed contribution photon map" ); #ifdef PMAP_PHOTONFLOW - puts("-apV file nPhotons\t\t\t# light flow volume photon map"); + puts("-apV file nPhotons\t\t\t# light flow photon map"); + puts("-apT file nPhotons\t\t\t# temporal light flow photon map"); #endif printf("-apD %f\t\t\t\t# predistribution factor\n", preDistrib); printf("-apM %d\t\t\t\t\t# max predistrib passes\n", maxPreDistrib); #if 1 /* Kept for backwards compat, will be gradually phased out by -ld, -lr */ printf("-apm %ld\t\t\t\t# limit photon bounces\n", photonMaxBounce); #endif puts("-apo+ mod\t\t\t\t# photon port modifier"); puts("-apO+ file\t\t\t\t# photon ports from file"); printf("-apP %f\t\t\t\t# precomputation factor\n", finalGather); printf("-apr %d\t\t\t\t\t# random seed\n", randSeed); puts("-aps mod\t\t\t\t# antimatter sensor modifier"); puts("-apS file\t\t\t\t# antimatter sensors from file"); printf(backvis ? "-bv+\t\t\t\t\t# back face visibility on\n" : "-bv-\t\t\t\t\t# back face visibility off\n" ); printf("-dp %.1f\t\t\t\t# PDF samples / sr\n", pdfSamples); printf("-ds %f\t\t\t\t# source partition size ratio\n", srcsizerat); printf("-e %s\t\t\t\t# diagnostics output file\n", diagFile); printf(clobber ? "-fo+\t\t\t\t\t# force overwrite\n" : "-fo-\t\t\t\t\t# do not overwrite\n" ); #ifdef PMAP_EKSPERTZ /* (Formerly) NU STUFF for Ze Exspertz! */ printf("-ld %.1f\t\t\t\t\t# limit photon distance\n", photonMaxDist); printf("-lr %ld\t\t\t\t# limit photon bounces\n", photonMaxBounce); #endif printf("-ma %.2f %.2f %.2f\t\t\t# scattering albedo\n", colval(salbedo,RED), colval(salbedo,GRN), colval(salbedo,BLU) ); printf("-me %.2e %.2e %.2e\t\t# extinction coefficient\n", colval(cextinction,RED), colval(cextinction,GRN), colval(cextinction,BLU) ); printf("-mg %.2f\t\t\t\t# scattering eccentricity\n", seccg); #if NIX /* Multiprocessing on NIX only; so tuff luck, Windoze Weenies! */ printf("-n %d\t\t\t\t\t# number of parallel processes\n", nproc); #endif printf("-t %-9d\t\t\t\t# time between reports\n", photonRepTime); printf(verbose ? "-v+\t\t\t\t\t# verbose console output\n" : "-v-\t\t\t\t\t# terse console output\n" ); #if 0 /* TODO: Do contrib/coeffs here or in rcontrib? */ printf( "-V%c\t\t\t\t# precompute %s\n", contrib ? '+' : '-', contrib ? "contributions" : "coefficients" ); #endif } #if 0 /* Disabled for now; will attempt to extract bins from photon map header */ static char *appendParams (char *accParams, char **params, int n) /* Accumulate n next parameter strings from params to string accParams. Returns new accmulated string. */ { int i; if (params && n) for (i = 0; i < n; i++) if (params [i] && strlen(params [i])) /* Reallocate and factor in delimiter (space) and terminator */ if (accParams = realloc( accParams, strlen(accParams) + strlen(params [i]) + 2 )) { strcat(accParams, " "); strcat(accParams, params [i]); } else error( SYSTEM, "cannot append binning parameters for contrib photon map" ); return accParams; } #endif int main (int argc, char* argv []) { #define check(ol, al) if ( \ argv [i][ol] || badarg(argc - i - 1,argv + i + 1, al) \ ) goto badopt /* Evaluate boolean option, setting var accordingly */ #define check_bool(olen, var) switch (argv [i][olen]) { \ case '\0': \ var = !var; break; \ case 'y': case 'Y': case 't': case 'T': case '+': case '1': \ var = 1; break; \ case 'n': case 'N': case 'f': case 'F': case '-': case '0': \ var = 0; break; \ default: \ goto badopt; \ } /* Evaluate trinary option, setting bits v1 and v2 in var accordingly */ #define check_tri(olen, v1, v2, var) switch (argv [i][olen]) { \ case '\0': case '+': \ var = v1; break; \ case '-': \ var = v2; break;\ case '0': \ var = v1 | v2; break; \ default: \ goto badopt; \ } /* Check target number of photons with optional multiplier suffix begins with a digit */ #define checkNumPhotons(i) if (!isdigit(argv [i][0])) \ if (argv [i][0] == '-') \ goto badopt; \ else { \ sprintf(errmsg, "invalid number of photons %s", argv [i]); \ error(USER, errmsg); \ } int loadflags = IO_CHECK | IO_SCENE | IO_TREE | IO_BOUNDS, rval, i, j, n, binCnt = 0; char **portLp = photonPortList, **sensLp = photonSensorList, **amblp = NULL, sbuf [MAXSTR], portFlags [2] = "\0\0", *binVal = NULL, *binParm = NULL; struct stat pmstat; /* Global program name */ progname = fixargv0(argv [0]); /* Initialize object types */ initotypes(); /* Initialize cal function routines for contrib photon binning */ initfunc(); setcontext(RCCONTEXT); #if defined(_WIN32) || defined(_WIN64) /* Increase file limit to maximum */ /* XXX: DO WE NEED THIS FOR CONTRIB PHOTONS? _setmaxstdio(2048); */ #endif /* Parse options */ for (i = 1; i < argc; i++) { /* Eggs-pand arguments */ while ((rval = expandarg(&argc, &argv, i))) if (rval < 0) { sprintf(errmsg, "cannot eggs-pand '%s'", argv [i]); error(SYSTEM, errmsg); } if (argv[i] == NULL) break; if (!strcmp(argv [i], "-version")) { puts(VersionID); quit(0); } if (!strcmp(argv [i], "-defaults") || !strcmp(argv [i], "-help")) { printdefaults(); quit(0); } /* Get octree */ if (i == argc - 1) { octname = argv [i]; break; } switch (argv [i][1]) { case 'a': /* Ambient */ switch (argv [i][2]) { case 'i': /* Ambient include */ case 'I': check(3, "s"); if (ambincl != 1) { ambincl = 1; amblp = amblist; } if (isupper(argv [i][2])) { /* Add modifiers from file */ rval = wordfile( amblp, AMBLLEN - (amblp - amblist), getpath(argv [++i], getrlibpath(), R_OK) ); if (rval < 0) { sprintf( errmsg, "cannot open ambient include file \"%s\"", argv [i] ); error(SYSTEM, errmsg); } amblp += rval; } else { /* Add modifier from next arg */ *amblp++ = savqstr(argv [++i]); *amblp = NULL; } break; case 'e': /* Ambient exclude */ case 'E': check(3, "s"); if (ambincl != 0) { ambincl = 0; amblp = amblist; } if (isupper(argv [i][2])) { /* Add modifiers from file */ rval = wordfile( amblp, AMBLLEN - (amblp - amblist), getpath(argv [++i], getrlibpath(), R_OK) ); if (rval < 0) { sprintf(errmsg, "cannot open ambient exclude file \"%s\"", argv [i] ); error(SYSTEM, errmsg); } amblp += rval; } else { /* Add modifier from next arg */ *amblp++ = savqstr(argv [++i]); *amblp = NULL; } break; case 'p': /* Pmap-specific */ switch (argv [i][3]) { case 'g': /* Global photon map */ checkNumPhotons(i + 2); check(4, "ss"); globalPmapParams.fileName = argv [++i]; globalPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!globalPmapParams.distribTarget) goto badopt; globalPmapParams.minGather = globalPmapParams.maxGather = 0; break; case 'p': /* Precomputed global photon map */ checkNumPhotons(i + 2); check(4, "ssi"); preCompPmapParams.fileName = argv [++i]; preCompPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!preCompPmapParams.distribTarget) goto badopt; preCompPmapParams.minGather = preCompPmapParams.maxGather = atoi(argv [++i]); if (!preCompPmapParams.maxGather) goto badopt; break; case 'c': /* Caustic photon map */ checkNumPhotons(i + 2); check(4, "ss"); causticPmapParams.fileName = argv [++i]; causticPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!causticPmapParams.distribTarget) goto badopt; break; case 'v': /* Volume photon map */ checkNumPhotons(i + 2); check(4, "ss"); volumePmapParams.fileName = argv [++i]; volumePmapParams.distribTarget = parseMultiplier(argv [++i]); if (!volumePmapParams.distribTarget) goto badopt; break; case 'd': /* Direct photon map */ checkNumPhotons(i + 2); check(4, "ss"); directPmapParams.fileName = argv [++i]; directPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!directPmapParams.distribTarget) goto badopt; break; case 'C': /* Precomputed contribution photon map */ checkNumPhotons(i + 2); check(4, "ssif"); contribPmapParams.fileName = argv [++i]; contribPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!contribPmapParams.distribTarget) goto badopt; contribPmapParams.minGather = contribPmapParams.maxGather = atoi(argv [++i]); if (!contribPmapParams.maxGather) goto badopt; compressRatio = atof(argv [++i]); if (compressRatio < FTINY || compressRatio > 1 - FTINY ) { error(USER, "contribution photon " "compression ratio must be in range (0, 1)" ); goto badopt; } break; #ifdef PMAP_PHOTONFLOW - case 'V': /* Light flow volume photon map */ + /* Light flow is a variant of volume photon map */ + case 'V': /* Light flow photon map */ + case 'T': /* Temporal light flow photon map */ check(4, "ss"); lightFlowPmapParams.fileName = argv [++i]; lightFlowPmapParams.distribTarget = parseMultiplier(argv [++i]); if (!lightFlowPmapParams.distribTarget) goto badopt; /* Set zero absorption and forward scattering for global mist; extinction up to user of local mist defined in octree */ /* setcolor(cextinction, 1, 1, 1); */ setcolor(salbedo, 1, 1, 1); seccg = 1; break; #endif case 'D': /* Predistribution factor */ check(4, "f"); preDistrib = atof(argv [++i]); if (preDistrib <= 0) error(USER, "predistrib factor must be > 0"); break; case 'M': /* Max predistribution passes */ check(4, "i"); maxPreDistrib = atoi(argv [++i]); if (maxPreDistrib <= 0) error(USER, "max predistrib passes must be > 0"); break; #if 1 /* Kept for backwards compat, to be phased out by -lr */ case 'm': /* Max photon bounces */ check(4, "i"); photonMaxBounce = atol(argv [++i]); if (photonMaxBounce <= 0) error(USER, "max photon bounces must be > 0"); break; #endif #ifdef PMAP_EKSPERTZ case 'i': /* Add rectangular region of interest */ case 'I': /* Add spherical region of interest */ check(4, isupper(argv [j=i][3]) ? "ffff" : "ffffff"); n = pmapNumROI; pmapROI = realloc(pmapROI, ++pmapNumROI * sizeof(PhotonMapROI) ); if (!pmapROI) error(SYSTEM, "failed to allocate ROI"); pmapROI [n].pos [0] = atof(argv [++i]); pmapROI [n].pos [1] = atof(argv [++i]); pmapROI [n].pos [2] = atof(argv [++i]); pmapROI [n].siz [0] = atof(argv [++i]); if (isupper(argv [j][3])) { /* Spherical ROI; radius^2 */ pmapROI [n].siz [0] *= pmapROI [n].siz [0]; PMAP_ROI_SETSPHERE(pmapROI + n); if (pmapROI [n].siz [0] <= FTINY) error(USER, "region of interest has invalid radius" ); } else { /* Rectangular ROI */ pmapROI [n].siz [1] = atof(argv [++i]); pmapROI [n].siz [2] = atof(argv [++i]); for (j = 0; j < 3; j++) { /* Pos at rectangle centre, siz symmetric */ pmapROI [n].pos [j] = 0.5 * ( pmapROI [n].pos [j] + pmapROI [n].siz [j] ); pmapROI [n].siz [j] = fabs( pmapROI [n].siz [j] - pmapROI [n].pos [j] ); if (pmapROI [n].siz [j] <= FTINY) error(USER, "region of interest has invalid size" ); } } break; #endif case 'P': /* Global photon precomp ratio */ check(4, "f"); finalGather = atof(argv [++i]); if (finalGather <= 0 || finalGather > 1) error(USER, "global photon precomputation ratio " "must be in range ]0, 1]" ); break; case 'o': /* Photon port */ case 'O': /* Check for bad arg and length, taking into account * default forward orientation if none specified, in * order to maintain previous behaviour */ check(argv [i][4] ? 5 : 4, "s"); /* Get port orientation flags */ check_tri(4, PMAP_PORTFWD, PMAP_PORTBWD, portFlags [0] ); if (isupper(argv [i][3])) { /* Add port modifiers from file */ rval = wordfile(portLp, MAXSET - (portLp - photonPortList), getpath(argv [++i], getrlibpath(), R_OK) ); if (rval < 0) { sprintf(errmsg, "cannot open photon port file %s", argv [i] ); error(SYSTEM, errmsg); } /* HACK: append port orientation flags to every * modifier; note this requires reallocation */ for (; rval--; portLp++) { j = strlen(*portLp); if (!(*portLp = realloc(*portLp, j + 2))) { sprintf(errmsg, "cannot allocate photon port modifiers" " from file %s", argv [i] ); error(SYSTEM, errmsg); } strcat(*portLp, portFlags); } } else { /* Append port flags to port modifier, add to * port list and mark of end list with NULL */ strcpy(sbuf, argv [++i]); strcat(sbuf, portFlags); *portLp++ = savqstr(sbuf); *portLp = NULL; } break; case 'r': /* Random seed */ check(4, "i"); randSeed = atoi(argv [++i]); break; case 's': /* Antimatter sensor */ case 'S': check(4, "s"); if (isupper(argv[i][3])) { /* Add sensor modifiers from file */ rval = wordfile(sensLp, MAXSET - (sensLp - photonSensorList), getpath(argv [++i], getrlibpath(), R_OK) ); if (rval < 0) { sprintf(errmsg, "cannot open antimatter sensor file %s", argv [i] ); error(SYSTEM, errmsg); } sensLp += rval; } else { /* Append modifier to sensor list, mark end with * NULL */ *sensLp++ = savqstr(argv [++i]); *sensLp = NULL; } break; default: goto badopt; } break; default: goto badopt; } break; case 'b': switch (argv [i][2]) { case 'v': /* Back face visibility */ check_bool(3, backvis); break; case 'n': /* Binning count for precomputed contrib. photon map. Save params for export to file for rcontrib. */ check(3, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ binCnt = (int)(eval(argv [++i]) + .5); break; default: /* Binning expression for precomp. contrib. photon map. Save params for export to file for rcontrib. */ check(2,"s"); /* binParams = appendParams(binParams, argv + i, 2); */ binVal = argv [++i]; } break; case 'd': /* Direct */ switch (argv [i][2]) { case 'p': /* PDF samples */ check(3, "f"); pdfSamples = atof(argv [++i]); break; case 's': /* Source partition size ratio */ check(3, "f"); srcsizerat = atof(argv [++i]); break; default: goto badopt; } break; case 'e': switch (argv [i][2]) { case 'f': /* Diagnostics file */ check(2, "s"); diagFile = argv [++i]; break; default: /* Functional expression for precomputed contrib. pmap */ check(2, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ scompile(argv [++i], NULL, 0); } break; case 'f': switch (argv [i][2]) { case 'o': /* Force overwrite */ check_bool(3, clobber); break; default: /* Function file for precomputed contribution photon map */ check(2, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ loadfunc(argv[++i]); } break; #ifdef PMAP_EKSPERTZ case 'l': /* Limits */ switch (argv [i][2]) { case 'd': /* Limit photon path distance */ check(3, "f"); photonMaxDist = atof(argv [++i]); if (photonMaxDist <= 0) error(USER, "max photon distance must be > 0"); break; case 'r': /* Limit photon bounces */ check(3, "i"); photonMaxBounce = atol(argv [++i]); if (photonMaxBounce <= 0) error(USER, "max photon bounces must be > 0"); break; default: goto badopt; } break; #endif case 'm': switch (argv[i][2]) { case 'e': /* Mist Eggs-tinction coefficient */ check(3, "fff"); setcolor(cextinction, atof(argv [i + 1]), atof(argv [i + 2]), atof(argv [i + 3]) ); i += 3; break; case 'a': /* Mist albedo */ check(3, "fff"); setcolor(salbedo, atof(argv [i + 1]), atof(argv [i + 2]), atof(argv [i + 3]) ); i += 3; break; case 'g': /* Mist scattering eccentricity */ check(3, "f"); seccg = atof(argv [++i]); break; default: /* Modifier name for precomputed contrib. photon map */ check(2, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ addSourceModifier(&srcContrib, &numSrcContrib, argv [++i], binParm, binVal, binCnt ); break; } break; case 'M': /* Modifier file for precomputed contribution photon map */ check(2, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ addSourceModfile(&srcContrib, &numSrcContrib, argv [++i], binParm, binVal, binCnt ); break; #if NIX case 'n': /* Num parallel processes (NIX only) */ check(2, "i"); nproc = atoi(argv [++i]); if (nproc > PMAP_MAXPROC) { nproc = PMAP_MAXPROC; sprintf(errmsg, "too many parallel processes, " "clamping to %d\n", nproc ); error(WARNING, errmsg); } break; #endif case 'p': /* Parameter setting(s) for precomputed contrib. photon map */ check(2, "s"); /* binParams = appendParams(binParams, argv + i, 2); */ set_eparams(binParm = argv [++i]); break; case 't': /* Timer */ check(2, "i"); photonRepTime = atoi(argv [++i]); break; case 'v': /* Verbosity */ check_bool(2, verbose); break; #ifdef EVALDRC_HACK case 'A': /* Capt. B's (now historic) angular source file hack */ check(2,"s"); angsrcfile = argv[++i]; break; #endif default: goto badopt; } } /* Open diagnostics file */ if (diagFile) { if (!freopen(diagFile, "a", stderr)) quit(2); fprintf(stderr, "**************\n*** PID %5d: ", getpid()); printargs(argc, argv, stderr); putc('\n', stderr); fflush(stderr); } #ifdef NICE /* Lower priority */ nice(NICE); #endif if (octname == NULL) error(USER, "missing octree argument"); /* Allocate photon maps and set parameters */ for (i = 0; i < NUM_PMAP_TYPES; i++) { setPmapParam(photonMaps + i, pmapParams + i); /* Don't overwrite existing photon map unless clobbering enabled */ if (photonMaps [i] && !stat(photonMaps [i] -> fileName, &pmstat) && !clobber ) { sprintf(errmsg, "photon map file %s exists, not overwritten", photonMaps [i] -> fileName ); error(USER, errmsg); } } for (i = 0; i < NUM_PMAP_TYPES && !photonMaps [i]; i++); if (i >= NUM_PMAP_TYPES) error(USER, "no photon maps specified"); readoct(octname, loadflags, &thescene, NULL); #ifdef EVALDRC_HACK if (angsrcfile) readobj(angsrcfile); /* load angular sources */ #endif nsceneobjs = nobjects; /* Get sources */ marksources(); /* Do forward pass and build photon maps */ if (contribPmap) /* Just build contrib pmap, ignore others */ distribPhotonContrib(contribPmap, &srcContrib, numSrcContrib, nproc); else distribPhotons(photonMaps, nproc); /* TODO: Where do we save binParams and wavelet coeffs? */ /* Save photon maps; no idea why GCC needs an explicit cast here... */ savePmaps((const PhotonMap**)photonMaps, argc, argv); cleanUpPmaps(photonMaps); quit(0); badopt: sprintf(errmsg, "command line error at '%s'", argv[i]); error(USER, errmsg); #undef check #undef check_bool return 0; } diff --git a/pmapdata.c b/pmapdata.c index 91df123..1687d15 100644 --- a/pmapdata.c +++ b/pmapdata.c @@ -1,768 +1,774 @@ #ifndef lint static const char RCSid[] = "$Id: pmapdata.c,v 2.23 2021/04/14 11:26:25 rschregle Exp $"; #endif /* ========================================================================= Photon map types and high level interface to nearest neighbour lookups in underlying point cloud data structure. The default data structure is an in-core kd-tree (see pmapkdt.{h,c}). This can be overriden with the PMAP_OOC compiletime switch, which enables an out-of-core octree (see oococt.{h,c}). Roland Schregle (roland.schregle@{hslu.ch, gmail.com}) (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components", SNSF #179067, "Light Fields for Spatio-Temporal Glare Assessment") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ========================================================================= $Id: pmapdata.c,v 2.23 2021/04/14 11:26:25 rschregle Exp $ */ #include "pmapdata.h" #include "pmapdens.h" #include "pmaprand.h" #include "pmapmat.h" #include "otypes.h" #include "random.h" PhotonMap *photonMaps [NUM_PMAP_TYPES] = { NULL, NULL, NULL, NULL, NULL, NULL, #ifdef PMAP_PHOTONFLOW NULL #endif }; /* Include routines to handle underlying point cloud data structure */ #ifdef PMAP_OOC #include "pmapooc.c" #else #include "pmapkdt.c" #endif /* Ambient include/exclude set (from ambient.c) */ #ifndef MAXASET #define MAXASET 4095 #endif extern OBJECT ambset [MAXASET+1]; /* Callback to print photon attributes acc. to user defined format */ int (*printPhoton)(RAY *r, Photon *p, PhotonMap *pm); /* PHOTON MAP BUILD ROUTINES ------------------------------------------ */ void initPhotonMap (PhotonMap *pmap, PhotonMapType t) /* Init photon map 'n' stuff... */ { if (!pmap) return; pmap -> numPhotons = 0; pmap -> biasCompHist = NULL; pmap -> maxPos [0] = pmap -> maxPos [1] = pmap -> maxPos [2] = -FHUGE; pmap -> minPos [0] = pmap -> minPos [1] = pmap -> minPos [2] = FHUGE; pmap -> minGathered = pmap -> maxGathered = pmap -> totalGathered = 0; pmap -> gatherTolerance = gatherTolerance; pmap -> minError = pmap -> maxError = pmap -> rmsError = 0; pmap -> numDensity = 0; pmap -> distribRatio = 1; pmap -> type = t; pmap -> squeue.node = NULL; pmap -> squeue.len = 0; #ifdef PMAP_PHOTONFLOW /* Init path lookup table and its key buffer */ pmap -> pathLUT = NULL; pmap -> pathLUTKeys = NULL; pmap -> numPathLUTKeys = 0; #endif /* Init local RNG state */ pmap -> randState [0] = 10243; pmap -> randState [1] = 39829; pmap -> randState [2] = 9433; pmapSeed(randSeed, pmap -> randState); /* Set up type-specific photon lookup callback */ pmap -> lookup = pmapLookup [t]; /* Mark photon contribution origin as unused */ pmap -> lastContribOrg.srcIdx = pmap -> lastContribOrg.srcBin = -1; pmap -> numContribOrg = 0; pmap -> contribOrg = NULL; /* Init storage */ pmap -> heap = NULL; pmap -> heapBuf = NULL; pmap -> heapBufLen = 0; #ifdef PMAP_OOC OOC_Null(&pmap -> store); #else kdT_Null(&pmap -> store); #endif } void initPhotonHeap (PhotonMap *pmap) { int fdFlags; if (!pmap) error(INTERNAL, "undefined photon map in initPhotonHeap"); if (!pmap -> heap) { /* Open heap file */ mktemp(strcpy(pmap -> heapFname, PMAP_TMPFNAME)); if (!(pmap -> heap = fopen(pmap -> heapFname, "w+b"))) error(SYSTEM, "failed opening heap file in initPhotonHeap"); #ifdef F_SETFL /* XXX is there an alternative needed for Windows? */ fdFlags = fcntl(fileno(pmap -> heap), F_GETFL); fcntl(fileno(pmap -> heap), F_SETFL, fdFlags | O_APPEND); #endif/* ftruncate(fileno(pmap -> heap), 0); */ } } void flushPhotonHeap (PhotonMap *pmap) { int fd; const unsigned long len = pmap -> heapBufLen * sizeof(Photon); if (!pmap) error(INTERNAL, "undefined photon map in flushPhotonHeap"); if (!pmap -> heap || !pmap -> heapBuf) { /* Silently ignore undefined heap error(INTERNAL, "undefined heap in flushPhotonHeap"); */ return; } /* Atomically seek and write block to heap */ /* !!! Unbuffered I/O via pwrite() avoids potential race conditions * !!! and buffer corruption which can occur with lseek()/fseek() * !!! followed by write()/fwrite(). */ fd = fileno(pmap -> heap); #ifdef DEBUG_PMAP sprintf(errmsg, "Proc %d: flushing %ld photons from pos %ld\n", getpid(), pmap -> heapBufLen, lseek(fd, 0, SEEK_END) / sizeof(Photon) ); eputs(errmsg); #endif /*if (pwrite(fd, pmap -> heapBuf, len, lseek(fd, 0, SEEK_END)) != len) */ if (write(fd, pmap -> heapBuf, len) != len) error(SYSTEM, "failed append to heap file in flushPhotonHeap"); #if NIX if (fsync(fd)) error(SYSTEM, "failed fsync in flushPhotonHeap"); #endif pmap -> heapBufLen = 0; } #ifdef DEBUG_PMAP static int checkPhotonHeap (FILE *file) /* Check heap for nonsensical or duplicate photons */ { Photon p, lastp; int i, dup; rewind(file); memset(&lastp, 0, sizeof(lastp)); while (fread(&p, sizeof(p), 1, file)) { dup = 1; for (i = 0; i <= 2; i++) { if (p.pos [i] < thescene.cuorg [i] || p.pos [i] > thescene.cuorg [i] + thescene.cusize ) { sprintf(errmsg, "corrupt photon in heap at [%f, %f, %f]\n", p.pos [0], p.pos [1], p.pos [2] ); error(WARNING, errmsg); } dup &= p.pos [i] == lastp.pos [i]; } if (dup) { sprintf(errmsg, "consecutive duplicate photon in heap " "at [%f, %f, %f]\n", p.pos [0], p.pos [1], p.pos [2] ); error(WARNING, errmsg); } } return 0; } #endif int newPhoton (PhotonMap* pmap, const RAY* ray) { unsigned i; Photon photon; COLOR photonFlux; /* Account for distribution ratio */ if (!pmap || pmapRandom(pmap -> randState) > pmap -> distribRatio) return -1; /* Don't store on sources */ if (ray -> robj > -1 && islight(objptr(ray -> ro -> omod) -> otype)) return -1; /* Ignore photon if modifier in/outside exclude/include set */ if (ambincl != -1 && ray -> ro && ambincl != inset(ambset, ray->ro->omod) ) return -1; if (pmapNumROI && pmapROI) { unsigned inROI = 0; FVECT photonDist; /* Store photon if within a region of interest (for ze Ecksperts!) Note size of spherical ROI is squared. */ for (i = 0; !inROI && i < pmapNumROI; i++) { VSUB(photonDist, ray -> rop, pmapROI [i].pos); inROI = (PMAP_ROI_ISSPHERE(pmapROI + i) ? DOT(photonDist, photonDist) <= pmapROI [i].siz [0] : fabs(photonDist [0]) <= pmapROI [i].siz [0] && fabs(photonDist [1]) <= pmapROI [i].siz [1] && fabs(photonDist [2]) <= pmapROI [i].siz [2] ); } if (!inROI) return -1; } /* Adjust flux according to distribution ratio and ray weight */ copycolor(photonFlux, ray -> rcol); #if 0 /* Factored out ray -> rweight as deprecated (?) for pmap, and infact erroneously attenuates volume photon flux based on extinction, which is already factored in by photonParticipate() */ scalecolor( photonFlux, ray -> rweight / (pmap -> distribRatio ? pmap -> distribRatio : 1) ); #else scalecolor(photonFlux, 1.0 / (pmap -> distribRatio ? pmap -> distribRatio : 1) ); #endif setPhotonFlux(&photon, photonFlux); /* Set photon position and flags */ VCOPY(photon.pos, ray -> rop); photon.flags = 0; photon.caustic = PMAP_CAUSTICRAY(ray); if (isContribPmap(pmap)) { /* Set contrib photon's origin and subprocess index (the latter to * linearise the origin array after photon distribution is complete). * Also set origin's source index, thereby marking it as used. Note * the origin's bin has already been set by newPhotonOrigin(). */ photon.aux.contribOrgIdx = pmap -> numContribOrg; photon.proc = PMAP_GETRAYPROC(ray); pmap -> lastContribOrg.srcIdx = ray -> rsrc; } else { /* Set photon's path ID from ray serial num and subprocess index */ photon.aux.pathID = ray -> rno; photon.proc = PMAP_GETRAYPROC(ray); } /* Set normal */ for (i = 0; i <= 2; i++) photon.norm [i] = 127.0 * (isVolumePmap(pmap) ? ray -> rdir [i] : ray -> ron [i] ); if (!pmap -> heapBuf) { /* Lazily allocate heap buffa */ #if NIX /* Randomise buffa size to temporally decorellate flushes in * multiprocessing mode */ srandom(randSeed + getpid()); pmap -> heapBufSize = PMAP_HEAPBUFSIZE * (0.5 + frandom()); #else /* Randomisation disabled for single processes on WIN; also useful * for reproducability during debugging */ pmap -> heapBufSize = PMAP_HEAPBUFSIZE; #endif if (!(pmap -> heapBuf = calloc(pmap -> heapBufSize, sizeof(Photon)))) error(SYSTEM, "failed heap buffer allocation in newPhoton"); pmap -> heapBufLen = 0; } /* Photon initialised; now append to heap buffa */ memcpy(pmap -> heapBuf + pmap -> heapBufLen, &photon, sizeof(Photon)); if (++pmap -> heapBufLen >= pmap -> heapBufSize) /* Heap buffa full, flush to heap file */ flushPhotonHeap(pmap); pmap -> numPhotons++; /* Print photon attributes */ if (printPhoton) /* Non-const kludge */ printPhoton((RAY*)ray, &photon, pmap); return 0; } void buildPhotonMap (PhotonMap *pmap, double *photonFlux, PhotonContribOriginIdx *originOfs, unsigned nproc ) { unsigned long n, nCheck = 0; unsigned i; Photon *p; COLOR flux; char nuHeapFname [sizeof(PMAP_TMPFNAME)]; FILE *nuHeap; /* Need double here to reduce summation errors */ double avgFlux [3] = {0, 0, 0}, CoG [3] = {0, 0, 0}, CoGdist = 0; FVECT d; if (!pmap) error(INTERNAL, "undefined photon map in buildPhotonMap"); /* Get number of photons from heapfile size */ if (fseek(pmap -> heap, 0, SEEK_END) < 0) error(SYSTEM, "failed seek to end of photon heap in buildPhotonMap"); pmap -> numPhotons = ftell(pmap -> heap) / sizeof(Photon); if (!pmap -> numPhotons) error(INTERNAL, "empty photon map in buildPhotonMap"); if (!pmap -> heap) error(INTERNAL, "no heap in buildPhotonMap"); #ifdef DEBUG_PMAP eputs("Checking photon heap consistency...\n"); checkPhotonHeap(pmap -> heap); sprintf(errmsg, "Heap contains %ld photons\n", pmap -> numPhotons); eputs(errmsg); #endif /* Allocate heap buffa */ if (!pmap -> heapBuf) { pmap -> heapBufSize = PMAP_HEAPBUFSIZE; pmap -> heapBuf = calloc(pmap -> heapBufSize, sizeof(Photon)); if (!pmap -> heapBuf) error(SYSTEM, "failed to allocate postprocessed photon heap in buildPhotonMap" ); } /* We REALLY don't need yet another @%&*! heap just to hold the scaled * photons, but can't think of a quicker fix... */ mktemp(strcpy(nuHeapFname, PMAP_TMPFNAME)); if (!(nuHeap = fopen(nuHeapFname, "w+b"))) error(SYSTEM, "failed to open postprocessed photon heap in buildPhotonMap" ); rewind(pmap -> heap); #ifdef DEBUG_PMAP eputs("Postprocessing photons...\n"); #endif while (!feof(pmap -> heap)) { #ifdef DEBUG_PMAP printf("Reading %lu at %lu... ", pmap -> heapBufSize, ftell(pmap->heap) ); #endif pmap -> heapBufLen = fread(pmap -> heapBuf, sizeof(Photon), pmap -> heapBufSize, pmap -> heap ); #ifdef DEBUG_PMAP printf("Got %lu\n", pmap -> heapBufLen); #endif if (ferror(pmap -> heap)) error(SYSTEM, "failed to read photon heap in buildPhotonMap"); for (n = pmap -> heapBufLen, p = pmap -> heapBuf; n; n--, p++) { /* Update min and max pos and set photon flux */ for (i = 0; i <= 2; i++) { if (p -> pos [i] < pmap -> minPos [i]) pmap -> minPos [i] = p -> pos [i]; else if (p -> pos [i] > pmap -> maxPos [i]) pmap -> maxPos [i] = p -> pos [i]; /* Update centre of gravity with photon position */ CoG [i] += p -> pos [i]; } if (originOfs) /* Linearise contrib photon origin index from subprocess index * using the per-subprocess offsets in originOfs */ p -> aux.contribOrgIdx += originOfs [p -> proc]; /* Scale photon's flux (hitherto normalised to 1 over RGB); in * case of a contrib photon map, this is done per light source, * and photonFlux is assumed to be an array */ getPhotonFlux(p, flux); if (photonFlux) { scalecolor(flux, photonFlux [isContribPmap(pmap) ? photonSrcIdx(pmap, p) : 0] ); setPhotonFlux(p, flux); } /* Update average photon flux; need a double here */ addcolor(avgFlux, flux); } /* Write modified photons to new heap */ fwrite(pmap -> heapBuf, sizeof(Photon), pmap -> heapBufLen, nuHeap); if (ferror(nuHeap)) error(SYSTEM, "failed postprocessing photon flux in buildPhotonMap" ); nCheck += pmap -> heapBufLen; } #ifdef DEBUG_PMAP if (nCheck < pmap -> numPhotons) error(INTERNAL, "truncated photon heap in buildPhotonMap"); #endif /* Finalise average photon flux */ scalecolor(avgFlux, 1.0 / pmap -> numPhotons); copycolor(pmap -> photonFlux, avgFlux); /* Average photon positions to get centre of gravity */ for (i = 0; i < 3; i++) pmap -> CoG [i] = CoG [i] /= pmap -> numPhotons; rewind(pmap -> heap); /* Compute average photon distance to centre of gravity */ while (!feof(pmap -> heap)) { pmap -> heapBufLen = fread(pmap -> heapBuf, sizeof(Photon), pmap -> heapBufSize, pmap -> heap ); for (n = pmap -> heapBufLen, p = pmap -> heapBuf; n; n--, p++) { VSUB(d, p -> pos, CoG); CoGdist += DOT(d, d); } } pmap -> CoGdist = CoGdist /= pmap -> numPhotons; /* Swap heaps, discarding unscaled photons */ fclose(pmap -> heap); unlink(pmap -> heapFname); pmap -> heap = nuHeap; strcpy(pmap -> heapFname, nuHeapFname); #ifdef PMAP_OOC OOC_BuildPhotonMap(pmap, nproc); #else kdT_BuildPhotonMap(pmap); #endif /* Trash heap and its buffa */ free(pmap -> heapBuf); fclose(pmap -> heap); unlink(pmap -> heapFname); pmap -> heap = NULL; pmap -> heapBuf = NULL; } /* PHOTON MAP SEARCH ROUTINES ------------------------------------------ */ /* Dynamic max photon search radius increase and reduction factors */ #define PMAP_MAXDIST_INC 4 #define PMAP_MAXDIST_DEC 0.9 /* Num successful lookups before reducing in max search radius */ #define PMAP_MAXDIST_CNT 1000 /* Threshold below which we assume increasing max radius won't help */ #define PMAP_SHORT_LOOKUP_THRESH 1 /* Coefficient for adaptive maximum search radius */ #define PMAP_MAXDIST_COEFF 100 void findPhotons (PhotonMap* pmap, const RAY* ray) { int redo = 0; if (!pmap -> squeue.len) { /* Lazy init priority queue */ #ifdef PMAP_OOC OOC_InitFindPhotons(pmap); #else kdT_InitFindPhotons(pmap); #endif pmap -> minGathered = pmap -> maxGather; pmap -> maxGathered = pmap -> minGather; pmap -> totalGathered = 0; pmap -> numLookups = pmap -> numShortLookups = 0; pmap -> shortLookupPct = 0; pmap -> minError = FHUGE; pmap -> maxError = -FHUGE; pmap -> rmsError = 0; /* SQUARED max search radius limit is based on avg photon distance to * centre of gravity, unless fixed by user (maxDistFix > 0) */ pmap -> maxDist0 = pmap -> maxDist2Limit = maxDistFix > 0 ? maxDistFix * maxDistFix : PMAP_MAXDIST_COEFF * pmap -> squeue.len * pmap -> CoGdist / pmap -> numPhotons; } do { pmap -> squeue.tail = 0; pmap -> maxDist2 = pmap -> maxDist0; /* XXX/NOTE: status returned by XXX_FindPhotons() is currently ignored; if no photons are found, an empty queue is returned under the assumption all photons are too distant to contribute significant flux. */ #ifdef PMAP_PHOTONFLOW - if (isVolumePmap(pmap) && !isLightFlowPmap(pmap)) { + /* If sphericalIrrad = 0, planar irradiance is evaluated from + lightflow photons, hence these are associated with the plane + defined by the ray normal, and filtered accordingly by their + incident directions. + If spherical Irrad = 1, mean spherical irradiance is evaluated, + the normal is passed as NULL, which disables normal filtering. */ + if (isVolumePmap(pmap) && sphericalIrrad) { #else if (isVolumePmap(pmap)) { #endif /* Search position is ray -> rorg for volume photons, since we have no intersection point. Photon normals are ignored; these are incident directions for the volume scattering func). */ #ifdef PMAP_OOC OOC_FindPhotons(pmap, ray -> rorg, NULL); #else kdT_FindPhotons(pmap, ray -> rorg, NULL); #endif } else { #ifdef PMAP_OOC OOC_FindPhotons(pmap, ray -> rop, ray -> ron); #else kdT_FindPhotons(pmap, ray -> rop, ray -> ron); #endif } #ifdef PMAP_LOOKUP_INFO fprintf(stderr, "%d/%d %s photons found within radius %.3f " "at (%.2f,%.2f,%.2f) on %s\n", pmap -> squeue.tail, pmap -> squeue.len, pmapName [pmap -> type], sqrt(pmap -> maxDist2), ray -> rop [0], ray -> rop [1], ray -> rop [2], ray -> ro ? ray -> ro -> oname : "<null>" ); #endif if (pmap -> squeue.tail < pmap -> squeue.len * pmap->gatherTolerance) { /* Short lookup; too few photons found */ if (pmap -> squeue.tail > PMAP_SHORT_LOOKUP_THRESH) { /* Ignore short lookups which return fewer than * PMAP_SHORT_LOOKUP_THRESH photons under the assumption there * really are no photons in the vicinity, and increasing the max * search radius therefore won't help */ #ifdef PMAP_LOOKUP_WARN sprintf(errmsg, "%d/%d %s photons found at (%.2f,%.2f,%.2f) on %s", pmap -> squeue.tail, pmap->squeue.len, pmapName [pmap->type], ray -> rop [0], ray -> rop [1], ray -> rop [2], ray -> ro ? ray -> ro -> oname : "<null>" ); error(WARNING, errmsg); #endif /* Bail out after warning if maxDist is fixed */ if (maxDistFix > 0) return; if (pmap -> maxDist0 < pmap -> maxDist2Limit) { /* Increase max search radius if below limit & redo search */ pmap -> maxDist0 *= PMAP_MAXDIST_INC; #ifdef PMAP_LOOKUP_REDO redo = 1; #endif #ifdef PMAP_LOOKUP_WARN sprintf(errmsg, redo ? "restarting photon lookup with max radius %.1e" : "max photon lookup radius adjusted to %.1e", pmap -> maxDist0 ); error(WARNING, errmsg); #endif } #ifdef PMAP_LOOKUP_REDO else { sprintf(errmsg, "max photon lookup radius clamped to %.1e", pmap -> maxDist0 ); error(WARNING, errmsg); } #endif } /* Reset successful lookup counter */ pmap -> numLookups = 0; } else { /* Skip search radius reduction if maxDist is fixed */ if (maxDistFix > 0) return; /* Increment successful lookup counter and reduce max search radius if * wraparound */ pmap -> numLookups = (pmap -> numLookups + 1) % PMAP_MAXDIST_CNT; if (!pmap -> numLookups) pmap -> maxDist0 *= PMAP_MAXDIST_DEC; redo = 0; } } while (redo); } Photon *find1Photon (PhotonMap *pmap, const RAY* ray, Photon *photon) { /* Init (squared) search radius to avg photon dist to centre of gravity */ float maxDist2_0 = pmap -> CoGdist; int found = 0; #ifdef PMAP_LOOKUP_REDO #define REDO 1 #else #define REDO 0 #endif do { pmap -> maxDist2 = maxDist2_0; #ifdef PMAP_OOC found = OOC_Find1Photon(pmap, ray -> rop, ray -> ron, photon); #else found = kdT_Find1Photon(pmap, ray -> rop, ray -> ron, photon); #endif if (found < 0) { /* Expand search radius to retry */ maxDist2_0 *= 2; #ifdef PMAP_LOOKUP_WARN sprintf(errmsg, "failed 1-NN photon lookup" #ifdef PMAP_LOOKUP_REDO ", retrying with search radius %.2f", maxDist2_0 #endif ); error(WARNING, errmsg); #endif } } while (REDO && found < 0); /* Return photon buffer containing valid photon, else NULL */ return found < 0 ? NULL : photon; } void getPhoton (PhotonMap *pmap, PhotonIdx idx, Photon *photon) { #ifdef PMAP_OOC if (OOC_GetPhoton(pmap, idx, photon)) #else if (kdT_GetPhoton(pmap, idx, photon)) #endif error(INTERNAL, "failed photon lookup"); } Photon *getNearestPhoton (const PhotonSearchQueue *squeue, PhotonIdx idx) { #ifdef PMAP_OOC return OOC_GetNearestPhoton(squeue, idx); #else return kdT_GetNearestPhoton(squeue, idx); #endif } PhotonIdx firstPhoton (const PhotonMap *pmap) { #ifdef PMAP_OOC return OOC_FirstPhoton(pmap); #else return kdT_FirstPhoton(pmap); #endif } /* PHOTON MAP CLEANUP ROUTINES ------------------------------------------ */ void deletePhotons (PhotonMap* pmap) { #ifdef PMAP_OOC OOC_Delete(&pmap -> store); #else kdT_Delete(&pmap -> store); #endif free(pmap -> squeue.node); free(pmap -> biasCompHist); pmap -> numPhotons = pmap -> minGather = pmap -> maxGather = pmap -> squeue.len = pmap -> squeue.tail = 0; #ifdef PMAP_PHOTONFLOW if (pmap -> pathLUT) { lu_done(pmap -> pathLUT); free(pmap -> pathLUT); } if (pmap -> pathLUTKeys) { unsigned k; for (k = 0; k < pmap->squeue.len + 1; free(pmap->pathLUTKeys [k++])); free(pmap -> pathLUTKeys); } #endif } diff --git a/pmapdata.h b/pmapdata.h index f0e36af..d15e83e 100644 --- a/pmapdata.h +++ b/pmapdata.h @@ -1,392 +1,392 @@ /* RCSid $Id: pmapdata.h,v 2.14 2020/04/08 15:14:21 rschregle Exp $ */ /* ========================================================================= Photon map types and interface to nearest neighbour lookups in underlying point cloud data structure. The default data structure is an in-core kd-tree (see pmapkdt.{h,c}). This can be overriden with the PMAP_OOC compiletime switch, which enables an out-of-core octree (see oococt.{h,c}). Defining PMAP_FLOAT_FLUX stores photon flux as floats rather than packed RGBE for greater precision; this may be necessary when the flux differs significantly in individual colour channels, e.g. with highly saturated colours. Roland Schregle (roland.schregle@{hslu.ch, gmail.com}) (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components", SNSF #179067, "Light Fields for Spatio-Temporal Glare Assessment") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ========================================================================= $Id: pmapdata.h,v 2.14 2020/04/08 15:14:21 rschregle Exp $ */ #ifndef PMAPDATA_H #define PMAPDATA_H #ifndef NIX #if defined(_WIN32) || defined(_WIN64) #define NIX 0 #else #define NIX 1 #endif #endif #if (defined(PMAP_OOC) && !NIX) #error "OOC currently only supported on NIX -- tuff luck." #endif #include "ray.h" #include "pmaptype.h" #include "paths.h" #include "lookup.h" #include <stdint.h> /* Photon origin for light source contributions; these are only used to precompute contribution photons. They are referenced by contribution photons (see origin field in struct Photon below) in a surjective mapping, since multiple photons may share the same origin if they lie along its associated path. For this reason it is more efficient to factor this data out of the photons themselves and consolidate it here until the photons have been precomputed, after which it is no longer needed. */ typedef struct { int16 srcIdx, /* Index of emitting light source */ srcBin; /* Bin number corresponding to incident dir */ } PhotonContribOrigin; typedef uint32 PhotonPathID; typedef uint32 PhotonContribOriginIdx; #define PMAP_MAXCONTRIBORG UINT32_MAX #define photonSrcIdx(pm, p) ((pm) -> contribOrg \ ? (pm) -> contribOrg [(p) -> aux.contribOrgIdx].srcIdx \ : (p) -> aux.pathID\ ) #define photonSrcBin(pm, p) ( \ (pm) -> contribOrg [(p) -> aux.contribOrgIdx].srcBin \ ) #define photonSrcMod(pm, p) findmaterial(source [photonSrcIdx(pm, p)].so) - /* Multipurpose auxiliary photon attribute type and limit */ + /* Multipurpose auxiliary photon attribute type */ typedef union { /* Distance travelled by photon (= path length / time of flight) for temporal light flow */ float pathLen; /* Index into PhotonContribOrigin array for contrib photons */ PhotonContribOriginIdx contribOrgIdx; /* Unique path ID for all other photon types */ PhotonPathID pathID; } PhotonAuxAttrib; /* Macros for photon's generating subprocess field */ #ifdef PMAP_OOC #define PMAP_PROCBITS 7 #else #define PMAP_PROCBITS 5 #endif #define PMAP_MAXPROC (1 << PMAP_PROCBITS) #define PMAP_GETRAYPROC(r) ((r) -> crtype >> 8) #define PMAP_SETRAYPROC(r,p) ((r) -> crtype |= p << 8) typedef struct { float pos [3]; /* Photon position */ signed char norm [3]; /* Encoded normal / incident direction [volume photons] */ union { struct { #ifndef PMAP_OOC unsigned char discr : 2; /* kd-tree discriminator axis */ #endif unsigned char caustic : 1; /* Specularly scattered (=caustic) */ /* Photon's generating subprocess index, used for primary ray * index linearisation when building contrib pmaps; note this is * reduced for kd-tree to accommodate the discriminator field */ unsigned char proc : PMAP_PROCBITS; }; unsigned char flags; }; /* Photon flux in watts or lumen / photon contribution [contrib photons] / average wavelet coefficient [precomputed contrib photons] */ #ifdef PMAP_FLOAT_FLUX COLOR flux; #else COLR flux; #endif /* Auxiliary field; this is a multipurpose, type-specific field used by the following photon types (as identified by enum PhotonMapType in pmaptype.h): PMAP_TYPE_CONTRIB: Index into photon map's contrib origin array. PMAP_TYPE_TEMPLIGHTFLOW: Distance travelled by photon / time of flight All others: Photon path ID. */ PhotonAuxAttrib aux; } Photon; /* Define PMAP_FLOAT_FLUX to store photon flux as floats instead of * compact RGBE, which was found to improve accuracy in analytical * validation. */ #ifdef PMAP_FLOAT_FLUX #define setPhotonFlux(p,f) copycolor((p) -> flux, f) #define getPhotonFlux(p,f) copycolor(f, (p) -> flux) #else #define setPhotonFlux(p,f) setcolr((p)->flux, (f)[0], (f)[1], (f)[2]) #define getPhotonFlux(p,f) colr_color(f, (p) -> flux) #endif /* Bias compensation history node */ typedef struct { COLOR irrad; float weight; } PhotonBiasCompNode; /* Forward declaration */ struct PhotonMap; /* Define search queue and underlying data struct types */ #ifdef PMAP_OOC #include "pmapooc.h" #else #include "pmapkdt.h" #endif /* Mean size of heapfile write buffer, in number of photons */ #define PMAP_HEAPBUFSIZE 1e6 /* Mean idle time between heap locking attempts, in usec */ #define PMAP_HEAPBUFSLEEP 2e6 /* Temporary filename for photon heaps */ #define PMAP_TMPFNAME TEMPLATE #define PMAP_TMPFNLEN (TEMPLEN + 1) typedef struct PhotonMap { PhotonMapType type; /* See pmaptype.h */ char *fileName; /* Photon map file */ /* ================================================================ * PRE/POST-BUILD STORAGE * ================================================================ */ FILE *heap; /* Unsorted photon heap prior to construction of store */ char heapFname [sizeof(PMAP_TMPFNAME)]; Photon *heapBuf; /* Write buffer for above */ unsigned long heapBufLen, /* Current & max size of heapBuf */ heapBufSize; PhotonStorage store; /* Photon storage in space subdividing data struct */ /* ================================================================ * PHOTON DISTRIBUTION STUFF * ================================================================ */ unsigned long distribTarget, /* Num stored specified by user */ numPhotons; /* Num actually stored */ float distribRatio; /* Probability of photon storage */ COLOR photonFlux; /* Average photon flux */ unsigned short randState [3]; /* Local RNG state */ /* ================================================================ * PHOTON LOOKUP STUFF * ================================================================ */ union { /* Flags passed to findPhotons() */ char lookupCaustic : 1; char lookupFlags; }; PhotonSearchQueue squeue; /* Search queue for photon lookups */ unsigned minGather, /* Specified min/max photons per */ maxGather; /* density estimate */ /* NOTE: All distances are SQUARED */ float maxDist2, /* Max search radius */ maxDist0, /* Initial value for above */ maxDist2Limit, /* Hard limit for above */ gatherTolerance; /* Fractional deviation from minGather/ maxGather for short lookup */ void (*lookup)( struct PhotonMap*, RAY*, COLOR ); /* Callback for type-specific photon * lookup (usually density estimate) */ #ifdef PMAP_PHOTONFLOW LUTAB *pathLUT; /* Photon path lookup table to filter volume photons */ char **pathLUTKeys; /* Preallocated buffer to store keys for path lookup table */ unsigned numPathLUTKeys; /* Num keys currently in key buffer (= next free entry at tail) */ #endif /* ================================================================ * BIAS COMPENSATION STUFF * ================================================================ */ PhotonBiasCompNode *biasCompHist; /* Bias compensation history */ /* ================================================================ * STATISTIX * ================================================================ */ unsigned long totalGathered, /* Total photons gathered */ numDensity, /* Num density estimates */ numLookups, /* Counters for short photon lookups */ numShortLookups; unsigned minGathered, /* Min/max photons actually gathered */ maxGathered, /* per density estimate */ shortLookupPct; /* % of short lookups for stats */ float minError, /* Min/max/rms density estimate error */ maxError, rmsError, CoGdist, /* Avg distance to centre of gravity */ maxPos [3], /* Max & min photon positions */ minPos [3]; FVECT CoG; /* Centre of gravity (avg photon pos) */ /* ================================================================ * CONTRIBUTION PHOTON STUFF * ================================================================ */ PhotonContribOrigin *contribOrg, /* Contribution origin array */ lastContribOrg; /* Current contrib origin for contrib photon distribution */ PhotonContribOriginIdx numContribOrg; /* Number of contrib origins */ LUTAB *srcContrib; /* LUT for source contribs */ } PhotonMap; /* Photon maps by type (see PhotonMapType) */ extern PhotonMap *photonMaps []; /* Macros for specific photon map types */ #define globalPmap (photonMaps [PMAP_TYPE_GLOBAL]) #define preCompPmap (photonMaps [PMAP_TYPE_PRECOMP]) #define causticPmap (photonMaps [PMAP_TYPE_CAUSTIC]) #define directPmap (photonMaps [PMAP_TYPE_DIRECT]) #define contribPmap (photonMaps [PMAP_TYPE_CONTRIB]) #ifdef PMAP_PHOTONFLOW #define volumePmap ( \ lightFlowPmap ? lightFlowPmap : photonMaps [PMAP_TYPE_VOLUME] \ ) #define lightFlowPmap (photonMaps [PMAP_TYPE_LIGHTFLOW]) #else #define volumePmap (photonMaps [PMAP_TYPE_VOLUME]) #endif /* Photon map type tests */ #define isGlobalPmap(p) ((p) -> type == PMAP_TYPE_GLOBAL) #define isCausticPmap(p) ((p) -> type == PMAP_TYPE_CAUSTIC) #define isContribPmap(p) ((p) -> type == PMAP_TYPE_CONTRIB) #ifdef PMAP_PHOTONFLOW #define isVolumePmap(p) ( \ (p) -> type == PMAP_TYPE_VOLUME || isLightFlowPmap(p) \ ) #define isLightFlowPmap(p) ((p) -> type == PMAP_TYPE_LIGHTFLOW) #else #define isVolumePmap(p) ((p) -> type == PMAP_TYPE_VOLUME) #endif void initPhotonMap (PhotonMap *pmap, PhotonMapType t); /* Initialise empty photon map of specified type */ int newPhoton (PhotonMap *pmap, const RAY *ray); /* Create new photon with ray's direction, intersection point, and flux, and append to unsorted photon heap pmap -> heap. The photon is accepted with probability pmap -> distribRatio for global density control; if the photon is rejected, -1 is returned, else 0. The flux is scaled by ray -> rweight and 1 / pmap -> distribRatio. */ void initPhotonHeap (PhotonMap *pmap); /* Open photon heap file */ void flushPhotonHeap (PhotonMap *pmap); /* Flush photon heap buffa pmap -> heapBuf to heap file pmap -> heap; * used by newPhoton() and to finalise heap in distribPhotons(). */ void buildPhotonMap ( PhotonMap *pmap, double *photonFlux, PhotonContribOriginIdx *originOfs, unsigned nproc ); /* Postpress unsorted photon heap pmap -> heap and build underlying data * structure pmap -> store. This is prerequisite to photon lookups with * findPhotons(). */ /* PhotonFlux is the flux per photon averaged over RGB; this is * multiplied with each photon's flux during the postprocess. In the * case of a contribution photon map, this is an array with a separate * flux specific to each light source due to non-uniform photon emission; * Otherwise it is referenced as a scalar value. Flux is not scaled if * photonFlux == NULL. */ /* Photon map construction may be parallelised if nproc > 1, if * supported. The heap is destroyed on return. */ /* OriginOfs is an array of index offsets for the contribution photon * origins in pmap->contribOrg generated by each of the nproc subprocesses * during contrib photon distribution (see distribPhotonContrib()). These * offsets are used to linearise the photon origin indices in the * postprocess. This linearisation is skipped if originOfs == NULL, * e.g. when building a global/caustic/volume photon map, where the * origins are serial path IDs. */ void findPhotons (PhotonMap* pmap, const RAY *ray); /* Find pmap -> squeue.len closest photons to ray -> rop with similar normal. For volume photons ray -> rorg is used and the normal is ignored (being the incident direction in this case). Found photons are placed search queue starting with the furthest photon at pmap -> squeue.node, and pmap -> squeue.tail being the number actually found. */ Photon *find1Photon (PhotonMap *pmap, const RAY *ray, Photon *photon); /* Find single closest photon to ray -> rop with similar normal. Return NULL if none found, else the supplied Photon* buffer, indicating that it contains a valid photon. */ void getPhoton (PhotonMap *pmap, PhotonIdx idx, Photon *photon); /* Retrieve photon referenced by idx from pmap -> store */ Photon *getNearestPhoton (const PhotonSearchQueue *squeue, PhotonIdx idx); /* Retrieve photon from NN search queue after calling findPhotons() */ PhotonIdx firstPhoton (const PhotonMap *pmap); /* Index to first photon, to be passed to getPhoton(). Indices to * subsequent photons can be optained via increment operator (++) */ void deletePhotons (PhotonMap*); /* Free dem mammaries... */ #endif diff --git a/pmapdens.c b/pmapdens.c index 46a0278..2053763 100644 --- a/pmapdens.c +++ b/pmapdens.c @@ -1,574 +1,586 @@ #ifndef lint static const char RCSid[] = "$Id$"; #endif /* ====================================================================== Photon map density estimation routines Roland Schregle (roland.schregle@{hslu.ch, gmail.com}) (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ====================================================================== $Id$ */ #include "pmapdens.h" #include "pmapbias.h" #include "otypes.h" #include "random.h" /* Photon map lookup functions per type */ void (*pmapLookup [NUM_PMAP_TYPES])(PhotonMap*, RAY*, COLOR) = { photonDensity, photonPreCompDensity, photonDensity, volumePhotonDensity, photonDensity, photonDensity, #ifdef PMAP_PHOTONFLOW lightFlowPhotonDensity #endif }; void photonDensity (PhotonMap *pmap, RAY *ray, COLOR irrad) /* Surface-bound photon density estimate for global and caustic photons. Returns irradiance at ray -> rop. */ { unsigned i; float r2; COLOR flux; Photon *photon; const PhotonSearchQueueNode *sqn; setcolor(irrad, 0, 0, 0); if (!pmap -> maxGather) return; /* Ignore sources */ if (ray -> ro && islight(objptr(ray -> ro -> omod) -> otype)) return; findPhotons(pmap, ray); /* Need at least 2 photons */ if (pmap -> squeue.tail < 2) { #ifdef PMAP_NONEFOUND sprintf(errmsg, "no photons found on %s at (%.3f, %.3f, %.3f)", ray -> ro ? ray -> ro -> oname : "<null>", ray -> rop [0], ray -> rop [1], ray -> rop [2] ); error(WARNING, errmsg); #endif return; } if (pmap -> minGather == pmap -> maxGather) { /* No bias compensation. Just do a plain vanilla estimate */ sqn = pmap -> squeue.node + 1; /* Average radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); /* Skip the extra photon */ for (i = 1 ; i < pmap -> squeue.tail; i++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); getPhotonFlux(photon, flux); #ifdef PMAP_EPANECHNIKOV /* Apply Epanechnikov kernel to photon flux based on photon dist */ scalecolor(flux, 2 * (1 - sqn -> dist2 / r2)); #endif addcolor(irrad, flux); } /* Divide by search area PI * r^2, 1 / PI required as ambient normalisation factor */ scalecolor(irrad, 1 / (PI * PI * r2)); return; } else /* Apply bias compensation to density estimate */ biasComp(pmap, irrad); } void photonPreCompDensity (PhotonMap *pmap, RAY *r, COLOR irrad) /* Surface-bound photon density estimate for (single) precomputed photon. Returns precomputed irradiance at ray -> rop. */ { Photon p; setcolor(irrad, 0, 0, 0); /* Ignore sources */ if (r -> ro && islight(objptr(r -> ro -> omod) -> otype)) return; if (find1Photon(preCompPmap, r, &p)) /* p contains a found photon, so get its irradiance, otherwise it * remains zero under the assumption all photons are too distant * to contribute significantly */ getPhotonFlux(&p, irrad); } void volumePhotonDensity (PhotonMap *pmap, RAY *ray, COLOR irrad) /* Volume photon density estimate using Henyey-Greenstein phase function. Returns inscattered irradiance at ray -> rop. */ { unsigned i; float r2, gecc2, ph; COLOR flux; Photon *photon; const PhotonSearchQueueNode *sqn; setcolor(irrad, 0, 0, 0); if (!pmap -> maxGather) return; findPhotons(pmap, ray); /* Need at least 2 photons */ if (pmap -> squeue.tail < 2) return; gecc2 = ray -> gecc * ray -> gecc; sqn = pmap -> squeue.node + 1; /* Average radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); /* Skip the extra photon */ for (i = 1; i < pmap -> squeue.tail; i++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); /* Compute phase function for inscattering from photon */ if (gecc2 <= FTINY) ph = 1; else { ph = DOT(ray -> rdir, photon -> norm) / 127; ph = 1 + gecc2 - 2 * ray -> gecc * ph; ph = (1 - gecc2) / (ph * sqrt(ph)); } getPhotonFlux(photon, flux); scalecolor(flux, ph); addcolor(irrad, flux); } /* Divide by search volume 4 / 3 * PI * r^3 and phase function normalization factor 1 / (4 * PI) */ scalecolor(irrad, 3 / (16 * PI * PI * r2 * sqrt(r2))); } #ifdef PMAP_PHOTONFLOW void volumePhotonSphIrrad (PhotonMap *pmap, RAY *ray, COLOR irrad) /* Evaluate mean spherical irradiance from volume photons inscattered at * ray->rop, ignoring participating medium. This evaluates the scalar * irradiance of a physical light field represented by "photon flow". */ { unsigned i; float r2; COLOR flux; Photon *photon; const PhotonSearchQueueNode *sqn; setcolor(irrad, 0, 0, 0); if (!pmap -> maxGather) return; findPhotons(pmap, ray); /* Need at least 2 photons */ if (pmap -> squeue.tail < 2) return; /* Issue warning if few too photons; this will particularly happen if PMAP_PHOTONFILT is enabled, since photons from duplicate paths are pruned during the search, and this is not accounted for by the automatic search radius adjustment. As a workaround for now, it's up to the user provide a sufficiently large fixed search radius with the -ma option to rtrace */ if (pmap -> squeue.tail < pmap -> squeue.len) { - sprintf( - errmsg, + sprintf(errmsg, "short lookup in photon flow; consider -am %.3f or greater", 2 * sqrt(pmap -> maxDist2) ); error(WARNING, errmsg); } sqn = pmap -> squeue.node + 1; /* Average radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); /* Skip the extra photon */ for (i = 1; i < pmap -> squeue.tail; i++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); getPhotonFlux(photon, flux); #ifdef PMAP_EPANECHNIKOV /* Apply Epanechnikov kernel to photon flux based on photon dist */ /* XXX: THIS KERNEL IS UNTESTED!!! */ scalecolor(flux, 1 - sqn -> dist2 / r2); #endif addcolor(irrad, flux); #ifdef PMAP_DEBUGPATHS - printf( - "%f\t%f\t%f\t%f\t%f\t%d:%010d\n", - sqrt(sqn -> dist2), + printf("%f\t%f\t%f\t%f\t%f\t%d:%010d\n", sqrt(sqn -> dist2), photon -> pos [0], photon -> pos [1], photon -> pos [2], flux [0], photon -> proc, photon -> primary ); #endif } #ifdef PMAP_DEBUGPATHS printf("N = %d\tr = %f\n", pmap -> squeue.tail, sqrt(r2)); #endif #ifdef PMAP_PATHFILT /* Divide accumulated flux by sphere surface area and ambient normalisation factor PI */ scalecolor(irrad, 1 / (4 * PI * PI * r2)); #else /* Divide accumulated flux by sphere volume and ambient normalisation factor PI */ scalecolor(irrad, 3 / (4 * PI * PI * r2 * sqrt(r2))); #endif } void volumePhotonCubicIllum (PhotonMap *pmap, RAY *ray, COLOR illum) /* Evaluate cubic illuminance according to Cuttle from volume photons * inscattered at ray->rop, ignoring participating medium. The evaluation * interprets the volume photon map as a representation of the physical * light field ("photon flow"). */ { unsigned ax, p, i ,j; float r2, cosNorm, volNorm; Photon *photon; const PhotonSearchQueueNode *sqn; COLOR photonFlux, E [6], Evec [3], Esymm [3], EvecLum, EsymmSum = {0, 0, 0}; FVECT norm0; const FVECT norm [6] = { /* Cube face normals (INVERTED since volume photon directions point TOWARDS their halfspace of incidence */ {-1, 0, 0}, {1, 0, 0}, {0, -1, 0}, {0, 1, 0}, {0, 0, -1}, {0, 0, 1} }; setcolor(illum, 0, 0, 0); if (!pmap -> maxGather) return; /* Save ray normal */ VCOPY(norm0, ray -> ron); #if 1 /* Perform separate lookup for each axis. This improves accuracy particularly for low bandwitdhs, as the radius adjusts to the density of incident photons along each axis. This comes at the expense of 6 lookups.*/ for (ax = 0; ax < 6; ax++) { /* Set cube face normal for current axis */ VCOPY(ray -> ron, norm [ax]); setcolor(E [ax], 0, 0, 0); /* Find photons incident from this axis, filtering by normal */ findPhotons(pmap, ray); /* Need at least 2 photons, else skip */ if (pmap -> squeue.tail < 2) continue; if (pmap -> squeue.tail < pmap -> squeue.len) { sprintf( errmsg, "short lookup in photon flow; consider -am %.3f or greater", 2 * sqrt(pmap -> maxDist2) ); error(WARNING, errmsg); } sqn = pmap -> squeue.node + 1; /* Avg radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); #ifdef _PMAP_DEBUGPATHS printf("N = %d\tr = %f\n", pmap -> squeue.tail, sqrt(r2)); #endif /* Skip the extra photon */ for (p = 1; p < pmap -> squeue.tail; p++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); getPhotonFlux(photon, photonFlux); #ifdef PMAP_EPANECHNIKOV /* Apply Epanechnikov kernel to photon flux based on its dist */ /* XXX: THIS KERNEL IS UNTESTED!!! */ scalecolor(photonFlux, 1 - sqn -> dist2 / r2); #endif /* Project photon dir onto axis */ cosNorm = DOT(norm [ax], photon -> norm) / 127; scalecolor(photonFlux, cosNorm); addcolor(E [ax], photonFlux); #ifdef PMAP_DEBUGPATHS printf( "%f\t%f\t%f\t%f\t%f\t%d:%010d\n", sqrt(sqn -> dist2), photon -> pos [0], photon -> pos [1], photon -> pos [2], photonFlux [0], photon -> proc, photon -> primary ); #endif } /* Normalise illuminance for spherical volume */ scalecolor(E [ax], 3 / (4 * PI * r2 * sqrt(r2))); printf( "E [%s%d] = %.1f\n", ax & 1 ? "-" : "+", ax >> 1, luminance(E [ax]) ); } #else /* Perform 1 lookup for all axes, and distribute photon flux according incident direction. While faster than separate lookups per axis, the accuracy is poor along those axes with few incident photons, particularly in conjunction with low bandwidths. */ findPhotons(pmap, ray); /* Need at least 2 photons */ if (pmap -> squeue.tail < 2) return; if (pmap -> squeue.tail < pmap -> squeue.len) { sprintf( errmsg, "short lookup in photon flow; consider -am %.3f or greater", 2 * sqrt(pmap -> maxDist2) ); error(WARNING, errmsg); } sqn = pmap -> squeue.node + 1; /* Avg radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); /* Volumetric normalisation factor */ #ifdef PMAP_PATHFILT volNorm = 1 / (2 * PI * r2); #else # if 1 volNorm = 3 / (4 * PI * r2 * sqrt(r2)); #else /* Hemispherical volume */ volNorm = 3 / (2 * PI * r2 * sqrt(r2)); #endif #endif #ifdef PMAP_DEBUGPATHS printf("N = %d\tr = %f\n", pmap -> squeue.tail, sqrt(r2)); #endif for (ax = 0; ax < 6; ax++) { /* Set cube face normal for current axis */ setcolor(E [ax], 0, 0, 0); } /* Skip the extra photon */ for (p = 1; p < pmap -> squeue.tail; p++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); getPhotonFlux(photon, photonFlux); #ifdef PMAP_EPANECHNIKOV /* Apply Epanechnikov kernel to photon flux based on its dist */ /* XXX: THIS KERNEL IS UNTESTED!!! */ scalecolor(photonFlux, 1 - sqn -> dist2 / r2); #endif for (ax = 0; ax < 6; ax++) { cosNorm = DOT(norm [ax], photon -> norm) / 127; if (cosNorm > 0) { /* Project photon dir onto axis */ scalecolor(photonFlux, cosNorm); addcolor(E [ax], photonFlux); } } #ifdef PMAP_DEBUGPATHS printf( "%f\t%f\t%f\t%f\t%f\t%d:%010d\n", sqrt(sqn -> dist2), photon -> pos [0], photon -> pos [1], photon -> pos [2], photonFlux [0], photon -> proc, photon -> primary ); #endif } for (ax = 0; ax < 6; ax++) { /* Normalise illuminance for spherical volume */ scalecolor(E [ax], volNorm); printf( "E [%s%d] = %.1f\n", ax & 1 ? "-" : "+", ax >> 1, luminance(E [ax]) ); } #endif /* Calculate illuminance vector and symmetric illuminance. Unlike Cuttle's original formulation, we defer scalar reduction and preserve RGB components in chromatic analysis is required. */ for (i = 0; i < 3; i++) { for (j = 0; j < 3; j++) { Evec [i][j] = E [i << 1][j] - E [(i << 1) + 1][j]; Esymm [i][j] = 0.5 * ( E [i << 1][j] + E [(i << 1) + 1][j] - fabs(Evec [i][j]) ); } /* Reduce Evec to luminance per axis, sum Esymm over axes */ EvecLum [i] = luminance(Evec [i]); addcolor(EsymmSum, Esymm [i]); } scalecolor(EsymmSum, 1.0 / 3); printf( "Evec\t= [%.1f\t%.1f\t%.1f]\n", EvecLum [0], EvecLum [1], EvecLum [2] ); printf( "Esym\t= [%.1f\t%.1f\t%.1f]\n", luminance(Esymm [0]), luminance(Esymm [1]), luminance(Esymm [2]) ); /* Reduce to scalars and return as triplet: - illuminance vector magnitude |Evec| - scalar symmetry |Esymm| - scalar illuminance |Esr|. */ illum [0] = sqrt(DOT(EvecLum, EvecLum)); illum [1] = luminance(EsymmSum); illum [2] = 0.25 * illum [0] + illum [1]; /* Normalise by RADIANCE-specific irradiance factor PI */ scalecolor(illum, 1 / PI); /* Restore ray normal */ VCOPY(ray -> ron, norm0); } #endif /* PMAP_PHOTONFLOW */ #ifdef PMAP_PHOTONFLOW void lightFlowPhotonDensity (PhotonMap *pmap, RAY *ray, COLOR irrad) - /* Evaluate irradiance from volume photons at ray->rop for plane defined - * by normal ray -> ron, ignoring participating medium. The evaluation - * interprets the volume photon map as a representation of the "flow" - * of light, i.e. a physical light field. */ + /* Evaluate irradiance from volume photons at position ray -> rop, + ignoring participating medium. This evaluation interprets the volume + photon map as a representation of the "flow" of light, i.e. a physical + light field. + If sphericalIrrad == 0, the planar irradiance is evaluated with respect + to the plane defined by the ray normal ray -> ron. + If sphericalIrrad == 1, the mean spherical irradiance is evaluated, + and the normal ray -> ron is ignored. */ { unsigned p, i; float r2, cosNorm; Photon *photon; const PhotonSearchQueueNode *sqn; COLOR photonFlux; FVECT photonDir; setcolor(irrad, 0, 0, 0); if (!pmap -> maxGather) return; /* Flip normal since volume photon directions point towards plane of - incidence */ + incidence (has no effect if evaluating mean spherical irrad) */ flipsurface(ray); - + /* Find photons with incident direction filtered by dot product with normal (note that found photons may also be located _behind_ the plane, as if having passed through it. */ findPhotons(pmap, ray); if (pmap -> squeue.tail < pmap -> squeue.len) { sprintf(errmsg, "short lookup in photon flow; consider -am %.3f or greater", 2 * sqrt(pmap -> maxDist2) ); error(WARNING, errmsg); } /* Need at least 2 photons, else bail out */ if (pmap -> squeue.tail < 2) return; sqn = pmap -> squeue.node + 1; /* Avg radius^2 between furthest two photons to improve accuracy */ r2 = max(sqn -> dist2, (sqn + 1) -> dist2); r2 = 0.25 * (pmap -> maxDist2 + r2 + 2 * sqrt(pmap -> maxDist2 * r2)); #ifdef PMAP_DEBUGPATHS printf("N = %d\tr = %f\n", pmap -> squeue.tail, sqrt(r2)); #endif /* Skip the extra photon */ for (p = 1; p < pmap -> squeue.tail; p++, sqn++) { photon = getNearestPhoton(&pmap -> squeue, sqn -> idx); getPhotonFlux(photon, photonFlux); #ifdef PMAP_EPANECHNIKOV /* Apply Epanechnikov kernel to photon flux based on its dist */ /* XXX: THIS KERNEL IS UNTESTED!!! */ scalecolor(photonFlux, 1 - sqn -> dist2 / r2); #endif - /* Jitter photon direction since stored as 8-bit signed int */ - for (i = 0; i < 3; i++) { - photonDir [i] = photon -> norm [i]; + if (!sphericalIrrad) { + /* Associate photons with plane defined by ray normal */ + for (i = 0; i < 3; i++) { + /* Jitter photon direction since stored as 8-bit signed int */ + photonDir [i] = photon -> norm [i]; + if (photon -> norm [i] && photon -> norm [i] & 127 < 127) + photonDir [i] += photon -> norm [i] & 0x80 + ? frandom() : -frandom(); + } - if (photon -> norm [i] && photon -> norm [i] & 127 < 127) - photonDir [i] += photon -> norm [i] & 0x80 - ? frandom() : -frandom(); + /* Project photon direction onto normal */ + cosNorm = DOT(ray -> ron, photonDir) / 127; + scalecolor(photonFlux, cosNorm); } - - /* Project photon direction onto normal */ - cosNorm = DOT(ray -> ron, photonDir) / 127; - scalecolor(photonFlux, cosNorm); addcolor(irrad, photonFlux); #ifdef PMAP_DEBUGPATHS printf("%f\t%f\t%f\t%f\t%f\t%d:%010d\n", sqrt(sqn -> dist2), photon -> pos [0], photon -> pos [1], photon -> pos [2], photonFlux [0], photon -> proc, photon -> primary ); #endif } - /* Normalise accumulated flux by spherical volume, include - RADIANCE-specific irradiance factor PI */ + /* Normalise accumulated flux by spherical volume to obtain irradiance, + include RADIANCE-specific irradiance factor PI */ scalecolor(irrad, 3 / (4 * PI * PI * r2 * sqrt(r2))); + if (sphericalIrrad) + /* Scale irradiance by 1/4; this corresponds to the "true" scalar + illuminance as defined in equation (13) in: + + Mangkuto R.A., "Research note: The accuracy of the mean spherical + semi-cubic illuminance approach for determining scalar illuminance", + Lighting Research & Technology, 2020; 52: 151-158. */ + scalecolor(irrad, 0.25); + /* Restore ray normal */ flipsurface(ray); } #endif /* PMAP_PHOTONFLOW */ diff --git a/pmapopt.c b/pmapopt.c index dbde40c..5623f9a 100644 --- a/pmapopt.c +++ b/pmapopt.c @@ -1,142 +1,159 @@ #ifndef lint static const char RCSid[] = "$Id: pmapopt.c,v 2.10 2020/06/15 22:18:57 rschregle Exp $"; #endif /* ================================================================== Photon map interface to RADIANCE render options Roland Schregle (roland.schregle@{hslu.ch, gmail.com}) (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ================================================================== $Id: pmapopt.c,v 2.10 2020/06/15 22:18:57 rschregle Exp $ */ #include "ray.h" #include "pmapparm.h" int getPmapRenderOpt (int ac, char *av []) /* Parse next render option for photon map; interface to getrenderopt(); * return -1 if parsing failed, else number of parameters consumed */ { #define check(ol,al) (av[0][ol] || badarg(ac-1,av+1,al)) - /* Evaluate boolean option, setting var accordingly */ + /* Evaluate boolean option, setting var accordingly */ #define check_bool(olen, var) switch (av [0][olen]) { \ case '\0': \ var = !var; break; \ case 'y': case 'Y': case 't': case 'T': case '+': case '1': \ var = 1; break; \ case 'n': case 'N': case 'f': case 'F': case '-': case '0': \ var = 0; break; \ default: \ return -1; \ } static int t = -1; /* pmap parameter index */ if (ac < 1 || !av [0] || av [0][0] != '-') return -1; switch (av [0][1]) { case 'a': switch (av [0][2]) { case 'p': /* photon map */ /* Photon map bumps up ambounce from its default 0; SHOULD THIS REMAIN THE DEFAULT BEHAVIOUR? In some cases it's desirable to render photons directly via ab -1 */ ambounce += (ambounce == 0); if (!check(3, "s")) { /* File -> assume bwidth = 1 or precomputed pmap */ if (++t >= NUM_PMAP_TYPES) error(USER, "too many photon maps"); pmapParams [t].fileName = savqstr(av [1]); pmapParams [t].minGather = pmapParams [t].maxGather = defaultGather; } else return -1; if (!check(3, "si")) { /* File + bandwidth */ pmapParams [t].minGather = pmapParams [t].maxGather = atoi(av [2]); if (!pmapParams [t].minGather) return -1; } else { - sprintf(errmsg, "missing photon lookup bandwidth, " - "defaulting to %d", defaultGather); + sprintf(errmsg, + "missing photon lookup bandwidth, defaulting to %d", + defaultGather + ); error(WARNING, errmsg); return 1; } if (!check(3, "sii")) { /* File + min bwidth + max bwidth -> bias compensation */ pmapParams [t].maxGather = atoi(av [3]); if (pmapParams [t].minGather >= pmapParams [t].maxGather) return -1; return 3; } else return 2; case 'm': /* Fixed max photon search radius */ if (check(3, "f") || (maxDistFix = atof(av [1])) <= 0) error(USER, "invalid max photon search radius"); return 1; -#ifdef PMAP_OOC - case 'c': /* OOC pmap cache page size ratio */ +#ifdef PMAP_OOC + case 'c': + /* OOC pmap cache page size ratio */ if (check(3, "f") || (pmapCachePageSize = atof(av [1])) <= 0) error(USER, "invalid photon cache page size ratio"); return 1; - case 'C': /* OOC pmap cache size (in photons); 0 = no caching */ + case 'C': + /* OOC pmap cache size (in photons); 0 = no caching */ if (check(3, "s")) error(USER, "invalid number of cached photons"); /* Parsing failure sets to zero and disables caching */ pmapCacheSize = parseMultiplier(av [1]); return 1; -#endif - } +#endif + +#ifdef PMAP_PHOTONFLOW + case 'S': + /* Mean spherical / planar irrad switch for lightflow pmap */ + check_bool(3, sphericalIrrad); + return 0; +#endif + } } #undef check /* Unknown option */ return -1; } void printPmapDefaults () /* Print defaults for photon map render options */ { printf("-am %.1f\t\t\t\t# max photon search radius\n", maxDistFix); #ifdef PMAP_OOC printf("-ac %.1f\t\t\t\t# photon cache page size ratio\n", pmapCachePageSize); printf("-aC %ld\t\t\t# num cached photons\n", pmapCacheSize); #endif +#ifdef PMAP_PHOTONFLOW + printf(sphericalIrrad + ? "-aS+\t\t\t\t# spherical irradiance from lightflow photons\n" + : "-aS-\t\t\t\t# planar irradiance from lightflow photons\n" + ); +#endif } diff --git a/pmapparm.c b/pmapparm.c index 04eced8..58add9a 100644 --- a/pmapparm.c +++ b/pmapparm.c @@ -1,116 +1,121 @@ #ifndef lint static const char RCSid[] = "$Id: pmapparm.c,v 2.9 2018/02/02 19:47:55 rschregle Exp $"; #endif /* ====================================================================== Parameters for photon map generation and rendering; used by mkpmap and rpict/rvu/rtrace. Roland Schregle (roland.schregle@{hslu.ch, gmail.com} (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components", SNSF #179067, "Light Fields for Spatio-Temporal Glare Assessment") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ====================================================================== $Id: pmapparm.c,v 2.9 2018/02/02 19:47:55 rschregle Exp $ */ #include "pmapparm.h" #include "pmapdata.h" #include <ctype.h> float pdfSamples = 1000, /* PDF samples per steradian */ finalGather = 0.25, /* fraction of global photons for irradiance precomputation */ preDistrib = 0.25, /* fraction of num photons for distribution prepass */ gatherTolerance = 0.5, /* Photon map lookup tolerance; lookups returning fewer than this fraction of minGather/maxGather are restarted with a larger search radius */ maxDistFix = 0, /* Static maximum photon search radius (radius is adaptive if this is zero) */ photonMaxDist = 0, /* Maximum cumulative distance of photon path */ compressRatio = 0.9; /* Compression ratio for precomputed contribution photon map */ #ifdef PMAP_OOC float pmapCachePageSize = 8; /* OOC cache pagesize as multiple * of maxGather */ unsigned long pmapCacheSize = 1e6; /* OOC cache size in photons */ #endif +#ifdef PMAP_PHOTONFLOW + int sphericalIrrad = 0; /* Toggle for spherical / planar + irrad from lightflow pmap */ +#endif + /* Regions of interest */ unsigned pmapNumROI = 0; PhotonMapROI *pmapROI = NULL; unsigned verbose = 0; /* Verbose console output */ unsigned long photonMaxBounce = 5000; /* Runaway photon bounce limit */ unsigned photonRepTime = 0, /* Seconds between reports */ maxPreDistrib = 4, /* Max predistrib passes */ defaultGather = 40; /* Default num photons for lookup */ /* Per photon map params */ PhotonMapParams pmapParams [NUM_PMAP_TYPES] = { {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0}, {NULL, 0, 0, 0} #ifdef PMAP_PHOTONFLOW , {NULL, 0, 0, 0} #endif }; int setPmapParam (PhotonMap** pm, const PhotonMapParams* parm) { if (parm && parm -> fileName) { if (!(*pm = (PhotonMap*)malloc(sizeof(PhotonMap)))) error(INTERNAL, "failed to allocate photon map"); (*pm) -> fileName = parm -> fileName; (*pm) -> minGather = parm -> minGather; (*pm) -> maxGather = parm -> maxGather; (*pm) -> distribTarget = parm -> distribTarget; (*pm) -> maxDist0 = FHUGE; (*pm) -> srcContrib = NULL; return 1; } return 0; } unsigned long parseMultiplier (const char *num) { unsigned long mult = 1; const int strEnd = strlen(num) - 1; if (strEnd <= 0) return 0; if (!isdigit(num [strEnd])) switch (toupper(num [strEnd])) { case 'G': mult *= 1000; case 'M': mult *= 1000; case 'K': mult *= 1000; break; default : error(USER, "unknown multiplier"); } return (unsigned long)(mult * atof(num)); } diff --git a/pmapparm.h b/pmapparm.h index 62117ea..9dfd6b5 100644 --- a/pmapparm.h +++ b/pmapparm.h @@ -1,87 +1,91 @@ /* RCSid $Id: pmapparm.h,v 2.10 2021/04/14 11:26:25 rschregle Exp $ */ /* ====================================================================== Parameters for photon map generation and rendering; used by mkpmap and rpict/rvu/rtrace. Roland Schregle (roland.schregle@{hslu.ch, gmail.com} (c) Fraunhofer Institute for Solar Energy Systems, supported by the German Research Foundation (DFG LU-204/10-2, "Fassadenintegrierte Regelsysteme" (FARESYS)) (c) Lucerne University of Applied Sciences and Arts, supported by the Swiss National Science Foundation (SNSF #147053, "Daylight Redirecting Components", SNSF #179067, "Light Fields for Spatio-Temporal Glare Assessment") (c) Tokyo University of Science, supported by the JSPS Grants-in-Aid for Scientific Research (KAKENHI JP19KK0115, "Three-Dimensional Light Flow") ====================================================================== $Id: pmapparm.h,v 2.10 2021/04/14 11:26:25 rschregle Exp $ */ #ifndef PMAPPARAMS_H #define PMAPPARAMS_H #include "pmaptype.h" /* Struct for passing params per photon map from rpict/rtrace/rvu */ typedef struct { char *fileName; /* Photon map file */ unsigned minGather, maxGather; /* Num photons to gather */ unsigned long distribTarget; /* Num photons to store */ } PhotonMapParams; /* Region of interest */ typedef struct { /* siz [1], siz [2] <= 0 --> sphere, else rectangle */ float pos [3], siz [3]; } PhotonMapROI; #define PMAP_ROI_ISSPHERE(roi) ((roi)->siz[1] <= 0 && (roi)->siz[2] <= 0) #define PMAP_ROI_SETSPHERE(roi) ((roi)->siz[1] = (roi)->siz[2] = -1) extern PhotonMapParams pmapParams [NUM_PMAP_TYPES]; /* Macros for type specific photon map parameters */ #define globalPmapParams (pmapParams [PMAP_TYPE_GLOBAL]) #define preCompPmapParams (pmapParams [PMAP_TYPE_PRECOMP]) #define causticPmapParams (pmapParams [PMAP_TYPE_CAUSTIC]) #define volumePmapParams (pmapParams [PMAP_TYPE_VOLUME]) #define directPmapParams (pmapParams [PMAP_TYPE_DIRECT]) #define contribPmapParams (pmapParams [PMAP_TYPE_CONTRIB]) #ifdef PMAP_PHOTONFLOW #define lightFlowPmapParams (pmapParams [PMAP_TYPE_LIGHTFLOW]) #endif extern float pdfSamples, preDistrib, finalGather, gatherTolerance, maxDistFix, pmapMaxDist, photonMaxDist, compressRatio; extern unsigned long photonHeapSizeInc, photonMaxBounce; extern unsigned photonRepTime, maxPreDistrib, defaultGather, verbose; extern unsigned pmapNumROI; extern PhotonMapROI *pmapROI; #ifdef PMAP_OOC extern float pmapCachePageSize; extern unsigned long pmapCacheSize; - #endif + #endif + + #ifdef PMAP_PHOTONFLOW + int sphericalIrrad; + #endif struct PhotonMap; int setPmapParam (struct PhotonMap **pm, const PhotonMapParams *parm); /* Allocate photon map and set its parameters from parm */ unsigned long parseMultiplier (const char *num); /* Evaluate numeric parameter string with optional multiplier suffix (G = 10^9, M = 10^6, K = 10^3). Returns 0 if parsing fails. */ #endif