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

#include "PhBHomogeneousSurface.H"
#include "utils.H"

namespace xrml {

void PhBHomogeneousSurface::render(void)
{
  int i;
  for (i=0; i<edf.size; i++) edf[i]->render();
  for (i=0; i<bsdf.size; i++) bsdf[i]->render();

  // make sufficient space for weights
  int wsize = edf.size > bsdf.size ? edf.size : bsdf.size;
  if (wsize > w.size) w.grow(wsize - w.size);
}

bool PhBHomogeneousSurface::isLightSource(void)
{
  return (edf.size>0);
}

bool PhBHomogeneousSurface::isScatterer(void)
{
  return (bsdf.size>0);
}

Spectrum PhBHomogeneousSurface::evaluateEDF(const Vec3& point,
					     const Vec3& texco, const Vec3& texco3d,
				 const Vec3& direction,
				 const Spectrum& specWeight,
				 int range,
				 float *pdf)
{
  if (edf.size <= 0) {
    if (pdf) *pdf = 0.;
    return Spectrum(0.);
  }

  if (edf.size == 1) {
    return edf[0]->eval(direction, range, pdf);
  }

  Spectrum val(0.);
  float sumw = 0.;
  if (pdf) *pdf = 0.;
  for (int i=0; i<edf.size; i++) {
    float epdf;
    val += edf[i]->eval(direction, range, pdf ? &epdf : (float*)0);
    if (pdf) {
      // multiple importance sampling with weight proportional to
      // product of specWeight and edf component emittance.
      float w = fabs(edf[i]->emittance(range) & specWeight);
      sumw += w;
      *pdf += w * epdf;
    }
  }
  if (pdf && sumw > EPSILON) *pdf /= sumw;

  return val;
}

Vec3 PhBHomogeneousSurface::sampleEDF(const Vec3& point,
				      const Vec3& texco, const Vec3& texco3d,
			   const Spectrum& specWeight,
			   float xi1, float xi2, 
			   int range,
			   Spectrum *value, float *pdf)
{
  if (pdf)  *pdf = 0.;
  if (value) *value = Spectrum(0.);

  if (edf.size <= 0) {
    return Vec3(0.,0.,1.);
  }

  if (edf.size == 1) {	// single component -> easy
    Vec3 dir = edf[0]->sample(xi1, xi2, range);
    if (value || pdf) {
      Spectrum val = edf[0]->eval(dir, range, pdf);
      if (value) *value = val;
    }
    return dir;
  }

  // step 1: compute the weights of each EDF component
  float sumw = 0.;
  for (int i=0; i<edf.size; i++) {
    w[i] = fabs(edf[i]->emittance(range) & specWeight);
    sumw += w[i];
  }
  if (sumw < EPSILON)
    // product of specWeight and EDF yields black or no emission in range
    return Vec3(0.,0.,1.);

  // step 2: choose a component with probability proportional to its weight
  int chosen = SampleDiscretePDF(&w[0], edf.size, sumw, &xi1);

  // step 3: sample a direction according to the chosen 
  // component distribution
  Vec3 dir = edf[chosen]->sample(xi1, xi2, range);

  // step 4: if requested, compute EDF and pdf value for
  // the generated direction. This could be done with
  // a call to evalEDF(), but that would result in
  // the weights being computed again.
  if (value || pdf) {
    if (pdf) *pdf = 0.;
    for (int i=0; i<edf.size; i++) {
      float epdf;
      Spectrum eval = edf[i]->eval(dir, range, pdf ? &epdf : (float *)0);
      if (value) *value += eval;
      if (pdf) *pdf += w[i] * epdf;
    }
    if (pdf) *pdf /= sumw;
  }

  // cleanup and return.
  return dir;
}

Spectrum PhBHomogeneousSurface::emittance(const Vec3& point, 
					  const Vec3& texco, const Vec3& texco3d,					  
					  int range)
{
  Spectrum emittance(0.);
  for (int i=0; i<edf.size; i++) {
    emittance += edf[i]->emittance(range);
  }
  return emittance;
}

Spectrum PhBHomogeneousSurface::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)
{
  if (bsdf.size <= 0) {
    if (pdf) *pdf = 0.;
    return Spectrum(0.);
  }

  if (bsdf.size == 1) {
    return bsdf[0]->eval(inDir, outDir, indexIn, indexOut, modes, pdf);
  }

  Spectrum val(0);
  float sumw = 0.;
  if (pdf) *pdf = 0.;

  for (int i=0; i<bsdf.size; i++) {
    float cpdf = 0;

    // resulting bsdf value is simply the sum of the component values
    val += bsdf[i]->eval(inDir, outDir, indexIn, indexOut, modes, pdf ? &cpdf : (float*)0);

    if (pdf) {
      // multiple importance sampling. The weight given to each bsdf component is
      // proportional to its albedo times the inSpec. E.g. is inSpec is yellow,
      // and albedo is blue, the product is 0 and the component will not be sampled.
      float w = fabs(bsdf[i]->albedo(inDir, indexIn, indexOut, modes) & inSpec);
      sumw += w;
      *pdf += w * cpdf;
    }
  }
  if (pdf && sumw > EPSILON) *pdf /= sumw;
 
  return val;
}

Vec3 PhBHomogeneousSurface::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)
{
  if (pdf) *pdf = 0.;		// avoid division by zero
  if (value) *value = Spectrum(0.);
  
  if (bsdf.size <= 0) {
    return Vec3(0.,0.,1.);
  }

  if (bsdf.size == 1) {		// single component -> easy
    Vec3 outDir = bsdf[0]->sample(inDir, indexIn, indexOut, xi1, xi2, modes);
    if (value || pdf) {
      Spectrum val = bsdf[0]->eval(inDir, outDir, indexIn, indexOut, modes, pdf);
      if (value) *value = val;
    }
    return outDir;
  }

  // step 1: compute weight of each component
  float	sumw = 0.;
  for (int i=0; i<bsdf.size; i++) {
    w[i] = fabs(bsdf[i]->albedo(inDir, indexIn, indexOut, modes) & inSpec);
    sumw += w[i];
  }
  if (sumw < EPSILON)
    // light with spectrum inSpec coming from inDir is completely absorbed
    return Vec3(0.,0.,1.);

  // step 2: select a component to be sampled
  int chosen = SampleDiscretePDF(&w[0], bsdf.size, sumw, &xi1);

  // step 3: sample a scattered direction according to the selected component
  Vec3 outDir = bsdf[chosen]->sample(inDir, indexIn, indexOut, xi1, xi2, modes);

  // step 4: compute value of BSDF and PDF for chosen direction as requested
  if (value || pdf) {
    if (pdf) *pdf = 0.;
    for (int i=0; i<bsdf.size; i++) {
      float cpdf = 0;
      Spectrum cval = bsdf[i]->eval(inDir, outDir, indexIn, indexOut, modes, pdf ? &cpdf : (float*)0);
      if (value) *value += cval;
      if (pdf) *pdf += w[i] * cpdf;
    }
    if (pdf) *pdf /= sumw;
  }

  return outDir;
}

Spectrum PhBHomogeneousSurface::albedo(const Vec3& point,
			    const Vec3& texco, const Vec3& texco3d,
			    const Vec3& inDir,
			    const complex& indexIn,
			    const complex& indexOut,
			    int modes)
{
  Spectrum albedo(0.);
  for (int i=0; i<bsdf.size; i++)
    albedo += bsdf[i]->albedo(inDir, indexIn, indexOut, modes);
  return albedo;
}



}  // namespace xrml
