/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This file contains the implementation of the CPlane class.          //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
#include "popassert.hpp"
#include "portable_gl.h"
#include <iostream>
#include <string>
#include "auto_array_ptr.hpp"
#include "CPlane.hpp"
#include "CRay.hpp"
using std::ostream;
using std::string;




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// Class    : CPlane                                                   //
//                                                                     //
// Function : CPlane (constructor)                                     //
//                                                                     //
// Comments : Default constructor, just registers properties and give  //
//            default values for them.                                 //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CPlane::CPlane(const string& strName)
  : CConcreteSurface(strName, false),
    m_PropNormal(0,0,1),
    m_PropConstant(0),
    m_PropSize(1000),
    m_PropPrecisionU(10),
    m_PropPrecisionV(10)
{
  REGISTER_PROPERTY(Normal);
  REGISTER_PROPERTY(Constant);
  REGISTER_PROPERTY(Size);
  REGISTER_PROPERTY(PrecisionU);
  REGISTER_PROPERTY(PrecisionV);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : Preprocess()                                            //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CPlane::Preprocess()
{
  CMatrix<real_t, 4, 1> planeBefore;

  planeBefore(0,0) = m_PropNormal.x;
  planeBefore(1,0) = m_PropNormal.y;
  planeBefore(2,0) = m_PropNormal.z;
  planeBefore(3,0) = m_PropConstant;

  CMatrix<real_t, 4, 1> planeAfter(m_InverseTransformation*planeBefore);

  m_PropNormal.x = planeAfter(0,0);
  m_PropNormal.y = planeAfter(1,0);
  m_PropNormal.z = planeAfter(2,0);
  m_PropConstant = planeAfter(3,0);

  m_PropNormal.normalize();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : Intersect()                                             //
//                                                                     //
//  Comments : This function fills a container with intersections      //
//             between the ray and the scene object.  It returns the   //
//             number of hits.                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
int CPlane::Intersect(const CRay& ray, hitcontainer& hits) const
{
  hits = CSurface::hitcontainer();

  // From http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm
  real_t Vd = m_PropNormal * ray.m_Direction;

  if (Vd != 0.0) {
    hits.push_back(-(m_PropNormal*ray.m_Origin + m_PropConstant) / Vd);
  }

  return hits.size();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : Normal()                                                //
//                                                                     //
//  Comments : This function calculates the normal vector at the       //
//             given point.                                            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CPlane::Normal(const CVector& hit, CVector& normal) const
{
  normal = m_PropNormal;
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : GenerateShaderForObject()                               //
//                                                                     //
//  Comments : This function generates the shader code describing a    //
//             specific plane.                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CPlane::GenerateShaderForObject(ostream& o) const
{
  CConcreteSurface::GenerateShaderForObject(o, "Plane");

  o << ",\n    " << "vec4("
    << m_PropNormal.x << " , "
    << m_PropNormal.y << " , "
    << m_PropNormal.z << " , "
    << m_PropConstant << ")" << "\n);\n\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : generateShaderForClass() (static)                       //
//                                                                     //
//  Comments : This function generates the shader code needed to       //
//             render a plane.                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CPlane::generateShaderForClass(ostream& o)
{
 o <<
    "// Plane specific code\n"
   "struct Plane {\n"
   "    Surface surface;\n"
   "    vec4    params;\n"
   "};\n"
   "\n"
   "Intersection intersect(Plane plane, Ray ray)\n"
   "{\n"
   "    Intersection result;\n"
   "\n"
   "    // From http://www.siggraph.org/education/materials/HyperGraph/raytrace/rayplane_intersection.htm\n"
   "    float Vd = dot(plane.params.xyz, normalize(ray.direction));\n"
   "\n"
   "    if (Vd != 0.0) {\n"
   "        // Using dot(plane.params, vec4(ray.origin, 1.0)) does not\n"
   "        // seem to work in some cases for no apparent reason, so we\n"
   "        // compute the dot product without using the built-in\n"
   "        // function.\n"
   "        // float tempDot = dot(plane.params, vec4(ray.origin, 1.0));\n"
   "        float tempDot = dot(plane.params.xyz, ray.origin) + plane.params.w;\n"
   "        float distance = -(tempDot) / Vd;\n"
   "\n"
   "        result.surface  = plane.surface;\n"
   "        result.distance = distance;\n"
   "        result.normal   = normalize(plane.params.xyz);\n"
   "    }\n"
   "    else {\n"
   "        result.distance = -1.0;\n"
   "    }\n"
   "\n"
   "    return result;\n"
   "}\n"
   "\n"
   "\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CPlane                                                  //
//                                                                     //
//  Function : ConcreteDrawGL()                                        //
//                                                                     //
//  Comments : This function renders the surface in OpenGL.            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CPlane::ConcreteDrawGL() const
{
  CVector tempVector, planeAxis1, planeAxis2, planeAxis3;

  // Get vector that is the most "perpendicular" to the normal vector
  // between the three axis.
  planeAxis1 = m_PropNormal ^ CVector(1,0,0);
  tempVector = m_PropNormal ^ CVector(0,1,0);

  if (tempVector.norm() > planeAxis1.norm())
    planeAxis1 = tempVector;

  tempVector = m_PropNormal ^ CVector(0,0,1);
  if (tempVector.norm() > planeAxis1.norm())
    planeAxis1 = tempVector;

  // Get the second plane axis
  planeAxis2 = m_PropNormal ^ planeAxis1;

  // The third plane axis represent the offset from the origin
  planeAxis3 = -m_PropNormal * m_PropConstant / m_PropNormal.norm();

  // Scale the plane so it has the right size
  planeAxis1 *= m_PropSize / planeAxis1.norm();
  planeAxis2 *= m_PropSize / planeAxis2.norm();


// This is just a macro that assign a vector according to the value in
// the loop
#define ASSIGN_TO_VECTOR_PLANE(v,i,j)                                \
  v = (MAKE_REAL(-0.5) + ((i)/(real_t)(m_PropPrecisionU-1)))*planeAxis1 + \
      (MAKE_REAL(-0.5) + ((j)/(real_t)(m_PropPrecisionV-1)))*planeAxis2 + \
      planeAxis3;


  auto_array_ptr<CVector> tmpVects(new CVector[m_PropPrecisionU]);
  CVector v1, v2;

  for (int j = 0; j < m_PropPrecisionV-1; ++j)
    {
      glNormal3d(m_PropNormal.x, m_PropNormal.y, m_PropNormal.z);
      glBegin(GL_QUAD_STRIP);
      for (int i = 0; i < m_PropPrecisionU; ++i)
        {
          if (j > 0)
            {
              v1 = tmpVects[i];
            }
          else
            {
              ASSIGN_TO_VECTOR_PLANE(v1,i,j)
            }

          ASSIGN_TO_VECTOR_PLANE(v2,i,j+1)

          tmpVects[i] = v2;

          // The vertices must be drawn in this order for the front
          // face to be outside
          glVertex3d(v2.x, v2.y, v2.z);
          glVertex3d(v1.x, v1.y, v1.z);
        }
      glEnd();
    }
}
