/*******************************************************************************
* photons.cpp
*
* This module implements Photon Mapping.
*
* Author: Nathan Kopp
*
* ---------------------------------------------------------------------------
* Persistence of Vision Ray Tracer ('POV-Ray') version 3.7.
* Copyright 1991-2013 Persistence of Vision Raytracer Pty. Ltd.
*
* POV-Ray is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
* published by the Free Software Foundation, either version 3 of the
* License, or (at your option) any later version.
*
* POV-Ray is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see .
* ---------------------------------------------------------------------------
* POV-Ray is based on the popular DKB raytracer version 2.12.
* DKBTrace was originally written by David K. Buck.
* DKBTrace Ver 2.0-2.12 were written by David K. Buck & Aaron A. Collins.
* ---------------------------------------------------------------------------
* $File: //depot/public/povray/3.x/source/backend/lighting/photons.cpp $
* $Revision: #1 $
* $Change: 6069 $
* $DateTime: 2013/11/06 11:59:40 $
* $Author: chrisc $
*******************************************************************************/
// frame.h must always be the first POV file included (pulls in platform config)
#include "backend/frame.h"
#include "base/povms.h"
#include "base/povmsgid.h"
#include "backend/math/vector.h"
#include "backend/math/matrices.h"
#include "backend/scene/objects.h"
#include "backend/shape/csg.h"
#include "backend/support/octree.h"
#include "backend/bounding/bbox.h"
#include "backend/scene/threaddata.h"
#include "backend/scene/scene.h"
#include "backend/scene/view.h"
#include "backend/support/msgutil.h"
#include "backend/lighting/point.h"
#include "backend/lighting/photons.h"
#include "backend/texture/normal.h"
#include "backend/texture/pigment.h"
#include "backend/texture/texture.h"
#include "backend/colour/colour.h"
#include "lightgrp.h"
#include "backend/lighting/photonshootingstrategy.h"
#include
// this must be the last file included
#include "base/povdebug.h"
// behavior of pass_through objects
#define PT_FILTER_BEFORE_TARGET // pass_through objects encountered before target do affect photons color
//#define PT_AMPLIFY_BUG // replicate bogus 3.6 "photon amplifier" effect
namespace pov
{
/* ------------------------------------------------------ */
/* global variables */
/* ------------------------------------------------------ */
SinCosOptimizations sinCosData;
// statistics helpers
//int gPhotonStat_i = 0;
//int gPhotonStat_x_samples = 0;
//int gPhotonStat_y_samples = 0;
//int gPhotonStat_end = 0;
/* ------------------------------------------------------ */
/* external variables */
/* ------------------------------------------------------ */
//extern int Trace_Level;
//extern int disp_elem;
//extern int disp_nelems;
/* ------------------------------------------------------ */
/* static functions */
/* ------------------------------------------------------ */
static void FreePhotonMemory();
static void InitPhotonMemory();
static int savePhotonMap(void);
static int loadPhotonMap(void);
/* ------------------------------------------------------ */
/* static variables */
/* ------------------------------------------------------ */
const int PHOTON_BLOCK_POWER = 14;
// PHOTON_BLOCK_SIZE must be equal to 2 raised to the power PHOTON_BLOCK_POWER
const int PHOTON_BLOCK_SIZE = (16384);
const int PHOTON_BLOCK_MASK = (PHOTON_BLOCK_SIZE-1);
const int INITIAL_BASE_ARRAY_SIZE = 100;
PhotonTrace::PhotonTrace(shared_ptr sd, TraceThreadData *td, unsigned int mtl, DBL adcb, unsigned int qf, Trace::CooperateFunctor& cf) :
Trace(sd, td, qf, cf, mediaPhotons, noRadiosity),
mediaPhotons(sd, td, this, new PhotonGatherer(&sd->mediaPhotonMap, sd->photonSettings))
{
}
PhotonTrace::~PhotonTrace()
{
}
DBL PhotonTrace::TraceRay(const Ray& ray, Colour& colour, COLC weight, Trace::TraceTicket& ticket, bool continuedRay, DBL maxDepth)
{
Intersection bestisect;
bool found;
NoSomethingFlagRayObjectCondition precond;
TrueRayObjectCondition postcond;
POV_ULONG nrays = threadData->Stats()[Number_Of_Rays]++;
if(((unsigned char) nrays & 0x0f) == 0x00)
cooperate();
// Check for max. trace level or ADC bailout.
if((ticket.traceLevel >= ticket.maxAllowedTraceLevel) || (weight < ticket.adcBailout))
{
if(weight < ticket.adcBailout)
threadData->Stats()[ADC_Saves]++;
colour.clear();
return BOUND_HUGE;
}
// Set highest level traced.
ticket.traceLevel++;
ticket.maxFoundTraceLevel = max(ticket.maxFoundTraceLevel, ticket.traceLevel);
if (maxDepth >= EPSILON)
bestisect.Depth = maxDepth;
found = FindIntersection(bestisect, ray, precond, postcond);
if(found)
{
// NK phmap
int oldptflag = threadData->passThruPrev;
threadData->passThruPrev = threadData->passThruThis;
// if this is the photonPass and we're shooting at a target
if(threadData->photonTargetObject)
{
// start with this assumption
threadData->passThruThis = false;
// if either this is the first ray or the last thing we hit was a pass-through object
if((ticket.traceLevel==1) || threadData->passThruPrev)
{
if (Test_Flag(bestisect.Object, PH_TARGET_FLAG))
{
// We hit *some* photon target object
if (!IsObjectInCSG(bestisect.Object,threadData->photonTargetObject))
{
// We did *not* hit *the* photon target
if ( Test_Flag(bestisect.Object, PH_PASSTHRU_FLAG) //||
// ( Check_No_Shadow_Group(Best_Intersection.Object, photonOptions.Light) &&
// !Check_Light_Group(Best_Intersection.Object, photonOptions.Light) )
)
// Hit some passthrough object; just carry on tracing.
{
threadData->passThruThis = true;
}
else
{
// Hit some non-passthrough object other than our target; do *not* follow the ray any further.
threadData->passThruThis = threadData->passThruPrev;
threadData->passThruPrev = oldptflag;
ticket.traceLevel--;
return (BOUND_HUGE);
}
}
else
{
threadData->hitObject = true; // we need to know that we hit it
}
}
else
{
// We did *not* hit a photon target
if ( Test_Flag(bestisect.Object, PH_PASSTHRU_FLAG) //||
// ( Check_No_Shadow_Group(Best_Intersection.Object, photonOptions.Light) &&
// !Check_Light_Group(Best_Intersection.Object, photonOptions.Light) )
)
threadData->passThruThis = true;
else
{
// Hit some non-passthrough non-target object; do *not* follow the ray any further.
threadData->passThruThis = threadData->passThruPrev;
threadData->passThruPrev = oldptflag;
ticket.traceLevel--;
return (BOUND_HUGE);
}
}
}
}
ComputeTextureColour(bestisect, colour, ray, weight, true, ticket);
// NK phmap
threadData->passThruThis = threadData->passThruPrev;
threadData->passThruPrev = oldptflag;
}
ticket.traceLevel--;
if(found == false)
return HUGE_VAL;
else
return bestisect.Depth;
}
void PhotonTrace::ComputeLightedTexture(Colour& LightCol, const TEXTURE *Texture, vector& warps, const Vector3d& ipoint, const Vector3d& rawnormal,
const Ray& ray, COLC weight, Intersection& isect, Trace::TraceTicket& ticket)
{
int i;
int layer_number;
int one_colour_found, colour_found;
DBL w1;
DBL New_Weight, TempWeight;
DBL Att, Trans;
Vector3d LayNormal, TopNormal;
Colour LayCol;
RGBColour FilCol;
RGBColour AttCol, ResCol;
Colour TmpCol;
Interior *interior;
const TEXTURE *Layer;
Ray NewRay(ray);
int doReflection, doDiffuse, doRefraction;
DBL reflectionWeight, refractionWeight, diffuseWeight, dieWeight, totalWeight;
DBL choice;
DBL Cos_Angle_Incidence;
int TIR_occured;
WNRXVector listWNRX(wnrxPool);
assert(listWNRX->empty()); // verify that the WNRXVector pulled from the pool is in a cleaned-up condition
// LightCol is the color of the light beam.
// result color for doing diffuse
ResCol.clear();
// NK 1998 - switched transmit component to zero
FilCol = RGBColour(1.0);
Trans = 1.0;
// initialize the new ray... we will probably end up using it
Assign_Vector(NewRay.Origin, isect.IPoint);
// In the future, we could enhance this so that users can determine
// how and when photons are deposited into different media.
// TODO FIXME - [CLi] for the sake of performance, this should be handled in the calling function, in case we're dealing with averaged textures!
// Calculate participating media effects, and deposit photons in media as we go.
if((qualityFlags & Q_VOLUME) && (!ray.GetInteriors().empty()) && (ray.IsHollowRay() == true))
{
// Calculate effects of all media we're currently in.
MediaVector medialist;
for(RayInteriorVector::const_iterator i(ray.GetInteriors().begin()); i != ray.GetInteriors().end(); i++)
{
for(vector::iterator im((*i)->media.begin()); im != (*i)->media.end(); im++)
medialist.push_back(&(*im));
}
/* TODO FIXME lightgroups
if ((Trace_Level > 1) &&
!threadData->passThruPrev && sceneData->photonSettings.maxMediaSteps>0 &&
!Test_Flag(isect.Object,PH_IGNORE_PHOTONS_FLAG) &&
Check_Light_Group(isect.Object,photonOptions.Light))
*/
if(!medialist.empty())
{
if((ticket.traceLevel > 1) && !threadData->passThruPrev && (sceneData->photonSettings.maxMediaSteps > 0))
mediaPhotons.ComputeMediaAndDepositPhotons(medialist, ray, isect, LightCol, ticket);
else
// compute media WITHOUT depositing photons
mediaPhotons.ComputeMedia(medialist, ray, isect, LightCol, ticket);
}
}
// Get distance based attenuation.
interior = isect.Object->interior;
AttCol = RGBColour(interior->Old_Refract);
if (interior != NULL)
{
if (ray.IsInterior(interior) == true)
{
if ((interior->Fade_Power > 0.0) && (fabs(interior->Fade_Distance) > EPSILON))
{
// NK attenuate
if (interior->Fade_Power>=1000)
{
AttCol *= exp( -(RGBColour(1.0)-interior->Fade_Colour) * isect.Depth/interior->Fade_Distance );
}
else
{
Att = 1.0 + pow(isect.Depth / interior->Fade_Distance, (DBL) interior->Fade_Power);
AttCol *= (interior->Fade_Colour + (RGBColour(1.0) - interior->Fade_Colour) / Att);
}
}
}
}
LightCol.red() *= AttCol.red();
LightCol.green() *= AttCol.green();
LightCol.blue() *= AttCol.blue();
// set size here
threadData->photonDepth += isect.Depth;
// First, we should save this photon!
if ((ticket.traceLevel > 1) && !threadData->passThruPrev &&
!Test_Flag(isect.Object,PH_IGNORE_PHOTONS_FLAG) &&
Check_Photon_Light_Group(isect.Object))
{
addSurfacePhoton(isect.IPoint, ray.Origin, RGBColour(LightCol));
}
#ifndef PT_FILTER_BEFORE_TARGET
if (threadData->passThruThis)
{
Ray NRay(ray);
Assign_Vector(NRay.Origin, isect.IPoint); // [CLi] this erroneously used *ipoint before
Assign_Vector(NRay.Direction, ray.Direction);
#ifndef PT_AMPLIFY_BUG
// Make sure we get insideness right
if (interior != NULL)
{
if (!NRay.RemoveInterior(interior))
NRay.AppendInterior(interior);
}
#endif // PT_AMPLIFY_BUG
TraceRay(NRay, LightCol, weight, ticket, true);
#ifndef PT_AMPLIFY_BUG
return;
#endif // PT_AMPLIFY_BUG
}
#endif // PT_FILTER_BEFORE_TARGET
// Loop through the layers and compute the ambient, diffuse,
// phong and specular for these textures.
one_colour_found = false;
for (layer_number = 0, Layer = Texture;
(Layer != NULL) && (Trans > ticket.adcBailout);
layer_number++, Layer = (TEXTURE *)Layer->Next)
{
// Get perturbed surface normal.
LayNormal = rawnormal;
if ((qualityFlags & Q_NORMAL) && (Layer->Tnormal != NULL))
{
for(vector::iterator i(warps.begin()); i != warps.end(); i++)
Warp_Normal(*LayNormal, *LayNormal, reinterpret_cast(*i), Test_Flag((*i), DONT_SCALE_BUMPS_FLAG));
Perturb_Normal(*LayNormal, Layer->Tnormal, *ipoint, &isect, &ray, threadData);
if((Test_Flag(Layer->Tnormal, DONT_SCALE_BUMPS_FLAG)))
LayNormal.normalize();
for(vector::reverse_iterator i(warps.rbegin()); i != warps.rend(); i++)
UnWarp_Normal(*LayNormal, *LayNormal, reinterpret_cast(*i), Test_Flag((*i), DONT_SCALE_BUMPS_FLAG));
}
// Store top layer normal.
if (!layer_number)
{
TopNormal = LayNormal;
}
// Get surface colour.
New_Weight = weight * Trans;
colour_found = Compute_Pigment(LayCol, Layer->Pigment, *ipoint, &isect, &ray, threadData);
Att = Trans * (1.0 - min(1.0, (DBL)(LayCol[pFILTER] + LayCol[pTRANSM])));
LayCol.red() *= FilCol.red();
LayCol.green() *= FilCol.green();
LayCol.blue() *= FilCol.blue();
ResCol += RGBColour(LayCol) * Att;
// If a valid color was returned set one_colour_found to true.
// An invalid color is returned if a surface point is outside
// an image map used just once.
if (colour_found)
{
one_colour_found = true;
}
listWNRX->push_back(WNRX(New_Weight, LayNormal, RGBColour(), Layer->Finish->Reflect_Exp));
// angle-dependent reflectivity
VDot(Cos_Angle_Incidence, ray.Direction, *LayNormal);
Cos_Angle_Incidence *= -1.0;
if ((isect.Object->interior != NULL) ||
(Layer->Finish->Reflection_Type != 1))
{
ComputeReflectivity (listWNRX->back().weight, listWNRX->back().reflec,
Layer->Finish->Reflection_Max, Layer->Finish->Reflection_Min,
Layer->Finish->Reflection_Type, Layer->Finish->Reflection_Falloff,
Cos_Angle_Incidence, ray, isect.Object->interior);
}
else
{
throw POV_EXCEPTION_STRING("Reflection_Type 1 used with no interior."); // TODO FIXME - wrong place to report this [trf]
}
// Added by MBP for metallic reflection
if (Layer->Finish->Reflect_Metallic != 0.0)
{
DBL R_M=Layer->Finish->Reflect_Metallic;
DBL x = fabs(acos(Cos_Angle_Incidence)) / M_PI_2;
DBL F = 0.014567225 / Sqr(x - 1.12) - 0.011612903;
F=min(1.0,max(0.0,F));
listWNRX->back().reflec[pRED] *= (1.0 + R_M * (1.0 - F) * (LayCol[pRED] - 1.0));
listWNRX->back().reflec[pGREEN] *= (1.0 + R_M * (1.0 - F) * (LayCol[pGREEN] - 1.0));
listWNRX->back().reflec[pBLUE] *= (1.0 + R_M * (1.0 - F) * (LayCol[pBLUE] - 1.0));
}
// NK - I think we SHOULD do something like this: (to apply the layer's color)
//listWNRX->back().reflec.red()*=FilCol.red();
//listWNRX->back().reflec.green()*=FilCol.green();
//listWNRX->back().reflec.blue()*=FilCol.blue();
// Get new filter color.
if (colour_found)
{
FilCol *= LayCol.rgbTransm();
if(Layer->Finish->Conserve_Energy)
{
// adjust filcol based on reflection
// this would work so much better with r,g,b,rt,gt,bt
FilCol.red() *= min(1.0,1.0-listWNRX->back().reflec.red());
FilCol.green() *= min(1.0,1.0-listWNRX->back().reflec.green());
FilCol.blue() *= min(1.0,1.0-listWNRX->back().reflec.blue());
}
}
// Get new remaining translucency.
Trans = min(1.0f, std::fabs(FilCol.greyscale()));
}
//******************
// now that we have color info, we can determine what we want to do next
//*******************
if (threadData->photonTargetObject)
{
// if photon is for caustic map, then do
// reflection/refraction always
// diffuse never
doReflection = 1;
doRefraction = 1;
doDiffuse = 0;
}
else
{
// if photon is for global map, then decide which we want to do
diffuseWeight = max(0.0f, std::fabs(ResCol.greyscale()));
// use top-layer finish only
if(Texture->Finish)
diffuseWeight*=Texture->Finish->Diffuse;
refractionWeight = Trans;
// reflection only for top layer!!!!!!
// TODO is "rend()" the top layer or the bottom layer???
reflectionWeight = max(0.0f, std::fabs(listWNRX->rend()->reflec.greyscale()));
dieWeight = max(0.0,(1.0-diffuseWeight));
// normalize weights: make sum be 1.0
totalWeight = reflectionWeight + refractionWeight + diffuseWeight + dieWeight;
if ((reflectionWeight + refractionWeight + diffuseWeight) > ticket.adcBailout)
{
diffuseWeight /= totalWeight;
refractionWeight /= totalWeight;
reflectionWeight /= totalWeight;
dieWeight /= totalWeight;
// now, determine which we want to use
choice = randomNumberGenerator();
if (choicepassThruPrev = false;
}
else if (choicepassThruPrev = true;
}
else if (choicerend()->reflec /= reflectionWeight;
doReflection = 1;
doRefraction = 0;
doDiffuse = 0;
threadData->passThruPrev = true;
}
// else die
}
else
{
doReflection = 0;
doRefraction = 0;
doDiffuse = 0;
}
}
#ifdef PT_FILTER_BEFORE_TARGET
if (threadData->passThruThis)
{
w1 = max3(FilCol.red(), FilCol.green(), FilCol.blue());
New_Weight = weight * max(w1, 0.0); // TODO FIXME - check if the max() is necessary
// Trace refracted ray.
threadData->GFilCol = FilCol;
Vector3d tmpIPoint(isect.IPoint);
Ray NRay(ray);
Assign_Vector(NRay.Origin, isect.IPoint);
Assign_Vector(NRay.Direction, ray.Direction);
Colour CurLightCol;
RGBColour GFilCol = threadData->GFilCol;
CurLightCol.red() = LightCol.red() * GFilCol.red();
CurLightCol.green() = LightCol.green() * GFilCol.green();
CurLightCol.blue() = LightCol.blue() * GFilCol.blue();
#ifndef PT_AMPLIFY_BUG
// Make sure we get insideness right
if (interior != NULL)
{
if (!NRay.RemoveInterior(interior))
NRay.AppendInterior(interior);
}
#endif // PT_AMPLIFY_BUG
TraceRay(NRay, CurLightCol, (float)New_Weight, ticket, true);
#ifndef PT_AMPLIFY_BUG
return;
#endif // PT_AMPLIFY_BUG
}
#endif // PT_FILTER_BEFORE_TARGET
// Shoot diffusely scattered photons.
if (doDiffuse)
{
//ChooseRay(Ray &NewRay, VECTOR Normal, Ray &ray, VECTOR Raw_Normal, int WhichRay)
ChooseRay(NewRay, *LayNormal, ray, *rawnormal, rand()%400);
Colour CurLightCol;
CurLightCol.red() = LightCol.red()*ResCol.red();
CurLightCol.green() = LightCol.green()*ResCol.green();
CurLightCol.blue() = LightCol.blue()*ResCol.blue();
// don't trace if < EPSILON
// now trace it
TraceRay(NewRay, CurLightCol, 1.0, ticket, false);
}
// Shoot refracted photons.
TIR_occured = false;
// only really do refraction if...
// (1) We have already reached the target, -and-
// (2) (a) photon refraction is turned on for the object, and not explicitly turned off for the light, -or-
// (b) photon refraction is turned on for the light, and not explicitly turned off for the object
doRefraction = doRefraction &&
( ( Test_Flag(isect.Object, PH_RFR_ON_FLAG) && !(threadData->photonSourceLight->Flags & PH_RFR_OFF_FLAG) ) ||
( !Test_Flag(isect.Object, PH_RFR_OFF_FLAG) && (threadData->photonSourceLight->Flags & PH_RFR_ON_FLAG) ) ||
threadData->passThruThis );
// If the surface is translucent a transmitted ray is traced
// and its illunination is filtered by FilCol.
if (doRefraction && ((interior = isect.Object->interior) != NULL) && (Trans > ticket.adcBailout) && (qualityFlags & Q_REFRACT))
{
w1 = max3(FilCol.red(), FilCol.green(), FilCol.blue());
New_Weight = weight * max(w1, 0.0); // TODO FIXME - check if the max() is necessary
// Trace refracted ray.
threadData->GFilCol = FilCol;
Vector3d tmpIPoint(isect.IPoint);
TIR_occured = ComputeRefractionForPhotons(Texture->Finish, interior, tmpIPoint, ray, TopNormal, rawnormal, LightCol, New_Weight, ticket);
}
// Shoot reflected photons.
// If total internal reflection occured all reflections using
// TopNormal are skipped.
// only really do reflection if...
// (1) We have already reached the target, -and-
// (2) (a) photon reflection is turned on for the object, and not explicitly turned off for the light, -or-
// (b) photon reflection is turned on for the light, and not explicitly turned off for the object
doReflection = doReflection &&
( ( ( Test_Flag(isect.Object, PH_RFL_ON_FLAG) && !(threadData->photonSourceLight->Flags & PH_RFL_OFF_FLAG) ) ||
( !Test_Flag(isect.Object, PH_RFL_OFF_FLAG) && (threadData->photonSourceLight->Flags & PH_RFL_ON_FLAG) ) ) &&
!threadData->passThruThis );
if(doReflection && (qualityFlags & Q_REFLECT))
{
Layer = Texture;
for (i = 0; i < layer_number; i++)
{
if ((!TIR_occured) ||
(fabs(TopNormal[X]-(*listWNRX)[i].normal[X]) > EPSILON) ||
(fabs(TopNormal[Y]-(*listWNRX)[i].normal[Y]) > EPSILON) ||
(fabs(TopNormal[Z]-(*listWNRX)[i].normal[Z]) > EPSILON))
{
if (!(*listWNRX)[i].reflec.isZero())
{
// Added by MBP for metallic reflection
TmpCol.red() = LightCol.red();
TmpCol.green() = LightCol.green();
TmpCol.blue() = LightCol.blue();
if ((*listWNRX)[i].reflex != 1.0)
{
TmpCol.red() = (*listWNRX)[i].reflec.red() * pow(TmpCol.red(), (*listWNRX)[i].reflex);
TmpCol.green() = (*listWNRX)[i].reflec.green() * pow(TmpCol.green(), (*listWNRX)[i].reflex);
TmpCol.blue() = (*listWNRX)[i].reflec.blue() * pow(TmpCol.blue(), (*listWNRX)[i].reflex);
}
else
{
TmpCol.red() = (*listWNRX)[i].reflec.red() * TmpCol.red();
TmpCol.green() = (*listWNRX)[i].reflec.green() * TmpCol.green();
TmpCol.blue() = (*listWNRX)[i].reflec.blue() * TmpCol.blue();
}
TempWeight = (*listWNRX)[i].weight * max3((*listWNRX)[i].reflec.red(), (*listWNRX)[i].reflec.green(), (*listWNRX)[i].reflec.blue());
Vector3d tmpIPoint(isect.IPoint);
ComputeReflection(Layer->Finish, tmpIPoint, ray, LayNormal, rawnormal, TmpCol, TempWeight, ticket);
}
}
// if global photons, the stop after first layer
if (threadData->photonTargetObject==NULL)
break;
Layer = (TEXTURE *)Layer->Next;
}
}
// now reset the depth!
threadData->photonDepth -= isect.Depth;
}
bool PhotonTrace::ComputeRefractionForPhotons(const FINISH* finish, Interior *interior, const Vector3d& ipoint, const Ray& ray, const Vector3d& normal, const Vector3d& rawnormal, Colour& colour, COLC weight, Trace::TraceTicket& ticket)
{
Ray nray(ray);
Vector3d localnormal;
DBL n, ior, dispersion;
unsigned int dispersionelements = interior->Disp_NElems;
bool havedispersion = (dispersionelements > 0);
nray.SetFlags(Ray::RefractionRay, ray);
// Set up new ray.
Assign_Vector(nray.Origin, *ipoint);
// Get ratio of iors depending on the interiors the ray is traversing.
// Note:
// For the purpose of reflection, the space occupied by "nested" objects is considered to be "outside" the containing objects,
// i.e. when encountering (A (B B) A) we pretend that it's (A A|B B|A A).
// (Here "(X" and "X)" denote the entering and leaving of object X, and "X|Y" denotes an interface between objects X and Y.)
// In case of overlapping objects, the intersecting region is considered to be part of whatever object is encountered last,
// i.e. when encountering (A (B A) B) we pretend that it's (A A|B B|B B).
if(ray.GetInteriors().empty())
{
// The ray is entering from the atmosphere.
nray.AppendInterior(interior);
ior = sceneData->atmosphereIOR / interior->IOR;
if(havedispersion == true)
dispersion = sceneData->atmosphereDispersion / interior->Dispersion;
}
else
{
// The ray is currently inside an object.
if(interior == nray.GetInteriors().back()) // The ray is leaving the "innermost" object
{
nray.RemoveInterior(interior);
if(nray.GetInteriors().empty())
{
// The ray is leaving into the atmosphere
ior = interior->IOR / sceneData->atmosphereIOR;
if(havedispersion == true)
dispersion = interior->Dispersion / sceneData->atmosphereDispersion;
}
else
{
// The ray is leaving into another object, i.e. (A (B B) ...
// For the purpose of refraction, pretend that we weren't inside that other object,
// i.e. pretend that we didn't encounter (A (B B) ... but (A A|B B|A ...
ior = interior->IOR / nray.GetInteriors().back()->IOR;
if(havedispersion == true)
{
dispersion = interior->Dispersion / nray.GetInteriors().back()->Dispersion;
dispersionelements = max(dispersionelements, (unsigned int)(nray.GetInteriors().back()->Disp_NElems));
}
}
}
else if(nray.RemoveInterior(interior) == true) // The ray is leaving the intersection of overlapping objects, i.e. (A (B A) ...
{
// For the purpose of refraction, pretend that we had already left the other member of the intersection when we entered the overlap,
// i.e. pretend that we didn't encounter (A (B A) ... but (A A|B B|B ...
ior = 1.0;
dispersion = 1.0;
}
else
{
// The ray is entering a new object.
// For the purpose of refraction, pretend that we're leaving any containing objects,
// i.e. pretend that we didn't encounter (A (B ... but (A A|B ...
ior = nray.GetInteriors().back()->IOR / interior->IOR;
if(havedispersion == true)
dispersion = nray.GetInteriors().back()->Dispersion / interior->Dispersion;
nray.AppendInterior(interior);
}
}
// Do the two mediums traversed have the same indices of refraction?
if((fabs(ior - 1.0) < EPSILON) && (fabs(dispersion - 1.0) < EPSILON))
{
// Only transmit the ray.
Assign_Vector(nray.Direction, ray.Direction);
// Trace a transmitted ray.
threadData->Stats()[Transmitted_Rays_Traced]++;
// photon:
// added this block
// changed 2nd variable in next line from color to lc
// added return false
Colour lc;
RGBColour GFilCol = threadData->GFilCol;
lc.red() = colour.red() * GFilCol.red();
lc.green() = colour.green() * GFilCol.green();
lc.blue() = colour.blue() * GFilCol.blue();
TraceRay(nray, lc, weight, ticket, true);
return false;
}
else
{
// Refract the ray.
VDot(n, ray.Direction, *normal);
if(n <= 0.0)
{
localnormal = normal;
n = -n;
}
else
localnormal = -normal;
if(fabs (dispersion - 1.0) < EPSILON)
return TraceRefractionRayForPhotons(finish, ipoint, ray, nray, ior, n, normal, rawnormal, localnormal, colour, weight, ticket);
else if(ray.IsMonochromaticRay() == true)
return TraceRefractionRayForPhotons(finish, ipoint, ray, nray, ray.GetSpectralBand().GetDispersionIOR(ior, dispersion), n, normal, rawnormal, localnormal, colour, weight, ticket);
else
{
for(unsigned int i = 0; i < dispersionelements; i++)
{
SpectralBand spectralBand(i, dispersionelements);
Colour tempcolour;
tempcolour = Colour(RGBColour(colour) * spectralBand.GetHue() / DBL(dispersionelements));
// NB setting the dispersion factor also causes the MonochromaticRay flag to be set
nray.SetSpectralBand(spectralBand);
(void)TraceRefractionRayForPhotons(finish, ipoint, ray, nray, spectralBand.GetDispersionIOR(ior, dispersion), n, normal, rawnormal, localnormal, tempcolour, weight, ticket);
}
}
}
return false;
}
bool PhotonTrace::TraceRefractionRayForPhotons(const FINISH* finish, const Vector3d& ipoint, const Ray& ray, Ray& nray, DBL ior, DBL n, const Vector3d& normal, const Vector3d& rawnormal, const Vector3d& localnormal, Colour& colour, COLC weight, Trace::TraceTicket& ticket)
{
// Compute refrated ray direction using Heckbert's method.
DBL t = 1.0 + Sqr(ior) * (Sqr(n) - 1.0);
if(t < 0.0)
{
// Total internal reflection occures.
threadData->Stats()[Internal_Reflected_Rays_Traced]++;
ComputeReflection(finish, ipoint, ray, normal, rawnormal, colour, weight, ticket);
return true;
}
t = ior * n - sqrt(t);
VLinComb2(nray.Direction, ior, ray.Direction, t, *localnormal);
// Trace a refracted ray.
threadData->Stats()[Refracted_Rays_Traced]++;
Colour lc;
RGBColour GFilCol = threadData->GFilCol;
lc.red() = colour.red() * GFilCol.red();
lc.green() = colour.green() * GFilCol.green();
lc.blue() = colour.blue() * GFilCol.blue();
TraceRay(nray, lc, weight, ticket, false);
return false;
}
/*****************************************************************************
FUNCTION
addSurfacePhoton()
Adds a photon to the array of photons.
Preconditions:
InitBacktraceEverything() was called
'Point' is the intersection point to store the photon
'Origin' is the origin of the light ray
'LightCol' is the color of the light propogated through the scene
'RawNorm' is the raw normal of the surface intersected
Postconditions:
Another photon is allocated (by AllocatePhoton())
The information passed in (as well as renderer->sceneData->photonSettings.photonDepth)
is stored in the photon data structure.
******************************************************************************/
void PhotonTrace::addSurfacePhoton(const VECTOR Point, const VECTOR Origin, const RGBColour& LightCol)
{
// TODO FIXME - this seems to have a lot in common with addMediaPhoton()
Photon *Photon;
RGBColour LightCol2;
DBL Attenuation;
VECTOR d;
DBL d_len, phi, theta;
PhotonMap *map;
// first, compensate for POV's weird light attenuation
LightSource *photonLight = threadData->photonSourceLight;
if ((photonLight->Fade_Power > 0.0) && (fabs(photonLight->Fade_Distance) > EPSILON))
{
Attenuation = 2.0 / (1.0 + pow(threadData->photonDepth / photonLight->Fade_Distance, photonLight->Fade_Power));
}
else
Attenuation = 1;
LightCol2 = LightCol * (Attenuation * threadData->photonSpread*threadData->photonSpread);
if(!photonLight->Parallel)
LightCol2 *= (threadData->photonDepth*threadData->photonDepth);
// if too dark, maybe we should stop here
#ifdef GLOBAL_PHOTONS
if(threadData->photonObject==NULL)
{
map = &globalPhotonMap;
threadData->Stats()[Number_Of_Global_Photons_Stored]++;
}
else
#endif
{
map = (threadData->surfacePhotonMap);
threadData->Stats()[Number_Of_Photons_Stored]++;
}
// allocate the photon
Photon = map->AllocatePhoton();
// convert photon from three floats to 4 bytes
colour2photonRgbe(Photon->colour, LightCol2);
// store the location
Assign_Vector(Photon->Loc, Point);
// now determine rotation angles
VSub(d,Origin, Point);
VNormalizeEq(d);
d_len = sqrt(d[X]*d[X]+d[Z]*d[Z]);
phi = acos(d[X]/d_len);
if (d[Z]<0) phi = -phi;
theta = acos(d_len);
if (d[Y]<0) theta = -theta;
// cram these rotation angles into two signed bytes
Photon->theta=(signed char)(theta*127.0/M_PI);
Photon->phi=(signed char)(phi*127.0/M_PI);
}
PhotonMediaFunction::PhotonMediaFunction(shared_ptr sd, TraceThreadData *td, Trace *t, PhotonGatherer *pg) :
MediaFunction(td, t, pg),
sceneData(sd)
{
}
/*****************************************************************************
FUNCTION
addMediaPhoton()
Adds a photon to the array of photons.
Preconditions:
InitBacktraceEverything() was called
'Point' is the intersection point to store the photon
'Origin' is the origin of the light ray
'LightCol' is the color of the light propogated through the scene
Postconditions:
Another photon is allocated (by AllocatePhoton())
The information passed in (as well as renderer->sceneData->photonSettings.photonDepth)
is stored in the photon data structure.
******************************************************************************/
void PhotonMediaFunction::addMediaPhoton(const VECTOR Point, const VECTOR Origin, const RGBColour& LightCol, DBL depthDiff)
{
// TODO FIXME - this seems to have a lot in common with addSurfacePhoton()
Photon *Photon;
RGBColour LightCol2;
DBL Attenuation;
VECTOR d;
DBL d_len, phi, theta;
// first, compensate for POV's weird light attenuation
// TODO CLARIFY - what *exactly* does this compensate for (and how)??
LightSource *photonLight = threadData->photonSourceLight;
if ((photonLight->Fade_Power > 0.0) && (fabs(photonLight->Fade_Distance) > EPSILON))
Attenuation = 2.0 / (1.0 + pow((threadData->photonDepth+depthDiff) / photonLight->Fade_Distance, photonLight->Fade_Power));
else
Attenuation = 1;
#if 0
LightCol2 = LightCol * (sceneData->photonSettings.photonSpread*sceneData->photonSettings.photonSpread);
if(!photonLight->Parallel)
LightCol2 *= ( (sceneData->photonSettings.photonDepth+depthDiff)*(sceneData->photonSettings.photonDepth+depthDiff) * Attenuation );
#else
LightCol2 = LightCol * (Attenuation * threadData->photonSpread*threadData->photonSpread);
if(!photonLight->Parallel)
LightCol2 *= ( (threadData->photonDepth+depthDiff) * (threadData->photonDepth+depthDiff) );
#endif
// if too dark, maybe we should stop here
// allocate the photon
if(threadData->photonTargetObject==NULL) return;
threadData->Stats()[Number_Of_Media_Photons_Stored]++;
Photon = threadData->mediaPhotonMap->AllocatePhoton();
// convert photon from three floats to 4 bytes
colour2photonRgbe(Photon->colour, LightCol2);
// store the location
Assign_Vector(Photon->Loc, Point);
// now determine rotation angles
VSub(d,Origin, Point);
VNormalizeEq(d);
d_len = sqrt(d[X]*d[X]+d[Z]*d[Z]);
phi = acos(d[X]/d_len);
if (d[Z]<0) phi = -phi;
theta = acos(d_len);
if (d[Y]<0) theta = -theta;
// cram these rotation angles into two signed bytes
Photon->theta=(signed char)(theta*127.0/M_PI);
Photon->phi=(signed char)(phi*127.0/M_PI);
}
void PhotonMediaFunction::ComputeMediaAndDepositPhotons(MediaVector& medias, const Ray& ray, const Intersection& isect, Colour& colour, Trace::TraceTicket& ticket)
{
LightSourceEntryVector lights;
LitIntervalVector litintervals;
MediaIntervalVector mediaintervals;
Media *IMedia;
bool all_constant_and_light_ray = ray.IsShadowTestRay(); // is all the media constant?
bool ignore_photons = true;
bool use_extinction = false;
bool use_scattering = false;
int minSamples;
DBL aa_threshold = HUGE_VAL;
// Find media with the largest number of intervals.
IMedia = medias.front();
for(MediaVector::iterator i(medias.begin()); i != medias.end(); i++)
{
// find media with the most intervals
if((*i)->Intervals > IMedia->Intervals)
IMedia = (*i);
// find smallest AA_Threshold
if((*i)->AA_Threshold < aa_threshold)
aa_threshold = (*i)->AA_Threshold;
// do not ignore photons if at least one media wants photons
ignore_photons = ignore_photons && (*i)->ignore_photons;
// use extinction if at leeast one media wants extinction
use_extinction = use_extinction || (*i)->use_extinction;
// use scattering if at leeast one media wants scattering
use_scattering = use_scattering || (*i)->use_scattering;
// NK fast light_ray media calculation for constant media
if((*i)->Density)
all_constant_and_light_ray = all_constant_and_light_ray && ((*i)->Density->Type == PLAIN_PATTERN);
}
// If this is a light ray and no extinction is used we can return.
if((!use_extinction)) // TODO FIXME - this condition implies that when no extinction is used, no photons are deposited, which Nathan thinks is a bug [trf] September 5th, 2005
return;
// Prepare the Monte Carlo integration along the ray from P0 to P1.
// for photon trace, this is always a light ray, so always do this
ComputeMediaLightInterval(lights, litintervals, ray, isect);
if(litintervals.empty())
litintervals.push_back(LitInterval(false, 0.0, isect.Depth, 0, 0));
// Set up sampling intervals (makes sure we will always have enough intervals)
ComputeMediaSampleInterval(litintervals, mediaintervals, IMedia);
if(mediaintervals.front().s0 > 0.0)
mediaintervals.insert(mediaintervals.begin(),
MediaInterval(false, 0,
0.0,
mediaintervals.front().s0,
mediaintervals.front().s0,
0, 0));
if(mediaintervals.back().s1 < isect.Depth)
mediaintervals.push_back(MediaInterval(false, 0,
mediaintervals.back().s1,
isect.Depth,
isect.Depth - mediaintervals.back().s1,
0, 0));
minSamples = IMedia->Min_Samples;
// Sample all intervals.
// NOTE: We probably should change this to use only one interval
//if((IMedia->Sample_Method == 3) && !all_constant_and_light_ray) // adaptive sampling
// ComputeMediaAdaptiveSampling(medias, lights, mediaintervals, ray, IMedia, aa_threshold, minSamples, ignore_photons, use_scattering, false);
//else
DepositMediaPhotons(colour, medias, lights, mediaintervals, ray, minSamples, ignore_photons, use_scattering, all_constant_and_light_ray, ticket);
}
void PhotonMediaFunction::DepositMediaPhotons(Colour& colour, MediaVector& medias, LightSourceEntryVector& lights, MediaIntervalVector& mediaintervals,
const Ray& ray, int minsamples, bool ignore_photons, bool use_scattering, bool all_constant_and_light_ray, Trace::TraceTicket& ticket)
{
int j;
RGBColour Od;
DBL d0;
RGBColour C0;
RGBColour od0;
RGBColour PhotonColour;
Od = RGBColour(colour);
for(MediaIntervalVector::iterator i(mediaintervals.begin()); i != mediaintervals.end(); i++)
{
DBL mediaSpacingFactor;
if(!threadData->photonSourceLight->Parallel)
{
minsamples=(int)
((*i).ds /
(threadData->photonSpread *
threadData->photonDepth *
sceneData->photonSettings.mediaSpacingFactor));
}
else
{
minsamples=(int)
((*i).ds /
(threadData->photonSpread *
sceneData->photonSettings.mediaSpacingFactor));
}
if (minsamples<=sceneData->photonSettings.maxMediaSteps)
{
// all's well
mediaSpacingFactor = sceneData->photonSettings.mediaSpacingFactor;
}
else
{
// too many steps - use fewer steps and a bigger spacing factor
minsamples = sceneData->photonSettings.maxMediaSteps;
if(!threadData->photonSourceLight->Parallel)
{
mediaSpacingFactor =
((*i).ds /
(threadData->photonSpread *
threadData->photonDepth *
minsamples));
}
else
{
mediaSpacingFactor =
((*i).ds /
(threadData->photonSpread *
minsamples));
}
}
// Sample current interval.
threadData->Stats()[Media_Intervals]++;
for(j = 0; j < minsamples; j++)
{
d0 = (j + 0.5 + randomNumberGenerator()*sceneData->photonSettings.jitter - 0.5*sceneData->photonSettings.jitter) / minsamples;
ComputeOneMediaSample(medias, lights, *i, ray, d0, C0, od0, 2 /* use method 2 */, ignore_photons, use_scattering, true, ticket);
if (use_scattering && !ignore_photons)
{
if(!threadData->photonSourceLight->Parallel)
{
PhotonColour = Od * ( mediaSpacingFactor *
threadData->photonSpread *
(threadData->photonDepth+d0*(*i).ds+(*i).s0) );
}
else
{
PhotonColour = Od * ( mediaSpacingFactor *
threadData->photonSpread );
}
//Od[0] = colour[0]*exp(-(*i).od[0]/(minsamples*2));
//Od[1] = colour[1]*exp(-(*i).od[1]/(minsamples*2));
//Od[2] = colour[2]*exp(-(*i).od[2]/(minsamples*2));
VECTOR TempPoint;
VEvaluateRay(TempPoint, ray.Origin, d0*(*i).ds+(*i).s0, ray.Direction);
addMediaPhoton(TempPoint, ray.Origin, PhotonColour, d0*(*i).ds+(*i).s0);
}
}
}
// Sum the influences of all intervals.
Od.clear();
for(MediaIntervalVector::iterator i(mediaintervals.begin()); i != mediaintervals.end(); i++)
{
// Add optical depth of current interval.
Od += (*i).od / (DBL)(*i).samples;
}
// Add contribution estimated for the participating media.
colour[0] *= exp(-Od[0]);
colour[1] *= exp(-Od[1]);
colour[2] *= exp(-Od[2]);
}
/*****************************************************************************
FUNCTION
InitBacktraceEverything()
Allocates memory.
Initializes all photon mapping stuff.
Does not create the photon map.
Preconditions: InitBacktraceEverything() not yet called
or
both InitBacktraceEverything() and FreeBacktraceEverything() called
Postconditions:
If photonSettings.photonsEnabled is true, then
memory for photon mapping is allocated.
else
nothing is done
******************************************************************************/
PhotonMap::PhotonMap()
{
minGatherRad = 0.0;
// defaults
minGatherRadMult=1.0;
gatherRadStep=0.5;
gatherNumSteps=2;
// memory initialization
int k;
// allocate the base array
numPhotons = 0;
numBlocks = INITIAL_BASE_ARRAY_SIZE;
head = (PhotonBlock *)POV_MALLOC(sizeof(PhotonBlock *)*INITIAL_BASE_ARRAY_SIZE, "photons");
// zero the array
for(k=0; k 0..254
sinTheta = (DBL *)POV_MALLOC(sizeof(DBL)*255, "Photon Map Info");
cosTheta = (DBL *)POV_MALLOC(sizeof(DBL)*255, "Photon Map Info");
for(i=0; i<255; i++)
{
theta = (double)(i-127)*M_PI/127.0;
sinTheta[i] = sin(theta);
cosTheta[i] = cos(theta);
}
}
SinCosOptimizations::~SinCosOptimizations()
{
if (sinTheta)
POV_FREE(sinTheta);
sinTheta = NULL;
if (cosTheta)
POV_FREE(cosTheta);
cosTheta = NULL;
}
/*****************************************************************************
FUNCTION
AllocatePhoton(PhotonMap *map)
allocates a photon
Photons are allocated in blocks. map->head is a
dynamically-created array of these blocks. The blocks themselves
are allocated as they are needed.
Preconditions:
InitBacktraceEverything was called
Postconditions:
Marks another photon as allocated (and allocates another block of
photons if necessary).
Returns a pointer to the new photon.
This will be the next available photon in array.
******************************************************************************/
Photon* PhotonMap::AllocatePhoton()
{
// mutex would be needed if we were allocating photons into the same map
// from different threads...
// but we have a different map for each thread, so there's no danger there...
//mutex::scoped_lock lock(allocatePhotonMutex);
int i,j,k;
// array mapping funciton
// !!!!!!!!!!! warning
// This code does the same function as the macro PHOTON_AMF
// It is done here separatly instead of using the macro for
// speed reasons (to avoid duplicate operations). If the
// macro is changed, this MUST ALSO BE CHANGED!
i=(this->numPhotons & PHOTON_BLOCK_MASK);
j=(this->numPhotons >> (PHOTON_BLOCK_POWER));
// new photon
this->numPhotons++;
if(j == this->numBlocks)
{
// the base array is too small, we need to reallocate it
Photon **newMap;
newMap = (Photon **)POV_MALLOC(sizeof(Photon *)*this->numBlocks*2, "photons");
this->numBlocks*=2;
// copy entries
for(k=0; khead[k];
// set new entries to zero
for(k=j; knumBlocks; k++)
newMap[k] = NULL;
// free old map and put the new map in place
POV_FREE(this->head);
this->head = newMap;
}
if(this->head[j] == NULL)
// allocate a new block of photons
this->head[j] = (Photon *)POV_MALLOC(sizeof(Photon)*PHOTON_BLOCK_SIZE, "photons");
return &(this->head[j][i]);
}
/*
Merge the parameter photon map into this photon map.
"Delete" the contents of the parameter photon map after
the merge is complete.
*/
void PhotonMap::mergeMap(PhotonMap* map)
{
int thisi = ((this->numPhotons) & PHOTON_BLOCK_MASK);
int thisj = ((this->numPhotons) >> (PHOTON_BLOCK_POWER));
int mapi = ((map->numPhotons) & (PHOTON_BLOCK_MASK));
int mapj = ((map->numPhotons) >> (PHOTON_BLOCK_POWER));
int blocksNeeded = thisj+mapj+2;
// increase block count if necessary
if(blocksNeeded > this->numBlocks)
{
// the base array is too small, we need to reallocate it
Photon **newMap;
newMap = (Photon **)POV_MALLOC(sizeof(Photon *)*blocksNeeded, "photons");
this->numBlocks = blocksNeeded;
int k;
// copy entries
for(k=0; k<=thisj; k++)
newMap[k] = this->head[k];
// set new entries to zero
for(k=thisj+1; knumBlocks; k++)
newMap[k] = NULL;
// free old map and put the new map in place
POV_FREE(this->head);
this->head = newMap;
}
// backup the pointer to the last block.
Photon* lastBlock = this->head[thisj];
int j;
for(j=0; jhead[thisj] = map->head[j];
thisj++;
}
this->head[thisj] = lastBlock;
if(map->head[mapj]!=NULL)
{
if(this->head[thisj]==NULL)
this->head[thisj] = (Photon *)POV_MALLOC(sizeof(Photon)*PHOTON_BLOCK_SIZE, "photons");
int i;
for(i=0; thisihead[thisj][thisi] = map->head[mapj][i];
}
// continue in a new block if necessary
if(ihead[thisj] = (Photon *)POV_MALLOC(sizeof(Photon)*PHOTON_BLOCK_SIZE, "photons");
thisi=0;
for(/*nothing*/;ihead[thisj][thisi] = map->head[mapj][i];
}
}
POV_FREE(map->head[mapj]);
}
// pretented that the passed-in map is already freed, so that we don't
// de-allocate the memory when it is deconstructued (since we're using
// its blocks in this map)
map->head = NULL;
this->numPhotons = this->numPhotons + map->numPhotons;
}
/*****************************************************************************
FUNCTION
InitPhotonMemory()
Initializes photon memory.
Must only be called by InitBacktraceEverything().
******************************************************************************/
/*****************************************************************************
FUNCTION
FreePhotonMemory()
Frees all allocated blocks and the base array.
Must be called only by FreeBacktraceEverything()
******************************************************************************/
PhotonMap::~PhotonMap()
{
int j;
// if already freed then stop now
if (head==NULL)
return;
// free all non-NULL arrays
for(j=0; jArea_Size1);
Jitter_v = (int)(FRAND()*Light->Area_Size2);
*/
Jitter_u = area_x; //+(0.5*FRAND() - 0.25);
Jitter_v = area_y; //+(0.5*FRAND() - 0.25);
if (light->Area_Size1 > 1)
{
ScaleFactor = Jitter_u/(DBL)(light->Area_Size1 - 1) - 0.5;
VScale (NewAxis1, light->Axis1, ScaleFactor);
}
else
{
Make_Vector(NewAxis1, 0.0, 0.0, 0.0);
}
if (light->Area_Size2 > 1)
{
ScaleFactor = Jitter_v/(DBL)(light->Area_Size2 - 1) - 0.5;
VScale (NewAxis2, light->Axis2, ScaleFactor);
}
else
{
Make_Vector(NewAxis2, 0.0, 0.0, 0.0);
}
// need a new toctr & left
VAddEq(ray.Origin, NewAxis1);
VAddEq(ray.Origin, NewAxis2);
VSub(toctr, ctr, ray.Origin);
VLength(dist, toctr);
VNormalizeEq(toctr);
if ( fabs(fabs(toctr[Z])- 1.) < .1 )
{
// too close to vertical for comfort, so use cross product with horizon
up[X] = 0.; up[Y] = 1.; up[Z] = 0.;
}
else
{
up[X] = 0.; up[Y] = 0.; up[Z] = 1.;
}
VCross(left, toctr, up); VNormalizeEq(left);
if (fabs(dist)BBox.Lower_Left[X] + target->BBox.Lengths[X] / 2.0;
ctr[Y] = target->BBox.Lower_Left[Y] + target->BBox.Lengths[Y] / 2.0;
ctr[Z] = target->BBox.Lower_Left[Z] + target->BBox.Lengths[Z] / 2.0;
VSub(v, ctr,target->BBox.Lower_Left);
VLength(rad, v);
// find direction from object to bounding sphere
VSub(toctr, ctr, light->Center);
VLength(dist, toctr);
VNormalizeEq(toctr);
if ( fabs(fabs(toctr[Z])- 1.) < .1 )
{
// too close to vertical for comfort, so use cross product with horizon
up[X] = 0.; up[Y] = 1.; up[Z] = 0.;
}
else
{
up[X] = 0.; up[Y] = 0.; up[Z] = 1.;
}
// find "left", which is vector perpendicular to toctr
if(light->Parallel)
{
// for parallel lights, left is actually perpendicular to the direction of the
// light source
VCross(left, light->Direction, up); VNormalizeEq(left);
}
else
{
VCross(left, toctr, up); VNormalizeEq(left);
}
/*
light dist ctr
* ------------- +
--__ /
--__ / rad
--
*/
}
/* ====================================================================== */
/* ====================================================================== */
/* KD - TREE */
/* ====================================================================== */
/* ====================================================================== */
/*****************************************************************************
FUNCTION
swapPhotons
swaps two photons
Precondition:
photon memory initialized
'a' and 'b' are indexes within the range of photons in the map
(NO ERROR CHECKING IS DONE)
Postconditions:
the photons indexed by 'a' and 'b' are swapped
*****************************************************************************/
void PhotonMap::swapPhotons(int a, int b)
{
int ai,aj,bi,bj;
Photon tmp;
// !!!!!!!!!!! warning
// This code does the same function as the macro PHOTON_AMF
// It is done here separatly instead of using the macro for
// speed reasons (to avoid duplicate operations). If the
// macro is changed, this MUST ALSO BE CHANGED!
ai = a & PHOTON_BLOCK_MASK;
aj = a >> PHOTON_BLOCK_POWER;
bi = b & PHOTON_BLOCK_MASK;
bj = b >> PHOTON_BLOCK_POWER;
tmp = this->head[aj][ai];
this->head[aj][ai] = this->head[bj][bi];
this->head[bj][bi] = tmp;
}
/*****************************************************************************
FUNCTION
insertSort
(modified from Data Structures textbook)
Preconditions:
photon memory initialized
'start' is the index of the first photon
'end' is the index of the last photon
'd' is the dimension to sort on (X, Y, or Z)
Postconditions:
photons from 'start' to 'end' in the map are sorted in
ascending order on dimension d
******************************************************************************/
void PhotonMap::insertSort(int start, int end, int d)
{
int j,k;
Photon tmp;
for(k=end-1; k>=start; k--)
{
j=k+1;
tmp = PHOTON_AMF(this->head, k);
while ( (tmp.Loc[d] > PHOTON_AMF(this->head,j).Loc[d]) )
{
PHOTON_AMF(this->head,j-1) = PHOTON_AMF(this->head,j);
j++;
if (j>end) break;
}
PHOTON_AMF(this->head,j-1) = tmp;
}
}
/*****************************************************************************
FUNCTION
quickSortRec
(modified from Data Structures textbook)
Recursive part of the quicksort routine
This does not sort all the way. once this is done, insertSort
should be called to finish the sorting process!
Preconditions:
photon memory initialized
'left' is the index of the first photon
'right' is the index of the last photon
'd' is the dimension to sort on (X, Y, or Z)
Postconditions:
photons from 'left' to 'right' in the map are MOSTLY sorted in
ascending order on dimension d
******************************************************************************/
void PhotonMap::quickSortRec(int left, int right, int d)
{
int j,k;
if(left>1), left+1);
if(PHOTON_AMF(this->head,left+1).Loc[d] > PHOTON_AMF(this->head,right).Loc[d])
swapPhotons(left+1,right);
if(PHOTON_AMF(this->head,left).Loc[d] > PHOTON_AMF(this->head,right).Loc[d])
swapPhotons(left,right);
if(PHOTON_AMF(this->head,left+1).Loc[d] > PHOTON_AMF(this->head,left).Loc[d])
swapPhotons(left+1,left);
j=left+1; k=right;
while(j<=k)
{
for(j++; ((j<=right)&&(PHOTON_AMF(this->head,j).Loc[d]head,left).Loc[d])); j++) { }
for(k--; ((k>=left)&&(PHOTON_AMF(this->head,k).Loc[d]>PHOTON_AMF(this->head,left).Loc[d])); k--) { }
if(j 10)
{
quickSortRec(left,k-1,d);
}
if(right-k > 10)
{
quickSortRec(k+1,right,d);
}
// leave the rest for insertSort
}
}
/*****************************************************************************
FUNCTION
halfSortRec
(modified quicksort algorithm)
Recursive part of the quicksort routine, but it only does half
the quicksort. It only recurses one branch - the branch that contains
the midpoint (median).
Preconditions:
photon memory initialized
'left' is the index of the first photon
'right' is the index of the last photon
'd' is the dimension to sort on (X, Y, or Z)
'mid' is the index where the median will end up
Postconditions:
the photon at the midpoint (mid) is the median of the photons
when sorted on dimention d.
******************************************************************************/
void PhotonMap::halfSortRec(int left, int right, int d, int mid)
{
int j,k;
if(left>1), left+1);
if(PHOTON_AMF(this->head,left+1).Loc[d] > PHOTON_AMF(this->head,right).Loc[d])
swapPhotons(left+1,right);
if(PHOTON_AMF(this->head,left).Loc[d] > PHOTON_AMF(this->head,right).Loc[d])
swapPhotons(left,right);
if(PHOTON_AMF(this->head,left+1).Loc[d] > PHOTON_AMF(this->head,left).Loc[d])
swapPhotons(left+1,left);
j=left+1; k=right;
while(j<=k)
{
for(j++; ((j<=right)&&(PHOTON_AMF(this->head,j).Loc[d]head,left).Loc[d])); j++) { }
for(k--; ((k>=left)&&(PHOTON_AMF(this->head,k).Loc[d]>PHOTON_AMF(this->head,left).Loc[d])); k--) { }
if(j 0 && (mid>=left) && (mid 0 && (mid>k) && (mid<=right))
{
halfSortRec(k+1,right,d,mid);
}
}
}
/*****************************************************************************
FUNCTION
sortAndSubdivide
Finds the dimension with the greates range, sorts the photons on that
dimension. Then it recurses on the left and right halves (keeping
the median photon as a pivot). This produces a balanced kd-tree.
Preconditions:
photon memory initialized
'start' is the index of the first photon
'end' is the index of the last photon
'sorted' is the dimension that was last sorted (so we don't sort again)
Postconditions:
photons from 'start' to 'end' in the map are in a valid kd-tree format
******************************************************************************/
void PhotonMap::sortAndSubdivide(int start, int end, int /*sorted*/)
{
int i,j; // counters
SNGL_VECT min,max; // min/max vectors for finding range
int DimToUse; // which dimesion has the greatest range
int mid; // index of median (middle)
int len; // length of the array we're sorting
if (end==start)
{
PHOTON_AMF(this->head, start).info = 0;
return;
}
if(endhead,i));
if (ph->Loc[j] < min[j])
min[j]=ph->Loc[j];
if (ph->Loc[j] > max[j])
max[j]=ph->Loc[j];
}
}
// choose which dimension to use
DimToUse = X;
if((max[Y]-min[Y])>(max[DimToUse]-min[DimToUse]))
DimToUse=Y;
if((max[Z]-min[Z])>(max[DimToUse]-min[DimToUse]))
DimToUse=Z;
// find midpoint
mid = (end+start)>>1;
// use half of a quicksort to find the median
len = end-start;
if (len>=2)
{
// only display status every so often
if(len > 1000)
{
//gPhotonStat_end = end;
// Send_ProgressUpdate(PROGRESS_SORTING_PHOTONS);
}
halfSortRec(start, end, DimToUse, mid);
//don't do this - but why? quickSortRec(start, end, DimToUse);
}
// set DimToUse for the midpoint
PHOTON_AMF(this->head, mid).info = DimToUse;
// now recurse to continue building the kd-tree
sortAndSubdivide(start, mid - 1, DimToUse);
sortAndSubdivide(mid + 1, end, DimToUse);
}
/*****************************************************************************
FUNCTION
buildTree
Builds the kd-tree by calling sortAndSubdivide().
Preconditions:
photon memory initialized
'map' is a pointer to a photon map containing an array of unsorted
photons
Postconditions:
photons are in a valid kd-tree format
******************************************************************************/
void PhotonMap::buildTree()
{
// Send_Progress("Sorting photons", PROGRESS_SORTING_PHOTONS);
sortAndSubdivide(0,this->numPhotons-1,X+Y+Z/*this is not X, Y, or Z*/);
}
/*****************************************************************************
FUNCTION
setGatherOptions
determines gather options
Preconditions:
photon memory initialized
'map' points to an already-built (and sorted) photon map
'mediaMap' is true if 'map' contians media photons, and false if
'map' contains surface photons
Postconditions:
gather gather options are set for this map
******************************************************************************/
void PhotonMap::setGatherOptions(ScenePhotonSettings &photonSettings, int mediaMap)
{
DBL r;
DBL density;
VECTOR Point;
int numToSample;
int n,i,j;
DBL mind,maxd,avgd;
DBL sum,sum2;
DBL saveDensity;
//int greaterThan;
int lessThan;
// if user did not set minimum gather radius,
// then we should calculate it
if (this->minGatherRad <= 0.0)
{
// TODO FIXME - lots of magic numbers here!
mind=10000000.0;
maxd=avgd=sum=sum2=0.0;
// use 5% of photons, min 100, max 10000
numToSample = this->numPhotons/20;
if (numToSample>1000) numToSample = 1000;
if (numToSample<100) numToSample = 100;
for(i=0; inumPhotons;
Assign_Vector(Point,(PHOTON_AMF(this->head, j)).Loc);
// TODO FIXME (this allocates then frees memory each time around the loop)
PhotonGatherer gatherer(this, photonSettings);
n=gatherer.gatherPhotons(Point, 10000000.0, &r, NULL, false);
// sometimes, if we gather all (100) photons at a single point, the radius will
// be zero, which will lead to an infinite density, which will mess up all the
// calculations. For now (until a better fix is found), just IGNORE any sample
// points that have a zero radius. This does throw off the averaging calculation
// a little bit, because this density counts as zero... but that's OK... we can
// live with that.... actually, we can use the current average
if(r>0.0)
{
if(mediaMap)
density = 3.0 * n / (4.0*M_PI*r*r*r); // should that be 4/3?
else
density = n / (M_PI*r*r);
}
else
{
//povwin::WIN32_DEBUG_FILE_OUTPUT("FOUND ZERO RADIUS AT: %lf, %lf, %lf\n",Point[0],Point[1],Point[2]);
// somehow we ended up with infinite density at this point... that CAN'T be
// right, so we'll use the current average as the density for this point.
// that should be SAFE.
density = sum / i;
}
//povwin::WIN32_DEBUG_FILE_OUTPUT("num, rad, density: %d, %lf, %lf\n",n,r,density);
if (density>maxd) maxd=density;
if (densitynumPhotons;
Assign_Vector(Point,(PHOTON_AMF(this->head, j)).Loc);
n=gatherPhotons(Point, 10000000.0, &r, NULL, false, map);
if(mediaMap)
density = 3.0 * n / (4.0*M_PI*r*r*r); // should that be 4/3?
else
density = n / (M_PI*r*r);
if (density>saveDensity)
greaterThan++;
}
density = saveDensity * (DBL)greaterThan / numToSample;
*/
density = saveDensity;
if(mediaMap)
// TODO FIXME - what's that 0.3333 back there? Is that supposed to be 1/3?
this->minGatherRad = pow(3.0 * photonSettings.maxGatherCount / (density*M_PI*4.0), 0.3333);
else
this->minGatherRad = sqrt(photonSettings.maxGatherCount / (density*M_PI));
//povwin::WIN32_DEBUG_FILE_OUTPUT("photonSettings.maxGatherCount %d\n",photonSettings.maxGatherCount);
//povwin::WIN32_DEBUG_FILE_OUTPUT("this->minGatherRad %lf\n",this->minGatherRad);
lessThan = 0;
for(i=0; inumPhotons;
Assign_Vector(Point,(PHOTON_AMF(this->head, j)).Loc);
PhotonGatherer gatherer(this, photonSettings);
n=gatherer.gatherPhotons(Point, this->minGatherRad, &r, NULL, false);
if(r>0)
{
if(mediaMap)
density = 3.0 * n / (4.0*M_PI*r*r*r); // should that be 4/3?
else
density = n / (M_PI*r*r);
// count as "lessThan" if the density is below 70% of the average,
// and if it is at least above 5% of the average.
if (density<(saveDensity*0.7) && density>(saveDensity*0.05))
lessThan++;
}
else
{
//povwin::WIN32_DEBUG_FILE_OUTPUT("FOUND ZERO RADIUS AT: %lf, %lf, %lf\n",Point[0],Point[1],Point[2]);
}
}
//povwin::WIN32_DEBUG_FILE_OUTPUT("lessThan %d\n",lessThan);
//povwin::WIN32_DEBUG_FILE_OUTPUT("numToSample %d\n",numToSample);
//povwin::WIN32_DEBUG_FILE_OUTPUT("this->minGatherRad %lf\n",this->minGatherRad);
// the 30.0 is a bit of a fudge-factor.
this->minGatherRad*=(1.0+20.0*((DBL)lessThan/(numToSample)));
//povwin::WIN32_DEBUG_FILE_OUTPUT("this->minGatherRad %lf\n",this->minGatherRad);
}
// Now apply the user-defined multiplier, so that the user can tell
// POV to take shortcuts which will improve speed at the expensive of
// quality. Alternatively, the user could tell POV to use a bigger
// radius to improve quality.
this->minGatherRad *= this->minGatherRadMult;
//povwin::WIN32_DEBUG_FILE_OUTPUT("this->minGatherRad %lf\n",this->minGatherRad);
if(mediaMap)
{
// double the radius if it is a media map
// TODO CLARIFY - why?
this->minGatherRad *= 2;
}
// always do this! - use 6 times the area
this->gatherRadStep = this->minGatherRad*2;
// somehow we could automatically determine the number of steps
}
/**************************************************************
=========== PRIORITY QUEUES ===============
Each priority stores its data in the static variables below (such as
numfound_s) and in the global variables
Preconditions:
static DBL size_sq_s; - maximum radius given squared
static DBL Size_s; - radius
static DBL dmax_s; - square of radius used so far
static int TargetNum_s; - target number
static DBL *pt_s; - center point
static numfound_s; - number of photons in priority queue
these must be allocated:
renderer->sceneData->photonSettings.photonGatherList - array of photons in priority queue
renderer->sceneData->photonSettings.photonDistances - corresponding priorities(distances)
*Each priority queue has the following functions:
function PQInsert(Photon *photon, DBL d)
Inserts 'photon' into the priority queue with priority (distance
from target point) 'd'.
void PQDelMax()
Removes the photon with the greates distance (highest priority)
from the queue.
********************************************************************/
// try different priority queues
#define ORDERED 0
#define UNORDERED 1
#define HEAP 2
#define PRI_QUE HEAP
// -------------- ordered list implementation -----------------
#if (PRI_QUE == ORDERED)
static void PQInsert(Photon *photon, DBL d)
{
int i,j;
GetViewDataPtr()->Stats()[Priority_Queue_Add]++;
// save this in order, remove maximum, save new dmax_s
// store in array and shift - assumption is that we don't have
// to shift often
for (i=0; GetSceneData()->photonSettings.photonDistances[i]i; j--)
{
GetSceneData()->photonSettings.photonGatherList[j] = GetSceneData()->photonSettings.photonGatherList[j-1];
GetSceneData()->photonSettings.photonDistances[j] = GetSceneData()->photonSettings.photonDistances[j-1];
}
gatheredPhotons.numFound++;
GetSceneData()->photonSettings.photonGatherList[i] = photon;
GetSceneData()->photonSettings.photonDistances[i] = d;
if (gatheredPhotons.numFound==TargetNum_s)
dmax_s=GetSceneData()->photonSettings.photonDistances[gatheredPhotons.numFound-1];
}
static void PQDelMax()
{
GetViewDataPtr()->Stats()[Priority_Queue_Remove]++;
gatheredPhotons.numFound--;
}
#endif
// -------------- unordered list implementation -----------------
#if (PRI_QUE == UNORDERED)
static void PQInsert(Photon *photon, DBL d)
{
GetViewDataPtr()->Stats()[Priority_Queue_Add]++;
GetSceneData()->photonSettings.photonGatherList[gatheredPhotons.numFound] = photon;
GetSceneData()->photonSettings.photonDistances[gatheredPhotons.numFound] = d;
if (d>dmax_s)
dmax_s=d;
gatheredPhotons.numFound++;
}
static void PQDelMax()
{
int i,max;
GetViewDataPtr()->Stats()[Priority_Queue_Remove]++;
max=0;
// find max
for(i=1; iphotonSettings.photonDistances[i]>GetSceneData()->photonSettings.photonDistances[max]) max = i;
// remove it, shifting the photons
for(i=max+1; iphotonSettings.photonGatherList[i-1] = GetSceneData()->photonSettings.photonGatherList[i];
GetSceneData()->photonSettings.photonDistances[i-1] = GetSceneData()->photonSettings.photonDistances[i];
}
gatheredPhotons.numFound--;
// find a new dmax_s
dmax_s=GetSceneData()->photonSettings.photonDistances[0];
for(i=1; iphotonSettings.photonDistances[i]>dmax_s) dmax_s = GetSceneData()->photonSettings.photonDistances[i];
}
#endif
// -------------- heap implementation -----------------
// this is from Sejwick (spelling?)
#if (PRI_QUE == HEAP)
void PhotonGatherer::PQInsert(Photon *photon, DBL d)
{
// TODO FIXME STATS threadData->Stats()[Priority_Queue_Add]++;
DBL* Distances = gatheredPhotons.photonDistances;
Photon** Photons = gatheredPhotons.photonGatherList;
unsigned int k = ++gatheredPhotons.numFound;
while (k > 1)
{
unsigned int half_k = k / 2;
DBL d_half_k_m1 = Distances[half_k - 1];
if (d < d_half_k_m1)
break;
Distances [k - 1] = d_half_k_m1;
Photons[k - 1] = Photons[half_k - 1];
k = half_k;
}
Distances [k - 1] = d;
Photons[k - 1] = photon;
}
void PhotonGatherer::FullPQInsert(Photon *photon, DBL d)
{
// TODO FIXME STATS threadData->Stats()[Priority_Queue_Remove]++;
DBL* Distances = gatheredPhotons.photonDistances;
Photon** Photons = gatheredPhotons.photonGatherList;
int k = 1, k2 = 2;
for (; k2 < gatheredPhotons.numFound; k = k2, k2 += k)
{
DBL d_k2_m1 = Distances[k2 - 1],
d_k2 = Distances[k2];
if (d_k2 > d_k2_m1)
{
d_k2_m1 = d_k2;
++k2;
}
if (!(d_k2_m1 > d))
break;
Distances [k - 1] = d_k2_m1;
Photons[k - 1] = Photons[k2 - 1];
}
if (k2 == gatheredPhotons.numFound) {
DBL d_k2_m1 = Distances[k2 - 1];
if (d_k2_m1 > d) {
Distances [k - 1] = d_k2_m1;
Photons[k - 1] = Photons[k2 - 1];
k = k2;
}
}
Distances [k - 1] = d;
Photons[k - 1] = photon;
// find a new dmax_s
dmax_s = Distances[0];
}
#endif
/*****************************************************************************
FUNCTION
gatherPhotonsRec()
Recursive part of gatherPhotons
Searches the kd-tree with range start..end (midpoint is pivot)
Preconditions:
same preconditions as priority queue functions
static variable map_s points to the map to use
'start' is the first photon in this range
'end' is the last photon in this range
the range 'start..end' must have been used in building photon map!!!
Postconditions:
photons within the range of start..end are added to the priority
queue (photons may be delted from the queue to make room for photons
of lower priority)
******************************************************************************/
void PhotonGatherer::gatherPhotonsRec(int start, int end)
{
DBL delta;
int DimToUse;
DBL d,dx,dy,dz;
int mid;
Photon *photon;
VECTOR ptToPhoton;
DBL discFix; // use disc(ellipsoid) for gathering instead of sphere
// find midpoint
mid = (end+start)>>1;
photon = &(PHOTON_AMF(map->head, mid));
DimToUse = photon->info;// & PH_MASK_XYZ;
// check this photon
// find distance from pt
ptToPhoton[X] = - pt_s[X] + photon->Loc[X];
ptToPhoton[Y] = - pt_s[Y] + photon->Loc[Y];
ptToPhoton[Z] = - pt_s[Z] + photon->Loc[Z];
// all distances are squared
dx = ptToPhoton[X]*ptToPhoton[X];
dy = ptToPhoton[Y]*ptToPhoton[Y];
dz = ptToPhoton[Z]*ptToPhoton[Z];
if (!( ((dx>dmax_s) && ((DimToUse)==X)) ||
((dy>dmax_s) && ((DimToUse)==Y)) ||
((dz>dmax_s) && ((DimToUse)==Z)) ))
{
// it fits manhatten distance - maybe we can use this photon
// find euclidian distance (squared)
d = dx + dy + dz;
// now fix this distance so that we gather using an ellipsoid
// alligned with the surface normal instead of a sphere. This
// minimizes false bleeding of photons at sharp corners
// dmax_s is square of radius of major axis
// dmax_s/16 is " " " " minor " (1/6 of major axis)
/*
VDot(discFix,norm_s,ptToPhoton);
discFix*=discFix*(dmax_s/1000.0-dmax_s); // TODO FIXME - magic number
*/
if (flattenFactor!=0.0)
{
VDot(discFix,norm_s,ptToPhoton);
discFix = fabs(discFix);
d += flattenFactor*(discFix)*d*16;
}
// this will add zero if on the plane, and will double distance from
// point to photon if it is ptToPhoton is perpendicular to the surface
if(d < dmax_s)
{
if (gatheredPhotons.numFound+1>TargetNum_s)
{
FullPQInsert(photon, d);
sqrt_dmax_s = sqrt(dmax_s);
}
else
PQInsert(photon, d);
}
}
// now go left & right if appropriate - if going left or right goes out
// the current range, then don't go that way.
/*
delta=pt_s[DimToUse]-photon->Loc[DimToUse];
if(delta<0)
{
if (end>=mid+1) gatherPhotonsRec(start, mid - 1);
if (delta*delta < dmax_s )
if (mid-1>=start) gatherPhotonsRec(mid + 1, end);
}
else
{
if (mid-1>=start) gatherPhotonsRec(mid+1,end);
if (delta*delta < dmax_s )
if (end>=mid+1) gatherPhotonsRec(start, mid - 1);
}
*/
delta=pt_s[DimToUse]-photon->Loc[DimToUse];
if(delta<0)
{
// on left - go left first
if (pt_s[DimToUse]-sqrt_dmax_s < photon->Loc[DimToUse])
{
if (mid-1>=start)
gatherPhotonsRec(start, mid - 1);
}
if (pt_s[DimToUse]+sqrt_dmax_s > photon->Loc[DimToUse])
{
if(end>=mid+1)
gatherPhotonsRec(mid + 1, end);
}
}
else
{
// on right - go right first
if (pt_s[DimToUse]+sqrt_dmax_s > photon->Loc[DimToUse])
{
if(end>=mid+1)
gatherPhotonsRec(mid + 1, end);
}
if (pt_s[DimToUse]-sqrt_dmax_s < photon->Loc[DimToUse])
{
if (mid-1>=start)
gatherPhotonsRec(start, mid - 1);
}
}
}
/*****************************************************************************
FUNCTION
gatherPhotons()
gathers photons from the global photon map
Preconditons:
renderer->sceneData->photonSettings.photonGatherList and renderer->sceneData->photonSettings.photonDistances
are allocated and are each maxGatherCount in length
'Size' - maximum search radius
'r' points to a double
BuildPhotonMaps() has been called for this scene.
Postconditions:
*r is radius actually used for gathereing (maximum value is 'Size')
renderer->sceneData->photonSettings.photonGatherList and renderer->sceneData->photonSettings.photonDistances
contain the gathered photons
returns number of photons gathered
******************************************************************************/
int PhotonGatherer::gatherPhotons(const VECTOR pt, DBL Size, DBL *r, const VECTOR norm, bool flatten)
{
if (map->numPhotons<=0) return 0; // no crashes, please...
// set the static variables
gatheredPhotons.numFound=0;
size_sq_s = Size*Size;
dmax_s = size_sq_s;
sqrt_dmax_s = Size;
norm_s = norm;
if(flatten)
{
flattenFactor = 1.0;
}
else
{
flattenFactor = 0.0;
}
Size_s = Size;
TargetNum_s = photonSettings.maxGatherCount;
pt_s = pt;
// now search the kd-tree recursively
gatherPhotonsRec(0, map->numPhotons-1);
// set the radius variable
*r = sqrt_dmax_s;
if(gatheredPhotons.numFound>0)
{
gatheredPhotons.numFound++;
gatheredPhotons.numFound--;
}
// return the number of photons found
return(gatheredPhotons.numFound);
}
PhotonGatherer::PhotonGatherer(PhotonMap *map, ScenePhotonSettings& photonSettings): map(map),photonSettings(photonSettings),gatheredPhotons(photonSettings.maxGatherCount)
{
gathered = false;
}
DBL PhotonGatherer::gatherPhotonsAdaptive(const VECTOR pt, const VECTOR norm, bool flatten)
{
// set up initial radius
int num;
DBL radius;
DBL Size = map->minGatherRad;
DBL prevDensity, thisDensity;
bool expanded = false;
// first try at gathering
num=gatherPhotons(pt, Size, &radius, norm, flatten);
prevDensity = thisDensity = num / (radius*radius);
if (prevDensity==0)
// TODO FIXME - Magic Value
prevDensity = 0.0000000000000001; // avoid div-by-zero error
// start looping if necessary
int step=1;
while(numgatherNumSteps)
{
step++;
DBL tempr = 0;
int tempn;
// save out the current set in case we want to revert
GatheredPhotons savedGatheredPhotons(photonSettings.maxGatherCount);
savedGatheredPhotons.swapWith(this->gatheredPhotons);
// increase the size
Size+=map->gatherRadStep;
// gather again, with the new size
tempn=gatherPhotons(pt, Size, &tempr, norm, flatten);
// compute the density of this search
thisDensity = tempn / (tempr*tempr);
/*
this next line handles the adaptive search
if
the density change ((thisDensity-prevDensity)/prevDensity) is small enough
or
this is the first time through (step==0)
or
the number gathered is less than photonSettings.minExpandCount and greater than zero
then
use the color from this new gathering step and discard any previous
color
This adaptive search is explained my paper "Simulating Reflective and Refractive
Caustics in POV-Ray Using a Photon Map" - May, 1999
*/
if(((thisDensity-prevDensity)/prevDensity < photonSettings.expandTolerance)
|| (step==0)
|| (tempn0))
{
// it passes the tests, so use the new color
expanded = true;
// save variables in case we loop again
prevDensity = thisDensity;
if (prevDensity==0)
// TODO FIXME - Magic Value
prevDensity = 0.0000000000000001; // avoid div-by-zero error
// keep
radius = tempr;
num = tempn;
}
else
{
// put the old gathered photons back
savedGatheredPhotons.swapWith(this->gatheredPhotons);
// we're done - break out of the loop
break;
}
}
// TODO FIXME STATS
//if (expanded)
//threadData->Stats()[Gather_Expanded_Count]++;
gathered = true;
alreadyGatheredRadius = radius;
return radius;
}
/******************************************************************
stuff grabbed from radiosit.h & radiosit.c
******************************************************************/
extern BYTE_XYZ rad_samples[];
/******************************************************************
******************************************************************/
void ChooseRay(Ray &NewRay, const VECTOR Normal, const Ray & /*ray*/, const VECTOR Raw_Normal, int WhichRay)
{
Vector3d random_vec;
VECTOR up, n2, n3;
int i;
DBL NRay_Direction;
#define REFLECT_FOR_RADIANCE 0
#if (REFLECT_FOR_RADIANCE)
// Get direction of reflected ray.
DBL n = -2.0 * (ray->Direction[X] * Normal[X] + ray->Direction[Y] * Normal[Y] + ray->Direction[Z] * Normal[Z]);
VLinComb2(NewRay->Direction, n, Normal, 1.0, ray->Direction);
VDot(NRay_Direction, NewRay->Direction, Raw_Normal);
if (NRay_Direction < 0.0)
{
// subtract 2*(projection of NRay.Direction onto Raw_Normal)
// from NRay.Direction
DBL Proj;
Proj = NRay_Direction * -2;
VAddScaledEq(NewRay.Direction, Proj, Raw_Normal);
}
return;
#else
Assign_Vector(NewRay.Direction, Normal);
#endif
if ( fabs(fabs(NewRay.Direction[Z])- 1.) < .1 )
{
// too close to vertical for comfort, so use cross product with horizon
up[X] = 0.; up[Y] = 1.; up[Z] = 0.;
}
else
{
up[X] = 0.; up[Y] = 0.; up[Z] = 1.;
}
VCross(n2, NewRay.Direction, up); VNormalizeEq(n2);
VCross(n3, NewRay.Direction, n2); VNormalizeEq(n3);
// TODO FIXME - Magic Numbers
//i = (int)(FRAND()*1600);
i = WhichRay;
WhichRay = (WhichRay + 1) % 1600;
VUnpack(random_vec, &rad_samples[i]);
if ( fabs(NewRay.Direction[Y] - 1.) < .001 ) // pretty well straight Y, folks
{
// we are within 1/20 degree of pointing in the Y axis.
// use all vectors as is--they're precomputed this way
Assign_Vector(NewRay.Direction, *random_vec);
}
else
{
NewRay.Direction[X] = n2[X]*random_vec[X] + NewRay.Direction[X]*random_vec[Y] + n3[X]*random_vec[Z];
NewRay.Direction[Y] = n2[Y]*random_vec[X] + NewRay.Direction[Y]*random_vec[Y] + n3[Y]*random_vec[Z];
NewRay.Direction[Z] = n2[Z]*random_vec[X] + NewRay.Direction[Z]*random_vec[Y] + n3[Z]*random_vec[Z];
}
// if our new ray goes through, flip it back across raw_normal
VDot(NRay_Direction, NewRay.Direction, Raw_Normal);
if (NRay_Direction < 0.0)
{
// subtract 2*(projection of NRay.Direction onto Raw_Normal)
// from NRay.Direction
DBL Proj;
Proj = NRay_Direction * -2;
VAddScaledEq(NewRay.Direction, Proj, Raw_Normal);
}
VNormalizeEq(NewRay.Direction);
}
#if(0)
int GetPhotonStat(POVMSType a)
{
switch(a)
{
case kPOVAttrib_ObjectPhotonCount:
return gPhotonStat_i;
case kPOVAttrib_TotalPhotonCount:
return surfacePhotonMap.numPhotons;
case kPOVAttrib_MediaPhotonCount:
return mediaPhotonMap.numPhotons;
case kPOVAttrib_PhotonXSamples:
return gPhotonStat_x_samples;
case kPOVAttrib_PhotonYSamples:
return gPhotonStat_y_samples;
case kPOVAttrib_CurrentPhotonCount:
return gPhotonStat_end;
}
return 0;
}
#endif
void GatheredPhotons::swapWith(GatheredPhotons& toCopy)
{
Photon** tmpPhotonGatherList = photonGatherList;
DBL* tmpPhotonDistances = photonDistances;
int tmpNumFound = numFound;
photonGatherList = toCopy.photonGatherList;
photonDistances = toCopy.photonDistances;
numFound = toCopy.numFound;
toCopy.photonGatherList = tmpPhotonGatherList;
toCopy.photonDistances = tmpPhotonDistances;
toCopy.numFound = tmpNumFound;
}
GatheredPhotons::GatheredPhotons(int maxGatherCount)
{
numFound = 0;
photonGatherList = (Photon**)POV_MALLOC(sizeof(Photon *)*maxGatherCount, "Photon Map Info");
photonDistances = (DBL *)POV_MALLOC(sizeof(DBL)*maxGatherCount, "Photon Map Info");
}
GatheredPhotons::~GatheredPhotons()
{
if(photonGatherList)
POV_FREE(photonGatherList);
photonGatherList = NULL;
if(photonDistances)
POV_FREE(photonDistances);
photonDistances = NULL;
}
int LightTargetCombo::computeMergedFlags()
{
if(target)
{
return light->Flags | target->Flags;
}
else
{
return light->Flags = PH_RFR_ON_FLAG | PH_RFL_ON_FLAG; //light->Flags;
}
}
void LightTargetCombo::computeAnglesAndDeltas(shared_ptr sceneData)
{
shootingDirection.compute();
// calculate the spacial separation (spread)
photonSpread = target->Ph_Density*sceneData->photonSettings.surfaceSeparation;
// if rays aren't parallel, divide by dist so we get separation at a distance of 1 unit
if (!light->Parallel)
{
photonSpread /= shootingDirection.dist;
}
// adjust spread if we are using an area light
if(light->Area_Light && light->Photon_Area_Light)
{
photonSpread *= sqrt((DBL)(light->Area_Size1*light->Area_Size2));
}
// set the photon density - calculate angular density from spacial
if(light->Parallel)
{
// OK, here we fake things a bit for parallel lights. Theta is not really theta.
// It really represents the radius... but why re-code an entire loop. For POV 4.0
// this should be re-written as an abstract class with polymorphism.
dtheta = photonSpread;
}
else
{
// calculate delta theta
dtheta = atan(photonSpread);
}
// if photonSpread <= 0.0, we must return or we'll get stuck in an infinite loop!
if (photonSpread <= 0.0)
return;
mintheta = 0;
if (light->Parallel)
{
maxtheta = shootingDirection.rad;
}
else if (shootingDirection.dist>=shootingDirection.rad)
{
maxtheta = asin(shootingDirection.rad/shootingDirection.dist);
}
else
{
maxtheta = M_PI;
if (fabs(shootingDirection.dist)