// ============================================================================
// Filename: DOT3DINPUT.cpp
// Description: DirectInput Mouse and Dot3 Bumpmapping. This code corresponds
//				to DirectX Techniques Series 1 tutorial 5.
//
// Created by wizard version 0.1 Beta.
//
// Found a bug in this code framework? Need some help? Come to www.32bits.co.uk
// for support, feedback and the best DirectX dev community on the net!
// ============================================================================

#define WIN32_LEAN_AND_MEAN

#include <windows.h>
#include <d3d9.h>
#include <d3dx9.h>
#include <dxerr9.h>

// Include helper functions.
#include "D3DFuncs.h"
#include "DIMouse.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 AppInit();
HRESULT AppLoop();
HRESULT AppShutDown();
HRESULT Render();


// Globals required for all apps
static char szAppname[]="[32Bits.co.uk] DInput Mouse & Dot3 Bumpmap";
LPDIRECT3D9 g_pD3D;
LPDIRECT3DDEVICE9 g_pDevice;
HWND g_hWnd;

D3DCURRENTSETTINGS g_D3DSettings;

// Globals specifically for this app

LPDIRECT3DVERTEXBUFFER9 g_pVertexBuffer;
LPDIRECT3DTEXTURE9		g_pBumpMapTexture;
LPDIRECT3DTEXTURE9		g_pNormalMapTexture;

CDIMouse*				g_pDIMouse;


// Change our normal vertex struct to include members for texturemapping...
typedef struct _tagSimpleTexMapVertex
{
	float		x,y,z;
	D3DCOLOR	dwDiffuse;
	float		tu,tv;
} SIMPLETEXMAPVERTEX;

// ...and update our FVF define to match.
#define FVF_SIMPLETEXMAPVERTEX (D3DFVF_XYZ | D3DFVF_DIFFUSE | D3DFVF_TEX1)


// =====================================================================================
//                      Application code begins here
// =====================================================================================


// =====================================================================================
// Function Name: WinMain
// Purpose: Application 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=szAppname;							// name for this class

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

	g_D3DSettings.m_nDeviceWidth=800;
	g_D3DSettings.m_nDeviceHeight=600;
	g_D3DSettings.m_fScreenAspect=(float)g_D3DSettings.m_nDeviceWidth / (float)g_D3DSettings.m_nDeviceHeight;

    // Create the application's window
    g_hWnd = CreateWindow(szAppname, szAppname, 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(AppInit()))
	{
		UnregisterClass(szAppname, 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
		{
			AppLoop();
		}
	}

	AppShutDown();
	UnregisterClass(szAppname, wc.hInstance);
    return 0;
}


// =====================================================================================
// Function Name: MsgProc
// Purpose: Message handler for main app window
// =====================================================================================

LRESULT WINAPI MsgProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
    {
	case WM_KEYDOWN:
		{
			switch(wParam)
			{
			
			// Exit app on space or escape keypress
			case VK_SPACE:
			case VK_ESCAPE:
				{
					PostQuitMessage(0);
					return 0;			
				}
			}

			return DefWindowProc(hWnd, msg, wParam, lParam);
		}
	
	case WM_DESTROY:
		{
			PostQuitMessage(0);
			return 0;
		}
		
	default:	
		return DefWindowProc(hWnd, msg, wParam, lParam);
    }
	
}


// =====================================================================================
// Function Name: AppInit
// Purpose: Initialisation for D3D and the app's global objects
// =====================================================================================

HRESULT AppInit()
{
	HRESULT rslt=0;

	g_pD3D=Direct3DCreate9(D3D_SDK_VERSION);
	if(g_pD3D==NULL)
	{
		return D3DError(E_FAIL, __LINE__, __FILE__, "Failed to create a D3D9 object.");
	}


	// 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 E_FAIL;
	}
	
	
	
	// ===================================================================================
	// Set up our Projection, View and World transformations
	// ===================================================================================
	
	// Create a matrix to store our Projection transform. Null all the fields.
	D3DXMATRIX matProjection;
	ZeroMemory(&matProjection, sizeof(matProjection));

	// Use D3DX to create a left handed cartesian Field Of View transform
	D3DXMatrixPerspectiveFovLH(&matProjection, D3DX_PI/4, g_D3DSettings.m_fScreenAspect,
							   1.0f, 100.0f);
	
	// Tell D3D to use our Projection matrix for the projection transformation stage
	rslt=g_pDevice->SetTransform(D3DTS_PROJECTION, &matProjection);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to set Projection Transform."); }

	

	// Create a matrix to store our View transform. Null all the fields.
	D3DXMATRIX matView;
	ZeroMemory(&matView, sizeof(matView));

	// Use D3DX to create a Look At matrix from eye, lookat and up vectors.
	D3DXMatrixLookAtLH(&matView, &D3DXVECTOR3(0.0f, 0.0f, -5.0f),
								 &D3DXVECTOR3(0.0f, 0.0f, 0.0f),
								 &D3DXVECTOR3(0.0f, 1.0f,  0.0f));

	// Tell D3D to use our View matrix for the view transformation stage
	rslt=g_pDevice->SetTransform(D3DTS_VIEW, &matView);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to set View Transform."); }



	// Create a matrix to store our World transform
	D3DXMATRIX matWorld;
	// Set the matrix to an identity matrix (one that makes no change)
	D3DXMatrixIdentity(&matWorld);

	// Tell D3D to use our World matrix for the world transformation stage
	rslt=g_pDevice->SetTransform(D3DTS_WORLD, &matWorld);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to set World Transform."); }

	
	
	// ===================================================================================
	// Set up our scene states
	// ===================================================================================

	// Set our culling & lighting renderstates
	g_pDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_CCW);
	g_pDevice->SetRenderState(D3DRS_LIGHTING, FALSE);
		

	// ===================================================================================
	// Create the objects for this app
	// ===================================================================================
	
	// Create a quad for texturemapping the bumpmap onto, and assign each vertex a texcoord:
	
	SIMPLETEXMAPVERTEX Quad[4];
	Quad[0].x=-1.0f; Quad[0].y=-1.0f; Quad[0].z=0.0f;
	Quad[0].tu=0.0f;
	Quad[0].tv=1.0f;
	Quad[0].dwDiffuse=D3DCOLOR_XRGB(255,255,255);
	
	Quad[1].x=-1.0f; Quad[1].y=1.0f; Quad[1].z=0.0f;
	Quad[1].tu=0.0f;
	Quad[1].tv=0.0f;
	Quad[1].dwDiffuse=D3DCOLOR_XRGB(255,255,255);
	
	Quad[2].x=1.0f; Quad[2].y=-1.0f; Quad[2].z=0.0f;
	Quad[2].tu=1.0f;
	Quad[2].tv=1.0f;
	Quad[2].dwDiffuse=D3DCOLOR_XRGB(255,255,255);
	
	Quad[3].x=1.0f; Quad[3].y=1.0f; Quad[3].z=0.0f;
	Quad[3].tu=1.0f;
	Quad[3].tv=0.0f;
	Quad[3].dwDiffuse=D3DCOLOR_XRGB(255,255,255);
	
	
	
	// Next, create our vertex buffer using the properties of our custom struct and FVF
	rslt=g_pDevice->CreateVertexBuffer(sizeof(Quad), 0, FVF_SIMPLETEXMAPVERTEX, D3DPOOL_DEFAULT,
									   &g_pVertexBuffer, NULL);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "CreateVertexBuffer() failed."); }
	
	// Now lock the vertex buffer...
	BYTE* pVerticeLock=0;
	rslt=g_pVertexBuffer->Lock(0, sizeof(Quad), (void**)&pVerticeLock, 0);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Failed to lock Vertex Buffer."); }
	
	// ...and copy our array of vertices straight into it
	CopyMemory(pVerticeLock, &Quad, sizeof(Quad));
	
	// Remember to unlock the vertex buffer once we're done.
	g_pVertexBuffer->Unlock();
	
	
	// ===================================================================================
	// Create our texture surface for texturemapping
	// ===================================================================================
	
	// Create the texture surface from the file
	rslt=D3DXCreateTextureFromFileEx(g_pDevice, "bumpmap.png", 256, 256,
									 1, 0, D3DFMT_UNKNOWN, D3DPOOL_MANAGED,
									 D3DX_DEFAULT, D3DX_DEFAULT, 0, NULL, NULL,
									 &g_pBumpMapTexture);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create texture."); }
	
	
	// Create a brand new texture to receive our normal map
	rslt=D3DXCreateTexture(g_pDevice, 256, 256, 1, D3DX_DEFAULT, D3DFMT_A8R8G8B8,
						   D3DPOOL_DEFAULT,	&g_pNormalMapTexture);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create texture 1"); }
	
	
	// Calculate the normal map from the source png image, and write it to the normal map texture
	rslt = D3DXComputeNormalMap(g_pNormalMapTexture, g_pBumpMapTexture, NULL, 0, D3DX_CHANNEL_RED, 1.0f);
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "Could not create normal map texture"); }
	
	
	// ===================================================================================
	// Initialise our DirectInput mouse
	// ===================================================================================
	
	// Create the mouse on the heap, we might want to kill it later.
	g_pDIMouse = new CDIMouse;
	g_pDIMouse->Initialise(g_hWnd, g_D3DSettings.m_nDeviceWidth, g_D3DSettings.m_nDeviceHeight);
	
	
	return S_OK;
}

// =====================================================================================
// Function Name: AppLoop
// Purpose: Controls the flow of the app, called by the messageloop.
// =====================================================================================

HRESULT AppLoop()
{
	// Update the mouse position
	g_pDIMouse->Update();

	return Render();
}

// =====================================================================================
// Function Name: AppShutDown
// Purpose: Called when the app exits. Releases all global COM interfaces.
// =====================================================================================

HRESULT AppShutDown()
{
	// Don't forget to release the vertex buffer - it's a COM interface!
	if(g_pVertexBuffer)
		g_pVertexBuffer->Release();
	
	if(g_pBumpMapTexture)
		g_pBumpMapTexture->Release();
	if(g_pNormalMapTexture)
		g_pNormalMapTexture->Release();
	
	// Mouse destructor handles the DirectInput deallocation
	delete g_pDIMouse;
	
	if(g_pDevice)
		g_pDevice->Release();
	if(g_pD3D)
		g_pD3D->Release();
		
	return S_OK;
}


// =====================================================================================
// Function Name: Render()
// Purpose: Main rendering 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_D3DSettings);

	// Clear the back buffer
	if(g_pDIMouse->IsMouseButtonDown(DIMOUSE_LEFTBUTTON))
		g_pDevice->Clear(0,0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,100,55), 1.0f, 0);
	else
		g_pDevice->Clear(0,0, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, D3DCOLOR_XRGB(0,0,55), 1.0f, 0);	

	rslt=g_pDevice->BeginScene();
	if(FAILED(rslt)) { return D3DError(rslt, __LINE__, __FILE__, "BeginScene() failed."); }

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


	// Tell D3D we want to use our vertex buffer as the "source stream"
	g_pDevice->SetStreamSource(0, g_pVertexBuffer, 0, sizeof(SIMPLETEXMAPVERTEX));
	
	// Tell D3D we want to use our custom FVF define
	g_pDevice->SetFVF(FVF_SIMPLETEXMAPVERTEX);
	
	// We are going to use the mouse cursor position as the light source for the bump
	// mapping. In other words, we need to create a vector from the cursor to the origin
	D3DXVECTOR3 vLight;
    POINT kPoint;
	g_pDIMouse->GetAbsolutePos(&kPoint);
	
	// Create a vector from the cursor position (no Z axis for this, the mouse is in 2D)
    vLight.x = (((float)kPoint.x / (float)g_D3DSettings.m_nDeviceWidth) - 1);
    vLight.y = (((float)kPoint.y / (float)g_D3DSettings.m_nDeviceHeight) - 1);
    vLight.z = 1.0f;
	
	// Normalize the vector. See the tutorial for more info! Normalization reduces the
	// magnitude of a vector to 1, but keeps the original direction. This makes it a
	// unit vector. Also see the Cheaters Guide to 3D Maths! 
    D3DXVec3Normalize( &vLight, &vLight );
	
	// Now that we have our light vector, we need to turn it back into a greyscale
	// RGB to use to modulate the texture with. Remember that we've normalized this
	// vector, so .x and .y will be between 0.0 and 1.0f (which is why we need to
	// * 127.0f and + 128.0f). Calculate it yourself, if vLight.x was 0.5f.
    DWORD r = (DWORD)(127.0f * vLight.x + 128.0f);
    DWORD g = (DWORD)(127.0f * vLight.y + 128.0f);
    DWORD b = (DWORD)(127.0f * vLight.z + 128.0f);
	
    DWORD dwFactor = D3DCOLOR_XRGB(r, g, b);
	
	// Set the texture factor to the rgb calculated above. For more info on the texture
	// factor and how it works with the D3DTA_TFACTOR state, refer to DirectX Basics
	// Series 3 tutorial 4.
    g_pDevice->SetRenderState(D3DRS_TEXTUREFACTOR, dwFactor);
	
    // Modulate the texture (the normal map) with the light vector (stored
    // above in the texture factor). From the SDK: "Modulate the components of each argument
	// as signed components, add their products; then replicate the sum to all color
	// channels, including alpha".
    g_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG1, D3DTA_TEXTURE );
    g_pDevice->SetTextureStageState( 0, D3DTSS_COLOROP,   D3DTOP_DOTPRODUCT3 );
    g_pDevice->SetTextureStageState( 0, D3DTSS_COLORARG2, D3DTA_TFACTOR );
	
	// Set the texture to be the normal map
    g_pDevice->SetTexture( 0, g_pNormalMapTexture );
	
	// And finally tell D3D to draw our bumpmapped quad!
	g_pDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, 2);

	// If all that looked too simple to do bumpmapping, don't worry - you're right. Most
	// of the "magic" takes place in the D3DTOP_DOTPRODUCT3 texture stage, which does
	// all the per pixel calculations for us.
	
	// ====================================================================================
	// - Render the mouse last so that Z-ordering keeps the cursor on top
	// ====================================================================================
	
	g_pDIMouse->Render(g_pDevice);

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

	g_pDevice->EndScene();

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


	return S_OK;
}
