home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
OS/2 Shareware BBS: Graphics
/
Graphics.zip
/
povsrc31.zip
/
photons.c
< prev
next >
Wrap
C/C++ Source or Header
|
2000-10-16
|
72KB
|
2,381 lines
/*******************************************************
File: photons.c
Author: Nathan Kopp
This is part of the Photon Mapping custom version of
POV-Ray.
********************************************************/
#include "frame.h"
#include "povray.h"
#include "vector.h"
#include "povproto.h"
#include "texture.h" /* for FRAND() */
#include "parse.h" /* for Warn() */
#include "matrices.h"
#include "objects.h"
#include "csg.h"
#include "photons.h"
#include "ray.h"
#define BLACK_LEVEL 0.003
/* ------------------------------------------------------ */
/* global variables */
/* ------------------------------------------------------ */
int backtraceFlag;
PHOTON_OPTIONS photonOptions;
int InitBacktraceWasCalled;
/* ------------------------------------------------------ */
/* external variables */
/* ------------------------------------------------------ */
extern int Trace_Level;
#ifdef MotionBlurPatch
extern int TimeStamp;
#endif
/* ------------------------------------------------------ */
/* static functions */
/* ------------------------------------------------------ */
static PHOTON* AllocatePhoton(PHOTON_MAP *map);
static void FreePhotonMemory();
static void InitPhotonMemory();
static void sortAndSubdivide(int start, int end, int sorted);
static void buildTree(PHOTON_MAP *map);
static void ShootPhotonsAtObject(OBJECT *Object, LIGHT_SOURCE *Light, int count);
static void SearchThroughObjects(OBJECT *Object, LIGHT_SOURCE *Light, int count);
static int savePhotonMap(void);
static int loadPhotonMap(void);
static void swapPhotons(int a, int b);
static void PQInsert(PHOTON *photon, DBL d);
static void PQDelMax(void);
static void gatherPhotonsRec(int start, int end);
static void setGatherOptions(PHOTON_MAP *map, int mediaMap);
/* ------------------------------------------------------ */
/* static variables */
/* ------------------------------------------------------ */
/*
These static variables are used to conserve stack space during
extensive recursion when gathering photons. All of these static
variables end in "_s".
*/
static PHOTON **map_s; /* photon map */
static DBL size_sq_s; /* search radius squared */
static DBL Size_s; /* search radius (static) */
static DBL dmax_s; /* dynamic search radius... current maximum */
static int TargetNum_s; /* how many to gather */
static DBL *pt_s; /* point around which we are gathering */
static int numfound_s; /* number of photons found */
static DBL *norm_s; /* surface normal */
static DBL flattenFactor; /* amount to flatten the spher to make it */
/* an ellipsoid when gathering photons */
/* zero = no flatten, one = regular */
static DBL photonCountEstimate;
/*****************************************************************************
FUNCTION
CheckPassThru()
Checks to make sure that pass-through, high-density, and refraction
are not simultaneously selected. If all three are turned on, we need
to choose an appropriate one to turn off.
Preconditions:
'o' is an initialized object
'flag' is PH_FLAG_PASSTHRU, ph_flag_target, or PH_FLAG_RFR_ON
(this is which flag was set most recently)
Postconditions:
One of these flags in 'o' is turned off, since they cannot all be turned on.
******************************************************************************/
void CheckPassThru(OBJECT *o, int flag)
{
if( (o->Ph_Flags & PH_FLAG_PASSTHRU) &&
(o->Ph_Flags & PH_FLAG_TARGET) &&
!(o->Ph_Flags & PH_FLAG_RFR_OFF) )
{
switch (flag)
{
case PH_FLAG_PASSTHRU:
Warning(0, "Cannot use pass through with refraction & high density photons.\nTurning off refraction.\n");
SET_PH_FLAG(o, PH_FLAG_RFR_OFF, PH_FLAG_RFR_ON);
break;
case PH_FLAG_TARGET:
if(o->Ph_Flags & PH_FLAG_RFR_ON)
{
Warning(0, "Cannot use pass through with refraction & high density photons.\nTurning off pass through.\n");
o->Ph_Flags &= ~PH_FLAG_PASSTHRU;
}
else
{
Warning(0, "Cannot use pass through with refraction & high density photons.\nTurning off pass refraction.\n");
SET_PH_FLAG(o, PH_FLAG_RFR_OFF, PH_FLAG_RFR_ON);
}
break;
case PH_FLAG_RFR_ON:
Warning(0, "Cannot use pass through with refraction & high density photons.\nTurning off pass through.\n");
o->Ph_Flags &= ~PH_FLAG_PASSTHRU;
break;
}
}
}
/*****************************************************************************
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 photonOptions.photonsEnabled is true, then
memory for photon mapping is allocated.
else
nothing is done
******************************************************************************/
void InitBacktraceEverything()
{
int i;
double theta;
if (photonOptions.photonsEnabled)
{
InitBacktraceWasCalled = TRUE;
photonOptions.photonMap.head = NULL;
photonOptions.photonMap.numPhotons = 0;
photonOptions.photonMap.numBlocks = 0;
photonOptions.globalPhotonMap.head = NULL;
photonOptions.globalPhotonMap.numPhotons = 0;
photonOptions.globalPhotonMap.numBlocks = 0;
photonOptions.mediaPhotonMap.head = NULL;
photonOptions.mediaPhotonMap.numPhotons = 0;
photonOptions.mediaPhotonMap.numBlocks = 0;
photonOptions.photonGatherList = (PHOTON**)POV_MALLOC(sizeof(PHOTON *)*photonOptions.maxGatherCount, "Photon Map Info");
photonOptions.photonDistances = (DBL *)POV_MALLOC(sizeof(DBL)*photonOptions.maxGatherCount, "Photon Map Info");
InitPhotonMemory();
/* create the sin/cos arrays for speed */
/* range is -127..+127 => 0..254 */
photonOptions.sinTheta = (DBL *)POV_MALLOC(sizeof(DBL)*255, "Photon Map Info");
photonOptions.cosTheta = (DBL *)POV_MALLOC(sizeof(DBL)*255, "Photon Map Info");
for(i=0; i<255; i++)
{
theta = (double)(i-127)*M_PI/127.0;
photonOptions.sinTheta[i] = sin(theta);
photonOptions.cosTheta[i] = cos(theta);
}
}
}
/* savePhotonMap()
Saves the caustic photon map to a file.
Preconditions:
InitBacktraceEverything was called
the photon map has been built and balanced
photonOptions.fileName contains the filename to save
Postconditions:
Returns 1 if success, 0 if failure.
If success, the photon map has been written to the file.
*/
static int savePhotonMap()
{
PHOTON *ph;
FILE *f;
int i, err;
f = fopen(photonOptions.fileName, WRITE_BINFILE_STRING);
if (!f) return 0;
/* caustic photons */
fwrite(&photonOptions.photonMap.numPhotons, sizeof(photonOptions.photonMap.numPhotons),1,f);
fwrite(&photonOptions.photonMap.rangeSelector, sizeof(photonOptions.photonMap.rangeSelector),1,f);
if (photonOptions.photonMap.numPhotons>0 && photonOptions.photonMap.head)
{
for(i=0; i<photonOptions.photonMap.numPhotons; i++)
{
ph = &(PHOTON_AMF(photonOptions.photonMap.head, i));
err = fwrite(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fwrite returned an error! */
fclose(f);
return 0;
}
}
}
/* global photons */
fwrite(&photonOptions.globalPhotonMap.numPhotons, sizeof(photonOptions.globalPhotonMap.numPhotons),1,f);
fwrite(&photonOptions.globalPhotonMap.rangeSelector, sizeof(photonOptions.globalPhotonMap.rangeSelector),1,f);
if (photonOptions.globalPhotonMap.numPhotons>0 && photonOptions.globalPhotonMap.head)
{
for(i=0; i<photonOptions.globalPhotonMap.numPhotons; i++)
{
ph = &(PHOTON_AMF(photonOptions.globalPhotonMap.head, i));
err = fwrite(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fwrite returned an error! */
fclose(f);
return 0;
}
}
}
/* media photons */
fwrite(&photonOptions.mediaPhotonMap.numPhotons, sizeof(photonOptions.mediaPhotonMap.numPhotons),1,f);
fwrite(&photonOptions.mediaPhotonMap.rangeSelector, sizeof(photonOptions.mediaPhotonMap.rangeSelector),1,f);
if (photonOptions.mediaPhotonMap.numPhotons>0 && photonOptions.mediaPhotonMap.head)
{
for(i=0; i<photonOptions.mediaPhotonMap.numPhotons; i++)
{
ph = &(PHOTON_AMF(photonOptions.mediaPhotonMap.head, i));
err = fwrite(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fwrite returned an error! */
fclose(f);
return 0;
}
}
}
fclose(f);
return TRUE;
}
/* loadPhotonMap()
Loads the caustic photon map from a file.
Preconditions:
InitBacktraceEverything was called
the photon map is empty
photonOptions.fileName contains the filename to load
Postconditions:
Returns 1 if success, 0 if failure.
If success, the photon map has been loaded from the file.
If failure then the render should stop with an error
*/
static int loadPhotonMap()
{
int i;
int err;
PHOTON *ph;
FILE *f;
int numph;
if (!photonOptions.photonsEnabled) return 0;
f = fopen(photonOptions.fileName, READ_BINFILE_STRING);
if (!f) return 0;
fread(&numph, sizeof(numph),1,f);
fread(&photonOptions.photonMap.rangeSelector, sizeof(photonOptions.photonMap.rangeSelector),1,f);
for(i=0; i<numph; i++)
{
ph = AllocatePhoton(&photonOptions.photonMap);
err = fread(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fread returned an error! */
fclose(f);
return 0;
}
}
if (!feof(f)) /* for backwards file format compatibility */
{
/* global photons */
if (fread(&numph, sizeof(numph),1,f) > 0)
{
fread(&photonOptions.globalPhotonMap.rangeSelector, sizeof(photonOptions.globalPhotonMap.rangeSelector),1,f);
for(i=0; i<numph; i++)
{
ph = AllocatePhoton(&photonOptions.globalPhotonMap);
err = fread(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fread returned an error! */
fclose(f);
return 0;
}
}
/* media photons */
fread(&numph, sizeof(numph),1,f);
fread(&photonOptions.mediaPhotonMap.rangeSelector, sizeof(photonOptions.mediaPhotonMap.rangeSelector),1,f);
for(i=0; i<numph; i++)
{
ph = AllocatePhoton(&photonOptions.mediaPhotonMap);
err = fread(ph, sizeof(PHOTON), 1, f);
if (err<=0)
{
/* fread returned an error! */
fclose(f);
return 0;
}
}
}
}
fclose(f);
return TRUE;
}
/*****************************************************************************
FUNCTION
FreeBacktraceEverything()
Preconditions:
if photonOptions.photonsEnabled is true, then InitBacktraceEverything()
must have been called
PostConditions:
if photonOptions.photonsEnabled is true, then
photon memory is freed
sets photonOptions.photonsEnabled to false
else
does nothing
******************************************************************************/
void FreeBacktraceEverything()
{
if (!InitBacktraceWasCalled) return;
if (photonOptions.photonsEnabled)
{
/* free everything that we allocated */
if(photonOptions.photonGatherList)
POV_FREE(photonOptions.photonGatherList);
photonOptions.photonGatherList = NULL;
if(photonOptions.photonDistances)
POV_FREE(photonOptions.photonDistances);
photonOptions.photonDistances = NULL;
if (photonOptions.sinTheta)
POV_FREE(photonOptions.sinTheta);
photonOptions.sinTheta = NULL;
if (photonOptions.cosTheta)
POV_FREE(photonOptions.cosTheta);
photonOptions.cosTheta = NULL;
FreePhotonMemory();
photonOptions.photonsEnabled = FALSE;
}
}
/*****************************************************************************
FUNCTION
AllocatePhoton(PHOTON_MAP *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* AllocatePhoton(PHOTON_MAP *map)
{
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=(map->numPhotons & PHOTON_BLOCK_MASK);
j=(map->numPhotons >> (PHOTON_BLOCK_POWER));
/* new photon */
map->numPhotons++;
if(j == map->numBlocks)
{
/* the base array is too small, we need to reallocate it */
PHOTON **newMap;
newMap = (PHOTON **)POV_MALLOC(sizeof(PHOTON *)*map->numBlocks*2, "photons");
map->numBlocks*=2;
/* copy entries */
for(k=0; k<j; k++)
newMap[k] = map->head[k];
/* set new entries to zero */
for(k=j; k<map->numBlocks; k++)
newMap[k] = NULL;
/* free old map and put the new map in place */
POV_FREE(map->head);
map->head = newMap;
}
if(map->head[j] == NULL)
/* allocate a new block of photons */
map->head[j] = (PHOTON *)POV_MALLOC(sizeof(PHOTON)*PHOTON_BLOCK_SIZE, "photons");
return &(map->head[j][i]);
}
/*****************************************************************************
FUNCTION
InitPhotonMemory()
Initializes photon memory.
Must only be called by InitBacktraceEverything().
******************************************************************************/
static void InitPhotonMemory()
{
int k;
/* allocate the base array */
photonOptions.photonMap.numPhotons = 0;
photonOptions.photonMap.numBlocks = INITIAL_BASE_ARRAY_SIZE;
photonOptions.photonMap.head = (PHOTON_BLOCK *)POV_MALLOC(sizeof(PHOTON_BLOCK *)*INITIAL_BASE_ARRAY_SIZE, "photons");
/* zero the array */
for(k=0; k<photonOptions.photonMap.numBlocks; k++)
photonOptions.photonMap.head[k] = NULL;
/* ------------ global photons ----------------*/
/* allocate the base array */
photonOptions.globalPhotonMap.numPhotons = 0;
photonOptions.globalPhotonMap.numBlocks = INITIAL_BASE_ARRAY_SIZE;
photonOptions.globalPhotonMap.head = (PHOTON_BLOCK *)POV_MALLOC(sizeof(PHOTON_BLOCK *)*INITIAL_BASE_ARRAY_SIZE, "photons");
/* zero the array */
for(k=0; k<photonOptions.globalPhotonMap.numBlocks; k++)
photonOptions.globalPhotonMap.head[k] = NULL;
/* ------------ media photons ----------------*/
/* allocate the base array */
photonOptions.mediaPhotonMap.numPhotons = 0;
photonOptions.mediaPhotonMap.numBlocks = INITIAL_BASE_ARRAY_SIZE;
photonOptions.mediaPhotonMap.head = (PHOTON_BLOCK *)POV_MALLOC(sizeof(PHOTON_BLOCK *)*INITIAL_BASE_ARRAY_SIZE, "photons");
/* zero the array */
for(k=0; k<photonOptions.mediaPhotonMap.numBlocks; k++)
photonOptions.mediaPhotonMap.head[k] = NULL;
}
/*****************************************************************************
FUNCTION
FreePhotonMemory()
Frees all allocated blocks and the base array.
Must be called only by FreeBacktraceEverything()
******************************************************************************/
static void FreePhotonMemory()
{
int j;
/* if already freed then stop now */
if (photonOptions.photonMap.head==NULL)
return;
/*YS sept 17 2000 Memory leak */
if ( photonOptions.fileName) /* file name to load or save caustic photon map */
{
POV_FREE(photonOptions.fileName);
photonOptions.fileName=NULL;
}
/* free all non-NULL arrays */
for(j=0; j<photonOptions.photonMap.numBlocks; j++)
{
if(photonOptions.photonMap.head[j] != NULL)
{
POV_FREE(photonOptions.photonMap.head[j]);
}
}
/* free the base array */
POV_FREE(photonOptions.photonMap.head);
photonOptions.photonMap.head = NULL;
/* ---------------- global photons -------------- */
/* if already freed then stop now */
if (photonOptions.globalPhotonMap.head==NULL)
return;
/* free all non-NULL arrays */
for(j=0; j<photonOptions.globalPhotonMap.numBlocks; j++)
{
if(photonOptions.globalPhotonMap.head[j] != NULL)
{
POV_FREE(photonOptions.globalPhotonMap.head[j]);
}
}
/* free the base array */
POV_FREE(photonOptions.globalPhotonMap.head);
photonOptions.globalPhotonMap.head = NULL;
/* ---------------- media photons -------------- */
/* if already freed then stop now */
if (photonOptions.mediaPhotonMap.head==NULL)
return;
/* free all non-NULL arrays */
for(j=0; j<photonOptions.mediaPhotonMap.numBlocks; j++)
{
if(photonOptions.mediaPhotonMap.head[j] != NULL)
{
POV_FREE(photonOptions.mediaPhotonMap.head[j]);
}
}
/* free the base array */
POV_FREE(photonOptions.mediaPhotonMap.head);
photonOptions.mediaPhotonMap.head = NULL;
}
/*****************************************************************************
*
* FUNCTION
*
* cubic_spline (copied from point.c for use with light attenuation )
*
* INPUT
*
* OUTPUT
*
* RETURNS
*
* AUTHOR
*
* POV-Ray Team
*
* DESCRIPTION
*
* Cubic spline that has tangents of slope 0 at x == low and at x == high.
* For a given value "pos" between low and high the spline value is returned.
*
* CHANGES
*
* -
*
******************************************************************************/
static DBL cubic_spline(DBL low, DBL high, DBL pos)
{
/* Check to see if the position is within the proper boundaries. */
if (pos < low)
return(0.0);
else
{
if (pos >= high)
return(1.0);
}
/* Normalize to the interval [0...1]. */
pos = (pos - low) / (high - low);
/* See where it is on the cubic curve. */
return(3 - 2 * pos) * pos * pos;
}
/*****************************************************************************
FUNCTION
SearchThroughObjects()
Searches through 'object' and all siblings and children of 'object' to
locate objects with PH_FLAG_TARGET set. This flag means that the object
receives photons.
Preconditions:
Photon mapping initialized (InitBacktraceEverything() called)
'Object' is a object (with or without siblings)
'Light' is a light source in the scene
Postconditions:
Photons may have been shot at the object, its siblings, or its children.
******************************************************************************/
static void SearchThroughObjects(OBJECT *Object, LIGHT_SOURCE *Light, int count)
{
OBJECT *Sib;
/* check this object and all siblings */
for (Sib = Object; Sib != NULL; Sib = Sib -> Sibling)
{
if ((Sib->Ph_Flags & PH_FLAG_TARGET) &&
!(Sib->Type & LIGHT_SOURCE_OBJECT))
{
ShootPhotonsAtObject(Sib, Light, count);
}
else
{
/* if it has children, check them too */
#ifdef MotionBlurPatch
if ((Sib->Type & COMPOUND_OBJECT) &&
!(Sib->Type & MOTION_BLUR_OBJECT))
#else
if ((Sib->Type & COMPOUND_OBJECT) )
#endif
{
SearchThroughObjects(((CSG *)Sib)->Children, Light, count);
}
}
}
}
/*****************************************************************************
FUNCTION
ShootPhotonsAtObject()
Shoots photons from 'Light' to 'Object'
Preconditions:
Photon mapping initialized (InitBacktraceEverything() called)
'Object' is a object with PH_FLAG_TARGET set
'Light' is a light source in the scene
Possible future expansion:
Object==NULL means create a global photon map.
Postconditions:
Photons may have been shot at the object, depending on a variety
of flags
******************************************************************************/
static void ShootPhotonsAtObject(OBJECT *Object, LIGHT_SOURCE *Light, int count)
{
RAY Ray; /* ray that we shoot */
COLOUR Colour, PhotonColour; /* light color and photon color */
int i; /* counter */
DBL theta, phi; /* rotation angles */
DBL dtheta, dphi; /* deltas for theta and phi */
DBL jittheta, jitphi; /* jittered versions of theta and phi */
DBL mintheta,maxtheta,minphi,maxphi;
/* these are minimum and maximum for theta and
phi for the spiral shooting */
DBL dist; /* distance from light to bounding sphere */
DBL rad; /* radius of bounding sphere */
DBL st,ct; /* cos(theta) & sin(theta) for rotation */
DBL Attenuation; /* light attenuation for spotlight */
DBL costheta_spot;
VECTOR up, left, ctr, toctr, v; /* vectors to determine direction of shot */
TRANSFORM Trans; /* transformation for rotation */
int mergedFlags=0; /* merged flags to see if we should shoot photons */
int notComputed=TRUE; /* have the ray containers been computed for this point yet?*/
int hitAtLeastOnce = FALSE; /* have we hit the object at least once - for autostop stuff */
/* get the light source colour */
Assign_Colour(Colour, Light->Colour);
/* set global variable stuff */
photonOptions.Light = Light;
photonOptions.photonObject = Object;
/* first, check on various flags... make sure all is a go for this object */
if(Object)
{
mergedFlags = Light->Ph_Flags | photonOptions.photonObject->Ph_Flags;
photonOptions.lightFlags = Light->Ph_Flags;
}
else
{
mergedFlags = photonOptions.lightFlags = PH_FLAG_RFR_ON | PH_FLAG_RFL_ON; /*Light->Ph_Flags;*/
}
if (!(((mergedFlags & PH_FLAG_RFR_ON) && !(mergedFlags & PH_FLAG_RFR_OFF)) ||
((mergedFlags & PH_FLAG_RFL_ON) && !(mergedFlags & PH_FLAG_RFL_OFF))))
/* it is a no-go for this object... bail out now */
return;
if(Object)
{
/* find bounding sphere based on bounding box */
ctr[X] = photonOptions.photonObject->BBox.Lower_Left[X] + photonOptions.photonObject->BBox.Lengths[X] / 2.0;
ctr[Y] = photonOptions.photonObject->BBox.Lower_Left[Y] + photonOptions.photonObject->BBox.Lengths[Y] / 2.0;
ctr[Z] = photonOptions.photonObject->BBox.Lower_Left[Z] + photonOptions.photonObject->BBox.Lengths[Z] / 2.0;
VSub(v, ctr,photonOptions.photonObject->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.;
}
VCross(left, toctr, up); VNormalizeEq(left);
/* VCross(up, toctr, left); VNormalizeEq(up); not needed */
/*
light dist ctr
* ------------------ +
---___ |
---___ | rad
---_|
*/
mintheta = 0;
if (dist>=rad)
maxtheta = atan(rad/dist);
else
{
maxtheta = M_PI;
if (fabs(dist)<EPSILON)
{
Make_Vector(up, 1,0,0);
Make_Vector(left, 0,1,0);
Make_Vector(toctr, 0,0,1);
}
dist = rad;
}
/* set the photon density - calculate angular density from spacial */
photonOptions.photonSpread = photonOptions.photonObject->Ph_Density*photonOptions.surfaceSeparation / dist;
if (count)
{
/* try to guess the number of photons */
DBL x=rad / (photonOptions.photonObject->Ph_Density*photonOptions.surfaceSeparation);
x=x*x*M_PI;
#if(1)
if ( ((mergedFlags & PH_FLAG_RFR_ON) && !(mergedFlags & PH_FLAG_RFR_OFF)) &&
((mergedFlags & PH_FLAG_RFL_ON) && !(mergedFlags & PH_FLAG_RFL_OFF)) )
{
x *= 1.5; /* assume 2 times as many photons with both reflection & refraction */
}
if ( !(photonOptions.photonObject->Ph_Flags & PH_FLAG_IGNORE_PHOTONS) )
{
if ( ((mergedFlags & PH_FLAG_RFR_ON) && !(mergedFlags & PH_FLAG_RFR_OFF)) )
{
if ( ((mergedFlags & PH_FLAG_RFL_ON) && !(mergedFlags & PH_FLAG_RFL_OFF)) )
x *= 3; /* assume 3 times as many photons if ignore_photons not used */
else
x *= 2; /* assume less for only refraction */
}
}
x *= 0.5; /* assume 1/2 of photons hit target object */
#endif
photonCountEstimate += x;
return;
}
}
else
{
/* set up for global photon map */
mintheta = 0;
maxtheta = M_PI;
/* determine photonSpread from a number? */
photonOptions.photonSpread = Light->Ph_Density*photonOptions.globalSeparation;
Make_Vector(up, 1,0,0);
Make_Vector(left, 0,1,0);
Make_Vector(toctr, 0,0,1);
dist = 1.0;
if (count)
{
/* try to guess the number of photons */
photonCountEstimate += M_PI*4.0/(photonOptions.photonSpread*photonOptions.photonSpread);
return;
}
}
if(Light->Area_Light && Light->Photon_Area_Light)
{
/* multiply spread if we are using an area light */
photonOptions.photonSpread *= sqrt(Light->Area_Size1*Light->Area_Size2);
}
/* calculate delta theta */
dtheta = atan(photonOptions.photonSpread);
/* if photonSpread <= 0.0, we must return or we'll get stuck in an infinite loop! */
if (photonOptions.photonSpread <= 0.0)
return;
/* ---------------------------------------------
main ray-shooting loop
--------------------------------------------- */
i = 0;
notComputed = TRUE;
for(theta=mintheta; theta<maxtheta; theta+=dtheta)
{
photonOptions.hitObject = FALSE;
if (theta<EPSILON)
dphi=2*M_PI;
else
dphi=dtheta/sin(theta);
minphi = -M_PI + dphi*FRAND()*0.5;
maxphi = M_PI - dphi/2 + (minphi+M_PI);
for(phi=minphi; phi<maxphi; phi+=dphi)
{
int x_samples,y_samples;
int area_x, area_y;
/* ------------------- shoot one photon ------------------ */
/* jitter theta & phi */
jitphi = phi + (dphi)*(FRAND() - 0.5)*1.0*photonOptions.jitter;
jittheta = theta + (dtheta)*(FRAND() - 0.5)*1.0*photonOptions.jitter;
/* actually, shoot multiple samples for area light */
if(Light->Area_Light && Light->Photon_Area_Light)
{
x_samples = Light->Area_Size1;
y_samples = Light->Area_Size2;
}
else
{
x_samples = 1;
y_samples = 1;
}
for(area_x=0; area_x<x_samples; area_x++)
for(area_y=0; area_y<y_samples; area_y++)
{
Assign_Vector(Ray.Initial,Light->Center);
if (Light->Area_Light)
{
/* ------------- area light ----------- */
/* we need to make new up, left, and toctr vectors so we can
do proper rotations of theta and phi about toctr. The
ray's initial point and ending points are both jittered to
produce the area-light effect. */
DBL Jitter_u, Jitter_v, ScaleFactor;
VECTOR NewAxis1, NewAxis2;
/* we must recompute the media containers (new start point) */
notComputed = TRUE;
/*
Jitter_u = (int)(FRAND()*Light->Area_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.Initial, NewAxis1);
VAddEq(Ray.Initial, NewAxis2);
VSub(toctr, ctr, Ray.Initial);
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)<EPSILON)
{
Make_Vector(up, 1,0,0);
Make_Vector(left, 0,1,0);
Make_Vector(toctr, 0,0,1);
}
}
/* rotate toctr by theta around up */
st = sin(jittheta);
ct = cos(jittheta);
/* use fast rotation */
v[X] = -st*left[X] + ct*toctr[X];
v[Y] = -st*left[Y] + ct*toctr[Y];
v[Z] = -st*left[Z] + ct*toctr[Z];
/* then rotate by phi around toctr */
/* use POV funcitons... slower but easy */
Compute_Axis_Rotation_Transform(&Trans,toctr,jitphi);
MTransPoint(Ray.Direction, v, &Trans);
/* ------ attenuation for spot/cylinder (copied from point.c) ---- */
Attenuation = 1.0;
VDot(costheta_spot, Ray.Direction, Light->Direction);
/*costheta_spot *= -1.0; - not needed?*/
/* ---------- spot light --------- */
if (Light->Light_Type == SPOT_SOURCE)
{
if (costheta_spot > 0.0)
{
Attenuation = pow(costheta_spot, Light->Coeff);
if (Light->Radius > 0.0)
Attenuation *= cubic_spline(Light->Falloff, Light->Radius, costheta_spot);
}
else
Attenuation = 0.0;
}
/* ---------- cylinder light ----------- */
else if (Light->Light_Type == CYLINDER_SOURCE)
{
VECTOR P;
DBL k, len;
VDot(k, Ray.Direction, Light->Direction);
if (k > 0.0)
{
VLinComb2(P, 1.0, Ray.Direction, -k, Light->Direction);
VLength(len, P);
if (len < Light->Falloff)
{
len = 1.0 - len / Light->Falloff;
Attenuation = pow(len, Light->Coeff);
if (Light->Radius > 0.0)
Attenuation *= cubic_spline(1.0 - Light->Radius / Light->Falloff, 1.0, len);
}
else
Attenuation = 0.0;
}
else
Attenuation = 0.0;
}
/* set up defaults for reflection, refraction */
photonOptions.passThruPrev = TRUE;
photonOptions.passThruThis = FALSE;
photonOptions.photonDepth = 0.0;
Trace_Level = 1;
Total_Depth = 0.0;
Increase_Counter(stats[Number_Of_Photons_Shot]);
/* attenuate for area light extra samples */
Attenuation/=(x_samples*y_samples);
/* compute photon color from light source & attenuation */
if (Attenuation<BLACK_LEVEL) continue;
PhotonColour[0] = Colour[0]*Attenuation;
PhotonColour[1] = Colour[1]*Attenuation;
PhotonColour[2] = Colour[2]*Attenuation;
PhotonColour[3] = 0.0;
PhotonColour[4] = 0.0;
/* As mike said, "fire photon torpedo!" */
Initialize_Ray_Containers(&Ray);
initialize_ray_container_state(&Ray, notComputed);
notComputed = FALSE;
#ifdef MotionBlurPatch
/*TimeStamp = opts.photonTimeStamp;*/
TimeStamp = (opts.motionBlurCount+1)/2; /* must be non-zero */
#endif
Trace(&Ray, PhotonColour, 1.0, NULL);
/* display here */
i++;
if ((i%100) == 0)
{
#ifndef ProgressOnOneLine
Status_Info ("\rBuilding Photon Maps... this=%d, total=%d, m=%d, area %d, %d : %d", i, photonOptions.photonMap.numPhotons, photonOptions.mediaPhotonMap.numPhotons,x_samples,y_samples,(int)photonOptions.photonMap.rangeSelector);
#else
Status_Info ("\rthis=%5otal=%5d, m=%5d, area %5d, %5d : %5d ", i, photonOptions.photonMap.numPhotons, photonOptions.mediaPhotonMap.numPhotons,x_samples,y_samples,(int)photonOptions.photonMap.rangeSelector);
#endif
}
} /* end of multiple samples */
}
/* if we didn't hit anything and we're past the autostop angle, then
we should stop
as per suggestion from Smellenberg, changed autostop to a percentage
of the object's bounding sphere. */
/* suggested by Pabs, we only use autostop if we have it it once */
if (photonOptions.hitObject) hitAtLeastOnce=TRUE;
if (hitAtLeastOnce && !photonOptions.hitObject && photonOptions.photonObject)
if (theta>photonOptions.autoStopPercent*maxtheta) break;
} /* end of rays loop */
#ifndef ProgressOnOneLine
Status_Info ("\rBuilding Photon Maps... this=%d, total=%d, m=%d : %d", i, photonOptions.photonMap.numPhotons, photonOptions.mediaPhotonMap.numPhotons,(int)photonOptions.photonMap.rangeSelector);
#else
Status_Info ("\rthis=%5otal=%5d, m=%5d : %5d ", i, photonOptions.photonMap.numPhotons, photonOptions.mediaPhotonMap.numPhotons,(int)photonOptions.photonMap.rangeSelector);
#endif
}
/*****************************************************************************
FUNCTION
BuildPhotonMaps()
This is the primary function for building photon maps.
Preconditions:
Photon memory is allocated (InitBacktraceEverything has been called).
The entire scene has been parsed.
The ray-tracer has been completely initialized.
Postconditions:
The photon map is built based on options specified in the scene file.
******************************************************************************/
void BuildPhotonMaps(void)
{
LIGHT_SOURCE *Light; /* light source to use */
int old_mtl; /* saved max_trace_level */
DBL old_adc; /* saved adc_bailout */
/* if not enabled, then return */
if (!photonOptions.photonsEnabled)
return;
/* should we load the photon map instead of building? */
if (photonOptions.fileName && photonOptions.loadFile)
{
/* status bar for user */
Status_Info ("\nLoading Photon Maps...");
if (!loadPhotonMap())
{
Error("Could not load photon map (%s)\n",photonOptions.fileName);
}
/* don't build */
/* but do set photon options automatically */
/* ----------- surface photons ------------- */
if (photonOptions.photonMap.numPhotons>0)
{
setGatherOptions(&photonOptions.photonMap, FALSE);
}
/* ----------- global photons ------------- */
if (photonOptions.globalPhotonMap.numPhotons>0)
{
setGatherOptions(&photonOptions.globalPhotonMap, FALSE);
}
/* ----------- media photons ------------- */
if (photonOptions.mediaPhotonMap.numPhotons>0)
{
setGatherOptions(&photonOptions.mediaPhotonMap, TRUE);
}
return;
}
/* set flag so POV knows we're in backtrace step */
backtraceFlag = 1;
/* status bar for user */
Status_Info ("\nBuilding Photon Maps...");
/* save adc_bailout and max_trace_level */
old_adc = ADC_Bailout;
old_mtl = Max_Trace_Level;
/* use the photon-specific ones if they were specified */
if (photonOptions.Max_Trace_Level>=0)
Max_Trace_Level = photonOptions.Max_Trace_Level;
if (photonOptions.ADC_Bailout>=0)
ADC_Bailout = photonOptions.ADC_Bailout;
/* COUNT THE PHOTONS */
if(photonOptions.surfaceCount>0)
{
DBL factor;
photonCountEstimate = 0.0;
for (Light = Frame.Light_Sources;
Light != NULL;
Light = Light->Next_Light_Source)
if (Light->Light_Type != FILL_LIGHT_SOURCE)
{
SearchThroughObjects(Frame.Objects, Light, TRUE);
}
factor = (DBL)photonCountEstimate/photonOptions.surfaceCount;
factor = sqrt(factor);
photonOptions.surfaceSeparation *= factor;
}
/* COUNT THE GLOBAL PHOTONS */
if(photonOptions.globalCount>0)
{
DBL factor;
photonCountEstimate = 0.0;
for (Light = Frame.Light_Sources;
Light != NULL;
Light = Light->Next_Light_Source)
if (Light->Light_Type != FILL_LIGHT_SOURCE)
{
ShootPhotonsAtObject(NULL, Light, TRUE);
}
factor = (DBL)photonCountEstimate/photonOptions.globalCount;
factor = sqrt(factor);
photonOptions.globalSeparation *= factor;
}
/* loop through light sources */
for (Light = Frame.Light_Sources;
Light != NULL;
Light = Light->Next_Light_Source)
if (Light->Light_Type != FILL_LIGHT_SOURCE)
{
/* give a warning for any parallel light, since they do not
currently function properly */
if (Light->Parallel)
{
Warning(0,"Parallel lights not supported by photons. Using a point source instead.\n");
}
if (Light->Light_Type == CYLINDER_SOURCE)
{
Warning(0,"Cylinder lights do not work perfectly with photons.\n");
}
/* do global lighting here if it is ever implemented */
if ((Light->Ph_Flags & PH_FLAG_TARGET) && (photonOptions.globalCount>0))
ShootPhotonsAtObject(NULL, Light, FALSE);
/* do object-specific lighting */
SearchThroughObjects(Frame.Objects, Light, FALSE);
}
/* clear this flag */
backtraceFlag = 0;
/* restore saved variables */
ADC_Bailout = old_adc;
Max_Trace_Level = old_mtl;
/* now actually build the kd-tree by sorting the array of photons */
if (photonOptions.photonMap.numPhotons>0)
{
buildTree(&photonOptions.photonMap);
setGatherOptions(&photonOptions.photonMap, FALSE);
}
/* ----------- global photons ------------- */
if (photonOptions.globalPhotonMap.numPhotons>0)
{
buildTree(&photonOptions.globalPhotonMap);
setGatherOptions(&photonOptions.globalPhotonMap, FALSE);
}
/* ----------- media photons ------------- */
if (photonOptions.mediaPhotonMap.numPhotons>0)
{
buildTree(&photonOptions.mediaPhotonMap);
setGatherOptions(&photonOptions.mediaPhotonMap, TRUE);
}
if (photonOptions.photonMap.numPhotons+
photonOptions.globalPhotonMap.numPhotons+
photonOptions.mediaPhotonMap.numPhotons > 0)
{
/* should we load the photon map now that it is built? */
if (photonOptions.fileName && !photonOptions.loadFile)
{
/* status bar for user */
Status_Info ("\nSaving Photon Maps...");
if (!savePhotonMap())
{
Warn(0,"Could not save photon map.\n");
}
}
}
else
{
if (photonOptions.fileName && !photonOptions.loadFile)
{
Warn(0,"Could not save photon map - no photons!\n");
}
}
}
/*****************************************************************************
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 photonOptions.photonDepth)
is stored in the photon data structure.
******************************************************************************/
void addSurfacePhoton(VECTOR Point, VECTOR Origin, COLOUR LightCol, VECTOR RawNorm)
{
PHOTON *Photon;
int intensity;
COLOUR LightCol2;
DBL Attenuation;
VECTOR d;
DBL d_len, phi, theta;
PHOTON_MAP *map;
/* first, compensate for POV's weird light attenuation */
if ((photonOptions.Light->Fade_Power > 0.0) && (fabs(photonOptions.Light->Fade_Distance) > EPSILON))
{
Attenuation = 2.0 / (1.0 + pow(photonOptions.photonDepth / photonOptions.Light->Fade_Distance, photonOptions.Light->Fade_Power));
}
else
Attenuation = 1;
VScale(LightCol2, LightCol, Attenuation);
VScaleEq(LightCol2, photonOptions.photonDepth*photonOptions.photonDepth);
VScaleEq(LightCol2, photonOptions.photonSpread*photonOptions.photonSpread);
/* if too dark, maybe we should stop here */
if(photonOptions.photonObject==NULL)
{
map = &photonOptions.globalPhotonMap;
Increase_Counter(stats[Number_Of_Global_Photons_Stored]);
}
else
{
map = &photonOptions.photonMap;
Increase_Counter(stats[Number_Of_Photons_Stored]);
}
/* allocate the photon */
Photon = AllocatePhoton(map);
/* adaptively choose a range selector for RGBI format */
/* only do this if we didn't use it yet */
/* and if user didn't give a range selector */
if (map->rangeSelector == 0)
{
map->rangeSelector = 2.0/max3(LightCol2[0],LightCol2[1],LightCol2[2]);
}
/* convert photon from three floats to two bytes
this is currently stored in RGBI format (designed by Nathan Kopp)
RGBE format (by Greg Ward) could be used instead
*/
intensity = (int)(256.0/(max3(LightCol2[0],LightCol2[1],LightCol2[2])*map->rangeSelector))-1;
if (intensity<0) intensity = 0;
if (intensity>255) intensity = 255;
Photon->Colour.i = intensity;
Photon->Colour.r = (int)(LightCol2[0]*(map->rangeSelector*(intensity+1)));
Photon->Colour.g = (int)(LightCol2[1]*(map->rangeSelector*(intensity+1)));
Photon->Colour.b = (int)(LightCol2[2]*(map->rangeSelector*(intensity+1)));
/* store the location */
Assign_SNGL_Vect(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=(char)(theta*127.0/M_PI);
Photon->phi=(char)(phi*127.0/M_PI);
}
/*****************************************************************************
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 photonOptions.photonDepth)
is stored in the photon data structure.
******************************************************************************/
void addMediaPhoton(VECTOR Point, VECTOR Origin, COLOUR LightCol, DBL depthDiff)
{
PHOTON *Photon;
int intensity;
COLOUR LightCol2;
DBL Attenuation;
VECTOR d;
DBL d_len, phi, theta;
/* first, compensate for POV's weird light attenuation */
if ((photonOptions.Light->Fade_Power > 0.0) && (fabs(photonOptions.Light->Fade_Distance) > EPSILON))
{
Attenuation = 2.0 / (1.0 + pow((photonOptions.photonDepth+depthDiff) / photonOptions.Light->Fade_Distance, photonOptions.Light->Fade_Power));
}
else
Attenuation = 1;
#if 0
VScale(LightCol2, LightCol, photonOptions.photonSpread*photonOptions.photonSpread);
VScaleEq(LightCol2, (photonOptions.photonDepth+depthDiff)*(photonOptions.photonDepth+depthDiff)*Attenuation);
#else
VScale(LightCol2, LightCol, Attenuation);
VScaleEq(LightCol2, (photonOptions.photonDepth+depthDiff) *
(photonOptions.photonDepth+depthDiff));
VScaleEq(LightCol2, photonOptions.photonSpread*photonOptions.photonSpread);
#endif
/* if too dark, maybe we should stop here */
/* allocate the photon */
if(photonOptions.photonObject==NULL) return;
Increase_Counter(stats[Number_Of_Media_Photons_Stored]);
Photon = AllocatePhoton(&photonOptions.mediaPhotonMap);
/* adaptively choose a range selector for RGBI format */
/* only do this if we didn't use it yet */
/* and if user didn't give a range selector */
if (photonOptions.mediaPhotonMap.rangeSelector == 0)
{
photonOptions.mediaPhotonMap.rangeSelector = 2.0/max3(LightCol2[0],LightCol2[1],LightCol2[2]);
}
/* convert photon from three floats to two bytes
this is currently stored in RGBI format (designed by Nathan Kopp)
RGBE format (by Greg Ward) could be used instead
*/
intensity = (int)(256.0/(max3(LightCol2[0],LightCol2[1],LightCol2[2])*photonOptions.mediaPhotonMap.rangeSelector))-1;
if (intensity<0) intensity = 0;
if (intensity>255) intensity = 255;
Photon->Colour.i = intensity;
Photon->Colour.r = (int)(LightCol2[0]*(photonOptions.mediaPhotonMap.rangeSelector*(intensity+1)));
Photon->Colour.g = (int)(LightCol2[1]*(photonOptions.mediaPhotonMap.rangeSelector*(intensity+1)));
Photon->Colour.b = (int)(LightCol2[2]*(photonOptions.mediaPhotonMap.rangeSelector*(intensity+1)));
/* store the location */
Assign_SNGL_Vect(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=(char)(theta*127.0/M_PI);
Photon->phi=(char)(phi*127.0/M_PI);
}
/* ====================================================================== */
/* ====================================================================== */
/* KD - TREE */
/* ====================================================================== */
/* ====================================================================== */
/*****************************************************************************
FUNCTION
swapPhotons
swaps two photons
Precondition:
photon memory initialized
static map_s points to the photon map
'a' and 'b' are indexes within the range of photons in map_s
(NO ERROR CHECKING IS DONE)
Postconditions:
the photons indexed by 'a' and 'b' are swapped
*****************************************************************************/
static void 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 = map_s[aj][ai];
map_s[aj][ai] = map_s[bj][bi];
map_s[bj][bi] = tmp;
}
/*****************************************************************************
FUNCTION
insertSort
(modified from Data Structures textbook)
Preconditions:
photon memory initialized
static map_s points to the photon map
'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 map_s are sorted in
ascending order on dimension d
******************************************************************************/
static void 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(map_s, k);
while ( (tmp.Loc[d] > PHOTON_AMF(map_s,j).Loc[d]) )
{
PHOTON_AMF(map_s,j-1) = PHOTON_AMF(map_s,j);
j++;
if (j>end) break;
}
PHOTON_AMF(map_s,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
static map_s points to the photon map
'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 map_s are MOSTLY sorted in
ascending order on dimension d
******************************************************************************/
static void quickSortRec(int left, int right, int d)
{
int j,k;
if(left<right)
{
swapPhotons(((left+right)>>1), left+1);
if(PHOTON_AMF(map_s,left+1).Loc[d] > PHOTON_AMF(map_s,right).Loc[d])
swapPhotons(left+1,right);
if(PHOTON_AMF(map_s,left).Loc[d] > PHOTON_AMF(map_s,right).Loc[d])
swapPhotons(left,right);
if(PHOTON_AMF(map_s,left+1).Loc[d] > PHOTON_AMF(map_s,left).Loc[d])
swapPhotons(left+1,left);
j=left+1; k=right;
while(j<=k)
{
for(j++; ((j<=right)&&(PHOTON_AMF(map_s,j).Loc[d]<PHOTON_AMF(map_s,left).Loc[d])); j++);
for(k--; ((k>=left)&&(PHOTON_AMF(map_s,k).Loc[d]>PHOTON_AMF(map_s,left).Loc[d])); k--);
if(j<k)
swapPhotons(j,k);
}
swapPhotons(left,k);
if(k-left > 10)
{
quickSortRec(left,k-1,d);
}
if(right-k > 10)
{
quickSortRec(k+1,right,d);
}
/* leave the rest for insertSort */
}
}
/*****************************************************************************
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
static map_s points to the photon map
'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 map_s are in a valid kd-tree format
******************************************************************************/
static void 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(map_s, start).info = 0;
return;
}
if(end<start) return;
/*
* loop and find greatest range
*/
Make_Vector(min, 1/EPSILON, 1/EPSILON, 1/EPSILON);
Make_Vector(max, -1/EPSILON, -1/EPSILON, -1/EPSILON);
for(i=start; i<=end; i++)
{
for(j=X; j<=Z; j++)
{
PHOTON *ph = &(PHOTON_AMF(map_s,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;
if(DimToUse != sorted)
{
/* start with quicksort */
len = end-start;
if (len>=8)
{
/* only display status every so often */
if(len > 1000)
#ifndef ProgressOnOneLine
Status_Info ("\nSorting photons... %d",end);
#else
Status_Info ("\rSorting photons... %8d",end);
#endif
quickSortRec(start,end,DimToUse);
}
/* finish up with quicksort */
insertSort(start,end,DimToUse);
}
/* find midpoint and set DimToUse for it */
mid = (end+start)>>1;
PHOTON_AMF(map_s, 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(). Sets the static
variable map_s.
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
******************************************************************************/
static void buildTree(PHOTON_MAP *map)
{
#ifdef ProgressOnOneLine
Status_Info ("\n");
#endif
map_s = map->head;
sortAndSubdivide(0,map->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
******************************************************************************/
static void setGatherOptions(PHOTON_MAP *map, 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;
/* if user did not set minimum gather radius,
then we should calculate it */
if (map->minGatherRad <= 0.0)
{
mind=10000000.0;
maxd=avgd=sum=sum2=0.0;
/* use 5% of photons, min 100, max 10000 */
numToSample = map->numPhotons/20;
if (numToSample>1000) numToSample = 1000;
if (numToSample<100) numToSample = 100;
for(i=0; i<numToSample; i++)
{
j = rand() % map->numPhotons;
Assign_SNGL_Vect(Point,(PHOTON_AMF(map->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>maxd) maxd=density;
if (density<mind) mind=density;
sum+=density;
sum2+=density*density;
}
avgd = sum/numToSample;
/* try using some standard deviation stuff instead of this */
saveDensity = avgd;
greaterThan = 0;
for(i=0; i<numToSample; i++)
{
j = rand() % map->numPhotons;
Assign_SNGL_Vect(Point,(PHOTON_AMF(map->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;
if(mediaMap)
{
map->minGatherRad = pow(3.0 * photonOptions.maxGatherCount / (density*M_PI*4.0), 0.3333);
}
else
map->minGatherRad = sqrt(photonOptions.maxGatherCount / (density*M_PI));
}
if(mediaMap)
{
/* double the radius if it is a media map */
map->minGatherRad *= 2;
}
/* always do this! */
map->gatherRadStep = map->minGatherRad*1.0;
/* 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:
photonOptions.photonGatherList - array of photons in priority queue
photonOptions.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;
Increase_Counter(stats[Priority_Queue_Insert]);
/* 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; photonOptions.photonDistances[i]<d && i<(numfound_s); i++);
for (j=numfound_s; j>i; j--)
{
photonOptions.photonGatherList[j] = photonOptions.photonGatherList[j-1];
photonOptions.photonDistances[j] = photonOptions.photonDistances[j-1];
}
numfound_s++;
photonOptions.photonGatherList[i] = photon;
photonOptions.photonDistances[i] = d;
if (numfound_s==TargetNum_s)
dmax_s=photonOptions.photonDistances[numfound_s-1];
}
static void PQDelMax()
{
Increase_Counter(stats[Priority_Queue_Remove]);
numfound_s--;
}
#endif
/* -------------- unordered list implementation ----------------- */
#if (PRI_QUE == UNORDERED)
static void PQInsert(PHOTON *photon, DBL d)
{
Increase_Counter(stats[Priority_Queue_Insert]);
photonOptions.photonGatherList[numfound_s] = photon;
photonOptions.photonDistances[numfound_s] = d;
if (d>dmax_s)
dmax_s=d;
numfound_s++;
}
static void PQDelMax()
{
int i,max;
Increase_Counter(stats[Priority_Queue_Remove]);
max=0;
/* find max */
for(i=1; i<numfound_s; i++)
if (photonOptions.photonDistances[i]>photonOptions.photonDistances[max]) max = i;
/* remove it, shifting the photons */
for(i=max+1; i<numfound_s; i++)
{
photonOptions.photonGatherList[i-1] = photonOptions.photonGatherList[i];
photonOptions.photonDistances[i-1] = photonOptions.photonDistances[i];
}
numfound_s--;
/* find a new dmax_s */
dmax_s=photonOptions.photonDistances[0];
for(i=1; i<numfound_s; i++)
if (photonOptions.photonDistances[i]>dmax_s) dmax_s = photonOptions.photonDistances[i];
}
#endif
/* -------------- heap implementation ----------------- */
/* this is from Sejwick (spelling?) */
#if (PRI_QUE == HEAP)
static void fixUp()
{
int k,k2,k3;
DBL d;
PHOTON *ph;
k=numfound_s;
while(k>1 && photonOptions.photonDistances[(k>>1)-1]<photonOptions.photonDistances[k-1])
{
/* exchange k & k/2 */
k2=k-1;
k3=(k>>1)-1;
ph = photonOptions.photonGatherList[k2];
d = photonOptions.photonDistances[k2];
photonOptions.photonGatherList[k2] = photonOptions.photonGatherList[k3];
photonOptions.photonDistances[k2] = photonOptions.photonDistances[k3];
photonOptions.photonGatherList[k3] = ph;
photonOptions.photonDistances[k3] = d;
k=k>>1;
}
}
static void fixDown()
{
DBL d;
PHOTON *ph;
int j,j2;
int k,k2;
k=1;
while (2*k <= numfound_s)
{
j=2*k;
if (j<numfound_s && photonOptions.photonDistances[j-1]<photonOptions.photonDistances[j+1-1]) j++;
k2=k-1;
j2=j-1;
if (!(photonOptions.photonDistances[k2]<photonOptions.photonDistances[j2])) break;
/* exchange k & j */
ph = photonOptions.photonGatherList[k2];
d = photonOptions.photonDistances[k2];
photonOptions.photonGatherList[k2] = photonOptions.photonGatherList[j2];
photonOptions.photonDistances[k2] = photonOptions.photonDistances[j2];
photonOptions.photonGatherList[j2] = ph;
photonOptions.photonDistances[j2] = d;
k=j;
}
}
static void PQInsert(PHOTON *photon, DBL d)
{
Increase_Counter(stats[Priority_Queue_Insert]);
numfound_s++;
photonOptions.photonGatherList[numfound_s-1] = photon;
photonOptions.photonDistances[numfound_s-1] = d;
fixUp();
if (d>dmax_s)
dmax_s=d;
}
static void PQDelMax()
{
DBL d;
PHOTON *ph;
int i;
Increase_Counter(stats[Priority_Queue_Remove]);
/* exchange 1 & numfound_s */
i = numfound_s-1;
ph = photonOptions.photonGatherList[1-1];
d = photonOptions.photonDistances[1-1];
photonOptions.photonGatherList[1-1] = photonOptions.photonGatherList[i];
photonOptions.photonDistances[1-1] = photonOptions.photonDistances[i];
photonOptions.photonGatherList[i] = ph;
photonOptions.photonDistances[i] = d;
numfound_s--;
fixDown();
/* find a new dmax_s */
dmax_s=photonOptions.photonDistances[0];
/*
this is not needed. we know that the biggest is at the top of the heap
for(i=1; i<numfound_s; i++)
if (photonOptions.photonDistances[i]>dmax_s) dmax_s = photonOptions.photonDistances[i];
*/
}
#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)
******************************************************************************/
static void gatherPhotonsRec(int start, int end)
{
DBL sqrt_dmax_s, 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_s, 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);
*/
if (flattenFactor!=0.0)
{
VDot(discFix,norm_s,ptToPhoton);
d += flattenFactor*fabs(discFix);
}
/* 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 (numfound_s+1>TargetNum_s)
{ PQDelMax(); }
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);
}
*/
sqrt_dmax_s = sqrt(dmax_s);
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:
photonOptions.photonGatherList and photonOptions.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')
photonOptions.photonGatherList and photonOptions.photonDistances
contain the gathered photons
returns number of photons gathered
******************************************************************************/
int gatherPhotons(VECTOR pt, DBL Size, DBL *r, VECTOR norm, int flatten, PHOTON_MAP *map)
{
if (map->numPhotons<=0) return 0; /* no crashes, please... */
/* set the static variables */
numfound_s=0;
size_sq_s = Size*Size;
dmax_s = size_sq_s;
norm_s = norm;
if(flatten)
{
flattenFactor = 1.0;
}
else
{
flattenFactor = 0.0;
}
Size_s = Size;
TargetNum_s = photonOptions.maxGatherCount;
pt_s = pt;
map_s = map->head;
/* now search the kd-tree recursively */
gatherPhotonsRec(0, map->numPhotons-1);
/* set the radius variable */
*r = sqrt(dmax_s);
/* return the number of photons found */
return(numfound_s);
}
/******************************************************************
stuff grabbed from radiosit.h & radiosit.c
******************************************************************/
#ifndef MACOS
typedef struct byte_xyz BYTE_XYZ;
struct byte_xyz {
unsigned char x, y, z;
};
#endif
extern BYTE_XYZ rad_samples[];
static void VUnpack(VECTOR dest_vec, BYTE_XYZ * pack_vec)
{
dest_vec[X] = ((double)pack_vec->x * (1./ 255.))*2.-1.;
dest_vec[Y] = ((double)pack_vec->y * (1./ 255.))*2.-1.;
dest_vec[Z] = ((double)pack_vec->z * (1./ 255.));
VNormalizeEq(dest_vec); /* already good to about 1%, but we can do better */
}
/******************************************************************
******************************************************************/
void ChooseRay(RAY *NewRay, VECTOR Normal, RAY *Ray, VECTOR Raw_Normal, int WhichRay)
{
VECTOR random_vec, up, n2, n3;
int i;
DBL /*n,*/ NRay_Direction;
#define REFLECT_FOR_RADIANCE 0
#if (REFLECT_FOR_RADIANCE)
/* Get direction of reflected ray. */
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);
/*i = (int)(FRAND()*1600);*/
i = WhichRay;
WhichRay = (WhichRay + 1) % 1600;
VUnpack(random_vec, &rad_samples[i]);
if ( fabs(NewRay->Direction[Z] - 1.) < .001 ) /* pretty well straight Z, folks */
{
/* we are within 1/20 degree of pointing in the Z 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] + n3[X]*random_vec[Y] + NewRay->Direction[X]*random_vec[Z];
NewRay->Direction[Y] = n2[Y]*random_vec[X] + n3[Y]*random_vec[Y] + NewRay->Direction[Y]*random_vec[Z];
NewRay->Direction[Z] = n2[Z]*random_vec[X] + n3[Z]*random_vec[Y] + NewRay->Direction[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);
}