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




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// Class    : CQuadric                                                 //
//                                                                     //
// Function : CQuadric (constructor)                                   //
//                                                                     //
// Comments : Default constructor, just registers properties and give  //
//            default values for them.                                 //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CQuadric::CQuadric(const string& strName)
  : CConcreteSurface(strName, false),
    m_PropQuadratic(1,1,1),
    m_PropLinear(0,0,0),
    m_PropMix(0,0,0),
    m_PropConstant(-1),
    m_PropSmallReal(0.000001)
{
  REGISTER_PROPERTY(Quadratic);
  REGISTER_PROPERTY(Linear);
  REGISTER_PROPERTY(Mix);
  REGISTER_PROPERTY(Constant);
  REGISTER_PROPERTY(SmallReal);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CQuadric                                                //
//                                                                     //
//  Function : Preprocess()                                            //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CQuadric::Preprocess()
{
  CSquareMatrix<real_t,4> FirstMat;

  FirstMat(0,0) = m_PropQuadratic.x;
  FirstMat(0,1) = m_PropMix.z    / 2;
  FirstMat(0,2) = m_PropMix.y    / 2;
  FirstMat(0,3) = m_PropLinear.x / 2;

  FirstMat(1,0) = m_PropMix.z    / 2;
  FirstMat(1,1) = m_PropQuadratic.y;
  FirstMat(1,2) = m_PropMix.x    / 2;
  FirstMat(1,3) = m_PropLinear.y / 2;

  FirstMat(2,0) = m_PropMix.y    / 2;
  FirstMat(2,1) = m_PropMix.x    / 2;
  FirstMat(2,2) = m_PropQuadratic.z;
  FirstMat(2,3) = m_PropLinear.z / 2;

  FirstMat(3,0) = m_PropLinear.x / 2;
  FirstMat(3,1) = m_PropLinear.y / 2;
  FirstMat(3,2) = m_PropLinear.z / 2;
  FirstMat(3,3) = m_PropConstant;

  CSquareMatrix<real_t,4> AfterMat;
  AfterMat = m_InverseTransformation *
             FirstMat                *
             m_InverseTransformation.transpose();

  m_PropQuadratic.x    = AfterMat(0,0);
  m_PropQuadratic.y    = AfterMat(1,1);
  m_PropQuadratic.z    = AfterMat(2,2);

  m_PropMix.x          = AfterMat(1,2) * 2;
  m_PropMix.y          = AfterMat(0,2) * 2;
  m_PropMix.z          = AfterMat(0,1) * 2;

  m_PropLinear.x       = AfterMat(0,3) * 2;
  m_PropLinear.y       = AfterMat(1,3) * 2;
  m_PropLinear.z       = AfterMat(2,3) * 2;

  m_PropConstant       = AfterMat(3,3);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CQuadric                                                //
//                                                                     //
//  Function : Intersect()                                             //
//                                                                     //
//  Comments : This function fills a container with intersections      //
//             between the ray and the scene object.  It returns the   //
//             number of hits.                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
int CQuadric::Intersect(const CRay& ray, hitcontainer& hits) const
{
  real_t acoef; // square term coef
  real_t bcoef; // linear term coef
  real_t ccoef; // constant term coef

  // The intersection of a ray and a quadric is a quadratic polynom
  // (0, 1 or 2 solutions)

  acoef =
    m_PropQuadratic.x * ray.m_Direction.x * ray.m_Direction.x +
    m_PropQuadratic.y * ray.m_Direction.y * ray.m_Direction.y +
    m_PropQuadratic.z * ray.m_Direction.z * ray.m_Direction.z +
    m_PropMix.x * ray.m_Direction.y * ray.m_Direction.z +
    m_PropMix.y * ray.m_Direction.x * ray.m_Direction.z +
    m_PropMix.z * ray.m_Direction.x * ray.m_Direction.y;

  bcoef =
    2 * m_PropQuadratic.x * ray.m_Origin.x * ray.m_Direction.x +
    2 * m_PropQuadratic.y * ray.m_Origin.y * ray.m_Direction.y +
    2 * m_PropQuadratic.z * ray.m_Origin.z * ray.m_Direction.z +
    m_PropMix.x * ( ray.m_Origin.y * ray.m_Direction.z +
                    ray.m_Origin.z * ray.m_Direction.y ) +
    m_PropMix.y * ( ray.m_Origin.x * ray.m_Direction.z +
                    ray.m_Origin.z * ray.m_Direction.x ) +
    m_PropMix.z * ( ray.m_Origin.x * ray.m_Direction.y +
                    ray.m_Origin.y * ray.m_Direction.x ) +
    m_PropLinear.x * ray.m_Direction.x +
    m_PropLinear.y * ray.m_Direction.y +
    m_PropLinear.z * ray.m_Direction.z;

  ccoef =
    m_PropQuadratic.x * ray.m_Origin.x * ray.m_Origin.x +
    m_PropQuadratic.y * ray.m_Origin.y * ray.m_Origin.y +
    m_PropQuadratic.z * ray.m_Origin.z * ray.m_Origin.z +
    m_PropMix.x * ray.m_Origin.y * ray.m_Origin.z +
    m_PropMix.y * ray.m_Origin.x * ray.m_Origin.z +
    m_PropMix.z * ray.m_Origin.x * ray.m_Origin.y +
    m_PropLinear.x * ray.m_Origin.x +
    m_PropLinear.y * ray.m_Origin.y +
    m_PropLinear.z * ray.m_Origin.z +
    m_PropConstant;


  hits = CSurface::hitcontainer();

  if (ABS(acoef) < m_PropSmallReal)
    {
      // Linear equation
      if (ABS(bcoef) >= m_PropSmallReal)
        {
          hits.push_back( -ccoef / bcoef );
        }
    }
  else
    {
      // Quadratic equation

      // Check for negative, null or positive delta
      real_t fDelta = bcoef * bcoef - 4 * acoef * ccoef;

      if (fDelta >= m_PropSmallReal)
        {
          // Positive delta
          if (acoef > 0)
            {
              hits.push_back( ( -bcoef-sqrt(fDelta) ) / ( 2*acoef ) );
              hits.push_back( ( -bcoef+sqrt(fDelta) ) / ( 2*acoef ) );
            }
          else if (acoef < 0)
            {
              hits.push_back( ( -bcoef+sqrt(fDelta) ) / ( 2*acoef ) );
              hits.push_back( ( -bcoef-sqrt(fDelta) ) / ( 2*acoef ) );
            }
        }
      else if (fDelta > -m_PropSmallReal)
        {
          // Null delta: only one solution, add it twice
          hits.push_back( -bcoef / (2*acoef) );
          hits.push_back( -bcoef / (2*acoef) );
        }
    }

  return ( hits.size() );
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CQuadric                                                //
//                                                                     //
//  Function : Normal()                                                //
//                                                                     //
//  Comments : This function calculates the normal vector at the       //
//             given point.                                            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CQuadric::Normal(const CVector& hit, CVector& normal) const
{
  normal.x =
    2 * m_PropQuadratic.x * hit.x +
    m_PropMix.y * hit.z +
    m_PropMix.z * hit.y +
    m_PropLinear.x;

  normal.y =
    2 * m_PropQuadratic.y * hit.y +
    m_PropMix.x * hit.z +
    m_PropMix.z * hit.x +
    m_PropLinear.y;

  normal.z =
    2 * m_PropQuadratic.z * hit.z +
    m_PropMix.x * hit.y +
    m_PropMix.y * hit.x +
    m_PropLinear.z;

  normal.normalize();
}




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

  o << ",\n    vec3("
    << m_PropQuadratic.x << " , "
    << m_PropQuadratic.y << " , "
    << m_PropQuadratic.z << "),\n    vec3("
    << m_PropLinear.x << " , "
    << m_PropLinear.y << " , "
    << m_PropLinear.z << "),\n    vec3("
    << m_PropMix.x << " , "
    << m_PropMix.y << " , "
    << m_PropMix.z << "),\n    "
    << m_PropConstant << "\n);\n\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CQuadric                                                //
//                                                                     //
//  Function : generateShaderForClass() (static)                       //
//                                                                     //
//  Comments : This functions generates the shader code needed to      //
//             render a quadric.                                       //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CQuadric::generateShaderForClass(ostream& o)
{
  o <<
    "// Quadric specific code\n"
    "struct Quadric {\n"
    "    Surface surface;\n"
    "    vec3    quadratic;\n"
    "    vec3    linear;\n"
    "    vec3    mix;\n"
    "    float   constant;\n"
    "};\n"
    "\n"
    "Intersection intersect(Quadric quadric, Ray ray)\n"
    "{\n"
    "    Intersection result;\n"
    "\n"
    "    // The intersection of a ray and a quadric is a quadratic polynom\n"
    "    // (0, 1 or 2 solutions)\n"
    "\n"
    "    // Square term coef\n"
    "    float acoef =\n"
    "        quadric.quadratic.x * ray.direction.x * ray.direction.x +\n"
    "        quadric.quadratic.y * ray.direction.y * ray.direction.y +\n"
    "        quadric.quadratic.z * ray.direction.z * ray.direction.z +\n"
    "        quadric.mix.x * ray.direction.y * ray.direction.z +\n"
    "        quadric.mix.y * ray.direction.x * ray.direction.z +\n"
    "        quadric.mix.z * ray.direction.x * ray.direction.y;\n"
    "\n"
    "    float bcoef =\n"
    "        2.0 * quadric.quadratic.x * ray.origin.x * ray.direction.x +\n"
    "        2.0 * quadric.quadratic.y * ray.origin.y * ray.direction.y +\n"
    "        2.0 * quadric.quadratic.z * ray.origin.z * ray.direction.z +\n"
    "        quadric.mix.x * ( ray.origin.y * ray.direction.z +\n"
    "                        ray.origin.z * ray.direction.y ) +\n"
    "        quadric.mix.y * ( ray.origin.x * ray.direction.z +\n"
    "                        ray.origin.z * ray.direction.x ) +\n"
    "        quadric.mix.z * ( ray.origin.x * ray.direction.y +\n"
    "                        ray.origin.y * ray.direction.x ) +\n"
    "        quadric.linear.x * ray.direction.x +\n"
    "        quadric.linear.y * ray.direction.y +\n"
    "        quadric.linear.z * ray.direction.z;\n"
    "\n"
    "    float ccoef =\n"
    "        quadric.quadratic.x * ray.origin.x * ray.origin.x +\n"
    "        quadric.quadratic.y * ray.origin.y * ray.origin.y +\n"
    "        quadric.quadratic.z * ray.origin.z * ray.origin.z +\n"
    "        quadric.mix.x * ray.origin.y * ray.origin.z +\n"
    "        quadric.mix.y * ray.origin.x * ray.origin.z +\n"
    "        quadric.mix.z * ray.origin.x * ray.origin.y +\n"
    "        quadric.linear.x * ray.origin.x +\n"
    "        quadric.linear.y * ray.origin.y +\n"
    "        quadric.linear.z * ray.origin.z +\n"
    "        quadric.constant;\n"
    "\n"
    "    float delta = bcoef*bcoef - 4.0*acoef*ccoef;\n"
    "\n"
    "    if (delta > 0.0) {\n"
    "        float a1 = (-bcoef + sqrt(delta)) / (2.0*acoef);\n"
    "        float a2 = (-bcoef - sqrt(delta)) / (2.0*acoef);\n"
    "\n"
    "        float distance = min(a1,a2);\n"
    "        if (distance < 0.0)\n"
    "            distance = max(a1,a2);\n"
    "        if (distance < 0.0) {\n"
    "            result.distance = -1.0;\n"
    "        }\n"
    "        else {\n"
    "            result.distance = distance;\n"
    "            result.surface  = quadric.surface;\n"
    "\n"
    "            vec3 hitPoint = ray.origin + distance * ray.direction;\n"
    "            result.normal = normalize(vec3(\n"
    "                2.0*quadric.quadratic.x * hitPoint.x +\n"
    "                quadric.mix.y * hitPoint.z +\n"
    "                quadric.mix.z * hitPoint.y +\n"
    "                quadric.linear.x,\n"
    "                2.0*quadric.quadratic.y * hitPoint.y +\n"
    "                quadric.mix.x * hitPoint.z +\n"
    "                quadric.mix.z * hitPoint.x +\n"
    "                quadric.linear.y,\n"
    "                2.0*quadric.quadratic.z * hitPoint.z +\n"
    "                quadric.mix.x * hitPoint.y +\n"
    "                quadric.mix.y * hitPoint.x +\n"
    "                quadric.linear.z));\n"
    "        }\n"
    "    }\n"
    "    else\n"
    "        result.distance = -1.0;\n"
    "\n"
    "\n"
    "    return result;\n"
    "}\n"
    "\n"
    "\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CQuadric                                                //
//                                                                     //
//  Function : ConcreteDrawGL()                                        //
//                                                                     //
//  Comments : This function renders the surface in OpenGL.            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CQuadric::ConcreteDrawGL() const
{
  POP_DEBUG("OpenGL quadric rendering not implemented yet.");
}
