   //  
  // PC Underground: DX9 - Depth of Field
 // (w)(c)2004 Carsten Dachsbacher
//

#include <windows.h>
#include <d3dx9.h>
#include "resource.h"

// vereinfachtes Single-Pass Verfahren
//#define SINGLE_PASS

const bool  DX9Fullscreen = FALSE;
const DWORD	DX9ScreenX    = 512;
const DWORD	DX9ScreenY    = 512;
const DWORD DX9Refresh    = 75;

const char windowTitle[] = "PCU Depth of Field - (w)(c)2004 Carsten Dachsbacher";

#define	RENDERTOSURFACE_WIDTH	512
#define	RENDERTOSURFACE_HEIGHT	512

HWND					gHWND      = NULL;
LPDIRECT3D9				pD3D       = NULL;
LPDIRECT3DDEVICE9		pD3DDevice = NULL;

LPD3DXEFFECT            pEffect        = NULL;

LPDIRECT3DTEXTURE9      pDynamicTexture5  = NULL;
LPDIRECT3DSURFACE9      pTextureSurface5  = NULL;
LPDIRECT3DTEXTURE9      pDynamicTexture4  = NULL;
LPDIRECT3DSURFACE9      pTextureSurface4  = NULL;
LPDIRECT3DTEXTURE9      pDynamicTexture3  = NULL;
LPDIRECT3DSURFACE9      pTextureSurface3  = NULL;
LPDIRECT3DTEXTURE9      pDynamicTexture2  = NULL;
LPDIRECT3DSURFACE9      pTextureSurface2  = NULL;
LPDIRECT3DTEXTURE9      pDynamicTexture1  = NULL;
LPDIRECT3DSURFACE9      pTextureSurface1  = NULL;

LPDIRECT3DTEXTURE9      pDiffuseTexture = NULL;

LPD3DXFONT				pD3DXFont       = NULL;

#include <stdio.h>
#include "vertex3dop.h"
#include "obj.h"

float focalPlane = 25.0f;

  //
 // prototypes
//
int WINAPI WinMain( HINSTANCE hInst, HINSTANCE hPrevInst,
                    LPSTR lpCmdLine, int nCmdShow );

void	initialize3D();
void	shutdown3D();
void	render3D();

  //
 // WindowProc: Window Class Message Handler
//
LRESULT CALLBACK WindowProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam )
{
    switch( msg )
	{
        case WM_KEYDOWN:
			if ( wParam == VK_ESCAPE )
				PostQuitMessage(0);

			if ( wParam == VK_RIGHT )
				focalPlane += 0.25f;
			if ( wParam == VK_LEFT )
				focalPlane -= 0.25f;

	        break;

		case WM_CLOSE:
        case WM_DESTROY:
            PostQuitMessage(0);
	        break;

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

	return 0;
}

  //
 // WinMain: Register Window Class, Create Window and wait until program end
//
int WINAPI WinMain( HINSTANCE hInst,
                    HINSTANCE hPrevInst,
                    LPSTR     lpCmdLine,
                    int       nCmdShow )
{
	WNDCLASSEX wndClass;

	wndClass.lpszClassName = "PCUvsD3D9";
	wndClass.cbSize        = sizeof( WNDCLASSEX );
	wndClass.style         = CS_HREDRAW | CS_VREDRAW;
	wndClass.lpfnWndProc   = WindowProc;
	wndClass.cbClsExtra    = 0;
	wndClass.cbWndExtra    = 0;
	wndClass.hInstance     = hInst;
	wndClass.hIcon	       = LoadIcon( hInst, (LPCTSTR)IDI_PCUICON );
	wndClass.hCursor       = LoadCursor( NULL, IDC_ARROW );
	wndClass.hbrBackground = (HBRUSH)GetStockObject( BLACK_BRUSH );
    wndClass.hIconSm	   = NULL;
	wndClass.lpszMenuName  = NULL;

	if( RegisterClassEx( &wndClass) == 0 ) return E_FAIL;

	DWORD windowParam;

	if ( DX9Fullscreen )
		windowParam = WS_POPUP | WS_SYSMENU | WS_VISIBLE; else
		windowParam = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

	gHWND = CreateWindowEx( NULL, "PCUvsD3D9", windowTitle,
							 windowParam, 0, 0, 
							 DX9ScreenX, DX9ScreenY, 
							 NULL, NULL, hInst, NULL );

	if( gHWND == NULL ) return E_FAIL;

    ShowWindow( gHWND, nCmdShow );
    UpdateWindow( gHWND );

	initialize3D();
  
	MSG msg;
	ZeroMemory( &msg, sizeof( msg ) );

	while( msg.message != WM_QUIT )
	{
		if( PeekMessage( &msg, NULL, 0, 0, PM_REMOVE ) )
		{ 
			TranslateMessage( &msg );
			DispatchMessage ( &msg );
		} else
		    render3D();
	}

	shutdown3D();

    UnregisterClass( "PCUvsD3D9", wndClass.hInstance );

	return msg.wParam;
}


  //
 // error: Display error message and quit program
//
void error( char *msg = NULL )
{
	if ( msg )
		MessageBox( NULL, msg, "PCUvsDX9", MB_OK | MB_ICONEXCLAMATION ); else
		MessageBox( NULL, "Error occured !", "PCUvsDX9", MB_OK | MB_ICONEXCLAMATION );

	shutdown3D();

	exit( 1 );
}

OBJmodel *obj3D;

void initializeEffect( void )
{
	// look up texture laden !	
	D3DXCreateTextureFromFile( pD3DDevice, "./data/mbasemap.bmp", &pDiffuseTexture );
	
	HRESULT hr;
	LPD3DXBUFFER pBufferErrors = NULL;

	hr = D3DXCreateEffectFromFile( pD3DDevice, 
		                           "dof.fx",
		                           NULL, 
		                           NULL, 
		                           0, 
		                           NULL, 
		                           &pEffect, 
		                           &pBufferErrors );

	if( FAILED(hr) )
	{
		LPVOID pCompileErrors = pBufferErrors->GetBufferPointer();
		MessageBox(NULL, (const char*)pCompileErrors, "Fx Compile Error",
			MB_OK|MB_ICONEXCLAMATION);
	}
	
}

void createFont( void )
{
    HDC   hDC;
    int   fontHeight;
    int   fontPointSize = 10;

    hDC = GetDC( NULL );

    fontHeight = -( MulDiv( fontPointSize, GetDeviceCaps( hDC, LOGPIXELSY ), 72 ) );

    ReleaseDC( NULL, hDC );

    D3DXCreateFont( pD3DDevice, 
		fontHeight, 
        0, 
        FW_DONTCARE,
        0, false, 
        DEFAULT_CHARSET,
        OUT_DEFAULT_PRECIS,
        DEFAULT_QUALITY,
        DEFAULT_PITCH | FF_DONTCARE,
        "Tahoma",
		&pD3DXFont );
}


void createRenderTarget( int width, int height, LPDIRECT3DTEXTURE9 *pDynamicTexture, LPDIRECT3DSURFACE9 *pTextureSurface, D3DFORMAT format, int additionalFlags = 0 )
{
	D3DXCreateTexture( pD3DDevice, 
		                    width, 
		                    height, 
							1, 
							D3DUSAGE_RENDERTARGET | additionalFlags, 
							format, 
							D3DPOOL_DEFAULT, 
							pDynamicTexture );

	// get render-to surface
    (*pDynamicTexture)->GetSurfaceLevel( 0, pTextureSurface );
}



  //
 // initialize3D: initialize Direct3D stuff
//
void initialize3D( void )
{
	HRESULT			hr;
	D3DDISPLAYMODE	dm;

    pD3D = Direct3DCreate9( D3D_SDK_VERSION );

	if( pD3D == NULL ) error( "Error creating Direct3D Object" );

	if ( DX9Fullscreen )
	{
		// fullscreen
		//
		int nMaxModes = pD3D->GetAdapterModeCount( D3DADAPTER_DEFAULT, D3DFMT_X8R8G8B8 );

		bool foundMode = false;

		for( int m = 0; m < nMaxModes; m ++ )
		{
		  if ( FAILED( pD3D->EnumAdapterModes( D3DADAPTER_DEFAULT, 
											   D3DFMT_X8R8G8B8, m, &dm ) ) )
			 error( "Error enumerating adapter mode" );

		  if ( dm.Width  != DX9ScreenX || 
			   dm.Height != DX9ScreenY ||
			   dm.RefreshRate != DX9Refresh ||
			   dm.Format != D3DFMT_X8R8G8B8 )
			   continue;

		  foundMode = true;
		  break;
		}

		if ( foundMode == false )
		  error( "Not suitable graphic mode found !" );

		if ( FAILED( pD3D->CheckDeviceType( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL,
											D3DFMT_X8R8G8B8, D3DFMT_X8R8G8B8, FALSE ) ) )
		   error( "No hardware acceleration for this graphic mode !" );
	} else
	{
		// windowed
		//
		if( FAILED( pD3D->GetAdapterDisplayMode( D3DADAPTER_DEFAULT, &dm ) ) )
			error( "Error getting Adapter Display Mode" );
	}

	hr = pD3D->CheckDeviceFormat( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, 
								  dm.Format, D3DUSAGE_DEPTHSTENCIL,
								  D3DRTYPE_SURFACE, D3DFMT_D16 );

	if ( hr == D3DERR_NOTAVAILABLE ) error( "Desired Z-Buffer Format (16Bit) not available" );

	D3DCAPS9 caps;

	if( FAILED( pD3D->GetDeviceCaps( D3DADAPTER_DEFAULT, 
									 D3DDEVTYPE_HAL, &caps ) ) )
		error( "Error reading Device Caps" );
	
	DWORD flags = 0;

	if( caps.VertexProcessingCaps != 0 )
		flags |= D3DCREATE_HARDWARE_VERTEXPROCESSING;
	else
		flags |= D3DCREATE_SOFTWARE_VERTEXPROCESSING;

	D3DPRESENT_PARAMETERS pp;
	ZeroMemory( &pp, sizeof( pp ) );

	pp.BackBufferFormat       = dm.Format;
	pp.SwapEffect             = D3DSWAPEFFECT_DISCARD;
	pp.Windowed               = !DX9Fullscreen;
	pp.EnableAutoDepthStencil = TRUE;
	pp.AutoDepthStencilFormat = D3DFMT_D16;
	pp.BackBufferWidth        = DX9ScreenX;
    pp.BackBufferHeight       = DX9ScreenY;
    pp.BackBufferFormat       = D3DFMT_X8R8G8B8;

	if( FAILED( pD3D->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, gHWND,
									  flags, &pp, &pD3DDevice ) ) )
		error( "Error creating Direct3D device" );


	//
	// Init Demo Stuff
	//

	pD3DDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_NONE );

	pD3DDevice->SetRenderState( D3DRS_ZENABLE, TRUE );
	pD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_LESSEQUAL );

	pD3DDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );

	obj3D = new OBJmodel();
	obj3D->readOBJ( "./data/beethoven.obj" );

	obj3D->scaleIsotropic();
	obj3D->buildVertexBuffer( pD3DDevice, RENDER_SMOOTH_SHADED );

	//
	// Vertex/Pixel Shader
	//
	if ( caps.PixelShaderVersion < D3DVS_VERSION(2,0) )
		exit( 1 );

	if ( caps.VertexShaderVersion < D3DVS_VERSION(1,1) )
		exit( 1 );


	initializeEffect();
	createFont();

	//
	// create dynamic render-to textures
	//
	createRenderTarget( RENDERTOSURFACE_WIDTH, RENDERTOSURFACE_HEIGHT, &pDynamicTexture1, &pTextureSurface1, D3DFMT_A8R8G8B8, D3DUSAGE_AUTOGENMIPMAP );
	createRenderTarget( RENDERTOSURFACE_WIDTH/4, RENDERTOSURFACE_HEIGHT/4, &pDynamicTexture2, &pTextureSurface2, D3DFMT_A8R8G8B8 );
	createRenderTarget( RENDERTOSURFACE_WIDTH, RENDERTOSURFACE_HEIGHT, &pDynamicTexture3, &pTextureSurface3, D3DFMT_A8R8G8B8 );
	createRenderTarget( RENDERTOSURFACE_WIDTH, RENDERTOSURFACE_HEIGHT, &pDynamicTexture4, &pTextureSurface4, D3DFMT_A8R8G8B8 );
	createRenderTarget( RENDERTOSURFACE_WIDTH, RENDERTOSURFACE_HEIGHT, &pDynamicTexture5, &pTextureSurface5, D3DFMT_A8R8G8B8 );
}

typedef struct
{
	float x, y, z, u, v;
}TEXTUREDVERTEX;

TEXTUREDVERTEX screenQuad[] =
{
	{ -1, -1, 0, 0, 1 },
	{ -1,  1, 0, 0, 0 },
	{  1, -1, 0, 1, 1 },
	{  1,  1, 0, 1, 0 },
};

D3DXMATRIX mProjection;
D3DXMATRIX matWorld;
D3DXMATRIX matTrans;
D3DXMATRIX matView;

//
// Render Complete Scene
//
void	renderObject( float x, float y, float z )
{
	D3DXMatrixTranslation( &matWorld, x, y, z );
    pD3DDevice->SetTransform( D3DTS_WORLD, &matWorld );

    D3DXMATRIX modelViewProjection = matWorld * matView * mProjection;
	D3DXMatrixTranspose( &modelViewProjection, &modelViewProjection );

	pEffect->SetMatrix( "matWVP", &modelViewProjection );
	pEffect->SetTexture( "diffuseTexture", pDiffuseTexture );
	
    D3DXMATRIX model2world = matWorld;
	D3DXMatrixTranspose( &model2world, &model2world );
	pEffect->SetMatrix( "matWV", &model2world );

	UINT nPasses;
    pEffect->Begin( &nPasses, 0 );
    
    for( UINT p = 0; p < nPasses; p++ )
    {
        pEffect->Pass( p );

		obj3D->drawModel( pD3DDevice );
    }
 
    pEffect->End();
}

void	renderScene()
{
	// set up matrices
	float zNear = 3.0f, zFar = 40.0f;

	D3DXMatrixPerspectiveFovLH( &mProjection, 45.0f, (float)DX9ScreenX/(float)DX9ScreenY, zNear, zFar );
	pD3DDevice->SetTransform( D3DTS_PROJECTION, &mProjection );

	focalPlane = min( zFar, max( zNear, focalPlane ) );
	D3DXVECTOR4 distances = D3DXVECTOR4( zNear, focalPlane, zFar, 1.0f );
	pEffect->SetVector( "distances", &distances );

	float time = GetTickCount() * 0.0003f;
	//time = 0.5f;
    float rotateZ = 0.0f;
	float rotateY = 0.0f;
	float rotateX = time;
	
    D3DXMatrixIdentity( &matView );

	D3DXVECTOR4 camDst = D3DXVECTOR4( 0.0f, -2.0f, 0.0f, 0.0f );

	D3DXVECTOR4 camPos = D3DXVECTOR4( cosf( time ) * 24.0f, 5.0f, sinf( time ) * 24.0f, 1.0 );
	D3DXMatrixLookAtLH( &matView, 
		(D3DXVECTOR3*)&camPos,
		(D3DXVECTOR3*)&camDst,
		&D3DXVECTOR3( 0.0f, 1.0f, 0.0f ) );

	D3DXVECTOR4 camDir = camDst - camPos;
	D3DXVec4Normalize( &camDir, &camDir );
	pEffect->SetVector( "cameraDirection", &camDir );
	pEffect->SetVector( "cameraPosition", &camPos );
	D3DXVECTOR4 lightPos = D3DXVECTOR4( 0.0f, 4.0f, 4.0f, 1.0 );
	pEffect->SetVector( "lightPosition", &lightPos );

	renderObject( 0.0f, 0.0f, 0.0f );

	for ( int i = 0; i < 5; i++ )
	{
		renderObject( cosf( (float)i / 2.5f * 3.1415f ) * 12.0f, 
					  0.0f,
					  sinf( (float)i / 2.5f * 3.1415f ) * 12.0f );
	}


}

// render screen aligned quad
void	renderScreenQuad()
{
    pD3DDevice->SetFVF( D3DFVF_XYZ|D3DFVF_TEX1 );

	pD3DDevice->SetRenderState( D3DRS_CULLMODE, D3DCULL_CCW );
	pD3DDevice->DrawPrimitiveUP( D3DPT_TRIANGLESTRIP, 2, screenQuad, sizeof( TEXTUREDVERTEX ) );
}


  //
 // render3D: render loop
//
void render3D()
{
	pD3DDevice->SetRenderState( D3DRS_LIGHTING,			FALSE );
	pD3DDevice->SetRenderState( D3DRS_ALPHABLENDENABLE, FALSE );
	pD3DDevice->SetRenderState( D3DRS_ZENABLE,			TRUE );
	pD3DDevice->SetRenderState( D3DRS_ZWRITEENABLE,		TRUE );
	pD3DDevice->SetRenderState( D3DRS_ZFUNC,			D3DCMP_LESSEQUAL );

	//
	// pass 1: render scene to rendertarget
	//
    pD3DDevice->BeginScene();

	HRESULT hres;

	LPDIRECT3DSURFACE9	lpBackBuffer;
	pD3DDevice->GetRenderTarget( 0, &lpBackBuffer );
	lpBackBuffer->Release();

	hres  = pD3DDevice->SetRenderTarget( 0, pTextureSurface1 );

    pD3DDevice->Clear( 0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
                       0x00000000, 1.0f, 0 );

	// set technique
	pEffect->SetTechnique( "InitialPass" );

	renderScene();

	//
	// pass 2: downsample backbuffer to lower resolution
	//
	hres  = pD3DDevice->SetRenderTarget( 0, pTextureSurface2 );

	pEffect->SetTechnique( "Downsample" );
	pEffect->SetTexture( "backBufferHigh", pDynamicTexture1 );

	UINT nPasses;
    pEffect->Begin( &nPasses, 0 );
    
    for( UINT p = 0; p < nPasses; p++ )
    {
        pEffect->Pass( p );
		renderScreenQuad();
    }
 
    pEffect->End();

#ifdef SINGLE_PASS

	pD3DDevice->SetRenderTarget( 0, lpBackBuffer );

	pEffect->SetTechnique( "DepthOfField_SinglePass" );
	pEffect->SetTexture( "backBufferHigh", pDynamicTexture1 );
	pEffect->SetTexture( "backBufferLow",  pDynamicTexture2 );

    pEffect->Begin( NULL, 0 );
    pEffect->Pass( 0 );
	renderScreenQuad();
    pEffect->End();
#else

	//
	// pass 3: depth of field !
	//
	pEffect->SetTechnique( "DepthOfField" );
	pEffect->SetTexture( "backBufferHigh", pDynamicTexture1 );
	pEffect->SetTexture( "backBufferLow",  pDynamicTexture2 );

	pD3DDevice->SetRenderTarget( 0, pTextureSurface3 );
    pEffect->Begin( NULL, 0 );
    pEffect->Pass( 0 );
	renderScreenQuad();
    pEffect->End();

	pD3DDevice->SetRenderTarget( 0, pTextureSurface4 );
    pEffect->Begin( NULL, 0 );
    pEffect->Pass( 0 );
	renderScreenQuad();
    pEffect->End();

	pD3DDevice->SetRenderTarget( 0, pTextureSurface5 );
    pEffect->Begin( NULL, 0 );
    pEffect->Pass( 0 );
	renderScreenQuad();
    pEffect->End();

	// pass 4: blend all together
	pEffect->SetTechnique( "BlendAll" );

	pD3DDevice->SetRenderTarget( 0, lpBackBuffer );
	pEffect->SetTexture( "blend1", pDynamicTexture3 );
	pEffect->SetTexture( "blend2", pDynamicTexture4 );
	pEffect->SetTexture( "blend3", pDynamicTexture5 );

    pEffect->Begin( NULL, 0 );
    pEffect->Pass( 0 );
	renderScreenQuad();
    pEffect->End();
#endif

	if ( pD3DXFont )
	{
		pD3DDevice->SetRenderState( D3DRS_ZFUNC, D3DCMP_ALWAYS );

		RECT r1 = { 5, DX9ScreenY - 20, DX9ScreenX - 5, DX9ScreenX };
		RECT r2 = { 5, DX9ScreenY - 40, DX9ScreenX - 5, DX9ScreenX };

		char buf[ 256 ];
		sprintf( buf, "cursor left/right to adjust focal plane: %2.2f\0", focalPlane );
		pD3DXFont->DrawText( NULL, buf, -1, &r1, DT_SINGLELINE, D3DCOLOR_COLORVALUE(1.0f, 1.0f, 1.0f, 1.0f) );
		pD3DXFont->DrawText( NULL, "PCU Depth of Field Demo\0", -1, &r2, DT_SINGLELINE, D3DCOLOR_COLORVALUE(1.0f, 1.0f, 1.0f, 1.0f) );
	}


    pD3DDevice->EndScene();

    pD3DDevice->Present( NULL, NULL, NULL, NULL );
}

  //
 // shutdown3D: free Direct3D ressources
//
void shutdown3D()
{
	if ( pDynamicTexture1 != NULL )	pDynamicTexture1->Release();
	if ( pTextureSurface1 != NULL )	pTextureSurface1->Release();
	if ( pDynamicTexture2 != NULL )	pDynamicTexture2->Release();
	if ( pTextureSurface2 != NULL )	pTextureSurface2->Release();
	if ( pDynamicTexture3 != NULL )	pDynamicTexture3->Release();
	if ( pTextureSurface3 != NULL )	pTextureSurface3->Release();
	if ( pDynamicTexture4 != NULL )	pDynamicTexture4->Release();
	if ( pTextureSurface4 != NULL )	pTextureSurface4->Release();
	if ( pDynamicTexture5 != NULL )	pDynamicTexture5->Release();
	if ( pTextureSurface5 != NULL )	pTextureSurface5->Release();

	delete obj3D;

	
	if ( pEffect ) pEffect->Release();
	if ( pDiffuseTexture ) pDiffuseTexture->Release();
	if ( pD3DXFont ) pD3DXFont->Release();

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

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