/******************************************************************************
 * $Id: gfx_opengl.cc,v 1.67 2006/11/19 15:14:40 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Functions/macros for OpenGL.
 * @verbatim
 * -----------------------------------------------------------------------------
 * Blending and depth-test:
 * Whether to enable depth test during blending varies.
 * Graphics literature say to disable depth test for proper blending,
 * but Palomino has exceptions.  Polygon class renders translucent polygons
 * with depth test enabled because the Engine doesn't sort polygons
 * If depth test was disabled, a Craft's cockpit glass could be visible thru
 * its fuselage when viewed from underneath.   Trees (via TexQuad) are also blended
 * with depth test enabled because the sorting of hills and trees WRT each other
 * isn't accurate (due to radius approximation).
 * -----------------------------------------------------------------------------
 * @endverbatim
 *//*
 * 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 {

#define COMPILE_ALPHA_TEST  0

bool gGfxSavedDepthTest;
bool gGfxAntialias = true;
static GLint sAuxBufferCnt = 0;  // number of OpenGL auxillary buffers (AUXn)

DEBUG2_CODE( int gGfxBeginCnt = 0; )

// Checking OpenGL capabilities/version ideally belong here.
// But OpenGL functions don't function until the OpenGL window is up,
// so the checks are done at eng_opengl.cc.
// bool GFX_OPENGL_CHECK( void )

/*****************************************************************************
 * GFXSYS (rather than GFX) means the underlying graphics system (OpenGL).
 * Call GFXSYS_SET_READY() when the graphic system is operational.
 * GFXSYS_IF_READY() can check if it's operational.
 *****************************************************************************/
static bool sGfxReady = false;
void
GFXSYS_SET_READY( bool ready )
{
	// OpenGL functions don't work until glutInit() is called by Engine
	// in which case the "ready" arg will be true, else false.

	if ( sGfxReady && ready ) return;  // don't repeat

	sGfxReady = ready;

	if ( ready )
	{
		// Can call OpenGL now.
		// Test if OpenGL auxillary buffer exists.
		glGetIntegerv( GL_AUX_BUFFERS, &sAuxBufferCnt );

		DEBUG2_CODE( *gStreamDebug << "OpenGL has " << sAuxBufferCnt << " AUX buffers.\n"; )
	}
}
bool
GFXSYS_IF_READY( void )
{
	return sGfxReady;
}

/*****************************************************************************
 * Begin/end section for blending.
 * Can tolerate being nested (unlike GFX_BEGIN()..GFX_END()).
 *****************************************************************************/
static int  sBlendNest;
static bool sSavedBlend;
static bool sSavedDepthTest;
#if COMPILE_ALPHA_TEST
static bool sSavedAlphaTest;
#endif
const int MAX_BLEND_NEST = 5;

// Proper blending requires turning off depth test (done by default).
// But special cases such as small transparent polygons (glass on aircraft)
// require depth test since the Engine doesn't sort polygons.
void
GFX_BEGIN_BLEND( eDepthTest depthTest )
{
	GFX_BEGIN_BLEND( GP_SRC_ALPHA, GP_ONE_MINUS_SRC_ALPHA, depthTest );
}

void
GFX_BEGIN_BLEND( eGpBlend src, eGpBlend dst, eDepthTest depthTest )
{
ASSERT2( ABS(sBlendNest) < MAX_BLEND_NEST );

	if ( sBlendNest++ == 0 )
	{
		// Disable depth test (prevents artifacts such as flickering textures etc).
		sSavedDepthTest = GFX_IF_ENABLED( GL_DEPTH_TEST );
        if ( depthTest == eDepthTest_DISABLE ) GFX_DISABLE( GL_DEPTH_TEST );

		sSavedBlend = GFX_IF_ENABLED( GL_BLEND );
        GFX_ENABLE(  GL_BLEND );

		glBlendFunc( src, dst );
#if COMPILE_ALPHA_TEST
		sSavedAlphaTest = GFX_IF_ENABLED( GL_ALPHA_TEST );
		GFX_ENABLE( GL_ALPHA_TEST );
		glAlphaFunc( GL_GEQUAL, 0.1 );  // interferes with illusion of flying thru clouds
#endif
	}
}

void
GFX_END_BLEND( void )
{
ASSERT2( ABS(sBlendNest) < MAX_BLEND_NEST );

	if ( --sBlendNest == 0 )
	{
		GFX_RESTORE( GL_DEPTH_TEST, sSavedDepthTest );
		GFX_RESTORE( GL_BLEND,      sSavedBlend );
#if COMPILE_ALPHA_TEST
		GFX_RESTORE( GL_ALPHA_TEST, sSavedAlphaTest );
#endif
	}
}

/*****************************************************************************
 * Abstraction of auxillary buffer.
 * As of late 2006, some/most OpenGL implementations don't have GL_AUXn buffers.
 * A slower alternative is to use the GL_BACK buffer and save/restore the affected area.
 *****************************************************************************/
DEBUG_CODE( static int sAuxBufferNestCnt; )
void
GFX_BEGIN_AUX_BUFFER( void )
{
DEBUG_CODE( ASSERT( sAuxBufferNestCnt == 0 ); ++sAuxBufferNestCnt; )  // prevent nesting

	if ( sAuxBufferCnt )
	{
		glDrawBuffer( GL_AUX0 );
		glReadBuffer( GL_AUX0 );
	}
	else
	{
		// NOP.  Default to GL_BACK.
        // Just skip save/restore of affected area in GL_BACK because
        // currently GFX_BEGIN_AUX_BUFFER() is only called during startup
	}
}

void
GFX_END_AUX_BUFFER( void )
{
DEBUG_CODE( ASSERT( sAuxBufferNestCnt == 1 ); --sAuxBufferNestCnt; )

	if ( sAuxBufferCnt )
	{
		glDrawBuffer( GL_BACK );
		glReadBuffer( GL_BACK );
	}
	else
	{
		// NOP.  See GFX_BEGIN_AUX_BUFFER().
	}
}

/*****************************************************************************
 * Support for blending 2D images.  Not to be confused with GFX_BEGIN/END_BLEND().
 * See header file for usage..
 *
 * An OpenGL auxillary buffer is used.
 * The first 2D image is drawn with no blending, then blending is enabled
 * for drawing more images into the same area.  glReadPixels() reads
 * the composite image from the aux buffer into app memory.
 *****************************************************************************/
struct BlendImageState
{
	void Reset( int width, eGpFormat format )
	{
		mWidth = width;
		mFormat = format;
		mCnt = 0;
		#if DEBUG
		switch ( format )
		{
			case GL_RGBA: case GL_BGRA: case GL_RGBA8: /*case GL_BGRA8:*/
				mLen = width * width * 4;
			break;

			default: ASSERT(0); break;
		}
		#endif
	}

	uint	mWidth;
	int		mFormat;
	uint	mCnt;
	#if DEBUG
	uint	mLen;	// only used to catch if caller used wrong image length
	#endif
};

static BlendImageState sBlendImage;

void
GFX_BEGIN_BLEND_IMAGE( const uint width, const eGpFormat format )
{
    GFX_BEGIN_AUX_BUFFER();
	sBlendImage.Reset( width, format );  // reset state
	glDisable( GL_DEPTH_TEST );
	glDepthMask( GL_FALSE );
}

void
GFX_BLEND_IMAGE( const void* img, UNUSED const uint imgLen )
{
ASSERT( imgLen == sBlendImage.mLen );  // check caller used the correct image length

	++sBlendImage.mCnt;

	glWindowPos2i( 0, 0 );
	glDrawPixels( sBlendImage.mWidth, sBlendImage.mWidth,  // w,h
	              GL_RGBA, GL_UNSIGNED_BYTE,   // format,type
		          img );

	// Enable blending after initial draw but not before.
	if ( sBlendImage.mCnt == 1 )
	{
		// For compositing images, use different blend functions for RGB and A.
		GFX_ENABLE( GL_BLEND );
	  //glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
	  //                     GL_ONE,       GL_ONE_MINUS_SRC_ALPHA );
		glBlendFuncSeparate( GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA,
		                     GL_SRC_ALPHA, GL_ONE );
	}
}

void
GFX_GET_BLEND_IMAGE( void* img/*OUT*/, UNUSED const uint imgLen )
{
ASSERT( imgLen == sBlendImage.mLen );

	glWindowPos2i( 0, 0 );
	glReadPixels( 0, 0, // x,y
	              sBlendImage.mWidth, sBlendImage.mWidth,
	              GL_RGBA, GL_UNSIGNED_BYTE,
	              img );  // pixels written to where img points
}

void
GFX_END_BLEND_IMAGE( void )
{
    GFX_END_AUX_BUFFER();
	GFX_DISABLE( GL_BLEND );
	glEnable( GL_DEPTH_TEST );
	glDepthMask( GL_TRUE );
	OPENGL_CHECK_ERROR();
}

/*****************************************************************************
 * 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.000025;  // tiny values have a big effect
		const int   FOG_MODE	= GL_EXP;  // LINEAR,EXP,EXP2
		GLfloat fog4f[4]; fogRgba.Get4f( fog4f );
		glFogfv( GL_FOG_COLOR, fog4f );
		glFogi( GL_FOG_MODE, FOG_MODE );
		glFogf( GL_FOG_DENSITY, fogDensity * FOG_DENSITY ); // tiny values have a big effect
		glEnable( GL_FOG );
		glHint( GL_FOG_HINT, GL_NICEST );
	}
}

/*****************************************************************************
 * OpenGL error handling.
 * OpenGL error code is sticky.  glGetError() clears it.
 *****************************************************************************/
void
OPENGL_CHECK_ERROR( const char* funcName, bool dumpcore )
{
	// Eventually stop printing errors.
	static int errorCnt = 0;
	int error = glGetError();
	if ( error != GL_NO_ERROR && errorCnt < 20 )
	{
		++errorCnt;
		*gStreamError << "OpenGL error (" << error << ") ";
		if ( funcName != NULL )	*gStreamError << "at " << funcName << ' ';
		*gStreamError << gluErrorString(error) << endl;
		DEBUG_CODE( if ( dumpcore ) abort(); )  // puke
	}
}

/*****************************************************************************
 * 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) / gfx::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
