//-----------------------------------------------------------------------------
//
// Repeat.cpp
//
// DirectX9 Tiling a part of a texture
//
// see thread 
//   http://www.flipcode.com/cgi-bin/msg.cgi?showThread=00008270&forum=3dtheory&id=-1
//
// Author:   Sylvain Lefebvre [Tride] - 05/26/2003
//-----------------------------------------------------------------------------
#include <Windows.h>
#include <commctrl.h>
#include <math.h>
#include <stdio.h>
#include <D3DX9.h>
#include <iostream>
#include "DXUtil.h"
#include "D3DEnumeration.h"
#include "D3DSettings.h"
#include "D3DApp.h"
#include "D3DFont.h"
#include "D3DUtil.h"
#include "D3DFile.h"
#include "resource.h"
//-----------------------------------------------------------------------------
// D3D vertex format
//-----------------------------------------------------------------------------
struct D3DVERTEX
{
	D3DXVECTOR3 p;
	D3DXVECTOR3 n;
	FLOAT       tu1, tv1;
	FLOAT       tu2, tv2;
	
	static const DWORD FVF;
};
// uses two texture coordinates
const DWORD D3DVERTEX::FVF = D3DFVF_XYZ | D3DFVF_NORMAL | D3DFVF_TEX2;
//-----------------------------------------------------------------------------
// struct for picking
//-----------------------------------------------------------------------------
struct INTERSECTION
{
	DWORD dwFace;                 // mesh face that was intersected
	FLOAT fBary1, fBary2;         // barycentric coords of intersection
	FLOAT fDist;                  // distance from ray origin to intersection
	FLOAT tu, tv;                 // texture coords of intersection
};

#define MAX_INTERSECTIONS 16
//-----------------------------------------------------------------------------
// Application class
//-----------------------------------------------------------------------------
class CMyD3DApplication : public CD3DApplication
{
protected:
	CD3DArcBall             m_ArcBall;
	double                  m_dDist;
	CD3DFont               *m_pFont;
	CD3DMesh               *m_pObject;
	LPDIRECT3DVERTEXBUFFER9 m_pVB;
	bool                    m_bWireframe;
	bool                    m_bUseShader;
	int						m_iTech;
	LPDIRECT3DTEXTURE9      m_pTexture;
	LPD3DXEFFECT            m_pEffect;
	D3DXHANDLE              m_hTechnique;
	D3DXHANDLE              m_hTexture;
	D3DXHANDLE              m_hTileIJ;
	float                   m_TileIJ[2];
	D3DXHANDLE              m_hWorldMatrix;
	D3DXHANDLE              m_hViewMatrix;
	D3DXHANDLE              m_hProjectionMatrix;
	bool                    m_bShowHelp;
	
protected:
	
	HRESULT ConfirmDevice( D3DCAPS9*, DWORD, D3DFORMAT, D3DFORMAT );
	HRESULT Render();
	HRESULT FrameMove();
	HRESULT InitDeviceObjects();
	HRESULT RestoreDeviceObjects();
	HRESULT InvalidateDeviceObjects(); 
	HRESULT DeleteDeviceObjects();
	HRESULT FinalCleanup();
	LRESULT MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
	VOID    SetMenuStates();
	
public:
	
	CMyD3DApplication();
};
//-----------------------------------------------------------------------------
// WinMain
//-----------------------------------------------------------------------------
INT WINAPI WinMain( HINSTANCE hInst, HINSTANCE, LPSTR, INT )
{
	CMyD3DApplication d3dApp;
	
	InitCommonControls();
	if( FAILED( d3dApp.Create( hInst ) ) )
		return 0;
	
	return d3dApp.Run();
}
//-----------------------------------------------------------------------------
// Application constructor. Sets attributes for the app.
//-----------------------------------------------------------------------------
CMyD3DApplication::CMyD3DApplication()
{
	// inherited
	m_strWindowTitle    = _T("Repeat part of a texture");
	m_d3dEnumeration.AppUsesDepthBuffer = TRUE;
	
	// intrinsic
	m_pFont                = new CD3DFont( _T("Arial"), 12, D3DFONT_BOLD );
	m_pObject              = new CD3DMesh();
	
	m_pVB             = NULL;
	m_pTexture		  = NULL;
	m_pEffect         = NULL;
	m_bWireframe      = false;
	m_dDist           = -12.0;
	m_bShowHelp       = false;
	m_bUseShader      = true;

	m_TileIJ[0]=0;
	m_TileIJ[1]=0;
}
//-----------------------------------------------------------------------------
//*****************************************************************************
//                              Direct3D
//*****************************************************************************
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::ConfirmDevice( D3DCAPS9* pCaps, DWORD dwBehavior,
										 D3DFORMAT adapterFormat, D3DFORMAT backBufferFormat )
{
	if( FAILED( m_pD3D->CheckDeviceFormat( pCaps->AdapterOrdinal, pCaps->DeviceType,
		adapterFormat, 0, D3DRTYPE_SURFACE, backBufferFormat ) ) )
	{
		return E_FAIL;
	}
	if( (dwBehavior & D3DCREATE_HARDWARE_VERTEXPROCESSING ) ||
		(dwBehavior & D3DCREATE_MIXED_VERTEXPROCESSING ) )
	{
		if( pCaps->VertexShaderVersion < D3DVS_VERSION(1,1) )
			return E_FAIL;
	}
	
	return S_OK;
}
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InitDeviceObjects()
{
	HRESULT hr;
	TCHAR strPath[MAX_PATH];
	
	m_pFont->InitDeviceObjects( m_pd3dDevice );
	
	// load the object
	if( FAILED( m_pObject->Create( m_pd3dDevice, _T("cube.x") ) ) )
		return D3DAPPERR_MEDIANOTFOUND;
	m_pObject->SetFVF( m_pd3dDevice, D3DVERTEX::FVF );
	m_pObject->UseMeshMaterials(false);
	
	// load the effect file
	if( FAILED( hr = DXUtil_FindMediaFileCb( strPath, sizeof(strPath), TEXT("repeat.fx") ) ) )
		return hr;
	if( FAILED( hr = D3DXCreateEffectFromFile( m_pd3dDevice, strPath, NULL, NULL, D3DXSHADER_DEBUG, NULL, &m_pEffect, NULL) ) )
		return hr;
	
	// save frequently used handles
	
	// -> find highest usable technique
	bool none=true;
	HMENU hMenu = GetMenu( m_hWnd );
	
	if (m_pEffect->ValidateTechnique(m_pEffect->GetTechniqueByName(TEXT("Repeat2_0"))) == D3D_OK)
	{
		m_hTechnique = m_pEffect->GetTechniqueByName(TEXT("Repeat2_0"));
		m_iTech = 20;
		none=false;
	}
	
	if (none)
	{
		MessageBox(NULL,"Your hardware does not support any of the available implementations. Sorry.","Error",MB_OK | MB_ICONSTOP);
		exit (-1);
	}
	
	// get HLSL shader parameters
	m_hTexture          = m_pEffect->GetParameterByName( NULL, TEXT("Tex0") );
	m_hTileIJ           = m_pEffect->GetParameterByName( NULL, TEXT("Tile_ij") );
	m_hWorldMatrix      = m_pEffect->GetParameterBySemantic( NULL, TEXT("WORLD") );
	m_hViewMatrix       = m_pEffect->GetParameterBySemantic( NULL, TEXT("VIEW") );
	m_hProjectionMatrix = m_pEffect->GetParameterBySemantic( NULL, TEXT("PROJECTION") );
	
	// make sure the render pass options are set in the menu
	SetMenuStates();
	
	return S_OK;
}
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::RestoreDeviceObjects()
{
	WORD* pBaseIndex = NULL;
	
	m_pFont->RestoreDeviceObjects();
	
	// set ArcBall parameters
	m_ArcBall.SetWindow( m_d3dsdBackBuffer.Width, m_d3dsdBackBuffer.Height, 2.0f );
	m_ArcBall.SetRadius( 10.0f );
	
	if( FAILED( D3DUtil_CreateTexture( m_pd3dDevice, "tiles.bmp", 
		&m_pTexture, D3DFMT_A8R8G8B8 ) ) )
	{
		return D3DAPPERR_MEDIANOTFOUND;
	}
	
    // restore device-memory objects for the mesh
	m_pObject->RestoreDeviceObjects(m_pd3dDevice);
	
	// create the vertex buffer for picking
	DWORD dwNumVertices = MAX_INTERSECTIONS * 3;
	if( FAILED( m_pd3dDevice->CreateVertexBuffer( dwNumVertices*sizeof(D3DVERTEX),
		D3DUSAGE_WRITEONLY, D3DVERTEX::FVF,
		D3DPOOL_MANAGED, &m_pVB, NULL ) ) )
		return E_FAIL;
	
	// set miscellaneous render states
	m_pd3dDevice->SetRenderState( D3DRS_DITHERENABLE,   FALSE );
	m_pd3dDevice->SetRenderState( D3DRS_SPECULARENABLE, FALSE );
	m_pd3dDevice->SetRenderState( D3DRS_LIGHTING, FALSE );
	
	// set the world matrix
	D3DXMATRIXA16 matIdentity;
	D3DXMatrixIdentity( &matIdentity );
	m_pd3dDevice->SetTransform( D3DTS_WORLD,  &matIdentity );
	m_pEffect->SetMatrix( m_hWorldMatrix, &matIdentity );
	
	// set the projection matrix
	D3DXMATRIXA16 matProj;
	FLOAT fAspect = ((FLOAT)m_d3dsdBackBuffer.Width) / m_d3dsdBackBuffer.Height;
	D3DXMatrixPerspectiveFovLH( &matProj, D3DX_PI/4, fAspect, 1.0f, 100.0f );
	m_pd3dDevice->SetTransform( D3DTS_PROJECTION, &matProj );
	m_pEffect->SetMatrix( m_hProjectionMatrix, &matProj );
	
	// warn effect
	if( m_pEffect != NULL )
		m_pEffect->OnResetDevice();
	
	// update menus
	SetMenuStates();
	
	return S_OK;
}
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::InvalidateDeviceObjects()
{
	m_pFont->InvalidateDeviceObjects();
	SAFE_RELEASE( m_pVB );
	if( m_pTexture )
	{
		SAFE_RELEASE( m_pTexture );
	}
	
	if( m_pEffect != NULL )
		m_pEffect->OnLostDevice();
	
	m_pObject->InvalidateDeviceObjects();
	
	return S_OK;
}
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::DeleteDeviceObjects()
{
	m_pFont->DeleteDeviceObjects();
	SAFE_RELEASE( m_pEffect );
	m_pObject->Destroy();
	return S_OK;
}
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FinalCleanup()
{
	SAFE_DELETE(m_pFont);
	SAFE_DELETE(m_pObject);
	return S_OK;
}
//-----------------------------------------------------------------------------
// Move the viewpoint
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::FrameMove()
{
	D3DXMATRIXA16 matView;
	
	D3DXVECTOR3 vFromPt   = D3DXVECTOR3( m_dDist, 0.0f, 0.0f );
	D3DXVECTOR3 vLookatPt = D3DXVECTOR3( 0.0f, 0.0f, 0.0f );
	D3DXVECTOR3 vUpVec    = D3DXVECTOR3( 0.0f, 1.0f, 0.0f );
	
	D3DXVec3TransformCoord(&vFromPt,&vFromPt,m_ArcBall.GetRotationMatrix());
	D3DXVec3TransformCoord(&vFromPt,&vFromPt,m_ArcBall.GetTranslationMatrix());
	
	D3DXMatrixLookAtLH( &matView, &vFromPt, &vLookatPt, &vUpVec );
	m_pd3dDevice->SetTransform( D3DTS_VIEW, &matView );
	m_pEffect->SetMatrix( m_hViewMatrix, &matView );
	
	return S_OK;
}
//-----------------------------------------------------------------------------
// Render the scene
//-----------------------------------------------------------------------------
HRESULT CMyD3DApplication::Render()
{
	
	// clear
	m_pd3dDevice->Clear( 0L, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER, 
		0x000000ff, 1.0f, 0L );
	
	// render
	if( SUCCEEDED( m_pd3dDevice->BeginScene() ) )
	{
		UINT numPasses;
		
		// draw solid object
		m_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_SOLID );
		// using effect
		if (m_bUseShader)
		{
			m_pEffect->SetTechnique( m_hTechnique );
			m_pEffect->Begin( &numPasses, 0 );
			for( UINT iPass = 0; iPass < numPasses; iPass++ )
			{
				m_pEffect->Pass( iPass );
				m_pEffect->SetTexture( m_hTexture, m_pTexture );
				m_pEffect->SetValue( m_hTileIJ, m_TileIJ, 2*sizeof(float));
				// render the mesh
				m_pObject->Render( m_pd3dDevice );
			}
			m_pEffect->End();
		}
		else
		{
			m_pd3dDevice->SetTexture(0,m_pTexture);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_MAGFILTER,D3DTEXF_LINEAR);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_MINFILTER,D3DTEXF_LINEAR);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_MIPFILTER,D3DTEXF_LINEAR);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_ADDRESSU,D3DTADDRESS_WRAP);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_ADDRESSV,D3DTADDRESS_WRAP);
			m_pd3dDevice->SetSamplerState(0,D3DSAMP_ADDRESSW,D3DTADDRESS_WRAP);
			m_pd3dDevice->SetTextureStageState(0,D3DTSS_TEXTURETRANSFORMFLAGS,D3DTTFF_COUNT2);
			// set a transform matrix for repeating the texture at least 6 times
			// (it is also done in the vertex program - see repeat.fx)
			D3DXMATRIXA16 matScale;
			D3DXMatrixScaling(&matScale,6.0f,6.0f,6.0f);
			m_pd3dDevice->SetTransform(D3DTS_TEXTURE0,&matScale);
			// render the mesh
			m_pObject->Render( m_pd3dDevice );
		}
		
		if (m_bWireframe)
		{
			// draw wireframe
			m_pd3dDevice->SetRenderState( D3DRS_FILLMODE, D3DFILL_WIREFRAME );
			m_pObject->Render( m_pd3dDevice );
		}
		
		// Dump out the FPS
		m_pFont->DrawText( 2,  0, D3DCOLOR_ARGB(255,255,255,0), m_strFrameStats );
		m_pFont->DrawText( 2, 20, D3DCOLOR_ARGB(255,255,255,0), m_strDeviceStats );
		// and tile number
		static char str[128];
		sprintf(str,"Tile [%d,%d]",(int)m_TileIJ[0],(int)m_TileIJ[1]);
		m_pFont->DrawText( 2, 40, D3DCOLOR_ARGB(255,255,255,255), str );
		
		if( m_bShowHelp )
		{
			m_pFont->DrawText(  2, 60, D3DCOLOR_ARGB(255,0,0,0),
				_T("Keyboard controls:") );
			m_pFont->DrawText( 20, 80, D3DCOLOR_ARGB(255,0,0,0),
				_T("SPACE   disable/enable repeat shader\n" )
				_T("[n]     next tile\n" ));
		}
		else
		{
			m_pFont->DrawText(  2, 60, D3DCOLOR_ARGB(255,0,0,0), 
				_T("Press F1 for help") );
		}
		
		m_pd3dDevice->EndScene();
	}
	
	return S_OK;
}
//-----------------------------------------------------------------------------
//*****************************************************************************
//                                  Windows
//*****************************************************************************
//-----------------------------------------------------------------------------
LRESULT CMyD3DApplication::MsgProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
	// Pass mouse messages to the ArcBall so it can build internal matrices
	m_ArcBall.HandleMouseMessages( hWnd, uMsg, wParam, lParam );
	// Handle key presses
	switch( uMsg )
	{
		
	case WM_RBUTTONDOWN:
		
		break;
		
	case WM_RBUTTONUP:
		
		break;
		
	case WM_KEYDOWN:
		
		break;
		
	case WM_CHAR:
		if (wParam == ' ')
			m_bUseShader=!m_bUseShader;
		if (wParam == 'n')
		{
			m_TileIJ[0]+=1.0;
			if (m_TileIJ[0] > 3)
			{
				m_TileIJ[0]=0;
				m_TileIJ[1]+=1.0;
				if (m_TileIJ[1] > 3)
					m_TileIJ[1]=0.0;
			}
		}
		break;
		
	case WM_COMMAND:
		{
			switch( LOWORD(wParam) )
			{
            case IDM_WIREFRAME:
                m_bWireframe = !m_bWireframe;
                break;
			case IDM_TOGGLEHELP:
				m_bShowHelp=!m_bShowHelp;
				break;
			case IDM_OPENFILE:
				TCHAR g_strFilename[512]   = _T("");
				
				// Display the OpenFileName dialog. Then, try to load the specified file
				OPENFILENAME ofn = { sizeof(OPENFILENAME), NULL, NULL,
					_T(".X Files (.x)\0*.x\0\0"), 
					NULL, 0, 1, NULL, 0, g_strFilename, 512, 
					".", _T("Open Mesh File"), 
					OFN_FILEMUSTEXIST, 0, 1, NULL, 0, NULL, NULL };
				
				if( TRUE == GetOpenFileName( &ofn ) )
				{
					// Load the object
					m_pObject->Destroy();
					SAFE_DELETE(m_pObject);
					m_pObject = new CD3DMesh();
					if( FAILED( m_pObject->Create( m_pd3dDevice, _T(g_strFilename) ) ) )
					{
						m_pObject->Create( m_pd3dDevice,"cube.x");
						MessageBox(NULL,"Unable to load mesh file","Error",MB_OK | MB_ICONSTOP);
					}
					m_pObject->SetFVF( m_pd3dDevice, D3DVERTEX::FVF );
					m_pObject->UseMeshMaterials(false);
					m_pObject->RestoreDeviceObjects(m_pd3dDevice);
				}
				break;
				
			}
			// Update the menus, in case any state changes occurred
			SetMenuStates();
		}
	}
	
	// Pass remaining messages to default handler
	return CD3DApplication::MsgProc( hWnd, uMsg, wParam, lParam );
}
//-----------------------------------------------------------------------------
void CMyD3DApplication::SetMenuStates()
{
	HMENU hMenu = GetMenu( m_hWnd );
	
    CheckMenuItem( hMenu, IDM_WIREFRAME,
		m_bWireframe ? MF_CHECKED : MF_UNCHECKED );
    CheckMenuItem( hMenu, IDM_T13,
		m_iTech == 13 ? MF_CHECKED : MF_UNCHECKED );
    CheckMenuItem( hMenu, IDM_T14,
		m_iTech == 14 ? MF_CHECKED : MF_UNCHECKED );
    CheckMenuItem( hMenu, IDM_T20,
		m_iTech == 20 ? MF_CHECKED : MF_UNCHECKED );
}
//-----------------------------------------------------------------------------
