#include "stdafx.h"
#include "OpenGLClass.h"
#include <iostream>
#include <fstream>
#include <string>
#include <sstream>
#include <cmath>

using namespace std;

//Konstruktor.
Vector3::Vector3()
{
	x = 0;
	y = 0;
	z = 0;
}

//Konstruktor.
Vector3::Vector3(float x, float y, float z)
{
	this->x = x;
	this->y = y;
	this->z = z;
}

//Metoda obliczajaca dlugosc wektora.
float Vector3::length()
{
	return sqrt(x*x + y*y + z*z);
}

//Metoda oblicajaca iloczyn skalarny dwoch wektorow.
Vector3 Vector3::crossProduct(Vector3 v1, Vector3 v2)
{
	return Vector3(v1.y*v2.z - v1.z*v2.y, v1.z*v2.x - v1.x*v2.z, v1.x*v2.y - v1.y*v2.x);
}

//Operator odejmujacy dwa wektory.
Vector3 operator-(Vector3 v1, Vector3 v2)
{
	return Vector3(v1.x - v2.x, v1.y - v2.y, v1.z * v2.z);
}

//Operator przypisania.
Vector3 &Vector3::operator=(const Vector3 &v)
{
	x = v.x;
	y = v.y;
	z = v.z;
	return *this;
}

//Operator dzielenia wektora przez liczbe.
Vector3 Vector3::operator/(float f)
{
	x = x/f;
	y = y/f;
	z = z/f;
	return *this;
}

//Operator dodajacy do wektora inny wektor.
Vector3 &Vector3::operator+=(const Vector3 &v)
{
	x += v.x;
	y += v.y;
	z += v.z;
	return *this;
}

//Metoda obliczajaca wektor normalny dla pojedynczego polygonu.
Vector3 OpenGLClass::calculateNormal(float x1, float y1, float z1, float x2, float y2, float z2, float x3, float y3, float z3)
{
    Vector3 t0(x1, y1, z1);
    Vector3 t1(x2, y2, z2);
    Vector3 t2(x3, y3, z3);

	//Liczymy iloczyn wektorowy i normalizujemy.
    Vector3 n = Vector3::crossProduct(t2 - t0, t1 - t0);
    n = n / n.length();
    return n;
} 

//Metoda obliczajaca wektory normalne dla kazdego z vertexow.
void OpenGLClass::calculateVertexNormals()
{
	//Inicjalizacja listy.
	vertexNormals = vector<Vector3>(list.size());

	//Dla kazdego vertexu dodajemy normalne scian, do ktorych nalezy i normalizujemy wynik.
	for (unsigned int i = 0; i < list.size(); i++)
    {
		for (unsigned j = 0; j < list[i].size(); j++) 
		 {
			 vertexNormals[i] += list[i][j];
		 }

		 vertexNormals[i] = vertexNormals[i] / (float)list[i].size();
     }
}

//Metoda wyznaczajaca bounding sphere.
void OpenGLClass::setBoundingSphere()
{
	//Wyznaczamy srodek.
	centerX = (float)(maxX + minX) / 2.0f;
    centerY = (float)(maxY + minY) / 2.0f;
    centerZ = (float)(maxZ + minZ) / 2.0f;
	//Wyznaczamy promien.
    if ((maxX - centerX) >= (maxY - centerY) && (maxX - centerX) >= (maxZ - centerZ))
       radius = abs(maxX - centerX);
     else if ((maxY - centerY) >= (maxX - centerX) && (maxY - centerY) >= (maxZ - centerZ))
       radius = abs(maxY - centerY);
     else if ((maxZ - centerZ) >= (maxX - centerX) && (maxZ - centerZ) >= (maxY - centerY))
       radius = abs(maxZ - centerZ);
}

//Konstruktor.
OpenGLClass::OpenGLClass()
{
	//Ustalamy poczatkowa pozycje.
	xPosition = 0.0f;
	yPosition = 0.0f;
	xRotation = 0.0f;
	yRotation = 0.0f;
	zoomScaling = 10.0f;

	useDefault = true;

	//Punkty skrajne dla domyslnego obiektu.
	maxX = 1;
	minX = -1;
	maxY = 1;
	minY = -1;
	maxZ = 1;
	minZ = -1;

	//Ustawienie bounding sphere dla domyslnego obiektu.
	setBoundingSphere();
} 

//Message map klasy OpenGLClass.
BEGIN_MESSAGE_MAP(OpenGLClass, CWnd)
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_CREATE()
	ON_WM_TIMER()
END_MESSAGE_MAP()

//Obsluga zdarzenia Paint.
void OpenGLClass::OnPaint()
{
	ValidateRect(NULL);
}

//Obsluga zdarzenia Size.
void OpenGLClass::OnSize(UINT nType, int cx, int cy)
{
	CWnd::OnSize(nType, cx, cy);

	//Jesli rozmiar okna jest mniejszy lub rowny zero, to nie robimy nic.
	if (0 >= cx || 0 >= cy) return;

	//Ustalenie widoku.
	glViewport(0, 0, cx, cy);

	//Udstawiamy kamere w zaleznosci od bounding sphere.
	glMatrixMode(GL_PROJECTION);

	glLoadIdentity();

	gluPerspective(35.0f, (float)cx / (float)cy, 0.1f * radius, 32 * radius);

	glMatrixMode(GL_MODELVIEW);
}

//Obsluga zdarzenia Create.
int OpenGLClass::OnCreate(LPCREATESTRUCT lpCreateStruct)
{
	if (CWnd::OnCreate(lpCreateStruct) == -1) return -1;

	//Inicjalizacja OpenGL.
	initializeOpenGL();

	return 0;
}

//Obsluga zdarzenia Draw.
void OpenGLClass::OnDraw(CDC *pDC)
{
	glLoadIdentity();

	//Ustawiamy i obracamy obiekt.
	glTranslatef(0.0f, 0.0f, -zoomScaling - radius);
	glTranslatef(xPosition, yPosition, 0.0f);
	glRotatef(xRotation, 1.0f, 0.0f, 0.0f);
	glRotatef(yRotation, 0.0f, 1.0f, 0.0f);
}

//Obsluga zdarzenia Timer - to dzieje sie w kazdej klatce.
void OpenGLClass::OnTimer(UINT nIDEvent)
{
	if (nIDEvent == 1)
	{
		glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);

		//Rysowanie sceny.
		drawScene();

		SwapBuffers(hdc);
	}

	CWnd::OnTimer(nIDEvent);
}

//Metoda tworzaca okno OpenGL.
void OpenGLClass::createOpenGL(RECT rect, CWnd *parent)
{
	CString className = AfxRegisterWndClass(CS_HREDRAW | CS_VREDRAW | CS_OWNDC, NULL, (HBRUSH)GetStockObject(BLACK_BRUSH), NULL);

	CreateEx(0, className, "OpenGL", WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS | WS_CLIPCHILDREN, rect, parent, 0);

	hWnd = parent;
}

void OpenGLClass::initializeOpenGL()
{
	int   pixelFormat;
	static PIXELFORMATDESCRIPTOR pfd;
	
	//Ustalamy pixel format.
	ZeroMemory( &pfd, sizeof(pfd) );
	pfd.nSize = sizeof(pfd);
	pfd.nVersion = 1;
	pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER;
	pfd.iPixelType = PFD_TYPE_RGBA;
	pfd.cColorBits = 24;
	pfd.cDepthBits = 16;

	//Pobieramy device context.
	hdc = GetDC()->m_hDC;
	
	//Ustawiamy pixel format.
	pixelFormat = ChoosePixelFormat(hdc, &pfd);
	SetPixelFormat(hdc, pixelFormat, &pfd);

	//Tworzymy rendering context.
	hrc = wglCreateContext(hdc);
	wglMakeCurrent(hdc, hrc);

	//Ustawiamy parametry renderowania sceny.
	glClearColor(0.0f, 0.0f, 1.0f, 1.0f);
	glClearDepth(1.0f);
	glFrontFace(GL_CCW);
	glCullFace(GL_BACK);
	glEnable(GL_DEPTH_TEST);
	glDepthFunc(GL_LEQUAL);

	//Ustalamy pozycje i kolor swiatla.
	float LightPosition[] = { 10.0f * radius, -5.0f * radius, -5.0f*radius, 0.0f };
    float LightColor[] = { 1.0f, 1.0f, 1.0f, 1.0f };

	//Ustawiamt shading model na smooth.
	glShadeModel(GL_SMOOTH);

	//Ustawienie swiatel.
	glLightfv(GL_LIGHT0, GL_POSITION, LightPosition);
	glLightfv(GL_LIGHT0, GL_DIFFUSE, LightColor);
	glEnable(GL_LIGHTING);
    glEnable(GL_LIGHT0);

	//Ustawienie kolorowego materialu.
	glEnable(GL_COLOR_MATERIAL);

	OnDraw(NULL);
}

//Metoda ustawiajaca nowe punkty skrajne.
void OpenGLClass::setMinMax(float x, float y, float z)
{
	if (x > maxX)
            maxX = x;
    if (x < minX)
            minX = x;
    if (y > maxY)
            maxY = y;
    if (y < minY)
            minY = y;
    if (z > maxZ)
            maxZ = z;
    if (z < minZ)
            minZ = z;
}

//Metoda wczytujaca obiekt z pliku *.obj.
vector<Vertex> OpenGLClass::loadFromFile(char *path)
{	
	//Pojedyncza linia.
	string line;
	//Plik.
	ifstream file(path);
	//Tymczasowa lista vertexow.
	vector<Vertex> vertices;
	
	//Tymczasowy vertex.
	Vertex vertex;

	if (file.is_open())
	{
		//Czytamy do konca pliku.
		while ( file.good() )
		{
			//Pobranie linii.
			getline (file, line);
			if (line != "")
			{
				//Wczytanie vertexu.
				if (line[0] == 'v' && line[1] == ' ')
				{
					//Pomijamy poczatek linii.
					string s = line.substr(2);
					string buffer;
					stringstream ss(s); 

					vector<string> tokens;

					//Usuniecie spacji i wczytanie kolejnych wartosci na liste.
					while (ss >> buffer)
						tokens.push_back(buffer);

					//Przypisanie wspolrzednych tymczasowemu vertexowi.
					vertex.x = (float)atof(tokens[0].c_str());
					vertex.y = (float)atof(tokens[1].c_str());
					vertex.z = (float)atof(tokens[2].c_str());

					//Ustawienie nowych punktow skrajnych.
					setMinMax(vertex.x, vertex.y, vertex.z);

					//Dodanie tymczasowego vertexu na liste vertexow.
					vertices.push_back(vertex);
				}
				//Wczytanie polygonu.
				if (line[0] == 'f')
				{
					//Pomijamy poczatek linii.
					string s = line.substr(2);
					string buffer;
					stringstream ss(s); 

					vector<int> tokens;

					//Usuniecie spacji i wczytanie kolejnych wartosci na liste.
					while (ss >> buffer)
					{
						//Rozroznienie formatu zapisu informacji o polygonach.
						if (!(buffer.find("/") != std::string::npos))
							tokens.push_back(atoi(buffer.c_str())-1);
						else
						{
							size_t pos = buffer.find("/");
							buffer = buffer.substr(0,pos);
							tokens.push_back(atoi(buffer.c_str())-1);
						}
					}

					//Dodanie polygonu do listy polygonow.
					faces.push_back(tokens);
				}
			}
		}
		file.close();
	}

	return vertices;
}

//Metoda rysujaca scene.
void OpenGLClass::drawScene()
{
	//Rozroznienie sytuacji poczatowej od tej po wczytaniu pliku.
	if (!useDefault)
	{		
		//Sytuacja po wczytaniu pliku.

		//Blokujemy oswietlenie i rysujemy siatke.
		glDisable(GL_LIGHTING);			
		glPolygonMode(GL_FRONT, GL_LINE);
		for (unsigned int i = 0; i < faces.size(); i++)
		{
			glBegin(GL_LINE_LOOP);
			for (unsigned int j = 0; j < faces[i].size(); j++)
			{
				glVertex3f(ver[faces[i][j]].x,  ver[faces[i][j]].y, ver[faces[i][j]].z);
			}
			glEnd();
		}	

		//Odblokowujemy oswietlenie i rysujemy powierznie oraz ustawiamy normalne vertexow.
		glEnable(GL_LIGHTING);

		glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
			
		for (unsigned int i = 0; i < faces.size(); i++)
		{
			glBegin(GL_POLYGON);
			for (unsigned int j = 0; j < faces[i].size(); j++)
			{
				glNormal3f(vertexNormals[faces[i][j]].x, vertexNormals[faces[i][j]].y, vertexNormals[faces[i][j]].z);
				glVertex3f(ver[faces[i][j]].x,  ver[faces[i][j]].y, ver[faces[i][j]].z);
			}
			glEnd();
		}		
	}
	else
	{
		//Sytuacja domyslna.

		//Wylaczamy oswietlenie i rysujemy siatke szescianu.
		glDisable(GL_LIGHTING);

		glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
		glBegin(GL_QUADS);

		glVertex3f( 1.0f,  1.0f, 1.0f);
		glVertex3f(-1.0f,  1.0f, 1.0f);
		glVertex3f(-1.0f, -1.0f, 1.0f);
		glVertex3f( 1.0f, -1.0f, 1.0f);
	
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f(-1.0f,  1.0f, -1.0f);
		glVertex3f( 1.0f,  1.0f, -1.0f);
		glVertex3f( 1.0f, -1.0f, -1.0f);
			
		glVertex3f( 1.0f, 1.0f,  1.0f);
		glVertex3f( 1.0f, 1.0f, -1.0f);
		glVertex3f(-1.0f, 1.0f, -1.0f);
		glVertex3f(-1.0f, 1.0f,  1.0f);

			
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f( 1.0f, -1.0f, -1.0f);
		glVertex3f( 1.0f, -1.0f,  1.0f);
		glVertex3f(-1.0f, -1.0f,  1.0f);

			
		glVertex3f( 1.0f,  1.0f,  1.0f);
		glVertex3f( 1.0f, -1.0f,  1.0f);
		glVertex3f( 1.0f, -1.0f, -1.0f);
		glVertex3f( 1.0f,  1.0f, -1.0f);

			
		glVertex3f(-1.0f, -1.0f, -1.0f);
		glVertex3f(-1.0f, -1.0f,  1.0f);
		glVertex3f(-1.0f,  1.0f,  1.0f);
		glVertex3f(-1.0f,  1.0f, -1.0f);

		glEnd();
		glEnable(GL_LIGHTING);
	}
}

//Metoda przygotowyaca obiekt po wczytaniu pliku.
void OpenGLClass::prepareLoadedFile()
{	
	//Ustawienie bounding sphere.
	setBoundingSphere();
	//Inicjalizacja listy.
	list = vector<vector<Vector3>>(ver.size());

	for (unsigned int i = 0; i < faces.size(); i++)
	{							
		//Obliczenie normalnej aktualnego polygonu.
		Vector3 normal = calculateNormal(
			ver[faces[i][0]].x, ver[faces[i][0]].y, ver[faces[i][0]].z, 
			ver[faces[i][1]].x, ver[faces[i][1]].y, ver[faces[i][1]].z,
			ver[faces[i][2]].x, ver[faces[i][2]].y, ver[faces[i][2]].z		
		); 
		for (unsigned int j = 0; j < faces[i].size(); j++)
		{
			//Dodanie normalnej polygonu do listy dla danego vertexu.
			list[faces[i][j]].push_back(normal);
		}
	}

	//Obliczenie normalnych vertexow.
	calculateVertexNormals();

	//Ustawienie trybu wyswietlania na "wczytane z pliku".
	if (useDefault)
		useDefault = false;
}