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




/////////////////////////////////////////////////////////////////////////
//                                                                     //
// Class    : CSphere                                                  //
//                                                                     //
// Function : CSphere (constructor)                                    //
//                                                                     //
// Comments : Default constructor, just registers properties and give  //
//            default values for them.                                 //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CSphere::CSphere(const string& strName)
  : CConcreteSurface(strName),
    m_PropCenter(0, 0, 0),
    m_PropRadius(1),
    m_PropPrecisionU(10),
    m_PropPrecisionV(10),
    m_PropSmallReal(0.000001)
{
  REGISTER_PROPERTY(Center);
  REGISTER_PROPERTY(Radius);
  REGISTER_PROPERTY(PrecisionU);
  REGISTER_PROPERTY(PrecisionV);
  REGISTER_PROPERTY(SmallReal);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CSphere                                                 //
//                                                                     //
//  Function : Preprocess()                                            //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CSphere::Preprocess()
{
}




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

  CVector dst  = ray.m_Origin - m_PropCenter;
  CVector dir  = ray.m_Direction;

  // The algorithm expects a unit vector
  real_t origNorm = dir.norm();
  dir /= origNorm;

  real_t B = dst * dir;
  real_t C = dst * dst - m_PropRadius * m_PropRadius;
  real_t D = B * B - C;

  if (D > m_PropSmallReal) {
    // Because dir is normalized, we have to divide the result by the
    // original direction length
    real_t distance1 = -B - sqrt(D);
    real_t distance2 = -B + sqrt(D);

    hits.push_back(distance1 / origNorm);
    hits.push_back(distance2 / origNorm);
  }

  return hits.size();
}




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




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

  o << ",\n    " << m_PropRadius << ",\n"
    << "    " << "vec3("
    << m_PropCenter.x << " , "
    << m_PropCenter.y << " , "
    << m_PropCenter.z << ")" << "\n);\n\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CSphere                                                 //
//                                                                     //
//  Function : generateShaderForClass() (static)                       //
//                                                                     //
//  Comments : This functions generates the shader code needed to      //
//             render a sphere.                                        //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CSphere::generateShaderForClass(ostream& o)
{
  o <<
    "// Sphere specific code\n"
    "struct Sphere {\n"
    "    Surface surface;\n"
    "    float   radius;\n"
    "    vec3    center;\n"
    "};\n"
    "\n"
    "Intersection intersect(Sphere sphere, Ray ray)\n"
    "{\n"
    "    Intersection result;\n"
    "\n"
    "    // From http://www.devmaster.net/forums/showthread.php?t=413\n"
    "    vec3 dst = ray.origin - sphere.center;\n"
    "    float B = dot(dst,normalize(ray.direction));\n"
    "    float C = dot(dst,dst) - sphere.radius*sphere.radius;\n"
    "    float D = B*B - C;\n"
    "\n"
    "    if (D > sphere.radius) {\n"
    "        float distance = - B - sqrt(D);\n"
    "        if (distance < 0.0)\n"
    "            distance = - B + sqrt(D);\n"
    "        if (distance < 0.0)\n"
    "            distance = -1.0;\n"
    "\n"
    "        result.distance = distance;\n"
    "        result.surface  = sphere.surface;\n"
    "        result.normal   = normalize(ray.origin                    +\n"
    "                                    result.distance*ray.direction -\n"
    "                                    sphere.center);\n"
    "    }\n"
    "    else {\n"
    "        result.distance = -1.0;\n"
    "    }\n"
    "\n"
    "    return result;\n"
    "}\n"
    "\n"
    "\n";
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CSphere                                                 //
//                                                                     //
//  Function : ConcreteDrawGL()                                        //
//                                                                     //
//  Comments : This function renders the surface in OpenGL.            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CSphere::ConcreteDrawGL() const
{
  // No need to push matrix, will be done already to set the
  // transformation matrix
  glTranslated(m_PropCenter.x, m_PropCenter.y, m_PropCenter.z);
  glScaled(m_PropRadius, m_PropRadius, m_PropRadius);

  DrawFace(); // Right
  glRotatef(90, 1, 0, 0);
  DrawFace(); // Top
  glRotatef(90, 1, 0, 0);
  DrawFace(); // Left
  glRotatef(90, 1, 0, 0);
  DrawFace(); // Bottom
  glRotatef(90, 0, 1, 0);
  DrawFace(); // Back
  glRotatef(180, 0, 1, 0);
  DrawFace(); // Front
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CSphere                                                 //
//                                                                     //
//  Function : DrawFace()                                              //
//                                                                     //
//  Comments : This function renders only one face of the sphere.  It  //
//             is called six times by the DrawGL() functions to render //
//             the whole sphere.                                       //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CSphere::DrawFace() const
{
// This is just a macro that assign a vector according to the value in the loop
#define ASSIGN_TO_VECTOR_SPHERE(v,i,j)                        \
  v.x = MAKE_REAL(-0.5) + ((i)/(real_t)(m_PropPrecisionU-1)); \
  v.y = MAKE_REAL(-0.5) + ((j)/(real_t)(m_PropPrecisionV-1)); \
  v.z = MAKE_REAL(0.5);                                       \
  v.normalize();


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

  for (int j = 0; j < m_PropPrecisionV-1; ++j)
    {
      glBegin(GL_QUAD_STRIP);
      for (int i = 0; i < m_PropPrecisionU; ++i)
        {
          if (j > 0)
            {
              v1 = tmpVects[i];
            }
          else
            {
              ASSIGN_TO_VECTOR_SPHERE(v1,i,j)
            }

          ASSIGN_TO_VECTOR_SPHERE(v2,i,j+1)

          tmpVects[i] = v2;

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