// ================================================================
 // Filename: Terrain.cpp
 // Description: A terrain engine
 // ================================================================


 #define WIN32_LEAN_AND_MEAN
 #include <windows.h>
 #include <d3d8.h>
 #include <d3dx8.h>
 #include <dxerr8.h>
 #include <mmsystem.h>

 #include "D3DFuncs.h"
 #include "cd3drasterfont.h"
 #include "cd3dtimer.h"

 #include "TerrainEngine.h"
 #include "Skybox.h"

 // Function declarations
 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
 LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam );

 HRESULT GameInit();
 HRESULT GameLoop();
 HRESULT GameShutDown();
 HRESULT Render();


 // Globals
 static char strAppname[]="Simple Terrain demo by 32Bits.co.uk";
 LPDIRECT3D8 g_pD3D;
 LPDIRECT3DDEVICE8 g_pDevice;
 LPDIRECT3DSURFACE8 g_pBackSurface;
 HWND g_hWnd;

 D3DCURRENTSETTINGS g_D3DSettings;

 CD3DRasterFont g_FPSFont;
 CD3DRasterFont g_32BitsFont;
 CD3DTimer g_TimerGlobal;

 // Globals specifically for this source

 CTerrainEngine g_Terrain;
 CSkyBox g_SkyBox;

 //-----------------------------------------------------------------------------
 // Name: WinMain()
 // Desc: The application's entry point
 //-----------------------------------------------------------------------------

 int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
 {
     WNDCLASSEX wc;

     ZeroMemory(&wc, sizeof(WNDCLASSEX));
     wc.cbSize=sizeof(WNDCLASSEX);                       // size of the window struct in bytes
     wc.style=CS_HREDRAW | CS_VREDRAW | CS_OWNDC;        // window styles to use
     wc.lpfnWndProc=MsgProc;                             // function name of event handler
     wc.hInstance=hInstance;                             // handle to this apps instance
     wc.hbrBackground=(HBRUSH)GetStockObject(GRAY_BRUSH);// background colour of window
     wc.hIcon= LoadIcon(NULL, IDI_APPLICATION);          // icon for the app window
     wc.hIconSm=LoadIcon(NULL, IDI_APPLICATION);         // icon when minimized to taskbar
     wc.hCursor=LoadCursor(NULL, IDC_ARROW);             // cursor to use for this window
     wc.lpszClassName=strAppname;                        // name for this class

     // Register the window class
     RegisterClassEx( &wc );

     g_D3DSettings.m_nDeviceWidth=800;
     g_D3DSettings.m_nDeviceHeight=600;

     // Create the application's window
     g_hWnd = CreateWindow(strAppname, strAppname, WS_OVERLAPPEDWINDOW, 10, 10,
                           g_D3DSettings.m_nDeviceWidth, g_D3DSettings.m_nDeviceHeight,
                           NULL, NULL, wc.hInstance, NULL );

     // Show the window
     ShowWindow(g_hWnd, nCmdShow);
     UpdateWindow(g_hWnd);

     if(FAILED(GameInit()))
     {
         UnregisterClass( strAppname, wc.hInstance );
         return -1;
     }

     // Enter the message loop
     MSG msg;
     ZeroMemory( &msg, sizeof(msg) );
     int count=0;
     while( msg.message!=WM_QUIT )
     {
         if( PeekMessage( &msg, NULL, 0U, 0U, PM_REMOVE ) )
         {
             TranslateMessage( &msg );
             DispatchMessage( &msg );
         }
         else
         {
             GameLoop();
         }
     }

     GameShutDown();
     UnregisterClass( strAppname, wc.hInstance );
     return 0;
 }


 //-----------------------------------------------------------------------------
 // Name: MsgProc()
 // Desc: The window's message handler
 //-----------------------------------------------------------------------------
 LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
 {
     HDC hDC;
     PAINTSTRUCT PaintStruct;


     switch( msg )
     {
     case WM_PAINT:
         {
             hDC=BeginPaint(hWnd, &PaintStruct);     // Tell windows we want to update the window

             // Do GDI drawing here

             EndPaint(hWnd, &PaintStruct);
             return 0;
         }
     case WM_KEYDOWN:
         {
             switch(wParam)
             {

             case VK_SPACE:
                 {
                     PostQuitMessage( 0 );
                     return 0;
                 }
             }
         }

     case WM_DESTROY:
         {
             PostQuitMessage( 0 );
             return 0;
         }

     default:
         return DefWindowProc( hWnd, msg, wParam, lParam );
     }

 }


 // =====================================================================================
 //  High level functions for initialization, loop and shutdown
 // =====================================================================================

 HRESULT GameInit()
 {
     HRESULT rslt=0;

     g_pD3D=Direct3DCreate8(D3D_SDK_VERSION);
     if(g_pD3D==NULL)
     {
         return E_FAIL;
     }


     // Populate our struct with how we want to set up D3D...
     g_D3DSettings.m_bWindowed=TRUE;
     g_D3DSettings.m_bMultiSampling=FALSE;
     g_D3DSettings.m_D3DFormat=D3DFMT_X8R8G8B8;

     // ...and pass it to our function to create the device!
     rslt=InitDirect3DDevice(g_hWnd, g_D3DSettings, g_pD3D, &g_pDevice);
     if(FAILED(rslt))
     {
         return rslt;
     }

     // Set our culling renderstate, turn off lighting, and enable ZBuffering
     g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
     g_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
     g_pDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_TRUE);

     // Use bilinear filtering to smooth out the texture
     g_pDevice->SetTextureStageState( 0, D3DTSS_MAGFILTER, D3DTEXF_LINEAR);
     g_pDevice->SetTextureStageState( 0, D3DTSS_MINFILTER, D3DTEXF_LINEAR);
     g_pDevice->SetTextureStageState( 0, D3DTSS_MIPFILTER, D3DTEXF_LINEAR);


     // Set the viewport
     D3DVIEWPORT8 ViewPort;
     ZeroMemory(&ViewPort, sizeof(D3DVIEWPORT8));
     ViewPort.Height=g_D3DSettings.m_nDeviceHeight;
     ViewPort.Width=g_D3DSettings.m_nDeviceWidth;
     ViewPort.MaxZ=1.0f;
     g_pDevice->SetViewport(&ViewPort);


     // Set the projection matrix
     D3DXMATRIX ProjectionMatrix;
     ZeroMemory(&ProjectionMatrix, sizeof(ProjectionMatrix));

     float ScreenAspect=(float)g_D3DSettings.m_nDeviceWidth / (float)g_D3DSettings.m_nDeviceHeight;
     float FOV = D3DX_PI / 4;

     D3DXMatrixPerspectiveFovLH(&ProjectionMatrix, FOV, ScreenAspect, 1.0f, 330.0f);
     g_pDevice->SetTransform(D3DTS_PROJECTION, &ProjectionMatrix);


     D3DXMATRIX ViewMatrix;
     D3DXMatrixLookAtLH(&ViewMatrix, &D3DXVECTOR3(0.0f, 70.0f, -100.0f), &D3DXVECTOR3(0.0f, 45.0f,-30.0f), &D3DXVECTOR3(0.0f,1.0f,0.0f));
     g_pDevice->SetTransform(D3DTS_VIEW, &ViewMatrix);

     D3DXMATRIX WorldMatrix;
     D3DXMatrixIdentity(&WorldMatrix);
     g_pDevice->SetTransform(D3DTS_WORLD, &WorldMatrix);


     // =====================================================================================
     // Set up the terrain engine
     // =====================================================================================

     g_Terrain.Initialise(g_pDevice, "island.bmp");

     g_SkyBox.Initialise(g_pDevice);

     // =====================================================================================
     // Font for FPS
     // =====================================================================================

     g_FPSFont.SetColourKey(g_pDevice, D3DCOLOR_XRGB(0,0,0));
     g_FPSFont.SetText("32BITS.CO.UK");
     g_FPSFont.SetCharsPerLine(10);
     g_FPSFont.SetLetterWidth(32);
     g_FPSFont.SetLetterHeight(32);
     g_FPSFont.SetTranslation(D3DXVECTOR2(290.0f, 550.0f));
     rslt=g_FPSFont.Load(g_pDevice, "font1.bmp");
     if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to load raster font."); }



     g_32BitsFont.SetColourKey(g_pDevice, D3DCOLOR_XRGB(0,0,0));
     g_32BitsFont.SetText("32BITS.CO.UK");
     g_32BitsFont.SetCharsPerLine(10);
     g_32BitsFont.SetLetterWidth(32);
     g_32BitsFont.SetLetterHeight(32);
     g_32BitsFont.SetTranslation(D3DXVECTOR2(5.0f, 15.0f));
     rslt=g_32BitsFont.Load(g_pDevice, "font1.bmp");
     if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to load raster font."); }

     rslt=g_TimerGlobal.Initialise();
     if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to initialise timer."); }


     return S_OK;
 }

 HRESULT GameLoop()
 {
     return Render();
 }

 HRESULT GameShutDown()
 {
     if(g_pBackSurface)
         g_pBackSurface->Release();
     if(g_pDevice)
         g_pDevice->Release();
     if(g_pD3D)
         g_pD3D->Release();
     return S_OK;
 }



 // =====================================================================================
 // Main render function to perform D3D drawing
 // =====================================================================================

 HRESULT Render()
 {
     HRESULT rslt=NULL;


     // ====================================================================================
     // - Do all the usual checks to make sure we have the right pointers, etc...
     // ====================================================================================

     // Make sure we have a valid D3D Device
     if(!g_pDevice) { return E_FAIL; }

     // Return if the device is not ready
     rslt=ValidateDevice(g_pDevice, g_pBackSurface, g_D3DSettings);
     if(FAILED(rslt)) { return rslt; }

     // Clear the back buffer
     g_pDevice->Clear(0,0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,55), 1.0f, 0);

     // Get a pointer to the back buffer (remember, page flipping has taken place)
     rslt=g_pDevice->GetBackBuffer(0, D3DBACKBUFFER_TYPE_MONO, &g_pBackSurface);
     if(FAILED(rslt)) { return E_FAIL; }



     rslt=g_pDevice->BeginScene();
     if(FAILED(rslt)) { return E_FAIL; }

     // ====================================================================================
     // - Do our drawing operations
     // ====================================================================================

     D3DXMATRIX WorldMatrix;
     D3DXMatrixRotationY(&WorldMatrix, (timeGetTime()/1500.0f));
     g_pDevice->SetTransform(D3DTS_WORLD, &WorldMatrix);

     //g_pDevice->SetRenderState(D3DRS_FILLMODE, D3DFILL_WIREFRAME);

     g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
     g_SkyBox.Render(g_pDevice, g_pBackSurface);

     g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
     g_Terrain.Render(g_pDevice, g_pBackSurface);



     char frate[15];
     wsprintf(frate, "FRAMERATE: %d", g_TimerGlobal.GetFrameRate());
     g_FPSFont.SetText(frate);
     g_FPSFont.Render(g_pDevice, g_pBackSurface);
     g_32BitsFont.Render(g_pDevice, g_pBackSurface);


     // ====================================================================================
     // - Clean up and present the back buffer to be page flipped
     // ====================================================================================

     g_pDevice->EndScene();
     g_pBackSurface->Release();

     // Present the back buffer to the display adapter to be drawn
     g_pDevice->Present(NULL, NULL, NULL, NULL);

     g_TimerGlobal.Frame();

     return S_OK;
 }