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

#include "PhBTexturedBackground.H"
#include "utils.H"
#include "renderer.H"
#include "world.H"

namespace xrml {

void PhBTexturedBackground::set_bind(const SFBool& set)
{
  Bindable::set_bind(set);
}

void PhBTexturedBackground::precalc(void)
{
  // if a texture and texture projection exists, precalculate:
  //                                      - total power for the hemisphere
  //                                      - total power per row
  if (texture && textureProjection)
    {
      Vec3 ur, ul, dr, dl, s, l;
      Spectrum spectrum;
      int channels;
      int pos = 0;
      float *weight; 
      float stepx = 1. / texture->width();
      float stepy = 1. / texture->height();
      
      // allocate sample information (total power per row)
      rowPower = new float[texture->height() + 1];
     
      // calc total power
      for(float y=0; y < 1; y += stepy)
	{
	  Spectrum rowp = Spectrum(0.);

	  // get start (texture-coord) (s.x) for row y (texture-coord)
	  // get length (l.x) of row s
	  s = textureProjection->start(Vec3(0, y, 0));
	  l = textureProjection->size(Vec3(0, y, 0));

	  if(l.x < EPSILON) Warning(NULL, "PhBTeturedbackground::precalc() precondition failure: assumed at least one pixel per texture-line is used!"); 

	  for(float x=s.x; x < s.x + l.x; x += stepx)
	    {
	      // corner points of the selected pixel (in texture-coord)
	      dl = Vec3(x        , y,         0);
	      dr = Vec3(x + stepx, y,         0);
	      ul = Vec3(x        , y + stepy, 0);
	      ur = Vec3(x + stepx, y + stepy, 0);
	  
	      // get pixel-value
	      weight = texture->values(x, y, &channels); 
	      
	      // weighted sum (spectrum)
	      int min = (spectralBasis.size < channels) ? spectralBasis.size : channels;

	      spectrum = Spectrum(0.);
	      for (int i=0; i < min; i++)
		{
		  spectrum +=  (spectralBasis[i]->value) * (weight[i] * spectralWeights[i]);
		}
	      
	      // add to total power (of hemisphere) (spectrum * area of pixel)
	      totalPower += spectrum * textureProjection->area(ur, ul, dr, dl);
	      rowp += spectrum;
	    }

	  // next sample information (total power (luminance) per row)
	  rowPower[pos] = rowp.luminance();
	  mapLuminance += rowp.luminance();
	  pos++;
	}
    }
  else {
      totalPower = Spectrum(0.);
      mapLuminance = 0.;
  }
}

void PhBTexturedBackground::render(void)
{
  if(texture) {
    texture->render();
   }
  for(int i=0; i < spectralBasis.size; i++) { 
    spectralBasis[i]->render();
  }

  // do some precalcuations concerning total power per hemisphere ,power per texturemap row and total power of the map
  if(!rowPower) precalc();

  // render background
  world->renderer->phbTexturedBackground(this);
}

Spectrum PhBTexturedBackground::eval(const Vec3& /*position*/, 
				     const Vec3& direction,
				     const Spectrum& specWeight, 
				     float *pdf)
{
  Spectrum spectrum(0.);
  Vec3 coord = direction;
  float *weight;
  int channels;

  // apply viewOrientation
  Mat3 rot = Mat3(Vec4(viewOrientation));
  Vec3 dir = (Vec3)direction * rot;

  if (pdf) *pdf = 0;
  if (textureProjection) coord = textureProjection->direction2surface(dir);
  else return spectrum;

  if(texture) 
    { 
      weight = texture->values(coord.x, coord.y, &channels); 

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

      for (int i=0; i < min; i++)
	{
	  spectrum +=  (spectralBasis[i]->value) * (weight[i] * spectralWeights[i]);
	}

    
      // calculate pdf
      if (pdf) 
	{
	  // Jacobian from texture projection
	  float projectionDistortion = textureProjection->jacobian(coord);

	  // row distortion caused by unequal length in row length (1/length)
	  Vec3  l  = textureProjection->size(coord);	  
	  float rowDistortion = 1. / l.x;

	  // pdf from selecting a point in a pixel * selection of a pixel
	  float pixelPdf = (float)(texture->width()) * (float)(texture->height()) * spectrum.luminance() / mapLuminance;

	  *pdf =  pixelPdf * rowDistortion * projectionDistortion;
	}

    }

  // return result
  return spectrum;
}

Vec3 PhBTexturedBackground::sample(const Spectrum& specWeight,
				   const Vec3& position, 
				   const float xi1, const float xi2,
				   Spectrum *value, 
				   float *pdf)
{
  Vec3 dir = Vec3(0., 0., 0.);

  if (value) *value = 0.;
  if (pdf) *pdf = 0.;

  if(texture && textureProjection)
    {
      float w = 1. / texture->width();
      float h = 1. / texture->height();
      float ni1 = xi1;
      float ni2 = xi2;

      // sample by row
      int row = SampleDiscretePDF(rowPower, texture->height(), mapLuminance, &ni2);
      
      // copy row into memory
      float y  = h * (row + ni2);
      float *r = new float[texture->width() + 1];
      int pos  = 0;
      Spectrum   spectrum;
      Vec3       direction;
      Vec3  sx = textureProjection->start(Vec3(0, y, 0));
      Vec3  l  = textureProjection->size(Vec3(sx.x, y, 0));

      for(float x1=sx.x; x1 < sx.x + l.x; x1+= w)
	{
	  direction = textureProjection->surface2direction(Vec3(x1, y, 0));
	  spectrum  = eval(position, direction, specWeight, NULL);
	  r[pos++]  = spectrum.luminance();
	}

      // sample by collumn in the chosen row
      int width = (int)(ceil(texture->width() * l.x));
      int col = SampleDiscretePDF(r, width, rowPower[row], &ni1);

      // sample within pixel
      float x = (float)((col + ni1) * w);

      Vec3 coord = Vec3(x, y, 0);
      dir = textureProjection->surface2direction(coord);

      Spectrum v = eval(position, dir, specWeight, pdf);
      if (value) *value = v;

      // check if pdf == 0
      if (pdf && *pdf == 0.) dir = Vec3(0,0,0);

      // free memory
      delete[] r;      
      }

  // return sampled dir
  return dir;
}

Spectrum PhBTexturedBackground::power(const Vec3& /* position */)
{
  return(totalPower);
}

}  // namespace xrml
