/* PhBSchlickReflector.C: PhBSchlickReflector nodes (public source) */
// C. Schlick, "An inexpensive BRDF model for physically based rendering",
// Computer Graphics Forum Vol 13, nr 3 (Proceedings of EuroGraphics 1994), p 233
// With proper pdf.

#include "PhBSchlickReflector.H"
#include "PhBDiffuseReflector.H"
#include "PhBPerfectSpecularReflector.H"

namespace xrml {

static PhBDiffuseReflector Diffuse;
static PhBPerfectSpecularReflector Specular;

void PhBSchlickReflector::render(void)
{
  if (roughness < EPSILON) roughness = EPSILON;
  if (isotropy  < EPSILON) isotropy = EPSILON;

  // TODO: compute all kind of constants (independent of directions)
  // here, that are needed in the computations below, e.g. 1-p, p^2, ...
  // and worse.
}

// Schlicks paper p. C-245 formula (33)
static void getweights(float roughness, float *a, float *b, float *c)
{
  *b = 4.*roughness*(1.-roughness);
  *a = 0.;
  *c = 0.;
  if (roughness < 0.5) {
    *c = 1.-(*b);
  } else {
    *a = 1.-(*b);
  }
}

Vec3 PhBSchlickReflector::sample(const Vec3& inDir, 
				 const complex& IndexIn,
				 const complex& IndexOut,
				 float xi_1, float xi_2,
				 int modes)
{
  float a, b, c;
  getweights(roughness, &a, &b, &c);

  // choose which mode to sample
  if (xi_1 < a) {	// 0 <= xi_1 < a  ->  diffuse
    xi_1 /= a;
    return Diffuse.sample(inDir, IndexIn, IndexOut, xi_1, xi_2, modes);
  }

  xi_1 -= a;
  if (xi_1 < c) {	// a <= xi_1 < c  ->  specular
    xi_1 /= c;
    return Specular.sample(inDir, IndexIn, IndexOut, xi_1, xi_2, modes);
  }

  xi_1 -= c;		// a+c <= xi_1 < 1  ->  glossy
  xi_1 /= 1.-(a+c);	// xi_1 again in range 0 <= xi_1 < 1

  if (!(modes & SM_GLOSSY_REFLECTION))
    return Vec3(0.,0.,0.);
  
  // zenith angle
  double c2 = xi_1 / (roughness + (1.-roughness) * xi_1);
  double s = sqrt(1. - c2);
  
  // determine in which quadrant the azimuthal angle must be
  if (xi_2 > 1.-EPSILON) xi_2 = 1.-EPSILON;
  xi_2 *= 4.;
  double fxi2 = floor(xi_2);
  int quadrant = (int)fxi2;
  
  // make random nr in the rand [0,1) again
  // mirrored in quadrant 1 and 3, so result will change
  // continuously as a function of xi2.
  xi_2 = xi_2 - fxi2;
  if (quadrant&1) xi_2 = 1. - xi_2;
  
  // azimuth angle in first quadrant
  double p2 = isotropy * isotropy;
  double b2 = xi_2 * xi_2;
  double psi = 0.5*M_PI * sqrt( p2 * b2 / (1. - (1.-p2) * b2) );
  
  Vec3 h(cos(psi)*s, sin(psi)*s, sqrt(c2));

  // mirror h from first quadrant to its proper quadrant
  switch (quadrant) {
  case 0: break;
  case 1: h.x = -h.x; break;
  case 2: h.x = -h.x; h.y = -h.y; break;
  case 3: h.y = -h.y; break;
  }
  
  return h * (2. * (h & inDir)) - inDir;	// mirror inDir w.r.t. h
}

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

  float a, b, c;
  getweights(roughness, &a, &b, &c);

  float diffuse = 0., diffusepdf = 0.;
  if (a > 0.) diffuse = Diffuse.eval(inDir, outDir, IndexIn, IndexOut, modes, pdf ? &diffusepdf : (float*)0);

  float spec = 0., specpdf = 0.;
  if (c > 0.) spec = Specular.eval(inDir, outDir, IndexIn, IndexOut, modes, pdf ? &specpdf : (float*)0);

  float glossy = 0., glossypdf = 0.;

  if (b>0. && (modes & SM_GLOSSY_REFLECTION)) {
    // halfway vector (microfacet normal)
    Vec3 h = (Vec3)inDir + (Vec3)outDir;
    double H = (h & h);
    if (H < EPSILON) {
      // inDir == -outDir, rarely results from the sampling algorithm above.
      goto eval_done;
    }
    H = sqrt(H);
    h *= (1./H);
  
    // zenith angle dependence
    double t2 = h.z*h.z; 					// t^2
    double Z = (1-t2) + roughness * t2;
    Z = roughness / (Z*Z);
  
    // azimuth angle dependence
    double w2 = (t2 > 1.-EPSILON) ? 0. : h.x*h.x / (1. - t2) ; 	// w^2
    double A = sqrt( isotropy / (isotropy*isotropy * (1-w2) + w2) );
  
    if (pdf) {
      // The pdf corresponding to Schlicks sampling formula (formula 29-right
      // on page C-244) is NOT A(w) dw / sqrt(1-w^2) but something completely 
      // different !!! It is not even particularly suited to integrate Schlicks
      // own BRDF when anisotropic !!!
      double x = (2./M_PI) * ((w2<EPSILON*EPSILON) ? (M_PI/2.) : (w2>=1.) ? 0. : acos(sqrt(w2)));
      double p = isotropy;
      double p2 = p*p;
      double A1 = 1 + (1-p2)/p2 * x*x;
      glossypdf = fabs(((Vec3)h & (Vec3)outDir) * h.z) / (M_PI * H*H) * Z / (p * A1 * sqrt(A1));
    }
  
    double vi = inDir.z;
    double vo = outDir.z;
  
    if (fabs(vi) > EPSILON || fabs(vo) > EPSILON || (vi>0.) == (vo>0.)) {
      if (selfShadowing) {
	double Gi = vi / (roughness - roughness * vi + vi);
	double Go = vo / (roughness - roughness * vo + vo);
	glossy = (Gi * Go * A * Z + (1. - Gi * Go)) / (4.*M_PI * vi * vo);
      } else {
	// albedo for normal incidence and isotropy equals
	// 1 / (1. + roughness)
	glossy = A * Z  / (4.*M_PI * vi * vo);
      }
    }
  }

 eval_done:
  if (pdf) { *pdf = a * diffusepdf + b * glossypdf + c * specpdf; };
  return a * diffuse + b * glossy + c * spec ;
}

float PhBSchlickReflector::albedo(const Vec3& inDir,
				  const complex& IndexIn,
				  const complex& IndexOut,
				  int modes)
{
  float a, b, c;
  getweights(roughness, &a, &b, &c);

  float albedo = 0.;
  if (a>0.) albedo += a * Diffuse.albedo(inDir, IndexIn, IndexOut, modes);
  if (c>0.) albedo += c * Specular.albedo(inDir, IndexIn, IndexOut, modes);
  if (b>0. && (modes & SM_GLOSSY_REFLECTION)) {
    if (selfShadowing) {
      // fabs(inDir.z) is only an approximation. The real albedo seems
      // to be slightly different in a difficult to predict manner.
      albedo += b * fabs(inDir.z);
    } else
      albedo += b / (1. + roughness);
  }

  return albedo;
}

}  // namespace xrml
