// tfogl.h
// by Glenn G. Chappell <chappellg@member.ams.org>
// VERSION 0.6
// 23 Jan 2004
// 
// TFOGL: An experimental OpenGL interface for the TRANSF package.
// Requires TRANSF files "vecpos.h" and "transf.h".
//
// Copyright (c) 2003 Glenn G. Chappell.
// This file is provided with no warranties whatsoever. "tfogl.h" may be
// freely copied, modified, and used for any purpose, as long as this
// notice is retained.

// Note to MS-Windows users: If you are using GLUT, then glut.h should be
// included BEFORE including this file. More generally, this file does not
// define WINGDIAPI; nor does it give the linker any help. Whatever you do
// to set all this up (for example, including glut.h) should be done before
// including this file.

#ifndef TF_FILE_TFOGL_H_INCLUDED
#define TF_FILE_TFOGL_H_INCLUDED

// ************************************************************************
// Includes & macro definitions
// ************************************************************************

#include "transf.h"  // for tf::vec, tf::pos, tf::rot, tf::transf
#include <GL/gl.h>
#include <GL/glu.h>

// All macros ending in "_INTERNAL" are #undef'd at end of file.

#define TF_INLINE_INTERNAL inline
   // Dirty trick to avoid redefined-function compiler complaints

// ************************************************************************
// Begin namespace tf
// ************************************************************************

namespace tf {

// ************************************************************************
// class tfogl_internal - Declaration [internal use only]
// ************************************************************************

// class tfogl_internal
// Holds internal-use functions.
class tfogl_internal {

// ***** tfogl_internal: ctor (prevents instantiation) *****
private:
   // No public ctors
   tfogl_internal(const tfogl_internal &);

// ***** tfogl_internal: friend declarations *****
   friend inline void glNormal(const vec & norm);
   friend inline void glRasterPos(const pos & rastpos);
   friend inline void glRasterPos(const vec & rastpos);
   friend inline void glTexCoord(const pos & texcoord);
   friend inline void glTexCoord(const vec & texcoord);
   friend inline void glVertex(const pos & vert);
   friend inline void glVertex(const vec & vert);
   friend inline void glColor(const vec & rgbcolor);

// ***** tfogl_internal: TFOGL internal-use functions *****
private:
   static void glNormal3v_internal(const GLdouble * normp)
   { glNormal3dv(normp); }
   static void glNormal3v_internal(const GLfloat * normp)
   { glNormal3fv(normp); }
   template<typename T>
   static void glNormal3v_internal(const T & normp)
   {
      glNormal3d(GLdouble(normp[0]),
                 GLdouble(normp[1]),
                 GLdouble(normp[2]));
   }

   static void glRasterPos3v_internal(const GLdouble * rastposp)
   { glRasterPos3dv(rastposp); }
   static void glRasterPos3v_internal(const GLfloat * rastposp)
   { glRasterPos3fv(rastposp); }
   template<typename T>
   static void glRasterPos3v_internal(const T & rastposp)
      // MS-Visual C++ 6.0 complains if I pass the above by value.
      // By-reference seems to work everywhere, so why not?
   {
      glRasterPos3d(GLdouble(rastposp[0]),
                    GLdouble(rastposp[1]),
                    GLdouble(rastposp[2]));
   }

   static void glTexCoord3v_internal(const GLdouble * texcoordp)
   { glTexCoord3dv(texcoordp); }
   static void glTexCoord3v_internal(const GLfloat * texcoordp)
   { glTexCoord3fv(texcoordp); }
   template<typename T>
   static void glTexCoord3v_internal(const T & texcoordp)
   {
      glTexCoord3d(GLdouble(texcoordp[0]),
                   GLdouble(texcoordp[1]),
                   GLdouble(texcoordp[2]));
   }

   static void glVertex3v_internal(const GLdouble * vertp)
   { glVertex3dv(vertp); }
   static void glVertex3v_internal(const GLfloat * vertp)
   { glVertex3fv(vertp); }
   template<typename T>
   static void glVertex3v_internal(const T & vertp)
   {
      glVertex3d(GLdouble(vertp[0]),
                 GLdouble(vertp[1]),
                 GLdouble(vertp[2]));
   }

   static void glColor3v_internal(const GLdouble * rgbcolorp)
   { glColor3dv(rgbcolorp); }
   static void glColor3v_internal(const GLfloat * rgbcolorp)
   { glColor3fv(rgbcolorp); }
   template<typename T>
   static void glColor3v_internal(const T & rgbcolorp)
   {
      glColor3d(GLdouble(rgbcolorp[0]),
                GLdouble(rgbcolorp[1]),
                GLdouble(rgbcolorp[2]));
   }

}; // End class tfogl_internal

// ************************************************************************
// TFOGL - Global functions for specifying positions and vectors
// ************************************************************************

inline void glNormal(const vec & norm)
{ tfogl_internal::glNormal3v_internal(norm.c_array()); }

inline void glRasterPos(const pos & rastpos)
{ tfogl_internal::glRasterPos3v_internal(rastpos.c_array()); }

inline void glRasterPos(const vec & rastpos)
{ tfogl_internal::glRasterPos3v_internal(rastpos.c_array()); }

inline void glTexCoord(const pos & texcoord)
{ tfogl_internal::glTexCoord3v_internal(texcoord.c_array()); }

inline void glTexCoord(const vec & texcoord)
{ tfogl_internal::glTexCoord3v_internal(texcoord.c_array()); }

inline void glVertex(const pos & vert)
{ tfogl_internal::glVertex3v_internal(vert.c_array()); }

inline void glVertex(const vec & vert)
{ tfogl_internal::glVertex3v_internal(vert.c_array()); }

// ************************************************************************
// TFOGL - Global functions for specifying transformations
// ************************************************************************

TF_INLINE_INTERNAL
void glRotate(const rot & rotation)
{
   const vec axis = rotation.axis();
   glRotated(GLdouble(rotation.angle()),
             GLdouble(axis[0]),
             GLdouble(axis[1]),
             GLdouble(axis[2]));

   // We could have constructed the matrix ourselves. However, the above
   // lets us take advantage of possible OpenGL pipeline optimizations
   // that take effect when a transformation is known to consist only of
   // rotations and translations.
}

TF_INLINE_INTERNAL
void glRotate(double angle,
              const vec & axis)
{
   glRotated(GLdouble(angle),
             GLdouble(axis[0]),
             GLdouble(axis[1]),
             GLdouble(axis[2]));
}

TF_INLINE_INTERNAL
void glScale(double scalefactor)
{
   glScaled(GLdouble(scalefactor),
            GLdouble(scalefactor),
            GLdouble(scalefactor));
}

// glTransform
// This is the (currently) the only function provided by TFOGL that is not
// simply a more convenient way of calling an existing OpenGL command.
// However, since we have glTranslate(vec) and glRotate(rot), it seems
// reasonable to provide a corresponding function to handle the third
// kind of transformation defined in the TRANSF package.
TF_INLINE_INTERNAL
void glTransform(const transf & transform)
{
   const vec vpart = transform.vecpart();
   glTranslated(GLdouble(vpart[0]),
                GLdouble(vpart[1]),
                GLdouble(vpart[2]));

   const vec axis = transform.rotpart().axis();
   glRotated(GLdouble(transform.rotpart().angle()),
             GLdouble(axis[0]),
             GLdouble(axis[1]),
             GLdouble(axis[2]));

   // We could have constructed the matrix ourselves. However, the above
   // lets us take advantage of possible OpenGL pipeline optimizations
   // that take effect when a transformation is known to consist only of
   // rotations and translations.
}

TF_INLINE_INTERNAL
void glTranslate(const vec & tlation)
{
   glTranslated(GLdouble(tlation[0]),
                GLdouble(tlation[1]),
                GLdouble(tlation[2]));
}

TF_INLINE_INTERNAL
void gluLookAt(const pos & eyept,
               const pos & atpt,
               const vec & upvec)
{
   ::gluLookAt(
      GLdouble(eyept[0]), GLdouble(eyept[1]), GLdouble(eyept[2]),
      GLdouble(atpt[0]),  GLdouble(atpt[1]),  GLdouble(atpt[2]),
      GLdouble(upvec[0]), GLdouble(upvec[1]), GLdouble(upvec[2]));
}

TF_INLINE_INTERNAL
void gluLookAt(const vec & eyept,
               const vec & atpt,
               const vec & upvec)
{
   ::gluLookAt(
      GLdouble(eyept[0]), GLdouble(eyept[1]), GLdouble(eyept[2]),
      GLdouble(atpt[0]),  GLdouble(atpt[1]),  GLdouble(atpt[2]),
      GLdouble(upvec[0]), GLdouble(upvec[1]), GLdouble(upvec[2]));
}

// ************************************************************************
// TFOGL - Global functions related to colors in general
// ************************************************************************

// See the lighting section for other color-related functions

TF_INLINE_INTERNAL
void glClearColor(const vec & rgbcolor,
                  double alpha = 1.)
{
   ::glClearColor(GLclampf(rgbcolor[0]),
                  GLclampf(rgbcolor[1]),
                  GLclampf(rgbcolor[2]),
                  GLclampf(alpha));
}

// The following pair of functions simulates a single function that is
// prototyped as:
// inline void glColor(const tf::vec & rgbcolor,
//                     double alpha = 1.);

inline void glColor(const vec & rgbcolor)
{ tfogl_internal::glColor3v_internal(rgbcolor.c_array()); }

inline void glColor(const vec & rgbcolor,
                    double alpha)
{
   glColor4d(GLdouble(rgbcolor[0]),
             GLdouble(rgbcolor[1]),
             GLdouble(rgbcolor[2]),
             GLdouble(alpha));
}

// ************************************************************************
// TFOGL - Global functions related to lighting
// ************************************************************************

TF_INLINE_INTERNAL
void glLightAmbient(GLenum light,
                    const vec & rgbcolor,
                    double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glLightfv(light, GL_AMBIENT, colorarr);
}

TF_INLINE_INTERNAL
void glLightDiffuse(GLenum light,
                    const vec & rgbcolor,
                    double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glLightfv(light, GL_DIFFUSE, colorarr);
}

TF_INLINE_INTERNAL
void glLightDiffuseAndSpecular(GLenum light,
                               const vec & rgbcolor,
                               double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glLightfv(light, GL_DIFFUSE, colorarr);
   glLightfv(light, GL_SPECULAR, colorarr);
}

TF_INLINE_INTERNAL
void glLightDirection(GLenum light,
                      const vec & dir)
{
   GLfloat dirarr[4] = { GLfloat(dir[0]),
                         GLfloat(dir[1]),
                         GLfloat(dir[2]),
                         GLfloat(0.) };
   glLightfv(light, GL_POSITION, dirarr);
}

TF_INLINE_INTERNAL
void glLightModelAmbient(const vec & rgbcolor,
                         double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glLightModelfv(GL_LIGHT_MODEL_AMBIENT, colorarr);
}

TF_INLINE_INTERNAL
void glLightPosition(GLenum light,
                     const pos & position)
{
   GLfloat locarr[4] = { GLfloat(position[0]),
                         GLfloat(position[1]),
                         GLfloat(position[2]),
                         GLfloat(1.) };
   glLightfv(light, GL_POSITION, locarr);
}

TF_INLINE_INTERNAL
void glLightPosition(GLenum light,
                     const vec & position)
{
   GLfloat locarr[4] = { GLfloat(position[0]),
                         GLfloat(position[1]),
                         GLfloat(position[2]),
                         GLfloat(1.) };
   glLightfv(light, GL_POSITION, locarr);
}

TF_INLINE_INTERNAL
void glLightSpecular(GLenum light,
                     const vec & rgbcolor,
                     double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glLightfv(light, GL_SPECULAR, colorarr);
}

TF_INLINE_INTERNAL
void glLightSpotCutoff(GLenum light,
                       double spotcutoff)
{
   glLightf(light, GL_SPOT_CUTOFF, GLfloat(spotcutoff));
}

TF_INLINE_INTERNAL
void glLightSpotDirection(GLenum light,
                          const vec & dir)
{
   GLfloat dirarr[3] = { GLfloat(dir[0]),
                         GLfloat(dir[1]),
                         GLfloat(dir[2]) };
   glLightfv(light, GL_SPOT_DIRECTION, dirarr);
}

TF_INLINE_INTERNAL
void glLightSpotExponent(GLenum light,
                         double spotexponent)
{
   glLightf(light, GL_SPOT_EXPONENT, GLfloat(spotexponent));
}

TF_INLINE_INTERNAL
void glLightAttenuation(GLenum light,
                        double constant_attfact = 1.,
                        double linear_attfact = 0.,
                        double quadratic_attfact = 0.)
{
   glLightf(light, GL_CONSTANT_ATTENUATION, GLfloat(constant_attfact));
   glLightf(light, GL_LINEAR_ATTENUATION, GLfloat(linear_attfact));
   glLightf(light, GL_QUADRATIC_ATTENUATION, GLfloat(quadratic_attfact));
}

TF_INLINE_INTERNAL
void glLightConstantAttenuation(GLenum light,
                                double constant_attfact)
{
   glLightf(light, GL_CONSTANT_ATTENUATION, GLfloat(constant_attfact));
}

TF_INLINE_INTERNAL
void glLightLinearAttenuation(GLenum light,
                              double linear_attfact)
{
   glLightf(light, GL_LINEAR_ATTENUATION, GLfloat(linear_attfact));
}

TF_INLINE_INTERNAL
void glLightQuadraticAttenuation(GLenum light,
                                 double quadratic_attfact)
{
   glLightf(light, GL_QUADRATIC_ATTENUATION, GLfloat(quadratic_attfact));
}

TF_INLINE_INTERNAL
void glMaterialAmbient(GLenum face,
                       const vec & rgbcolor,
                       double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glMaterialfv(face, GL_AMBIENT, colorarr);
}

TF_INLINE_INTERNAL
void glMaterialDiffuse(GLenum face,
                       const vec & rgbcolor,
                       double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glMaterialfv(face, GL_DIFFUSE, colorarr);
}

TF_INLINE_INTERNAL
void glMaterialSpecular(GLenum face,
                        const vec & rgbcolor,
                        double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glMaterialfv(face, GL_SPECULAR, colorarr);
}

TF_INLINE_INTERNAL
void glMaterialAmbientAndDiffuse(GLenum face,
                                 const vec & rgbcolor,
                                 double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glMaterialfv(face, GL_AMBIENT_AND_DIFFUSE, colorarr);
}

TF_INLINE_INTERNAL
void glMaterialEmission(GLenum face,
                        const vec & rgbcolor,
                        double alpha = 1.)
{
   GLfloat colorarr[4] = { GLfloat(rgbcolor[0]),
                           GLfloat(rgbcolor[1]),
                           GLfloat(rgbcolor[2]),
                           GLfloat(alpha) };
   glMaterialfv(face, GL_EMISSION, colorarr);
}

TF_INLINE_INTERNAL
void glMaterialShininess(GLenum face,
                         double shininess)
{
   glMaterialf(face, GL_SHININESS, GLfloat(shininess));
}

// ************************************************************************
// TFOGL - Global functions: variants of gluProject, gluUnProject
// ************************************************************************

TF_INLINE_INTERNAL
pos gluProject(const pos & objpt,
               const GLdouble modelMatrix[16],
               const GLdouble projMatrix[16],
               const GLint viewport[4])
{
   GLdouble winpt[3];
   ::gluProject(objpt[0], objpt[1], objpt[2],
                modelMatrix, projMatrix, viewport,
                winpt, winpt+1, winpt+2);
   return pos(double(winpt[0]),
              double(winpt[1]),
              double(winpt[2]));
}

TF_INLINE_INTERNAL
vec gluProject(const vec & objpt,
               const GLdouble modelMatrix[16],
               const GLdouble projMatrix[16],
               const GLint viewport[4])
{
   GLdouble winpt[3];
   ::gluProject(objpt[0], objpt[1], objpt[2],
                modelMatrix, projMatrix, viewport,
                winpt, winpt+1, winpt+2);
   return vec(double(winpt[0]),
              double(winpt[1]),
              double(winpt[2]));
}

TF_INLINE_INTERNAL
pos gluUnProject(const pos & winpt,
                 const GLdouble modelMatrix[16],
                 const GLdouble projMatrix[16],
                 const GLint viewport[4])
{
   GLdouble objpt[3];
   ::gluUnProject(winpt[0], winpt[1], winpt[2],
                  modelMatrix, projMatrix, viewport,
                  objpt, objpt+1, objpt+2);
   return pos(double(objpt[0]),
              double(objpt[1]),
              double(objpt[2]));
}

TF_INLINE_INTERNAL
vec gluUnProject(const vec & winpt,
                 const GLdouble modelMatrix[16],
                 const GLdouble projMatrix[16],
                 const GLint viewport[4])
{
   GLdouble objpt[3];
   ::gluUnProject(winpt[0], winpt[1], winpt[2],
                  modelMatrix, projMatrix, viewport,
                  objpt, objpt+1, objpt+2);
   return vec(double(objpt[0]),
              double(objpt[1]),
              double(objpt[2]));
}

// ************************************************************************
// End namespace tf
// ************************************************************************

}  // namespace tf

// ************************************************************************
// Internal-only macro undef's
// ************************************************************************

#undef TF_INLINE_INTERNAL

#endif  //#ifndef TF_FILE_TFOGL_H_INCLUDED
