/////////////////////////////////////////////////////////////////////////
//                                                                     //
// This file contains the implementation of the CBooleanSurface class. //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
#include "popassert.hpp"
#include "portable_gl.h"
#include "CBooleanSurface.hpp"
using std::ostream;
using std::string;




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : CBooleanSurface (constructor)                           //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CBooleanSurface::CBooleanSurface(const string& strName,
                                 CSurface* pOrigSurface1,
                                 CSurface* pOrigSurface2)
  : CSurface(strName)                ,
    pSurface1(pOrigSurface1->Copy()) ,
    pSurface2(pOrigSurface2->Copy())
{
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : CBooleanSurface (constructor)                           //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CBooleanSurface::CBooleanSurface(const CBooleanSurface& surface)
  : CSurface(surface)                          ,
    pSurface1(surface.pSurface1.get()->Copy()) ,
    pSurface2(surface.pSurface2.get()->Copy())
{
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : ~CBooleanSurface (destructor)                           //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
CBooleanSurface::~CBooleanSurface()
{
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : Preprocess()                                            //
//                                                                     //
//  Comments :                                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::Preprocess()
{
  pSurface1.get()->m_Transformation =
    pSurface1.get()->m_Transformation * m_Transformation;
  pSurface1.get()->m_InverseTransformation =
    m_InverseTransformation * pSurface1.get()->m_InverseTransformation;

  pSurface2.get()->m_Transformation =
    pSurface2.get()->m_Transformation * m_Transformation;
  pSurface2.get()->m_InverseTransformation =
     m_InverseTransformation * pSurface2.get()->m_InverseTransformation;

  pSurface1.get()->Preprocess();
  pSurface2.get()->Preprocess();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : Intersect()                                             //
//                                                                     //
//  Comments : This function fills a container with intersections      //
//             between the ray and the scene object.  It returns the   //
//             number of hits.                                         //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
int CBooleanSurface::Intersect(const CRay&              ray,
                               hitcontainer&            hits,
                               const CConcreteSurface **ppFirstHit) const
{
  // First surface intersection
  hitcontainer hits1;
  const CConcreteSurface *pFirstHit1 = NULL;
  pSurface1.get()->Intersect(ray, hits1, &pFirstHit1);
  hitcontainer::const_iterator it1 = hits1.begin();
  bool isIn1 = false;

  // Second surface intersection
  hitcontainer hits2;
  const CConcreteSurface *pFirstHit2 = NULL;
  pSurface2.get()->Intersect(ray, hits2, &pFirstHit2);
  hitcontainer::const_iterator it2 = hits2.begin();
  bool isIn2 = false;


  hits = CSurface::hitcontainer();
  const CConcreteSurface *pFirstHit = NULL;

  bool oldIsIn = false;

  while (it1 != hits1.end() || it2 != hits2.end())
    {
      bool   isIn;
      real_t hit;
      const CConcreteSurface *pCurrentHit;

      // Get next it from the first surface
      real_t hit1;
      if (it1 != hits1.end())
        hit1 = *it1;
      else
        hit1 = infinity();

      // Get next it from the second surface
      real_t hit2;
      if (it2 != hits2.end())
        hit2 = *it2;
      else
        hit2 = infinity();

      if (hit1 < hit2)
        {
          // First surface is the next hit
          isIn1 = !isIn1;
          ++it1;

          isIn        = IsIn(isIn1, isIn2);
          hit         = hit1;
          pCurrentHit = pFirstHit1;
        }
      else
        {
          // Second surface is the next hit
          isIn2 = !isIn2;
          ++it2;

          isIn        = IsIn(isIn1, isIn2);
          hit         = hit2;
          pCurrentHit = pFirstHit2;
        }

      if (oldIsIn != isIn)
        {
          // It is an actual hit for the boolean surface
          oldIsIn = isIn;
          hits.push_back(hit);

          if (pFirstHit == NULL)
            pFirstHit = pCurrentHit;
        }
    }

  // Return the values
  if (ppFirstHit != NULL)
    *ppFirstHit = pFirstHit;

  return hits.size();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CConcreteSurface                                        //
//                                                                     //
//  Function : PreprocessGL()                                          //
//                                                                     //
//  Comments : Do the necessary preprocessing that a surface needs the //
//             be rendered in OpenGL.                                  //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::PreprocessGL()
{
  pSurface1.get()->PreprocessGL();
  pSurface2.get()->PreprocessGL();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CBooleanSurface                                         //
//                                                                     //
//  Function : DrawGL()                                                //
//                                                                     //
//  Comments : This function renders the surface in OpenGL.            //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::DrawGL() const
{
  glPushAttrib(GL_DEPTH_TEST       |
               GL_POLYGON_BIT      |
               GL_COLOR_BUFFER_BIT |
               GL_STENCIL_BUFFER_BIT );

  glClear(GL_STENCIL_BUFFER_BIT);
  glEnable(GL_CULL_FACE);

  ConcreteDrawGL();

  glPopAttrib();
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CSurface                                                //
//                                                                     //
//  Function : GenerateShaderForObject()                               //
//                                                                     //
//  Comments : This function is supposed to generate shader code, but  //
//             this is the "unimplemented" function for classes that   //
//             can't be used in shader.                                //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::GenerateShaderForObject(ostream& o) const
{
  CSurface::GenerateShaderForObject(o);
}




// These functions are highly inspired from csg.c from SGI OpenGL
// examples:
// http://www.sgi.com/products/software/opengl/examples/more_samples/

/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CConcreteSurface                                        //
//                                                                     //
//  Function : drawInside()                                            //
//                                                                     //
//  Comments : This function sets the stencil buffer to show the part  //
//             of the first surface (A) (front or back face according  //
//             to 'face') that is inside of the second surface B.      //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::drawInside(CSurface *pA,
                                 CSurface *pB,
                                 GLenum face,
                                 GLenum test) const
{
  /* draw A into depth buffer, but not into color buffer */
  glEnable(GL_DEPTH_TEST);
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  glCullFace(face);
  pA->DrawGL();
    
  /* use stencil buffer to find the parts of A that are inside of B
   * by first incrementing the stencil buffer wherever B's front faces
   * are...
   */
  glDepthMask(GL_FALSE);
  glEnable(GL_STENCIL_TEST);
  glStencilFunc(GL_ALWAYS, 0, 0);
  glStencilOp(GL_KEEP, GL_KEEP, GL_INCR);
  glCullFace(GL_BACK);
  pB->DrawGL();

  /* ...then decrement the stencil buffer wherever B's back faces are */
  glStencilOp(GL_KEEP, GL_KEEP, GL_DECR);
  glCullFace(GL_FRONT);
  pB->DrawGL();

  /* now draw the part of A that is inside of B */
  glDepthMask(GL_TRUE);
  glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE);
  glStencilFunc(test, 0, 1);
  glDisable(GL_DEPTH_TEST);
  glCullFace(face);
  pA->DrawGL();

  /* reset stencil test */
  glDisable(GL_STENCIL_TEST);
}




/////////////////////////////////////////////////////////////////////////
//                                                                     //
//  Class    : CConcreteSurface                                        //
//                                                                     //
//  Function : drawFixup()                                             //
//                                                                     //
//  Comments : This function fixes up the depth buffer with the        //
//             surface depth values.                                   //
//                                                                     //
/////////////////////////////////////////////////////////////////////////
void CBooleanSurface::drawFixup(CSurface *pA) const
{
  /* fix up the depth buffer */
  glColorMask(GL_FALSE, GL_FALSE, GL_FALSE, GL_FALSE);
  glEnable(GL_DEPTH_TEST);
  glDisable(GL_STENCIL_TEST);
  glDepthFunc(GL_ALWAYS);
  pA->DrawGL();

  /* reset depth func */
  glDepthFunc(GL_LESS);
}
