//-----------------------------------------------------------------------------
// File: XBUtil.cpp
//
// Desc: Shortcut macros and helper functions for the XBox samples
//
// Hist: 11.01.00 - New for November XDK release
//       12.01.00 - Moved input code to XBInput.cpp
//       12.15.00 - Changes for December XDK release
//       02.19.00 - Changes for March XDK release
//       12.09.02 - Added a DrawRect() function
//
// Copyright (c) Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#include <xtl.h>
#include <xgraphics.h>
#include <tchar.h> 
#include <cstdio>
#include <cassert>
#include "XBUtil.h"

//-----------------------------------------------------------------------------
// Path to the XBox media files on the target machine
//-----------------------------------------------------------------------------
CHAR g_strMediaPath[512] = "D:\\Media\\";

//-----------------------------------------------------------------------------
// Name: XBUtil_DebugPrint()
// Desc: For printing to the debugger with formatting.
//-----------------------------------------------------------------------------
void XBUtil_DebugPrint( const CHAR* buf, ... )
{
	CHAR strBuffer[1024];

	va_list arglist;
	va_start( arglist, buf );
	_vsnprintf( strBuffer, sizeof(strBuffer), buf, arglist );
	va_end( arglist );

	strBuffer[sizeof(strBuffer)-1] = '\0';
	OutputDebugStringA( strBuffer );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_SetMediaPath()
// Desc: Sets the path to media files
//-----------------------------------------------------------------------------
VOID XBUtil_SetMediaPath( const CHAR* strPath )
{
	strcpy( g_strMediaPath, strPath );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_FindMediaFile()
// Desc: Returns a valid path to a media file.
//-----------------------------------------------------------------------------
HRESULT XBUtil_FindMediaFile( CHAR* strPath, const CHAR* strFilename )
{
	// Check for valid arguments
	if( NULL==strFilename || NULL==strPath )
	{
		OUTPUT_DEBUG_STRING( "XBUtil_FindMediaFile(): Invalid arguments\n" );
		return E_INVALIDARG;
	}

	// Default path is the filename itself as a fully qualified path
	strcpy( strPath, strFilename );

	// Check for the ':' character to see if the filename is a fully
	// qualified path. If not, pre-pend the media directory
	if( strFilename[1] != ':' )
		sprintf( strPath, "%s%s", g_strMediaPath, strFilename );

	// Try to open the file
	HANDLE hFile = CreateFile( strPath, GENERIC_READ, FILE_SHARE_READ, NULL, 
		OPEN_EXISTING, 0, NULL );
	if( INVALID_HANDLE_VALUE == hFile )
	{
		// Return error
		CHAR strBuffer[80];
		sprintf( strBuffer, "XBUtil_FindMediaFile(): Could not find file [%s]\n", 
			strFilename );
		OUTPUT_DEBUG_STRING( strBuffer );
		return 0x82000004;
	}

	// Found the file. Close the file and return
	CloseHandle( hFile );

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_Timer()
// Desc: Performs timer operations. Use the following commands:
//          TIMER_RESET           - to reset the timer
//          TIMER_START           - to start the timer
//          TIMER_STOP            - to stop (or pause) the timer
//          TIMER_ADVANCE         - to advance the timer by 0.1 seconds
//          TIMER_GETABSOLUTETIME - to get the absolute system time
//          TIMER_GETAPPTIME      - to get the current time
//-----------------------------------------------------------------------------
FLOAT XBUtil_Timer( TIMER_COMMAND command )
{
	static BOOL  m_bTimerInitialized = FALSE;
	static FLOAT m_fSecsPerTick = 0.0f;
	static FLOAT m_fBaseTime    = 0.0f;
	static FLOAT m_fStopTime    = 0.0f;
	FLOAT        fTime;

	// Initialize the timer
	if( FALSE == m_bTimerInitialized )
	{
		m_bTimerInitialized = TRUE;

		// Use QueryPerformanceFrequency() to get frequency of timer.
		LARGE_INTEGER qwTicksPerSec;
		QueryPerformanceFrequency( &qwTicksPerSec );
		m_fSecsPerTick = 1.0f / (FLOAT)qwTicksPerSec.QuadPart;
	}

	// Get the current time using QueryPerformanceCounter() or timeGetTime()
	LARGE_INTEGER qwTime;
	QueryPerformanceCounter( &qwTime );
	fTime = ((FLOAT)qwTime.QuadPart) * m_fSecsPerTick;

	// Reset the timer
	if( command == TIMER_RESET )
	{
		m_fBaseTime = fTime;
		return 0.0f;
	}

	// Return the current time
	if( command == TIMER_GETAPPTIME )
		return fTime - m_fBaseTime;

	// Start the timer
	if( command == TIMER_START )
		m_fBaseTime += fTime - m_fStopTime;

	// Stop the timer
	if( command == TIMER_STOP )
		m_fStopTime = fTime;

	// Advance the timer by 1/10th second
	if( command == TIMER_ADVANCE )
		m_fBaseTime += fTime - ( m_fStopTime + 0.1f );

	// Retract the timer by 1/10th second
	if( command == TIMER_RETRACT )
		m_fBaseTime += fTime - ( m_fStopTime - 0.1f );

	return fTime;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_DrawRect()
// Desc: Draws an outlined, filled rectangle
//-----------------------------------------------------------------------------
VOID XBUtil_DrawRect( FLOAT x1, FLOAT y1, FLOAT x2, FLOAT y2,
					 DWORD dwFillColor, DWORD dwOutlineColor )
{
	D3DXVECTOR4 v[4];
	v[0] = D3DXVECTOR4( x1-0.5f, y1-0.5f, 1.0f, 1.0f );
	v[1] = D3DXVECTOR4( x2-0.5f, y1-0.5f, 1.0f, 1.0f );
	v[2] = D3DXVECTOR4( x2-0.5f, y2-0.5f, 1.0f, 1.0f );
	v[3] = D3DXVECTOR4( x1-0.5f, y2-0.5f, 1.0f, 1.0f );

	// Set render states
	D3DDevice::SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1 );
	D3DDevice::SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TFACTOR );
	D3DDevice::SetTextureStageState( 0, D3DTSS_ALPHAOP,   D3DTOP_SELECTARG1 );
	D3DDevice::SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TFACTOR );
	D3DDevice::SetTextureStageState( 1, D3DTSS_COLOROP,   D3DTOP_DISABLE );
	D3DDevice::SetRenderState( D3DRS_ALPHABLENDENABLE,   TRUE );
	D3DDevice::SetRenderState( D3DRS_SRCBLEND,  D3DBLEND_SRCALPHA );
	D3DDevice::SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
	D3DDevice::SetVertexShader( D3DFVF_XYZRHW );

	// Draw rectangle inside
	D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, dwFillColor );
	D3DDevice::DrawVerticesUP( D3DPT_QUADLIST, 4, v, sizeof(D3DXVECTOR4) );

	// Draw rectangle outline
	D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, dwOutlineColor );
	D3DDevice::DrawVerticesUP( D3DPT_LINELOOP, 4, v, sizeof(D3DXVECTOR4) );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_InitMaterial()
// Desc: Initializes a D3DMATERIAL8 structure, setting the diffuse and ambient
//       colors. It does not set emissive or specular colors.
//-----------------------------------------------------------------------------
VOID XBUtil_InitMaterial( D3DMATERIAL8& mtrl, FLOAT r, FLOAT g, FLOAT b,
						 FLOAT a )
{
	ZeroMemory( &mtrl, sizeof(D3DMATERIAL8) );
	mtrl.Diffuse.r = mtrl.Ambient.r = r;
	mtrl.Diffuse.g = mtrl.Ambient.g = g;
	mtrl.Diffuse.b = mtrl.Ambient.b = b;
	mtrl.Diffuse.a = mtrl.Ambient.a = a;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_InitLight()
// Desc: Initializes a D3DLIGHT structure, setting the light position. The
//       diffuse color is set to white, specular and ambient left as black.
//-----------------------------------------------------------------------------
VOID XBUtil_InitLight( D3DLIGHT8& light, D3DLIGHTTYPE ltType,
					  FLOAT x, FLOAT y, FLOAT z )
{
	ZeroMemory( &light, sizeof(D3DLIGHT8) );
	light.Type         = ltType;
	light.Diffuse.r    = 1.0f;
	light.Diffuse.g    = 1.0f;
	light.Diffuse.b    = 1.0f;
	light.Position     = D3DXVECTOR3(x,y,z);
	light.Position.x   = x;
	light.Position.y   = y;
	light.Position.z   = z;
	light.Direction    = D3DXVECTOR3( x, y, z );
	D3DXVec3Normalize( (D3DXVECTOR3*)&light.Direction, (D3DXVECTOR3*)&light.Direction );
	light.Range        = 1000.0f;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_CreateTexture()
// Desc: Helper function to create a texture.
//-----------------------------------------------------------------------------
HRESULT XBUtil_CreateTexture( LPDIRECT3DDEVICE8 pd3dDevice, const CHAR* strTexture,
							 LPDIRECT3DTEXTURE8* ppTexture, D3DFORMAT d3dFormat )
{
	HRESULT hr;

	// Find the media file
	CHAR strTexturePath[512];
	if( FAILED( hr = XBUtil_FindMediaFile( strTexturePath, strTexture ) ) )
		return hr;

	// Create the texture using D3DX. Check the current directory
	return D3DXCreateTextureFromFileEx( pd3dDevice, strTexturePath, 
		D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 
		0, d3dFormat, D3DPOOL_DEFAULT, 
		D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL, 
		ppTexture );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_UnswizzleTexture2D()
// Desc: Unswizzles a 2D texture before it gets unlocked. Note: this operation
//       can be very slow.
//-----------------------------------------------------------------------------
VOID XBUtil_UnswizzleTexture2D( D3DLOCKED_RECT* pLock, const D3DSURFACE_DESC* pDesc )
{
	DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
	DWORD dwTextureSize = pDesc->Width * pDesc->Height * dwPixelSize;

	BYTE* pSrcBits = new BYTE[ dwTextureSize ];
	memcpy( pSrcBits, pLock->pBits, dwTextureSize );

	XGUnswizzleRect( pSrcBits, pDesc->Width, pDesc->Height, NULL, pLock->pBits, 
		0, NULL, dwPixelSize );

	SAFE_DELETE_ARRAY( pSrcBits );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_UnswizzleTexture3D()
// Desc: Unswizzles a 3D texture before it gets unlocked. Note: this operation
//       can be very slow.
//-----------------------------------------------------------------------------
VOID XBUtil_UnswizzleTexture3D( D3DLOCKED_BOX* pLock, const D3DVOLUME_DESC* pDesc )
{
	DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
	DWORD dwTextureSize = pDesc->Width * pDesc->Height * pDesc->Depth * dwPixelSize;

	BYTE* pSrcBits = new BYTE[ dwTextureSize ];
	memcpy( pSrcBits, pLock->pBits, dwTextureSize );

	XGUnswizzleBox( pSrcBits, pDesc->Width, pDesc->Height, pDesc->Depth, NULL, pLock->pBits,
		0, 0, NULL, dwPixelSize );

	SAFE_DELETE_ARRAY( pSrcBits );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_SwizzleTexture2D()
// Desc: Swizzles a 2D texture before it gets unlocked. Note: this operation
//       can be very slow.
//-----------------------------------------------------------------------------
VOID XBUtil_SwizzleTexture2D( D3DLOCKED_RECT* pLock, const D3DSURFACE_DESC* pDesc )
{
	DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
	DWORD dwTextureSize = pDesc->Width * pDesc->Height * dwPixelSize;

	BYTE* pSrcBits = new BYTE[ dwTextureSize ];
	memcpy( pSrcBits, pLock->pBits, dwTextureSize );

	XGSwizzleRect( pSrcBits, 0, NULL, pLock->pBits,
		pDesc->Width, pDesc->Height, 
		NULL, dwPixelSize );

	SAFE_DELETE_ARRAY( pSrcBits );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_SwizzleTexture3D()
// Desc: Swizzles a 3D texture before it gets unlocked. Note: this operation
//       can be very slow.
//-----------------------------------------------------------------------------
VOID XBUtil_SwizzleTexture3D( D3DLOCKED_BOX* pLock, const D3DVOLUME_DESC* pDesc )
{
	DWORD dwPixelSize   = XGBytesPerPixelFromFormat( pDesc->Format );
	DWORD dwTextureSize = pDesc->Width * pDesc->Height * pDesc->Depth * dwPixelSize;

	BYTE* pSrcBits = new BYTE[ dwTextureSize ];
	memcpy( pSrcBits, pLock->pBits, dwTextureSize );

	XGSwizzleBox( pSrcBits, 0, 0, NULL, pLock->pBits,
		pDesc->Width, pDesc->Height, pDesc->Depth, 
		NULL, dwPixelSize );

	SAFE_DELETE_ARRAY( pSrcBits );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_CreateVertexShader()
// Desc: Creates a file-based vertex shader
//-----------------------------------------------------------------------------
HRESULT XBUtil_CreateVertexShader( const CHAR* strFilename,
								  const DWORD* pdwVertexDecl,
								  DWORD* pdwVertexShader )
{
	HRESULT hr;

	// Find the media file
	CHAR strShaderPath[512];
	if( FAILED( hr = XBUtil_FindMediaFile( strShaderPath, strFilename ) ) )
		return hr;

	// Open the vertex shader file
	HANDLE hFile;
	DWORD dwNumBytesRead;
	hFile = CreateFile( strShaderPath, GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
		return E_FAIL;

	// Allocate memory to read the vertex shader file
	DWORD dwSize = GetFileSize(hFile, NULL);
	BYTE* pData  = new BYTE[dwSize+4];
	if( NULL == pData )
	{
		CloseHandle( hFile );
		return E_FAIL;
	}
	ZeroMemory( pData, dwSize+4 );

	// Read the pre-compiled vertex shader microcode
	ReadFile(hFile, pData, dwSize, &dwNumBytesRead, 0);

	// Create the vertex shader
	hr = D3DDevice::CreateVertexShader( pdwVertexDecl, (const DWORD*)pData,
		pdwVertexShader, 0 );

	// Cleanup and return
	CloseHandle( hFile );
	delete [] pData;
	return hr;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_CreatePixelShader()
// Desc: Creates a file-based pixel shader
//-----------------------------------------------------------------------------
HRESULT XBUtil_CreatePixelShader( const CHAR* strFilename, DWORD* pdwPixelShader )
{
	HRESULT hr;

	// Find the media file
	CHAR strShaderPath[512];
	if( FAILED( hr = XBUtil_FindMediaFile( strShaderPath, strFilename ) ) )
		return hr;

	// Open the pixel shader file
	HANDLE hFile;
	DWORD dwNumBytesRead;
	hFile = CreateFile( strShaderPath, GENERIC_READ, FILE_SHARE_READ, NULL,
		OPEN_EXISTING, FILE_ATTRIBUTE_READONLY, NULL );
	if( hFile == INVALID_HANDLE_VALUE )
		return E_FAIL;

	// Load the pre-compiled pixel shader microcode
	D3DPIXELSHADERDEF_FILE psdf;
	ReadFile( hFile, &psdf, sizeof(D3DPIXELSHADERDEF_FILE), &dwNumBytesRead, NULL );

	// Make sure the pixel shader is valid
	if( psdf.FileID != D3DPIXELSHADERDEF_FILE_ID )
	{
		CloseHandle( hFile );
		OUTPUT_DEBUG_STRING( "XBUtil_CreatePixelShader(): Invalid pixel shader file\n" );
		return E_FAIL;
	}

	// Create the pixel shader
	if( FAILED( hr = D3DDevice::CreatePixelShader( &(psdf.Psd), pdwPixelShader ) ) )
	{
		CloseHandle( hFile );
		OUTPUT_DEBUG_STRING( "XBUtil_CreatePixelShader(): Could not create pixel shader\n" );
		return hr;
	}

	// Cleanup
	CloseHandle( hFile );

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_GetCubeMapViewMatrix()
// Desc: Returns a view matrix for rendering to a face of a cubemap.
//-----------------------------------------------------------------------------
D3DXMATRIX XBUtil_GetCubeMapViewMatrix( DWORD dwFace )
{
	D3DXVECTOR3 vEyePt   = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
	D3DXVECTOR3 vLookDir = D3DXVECTOR3( 1.0f, 0.0f, 0.0f );
	D3DXVECTOR3 vUpDir   = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );

	switch( dwFace )
	{
	case D3DCUBEMAP_FACE_POSITIVE_X:
		vLookDir = D3DXVECTOR3( 1.0f, 0.0f, 0.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
		break;
	case D3DCUBEMAP_FACE_NEGATIVE_X:
		vLookDir = D3DXVECTOR3(-1.0f, 0.0f, 0.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
		break;
	case D3DCUBEMAP_FACE_POSITIVE_Y:
		vLookDir = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 0.0f,-1.0f );
		break;
	case D3DCUBEMAP_FACE_NEGATIVE_Y:
		vLookDir = D3DXVECTOR3( 0.0f,-1.0f, 0.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 0.0f, 1.0f );
		break;
	case D3DCUBEMAP_FACE_POSITIVE_Z:
		vLookDir = D3DXVECTOR3( 0.0f, 0.0f, 1.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
		break;
	case D3DCUBEMAP_FACE_NEGATIVE_Z:
		vLookDir = D3DXVECTOR3( 0.0f, 0.0f,-1.0f );
		vUpDir   = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
		break;
	}

	// Set the view transform for this cubemap surface
	D3DXMATRIX matView;
	D3DXMatrixLookAtLH( &matView, &vEyePt, &vLookDir, &vUpDir );
	return matView;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_CreateNormalizationCubeMap()
// Desc: Creates a cubemap and fills it with normalized RGBA vectors
//-----------------------------------------------------------------------------
HRESULT XBUtil_CreateNormalizationCubeMap( DWORD dwSize, 
										  LPDIRECT3DCUBETEXTURE8* ppCubeMap )
{
	HRESULT hr;

	// Create the cube map
	if( FAILED( hr = D3DDevice::CreateCubeTexture( dwSize, 1, 0, D3DFMT_X8R8G8B8, 
		D3DPOOL_DEFAULT, ppCubeMap ) ) )
		return E_FAIL;

	// Allocate temp space for swizzling the cubemap surfaces
	DWORD* pSourceBits = new DWORD[ dwSize * dwSize ];

	// Fill all six sides of the cubemap
	for( DWORD i=0; i<6; i++ )
	{
		// Lock the i'th cubemap surface
		LPDIRECT3DSURFACE8 pCubeMapFace;
		(*ppCubeMap)->GetCubeMapSurface( (D3DCUBEMAP_FACES)i, 0, &pCubeMapFace );

		// Write the RGBA-encoded normals to the surface pixels
		DWORD*      pPixel = pSourceBits;
		D3DXVECTOR3 n;
		FLOAT       w, h;

		for( DWORD y = 0; y < dwSize; y++ )
		{
			h  = (FLOAT)y / (FLOAT)(dwSize-1);  // 0 to 1
			h  = ( h * 2.0f ) - 1.0f;           // -1 to 1

			for( DWORD x = 0; x < dwSize; x++ )
			{
				w = (FLOAT)x / (FLOAT)(dwSize-1);   // 0 to 1
				w = ( w * 2.0f ) - 1.0f;            // -1 to 1

				// Calc the normal for this texel
				switch( i )
				{
				case D3DCUBEMAP_FACE_POSITIVE_X:    // +x
					n.x = +1.0;
					n.y = -h;
					n.z = -w;
					break;

				case D3DCUBEMAP_FACE_NEGATIVE_X:    // -x
					n.x = -1.0;
					n.y = -h;
					n.z = +w;
					break;

				case D3DCUBEMAP_FACE_POSITIVE_Y:    // y
					n.x = +w;
					n.y = +1.0;
					n.z = +h;
					break;

				case D3DCUBEMAP_FACE_NEGATIVE_Y:    // -y
					n.x = +w;
					n.y = -1.0;
					n.z = -h;
					break;

				case D3DCUBEMAP_FACE_POSITIVE_Z:    // +z
					n.x = +w;
					n.y = -h;
					n.z = +1.0;
					break;

				case D3DCUBEMAP_FACE_NEGATIVE_Z:    // -z
					n.x = -w;
					n.y = -h;
					n.z = -1.0;
					break;
				}

				// Store the normal as an RGBA color
				D3DXVec3Normalize( &n, &n );
				*pPixel++ = XBUtil_VectorToRGBA( &n );
			}
		}

		// Swizzle the result into the cubemap face surface
		D3DLOCKED_RECT lock;
		pCubeMapFace->LockRect( &lock, 0, 0L );
		XGSwizzleRect( pSourceBits, 0, NULL, lock.pBits, dwSize, dwSize,
			NULL, sizeof(DWORD) );
		pCubeMapFace->UnlockRect();

		// Release the cubemap face
		pCubeMapFace->Release();
	}

	// Free temp space
	SAFE_DELETE_ARRAY( pSourceBits );

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_DumpSurface()
// Desc: Writes the contents of a surface (32-bit only) to a .tga file. This
//       could be a backbuffer, texture, or any other 32-bit surface.
//-----------------------------------------------------------------------------
HRESULT XBUtil_DumpSurface( LPDIRECT3DSURFACE8 pSurface, const CHAR* strFileName,
						   BOOL bSurfaceIsTiled )
{
	// Get the surface description. Make sure it's a 32-bit format
	D3DSURFACE_DESC desc;
	pSurface->GetDesc( &desc );
	if( desc.Size != ( desc.Width * desc.Height * sizeof(DWORD) ) )
		return E_NOTIMPL;

	// Lock the surface
	D3DLOCKED_RECT lock;
	if( FAILED( pSurface->LockRect( &lock, 0, bSurfaceIsTiled ? D3DLOCK_TILED : 0 ) ) )
		return E_FAIL;

	// Allocate memory for storing the surface bits
	VOID* pBits = (VOID*)new DWORD[desc.Width*desc.Height];

	// Unswizzle the bits, if necessary
	if( XGIsSwizzledFormat( desc.Format ) )
		XGUnswizzleRect( lock.pBits, desc.Width, desc.Height, NULL,
		pBits, lock.Pitch, NULL, sizeof(DWORD) );
	else
		memcpy( pBits, lock.pBits, desc.Size );

	// Unlock the surface
	pSurface->UnlockRect();

	// Setup the TGA file header
	struct TargaHeader
	{
		BYTE IDLength;
		BYTE ColormapType;
		BYTE ImageType;
		BYTE ColormapSpecification[5];
		WORD XOrigin;
		WORD YOrigin;
		WORD ImageWidth;
		WORD ImageHeight;
		BYTE PixelDepth;
		BYTE ImageDescriptor;
	} tgaHeader;

	ZeroMemory( &tgaHeader, sizeof(tgaHeader) );
	tgaHeader.IDLength        = 0;
	tgaHeader.ImageType       = 2;
	tgaHeader.ImageWidth      = (WORD)desc.Width;
	tgaHeader.ImageHeight     = (WORD)desc.Height;
	tgaHeader.PixelDepth      = 32;
	tgaHeader.ImageDescriptor = 0x28;

	// Create a new file
	FILE* file = fopen( strFileName, "wb" );
	if( NULL == file )
	{
		pSurface->UnlockRect();
		delete[] pBits;
		return E_FAIL;
	}

	// Write the Targa header and the surface pixels to the file
	fwrite( &tgaHeader, sizeof(TargaHeader), 1, file );
	fwrite( pBits, sizeof(BYTE), desc.Size, file );
	fclose( file );

	// Cleanup and return
	delete[] pBits;

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_EvaluateHermite()
// Desc: Evaluate a cubic parametric equation. Returns the point at u on a
//       Hermite curve.
//-----------------------------------------------------------------------------
D3DXVECTOR3 XBUtil_EvaluateHermite( const D3DXVECTOR3& p0, const D3DXVECTOR3& p1, 
								   const D3DXVECTOR3& v0, const D3DXVECTOR3& v1,
								   FLOAT u )
{
	// Generate coefficients from the two end points and two tangents
	D3DXVECTOR3 a =  2*p0 - 2*p1 +   v0 + v1; // a = 2p0 - 2p1 +  v0 + v1
	D3DXVECTOR3 b = -3*p0 + 3*p1 - 2*v0 - v1; // b =-3p0 + 3p1 - 2v0 + v1
	D3DXVECTOR3 c =                  v0;      // c = v0  
	D3DXVECTOR3 d =    p0;                    // d = p0

	// Evaluate the equation at u, where:
	//    f(u) = au^3 + bu^2 + cu + d
	return ( ( a * u + b ) * u + c ) * u + d;
}

//-----------------------------------------------------------------------------
// Name: XBUtil_EvaluateCatmullRom()
// Desc: Evaluate a cubic parametric equation. Returns the point at u on a
//       Catmull-Rom curve.
//-----------------------------------------------------------------------------
D3DXVECTOR3 XBUtil_EvaluateCatmullRom( const D3DXVECTOR3& p1, const D3DXVECTOR3& p2, 
									  const D3DXVECTOR3& p3, const D3DXVECTOR3& p4,
									  FLOAT u )
{
	// Generate coefficients from four spline points
	D3DXVECTOR3 a =   -p1 + 3*p2 - 3*p3 + p4;
	D3DXVECTOR3 b =  2*p1 - 5*p2 + 4*p3 - p4;
	D3DXVECTOR3 c =   -p1        +   p3;
	D3DXVECTOR3 d =         2*p2;

	// Evaluate the equation at u, where:
	//    f(u) = 0.5 * ( au^3 + bu^2 + cu + d )
	return 0.5f * ( ( ( a * u + b ) * u + c ) * u + d );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_GetSplinePoint()
// Desc: Returns a point on a spline. The spline is defined by an array of
//       points, and the point and tangent returned are located at position t
//       on the spline, where 0 < t < dwNumSplinePts.
//-----------------------------------------------------------------------------
VOID XBUtil_GetSplinePoint( const D3DXVECTOR3* pSpline, DWORD dwNumSplinePts,
						   FLOAT t, D3DXVECTOR3* pvPoint, D3DXVECTOR3* pvTangent )
{
	DWORD p0 = ( t > 1.0 ) ? (DWORD)floorf(t)-1 : dwNumSplinePts-1;
	DWORD p1 = ( p0 < dwNumSplinePts-1 ) ? p0 + 1 : 0;
	DWORD p2 = ( p1 < dwNumSplinePts-1 ) ? p1 + 1 : 0;
	DWORD p3 = ( p2 < dwNumSplinePts-1 ) ? p2 + 1 : 0;
	FLOAT u  = t - floorf(t);

	if( pvPoint )
		(*pvPoint) = XBUtil_EvaluateCatmullRom( pSpline[p0], pSpline[p1], 
		pSpline[p2], pSpline[p3], u );

	if( pvTangent )
		(*pvTangent) = 0.5f * ( (1-u) * ( pSpline[p2] - pSpline[p0] ) + 
		(u) * ( pSpline[p3] - pSpline[p1] ) );
}




//-----------------------------------------------------------------------------
// Name: XBUtil_RenderSpline()
// Desc: For debugging purposes, visually renders a spline.
//-----------------------------------------------------------------------------
VOID XBUtil_RenderSpline( const D3DXVECTOR3* pSpline, DWORD dwNumSplinePts,
						 DWORD dwColor, BOOL bRenderAxes )
{
	D3DDevice::SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
	D3DDevice::SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_SELECTARG1 );
	D3DDevice::SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TFACTOR );
	D3DDevice::SetVertexShader( D3DFVF_XYZ );

	for( FLOAT u = 0; u < dwNumSplinePts; u += 1.0f )
	{
		D3DXVECTOR3 p[2];
		D3DXVECTOR3 vTangent, vSide, vUp;

		XBUtil_GetSplinePoint( pSpline, dwNumSplinePts, u+0, &p[0], &vTangent );
		XBUtil_GetSplinePoint( pSpline, dwNumSplinePts, u+1, &p[1], NULL );

		D3DXVec3Normalize( &vTangent, &vTangent );
		D3DXVECTOR3 v1( 0, 1, 0 );
		D3DXVec3Cross( &vSide, &v1, &vTangent );
		D3DXVec3Cross( &vUp, &vTangent, &vSide );

		D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, dwColor );
		D3DDevice::DrawPrimitiveUP( D3DPT_LINELIST, 1, p, sizeof(D3DXVECTOR3) );

		if( bRenderAxes )
		{
			p[1] = p[0] + vTangent/4;
			D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, 0xffff0000 );
			D3DDevice::DrawPrimitiveUP( D3DPT_LINELIST, 1, p, sizeof(D3DXVECTOR3) );

			p[1] = p[0] + vSide/4;
			D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, 0xff00ff00 );
			D3DDevice::DrawPrimitiveUP( D3DPT_LINELIST, 1, p, sizeof(D3DXVECTOR3) );

			p[1] = p[0] + vUp/4;
			D3DDevice::SetRenderState( D3DRS_TEXTUREFACTOR, 0xffffffff );
			D3DDevice::DrawPrimitiveUP( D3DPT_LINELIST, 1, p, sizeof(D3DXVECTOR3) );
		}
	}
}




//-----------------------------------------------------------------------------
// Name: XBUtil_DeclaratorFromFVF()
// Desc: Create a vertex declaration from an FVF. Registers are assigned as
//       follows:
//          v0     = Vertex position
//          v1     = Vertex blend weights
//          v2     = Vertex normal
//          v3     = Vertex diffuse color
//          v4     = Vertex specular color
//       // v5     = Vertex fog (no FVF code)
//       // v6     = Vertex pointsize (no FVF code)
//       // v7     = Vertex back diffuse color (no FVF code)
//       // v8     = Vertex back specular color (no FVF code)
//          v9-v12 = Vertex texture coords
//-----------------------------------------------------------------------------
HRESULT XBUtil_DeclaratorFromFVF( DWORD dwFVF, 
								 DWORD Declaration[MAX_FVF_DECL_SIZE] )
{
	// Start the declaration
	DWORD decl = 0;
	Declaration[decl++] = D3DVSD_STREAM(0);

	// Handle position
	DWORD dwPositionFVF = ( dwFVF & D3DFVF_POSITION_MASK );
	if( dwPositionFVF == D3DFVF_XYZRHW ) Declaration[decl++] = D3DVSD_REG( 0, D3DVSDT_FLOAT4 ); 
	else                                 Declaration[decl++] = D3DVSD_REG( 0, D3DVSDT_FLOAT3 ); 

	// Handle blend weights
	if( dwPositionFVF == D3DFVF_XYZB1 )  Declaration[decl++] = D3DVSD_REG( 1, D3DVSDT_FLOAT1 ); 
	if( dwPositionFVF == D3DFVF_XYZB2 )  Declaration[decl++] = D3DVSD_REG( 1, D3DVSDT_FLOAT2 ); 
	if( dwPositionFVF == D3DFVF_XYZB3 )  Declaration[decl++] = D3DVSD_REG( 1, D3DVSDT_FLOAT3 ); 
	if( dwPositionFVF == D3DFVF_XYZB4 )  Declaration[decl++] = D3DVSD_REG( 1, D3DVSDT_FLOAT4 ); 

	// Handle normal, diffuse, and specular
	if( dwFVF & D3DFVF_NORMAL )          Declaration[decl++] = D3DVSD_REG( 2, D3DVSDT_FLOAT3 );
	if( dwFVF & D3DFVF_DIFFUSE )         Declaration[decl++] = D3DVSD_REG( 3, D3DVSDT_D3DCOLOR );
	if( dwFVF & D3DFVF_SPECULAR )        Declaration[decl++] = D3DVSD_REG( 4, D3DVSDT_D3DCOLOR );

	// Handle texture coordinates
	DWORD dwNumTextures = (dwFVF & D3DFVF_TEXCOUNT_MASK) >> D3DFVF_TEXCOUNT_SHIFT;

	for( DWORD i=0; i<dwNumTextures; i++ )
	{
		LONG lTexCoordSize = ( dwFVF & (0x00030000<<(i*2)) );

		DWORD dwNumTexCoords = 0;
		if( lTexCoordSize == D3DFVF_TEXCOORDSIZE1(i) ) dwNumTexCoords = D3DVSDT_FLOAT1;
		if( lTexCoordSize == D3DFVF_TEXCOORDSIZE2(i) ) dwNumTexCoords = D3DVSDT_FLOAT2;
		if( lTexCoordSize == D3DFVF_TEXCOORDSIZE3(i) ) dwNumTexCoords = D3DVSDT_FLOAT3;
		if( lTexCoordSize == D3DFVF_TEXCOORDSIZE4(i) ) dwNumTexCoords = D3DVSDT_FLOAT4;

		Declaration[decl++] = D3DVSD_REG( 9 + i, dwNumTexCoords );
	}

	// End the declarator
	Declaration[decl++] = D3DVSD_END();

	return S_OK;
}




//-----------------------------------------------------------------------------
// Name: XBUtil_GetWide()
// Desc: Convert CHAR string to WCHAR string. dwMax includes the null byte.
//       Never copies more than dwMax-1 characters into strWide.
//          Ex: GetWide( "abc", strWide, 3 ) gives strWide = L"ab"
//-----------------------------------------------------------------------------
VOID XBUtil_GetWide( const CHAR* strThin, WCHAR* strWide, DWORD dwMax )
{
	assert( strThin != NULL );
	assert( strWide != NULL );

	// dwMax includes the null bytes, so must always be at least one.
	// Furthermore, if dwMax is 0, MultiByteToWideChar doesn't do any 
	// conversion, but returns the number of chars it *would* convert.
	assert( dwMax > 0 );

	// Determine how many characters we will convert. Can never be more
	// than dwMax-1
	int nChars = strlen( strThin );
	if( nChars > int(dwMax) - 1 )
		nChars = int(dwMax) - 1;

	// Perform the conversion
	int nWide = MultiByteToWideChar( CP_ACP, 0, strThin, nChars, 
		strWide, dwMax );
	assert( nChars == nWide - 1 );
	(VOID)nWide; // avoid compiler warning in release mode
}




//-----------------------------------------------------------------------------
// Name: XBUtil_LoadFile()
// Desc: Loads the specified file into a newly allocated buffer. Note that
//       dwSize is the size of the actual data, which is not necessarily the
//       size of the buffer. Caller is expected to free the buffer using free().  
//-----------------------------------------------------------------------------
HRESULT XBUtil_LoadFile( const CHAR* strFile, VOID** ppFileData, DWORD* pdwSize )
{
	if( ( NULL == strFile ) || ( NULL ==ppFileData ) )
		return E_INVALIDARG;

	// Initialize output values in case of error
	if( ppFileData )   (*ppFileData) = NULL;
	if( pdwSize )      (*pdwSize)    = 0;

	// Attempt to open the file
	HANDLE hFile = CreateFile( strFile, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING,
		FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING, NULL );
	if( INVALID_HANDLE_VALUE == hFile )
		return E_FAIL;

	// Determine how large the file is
	DWORD dwFileSize = GetFileSize( hFile, NULL );
	if( dwFileSize == (DWORD)-1 )
	{
		CloseHandle( hFile );
		return E_FAIL;
	}

	// Round the file size up to DVD sector multiple
	dwFileSize += XBOX_DVD_SECTOR_SIZE - 1;
	dwFileSize /= XBOX_DVD_SECTOR_SIZE;
	dwFileSize *= XBOX_DVD_SECTOR_SIZE;

	// The file had better fit into memory
	assert( dwFileSize <= 64 * 1024 * 1024 );

	// Allocate a buffer to store the file data
	VOID* pDataBuffer = malloc( dwFileSize );
	if( pDataBuffer == NULL )
	{
		CloseHandle( hFile );
		return E_OUTOFMEMORY;
	}

	// Kick off the read
	OVERLAPPED overlapped = {0};
	BOOL bResult = ReadFile( hFile, pDataBuffer, dwFileSize, NULL, &overlapped );

	// If there was an error other than IO_PENDING, the read failed
	if( !bResult && GetLastError() != ERROR_IO_PENDING )
	{
		free( pDataBuffer );
		CloseHandle( hFile );
		return GetLastError();
	}

	// Wait for the operation to finish
	DWORD dwBytesRead;
	if( !GetOverlappedResult( hFile, &overlapped, &dwBytesRead, TRUE ) )
	{
		free( pDataBuffer );
		CloseHandle( hFile );
		return GetLastError();
	}

	// Pass the data pointer back to the caller and clean up
	if( ppFileData )    (*ppFileData) = pDataBuffer;
	if( pdwSize )       (*pdwSize)    = dwBytesRead;
	CloseHandle( hFile );

	return S_OK;
}
