//-----------------------------------------------------------------------------
// 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 "stdafx.h"


#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_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[256];
        sprintf( strBuffer, "XBUtil_FindMediaFile(): Could not find file [%s]\n", 
                            strFilename );
        OUTPUT_DEBUG_STRING( strBuffer );
//        return 0x82000004;
		  return E_FAIL;
    }

    // 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)floor(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 - floor(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.  Caller is
//          expected to free the buffer using free().  Note that dwSize is the
//          size of the actual data, which is not necessarily the size of the
//          buffer.
//-----------------------------------------------------------------------------
HRESULT XBUtil_LoadFile( const CHAR* strFile, VOID** ppFileData, DWORD* pdwSize )
{
    assert( strFile    != NULL );
    assert( ppFileData != NULL );
    assert( pdwSize    != NULL );

    // Initialize output values in case of error
    *ppFileData = NULL;
    *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( hFile == INVALID_HANDLE_VALUE )
        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
    *ppFileData = pDataBuffer;
    *pdwSize    = dwBytesRead;
    CloseHandle( hFile );

    return S_OK;
}
