/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This file contains the implementation of the CScene class.          //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
#include "template_defs.hpp"
#include "CMatrix3.hpp"
#include "CImage.hpp"
#include "CSurface.hpp"
#include "CLight.hpp"
#include "CScene.hpp"
#include "CRay.hpp"

#include <algorithm>
using std::for_each;




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CScene                                                  //
//                                                                     //
//  Function : CScene (constructor)                                    //
//                                                                     //
//  Comments : Gives default values to properties and registers them.  //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CScene::CScene()
  : CPropertyObject("General")            ,
    m_renderingModes(ModeNone)            ,
    m_PropSmallReal(0.0001)               ,
    m_PropWidth(320)                      ,
    m_PropHeight(200)                     ,
    m_PropBackColor(0, 0, 0)              ,
    m_PropCameraPos(0, 0, 0)              ,
    m_PropCameraTarget(0, 0, 0)           ,
    m_PropUpVector(0, 0, 1)               ,
    m_PropFocale(50)                      ,
    m_PropDimFilm(MAKE_REAL(0.024))       ,
    m_PropNearClipPlane(10)               ,
    m_PropFarClipPlane(10000)             ,
    m_PropGenRefrIndex(1)                 ,
    m_PropMaxLevel(8)                     ,
    m_PropMinEnergy(0.01)                 ,
    m_PropPatternFile("")                 ,
    m_PropRandomPatternWidth(100)         ,
    m_PropRandomPatternHeight(100)        ,
    m_PropNearestDepth(0)                 ,
    m_PropFarthestDepth(0)                ,
    m_PropNearestMapsTo(0.25)             ,
    m_PropFarthestMapsTo(0.75)            ,
    m_PropReflectionEnabled(1)            ,
    m_PropRefractionEnabled(1)            ,
    m_PropRayOffset(0.01)                 ,
    m_PropShadowType(3)
{
  REGISTER_PROPERTY(SmallReal);
  REGISTER_PROPERTY(Width);
  REGISTER_PROPERTY(Height);
  REGISTER_PROPERTY(BackColor);
  REGISTER_PROPERTY(CameraPos);
  REGISTER_PROPERTY(CameraTarget);
  REGISTER_PROPERTY(UpVector);
  REGISTER_PROPERTY(Focale);
  REGISTER_PROPERTY(DimFilm);
  REGISTER_PROPERTY(NearClipPlane);
  REGISTER_PROPERTY(FarClipPlane);
  REGISTER_PROPERTY(GenRefrIndex);
  REGISTER_PROPERTY(MaxLevel);
  REGISTER_PROPERTY(MinEnergy);
  REGISTER_PROPERTY(PatternFile);
  REGISTER_PROPERTY(RandomPatternWidth);
  REGISTER_PROPERTY(RandomPatternHeight);
  REGISTER_PROPERTY(NearestDepth);
  REGISTER_PROPERTY(FarthestDepth);
  REGISTER_PROPERTY(NearestMapsTo);
  REGISTER_PROPERTY(FarthestMapsTo);
  REGISTER_PROPERTY(ReflectionEnabled);
  REGISTER_PROPERTY(RefractionEnabled);
  REGISTER_PROPERTY(RayOffset);
  REGISTER_PROPERTY(ShadowType);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CScene                                                  //
//                                                                     //
//  Function : ~CScene (destructor)                                    //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CScene::~CScene()
{
  for_each(m_vLights.begin(),
           m_vLights.end(),
           deletor<CLight*>());

  for_each(m_vSurfaces.begin(),
           m_vSurfaces.end(),
           deletor<CSurface*>());
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CScene                                                  //
//                                                                     //
//  Function : init()                                                  //
//                                                                     //
//  Comments : This function does the necessary preprocessing of the   //
//             scene.                                                  //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CScene::init()
{
  for (surface_container::iterator it = m_vSurfaces.begin();
       it != m_vSurfaces.end();
       ++it)
    {
      (*it)->Preprocess();
    }
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CScene                                                  //
//                                                                     //
//  Function : rayTrace()                                              //
//                                                                     //
//  Comments : This function computes the image generated by           //
//             raytracing the scene.                                   //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CScene::rayTrace(CImage *pImage, CImage *pDepthImage /* = NULL */) const
{
  if (pImage == NULL && pDepthImage == NULL)
    return;

  if (pImage)
    pImage->allocate(m_PropWidth, m_PropHeight);

  if (pDepthImage)
    pDepthImage->allocate(m_PropWidth, m_PropHeight);

  // angle of the camera
  //
  //
  //                  lens                      |
  //                    |                       |
  // screen/            |                       y2
  // film               |                       |
  // -+---------+-------+-------+---------------+
  //  |         |<- f ->|<- f ->|
  //  y1                |
  //  |                 |
  //  |
  //
  //  <------ d1 ------>|<--------- d2 --------->
  //
  // From: 1/f   = 1/d1 + 1/d2
  //  and: y1/y2 = d1/d2
  //  and: d1    = DIM_FILM
  //       y2    = (d2/f - 1) * DIM_FILM

  real_t d2 = (m_PropCameraPos - m_PropCameraTarget).norm();
  real_t y2 = (d2 / (m_PropFocale*MAKE_REAL(0.001)) - 1) * (m_PropDimFilm);
  real_t scale = y2 / m_PropHeight;

  CVector N = (m_PropCameraPos - m_PropCameraTarget);
  N.normalize();

  CVector V = (m_PropUpVector - (m_PropUpVector >> N));
  V.normalize();

  CVector U = V ^ N;

  CMatrix3<real_t> rot;
  rot(0,0) = U.x  ; rot(0,1) = U.y  ; rot(0,2) = U.z  ; rot(0,3) = 0 ;
  rot(1,0) = V.x  ; rot(1,1) = V.y  ; rot(1,2) = V.z  ; rot(1,3) = 0 ;
  rot(2,0) = N.x  ; rot(2,1) = N.y  ; rot(2,2) = N.z  ; rot(2,3) = 0 ;
  rot(3,0) = 0    ; rot(3,1) = 0    ; rot(3,2) = 0    ; rot(3,3) = 1 ;

  int     i, j;
  real_t  x, y;
  CRay    ray;

  ray.m_Origin     = m_PropCameraPos;
  ray.m_Level      = 0;
  ray.m_Energy     = 1;
  ray.m_RefrIndex  = m_PropGenRefrIndex;

  for (j = 0, y = (m_PropHeight / 2)*scale;
       j < m_PropHeight;
       y -= scale , ++j)
    {
      for (i = 0, x = -(m_PropWidth / 2)*scale;
           i < m_PropWidth;
           x += scale , ++i)
        {
          ray.m_Direction.x = x;
          ray.m_Direction.y = y;
          ray.m_Direction.z = -((m_PropCameraTarget - m_PropCameraPos).norm());

          ray.m_Direction.normalize();
          ray.m_Direction *= rot;

          if (pDepthImage == NULL)
            {
              POP_ASSERT(pImage != NULL,
                         "pDepthImage and pImage should not be both NULL.");
              (*pImage)(i,j) = ray.colorize(*this);
            }
          else
            {
              real_t t;
              CColor pixelColor = ray.colorize(*this, &t);
              (*pDepthImage)(i,j) = CColor(t,t,t);
              if (pImage)
                (*pImage)(i,j) = pixelColor;
            }
        }
    }
}
