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

#include "PhBPhongRefractor.H"
#include "matrix.H"

namespace xrml {

float PhBPhongRefractor::glossy_threshold = 200;

void PhBPhongRefractor::render(void)
{
  //
}

// compute ideal refracted direction. If total internal reflection occurs (incident
// angle smaller than critical angle), ideal reflected direction is returned.
static Vec3 IdealRefractedDirection(const Vec3& inDir,
				    const complex& IndexIn,
				    const complex& IndexOut)
{
  // Ref: Glassner PDI, part II, page 577-578.
  double t = (inDir.z > 0.) ? IndexOut.r / IndexIn.r : IndexIn.r / IndexOut.r;
  double zz = 1. - (1. - inDir.z * inDir.z) * (t * t);
  return (zz > 0.)
    ? Vec3(-inDir.x * t, -inDir.y * t, (inDir.z > 0.) ? -sqrt(zz) : sqrt(zz))
    : Vec3(-inDir.x, -inDir.y, inDir.z);  // total internal reflection
}


// returns approximate albedo of modified Phong BTDF: we assume that
// the cosine of the angle between outgoing direction and surface normal
// remains constant over the Phong lobe. This is a good approximation for
// sharp distributions.
float PhBPhongRefractor::albedo(const Vec3& inDir,
				const complex& IndexIn,
				const complex& IndexOut,
				int modes)
{
  if (!(modes & SM_REFRACTION))
      return 0.;

  if ((sharpness == 0. && (modes & SM_DIFFUSE_REFRACTION)) ||
      (sharpness <= glossy_threshold && (modes & SM_GLOSSY_REFRACTION)) ||
      (sharpness > glossy_threshold && (modes & SM_SPECULAR_REFRACTION))) {
    return fabs(IdealRefractedDirection(inDir, IndexIn, IndexOut).z);
  } else
    return 0.;
}

Vec3 PhBPhongRefractor::sample(const Vec3& inDir,
			       const complex& IndexIn,
			       const complex& IndexOut,
			       float xi1, float xi2,
			       int modes)
{
  // rotation transforming the Z axis into
  // the ideal refracted direction.
  Vec3 Z = IdealRefractedDirection(inDir, IndexIn, IndexOut);
  double zz = sqrt(1 - Z.z*Z.z);
  Vec3 X = (zz < EPSILON)
    ? Vec3(1.,0.,0.)
    : Vec3(Z.y/zz, -Z.x/zz, 0.);
  Vec3 Y = Z ^ X;
  Mat3 rot(X,Y,Z);

  double phi = 2. * M_PI * xi1;
  double cos_phi = cos(phi);
  double sin_phi = sin(phi);
  double cos_theta = pow(xi2, 1./(sharpness+1.));
  double sin_theta = sqrt(1. - cos_theta*cos_theta);
  return Vec3(cos_phi * sin_theta,
	      sin_phi * sin_theta,
	      cos_theta) * rot;
}

float PhBPhongRefractor::eval(const Vec3& inDir,
			      const Vec3& outDir,
			      const complex& IndexIn,
			      const complex& IndexOut,
			      int modes,
			      float *pdf)
{
  if (pdf) *pdf = 0.;

  if (!(modes & SM_REFRACTION) ||
      !((sharpness == 0. && (modes & SM_DIFFUSE_REFRACTION)) ||
	(sharpness <= glossy_threshold && (modes & SM_GLOSSY_REFRACTION)) ||
	(sharpness > glossy_threshold && (modes & SM_SPECULAR_REFRACTION)))) {
    return 0.;
  }

  double cosa, val;

  double t = (inDir.z > 0.) ? IndexOut.r / IndexIn.r : IndexIn.r / IndexOut.r;
  double zz = 1. - (1. - inDir.z * inDir.z) * (t * t);
  if (zz > 0.) {	// refraction
    if ((outDir.z > 0.) == (inDir.z > 0.))
      return 0.;	// not a refracted direction
    cosa = Vec3(-inDir.x * t, -inDir.y * t, (inDir.z > 0.) ? -sqrt(zz) : sqrt(zz))
      & outDir;
  } else {		// total internal reflection
    if ((outDir.z > 0.) != (inDir.z > 0.))
      return 0.;	// not a reflected direction
    cosa = Vec3(-inDir.x, -inDir.y, inDir.z) & outDir;
  }

  if (cosa <= 0.)	// angle >= 90 degrees
    return 0.;
  val = pow(cosa, sharpness);

  if (pdf) *pdf = val * (sharpness+1.) / (2. * M_PI);
  return val * (sharpness+2.) / (2. * M_PI);
}



}  // namespace xrml
