/*****************************************************************************
 * $Id: eng_tex.cc,v 1.29 2006/11/20 22:43:27 devel Exp $
 * @file
 * @author	Jim E. Brooks  http://www.jimbrooks.org
 * @brief	Texture code.
 *//*
 * LEGAL:	COPYRIGHT (C) 2004 JIM E. BROOKS
 *          THIS SOURCE CODE IS RELEASED UNDER THE TERMS
 *          OF THE GNU GENERAL PUBLIC LICENSE (GPL).
 *****************************************************************************/

#define ENG_TEX_CC 1
#include "eng.hh"
#include "eng_tex.hh"

// Shared objects used by TexBiquad and TexQuad.
// Texcoord8 is for texturing the front/back of one quad.
// TexBiquad reuses the same Texcoord8 twice for two quads.
namespace gfx {
template<> SharedValFactory<Texcoord8>* SharedValFactory<Texcoord8>::mInstance = NULL;
}

////////////////////////////////////////////////////////////////////////////////
////////////////////////////////  TexQuad  /////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor/destructor.
 *****************************************************************************/
TexQuad::TexQuad( void )
{
	mTexture	= NULL;
	mTexcoords	= NULL;
}
TexQuad::~TexQuad()
{
	mTexture = NULL;	// don't delete Texture objects
	UnshareVal<Texcoord8>( mTexcoords ); mTexcoords = NULL;
}

/*****************************************************************************
 * Set() assigns all members except the public mWorldVertexs[]
 * which user can do directly for speed.
 *****************************************************************************/
void
TexQuad::Set( Texture2PtrConst texture,
              const Texcoord8& texcoords,
              const RGBA rgba )
{
	// same as TexBiquad::Set()

	mTexture = texture;									// just assign it

	UnshareVal<Texcoord8>( mTexcoords );       			// unshare outgoing
	mTexcoords = ShareVal<Texcoord8>( &texcoords );		// share incoming
ASSERT( *mTexcoords == texcoords );

	mRgba = rgba;
}

/*****************************************************************************
 * Draw a single textured quad.
 *****************************************************************************/
void
TexQuad::Draw( eGpBlend blend1, eGpBlend blend2, eDepthTest depthTest )
{
	// world --> eye
	static EyeVertex ev[4];
	TRANSFORM_VERTEX( ev[0], mWorldVertexs[0] );
	TRANSFORM_VERTEX( ev[1], mWorldVertexs[1] );
	TRANSFORM_VERTEX( ev[2], mWorldVertexs[2] );
	TRANSFORM_VERTEX( ev[3], mWorldVertexs[3] );

	GFX_RGBA( mRgba );

	// Subtlety: The outer-level blending overrides the inner-level Texture.
	GFX_BEGIN_BLEND( blend1, blend2, depthTest );
		GFX_BEGIN( GP_QUADS, mTexture );
			GFX_MAP_TEXTURE_2D( (*mTexcoords)[0], (*mTexcoords)[1] ); GFX_VERTEXV( &ev[0] );
			GFX_MAP_TEXTURE_2D( (*mTexcoords)[2], (*mTexcoords)[3] ); GFX_VERTEXV( &ev[1] );
			GFX_MAP_TEXTURE_2D( (*mTexcoords)[4], (*mTexcoords)[5] ); GFX_VERTEXV( &ev[2] );
			GFX_MAP_TEXTURE_2D( (*mTexcoords)[6], (*mTexcoords)[7] ); GFX_VERTEXV( &ev[3] );
		GFX_END( mTexture );
	GFX_END_BLEND();
}

////////////////////////////////////////////////////////////////////////////////
///////////////////////////////  TexBiquad  ////////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Constructor.  Set() really does it.
 *****************************************************************************/
TexBiquad::TexBiquad( void )
{
	Init();
}
void
TexBiquad::Init( void )
{
	mWorldVertexs = NULL;
	mTexture	  = NULL;
	mTexcoords    = NULL;
}
TexBiquad::~TexBiquad()
{
	DELETE_ARR_NULL( mWorldVertexs );
	mTexture = NULL;	// don't delete Texture object
	UnshareVal<Texcoord8>( mTexcoords ); mTexcoords = NULL;
}

/*****************************************************************************
 * Variant to calculate world vertexs from a given position,width,height.
 *****************************************************************************/
void
TexBiquad::Set( const Position& center,
                const fp width,
                const fp height,
                Texture2PtrConst texture,
                const Texcoord8& texcoords,
                const RGBA rgba )
{
	Set( texture, texcoords, rgba );

	// Calculate world vertexs from position,width,height.

	DELETE_ARR_NULL( mWorldVertexs );
	mWorldVertexs = new WorldVertex[8];

	const fp widthHalf = width * 0.5;
	fp y = center.y - height * 0.5;
	for ( uint i = 0; i < 8; i +=4 )
	{
		mWorldVertexs[i+0].x = center.x;
		mWorldVertexs[i+0].y = y;
		mWorldVertexs[i+0].z = center.z + widthHalf;

		mWorldVertexs[i+1].x = center.x - widthHalf;
		mWorldVertexs[i+1].y = y;
		mWorldVertexs[i+1].z = center.z;

		mWorldVertexs[i+2].x = center.x;
		mWorldVertexs[i+2].y = y;
		mWorldVertexs[i+2].z = center.z - widthHalf;

		mWorldVertexs[i+3].x = center.x + widthHalf;
		mWorldVertexs[i+3].y = y;
		mWorldVertexs[i+3].z = center.z;

		y += height; // loop again once
	}
}

/*****************************************************************************
 * Variant for when WorldVertex array will be given to Draw().
 *****************************************************************************/
void
TexBiquad::Set( Texture2PtrConst texture,
                const Texcoord8& texcoords,
                const RGBA rgba )
{
	mTexture = texture;									// just assign it

	UnshareVal<Texcoord8>( mTexcoords );       			// unshare outgoing
	mTexcoords = ShareVal<Texcoord8>( &texcoords );    	// share incoming
ASSERT( *mTexcoords == texcoords );

	mRgba = rgba;
}

/*****************************************************************************
 * Draw a textured biquad.
 *****************************************************************************/
void
TexBiquad::Draw( eDepthTest depthTest )
{
	Draw( mWorldVertexs, depthTest );
}
void
TexBiquad::Draw( const WorldVertex wv[8], eDepthTest depthTest )
{
	// world --> eye
	static EyeVertex ev[8];
	TRANSFORM_VERTEX( ev[0], wv[0] );
	TRANSFORM_VERTEX( ev[1], wv[1] );
	TRANSFORM_VERTEX( ev[2], wv[2] );
	TRANSFORM_VERTEX( ev[3], wv[3] );
	TRANSFORM_VERTEX( ev[4], wv[4] );
	TRANSFORM_VERTEX( ev[5], wv[5] );
	TRANSFORM_VERTEX( ev[6], wv[6] );
	TRANSFORM_VERTEX( ev[7], wv[7] );

	// Draw both intersecting quads using the same texture.
	#define DRAW_TEXBIQUAD( VI1, VI2, VI3, VI4 )									\
	GFX_MAP_TEXTURE_2D( (*mTexcoords)[0], (*mTexcoords)[1] ); GFX_VERTEXV( &ev[VI1] );	\
	GFX_MAP_TEXTURE_2D( (*mTexcoords)[2], (*mTexcoords)[3] ); GFX_VERTEXV( &ev[VI2] );	\
	GFX_MAP_TEXTURE_2D( (*mTexcoords)[4], (*mTexcoords)[5] ); GFX_VERTEXV( &ev[VI3] );	\
	GFX_MAP_TEXTURE_2D( (*mTexcoords)[6], (*mTexcoords)[7] ); GFX_VERTEXV( &ev[VI4] );

	GFX_RGBA( mRgba );
	GFX_BEGIN_BLEND( depthTest );
		GFX_BEGIN( GP_QUADS, mTexture );
			DRAW_TEXBIQUAD( 5, 1, 3, 7 );
			DRAW_TEXBIQUAD( 6, 2, 0, 4 );
		GFX_END( mTexture );
	GFX_END_BLEND();
}

////////////////////////////////////////////////////////////////////////////////
//////////////////////////  Misc texture code  /////////////////////////////////
////////////////////////////////////////////////////////////////////////////////

/*****************************************************************************
 * Initialize texture objects (even if disabled by cmd-line).
 *****************************************************************************/
void
Engine::InitTextures( bool gfxsysReady )
{
	InitTexturesTgaFiles( gfxsysReady );
}
// Targa files.
void
Engine::InitTexturesTgaFiles( bool gfxsysReady )
{
	// The engine doesn't know where the texture directories are, so it asks.
	gEngineEvent.mNeedTextureDirVec.Broadcast();
	if ( mTextureDirVec.empty() )  // nobody responded?
	{
	ErrorNoTextures:
		*gStreamError << "ERROR: found no texture files" << endl;
		Exit(1);
	}

	// For each directory, find all .tga files and load them into STL map.
	vector<string>::const_iterator itrDirs;
	for ( itrDirs = mTextureDirVec.begin();
          itrDirs != mTextureDirVec.end();
          ++itrDirs )
	{
		const string& dirName = *itrDirs;
		vector<string> files;
		if ( FilesInDir( dirName, files ) )  // empty dir is OK (unless no dirs have textures)
		{
			vector<string>::iterator itrFiles;
			for ( itrFiles = files.begin(); itrFiles != files.end(); ++itrFiles )
			{
				if ( (*itrFiles).find( ".tga", 0 ) != string::npos )
				{
					string fileBuf;
					const string pathname = dirName + *itrFiles;
#if GFX_ZLIB
					string textureName = *itrFiles; RemoveSuffix( textureName, ".gz" );
					bool textureFileRead = ReadFileGzip( pathname, fileBuf );
#else // zlib isn't available and the user had to gunzip *.tga.gz
					if ( pathname.find( ".gz", 0 ) != string::npos )
					{
						*gStreamError << "ERROR: can't read compressed texture files" << endl;
						*gStreamError << "ERROR: compile with zlib or use gunzip *.tga.gz" << endl;
						Exit(1);
					}
					string textureName = *itrFiles;
					bool textureFileRead = ReadFile( pathname, fileBuf );
#endif
					if ( textureFileRead )
					{
                        // Broadcast to give any module the opportunity to pre-filter a texture.
                        // A module that listens can respond by calling Engine::SetTextureFilterFunc().
                        SetTextureFilterFunc( DefaultTargaFilterFunc );  // default
                    	gEngineEvent.mTextureFilter.Broadcast( textureName );

						// Create Texture object.
						Targa targa( (uchar*)fileBuf.c_str(), fileBuf.size(), GetTextureFilterFunc() );
						ASSERT( targa.IfValid() );
						Texture2* texture = new Texture2( targa, DO_MIPMAP );
						texture->SetMinFilter( Texture::eMinFilter_LINEAR_MIPMAP_NEAREST );
						texture->SetMagFilter( Texture::eMagFilter_LINEAR );

						// Insert Texture object into map that gives names to textures.
						mTextureMap.insert( pair<string,Texture2*>( textureName, texture ) );
					}
					else
					{
						*gStreamError << "ERROR: ReadFile() cannot read texture file: " << pathname << endl;
						Exit(1);
					}
				}
			}
		}
	}

    // Were any textures found?
    if ( mTextureMap.empty() )
	{
		goto ErrorNoTextures;
	}
}
