/*********************************************************************

Name:		Raytracer
Author:		Kamil Szatkowski "Netrix" (C)
Website:	http://www.netrix.org.pl/

*********************************************************************/
#include "Scene.hpp"
#include "NLib/nAssert.hpp"
#include "NLib/nLogger.hpp"
#include <stdio.h>
#include <math.h>
using namespace NIne;
#pragma warning(disable: 4996)	// fopen deprecated on windows

namespace
{
	const NIne::NUint32 ALIGNMENT = 16;
}

Scene::Scene() 
: m_bLoaded(false)
{
}
/********************************************************/
Scene::~Scene()
{
	releaseScene();
}
/********************************************************/
bool Scene::loadScene(const char* szFile)
{
	NAssert(m_bLoaded == false, "Release file first");

	FILE* pFile = fopen(szFile, "r");
	if(pFile == null)
	{
		NLogHead();
		NLogMessage("Failed to open file: %s", szFile);
		return true;
	}

	// States
	enum
	{
		E_VERTICES_COUNT,
		E_VERTICES,
		E_INDICES_COUNT,
		E_INDICES,
		E_SUBSETS,
		E_END
	};
	
	NUint32 uLine = 0;
	NUint32 uState = E_VERTICES_COUNT;
	char buffer[2048] = { 0 };
	NUint32 uIndex = 0;
	bool bArray = false;

	while(uState < E_END && !ferror(pFile) && !feof(pFile))
	{
		fgets(buffer, sizeof(buffer), pFile);	// read line
		++uLine;

		if(0 == buffer[0] || 0xA == buffer[0] || 0xD == buffer[0])
		{
			if(bArray && (uState == E_VERTICES || uState == E_INDICES || uState == E_SUBSETS))
			{
				++uState;
				bArray = false;
				uIndex = 0;
			}
			continue; 
		}
		else if(buffer[0] == '/' && buffer[1] == '/')
		{ continue;	}
		else if(buffer[0] == ';' && buffer[1] == ';')
		{ continue;	}
		else if(uState == E_VERTICES_COUNT)
		{
			NUint32 uTemp;
			if(strstr(buffer, "vertices") == buffer)
			{
				if(!sscanf(buffer, "vertices %d", &uTemp))
				{
					uIndex = __LINE__;
					goto InvalidFile;
				}
			}
			else if(!sscanf(buffer, "%d", &uTemp))
			{
				uIndex = __LINE__;
				goto InvalidFile;
			}
			m_aVertices.Create(uTemp);
			memset(m_aVertices.GetBuffer(), 0, m_aVertices.GetSize() * sizeof(Vertex));
			uIndex = 0;
			bArray = false;
			++uState;
		}
		else if(uState == E_INDICES_COUNT)
		{
			NUint32 uTemp;
			if(strstr(buffer, "triangles") == buffer)
			{
				if(!sscanf(buffer, "triangles %d", &uTemp))
				{
					uIndex = __LINE__;
					goto InvalidFile;
				}
			}
			else if(!sscanf(buffer, "%d", &uTemp))
			{
				uIndex = __LINE__;
				goto InvalidFile;
			}

			m_aFaces.Create(uTemp, ALIGNMENT);
			uIndex = 0;
			bArray = false;
			++uState;
		}
		else if(uState == E_VERTICES)
		{
			float x, y, z;
			if(!sscanf(buffer, "%f %f %f", &x, &y, &z))
			{
				uIndex = __LINE__;
				goto InvalidFile;
			}
			bArray = true;
			m_aVertices[uIndex].v3Position.x = x;
			m_aVertices[uIndex].v3Position.y = y;
			m_aVertices[uIndex].v3Position.z = z;			
			uIndex++;
		}
		else if(uState == E_INDICES)
		{
			NUint32 a, b, c;
			if(!sscanf(buffer, "%d %d %d", &a, &b, &c))
			{
				uIndex = __LINE__;
				goto InvalidFile;
			}
			m_aFaces[uIndex].pVertices[0] = &m_aVertices[a];
			m_aFaces[uIndex].pVertices[1] = &m_aVertices[b];
			m_aFaces[uIndex].pVertices[2] = &m_aVertices[c];

			bArray = true;
			uIndex++;
		}
		else if(uState == E_SUBSETS)
		{
			NUint32 uFace[10];	// Ugly but cannot do it other way for that moment
			NUint32 uCount;

			uCount = sscanf(buffer, "%d %d %d %d %d %d %d %d %d %d", 
					   uFace, uFace + 1, uFace + 2, uFace + 3, uFace + 4, uFace + 5, uFace + 6,
					   uFace + 7, uFace + 8, uFace + 9);
			//while(sscanf(buffer, "%d", &uFace) > 0)
			for(NUint32 i = 0; i < uCount; ++i)
			{
				m_aFaces[uIndex++].uAttributeIndex = uFace[i];
			}
			bArray = true;
		}
	}

	strcpy(m_szName, szFile);
	m_bLoaded = true;
	fclose(pFile);
	return false;
InvalidFile:
	m_aVertices.Release();
	m_aFaces.Release();
	NLogError("Invalid file"); NLogMessage(", scene line: %u, file line: %u\n", uIndex, uLine);
	fclose(pFile);
	return true;
}
/********************************************************/
void Scene::releaseScene()
{
	m_aVertices.Release();
	m_aFaces.Release();

	m_bLoaded = false;
}
/********************************************************/
const NUint32 g_modulo[] = { 0, 1, 2, 0, 1 };

bool Scene::testTriangle(NSize_t uFaceIndex, const NMVector3f& rayPos, const NMVector3f& rayDir, RayTestOutput* pRayOut) const
{
	NAssert(m_bLoaded == true, "Load file first");
	NAssert(pRayOut != null, "Invalid output pointer");

	const Vertex* pP3 = m_aFaces[uFaceIndex].pVertices[2];

	NUint32 axis = m_aFaces[uFaceIndex].uAxis;
	NUint32 a = g_modulo[axis + 1];//(axis + 1) % 3;
	NUint32 b = g_modulo[axis + 2];//(axis + 2) % 3;

	float x = (rayDir.v[axis] + m_aFaces[uFaceIndex].na * rayDir.v[a] + m_aFaces[uFaceIndex].nb * rayDir.v[b]);

	// Perpendicular to plane
	if(NM_IN_EPSILON_F(x)) { return false; }

	pRayOut->bFront = x * m_aFaces[uFaceIndex].v3Normal.v[axis] < 0.0f;
	pRayOut->t = (m_aFaces[uFaceIndex].nd - rayPos.v[axis] - m_aFaces[uFaceIndex].na * rayPos.v[a] - m_aFaces[uFaceIndex].nb * rayPos.v[b]) / x;

	if(pRayOut->t < 0.0f) { return false; }

	pRayOut->oPoint = rayPos + rayDir * pRayOut->t;

	float oa = pRayOut->oPoint.v[a] - pP3->v3Position.v[a];
	float ob = pRayOut->oPoint.v[b] - pP3->v3Position.v[b];

	pRayOut->u = ob * m_aFaces[uFaceIndex].ua + oa * m_aFaces[uFaceIndex].ub;
	if(pRayOut->u < 0.0f) { return false; }
	pRayOut->v = ob * m_aFaces[uFaceIndex].va + oa * m_aFaces[uFaceIndex].vb;
	if(pRayOut->v < 0.0f || pRayOut->u + pRayOut->v > 1.0f) { return false; }

	return true;
}
/********************************************************/
bool Scene::testTriangle(const FaceData* pFace, const NMVector3f& rayPos, const NMVector3f& rayDir, float fMaxDist, RayTestOutput* pRayOut) const
{
	NAssert(m_bLoaded == true, "Load file first");
	NAssert(pRayOut != null, "Invalid output pointer");

	const Vertex* pP3 = pFace->pVertices[2];

	NUint32 axis = pFace->uAxis;
	NUint32 a = g_modulo[axis + 1];//(axis + 1) % 3;
	NUint32 b = g_modulo[axis + 2];//(axis + 2) % 3;

	float x = (rayDir.v[axis] + pFace->na * rayDir.v[a] + pFace->nb * rayDir.v[b]);

	// Perpendicular to plane
	if(NM_IN_EPSILON_F(x)) { return false; }

	pRayOut->bFront = x * pFace->v3Normal.v[axis] < 0.0f;
	pRayOut->t = (pFace->nd - rayPos.v[axis] - pFace->na * rayPos.v[a] - pFace->nb * rayPos.v[b]) / x;

	if(pRayOut->t < 0.0f || pRayOut->t > fMaxDist) { return false; }

	pRayOut->oPoint = rayPos + rayDir * pRayOut->t;

	float oa = pRayOut->oPoint.v[a] - pP3->v3Position.v[a];
	float ob = pRayOut->oPoint.v[b] - pP3->v3Position.v[b];

	pRayOut->u = ob * pFace->ua + oa * pFace->ub;
	if(pRayOut->u < 0.0f) { return false; }
	pRayOut->v = ob * pFace->va + oa * pFace->vb;
	if(pRayOut->v < 0.0f || pRayOut->u + pRayOut->v > 1.0f) { return false; }

	return true;
}
/********************************************************/
bool Scene::rayTriangleTestFast(NSize_t uFaceIndex, const NMVector3f& rayPos, const NMVector3f& rayDir, float fMaxDist, RayTestOutput* pRayOut) const
{
	NAssert(m_bLoaded == true, "Load file first");
	NAssert(pRayOut != null, "Invalid output pointer");

	const NMVector3f& p1 = m_aFaces[uFaceIndex].pVertices[0]->v3Position;
	const NMVector3f& p2 = m_aFaces[uFaceIndex].pVertices[1]->v3Position;
	const NMVector3f& p3 = m_aFaces[uFaceIndex].pVertices[2]->v3Position;

	NMVector3f v1 = p2 - p1;
	NMVector3f v2 = p3 - p1;

	NMVector3f dir_v2 = NMVector3fCross(rayDir, v2);
	float det = NMVector3fDot(v1, dir_v2);

	if(NM_IN_EPSILON_F(det))
	{
		return false;	// Not in triangle
	}
	pRayOut->bFront = det > 0.0f;	// Side of triangle

	float invDet = 1.0f / det;
	NMVector3f T = NMVector3fSub(rayPos, p1);
	pRayOut->u = NMVector3fDot(T, dir_v2) * invDet;
	if(pRayOut->u < 0.0f || pRayOut->u > 1.0f)
	{
		return false;	// Not in triangle
	}

	NMVector3f t_v1 = NMVector3fCross(T, v1);
	pRayOut->v = NMVector3fDot(rayDir, t_v1) * invDet;

	if(pRayOut->v < 0.0f || pRayOut->v + pRayOut->u > 1.0f)
	{
		return false;	// Not in triangle
	}

	pRayOut->t = NMVector3fDot(v2, t_v1) * invDet;
	if(pRayOut->t > fMaxDist) { return false; }

	pRayOut->oPoint = rayPos + pRayOut->t * rayDir;

	return true;
}
/********************************************************/
bool Scene::rayTriangleTestFast(const FaceData* pFace, const NMVector3f& rayPos, const NMVector3f& rayDir, float fMaxDist, RayTestOutput* pRayOut) const
{
	NAssert(m_bLoaded == true, "Load file first");
	NAssert(pRayOut != null, "Invalid output pointer");

	const NMVector3f& p1 = pFace->pVertices[0]->v3Position;
	const NMVector3f& p2 = pFace->pVertices[1]->v3Position;
	const NMVector3f& p3 = pFace->pVertices[2]->v3Position;

	NMVector3f v1 = p2 - p1;
	NMVector3f v2 = p3 - p1;

	NMVector3f dir_v2 = NMVector3fCross(rayDir, v2);
	float det = NMVector3fDot(v1, dir_v2);

	if(NM_IN_EPSILON_F(det))
	{
		return false;	// Not in triangle
	}
	pRayOut->bFront = det > 0.0f;	// Side of triangle

	float invDet = 1.0f / det;
	NMVector3f T = NMVector3fSub(rayPos, p1);
	pRayOut->u = NMVector3fDot(T, dir_v2) * invDet;
	if(pRayOut->u < 0.0f || pRayOut->u > 1.0f)
	{
		return false;	// Not in triangle
	}

	NMVector3f t_v1 = NMVector3fCross(T, v1);
	pRayOut->v = NMVector3fDot(rayDir, t_v1) * invDet;

	if(pRayOut->v < 0.0f || pRayOut->v + pRayOut->u > 1.0f)
	{
		return false;	// Not in triangle
	}

	pRayOut->t = NMVector3fDot(v2, t_v1) * invDet;
	if(pRayOut->t > fMaxDist) { return false; }

	pRayOut->oPoint = rayPos + pRayOut->t * rayDir;

	return true;
}
/********************************************************/
void Scene::precalculateFaces()
{
	for(NUint32 iFace = 0; iFace < m_aFaces.GetSize(); ++iFace)
	{
		const NMVector3f& p1 = m_aFaces[iFace].pVertices[0]->v3Position;
		const NMVector3f& p2 = m_aFaces[iFace].pVertices[1]->v3Position;
		const NMVector3f& p3 = m_aFaces[iFace].pVertices[2]->v3Position;

		NMVector3f v1 = p1 - p3;
		NMVector3f v2 = p2 - p3;
		NMVector3f vNormal = NMVector3fCross(v2, v1);

		// Sum normal for each vertex
		FaceData& face = m_aFaces[iFace];
		const NMVector3f v[3] = { p1, p2, p3 };
		for(NUint32 k = 0; k < 3; k++)
		{
			NMVector3f a = v[(k + 1) % 3] - v[k];
			NMVector3f b = v[(k + 2) % 3] - v[k];
			
			float weight = acosf(NMVector3fDot(a, b) / (NMVector3fLength(a) * NMVector3fLength(b)));
			face.pVertices[k]->v3Normal += weight * NMVector3fNormalize(vNormal);//vNormal;
		}

		vNormal = NMVector3fNormalize(vNormal);

		// Choosing dominating axis
		if(fabs(vNormal.x) > fabs(vNormal.y))
		{
			if(fabs(vNormal.x) > fabs(vNormal.z))		{ m_aFaces[iFace].uAxis = X_AXIS;	}
			else										{ m_aFaces[iFace].uAxis = Z_AXIS;	}
		}
		else
		{
			if(fabs(vNormal.y) > fabs(vNormal.z))		{ m_aFaces[iFace].uAxis = Y_AXIS;	}
			else										{ m_aFaces[iFace].uAxis = Z_AXIS;	}
		}

		NUint32 axis = m_aFaces[iFace].uAxis;
		NUint32 a = (axis + 1) % 3;
		NUint32 b = (axis + 2) % 3;

		float vden = 1.0f / (v1.v[a] * v2.v[b] - v2.v[a] * v1.v[b]);
		face.va = v1.v[a] * vden;
		face.vb = -v1.v[b] * vden;

		float uden = 1.0f / (v1.v[b] * v2.v[a] - v2.v[b] * v1.v[a]);
		face.ua = v2.v[a] * uden;
		face.ub = -v2.v[b] * uden;

		float den = 1.0f / vNormal.v[axis];
		face.na = vNormal.v[a] * den;
		face.nb = vNormal.v[b] * den;
		face.nd = NMVector3fDot(vNormal, p3) * den;

		face.v3Normal = vNormal;

		// Finding texture info
		face.v3Max = NMVector3fMax(p1, NMVector3fMax(p2, p3));
		face.v3Min = NMVector3fMin(p1, NMVector3fMin(p2, p3));

		switch(face.uAxis)
		{
		case Y_AXIS:
			if(vNormal.y > 0)
			{
				face.v3Binormal = NMVector3fLoad(0.0f, 0.0f, -1.0f);
				face.v3Tangent = NMVector3fCross(vNormal, face.v3Binormal);
			}
			else
			{
				face.v3Binormal = NMVector3fLoad(0.0f, 0.0f, 1.0f);
				face.v3Tangent = NMVector3fCross(vNormal, face.v3Binormal);
			}
			break;
		case X_AXIS:
		case Z_AXIS:
			{
				face.v3Binormal = NMVector3fLoad(0.0f, 1.0f, 0.0f);
				face.v3Tangent = NMVector3fCross(vNormal, face.v3Binormal);
			}
			break;
		}

		NMVector3f v3FaceSize = face.v3Max - face.v3Min;
		face.v3TangentSize = NMVector3fLoad(NMVector3fDot(v3FaceSize, face.v3Tangent), NMVector3fDot(v3FaceSize, face.v3Binormal), NMVector3fDot(v3FaceSize, vNormal));
	}

	for(NSize_t i = 0; i < m_aVertices.GetSize(); ++i)
	{
		m_aVertices[i].v3Normal = NMVector3fNormalize(m_aVertices[i].v3Normal);
	}
}
/********************************************************/
void Scene::findMinMax()
{
	m_v3Min = m_v3Max = m_aVertices[0].v3Position;

	for(NUint32 i = 1; i < m_aVertices.GetSize(); ++i)
	{
		m_v3Max = NMVector3fMax(m_v3Max, m_aVertices[i].v3Position);
		m_v3Min = NMVector3fMin(m_v3Min, m_aVertices[i].v3Position);
	}

	m_v3Min -= NMVector3fLoad(0.1f, 0.1f, 0.1f);
	m_v3Max += NMVector3fLoad(0.1f, 0.1f, 0.1f);

	m_v3Center = (m_v3Min + m_v3Max) / 2;

	m_fMaxDomainLength = NMVector3fLength(m_v3Max - m_v3Min);
}
/********************************************************/
void Scene::fixDomain()
{
	// Searching for coefficient
	NMVector3f v3Size = m_v3Max - m_v3Min;

	float fMaxDim = max(max(v3Size.x, v3Size.y), v3Size.z);

	float PERFECT_DOMAIN_SIZE = 10000.0f;

	m_fDomainFactor = PERFECT_DOMAIN_SIZE / fMaxDim;

	// Fixing positions
	for(NSize_t i = 0; i < m_aVertices.GetSize(); ++i)
	{
		m_aVertices[i].v3Position *= m_fDomainFactor;
	}

	m_v3Min *= m_fDomainFactor;
	m_v3Max *= m_fDomainFactor;
	m_fMaxDomainLength *= m_fDomainFactor;
}
/********************************************************/
void Scene::preprocessScene()
{
	findMinMax();
	fixDomain();
	precalculateFaces();
}