/* pmap.C */

#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <signal.h>
#include <time.h>

#include "pmap.h"
#include "pmapP.h"
#include "pmapoptions.H"
#include "pmapconfig.H"
#include "pmapimportance.H"

#include "pools.h"
#include "scene.h"	/* contains global scene variables. */
#include "error.h"
#include "options.h"	/* for command line option processing relevant to the PMAP */

#include "vertex.h"
#include "render.h"
#include "ui.h"

#include "raytracing.h"
#include "raycasting.h"

#include "patch.h"
#include "pathnode.H"
#include "bipath.H"
#include "eyesampler.H"
#include "lightsampler.H"
#include "lightdirsampler.H"
#include "bsdfsampler.H"
#include "photonmapsampler.H"
#include "samplertools.H"
#include "raytools.H"
#include "lightlist.H"
#include "screensampler.H"
#include "pixelsampler.H"

#include "screenbuffer.H"

#include "color.h"
#include "rgb.h"

#include "photonmap.H"
#include "importancemap.H"

#include "QMC/nied31.h"

#define PMAP_MISFUNC(a) (a)

PMAPCONFIG pmapconfig;


/* Initializes the rendering methods 'state' structure. Don't forget to
 * update radiance.c to call this routine! */
static void PmapDefaults(void)
{
  pmapstate.Defaults();
}

/* for counting how much CPU time was used for the computations */
static void UpdateCpuSecs(void)
{
  clock_t t;

  t = clock();
  pmapstate.cpu_secs += (float)(t - pmapstate.lastclock)/(float)CLOCKS_PER_SEC;
  pmapstate.lastclock = t;
}

static void wake_up(int)
{
  pmapstate.wake_up = TRUE;

  signal(SIGALRM, wake_up);
  alarm(/*after*/ 1 /*second*/);

  UpdateCpuSecs();
}

static void *CreatePatchData(PATCH *patch)
{
  return patch->radiance_data = NULL;
}

static void PrintPatchData(FILE *out, PATCH *patch)
{
  fprintf(out, "No data\n");
}

static void DestroyPatchData(PATCH *patch)
{
  patch->radiance_data = (void *)NULL;
}

/* static function initializing a patch (it's a good idea to declare static 
 * all your functions that are only needed here). */
static void InitPatch(PATCH *P)
{
  // Nothing to do yet. No patch data in photonmaps
}


/* compute new color for the patch */
static void PatchComputeNewColor(PATCH *patch)
{
  COLOR Rd = PatchAverageNormalAlbedo(patch, BRDF_DIFFUSE_COMPONENT);
  ColorToRGB(Rd, &patch->color);
  PatchComputeVertexColors(patch);
}

static void PatchUpdateColor(PATCH *P)
{
  PatchComputeNewColor(P);
}

static void PatchDefaultColor(PATCH *patch)
{
  COLOR Rd = PatchAverageNormalAlbedo(patch, BRDF_DIFFUSE_COMPONENT);
  ColorToRGB(Rd, &patch->color);
  PatchComputeVertexColors(patch);  
}

static void ChooseSurfaceSampler(CSurfaceSampler **samplerPtr)
{
  if(*samplerPtr)
    delete *samplerPtr;
  
  if(pmapstate.usePhotonMapSampler)
  {
    *samplerPtr = new CPhotonMapSampler;
  }
  else
  {
    *samplerPtr = new CBsdfSampler;    
  }
}

void PmapChooseSurfaceSampler(void)
{
  ChooseSurfaceSampler(&(pmapconfig.eyeConfig.surfaceSampler));  
  ChooseSurfaceSampler(&(pmapconfig.lightConfig.surfaceSampler));  
}


/* Initializes the computations for the current scene (if any). */
static void InitPmap(void)
{
  fprintf(stderr, "Photonmap activated\n");

  /*
  ForAllPatches(P, Patches) {
    InitPatch(P);
    PatchDefaultColor(P);
  } EndForAll;
  */

  pmapstate.lastclock = clock();
  pmapstate.cpu_secs = 0.;
  pmapstate.g_iteration_nr = 0;
  pmapstate.c_iteration_nr = 0;
  pmapstate.i_iteration_nr = 0;
  pmapstate.iteration_nr = 0;
  pmapstate.runstop_nr = 0;
  pmapstate.total_gpaths = 0;
  pmapstate.total_cpaths = 0;
  pmapstate.total_ipaths = 0;
  pmapstate.total_rays = 0;

  if(pmapconfig.screen) delete pmapconfig.screen;
  pmapconfig.screen = new CScreenBuffer;

  // Init samplers

  pmapconfig.lightConfig.ReleaseVars();
  pmapconfig.eyeConfig.ReleaseVars();

  CSamplerConfig *cfg = &pmapconfig.eyeConfig;

  cfg->pointSampler = new CEyeSampler;

  cfg->dirSampler = new CScreenSampler;  // ps;

  ChooseSurfaceSampler(&cfg->surfaceSampler);
  // cfg->surfaceSampler = new CPhotonMapSampler;
  // cfg->surfaceSampler = new CBsdfSampler;
  cfg->surfaceSampler->SetComputeFromNextPdf(false);
  cfg->neSampler = NULL;

  cfg->minDepth = 1;
  cfg->maxDepth = 1;  // Only eye point needed, for Particle tracing test

  cfg = &pmapconfig.lightConfig;

  cfg->pointSampler = new CUniformLightSampler;
  cfg->dirSampler = new CLightDirSampler;
  ChooseSurfaceSampler(&cfg->surfaceSampler);
  // cfg->surfaceSampler = new CPhotonMapSampler; //new CBsdfSampler; 
  cfg->surfaceSampler->SetComputeFromNextPdf(false);  // Only 1 pdf

  cfg->minDepth = pmapstate.minimumLightPathDepth;
  cfg->maxDepth = pmapstate.maximumLightPathDepth;
  
  rt_raycount = 0;

  // Init the photonmap

  if(pmapconfig.globalMap) { delete pmapconfig.globalMap; }
  pmapconfig.globalMap = new CPhotonMap(&pmapstate.reconGPhotons, 
					pmapstate.precomputeGIrradiance);

  if(pmapconfig.importanceMap) { delete pmapconfig.importanceMap; }
  pmapconfig.importanceMap = new CImportanceMap(&pmapstate.reconIPhotons,
						&pmapstate.gImpScale);

  if(pmapconfig.importanceCMap) { delete pmapconfig.importanceCMap; }
  pmapconfig.importanceCMap = new CImportanceMap(&pmapstate.reconIPhotons,
						 &pmapstate.cImpScale);

  if(pmapconfig.causticMap) { delete pmapconfig.causticMap; }
  pmapconfig.causticMap = new CPhotonMap(&pmapstate.reconCPhotons);
}


static void PatchReduceFlux(PATCH *P)
{
}


/***********************************************************************/

/* Adapted from bidirpath.C, this is a bit overkill for here... */
static COLOR DoComputePixelFluxEstimate(PMAPCONFIG *config)
{
  CBiPath *bp = &config->bipath;
  CPathNode *eyePrevNode, *lightPrevNode;
  COLOR oldBsdfL, oldBsdfE;
  CBsdfComp oldBsdfCompL, oldBsdfCompE;
  double oldPdfL, oldPdfE, oldRRPdfL, oldRRPdfE;
  double oldPdfLP=0., oldPdfEP=0., oldRRPdfLP=0., oldRRPdfEP=0.;
  COLOR f;
  CPathNode *eyeEndNode, *lightEndNode;

  // Store PDF and BSDF evals that will be overwritten
  
  eyeEndNode = bp->m_eyeEndNode;
  lightEndNode = bp->m_lightEndNode;
  eyePrevNode = eyeEndNode->Previous();
  lightPrevNode = lightEndNode->Previous();

  oldBsdfL = lightEndNode->m_bsdfEval;
  oldBsdfCompL = lightEndNode->m_bsdfComp;

  oldBsdfE = eyeEndNode->m_bsdfEval;
  oldBsdfCompE = eyeEndNode->m_bsdfComp;
  
  oldPdfL = lightEndNode->m_pdfFromNext;
  PNAN(lightEndNode->m_pdfFromNext);

  oldRRPdfL = lightEndNode->m_rrPdfFromNext;
  
  if(lightPrevNode)
  {
    oldPdfLP = lightPrevNode->m_pdfFromNext;
    PNAN(lightPrevNode->m_pdfFromNext);
    oldRRPdfLP = lightPrevNode->m_rrPdfFromNext;
  }

  oldPdfE = eyeEndNode->m_pdfFromNext;
  PNAN(eyeEndNode->m_pdfFromNext);
  oldRRPdfE = eyeEndNode->m_rrPdfFromNext;

  if(eyePrevNode)
  {
    oldPdfEP = eyePrevNode->m_pdfFromNext;
    PNAN(eyePrevNode->m_pdfFromNext);
    oldRRPdfEP = eyePrevNode->m_rrPdfFromNext;
  }

  // Connect the subpaths
  
  bp->m_geomConnect = 
    PathNodeConnect(eyeEndNode, lightEndNode, 
		    &config->eyeConfig, &config->lightConfig,
		    CONNECT_EL | CONNECT_LE,
		    BSDF_ALL_COMPONENTS, BSDF_ALL_COMPONENTS, &bp->m_dirEL);
  
  VECTORSCALE(-1, bp->m_dirEL, bp->m_dirLE);

  //PNAN(eyeEndNode->m_pdfFromNext);
  //if(eyePrevNode) PNAN(eyePrevNode->m_pdfFromNext);
  //PNAN(lightEndNode->m_pdfFromNext);
  //if(lightPrevNode) PNAN(lightPrevNode->m_pdfFromNext);

  // Evaluate radiance and pdf and weight

  f = bp->EvalRadiance();

  double factor = 1.0 / bp->EvalPDFAcc();

  COLORSCALE(factor, f, f); // Flux estimate

  // Restore old values
  
  lightEndNode->m_bsdfEval = oldBsdfL;
  lightEndNode->m_bsdfComp = oldBsdfCompL;

  eyeEndNode->m_bsdfEval = oldBsdfE;
  eyeEndNode->m_bsdfComp = oldBsdfCompE;
  
  lightEndNode->m_pdfFromNext = oldPdfL;
  lightEndNode->m_rrPdfFromNext = oldRRPdfL;
  
  if(lightPrevNode)
  {
    lightPrevNode->m_pdfFromNext = oldPdfLP;
    lightPrevNode->m_rrPdfFromNext = oldRRPdfLP;
  }
  
  eyeEndNode->m_pdfFromNext = oldPdfE;
  eyeEndNode->m_rrPdfFromNext = oldRRPdfE;

  if(eyePrevNode)
  {
    eyePrevNode->m_pdfFromNext = oldPdfEP;
    eyePrevNode->m_rrPdfFromNext = oldRRPdfEP;
  }

  return f;
}






/** Test next event estimator to the screen. The result is standard
    particle tracing, although constructing global & caustic together
    does not give correct display **/

void DoScreenNEE(PMAPCONFIG *config)
{
  int nx,ny;
  float pix_x, pix_y;
  COLOR f;
  CBiPath *bp = &config->bipath;

  if(config->currentMap == config->importanceMap)
  {
    return;
  }

  //if(bp->m_lightSize <= 2)
  //  return;

  // First we need to determine if the lightEndNode can be seen from
  // the camera. At the same time the pixel hit is computed

  if(EyeNodeVisible(bp->m_eyeEndNode, bp->m_lightEndNode, 
		    &pix_x, &pix_y))
  {
    // Visible !

    f = DoComputePixelFluxEstimate(config);

    config->screen->GetPixel(pix_x, pix_y, &nx, &ny);

    float factor;

    if(config->currentMap == config->globalMap)
    {
      factor = (ComputeFluxToRadFactor(nx, ny) 
		/ (float)pmapstate.total_gpaths);
    }
    else
    {
      factor = (ComputeFluxToRadFactor(nx, ny) 
		/ (float)pmapstate.total_cpaths);
    }

    COLORSCALE(factor, f, f);

    config->screen->Add(nx, ny, f);
  }
}



/** Store a photon. Some acceptance tests are performed first **/

bool DoPhotonStore(CPathNode *node, COLOR power)
{
  //float scatteredPower;
  //COLOR col;
  BSDF *bsdf;

  if(node->m_hit.patch && node->m_hit.patch->surface->material)
  {
    // Only add photons on surfaces with a certain reflection
    // coefficient.

    bsdf = node->m_hit.patch->surface->material->bsdf;

    if(!ZeroAlbedo(bsdf, &node->m_hit, BSDF_DIFFUSE_COMPONENT|BSDF_GLOSSY_COMPONENT))
    {
      CPhoton photon(node->m_hit.point, power, node->m_inDirF);
      
      // Determine photon flags
      short flags = 0;

      if(node->m_depth == 1)
      {
	// Direct light photon
	flags |= DIRECT_LIGHT_PHOTON;
      }

      if(pmapstate.densityControl == NO_DENSITY_CONTROL)
      {
	return pmapconfig.currentMap->AddPhoton(photon, node->m_hit.normal,
					    flags);
      }
      else
      {
	float reqDensity;
	if(pmapstate.densityControl == CONSTANT_RD)
	{
	  reqDensity = pmapstate.constantRD;
	}
	else
	{
	  reqDensity = pmapconfig.currentImpMap->GetRequiredDensity(node->m_hit.point, 
								    node->m_hit.normal);
	}

	return pmapconfig.currentMap->DC_AddPhoton(photon, node->m_hit,
					       reqDensity, flags);
      } 
    }
  }
  return false;
}


/** Handle one path : store at all end points and
    for testing, connect to the eye **/
void HandlePath(PMAPCONFIG *config)
{
  bool ldone;
  CBiPath *bp = &config->bipath;
  COLOR accPower;
  float factor;


  // Iterate over all light nodes

  bp->m_lightSize = 1;
  CPathNode *currentNode = bp->m_lightPath;

  bp->m_eyeSize = 1;
  bp->m_eyeEndNode = bp->m_eyePath;
  bp->m_geomConnect = 1.0; // No connection yet 

  ldone = false;
  COLORSETMONOCHROME(accPower, 1.0);

  while(!ldone)
  {
    // Adjust accPower

    factor = currentNode->m_G / currentNode->m_pdfFromPrev;
    COLORSCALE(factor, accPower, accPower);

    // Store photon, but not emitted light

    if(config->currentMap == config->globalMap)
    {
      if(bp->m_lightSize > 1)
      {
	// Store
	
	if(DoPhotonStore(currentNode, accPower))
	{
	  // Screen next event estimation for testing
	  
	  bp->m_lightEndNode = currentNode;
	  DoScreenNEE(config);
	}
      }
    }
    else  // Caustic map...
    {
      if(bp->m_lightSize > 2)
      {
	// Store
	
	if(DoPhotonStore(currentNode, accPower))
	{
	  // Screen next event estimation for testing
	  
	  bp->m_lightEndNode = currentNode;
	  DoScreenNEE(config);
	}
      }
    }


    // Account for bsdf, node that for the first node, this accounts
    // for the emitted radiance.

    if(!(currentNode->Ends()))
    {
      COLORPROD(currentNode->m_bsdfEval, accPower, accPower);

      currentNode = currentNode->Next();
      bp->m_lightSize++;
    }
    else
    {
      ldone = true;
    }
  }
}

/********* TESTING routines for the photonmap *********/


void TestPMAP(void)
{
  // COLOR col;

  // Trace a test eye path

  pmapconfig.eyeConfig.maxDepth = 2;

  pmapconfig.bipath.m_eyePath = pmapconfig.eyeConfig.TracePath(pmapconfig.bipath.m_eyePath);

  // Reconstruct at the nodes

  CPathNode *node = pmapconfig.bipath.m_eyePath;
  bool done = (node->Ends());

  while(!done)
  {
    node = node->Next();
    pmapconfig.currentMap->Reconstruct(&node->m_hit, node->m_inDirF, 
				   node->m_useBsdf,
				   node->m_inBsdf, node->m_outBsdf);

    done = (node->Ends());
  }

  // Restore sampler settings

  pmapconfig.eyeConfig.maxDepth = 1;
}



/********* ONE iteration ********/

static unsigned pmapQMCseed_s = 123456;

static void TracePath(PMAPCONFIG *config, BSDFFLAGS bsdfFlags)
{
  config->bipath.m_eyePath = config->eyeConfig.TracePath(config->bipath.m_eyePath);

  // Use qmc for light sampling

  double x_1, x_2;
  // unsigned *nrs = Nied31(pmapQMCseed_s++);

  CPathNode *path = config->bipath.m_lightPath;

  // First node
  x_1 = drand48(); //nrs[0] * RECIP;
  x_2 = drand48(); //nrs[1] * RECIP;

  path = config->lightConfig.TraceNode(path, x_1, x_2, bsdfFlags);
  if(path == NULL) return;

  config->bipath.m_lightPath = path;  // In case no nodes were present

  path->EnsureNext();

  // Second node
  CPathNode *node = path->Next();
  x_1 = drand48(); // nrs[2] * RECIP;
  x_2 = drand48(); // nrs[3] * RECIP; // 4D Niederreiter...

  if(config->lightConfig.TraceNode(node, x_1, x_2, bsdfFlags))
  {
    // Succesful trace...
    node->EnsureNext();
    config->lightConfig.TracePath(node->Next(), bsdfFlags);
  }
}

static void TracePaths(int nrPaths, BSDFFLAGS bsdfFlags = BSDF_ALL_COMPONENTS)
{
  int i;

  // Fill in config structures

  for(i = 0; i < nrPaths; i++)
  {
    TracePath(&pmapconfig, bsdfFlags);
    // config.currentMap->AddPath();

    HandlePath(&pmapconfig);

    if (pmapstate.wake_up) {
      ProcessWaitingEvents();
      pmapstate.wake_up = FALSE;
    }
  }
}

static void BRRealIteration(void)
{
  pmapstate.iteration_nr++;

  fprintf(stderr, "Pmap Iteration %li\n", (long)pmapstate.iteration_nr);

  if((pmapstate.iteration_nr > 1) && (pmapstate.doGlobalMap || pmapstate.doCausticMap))
  {
    float scaleFactor = (pmapstate.iteration_nr-1.0)/(float)pmapstate.iteration_nr; 
    pmapconfig.screen->ScaleRadiance(scaleFactor);
  }

  if((pmapstate.densityControl == IMPORTANCE_RD) && pmapstate.doImportanceMap)
  {
    pmapstate.i_iteration_nr++;
    pmapconfig.currentMap = pmapconfig.importanceMap;
    pmapstate.total_ipaths = pmapstate.i_iteration_nr * pmapstate.ipaths_per_iteration;
    pmapconfig.currentMap->SetTotalPaths(pmapstate.total_ipaths);
    pmapconfig.importanceCMap->SetTotalPaths(pmapstate.total_ipaths);
    
    TracePotentialPaths(pmapstate.ipaths_per_iteration);
    
    fprintf(stderr,"Total potential paths : %li, Total rays %li\n", 
	    pmapstate.total_ipaths,
	    rt_raycount);
    //pmapconfig.importanceMap->BalanceAnalysis();
  }


  // Global map

  if(pmapstate.doGlobalMap)
  {
    pmapstate.g_iteration_nr++;
    pmapconfig.currentMap = pmapconfig.globalMap;
    pmapstate.total_gpaths = pmapstate.g_iteration_nr * pmapstate.gpaths_per_iteration;
    pmapconfig.currentMap->SetTotalPaths(pmapstate.total_gpaths);

    // Set correct importance map: indirect importance
    pmapconfig.currentImpMap = pmapconfig.importanceMap;
    
    TracePaths(pmapstate.gpaths_per_iteration);
    
    fprintf(stderr, "Global map: ");
    pmapconfig.globalMap->PrintStats(stderr);

    /*
    fprintf(stderr,"Total global paths : %li, Total rays %li\n", 
	    pmapstate.total_gpaths,
	    rt_raycount);
    pmapconfig.globalMap->BalanceAnalysis();
    */
  }

  // Caustic map

  if(pmapstate.doCausticMap)
  {
    pmapstate.c_iteration_nr++;
    pmapconfig.currentMap = pmapconfig.causticMap;
    pmapstate.total_cpaths = pmapstate.c_iteration_nr * pmapstate.cpaths_per_iteration;
    pmapconfig.currentMap->SetTotalPaths(pmapstate.total_cpaths);

    // Set correct importance map: direct importance
    pmapconfig.currentImpMap = pmapconfig.importanceCMap;
    
    TracePaths(pmapstate.cpaths_per_iteration, BSDF_SPECULAR_COMPONENT);

    fprintf(stderr, "Caustic map: ");
    pmapconfig.causticMap->PrintStats(stderr);

    /*
    fprintf(stderr,"Total caustic paths : %li, Total rays %li\n", 
	    pmapstate.total_cpaths,
	    rt_raycount);
    pmapconfig.causticMap->BalanceAnalysis();
    */
  }

}



/* Performs one step of the radiance computations. The goal most often is
 * to fill in a RGB color for display of each patch and/or vertex. See
 * the PATCH and VERTEX structs in ../patch.h and ../vertex.h. These
 * colors are used for hardware rendering if the default hardware rendering
 * method is not superceeded in this file. */
static int DoPmapStep(void)
{
  void (*prev_alrm_handler)(int signr); 
  unsigned prev_alarm_left;

  /* install a timer that will wake us up ofter one second for checking for 
   * user events */
  prev_alrm_handler = signal(SIGALRM, wake_up);
  prev_alarm_left = alarm(/*after*/ 1 /*second*/);
  pmapstate.wake_up = FALSE;
  pmapstate.lastclock = clock();

  /* do some real work now */

  BRRealIteration();

  UpdateCpuSecs();

  /* reinstall the previous alarm handler */
  signal(SIGALRM, prev_alrm_handler);
  alarm(prev_alarm_left);

  pmapstate.runstop_nr++;
  
  return FALSE;	/* done. Return FALSE if you want the computations to 
		 * continue. */
}

/* undoes the effect of Init() and all side-effects of Step() */
static void TerminatePmap(void)
{
  if(pmapconfig.screen) 
  {
    delete pmapconfig.screen;
    pmapconfig.screen = NULL;
  }

  pmapconfig.lightConfig.ReleaseVars();
  pmapconfig.eyeConfig.ReleaseVars();

  if(pmapconfig.globalMap) 
  { 
    delete pmapconfig.globalMap;
    pmapconfig.globalMap = NULL;
  }

  if(pmapconfig.importanceMap) 
  { 
    delete pmapconfig.importanceMap; 
    pmapconfig.importanceMap = NULL;
  }

  if(pmapconfig.importanceCMap) 
  { 
    delete pmapconfig.importanceCMap; 
    pmapconfig.importanceCMap = NULL;
  }

  if(pmapconfig.causticMap) 
  { 
    delete pmapconfig.causticMap;
    pmapconfig.causticMap = NULL;
  }
}


// Returns the radiance emitted in the node related direction
COLOR GetPmapNodeGRadiance(CPathNode *node)
{
  COLOR col;

  pmapconfig.globalMap->DoBalancing(pmapstate.balanceKDTree);
  col = pmapconfig.globalMap->Reconstruct(&node->m_hit, node->m_inDirF, 
				      node->m_useBsdf,
				      node->m_inBsdf, node->m_outBsdf);
  return col;
}


// Returns the radiance emitted in the node related direction
COLOR GetPmapNodeImportance(CPathNode *node)
{
  COLOR col;

  // pmapconfig.importanceMap->DoBalancing(pmapstate.balanceKDTree);

  col = pmapconfig.importanceMap->Reconstruct(&node->m_hit, node->m_inDirF, 
				      node->m_useBsdf,
				      node->m_inBsdf, node->m_outBsdf);
  return col;
}


// Returns the radiance emitted in the node related direction
COLOR GetPmapNodeCRadiance(CPathNode *node)
{
  COLOR col;

  pmapconfig.causticMap->DoBalancing(pmapstate.balanceKDTree);

  col = pmapconfig.causticMap->Reconstruct(&node->m_hit, node->m_inDirF, 
				      node->m_useBsdf,
				      node->m_inBsdf, node->m_outBsdf);
  return col;
}

CPhotonMap *GetPmapGlobalMap(void)
{
  return pmapconfig.globalMap;
}


// To adjust GetPmapRadiance returns
static bool s_doingLocalRaycast = false;

static COLOR GetPmapRadiance(PATCH *patch, 
			      double u, double v, 
			      VECTOR dir)
{
  HITREC hit;
  VECTOR point;
  BSDF *bsdf = patch->surface->material->bsdf;
  COLOR col;
  float density;

  PatchPoint(patch, u, v, &point);
  InitHit(&hit, patch, NULL, &point, &patch->normal, patch->surface->material, 0.);
  HitShadingNormal(&hit, &hit.normal);

  if(ZeroAlbedo(bsdf, &hit, BSDF_DIFFUSE_COMPONENT | BSDF_GLOSSY_COMPONENT))
  {
    COLORCLEAR(col);
    return col;
  }

  RADRETURN_OPTION radreturn = GLOBAL_RADIANCE;

  if(s_doingLocalRaycast)
  {
    radreturn = pmapstate.radianceReturn;
  }

  switch(radreturn)
  {
  case GLOBAL_DENSITY:
    col = pmapconfig.globalMap->GetDensityColor(hit);
    break;
  case CAUSTIC_DENSITY:
    col = pmapconfig.causticMap->GetDensityColor(hit);
    break;
  case IMPORTANCE_CDENSITY:
    col = pmapconfig.importanceCMap->GetDensityColor(hit);
    break;
  case IMPORTANCE_GDENSITY:
    col = pmapconfig.importanceMap->GetDensityColor(hit);
    break;
  case REC_CDENSITY:
    pmapconfig.importanceCMap->DoBalancing(pmapstate.balanceKDTree);
    density = pmapconfig.importanceCMap->GetRequiredDensity(hit.point, hit.normal);
    col = GetFalseColor(density);
    break;
  case REC_GDENSITY:
    pmapconfig.importanceMap->DoBalancing(pmapstate.balanceKDTree);
    density = pmapconfig.importanceMap->GetRequiredDensity(hit.point, hit.normal);
    col = GetFalseColor(density);
    break;
  case GLOBAL_RADIANCE:
    col = pmapconfig.globalMap->Reconstruct(&hit, dir, 
					bsdf,
					NULL, bsdf);
    break;
  case CAUSTIC_RADIANCE:
    col = pmapconfig.causticMap->Reconstruct(&hit, dir, 
					 bsdf,
					 NULL, bsdf);
    break;
  default:
    COLORCLEAR(col);
    Error("GetPmapRadiance", "Unknown radiance return");
  }

  return col;
}


void PmapBalance(PMAP_TYPE type)
{
  CPhotonMap *map;
  switch(type)
  {
  case GLOBAL_MAP:
    map = pmapconfig.globalMap;
    break;
  case CAUSTIC_MAP:
    map = pmapconfig.causticMap;
    break;
  case IMPORTANCE_MAP:
    map = pmapconfig.importanceMap;
    break;
  default:
    return;
  }

  if(map)
    map->Balance();
}

void PmapRaycast(void)
{
  if(pmapstate.rcScreen) delete pmapstate.rcScreen;
  pmapstate.rcScreen = new CScreenBuffer();

  if((pmapstate.radianceReturn == GLOBAL_DENSITY) ||
     (pmapstate.radianceReturn == CAUSTIC_DENSITY) ||
     (pmapstate.radianceReturn == IMPORTANCE_GDENSITY) ||
     (pmapstate.radianceReturn == IMPORTANCE_CDENSITY) ||
     (pmapstate.radianceReturn == REC_CDENSITY) ||
     (pmapstate.radianceReturn == REC_GDENSITY)
    )
  {
    pmapstate.rcScreen->SetRGBImage(true);
  }

  // We need to know if we're raycasting ourselves, so that we can adjust the radiance
  // return
  s_doingLocalRaycast = true;
  RayCast(GetPmapRadiance, pmapstate.rcScreen);
  s_doingLocalRaycast = false;
}

void PmapRaycastInterrupt(void)
{
  if(s_doingLocalRaycast)
    RayCasting.InterruptRayTracing();
}

void PmapRedisplayRaycast(void)
{
  if(pmapstate.rcScreen)
    pmapstate.rcScreen->Render();
}

void PmapDoPrintValue(PATCH *P, VECTOR *hitp)
{
  double u,v;
  PatchUV(P, hitp, &u, &v);
  COLOR col = GetPmapRadiance(P, u, v, P->normal);

  fprintf(stderr, "Value: ");
  ColorPrint(stderr, col);
  fprintf(stderr, "\n");

  VECTOR d = *hitp - Camera.eyep;
  float dist = VECTORNORM(d);
  d = d / dist;
  fprintf(stderr, "Distance : %g\n", dist);
  fprintf(stderr, "Cos: %g\n", (d & P->normal));
}

static void PmapRenderScreen(void)
{
  if(pmapconfig.screen && pmapstate.renderImage)
    pmapconfig.screen->Render();
  else
  {
    PatchListIterate(Patches, RenderPatch);
  }
}

static char *GetPmapStats(void)
{
  static char stats[1000];
  char *p;
  int n;

  p = stats;
  sprintf(p, "PMAP Statistics:\n\n%n", &n); p += n;
  sprintf(p, "Ray count %li\n%n", rt_raycount, &n); p += n;
  sprintf(p, "Time %g\n%n",pmapstate.cpu_secs, &n); p += n;

  if(pmapconfig.globalMap)
  {
    sprintf(p, "Global Map: %n", &n); p += n;
    pmapconfig.globalMap->GetStats(p);
    p += strlen(p);
    sprintf(p, "\n%n", &n); p += n;
  }
  if(pmapconfig.causticMap)
  {
    sprintf(p, "Caustic Map: %n", &n); p += n;
    pmapconfig.causticMap->GetStats(p);
    p += strlen(p);
    sprintf(p, "\n%n", &n); p += n;
  }
  if(pmapconfig.importanceMap)
  {
    sprintf(p, "Global Importance Map: %n", &n); p += n;
    pmapconfig.importanceMap->GetStats(p);
    p += strlen(p);
    sprintf(p, "\n%n", &n); p += n;
  }
  if(pmapconfig.importanceCMap)
  {
    sprintf(p, "Caustic Importance Map: %n", &n); p += n;
    pmapconfig.importanceCMap->GetStats(p);
    p += strlen(p);
    sprintf(p, "\n%n", &n); p += n;
  }

  return stats;
}

static void PmapRecomputeDisplayColors(void)
{
  ForAllPatches(P, Patches) {
    PatchComputeNewColor(P);
  } EndForAll;
}

static void PmapUpdateMaterial(MATERIAL */*oldmaterial*/, 
				MATERIAL */*newmaterial*/)
{
  Error("PmapUpdateMaterial", "Not yet implemented");
}

RADIANCEMETHOD Pmap = {
  "PMAP", 4,
  "PhotonMap",
  "pmapButton",
  PmapDefaults,
  ParsePmapOptions,
  PrintPmapOptions,
  InitPmap,
  DoPmapStep,
  TerminatePmap,
  GetPmapRadiance,
  CreatePatchData,
  PrintPatchData,
  DestroyPatchData,
  CreatePmapControlPanel,
  (void (*)(void *))NULL,
  ShowPmapControlPanel,
  HidePmapControlPanel,
  GetPmapStats,
  PmapRenderScreen,		/* render the screen */
  PmapRecomputeDisplayColors,
  PmapUpdateMaterial,
  (void (*)(FILE *))NULL	/* use default VRML model saver */
};
