/******************************************************************************
 * $Id: gfx_opengl.cc,v 1.56 2006/08/01 23:14:02 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Functions/macros for OpenGL.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 ******************************************************************************/

#define GFX_OPENGL_CC 1
#include "gfx.hh"
#include "gfx_math.hh"
#include "gfx_gfxsys.hh"
#if GFXSYS_OPENGL
namespace gfx {

bool gGfxSavedDepthTest;
bool gGfxAntialias = true;

DEBUG2_CODE( int gGfxBeginCnt = 0; )

/*****************************************************************************
 * Call GFX_SET_READY() when the graphic system has been initialized.
 * GFX_IF_READY() can be called to determine if it is ready/initialized.
 *****************************************************************************/
static bool sGfxReady = false;
void
GFX_SET_READY( bool ready )
{
	sGfxReady = ready;
}
bool
GFX_IF_READY( void )
{
	return sGfxReady;
}

/*****************************************************************************
 * Begin/end section for alpha blending.
 * Can tolerate being nested (unlike GFX_BEGIN()..GFX_END()).
 *****************************************************************************/
static int  sBlendNest;
static bool sSavedBlend;
static bool sSavedAlphaTest;
void
GFX_BEGIN_ALPHA_BLEND( void )
{
	GFX_BEGIN_ALPHA_BLEND( GP_SRC_ALPHA, GP_ONE_MINUS_SRC_ALPHA );
}
void
GFX_BEGIN_ALPHA_BLEND( eGpAlpha src, eGpAlpha dst )
{
	if ( sBlendNest++ == 0 )
	{
		sSavedBlend = GFX_IF_ENABLED( GL_BLEND );
		GFX_ENABLE( GL_BLEND );
		glBlendFunc( src, dst );

		sSavedAlphaTest = GFX_IF_ENABLED( GL_ALPHA_TEST );
		GFX_ENABLE( GL_ALPHA_TEST );
		glAlphaFunc( GL_GEQUAL, 0.1 );
	}
}
void
GFX_END_ALPHA_BLEND( void )
{
	if ( --sBlendNest == 0 )
	{
		GFX_RESTORE( GL_BLEND,      sSavedBlend );
		GFX_RESTORE( GL_ALPHA_TEST, sSavedAlphaTest );
	}
}

/*****************************************************************************
 * Enable fog in OpenGL.
 * Setting the fog parameters is tricky (esp. across OpenGL implementations).
 * GL_FOG_DENSITY is extremely sensitive and the value used here is far lower
 * than the example in the red book.
 *****************************************************************************/
void
GFX_FOG( bool enable, const float fogDensity, const RGBA fogRgba )
{
	if ( ! enable || fogDensity < 0.01 )
	{
		glDisable( GL_FOG );
	}
	else
	{
		// Setting the fog parameters is tricky (esp. across OpenGL implementations).
		// GL_FOG_DENSITY is extremely sensitive and the value used here is far lower
		// than the example in the red book.
		const float FOG_DENSITY = 0.000080;		// tiny values have a big effect
//		const int   FOG_MODE	= GL_EXP2;
		const int   FOG_MODE	= GL_EXP;
		glEnable( GL_FOG );
		glFogi( GL_FOG_MODE, FOG_MODE );
		glFogf( GL_FOG_DENSITY, fogDensity * FOG_DENSITY ); // tiny values have a big effect
		glFogfv( GL_FOG_COLOR, fogRgba.Getfv() );
		//glHint( GL_FOG_HINT, GL_DONT_CARE );
		glHint( GL_FOG_HINT, GL_NICEST );
		//glFogf( GL_FOG_START, 1.0 );
		//glFogf( GL_FOG_END, 2.0 );
	}
}

/*****************************************************************************
 * Calculate the angle to be passed to gluPerspective() so that a scene
 * is visible.  This function originates from the OpenGL Red Book.
 * @param	size
 *          The size of the segment when the angle is intersected at "dist"
 *          (ie at the outermost edge of the angle of vision).
 * @param	dist
 *          Distance from viewpoint to scene.
 *****************************************************************************/
fp
PerspectiveAngle( fp size, fp dist )
{
	GLfloat radTheta, degTheta;
	radTheta = 2.0 * atan2( fp(size / 2.0), fp(dist) ); // fp() cast necessary for g++ 3.3
	degTheta = (180.0 * radTheta) / M_PI;
	return degTheta;
}

/*****************************************************************************
 * Draw a triangle strip.
 * Strip is drawn using two arrays of vertexes which are offest in one coord
 * (they are parallel).  This is an alternative to drawing as a quad strip.
 * An optional increment value controls LOD (pass 1 for max. LOD).
 * @pre	Caller must swap vertex arrays during successive calls
 *		(if strips are adjacent) or stitching may occur.
 *		Will malfunction on very small arrays (len<4 or so).
 *****************************************************************************/
void
DrawTriangleStrip( const EyeVertex* va,
                   const EyeVertex* vb,
                   int idxEnd,
                   int inc )
{
ASSERT( idxEnd > 4 ); // TODO contigency or correction

	GFX_BEGIN( GP_TRIANGLE_STRIP );

	// Vertexes for an OpenGL triangle strip should be submitted in this order:
	// top   : 0 2 4 6 8
	// bottom: 1 3 5 7 9

	// Start with first vertex of both arrays.
	GFX_VERTEXV( va );
	GFX_VERTEXV( vb );

	// For each alternative vertex in either array,
	// excluding the first and last.
	const EyeVertex* pa = va + inc;
	const EyeVertex* pb = vb + inc;
	for ( ; pa < &va[idxEnd]-inc; )
	{
		GFX_VERTEXV( pa ); // vertex a
		GFX_VERTEXV( pb+inc ); // vertex b offset
		pa += 2*inc;
		pb += 2*inc;
	}

	// End with last vertex of both arrays.
	pa = &va[idxEnd];
	pb = &vb[idxEnd];
	GFX_VERTEXV( pa );
	GFX_VERTEXV( pb );

	GFX_END();
}

/*****************************************************************************
 * Draw a triangle strip.
 * Pass cclock as 1.0 or -1.0.
 * Strip is drawn using two arrays of vertexes which are offest in one coord
 * (they are parallel).  This is an alternative to drawing as a quad strip.
 * An optional increment value controls LOD (pass 1 for max. LOD).
 * @pre	Caller must swap vertex arrays during successive calls
 *		(if strips are adjacent) or stitching may occur.
 *		Will malfunction on very small arrays (len<4 or so).
 *****************************************************************************/
void
DrawTriangleStripTexture( const EyeVertex* va,
                          const EyeVertex* vb,
                          int   idxEnd,
                          int   inc,
                          fp    cclock )
{
ASSERT( idxEnd > 4 ); // TODO contigency or correction

	GFX_BEGIN( GP_TRIANGLES );

	// For each alternative vertex in either array,
	// excluding the first and last.
	const EyeVertex* pa = va;
	const EyeVertex* pb = vb;
	for (;;)
	{
		if ( pa+inc > &va[idxEnd] ) break;
		DrawTriangleCull( cclock, true, pa, pb,     pb+inc );
		DrawTriangleCull( cclock, true, pa, pb+inc, pa+inc );
		pa += inc;
		pb += inc;
	}

	// End with last vertex of both arrays.
	DrawTriangleCull( cclock, true, va+idxEnd-inc, vb+idxEnd-inc, vb+idxEnd );
	DrawTriangleCull( cclock, true, va+idxEnd-inc, vb+idxEnd,     va+idxEnd );

	GFX_END();
}

/*****************************************************************************
 * Draw a triangle with/out texturing.
 * Pass cclock as 1.0 or -1.0.
 * Culling variant follows.
 *****************************************************************************/
void
DrawTriangle( bool  applyTexture,
              const EyeVertex* v1,
              const EyeVertex* v2,
              const EyeVertex* v3 )
{
	if ( applyTexture )
	{
		glTexCoord2f( 0, 0 ); GFX_VERTEXV( v1 );
		glTexCoord2f( 0, 1 ); GFX_VERTEXV( v2 );
		glTexCoord2f( 1, 1 ); GFX_VERTEXV( v3 );
	}
	else
	{
		GFX_VERTEXV( v1 );
		GFX_VERTEXV( v2 );
		GFX_VERTEXV( v3 );
	}
}
void
DrawTriangleCull( fp    cclock,
                  bool  applyTexture,
                  const EyeVertex* v1,
                  const EyeVertex* v2,
                  const EyeVertex* v3 )
{
	NormalVertex norm;
	CrossProduct( norm, *v1, *v2, *v3 );
	if ( cclock * DotProduct( norm, *v1 ) >= 0 )
	{
		if ( applyTexture )
		{
			glTexCoord2f( 0, 0 ); GFX_VERTEXV( v1 );
			glTexCoord2f( 0, 1 ); GFX_VERTEXV( v2 );
			glTexCoord2f( 1, 1 ); GFX_VERTEXV( v3 );
		}
		else
		{
			GFX_VERTEXV( v1 );
			GFX_VERTEXV( v2 );
			GFX_VERTEXV( v3 );
		}
	}
}

/*****************************************************************************
 * Draw a quad with/out texturing.
 *****************************************************************************/
void
DrawQuad( bool  applyTexture,
          const EyeVertex* v1,
          const EyeVertex* v2,
          const EyeVertex* v3,
          const EyeVertex* v4 )
{
	if ( applyTexture )
	{
		glTexCoord2f( 0, 0 ); GFX_VERTEXV( v1 );
		glTexCoord2f( 0, 1 ); GFX_VERTEXV( v2 );
		glTexCoord2f( 1, 1 ); GFX_VERTEXV( v3 );
		glTexCoord2f( 1, 0 ); GFX_VERTEXV( v4 );
	}
	else
	{
		GFX_VERTEXV( v1 );
		GFX_VERTEXV( v2 );
		GFX_VERTEXV( v3 );
		GFX_VERTEXV( v4 );
	}
}

} // namespace gfx
#endif // GFXSYS_OPENGL
