/*****************************************************************************
 * $Id: gfx_opengl.hh,v 1.90 2006/11/25 17:10:28 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Functions/macros for OpenGL.
 * @verbatim
 * ------------------------------------------------------------------------------
 * Macros/functions to abstract programming the OpenGL way.
 *
 * GFX_POST_REDISPLAY()         tell gfxsys to post repaint event
 * GFXSYS_SET_READY()           call this when graphics system is fully initialized
 * GFXSYS_IF_READY()            graphics system is operational
 * GFX_ENABLE()                 enable a mode
 * GFX_DISABLE()                disable a mode
 * GFX_RESTORE()                restore a mode
 * GFX_BEGIN(GP_*), GFX_END()   block that renders in 3D
 * GFX_BEGIN/END_AUX_BUFFER()   abstraction of auxillary color buffer
 * GFX_BEGIN/END_2D()           block that renders in 2D orthogonal (pass Geom struct relative to viewport)
 * GFX_BEGIN/END_BLEND()        block that renders using blending
 * GFX_RGBA()                   specify RGBA
 * GFX_VERTEXV(fp[3])           specify a 3D vertex (vector)
 * GFX_VERTEX_2D()              specify a 2D vertex
 * GFX_SHADE()                  specifying shading (smooth/flat)
 * GFX_LINE_WIDTH()             specify line width
 * GFX_DEPTH_MASK()             returns current mask (for restoring), pass false to disable writing to depth buffer
 * GFX_BEGIN/END_ANTIALIAS_LOOP block that renders using anti-aliasing
 * GFX_ENABLE_ANTIALIAS()       control anti-aliasing
 * GFX_IF_ANTIALIAS()           if anti-aliasing is enabled
 * GFX_FOG()                    enable fog, change fog density/color
 *
 * ## NOTE ## Callers must pass type fp args!
 * The vector forms will malfunction if a pointer to a wrong float type is passed.
 * ------------------------------------------------------------------------------
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#if GFXSYS_OPENGL
#ifndef GFX_OPENGL_HH
#define GFX_OPENGL_HH 1

// -----------------------------------------------------------------------------
// Minimum OpenGL requirements.

// OpenGL 1.4 provides glWindowPos().
const float GFX_MININUM_OPENGL_VERSION_FLOAT = 1.4;
#ifndef GL_VERSION_1_4
#error "Below minimum requirement: OpenGL 1.4"
#endif

// -----------------------------------------------------------------------------

#include "gfx_rgb.hh"

namespace gfx {

/// GFX enums.  Naming convention parallels OpenGL.
enum eGp
{
	// GFX_BEGIN()
	GP_LINE_LOOP		= GL_LINE_LOOP,
	GP_POLYGON			= GL_POLYGON,
	GP_TRIANGLES		= GL_TRIANGLES,
	GP_TRIANGLE_STRIP	= GL_TRIANGLE_STRIP,
	GP_QUADS			= GL_QUADS,
	GP_QUAD_STRIP		= GL_QUAD_STRIP,

	// GFX_ENABLE()/GFX_DISABLE()
	GP_LIGHTING			= GL_LIGHTING,
	GP_DEPTH_TEST		= GL_DEPTH_TEST
};

/// Fragment blending.
enum eGpBlend
{
	// src
	GP_SRC_ALPHA			= GL_SRC_ALPHA,
	GP_SRC_COLOR			= GL_SRC_COLOR,
	GP_ONE_MINUS_SRC_ALPHA	= GL_ONE_MINUS_SRC_ALPHA,
	// dst
	GP_DST_ALPHA			= GL_DST_ALPHA,
	GP_DST_COLOR			= GL_DST_COLOR,
	GP_ONE_MINUS_DST_ALPHA	= GL_ONE_MINUS_DST_ALPHA,
};

enum eGpFormat
{
	GP_RGBA					= GL_RGBA,
	GP_RGBA8				= GL_RGBA8,
	GP_BGRA					= GL_BGRA,
};

enum eGpTexEnvMode
{
	GP_TEX_ENV_MODE_MODULATE	= GL_MODULATE,
	GP_TEX_ENV_MODE_DECAL		= GL_DECAL,
	GP_TEX_ENV_MODE_REPLACE		= GL_REPLACE,
};

/// Depth test (GP_DEPTH_TEST name already taken).
enum eDepthTest
{
    eDepthTest_DISABLE,
    eDepthTest_ENABLE,
};

enum eShade
{
	eShade_FLAT			= GL_FLAT,
	eShade_NORMAL		= GL_FLAT,
	eShade_SMOOTH		= GL_SMOOTH,
};

/*****************************************************************************
 * Enable/disable gfxsys capabilities.
 *****************************************************************************/
static inline bool
GFX_IF_ENABLED( int mode )
{
	return glIsEnabled( mode );
}
static inline void
GFX_ENABLE( int mode )
{
	glEnable( mode );
}
static inline void
GFX_ENABLE( int mode, bool flag )
{
	if ( flag )	glEnable( mode ); else glDisable( mode );
}
static inline void
GFX_DISABLE( int mode )
{
	glDisable( mode );
}
static inline void
GFX_RESTORE( int mode, bool flag )
{
	GFX_ENABLE( mode, flag );  // equivalent
}

/*****************************************************************************
 * Mark code block for drawing of graphics primitives.
 * Note: Only a restricted set of OpenGL functions are available inside glBegin()...glEnd().
 *****************************************************************************/
DEBUG2_CODE( extern int gGfxBeginCnt; )
static inline void
GFX_BEGIN( int gp )
{
	DEBUG2_CODE( ++gGfxBeginCnt; )
	glBegin( gp );
}
static inline void
GFX_END( void )
{
	DEBUG2_CODE( --gGfxBeginCnt; ASSERT( gGfxBeginCnt == 0 ); )
	glEnd();
}

/*****************************************************************************
 * Abstraction of auxillary color buffer.
 *****************************************************************************/
void
GFX_BEGIN_AUX_BUFFER( void );

void
GFX_END_AUX_BUFFER( void );

/*****************************************************************************
 * Render in 2D, restore to 3D afterwards.  Should enclose the usual GFX_BEGIN().
 * PGEOM is a pointer to a Geom struct and is relative to the current viewport
 * (not the main window).
 *****************************************************************************/
extern bool gGfxSavedDepthTest;
#define GFX_BEGIN_2D( PGEOM )											\
{																		\
	gGfxSavedDepthTest = GFX_IF_ENABLED( GP_DEPTH_TEST );				\
	GFX_DISABLE( GP_DEPTH_TEST );										\
	glMatrixMode( GL_PROJECTION ); glPushMatrix(); glLoadIdentity();	\
	glMatrixMode( GL_MODELVIEW  ); glPushMatrix(); glLoadIdentity();	\
	gluOrtho2D( (PGEOM)->x, (PGEOM)->x + (PGEOM)->w,					\
                (PGEOM)->y, (PGEOM)->y + (PGEOM)->h );					\
}
#define GFX_END_2D()													\
{																		\
 /*	glMatrixMode( GL_MODELVIEW ); */ glPopMatrix();						\
	glMatrixMode( GL_PROJECTION );   glPopMatrix();						\
	GFX_RESTORE( GP_DEPTH_TEST, gGfxSavedDepthTest );					\
}

/*****************************************************************************
 * Fragment blending.
 *****************************************************************************/
// Depth test is enabled by default (yes indeed, see gfx_opengl.cc).
void
GFX_BEGIN_BLEND( eDepthTest depthTest = eDepthTest_ENABLE );
void
GFX_BEGIN_BLEND( eGpBlend src, eGpBlend dst, eDepthTest depthTest = eDepthTest_ENABLE );
void
GFX_END_BLEND( void );

/*****************************************************************************
 * Blending images.  Not to be confused with GFX_BEGIN/END_BLEND().
 * How to use:
 *     GFX_BEGIN_BLEND_IMAGE( 512, GP_RGBA );
 *         GFX_BLEND_IMAGE( buf0, buf0Len );
 *         GFX_BLEND_IMAGE( buf1, buf1Len );
 *         ...
 *         GFX_BLEND_IMAGE_GET( blendedBuf, blendedBufLen );
 *     GFX_END_BLEND_IMAGE();
 *****************************************************************************/
void
GFX_BEGIN_BLEND_IMAGE( const uint width, const eGpFormat format = GP_RGBA );

void
GFX_BLEND_IMAGE( const void* img, const uint imgLen );

void
GFX_GET_BLEND_IMAGE( void* img/*OUT*/, const uint imgLen );

void
GFX_END_BLEND_IMAGE( void );

/*****************************************************************************
 * Anti-aliasing.  This uses GL_LINE_SMOOTH to draw a smooth outline over
 * a polyon in order to anti-alias.  The subroutine which these
 * two macros enclose must be capable of drawing the same object twice.
 *
 * Note: This set of OpenGL function calls is magic.
 * The previous set worked on various OpenGL implementations
 * but on Mesa 4 (FreeBSD 4.10) polygons weren't filled.
 *****************************************************************************/
#define GFX_BEGIN_ANTIALIAS_LOOP( ANTIALIAS )						\
for ( int gfx_aa = 0; gfx_aa < (ANTIALIAS ? 2 : 1); ++gfx_aa )		\
{																	\
	bool gfx_using_antialias = false;								\
	if ( ANTIALIAS && GFX_IF_ANTIALIAS() )							\
	{																\
		gfx_using_antialias = true;									\
		if ( gfx_aa == 0 )            								\
		{															\
			glDisable( GL_LINE_SMOOTH );							\
			glDisable( GL_BLEND );									\
			glDisable( GL_POLYGON_OFFSET_FILL );					\
			glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );			\
		}															\
		else                       									\
		{															\
			glEnable( GL_POLYGON_OFFSET_FILL );						\
			glEnable( GL_BLEND );									\
			glEnable( GL_LINE_SMOOTH );								\
			glPolygonOffset( 1.0, 1.0 );							\
			glPolygonMode( GL_FRONT_AND_BACK, GL_LINE );			\
			glLineWidth( 1.0 );										\
			glBlendFunc( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA );	\
		}															\
	}

#define GFX_END_ANTIALIAS_LOOP										\
	if ( gfx_using_antialias )										\
	{																\
		glDisable( GL_LINE_SMOOTH );								\
		glDisable( GL_BLEND );										\
		glDisable( GL_POLYGON_OFFSET_FILL );						\
		glPolygonMode( GL_FRONT_AND_BACK, GL_FILL );				\
	}																\
}

// Safe to call before gfxsys is ready.
static inline void
GFX_ENABLE_ANTIALIAS( bool flag )
{
	extern bool gGfxAntialias;
	gGfxAntialias = flag;
}

// Safe to call before gfxsys is ready.
static inline bool
GFX_IF_ANTIALIAS( void )
{
	extern bool gGfxAntialias;
	return gGfxAntialias;
}

/*****************************************************************************
 * GFX_VERTEXV()
 * The matrix math code is based on WorldVertex/Object/Eye structs with .x/.y/.z members.
 * But OpenGL is more efficient when its vector functions (array arg) functions are called
 * as opposed to the non-vector functions that push .x/.y/.z onto the stack.
 * GFX_SELF_CHECK() (which main() should call) ensures that the vertex* structs and GLfloat[3]
 * are equivalent and can be cast interchangeably.
 * The inline functions use the compiler to convert float types (the faster macros don't!).
 *****************************************************************************/
#if ALTVERTEX
static inline void	GFX_VERTEXV( const EyeVertex* v ) { glVertex3f( v->x, v->y, v->z ); }
#else
#define				GFX_VERTEXV( v )					glVertex3fv( reinterpret_cast<const GLfloat*>((v)) )
#endif

#define GFX_VERTEX		ERROR_USE_GFX_VERTEXV_INSTEAD

static inline void
GFX_VERTEX_2D( fp x, fp y )
{
	// glVertex3f() with Z=1.0 is actually used for GFX_VERTEX_2D()
	// in order to try to make 2D vertexs appear over a 3D scene.
	// A particular problem is rendering 2D fonts when a 3D land polygon
	// at low altitude becomes so close that it would obscure the 2D fonts.
	glVertex3f( x, y, 1.0 );
}

/*****************************************************************************
 * Change depth mask.
 * Returns current depth mask for restoring, eg:
 * bool savedDepthMask = GFX_DEPTH_MASK( false );
 * GFX_DEPTH_MASK( savedDepthMask );
 *****************************************************************************/
static inline bool
GFX_DEPTH_MASK( bool f )
{
	GLboolean prev;
	glGetBooleanv( GL_DEPTH_WRITEMASK, &prev );
	glDepthMask( f );
	return (bool)prev;
}

/*****************************************************************************
 * Misc.
 *****************************************************************************/
static inline void
GFX_LINE_WIDTH( uint w  )
{
	glLineWidth( w );
}

static inline void
GFX_POST_REDISPLAY( void )
{
	glutPostRedisplay();
}

static inline void
GFX_SHADE( eShade shade )
{
	glShadeModel( shade );
}

/*****************************************************************************
 * GFXSYS (rather than GFX) means the underlying graphics system (OpenGL).
 * Call GFXSYS_SET_READY() when the graphic system has been initialized.
 * GFXSYS_IF_READY() can be called to determine if it is ready/initialized.
 *****************************************************************************/
void
GFXSYS_SET_READY( bool ready = true );

bool
GFXSYS_IF_READY( void );

/*****************************************************************************
 * Historical note: This used to have the aggressive optimization of skipping glColor()
 * if the RGBA address didn't change, but sometimes different RGBA objects
 * are recycled at the same address.
 *****************************************************************************/
static inline void
GFX_RGBA( const RGBA rgba )
{
	glColor4ubv( const_cast<uint8*>( rgba.Get() ) );
}

void
GFX_FOG( bool enable, const float fogDensity, const RGBA fogRgba );

#define OPENGL_ASSERT() OPENGL_CHECK_ERROR(NULL,true)
#define OPENGL_CLEAR_ERROR() glGetError()
void OPENGL_CHECK_ERROR( const char* funcName = NULL, bool dumpcore = false );

//------------------------------------------------------------------------------

fp
PerspectiveAngle( fp size, fp dist );

void
DrawTriangleStrip( const EyeVertex* va,
                   const EyeVertex* vb,
                   int idxEnd,
                   int inc );

void
DrawTriangleStripTexture( const EyeVertex* va,
                          const EyeVertex* vb,
                          int   idxEnd,
                          int   inc,
                          fp    cclock );

void
DrawTriangle( bool  applyTexture,
              const EyeVertex* v1,
              const EyeVertex* v2,
              const EyeVertex* v3 );

void
DrawTriangleCull( fp    cclock,
                  bool  applyTexture,
                  const EyeVertex* v1,
                  const EyeVertex* v2,
                  const EyeVertex* v3 );

void
DrawQuad( bool  applyTexture,
          const EyeVertex* v1,
          const EyeVertex* v2,
          const EyeVertex* v3,
          const EyeVertex* v4 );

//------------------------------------------------------------------------------

// Keyboard.
// Old names were KEY_* but they're also used in linux/input.h
enum eGfxKey
{								// naming convention parallels OpenGL
	eGfxKey_ENTER	= 0xd,
	eGfxKey_ESC		= 27,
	eGfxKey_TAB		= 0x9,
	eGfxKey_BS		= 0x8,
	eGfxKey_DEL		= 0x7f,
	eGfxKey_CTRL_C	= 0x3,
	eGfxKey_CTRL_P	= 0x10,
	eGfxKey_CTRL_S	= 0x13,
	eGfxKey_CTRL_X	= 0x18,
	eGfxKey_CTRL_Z	= 0x1a,
	eGfxKey_1		= 0x31,
	eGfxKey_PLUS	= 0x2b,
	eGfxKey_MINUS	= 0x2d,
	eGfxKey_LEFT	= 0x64,		// for GLUT special key handler
	eGfxKey_RIGHT	= 0x66,		// for GLUT special key handler
	eGfxKey_UP		= 0x65,		// for GLUT special key handler
	eGfxKey_DOWN	= 0x67,		// for GLUT special key handler
	eGfxKey_HOME	= 0x6a,		// for GLUT special key handler
	eGfxKey_END		= 0x6b,		// for GLUT special key handler
	eGfxKey_F1		= 0x1,		// for GLUT special key handler
	eGfxKey_F2		= 0x2,		// for GLUT special key handler
	eGfxKey_F3		= 0x3,		// for GLUT special key handler
	eGfxKey_F4		= 0x4,		// for GLUT special key handler
	eGfxKey_F5		= 0x5,		// for GLUT special key handler
	eGfxKey_F6		= 0x6,		// for GLUT special key handler
	eGfxKey_F7		= 0x7,		// for GLUT special key handler
	eGfxKey_F8		= 0x8,		// for GLUT special key handler
	eGfxKey_F9		= 0x9,		// for GLUT special key handler
	eGfxKey_F10		= 0xa,		// for GLUT special key handler
	eGfxKey_F11		= 0xb,		// for GLUT special key handler
	eGfxKey_F12		= 0xc,		// for GLUT special key handler
};

} // namespace gfx

#endif // GFX_OPENGL_HH
#endif // GFXSYS_OPENGL
