1 //******************************************************************************
2 ///
3 /// @file backend/lighting/photonshootingtask.cpp
4 ///
5 /// This module implements Photon Mapping.
6 ///
7 /// @copyright
8 /// @parblock
9 ///
10 /// Persistence of Vision Ray Tracer ('POV-Ray') version 3.8.
11 /// Copyright 1991-2021 Persistence of Vision Raytracer Pty. Ltd.
12 ///
13 /// POV-Ray is free software: you can redistribute it and/or modify
14 /// it under the terms of the GNU Affero General Public License as
15 /// published by the Free Software Foundation, either version 3 of the
16 /// License, or (at your option) any later version.
17 ///
18 /// POV-Ray is distributed in the hope that it will be useful,
19 /// but WITHOUT ANY WARRANTY; without even the implied warranty of
20 /// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
21 /// GNU Affero General Public License for more details.
22 ///
23 /// You should have received a copy of the GNU Affero General Public License
24 /// along with this program.  If not, see <http://www.gnu.org/licenses/>.
25 ///
26 /// ----------------------------------------------------------------------------
27 ///
28 /// POV-Ray is based on the popular DKB raytracer version 2.12.
29 /// DKBTrace was originally written by David K. Buck.
30 /// DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
31 ///
32 /// @endparblock
33 ///
34 //------------------------------------------------------------------------------
35 // SPDX-License-Identifier: AGPL-3.0-or-later
36 //******************************************************************************
37 
38 // Unit header file must be the first file included within POV-Ray *.cpp files (pulls in config)
39 #include "backend/lighting/photonshootingtask.h"
40 
41 // Standard C++ header files
42 #include <algorithm>
43 
44 // POV-Ray header files (core module)
45 #include "core/bounding/boundingbox.h"
46 #include "core/lighting/lightgroup.h"
47 #include "core/lighting/lightsource.h"
48 #include "core/math/matrix.h"
49 #include "core/render/ray.h"
50 #include "core/scene/object.h"
51 #include "core/shape/csg.h"
52 #include "core/support/octree.h"
53 
54 // POV-Ray header files (POVMS module)
55 #include "povms/povmscpp.h"
56 #include "povms/povmsid.h"
57 #include "povms/povmsutil.h"
58 
59 // POV-Ray header files (backend module)
60 #include "backend/lighting/photonshootingstrategy.h"
61 #include "backend/scene/backendscenedata.h"
62 #include "backend/scene/view.h"
63 #include "backend/scene/viewthreaddata.h"
64 
65 // this must be the last file included
66 #include "base/povdebug.h"
67 
68 namespace pov
69 {
70 
PhotonShootingTask(ViewData * vd,PhotonShootingStrategy * strategy,size_t seed)71 PhotonShootingTask::PhotonShootingTask(ViewData *vd, PhotonShootingStrategy* strategy, size_t seed) :
72     RenderTask(vd, seed, "Photon"),
73     trace(vd->GetSceneData(), GetViewDataPtr(), vd->GetQualityFeatureFlags(), cooperate),
74     rands(0.0, 1.0, 32768),
75     randgen(&rands),
76     strategy(strategy),
77     cooperate(*this),
78     maxTraceLevel(vd->GetSceneData()->photonSettings.Max_Trace_Level),
79     adcBailout(vd->GetSceneData()->photonSettings.adcBailout)
80 {
81 }
82 
~PhotonShootingTask()83 PhotonShootingTask::~PhotonShootingTask()
84 {
85 }
86 
87 
getMediaPhotonMap()88 PhotonMap* PhotonShootingTask::getMediaPhotonMap()
89 {
90     return GetViewDataPtr()->mediaPhotonMap;
91 }
92 
getSurfacePhotonMap()93 PhotonMap* PhotonShootingTask::getSurfacePhotonMap()
94 {
95     return GetViewDataPtr()->surfacePhotonMap;
96 }
97 
SendProgress(void)98 void PhotonShootingTask::SendProgress(void)
99 {
100     if (timer.ElapsedRealTime() > 1000)
101     {
102         // TODO FIXME PHOTONS
103         // with multiple threads shooting photons, the stats messages get confusing on the front-end.
104         // this is because each thread sends its own count, and so varying numbers get displayed.
105         // the totals should be combined and sent from a single thread.
106         timer.Reset();
107         POVMS_Object obj(kPOVObjectClass_PhotonProgress);
108         obj.SetInt(kPOVAttrib_CurrentPhotonCount, GetViewDataPtr()->surfacePhotonMap->numPhotons + GetViewDataPtr()->mediaPhotonMap->numPhotons);
109         RenderBackend::SendViewOutput(GetViewData()->GetViewId(), GetSceneData()->frontendAddress, kPOVMsgIdent_Progress, obj);
110     }
111 }
112 
113 
114 
Run()115 void PhotonShootingTask::Run()
116 {
117     // quit right away if photons not enabled
118     if (!GetSceneData()->photonSettings.photonsEnabled) return;
119 
120     Cooperate();
121 
122     PhotonShootingUnit* unit = strategy->getNextUnit();
123     while(unit)
124     {
125         //ShootPhotonsAtObject(unit->lightAndObject.target, unit->lightAndObject.light);
126         ShootPhotonsAtObject(unit->lightAndObject);
127         unit = strategy->getNextUnit();
128     }
129 
130 
131     // good idea to make sure all warnings and errors arrive frontend now [trf]
132     SendProgress();
133     Cooperate();
134 }
135 
Stopped()136 void PhotonShootingTask::Stopped()
137 {
138     // nothing to do for now [trf]
139 }
140 
Finish()141 void PhotonShootingTask::Finish()
142 {
143     GetViewDataPtr()->timeType = TraceThreadData::kPhotonTime;
144     GetViewDataPtr()->realTime = ConsumedRealTime();
145     GetViewDataPtr()->cpuTime = ConsumedCPUTime();
146 }
147 
148 
149 
150 
151 
152 
ShootPhotonsAtObject(LightTargetCombo & combo)153 void PhotonShootingTask::ShootPhotonsAtObject(LightTargetCombo& combo)
154 {
155     MathColour colour;             /* light color */
156     MathColour photonColour;       /* photon color */
157     ColourChannel dummyTransm;
158     int i;                         /* counter */
159     DBL theta, phi;                /* rotation angles */
160     DBL dphi;              /* deltas for theta and phi */
161     DBL jittheta, jitphi;          /* jittered versions of theta and phi */
162     DBL minphi,maxphi;
163                                    /* these are minimum and maximum for theta and
164                                        phi for the spiral shooting */
165     DBL Attenuation;               /* light attenuation for spotlight */
166     TRANSFORM Trans;               /* transformation for rotation */
167     int mergedFlags=0;             /* merged flags to see if we should shoot photons */
168     int notComputed=true;          /* have the ray containers been computed for this point yet?*/
169     int hitAtLeastOnce = false;    /* have we hit the object at least once - for autostop stuff */
170     ViewThreadData *renderDataPtr = GetViewDataPtr();
171 
172     /* get the light source colour */
173     colour = combo.light->colour;
174 
175     /* set global variable stuff */
176     renderDataPtr->photonSourceLight = combo.light;
177     renderDataPtr->photonTargetObject = combo.target;
178 
179     /* first, check on various flags... make sure all is a go for this ObjectPtr */
180     mergedFlags = combo.computeMergedFlags();
181 
182     if (!( ((mergedFlags & PH_RFR_ON_FLAG) && !(mergedFlags & PH_RFR_OFF_FLAG)) ||
183            ((mergedFlags & PH_RFL_ON_FLAG) && !(mergedFlags & PH_RFL_OFF_FLAG)) ))
184         /* it is a no-go for this object... bail out now */
185         return;
186 
187     renderDataPtr->photonSpread = combo.photonSpread;
188 
189     /* ---------------------------------------------
190            main ray-shooting loop
191        --------------------------------------------- */
192     i = 0;
193     notComputed = true;
194     for(theta=combo.mintheta; theta<combo.maxtheta; theta+=combo.dtheta)
195     {
196         Cooperate();
197         SendProgress();
198         renderDataPtr->hitObject = false;
199 
200         if (theta<EPSILON)
201         {
202             dphi=2*M_PI;
203         }
204         else
205         {
206             /* remember that for area lights, "theta" really means "radius" */
207             if (combo.light->Parallel)
208             {
209                 dphi = combo.dtheta / theta;
210             }
211             else
212             {
213                 dphi=combo.dtheta/sin(theta);
214             }
215         }
216 
217         // FIXME: should copy from previously computed shootingdirection
218         ShootingDirection shootingDirection(combo.light,combo.target);
219         shootingDirection.compute();
220 
221         minphi = -M_PI + dphi*randgen()*0.5;
222         maxphi = M_PI - dphi/2 + (minphi+M_PI);
223         for(phi=minphi; phi<maxphi; phi+=dphi)
224         {
225             int x_samples,y_samples;
226             int area_x, area_y;
227             /* ------------------- shoot one photon ------------------ */
228 
229             /* jitter theta & phi */
230             jitphi = phi + (dphi)*(randgen() - 0.5)*1.0*GetSceneData()->photonSettings.jitter;
231             jittheta = theta + (combo.dtheta)*(randgen() - 0.5)*1.0*GetSceneData()->photonSettings.jitter;
232 
233             /* actually, shoot multiple samples for area light */
234             if(combo.light->Area_Light && combo.light->Photon_Area_Light && !combo.light->Parallel)
235             {
236                 x_samples = combo.light->Area_Size1;
237                 y_samples = combo.light->Area_Size2;
238             }
239             else
240             {
241                 x_samples = 1;
242                 y_samples = 1;
243             }
244 
245             for(area_x=0; area_x<x_samples; area_x++)
246             {
247                 for(area_y=0; area_y<y_samples; area_y++)
248                 {
249                     TraceTicket ticket(maxTraceLevel, adcBailout);
250                     Ray ray(ticket);
251 
252                     ray.Origin = combo.light->Center;
253 
254                     if (combo.light->Area_Light && combo.light->Photon_Area_Light && !combo.light->Parallel)
255                     {
256                         shootingDirection.recomputeForAreaLight(ray,area_x,area_y);
257                         /* we must recompute the media containers (new start point) */
258                         notComputed = true;
259                     }
260 
261                     DBL dist_of_initial_from_center;
262 
263                     if (combo.light->Parallel)
264                     {
265                         DBL a;
266                         Vector3d v;
267                         /* assign the direction */
268                         ray.Direction = combo.light->Direction;
269 
270                         /* project ctr onto plane defined by Direction & light location */
271 
272                         a = dot(ray.Direction, shootingDirection.toctr);
273                         v = ray.Direction * (-a*shootingDirection.dist); /* MAYBE NEEDS TO BE NEGATIVE! */
274 
275                         ray.Origin = shootingDirection.ctr + v;
276 
277                         /* move point along "left" distance theta (remember theta means rad) */
278                         v = shootingDirection.left * jittheta;
279 
280                         /* rotate pt around ray.Direction by phi */
281                         /* use POV funcitons... slower but easy */
282                         Compute_Axis_Rotation_Transform(&Trans,combo.light->Direction,jitphi);
283                         MTransPoint(v, v, &Trans);
284 
285                         ray.Origin += v;
286 
287                         // compute the length of "v" if we're going to use it
288                         if (combo.light->Light_Type == CYLINDER_SOURCE)
289                         {
290                             Vector3d initial_from_center;
291                             initial_from_center = ray.Origin - combo.light->Center;
292                             dist_of_initial_from_center = initial_from_center.length();
293                         }
294                     }
295                     else
296                     {
297                         DBL st,ct;                     /* cos(theta) & sin(theta) for rotation */
298                         /* rotate toctr by theta around up */
299                         st = sin(jittheta);
300                         ct = cos(jittheta);
301                         /* use fast rotation */
302                         Vector3d v = -st * shootingDirection.left + ct * shootingDirection.toctr;
303 
304                         /* then rotate by phi around toctr */
305                         /* use POV funcitons... slower but easy */
306                         Compute_Axis_Rotation_Transform(&Trans,shootingDirection.toctr,jitphi);
307                         MTransPoint(ray.Direction, v, &Trans);
308                     }
309 
310                     /* ------ attenuation for spot/cylinder (copied from point.c) ---- */
311                     Attenuation = computeAttenuation(combo.light, ray, dist_of_initial_from_center);
312 
313                     /* set up defaults for reflection, refraction */
314                     renderDataPtr->passThruPrev = true;
315                     renderDataPtr->passThruThis = false;
316 
317                     renderDataPtr->photonDepth = 0.0;
318                     // GetViewDataPtr()->Trace_Level = 0;
319                     // Total_Depth = 0.0;
320                     renderDataPtr->Stats()[Number_Of_Photons_Shot]++;
321 
322                     /* attenuate for area light extra samples */
323                     Attenuation/=(x_samples*y_samples);
324 
325                     /* compute photon color from light source & attenuation */
326 
327                     photonColour = colour * Attenuation;
328 
329                     if (Attenuation<0.00001) continue;
330 
331                     /* handle the projected_through object if it exists */
332                     if (combo.light->Projected_Through_Object != nullptr)
333                     {
334                         /* try to intersect ray with projected-through ObjectPtr */
335                         Intersection Intersect;
336 
337                         Intersect.Object = nullptr;
338                         if ( trace.FindIntersection(combo.light->Projected_Through_Object, Intersect, ray) )
339                         {
340                             /* we must recompute the media containers (new start point) */
341                             notComputed = true;
342 
343                             /* we did hit it, so find the 'real' starting point of the ray */
344                             /* find the farthest intersection */
345                             ray.Origin += (Intersect.Depth+EPSILON) * ray.Direction;
346                             renderDataPtr->photonDepth += Intersect.Depth+EPSILON;
347                             while(trace.FindIntersection( combo.light->Projected_Through_Object, Intersect, ray) )
348                             {
349                                 ray.Origin += (Intersect.Depth+EPSILON) * ray.Direction;
350                                 renderDataPtr->photonDepth += Intersect.Depth+EPSILON;
351                             }
352                         }
353                         else
354                         {
355                             /* we didn't hit it, so stop now */
356                             continue;
357                         }
358 
359                     }
360 
361                     /* As mike said, "fire photon torpedo!" */
362                     //Initialize_Ray_Containers(&ray);
363                     ray.ClearInteriors ();
364 
365                     for(vector<ObjectPtr>::iterator object = GetSceneData()->objects.begin(); object != GetSceneData()->objects.end(); object++)
366                     {
367                         if ((*object)->Inside(ray.Origin, renderDataPtr) && ((*object)->interior != nullptr))
368                             ray.AppendInterior((*object)->interior.get());
369                     }
370 
371                     notComputed = false;
372                     //disp_elem = 0;   /* for dispersion */
373                     //disp_nelems = 0; /* for dispersion */
374 
375                     ray.SetFlags(Ray::PrimaryRay, false, true);
376                     trace.TraceRay(ray, photonColour, dummyTransm, 1.0, false);
377 
378                     /* display here */
379                     if ((i++%100) == 0)
380                     {
381                         Cooperate();
382                         SendProgress();
383                     }
384 
385                 } // for(area_y...)
386             } // for(area_x...)
387         }
388 
389         /* if we didn't hit anything and we're past the autostop angle, then
390              we should stop
391 
392              as per suggestion from Smellenberg, changed autostop to a percentage
393              of the object's bounding sphere. */
394 
395         /* suggested by Pabs, we only use autostop if we have it it once */
396         if (renderDataPtr->hitObject) hitAtLeastOnce=true;
397 
398         if (hitAtLeastOnce && !renderDataPtr->hitObject && renderDataPtr->photonTargetObject)
399             if (theta > GetSceneData()->photonSettings.autoStopPercent*combo.maxtheta)
400                 break;
401     } /* end of rays loop */
402 }
403 
computeAttenuation(const LightSource * Light,const Ray & ray,DBL dist_of_initial_from_center)404 DBL PhotonShootingTask::computeAttenuation(const LightSource* Light, const Ray& ray, DBL dist_of_initial_from_center)
405 {
406     DBL costheta_spot;
407     DBL Attenuation = 1.0;
408 
409     /* ---------- spot light --------- */
410     if (Light->Light_Type == SPOT_SOURCE)
411     {
412         costheta_spot = dot(ray.Direction, Light->Direction);
413 
414         if (costheta_spot > 0.0)
415         {
416             Attenuation = pow(costheta_spot, Light->Coeff);
417 
418             if (Light->Radius > 0.0)
419                 Attenuation *= cubic_spline(Light->Falloff, Light->Radius, costheta_spot);
420 
421         }
422         else
423             Attenuation = 0.0;
424     }
425     /* ---------- cylinder light ----------- */
426     else if (Light->Light_Type == CYLINDER_SOURCE)
427     {
428         DBL k, len;
429 
430         k = dot(ray.Direction, Light->Direction);
431 
432         if (k > 0.0)
433         {
434             len = dist_of_initial_from_center;
435 
436             if (len < Light->Falloff)
437             {
438                 DBL dist = 1.0 - len / Light->Falloff;
439                 Attenuation = pow(dist, Light->Coeff);
440 
441                 if (Light->Radius > 0.0 && len > Light->Radius)
442                     Attenuation *= cubic_spline(0.0, 1.0 - Light->Radius / Light->Falloff, dist);
443 
444             }
445             else
446                 Attenuation = 0.0;
447         }
448         else
449             Attenuation = 0.0;
450     }
451     return Attenuation;
452 }
453 
454 } // end of namespace
455