///////////////////////////////////////////////////////////////////////////////
//
// File   : Tools_TangentSpace.cpp
// Author : Andy Tate
// Date   : 13-May-2005
//
// Copyright (C) 2005 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "MeshProcessing/Tools_TangentSpace.h"

// Engine independent.
#include "Core/Asura_Maths.h"
#include "Mesh/Asura_Mesh.h"
#include "Rendering/Asura_Module.h"

//Tools
#include "Load&Save/LevelExport/Tools_Level_Export.h"
#include "Misc/Tools_Adjacency.h"

// P R O T O T Y P E S   A N D   I N L I N E   F U N C T I O N S /////////////

static void Swap( float& xValue1, float& xValue2 ) { float xTemp = xValue1; xValue1 = xValue2; xValue2 = xTemp; }
//static void Swap( Asura_Vector_2& xValue1, Asura_Vector_2& xValue2 ) { Asura_Vector_2 xTemp = xValue1; xValue1 = xValue2; xValue2 = xTemp; }
static void Swap( Asura_Vector_3& xValue1, Asura_Vector_3& xValue2 ) { Asura_Vector_3 xTemp = xValue1; xValue1 = xValue2; xValue2 = xTemp; }

// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

void Tools_TangentSpace::CalculateTangentAndBinormalForPoly( const Asura_Mesh* pxMesh, int iPolyIdx, u_int uTextureIndex,
                                                             Asura_Vector_3& xTangent, Asura_Vector_3& xBinormal )
{
    Asura_Vector_3 axPositions[3];
    Asura_Vector_2 axTextureCoords[3];
	Asura_Vector_2 axTextureCoords2[3];

    for( u_int u = 0; u < 3; ++u )
    {
        int iVertexID = pxMesh->PolygonDataPtr[iPolyIdx].VertexID[u];

        axPositions[u]       = pxMesh->VertexDataPtr[iVertexID].Position;

		const Asura_Mesh_TextureInfo* pxTexInfo = pxMesh->GetTextureInfoForExport(iPolyIdx);
        axTextureCoords[u].x = pxTexInfo->U[uTextureIndex][u];
        axTextureCoords[u].y = 1.0f - pxTexInfo->V[uTextureIndex][u];
    }

    CalculateTangentAndBinormal( axPositions, axTextureCoords, xTangent, xBinormal, pxMesh->Name.GetString() );
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_TangentSpace::CalculateTangentAndBinormal
// Author       : Andy Tate
// Date         : 13-May-2005
//
// Description  : Calculates the tangent and binormal vectors given the specified
//				  array of 3 vertices making up a triangle.
//
// Parameters   : Arrays of vertex positions and texture co-ordinates along with
//				  references to the vectors to be calculated.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Tools_TangentSpace::CalculateTangentAndBinormal( const Asura_Vector_3* pxPositions,
													  const Asura_Vector_2* pxTextureCoords,
													  Asura_Vector_3& rxTangent,
													  Asura_Vector_3& rxBinormal,
													  const char* szMeshName )
{
	float			afU [3];
	float			afV [3];
	Asura_Vector_3	axPositions [3];


	// Initialise the local data...

	for (int i = 0; i < 3; i++)
	{
		axPositions [i]	= pxPositions [i];
		afU [i]			= pxTextureCoords [i].x;
		afV [i]			= pxTextureCoords [i].y;
	}


	// *** calc the tangent vector ***
	{
		// first sort vertices based on v coord
		if (afV [0] < afV [1])
		{
			Swap	(afU [0],			afU [1]);
			Swap	(afV [0],			afV [1]);
			Swap	(axPositions [0],	axPositions [1]);
		}
		if (afV [0] < afV [2])
		{
			Swap	(afU [0],			afU [2]);
			Swap	(afV [0],			afV [2]);
			Swap	(axPositions [0],	axPositions [2]);
		}
		if (afV [1] < afV [2])
		{
			Swap	(afU [1],			afU [2]);
			Swap	(afV [1],			afV [2]);
			Swap	(axPositions [1],	axPositions [2]);
		}

		// find offset along edge2 to the 'middle' v coord
		float fLambda;
		float fDivisor = (afV [2] - afV [0]);

		if (Asura_Maths::Abs(fDivisor) < 0.0001f)
		{
			fLambda = 1.0f;
		}
		else
		{
			fLambda = (afV [1] - afV [0]) / fDivisor;
		}

		// find the vertex position associated with this
		const Asura_Vector_3 xDeltaPos = ( axPositions[2] - axPositions[0] );
		const Asura_Vector_3 xPos = xDeltaPos * fLambda + axPositions[0];

		// tangent is the ray from the middle vertex to the lerped vertex
		rxTangent = xPos - axPositions [1];

		Asura_Assertf( rxTangent.MagnitudeSquared() > 1.0e-15f, ("Tangent is too small\nMesh : %s\nIf the mesh name is not given or is tempMultiMaterialCollectionMesh then using 'Find Tangent Errors' with 'Check All Loaded Meshes' may help with identifying it",
													 szMeshName ? szMeshName : "(Name not provided)" ) );

		// make sure tangent points in the right direction
		const float fInterpolatedU = ( afU[2] - afU[0] ) * fLambda + afU[0];

		if ( fInterpolatedU < afU[1] )
		{
			rxTangent = -rxTangent;
		}
	}

	// *** calc the binormal vector ***
	{
		// first sort vertices based on u coord
		if (afU [0] < afU [1])
		{
			Swap	(afU [0],			afU [1]);
			Swap	(afV [0],			afV [1]);
			Swap	(axPositions [0],	axPositions [1]);
		}
		if (afU [0] < afU [2])
		{
			Swap	(afU [0],			afU [2]);
			Swap	(afV [0],			afV [2]);
			Swap	(axPositions [0],	axPositions [2]);
		}
		if (afU [1] < afU [2])
		{
			Swap	(afU [1],			afU [2]);
			Swap	(afV [1],			afV [2]);
			Swap	(axPositions [1],	axPositions [2]);
		}

		// find offset along edge2 to the 'middle' v coord
		float fLambda;
		float fDivisor = (afU [2] - afU [0]);

		if (Asura_Maths::Abs(fDivisor) < 0.0001f)
		{
			fLambda = 1.0f;
		}
		else
		{
			fLambda = (afU [1] - afU [0]) / fDivisor;
		}

		// find the vertex position associated with this
		const Asura_Vector_3 xDeltaPos = ( axPositions[2] - axPositions[0] );
		const Asura_Vector_3 xPos = xDeltaPos * fLambda + axPositions [0];

		// binormal is the ray form the middle vertex to the lerped vertex
		rxBinormal = xPos - axPositions [1];
		Asura_Assertf( rxBinormal.MagnitudeSquared() > 1.0e-15f, ("Binormal is too small\nMesh : %s\nIf the mesh name is not given or is tempMultiMaterialCollectionMesh then using 'Find Tangent Errors' with 'Check All Loaded Meshes' may help with identifying it",
													 szMeshName ? szMeshName : "(Name not provided)" ) );

		// make sure binormal points in the right direction
		const float fInterpolatedV = (afV [2] - afV [0]) * fLambda + afV [0];

		if (fInterpolatedV > afV [1])
		{
			rxBinormal = -rxBinormal;
		}
	}
}

/// End of function 'Tools_TangentSpace::CalculateTangentAndBinormal'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Tools_TangentSpace::ClampPerpendicularToNormal
// Author       : Andy Tate
// Date         : 20-Sep-2005
//
// Description  : Clamps the specified input-vector to be perpendicular to the
//				  specified normal-vector.
//
// Parameters   : A reference to the normal-vector to use and a reference to the
//				  vector to be clammped perpendicular to it.
//
// Return value : The normalised perpendicular vector calculated.
////////////////////////////////////////////////////////////////////////////////

Asura_Vector_3 Tools_TangentSpace::ClampPerpendicularToNormal (const Asura_Vector_3& xNormal,
															   const Asura_Vector_3& xInput,
															   const char* pszMeshName /*= "<Unknown>"*/, const int iPolygonIndex, bool* pbErrorFlag)
{
#if defined( ASURA_ASSERT)
	if ( xInput.MagnitudeSquared() < 1.0e-15f )
	{
		if(pbErrorFlag) *pbErrorFlag = true;
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Code: bad input perpendicular vector. Mesh '%s'", pszMeshName );
	}
	Asura_Vector_3 xNNormal( xNormal);
	xNNormal.Normalise();
	Asura_Vector_3 xNInput( xInput);
	xNInput.Normalise();
	const float fNNDotNI = xNNormal * xNInput;
	const float fAbs = 1.0f - Asura_Maths::Abs( fNNDotNI );
	if ( fAbs < 1.0e-15f )
	{
		if(pbErrorFlag) *pbErrorFlag = true;
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Code: Parallel Vectors cannot be used to calc Clamped Perpendicular. xNormal( %f, %f, %f) xInput( %f, %f, %f). Mesh '%s Polygon %d'", xNormal.GetX(), xNormal.GetY(), xNormal.GetZ(), xInput.GetX(), xInput.GetY(), xInput.GetZ(), pszMeshName, iPolygonIndex );
	}
#endif // #if defined( ASURA_ASSERT)

	float			fDotProduct	= xNormal * xInput;

	Asura_Vector_3	xOutput (xNormal);

	// Make sure the output-vector is perpendicular to the normal...
	xOutput		*= -fDotProduct;
	xOutput		+= xInput;

#if defined( ASURA_ASSERT)
	if ( xOutput.MagnitudeSquared() < 1.0e-15f )
	{
		if(pbErrorFlag) *pbErrorFlag = true;
		Tools_Level_Export::AddMessage( Tools_Level_Export::MT_WARNING, "Code: bad output perpendicular vector. Mesh '%s Polygon %d'", pszMeshName, iPolygonIndex );
	}
#endif // #if defined( ASURA_ASSERT)

	xOutput.Normalise();

	// Return the output-vector...
	return	(xOutput);
}

/// End of function 'Tools_TangentSpace::ClampPerpendicularToNormal'. ///


/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
/*-------------------------**
** Tools_Mesh_TangentSpace **
**-------------------------*/


Tools_Mesh_TangentSpace::Tools_Mesh_TangentSpace()
: m_iModuleIndex(-1)
, m_pxMesh(0)
, m_pxMeshAdj(0)
, m_pxPolyTangents(0)
, m_pxPolyBinormals(0)
{
}

Tools_Mesh_TangentSpace::~Tools_Mesh_TangentSpace()
{
	delete m_pxMeshAdj;
	delete [] m_pxPolyTangents;
	delete [] m_pxPolyBinormals;
}

void Tools_Mesh_TangentSpace::Initialise(const Asura_Mesh* pxMesh, int iModuleIndex)
{
	Asura_Assert(m_iModuleIndex == -1 && m_pxMesh == 0,"Already initialised");

	if(iModuleIndex != -1)
	{
		m_iModuleIndex = iModuleIndex;
		m_pxMesh = Asura_Module_System::GetMeshForModule(iModuleIndex);
		Asura_Assert(m_pxMesh,"");
	}
	else
	{
		Asura_Assert(pxMesh,"");
		m_pxMesh = pxMesh;
		//Doing an individual mesh , so need to initialise a local copy of the adjacencies
		m_pxMeshAdj = new Tools_MeshAdjacency;
		m_pxMeshAdj->Initialise(m_pxMesh);
	}

	//calculate the tangent and binormals for each polygon. RAR 06/07/2007
	m_pxPolyTangents = new Asura_Vector_3[m_pxMesh->NumberOfPolygons][2];
	m_pxPolyBinormals = new Asura_Vector_3[m_pxMesh->NumberOfPolygons][2];

	for(int p=0; p<m_pxMesh->NumberOfPolygons; p++)
	{
		Tools_TangentSpace::CalculateTangentAndBinormalForPoly(m_pxMesh, p, 0, m_pxPolyTangents[p][0], m_pxPolyBinormals[p][0]);
		Tools_TangentSpace::CalculateTangentAndBinormalForPoly(m_pxMesh, p, 1, m_pxPolyTangents[p][1], m_pxPolyBinormals[p][1]);
		m_pxPolyTangents[p][0].Normalise();
		m_pxPolyBinormals[p][0].Normalise();
		m_pxPolyTangents[p][1].Normalise();
		m_pxPolyBinormals[p][1].Normalise();
	}
}

void Tools_Mesh_TangentSpace::GetTangentAndBinormal(int iPoly,int iVert, u_int uTextureIndex, const Asura_Vector_3& xVertexNormal, Asura_Vector_3& xOutputTangent, Asura_Vector_3& xOutputBinormal, bool* pbErrorFlag /*=NULL*/)
{
	
	Asura_Assert(m_pxMesh,"Not initialised");
	Asura_Assert(iPoly>=0 && iPoly < m_pxMesh->NumberOfPolygons, "");
	Asura_Assert(iVert>=0 && iVert < m_pxMesh->NumberOfVertices, "");

	if(!m_pxMesh->AreFaceNormalsValid())
	{
		//Do need the face normals , so calculate them despite the constness
		const_cast<Asura_Mesh*>(m_pxMesh)->CreateFaceNormals();
	}

	//Average together the binormals and tangents for any polygons that share this vertex
	Asura_Vector_3 xThisPolyTangent = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, m_pxPolyTangents[iPoly][uTextureIndex], m_pxMesh->Name.GetString(), iPoly, pbErrorFlag );
	Asura_Vector_3 xThisPolyBinormal = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, m_pxPolyBinormals[iPoly][uTextureIndex], m_pxMesh->Name.GetString(), iPoly, pbErrorFlag );

	xOutputTangent = xThisPolyTangent;
	xOutputBinormal = xThisPolyBinormal;


	const Tools_Polygon_Adjacency* pxPolyAdj;
	if(m_pxMeshAdj)
	{
		pxPolyAdj = m_pxMeshAdj->GetVertexAdjacencies(iVert);
	}
	else
	{
		pxPolyAdj = Tools_Adjacency::GetVertexAdjacencies(m_iModuleIndex,iVert);
	}

	for(const Tools_Adjacent_Polygon* pxAdj = pxPolyAdj->GetFirstAdjacency(); pxAdj; pxAdj = pxAdj->GetNext())
	{

		int iAdjPoly = pxAdj->GetPolygonIndex();

		if(pxAdj->IsBetweenModules())
		{
			//smoothing between modules
			if(m_iModuleIndex == -1) continue;

			int iModuleIndex2 = pxAdj->GetModuleIndex();

			#ifdef ASURA_USE_STREAMING
			Asura_Module_Handler* pxModHandler2 = Asura_Module_System::GetHandler(pxAdj->GetZoneHash());
			#else
			Asura_Module_Handler* pxModHandler2 = &Asura_Module_System::GetCurrentHandler();
			#endif

			Asura_Mesh* pxMesh2 = pxModHandler2->GetMeshForModule(iModuleIndex2);

			//Make sure the polygons share a smoothing group
			if(m_pxMesh->SmoothingGroupDataPtr && pxMesh2->SmoothingGroupDataPtr)
			{
				u_int uSharedGroups = 0x7fffffff & m_pxMesh->SmoothingGroupDataPtr[iPoly] & pxMesh2->SmoothingGroupDataPtr[iAdjPoly];
				if(!uSharedGroups) continue;
			}
			if(!pxMesh2->AreFaceNormalsValid()) pxMesh2->CreateFaceNormals();

			//Ignore polygons whose face normal is too close to being perpendicular to the vertex normal , since these
			//can create tangents/binormals that are parallel to the normal
			//RAR 24/10/2008
			if(pxMesh2->NormalsDataPtr[iAdjPoly].FaceNormal * xVertexNormal < 0.1f)
			{
				continue;
			}

			Asura_Vector_3 xThatPolyTangent,xThatPolyBinormal;
			Tools_TangentSpace::CalculateTangentAndBinormalForPoly(pxMesh2, iAdjPoly, 0, xThatPolyTangent, xThatPolyBinormal);

			xThatPolyTangent = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, xThatPolyTangent, m_pxMesh->Name.GetString(), iAdjPoly, pbErrorFlag );
			xThatPolyBinormal = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, xThatPolyBinormal, m_pxMesh->Name.GetString(), iAdjPoly, pbErrorFlag );
			
			//Ignore any polygons that have binormals and tangents that differ by too much
			//For now allow if within 90 degrees.
			//(This may now be redundant with the previous check)
			//RAR 06/07/2007
			if((xThisPolyTangent*xThatPolyTangent > 0.0f) && (xThisPolyBinormal*xThatPolyBinormal > 0.0f))
			{
				xOutputTangent += xThatPolyTangent;
				xOutputBinormal += xThatPolyBinormal;
			}
			
		}
		else
		{
			//smoothing within a module
			if(iAdjPoly == iPoly) continue;

			//Make sure the polygons share a smoothing group
			if(m_pxMesh->SmoothingGroupDataPtr)
			{
				u_int uSharedGroups = 0x7fffffff & m_pxMesh->SmoothingGroupDataPtr[iPoly] & m_pxMesh->SmoothingGroupDataPtr[iAdjPoly];
				if(!uSharedGroups) continue;
			}
		
			//Ignore polygons whose face normal is too close to being perpendicular to the vertex normal , since these
			//can create tangents/binormals that are parallel to the normal
			//RAR 24/10/2008
			if(m_pxMesh->NormalsDataPtr[iAdjPoly].FaceNormal * xVertexNormal < 0.1f)
			{
				continue;
			}
			Asura_Vector_3 xThatPolyTangent = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, m_pxPolyTangents[iAdjPoly][uTextureIndex], m_pxMesh->Name.GetString(), iAdjPoly, pbErrorFlag );
			Asura_Vector_3 xThatPolyBinormal = Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, m_pxPolyBinormals[iAdjPoly][uTextureIndex], m_pxMesh->Name.GetString(), iAdjPoly, pbErrorFlag );


			//Ignore any polygons that have binormals and tangents that differ by too much
			//For now allow if within 90 degrees.
			//(This may now be redundant with the previous check)
			//RAR 06/07/2007
			if((xThisPolyTangent*xThatPolyTangent > 0.0f) && (xThisPolyBinormal*xThatPolyBinormal > 0.0f))
			{
				xOutputTangent += xThatPolyTangent;
				xOutputBinormal += xThatPolyBinormal;
			}
		}
	}

	xOutputTangent.Normalise();
	xOutputBinormal.Normalise();

	// Clamp the tangent and binormal to the normal...
	xOutputTangent	= Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, xOutputTangent, m_pxMesh->Name.GetString(), iPoly, pbErrorFlag );
	xOutputBinormal	= Tools_TangentSpace::ClampPerpendicularToNormal( xVertexNormal, xOutputBinormal, m_pxMesh->Name.GetString(), iPoly, pbErrorFlag );

}



/// End of file. ///
