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

#include "PhB3DTexturedSurface.H"
#include "utils.H"

namespace xrml {

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

  if (textureTransform3D) textureTransform3D->render();

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

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

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

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

Spectrum PhB3DTexturedSurface::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)
{
  float *weight, pdfi, sum = 0;
  int channels;
  
 if (pdf) *pdf = 0.;
 Spectrum spectrum(0.);

  // get weights
  if (!texture)
    return spectrum;
  Vec3 coord = get_texcoord(point, texco3d);
  weight = texture->values(coord, &channels);

  // weighted sum of BSDF's, weighted sum of pdf
  int min = (surfaces.size < channels) ? surfaces.size : channels;

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

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

  return spectrum;
}

Vec3 PhB3DTexturedSurface::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)
{
  float *weight, sum = 0;
  int channels;

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

  // get weights and their sum
  if (!texture)
    return Vec3(0.,0.,0.);
  Vec3 coord = get_texcoord(point, texco3d);
  weight = texture->values(coord, &channels);

  int min = (surfaces.size < channels) ? surfaces.size : channels;

  for (int i=0; i < min; i++)
      sum += weight[i];

  // sum cannot be 0!
  if (sum < EPSILON) 
    return Vec3(0.,0.,1.);

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

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

  // compute bsdf value and pdf if requested
  if (value || pdf) {
    Spectrum spec = evaluateBSDF(point, texco, texco3d, inDir, outDir, inSpec, indexIn, indexOut, modes, pdf);
    if (value) *value = spec;
  }

  // return outDir
  return outDir;
}

Spectrum PhB3DTexturedSurface::albedo(const Vec3& point,
				    const Vec3& texco, const Vec3& texco3d,
				    const Vec3& inDir,
				    const complex& indexIn,
				    const complex& indexOut,
				    int modes)
{
  float *weight;
  int channels;
  Spectrum spectrum;

  // get weights
  if (!texture)
    return spectrum;
  Vec3 coord = get_texcoord(point, texco3d);
  weight = texture->values(coord, &channels);

  // weighted sum of BSDF's
  int min = (surfaces.size < channels) ? surfaces.size : channels;

  for (int i=0; i < min; i++)
    spectrum += surfaces[i]->albedo(point, texco, coord, inDir, indexIn, indexOut, modes) * weight[i];

  return spectrum;
}

}  // namespace xrml
