#include "OknoGL.h"
#include "Werteks.h"
#include "resource.h"
#include "glew.h"
#include "wglew.h"
#include "Kwadryka.h"

#include <math.h>

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	return okno.WndProc(hWnd, message, wParam, lParam);
}

void Testy()
{
	int ziarno = rand();

	Wektor3 w;
	Wektor3 w1 = -w;

	/*
	Macierz4 m = Macierz4::Jednostkowa;	
	Macierz4 kopia = m;
	m.UstawElement(0, 0, 2);
	const Macierz4 k = kopia;
	float a = k[0];
	*/

	//*
	if (!TestyMacierzyKwadratowej<float, 1>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<float, 1>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<float, 1>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyKwadratowej<float, 2>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<float, 2>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<float, 2>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyKwadratowej<float, 3>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<float, 3>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<float, 3>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyKwadratowej<float, 4>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<float, 4>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<float, 4>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyKwadratowej<double, 3>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<double, 3>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<double, 3>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyKwadratowej<double, 4>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestMacierzyKwadratowej<double, 4>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestMacierzyKwadratowej<double, 4>", "", MB_OK | MB_ICONINFORMATION);
	//*/

	if (!TestyMacierzyGrafika3D<double>(ziarno, 1E-14)) MessageBox(NULL, "Niezaliczony test: TestMacierzyGrafika3D<double>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestyMacierzyGrafika3D<double>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyMacierzyGrafika3D<float>(ziarno, (float)1E-5)) MessageBox(NULL, "Niezaliczony test: TestMacierzyGrafika3D<float>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestyMacierzyGrafika3D<float>", "", MB_OK | MB_ICONINFORMATION);

	if (!TestyWektora3<double>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestWektora3<double>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestyWektora3<double>", "", MB_OK | MB_ICONINFORMATION);
	if (!TestyWektora3<float>(ziarno)) MessageBox(NULL, "Niezaliczony test: TestWektora3<float>", "", MB_OK | MB_ICONERROR);
	else MessageBox(NULL, "Zaliczony test: TestyWektora3<float>", "", MB_OK | MB_ICONINFORMATION);


	exit(EXIT_SUCCESS);
}

int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
	LPSTR lpCmdLine, int nCmdShow)
{
	//Testy();

	POINT polozenieOkna = { 100, 100 };
	POINT rozmiarOkna = { 800, 600 };
	if (!okno.Inicjuj(hInstance, polozenieOkna, rozmiarOkna))
	{
		MessageBox(NULL, "Inicjacja okna nie powiodła się", "Aplikacja OpenGL", MB_OK | MB_ICONERROR);
		return EXIT_FAILURE;
	}
	else return okno.Uruchom();
}

LRESULT COkno::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	switch (message)
	{
	case WM_DESTROY: //zamykanie okna => kończenie aplikacji
		PostQuitMessage(0);
		break;
	case WM_SIZE: //Zmiana rozmiaru okna
		RECT rect;
		GetClientRect(hWnd, &rect);
		szerokoscObszaruUzytkownika = rect.right - rect.left;
		wysokoscObszaruUzytkownika = rect.bottom - rect.top;
		break;
	default: //automatyczne przetwarzanie komunikatów
		return (DefWindowProc(hWnd, message, wParam, lParam));
	}

	return 0L;
}

bool COkno::Inicjuj(HINSTANCE uchwytAplikacji, POINT polozenieOkna, POINT rozmiarOkna, bool trybPelnoekranowy, bool zmianaRozdzielczosci)
{
	char nazwaOkna[] = "Aplikacja OpenGL";

	WNDCLASSEX wc;
	wc.cbSize = sizeof(wc);
	wc.style = CS_HREDRAW | CS_VREDRAW | CS_OWNDC; //styl okna
	wc.lpfnWndProc = (WNDPROC)::WndProc; //procedura okna
	wc.cbClsExtra = 0; //dodatkowe bajty zarezerwowane za klasą okna
	wc.cbWndExtra = 0; //dodatkowe bajty zarezerwowane za instancją okna
	wc.hInstance = uchwytAplikacji; //instancja aplikacji
	//wc.hIcon = NULL; //uchwyt ikony
	//wc.hIconSm = NULL; //uchwyt ikony
	wc.hIcon = LoadIcon(uchwytAplikacji, MAKEINTRESOURCE(IDI_ICON1)); //uchwyt ikony
	wc.hIconSm = LoadIcon(uchwytAplikacji, MAKEINTRESOURCE(IDI_ICON1)); //uchwyt ikony
	wc.hCursor = LoadCursor(NULL, IDC_ARROW); //uchwyt kursora
	wc.hbrBackground = NULL; //uchwyt pędzla tła
	wc.lpszMenuName = NULL; //nazwa menu
	wc.lpszClassName = nazwaOkna; //nazwa klasy okna   

	//Rejestracja klasy okna
	if (RegisterClassEx(&wc) == 0) return false;

	//ustawienia dla okna
	DWORD stylOkna = WS_OVERLAPPEDWINDOW;

	if (trybPelnoekranowy)
	{
		//ustawienia dla trybu pełnoekranowego
		stylOkna = WS_POPUP;
		polozenieOkna.x = 0;
		polozenieOkna.y = 0;
		if (zmianaRozdzielczosci)
		{
			rozmiarOkna.x = 1024;
			rozmiarOkna.y = 768;
			if (!ZmianaRozdzielczosci(rozmiarOkna.x, rozmiarOkna.y)) return false;
		}
		else
		{
			RECT rozmiarEkranu;
			GetWindowRect(GetDesktopWindow(), &rozmiarEkranu);
			rozmiarOkna.x = rozmiarEkranu.right - rozmiarEkranu.left;
			rozmiarOkna.y = rozmiarEkranu.bottom - rozmiarEkranu.top;
		}
	}

	//Tworzenie okna
	uchwytOkna = CreateWindow(
		nazwaOkna, //nazwa klasy okna
		nazwaOkna, //nazwa okna
		stylOkna, //styl okna
		polozenieOkna.x, polozenieOkna.y, //położenie okna (x,y)
		rozmiarOkna.x, rozmiarOkna.y, //rozmiar okna (szerokość, wysokość)
		NULL, //uchwyt okna nadrzędnego (parent)
		NULL, //uchwyt menu
		uchwytAplikacji, //uchwyt instancji aplikacji
		NULL //parametr komunikatu informujacego o utworzeniu okna
		);

	if (uchwytOkna == NULL) return false;

	//Pokazanie i aktualizacja okna
	ShowWindow(uchwytOkna, SW_SHOW);
	UpdateWindow(uchwytOkna);

	return true;
}

bool COkno::ZmianaRozdzielczosci(long szerokosc, long wysokosc, long glebiaKolorow) const
{
	DEVMODE dmScreenSettings;   //struktura trybu wyświetlania
	memset(&dmScreenSettings, 0, sizeof(dmScreenSettings)); //czyszczenie pamięci
	dmScreenSettings.dmSize = sizeof(dmScreenSettings);     //rozmiar struktury
	dmScreenSettings.dmPelsWidth = szerokosc;        //nowa szerokość ekranu
	dmScreenSettings.dmPelsHeight = wysokosc;        //nowa wysokość ekranu
	dmScreenSettings.dmBitsPerPel = glebiaKolorow;     //ilość bitów na piksel
	dmScreenSettings.dmFields = DM_BITSPERPEL | DM_PELSWIDTH | DM_PELSHEIGHT;
	return ChangeDisplaySettings(&dmScreenSettings, CDS_FULLSCREEN) == DISP_CHANGE_SUCCESSFUL;
}

WPARAM COkno::Uruchom()
{
	//Pętla główna - obsługa komunikatów
	MSG msg;
	while (GetMessage(&msg, NULL, 0, 0))
	{
		TranslateMessage(&msg);
		DispatchMessage(&msg);
	}
	return msg.wParam;
}

//---------------------------------------------------

bool COknoGL::UstalFormatPikseli(HDC uchwytDC) const
{	
	PIXELFORMATDESCRIPTOR opisFormatuPikseli;
	ZeroMemory(&opisFormatuPikseli, sizeof(opisFormatuPikseli));
	opisFormatuPikseli.nVersion = 1;
	opisFormatuPikseli.dwFlags = PFD_SUPPORT_OPENGL | PFD_DRAW_TO_WINDOW | PFD_DOUBLEBUFFER; //w oknie, podwojne buforowanie 
	opisFormatuPikseli.iPixelType = PFD_TYPE_RGBA; //typ koloru RGB z kanalem alfa
	opisFormatuPikseli.cColorBits = 32; //jakość kolorów 4 bajty (po bajcie na każdy kanał)
	opisFormatuPikseli.cDepthBits = 32; //glębokość bufora głębii (z-buffer) 
	opisFormatuPikseli.iLayerType = PFD_MAIN_PLANE;

	int formatPikseli = ChoosePixelFormat(uchwytDC, &opisFormatuPikseli);
	if (formatPikseli == 0) return false;

	if (!SetPixelFormat(uchwytDC, formatPikseli, &opisFormatuPikseli)) return false;
	return true;
}

bool COknoGL::InicjujWGL(HWND uchwytOkna)
{
	uchwytDC = ::GetDC(uchwytOkna);
	if (!UstalFormatPikseli(uchwytDC)) return false;

	HGLRC uchwytTymczasowegoRC = wglCreateContext(uchwytDC); //tworzenie tymczasowego kontekstu renderowania (tradycyjnie)
	if (uchwytTymczasowegoRC == NULL) return false;
	if (!wglMakeCurrent(uchwytDC, uchwytTymczasowegoRC)) return false;

	GLenum err = glewInit();
	if (GLEW_OK != err)
	{
		MessageBox(NULL, "Inicjacja biblioteki GLEW nie powiodła się", "Aplikacja OpenGL", MB_OK | MB_ICONERROR);
		return false;
	}

	const int major_min = 3; const int minor_min = 3;
	int major, minor; glGetIntegerv(GL_MAJOR_VERSION, &major); glGetIntegerv(GL_MINOR_VERSION, &minor);
	if (major<major_min || (major==major_min && minor<minor_min))
	{
		MessageBox(NULL, "Wersja OpenGL jest niewystarczająca", "Aplikacja OpenGL", MB_OK | MB_ICONERROR);
		return false;
	}

	int atrybuty[] =
	{
		WGL_CONTEXT_MAJOR_VERSION_ARB, major_min,
		WGL_CONTEXT_MINOR_VERSION_ARB, minor_min,
		WGL_CONTEXT_FLAGS_ARB, WGL_CONTEXT_FORWARD_COMPATIBLE_BIT_ARB,
		//WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_COMPATIBILITY_PROFILE_BIT_ARB,
		WGL_CONTEXT_PROFILE_MASK_ARB, WGL_CONTEXT_CORE_PROFILE_BIT_ARB,		
		0
	};

	uchwytRC = wglCreateContextAttribsARB(uchwytDC, 0, atrybuty); //tworzenie zasadniczego kontekstu dla OpenGL 3.3+
	if (uchwytRC == NULL) return false;
	if (!wglMakeCurrent(uchwytDC, uchwytRC)) return false;

	wglDeleteContext(uchwytTymczasowegoRC); //usuniecie juz niepotrzebnego tymczasowego kontekstu renderowania

	return true;
}

void COknoGL::UsuńWGL()
{
	wglMakeCurrent(NULL, NULL);
	wglDeleteContext(uchwytRC);
	::ReleaseDC(uchwytOkna, uchwytDC);
}

LRESULT COknoGL::WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
	long wynik = COkno::WndProc(hWnd, message, wParam, lParam);

	static POINT poprzedniaPozycjaKursoraMyszyLMB = { -1, -1 };
	static POINT poprzedniaPozycjaKursoraMyszyRMB = { -1, -1 };

	const bool swobodneObrotyKameryMożliwe = true; //główny wyłącznik swobodnych obrotów kamery
	const int identyfikatorTimeraSwobodnychObrotówKamery = 1;
	const int okresTimeraSwobodnychObrotówKamery = 50; //50ms

	const bool animacjaMożliwa = true; //cykliczne wywoływanie metody AnimujScenę
	const int identyfikatorTimeraAnimacji = 0;
	const int okresTimeraAnimacji = 50; //50ms

	switch (message)
	{
	case WM_CREATE: //Utworzenie okna
		//zmienna uchwytOkna nie jest jeszcze zainicjowana
		if (!InicjujWGL(hWnd))
		{
			MessageBox(NULL, "Pobranie kontekstu renderowania nie powiodło się", "Aplikacja OpenGL", MB_OK | MB_ICONERROR);
			exit(EXIT_FAILURE);
		}
		idProgramuShaderow = PrzygotujShadery("Basic.vsh", "Basic.fsh", false);
		if (idProgramuShaderow == NULL)
		{
			MessageBox(NULL, "Przygotowanie shaderów nie powiodło się", "Aplikacja OpenGL", MB_OK | MB_ICONERROR);
			exit(EXIT_FAILURE);
		}
		UmieśćInformacjeNaPaskuTytułu(hWnd);
		//InicjujBuforWerteksów();
		if (teksturowanieWłączone) PrzygotujTekstury(3, new char*[3] { "earthmap1k.bmp", "earthspec1k.bmp", "earthnormal1k.bmp" });
		else glUniform1i(glGetUniformLocation(idProgramuShaderow, "Teksturowanie"), false);
		liczbaAktorów = PrzygotujAktorów();
		UstawienieSceny();
		if (swobodneObrotyKameryMożliwe)
		{
			if (SetTimer(hWnd, identyfikatorTimeraSwobodnychObrotówKamery, okresTimeraSwobodnychObrotówKamery, NULL) == 0)
				MessageBox(hWnd, "Nie udało się ustawić timera swobodnych obrotów kamery", "Błąd", MB_OK | MB_ICONERROR);			
		}
		if (animacjaMożliwa)
		{
			if (SetTimer(hWnd, identyfikatorTimeraAnimacji, okresTimeraAnimacji, NULL) == 0)
				MessageBox(hWnd, "Nie udało się ustawić timera animacji sceny", "Błąd", MB_OK | MB_ICONERROR);
		}
		break;
	case WM_DESTROY: //Zamknięcie okna
		//UsuńBuforWerteksów();
		UsuńTekstury();
		UsuńAktorów();
		UsuńWGL();
		KillTimer(uchwytOkna, identyfikatorTimeraSwobodnychObrotówKamery);
		KillTimer(uchwytOkna, identyfikatorTimeraAnimacji);
		break;
	case WM_SIZE: //Zmiana rozmiaru okna
		UstawienieSceny();
		break;
	case WM_PAINT: //Okno wymaga odrysowania
		RysujScenę();
		ValidateRect(hWnd, NULL);
		break;

	case WM_KEYDOWN:
		ObsługaKlawiszy(wParam);
		break;

	case WM_MOUSEMOVE:
		if (wParam & MK_LBUTTON) ObliczaniePrzesunięciaMyszy(lParam, 3.0f, poprzedniaPozycjaKursoraMyszyLMB, &COknoGL::ObsługaMyszyZWciśniętymLewymKlawiszem);
		if (wParam & MK_RBUTTON) ObliczaniePrzesunięciaMyszy(lParam, 3.0f, poprzedniaPozycjaKursoraMyszyRMB, &COknoGL::ObsługaMyszyZWciśniętymPrawymKlawiszem);
		break;	
	case WM_LBUTTONUP:
		poprzedniaPozycjaKursoraMyszyLMB = { -1, -1 };
		break;
	case WM_RBUTTONUP:
		poprzedniaPozycjaKursoraMyszyRMB = { -1, -1 };
		break;
	case WM_MOUSEWHEEL:
		ObsługaRolkiMyszy(wParam);
		break;

	case WM_TIMER:
		switch (wParam)
		{
		case identyfikatorTimeraSwobodnychObrotówKamery:
			if (swobodneObrotyKameryAktywne) SwobodneObrotyKamery(false);
			break;
		case identyfikatorTimeraAnimacji:
			if (animacjaAktywna) AnimujScenę();
			break;
		}
		wynik = 0;
		break;
	case WM_LBUTTONDOWN:
		swobodneObrotyKameryAktywne = false;
		break;
	}

	return wynik;
}

void COknoGL::UmieśćInformacjeNaPaskuTytułu(HWND uchwytOkna)
{
	char bufor[256]; GetWindowText(uchwytOkna, bufor, 256);
	const GLubyte* wersja = glGetString(GL_VERSION);
	strcat_s(bufor, " | OpenGL "); strcat_s(bufor, (char*)wersja);
	const GLubyte* dostawca = glGetString(GL_VENDOR);
	strcat_s(bufor, " | "); strcat_s(bufor, (char*)dostawca);
	const GLubyte* kartaGraficzna = glGetString(GL_RENDERER);
	strcat_s(bufor, " | "); strcat_s(bufor, (char*)kartaGraficzna);
	const GLubyte* wersjaGLSL = glGetString(GL_SHADING_LANGUAGE_VERSION);
	strcat_s(bufor, " | GLSL "); strcat_s(bufor, (char*)wersjaGLSL);
	const GLubyte* wersjaGLEW = glewGetString(GLEW_VERSION);
	strcat_s(bufor, " | GLEW "); strcat_s(bufor, (char*)wersjaGLEW);
	SetWindowText(uchwytOkna, bufor);	
}

void COknoGL::UstawienieSceny(bool rzutowanieIzometryczne) //wartość domyślna =false
{
	glViewport(0, 0, szerokoscObszaruUzytkownika, wysokoscObszaruUzytkownika); //okno OpenGL = wnętrze formy (domyślnie) 

	glEnable(GL_DEPTH_TEST); //z-buffer aktywny = ukrywanie niewidocznych powierzchni 
	//glDisable(GL_DEPTH_TEST);

	//*
	glFrontFace(GL_CCW); //odtworzenie ustawień domyślnych
	//glPolygonMode(GL_FRONT, GL_FILL);
	//glPolygonMode(GL_BACK, GL_LINE);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
	//glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
	glCullFace(GL_BACK); //odtworzenie ustawień domyślnych
	glEnable(GL_CULL_FACE);
	//*/


	//Parametry shadera
	GLint parametrUwzglednijKolorWerteksu = glGetUniformLocation(idProgramuShaderow, "UwzglednijKolorWerteksu");
	glUniform1i(parametrUwzglednijKolorWerteksu, true);

	GLint parametrKolor = glGetUniformLocation(idProgramuShaderow, "Kolor");
	float kolor[4] = { 1.0, 1.0, 0.0, 1.0 };
	glUniform4fv(parametrKolor, 1, kolor);

	//Macierze
	GLint parametrMacierzŚwiata = glGetUniformLocation(idProgramuShaderow, "macierzSwiata");
	macierzŚwiata.ZwiążZIdentyfikatorem(parametrMacierzŚwiata, true);	

	GLint parametrMacierzWidoku = glGetUniformLocation(idProgramuShaderow, "macierzWidoku");
	macierzWidoku.UstawJednostkową();
	macierzWidoku *= Macierz4::Przesunięcie(0, 0, -3);
	macierzWidoku.ZwiążZIdentyfikatorem(parametrMacierzWidoku, true);	

	GLint parametrMacierzRzutowania = glGetUniformLocation(idProgramuShaderow, "macierzRzutowania");
	macierzRzutowania.ZwiążZIdentyfikatorem(parametrMacierzRzutowania, false);
	float wsp = wysokoscObszaruUzytkownika / (float)szerokoscObszaruUzytkownika;
	if (!rzutowanieIzometryczne)
		//left,right,bottom,top,znear,zfar (clipping) - ustalanie frustum 
		macierzRzutowania.UstawRzutPerspektywiczny(-1.0f, 1.0f, wsp*-1.0f, wsp*1.0f, 1.0f, 10.0f);
	else
		macierzRzutowania.UstawRzutIzometryczny(-1.0f, 1.0f, wsp*-1.0f, wsp*1.0f, 1.0f, 10.0f);
	macierzRzutowania.PrześlijWartość();

	GLint parametrMacierzNormalnych = glGetUniformLocation(idProgramuShaderow, "macierzNormalnych");
	macierzNormalnych = macierzŚwiata.Odwrotna().Transponowana();
	macierzNormalnych.ZwiążZIdentyfikatorem(parametrMacierzNormalnych, true);

	UstawParametryŹródłaŚwiatła(Wektor3(1, 2, 2), Wektor4(1, 1, 1, 1), Wektor4(1, 1, 1, 1), Wektor4(1, 1, 1, 1));
	UstawParametryMateriału(Wektor4(0.1f, 0.1f, 0.1f, 1), Wektor4(1, 1, 1, 1), Wektor4(0, 1, 0, 1), 100);
	UstawParametryOsłabieniaOświetlenia(false, Wektor3(1, 0, 1));
}

void COknoGL::UstawParametryŹródłaŚwiatła(Wektor3 położenie, Wektor4 światłoOtoczenia, Wektor4 światłoRozpraszane, Wektor4 światłoRozbłysku)
{
	float bufor[4];
	położenie.KopiujElementy(bufor);
	GLint parametrPołożenieŹródłaŚwiatła = glGetUniformLocation(idProgramuShaderow, "PolozenieZrodlaSwiatla");
	glUniform3fv(parametrPołożenieŹródłaŚwiatła, 1, bufor);
	
	światłoOtoczenia.KopiujElementy(bufor);
	GLint parametrŚwiatłoOtoczenia = glGetUniformLocation(idProgramuShaderow, "Ca");
	glUniform4fv(parametrŚwiatłoOtoczenia, 1, bufor);

	światłoRozpraszane.KopiujElementy(bufor);
	GLint parametrŚwiatłoRozpraszane= glGetUniformLocation(idProgramuShaderow, "Cd");
	glUniform4fv(parametrŚwiatłoRozpraszane, 1, bufor);

	światłoRozbłysku.KopiujElementy(bufor);
	GLint parametrŚwiatłoRozbłysku = glGetUniformLocation(idProgramuShaderow, "Cs");
	glUniform4fv(parametrŚwiatłoRozbłysku, 1, bufor);
}

void COknoGL::UstawParametryMateriału(Wektor4 światłoOtoczenia, Wektor4 światłoRozpraszane, Wektor4 światłoRozbłysku, float wykładnikRozbłysku)
{
	float bufor[4];
	światłoOtoczenia.KopiujElementy(bufor);
	GLint parametrŚwiatłoOtoczenia = glGetUniformLocation(idProgramuShaderow, "Wa");
	glUniform4fv(parametrŚwiatłoOtoczenia, 1, bufor);

	światłoRozpraszane.KopiujElementy(bufor);
	GLint parametrŚwiatłoRozpraszane = glGetUniformLocation(idProgramuShaderow, "Wd");
	glUniform4fv(parametrŚwiatłoRozpraszane, 1, bufor);

	światłoRozbłysku.KopiujElementy(bufor);
	GLint parametrŚwiatłoRozbłysku = glGetUniformLocation(idProgramuShaderow, "Ws");
	glUniform4fv(parametrŚwiatłoRozbłysku, 1, bufor);

	GLint parametrWykładnikRozbłysku = glGetUniformLocation(idProgramuShaderow, "WNs");
	glUniform1f(parametrWykładnikRozbłysku, wykładnikRozbłysku);
}

void COknoGL::UstawParametryOsłabieniaOświetlenia(bool włączone, Wektor3 współczynniki)
{
	GLint parametrTłumienieOświetlenia = glGetUniformLocation(idProgramuShaderow, "OslabienieOswietlenia");
	glUniform1i(parametrTłumienieOświetlenia, włączone);

	float bufor[3];
	współczynniki.KopiujElementy(bufor);
	GLint parametrWspółczynnikiTłumienia = glGetUniformLocation(idProgramuShaderow, "WspolczynnikiOslabieniaOswietlenia");
	glUniform3fv(parametrWspółczynnikiTłumienia, 1, bufor);
}


void COknoGL::AnimujScenę()
{
	for (unsigned int i = 0; i < liczbaAktorów; ++i)
	{		
		//bryły
		if (i < 5)
			aktorzy[i]->MacierzŚwiata.PomnóżZLewej(Macierz4::ObrótY(3)*Macierz4::ObrótZ(1));

		//podłoże
		if (i == 5) continue;

		//cienie
		if (i > 5)
		{
			const float przesunięcieY = -2.0f;
			const float korekta = 0.0001f;
			Macierz4 macierzRzutuNaPodłoże = Macierz4::RzutNaPłaszczyznę(Wektor3(1, 2, 2), Wektor3(0, 1, 0), -przesunięcieY - korekta);
			aktorzy[i]->MacierzŚwiata.Ustaw(macierzRzutuNaPodłoże*aktorzy[i - 6]->MacierzŚwiata);
		}
	}

	RysujScenę();
}


void COknoGL::RysujScenę()
{
	//Przygotowanie bufora 
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); //czyści bufory 	

	RysujAktorów();	

	//Z bufora na ekran 
	SwapBuffers(uchwytDC);
}

#pragma region Aktorzy
unsigned int COknoGL::PrzygotujAktorów()
{
	GLuint atrybutPołożenie = glGetAttribLocation(idProgramuShaderow, "polozenie_in");
	if (atrybutPołożenie == (GLuint)-1) atrybutPołożenie = 0;

	GLuint atrybutNormalna = glGetAttribLocation(idProgramuShaderow, "normalna_in");
	if (atrybutNormalna == (GLuint)-1) atrybutNormalna = 1;

	GLuint atrybutWspółrzędneTeksturowania = glGetAttribLocation(idProgramuShaderow, "wspTekstur_in");
	if (atrybutWspółrzędneTeksturowania == (GLuint)-1) atrybutWspółrzędneTeksturowania = 2;

	GLuint atrybutKolor = glGetAttribLocation(idProgramuShaderow, "kolor_in");
	if (atrybutKolor == (GLuint)-1) atrybutKolor = 3;

	int liczbaAktorów = 1;
	aktorzy = new Aktor*[liczbaAktorów];

	//bryły
	aktorzy[0] = new SferaZBuforemIndeksów(atrybutPołożenie, atrybutNormalna, atrybutWspółrzędneTeksturowania, atrybutKolor, 0.75f, 20, 20);
	aktorzy[0]->MacierzŚwiata = Macierz4::ObrótY(90) * Macierz4::ObrótX(90);
	aktorzy[0]->MateriałŚwiatłoOtoczenia = Wektor4(0.2f, 0.2f, 0.2f, 1);
	aktorzy[0]->MateriałŚwiatłoRozpraszane = Wektor4(1, 1, 1, 1);
	aktorzy[0]->MateriałŚwiatłoRozbłysku = Wektor4(1, 1, 1, 1);
	aktorzy[0]->MateriałWykładnikRozbłysku = 10;
	aktorzy[0]->IndeksTekstury = (teksturowanieWłączone) ? indeksyTekstur[0] : -1;

	return liczbaAktorów;
}

void COknoGL::RysujAktorów()
{
	for (unsigned int i = 0; i < liczbaAktorów; ++i)
	{
		macierzŚwiata.Ustaw(aktorzy[i]->MacierzŚwiata); //zachowuje związek z macierzą shaderów, z tego powodu nie należy używać operatora =
		macierzŚwiata.PrześlijWartość();
		try
		{
			macierzNormalnych.Ustaw(macierzŚwiata.Odwrotna().Transponowana());
		}
		catch (std::exception exc)
		{
			MessageBeep(0);
			macierzNormalnych.Ustaw(macierzŚwiata);
		}
		macierzNormalnych.PrześlijWartość();
		UstawParametryMateriału(
			aktorzy[i]->MateriałŚwiatłoOtoczenia,
			aktorzy[i]->MateriałŚwiatłoRozpraszane,
			aktorzy[i]->MateriałŚwiatłoRozbłysku,
			aktorzy[i]->MateriałWykładnikRozbłysku);

		if (teksturowanieWłączone)
		{
			glActiveTexture(GL_TEXTURE0);
			glBindTexture(GL_TEXTURE_2D, indeksyTekstur[0]);
			glUniform1i(glGetUniformLocation(idProgramuShaderow, "ProbnikTekstury0"), 0);
				
			glActiveTexture(GL_TEXTURE1);
			glBindTexture(GL_TEXTURE_2D, indeksyTekstur[1]);
			glUniform1i(glGetUniformLocation(idProgramuShaderow, "ProbnikTekstury1"), 1);

			glActiveTexture(GL_TEXTURE2);
			glBindTexture(GL_TEXTURE_2D, indeksyTekstur[2]);
			glUniform1i(glGetUniformLocation(idProgramuShaderow, "ProbnikTekstury2"), 2);
		}

		aktorzy[i]->Rysuj();
	}
}

void COknoGL::UsuńAktorów()
{
	for (unsigned int i = 0; i < liczbaAktorów; ++i) delete aktorzy[i];
	delete[] aktorzy;
	liczbaAktorów = 0;
}
#pragma endregion

#pragma region Shadery
void PokażKomunikat(char* tekst, UINT typ = 0)
{
	MessageBoxA(NULL, tekst, "Aplikacja OpenGL - Kompilacja shaderów", MB_OK | typ);
}

#include <fstream>
char* CzytajPlikTxt(const char* nazwaPliku, int rozmiarBufora, char* bufor)
{
	std::ifstream plik_we(nazwaPliku);
	if (plik_we.fail()) return NULL;
	plik_we.read(bufor, rozmiarBufora);
	if (plik_we.eof())
	{
		std::streamsize odczytane = plik_we.gcount();
		plik_we.close();
		bufor[odczytane] = '\0';
		return bufor;
	}
	else
	{
		plik_we.close();
		return NULL;
	}
}

GLuint COknoGL::KompilujShader(const char* nazwaPliku, GLenum typ, bool trybDebugowania)
{
	const int maksymalnaWielkoscKodu = 65535;

	//Wczytywanie kodu z pliku
	char kodShadera[maksymalnaWielkoscKodu] = "";
	if (CzytajPlikTxt(nazwaPliku, maksymalnaWielkoscKodu, kodShadera) == NULL)
	{
		PokażKomunikat("Brak lub niemożliwe odczytanie pliku shadera", MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat(kodShadera);

	//Tworzenie obiektu shadera
	GLuint idShadera = glCreateShader(typ);
	if (idShadera == NULL) return NULL;

	//Dostarczanie zrodla do obiektu shadera
	const GLchar* zrodlo[1];
	zrodlo[0] = kodShadera;
	glShaderSource(idShadera, 1, zrodlo, NULL);

	//Kompilacja shadera
	glCompileShader(idShadera);

	//Weryfikacje kompilacji
	GLint powodzenie;
	glGetShaderiv(idShadera, GL_COMPILE_STATUS, &powodzenie);
	if (!powodzenie)
	{
		const int maxInfoLogSize = 2048;
		GLchar infoLog[maxInfoLogSize];
		glGetShaderInfoLog(idShadera, maxInfoLogSize, NULL, infoLog);
		char komunikat[maxInfoLogSize + 64] = "Uwaga! Kompilacja shadera nie powiodła się:\n";
		strcat_s(komunikat, (char*)infoLog);
		PokażKomunikat(komunikat, MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat("Kompilacja shadera zakończyła się sukcesem", MB_ICONINFORMATION);

	return idShadera;
}

unsigned int COknoGL::PrzygotujShadery(const char* vsNazwaPliku, const char* fsNazwaPliku, bool trybDebugowania)
{
	//Kompilacja shadera werteksów
	GLuint idShaderaWerteksów = KompilujShader(vsNazwaPliku, GL_VERTEX_SHADER, trybDebugowania);
	if (idShaderaWerteksów == NULL)
	{
		PokażKomunikat("Kompilacja shadera werteksów nie powiodła się", MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat("Kompilacja shadera werteksów zakończyła się sukcesem", MB_ICONINFORMATION);

	//Kompilacja shadera fragmentów
	GLuint idShaderaFragmentów = KompilujShader(fsNazwaPliku, GL_FRAGMENT_SHADER, trybDebugowania);
	if (idShaderaFragmentów == NULL)
	{
		PokażKomunikat("Kompilacja shadera fragmentów nie powiodła się", MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat("Kompilacja shadera fragmentów zakończyła się sukcesem", MB_ICONINFORMATION);

	//Tworzenie obiektu programu
	GLuint idProgramu = glCreateProgram();

	//Przylaczanie shaderow
	glAttachShader(idProgramu, idShaderaWerteksów);
	glAttachShader(idProgramu, idShaderaFragmentów);
	
	//Linkowanie
	glLinkProgram(idProgramu);

	//Weryfikacja linkowania
	GLint powodzenie;
	glGetProgramiv(idProgramu, GL_LINK_STATUS, &powodzenie);
	if (!powodzenie)
	{
		const int maxInfoLogSize = 2048;
		GLchar infoLog[maxInfoLogSize];
		glGetProgramInfoLog(idProgramu, maxInfoLogSize, NULL, infoLog);
		char komunikat[maxInfoLogSize + 64] = "Uwaga! Linkowanie programu shaderów nie powiodło się:\n";
		strcat_s(komunikat, (char*)infoLog);
		PokażKomunikat(komunikat, MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat("Linkowanie programu shaderów powiodło się", MB_ICONINFORMATION);

	//Walidacja programu
	glGetProgramiv(idProgramu, GL_VALIDATE_STATUS, &powodzenie);
	if (!powodzenie)
	{
		const int maxInfoLogSize = 2048;
		GLchar infoLog[maxInfoLogSize];
		glGetProgramInfoLog(idProgramu, maxInfoLogSize, NULL, infoLog);
		char komunikat[maxInfoLogSize + 64] = "Uwaga! Walidacja programu shaderów nie powiodła się:\n";
		strcat_s(komunikat, (char*)infoLog);
		PokażKomunikat(komunikat, MB_ICONERROR);
		return NULL;
	}
	else if (trybDebugowania) PokażKomunikat("Walidacja programu shaderów powiodła się", MB_ICONINFORMATION);

	//Uzycie programu
	glUseProgram(idProgramu);

	//Usuwanie niepotrzebnych obiektow shadera
	glDeleteShader(idShaderaWerteksów);
	glDeleteShader(idShaderaFragmentów);

	return idProgramu;
}
#pragma endregion

#pragma region Obsługa klawiatury i myszy
Wektor3 COknoGL::PobierzPołożenieKamery(bool pomińObroty) const //położenie kamery w układzie sceny
{
	Wektor3 w3;
	if(pomińObroty)
	{
		Wektor4 w4 = macierzWidoku.KopiaKolumny(3);	
		for (int i = 0; i < 3; ++i) w3[i] = -w4[i];
	}
	else
	{
		Wektor4 w4 = macierzWidoku.Odwrotna().KopiaKolumny(3);
		for (int i = 0; i < 3; ++i) w3[i] = w4[i];
	}
	return w3;
}

float COknoGL::OdległośćKamery() const
{
	return PobierzPołożenieKamery().Długość();
}

void COknoGL::ModyfikujPołożenieKamery(Macierz4 macierzPrzekształcenia)
{
	switch (trybKontroliKamery)
	{
	case tkkFPP:
		macierzWidoku.PomnóżZLewej(macierzPrzekształcenia);
		break;
	case tkkTPP:
	{
		const float odległośćCentrumOdKamery = 3.0f;
		macierzWidoku.PomnóżZLewej(Macierz4::Przesunięcie(0, 0, -odległośćCentrumOdKamery)*macierzPrzekształcenia*Macierz4::Przesunięcie(0, 0, odległośćCentrumOdKamery));
	}
		break;
	case tkkArcBall:
	{
		Wektor3 położenieKamery = PobierzPołożenieKamery(true);
		macierzWidoku.PomnóżZLewej(Macierz4::Przesunięcie(-położenieKamery)*macierzPrzekształcenia*Macierz4::Przesunięcie(położenieKamery));
	}
		break;
	case tkkModel:
		macierzWidoku.PomnóżZPrawej(macierzPrzekształcenia);
	}
	macierzWidoku.PrześlijWartość();
	
	float położenieKamery[3];
	PobierzPołożenieKamery(false).KopiujElementy(położenieKamery);
	GLint parametrPołożenieKamery = glGetUniformLocation(idProgramuShaderow, "PolozenieKamery");
	glUniform3fv(parametrPołożenieKamery, 1, położenieKamery);
}

void COknoGL::ObsługaKlawiszy(WPARAM wParam)
{
	const float kąt = 5.0f;
	Macierz4 m = Macierz4::Jednostkowa;
	switch (wParam)
	{
	case 27:
		SendMessage(uchwytOkna, WM_CLOSE, 0, 0);
		break;
	case VK_F5:
	{
		bool komunikatNaPaskuTytułu = true;
		switch (trybKontroliKamery)
		{
		case tkkFPP:
			trybKontroliKamery = tkkTPP;
			if (komunikatNaPaskuTytułu) SetWindowText(uchwytOkna, "Tryb kontroli kamery: TPP");
			break;
		case tkkTPP:
			trybKontroliKamery = tkkArcBall;
			if (komunikatNaPaskuTytułu) SetWindowText(uchwytOkna, "Tryb kontroli kamery: ArcBall");
			break;
		case tkkArcBall:
			trybKontroliKamery = tkkModel;
			if (komunikatNaPaskuTytułu) SetWindowText(uchwytOkna, "Tryb kontroli kamery: Model");
			break;
		case tkkModel:
			trybKontroliKamery = tkkFPP;
			if (komunikatNaPaskuTytułu) SetWindowText(uchwytOkna, "Tryb kontroli kamery: FPP");
			break;
		}
		MessageBeep(MB_OK);
	}
		break;
	case VK_F6:
		animacjaAktywna = !animacjaAktywna;
		MessageBeep(MB_OK);
		break;
	case 'A':
	case VK_LEFT:
		m = Macierz4::ObrótY(kąt);
		break;
	case 'D':
	case VK_RIGHT:
		m = Macierz4::ObrótY(-kąt);
		break;
	case 'W':
	case VK_UP:
		m = Macierz4::ObrótX(kąt);
		break;
	case 'S':
	case VK_DOWN:
		m = Macierz4::ObrótX(-kąt);
		break;
	case VK_OEM_COMMA:
		m = Macierz4::ObrótZ(kąt);
		break;
	case VK_OEM_PERIOD:
		m = Macierz4::ObrótZ(-kąt);
		break;
	case VK_OEM_MINUS:
		m = Macierz4::Przesunięcie(0, 0, 0.1f);
		break;
	case VK_OEM_PLUS:
		m = Macierz4::Przesunięcie(0, 0, -0.1f);
		break;
	}
	ModyfikujPołożenieKamery(m);
	RysujScenę();
}

void COknoGL::ObliczaniePrzesunięciaMyszy(const LPARAM lParam, const float próg, POINT& poprzedniaPozycjaKursoraMyszy, TypMetodyObsługującejPrzesunięcieMyszy MetodaObsługującaPrzesunięcieMyszy)
{
	if (poprzedniaPozycjaKursoraMyszy.x == -1 && poprzedniaPozycjaKursoraMyszy.y == -1)
	{
		poprzedniaPozycjaKursoraMyszy = { LOWORD(lParam), HIWORD(lParam) };
		return;
	}
	POINT bieżącaPozycjaKursoraMyszy = { LOWORD(lParam), HIWORD(lParam) };
	POINT przesunięcie;
	przesunięcie.x = bieżącaPozycjaKursoraMyszy.x - poprzedniaPozycjaKursoraMyszy.x;
	przesunięcie.y = bieżącaPozycjaKursoraMyszy.y - poprzedniaPozycjaKursoraMyszy.y;
	float przesunięcieKursoraMyszy = (float)sqrt(przesunięcie.x*przesunięcie.x + przesunięcie.y*przesunięcie.y);
	
	if (przesunięcieKursoraMyszy > próg) 
	//próg w pikselach
	//zapobiega zbyt częstym zmianom - utrata dokładności
	{
		(this->*MetodaObsługującaPrzesunięcieMyszy)(bieżącaPozycjaKursoraMyszy, przesunięcie);
		poprzedniaPozycjaKursoraMyszy = bieżącaPozycjaKursoraMyszy;
	}
}

void COknoGL::ObsługaMyszyZWciśniętymLewymKlawiszem(POINT bieżącaPozycjaKursoraMyszy, POINT przesunięcieKursoraMyszy)
{
	//skalowanie
	const float wspX = 360.0f / szerokoscObszaruUzytkownika;
	const float wspY = 360.0f / wysokoscObszaruUzytkownika;
	float dx = przesunięcieKursoraMyszy.x * wspX;
	float dy = przesunięcieKursoraMyszy.y * wspY;

	//Macierz4 m = Macierz4::ObrótY(dx)*Macierz4::ObrótX(dy);
	Macierz4 m = Macierz4::ObrótXYZ(dy, dx, 0);

	ModyfikujPołożenieKamery(m);
	RysujScenę();

	SwobodneObrotyKamery(true, dx, dy, 0.95f); //inicjacja swobodnych obrotów kamery
}

void COknoGL::ObsługaMyszyZWciśniętymPrawymKlawiszem(POINT bieżącaPozycjaKursoraMyszy, POINT przesunięcieKursoraMyszy)
{
	//skalowanie
	const float wspX = 5.0f / szerokoscObszaruUzytkownika;
	const float wspY = 5.0f / wysokoscObszaruUzytkownika;
	float dx = przesunięcieKursoraMyszy.x * wspX;
	float dy = przesunięcieKursoraMyszy.y * wspY;

	Macierz4 m = Macierz4::Przesunięcie(dx, -dy, 0);

	ModyfikujPołożenieKamery(m);
	RysujScenę();
}

void COknoGL::ObsługaRolkiMyszy(WPARAM wParam)
{
	const float czułość = 10.0f;
	short zmianaPozycjiRolki = (short)HIWORD(wParam);
	float przesuniecie = zmianaPozycjiRolki / abs(zmianaPozycjiRolki) / czułość;
	ModyfikujPołożenieKamery(Macierz4::Przesunięcie(0, 0, przesuniecie)); //tu w niektórych trybach w kierunku położenia kamery
	RysujScenę();
}

void COknoGL::SwobodneObrotyKamery(const bool inicjacja, const float początkowe_dx, const float początkowe_dy, const float współczynnikWygaszania)
{
	static float dx = 0;
	static float dy = 0;
	static float wsp = 0;
	if (inicjacja)
	{
		swobodneObrotyKameryAktywne = true;
		dx = początkowe_dx;
		dy = początkowe_dy;
		wsp = współczynnikWygaszania; if (wsp < 0) wsp = 0; if (wsp>1) wsp = 1;
		return;
	}
	else
	{
		dx *= wsp;
		dy *= wsp;
		if (fabs(dx) < 0.001f && fabs(dy) < 0.001f) swobodneObrotyKameryAktywne = false;
		Macierz4 m = Macierz4::ObrótXYZ(dy, dx, 0);
		ModyfikujPołożenieKamery(m);
		RysujScenę();
	}
}
#pragma endregion

#pragma region Teksturowanie
#include "Pliki.h"

void COknoGL::PrzygotujTekstury(unsigned int liczbaTekstur, char** nazwyPlikówTekstur)
{
	indeksyTekstur = new GLuint[liczbaTekstur];
	glGenTextures(liczbaTekstur, indeksyTekstur); //generowanie indeksow dla tekstur (uchwyt)
	
	int szerokośćTekstury;
	int wysokośćTekstury;
	for (unsigned int i = 0; i<liczbaTekstur; ++i)
	{
		glBindTexture(GL_TEXTURE_2D, indeksyTekstur[i]); //kolejne polecenia beda dotyczyc tej tekstury
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
		
		//wczytywanie tekstury z pliku
		unsigned long* tekstura = WczytajObrazZPlikuBitmap(uchwytOkna, nazwyPlikówTekstur[i], szerokośćTekstury, wysokośćTekstury, false, 255);
		glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, szerokośćTekstury, wysokośćTekstury, 0, GL_RGBA, GL_UNSIGNED_BYTE, tekstura);
		delete[] tekstura;

		//generowanie mipmap
		glHint(GL_GENERATE_MIPMAP_HINT, GL_NICEST);
		glGenerateMipmap(GL_TEXTURE_2D);
	}

	glBindTexture(GL_TEXTURE_2D, NULL);
}

void COknoGL::UsuńTekstury()
{
	//glActiveTexture(GL_TEXTURE0);
	glBindTexture(GL_TEXTURE_2D, NULL);
	glDeleteTextures(liczbaTekstur, indeksyTekstur);
	delete[] indeksyTekstur;	
	liczbaTekstur = 0;
}
#pragma endregion