/* PhBTextureEmitter.C: PhBTextureEmitter nodes (public source) */
//
// Build up of TextureEmitter texturemap:
//
// texture-position (u,v) corresponds to the following direction:
//
// theta = u * 2pi
// delta = v * v * pi
//
// with theta angle of direction in XY-projection
// and delta the angle with the normal (Z)
//
// To avoid bias in sampling we asign a probability EPSILON to value smaller
// then EPSILON.


#include "PhBTextureEmitter.H"
#include "utils.H"

namespace xrml {

void PhBTextureEmitter::render(void)
{
  if(texture) texture->render();
  if(!rowEmittance) precalc();
}

static double area(double /*u*/, double v, double w, double h)
{
  // return area on sphere of (u,v)-(u+w, v+h) square in texture
  double height = fabs(cos(M_PI * v) - cos(M_PI * (v + h)));
  double width = w;

  return float(2. * M_PI * height * width);
}

void PhBTextureEmitter::precalc(void)
{
  // precalc total Emittance of sphere (mapEmittance)
  // precalc sum of all texels in the texturemap (mapSum)
  // precalc sum of all texels in a row of the texturemap (for all rows) (rowEmittance)
  mapEmittance = 0;
  mapSum = 0;

  if ((texture) && (texture->nrChannels() >= channel))
    {
      rowEmittance = new float[texture->height() + 1];
      int row = 0;
      float *value;
      double w = 1. / texture->width();
      double h = 1. / texture->height();

      for(double v=0; v < 1; v += h)
	{
	  rowEmittance[row] = 0;

	  for(double u=0; u < 1; u += w)
	    {
	      // get textgure value
	      value = texture->values(u, v, NULL);
	      
	      // increase total  emittance (avoiding bias!)
	      float val = (value[channel] < EPSILON) ? EPSILON : value[channel];
	      mapEmittance += value[channel] * area(u, v, w, h);
	      mapSum += val;
	      
	      // increance emittance of row
	      rowEmittance[row] += val;
	    }
	  
	  // next row
	  row++;
	}
    }
}

float PhBTextureEmitter::emittance(int /*range*/)
{
  return(mapEmittance);
}

static Vec3 uv2dir(float u, float v)
{
  // given texture coordinates (u,v)
  // return correspondent direction
  double theta = 2. * M_PI * u;
  double cos_theta = cos(theta);
  double sin_theta = sin(theta);
  double phi = v * M_PI;
  double cos_phi = cos(phi);
  double sin_phi = sin(phi);
    
  return Vec3(cos_theta * sin_phi,
	      sin_theta * sin_phi,
	      cos_phi);
}

Vec3 PhBTextureEmitter::sample(float xi1, float xi2, int /*range*/)
{
  Vec3 dir = Vec3(0., 0., 0.);

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

      // sample by row
      int row = SampleDiscretePDF(rowEmittance, texture->height(), mapSum, &ni1);
      
      // copy row into memory
      float y  = h * row;
      float *r = new float[texture->width() + 1];
      float *value;
      int pos  = 0;

      for(float x=0; x < 1; x+= w)
	{
	  value = texture->values(x, y, NULL);
	  r[pos++] = (value[channel] < EPSILON) ? EPSILON : value[channel];
	}

      // sample by collumn in the chosen row
      int col = SampleDiscretePDF(r, texture->width(), rowEmittance[row], &ni2);

      // sample within pixel
      float x1 = (float)(col * w);
      float y1 = (float)(row * h);

      dir = uv2dir(x1 + (ni1 * w), y1 + (ni2 * h));

      // free memory
      delete[] r;      
      }

  // return sampled dir
  return dir;
}

static Vec3 dir2uv(Vec3 dir)
{
  // given a direction, return texture coordinates
  double u, v;

  // surface coord are
  if ((1 - fabs(dir.z)) < EPSILON) u = 0;
  else u = (atan2(dir.x, dir.y) / (2 * M_PI));
  v = acos(dir.z) / M_PI;

  return Vec3(u, v, 0);
}
 
static float jacobian(Vec3 coord)
{
  // given a point on texturemap, give jacobian for projection to sphere
  double phi = M_PI * coord.y;
  double sinPhi = sin(phi);

  if(sinPhi < EPSILON) return 0.;
  return (float)(1. / (2. * M_PI * M_PI * sinPhi));
}

float PhBTextureEmitter::eval(const Vec3& direction, int /*range*/, float *pdf)
{
  if(pdf) *pdf = 0.;
  if(!texture) return 0.;

  // get texture-value from direction and channel
  Vec3 Coord = dir2uv(direction);
  int channels;
  float *value = texture->values(Coord.x, Coord.y, &channels); 

  // get channel value
  if(channels < channel) return(0.);
  float val = (value[channel] < EPSILON) ? EPSILON : value[channel];

  // calc pdf if needed
  if(pdf)
    {
      // incorporate change in measure space (texture to sphere)
      float projectionDistortion = jacobian(Coord);

      // pdf from selecting a point in a pixel * selection of a pixel (avoid bias!!!)
      float pixelPdf = texture->width() * texture->height() * val / mapSum;

      *pdf =  pixelPdf * projectionDistortion;
    }

  // return value
  return value[channel];
}

}  // namespace xrml
