/* PhBTexturedSurface.C: PhBTexturedSurface nodes (public source) */

#include "PhBTexturedSurface.H"
#include "utils.H"

namespace xrml {

void PhBTexturedSurface::render(void)
{
  if (texture) texture->render();

  if (textureProjection)
    textureProjection->render();

  if (textureTransform)
    textureTransform->render();

  for (int i=0; i<surfaces.size; i++) surfaces[i]->render();

  int nrtextchans = texture->nrChannels();
  int nrsurfs = surfaces.size;
  int nrcomps = (nrtextchans < nrsurfs) ? nrtextchans : nrsurfs;
  if (nrcomps != nrcomponents) {
    // resize the weights array
    if (W) delete[] W;
    nrcomponents = nrcomps;
    W = new float[nrcomponents];
  }
}

bool PhBTexturedSurface::isLightSource(void)
{
  for (int i=0; i<nrcomponents; i++)
    if (surfaces[i]->isLightSource())
      return true;
  return false;
}

bool PhBTexturedSurface::isScatterer(void)
{
  for (int i=0; i<nrcomponents; i++)
    if (surfaces[i]->isScatterer())
      return true;
  return false;
}

Vec3 PhBTexturedSurface::get_texcoord(const Vec3& point, const Vec3& texco)
{
  // TODO: implement caching (keep previous 'point' and 'coord' for each node,
  // (not static: we want per-object caching, no global caching)
  Vec3 coord = texco;
  if (textureProjection)
    coord = textureProjection->object2surface(point, NULL, NULL, NULL);
  if (textureTransform)
    coord *= textureTransform->xf;
  return coord;
}

// calculates bsdf component sampling weights, returns sum
// of weights.
float PhBTexturedSurface::bsdf_sampling_weights(const Vec3& point,
						const Vec3& texco, const Vec3& texco3d,
						const Vec3& inDir,
						const Spectrum& inSpec,
						const complex& indexIn,
						const complex& indexOut,
						const int modes,
						const float *texture_val)
{
  float sum = 0.;
  for (int i=0; i < nrcomponents; i++) {
    W[i] = clamppos(texture_val[i]) * (surfaces[i]->albedo(point, texco, texco3d, inDir, indexIn, indexOut, modes) & inSpec);
    sum += W[i];
  }
  return sum;
}

Spectrum PhBTexturedSurface::evaluateBSDF(const Vec3& point,
					  const Vec3& texco, const Vec3& texco3d,
					  const Vec3& inDir,
					  const Vec3& outDir,
					  const Spectrum& inSpec,
					  const complex& indexIn,
					  const complex& indexOut,
					  int modes, 
					  float *pdf)
{
  Spectrum spectrum(0.);
  
  if (pdf) *pdf = 0.;

  // get weights
  if (!texture)
    return spectrum;

  Vec3 coord = get_texcoord(point, texco);
  int channels;
  float *w = texture->values(coord.x, coord.y, &channels);

  // weighted sum of BSDF's, weighted sum of pdf (with different weights!)
  float sum = 0.;
  if (pdf)
    sum = bsdf_sampling_weights(point, texco, texco3d, inDir, inSpec, indexIn, indexOut, modes, w);

  for (int i=0; i < nrcomponents; i++) {
    float pdfi;
    spectrum += surfaces[i]->evaluateBSDF(point, coord, texco3d,  inDir, outDir, inSpec, indexIn, indexOut, modes, (pdf ? &pdfi : NULL) ) * w[i];
    if (pdf) *pdf += pdfi * W[i];
  }

  // normalise pdf
  if (pdf && (sum > EPSILON) ) *pdf /= sum;

  return spectrum;
}

Vec3 PhBTexturedSurface::sampleBSDF(const Vec3& point,
				    const Vec3& texco, const Vec3& texco3d,
				    const Vec3& inDir,
				    const Spectrum& inSpec,
				    const complex& indexIn,
				    const complex& indexOut,
				    float xi1, float xi2, 
				    int modes,
				    Spectrum *value, float *pdf)
{
  int i;

  // init value and pdf in case of failure
  if (value) *value = Spectrum(0.);
  if (pdf) *pdf = 0.;

  if (!texture)
    return Vec3(0.,0.,0.);

  // sample texture
  Vec3 coord = get_texcoord(point, texco);
  float *weight;
  int channels;
  weight = texture->values(coord.x, coord.y, &channels);

  // compute sampling weight for each component
  float sum = bsdf_sampling_weights(point, texco, texco3d, inDir, inSpec, indexIn, indexOut, modes, weight);
  if (sum < EPSILON)
    // sum cannot be 0!
    return Vec3(0.,0.,1.);

  // choose a component surface to sample
  int index = SampleDiscretePDF(W, nrcomponents, sum, &xi1);

  // sample surface
  Vec3 outDir = surfaces[index]->sampleBSDF(point, coord, texco3d, inDir, inSpec, indexIn, indexOut, xi1, xi2, modes, value, pdf);

  // compute bsdf value and pdf if requested
  if (value || pdf) {
    for (i=0; i<nrcomponents; i++) {
      float pdfi;
      Spectrum speci = surfaces[i]->evaluateBSDF(point, coord, texco3d,  inDir, outDir, inSpec, indexIn, indexOut, modes, (pdf ? &pdfi : NULL));
      if (pdf) *pdf += pdfi * W[i];
      if (value) *value += speci * weight[i];
    }
    if (pdf) *pdf /= sum;  // normalize pdf
  }

  return outDir;
}

Spectrum PhBTexturedSurface::albedo(const Vec3& point,
				    const Vec3& texco, const Vec3& texco3d,
				    const Vec3& inDir,
				    const complex& indexIn,
				    const complex& indexOut,
				    int modes)
{
  Spectrum spectrum(0.);

  // get weights
  if (!texture)
    return spectrum;
  Vec3 coord = get_texcoord(point, texco);
  int channels;
  float *w = texture->values(coord.x, coord.y, &channels);

  // weighted sum of BSDF's
  for (int i=0; i < nrcomponents; i++)
    spectrum += surfaces[i]->albedo(point, coord, texco3d, inDir, indexIn, indexOut, modes) * w[i];

  return spectrum;
}

float PhBTexturedSurface::edf_sampling_weights(const Vec3& point,
					       const Vec3& texco, const Vec3& texco3d,
					       const Spectrum& specWeight,
					       const int range,
					       const float *texture_val)
{
  float sum = 0.;
  for (int i=0; i < nrcomponents; i++) {
    W[i] = clamppos(texture_val[i]) * (surfaces[i]->emittance(point, texco, texco3d, range) & specWeight);
    sum += W[i];
  }
  return sum;
}

Spectrum PhBTexturedSurface::evaluateEDF(const Vec3& point,
					 const Vec3& texco, const Vec3& texco3d,
					 const Vec3& direction,
					 const Spectrum& specWeight,
					 int range,
					 float *pdf)
{
  Spectrum spectrum(0.);
  if (pdf) *pdf = 0.;

  // get mixing weights from texture
  if (!texture)
    return spectrum;
  Vec3 coord = get_texcoord(point, texco);
  int channels;
  float *w = texture->values(coord.x, coord.y, &channels);

  // calculate edf component sampling weights
  float sum = 0.;
  if (pdf)
    sum = edf_sampling_weights(point, texco, texco3d, specWeight, range, w);

  // weighted sum of component edf values and pdfs
  for (int i=0; i < nrcomponents; i++) {
    float pdfi;
    spectrum += surfaces[i]->evaluateEDF(point, coord, texco3d,  direction, specWeight, range, (pdf ? &pdfi : NULL) ) * w[i];
    if (pdf) *pdf += pdfi * W[i];
  }
  // normalise pdf
  if (pdf && (sum > EPSILON)) *pdf /= sum;

  return spectrum;
}

Vec3 PhBTexturedSurface::sampleEDF(const Vec3& point,
				   const Vec3& texco, const Vec3& texco3d,
				   const Spectrum& specWeight,
				   float xi1, float xi2, 
				   int range,
				   Spectrum *value, float *pdf)
{
  int i;

  // init value and pdf in case of failure
  if (value) *value = Spectrum(0.);
  if (pdf) *pdf = 0.;

  // get mixing weights from texture
  if (!texture)
    return Vec3(0.,0.,0.);
  Vec3 coord = get_texcoord(point, texco);
  int channels;
  float *w = texture->values(coord.x, coord.y, &channels);

  // calculate sampling weights for the components edfs.
  float sum = edf_sampling_weights(point, texco, texco3d, specWeight, range, w);
  if (sum < EPSILON)    // sum cannot be 0!
    return Vec3(0.,0.,1.);

  // search for right surface to sample
  int index = SampleDiscretePDF(W, nrcomponents, sum, &xi1);

  // sample surface
  Vec3 direction = surfaces[index]->sampleEDF(point, coord, texco3d, specWeight, xi1, xi2, range, value, pdf);

  // compute edf value and pdf if requested
  if (value || pdf) {
    for (i=0; i<nrcomponents; i++) {
      float pdfi;
      Spectrum speci = surfaces[i]->evaluateEDF(point, coord, texco3d,  direction, specWeight, range, (pdf ? &pdfi : NULL));
      if (value) *value += speci * w[i];
      if (pdf) *pdf += pdfi * W[i];
    }
    if (pdf) *pdf /= sum;  // normalize
  }

  // return sampled direction
  return direction;
}

Spectrum PhBTexturedSurface::emittance(const Vec3& point, 
				       const Vec3& texco, const Vec3& texco3d,					  
				       int range)
{
  Spectrum spectrum(0.);

  // get weights
  if (!texture)
    return spectrum;
  Vec3 coord = get_texcoord(point, texco);
  int channels;
  float *w = texture->values(coord.x, coord.y, &channels);

  // weighted sum of EDF's
  for (int i=0; i < nrcomponents; i++)
    spectrum += surfaces[i]->emittance(point, coord, texco3d, range) * w[i];

  return spectrum;
}



}  // namespace xrml
