// MeshDecVC.cpp : Defines the entry point for the console application.
//

#include "stdafx.h"
#include <iostream>
#include <fstream>
#include <vector>
#include <map>
#include <array>
#include <string>
#include <algorithm>
#include <chrono>
#include <windows.h>
using namespace std;
using namespace std::chrono;

typedef array<int, 3> Array3i;
typedef array<double, 3> Array3D;
typedef vector<array<int, 3>> VectArr3i;
typedef array<double, 3> Array3D;
typedef vector<array<double, 3>> VectArr3d;
typedef map<Array3i, pair<Array3D, int>> ClustMap;
struct CVertex
{
	double X;
	double Y;
	double Z;
	Array3i Cluster;
};
typedef vector<CVertex> VectorVertex;

bool isAscii(ifstream& sourceFile)
{
	char a[4];
	sourceFile.seekg(80);
	sourceFile.read(a, 4);

	unsigned int numberOfTriangles = *reinterpret_cast<unsigned int*>(a);

	int fileSizeCalculated = 84 + (numberOfTriangles * 50);

	sourceFile.seekg(0);
	streampos beginOfFile, endOfFile;
	beginOfFile = sourceFile.tellg();
	sourceFile.seekg(0, ios::end);
	endOfFile = sourceFile.tellg();
	int fileSize = endOfFile - beginOfFile;

	if (fileSizeCalculated == 0)
	{
		return false;
	}
	if (fileSize == fileSizeCalculated)
	{
		return false;
	}
	sourceFile.seekg(0);
	return true;
}

//Function getVertexValues extracts vertex coordinates (double) from Ascii file line.
CVertex getVertexValues(string lineOfFile, int positionInLine)
{
	string numberFromFile;
	double value;
	CVertex vertex;

	lineOfFile = lineOfFile + ' ';
	positionInLine = positionInLine + 7;
	for (int i = 0; i < 3; i++)
	{
		while (lineOfFile.at(positionInLine) != ' ')
		{
			numberFromFile = numberFromFile + lineOfFile.at(positionInLine);
			positionInLine = positionInLine + 1;
		}
		value = stod(numberFromFile);

		if (value< FLT_EPSILON && value > -FLT_EPSILON)
		{
			value = 0.0;
		}

		switch (i)
		{
		case 0: vertex.X = value;
			break;
		case 1: vertex.Y = value;
			break;
		case 2: vertex.Z = value;
			break;
		}

		numberFromFile.clear();
		positionInLine = positionInLine + 1;
	}
	return vertex;
}

// Function asciiFile extracts coordinates of all triangle vertexes from Ascii file.
void asciiFile(ifstream& sourceFile, VectorVertex& vertexes)
{
	int positionOfStrVertex;
	string line;
	while (getline(sourceFile, line))
	{
		positionOfStrVertex = line.find("vertex");
		if (positionOfStrVertex != -1)
		{
			vertexes.push_back(getVertexValues(line, positionOfStrVertex));
		}
	};
	sourceFile.seekg(0);
}

// Function binaryFile extracts coordinates of all triangle vertexes from binary file.
void binaryFile(ifstream& sourceFile, VectorVertex& vertexes)
{
	char a[4];
	CVertex vertex;
	sourceFile.seekg(80);
	sourceFile.read(a, 4);
	unsigned int numberOfTriangles = *reinterpret_cast<unsigned int*>(a);
	for (int i = 0; i < numberOfTriangles; i++)
	{
		for (int j = 0; j < 3; j++)
		{
			sourceFile.read(a, 4);
		}
		for (int j = 0; j < 3; j++)
		{
			for (int k = 0; k < 3; k++)
			{
				sourceFile.read(a, 4);
				double value = *reinterpret_cast<float*>(a);

				switch (k)
				{
				case 0:vertex.X = value;
					break;
				case 1:vertex.Y = value;
					break;
				case 2:vertex.Z = value;
					break;
				}
			}
			vertexes.push_back(vertex);
		}
		sourceFile.read(a, 2);
	}
	sourceFile.seekg(0);
}

//Return the triangles and points of an stl binary file.
//If the point is shared by multiple triangles, only one copy of point is kept
void getUniqVertAndTrianglesList(VectorVertex& vertexes, VectArr3i& listOfTriangles, VectorVertex& uniqueVertexes)
{
	Array3D v;
	Array3i pos;
	map<Array3D, int> a;
	map<Array3D, int>::iterator iti;
	int i = 0;
	int j = 0;

	for (VectorVertex::iterator it = vertexes.begin(); it != vertexes.end(); ++it, j++)
	{
		v = { (*it).X ,(*it).Y ,(*it).Z };
		iti = a.find(v);

		if (iti == a.end())
		{
			a[v] = i;
			pos[j % 3] = i;
			i++;
			uniqueVertexes.push_back((*it));
			
		}
		else
		{
			pos[j % 3] = (*iti).second;
		}

		if (j % 3 == 2)
		{
			listOfTriangles.push_back(pos);
		}
	}
}
void vertToClusters(VectorVertex& vertexes, double epsilon, VectArr3i& listOfTriangles, VectArr3i& reducedTriangles, ClustMap& mapOfClusters)
{
	for (VectorVertex::iterator it = vertexes.begin(); it != vertexes.end(); it++)
	{
		int a = floor((*it).X / epsilon);
		int b = floor((*it).Y / epsilon);
		int c = floor((*it).Z / epsilon);
		(*it).Cluster = { a,b,c };
		ClustMap::iterator cm = mapOfClusters.find({ a,b,c });
		if (cm == mapOfClusters.end())
		{
			pair<Array3D, int > p;
			p.first = { (*it).X , (*it).Y ,(*it).Z };
			p.second = 1;
			mapOfClusters[{ a, b, c }] = p;
		}
		else
		{
			pair<Array3D, int > p;
			p.first = { (*cm).second.first[0] + (*it).X,(*cm).second.first[1] + (*it).Y,(*cm).second.first[2] + (*it).Z };
			p.second = (*cm).second.second + 1;
			(*cm).second = p;
		}
	}


	for (VectArr3i::iterator it = listOfTriangles.begin(); it != listOfTriangles.end(); it++)
	{
		////////
		bool ok = true;
		for (int i = 0; i < 3; i++)
		{
			if ((vertexes[(*it)[0]].Cluster[0] == vertexes[(*it)[1]].Cluster[0]) && (vertexes[(*it)[0]].Cluster[1] == vertexes[(*it)[1]].Cluster[1]) && (vertexes[(*it)[0]].Cluster[2] == vertexes[(*it)[1]].Cluster[2]))
			{
				ok = false;
			}
			else if ((vertexes[(*it)[0]].Cluster[0] == vertexes[(*it)[2]].Cluster[0]) && (vertexes[(*it)[0]].Cluster[1] == vertexes[(*it)[2]].Cluster[1]) && (vertexes[(*it)[0]].Cluster[2] == vertexes[(*it)[2]].Cluster[2]))
			{
				ok = false;
			}
			else if ((vertexes[(*it)[1]].Cluster[0] == vertexes[(*it)[2]].Cluster[0]) && (vertexes[(*it)[1]].Cluster[1] == vertexes[(*it)[2]].Cluster[1]) && (vertexes[(*it)[1]].Cluster[2] == vertexes[(*it)[2]].Cluster[2]))
			{
				ok = false;
			}
		}

		if (ok)
		{
			reducedTriangles.push_back((*it));
		}
	}
}
Array3D normal(CVertex A, CVertex B, CVertex C)
{
	Array3D normal, v1, v2;
	double lenght;

	v1 = { A.X - B.X, A.Y - B.Y, A.Z - B.Z };
	v2 = { C.X - B.X, C.Y - B.Y, C.Z - B.Z };

	normal[0] = v1[1] * v2[2] - v1[2] * v2[1];
	normal[1] = v1[2] * v2[0] - v1[0] * v2[2];
	normal[2] = v1[0] * v2[1] - v1[1] * v2[0];

	lenght = sqrt(normal[0] * normal[0] + normal[1] * normal[1] + normal[2] * normal[2]);

	if (lenght>=DBL_EPSILON)
	{
		normal[0] = normal[0] / lenght;
		normal[1] = normal[1] / lenght;
		normal[2] = normal[2] / lenght;
		return normal;
	}

		return { 0.0,0.0,0.0 };

}
void writeToSTL(VectArr3i& reducedTriangles, ClustMap& mapOfClusters, ofstream& stlFile, VectorVertex& vertexes)
{
	stlFile << "solid" << '\n';
	for (VectArr3i::iterator it = reducedTriangles.begin(); it != reducedTriangles.end(); it++)
	{
		CVertex A, B, C;
		Array3D n;
		ClustMap::iterator cm;

		cm = mapOfClusters.find(vertexes[(*it)[0]].Cluster);
		A.X = (*cm).second.first[0] / (*cm).second.second; A.Y = (*cm).second.first[1] / (*cm).second.second; A.Z = (*cm).second.first[2] / (*cm).second.second;
		cm = mapOfClusters.find(vertexes[(*it)[1]].Cluster);
		B.X = (*cm).second.first[0] / (*cm).second.second; B.Y = (*cm).second.first[1] / (*cm).second.second; B.Z = (*cm).second.first[2] / (*cm).second.second;
		cm = mapOfClusters.find(vertexes[(*it)[2]].Cluster);
		C.X = (*cm).second.first[0] / (*cm).second.second; C.Y = (*cm).second.first[1] / (*cm).second.second; C.Z = (*cm).second.first[2] / (*cm).second.second;
		
		n = normal(A, B, C);
		stlFile << "facet normal " << n[0] << " " << n[1] << " " << n[2] << '\n';
		stlFile << "outer loop" << '\n';
		stlFile << "vertex " << A.X << " " << A.Y << " " << A.Z << '\n';
		stlFile << "vertex " << B.X << " " << B.Y << " " << B.Z << '\n';
		stlFile << "vertex " << C.X << " " << C.Y << " " << C.Z << '\n';
		stlFile << "endloop" << '\n';
		stlFile << "endfacet" << '\n';
	}
	stlFile << "endsolid";
	stlFile.close();
}
void vertexClustering(string path, string name, double epsilon)
{
	VectorVertex vertices, uniqueVertices;
	VectArr3i triangles;
	VectArr3i reducedTriangles;
	ClustMap mapOfClusters;

	ifstream sourceFile(path, ios::binary);
	ofstream outputFile(name);

	if (isAscii(sourceFile))
	{
		asciiFile(sourceFile, vertices);
	}
	else
	{
		binaryFile(sourceFile, vertices);
	}

	getUniqVertAndTrianglesList(vertices, triangles, uniqueVertices);
	cout << "puvodni pocet trojuhelniku: " << triangles.size() << endl;
	vertToClusters(uniqueVertices, epsilon, triangles, reducedTriangles, mapOfClusters);
	cout << "vysledny pocet trojuhelniku: " << reducedTriangles.size() << endl;
	writeToSTL(reducedTriangles, mapOfClusters, outputFile, uniqueVertices);
	sourceFile.close();

}

int main()
{
	time_point<steady_clock> start, finish;
	start =high_resolution_clock::now();
	vertexClustering("..\\elephant.stl", "elephant1.stl", 1.0);

	finish = high_resolution_clock::now();

	auto sec = duration_cast<milliseconds>(finish - start);
	cout << "time: " << sec.count() <<" us"<< endl;
	cin.get();
    return 0;
}

