#include "D3DInterface.h"
#include "DDInterface.h"
#include "Graphics.h"
#include "DirectXErrorString.h"
#include "SexyMatrix.h"
#include "SexyAppBase.h"
#include "TriVertex.h"
#include <assert.h>
#include <algorithm>

#pragma warning(disable:4244)

using namespace Sexy;

static int gMinTextureWidth;
static int gMinTextureHeight;
static int gMaxTextureWidth;
static int gMaxTextureHeight;
static int gMaxTextureAspectRatio;
static DWORD gSupportedPixelFormats;
static bool gTextureSizeMustBePow2;
static const int MAX_TEXTURE_SIZE = 1024;
static bool gLinearFilter = false;
std::string D3DInterface::mErrorString;
static const int gVertexType = D3DFVF_TLVERTEX;

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void DisplayError(HRESULT theError, const char *theMsg)
{
	static bool hadError = false;
	if (!hadError)
	{
		std::string aMsg;
		std::string anError = GetDirectXErrorString(theError);
		aMsg = theMsg;
		aMsg += ": ";
		aMsg += anError;

		hadError = true;
		int aResult = MessageBoxA(NULL,aMsg.c_str(),"Error",MB_ABORTRETRYIGNORE);
		if (aResult==IDABORT)
			exit(0);
		else if (aResult==IDRETRY)
			_asm int 3;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool D3DInterface::CheckDXError(HRESULT theError, const char *theMsg)
{
	if(FAILED(theError))
	{
		std::string aMsg;
		std::string anError = GetDirectXErrorString(theError);
		aMsg = theMsg;
		aMsg += ": ";
		aMsg += anError;
		mErrorString = aMsg;
		gSexyAppBase->RegistryWriteString("Test3D\\RuntimeError",aMsg);

	//	DisplayError(theError,theMsg);
		return true;
	}
	else
		return false;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
D3DInterface::D3DInterface()
{
	mHWnd = NULL;
	mWidth = 640;
	mHeight = 480;
	mDD = NULL;
	mDDSDrawSurface = NULL;
	mZBuffer = NULL;

	mD3D = NULL;
	mD3DDevice = NULL;
	//mD3DViewport = NULL;
	mSceneBegun = false;
	mIsWindowed = true;

	gMinTextureWidth = 64;
	gMinTextureHeight = 64;
	gMaxTextureWidth = 64;
	gMaxTextureHeight = 64;
	gMaxTextureAspectRatio = 1;

/*
	//Test Transform
	SexyTransform2D aTrans;
	aTrans.Translate(-320,-240);
	aTrans.Rotate(45*3.14159f/180);
//	aTrans.Scale(1.3f,1.3f);
	aTrans.Translate(320,240);
	PushTransform(aTrans);
*/
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
D3DInterface::~D3DInterface()
{
	Cleanup();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::MakeDDPixelFormat(PixelFormat theFormatType, DDPIXELFORMAT* theFormat)
{
	ZeroMemory(theFormat,sizeof(DDPIXELFORMAT));
	theFormat->dwSize = sizeof(DDPIXELFORMAT);

	switch(theFormatType)
	{
		case PixelFormat_A8R8G8B8:
			theFormat->dwFlags = DDPF_ALPHAPIXELS | DDPF_RGB;
			theFormat->dwRGBBitCount = 32;
			theFormat->dwRGBAlphaBitMask	= 0xFF000000;
			theFormat->dwRBitMask			= 0x00FF0000;
			theFormat->dwGBitMask			= 0x0000FF00;
			theFormat->dwBBitMask			= 0x000000FF;
			break;


		case PixelFormat_A4R4G4B4:
			theFormat->dwFlags = DDPF_ALPHAPIXELS | DDPF_RGB;
			theFormat->dwRGBBitCount = 16;
			theFormat->dwRGBAlphaBitMask	= 0xF000;
			theFormat->dwRBitMask			= 0x0F00;
			theFormat->dwGBitMask			= 0x00F0;
			theFormat->dwBBitMask			= 0x000F;
			break;

		case PixelFormat_R5G6B5:
			theFormat->dwFlags = DDPF_RGB;
			theFormat->dwRGBBitCount = 16;
			theFormat->dwRBitMask			= 0xF800;
			theFormat->dwGBitMask			= 0x07E0;
			theFormat->dwBBitMask			= 0x001F;
			break;

		case PixelFormat_Palette8:
			theFormat->dwFlags = DDPF_RGB | DDPF_PALETTEINDEXED8;
			theFormat->dwRGBBitCount = 8;
			break;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
PixelFormat D3DInterface::GetDDPixelFormat(LPDDPIXELFORMAT theFormat) 
{
	if (theFormat->dwFlags						==	(DDPF_ALPHAPIXELS | DDPF_RGB)	&& 
		theFormat->dwRGBBitCount				==	32								&& 
		theFormat->dwRGBAlphaBitMask			==	0xFF000000						&&
		theFormat->dwRBitMask					==	0x00FF0000						&&
		theFormat->dwGBitMask					==	0x0000FF00						&&
		theFormat->dwBBitMask					==	0x000000FF)
	{
		return PixelFormat_A8R8G8B8;
	}

	if (theFormat->dwFlags						==	(DDPF_ALPHAPIXELS | DDPF_RGB)	&& 
		theFormat->dwRGBBitCount				==	16								&& 
		theFormat->dwRGBAlphaBitMask			==	0xF000							&&
		theFormat->dwRBitMask					==	0x0F00							&&
		theFormat->dwGBitMask					==	0x00F0							&&
		theFormat->dwBBitMask					==	0x000F)
	{
		return PixelFormat_A4R4G4B4;
	}

	if (theFormat->dwFlags						==	DDPF_RGB						&& 
		theFormat->dwRGBBitCount				==	16								&& 
		theFormat->dwRGBAlphaBitMask			==	0x0000							&&
		theFormat->dwRBitMask					==	0xF800							&&
		theFormat->dwGBitMask					==	0x07E0							&&
		theFormat->dwBBitMask					==	0x001F)
	{
		return PixelFormat_R5G6B5;
	}

	if (theFormat->dwFlags						== (DDPF_RGB | DDPF_PALETTEINDEXED8) &&
		theFormat->dwRGBBitCount				== 8)
	{
		return PixelFormat_Palette8;
	}

	return PixelFormat_Unknown;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
HRESULT CALLBACK D3DInterface::PixelFormatsCallback(LPDDPIXELFORMAT theFormat, LPVOID lpContext)
{
	gSupportedPixelFormats |= D3DInterface::GetDDPixelFormat(theFormat);
	
	return D3DENUMRET_OK; 
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::UpdateViewport()
{
	HRESULT hr;
	RECT aRect;
	GetClientRect(mHWnd, &aRect);

	POINT aTopLeft = {aRect.left, aRect.top};
	POINT aBotRight = {aRect.right, aRect.bottom};
	::ClientToScreen(mHWnd, &aTopLeft);
	::ClientToScreen(mHWnd, &aBotRight);

	RECT aScreenRect = {aTopLeft.x, aTopLeft.y, aBotRight.x, aBotRight.y};

	D3DVIEWPORT7 &aD3DViewport = mD3DViewport;
	aD3DViewport.dwX = 0;
	aD3DViewport.dwY = 0;
	aD3DViewport.dwWidth = aScreenRect.right - aScreenRect.left;
	aD3DViewport.dwHeight = aScreenRect.bottom - aScreenRect.top;
	aD3DViewport.dvMinZ = 0; //-2048.0f;
	aD3DViewport.dvMaxZ = 1; //2048.0f;

	hr = mD3DDevice->SetViewport(&mD3DViewport);	
}


//-----------------------------------------------------------------------------
// Name: EnumZBufferCallback()
// Desc: Enumeration function to report valid pixel formats for z-buffers.
//-----------------------------------------------------------------------------
static HRESULT WINAPI EnumZBufferCallback( DDPIXELFORMAT* pddpf, VOID* pddpfDesired )
{
    if( pddpf->dwFlags == DDPF_ZBUFFER )
    {
        memcpy( pddpfDesired, pddpf, sizeof(DDPIXELFORMAT) );
		return D3DENUMRET_CANCEL;
    }

    return D3DENUMRET_OK;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool D3DInterface::InitD3D()
{	
	if(CheckDXError(mDD->QueryInterface(IID_IDirect3D7, (LPVOID*) &mD3D),"QueryInterface IID_IDirect3D7"))
		return false;

	if(CheckDXError(mD3D->CreateDevice(IID_IDirect3DHALDevice, mDDSDrawSurface, &mD3DDevice),"CreateDevice IID_IDirect3DHALDevice"))
		return false;

	D3DDEVICEDESC7 aCaps;
	ZeroMemory(&aCaps,sizeof(aCaps));
	if(CheckDXError(mD3DDevice->GetCaps(&aCaps)))
		return false;

	gTextureSizeMustBePow2 = aCaps.dpcTriCaps.dwTextureCaps&D3DPTEXTURECAPS_NONPOW2CONDITIONAL?false:true;
	gMinTextureWidth = aCaps.dwMinTextureWidth;
	gMinTextureHeight = aCaps.dwMinTextureHeight;
	gMaxTextureWidth = aCaps.dwMaxTextureWidth;
	gMaxTextureHeight = aCaps.dwMaxTextureHeight;
	gMaxTextureAspectRatio = aCaps.dwMaxTextureAspectRatio;
	gLinearFilter = false;

	if (gMaxTextureWidth==0) // the card is not filling in these values so default them to something that will work
	{
		gMaxTextureWidth = 64;
		gMaxTextureHeight = 64;
		gMinTextureWidth = 64;
		gMinTextureHeight = 64;
		gMaxTextureAspectRatio = 1;
	}

	if (gMaxTextureWidth > MAX_TEXTURE_SIZE)
		gMaxTextureWidth = MAX_TEXTURE_SIZE;
	if (gMaxTextureHeight > MAX_TEXTURE_SIZE)
		gMaxTextureHeight = MAX_TEXTURE_SIZE;

	if (gMinTextureWidth < 1)
		gMinTextureWidth = 1;
	if (gMinTextureHeight < 1)
		gMinTextureWidth = 1;

	if (gMaxTextureAspectRatio==0)
		gMaxTextureAspectRatio = 65536;

	gSupportedPixelFormats = 0;
	mD3DDevice->EnumTextureFormats(PixelFormatsCallback,NULL);
/*	if (!(gSupportedPixelFormats & PixelFormat_A8R8G8B8))
	{
		mErrorString = "A8R8G8B8 texture format not supported.";
		return false;
	}*/

	if (!(aCaps.dpcTriCaps.dwTextureCaps & D3DPTEXTURECAPS_ALPHAPALETTE)) // need alpha in palettes
		gSupportedPixelFormats &= ~PixelFormat_Palette8;

	UpdateViewport();

	// Create ZBuffer
	DDPIXELFORMAT ddpfZBuffer;
	mD3D->EnumZBufferFormats( IID_IDirect3DHALDevice, EnumZBufferCallback, (VOID*)&ddpfZBuffer );

	DDSURFACEDESC2 ddsd;
	ZeroMemory( &ddsd, sizeof(DDSURFACEDESC2) );
	ddsd.dwSize         = sizeof(DDSURFACEDESC2);
    ddsd.dwFlags        = DDSD_CAPS|DDSD_WIDTH|DDSD_HEIGHT|DDSD_PIXELFORMAT;
    ddsd.ddsCaps.dwCaps = DDSCAPS_ZBUFFER | DDSCAPS_VIDEOMEMORY;
	ddsd.dwWidth        = mD3DViewport.dwWidth;
	ddsd.dwHeight       = mD3DViewport.dwHeight;
    memcpy( &ddsd.ddpfPixelFormat, &ddpfZBuffer, sizeof(DDPIXELFORMAT) );

	/*
	// Create z-buffer
    if( CheckDXError(mDD->CreateSurface( &ddsd, &mZBuffer, NULL ) ) )
		return false;

	// Attach the z-buffer to the back buffer.
    if( CheckDXError( mDDSDrawSurface->AddAttachedSurface( mZBuffer ) ) )
		return false;*/

	mD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET ,0xff000000, 1.0f, 0L);
	return true;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool D3DInterface::InitFromDDInterface(DDInterface *theInterface)
{
	mErrorString.erase();

	mDD = theInterface->mDD7;
	mHWnd = theInterface->mHWnd;
	mWidth = theInterface->mWidth;
	mHeight = theInterface->mHeight;

	if (CheckDXError(theInterface->mDrawSurface->QueryInterface(IID_IDirectDrawSurface7,(LPVOID*)&mDDSDrawSurface), "Query DrawSurface"))
		return false;


	mIsWindowed = theInterface->mIsWindowed;
	return InitD3D();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool gD3DInterfacePreDrawError = false;
bool D3DInterface::PreDraw()
{
	if (gSexyAppBase->mPhysMinimized)
		return false;

	if (!mSceneBegun)
	{
		HRESULT hr;


		if (!SUCCEEDED(mD3DDevice->SetRenderTarget(mDDSDrawSurface, 0))) // this happens when there's been a mode switch (this caused the nvidia screensaver bluescreen)
		{
			gD3DInterfacePreDrawError = true;
			return false;
		}
		else
			gD3DInterfacePreDrawError = false;

//		mD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET ,0xff000000, 1.0f, 0L);

		hr = mD3DDevice->BeginScene();
		
		// alphablend states 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);
		mD3DDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  D3DBLEND_SRCALPHA); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_LIGHTING , FALSE);

		// filter states 
		mD3DDevice->SetTextureStageState(0,D3DTSS_MINFILTER, D3DTFG_POINT); 
		mD3DDevice->SetTextureStageState(0,D3DTSS_MAGFILTER, D3DTFG_POINT); 
		mD3DDevice->SetTextureStageState(0,D3DTSS_MIPFILTER, D3DTFG_POINT); 
//		mD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG2, D3DTA_CURRENT );
//		mD3DDevice->SetTextureStageState(0,D3DTSS_ALPHAARG2, D3DTA_CURRENT );
//		mD3DDevice->SetTextureStageState(0,D3DTSS_COLORARG1, D3DTA_TEXTURE );
//		mD3DDevice->SetTextureStageState(0,D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
//		mD3DDevice->SetTextureStageState(0,D3DTSS_COLOROP , D3DTOP_MODULATE  );
		mD3DDevice->SetTextureStageState(0,D3DTSS_ALPHAOP, D3DTOP_MODULATE  );
		mD3DDevice->SetTextureStageState(0, D3DTSS_ADDRESSU, D3DTADDRESS_CLAMP); 
		hr = mD3DDevice->SetTextureStageState(0, D3DTSS_ADDRESSV, D3DTADDRESS_CLAMP); 

		// Setup non-texture render states 				
		mD3DDevice->SetRenderState(D3DRENDERSTATE_DITHERENABLE, FALSE); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_SPECULARENABLE, FALSE); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_ZWRITEENABLE, FALSE); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_ZENABLE, FALSE); 
		hr = mD3DDevice->SetRenderState(D3DRENDERSTATE_CULLMODE, D3DCULL_NONE); 				

		mSceneBegun = true;
		gLinearFilter = false;
	}

	return true;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static LPDIRECTDRAWSURFACE7 CreateTextureSurface(LPDIRECT3DDEVICE7 theDevice, LPDIRECTDRAW7 theDraw, int theWidth, int theHeight, PixelFormat theFormat)
{
	if (D3DInterface::CheckDXError(theDevice->SetTexture(0, NULL),"SetTexture NULL"))
		return NULL;

	DDSURFACEDESC2 aDesc;
	LPDIRECTDRAWSURFACE7 aSurface;

	ZeroMemory(&aDesc, sizeof(aDesc));
	aDesc.dwSize = sizeof(aDesc);
	
	aDesc.dwFlags = DDSD_CAPS | DDSD_HEIGHT | DDSD_WIDTH | DDSD_PIXELFORMAT;
	
	aDesc.ddsCaps.dwCaps = DDSCAPS_TEXTURE;	
//	aDesc.ddsCaps.dwCaps2 = DDSCAPS2_TEXTUREMANAGE;
	aDesc.ddsCaps.dwCaps2 = DDSCAPS2_D3DTEXTUREMANAGE;

	aDesc.dwWidth = theWidth;
	aDesc.dwHeight = theHeight;	

	aDesc.ddpfPixelFormat.dwSize = sizeof(DDPIXELFORMAT);
	D3DInterface::MakeDDPixelFormat(theFormat, &aDesc.ddpfPixelFormat);
//	D3DXMakeDDPixelFormat(theFormat, &aDesc.ddpfPixelFormat);

	HRESULT hr = theDraw->CreateSurface(&aDesc, &aSurface, NULL);

	if (FAILED(hr))
	{
		std::string anError = GetDirectXErrorString(hr);
		return NULL;	
	}

	return aSurface;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyImageToTexture8888(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight, bool rightPad)
{

	if (theImage->mColorTable == NULL)
	{
		DWORD *srcRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;
		char *dstRow = (char*)theDest;

		for(int y=0; y<theHeight; y++)
		{
			DWORD *src = srcRow;
			DWORD *dst = (DWORD*)dstRow;
			for(int x=0; x<theWidth; x++)
			{
				*dst++ = *src++;
			}

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}
	}
	else // palette
	{
		uchar *srcRow = (uchar*)theImage->mColorIndices + offy * theImage->GetWidth() + offx;
		uchar *dstRow = (uchar*)theDest;
		DWORD *palette = theImage->mColorTable;

		for(int y=0; y<theHeight; y++)
		{
			uchar *src = srcRow;
			DWORD *dst = (DWORD*)dstRow;
			for(int x=0; x<theWidth; x++)
				*dst++ = palette[*src++];

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyTexture8888ToImage(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight)
{		
	char *srcRow = (char*)theDest;
	DWORD *dstRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;

	for(int y=0; y<theHeight; y++)
	{
		DWORD *src = (DWORD*)srcRow;
		DWORD *dst = dstRow;
		
		for(int x=0; x<theWidth; x++)
			*dst++ = *src++;		

		dstRow += theImage->GetWidth();
		srcRow += theDestPitch;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyImageToTexture4444(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight, bool rightPad)
{
	if (theImage->mColorTable == NULL)
	{
		DWORD *srcRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;
		char *dstRow = (char*)theDest;

		for(int y=0; y<theHeight; y++)
		{
			DWORD *src = srcRow;
			ushort *dst = (ushort*)dstRow;
			for(int x=0; x<theWidth; x++)
			{
				DWORD aPixel = *src++;
				*dst++ = ((aPixel>>16)&0xF000) | ((aPixel>>12)&0x0F00) | ((aPixel>>8)&0x00F0) | ((aPixel>>4)&0x000F);
			}

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}
	}
	else // palette
	{
		uchar *srcRow = (uchar*)theImage->mColorIndices + offy * theImage->GetWidth() + offx;
		uchar *dstRow = (uchar*)theDest;
		DWORD *palette = theImage->mColorTable;

		for(int y=0; y<theHeight; y++)
		{
			uchar *src = srcRow;
			ushort *dst = (ushort*)dstRow;
			for(int x=0; x<theWidth; x++)
			{
				DWORD aPixel = palette[*src++];
				*dst++ = ((aPixel>>16)&0xF000) | ((aPixel>>12)&0x0F00) | ((aPixel>>8)&0x00F0) | ((aPixel>>4)&0x000F);
			}

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}

	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyTexture4444ToImage(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight)
{		
	char *srcRow = (char*)theDest;
	DWORD *dstRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;

	for(int y=0; y<theHeight; y++)
	{
		ushort *src = (ushort*)srcRow;
		DWORD *dst = dstRow;
		
		for(int x=0; x<theWidth; x++)
		{
			ushort aPixel = *src++;			
			*dst++ = 0xFF000000 | ((aPixel & 0xF000) << 16) | ((aPixel & 0x0F00) << 12) | ((aPixel & 0x00F0) << 8) | ((aPixel & 0x000F) << 4);
		}

		dstRow += theImage->GetWidth();
		srcRow += theDestPitch;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyImageToTexture565(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight, bool rightPad)
{
	if (theImage->mColorTable == NULL)
	{
		DWORD *srcRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;
		char *dstRow = (char*)theDest;

		for(int y=0; y<theHeight; y++)
		{
			DWORD *src = srcRow;
			ushort *dst = (ushort*)dstRow;
			for(int x=0; x<theWidth; x++)
			{
				DWORD aPixel = *src++;
				*dst++ = ((aPixel>>8)&0xF800) | ((aPixel>>5)&0x07E0) | ((aPixel>>3)&0x001F);
			}

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}
	}
	else // palette
	{
		uchar *srcRow = (uchar*)theImage->mColorIndices + offy * theImage->GetWidth() + offx;
		uchar *dstRow = (uchar*)theDest;
		DWORD *palette = theImage->mColorTable;

		for(int y=0; y<theHeight; y++)
		{
			uchar *src = srcRow;
			ushort *dst = (ushort*)dstRow;
			for(int x=0; x<theWidth; x++)
			{
				DWORD aPixel = palette[*src++];
				*dst++ = ((aPixel>>8)&0xF800) | ((aPixel>>5)&0x07E0) | ((aPixel>>3)&0x001F);
			}

			if (rightPad) 
				*dst = *(dst-1);

			srcRow += theImage->GetWidth();
			dstRow += theDestPitch;
		}

	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyTexture565ToImage(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight)
{		
	char *srcRow = (char*)theDest;
	DWORD *dstRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;

	for(int y=0; y<theHeight; y++)
	{
		ushort *src = (ushort*)srcRow;
		DWORD *dst = dstRow;
		
		for(int x=0; x<theWidth; x++)
		{
			ushort aPixel = *src++;			
			*dst++ = 0xFF000000 | ((aPixel & 0xF800) << 8) | ((aPixel & 0x07E0) << 5) | ((aPixel & 0x001F) << 3);
		}

		dstRow += theImage->GetWidth();
		srcRow += theDestPitch;
	}
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyImageToTexturePalette8(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight, bool rightPad)
{
	uchar *srcRow = (uchar*)theImage->mColorIndices + offy * theImage->GetWidth() + offx;
	uchar *dstRow = (uchar*)theDest;

	for(int y=0; y<theHeight; y++)
	{
		uchar *src = srcRow;
		uchar *dst = dstRow;
		for(int x=0; x<theWidth; x++)
			*dst++ = *src++;

		if (rightPad) 
			*dst = *(dst-1);

		srcRow += theImage->GetWidth();
		dstRow += theDestPitch;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyTexturePalette8ToImage(void *theDest, DWORD theDestPitch, MemoryImage *theImage, int offx, int offy, int theWidth, int theHeight, LPDIRECTDRAWPALETTE thePalette)
{
	char *srcRow = (char*)theDest;
	DWORD *dstRow = theImage->GetBits() + offy * theImage->GetWidth() + offx;

	PALETTEENTRY aPaletteEntries[256];
	thePalette->GetEntries(0, 0, 256, aPaletteEntries); 

	for(int y=0; y<theHeight; y++)
	{
		uchar *src = (uchar*) srcRow;
		DWORD *dst = dstRow;
		
		for(int x=0; x<theWidth; x++)
		{
			DWORD aPixel = *((DWORD*)(aPaletteEntries+*src++));
			*dst++ = (aPixel&0xFF00FF00) | ((aPixel>>16)&0xFF) | ((aPixel<<16)&0xFF0000);
		}

		dstRow += theImage->GetWidth();
		srcRow += theDestPitch;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void CopyImageToTexture(LPDIRECTDRAWSURFACE7 theTexture, MemoryImage *theImage, int offx, int offy, int texWidth, int texHeight, PixelFormat theFormat)
{
	if (theTexture==NULL)
		return;

	DDSURFACEDESC2 aDesc;
	aDesc.dwSize = sizeof(aDesc);
	if (D3DInterface::CheckDXError(theTexture->Lock(NULL,&aDesc,DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_WRITEONLY,NULL),"Lock Texture"))
		return;

	int aWidth = min(texWidth,(theImage->GetWidth()-offx));
	int aHeight = min(texHeight,(theImage->GetHeight()-offy));

	bool rightPad = aWidth<texWidth;
	bool bottomPad = aHeight<texHeight;
//	if(aWidth < texWidth || aHeight < texHeight)
//		memset(aDesc.lpSurface, 0, aDesc.lPitch*aDesc.dwHeight);

	if(aWidth>0 && aHeight>0)
	{
		switch (theFormat)
		{
			case PixelFormat_A8R8G8B8:	CopyImageToTexture8888(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight, rightPad); break;
			case PixelFormat_A4R4G4B4:	CopyImageToTexture4444(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight, rightPad); break;
			case PixelFormat_R5G6B5:	CopyImageToTexture565(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight, rightPad); break;
			case PixelFormat_Palette8:	CopyImageToTexturePalette8(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight, rightPad); break;
		}

		if (bottomPad)
		{
			uchar *dstrow = ((uchar*)aDesc.lpSurface)+aDesc.lPitch*aHeight;
			memcpy(dstrow,dstrow-aDesc.lPitch,aDesc.lPitch);
		}
	}

	D3DInterface::CheckDXError(theTexture->Unlock(NULL),"Texture Unlock");
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static int GetClosestPowerOf2Above(int theNum)
{
	int aPower2 = 1;
	while (aPower2 < theNum)
		aPower2<<=1;

	return aPower2;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static bool IsPowerOf2(int theNum)
{
	int aNumBits = 0;
	while (theNum>0)
	{
		aNumBits += theNum&1;
		theNum >>= 1;
	}

	return aNumBits==1;
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void GetBestTextureDimensions(int &theWidth, int &theHeight, bool isEdge, bool usePow2, DWORD theImageFlags)
{
//	theImageFlags = D3DImageFlag_MinimizeNumSubdivisions;
	if (theImageFlags & D3DImageFlag_Use64By64Subdivisions)
	{
		theWidth = theHeight = 64;
		return;
	}

	static int aGoodTextureSize[MAX_TEXTURE_SIZE];
	static bool haveInited = false;
	if (!haveInited)
	{
		haveInited = true;
		int i;
		int aPow2 = 1;
		for (i=0; i<MAX_TEXTURE_SIZE; i++)
		{
			if (i > aPow2)
				aPow2 <<= 1;

			int aGoodValue = aPow2;
			if ((aGoodValue - i ) > 64)
			{
				aGoodValue >>= 1;
				while (true)
				{
					int aLeftOver = i % aGoodValue;
					if (aLeftOver<64 || IsPowerOf2(aLeftOver))
						break;

					aGoodValue >>= 1;
				}
			}
			aGoodTextureSize[i] = aGoodValue;
		}
	}

	int aWidth = theWidth;
	int aHeight = theHeight;

	if (usePow2)
	{
		if (isEdge || (theImageFlags & D3DImageFlag_MinimizeNumSubdivisions))
		{
			aWidth = aWidth >= gMaxTextureWidth ? gMaxTextureWidth : GetClosestPowerOf2Above(aWidth);
			aHeight = aHeight >= gMaxTextureHeight ? gMaxTextureHeight : GetClosestPowerOf2Above(aHeight);
		}
		else
		{
			aWidth = aWidth >= gMaxTextureWidth ? gMaxTextureWidth : aGoodTextureSize[aWidth];
			aHeight = aHeight >= gMaxTextureHeight ? gMaxTextureHeight : aGoodTextureSize[aHeight];
		}
	}

	if (aWidth < gMinTextureWidth)
		aWidth = gMinTextureWidth;

	if (aHeight < gMinTextureHeight)
		aHeight = gMinTextureHeight;

	if (aWidth > aHeight)
	{
		while (aWidth > gMaxTextureAspectRatio*aHeight)
			aHeight <<= 1;
	}
	else if (aHeight > aWidth)
	{
		while (aHeight > gMaxTextureAspectRatio*aWidth)
			aWidth <<= 1;
	}

	theWidth = aWidth;
	theHeight = aHeight;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
TextureData::TextureData()
{
	mWidth = 0;
	mHeight = 0;
	mTexVecWidth = 0;
	mTexVecHeight = 0;
	mBitsChangedCount = 0;
	mTexMemSize = 0;
	mTexPieceWidth = 64;
	mTexPieceHeight = 64;

	mPalette = NULL;
	mPixelFormat = PixelFormat_Unknown;
	mImageFlags = 0;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
TextureData::~TextureData()
{
	ReleaseTextures();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::ReleaseTextures()
{
	for(int i=0; i<(int)mTextures.size(); i++)
	{
		LPDIRECTDRAWSURFACE7 aSurface = mTextures[i].mTexture;
		if (aSurface!=NULL)
			aSurface->Release();
	}

	mTextures.clear();

	mTexMemSize = 0;

	if (mPalette!=NULL)
	{
		mPalette->Release();
		mPalette = NULL;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::CreateTextureDimensions(MemoryImage *theImage)
{
	int aWidth = theImage->GetWidth();
	int aHeight = theImage->GetHeight();
	int i;
/**/
	// Calculate inner piece sizes
	mTexPieceWidth = aWidth;
	mTexPieceHeight = aHeight;
	bool usePow2 = true; //gTextureSizeMustBePow2 || mPixelFormat==PixelFormat_Palette8;
	GetBestTextureDimensions(mTexPieceWidth, mTexPieceHeight,false,usePow2,mImageFlags);

	// Calculate right boundary piece sizes
	int aRightWidth = aWidth%mTexPieceWidth;
	int aRightHeight = mTexPieceHeight;
	if (aRightWidth > 0)
		GetBestTextureDimensions(aRightWidth, aRightHeight,true,usePow2,mImageFlags);
	else
		aRightWidth = mTexPieceWidth;

	// Calculate bottom boundary piece sizes
	int aBottomWidth = mTexPieceWidth;
	int aBottomHeight = aHeight%mTexPieceHeight;
	if (aBottomHeight > 0)
		GetBestTextureDimensions(aBottomWidth, aBottomHeight,true,usePow2,mImageFlags);
	else
		aBottomHeight = mTexPieceHeight;

	// Calculate corner piece size
	int aCornerWidth = aRightWidth;
	int aCornerHeight = aBottomHeight;
	GetBestTextureDimensions(aCornerWidth, aCornerHeight,true,usePow2,mImageFlags);
/**/

//	mTexPieceWidth = 64;
//	mTexPieceHeight = 64;


	// Allocate texture array
	mTexVecWidth = (aWidth + mTexPieceWidth - 1)/mTexPieceWidth;
	mTexVecHeight = (aHeight + mTexPieceHeight - 1)/mTexPieceHeight;
	mTextures.resize(mTexVecWidth*mTexVecHeight);
	
	// Assign inner pieces
	for(i=0; i<(int)mTextures.size(); i++)
	{
		TextureDataPiece &aPiece = mTextures[i];
		aPiece.mTexture = NULL;
		aPiece.mWidth = mTexPieceWidth;
		aPiece.mHeight = mTexPieceHeight;
	}

	// Assign right pieces
/**/
	for(i=mTexVecWidth-1; i<(int)mTextures.size(); i+=mTexVecWidth)
	{
		TextureDataPiece &aPiece = mTextures[i];
		aPiece.mWidth = aRightWidth;
		aPiece.mHeight = aRightHeight;
	}

	// Assign bottom pieces
	for(i=mTexVecWidth*(mTexVecHeight-1); i<(int)mTextures.size(); i++)
	{
		TextureDataPiece &aPiece = mTextures[i];
		aPiece.mWidth = aBottomWidth;
		aPiece.mHeight = aBottomHeight;
	}

	// Assign corner piece
	mTextures.back().mWidth = aCornerWidth;
	mTextures.back().mHeight = aCornerHeight;
/**/

	mMaxTotalU = aWidth/(float)mTexPieceWidth;
	mMaxTotalV = aHeight/(float)mTexPieceHeight;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::CreateTextures(MemoryImage *theImage, LPDIRECT3DDEVICE7 theDevice, LPDIRECTDRAW7 theDraw)
{
	theImage->DeleteSWBuffers(); // don't need these buffers for 3d drawing

	// Choose appropriate pixel format
	PixelFormat aFormat = PixelFormat_A8R8G8B8;
	//theImage->mD3DFlags = D3DImageFlag_UseA4R4G4B4;

	theImage->CommitBits();
	if (!theImage->mHasAlpha && !theImage->mHasTrans && (gSupportedPixelFormats & PixelFormat_R5G6B5))
	{
		if (!(theImage->mD3DFlags & D3DImageFlag_UseA8R8G8B8))
			aFormat = PixelFormat_R5G6B5;
	}

	LPDIRECTDRAWPALETTE aDDPalette = NULL;
	if (theImage->mColorIndices != NULL && (gSupportedPixelFormats & PixelFormat_Palette8))
	{
		PALETTEENTRY aPalette[256];
		for (int i=0; i<256; i++)
		{
			DWORD aPixel = theImage->mColorTable[i];
			*(DWORD*)(aPalette+i) = (aPixel&0xFF00FF00) | ((aPixel>>16)&0xFF) | ((aPixel<<16)&0xFF0000);
		}
		HRESULT aResult = theDraw->CreatePalette(DDPCAPS_8BIT | DDPCAPS_ALPHA | DDPCAPS_ALLOW256,aPalette, &aDDPalette, NULL);
		if (SUCCEEDED(aResult))
			aFormat = PixelFormat_Palette8;
		else
		{
			std::string anError = GetDirectXErrorString(aResult);
			gSupportedPixelFormats &= ~PixelFormat_Palette8;
		}
	}

	if ((theImage->mD3DFlags & D3DImageFlag_UseA4R4G4B4) && aFormat==PixelFormat_A8R8G8B8 && (gSupportedPixelFormats & PixelFormat_A4R4G4B4))
		aFormat = PixelFormat_A4R4G4B4;

	if (aFormat==PixelFormat_A8R8G8B8 && !(gSupportedPixelFormats & PixelFormat_A8R8G8B8))
		aFormat = PixelFormat_A4R4G4B4;


	// Release texture if image size has changed
	bool createTextures = false;
	if (mWidth!=theImage->mWidth || mHeight!=theImage->mHeight || aFormat!=mPixelFormat || theImage->mD3DFlags!=mImageFlags)
	{
		ReleaseTextures();

		mPixelFormat = aFormat;
		mImageFlags = theImage->mD3DFlags;
		CreateTextureDimensions(theImage);
		createTextures = true;
	}

	mPalette =  aDDPalette;

	int i,x,y;

	int aHeight = theImage->GetHeight();
	int aWidth = theImage->GetWidth();

	if (mPalette!=NULL)
		mTexMemSize += 256*4;

	int aFormatSize = 4;
	if (aFormat==PixelFormat_Palette8)
		aFormatSize = 1;
	else if (aFormat==PixelFormat_R5G6B5)
		aFormatSize = 2;
	else if (aFormat==PixelFormat_A4R4G4B4)
		aFormatSize = 2;

	i=0;
	for(y=0; y<aHeight; y+=mTexPieceHeight)
	{
		for(x=0; x<aWidth; x+=mTexPieceWidth, i++)
		{
			TextureDataPiece &aPiece = mTextures[i];
			if (createTextures)
			{
				aPiece.mTexture = CreateTextureSurface(theDevice, theDraw, aPiece.mWidth, aPiece.mHeight, aFormat);
				if (aPiece.mTexture==NULL) // create texture failure
				{
					mPixelFormat = PixelFormat_Unknown;
					return;
				}

				if (mPalette!=NULL)
					aPiece.mTexture->SetPalette(mPalette);
					
				mTexMemSize += aPiece.mWidth*aPiece.mHeight*aFormatSize;
			}

			CopyImageToTexture(aPiece.mTexture,theImage,x,y,aPiece.mWidth,aPiece.mHeight,aFormat);
		}
	}

	mWidth = theImage->mWidth;
	mHeight = theImage->mHeight;
	mBitsChangedCount = theImage->mBitsChangedCount;
	mPixelFormat = aFormat;
}
	
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::CheckCreateTextures(MemoryImage *theImage, LPDIRECT3DDEVICE7 theDevice, LPDIRECTDRAW7 theDraw)
{
	if(mPixelFormat==PixelFormat_Unknown || theImage->mWidth != mWidth || theImage->mHeight != mHeight || theImage->mBitsChangedCount != mBitsChangedCount || theImage->mD3DFlags != mImageFlags)
		CreateTextures(theImage, theDevice, theDraw);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
LPDIRECTDRAWSURFACE7 TextureData::GetTexture(int x, int y, int &width, int &height, float &u1, float &v1, float &u2, float &v2)
{
	int tx = x/mTexPieceWidth;
	int ty = y/mTexPieceHeight;

	TextureDataPiece &aPiece = mTextures[ty*mTexVecWidth + tx];

	int left = x%mTexPieceWidth;
	int top = y%mTexPieceHeight;
	int right = left+width;
	int bottom = top+height;

	if(right > aPiece.mWidth)
		right = aPiece.mWidth;

	if(bottom > aPiece.mHeight)
		bottom = aPiece.mHeight;

	width = right-left;
	height = bottom-top;

	u1 = (float)left/aPiece.mWidth;
	v1 = (float)top/aPiece.mHeight;
	u2 = (float)right/aPiece.mWidth;
	v2 = (float)bottom/aPiece.mHeight;

	return aPiece.mTexture;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
LPDIRECTDRAWSURFACE7 TextureData::GetTextureF(float x, float y, float &width, float &height, float &u1, float &v1, float &u2, float &v2)
{
	int tx = x/mTexPieceWidth;
	int ty = y/mTexPieceHeight;

	TextureDataPiece &aPiece = mTextures[ty*mTexVecWidth + tx];

	float left = x - tx*mTexPieceWidth;
	float top = y - ty*mTexPieceHeight;
	float right = left+width;
	float bottom = top+height;

	if(right > aPiece.mWidth)
		right = aPiece.mWidth;

	if(bottom > aPiece.mHeight)
		bottom = aPiece.mHeight;

	width = right-left;
	height = bottom-top;

	u1 = (float)left/aPiece.mWidth;
	v1 = (float)top/aPiece.mHeight;
	u2 = (float)right/aPiece.mWidth;
	v2 = (float)bottom/aPiece.mHeight;

	return aPiece.mTexture;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void SetLinearFilter(LPDIRECT3DDEVICE7 theDevice, bool linear)
{
	if (gLinearFilter != linear)
	{
		D3DTEXTUREMAGFILTER aFilter = linear ? D3DTFG_LINEAR : D3DTFG_POINT;		

		const char *aDebugContext = linear ? "SetTextureStageState LINEAR" : "SetTextureStageState Point";

		D3DInterface::CheckDXError(theDevice->SetTextureStageState(0,D3DTSS_MINFILTER, aFilter),aDebugContext);
		D3DInterface::CheckDXError(theDevice->SetTextureStageState(0,D3DTSS_MAGFILTER, aFilter),aDebugContext);
		D3DInterface::CheckDXError(theDevice->SetTextureStageState(0,D3DTSS_MIPFILTER, aFilter),aDebugContext);

		gLinearFilter = linear;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::Blt(LPDIRECT3DDEVICE7 theDevice, float theX, float theY, const Rect& theSrcRect, const Color& theColor)
{
	int srcLeft = theSrcRect.mX;
	int srcTop = theSrcRect.mY;
	int srcRight = srcLeft + theSrcRect.mWidth;
	int srcBottom = srcTop + theSrcRect.mHeight;
	int srcX, srcY;
	float dstX, dstY;
	int aWidth,aHeight;
	float u1,v1,u2,v2;

	srcY = srcTop;
	dstY = theY;

	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			

	if ((srcLeft >= srcRight) || (srcTop >= srcBottom))
		return;

	while(srcY < srcBottom)
	{
		srcX = srcLeft;
		dstX = theX;
		while(srcX < srcRight)
		{
			aWidth = srcRight-srcX;
			aHeight = srcBottom-srcY;
			LPDIRECTDRAWSURFACE7 aTexture = GetTexture(srcX, srcY, aWidth, aHeight, u1, v1, u2, v2);

			float x = dstX - 0.5f;
			float y = dstY - 0.5f;

			D3DTLVERTEX aVertex[4] = 
			{
				{ x,				y,				0,	1,	aColor,	0,	u1,		v1 },
				{ x,				y+aHeight,		0,	1,	aColor,	0,	u1,		v2 },
				{ x+aWidth,			y,				0,	1,	aColor,	0,	u2,		v1 },
				{ x+aWidth,			y+aHeight,		0,	1,	aColor,	0,	u2,		v2 }
			};

			
			D3DInterface::CheckDXError(theDevice->SetTexture(0, aTexture),"SetTexture gTexture");			
			D3DInterface::CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, gVertexType, aVertex, 4, 0),"DrawPrimitive (Tri) 1");

			srcX += aWidth;
			dstX += aWidth;
		}

		srcY += aHeight;
		dstY += aHeight;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
//typedef std::vector<D3DTLVERTEX> VertexList;
struct VertexList
{
	enum { MAX_STACK_VERTS = 100 };
	D3DTLVERTEX mStackVerts[MAX_STACK_VERTS];
	D3DTLVERTEX *mVerts;
	int mSize;
	int mCapacity;

	typedef int size_type;

	VertexList() : mSize(0), mCapacity(MAX_STACK_VERTS), mVerts(mStackVerts) { }
	VertexList(const VertexList &theList) : mSize(theList.mSize), mCapacity(MAX_STACK_VERTS), mVerts(mStackVerts)  
	{ 
		reserve(mSize);
		memcpy(mVerts,theList.mVerts,mSize*sizeof(mVerts[0]));
	}
	
	~VertexList() 
	{ 
		if (mVerts != mStackVerts)
			delete mVerts; 
	}

	void reserve(int theCapacity)
	{
		if (mCapacity < theCapacity)
		{
			mCapacity = theCapacity;
			D3DTLVERTEX *aNewList = new D3DTLVERTEX[theCapacity];
			memcpy(aNewList,mVerts,mSize*sizeof(mVerts[0]));
			if (mVerts != mStackVerts)
				delete mVerts;

			mVerts = aNewList;
		}
	}

	void push_back(const D3DTLVERTEX &theVert) 
	{ 
		if (mSize==mCapacity)
			reserve(mCapacity*2);
			
		mVerts[mSize++] = theVert; 
	}
	
	void operator=(const VertexList &theList) 
	{ 
		reserve(theList.mSize);
		mSize = theList.mSize; 
		memcpy(mVerts,theList.mVerts,mSize*sizeof(mVerts[0]));
	}
	

	D3DTLVERTEX& operator[](int thePos) 
	{ 
		return mVerts[thePos]; 
	}

	int size() { return mSize; }
	void clear() { mSize = 0; }
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static inline float GetCoord(const D3DTLVERTEX &theVertex, int theCoord)
{
	switch (theCoord)
	{
		case 0: return theVertex.sx;
		case 1: return theVertex.sy;
		case 2: return theVertex.sz;
		case 3: return theVertex.tu;
		case 4: return theVertex.tv;
		default: return 0;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static inline D3DTLVERTEX Interpolate(const D3DTLVERTEX &v1, const D3DTLVERTEX &v2, float t)
{
	D3DTLVERTEX aVertex = v1;
	aVertex.sx = v1.sx + t*(v2.sx-v1.sx);
	aVertex.sy = v1.sy + t*(v2.sy-v1.sy);
	aVertex.tu = v1.tu + t*(v2.tu-v1.tu);
	aVertex.tv = v1.tv + t*(v2.tv-v1.tv);
	if (v1.color!=v2.color)
	{
		int r = RGBA_GETRED(v1.color) + t*(RGBA_GETRED(v2.color) - RGBA_GETRED(v1.color));
		int g = RGBA_GETGREEN(v1.color) + t*(RGBA_GETGREEN(v2.color) - RGBA_GETGREEN(v1.color));
		int b = RGBA_GETBLUE(v1.color) + t*(RGBA_GETBLUE(v2.color) - RGBA_GETBLUE(v1.color));
		int a = RGBA_GETALPHA(v1.color) + t*(RGBA_GETALPHA(v2.color) - RGBA_GETALPHA(v1.color));
		aVertex.color = RGBA_MAKE(r,g,b,a);
	}
	
	return aVertex;
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
template<class Pred>
struct PointClipper
{
	Pred mPred;

	void ClipPoint(int n, float clipVal, const D3DTLVERTEX &v1, const D3DTLVERTEX &v2, VertexList &out);
	void ClipPoints(int n, float clipVal, VertexList &in, VertexList &out);
};

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
template<class Pred>
void PointClipper<Pred>::ClipPoint(int n, float clipVal, const D3DTLVERTEX &v1, const D3DTLVERTEX &v2, VertexList &out)
{
	if (!mPred(GetCoord(v1,n), clipVal))
	{
		if (!mPred(GetCoord(v2,n), clipVal)) // both inside
			out.push_back(v2);
		else // inside -> outside
		{
			float t = (clipVal - GetCoord(v1,n))/(GetCoord(v2,n)-GetCoord(v1,n));
			out.push_back(Interpolate(v1,v2,t));
		}
	}
	else
	{
		if (!mPred(GetCoord(v2,n), clipVal)) // outside -> inside
		{
			float t = (clipVal - GetCoord(v1, n))/(GetCoord(v2,n)-GetCoord(v1,n));
			out.push_back(Interpolate(v1,v2,t));
			out.push_back(v2);
		}
//			else // outside -> outside
	}
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
template<class Pred>
void PointClipper<Pred>::ClipPoints(int n, float clipVal, VertexList &in, VertexList &out)
{
	if(in.size()<2)
		return;

	ClipPoint(n,clipVal,in[in.size()-1],in[0],out);
	for(VertexList::size_type i=0; i<in.size()-1; i++)
		ClipPoint(n,clipVal,in[i],in[i+1],out);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void DrawPolyClipped(LPDIRECT3DDEVICE7 theDevice, const Rect *theClipRect, const VertexList &theList)
{
	VertexList l1, l2;
	l1 = theList;

	int left = theClipRect->mX;
	int right = left + theClipRect->mWidth;
	int top = theClipRect->mY;
	int bottom = top + theClipRect->mHeight;

	VertexList *in = &l1, *out = &l2;
	PointClipper<std::less<float> > aLessClipper;
	PointClipper<std::greater_equal<float> > aGreaterClipper;

	aLessClipper.ClipPoints(0,left,*in,*out); std::swap(in,out); out->clear();
	aLessClipper.ClipPoints(1,top,*in,*out); std::swap(in,out); out->clear();
	aGreaterClipper.ClipPoints(0,right,*in,*out); std::swap(in,out); out->clear();
	aGreaterClipper.ClipPoints(1,bottom,*in,*out); 

	VertexList &aList = *out;
	
	if (aList.size() >= 3)
		D3DInterface::CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, gVertexType, &aList[0], aList.size(), 0),"DrawPrimitive (Tri) 2");
}


///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
static void DoPolyTextureClip(VertexList &theList)
{
	VertexList l2;

	float left = 0;
	float right = 1;
	float top = 0;
	float bottom = 1;

	VertexList *in = &theList, *out = &l2;
	PointClipper<std::less<float> > aLessClipper;
	PointClipper<std::greater_equal<float> > aGreaterClipper;

	aLessClipper.ClipPoints(3,left,*in,*out); std::swap(in,out); out->clear();
	aLessClipper.ClipPoints(4,top,*in,*out); std::swap(in,out); out->clear();
	aGreaterClipper.ClipPoints(3,right,*in,*out); std::swap(in,out); out->clear();
	aGreaterClipper.ClipPoints(4,bottom,*in,*out); 
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::BltTransformed(LPDIRECT3DDEVICE7 theDevice, const SexyMatrix3 &theTrans, const Rect& theSrcRect, const Color& theColor, const Rect *theClipRect, float theX, float theY, bool center)
{
	int srcLeft = theSrcRect.mX;
	int srcTop = theSrcRect.mY;
	int srcRight = srcLeft + theSrcRect.mWidth;
	int srcBottom = srcTop + theSrcRect.mHeight;
	int srcX, srcY;
	float dstX, dstY;
	int aWidth;
	int aHeight;
	float u1,v1,u2,v2;
	float startx = 0, starty = 0;
	float pixelcorrect = 0.5f;

	if (center)
	{
		startx = -theSrcRect.mWidth/2.0f;
		starty = -theSrcRect.mHeight/2.0f;
		pixelcorrect = 0.0f;
	}			

	srcY = srcTop;
	dstY = starty;

	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			

	if ((srcLeft >= srcRight) || (srcTop >= srcBottom))
		return;

	while(srcY < srcBottom)
	{
		srcX = srcLeft;
		dstX = startx;
		while(srcX < srcRight)
		{
			aWidth = srcRight-srcX;
			aHeight = srcBottom-srcY;
			LPDIRECTDRAWSURFACE7 aTexture = GetTexture(srcX, srcY, aWidth, aHeight, u1, v1, u2, v2);

			float x = dstX; // - 0.5f;
			float y = dstY; // - 0.5f;

			SexyVector2 p[4] = { SexyVector2(x, y), SexyVector2(x,y+aHeight), SexyVector2(x+aWidth, y) , SexyVector2(x+aWidth, y+aHeight) };
			SexyVector2 tp[4];

			int i;
			for (i=0; i<4; i++)
			{
				tp[i] = theTrans*p[i];
				tp[i].x -= pixelcorrect - theX;
				tp[i].y -= pixelcorrect - theY;
			}

			bool clipped = false;
			if (theClipRect != NULL)
			{
				int left = theClipRect->mX;
				int right = left + theClipRect->mWidth;
				int top = theClipRect->mY;
				int bottom = top + theClipRect->mHeight;
				for (i=0; i<4; i++)
				{
					if (tp[i].x<left || tp[i].x>=right || tp[i].y<top || tp[i].y>=bottom)
					{
						clipped = true;
						break;
					}
				}
			}

			D3DTLVERTEX aVertex[4] = 
			{
				{ tp[0].x,				tp[0].y,			0,	1,	aColor,	0,	u1,		v1 },
				{ tp[1].x,				tp[1].y,			0,	1,	aColor,	0,	u1,		v2 },
				{ tp[2].x,				tp[2].y,			0,	1,	aColor,	0,	u2,		v1 },
				{ tp[3].x,				tp[3].y,			0,	1,	aColor,	0,	u2,		v2 }
			};

			D3DInterface::CheckDXError(theDevice->SetTexture(0, aTexture),"SetTexture gTexture");

			if (!clipped)
				D3DInterface::CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, gVertexType, aVertex, 4, 0),"DrawPrimitive (Tri) 3");
			else
			{
				VertexList aList;
				aList.push_back(aVertex[0]);
				aList.push_back(aVertex[1]);
				aList.push_back(aVertex[3]);
				aList.push_back(aVertex[2]);

				DrawPolyClipped(theDevice, theClipRect, aList);
//				DrawPolyClipped(theDevice, theClipRect, aVertex+1, 3);
			}

//			D3DInterface::CheckDXError(theDevice->SetTexture(0, NULL),"SetTexture NULL");

			srcX += aWidth;
			dstX += aWidth;
		}

		srcY += aHeight;
		dstY += aHeight;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
#define GetColorFromTriVertex(theVertex, theColor) (theVertex.color?theVertex.color:theColor)

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void TextureData::BltTriangles(LPDIRECT3DDEVICE7 theDevice, const TriVertex theVertices[][3], int theNumTriangles, DWORD theColor, float tx, float ty)
{	
	if ((mMaxTotalU <= 1.0) && (mMaxTotalV <= 1.0))
	{
		D3DInterface::CheckDXError(theDevice->SetTexture(0, mTextures[0].mTexture),"SetTexture gTexture");

		D3DTLVERTEX aVertexCache[100][3];
		int aVertexCacheNum = 0;

		for (int aTriangleNum = 0; aTriangleNum < theNumTriangles; aTriangleNum++)
		{
			TriVertex* aTriVerts = (TriVertex*) theVertices[aTriangleNum];
			D3DTLVERTEX* aD3DVertex = (D3DTLVERTEX*) aVertexCache[aVertexCacheNum++];

			aD3DVertex[0].sx = aTriVerts[0].x + tx;
			aD3DVertex[0].sy = aTriVerts[0].y + ty;
			aD3DVertex[0].sz = 0;
			aD3DVertex[0].rhw = 1;
			aD3DVertex[0].color = GetColorFromTriVertex(aTriVerts[0],theColor);
			aD3DVertex[0].specular = 0;
			aD3DVertex[0].tu = aTriVerts[0].u * mMaxTotalU;
			aD3DVertex[0].tv = aTriVerts[0].v * mMaxTotalV;

			aD3DVertex[1].sx = aTriVerts[1].x + tx;
			aD3DVertex[1].sy = aTriVerts[1].y + ty;
			aD3DVertex[1].sz = 0;
			aD3DVertex[1].rhw = 1;
			aD3DVertex[1].color = GetColorFromTriVertex(aTriVerts[1],theColor);
			aD3DVertex[1].specular = 0;
			aD3DVertex[1].tu = aTriVerts[1].u * mMaxTotalU;
			aD3DVertex[1].tv = aTriVerts[1].v * mMaxTotalV;

			aD3DVertex[2].sx = aTriVerts[2].x + tx;
			aD3DVertex[2].sy = aTriVerts[2].y + ty;
			aD3DVertex[2].sz = 0;
			aD3DVertex[2].rhw = 1;
			aD3DVertex[2].color = GetColorFromTriVertex(aTriVerts[2],theColor);
			aD3DVertex[2].specular = 0;
			aD3DVertex[2].tu = aTriVerts[2].u * mMaxTotalU;
			aD3DVertex[2].tv = aTriVerts[2].v * mMaxTotalV;
			
			if ((aVertexCacheNum == 100) || (aTriangleNum == theNumTriangles - 1))
			{
				// Flush the triangles now
				D3DInterface::CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLELIST, gVertexType, aVertexCache, aVertexCacheNum*3, 0),"DrawPrimitive (TriList)");				
				aVertexCacheNum = 0;
			}
		}
	}
	else
	{
		for (int aTriangleNum = 0; aTriangleNum < theNumTriangles; aTriangleNum++)
		{
			TriVertex* aTriVerts = (TriVertex*) theVertices[aTriangleNum];

			D3DTLVERTEX aVertex[3] = 
			{
				{ aTriVerts[0].x + tx,	aTriVerts[0].y + ty,	0,	1,	GetColorFromTriVertex(aTriVerts[0],theColor),	0,	aTriVerts[0].u*mMaxTotalU,	aTriVerts[0].v*mMaxTotalV },
				{ aTriVerts[1].x + tx,	aTriVerts[1].y + ty,	0,	1,	GetColorFromTriVertex(aTriVerts[1],theColor),	0,	aTriVerts[1].u*mMaxTotalU,	aTriVerts[1].v*mMaxTotalV },
				{ aTriVerts[2].x + tx,	aTriVerts[2].y + ty,	0,	1,	GetColorFromTriVertex(aTriVerts[2],theColor),	0,	aTriVerts[2].u*mMaxTotalU,	aTriVerts[2].v*mMaxTotalV }
			};

			float aMinU = mMaxTotalU, aMinV = mMaxTotalV;
			float aMaxU = 0, aMaxV = 0;

			int i,j,k;
			for (i=0; i<3; i++)
			{
				if(aVertex[i].tu < aMinU)
					aMinU = aVertex[i].tu;

				if(aVertex[i].tv < aMinV)
					aMinV = aVertex[i].tv;

				if(aVertex[i].tu > aMaxU)
					aMaxU = aVertex[i].tu;

				if(aVertex[i].tv > aMaxV)
					aMaxV = aVertex[i].tv;
			}
			
			VertexList aMasterList;
			aMasterList.push_back(aVertex[0]);
			aMasterList.push_back(aVertex[1]);
			aMasterList.push_back(aVertex[2]);


			VertexList aList;

			int aLeft = floorf(aMinU);
			int aTop = floorf(aMinV);
			int aRight = ceilf(aMaxU);
			int aBottom = ceilf(aMaxV);
			if (aLeft < 0)
				aLeft = 0;
			if (aTop < 0)
				aTop = 0;
			if (aRight > mTexVecWidth)
				aRight = mTexVecWidth;
			if (aBottom > mTexVecHeight)
				aBottom = mTexVecHeight;

			TextureDataPiece &aStandardPiece = mTextures[0];
			for (i=aTop; i<aBottom; i++)
			{
				for (j=aLeft; j<aRight; j++)
				{
					TextureDataPiece &aPiece = mTextures[i*mTexVecWidth + j];


					VertexList aList = aMasterList;
					for(k=0; k<3; k++)
					{
						aList[k].tu -= j;
						aList[k].tv -= i;
						if (i==mTexVecHeight-1)
							aList[k].tv *= (float)aStandardPiece.mHeight / aPiece.mHeight;
						if (j==mTexVecWidth-1)
							aList[k].tu *= (float)aStandardPiece.mWidth / aPiece.mWidth;
					}
			
					DoPolyTextureClip(aList);
					if (aList.size() >= 3)
					{
						D3DInterface::CheckDXError(theDevice->SetTexture(0, aPiece.mTexture),"SetTexture gTexture");
						D3DInterface::CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, gVertexType, &aList[0], aList.size(), 0),"DrawPrimitive (Tri) 4");

		/*				CheckDXError(theDevice->SetTexture(0, NULL),"SetTexture NULL");
						theDevice->SetRenderState(D3DRENDERSTATE_FILLMODE, D3DFILL_WIREFRAME);
						CheckDXError(theDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, gVertexType, &aList[0], aList.size(), D3DDP_WAIT),"DrawPrimitive (Tri)");
						theDevice->SetRenderState(D3DRENDERSTATE_FILLMODE, D3DFILL_SOLID);*/
					}
				}
			}
		}
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool D3DInterface::CreateImageTexture(MemoryImage *theImage)
{
	bool wantPurge = false;

	if(theImage->mD3DData==NULL)
	{
		theImage->mD3DData = new TextureData();
		
		// The actual purging was deferred
		wantPurge = theImage->mPurgeBits;

		AutoCrit aCrit(gSexyAppBase->mDDInterface->mCritSect); // Make images thread safe
		mImageSet.insert(theImage);
	}

	TextureData *aData = (TextureData*)theImage->mD3DData;
	aData->CheckCreateTextures(theImage, mD3DDevice, mDD);
	
	if (wantPurge)
		theImage->PurgeBits();

	return aData->mPixelFormat != PixelFormat_Unknown;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
bool D3DInterface::RecoverBits(MemoryImage* theImage)
{
	if (theImage->mD3DData == NULL)
		return false;

	TextureData* aData = (TextureData*) theImage->mD3DData;
	if (aData->mBitsChangedCount != theImage->mBitsChangedCount) // bits have changed since texture was created
		return false;
	
	for (int aPieceRow = 0; aPieceRow < aData->mTexVecHeight; aPieceRow++)
	{
		for (int aPieceCol = 0; aPieceCol < aData->mTexVecWidth; aPieceCol++)
		{
			TextureDataPiece* aPiece = &aData->mTextures[aPieceRow*aData->mTexVecWidth + aPieceCol];
		
			DDSURFACEDESC2 aDesc;
			aDesc.dwSize = sizeof(aDesc);
			if (D3DInterface::CheckDXError(aPiece->mTexture->Lock(NULL,&aDesc,DDLOCK_SURFACEMEMORYPTR | DDLOCK_WAIT | DDLOCK_READONLY,NULL),"Lock Texture"))
				return false;

			int offx = aPieceCol*aData->mTexPieceWidth;
			int offy = aPieceRow*aData->mTexPieceHeight;
			int aWidth = min(theImage->mWidth-offx, aPiece->mWidth);
			int aHeight = min(theImage->mHeight-offy, aPiece->mHeight);

			switch (aData->mPixelFormat)
			{
			case PixelFormat_A8R8G8B8:	CopyTexture8888ToImage(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight); break;
			case PixelFormat_A4R4G4B4:	CopyTexture4444ToImage(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight); break;
			case PixelFormat_R5G6B5: CopyTexture565ToImage(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight); break;
			case PixelFormat_Palette8:	CopyTexturePalette8ToImage(aDesc.lpSurface, aDesc.lPitch, theImage, offx, offy, aWidth, aHeight, aData->mPalette); break;
			}

			D3DInterface::CheckDXError(aPiece->mTexture->Unlock(NULL),"Texture Unlock");
		}
	}

	return true;
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::SetCurTexture(MemoryImage *theImage)
{
	if (theImage==NULL)
	{
		mD3DDevice->SetTexture(0,NULL);
		return;
		}

	if (!CreateImageTexture(theImage))
		return;

	TextureData *aData = (TextureData*)theImage->mD3DData;
	mD3DDevice->SetTexture(0,aData->mTextures[0].mTexture);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::PushTransform(const SexyMatrix3 &theTransform, bool concatenate)
{
	if (mTransformStack.empty() || !concatenate)
		mTransformStack.push_back(theTransform);
	else
	{
		SexyMatrix3 &aTrans = mTransformStack.back();
		mTransformStack.push_back(theTransform*aTrans);
	}
}
	
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::PopTransform()
{
	if (!mTransformStack.empty())
		mTransformStack.pop_back();
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::RemoveMemoryImage(MemoryImage *theImage)
{
	if (theImage->mD3DData != NULL)
	{
		delete (TextureData*)theImage->mD3DData;
		theImage->mD3DData = NULL;

		AutoCrit aCrit(gSexyAppBase->mDDInterface->mCritSect); // Make images thread safe
		mImageSet.erase(theImage);
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::Cleanup()
{
	Flush();

	ImageSet::iterator anItr;
	for(anItr = mImageSet.begin(); anItr != mImageSet.end(); ++anItr)
	{
		MemoryImage *anImage = *anItr;
		delete (TextureData*)anImage->mD3DData;
		anImage->mD3DData = NULL;
	}

	mImageSet.clear();

	if (mD3DDevice != NULL)
	{
		mD3DDevice->Release();
		mD3DDevice = NULL;
	}

	if (mD3D != NULL)
	{
		mD3D->Release();
		mD3D = NULL;
	}

	if (mDDSDrawSurface != NULL)
	{
		mDDSDrawSurface->Release();
		mDDSDrawSurface = NULL;
	}

	if (mZBuffer != NULL)
	{
		mZBuffer->Release();
		mZBuffer = NULL;
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::SetupDrawMode(int theDrawMode, const Color &theColor, Image *theImage)
{
	if (theDrawMode == Graphics::DRAWMODE_NORMAL)
	{
/*		if (theImage != NULL)
		{
			MemoryImage *anImage = (MemoryImage*)theImage;
			mD3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, (anImage->mHasAlpha || anImage->mHasTrans || theColor.mAlpha<255)?TRUE:FALSE);
		}
		else
			mD3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, theColor.mAlpha<255?TRUE:FALSE);
*/

		mD3DDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  D3DBLEND_SRCALPHA); 
		mD3DDevice->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_INVSRCALPHA); 
	}
	else // Additive
	{
//		mD3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, TRUE);
//		theColor.mRed = theColor.mRed * theColor.mAlpha / 255;
//		theColor.mGreen = theColor.mGreen * theColor.mAlpha / 255;
//		theColor.mBlue = theColor.mBlue * theColor.mAlpha / 255;

//		mD3DDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  D3DBLEND_ONE);
		mD3DDevice->SetRenderState(D3DRENDERSTATE_SRCBLEND,  D3DBLEND_SRCALPHA);
		mD3DDevice->SetRenderState(D3DRENDERSTATE_DESTBLEND, D3DBLEND_ONE);
	}			
//	mD3DDevice->SetRenderState(D3DRENDERSTATE_ALPHABLENDENABLE, FALSE);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::Blt(Image* theImage, float theX, float theY, const Rect& theSrcRect, const Color& theColor, int theDrawMode, bool linearFilter)
{
	if (!mTransformStack.empty())
	{
		BltClipF(theImage,theX,theY,theSrcRect,NULL,theColor,theDrawMode);
		return;
	}

	if (!PreDraw())
		return;

	MemoryImage* aSrcMemoryImage = (MemoryImage*) theImage;

	if (!CreateImageTexture(aSrcMemoryImage))
		return;

	SetupDrawMode(theDrawMode, theColor, theImage);	

	TextureData *aData = (TextureData*)aSrcMemoryImage->mD3DData;

	SetLinearFilter(mD3DDevice, linearFilter);
	aData->Blt(mD3DDevice,theX,theY,theSrcRect,theColor);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::BltMirror(Image* theImage, float theX, float theY, const Rect& theSrcRect, const Color& theColor, int theDrawMode, bool linearFilter)
{
	SexyTransform2D aTransform;		

	aTransform.Translate(-theSrcRect.mWidth,0);
	aTransform.Scale(-1, 1);
	aTransform.Translate(theX, theY);

	BltTransformed(theImage,NULL,theColor,theDrawMode,theSrcRect,aTransform,linearFilter);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::BltClipF(Image* theImage, float theX, float theY, const Rect& theSrcRect, const Rect *theClipRect, const Color& theColor, int theDrawMode)
{
	SexyTransform2D aTransform;
	aTransform.Translate(theX, theY);

	BltTransformed(theImage,theClipRect,theColor,theDrawMode,theSrcRect,aTransform,true);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::StretchBlt(Image* theImage,  const Rect& theDestRect, const Rect& theSrcRect, const Rect* theClipRect, const Color &theColor, int theDrawMode, bool fastStretch, bool mirror)
{
	float xScale = (float)theDestRect.mWidth / theSrcRect.mWidth;
	float yScale = (float)theDestRect.mHeight / theSrcRect.mHeight;

	SexyTransform2D aTransform;
	if (mirror)
	{
		aTransform.Translate(-theSrcRect.mWidth,0);
		aTransform.Scale(-xScale, yScale);
	}
	else
		aTransform.Scale(xScale, yScale);

	aTransform.Translate(theDestRect.mX, theDestRect.mY);
	BltTransformed(theImage,theClipRect,theColor,theDrawMode,theSrcRect,aTransform,!fastStretch);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::BltRotated(Image* theImage, float theX, float theY, const Rect* theClipRect, const Color& theColor, int theDrawMode, double theRot, float theRotCenterX, float theRotCenterY, const Rect &theSrcRect)
{
	SexyTransform2D aTransform;

	aTransform.Translate(-theRotCenterX, -theRotCenterY);
	aTransform.RotateRad(theRot);
	aTransform.Translate(theX+theRotCenterX,theY+theRotCenterY);

	BltTransformed(theImage,theClipRect,theColor,theDrawMode,theSrcRect,aTransform,true);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::BltTransformed(Image* theImage, const Rect* theClipRect, const Color& theColor, int theDrawMode, const Rect &theSrcRect, const SexyMatrix3 &theTransform, bool linearFilter, float theX, float theY, bool center)
{
	if (!PreDraw())
		return;

	MemoryImage* aSrcMemoryImage = (MemoryImage*) theImage;

	if (!CreateImageTexture(aSrcMemoryImage))
		return;

	SetupDrawMode(theDrawMode, theColor, theImage);	

	TextureData *aData = (TextureData*)aSrcMemoryImage->mD3DData;

	if (!mTransformStack.empty())
	{
		SetLinearFilter(mD3DDevice, true); // force linear filtering in the case of a global transform
		if (theX!=0 || theY!=0)
		{
			SexyTransform2D aTransform;
			if (center)
				aTransform.Translate(-theSrcRect.mWidth/2.0f,-theSrcRect.mHeight/2.0f);

			aTransform = theTransform * aTransform;
			aTransform.Translate(theX,theY);
			aTransform = mTransformStack.back() * aTransform;

			aData->BltTransformed(mD3DDevice, aTransform, theSrcRect, theColor, theClipRect);
		}
		else
		{
			SexyTransform2D aTransform = mTransformStack.back()*theTransform;
			aData->BltTransformed(mD3DDevice, aTransform, theSrcRect, theColor, theClipRect, theX, theY, center);
		}
	}
	else
	{
		SetLinearFilter(mD3DDevice, linearFilter);
		aData->BltTransformed(mD3DDevice, theTransform, theSrcRect, theColor, theClipRect, theX, theY, center);
	}
}
///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::DrawLine(double theStartX, double theStartY, double theEndX, double theEndY, const Color& theColor, int theDrawMode)
{
	if (!PreDraw())
		return;

	SetupDrawMode(theDrawMode, theColor, NULL);

	float x1, y1, x2, y2;
	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			

	if (!mTransformStack.empty())
	{
		SexyVector2 p1(theStartX,theStartY);
		SexyVector2 p2(theEndX,theEndY);
		p1 = mTransformStack.back()*p1;
		p2 = mTransformStack.back()*p2;

		x1 = p1.x;
		y1 = p1.y;
		x2 = p2.x;
		y2 = p2.y;
	}
	else
	{
		x1 = theStartX;
		y1 = theStartY;
		x2 = theEndX;
		y2 = theEndY;
	}

	D3DTLVERTEX aVertex[3] = 
	{
		{ x1,				y1,				0,	1,	aColor,	0,	0,		0 },
		{ x2,				y2,				0,	1,	aColor,	0,	0,		0 },
		{ x2+0.5f,			y2+0.5f,		0,	1,	aColor,	0,	0,		0 }
	};

	D3DInterface::CheckDXError(mD3DDevice->SetTexture(0, NULL),"SetTexture NULL");
	D3DInterface::CheckDXError(mD3DDevice->DrawPrimitive(D3DPT_LINESTRIP, gVertexType, aVertex, 3, 0),"DrawPrimitive (Line)");
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::FillRect(const Rect& theRect, const Color& theColor, int theDrawMode)
{
	if (!PreDraw())
		return;

	SetupDrawMode(theDrawMode, theColor, NULL);

	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			
	float x = theRect.mX - 0.5f;
	float y = theRect.mY - 0.5f;
	float aWidth = theRect.mWidth;
	float aHeight = theRect.mHeight;

	D3DTLVERTEX aVertex[4] = 
	{
		{ x,				y,				0,	1,	aColor,	0,	0,		0 },
		{ x,				y+aHeight,		0,	1,	aColor,	0,	0,		0 },
		{ x+aWidth,			y,				0,	1,	aColor,	0,	0,		0 },
		{ x+aWidth,			y+aHeight,		0,	1,	aColor,	0,	0,		0 }
	};


	if (!mTransformStack.empty())
	{
		SexyVector2 p[4] = { SexyVector2(x, y), SexyVector2(x,y+aHeight), SexyVector2(x+aWidth, y) , SexyVector2(x+aWidth, y+aHeight) };

		int i;
		for (i=0; i<4; i++)
		{
			p[i] = mTransformStack.back()*p[i];
			p[i].x -= 0.5f;
			p[i].y -= 0.5f;
			aVertex[i].sx = p[i].x;
			aVertex[i].sy = p[i].y;
		}
	}

	CheckDXError(mD3DDevice->SetTexture(0, NULL),"SetTexture NULL");
	CheckDXError(mD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, gVertexType, aVertex, 4, 0),"DrawPrimitive (Rect)");
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::DrawTriangle(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, int theDrawMode)
{
	if (!PreDraw())
		return;

	SetupDrawMode(theDrawMode, theColor, NULL);

	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			
	D3DTLVERTEX aVertex[3] = 
	{
		{ p1.x,			p1.y,			0,	1,	GetColorFromTriVertex(p1, aColor),	0,	0,		0 },
		{ p2.x,			p2.y,			0,	1,	GetColorFromTriVertex(p2, aColor),	0,	0,		0 },
		{ p3.x,			p3.y,			0,	1,	GetColorFromTriVertex(p3, aColor),	0,	0,		0 }
	};


	CheckDXError(mD3DDevice->SetTexture(0, NULL),"SetTexture NULL");
	CheckDXError(mD3DDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, gVertexType, aVertex, 3, 0),"DrawPrimitive (Tri) 5");
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::FillPoly(const Point theVertices[], int theNumVertices, const Rect *theClipRect, const Color &theColor, int theDrawMode, int tx, int ty)
{
	if (theNumVertices<3)
		return;

	if (!PreDraw())
		return;

	SetupDrawMode(theDrawMode, theColor, NULL);
	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			

	CheckDXError(mD3DDevice->SetTexture(0, NULL),"SetTexture NULL");

	VertexList aList;
	for (int i=0; i<theNumVertices; i++)
	{
		D3DTLVERTEX vert = 	{ theVertices[i].mX + tx, theVertices[i].mY + ty,	0,	1,	aColor,	0,	0,		0 };
		if (!mTransformStack.empty())
		{
			SexyVector2 v(vert.sx,vert.sy);
			v = mTransformStack.back()*v;
			vert.sx = v.x;
			vert.sy = v.y;
		}

		aList.push_back(vert);
	}

	if (theClipRect != NULL)
		DrawPolyClipped(mD3DDevice,theClipRect,aList);
	else
		D3DInterface::CheckDXError(mD3DDevice->DrawPrimitive(D3DPT_TRIANGLEFAN, gVertexType, &aList[0], aList.size(), 0),"DrawPrimitive (FillPoly)");
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::DrawTriangleTex(const TriVertex &p1, const TriVertex &p2, const TriVertex &p3, const Color &theColor, int theDrawMode, Image *theTexture, bool blend)
{
	TriVertex aVertices[1][3] = {{p1, p2, p3}};
	DrawTrianglesTex(aVertices,1,theColor,theDrawMode,theTexture,blend);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::DrawTrianglesTex(const TriVertex theVertices[][3], int theNumTriangles, const Color &theColor, int theDrawMode, Image *theTexture, float tx, float ty, bool blend)
{
	if (!PreDraw())
		return;

	MemoryImage* aSrcMemoryImage = (MemoryImage*)theTexture;

	if (!CreateImageTexture(aSrcMemoryImage))
		return;

	SetupDrawMode(theDrawMode, theColor, theTexture);
	DWORD aColor = RGBA_MAKE(theColor.mRed, theColor.mGreen, theColor.mBlue, theColor.mAlpha);			
	
	TextureData *aData = (TextureData*)aSrcMemoryImage->mD3DData;

	SetLinearFilter(mD3DDevice, blend);	

	aData->BltTriangles(mD3DDevice, theVertices, theNumTriangles, aColor, tx, ty);
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::DrawTrianglesTexStrip(const TriVertex theVertices[], int theNumTriangles, const Color &theColor, int theDrawMode, Image *theTexture, float tx, float ty, bool blend)
{
	TriVertex aList[100][3];
	int aTriNum = 0;
	while (aTriNum < theNumTriangles)
	{
		int aMaxTriangles = min(100,theNumTriangles - aTriNum);
		for (int i=0; i<aMaxTriangles; i++)
		{
			aList[i][0] = theVertices[aTriNum];
			aList[i][1] = theVertices[aTriNum+1];
			aList[i][2] = theVertices[aTriNum+2];
			aTriNum++;
		}
		DrawTrianglesTex(aList,aMaxTriangles,theColor,theDrawMode,theTexture, tx, ty, blend);
	}
}

///////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////
void D3DInterface::Flush()
{
	if (mSceneBegun)
	{
		mD3DDevice->EndScene();

		mSceneBegun = false;
		mErrorString.erase();
	}
}

