/*****************************************************************************
 * $Id: eng_polygon.hh,v 1.86 2006/11/19 15:15:57 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Polygon classes.
 * @verbatim
 *
 * Polygon data must be kept strictly private to hide if the data is shared
 * or private since allowing direct write access to shared data could be wrong.
 *
 * @endverbatim
 *//*
 * LEGAL:	COPYRIGHT (C) JIM E. BROOKS 2005
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#ifndef ENG_POLYGON_HH
#define ENG_POLYGON_HH 1

#include "gfx_texture.hh"

class ObjectMesh;

//------------------------------------------------------------------------------
// Draw parameters:
//
// Usage:
// Draw parameters affect PolygonList::Draw().
// They are defined by a DrawParm object.
// Temporary drawing parameters can be passed in a DrawParm to PolygonList::Draw().
// For example, for craft shadows, a drawing parameters is a dark gray color.
// Persistent drawing parameters (for speed) can be specified at Object initialization
// by calling DrawParm methods in the PolygonList::mDrawParm member
// (typically after the call to PolygonList::Optimize() when a PolygonList was completed).
//
// Implementation:
// For the common case (draw parameters are rarely changed),
// PolygonList has a non-pointer DrawParm member.
// PolygonList::Draw(DrawParm) temporarily saves/changes/restores mDrawParm by copying.
// Copying isn't slow as DrawParm is a light-weight class.
//------------------------------------------------------------------------------

// Draw() parameter bits.
enum eDraw
{
	eDraw_ANTIALIAS			= (1<<0),	///< enable anti-aliasing
	eDraw_BLEND				= (1<<1),	///< enable blending
	eDraw_NO_TEXTURE		= (1<<2),	///< disable texturing
	eDraw_NO_CULL			= (1<<3),	///< disable culling polygons (overrides Polygon)
	eDraw_NO_VERTEX_ARRAY	= (1<<4),	///< disable gfxsys vertex array
};

////////////////////////////////////////////////////////////////////////////////
// Draw() parameters for PolygonList::Draw().
// This class should be small, but it has byte flags rather than a bit-field
// to speed PolygonList::Draw().  Overriding draw parameters results in copying
// DrawParm objects but is rarely done.
// Draw parameters can be specified by Enable*() methods.
// Many can be specified in one-step by merging eDraw bits.
//
class DrawParm
{
private:
	// mFlags[] indexs.
	enum
	{
		// "activate" means to turn it on (really do it).
		// "allow" means to allow turning it on if requested/appropriate.
		// For example, even if texturing is allowed, texturing
		// won't really be applied if no polygon requests texturing.
		IDX_ANTIALIAS,		// activate anti-aliasing
		IDX_BLEND,			// activate blending
		IDX_CULL,			// activate polygon culling
		IDX_TEXTURE,		// allow texturing
		IDX_VERTEX_ARRAY,	// allow gfxsys vertex array
		IDX_RGBA,			// true if mRGBA is valid, force same RGBA for all polygons
		IDX_END
	};

public:
	DrawParm( void )
	{
		// Normally, texturing, culling and vertex arrays are allowed if appropriate,
		// but anti-aliasing and blending are disabled.
		mFlags[IDX_ANTIALIAS]		= false;
		mFlags[IDX_BLEND]			= false;
		mFlags[IDX_CULL]			= true;
		mFlags[IDX_TEXTURE]			= true;
		mFlags[IDX_VERTEX_ARRAY]	= true;
		mFlags[IDX_RGBA]			= false;
		//mRgba	// default
		mEyeVertexs = NULL;
	}

	DrawParm( eDraw bits, const RGBA rgba )
	{
		InitFlags( bits );
		mRgba = rgba; mFlags[IDX_RGBA] = true;	// flag to indicate mRgba was specified
		mEyeVertexs = NULL;
	}

	explicit DrawParm( eDraw bits )
	{
		InitFlags( bits );
		mEyeVertexs = NULL;
	}

	// Dtor does nothing.  Default copy ctor suffices.
	~DrawParm() { }

private:
	void InitFlags( eDraw bits )
	{
//		mFlags[IDX_ANTIALIAS]		= bits & eDraw_ANTIALIAS;			// pitfall: int-to-char truncation
		mFlags[IDX_ANTIALIAS]		= (bits & eDraw_ANTIALIAS)  != 0;	// result 0 or 1
		mFlags[IDX_BLEND]			= (bits & eDraw_BLEND) != 0;
		mFlags[IDX_CULL]			= (bits & eDraw_NO_CULL)    == 0;
		mFlags[IDX_TEXTURE]			= (bits & eDraw_NO_TEXTURE) == 0;
		mFlags[IDX_VERTEX_ARRAY]	= (bits & eDraw_NO_VERTEX_ARRAY) == 0;
		mFlags[IDX_RGBA]			= false;
	}

public:
	bool		IfAntialias( void ) const	{ return mFlags[IDX_ANTIALIAS]; }	 // if activated
	bool		IfBlend( void ) const		{ return mFlags[IDX_BLEND]; }		 // if activated
	bool		IfCull( void ) const		{ return mFlags[IDX_CULL]; }		 // if activated
	bool		IfTexture( void ) const		{ return mFlags[IDX_TEXTURE]; }		 // if allowed
	bool		IfVertexArray( void ) const	{ return mFlags[IDX_VERTEX_ARRAY]; } // if allowed
	bool		IfRgba( void ) const 		{ return mFlags[IDX_RGBA]; }		 // if specified
	bool		IfEyeVertexs( void ) const	{ return mEyeVertexs != NULL; }		 // if specified

	void		EnableAntialias( bool f )	{ mFlags[IDX_ANTIALIAS]		= f; }	 // activate
	void		EnableBlend( bool f )		{ mFlags[IDX_BLEND]	        = f; }	 // activate
	void		EnableCull( bool f )		{ mFlags[IDX_CULL]			= f; }   // activate
	void		EnableTexture( bool f )		{ mFlags[IDX_TEXTURE]		= f; }   // allow texturing
	void		EnableVertexArray( bool f )	{ mFlags[IDX_VERTEX_ARRAY]	= f; }   // allow vertex arrays

	void			 SetRgba( const RGBA rgba ) { mFlags[IDX_RGBA] = true; mRgba = rgba; }   // set flag
	const RGBA*		 GetRgba( void ) const       { return mFlags[IDX_RGBA] ? &mRgba : NULL; } // check flag
	void			 SetEyeVertexs( const EyeVertex* ev ) { mEyeVertexs = ev; }
	const EyeVertex* GetEyeVertexs( void ) const          { return mEyeVertexs; } // may return NULL

private:
	uint8				mFlags[IDX_END];		// be careful of int-to-char truncation
	RGBA				mRgba;					// persists across frames, valid if mFlags[IDX_RGBA]
	const EyeVertex*	mEyeVertexs;			// don't delete, temp during one frame
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Vertex array class.
/// @verbatim
///
/// The associated arrays are stored in this object but not the array
/// of eye vertexs as they're computed dynamically by VertexArray::Linearize().
///
/// Vix array mVixArr:
/// Don't confuse it as support for gfxsys indexed vertex arrays.
/// Rather, it is is used by Linearize() to build a linear non-indexed vertex array.
///
/// @endverbatim
///
class VertexArray
{
	PREVENT_COPYING(VertexArray);
public:

	typedef Vix      VaVix;			// type for a vertex index (vix)
#if GFXSYS_OPENGL
	// -- OpenglDrawVertexArray() depends on these exact types --
	typedef GLubyte  VaRgbaElem;	// type for a RGBA element (not the whole RGBA)
	typedef GLfloat  VaTexCoord;	// type for one texture coordinate
#endif

	VertexArray( const uint polygonCnt, const uint vertexPerPolygon, Texture2PtrConst texture );
	~VertexArray();
	const EyeVertex* Linearize( const EyeVertex* const ev );

	// Get enum of the type of graphics primitive that comprises the vertex array
	eGp GetPrimEnum( void ) const
	{
		return mPrim;
	}

	// Get count of graphics primitives in the vertex array.
	uint GetPrimCnt( void ) const
	{
		return mVertexCnt;
	}

	VaTexCoord* GetTexCoordArr( void ) const
	{
		return mTexCoordArr;
	}

	VaRgbaElem* GetRgbaArr( void ) const
	{
		return mRgbaElemArr;
	}

	bool IfTextured( void ) const
	{
		return mTexture != NULL;
	}

	Texture2PtrConst GetTexture( void ) const
	{
		return mTexture;
	}

	// Write an element into the array of vertex indexs.
	void SetVix( uint i, Vix vix )
	{
		ASSERT( i < mVixCnt );
		mVixArr[i] = vix;
	}

	// Write 2 texture coordinates into the 2D texture coordinate array.
	// Index i references the whole object (not elements).
	void SetTexCoord( uint i, const TexCoord2* texCoord )
	{
		i <<= 1;
		ASSERT( i < mTexCoordCnt );
		mTexCoordArr[i]   = texCoord->x;
		mTexCoordArr[i+1] = texCoord->y;
	}

	// Write a whole RGBA value into the color array (4 byte RGBA).
	// Index i references the whole object (not elements).
	void SetRgba( uint i, const RGBA rgba )
	{
		// Convert float RGBA to 4 bytes.
		i <<= 2;
		ASSERT( i < mRgbaElemCnt );
		rgba.Get4ub( &mRgbaElemArr[i] );  // mRgbaElemArr[i] = rgba
	}

private:
	eGp				mPrim;				// graphics primitive
	uint			mVertexCnt;			// count of whole vertexs
	uint			mVixCnt;			// count of vertex indexs
	VaVix*			mVixArr;			// array of vertex indexs
	uint			mTexCoordCnt;		// count of individual texture coordinates
	VaTexCoord*		mTexCoordArr;		// array of texture coordinates
	uint			mRgbaElemCnt;		// count of RGBA components (whole counts as 4)
	VaRgbaElem*		mRgbaElemArr;		// array of 4-byte RGBA elements (unsigned bytes)
	Texture2Ptr		mTexture;			// NULL if untextured
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Polygon attributes class.
class PolygonAttribs
{
public:
					PolygonAttribs( void );
					PolygonAttribs( const RGBA rgba, Texture2PtrConst texture = NULL, uint texCoordCnt = 0, bool shared = true );
					PolygonAttribs( const PolygonAttribs& src );  // copying
					~PolygonAttribs();
	PolygonAttribs&	operator=( const PolygonAttribs& src );  // copying
	bool			IfTexture( void )     const { return mTexture != NULL; }
	bool			IfShared( void )      const { return mBits.mShared; }
	bool			IfWireframe( void )   const { return mBits.mWireframe; }
	bool			IfTranslucent( void ) const { return mBits.mTranslucent; }
	bool			IfCull( void )        const { return ! mBits.mNoCull; }
	const RGBA		GetRgba( void )       const { return mRgba; }
	/// @return Texture object.
	Texture2PtrConst GetTexture( void )    const { return mTexture != NULL ? mTexture : NULL; }
	/// @return 2D texture coordinate by index.
	const TexCoord2* GetTexCoord( uint idx )   const { ASSERT(idx < mBits.mTexCoordsTail); return &mTexCoords[idx]; }
	///< Be aware changing polygon attributes might affect multiple polygons if shared.
	void			SetWireframe( bool b )      { mBits.mWireframe = b; }
	void			SetTranslucent( bool b )    { mBits.mTranslucent = b; }
	void			SetCull( bool b )           { mBits.mNoCull = ! b; }
	void			SetRgba( const RGBA rgba );
	void			SetTexture( Texture2PtrConst texture );
	void			AddTexCoord( const TexCoord2* const texcoord );
	void			AddTexCoord( fp x, fp y );
	void			AddTexCoordsTriangle( void );

private:
	void			Init( void );
	void			Free( void );

    struct PolygonAttribsBits
    {
        uint mShared		: 1;	///< if this is shared using SharedVal
        uint mWireframe		: 1;
		uint mTranslucent	: 1;
		uint mNoCull		: 1;	///< true if both sides could face view-point
#define TEXTURE_COORDS_BITS	6
#define TEXTURE_COORDS_MAX  ((1<<TEXTURE_COORDS_BITS)-1)
        uint mTexCoordCnt	: TEXTURE_COORDS_BITS;
		uint mTexCoordsTail	: TEXTURE_COORDS_BITS;
    };

	RGBA				mRgba;
	PolygonAttribsBits	mBits;
	Texture2Ptr			mTexture;		///< Texture object
	TexCoord2*			mTexCoords;		///< 2D texture coordinates

friend bool operator<( const PolygonAttribs& a, const PolygonAttribs& b );
friend bool SelfCheck( void );

public:
	DECL_TYPESIG( TYPESIG_POLYGON_ATTRIBS )
	static bool SelfCheck( void )
	{
		// operator<(PolygonAttribs) eng_polygon.cc assumes this for speed.
		if ( sizeof(uint) != sizeof(PolygonAttribsBits) )
		{
			*gStreamError << "ERROR: sizeof(uint) != sizeof(PolygonAttribs::PolygonAttribsBits)" << endl;
			return false;
		}
		else return true;
	}
};

////////////////////////////////////////////////////////////////////////////////
/// @brief Polygon class.
class Polygon
{
	PREVENT_COPYING(Polygon)
public:
					Polygon( PolygonAttribs* attribs, uint vixCnt, uint num );
					~Polygon();
	PolygonAttribs*	GetAttribs( void ) const { return mAttribs; }
	uint			GetVixCnt( void ) const { return mBits.mVixCnt; }
	const Vix*		GetVixArr( void ) const { return mVixArr; }
	Vix				GetVix( uint i ) const { ASSERT( i < mBits.mVixCnt ); return mVixArr[i]; }
	void			AddVix( Vix vix );
	uint			GetNum( void ) const { return mBits.mNum; }	///< equals normal vector index
	void			SetNum( uint num ) { mBits.mNum = num; }	///< for PolygonList::AddPolygon()

private:
	struct PolygonBits
	{
		uint mNum		: 16;				///< polygon number (equals normal vector index)
        uint mVixCnt	: 16;				///< amount of vertex indexs in the polygon
	};
	PolygonBits			mBits;
	PolygonAttribs*		mAttribs;			///< polygons may share the same attribs
	Vix*				mVixArr;			///< array of vertex indexs

public:
	DECL_TYPESIG( TYPESIG_POLYGON )
};

////////////////////////////////////////////////////////////////////////////////
/// @brief PolygonList class.
/// @verbatim
///
/// Capabilities:
/// -------------
/// - Adding polygons one-by-one.
/// - Drawing polygons.
/// - PolygonList object resources can be shared (not the object itself).
/// - Sharing of the same RGBA and texture objects (to save memory).
///
/// Sharing PolygonList resources:
/// ------------------------------
/// PolygonList objects themselves aren't really shared, rather, their resources are.
/// Specifically, the shared resources are Dlist<Polygon> and PolygonAttrib objects.
/// A PolygonList object can effectively be shared using the copy constructor.
/// This is explained at implementation.html.
///
/// @endverbatim
class PolygonList
{
COPYABLE(PolygonList)
public:
					PolygonList( void );
					PolygonList( const PolygonList& src ); // copying
	PolygonList&	operator=( const PolygonList& src );  // copying
					~PolygonList();
	void			Optimize( const uint vertexCnt );
	Polygon*		AddPolygon( PolygonAttribs* attribs, uint vixCnt );
	uint			GetPolygonCnt( void ) const { return mPolygonCnt; }
	const Dlist<Polygon>& GetDlist( void ) { return *mPolygonDlistAll; }  ///< for external code
	void			Draw( ObjectMesh* obj ) const;
	void			Draw( ObjectMesh* obj, const DrawParm& drawParm ) const;

private:
	VertexArray*	BuildVertexArray( const uint triQuad, const uint triQuadCnt );
	void			DrawVertexArray( VertexArray* const va, const EyeVertex* const ev ) const;
	void			DrawOpaque( Dlist<Polygon>* dlist ) const;
	void			DrawTextured( void ) const;
	void			DrawTranslucent( void ) const;

private:
    struct PolygonListBits
    {
        uint mVertexArray	 : 1;		///< true if use vertex array
        uint mAnyOpaque		 : 1;		///< true if any polygon is opaque
        uint mAnyTextured	 : 1;		///< true if any polygon is textured
        uint mAnyTranslucent : 1;		///< true if any polygon is translucent
		uint mAnyWireframe	 : 1;		///< true if any polygon is wireframe
        uint mTexturesDiffer : 1;		///< true if any polygons have different textures
		uint mOptimized		 : 1;		///< true if Optimize() was called (required)
		uint mTriQuad		 : 1;		///< true if all polygons are triangles and/or quads
		uint mSameVixCnt     : MAX_POLYGON_VERTEXS_BITS; ///< vertex count if all polygons have the same amount
    };
	PolygonListBits		mBits;

public:
	mutable DrawParm	mDrawParm;					///< controls Draw(), can override polygon attribs

private:
    /// For sharing, the Dlist members are pointers.
	/// mPolygonListAll equals the sum of the segregated lists (opaque, textured, translucent).
	/// mPolygonListAll.Keep() is called to avoid double-deletion.
	Dlist<Polygon>*		mPolygonDlistAll;			///< list of all polygons
	Dlist<Polygon>*		mPolygonDlistOpaque;		///< list of opaque untextured polygons
	Dlist<Polygon>*		mPolygonDlistTextured;		///< list of textured polygons
	Dlist<Polygon>*		mPolygonDlistTranslucent;	///< list of translucent untextured polygons
	Texture2Ptr			mTextureSingle;				///< assigned if there is only one Texture object in PolygonList
	uint				mPolygonCnt;				///< amount of polygons
	// An indexed vertex array is used in the common case for speed
	// but under certain restrictions on polygons (see DrawVertexArray()).
	VertexArray*		mVertexArray[2];
	int*				mRefCntPtr;					///< untied reference count
	static ObjectMesh*	msDrawObj;					///< alive only during Draw*()

public:
	DECL_TYPESIG( TYPESIG_POLYGON_LIST )
};

// prototypes

void
init_eng_polygon( void );

#endif // ENG_POLYGON_HH
