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




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// Class    : CTriangle                                                //
//                                                                     //
// Function : CTriangle (constructor)                                  //
//                                                                     //
// Comments : Default constructor, just registers properties and give  //
//            default values for them.                                 //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CTriangle::CTriangle(const string& strName)
  : CConcreteSurface(strName, false),
    m_PropVertex0(1, 0, 0),
    m_PropVertex1(0, 1, 0),
    m_PropVertex2(0, 0, 1)
{
  REGISTER_PROPERTY(Vertex0);
  REGISTER_PROPERTY(Vertex1);
  REGISTER_PROPERTY(Vertex2);
}




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

  // Process points

  // Vertex 0
  tempMatrix(0,0) = m_PropVertex0.x;
  tempMatrix(1,0) = m_PropVertex0.y;
  tempMatrix(2,0) = m_PropVertex0.z;
  tempMatrix(3,0) = 1;
  tempMatrix = m_Transformation.transpose() * tempMatrix;
  m_PropVertex0.x = tempMatrix(0,0);
  m_PropVertex0.y = tempMatrix(1,0);
  m_PropVertex0.z = tempMatrix(2,0);
  m_PropVertex0  /= tempMatrix(3,0);

  // Vertex 1
  tempMatrix(0,0) = m_PropVertex1.x;
  tempMatrix(1,0) = m_PropVertex1.y;
  tempMatrix(2,0) = m_PropVertex1.z;
  tempMatrix(3,0) = 1;
  tempMatrix = m_Transformation.transpose() * tempMatrix;
  m_PropVertex1.x = tempMatrix(0,0);
  m_PropVertex1.y = tempMatrix(1,0);
  m_PropVertex1.z = tempMatrix(2,0);
  m_PropVertex1  /= tempMatrix(3,0);

  // Vertex 2
  tempMatrix(0,0) = m_PropVertex2.x;
  tempMatrix(1,0) = m_PropVertex2.y;
  tempMatrix(2,0) = m_PropVertex2.z;
  tempMatrix(3,0) = 1;
  tempMatrix = m_Transformation.transpose() * tempMatrix;
  m_PropVertex2.x = tempMatrix(0,0);
  m_PropVertex2.y = tempMatrix(1,0);
  m_PropVertex2.z = tempMatrix(2,0);
  m_PropVertex2  /= tempMatrix(3,0);


  // Compute helper variables
  m_Normal = (m_PropVertex1 - m_PropVertex0) ^ (m_PropVertex2 - m_PropVertex0);

#if USE_INF4702_RAY_TRIANGLE_ALGORITHM
  m_D      = - (m_PropVertex0 * m_Normal);
  if (ABS(m_Normal.x) > ABS(m_Normal.y) && ABS(m_Normal.x) > ABS(m_Normal.z)) {
    m_T1 =
      (m_PropVertex1.y - m_PropVertex0.y) *
      (m_PropVertex2.z - m_PropVertex0.z) -
      (m_PropVertex1.z - m_PropVertex0.z) *
      (m_PropVertex2.y - m_PropVertex0.y);

    m_MaxDirection = DIRECTION_X;
  }
  else if (ABS(m_Normal.y) > ABS(m_Normal.z)) {
    m_T1 =
      (m_PropVertex1.z - m_PropVertex0.z) *
      (m_PropVertex2.x - m_PropVertex0.x) -
      (m_PropVertex1.x - m_PropVertex0.x) *
      (m_PropVertex2.z - m_PropVertex0.z);

    m_MaxDirection = DIRECTION_Y;
  }
  else {
    m_T1 =
      (m_PropVertex1.x - m_PropVertex0.x) *
      (m_PropVertex2.y - m_PropVertex0.y) -
      (m_PropVertex1.y - m_PropVertex0.y) *
      (m_PropVertex2.x - m_PropVertex0.x);

    m_MaxDirection = DIRECTION_Z;
  }

  m_T2 = m_T1;
  if (m_T1 > 0)
    m_T1 = 0;
  else
    m_T2 = 0;
#endif
}




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

#if USE_INF4702_RAY_TRIANGLE_ALGORITHM
  // This is the intersection algorithm as seen in the INF4702
  // ("Complement d'infographie") course
  real_t b = m_Normal * ray.m_Direction;

  if (b != 0)
    {
      real_t t = - (m_D + m_Normal * ray.m_Origin) / b;
      CVector hitPoint = ray.m_Origin + t * ray.m_Direction;

      // Check with every edge of the triangle
      switch (m_MaxDirection) {
      case DIRECTION_X:
        {
          real_t beta =
            (m_PropVertex2.y - m_PropVertex1.y) *
            (hitPoint.z      - m_PropVertex1.z) -
            (m_PropVertex2.z - m_PropVertex1.z) *
            (hitPoint.y      - m_PropVertex1.y);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex0.y - m_PropVertex2.y) *
            (hitPoint.z      - m_PropVertex2.z) -
            (m_PropVertex0.z - m_PropVertex2.z) *
            (hitPoint.y      - m_PropVertex2.y);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex1.y - m_PropVertex0.y) *
            (hitPoint.z      - m_PropVertex0.z) -
            (m_PropVertex1.z - m_PropVertex0.z) *
            (hitPoint.y      - m_PropVertex0.y);

          if (beta < m_T1 || beta > m_T2)
            break;

          hits.push_back(t);
        }
        break;
      case DIRECTION_Y:
        {
          real_t beta =
            (m_PropVertex2.z - m_PropVertex1.z) *
            (hitPoint.x      - m_PropVertex1.x) -
            (m_PropVertex2.x - m_PropVertex1.x) *
            (hitPoint.z      - m_PropVertex1.z);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex0.z - m_PropVertex2.z) *
            (hitPoint.x      - m_PropVertex2.x) -
            (m_PropVertex0.x - m_PropVertex2.x) *
            (hitPoint.z      - m_PropVertex2.z);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex1.z - m_PropVertex0.z) *
            (hitPoint.x      - m_PropVertex0.x) -
            (m_PropVertex1.x - m_PropVertex0.x) *
            (hitPoint.z      - m_PropVertex0.z);

          if (beta < m_T1 || beta > m_T2)
            break;

          hits.push_back(t);
        }
        break;
      case DIRECTION_Z:
        {
          real_t beta =
            (m_PropVertex2.x - m_PropVertex1.x) *
            (hitPoint.y      - m_PropVertex1.y) -
            (m_PropVertex2.y - m_PropVertex1.y) *
            (hitPoint.x      - m_PropVertex1.x);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex0.x - m_PropVertex2.x) *
            (hitPoint.y      - m_PropVertex2.y) -
            (m_PropVertex0.y - m_PropVertex2.y) *
            (hitPoint.x      - m_PropVertex2.x);

          if (beta < m_T1 || beta > m_T2)
            break;

          beta =
            (m_PropVertex1.x - m_PropVertex0.x) *
            (hitPoint.y      - m_PropVertex0.y) -
            (m_PropVertex1.y - m_PropVertex0.y) *
            (hitPoint.x      - m_PropVertex0.x);

          if (beta < m_T1 || beta > m_T2)
            break;

          hits.push_back(t);
        }
        break;
      }
    }

#else

  // This comes from
  // http://www.cs.virginia.edu/~gfx/Courses/2003/ImageSynthesis/papers/Acceleration/Fast%20MinimumStorage%20RayTriangle%20Intersection.pdf#search=%22ray%20triangle%20intersection%22

  // find vectors for two edges sharing vert0
  CVector edge1 = m_PropVertex1 - m_PropVertex0;
  CVector edge2 = m_PropVertex2 - m_PropVertex0;

  // begin calculating determinant - also used to calculate U parameter
  CVector pvec = ray.m_Direction ^ edge2;

  // if determinant is near zero, ray lies in plane of triangle
  real_t det = edge1 * pvec;

  if (det == 0)
    return 0;

  real_t inv_det = 1 / det;

  // calculate distance from vert0 to ray origin
  CVector tvec = ray.m_Origin - m_PropVertex0;

  // calculate U parameter and test bounds
  real_t u = (tvec * pvec) * inv_det;
  if (u < 0 || u > 1)
    return 0;

  // prepare to test V parameter
  CVector qvec = tvec ^ edge1;

  // calculate V parameter and test bounds
  real_t v = (ray.m_Direction * qvec) * inv_det;
  if (v < 0 || u + v > 1)
    return 0;

  hits.push_back((edge2 * qvec) * inv_det);

#endif

  return hits.size();
}




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




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

  o << ",\n"
    << "    " << "vec3("
    << m_PropVertex0.x << " , "
    << m_PropVertex0.y << " , "
    << m_PropVertex0.z << "),\n"
    << "    " << "vec3("
    << m_PropVertex1.x << " , "
    << m_PropVertex1.y << " , "
    << m_PropVertex1.z << "),\n"
    << "    " << "vec3("
    << m_PropVertex2.x << " , "
    << m_PropVertex2.y << " , "
    << m_PropVertex2.z << ")"
    << "\n);\n\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CTriangle                                               //
//                                                                     //
//  Function : generateShaderForClass() (static)                       //
//                                                                     //
//  Comments : This functions generates the shader code needed to      //
//             render a triangle.                                      //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CTriangle::generateShaderForClass(ostream& o)
{
  o <<
    "// Triangle specific code\n"
    "struct Triangle {\n"
    "    Surface surface;\n"
    "    vec3    vertex0;\n"
    "    vec3    vertex1;\n"
    "    vec3    vertex2;\n"
    "};\n"
    "\n"
    "Intersection intersect(Triangle triangle, Ray ray)\n"
    "{\n"
    "    Intersection result;\n"
    "    result.distance = -1.0;\n"
    "\n"
    "    // find vectors for two edges sharing vert0\n"
    "    vec3 edge1 = triangle.vertex1 - triangle.vertex0;\n"
    "    vec3 edge2 = triangle.vertex2 - triangle.vertex0;\n"
    "\n"
    "    // begin calculating determinant - also used to calculate U parameter\n"
    "    vec3 pvec = cross(ray.direction, edge2);\n"
    "\n"
    "    // if determinant is near zero, ray lies in plane of triangle\n"
    "    float det = dot(edge1, pvec);\n"
    "\n"
    "    if (det != 0.0) {\n"
    "        float inv_det = 1.0 / det;\n"
    "\n"
    "        // calculate distance from vert0 to ray origin\n"
    "        vec3 tvec = ray.origin - triangle.vertex0;\n"
    "\n"
    "        // calculate U parameter and test bounds\n"
    "        float u = dot(tvec, pvec) * inv_det;\n"
    "        if ( !(u < 0.0 || u > 1.0) ) {\n"
    "            // prepare to test V parameter\n"
    "            vec3 qvec = cross(tvec, edge1);\n"
    "\n"
    "            // calculate V parameter and test bounds\n"
    "            float v = dot(ray.direction, qvec) * inv_det;\n"
    "            if ( !(v < 0.0 || u + v > 1.0) ) {\n"
    "                result.surface  = triangle.surface;\n"
    "                result.distance = dot(edge2, qvec) * inv_det;\n"
    "                result.normal   = normalize(cross(edge1, edge2));\n"
    "            }\n"
    "        }\n"
    "    }\n"
    "\n"
    "    return result;\n"
    "}\n"
    "\n"
    "\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CTriangle                                               //
//                                                                     //
//  Function : ConcreteDrawGL()                                        //
//                                                                     //
//  Comments : This function renders the surface in OpenGL.            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CTriangle::ConcreteDrawGL() const
{
  glBegin(GL_TRIANGLES);
  glNormal3d(m_Normal.x, m_Normal.y, m_Normal.z);
  glVertex3d(m_PropVertex0.x, m_PropVertex0.y, m_PropVertex0.z);
  glVertex3d(m_PropVertex1.x, m_PropVertex1.y, m_PropVertex1.z);
  glVertex3d(m_PropVertex2.x, m_PropVertex2.y, m_PropVertex2.z);
  glEnd();
}
