//----------------------------------------------------------------------------
// File: d3dgraphics.cpp
//
// Desc: see main.cpp
//
// Copyright (c) 1999-2001 Microsoft Corp. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#define D3D_OVERLOADS
#include <windows.h>
#include <D3DX9.h>
#include <dplay8.h>
#include <stdio.h>
#include <math.h>
#include <mmsystem.h>
#include <process.h>
#include <dxerr9.h>
#include <tchar.h>
#include <dplay8.h>
#include "SyncObjects.h"
#include "DummyConnector.h"
#include "IMazeGraphics.h"
#include "SmartVB.h"
#include "MazeServer.h"
#include "MazeApp.h"
#include "IMazeGraphics.h"
#include "d3dSaver.h"
#include "D3DFont.h"
#include "D3DFile.h"
#include "D3DUtil.h"
#include "D3DGraphics.h"
#include "resource.h"
#include "DXUtil.h"

static CD3DGraphics* s_pGraphics = NULL;



//-----------------------------------------------------------------------------
// Name: CD3DGraphics
// Desc: Constructor
//-----------------------------------------------------------------------------
CD3DGraphics::CD3DGraphics()
{
    s_pGraphics             = this;
    m_pMazeApp              = NULL;

    m_pSphere               = NULL;
    m_pWallTexture          = NULL;
    m_pFloorTexture         = NULL;
    m_pCeilingTexture       = NULL;
    m_pNetIconTexture       = NULL;
    m_pLocalIconTexture     = NULL;

    // Tell the d3dsaver framework to run on one monitor only
    m_bOneScreenOnly        = TRUE;

    m_bUseDepthBuffer       = TRUE;
 
    // Create fonts
    m_pFont      = new CD3DFont( _T("Courier"), 12, D3DFONT_BOLD );
    m_pStatsFont = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD );
    m_pPlayerMesh  = new CD3DFile();
    lstrcpy( m_strRegPath, MAZE_REGKEYNAME );
    
}




//-----------------------------------------------------------------------------
// Name: ~CD3DGraphics
// Desc: Destructor
//-----------------------------------------------------------------------------
CD3DGraphics::~CD3DGraphics()
{
    SAFE_DELETE( m_pFont );
    SAFE_DELETE( m_pStatsFont );
    SAFE_DELETE( m_pPlayerMesh );
}




//-----------------------------------------------------------------------------
// Name: Init
// Desc: Initializes the member variables
//-----------------------------------------------------------------------------
VOID CD3DGraphics::Init( CMazeApp* pMazeApp, CDPlay8Client* pDP8Client, 
                         CMazeClient* pMazeClient )
{
    m_pMazeApp      = pMazeApp;
    m_pMazeClient   = pMazeClient;
    m_pDP8Client    = pDP8Client;
}




//-----------------------------------------------------------------------------
// Name: Create
// Desc: Calls the Create method of the CD3DScreensaver base class
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::Create( HINSTANCE hInstance )
{
    return CD3DScreensaver::Create( hInstance );
}



//-----------------------------------------------------------------------------
// Name: PostCreate()
// Desc: Creates the screen saver window for full screen modes
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::PostCreate()
{
    HRESULT hr;

    // Create the screen saver window
    if( m_SaverMode == sm_test    || 
        m_SaverMode == sm_full )
    {
        if( FAILED( hr = CreateSaverWindow() ) )
        {
            m_bErrorMode = TRUE;
            m_hrError = hr;
        }
    }

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: Run()
// Desc: Launch the screen saver based on command line options
//-----------------------------------------------------------------------------
INT CD3DGraphics::Run()
{
    HRESULT hr;
    BOOL bIsScreenSaver = TRUE;
    TCHAR* strCmdLine = GetCommandLine();

    // Parse the command line and do the appropriate thing
    m_SaverMode = ParseCommandLine( strCmdLine );

    // Skip along to the first option delimiter "/" or "-"
    while ( *strCmdLine && *strCmdLine != '/' && *strCmdLine != '-' )
        strCmdLine++;
    
    // If there weren't any params, then it must have run from a exe form
    if ( *strCmdLine == 0 )
    {
        m_SaverMode = sm_config;
        bIsScreenSaver = FALSE;
    }

    m_pMazeApp->SetAllowLoopback( TRUE );

    switch ( m_SaverMode )
    {
        case sm_config:
            ScreenSaverDoConfig( bIsScreenSaver );
            if( m_dwStartMode == 0 )
                break;

            // Don't do AutoDisconnnect or Stress Connect in 3D client
            m_pConfig = m_pMazeApp->GetConfig();
            m_pConfig->bAutoDisconnnect = FALSE;
            m_pConfig->dwStressConnect = 0;

            if( m_pConfig->bFullScreen )
                m_SaverMode = sm_full;
            else
                m_SaverMode = sm_test;

            m_pMazeApp->SetAllowConnect( TRUE );

            PostCreate();   

            if( FAILED( hr = DoSaver() ) )
                DisplayErrorMsg( hr, 0 );
            break;

        case sm_preview:
/*
            // This is temp code to allow stepping thru code in the preview case
            WNDCLASS    cls;
            cls.hCursor        = NULL; 
            cls.hIcon          = NULL; 
            cls.lpszMenuName   = NULL;
            cls.lpszClassName  = "Parent"; 
            cls.hbrBackground  = (HBRUSH) GetStockObject(WHITE_BRUSH);
            cls.hInstance      = m_hInstance; 
            cls.style          = CS_VREDRAW|CS_HREDRAW|CS_SAVEBITS|CS_DBLCLKS;
            cls.lpfnWndProc    = DefWindowProc;
            cls.cbWndExtra     = 0; 
            cls.cbClsExtra     = 0; 
            RegisterClass( &cls );

            // Create the window
            RECT    rect;
            HWND hwnd;
            rect.left = rect.top = 40;
            rect.right = rect.left+100;
            rect.bottom = rect.top+100;
            AdjustWindowRect( &rect , WS_VISIBLE|WS_OVERLAPPED|WS_CAPTION|WS_POPUP , FALSE );
            hwnd= CreateWindow( "Parent", "FakeShell" ,
                                   WS_VISIBLE|WS_OVERLAPPED|WS_CAPTION|WS_POPUP , rect.left , rect.top ,
                                   rect.right-rect.left , rect.bottom-rect.top , NULL ,
                                   NULL , m_hInstance , NULL );
            m_hWndParent = hwnd;
*/
        case sm_test:
        case sm_full:
            if( m_SaverMode == sm_preview )
                m_pMazeApp->SetAllowConnect( FALSE );
            else
                m_pMazeApp->SetAllowConnect( TRUE );
            
            // Don't do AutoDisconnnect or StressConnect in 3D client
            m_pConfig = m_pMazeApp->GetConfig();
            m_pConfig->bAutoDisconnnect = FALSE;
            m_pConfig->dwStressConnect = 0;

            if( m_SaverMode != sm_preview && m_pConfig->bFullScreen )
                m_SaverMode = sm_full;
            else
            {
                m_SaverMode = sm_test;
            }

            if( FAILED( hr = DoSaver() ) )
                DisplayErrorMsg( hr, 0 );
            break;
    
        case sm_passwordchange:
            ChangePassword();
            break;
    }

    return 0;
}




//-----------------------------------------------------------------------------
// Name: Shutdown
// Desc: Shutdown the screen saver
//-----------------------------------------------------------------------------
void CD3DGraphics::Shutdown()
{
}





//-----------------------------------------------------------------------------
// Name: ScreenSaverDoConfig
// Desc: Open the configuration UI and save user settings
//-----------------------------------------------------------------------------
int CD3DGraphics::ScreenSaverDoConfig( BOOL bIsScreenSaverSettings )
{
    m_bIsScreenSaverSettings = bIsScreenSaverSettings;

    m_pMazeApp->ReadConfig();

    HKEY hKey = NULL;
    if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_CURRENT_USER, MAZE_REGKEYNAME, 0, KEY_READ, &hKey ) )
    {
        ReadScreenSettings( hKey );
        RegCloseKey( hKey );
    }

    m_pConfig = m_pMazeApp->GetConfig();

    if( DialogBox( m_hInstance, MAKEINTRESOURCE(IDD_SAVERCONFIG), 
                   NULL, StaticSaverConfigDlgProc ) == IDOK )
    {
        m_pMazeApp->WriteConfig();

        hKey = NULL;
        if( ERROR_SUCCESS == RegOpenKeyEx( HKEY_CURRENT_USER, MAZE_REGKEYNAME, 0, KEY_READ, &hKey ) )
        {
            WriteScreenSettings( hKey );
            RegCloseKey( hKey );
        }
    }

    return 0;
}




//-----------------------------------------------------------------------------
// Name: StaticSaverConfigDlgProc
// Desc: Static helper method for the dialog procedure
//-----------------------------------------------------------------------------
INT_PTR CALLBACK CD3DGraphics::StaticSaverConfigDlgProc( HWND hDlg, UINT uMsg, 
                                                         WPARAM wParam, LPARAM lParam )
{
    return s_pGraphics->SaverConfigDlgProc( hDlg, uMsg, wParam, lParam );
}




//-----------------------------------------------------------------------------
// Name: SaverConfigDlgProc
// Desc: Dialog procedure for configuration dialog
//-----------------------------------------------------------------------------
INT_PTR CALLBACK CD3DGraphics::SaverConfigDlgProc( HWND hDlg, UINT uMsg, 
                                                   WPARAM wParam, LPARAM lParam )
{
    switch( uMsg )
    {
        case WM_INITDIALOG:
            PopulateSaverConfigDlg( hDlg );
            return TRUE;

        case WM_CLOSE:
            EndDialog( hDlg, IDCANCEL );
            break;

        case WM_COMMAND:
            switch( HIWORD(wParam) )
            {
                case BN_CLICKED:
                    switch( LOWORD(wParam) )
                    {
                        case IDOK:
                            ExtractSaverConfigDlgSettings( hDlg );
                            m_dwStartMode = 0;
                            EndDialog( hDlg, IDOK );
                            break;

                        case IDC_LAUNCH:
                            ExtractSaverConfigDlgSettings( hDlg );
                            m_dwStartMode = 1;
                            EndDialog( hDlg, IDOK );
                            break;

                        case IDC_SCREENSETTINGS:
                            DoScreenSettingsDialog( hDlg );
                            break;

                        case IDCANCEL:
                            EndDialog( hDlg, IDCANCEL );
                            return TRUE;
                    }
                    break;
            }
            break;
    }

    return FALSE;
}




//-----------------------------------------------------------------------------
// Name: PopulateSaverConfigDlg
// Desc: Populate the configuration dialog based on stored user options
//-----------------------------------------------------------------------------
void CD3DGraphics::PopulateSaverConfigDlg( HWND hDlg )
{
    if( m_bIsScreenSaverSettings )
        ShowWindow( GetDlgItem( hDlg, IDOK ), SW_SHOW );
    else
        ShowWindow( GetDlgItem( hDlg, IDOK ), SW_HIDE );

    CheckDlgButton( hDlg, IDC_MICROSOFT, m_pConfig->bConnectToMicrosoftSite );
    CheckDlgButton( hDlg, IDC_LOCAL_SERVER, m_pConfig->bConnectToLocalServer );
    CheckDlgButton( hDlg, IDC_REMOTE_SERVER, m_pConfig->bConnectToRemoteServer );
    SetDlgItemInt( hDlg, IDC_RETRY_DELAY, m_pConfig->dwNetworkRetryDelay, FALSE );
    CheckDlgButton( hDlg, IDC_FRAMERATE, m_pConfig->bShowFramerate );
    CheckDlgButton( hDlg, IDC_INDICATORS, m_pConfig->bShowIndicators );
    CheckDlgButton( hDlg, IDC_FULLSCREEN, m_pConfig->bFullScreen );
    CheckDlgButton( hDlg, IDC_MINIMAP, m_pConfig->bDrawMiniMap );
    CheckDlgButton( hDlg, IDC_REFLECTIONS, m_pConfig->bReflections );
    CheckDlgButton( hDlg, IDC_LATENCY, m_pConfig->bSmoothLatency );
    CheckDlgButton( hDlg, IDC_FILELOGGING, m_pConfig->bFileLogging );

    SetDlgItemText( hDlg, IDC_IPADDRESS, m_pConfig->szIPAddress );
    SendDlgItemMessage( hDlg, IDC_IPADDRESS, EM_SETLIMITTEXT, ( (sizeof(m_pConfig->szIPAddress) / sizeof(TCHAR)) - 1 ), 0 );
}




//-----------------------------------------------------------------------------
// Name: ExtractSaverConfigDlgSettings
// Desc: Save the current UI values to the stored user settings
//-----------------------------------------------------------------------------
void CD3DGraphics::ExtractSaverConfigDlgSettings( HWND hDlg )
{
    m_pConfig->bConnectToMicrosoftSite    = IsDlgButtonChecked( hDlg, IDC_MICROSOFT );
    m_pConfig->bConnectToLocalServer      = IsDlgButtonChecked( hDlg, IDC_LOCAL_SERVER );
    m_pConfig->bConnectToRemoteServer     = IsDlgButtonChecked( hDlg, IDC_REMOTE_SERVER );
    m_pConfig->dwNetworkRetryDelay        = GetDlgItemInt( hDlg, IDC_RETRY_DELAY, NULL, FALSE );
    m_pConfig->bShowFramerate             = IsDlgButtonChecked( hDlg, IDC_FRAMERATE );
    m_pConfig->bShowIndicators            = IsDlgButtonChecked( hDlg, IDC_INDICATORS );
    m_pConfig->bFullScreen                = IsDlgButtonChecked( hDlg, IDC_FULLSCREEN );
    m_pConfig->bDrawMiniMap               = IsDlgButtonChecked( hDlg, IDC_MINIMAP );
    m_pConfig->bReflections               = IsDlgButtonChecked( hDlg, IDC_REFLECTIONS );
    m_pConfig->bSmoothLatency             = IsDlgButtonChecked( hDlg, IDC_LATENCY );
    m_pConfig->bFileLogging               = IsDlgButtonChecked( hDlg, IDC_FILELOGGING );

    GetDlgItemText( hDlg, IDC_IPADDRESS, m_pConfig->szIPAddress, sizeof(m_pConfig->szIPAddress) / sizeof(TCHAR) );
}




//-----------------------------------------------------------------------------
// Name: HandleOutputMsg
// Desc: Null override
//-----------------------------------------------------------------------------
void CD3DGraphics::HandleOutputMsg( EnumLineType enumLineType, TCHAR* strLine )
{
    // Nothing needs to be done here
    return;
}




//-----------------------------------------------------------------------------
// Name: SaverProc
// Desc: Callback procedure to handle screensaver messages
//-----------------------------------------------------------------------------
LRESULT CD3DGraphics::SaverProc( HWND hWnd , UINT message , WPARAM wParam , LPARAM lParam )
{
    switch( message )
    {
        case WM_KEYDOWN:
            if( wParam == 'R' )
                m_pConfig->bReflections = !m_pConfig->bReflections;
            else if( wParam == 'L' )
                m_pConfig->bSmoothLatency = !m_pConfig->bSmoothLatency;
            else if( wParam == 'A' )
                m_pMazeClient->EngageAutopilot( !m_pMazeClient->IsAutopilot() );
            break;

        case WM_POWERBROADCAST:
            switch( wParam )
            {
                #ifndef PBT_APMQUERYSUSPEND  // Defines are here for old compilers
                    #define PBT_APMQUERYSUSPEND 0x0000
                #endif
                #ifndef BROADCAST_QUERY_DENY
                    #define BROADCAST_QUERY_DENY         0x424D5144 
                #endif
                case PBT_APMQUERYSUSPEND:
                    // Tell the OS not to suspend, otherwise we
                    // will loose the network connection.
                    return BROADCAST_QUERY_DENY;

                #ifndef PBT_APMRESUMESUSPEND
                    #define PBT_APMRESUMESUSPEND 0x0007
                #endif
                case PBT_APMRESUMESUSPEND:
                    return TRUE;
            }
            break;
    }

    return CD3DScreensaver::SaverProc( hWnd, message, wParam, lParam );
}




//-----------------------------------------------------------------------------
// Name: ConfirmDevice
// Desc: Confirm the device selection
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::ConfirmDevice(D3DCAPS9* pCaps, DWORD dwBehavior,
                                          D3DFORMAT Format )
{ 
    if( dwBehavior & D3DCREATE_PUREDEVICE )
        return E_FAIL; // GetTransform doesn't work on PUREDEVICE

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: OneTimeSceneInit
// Desc: One time scene initialization
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::OneTimeSceneInit()                         
{ 
    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: CreateTextureFromResources
// Desc: Calls the D3DX helper function to load the given resource
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::CreateTextureFromResource( INT nResource, LPDIRECT3DTEXTURE9* ppTexture )                         
{ 
    if( FAILED( D3DXCreateTextureFromResourceEx( m_pd3dDevice, NULL, MAKEINTRESOURCE( nResource ), 
                        D3DX_DEFAULT, D3DX_DEFAULT, D3DX_DEFAULT, 0, D3DFMT_A8R8G8B8, 
                        D3DPOOL_MANAGED, D3DX_FILTER_TRIANGLE|D3DX_FILTER_MIRROR, 
                        D3DX_FILTER_TRIANGLE|D3DX_FILTER_MIRROR, 0xFF000000, NULL, NULL, ppTexture ) ) )
        return D3DAPPERR_MEDIANOTFOUND;

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: InitDeviceObjects
// Desc: Initialize device objects
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::InitDeviceObjects()
{ 
    HRESULT hr;

    m_dwTesselation = 3;

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 1, D3DTSS_COLOROP, D3DTOP_DISABLE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG2, D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 1, D3DTSS_ALPHAOP, D3DTOP_DISABLE );

    // Restore the fonts
    m_pFont->InitDeviceObjects( m_pd3dDevice );
    m_pStatsFont->InitDeviceObjects( m_pd3dDevice );

    if( FAILED( CreateTextureFromResource( IDB_WALLTEXTURE, &m_pWallTexture ) ) )
        return DXTRACE_ERR( TEXT("IDB_WALLTEXTURE"), D3DAPPERR_MEDIANOTFOUND );
    if( FAILED( CreateTextureFromResource( IDB_FLOORTEXTURE, &m_pFloorTexture ) ) )
        return DXTRACE_ERR( TEXT("IDB_FLOORTEXTURE"), D3DAPPERR_MEDIANOTFOUND );
    if( FAILED( CreateTextureFromResource( IDB_CEILINGTEXTURE, &m_pCeilingTexture ) ) )
        return DXTRACE_ERR( TEXT("IDB_CEILINGTEXTURE"), D3DAPPERR_MEDIANOTFOUND );
    if( FAILED( CreateTextureFromResource( IDB_NETICON, &m_pNetIconTexture ) ) )
        return DXTRACE_ERR( TEXT("IDB_NETICON"), D3DAPPERR_MEDIANOTFOUND );
    if( FAILED( CreateTextureFromResource( IDB_LOCALICON, &m_pLocalIconTexture ) ) )
        return DXTRACE_ERR( TEXT("IDB_LOCALICON"), D3DAPPERR_MEDIANOTFOUND );

    // Create airplane for rendering players
    if( FAILED( m_pPlayerMesh->CreateFromResource( m_pd3dDevice, MAKEINTRESOURCE(IDB_TANK), TEXT("XFILE") ) ) )
        return DXTRACE_ERR_MSGBOX( TEXT("tank.x"), D3DAPPERR_MEDIANOTFOUND );

    // Create sphere for rendering clients
    if( FAILED( hr = D3DXCreateSphere( m_pd3dDevice, 0.2f, 8, 8, &m_pSphere, NULL ) ) )
        return DXTRACE_ERR( TEXT("D3DXCreateSphere"), hr );

    if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( sizeof(VERTEX_TL_DIFFUSE) * 4, 
                                                D3DUSAGE_WRITEONLY, FVF_TL_DIFFUSE, 
                                                D3DPOOL_MANAGED, &m_pMiniMapBackgroundVB, NULL ) ) )
        return DXTRACE_ERR( TEXT("CreateVertexBuffer"), hr );

    if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( sizeof(VERTEX_TL_TEXTURED) * 4, 
                                                D3DUSAGE_WRITEONLY, FVF_TL_TEXTURED, 
                                                D3DPOOL_MANAGED, &m_pIndicatorVB, NULL ) ) )
        return DXTRACE_ERR( TEXT("CreateVertexBuffer"), hr );

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: RestoreDeviceObject
// Desc: Restore device objects
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::RestoreDeviceObjects()
{ 
    HRESULT hr;

    // Restore the fonts
    m_pFont->RestoreDeviceObjects();
    m_pStatsFont->RestoreDeviceObjects();

    m_pPlayerMesh->RestoreDeviceObjects( m_pd3dDevice );

    // Initialise the SmartVB for the walls
    if( FAILED( hr = m_SmartVB.Init( m_pD3D, m_pd3dDevice, 2048 ) ) )
        return DXTRACE_ERR( TEXT("m_SmartVB.Init"), hr );

    if( FAILED( hr = m_pd3dDevice->CreateVertexBuffer( sizeof(VERTEX_DIFFUSE) * 1024, 
                                                D3DUSAGE_DYNAMIC|D3DUSAGE_WRITEONLY, FVF_DIFFUSE, 
                                                D3DPOOL_DEFAULT, &m_pMiniMapVB, NULL ) ) )
        return DXTRACE_ERR( TEXT("CreateVertexBuffer"), hr );

    // Set projection matrix
    const float fov = 1.8f;
    const float znear = 0.1f;
    const float zfar = 30.0f;
    const float viewwidth = float(tan(fov*0.5f) ) * znear;
    const float viewheight = viewwidth * float(m_d3dsdBackBuffer.Height)/float(m_d3dsdBackBuffer.Width);
    D3DXMatrixPerspectiveLH( &m_Projection, viewwidth, viewheight, znear, zfar );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_Projection );

    // Set renderstates and lighting
    D3DXMATRIX matIden; D3DXMatrixIdentity( &matIden );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &matIden );
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x333333 );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MINFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_MIPFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );
    m_pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE );
    m_pd3dDevice->SetRenderState( D3DRS_FOGENABLE, FALSE );

    D3DMATERIAL9 mtrl;
    D3DUtil_InitMaterial( mtrl, 1.0f, 1.0f, 1.0f );
    m_pd3dDevice->SetMaterial( &mtrl );

    // Init light
    D3DUtil_InitLight( m_Light, D3DLIGHT_POINT, m_vCameraPos.x, m_vCameraPos.y, m_vCameraPos.z );
    m_Light.Direction = D3DXVECTOR3(0,0,0);
    m_Light.Range = 80.0f;
    m_Light.Attenuation0 = 1.0f;
    m_Light.Attenuation1 = 0.0f;
    m_Light.Attenuation2 = 1.0f;
    m_pd3dDevice->SetLight( 0, &m_Light );
    m_pd3dDevice->LightEnable( 0, TRUE );

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: FrameMove
// Desc: Handle single frame increment
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::FrameMove()
{ 
    m_pMazeApp->FrameMove( m_fElapsedTime );

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: Render
// Desc: Render the 3D scene
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::Render()
{ 
    // Clear the viewport
    m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER, 0x00000000, 1.0f, 0L );

    // Begin the scene 
    if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
    {
        m_vCameraPos = m_pMazeClient->GetCameraPos();
        m_fCameraYaw = AngleToFloat(m_pMazeClient->GetCameraYaw() );

        // Compute and set view matrix
        D3DXMATRIX matTrans,matRot;
        D3DXMatrixTranslation( &matTrans, -m_vCameraPos.x, -m_vCameraPos.y, -m_vCameraPos.z );
        D3DXMatrixRotationY( &matRot, m_fCameraYaw );
        m_Camera = matTrans * matRot;
        m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_Camera );

        // Move light
        m_Light.Position = m_vCameraPos;
        m_pd3dDevice->SetLight( 0, &m_Light );

        // Get visibility set of maze
        D3DXVECTOR2 dir;
        dir.y = float(cos(m_fCameraYaw) );
        dir.x = float(-sin(m_fCameraYaw) );
        m_dwNumVisibleCells = m_pMazeClient->m_Maze.GetVisibleCells( D3DXVECTOR2(m_vCameraPos.x, m_vCameraPos.z) ,
                                                                   dir, 1.8f, m_mcrVisList, MAX_VISLIST );

        // Draw reflection of walls and ceiling in floor
        if( m_pConfig->bReflections )
        {
            D3DXMATRIX reflectcamera = m_Camera;
            reflectcamera.m[1][0] = -reflectcamera.m[1][0];
            reflectcamera.m[1][1] = -reflectcamera.m[1][1];
            reflectcamera.m[1][2] = -reflectcamera.m[1][2];
            reflectcamera.m[1][3] = -reflectcamera.m[1][3];
            m_pd3dDevice->SetTransform( D3DTS_VIEW, &reflectcamera );

            m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CW );

            m_Light.Diffuse = D3DXCOLOR(0.7f,0.7f,0.7f,0);
            m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x222222 );
            m_pd3dDevice->SetLight( 0, &m_Light );

            DrawWalls();
            DrawCeiling();
//            DrawPlayers();

            m_Light.Diffuse = D3DXCOLOR(1,1,1,0);
            m_pd3dDevice->SetRenderState( D3DRS_AMBIENT, 0x333333 );
            m_pd3dDevice->SetLight( 0, &m_Light );

            m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
            m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_Camera );
        }

        // Draw floor
        if( m_pConfig->bReflections )
        {
            m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
            m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_ONE );
            m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_ONE );
        }

        DrawFloor();
        if( m_pConfig->bReflections )
            m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

        // Draw walls and ceiling
        DrawWalls();
        DrawCeiling();

        // Draw Players
        DrawPlayers();

        // Draw mini-map
        if( m_pConfig->bDrawMiniMap )
            DrawMiniMap();

        // Draw indicators
        if( m_pConfig->bShowIndicators )
            DrawIndicators();

        // Show frame rate
        m_pStatsFont->DrawText( 2,  0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats );
        m_pStatsFont->DrawText( 2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats );

        // End the scene.
        m_pd3dDevice->EndScene();
    }

    return S_OK;
}





//-----------------------------------------------------------------------------
// Name: DrawPlayers
// Desc: As part of the render process, draw each player mesh
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawPlayers()
{
    D3DXMATRIX matWorld;
    D3DXMATRIX matScale;
    D3DXMATRIX matTrans;
    D3DXMATRIX matRot;

    D3DXMatrixScaling( &matScale, 0.25f, 0.25f, 0.25f );

    MazeCellRef* pCell = m_mcrVisList;
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTexture( 0, m_pCeilingTexture );

    m_pMazeClient->LockWorld();
    for( DWORD i = 0; i < m_dwNumVisibleCells; i++, pCell++ )
    {
        PlayerObject* pPlayerObject = m_pMazeClient->GetFirstPlayerObjectInCell( pCell->x, pCell->y );
        while( pPlayerObject )
        {
            // Predict the current position using the last known position and velocities
            D3DXVECTOR3 vPosGuess = pPlayerObject->vPos;
            ANGLE       aYawGuess = pPlayerObject->aYaw;

            if( m_pConfig->bSmoothLatency )
            {
                FLOAT fTimeLapse = DXUtil_Timer( TIMER_GETAPPTIME ) - pPlayerObject->fLastValidTime;

                vPosGuess.x += pPlayerObject->vVelPos.x * fTimeLapse;
                vPosGuess.y += pPlayerObject->vVelPos.y * fTimeLapse;
                vPosGuess.z += pPlayerObject->vVelPos.z * fTimeLapse;

                aYawGuess = (ANGLE) ( aYawGuess + ( pPlayerObject->nVelYaw * fTimeLapse ) );
            }

            D3DXMatrixTranslation( &matTrans, vPosGuess.x, vPosGuess.y - 0.5f, vPosGuess.z );
            D3DXMatrixRotationYawPitchRoll( &matRot, -AngleToFloat(aYawGuess), -D3DX_PI/2.0f, 0.0f );
            matWorld = matScale * matRot * matTrans;

            m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
            // m_pSphere->DrawSubset( 0 );
            m_pPlayerMesh->Render( m_pd3dDevice );

            pPlayerObject = pPlayerObject->pNext;
        }
    }

    m_pd3dDevice->SetTexture( 0, NULL );

    D3DMATERIAL9 mtrl;
    D3DUtil_InitMaterial( mtrl, 1.0f, 1.0f, 1.0f );
    m_pd3dDevice->SetMaterial( &mtrl );

    m_pMazeClient->UnlockWorld();
}




//-----------------------------------------------------------------------------
// Name: DrawFloor
// Desc: As part of the render process, render the floor surface
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawFloor()
{
    MazeCellRef*    pCell = m_mcrVisList;
    VERTEX_TEXURED* pVert;
    WORD*           pIndex;
    WORD            offset;
    D3DXVECTOR3     normal(0,1,0);
    DWORD           verts_per_side = (m_dwTesselation+1)*(m_dwTesselation+1);
    DWORD           indicies_per_side = m_dwTesselation*m_dwTesselation*6;

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTexture( 0, m_pFloorTexture );

    m_SmartVB.Begin();
    for( DWORD i = 0; i < m_dwNumVisibleCells; i++, pCell++ )
    {
        D3DXVECTOR3 pos( float(pCell->x), 0, float(pCell->y) );
        m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
        LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(1,0,0), D3DXVECTOR3(0,0,1) ,
                  D3DXVECTOR3(0,1,0) );
    }
    m_SmartVB.End();
}




//-----------------------------------------------------------------------------
// Name: DrawCeiling
// Desc: As part of the render process, draw the ceiling surface
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawCeiling()
{
    MazeCellRef*    pCell = m_mcrVisList;
    VERTEX_TEXURED* pVert;
    WORD*           pIndex;
    WORD            offset;
    D3DXVECTOR3     normal(0,1,0);
    DWORD           verts_per_side = (m_dwTesselation+1)*(m_dwTesselation+1);
    DWORD           indicies_per_side = m_dwTesselation*m_dwTesselation*6;

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTexture( 0, m_pCeilingTexture );

    m_SmartVB.Begin();
    for( DWORD i = 0; i < m_dwNumVisibleCells; i++, pCell++ )
    {
        D3DXVECTOR3 pos( float(pCell->x), 1, float(1+pCell->y) );
        m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
        LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(1,0,0), D3DXVECTOR3(0,0,-1) ,
                  D3DXVECTOR3(0,-1,0) );
    }
    m_SmartVB.End();
}




//-----------------------------------------------------------------------------
// Name: DrawWalls
// Desc: As part of the render process, draw the wall surfaces
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawWalls()
{
    MazeCellRef*  pCell = m_mcrVisList;
    VERTEX_TEXURED* pVert;
    WORD*         pIndex;
    WORD          offset;
    DWORD         verts_per_side = (m_dwTesselation+1)*(m_dwTesselation+1);
    DWORD         indicies_per_side = m_dwTesselation*m_dwTesselation*6;

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTexture( 0, m_pWallTexture );

    m_SmartVB.Begin();
    for( DWORD i = 0; i < m_dwNumVisibleCells; i++, pCell++ )
    {
        BYTE cell = m_pMazeClient->m_Maze.GetCell(pCell->x,pCell->y);

        if( cell & MAZE_WALL_NORTH )
        {
            D3DXVECTOR3 pos( float(pCell->x), 1, float(pCell->y) );
            m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
            LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(1,0,0), D3DXVECTOR3(0,-1,0) ,
                      D3DXVECTOR3(0,0,1) );
        }
        if( cell & MAZE_WALL_SOUTH )
        {
            D3DXVECTOR3 pos( float(1+pCell->x), 1, float(1+pCell->y) );
            m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
            LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(-1,0,0), D3DXVECTOR3(0,-1,0) ,
                      D3DXVECTOR3(0,0,-1) );
        }
        if( cell & MAZE_WALL_WEST )
        {
            D3DXVECTOR3 pos( float(pCell->x), 1, float(1+pCell->y) );
            m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
            LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(0,0,-1), D3DXVECTOR3(0,-1,0) ,
                      D3DXVECTOR3(1,0,0) );
        }
        if( cell & MAZE_WALL_EAST )
        {
            D3DXVECTOR3 pos( float(1+pCell->x), 1, float(pCell->y) );
            m_SmartVB.MakeRoom( verts_per_side, indicies_per_side, &pVert, &pIndex, &offset );
            LoadQuad( pVert, pIndex, offset, pos, D3DXVECTOR3(0,0,1), D3DXVECTOR3(0,-1,0) ,
                      D3DXVECTOR3(-1,0,0) );
        }
    }
    m_SmartVB.End();
}




//-----------------------------------------------------------------------------
// Name: LoadQuad
// Desc: Create and tesselate the given quad surface
//-----------------------------------------------------------------------------
void CD3DGraphics::LoadQuad( VERTEX_TEXURED* pVerts, WORD* pIndex, WORD wOffset,
                      const D3DXVECTOR3& vOrigin, const D3DXVECTOR3& vBasis1,
                      const D3DXVECTOR3& vBasis2, const D3DXVECTOR3& vNormal )
{
    // Compute edge steps
    float       fStep = 1.0f / m_dwTesselation;
    D3DXVECTOR3 vStep1 = vBasis1 * fStep;
    D3DXVECTOR3 vStep2 = vBasis2 * fStep;

    // Fill out grid of vertices
    D3DXVECTOR3 rowstart = vOrigin;
    float u = 0;
    for( DWORD i = 0; i <= m_dwTesselation; i++, rowstart += vStep1, u += fStep )
    {
        D3DXVECTOR3 pos = rowstart;
        float     v = 0;
        for( DWORD j = 0; j <= m_dwTesselation; j++, pos += vStep2, v += fStep )
        {
            pVerts->vPos = pos;
            pVerts->vNormal = vNormal;
            pVerts->fU = u;
            pVerts->fV = v;
            pVerts++;
        }
    }

    // Fill out grid of indicies
    for( i = 0; i < m_dwTesselation; i++ )
    {
        for( DWORD j = 0; j < m_dwTesselation; j++ )
        {
            *pIndex++ = wOffset;
            *pIndex++ = wOffset+1;
            *pIndex++ = wOffset+1+(WORD(m_dwTesselation)+1);
            *pIndex++ = wOffset;
            *pIndex++ = wOffset+1+(WORD(m_dwTesselation)+1);
            *pIndex++ = wOffset+(WORD(m_dwTesselation)+1);
            wOffset++;
        }
        wOffset++;
    }
}




//-----------------------------------------------------------------------------
// Name: DrawMiniMap
// Desc: Draw the superimposed overhead map
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawMiniMap()
{
    DWORD           y;
    DWORD           x;
    VERTEX_DIFFUSE* verts = NULL;
    D3DXVECTOR3     pos;
    D3DXVECTOR3     origin;
    D3DXMATRIX      matWorld;
    D3DXMATRIX      matTrans;
    D3DXMATRIX      matRot;

    // Compute size of mini-map based on display size
    DWORD mapsize = m_d3dsdBackBuffer.Width / 4;
    int mapx = m_d3dsdBackBuffer.Width  - mapsize - 16;
    int mapy = m_d3dsdBackBuffer.Height - mapsize - 16;

    if( mapx < 0 || mapy < 0 )
        return;

    // Disable z-buffering
    m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, FALSE );
    m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, FALSE );

    D3DXMatrixIdentity( &matWorld );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

    // Draw alpha blended background
    VERTEX_TL_DIFFUSE* v;   

    m_pMiniMapBackgroundVB->Lock( 0, 0, (void**)&v, 0 );
    v[0].vPos.x = float(mapx)-0.5f;             v[0].vPos.y = float(mapy)-0.5f;
    v[1].vPos.x = float(mapx+mapsize)-0.5f;     v[1].vPos.y = float(mapy)-0.5f;
    v[2].vPos.x = float(mapx+mapsize)-0.5f;     v[2].vPos.y = float(mapy+mapsize)-0.5f;
    v[3].vPos.x = float(mapx)-0.5f;             v[3].vPos.y = float(mapy+mapsize)-0.5f;
    v[0].vPos.z     = v[1].vPos.z     = v[2].vPos.z     = v[3].vPos.z    = 0.1f;
    v[0].fRHW       = v[1].fRHW       = v[2].fRHW       = v[3].fRHW      = 1.0f;
    v[0].dwDiffuse  = v[1].dwDiffuse  = v[2].dwDiffuse  = v[3].dwDiffuse = 0x80008000;
    m_pMiniMapBackgroundVB->Unlock();

    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG2 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG2 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
    m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

    m_pd3dDevice->SetFVF( FVF_TL_DIFFUSE );
    m_pd3dDevice->SetStreamSource( 0, m_pMiniMapBackgroundVB, 0, sizeof(VERTEX_TL_DIFFUSE) );
    m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );

    DWORD dwLineCount = 0;

    // Now set matrices for orthogonal top-down line drawing into mini-map area
    D3DXMATRIX orthproj;
    D3DXMatrixOrthoLH( &orthproj, 8, 8, 1, 2 );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &orthproj );

    D3DXMATRIX orthcam; D3DXMatrixIdentity( &orthcam );
    orthcam.m[3][0] = -m_vCameraPos.x;
    orthcam.m[3][1] = -m_vCameraPos.z;
    orthcam.m[0][0] = orthcam.m[2][1] = orthcam.m[1][2] = 1;
    orthcam.m[1][1] = orthcam.m[2][2] = 0;
    D3DXMATRIX rot; D3DXMatrixRotationZ( &rot, -m_fCameraYaw );
    orthcam = orthcam * rot;
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &orthcam );

    // Compute range of cells we're interested in
    DWORD centrecellx = DWORD(m_vCameraPos.x);
    DWORD centrecelly = DWORD(m_vCameraPos.z);
    DWORD mazewidth = m_pMazeClient->m_Maze.GetWidth();
    DWORD mazeheight = m_pMazeClient->m_Maze.GetHeight();
    DWORD minx = (centrecellx < 6) ? 0 : centrecellx - 6;
    DWORD miny = (centrecelly < 6) ? 0 : centrecelly - 6;
    DWORD maxx = centrecellx+6 > mazewidth - 1  ? mazewidth - 1 : centrecellx+5;
    DWORD maxy = centrecelly+6 > mazeheight - 1 ? mazeheight - 1 : centrecelly+5;

    // Change viewport to clip to the map region
    D3DVIEWPORT9 oldvp;
    oldvp.X         = 0;
    oldvp.Y         = 0;
    oldvp.Width     = m_d3dsdBackBuffer.Width;
    oldvp.Height    = m_d3dsdBackBuffer.Height;
    oldvp.MinZ      = 0.0f;
    oldvp.MaxZ      = 1.0f;

    D3DVIEWPORT9 mapvp;
    mapvp.X       = mapx; 
    mapvp.Y       = mapy;
    mapvp.Width   = mapsize;
    mapvp.Height  = mapsize;
    mapvp.MinZ    = 0.0f; 
    mapvp.MaxZ    = 1.0f;
    m_pd3dDevice->SetViewport( &mapvp );

    // Lock mini-map VB
    m_pMiniMapVB->Lock( 0, 0, (void**)&verts, D3DLOCK_DISCARD );

    // Loop through cells
    origin = D3DXVECTOR3( float(minx),1,float(miny) );
    for( x = minx; x <= maxx; x++ )
    {
        pos = origin;
        for( y = miny; y <= maxy; y++ )
        {
            // Grab wall bitmask
            BYTE cell = m_pMazeClient->m_Maze.GetCell(x,y);

            // Check for north wall
            if( cell & MAZE_WALL_NORTH )
            {
                verts->vPos = pos;                  verts->dwDiffuse = 0xffffff; verts++;
                verts->vPos = pos; verts->vPos.x++; verts->dwDiffuse = 0xffffff; verts++;
                dwLineCount++;
            }

            // Check for west wall
            if( cell & MAZE_WALL_WEST )
            {
                verts->vPos = pos;                  verts->dwDiffuse = 0xffffff; verts++;
                verts->vPos = pos; verts->vPos.z++; verts->dwDiffuse = 0xffffff; verts++;
                dwLineCount++;
            }

            if( y==maxy )
            {
                // Check for north wall
                if( cell & MAZE_WALL_SOUTH )
                {
                    verts->vPos = pos; verts->vPos.z++;                  verts->dwDiffuse = 0xffffff; verts++;
                    verts->vPos = pos; verts->vPos.z++; verts->vPos.x++; verts->dwDiffuse = 0xffffff; verts++;
                    dwLineCount++;
                }
            }

            if( x==maxx )
            {
                // Check for west wall
                if( cell & MAZE_WALL_EAST )
                {
                    verts->vPos = pos; verts->vPos.x++;                  verts->dwDiffuse = 0xffffff; verts++;
                    verts->vPos = pos; verts->vPos.x++; verts->vPos.z++; verts->dwDiffuse = 0xffffff; verts++;
                    dwLineCount++;
                }
            }

            pos.z += 1;
        }

        origin.x += 1;
    }

    // Unlock VB and submit for rendering
    m_pMiniMapVB->Unlock();
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );

    m_pd3dDevice->SetFVF( FVF_DIFFUSE );
    m_pd3dDevice->SetStreamSource( 0, m_pMiniMapVB, 0, sizeof(VERTEX_DIFFUSE) );
    m_pd3dDevice->DrawPrimitive( D3DPT_LINELIST, 0, dwLineCount );

    // Draw marker for camera
    D3DXMatrixRotationY( &rot, -m_fCameraYaw );
    rot.m[3][0] = m_vCameraPos.x; rot.m[3][2] = m_vCameraPos.z;
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &rot );

    m_pMiniMapVB->Lock( 0, 0, (void**)&verts, D3DLOCK_DISCARD );
    verts[0].vPos.y     = verts[1].vPos.y       = verts[2].vPos.y       = verts[3].vPos.y       = 1.0f;
    verts[0].dwDiffuse  = verts[1].dwDiffuse    = verts[2].dwDiffuse    = verts[3].dwDiffuse    = 0xb01010;
    verts[0].vPos.x =  0.0f; verts[0].vPos.z =  0.4f;
    verts[1].vPos.x =  0.2f; verts[1].vPos.z =  0.0f;
    verts[2].vPos.x =  0.0f; verts[2].vPos.z = -0.2f;
    verts[3].vPos.x = -0.2f; verts[3].vPos.z =  0.0f;
    m_pMiniMapVB->Unlock();

    m_pd3dDevice->SetFVF( FVF_DIFFUSE );
    m_pd3dDevice->SetStreamSource( 0, m_pMiniMapVB, 0, sizeof(VERTEX_DIFFUSE) );
    m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

    // Draw things in mini-map
    m_pMazeClient->LockWorld();

    m_pMiniMapVB->Lock( 0, 0, (void**)&verts, D3DLOCK_DISCARD );
    verts[0].vPos.y     = verts[1].vPos.y       = verts[2].vPos.y       = verts[3].vPos.y       = 1.0f;
    verts[0].dwDiffuse  = verts[1].dwDiffuse    = verts[2].dwDiffuse    = verts[3].dwDiffuse    = 0x00FFFF00;
    verts[0].vPos.x =  0.0f; verts[0].vPos.z =  0.4f;
    verts[1].vPos.x =  0.2f; verts[1].vPos.z =  0.0f;
    verts[2].vPos.x =  0.0f; verts[2].vPos.z = -0.2f;
    verts[3].vPos.x = -0.2f; verts[3].vPos.z =  0.0f;
    m_pMiniMapVB->Unlock();

    m_pd3dDevice->SetFVF( FVF_DIFFUSE );
    m_pd3dDevice->SetStreamSource( 0, m_pMiniMapVB, 0, sizeof(VERTEX_DIFFUSE) );

    for( x = minx; x <= maxx; x++ )
    {
        for( y = miny; y <= maxy; y++ )
        {
            PlayerObject* pPlayerObject = m_pMazeClient->GetFirstPlayerObjectInCell( x, y );
            while( pPlayerObject )
            {
                D3DXMatrixTranslation( &matTrans, pPlayerObject->vPos.x, pPlayerObject->vPos.y, pPlayerObject->vPos.z );
                D3DXMatrixRotationYawPitchRoll( &matRot, -AngleToFloat(pPlayerObject->aYaw), 0.0f, 0.0f );
                matWorld = matRot * matTrans;
                m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
                m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

                pPlayerObject = pPlayerObject->pNext;
            }

            pos.z += 1;
        }
    }
    m_pMazeClient->UnlockWorld();

    D3DXMatrixIdentity( &rot );
    m_pd3dDevice->SetTransform( D3DTS_WORLD, &rot );

    // Re-enable z-buffering and reset viewport and matrices
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_ZWRITEENABLE, TRUE );
    m_pd3dDevice->SetViewport( &oldvp );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &m_Projection );
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &m_Camera );

}




//-----------------------------------------------------------------------------
// Name: DrawIndicators
// Desc: Output in-game statistics
//-----------------------------------------------------------------------------
void CD3DGraphics::DrawIndicators()
{
    VERTEX_TL_TEXTURED* v;   

    float xscale = float(m_d3dsdBackBuffer.Width)/640.0f;
    float yscale = float(m_d3dsdBackBuffer.Height)/480.0f;

    // Draw network/local icon and player icon (if connected)
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP, D3DTOP_SELECTARG1 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAOP, D3DTOP_SELECTARG1 );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_ALPHAARG1, D3DTA_TEXTURE );

    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_SRCBLEND, D3DBLEND_SRCALPHA );
    m_pd3dDevice->SetRenderState( D3DRS_DESTBLEND, D3DBLEND_INVSRCALPHA );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_CLAMP );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_CLAMP );

    m_pIndicatorVB->Lock( 0, 0, (void**)&v, 0 );
    v[0].dwSpecular = v[1].dwSpecular = v[2].dwSpecular = v[3].dwSpecular = 0;
    v[0].vPos.z     = v[1].vPos.z     = v[2].vPos.z     = v[3].vPos.z     = 0.0f;
    v[0].fRHW       = v[1].fRHW       = v[2].fRHW       = v[3].fRHW       = 1.0f;
    v[0].dwDiffuse  = v[1].dwDiffuse  = v[2].dwDiffuse  = v[3].dwDiffuse  = 0xFFFFFFFF;
    v[0].fU = 0; v[0].fV = 0;
    v[1].fU = 1; v[1].fV = 0;
    v[2].fU = 1; v[2].fV = 1;
    v[3].fU = 0; v[3].fV = 1;
    v[0].vPos.x = (xscale*16)-0.5f; v[0].vPos.y = ((480-68)*yscale)-0.5f;
    v[1].vPos.x = (xscale*48)-0.5f; v[1].vPos.y = ((480-68)*yscale)-0.5f;
    v[2].vPos.x = (xscale*48)-0.5f; v[2].vPos.y = ((480-36)*yscale)-0.5f;
    v[3].vPos.x = (xscale*16)-0.5f; v[3].vPos.y = ((480-36)*yscale)-0.5f;
    m_pIndicatorVB->Unlock();

    if( m_pDP8Client->IsSessionLost() )
        m_pd3dDevice->SetTexture( 0, m_pLocalIconTexture );
    else
        m_pd3dDevice->SetTexture( 0, m_pNetIconTexture );

    m_pd3dDevice->SetFVF( FVF_TL_TEXTURED );
    m_pd3dDevice->SetStreamSource( 0, m_pIndicatorVB, 0, sizeof(VERTEX_TL_TEXTURED) );
    m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 2 );

    if( FALSE == m_pDP8Client->IsSessionLost() )
    {
        DWORD dwNumPlayers, dwNumNearbyPlayers;
        m_pMazeClient->GetPlayerStats( &dwNumPlayers, &dwNumNearbyPlayers );

        // Show frame rate
        POINT ptStart  = { (long)(xscale*60.0f), (long)((480-95)*yscale) };
        TCHAR strText[MAX_PATH];
        wsprintf( strText, TEXT("       Players: %4d"), dwNumPlayers );
        m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y, D3DCOLOR_ARGB(255,255,255,0), strText );
        wsprintf( strText, TEXT("Nearby Players: %4d"), dwNumNearbyPlayers );
        m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y+20, D3DCOLOR_ARGB(255,255,255,0), strText );
        wsprintf( strText, TEXT("          Ping: %4d ms"), m_pMazeClient->GetRoundTripLatencyMS() );
        m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y+40, D3DCOLOR_ARGB(255,255,255,0), strText );
        wsprintf( strText, TEXT("     Data Rate: %4d Bps"), m_pMazeClient->GetThroughputBPS() );
        m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y+60, D3DCOLOR_ARGB(255,255,255,0), strText );
    }
    else
    {
        if( m_pMazeApp->IsOutOfDateClient() )
        {
            POINT ptStart  = { (long)(xscale*60.0f), (long)((480-75)*yscale) };
            TCHAR strText[MAX_PATH];
            wsprintf( strText, TEXT("Client is out of date") );
            m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y, D3DCOLOR_ARGB(255,255,255,0), strText );
            wsprintf( strText, TEXT("Please get update at") );
            m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y+20, D3DCOLOR_ARGB(255,255,255,0), strText );
            wsprintf( strText, TEXT("http://msdn.microsoft.com/directx/") );
            m_pFont->DrawText( (float)ptStart.x, (float)ptStart.y+40, D3DCOLOR_ARGB(255,255,255,0), strText );
        }
    }

    m_pd3dDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSU, D3DTADDRESS_WRAP );
    m_pd3dDevice->SetSamplerState( 0, D3DSAMP_ADDRESSV, D3DTADDRESS_WRAP );
}




//-----------------------------------------------------------------------------
// Name: InvalidateDeviceObjects
// Desc: Invalidate device objects
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::InvalidateDeviceObjects()
{ 
    m_pFont->InvalidateDeviceObjects();
    m_pStatsFont->InvalidateDeviceObjects();
    m_pPlayerMesh->InvalidateDeviceObjects();

    m_SmartVB.Uninit();

    SAFE_RELEASE( m_pMiniMapVB );

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: DeleteDeviceObjects
// Desc: Delete device objects
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::DeleteDeviceObjects()
{ 
    m_pFont->DeleteDeviceObjects();
    m_pStatsFont->DeleteDeviceObjects();

    SAFE_RELEASE( m_pSphere );
    if( m_pPlayerMesh )
        m_pPlayerMesh->Destroy();
   
    SAFE_RELEASE( m_pMiniMapBackgroundVB );
    SAFE_RELEASE( m_pIndicatorVB );
    m_SmartVB.Uninit();

    SAFE_RELEASE( m_pWallTexture );
    SAFE_RELEASE( m_pFloorTexture );
    SAFE_RELEASE( m_pCeilingTexture );
    SAFE_RELEASE( m_pNetIconTexture );
    SAFE_RELEASE( m_pLocalIconTexture );

    return S_OK; 
}




//-----------------------------------------------------------------------------
// Name: FinalCleanup
// Desc: Release resources
//-----------------------------------------------------------------------------
HRESULT CD3DGraphics::FinalCleanup()
{ 
    SAFE_DELETE( m_pFont );
    SAFE_DELETE( m_pStatsFont );
    SAFE_DELETE( m_pPlayerMesh );

    return S_OK; 
}



