ViewVC Help
View File | Revision Log | Show Annotations | Download File | Root Listing
root/radiance/ray/src/rt/pmapsrc.c
Revision: 2.18
Committed: Fri Aug 7 01:21:13 2020 UTC (3 years, 9 months ago) by rschregle
Content type: text/plain
Branch: MAIN
Changes since 2.17: +271 -180 lines
Log Message:
feat(mkpmap): Extended -apo option to reorient photon ports.

File Contents

# Content
1 #ifndef lint
2 static const char RCSid[] = "$Id: pmapsrc.c,v 2.17 2018/11/08 00:54:07 greg Exp $";
3 #endif
4 /*
5 ======================================================================
6 Photon map support routines for emission from light sources
7
8 Roland Schregle (roland.schregle@{hslu.ch, gmail.com})
9 (c) Fraunhofer Institute for Solar Energy Systems,
10 supported by the German Research Foundation (DFG)
11 under the FARESYS project.
12 (c) Lucerne University of Applied Sciences and Arts,
13 supported by the Swiss National Science Foundation (SNSF #147053).
14 (c) Tokyo University of Science,
15 supported by the JSPS KAKENHI Grant Number JP19KK0115.
16 ======================================================================
17
18 $Id: pmapsrc.c,v 2.17 2018/11/08 00:54:07 greg Exp $"
19 */
20
21
22
23 #include "pmapsrc.h"
24 #include "pmap.h"
25 #include "pmaprand.h"
26 #include "otypes.h"
27 #include "otspecial.h"
28
29
30
31 /* List of photon port modifier names */
32 char *photonPortList [MAXSET + 1] = {NULL};
33 /* Photon port objects (with modifiers in photonPortMods) */
34 SRCREC *photonPorts = NULL;
35 unsigned numPhotonPorts = 0;
36
37 void (*photonPartition [NUMOTYPE]) (EmissionMap*);
38 void (*photonOrigin [NUMOTYPE]) (EmissionMap*);
39
40
41
42 /* PHOTON PORT SUPPORT ROUTINES ------------------------------------------ */
43
44
45
46 /* Get/set photon port orientation flags from/in source flags.
47 * HACK: the port orientation flags are embedded in the source flags and
48 * shifted so they won't clobber the latter, since these are interpreted
49 * by the *PhotonPartition() and *PhotonOrigin() routines! */
50 #define PMAP_SETPORTFLAGS(portdir) ((int)(portdir) << 14)
51 #define PMAP_GETPORTFLAGS(sflags) ((sflags) >> 14)
52
53 /* Set number of source partitions.
54 * HACK: this is doubled if the source acts as a bidirectionally
55 * emitting photon port, resulting in alternating front/backside partitions,
56 * although essentially each partition is just used twice with opposing
57 * normals. */
58 #define PMAP_SETNUMPARTITIONS(emap) ( \
59 (emap) -> numPartitions <<= ( \
60 (emap) -> port && \
61 PMAP_GETPORTFLAGS((emap) -> port -> sflags) == PMAP_PORTBI \
62 ) \
63 )
64
65 /* Get current source partition and numer of partitions
66 * HACK: halve the number partitions if the source acts as a bidrectionally
67 * emitting photon port, since each partition is used twice with opposing
68 * normals. */
69 #define PMAP_GETNUMPARTITIONS(emap) (\
70 (emap) -> numPartitions >> ( \
71 (emap) -> port && \
72 PMAP_GETPORTFLAGS((emap) -> port -> sflags) == PMAP_PORTBI \
73 ) \
74 )
75 #define PMAP_GETPARTITION(emap) ( \
76 (emap) -> partitionCnt >> ( \
77 (emap) -> port && \
78 PMAP_GETPORTFLAGS((emap) -> port -> sflags) == PMAP_PORTBI \
79 ) \
80 )
81
82
83
84 void getPhotonPorts (char **portList)
85 /* Find geometry declared as photon ports from modifiers in portList */
86 {
87 OBJECT i;
88 OBJREC *obj, *mat;
89 int mLen;
90 char **lp;
91
92 /* Init photon port objects */
93 photonPorts = NULL;
94
95 if (!portList [0])
96 return;
97
98 for (i = numPhotonPorts = 0; i < nobjects; i++) {
99 obj = objptr(i);
100 mat = findmaterial(obj);
101
102 /* Check if object is a surface and NOT a light source (duh) and
103 * resolve its material (if any) via any aliases, then check for
104 * inclusion in modifier list; note use of strncmp() to ignore port
105 * flags */
106 if (issurface(obj -> otype) && mat && !islight(mat -> otype)) {
107 mLen = strlen(mat -> oname);
108 for (lp = portList; *lp && strncmp(mat -> oname, *lp, mLen); lp++);
109
110 if (*lp) {
111 /* Add photon port */
112 photonPorts = (SRCREC*)realloc(
113 photonPorts, (numPhotonPorts + 1) * sizeof(SRCREC)
114 );
115 if (!photonPorts)
116 error(USER, "can't allocate photon ports");
117
118 photonPorts [numPhotonPorts].so = obj;
119 /* Extract port orientation flags and embed in source flags.
120 * Note setsource() combines (i.e. ORs) these with the actual
121 * source flags below. */
122 photonPorts [numPhotonPorts].sflags =
123 PMAP_SETPORTFLAGS((*lp) [mLen]);
124
125 if (!sfun [obj -> otype].of || !sfun[obj -> otype].of -> setsrc)
126 objerror(obj, USER, "illegal photon port");
127
128 setsource(photonPorts + numPhotonPorts, obj);
129 numPhotonPorts++;
130 }
131 }
132 }
133 if (!numPhotonPorts)
134 error(USER, "no valid photon ports found");
135 }
136
137
138
139 static void setPhotonPortNormal (EmissionMap* emap)
140 /* Set normal for current photon port partition based on its orientation */
141 {
142 /* Extract photon port orientation flags, set surface normal as follows:
143 -- Port oriented forwards --> flip surface normal to point
144 outwards, since normal points inwards per mkillum convention)
145 -- Port oriented backwards --> surface normal is NOT flipped,
146 since it already points inwards.
147 -- Port is bidirectionally/bilaterally oriented --> flip normal based
148 on the parity of the current partition emap -> partitionCnt.
149 In this case, photon emission alternates between port front/back
150 faces for consecutive partitions.
151 */
152 int i, portFlags = PMAP_GETPORTFLAGS(emap -> port -> sflags);
153
154 if (
155 portFlags == PMAP_PORTFWD ||
156 portFlags == PMAP_PORTBI && !(emap -> partitionCnt & 1)
157 )
158 for (i = 0; i < 3; i++)
159 emap -> ws [i] = -emap -> ws [i];
160 }
161
162
163
164 /* SOURCE / PHOTON PORT PARTITIONING ROUTINES----------------------------- */
165
166
167
168 static int flatPhotonPartition2 (
169 EmissionMap* emap, unsigned long mp, FVECT cent, FVECT u, FVECT v,
170 double du2, double dv2
171 )
172 /* Recursive part of flatPhotonPartition(..) */
173 {
174 FVECT newct, newax;
175 unsigned long npl, npu;
176
177 if (mp > emap -> maxPartitions) {
178 /* Enlarge partition array */
179 emap -> maxPartitions <<= 1;
180 emap -> partitions = (unsigned char*)realloc(
181 emap -> partitions, emap -> maxPartitions >> 1
182 );
183
184 if (!emap -> partitions)
185 error(USER, "can't allocate source partitions");
186
187 memset(
188 emap -> partitions + (emap -> maxPartitions >> 2), 0,
189 emap -> maxPartitions >> 2
190 );
191 }
192
193 if (du2 * dv2 <= 1) { /* hit limit? */
194 setpart(emap -> partitions, emap -> partitionCnt, S0);
195 emap -> partitionCnt++;
196 return 1;
197 }
198
199 if (du2 > dv2) { /* subdivide in U */
200 setpart(emap -> partitions, emap -> partitionCnt, SU);
201 emap -> partitionCnt++;
202 newax [0] = 0.5 * u [0];
203 newax [1] = 0.5 * u [1];
204 newax [2] = 0.5 * u [2];
205 u = newax;
206 du2 *= 0.25;
207 }
208
209 else { /* subdivide in V */
210 setpart(emap -> partitions, emap -> partitionCnt, SV);
211 emap -> partitionCnt++;
212 newax [0] = 0.5 * v [0];
213 newax [1] = 0.5 * v [1];
214 newax [2] = 0.5 * v [2];
215 v = newax;
216 dv2 *= 0.25;
217 }
218
219 /* lower half */
220 newct [0] = cent [0] - newax [0];
221 newct [1] = cent [1] - newax [1];
222 newct [2] = cent [2] - newax [2];
223 npl = flatPhotonPartition2(emap, mp << 1, newct, u, v, du2, dv2);
224
225 /* upper half */
226 newct [0] = cent [0] + newax [0];
227 newct [1] = cent [1] + newax [1];
228 newct [2] = cent [2] + newax [2];
229 npu = flatPhotonPartition2(emap, mp << 1, newct, u, v, du2, dv2);
230
231 /* return total */
232 return npl + npu;
233 }
234
235
236
237 static void flatPhotonPartition (EmissionMap* emap)
238 /* Partition flat source for photon emission */
239 {
240 RREAL *vp;
241 double du2, dv2;
242
243 memset(emap -> partitions, 0, emap -> maxPartitions >> 1);
244 emap -> partArea = srcsizerat * thescene.cusize;
245 emap -> partArea *= emap -> partArea;
246 vp = emap -> src -> ss [SU];
247 du2 = DOT(vp, vp) / emap -> partArea;
248 vp = emap -> src -> ss [SV];
249 dv2 = DOT(vp, vp) / emap -> partArea;
250 emap -> partitionCnt = 0;
251 emap -> numPartitions = flatPhotonPartition2(
252 emap, 1, emap -> src -> sloc,
253 emap -> src -> ss [SU], emap -> src -> ss [SV], du2, dv2
254 );
255 emap -> partitionCnt = 0;
256 emap -> partArea = emap -> src -> ss2 / emap -> numPartitions;
257 }
258
259
260
261 static void sourcePhotonPartition (EmissionMap* emap)
262 /* Partition scene cube faces or photon port for photon emission from
263 distant source */
264 {
265 if (emap -> port) {
266 /* Relay partitioning to photon port */
267 SRCREC *src = emap -> src;
268 emap -> src = emap -> port;
269 photonPartition [emap -> src -> so -> otype] (emap);
270 PMAP_SETNUMPARTITIONS(emap);
271 emap -> src = src;
272 }
273
274 else {
275 /* No photon ports defined; partition scene cube faces (SUBOPTIMAL) */
276 memset(emap -> partitions, 0, emap -> maxPartitions >> 1);
277 setpart(emap -> partitions, 0, S0);
278 emap -> partitionCnt = 0;
279 emap -> numPartitions = 1 / srcsizerat;
280 emap -> numPartitions *= emap -> numPartitions;
281 emap -> partArea = sqr(thescene.cusize) / emap -> numPartitions;
282 emap -> numPartitions *= 6;
283 }
284 }
285
286
287
288 static void spherePhotonPartition (EmissionMap* emap)
289 /* Partition spherical source into equal solid angles using uniform
290 mapping for photon emission */
291 {
292 unsigned numTheta, numPhi;
293
294 memset(emap -> partitions, 0, emap -> maxPartitions >> 1);
295 setpart(emap -> partitions, 0, S0);
296 emap -> partArea = 4 * PI * sqr(emap -> src -> srad);
297 emap -> numPartitions =
298 emap -> partArea / sqr(srcsizerat * thescene.cusize);
299
300 numTheta = max(sqrt(2 * emap -> numPartitions / PI) + 0.5, 1);
301 numPhi = 0.5 * PI * numTheta + 0.5;
302
303 emap -> numPartitions = (unsigned long)numTheta * numPhi;
304 emap -> partitionCnt = 0;
305 emap -> partArea /= emap -> numPartitions;
306 }
307
308
309
310 static int cylPhotonPartition2 (
311 EmissionMap* emap, unsigned long mp, FVECT cent, FVECT axis, double d2
312 )
313 /* Recursive part of cyPhotonPartition(..) */
314 {
315 FVECT newct, newax;
316 unsigned long npl, npu;
317
318 if (mp > emap -> maxPartitions) {
319 /* Enlarge partition array */
320 emap -> maxPartitions <<= 1;
321 emap -> partitions = (unsigned char*)realloc(
322 emap -> partitions, emap -> maxPartitions >> 1
323 );
324 if (!emap -> partitions)
325 error(USER, "can't allocate source partitions");
326
327 memset(
328 emap -> partitions + (emap -> maxPartitions >> 2), 0,
329 emap -> maxPartitions >> 2
330 );
331 }
332
333 if (d2 <= 1) {
334 /* hit limit? */
335 setpart(emap -> partitions, emap -> partitionCnt, S0);
336 emap -> partitionCnt++;
337 return 1;
338 }
339
340 /* subdivide */
341 setpart(emap -> partitions, emap -> partitionCnt, SU);
342 emap -> partitionCnt++;
343 newax [0] = 0.5 * axis [0];
344 newax [1] = 0.5 * axis [1];
345 newax [2] = 0.5 * axis [2];
346 d2 *= 0.25;
347
348 /* lower half */
349 newct [0] = cent [0] - newax [0];
350 newct [1] = cent [1] - newax [1];
351 newct [2] = cent [2] - newax [2];
352 npl = cylPhotonPartition2(emap, mp << 1, newct, newax, d2);
353
354 /* upper half */
355 newct [0] = cent [0] + newax [0];
356 newct [1] = cent [1] + newax [1];
357 newct [2] = cent [2] + newax [2];
358 npu = cylPhotonPartition2(emap, mp << 1, newct, newax, d2);
359
360 /* return total */
361 return npl + npu;
362 }
363
364
365
366 static void cylPhotonPartition (EmissionMap* emap)
367 /* Partition cylindrical source for photon emission */
368 {
369 double d2;
370
371 memset(emap -> partitions, 0, emap -> maxPartitions >> 1);
372 d2 = srcsizerat * thescene.cusize;
373 d2 = PI * emap -> src -> ss2 / (2 * emap -> src -> srad * sqr(d2));
374 d2 *= d2 * DOT(emap -> src -> ss [SU], emap -> src -> ss [SU]);
375
376 emap -> partitionCnt = 0;
377 emap -> numPartitions = cylPhotonPartition2(
378 emap, 1, emap -> src -> sloc, emap -> src -> ss [SU], d2
379 );
380 emap -> partitionCnt = 0;
381 emap -> partArea = PI * emap -> src -> ss2 / emap -> numPartitions;
382 }
383
384
385
386 /* PHOTON ORIGIN ROUTINES ------------------------------------------------ */
387
388
389
390 static void flatPhotonOrigin (EmissionMap* emap)
391 /* Init emission map with photon origin and associated surface axes on
392 flat light source surface. Also sets source aperture and sampling
393 hemisphere axes at origin */
394 {
395 int i, cent[3], size[3], parr[2];
396 FVECT vpos;
397
398 cent [0] = cent [1] = cent [2] = 0;
399 size [0] = size [1] = size [2] = emap -> maxPartitions;
400 parr [0] = 0;
401 parr [1] = PMAP_GETPARTITION(emap);
402
403 if (!skipparts(cent, size, parr, emap -> partitions))
404 error(CONSISTENCY, "bad source partition in flatPhotonOrigin");
405
406 vpos [0] = (1 - 2 * pmapRandom(partState)) * size [0] /
407 emap -> maxPartitions;
408 vpos [1] = (1 - 2 * pmapRandom(partState)) * size [1] /
409 emap -> maxPartitions;
410 vpos [2] = 0;
411
412 for (i = 0; i < 3; i++)
413 vpos [i] += (double)cent [i] / emap -> maxPartitions;
414
415 /* Get origin */
416 for (i = 0; i < 3; i++)
417 emap -> photonOrg [i] =
418 emap -> src -> sloc [i] +
419 vpos [SU] * emap -> src -> ss [SU][i] +
420 vpos [SV] * emap -> src -> ss [SV][i] +
421 vpos [SW] * emap -> src -> ss [SW][i];
422
423 /* Get surface axes */
424 VCOPY(emap -> us, emap -> src -> ss [SU]);
425 normalize(emap -> us);
426 VCOPY(emap -> ws, emap -> src -> ss [SW]);
427 /* Flip normal emap -> ws if port and required by its orientation */
428 setPhotonPortNormal(emap);
429 fcross(emap -> vs, emap -> ws, emap -> us);
430
431 /* Get hemisphere axes & aperture */
432 if (emap -> src -> sflags & SSPOT) {
433 VCOPY(emap -> wh, emap -> src -> sl.s -> aim);
434 i = 0;
435
436 do {
437 emap -> vh [0] = emap -> vh [1] = emap -> vh [2] = 0;
438 emap -> vh [i++] = 1;
439 fcross(emap -> uh, emap -> vh, emap -> wh);
440 } while (normalize(emap -> uh) < FTINY);
441
442 fcross(emap -> vh, emap -> wh, emap -> uh);
443 emap -> cosThetaMax = 1 - emap -> src -> sl.s -> siz / (2 * PI);
444 }
445
446 else {
447 VCOPY(emap -> uh, emap -> us);
448 VCOPY(emap -> vh, emap -> vs);
449 VCOPY(emap -> wh, emap -> ws);
450 emap -> cosThetaMax = 0;
451 }
452 }
453
454
455
456 static void spherePhotonOrigin (EmissionMap* emap)
457 /* Init emission map with photon origin and associated surface axes on
458 spherical light source. Also sets source aperture and sampling
459 hemisphere axes at origin */
460 {
461 int i = 0;
462 unsigned numTheta, numPhi, t, p;
463 RREAL cosTheta, sinTheta, phi;
464
465 /* Get current partition */
466 numTheta = max(sqrt(2 * PMAP_GETNUMPARTITIONS(emap) / PI) + 0.5, 1);
467 numPhi = 0.5 * PI * numTheta + 0.5;
468
469 t = PMAP_GETPARTITION(emap) / numPhi;
470 p = PMAP_GETPARTITION(emap) - t * numPhi;
471
472 emap -> ws [2] = cosTheta = 1 - 2 * (t + pmapRandom(partState)) / numTheta;
473 sinTheta = sqrt(1 - sqr(cosTheta));
474 phi = 2 * PI * (p + pmapRandom(partState)) / numPhi;
475 emap -> ws [0] = cos(phi) * sinTheta;
476 emap -> ws [1] = sin(phi) * sinTheta;
477 /* Flip normal emap -> ws if port and required by its orientation */
478 setPhotonPortNormal(emap);
479
480 /* Get surface axes us & vs perpendicular to ws */
481 do {
482 emap -> vs [0] = emap -> vs [1] = emap -> vs [2] = 0;
483 emap -> vs [i++] = 1;
484 fcross(emap -> us, emap -> vs, emap -> ws);
485 } while (normalize(emap -> us) < FTINY);
486
487 fcross(emap -> vs, emap -> ws, emap -> us);
488
489 /* Get origin */
490 for (i = 0; i < 3; i++)
491 emap -> photonOrg [i] = emap -> src -> sloc [i] +
492 emap -> src -> srad * emap -> ws [i];
493
494 /* Get hemisphere axes & aperture */
495 if (emap -> src -> sflags & SSPOT) {
496 VCOPY(emap -> wh, emap -> src -> sl.s -> aim);
497 i = 0;
498
499 do {
500 emap -> vh [0] = emap -> vh [1] = emap -> vh [2] = 0;
501 emap -> vh [i++] = 1;
502 fcross(emap -> uh, emap -> vh, emap -> wh);
503 } while (normalize(emap -> uh) < FTINY);
504
505 fcross(emap -> vh, emap -> wh, emap -> uh);
506 emap -> cosThetaMax = 1 - emap -> src -> sl.s -> siz / (2 * PI);
507 }
508
509 else {
510 VCOPY(emap -> uh, emap -> us);
511 VCOPY(emap -> vh, emap -> vs);
512 VCOPY(emap -> wh, emap -> ws);
513 emap -> cosThetaMax = 0;
514 }
515 }
516
517
518
519 static void sourcePhotonOrigin (EmissionMap* emap)
520 /* Init emission map with photon origin and associated surface axes
521 on scene cube face for distant light source. Also sets source
522 aperture (solid angle) and sampling hemisphere axes at origin */
523 {
524 unsigned long i, partsPerDim, partsPerFace;
525 unsigned face;
526 RREAL du, dv;
527
528 if (emap -> port) {
529 /* Relay to photon port; get origin on its surface */
530 SRCREC *src = emap -> src;
531 emap -> src = emap -> port;
532 photonOrigin [emap -> src -> so -> otype] (emap);
533 emap -> src = src;
534 }
535
536 else {
537 /* No ports defined, so get origin on scene cube face (SUBOPTIMAL) */
538 /* Get current face from partition number */
539 partsPerDim = 1 / srcsizerat;
540 partsPerFace = sqr(partsPerDim);
541 face = emap -> partitionCnt / partsPerFace;
542 if (!(emap -> partitionCnt % partsPerFace)) {
543 /* Skipped to a new face; get its normal */
544 emap -> ws [0] = emap -> ws [1] = emap -> ws [2] = 0;
545 emap -> ws [face >> 1] = face & 1 ? 1 : -1;
546
547 /* Get surface axes us & vs perpendicular to ws */
548 face >>= 1;
549 emap -> vs [0] = emap -> vs [1] = emap -> vs [2] = 0;
550 emap -> vs [(face + (emap -> ws [face] > 0 ? 2 : 1)) % 3] = 1;
551 fcross(emap -> us, emap -> vs, emap -> ws);
552 }
553
554 /* Get jittered offsets within face from partition number
555 (in range [-0.5, 0.5]) */
556 i = emap -> partitionCnt % partsPerFace;
557 du = (i / partsPerDim + pmapRandom(partState)) / partsPerDim - 0.5;
558 dv = (i % partsPerDim + pmapRandom(partState)) / partsPerDim - 0.5;
559
560 /* Jittered destination point within partition */
561 for (i = 0; i < 3; i++)
562 emap -> photonOrg [i] = thescene.cuorg [i] + thescene.cusize * (
563 0.5 + du * emap -> us [i] + dv * emap -> vs [i] +
564 0.5 * emap -> ws [i]
565 );
566 }
567
568 /* Get hemisphere axes & aperture */
569 VCOPY(emap -> wh, emap -> src -> sloc);
570 i = 0;
571
572 do {
573 emap -> vh [0] = emap -> vh [1] = emap -> vh [2] = 0;
574 emap -> vh [i++] = 1;
575 fcross(emap -> uh, emap -> vh, emap -> wh);
576 } while (normalize(emap -> uh) < FTINY);
577
578 fcross(emap -> vh, emap -> wh, emap -> uh);
579
580 /* Get aperture */
581 emap -> cosThetaMax = 1 - emap -> src -> ss2 / (2 * PI);
582 emap -> cosThetaMax = min(1, max(-1, emap -> cosThetaMax));
583 }
584
585
586
587 static void cylPhotonOrigin (EmissionMap* emap)
588 /* Init emission map with photon origin and associated surface axes
589 on cylindrical light source surface. Also sets source aperture
590 and sampling hemisphere axes at origin */
591 {
592 int i, cent[3], size[3], parr[2];
593 FVECT v;
594
595 cent [0] = cent [1] = cent [2] = 0;
596 size [0] = size [1] = size [2] = emap -> maxPartitions;
597 parr [0] = 0;
598 parr [1] = PMAP_GETPARTITION(emap);
599
600 if (!skipparts(cent, size, parr, emap -> partitions))
601 error(CONSISTENCY, "bad source partition in cylPhotonOrigin");
602
603 v [SU] = 0;
604 v [SV] = (1 - 2 * pmapRandom(partState)) * (double)size [1] /
605 emap -> maxPartitions;
606 v [SW] = (1 - 2 * pmapRandom(partState)) * (double)size [2] /
607 emap -> maxPartitions;
608 normalize(v);
609 v [SU] = (1 - 2 * pmapRandom(partState)) * (double)size [1] /
610 emap -> maxPartitions;
611
612 for (i = 0; i < 3; i++)
613 v [i] += (double)cent [i] / emap -> maxPartitions;
614
615 /* Get surface axes */
616 for (i = 0; i < 3; i++)
617 emap -> photonOrg [i] = emap -> ws [i] = (
618 v [SV] * emap -> src -> ss [SV][i] +
619 v [SW] * emap -> src -> ss [SW][i]
620 ) / 0.8559;
621
622 /* Flip normal emap -> ws if port and required by its orientation */
623 setPhotonPortNormal(emap);
624
625 normalize(emap -> ws);
626 VCOPY(emap -> us, emap -> src -> ss [SU]);
627 normalize(emap -> us);
628 fcross(emap -> vs, emap -> ws, emap -> us);
629
630 /* Get origin */
631 for (i = 0; i < 3; i++)
632 emap -> photonOrg [i] +=
633 v [SU] * emap -> src -> ss [SU][i] + emap -> src -> sloc [i];
634
635 /* Get hemisphere axes & aperture */
636 if (emap -> src -> sflags & SSPOT) {
637 VCOPY(emap -> wh, emap -> src -> sl.s -> aim);
638 i = 0;
639
640 do {
641 emap -> vh [0] = emap -> vh [1] = emap -> vh [2] = 0;
642 emap -> vh [i++] = 1;
643 fcross(emap -> uh, emap -> vh, emap -> wh);
644 } while (normalize(emap -> uh) < FTINY);
645
646 fcross(emap -> vh, emap -> wh, emap -> uh);
647 emap -> cosThetaMax = 1 - emap -> src -> sl.s -> siz / (2 * PI);
648 }
649
650 else {
651 VCOPY(emap -> uh, emap -> us);
652 VCOPY(emap -> vh, emap -> vs);
653 VCOPY(emap -> wh, emap -> ws);
654 emap -> cosThetaMax = 0;
655 }
656 }
657
658
659
660 /* PHOTON EMISSION ROUTINES ---------------------------------------------- */
661
662
663
664 static void defaultEmissionFunc (EmissionMap* emap)
665 /* Default behaviour when no emission funcs defined for this source type */
666 {
667 objerror(emap -> src -> so, INTERNAL,
668 "undefined photon emission function");
669 }
670
671
672
673 void initPhotonEmissionFuncs ()
674 /* Init photonPartition[] and photonOrigin[] dispatch tables */
675 {
676 int i;
677
678 for (i = 0; i < NUMOTYPE; i++)
679 photonPartition [i] = photonOrigin [i] = defaultEmissionFunc;
680
681 photonPartition [OBJ_FACE] = photonPartition [OBJ_RING] = flatPhotonPartition;
682 photonPartition [OBJ_SOURCE] = sourcePhotonPartition;
683 photonPartition [OBJ_SPHERE] = spherePhotonPartition;
684 photonPartition [OBJ_CYLINDER] = cylPhotonPartition;
685 photonOrigin [OBJ_FACE] = photonOrigin [OBJ_RING] = flatPhotonOrigin;
686 photonOrigin [OBJ_SOURCE] = sourcePhotonOrigin;
687 photonOrigin [OBJ_SPHERE] = spherePhotonOrigin;
688 photonOrigin [OBJ_CYLINDER] = cylPhotonOrigin;
689 }
690
691
692
693 void initPhotonEmission (EmissionMap *emap, float numPdfSamples)
694 /* Initialise photon emission from partitioned light source emap -> src;
695 * this involves integrating the flux emitted from the current photon
696 * origin emap -> photonOrg and setting up a PDF to sample the emission
697 * distribution with numPdfSamples samples */
698 {
699 unsigned i, t, p;
700 double phi, cosTheta, sinTheta, du, dv, dOmega, thetaScale;
701 EmissionSample* sample;
702 const OBJREC* mod = findmaterial(emap -> src -> so);
703 static RAY r;
704
705 photonOrigin [emap -> src -> so -> otype] (emap);
706 setcolor(emap -> partFlux, 0, 0, 0);
707 emap -> cdf = 0;
708 emap -> numSamples = 0;
709 cosTheta = DOT(emap -> ws, emap -> wh);
710
711 if (cosTheta <= 0 && sqrt(1-sqr(cosTheta)) <= emap -> cosThetaMax + FTINY)
712 /* Aperture completely below surface; no emission from current origin */
713 return;
714
715 if (
716 mod -> omod == OVOID && !emap -> port && (
717 cosTheta >= 1 - FTINY || (
718 emap -> src -> sflags & SDISTANT &&
719 acos(cosTheta) + acos(emap -> cosThetaMax) <= 0.5 * PI
720 )
721 )
722 ) {
723 /* Source is unmodified and has no port (which requires testing for
724 occlusion), and is either local with normal aligned aperture or
725 distant with aperture above surface
726 --> get flux & PDF via analytical solution */
727 setcolor(
728 emap -> partFlux, mod -> oargs.farg [0], mod -> oargs.farg [1],
729 mod -> oargs.farg [2]
730 );
731
732 /* Multiply radiance by projected Omega * dA to get flux */
733 scalecolor(
734 emap -> partFlux,
735 PI * cosTheta * (1 - sqr(max(emap -> cosThetaMax, 0))) *
736 emap -> partArea
737 );
738 }
739
740 else {
741 /* Source is either modified, has a port, is local with off-normal
742 aperture, or distant with aperture partly below surface
743 --> get flux via numerical integration */
744 thetaScale = (1 - emap -> cosThetaMax);
745
746 /* Figga out numba of aperture samples for integration;
747 numTheta / numPhi ratio is 1 / PI */
748 du = sqrt(pdfSamples * 2 * thetaScale);
749 emap -> numTheta = max(du + 0.5, 1);
750 emap -> numPhi = max(PI * du + 0.5, 1);
751
752 dOmega = 2 * PI * thetaScale / (emap -> numTheta * emap -> numPhi);
753 thetaScale /= emap -> numTheta;
754
755 /* Allocate PDF, baby */
756 sample = emap -> samples = (EmissionSample*)realloc(
757 emap -> samples,
758 sizeof(EmissionSample) * emap -> numTheta * emap -> numPhi
759 );
760 if (!emap -> samples)
761 error(USER, "can't allocate emission PDF");
762
763 VCOPY(r.rorg, emap -> photonOrg);
764 VCOPY(r.rop, emap -> photonOrg);
765 r.rmax = 0;
766
767 for (t = 0; t < emap -> numTheta; t++) {
768 for (p = 0; p < emap -> numPhi; p++) {
769 /* This uniform mapping handles 0 <= thetaMax <= PI */
770 cosTheta = 1 - (t + pmapRandom(emitState)) * thetaScale;
771 sinTheta = sqrt(1 - sqr(cosTheta));
772 phi = 2 * PI * (p + pmapRandom(emitState)) / emap -> numPhi;
773 du = cos(phi) * sinTheta;
774 dv = sin(phi) * sinTheta;
775 rayorigin(&r, PRIMARY, NULL, NULL);
776
777 for (i = 0; i < 3; i++)
778 r.rdir [i] = (
779 du * emap -> uh [i] + dv * emap -> vh [i] +
780 cosTheta * emap -> wh [i]
781 );
782
783 /* Sample behind surface? */
784 VCOPY(r.ron, emap -> ws);
785 if ((r.rod = DOT(r.rdir, r.ron)) <= 0)
786 continue;
787
788 /* Get radiance emitted in this direction; to get flux we
789 multiply by cos(theta_surface), dOmega, and dA. Ray
790 is directed towards light source for raytexture(). */
791 if (!(emap -> src -> sflags & SDISTANT))
792 for (i = 0; i < 3; i++)
793 r.rdir [i] = -r.rdir [i];
794
795 /* Port occluded in this direction? */
796 if (emap -> port && localhit(&r, &thescene))
797 continue;
798
799 raytexture(&r, mod -> omod);
800 setcolor(
801 r.rcol, mod -> oargs.farg [0], mod -> oargs.farg [1],
802 mod -> oargs.farg [2]
803 );
804 multcolor(r.rcol, r.pcol);
805
806 /* Multiply by cos(theta_surface) */
807 scalecolor(r.rcol, r.rod);
808
809 /* Add PDF sample if nonzero; importance info for photon emission
810 * could go here... */
811 if (colorAvg(r.rcol)) {
812 copycolor(sample -> pdf, r.rcol);
813 sample -> cdf = emap -> cdf += colorAvg(sample -> pdf);
814 sample -> theta = t;
815 sample++ -> phi = p;
816 emap -> numSamples++;
817 addcolor(emap -> partFlux, r.rcol);
818 }
819 }
820 }
821
822 /* Multiply by dOmega * dA */
823 scalecolor(emap -> partFlux, dOmega * emap -> partArea);
824 }
825 }
826
827
828
829 #define vomitPhoton emitPhoton
830 #define bluarrrghPhoton vomitPhoton
831
832 void emitPhoton (const EmissionMap* emap, RAY* ray)
833 /* Emit photon from current partition emap -> partitionCnt based on
834 emission distribution. Returns new photon ray. */
835 {
836 unsigned long i, lo, hi;
837 const EmissionSample* sample = emap -> samples;
838 RREAL du, dv, cosTheta, cosThetaSqr, sinTheta, phi;
839 const OBJREC* mod = findmaterial(emap -> src -> so);
840
841 /* Choose a new origin within current partition for every
842 emitted photon to break up clustering artifacts */
843 photonOrigin [emap -> src -> so -> otype] ((EmissionMap*)emap);
844 /* If we have a local glow source with a maximum radius, then
845 restrict our photon to the specified distance, otherwise we set
846 the limit imposed by photonMaxDist (or no limit if 0) */
847 if (
848 mod -> otype == MAT_GLOW &&
849 !(emap -> src -> sflags & SDISTANT) && mod -> oargs.farg[3] > FTINY
850 )
851 ray -> rmax = mod -> oargs.farg[3];
852 else
853 ray -> rmax = photonMaxDist;
854 rayorigin(ray, PRIMARY, NULL, NULL);
855
856 if (!emap -> numSamples) {
857 /* Source is unmodified and has no port, and either local with
858 normal aligned aperture, or distant with aperture above surface
859 --> use cosine weighted distribution */
860 cosThetaSqr = (1 -
861 pmapRandom(emitState) * (1 - sqr(max(emap -> cosThetaMax, 0)))
862 );
863 cosTheta = sqrt(cosThetaSqr);
864 sinTheta = sqrt(1 - cosThetaSqr);
865 phi = 2 * PI * pmapRandom(emitState);
866 setcolor(
867 ray -> rcol, mod -> oargs.farg [0], mod -> oargs.farg [1],
868 mod -> oargs.farg [2]
869 );
870 }
871
872 else {
873 /* Source is either modified, has a port, is local with off-normal
874 aperture, or distant with aperture partly below surface
875 --> choose direction from constructed cumulative distribution
876 function with Monte Carlo inversion using binary search. */
877 du = pmapRandom(emitState) * emap -> cdf;
878 lo = 1;
879 hi = emap -> numSamples;
880
881 while (hi > lo) {
882 i = (lo + hi) >> 1;
883 sample = emap -> samples + i - 1;
884
885 if (sample -> cdf >= du)
886 hi = i;
887 if (sample -> cdf < du)
888 lo = i + 1;
889 }
890
891 /* This is a uniform mapping, mon */
892 cosTheta = (1 -
893 (sample -> theta + pmapRandom(emitState)) *
894 (1 - emap -> cosThetaMax) / emap -> numTheta
895 );
896 sinTheta = sqrt(1 - sqr(cosTheta));
897 phi = 2 * PI * (sample -> phi + pmapRandom(emitState)) / emap -> numPhi;
898 copycolor(ray -> rcol, sample -> pdf);
899 }
900
901 /* Normalize photon flux so that average over RGB is 1 */
902 colorNorm(ray -> rcol);
903
904 VCOPY(ray -> rorg, emap -> photonOrg);
905 du = cos(phi) * sinTheta;
906 dv = sin(phi) * sinTheta;
907
908 for (i = 0; i < 3; i++)
909 ray -> rdir [i] = du * emap -> uh [i] + dv * emap -> vh [i] +
910 cosTheta * emap -> wh [i];
911
912 if (emap -> src -> sflags & SDISTANT)
913 /* Distant source; reverse ray direction to point into the scene. */
914 for (i = 0; i < 3; i++)
915 ray -> rdir [i] = -ray -> rdir [i];
916
917 if (emap -> port)
918 /* Photon emitted from port; move origin just behind port so it
919 will be scattered */
920 for (i = 0; i < 3; i++)
921 ray -> rorg [i] -= 2 * FTINY * ray -> rdir [i];
922
923 /* Assign emitting light source index */
924 ray -> rsrc = emap -> src - source;
925 }
926
927
928
929 /* SOURCE CONTRIBS FROM DIRECT / VOLUME PHOTONS -------------------------- */
930
931
932
933 void multDirectPmap (RAY *r)
934 /* Factor irradiance from direct photons into r -> rcol; interface to
935 * direct() */
936 {
937 COLOR photonIrrad;
938
939 /* Lookup direct photon irradiance */
940 (directPmap -> lookup)(directPmap, r, photonIrrad);
941
942 /* Multiply with coefficient in ray */
943 multcolor(r -> rcol, photonIrrad);
944
945 return;
946 }
947
948
949
950 void inscatterVolumePmap (RAY *r, COLOR inscatter)
951 /* Add inscattering from volume photon map; interface to srcscatter() */
952 {
953 /* Add ambient in-scattering via lookup callback */
954 (volumePmap -> lookup)(volumePmap, r, inscatter);
955 }
956