// BKScreenD3D.cpp: определяет экспортированные функции для приложения DLL.
//

#include "stdafx.h"
#include "BKScreenD3D.h"

#include "BKMessageBox.h"


#pragma comment (lib,"d3d9.lib")
#pragma comment (lib,"D3dx9.lib")
#ifdef _DEBUG
#include <DxErr.h>
#pragma comment (lib,"DxErr.lib")
#endif

// CScreenD3D
BKEXTERN_C
{
	BKSCRDLL_API BKSCREENHANDLE APIENTRY GetBKScreen()
	{
		return new CScreenD3D;
	}

	CScreenD3D::CScreenD3D(void) : CBKScreen_Shared()
		, m_pD3D(NULL)
		, m_pd3dDevice(NULL)
		, m_pVB(NULL)
		, m_pTexture(NULL)
	{
	}

	CScreenD3D::~CScreenD3D(void)
	{
	}


#define VLINES_NUM 2
#define POINTS_NUM (VLINES_NUM *2)
#define TRIANGLES_NUM (2*(VLINES_NUM-1))
// обработчики сообщений CScreenD3D


	void CScreenD3D::BKSS_OnSize(int cx, int cy)
	{
		if (m_hwndScreen)
		{
			CWnd::FromHandlePermanent(m_hwndScreen)->GetClientRect(&m_rectWnWnd);
		}
	}

	HRESULT CScreenD3D::BKSS_ScreenView_Init(ScrsParams & sspar, CWnd * pwndScr, CWnd * pwndFullScr)
	{
		CBKScreen_Shared::BKSS_ScreenView_Init(sspar, pwndScr, pwndFullScr);

		if (!m_hwndScreen)
		{
			return -1;
		}

		// Создаём D3D объект.
		if (NULL == (m_pD3D = Direct3DCreate9(D3D_SDK_VERSION)))
		{
			return E_FAIL;
		}

		//получим параметры оконного режима
		m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &m_d3dwindm);
		//а теперь получим параметры полноэкранного режима, это будет так же текущий режим дисплея
		m_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &m_d3dfsdm);

		if (m_bFullScreen)
		{
			setFSparam();
		}
		else
		{
			setWNDparam();
		}

		//создаём битмап
		SAFE_DELETE_ARRAY(m_bits); //на случай если будем пересоздавать этой функцией же.
		m_bits = new DWORD[m_nTextureWidth * m_nTextureHeight];
		ZeroMemory(m_bits, m_nTextureWidth * m_nTextureHeight * sizeof(DWORD));
		sspar.pBits = m_bits;
		HRESULT hr;
#ifdef _DEBUG
		const TCHAR *errstr;
		const TCHAR *errdesc;
#endif

		// Создаём D3DDevice
		if (FAILED(hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, m_hwndScreen,
		                                     D3DCREATE_MULTITHREADED | D3DCREATE_HARDWARE_VERTEXPROCESSING,
		                                     &m_d3dpp, &m_pd3dDevice)))
		{
			//если не удалось полностью хардварное, пробуем софтварное, с использованием специнструкций проца
#ifdef _DEBUG
			errstr = DXGetErrorString(hr);
			errdesc = DXGetErrorDescription(hr);
#endif

			if (FAILED(hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_REF, m_hwndScreen,
			                                     D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING,
			                                     &m_d3dpp, &m_pd3dDevice)))
			{
				//если и такое не удалось, пробуем чисто софтварное
#ifdef _DEBUG
				errstr = DXGetErrorString(hr);
				errdesc = DXGetErrorDescription(hr);
#endif

				if (FAILED(hr = m_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_SW, m_hwndScreen,
				                                     D3DCREATE_MULTITHREADED | D3DCREATE_SOFTWARE_VERTEXPROCESSING,
				                                     &m_d3dpp, &m_pd3dDevice)))
				{
#ifdef _DEBUG
					errstr = DXGetErrorString(hr);
					errdesc = DXGetErrorDescription(hr);
#endif
					CBKMessageBox(_T("Не создаётся экран."), MB_OK);
				}
			}
		}

		if (SUCCEEDED(hr))
		{
			setDeviceParam();
			m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET | D3DCLEAR_ZBUFFER,
			                    D3DCOLOR_XRGB(0, 0, 0), 1.0f, 0);
			m_pd3dDevice->Present(NULL, NULL, NULL, NULL); // показ вторичного буфера

			if SUCCEEDED(createtexture())
			{
				return hr = S_OK;
			}
		}

		return hr;
	}

	void CScreenD3D::BKSS_ScreenView_Done()
	{
		m_pd3dDevice->SetTexture(0, NULL);
		SAFE_RELEASE(m_pTexture);
		SAFE_RELEASE(m_pVB);
		SAFE_RELEASE(m_pd3dDevice);
		SAFE_RELEASE(m_pD3D);
		SAFE_DELETE_ARRAY(m_bits);
	}

	void CScreenD3D::BKSS_DrawScreen()
	{
		HRESULT hr = m_pd3dDevice->TestCooperativeLevel();

		if (hr != D3DERR_DEVICELOST)
		{
			if (hr == D3DERR_DEVICENOTRESET)
			{
				m_pd3dDevice->Reset(&m_d3dpp);
			}

			D3DLOCKED_RECT d3dlr;

			if (SUCCEEDED(m_pTexture->LockRect(0, &d3dlr, NULL, 0)))
			{
				//копируем новый битмап в текстуру
				memcpy(d3dlr.pBits, m_bits, m_nTextureWidth * m_nTextureHeight * sizeof(DWORD));
				m_pTexture->UnlockRect(0);
			}

			//очищаем экран. если этого не делать, получается каша
			m_pd3dDevice->Clear(0, NULL, D3DCLEAR_TARGET,
			                    D3DCOLOR_XRGB(0, 0, 0), 0.0f, 0);

			if (SUCCEEDED(m_pd3dDevice->BeginScene()))
			{
				// Рисуем наши треугольники, на которые натягивается текстура
				m_pd3dDevice->DrawPrimitive(D3DPT_TRIANGLESTRIP, 0, TRIANGLES_NUM);
				m_pd3dDevice->EndScene();
			}

			// Показываем на экран содержимое бакбуфера
			hr = m_pd3dDevice->Present(NULL, NULL, NULL, NULL);
		}
	}

	BOOL CScreenD3D::BKSS_SetFullScreenMode()
	{
		BOOL bRet = FALSE;
		SetWindowLongPtr(m_hwndScreen, GWL_STYLE, (GetWindowLongPtr(m_hwndScreen, GWL_STYLE) & ~WS_CHILD) | WS_POPUP);
		m_pwndScreen->SetWindowPos(NULL, 0, 0, m_d3dfsdm.Width, m_d3dfsdm.Height, SWP_SHOWWINDOW);
		setFSparam();
		HRESULT hr = m_pd3dDevice->Reset(&m_d3dpp);
#ifdef _DEBUG
		const TCHAR *errstr = DXGetErrorString(hr);
		const TCHAR *errdesc = DXGetErrorDescription(hr);
#endif

		if (SUCCEEDED(hr))
		{
			m_bFullScreen = TRUE;
			setDeviceParam();
			bRet = TRUE;

			if (FAILED(createtexture()))
			{
				bRet = FALSE;
			}
		}
		else
		{
			BKSS_SetWindowMode(); //делаем обратно оконный режим
		}

		return bRet;
	}

	BOOL CScreenD3D::BKSS_SetWindowMode()
	{
		BOOL bRet = FALSE;
		SetWindowLongPtr(m_hwndScreen, GWL_STYLE, (GetWindowLongPtr(m_hwndScreen, GWL_STYLE) & ~WS_POPUP) | WS_CHILD);
		setWNDparam();
		HRESULT hr = m_pd3dDevice->Reset(&m_d3dpp);
#ifdef _DEBUG
		const TCHAR *errstr = DXGetErrorString(hr);
		const TCHAR *errdesc = DXGetErrorDescription(hr);
#endif

		if (SUCCEEDED(hr))
		{
			m_bFullScreen = FALSE;
			setDeviceParam();
			bRet = TRUE;

			if (FAILED(createtexture()))
			{
				bRet = FALSE;
			}
		}

		return bRet;
	}

	void CScreenD3D::setFSparam()
	{
		ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
		m_d3dpp.Windowed = FALSE;
		m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		m_d3dpp.EnableAutoDepthStencil = TRUE;
		m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
		m_d3dpp.MultiSampleType = D3DMULTISAMPLE_4_SAMPLES;
		m_d3dpp.MultiSampleQuality = 4;
		m_d3dpp.BackBufferFormat = m_d3dwindm.Format;
		m_d3dpp.BackBufferWidth = m_d3dfsdm.Width;
		m_d3dpp.BackBufferHeight = m_d3dfsdm.Height;
		m_d3dpp.BackBufferCount = 3;
		m_d3dpp.FullScreen_RefreshRateInHz = m_d3dfsdm.RefreshRate;
		m_d3dvp.X = m_rectFSWnd.left;
		m_d3dvp.Y = m_rectFSWnd.top;
		m_d3dvp.Width = m_rectFSWnd.right;
		m_d3dvp.Height = m_rectFSWnd.bottom;
		m_d3dvp.MinZ = 1.0;
		m_d3dvp.MaxZ = 1.0;
	}

	void CScreenD3D::setWNDparam()
	{
		// Задаём структуру для создания D3DDevice.
		ZeroMemory(&m_d3dpp, sizeof(m_d3dpp));
		m_d3dpp.Windowed = TRUE;
		m_d3dpp.SwapEffect = D3DSWAPEFFECT_DISCARD;
		m_d3dpp.EnableAutoDepthStencil = TRUE;
		m_d3dpp.AutoDepthStencilFormat = D3DFMT_D16;
		m_d3dpp.MultiSampleType = D3DMULTISAMPLE_NONE; //D3DMULTISAMPLE_4_SAMPLES;
		m_d3dpp.MultiSampleQuality = 0;
		m_d3dpp.BackBufferFormat = m_d3dwindm.Format; //D3DFMT_UNKNOWN;
		m_d3dpp.BackBufferCount = 1;
		m_d3dvp.X = m_rectWnWnd.left;
		m_d3dvp.Y = m_rectWnWnd.top;
		m_d3dvp.Width = m_rectWnWnd.right;
		m_d3dvp.Height = m_rectWnWnd.bottom;
		m_d3dvp.MinZ = 1.0;
		m_d3dvp.MaxZ = 1.0;
	}

	HRESULT CScreenD3D::createtexture()
	{
		//создаём текстуру
		m_pd3dDevice->SetTexture(0, NULL);
		SAFE_RELEASE(m_pTexture);
		HRESULT hr = D3DXCreateTexture(m_pd3dDevice, m_nTextureWidth, m_nTextureHeight, 0, 0, D3DFMT_X8R8G8B8, D3DPOOL_MANAGED, &m_pTexture);

		if (SUCCEEDED(hr))
		{
			D3DLOCKED_RECT d3dlr;

			if (SUCCEEDED(m_pTexture->LockRect(0, &d3dlr, NULL, 0)))
			{
				// если текстуру удалось заблокировать, а т.к. больше никто кроме нас её не использует, то удастся всегда.
				//копируем битмап в текстуру
				memcpy(d3dlr.pBits, m_bits, m_nTextureWidth * m_nTextureHeight * sizeof(DWORD));
				m_pTexture->UnlockRect(0);
			}

			// Создаём вертексный буфер.
			SAFE_RELEASE(m_pVB);

			if (FAILED(m_pd3dDevice->CreateVertexBuffer(POINTS_NUM * sizeof(CUSTOMVERTEX),
			                                            0, D3DFVF_CUSTOMVERTEX,
			                                            D3DPOOL_MANAGED, &m_pVB, NULL)))
			{
				return E_FAIL;
			}

			// Заполняем вертексный буфер.
			//минимально необходимое кол-во треугольников 2, но можно сделать и больше, но непонятно, нужно ли
			CUSTOMVERTEX *pVertices;

			if (FAILED(m_pVB->Lock(0, 0, (void **)&pVertices, 0)))
			{
				return E_FAIL;
			}

			for (DWORD i = 0; i < VLINES_NUM; ++i)
			{
				pVertices[2 * i + 0].color = D3DCOLOR_XRGB(255, 255, 255);
				pVertices[2 * i + 1].color = D3DCOLOR_XRGB(255, 255, 255);
				pVertices[2 * i + 0].position = D3DXVECTOR3((FLOAT)i / (VLINES_NUM - 1), 0.0f, 0.0f);
				pVertices[2 * i + 1].position = D3DXVECTOR3((FLOAT)i / (VLINES_NUM - 1), 1.0f, 0.0f);
			}

			m_pVB->Unlock();
			m_pd3dDevice->SetStreamSource(0, m_pVB, 0, sizeof(CUSTOMVERTEX));
			m_pd3dDevice->SetFVF(D3DFVF_CUSTOMVERTEX);
			// устанавливаем нашу текстуру и её параметры
			m_pd3dDevice->SetTexture(0, m_pTexture);
			m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLOROP, D3DTOP_MODULATE);
			m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG1, D3DTA_TEXTURE);
			m_pd3dDevice->SetTextureStageState(0, D3DTSS_COLORARG2, D3DTA_DIFFUSE);
			m_pd3dDevice->SetTextureStageState(0, D3DTSS_ALPHAOP, D3DTOP_DISABLE);
			// Устанавливаем фильтрацию текстур
			// Поэкспериментируйте - увидите разницу:
			//
			// D3DTEXF_NONE         - фильтрация не используется
			// D3DTEXF_POINT        - точечная фильтрация
			// D3DTEXF_LINEAR       - линейная фильтрация
			// D3DTEXF_ANISOTROPIC  - анизотропная фильтрация
			m_pd3dDevice->SetSamplerState(0, D3DSAMP_MAGFILTER, D3DTEXF_LINEAR);
			m_pd3dDevice->SetSamplerState(0, D3DSAMP_MINFILTER, D3DTEXF_ANISOTROPIC);
			m_pd3dDevice->SetSamplerState(0, D3DSAMP_MIPFILTER, D3DTEXF_ANISOTROPIC);
			//задаём всякие преобразования матрицы.
			SetupMatrices();
		}

		return hr;
	}

	void CScreenD3D::setDeviceParam()
	{
		// Выключение отбраковки
		m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
		// Выключаем выборку обратных сторон треугольников
		m_pd3dDevice->SetRenderState(D3DRS_CULLMODE, D3DCULL_NONE);
		// Выключаем D3D освещение
		m_pd3dDevice->SetRenderState(D3DRS_LIGHTING, D3DZB_FALSE);
		// Выключаем Z буфер
		m_pd3dDevice->SetRenderState(D3DRS_ZENABLE, D3DZB_FALSE);
		m_pd3dDevice->SetRenderState(D3DRS_ZWRITEENABLE, D3DZB_FALSE);

		//для оконного режима не нужно устанавливать SetViewport, т.к. он и так совпадает с окном.
		if (m_bFullScreen)
		{
			m_pd3dDevice->SetViewport(&m_d3dvp);
		}
	}

	void CScreenD3D::SetupMatrices()
	{
		// Задаём матрицу мира, тут всю сцену можно вертеть как хочется, но нам это не надо.
//  D3DXMATRIXA16 matWorld;
//  D3DXMatrixIdentity( &matWorld );
//  D3DXMatrixRotationX( &matWorld, timeGetTime() / 1000.0f );
//  m_pd3dDevice->SetTransform( D3DTS_WORLD, &matWorld );
		/* вот ещё вариант отображения двумерного вида, через ортогональную проекцию
		    D3DXMATRIX  matproj, matview;
		    // Установка двухмерного представления и состояния визуализации
		    D3DXMatrixIdentity(&matview);
		    m_pd3dDevice->SetTransform(D3DTS_VIEW, &matview);

		    // Установка ортогональной проекции, т.е двухмерная графика в трехмерном пространстве
		    // - недостаток - нужны координаты окна, и при изменении размеров - всё время нужно будет менять матрицу проектирования
		    D3DXMatrixOrthoLH(&matproj, (float)g_iWindowWidth, (float)g_iWindowHeight, 0, 1);
		    // Задание матрицы проецирования
		    m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matproj);
		*/
		/*
		    Задаём нашу матрицу вида. Это точка, из которой мы смотрим,
		    точка, в которую мы смотрим, и вектор направления, показывающий,
		    где у нас верх.
		*/
		D3DXVECTOR3 vEyePt(0.5f, 0.5f, -1.0f);       // Точка из которой мы смотрим
		// Место положения камеры
		D3DXVECTOR3 vLookatPt(0.5f, 0.5f, 0.0f);     // Точка в которую смотрим
		D3DXVECTOR3 vUpVec(0.0f, 1.0f, 0.0f);        // Где верх?
		D3DXMATRIXA16 matView;
		D3DXMatrixLookAtLH(&matView, &vEyePt, &vLookatPt, &vUpVec);
		m_pd3dDevice->SetTransform(D3DTS_VIEW, &matView);
		/*
		    Тут задаётся матрица проекции. чтобы 3Д объект спроецировать в
		    2Д вид.
		*/
		D3DXMATRIXA16 matProj;
		D3DXMatrixPerspectiveFovLH(&matProj, D3DX_PI / 4, 1.0f, 0.0f, 1.0f);
		m_pd3dDevice->SetTransform(D3DTS_PROJECTION, &matProj);
		//трансформируем текстуру, а то она получается кривая
		D3DXMATRIXA16 mTextureTransform;
		//D3DXMATRIXA16 mProj;
		D3DXMATRIXA16 mTrans;
		D3DXMATRIXA16 mScale;
		//m_pd3dDevice->GetTransform( D3DTS_PROJECTION, &mProj );
		D3DXMatrixTranslation(&mTrans, 0.5f, 0.5f, 0.0f);
		D3DXMatrixScaling(&mScale, 0.5f, -0.5f, 1.0f);
		mTextureTransform = matProj * mScale * mTrans;
		m_pd3dDevice->SetTransform(D3DTS_TEXTURE0, &mTextureTransform);
		m_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXTURETRANSFORMFLAGS, D3DTTFF_COUNT4 | D3DTTFF_PROJECTED);
		m_pd3dDevice->SetTextureStageState(0, D3DTSS_TEXCOORDINDEX, D3DTSS_TCI_CAMERASPACEPOSITION);
	}

};
