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

#include "PhBAppearance.H"
#include "renderer.H"

namespace xrml {

void PhBAppearance::render(void)
{
  // does nothing, use begin_render() ... end_render()
}

void PhBAppearance::begin_render(void)
{
  if (medium) medium->render();
  if (bumpMap) bumpMap->render();
  if (displacementMap) displacementMap->render();
  if (textureProjection) textureProjection->render();
  if (textureTransform) textureTransform->render();
  if (textureTransform3D) textureTransform3D->render();
  if (surface) surface->render();

  world->renderer->begin_PhBAppearance(this);
}

void PhBAppearance::end_render(void)
{
  world->renderer->end_PhBAppearance(this);
}

class phbcontext: public PooledObj {
  friend class PhBAppearance;
  Vec3 point;
  Vec3 gnorm;
  Vec3 texcoord, texcoord3d;
  complex indexIn, indexOut;
  Mat3 shading_rot;	// shading frame to world/object frame rotation
};

class phbcontext* PhBAppearance::initquery(const Mat4* world2object,
					   const Mat4* object2world,
					   const Vec3& point,
					   const Vec3& gnorm,
					   const complex& indexOut,
					   texture_projection *default_projection)
{
  phbcontext *ctx = new phbcontext;

  ctx->indexIn = medium ? medium->get_indexOfRefraction() : complex(1.,0.);
  ctx->indexOut = indexOut;

  // point in object coordinates
  ctx->point = world2object ? (Vec3)(Vec4(point) * (*world2object)) : point;

  // Construct rotation rotating brush direction -> X and
  // shading normal -> Z. brush dir and shading normal are
  // in world coordinates if world2object is not null.
  // The (optional) textureProjection is known to return a
  // frame in object coordinates. If a default_projection is
  // given, the default_projection takes care itself of the
  // proper transform.
  Vec3 X, Y, Z;
  if (textureProjection) {
    ctx->texcoord = textureProjection->object2surface(ctx->point, &X, &Y, &Z);
    if (object2world && world2object) {
      // transform X (brush direction) and Z (shading normal) to
      // world coord frame. Construct new Y perpendicular to new X and Z.
      Z = (Mat3(*world2object) * Z); Z.normalize();
      X = (X * Mat3(*object2world)); X.normalize();
      Y = Z ^ X;
    }
  } else if (default_projection) {
    // no textureProjection, but there is a default_projection
    ctx->texcoord = default_projection->project(ctx->point, &X, &Y, &Z);
  } else {
    // no textureProjection and no default_projection
    ctx->texcoord = ctx->point;
    Z = gnorm;
    double zz = sqrt(1 - Z.z*Z.z);
    X = (zz < EPSILON)	// X axis remains in the Z=0 plane
      ? Vec3(1.,0.,0.)
      : Vec3(Z.y/zz, -Z.x/zz, 0.);
    Y = Z ^ X;
  }
  // with pre-multiplication: rotates Z axis into the normal
  // with post-multiplication: rotates normal into the Z axis
  ctx->shading_rot = Mat3(X,Y,Z);

  // apply 2D texture transform to 2D texture coordinates
  if (textureTransform)
    ctx->texcoord *= textureTransform->xf;

  // 3D texture coordinate
  ctx->texcoord3d = ctx->point;
  if (textureTransform3D)
    ctx->texcoord3d *= textureTransform3D->xf;

  // Bump mapping
  if (bumpMap)
    ctx->shading_rot = bumpMap->distort(ctx->texcoord) * ctx->shading_rot;

  // TODO: displacement mapping (= rotation of shading frame ?)

  // geometric normal in shading frame.
  ctx->gnorm = ctx->shading_rot * (Vec3)gnorm;

  return ctx;
}

void PhBAppearance::endquery(class phbcontext* ctx)
{
  delete ctx;
}

Mat3& PhBAppearance::get_shadingframe(class phbcontext* ctx)
{
  return ctx->shading_rot;
}

complex PhBAppearance::get_indexOfRefraction(void)
{
  return medium ? medium->get_indexOfRefraction() : complex(1.,0.);
}

bool PhBAppearance::isLightSource(void)
{
  return (surface && surface->isLightSource());
}

bool PhBAppearance::isScatterer(void)
{
  return (surface && surface->isScatterer());
}

bool PhBAppearance::isTextured(void)
{
  return (surface && surface->isTextured());
}

Spectrum PhBAppearance::evaluateEDF(class phbcontext* ctx,
		     const Vec3& direction,
		     const Spectrum& specWeight,
		     int range,
		     float *pdf)
{
  if (!surface) {
    if (pdf) *pdf = 0.;
    return Spectrum(0.);
  }
  Vec3 dir = ctx->shading_rot * direction;
  return surface->evaluateEDF(ctx->point, ctx->texcoord, ctx->texcoord3d, 
			      dir, specWeight, range, pdf);
}

Vec3 PhBAppearance::sampleEDF(class phbcontext* ctx,
	       const Spectrum& specWeight,
	       float xi1, float xi2,
	       int range,
	       Spectrum *value, float *pdf)
{
  if (!surface) {
    if (pdf) *pdf = 0.;
    if (value) *value = Spectrum(0.);
    return Vec3(0.,0.,1.);
  }
  Vec3 dir = surface->sampleEDF(ctx->point, ctx->texcoord, ctx->texcoord3d, 
				specWeight, xi1, xi2, range, value, pdf);
  return dir * ctx->shading_rot;
}



Spectrum PhBAppearance::emittance(class phbcontext* ctx, int range)
{
  if (!surface)
    return Spectrum(0.);
  return surface->emittance(ctx->point, ctx->texcoord, ctx->texcoord3d, range);
}

Spectrum PhBAppearance::evaluateBSDF(class phbcontext* ctx,
		      const Vec3& inDir,
		      const Vec3& outDir,
		      const Spectrum& inSpec,
		      int modes,
		      float *pdf)
{
  if (!surface) {
    if (pdf) *pdf = 0;
    return Spectrum(0.);
  }
  Vec3 in = ctx->shading_rot * inDir;
  Vec3 out = ctx->shading_rot * outDir;
  return surface->evaluateBSDF(ctx->point, ctx->texcoord, ctx->texcoord3d, 
			       in, out, inSpec, 
			       ctx->indexIn, ctx->indexOut, modes, pdf);
}


Vec3 PhBAppearance::sampleBSDF(class phbcontext* ctx,
		const Vec3& inDir,
		const Spectrum& inSpec,
		float xi1, float xi2,
		int modes,
		Spectrum *value, float *pdf)
{
  if (!surface) {
    if (pdf) *pdf = 0;
    if (value) *value = Spectrum(0.);
    return Vec3(0.,0.,1.);
  }

  Vec3 in = ctx->shading_rot * inDir;
  Vec3 out = surface->sampleBSDF(ctx->point, ctx->texcoord, ctx->texcoord3d, 
				 in, inSpec,
				 ctx->indexIn, ctx->indexOut, xi1, xi2,
				 modes, value, pdf);
  return out * ctx->shading_rot;
}




Spectrum PhBAppearance::albedo(class phbcontext* ctx,
		const Vec3& inDir,
		int modes)
{
  if (!surface)
    return Spectrum(0.);
  Vec3 in = ctx->shading_rot * inDir;
  return surface->albedo(ctx->point, ctx->texcoord, ctx->texcoord3d, 
			 in, ctx->indexIn, ctx->indexOut, modes);
}

}  // namespace xrml
