//-----------------------------------------------------------------------------
// File: Fog.cpp
//
// Desc: Example code showing how to do fog in D3D
//
// Copyright (c) 1997-2000 Microsoft Corporation. All rights reserved.
//-----------------------------------------------------------------------------
#define STRICT
#include "stdafx.h"
#include <tchar.h>
#include <math.h>
#include <D3DX8.h>
#include "resource.h"
#include "D3DApp.h"
#include "D3DUtil.h"
#include "DXUtil.h"
#include "fog.h"




//-----------------------------------------------------------------------------
// Structures and Macros
//-----------------------------------------------------------------------------
inline DWORD FtoDW( FLOAT f ) { return *((DWORD*)&f); }

struct FOGVERTEX
{
        D3DXVECTOR3 p;
        D3DXVECTOR3 n;
        FLOAT       tu, tv;
};

#define D3DFVF_FOGVERTEX (D3DFVF_XYZ|D3DFVF_NORMAL|D3DFVF_TEX1)

#define FAR_PLANE (150.0f)
#define NEAR_PLANE (1.0f)




//-----------------------------------------------------------------------------
// Application globals
//-----------------------------------------------------------------------------
TCHAR*        g_strAppTitle       = _T( "MFCFog: D3D Fog Sample Using MFC" );
CApp          g_App;
CAppForm*     g_AppFormView = NULL;




//-----------------------------------------------------------------------------
// The MFC macros are all listed here
//-----------------------------------------------------------------------------
IMPLEMENT_DYNCREATE( CAppDoc,      CDocument )
IMPLEMENT_DYNCREATE( CAppFrameWnd, CFrameWnd )
IMPLEMENT_DYNCREATE( CAppForm,     CFormView )




BEGIN_MESSAGE_MAP( CApp, CWinApp )
    //{{AFX_MSG_MAP(CApp)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()




BEGIN_MESSAGE_MAP( CAppForm, CFormView )
    //{{AFX_MSG_MAP(CAppForm)
    ON_COMMAND(    IDC_VIEWFULLSCREEN, OnToggleFullScreen )
    ON_COMMAND(    IDM_CHANGEDEVICE,   OnChangeDevice )
    ON_WM_HSCROLL()
    ON_BN_CLICKED( IDC_FOGCOLOR,       OnFogColor )
    ON_BN_CLICKED( IDC_RANGEBASEDFOG,  OnRangeBasedFog )
    ON_BN_CLICKED( IDC_VERTEXFOG,      OnVertexFog )
    ON_BN_CLICKED( IDC_TABLEFOG,       OnTableFog )
    ON_BN_CLICKED( IDC_LINEARFOGMODE,  OnFogMode )
    ON_BN_CLICKED( IDC_LORESTERRAIN,   OnTerrainResolution)
    ON_BN_CLICKED( IDM_CHANGEDEVICE,   OnChangeDevice )
    ON_BN_CLICKED( IDC_EXPFOGMODE,     OnFogMode )
    ON_BN_CLICKED( IDC_EXP2FOGMODE,    OnFogMode )
    ON_BN_CLICKED( IDC_HIRESTERRAIN,   OnTerrainResolution)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP()




//-----------------------------------------------------------------------------
// Name: InitInstance()
// Desc: This is the main entry point for the application. The MFC window stuff
//       is initialized here. See also the main initialization routine for the
//       CAppForm class, which is called indirectly from here.
//-----------------------------------------------------------------------------
BOOL CApp::InitInstance()
{
    // Asscociate the MFC app with the frame window and doc/view classes
    AddDocTemplate( new CSingleDocTemplate( IDR_MAINFRAME,
                                            RUNTIME_CLASS(CAppDoc),
                                            RUNTIME_CLASS(CAppFrameWnd),
                                            RUNTIME_CLASS(CAppForm) ) );

    // Dispatch commands specified on the command line (req'd by MFC). This
    // also initializes the the CAppDoc, CAppFrameWnd, and CAppForm classes.
    CCommandLineInfo cmdInfo;
    ParseCommandLine( cmdInfo );
    if( !ProcessShellCommand( cmdInfo ) )
        return FALSE;

    if( !g_AppFormView->IsReady() )
        return FALSE;

    // The formview window has been initialized, so show and update it.
    m_pMainWnd->MoveWindow( 0, 0, 590, 370, TRUE );
    m_pMainWnd->SetWindowText( g_strAppTitle );
    m_pMainWnd->UpdateWindow();

    return TRUE;
}




//-----------------------------------------------------------------------------
// Name: OnIdle()
// Desc: Uses idle time to render the 3D scene.
//-----------------------------------------------------------------------------
BOOL CApp::OnIdle( LONG )
{
    // Do not render if the app is minimized
    if( m_pMainWnd->IsIconic() )
        return FALSE;

    TCHAR strStatsPrev[200];

    lstrcpy(strStatsPrev, g_AppFormView->PstrFrameStats());

    // Update and render a frame
    if( g_AppFormView->IsReady() )
    {
        g_AppFormView->CheckForLostFullscreen();
        g_AppFormView->RenderScene();
        if (lstrcmp(strStatsPrev, g_AppFormView->PstrFrameStats()) != 0)
            g_AppFormView->GetDlgItem(IDC_FPS_TEXT)->SetWindowText(g_AppFormView->PstrFrameStats());
    }

    // Keep requesting more idle time
    return TRUE;
}




//-----------------------------------------------------------------------------
// Name: PreCreateWindow()
// Desc: Change the window style (so it cannot maximize or be sized) before
//       the main frame window is created.
//-----------------------------------------------------------------------------
BOOL CAppFrameWnd::PreCreateWindow( CREATESTRUCT& cs )
{
    cs.style = WS_OVERLAPPED|WS_CAPTION|WS_SYSMENU|WS_MINIMIZEBOX;

    return CFrameWnd::PreCreateWindow( cs );
}




//-----------------------------------------------------------------------------
// Name: CAppForm()
// Desc: Constructor for the dialog resource form
//-----------------------------------------------------------------------------
CAppForm::CAppForm()
         :CFormView( IDD_FORMVIEW )
{
    g_AppFormView          = this;
    m_bHiResTerrain        = FALSE;
    m_bHiResTerrainOld     = FALSE;
    m_dwFogColor           = 0x00b5b5ff;
    m_dwFogMode            = D3DFOG_LINEAR;
    m_bCanDoTableFog       = FALSE;
    m_bCanDoVertexFog      = FALSE;
    m_bCanDoWFog           = FALSE;
    m_bDeviceUsesWFog      = FALSE;
    m_bRangeBasedFog       = FALSE;
    m_bUsingTableFog       = FALSE;
    m_fFogStartSlider      = 0.0f;
    m_fFogEndSlider        = 1.0f;
    m_fFogStartValue       = 0.0f;
    m_fFogEndValue         = 1.0f;
    m_fFogDensity          = 0.0f;
    m_hwndRenderWindow     = NULL;
    m_hwndRenderFullScreen = NULL;
    m_pFloorTexture        = NULL;
    m_pTerrainVB           = NULL;
    m_dwNumTerrainVertices = 0L;
    m_pColumnVB            = NULL;
    m_dwNumColumnVertices  = 0L;

    // Override some CD3DApplication defaults:
    m_bUseDepthBuffer      = TRUE;
}




//-----------------------------------------------------------------------------
// Name: ~CAppForm()
// Desc: Destructor for the dialog resource form. Shuts down the app
//-----------------------------------------------------------------------------
CAppForm::~CAppForm()
{
    Cleanup3DEnvironment();
}




//-----------------------------------------------------------------------------
// Name: OnToggleFullScreen()
// Desc: Called when user toggles the fullscreen mode
//-----------------------------------------------------------------------------
void CAppForm::OnToggleFullScreen()
{
    ToggleFullscreen();
}




//-----------------------------------------------------------------------------
// Name: OnChangeDevice()
// Desc: Use hit the "Change Device.." button. Display the dialog for the user
//       to select a new device/mode, and call Change3DEnvironment to
//       use the new device/mode.
//-----------------------------------------------------------------------------
VOID CAppForm::OnChangeDevice()
{
    UserSelectNewDevice();

    // Update UI, and device's fog parameters
    UpdateUIForDeviceCapabilites();
    SetFogParameters();
}




//-----------------------------------------------------------------------------
// Name: AdjustWindowForChange()
// Desc: Adjusts the window properties for windowed or fullscreen mode
//-----------------------------------------------------------------------------
HRESULT CAppForm::AdjustWindowForChange()
{
    if( m_bWindowed )
    {
        ::ShowWindow( m_hwndRenderFullScreen, SW_HIDE );
        CD3DApplication::m_hWnd = m_hwndRenderWindow;
    }
    else
    {
        if( ::IsIconic( m_hwndRenderFullScreen ) )
            ::ShowWindow( m_hwndRenderFullScreen, SW_RESTORE );
        ::ShowWindow( m_hwndRenderFullScreen, SW_SHOW );
        CD3DApplication::m_hWnd = m_hwndRenderFullScreen;
    }
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: FullScreenWndProc()
// Desc: The WndProc funtion used when the app is in fullscreen mode. This is
//       needed simply to trap the ESC key.
//-----------------------------------------------------------------------------
LRESULT CALLBACK FullScreenWndProc( HWND hWnd, UINT msg, WPARAM wParam,
                                    LPARAM lParam )
{
    if( msg == WM_CLOSE )
    {
        // User wants to exit, so go back to windowed mode and exit for real
        g_AppFormView->OnToggleFullScreen();
        g_App.GetMainWnd()->PostMessage( WM_CLOSE, 0, 0 );
    }

    else if( msg == WM_SETCURSOR )
    {
        SetCursor( NULL );
    }

    else if( msg == WM_KEYUP && wParam == VK_ESCAPE )
    {
        // User wants to leave fullscreen mode
        g_AppFormView->OnToggleFullScreen();
    }
    return DefWindowProc( hWnd, msg, wParam, lParam );
}




//-----------------------------------------------------------------------------
// Name: CheckForLostFullscreen()
// Desc: If fullscreen and device was lost (probably due to alt-tab), 
//       automatically switch to windowed mode
//-----------------------------------------------------------------------------
HRESULT CAppForm::CheckForLostFullscreen()
{
    HRESULT hr;

    if( m_bWindowed )
        return S_OK;

    if( FAILED( hr = m_pd3dDevice->TestCooperativeLevel() ) )
        ForceWindowed();

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: OnTerrainResolution()
// Desc: Called when the user selects the terrain resolution
//-----------------------------------------------------------------------------
VOID CAppForm::OnTerrainResolution()
{
    m_bHiResTerrain = ((CButton*)GetDlgItem(IDC_HIRESTERRAIN))->GetCheck();

    SetFogParameters();
}




//-----------------------------------------------------------------------------
// Name: OnHScroll()
// Desc: Called when the user moves any scroll bar. Check which scrollbar was
//       moved, and extract the appropiate value to a global variable.
//-----------------------------------------------------------------------------
void CAppForm::OnHScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
    // Get the new fog parameters
    m_fFogStartSlider = ((CSliderCtrl*)GetDlgItem(IDC_FOGSTART_SLIDER))->GetPos()/100.0f;
    m_fFogEndSlider   = ((CSliderCtrl*)GetDlgItem(IDC_FOGEND_SLIDER))->GetPos()/100.0f;
    m_fFogDensity     = ((CSliderCtrl*)GetDlgItem(IDC_FOGDENSITY_SLIDER))->GetPos()/100.0f;

    if( m_fFogEndSlider < m_fFogStartSlider )
    {
        ((CSliderCtrl*)GetDlgItem(IDC_FOGEND_SLIDER))->SetPos((INT)(m_fFogStartSlider*100.0f));
        m_fFogEndSlider = m_fFogStartSlider;
    }
    
    SetFogParameters();

    CFormView::OnHScroll(nSBCode, nPos, pScrollBar);
}




//-----------------------------------------------------------------------------
// Name: OnFogColor()
// Desc: Called when the user hits the "fog color..." button. Display a color
//       selection dialog box, and set the global fog color variable.
//-----------------------------------------------------------------------------
void CAppForm::OnFogColor()
{
    CColorDialog dlg;

    if( IDOK == dlg.DoModal() )
    {
        m_dwFogColor = ((((DWORD)dlg.GetColor())&0x000000ff)<<16) +
                       ((((DWORD)dlg.GetColor())&0x0000ff00)) +
                       ((((DWORD)dlg.GetColor())&0x00ff0000)>>16);
        SetFogParameters();
    }
}




//-----------------------------------------------------------------------------
// Name: OnRangeBasedFog()
// Desc: Toggle the boolean variable for whether RangeBasedFog is enabled.
//-----------------------------------------------------------------------------
void CAppForm::OnRangeBasedFog()
{
    m_bRangeBasedFog = ((CButton*)GetDlgItem(IDC_RANGEBASEDFOG))->GetCheck();

    SetFogParameters();
}




//-----------------------------------------------------------------------------
// Name: OnVertexFog()
// Desc: User selected vertex fog. Upadte the global variables as appropiate.
//-----------------------------------------------------------------------------
void CAppForm::OnVertexFog()
{
    // Note: We always assume range fog is available if doing vertex fog
    GetDlgItem(IDC_RANGEBASEDFOG)->EnableWindow(TRUE);

    GetDlgItem(IDC_LINEARFOGMODE)->EnableWindow(TRUE);
    GetDlgItem(IDC_EXPFOGMODE)->EnableWindow(FALSE);
    GetDlgItem(IDC_EXP2FOGMODE)->EnableWindow(FALSE);
    ((CButton*)GetDlgItem(IDC_LINEARFOGMODE))->SetCheck(TRUE);
    ((CButton*)GetDlgItem(IDC_EXPFOGMODE))->SetCheck(FALSE);
    ((CButton*)GetDlgItem(IDC_EXP2FOGMODE))->SetCheck(FALSE);

    GetDlgItem(IDC_FOGSTARTMIN_TEXT)->SetWindowText( _T("near (1.0)") );
    GetDlgItem(IDC_FOGSTARTMAX_TEXT)->SetWindowText( _T("far (150.0)") );
    GetDlgItem(IDC_FOGENDMIN_TEXT)->SetWindowText( _T("near (1.0)") );
    GetDlgItem(IDC_FOGENDMAX_TEXT)->SetWindowText( _T("far (150.0)") );

    m_bUsingTableFog = FALSE;
    OnFogMode();
}




//-----------------------------------------------------------------------------
// Name: OnTableFog()
// Desc: User selected table fog. Upadte the global variables as appropiate.
//-----------------------------------------------------------------------------
void CAppForm::OnTableFog()
{
    // Note: We only assume range fog is available if doing vertex fog
    GetDlgItem(IDC_RANGEBASEDFOG)->EnableWindow(FALSE);
    ((CButton*)GetDlgItem(IDC_RANGEBASEDFOG))->SetCheck(FALSE);

    GetDlgItem(IDC_LINEARFOGMODE)->EnableWindow(TRUE);
    GetDlgItem(IDC_EXPFOGMODE)->EnableWindow(TRUE);
    GetDlgItem(IDC_EXP2FOGMODE)->EnableWindow(TRUE);

    if( m_bCanDoWFog )
    {
        GetDlgItem(IDC_FOGSTARTMIN_TEXT)->SetWindowText( _T("near (1.0)") );
        GetDlgItem(IDC_FOGSTARTMAX_TEXT)->SetWindowText( _T("far (150.0)") );
        GetDlgItem(IDC_FOGENDMIN_TEXT)->SetWindowText( _T("near (1.0)") );
        GetDlgItem(IDC_FOGENDMAX_TEXT)->SetWindowText( _T("far (150.0)") );
    }
    else
    {
        GetDlgItem(IDC_FOGSTARTMIN_TEXT)->SetWindowText( _T("near (0.0)") );
        GetDlgItem(IDC_FOGSTARTMAX_TEXT)->SetWindowText( _T("far (1.0)") );
        GetDlgItem(IDC_FOGENDMIN_TEXT)->SetWindowText( _T("near (0.0)") );
        GetDlgItem(IDC_FOGENDMAX_TEXT)->SetWindowText( _T("far (1.0)") );
    }

    m_bUsingTableFog = TRUE;
    OnFogMode();
}




//-----------------------------------------------------------------------------
// Name: OnFogMode()
// Desc: User changed the fog mode. Update the UI and global variables, as many
//       controls are mutually exclusive.
//-----------------------------------------------------------------------------
void CAppForm::OnFogMode()
{
    if( ((CButton*)GetDlgItem(IDC_LINEARFOGMODE))->GetCheck() )
    {
        m_dwFogMode = D3DFOG_LINEAR;

        GetDlgItem(IDC_FOGSTART_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGSTARTMIN_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGSTARTMAX_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGSTART_SLIDER)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGEND_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGENDMIN_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGENDMAX_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGEND_SLIDER)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGDENSITY_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGDENSITYMIN_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGDENSITYMAX_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGDENSITY_SLIDER)->EnableWindow(FALSE);
    }
    else
    {
        GetDlgItem(IDC_FOGSTART_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGSTARTMIN_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGSTARTMAX_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGSTART_SLIDER)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGEND_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGENDMIN_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGENDMAX_TEXT)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGEND_SLIDER)->EnableWindow(FALSE);
        GetDlgItem(IDC_FOGDENSITY_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGDENSITYMIN_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGDENSITYMAX_TEXT)->EnableWindow(TRUE);
        GetDlgItem(IDC_FOGDENSITY_SLIDER)->EnableWindow(TRUE);

        if( ((CButton*)GetDlgItem(IDC_EXPFOGMODE))->GetCheck() )
            m_dwFogMode = D3DFOG_EXP;
        if( ((CButton*)GetDlgItem(IDC_EXP2FOGMODE))->GetCheck() )
            m_dwFogMode = D3DFOG_EXP2;
    }

    SetFogParameters();
}




//-----------------------------------------------------------------------------
// Name: UpdateUIForDeviceCapabilites()
// Desc: Whenever we get a new device, call this function to enable/disable the
//       appropiate UI controls to match the device's capabilities.
//-----------------------------------------------------------------------------
VOID CAppForm::UpdateUIForDeviceCapabilites()
{
    // Check the capabilities of the device
    DWORD dwCaps = m_d3dCaps.RasterCaps;
    m_bCanDoTableFog  = (dwCaps&D3DPRASTERCAPS_FOGTABLE) &&
                        ((dwCaps&D3DPRASTERCAPS_ZFOG) || (dwCaps&D3DPRASTERCAPS_WFOG))    
                                                          ? TRUE : FALSE;
    m_bCanDoVertexFog = (dwCaps&D3DPRASTERCAPS_FOGVERTEX) ? TRUE : FALSE;
    m_bCanDoWFog      = (dwCaps&D3DPRASTERCAPS_WFOG)      ? TRUE : FALSE;

    // Update the UI checkbox states
    ((CButton*)GetDlgItem(IDC_TABLEFOG))->EnableWindow(m_bCanDoTableFog);
    ((CButton*)GetDlgItem(IDC_VERTEXFOG))->EnableWindow(m_bCanDoVertexFog);

    if( m_bCanDoWFog )
        GetDlgItem(IDC_USINGWFOG)->SetWindowText( _T("Device using W-fog") );
    else
        GetDlgItem(IDC_USINGWFOG)->SetWindowText( _T("Device using Z-fog") );

    if( m_bUsingTableFog && m_bCanDoTableFog )
    {
        ((CButton*)GetDlgItem(IDC_VERTEXFOG))->SetCheck(FALSE);
        ((CButton*)GetDlgItem(IDC_TABLEFOG))->SetCheck(TRUE);
    }
    else if( m_bCanDoVertexFog )
    {
        ((CButton*)GetDlgItem(IDC_VERTEXFOG))->SetCheck(TRUE);
        ((CButton*)GetDlgItem(IDC_TABLEFOG))->SetCheck(FALSE);
    }
    else
    {
        ((CButton*)GetDlgItem(IDC_VERTEXFOG))->SetCheck(FALSE);
        ((CButton*)GetDlgItem(IDC_TABLEFOG))->SetCheck(m_bCanDoTableFog);
    }

    // Set up table or vertex mode, as appropiate
    if( ((CButton*)GetDlgItem(IDC_TABLEFOG))->GetCheck() )
        OnTableFog();
    if( ((CButton*)GetDlgItem(IDC_VERTEXFOG))->GetCheck() )
        OnVertexFog();
}




//-----------------------------------------------------------------------------
// Name: OnInitialUpdate()
// Desc: When the AppForm object is created, this function is called to
//       initialize it. Here we getting access ptrs to some of the controls,
//       and setting the initial state of some of them as well.
//-----------------------------------------------------------------------------
VOID CAppForm::OnInitialUpdate()
{
    // Update the UI
    CFormView::OnInitialUpdate();
    ((CSliderCtrl*)GetDlgItem( IDC_FOGSTART_SLIDER ))->SetRange(0,100,TRUE);
    ((CSliderCtrl*)GetDlgItem( IDC_FOGSTART_SLIDER ))->SetPos(0);
    ((CSliderCtrl*)GetDlgItem( IDC_FOGEND_SLIDER ))->SetRange(0,100,TRUE);
    ((CSliderCtrl*)GetDlgItem( IDC_FOGEND_SLIDER ))->SetPos(100);
    ((CSliderCtrl*)GetDlgItem( IDC_FOGDENSITY_SLIDER ))->SetRange(0,100,TRUE);
    ((CSliderCtrl*)GetDlgItem( IDC_FOGDENSITY_SLIDER ))->SetPos(0);
    ((CButton*)GetDlgItem( IDC_LORESTERRAIN ))->SetCheck(TRUE);
    ((CButton*)GetDlgItem(IDC_VERTEXFOG))->SetCheck(TRUE);

    // Save static reference to the render window
    m_hwndRenderWindow = GetDlgItem(IDC_RENDERVIEW)->GetSafeHwnd();

    // Register a class for a fullscreen window
    WNDCLASS wndClass = { CS_HREDRAW | CS_VREDRAW, FullScreenWndProc, 0, 0, NULL,
                          NULL, NULL, (HBRUSH)GetStockObject(WHITE_BRUSH), NULL,
                          _T("Fullscreen Window") };
    RegisterClass( &wndClass );

    // We create the fullscreen window (not visible) at startup, so it can
    // be the focus window.  The focus window can only be set at CreateDevice
    // time, not in a Reset, so ToggleFullscreen wouldn't work unless we have
    // already set up the fullscreen focus window.
    m_hwndRenderFullScreen = CreateWindow( _T("Fullscreen Window"), NULL,
                                           WS_POPUP, CW_USEDEFAULT,
                                           CW_USEDEFAULT, 100, 100,
                                           GetTopLevelParent()->GetSafeHwnd(), 0L, NULL, 0L );

    // Note that for the MFC samples, the device window and focus window
    // are not the same.
    CD3DApplication::m_hWnd = m_hwndRenderWindow;
    CD3DApplication::m_hWndFocus = m_hwndRenderFullScreen;
    CD3DApplication::Create( AfxGetInstanceHandle() );

    // Update UI, and device's fog parameters
    OnVertexFog();
    UpdateUIForDeviceCapabilites();
    SetFogParameters();
}




//----------------------------------------------------------------------------
// Name: GenerateTerrainDisk()
// Desc: Generates a trianglestrip for a disk
//----------------------------------------------------------------------------
HRESULT CAppForm::GenerateTerrainDisk( LPDIRECT3DDEVICE8 pd3dDevice, DWORD dwNumSegments,
                                       FLOAT fScale )
{
    HRESULT hr;

    m_dwNumTerrainVertices = 2 * dwNumSegments * (dwNumSegments);

    // Destroy the old vertex buffer, if any
    SAFE_RELEASE( m_pTerrainVB );

    // Create a vertex buffer
    hr = pd3dDevice->CreateVertexBuffer( m_dwNumTerrainVertices*sizeof(FOGVERTEX),
                                         D3DUSAGE_WRITEONLY, D3DFVF_FOGVERTEX,
                                         D3DPOOL_MANAGED, &m_pTerrainVB );
    if( FAILED(hr) )
        return hr;

    FOGVERTEX* pVertices = NULL;
    hr = m_pTerrainVB->Lock( 0, m_dwNumTerrainVertices*sizeof(FOGVERTEX),
                             (BYTE**)&pVertices, 0 );
    if( FAILED(hr) )
        return hr;

    // Generate a spiralized trianglestrip
    for( DWORD ring = 0; ring < dwNumSegments; ring++ )
    {
        for( DWORD seg=0; seg < dwNumSegments; seg++ )
        {
            FLOAT fTheta = (seg*2*D3DX_PI) / dwNumSegments;
            FLOAT r0     = (ring + fTheta/(2*D3DX_PI))*fScale/dwNumSegments;
            FLOAT r1     = r0 + fScale/dwNumSegments;

            FLOAT x   = (FLOAT)sin( fTheta );
            FLOAT z   = (FLOAT)cos( fTheta );

            FLOAT y0  =  (FLOAT)sin(r0*z*z+r0*x*x);
            FLOAT nx0 = -(FLOAT)cos(r0*z*z+r0*x*x)*r0*2*x;
            FLOAT ny0 = 1.0f;
            FLOAT nz0 = -(FLOAT)cos(r0*z*z+r0*x*x)*r0*2*z;

            FLOAT y1  =  (FLOAT)sin(r1*z*z+r1*x*x);
            FLOAT nx1 = -(FLOAT)cos(r1*z*z+r1*x*x)*r1*2*x;
            FLOAT ny1 = 1.0f;
            FLOAT nz1 = -(FLOAT)cos(r1*z*z+r1*x*x)*r1*2*z;

            // Add two vertices to the strip at each step
            pVertices->p.x = r0*x;
            pVertices->p.y = y0;
            pVertices->p.z = r0*z;
            pVertices->n.x = nx0;
            pVertices->n.y = ny0;
            pVertices->n.z = nz0;
            pVertices->tu  = (r0*x)/fScale;
            pVertices->tv  = (r0*z)/fScale;
            pVertices++;

            pVertices->p.x = r1*x;
            pVertices->p.y = y1;
            pVertices->p.z = r1*z;
            pVertices->n.x = nx1;
            pVertices->n.y = ny1;
            pVertices->n.z = nz1;
            pVertices->tu  = (r1*x)/fScale;
            pVertices->tv  = (r1*z)/fScale;
            pVertices++;
        }
    }

    m_pTerrainVB->Unlock();

    return S_OK;
}




//----------------------------------------------------------------------------
// Name: GenerateColumn()
// Desc: Generates a trianglestrip for a column
//----------------------------------------------------------------------------
HRESULT CAppForm::GenerateColumn( LPDIRECT3DDEVICE8 pd3dDevice, DWORD dwNumSegments,
                                  FLOAT fRadius, FLOAT fHeight )
{
    HRESULT hr;

    m_dwNumColumnVertices = 2 * (dwNumSegments+1);

    // Destroy the old vertex buffer, if any
    SAFE_RELEASE( m_pColumnVB );

    // Create a vertex buffer
    hr = pd3dDevice->CreateVertexBuffer( m_dwNumColumnVertices*sizeof(FOGVERTEX),
                                         D3DUSAGE_WRITEONLY, D3DFVF_FOGVERTEX,
                                         D3DPOOL_MANAGED, &m_pColumnVB );
    if( FAILED(hr) )
        return hr;

    FOGVERTEX* pVertices = NULL;
    hr = m_pColumnVB->Lock( 0, m_dwNumColumnVertices*sizeof(FOGVERTEX),
                             (BYTE**)(&pVertices), 0 );
    if( FAILED(hr) )
        return hr;

    // Generate a trianglestrip
    for( DWORD seg=0; seg<=dwNumSegments; seg++ )
    {
        FLOAT fTheta = (2*D3DX_PI*seg)/dwNumSegments;
        FLOAT nx     = (FLOAT)sin(fTheta);
        FLOAT nz     = (FLOAT)cos(fTheta);
        FLOAT r      = fRadius;
        FLOAT u      = (1.0f*seg)/dwNumSegments;

        // Add two vertices to the strip at each step
        pVertices->p.x = r*nx;
        pVertices->p.y = fHeight;
        pVertices->p.z = r*nz;
        pVertices->n.x = nx;
        pVertices->n.y = 0;
        pVertices->n.z = nz;
        pVertices->tu  = u;
        pVertices->tv  = 1;
        pVertices++;

        pVertices->p.x = r*nx;
        pVertices->p.y = -1;
        pVertices->p.z = r*nz;
        pVertices->n.x = nx;
        pVertices->n.y = 0;
        pVertices->n.z = nz;
        pVertices->tu  = u;
        pVertices->tv  = 0;
        pVertices++;
    }

    m_pColumnVB->Unlock();

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::OneTimeSceneInit()
// Desc: Called during initial app startup, this function performs all the
//       permanent initialization.
//-----------------------------------------------------------------------------
HRESULT CAppForm::OneTimeSceneInit()
{
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::FrameMove()
// Desc: Called once per frame, the call is the entry point for animating
//       the scene.
//-----------------------------------------------------------------------------
HRESULT CAppForm::FrameMove()
{
    // Move the camera along an ellipse
    D3DXVECTOR3 from( 50*sinf(m_fTime/2), 5.0f, 60*cosf(m_fTime/2) );
    D3DXVECTOR3 at( 50*sinf(m_fTime/2+1.5f), 4.0f, 60*cosf(m_fTime/2+1.5f) );
    D3DXVECTOR3 up( 0.0f, 1.0f, 0.0f );

    D3DXMATRIX matView;
    D3DXMatrixLookAtLH( &matView, &from, &at, &up );
    m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::Render()
// Desc: Called once per frame, the call is the entry point for 3d
//       rendering. This function sets up render states, clears the
//       viewport, and renders the scene.
//-----------------------------------------------------------------------------
HRESULT CAppForm::Render()
{
    // Clear the viewport
    m_pd3dDevice->Clear( 0, NULL, D3DCLEAR_TARGET|D3DCLEAR_ZBUFFER,
                       m_dwFogColor, 1.0f, 0L );

    m_pd3dDevice->SetRenderState( D3DRS_FOGENABLE, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_FOGCOLOR,  m_dwFogColor );

    m_pd3dDevice->SetRenderState( D3DRS_FOGSTART,   FtoDW(m_fFogStartValue) );
    m_pd3dDevice->SetRenderState( D3DRS_FOGEND,     FtoDW(m_fFogEndValue) );
    m_pd3dDevice->SetRenderState( D3DRS_FOGDENSITY, FtoDW(m_fFogDensity) );

    if( m_bUsingTableFog )
    {
        m_pd3dDevice->SetRenderState( D3DRS_FOGVERTEXMODE,  D3DFOG_NONE );
        m_pd3dDevice->SetRenderState( D3DRS_FOGTABLEMODE,   m_dwFogMode );
    }
    else
    {
        m_pd3dDevice->SetRenderState( D3DRS_FOGTABLEMODE,   D3DFOG_NONE );
        m_pd3dDevice->SetRenderState( D3DRS_FOGVERTEXMODE,  m_dwFogMode );
        m_pd3dDevice->SetRenderState( D3DRS_RANGEFOGENABLE, m_bRangeBasedFog );
    }

    // Begin the scene
    if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
    {
        // Reset the world matrix
        D3DXMATRIX matWorld;
        D3DXMatrixIdentity( &matWorld );
        m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

        // Draw the terrain
        m_pd3dDevice->SetVertexShader( D3DFVF_FOGVERTEX );
        m_pd3dDevice->SetStreamSource( 0, m_pTerrainVB, sizeof(FOGVERTEX) );
        m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,
                                   0, m_dwNumTerrainVertices-2 );

        // Draw the columns
        for( DWORD i=0; i<20; i++ )
        {
            FLOAT tx = (i%10)*20.0f - 100.0f;
            FLOAT ty =  0.0f;
            FLOAT tz = (i<=10) ? 40.0f : -40.0f;

            D3DXMatrixTranslation( &matWorld, tx, ty, tz );
            m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );

            m_pd3dDevice->SetVertexShader( D3DFVF_FOGVERTEX );
            m_pd3dDevice->SetStreamSource( 0, m_pColumnVB, sizeof(FOGVERTEX) );
            m_pd3dDevice->DrawPrimitive( D3DPT_TRIANGLESTRIP,
                                         0, m_dwNumColumnVertices-2 );
        }

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

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::InitDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CAppForm::InitDeviceObjects()
{
    // Get the device caps
    D3DCAPS8 d3dCaps;
    m_pd3dDevice->GetDeviceCaps( &d3dCaps );

    if( d3dCaps.RasterCaps & D3DPRASTERCAPS_WFOG )
        m_bDeviceUsesWFog = TRUE;
    else
        m_bDeviceUsesWFog = FALSE;

    // Create the floor texture
    if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice, _T("SeaFloor.bmp"),
                                       &m_pFloorTexture, D3DFMT_R5G6B5 ) ) )
        return E_FAIL;

    // Generate some geometry for the app
    if( m_bHiResTerrain )
        GenerateTerrainDisk( m_pd3dDevice, 80, 100.0f );
    else
        GenerateTerrainDisk( m_pd3dDevice, 5, 100.0f );
    GenerateColumn( m_pd3dDevice, 30, 1.0f, 10.0f );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::RestoreDeviceObjects()
// Desc: Initialize scene objects.
//-----------------------------------------------------------------------------
HRESULT CAppForm::RestoreDeviceObjects()
{
    // Set up the object material
    D3DMATERIAL8 mtrl;
    D3DUtil_InitMaterial( mtrl, 1.0f, 1.0f, 1.0f );
    m_pd3dDevice->SetMaterial( &mtrl );
    m_pd3dDevice->SetRenderState( D3DRS_AMBIENT,  0x44444444 );

    // Set up a texture
    m_pd3dDevice->SetTexture( 0, m_pFloorTexture );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_DIFFUSE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_MODULATE );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR );
    m_pd3dDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR );

    m_pd3dDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

    // Set the transform matrices
    D3DXMATRIX matWorld, matProj;
    D3DXMatrixIdentity( &matWorld );
    D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4.0f, 1.0f, NEAR_PLANE, FAR_PLANE );
    m_pd3dDevice->SetTransform( D3DTS_WORLD,      &matWorld );
    m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );

    // Set up the light
    D3DLIGHT8 light;
    D3DUtil_InitLight( light, D3DLIGHT_POINT, 0.0f, 50.0f, 0.0f );
    light.Attenuation0 =  0.1f;
    light.Range        = 200.0f;
    m_pd3dDevice->SetLight( 0, &light );
    m_pd3dDevice->LightEnable( 0, TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_LIGHTING,    TRUE );

    m_pd3dDevice->SetRenderState( D3DRS_ZENABLE,            TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE,       TRUE );
    m_pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE,     FALSE );

    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::InvalidateDeviceObjects()
// Desc: Called when the device-dependent objects are about to be lost.
//-----------------------------------------------------------------------------
HRESULT CAppForm::InvalidateDeviceObjects()
{
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::DeleteDeviceObjects()
// Desc: Called when the app is exiting, or the device is being changed,
//       this function deletes any device dependent objects.
//-----------------------------------------------------------------------------
HRESULT CAppForm::DeleteDeviceObjects()
{
    SAFE_RELEASE( m_pColumnVB );
    SAFE_RELEASE( m_pTerrainVB );
    SAFE_RELEASE( m_pFloorTexture );
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::FinalCleanup()
// Desc: Called before the app exits, this function gives the app the chance
//       to cleanup after itself.
//-----------------------------------------------------------------------------
HRESULT CAppForm::FinalCleanup()
{
    return S_OK;
}




//-----------------------------------------------------------------------------
// Name: CAppForm::ConfirmDevice()
// Desc: Called during device intialization, this code checks the device
//       for some minimum set of capabilities
//-----------------------------------------------------------------------------
HRESULT CAppForm::ConfirmDevice( D3DCAPS8* pCaps, DWORD dwBehavior, 
                                 D3DFORMAT Format )
{
    if( pCaps->RasterCaps & D3DPRASTERCAPS_FOGVERTEX )
        return S_OK;

    return E_FAIL;
}




//-----------------------------------------------------------------------------
// Name: SetFogParameters()
// Desc: Sets the apps parameters for rendering the scene
//-----------------------------------------------------------------------------
VOID CAppForm::SetFogParameters()
{
    m_fFogStartValue = ( m_fFogStartSlider*(FAR_PLANE-NEAR_PLANE) ) + NEAR_PLANE;
    m_fFogEndValue   = ( m_fFogEndSlider*(FAR_PLANE-NEAR_PLANE) ) + NEAR_PLANE;

    // Set fog start and end values for table (pixel) fog mode on devices that
    // do not use WFOG. These devices expect fog between 0.0 and 1.0.
    if( (FALSE==m_bDeviceUsesWFog) && (TRUE==m_bUsingTableFog) )
    {
        m_fFogStartValue = m_fFogStartSlider;
        m_fFogEndValue   = m_fFogEndSlider;
    }

    // Adjust terrain if necessary
    if( m_bHiResTerrainOld != m_bHiResTerrain )
    {
        m_bHiResTerrainOld  = m_bHiResTerrain;

        if( m_bHiResTerrain )
            GenerateTerrainDisk( m_pd3dDevice, 80, 100.0f );
        else
            GenerateTerrainDisk( m_pd3dDevice, 5, 100.0f );
    }
}
