//----------------------------------------------------------------------------
// William Baxter III's Ray Tracer
//
//     Project for Comp 238, Raster Graphics
//     University of North Carolina at Chapel Hill
//     
// $Id:$
//----------------------------------------------------------------------------

#include <math.h>
#include <stdlib.h>
#include <assert.h>
#include "wb3ExtendedLight.hpp"
#include "wb3Material.hpp"
#include "wb3Artifact.hpp"
#include "wb3Scene.hpp"
#include "wb3Sampling.hpp"

static const float PI = 3.14159265358979f;
static const float TWOPI = 2*PI;
#define ABS(x) (((x)>0)?(x):(-x))

//----------------------------------------------------------------------------
wb3ExtendedLight::wb3ExtendedLight(const Vec3f& position)
  : wb3PointLight(position)
{
  m_fRadius = 0.2f;
}
//----------------------------------------------------------------------------
wb3ExtendedLight::wb3ExtendedLight(const Vec3f& color,const Vec3f& position,
                                   float radius)
  : wb3PointLight(color, position)
{
  m_fRadius = radius;
}

//----------------------------------------------------------------------------


// Computes the local portion of the illumination for a point
void wb3ExtendedLight::Contribute(
  const wb3Scene *scene, const wb3Artifact *hit,
  const Ray3f& I, 
  const Vec3f &isect, 
  const Vec3f &N, Vec3f& color) const
{
  using namespace wb3Sampling;

  Vec3f toL(GetPosition());
  toL -= isect;
  float NdotL = N*toL;
  if (NdotL < 0) // center point is is behind surface.  Pretend whole light is.
    return;

  // Determine a good number of samples to try 
  // based on approx projected area seen from surface point
  float fNSAMP = atan(2*GetRadius()/toL.Length())/PI; 
  int NSAMP = fNSAMP*fNSAMP*1e4; // PICK SCALE BY TRIAL AND ERROR
  if (!NSAMP) NSAMP = 1; // always have at least one sample!
  if (NSAMP > 50) NSAMP = 50; // Lets not be ridiculous.
//  printf("N:%d\t", NSAMP);

  Vec3f u,v;
  float lightDist = toL.Length();
  toL /= lightDist;
  ComputePerpendiculars(toL, u, v);

  // Attenuate light color proportional to 
  // number of samples to get same overall brightness as a point light
  Vec3f Lcolor( GetColor() / float(NSAMP) );

  for (int i=0; i<NSAMP; i++) 
  {
    Vec3f pos = GetPosition();
    if (i!=0) {
      PerturbSampleRadially(pos, u, v, GetRadius());
    }
    Vec3f toSample = pos - isect;
    float scale = N * toSample;
    if (scale > 0) { // SCALE IS NOT NORMALIZED YET!!
      // the light is on the front side.
      // Make sure nothing else is in the way
      float toSampleDist = toSample.Length();
      toSample /= toSampleDist;
      Ray3f shadowRay(isect, toSample);
      float dist;

      // We could REALLY speed shadow rays up by
      // 1) bailing out of the Intersect routine when we hit the first thing
      //    not behind us and
      // 2) trying the previous sample's hit object first

      if (!GetShadowing() || // kinda pointless but oh well
          !scene->Intersect(shadowRay, &dist, hit) ||
          dist > toSampleDist)
      {
        // Diffuse component
        scale /= toSampleDist; // normalize scale
        Vec3f hC;
        color += scale * Lcolor & hit->GetDiffuse(hC, isect);

        // Specular component
        // PROBLEM:  
        //   The Specular contribution doesn't scale
        //   linearly with the number of sources, so just do it once.
        if (i==0) {
          Vec3f R = I.V() + 2.0f * (-I.V() * N) * N; // reflected direction
          scale = float(pow(toSample * R, hit->GetSpecularity(isect)));
          color += scale * GetColor() & hit->GetSpecular(hC,isect);
        }
      }
    }
  }
}

