/* Filename:  example01.cpp

   Author:  Daniel Lioi

   Date:  December 2002

   Modified:  June 2003

   This is a Direct3D program that demonstrates some simple uses of Direct X.
   It draws a rotating pyramid.  Note that you stop the program by using
   ALT F4.

   Tested with:
      Microsoft Visual C++ 6.0, Visual C++ .NET, and the Direct X 8 SDK.

   Detailed explanation:

   Program execution pretty much begins at the WinMain function so we should
   too.  Take a look at the WinMain function in example one.  The program
   starts, we declare variables to store messages, the wndclass structure,
   and a handle to the window.  We put some data into the wndclass structure
   we declared.  Then we register that class, create the window, show the
   window and update it.  On the way we check for errors.

   The first function we use is init3D.  The function receives a handle to
   the main window that was created in WinMain.  First we declare a structure
   of type D3DPRESENT_PARAMETERS which will be used to store information about
   how we want direct3d to work.  We use a helper function called
   Direct3DCreate8 which retrieves the Direct3D8 interface.  Then set some
   values in our data structure and call create device.  If create device
   fails we change the screen size and attempt to create the device in
   software mode.  The device is the rendering system.  HAL (Hardware
   Abstraction Layer) uses the device as a software interface to the hardware.
   With HAL, software and hardware are mixed to create the best performance.
   REF is software only and much slower.  If we are running in software we
   disable dithering, if we are running in HAL we enable it.  Dithering is a
   higher quality technique for blending colors which requires more processing
   time.  Finally we disable lighting.  

   If you change HAL to SW it will create an error for you and will run in REF
   mode (in case you would like to see it just to get an idea what it looks
   like).  REF runs much slower than HAL.  There exist software rendering
   systems that outperform Direct3D but they are much simpler.  We are only
   including this code in case HAL doesn't work.  The code would be
   significantly changed if we intended the program to run in REF.

   The next function that is run is initData.  This function creates a vertex
   buffer, locks it, copies vertex data to it, and then unlocks it.  This type
   of vertex contains x, y, and z data, as well as a DWORD used for the color.
   This color component is known as Diffuse.

   After initData we enter a continuous loop.  This look continues as long as
   you don't press Alt+F4 to close the program. That's coded internally as
   part of Windows.  The function render is called repeatedly.

   The first thing render() does is to call the doMath function.  This function
   sets up the vertices for the world transform which is applied to our
   pyramid, as well as the view transform and the projection transform.  The
   view transform affects from where we are looking and which direction is up.
   The projection transform affects the viewing angle we are looking with, as
   well as the aspect ratio between width and height.

   Back to the render function.  We clear the target (the back buffer actually).
   Then we begin the scene, set the vertex stream source, set the vertex
   shader, which tells the device what kind of information the vertices
   contain, draw the primitives, end the scene, and finally call present which
   flips the back buffer to the front buffer.

   Back buffers and front buffers work like this:  On the screen is the front
   or primary buffer.  The computer draws the back buffer.  Then when it's
   done they switch.  By using this process you don't ever see an incomplete image.

   When the program ends the cleanup function is called.  This function
   releases interfaces and data that we created during execution.
*/


#define D3D_OVERLOADS
#define INITGUID

#include <windows.h>
#include <d3dx8.h>

LPDIRECT3D8 lpD3D8 = NULL;      // will store a pointer to the Direct3D8 object
      // which always exists as part of the directX runtime on the computer

LPDIRECT3DDEVICE8 lpD3DDevice8 = NULL;      // will store a pointer to the Direct3DDevice8
      // which is created.  Think of this as the object that is and controls
      // the portion of (or the entire) screen.

LPDIRECT3DVERTEXBUFFER8 lpD3DVertBuffer = NULL;      // a vertex buffer, kinda new to Direct3D
      // all vertices will be stored here along with any data such as color
      // in previous versions you had to create your own storage space
      // this buffer class handles it now, a good thing :-)

struct CUSTOMVERTEX   // temp storage for vertices that will be put into a vertex buffer
{     // this could/would be used as a destination for file loading of data
      // this vertex is quite simple, no textures...
    FLOAT x, y, z;   // position
    DWORD color;     // color
};


#define D3DFVF_CUSTOMVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE)   // FVF means Flexible Vertex Format
      // all this define does is put together two properties that are sent to the vertex shader...
      // for rendering basically

#define GREEN 0xff006600   // this is useless.. I put this in so I could easily
      // modify the color green seen in the pyramid

float thetaY = 0.0f;   // this is the angle in radians through which the pyramid is rotated..
      // I used this to keep the code simple and let the user/coder see the pyramid rotate..
      // a better example would allow for user input or a timer..
      // since its not timer based.. the spin speed will depend on the CPU..
      // this as well as Frames per Second are NOT locked, they will run as fast as possible


//*******
// a simple message handler.. keyboard input would go here for simple stuff.. 
// or it could go into a directInput section.. 
// if you are daring, try learning DirectInput and use that for input
// for now it just processes WM_DESTROY to kill the program
//*******
LRESULT CALLBACK WinProc(HWND hWnd, unsigned uMsg, WPARAM wParam, LPARAM lParam)
{
   switch(uMsg)             // switch for messages..
   {
   case WM_DESTROY:         // on WM_DESTROY message
      //  cleanup...
      PostQuitMessage(0);   // post quit message
      break;
   }

   return DefWindowProc(hWnd, uMsg, wParam, lParam);   // send the rest to def..
}


//*******
// this function initializes Direct3D... 
bool init3D(HWND hWnd)
{
    D3DPRESENT_PARAMETERS d3dpp;   // d3d present parameters..details on how it should present
         // a scene.. such as swap effect.. size.. windowed or full screen.. 

    // create D3D8.. if error (like that ever happens..) return false..
    if (NULL == (lpD3D8 = Direct3DCreate8(D3D_SDK_VERSION)))
        return false;
   
   ZeroMemory(&d3dpp, sizeof(d3dpp));        // blank the memory for good measure..
   //3dpp.Windowed = true;                   // use this for window mode..
   d3dpp.Windowed = false;                   // use this for full screen... its that easy!
   d3dpp.BackBufferWidth = 800;              // rather useless for Windowed version
   d3dpp.BackBufferHeight = 600;             // ditto..but left it in anyway for full screen
   d3dpp.SwapEffect = D3DSWAPEFFECT_FLIP;    // flip using a back buffer...easy performance
   d3dpp.BackBufferFormat = D3DFMT_R5G6B5;   // just set back buffer format from display mode
   d3dpp.BackBufferCount = 1;
   d3dpp.EnableAutoDepthStencil = true;
   d3dpp.AutoDepthStencilFormat =  D3DFMT_D16;   // 16 bit depth buffer..

   // Create the D3DDevice
   if (FAILED(lpD3D8->CreateDevice(D3DADAPTER_DEFAULT,   // which display adapter..
               D3DDEVTYPE_HAL,
               hWnd, D3DCREATE_MIXED_VERTEXPROCESSING,   // mixed, software, or hardware..
               &d3dpp, &lpD3DDevice8)))                  // sends stuff.. and to receive
   {  //  if it fails to create with HAL, then try (usually succeeds) to create
      //  with ref (software) using a much lower screen res
      d3dpp.BackBufferWidth = 320;
      d3dpp.BackBufferHeight = 240;
      d3dpp.Windowed = false;
      if (FAILED(lpD3D8->CreateDevice(D3DADAPTER_DEFAULT,
                  D3DDEVTYPE_REF,
                  hWnd, D3DCREATE_SOFTWARE_VERTEXPROCESSING,
                  &d3dpp, &lpD3DDevice8)))
      {
         return false;     
      }
      else
      {   
         
         // disable dithering for software..
         lpD3DDevice8->SetRenderState( D3DRS_DITHERENABLE, false);
      }
   }
   else
   {   
      // enable dithering for hardware... 
      lpD3DDevice8->SetRenderState( D3DRS_DITHERENABLE, true);
   }

   // turn off lighting since we have color values and no light sources exist
   lpD3DDevice8->SetRenderState( D3DRS_LIGHTING, false );
   return true;
}


bool initData()
{ 
   CUSTOMVERTEX verts[] =   // this is just data...
   {                  // it could be on a disk or such.. but this is easier
      {  0.0f, 0.750f, 0.0f, 0x00cccccc, },   // x, y, z, color
      { -1.0f, -0.50f, -1.0f, GREEN - 0x00002200, },
      { -1.0f, -0.50f, 1.0f, GREEN, },
      { 1.0f, -0.50f, 1.0f, GREEN - 0x00002200, },
      { 1.0f, -0.50f, -1.0f, GREEN, },
      { -1.0f, -0.50f, -1.0f, GREEN - 0x00002200, },    
   };
 
   if (FAILED(lpD3DDevice8->CreateVertexBuffer(sizeof(verts),   // create a vertex buffer..
              0,                     // type and processing style.. none for this 
              D3DFVF_CUSTOMVERTEX,   // use our defined properties..
              D3DPOOL_DEFAULT,       // memory class to place the resource..
              &lpD3DVertBuffer)))    // place its stored..
   {
      return false;   // on error return false
   }

   VOID * pVertices;   // stores pointer to the data portion of the vertex buffer
   if (FAILED(lpD3DVertBuffer->Lock(0, sizeof(verts), (BYTE**) &pVertices, 0 )))
      return false;   // gets and locks data portion of vert buffer

   memcpy(pVertices, verts, sizeof(verts));   // copys mem..
   lpD3DVertBuffer->Unlock();   // unlocks vert buffer.. VERY IMPORTANT!!!

   return true;
}


void doMath()
{
   thetaY += 0.001f;   // increase rotation.. could go anywhere
            // sinces it's global.. but it's math so I put it here for now
   D3DXMATRIX matWorld;      // matrix World..stores the world movements
   D3DXMatrixRotationY( &matWorld, thetaY);   //  creates rotation for world..
   lpD3DDevice8->SetTransform( D3DTS_WORLD, &matWorld );   // sets it..

   D3DXMATRIX matView;   // this is the view matrix..
   D3DXMatrixLookAtLH( &matView, &D3DXVECTOR3( 0.0f, 1.50f, -6.0f ),   // from point..
                                 &D3DXVECTOR3( 0.0f, 0.0f, 0.0f ),     // to point..
                                 &D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );   // world up..
   lpD3DDevice8->SetTransform( D3DTS_VIEW, &matView );                 // sets above

   D3DXMATRIX matProj;
   // set 90 degree view field..height/width aspect(1.3333)..near plane..(1.0f)..far plane (100.0f)
   // everything beyond the far plane is clipped.. you don't see it
   D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI / 4, 1.3333f, 1.0f, 100.0f );

   lpD3DDevice8->SetTransform( D3DTS_PROJECTION, &matProj );   // set the view field
}


void render()
{
   doMath();   // do the math.. :-P
    
   // Clear the back buffer to a black... values r g b are 0-256
   lpD3DDevice8->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(256, 256, 256), 1.0f, 0);

   // Begin the scene
   lpD3DDevice8->BeginScene();

   lpD3DDevice8->SetStreamSource( 0, lpD3DVertBuffer, sizeof(CUSTOMVERTEX) );   // set vertex stream..
   lpD3DDevice8->SetVertexShader( D3DFVF_CUSTOMVERTEX );     // set vertex shader options
   lpD3DDevice8->DrawPrimitive( D3DPT_TRIANGLEFAN, 0, 4 );   // triangle fan.... 4 triangles..
   // the 0 above is the start vertex.. usually 0..
   // a triangle fan.. the first point is special.. the rest of the points form a "fan"
   // around that point.  so... 1 2 and 3 are connected... then 1 3 4.. 1 4 5..
         
   // End the scene
   lpD3DDevice8->EndScene();

   // present the back buffer.. or "flip" the page
   lpD3DDevice8->Present( NULL, NULL, NULL, NULL );   // these options are for using rectangular
      // regions for rendering/drawing...
      // 3rd is which target window.. NULL makes it use the currently set one (default)
      // last one is NEVER used.. that happens with DirectX often
}


void cleanup()   // it's a dirty job.. but some function has to do it...
{
   if ( lpD3DVertBuffer != NULL )        
      lpD3DVertBuffer->Release();

   if ( lpD3DDevice8 != NULL ) 
      lpD3DDevice8->Release();

   if ( lpD3D8 != NULL )       
      lpD3D8->Release();
}


// windows stuff.. 
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
   MSG       msg;
   WNDCLASS  wc;
   HWND      hWnd;
   // set up and register wndclass wc... windows stuff
   wc.style = CS_HREDRAW | CS_VREDRAW;
   wc.lpfnWndProc = (WNDPROC) WinProc;
   wc.cbClsExtra = 0;
   wc.cbWndExtra = sizeof(DWORD);
   wc.hInstance = hInstance;
   wc.hIcon = NULL;
   wc.hCursor = LoadCursor(NULL, IDC_ARROW);
   wc.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
   wc.lpszMenuName = NULL;
   wc.lpszClassName = "D3D8PROG";

   if (! RegisterClass(&wc))
   {
      // error
      return false;
   }

   //  create window
   hWnd = CreateWindow("D3D8PROG", "Direct3D 8 Program",   // class and caption
            WS_VISIBLE | WS_POPUP,  // style
            0,                      // horizontal start..
            0,                      // vertical start..
            640,                    // horz end
            480,                    // vertical end..
            GetDesktopWindow(),     // parent..
            NULL,                   // menu..
            hInstance,              // instance
            NULL);                  // params..
   
   if (! hWnd)
      return false;

   ShowWindow(hWnd, nCmdShow);
   UpdateWindow(hWnd);

   if (init3D(hWnd) != true)   // error checking...
   {   
      cleanup();
      return false;
   }
    
   if (initData() != true)
   {
      cleanup();
      return false;
   }
   
   do   //  control loop... life of the program
   {
      if (PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE))   // gets messages when they appear
      {
         TranslateMessage(&msg);   // standard win message stuff..
         DispatchMessage(&msg);
      }
      else   // if no message.. render
      {
         // this is where you do stuff when there aren't messages.. mainly render..
         // might rearrange and doMath and/or other stuff here...
         // depends what would be best in the future.. 
         // for now just render         
         render();
      }
   } while (WM_QUIT != msg.message);

   cleanup();   // clean it up!
   return msg.wParam;   // more win stuff..
}


