/**
*
* \file Asura_Decal_System.cpp
*
* Created by Kevin Lea - 11/11/2002 12:51:24
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
* \brief
*
**/

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

#include "Core/Asura.h"

// This file's header.
#include "SpecialFX/Asura_Decal_System.h"

// Engine independent.
#include "Debugging/Asura_Assert.h"
#include "Light/Asura_GI_Solution.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/Asura_Decal_Template.h"
#include "Physics/Asura_Physics_Environment.h"
#include "Physics/Asura_Physics_EnvironmentCache.h"

#ifdef ASURA_TOOLS
#ifdef ASURA_USE_STATIC_DECAL_SYSTEM
#include "Decals/Tools_StaticDecal_System.h"
#endif
#endif

/////////////////
// Static data //
/////////////////
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
Asura_Decal_Handler*						Asura_Decal_System::s_pxCurrentHandler	= NULL;	// Currently active handler.
Asura_TemplateHashTree<Asura_Decal_Handler>	Asura_Decal_System::s_xHandlerTree;				// Tree of all of the handlers available.
#else
Asura_Decal_Handler*						Asura_Decal_System::s_pxHandler = NULL;		// Single static-instance of a zone.
#endif

#ifdef ASURA_TARGET_SM3
Asura_Vector_3								Asura_Decal_System::s_xCurrentDecalNormal;		
#endif

#ifdef ASURA_TOOLS
#ifdef ASURA_USE_STATIC_DECAL_SYSTEM
bool										Asura_Decal_System::s_bCreatingStaticDecals = false;
bool										Asura_Decal_System::s_bAllowBackFaceProjection = false;
#endif
#endif

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

///////////////////////////////////////////////////////////////////////////////
// Asura_Decal_System
///////////////////////////////////////////////////////////////////////////////
void Asura_Decal_System::Initialise()
{
	// Setup project templates. Load textures.
    Project_SetupTemplates();
	Platform_Initialise();

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	Asura_Assert(!s_pxCurrentHandler, "Current handler should be null!");
#else
	if (!s_pxHandler)
	{
		s_pxHandler = new Asura_Decal_Handler();
	}
#endif
}

// MGH 14/1/2008: Reset both static and runtime decals.
void Asura_Decal_System::Reset()
{
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Reset each handler in-turn...
	s_xHandlerTree.Traverse(ResetHandlerCallback);
#else
	// Reset the current instance...
	GetCurrentHandler().Reset();
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	Platform_Reset();
}

// MGH 14/1/2008: Reset only runtime decals.
void Asura_Decal_System::ResetBeforeLoad()
{
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Reset each handler in-turn...
	s_xHandlerTree.Traverse(ResetHandlerCallback);
#else
	// Reset the current instance...
	GetCurrentHandler().Reset();
#endif	//defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
}

void Asura_Decal_System::Shutdown(void)
{
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Reset the entire handler tree...
	s_xHandlerTree.Reset();
	s_pxCurrentHandler = NULL;
#else
	// Reset the current instance...
	if(s_pxHandler)
	{
		s_pxHandler->Reset();
		delete s_pxHandler;
	}

	s_pxHandler = NULL;

#endif
	Platform_Shutdown();
}

void Asura_Decal_System::ResetHandlerCallback(Asura_Hash_ID uHash, Asura_Decal_Handler* pxHandler)
{
	pxHandler->Reset();
}

//////////////
// AddDecal //
//////////////
void Asura_Decal_System::AddDecal(const Asura_Vector_3& xPosition, const Asura_Vector_3& xSurfaceNormal, const u_int uDecalTemplateID, float fScale, float fRotation)
{
	/// Kevin Lea - 13/12/2002 17:14:40 - I'm using a quat to make an intuitive 'rotation around normal' here;
	/// this highlights the need for a fast 'random orientation from direction' function.

	Asura_Matrix_3x3 xOrient;

#ifdef ASURA_TARGET_SM3
	s_xCurrentDecalNormal = xSurfaceNormal;
#endif

	Asura_Maths::OrientationFromDirection(xOrient,xSurfaceNormal);

	const Asura_Decal_Template* pxDecalTemplate = Project_GetDecalTemplate(uDecalTemplateID);

	if (!pxDecalTemplate)
	{
		Asura_Assert(false,"null decal template");
		return;
	}
	
	if (pxDecalTemplate->m_uFlags & ASURA_DECAL_RANDOMROTATION)
	{
		fRotation = Asura_Maths::Pi * Asura_Random::GetFloat();
	}
	else
	{
		fRotation /= 2.0f;
	}

	if (fRotation!=0.0f)
	{
		float fSine = Asura_Maths::Sine(fRotation);
		float fCosine = Asura_Maths::Cosine(fRotation);
		Asura_Quat quat(xSurfaceNormal.x * fSine, xSurfaceNormal.y * fSine, xSurfaceNormal.z * fSine, fCosine);
		Asura_Matrix_3x3 xRotation;
		quat.ToMatrix(xRotation);
		xOrient = xRotation*xOrient;
	}
	AddDecal( xPosition, xSurfaceNormal, pxDecalTemplate, xOrient, fScale );
}


// MGH 23/11/2007: Reinstated this function, as the collision FX system needs it in order to render
// scratch mark decals that have a specific world-space orientation (i.e. are parallel to the collision velocity).
// xDecalTopDirection doesn't need to be normalised.
void Asura_Decal_System::AddDecal(const Asura_Vector_3& xPosition, const Asura_Vector_3& xSurfaceNormal,
								  const Asura_Vector_3& xDecalTopDirection, const u_int uDecalTemplateID,
								  float fScale /*=1.0f*/, float fRotation /*=0.0f*/)
{
#ifdef ASURA_TARGET_SM3
	s_xCurrentDecalNormal = xSurfaceNormal;
#endif //ASURA_TARGET_SM3

	const Asura_Decal_Template* pxDecalTemplate = Project_GetDecalTemplate(uDecalTemplateID);

	if (!pxDecalTemplate)
	{
		Asura_Assert(0, "null decal template");
		return;
	}

	// Determine the axes.
	Asura_Vector_3 xZDir = xSurfaceNormal;
	if (xZDir.Normalise() == 0.0f)
	{
		Asura_Assert(0, "Null surface normal vector");
		return;
	}

	Asura_Vector_3 xXDir = xDecalTopDirection ^ xZDir;
	if (xXDir.Normalise() == 0.0f)
	{
		// We could use an arbitary orientation, but it is probably best
		// if we don't try to render the decal at all. The calling code
		// should probably test for this case beforehand.
		Asura_Assert(0, "Surface normal parallel to top direction");
		return;
	}

	const Asura_Vector_3 xYDir = xZDir ^ xXDir;
	
	Asura_Matrix_3x3 xOrient( xXDir.x, xYDir.x, xZDir.x,
							  xXDir.y, xYDir.y, xZDir.y,
							  xXDir.z, xYDir.z, xZDir.z );

	// Add in any extra rotation about the normal, although this will typically be zero.
	if (pxDecalTemplate->m_uFlags & ASURA_DECAL_RANDOMROTATION)
	{
		fRotation = Asura_Maths::Pi * Asura_Random::GetFloat();
	}
	else
	{
		fRotation /= 2.0f;
	}

	if (fRotation!=0.0f)
	{
		const float fSine = Asura_Maths::Sine(fRotation);
		const float fCosine = Asura_Maths::Cosine(fRotation);
		Asura_Quat xQuat(xZDir.x * fSine, xZDir.y * fSine, xZDir.z * fSine, fCosine);
		Asura_Matrix_3x3 xRotation;
		xQuat.ToMatrix(xRotation);
		xOrient = xRotation*xOrient;
	}

	AddDecal( xPosition, xSurfaceNormal, pxDecalTemplate, xOrient, fScale );
}


void Asura_Decal_System::AddDecal(const Asura_Vector_3& xPosition, const Asura_Vector_3& xSurfaceNormal, const Asura_Decal_Template* pxDecalTemplate, const Asura_Matrix_3x3& xOrient, float fScale )
{
	Asura_Vector_3 xUOffset(xOrient.e11, xOrient.e21, xOrient.e31);
	Asura_Vector_3 xVOffset(xOrient.e12, xOrient.e22, xOrient.e32);
	Asura_Vector_3 xNOffset = xSurfaceNormal;

	float fHeight	=	pxDecalTemplate->m_fHeight * fScale;
	float fWidth	=	pxDecalTemplate->m_fWidth * fScale;
	float fDepth	=	pxDecalTemplate->m_fDepth * fScale;
#if defined ASURA_TOOLS && defined ASURA_USE_STATIC_DECAL_SYSTEM
	if (!s_bCreatingStaticDecals)
#endif
	{
		// (MH - 01.05.2008) limit depth to 5cm to avoid stretching.
		fDepth = Asura_Maths::Min(fDepth, 0.05f);
	}

	xUOffset*=fWidth;
	xVOffset*=fHeight;
	xNOffset*=fDepth;

	Asura_Bounding_Box xBox;
	{
		xBox.Reset();
	
		Asura_Vector_3 xVertex1 = -xUOffset -xVOffset +xNOffset; 
		Asura_Vector_3 xVertex2 =  xUOffset -xVOffset +xNOffset; 
		Asura_Vector_3 xVertex3 = -xUOffset -xVOffset -xNOffset; 
		Asura_Vector_3 xVertex4 =  xUOffset -xVOffset -xNOffset; 
		Asura_Vector_3 xVertex5 = -xUOffset +xVOffset +xNOffset; 
		Asura_Vector_3 xVertex6 =  xUOffset +xVOffset +xNOffset; 
		Asura_Vector_3 xVertex7 = -xUOffset +xVOffset -xNOffset; 
		Asura_Vector_3 xVertex8 =  xUOffset +xVOffset -xNOffset; 

		xBox.Set(xVertex1);
		xBox.Set(xVertex2);
		xBox.Set(xVertex3);
		xBox.Set(xVertex4);
		xBox.Set(xVertex5);
		xBox.Set(xVertex6);
		xBox.Set(xVertex7);
		xBox.Set(xVertex8);

		xBox.Displace(xPosition);
	}

	xUOffset.Normalise();
	xVOffset.Normalise();
	xNOffset.Normalise();



	// make an environment cache of the polys where the poly
	Asura_Physics_EnvironmentCache xCache;

	// if we can see it, add a decal to it.
	xCache.SetFlagsOfPolysToIgnore( AsuraPolygonFlag_DontRender );
	xCache.SetFacesOnly( true );
	xCache.Update( xBox );

	const Asura_Physics_EnvironmentCache_Face* pxFace = xCache.GetFaceList();

	for (; pxFace!=NULL; pxFace = pxFace->m_pxNext )
	{
		const Asura_AVector3& xV0 = Asura_Physics_Environment::GetFaceVertex( pxFace->m_uFaceID, 0 );
		const Asura_AVector3& xV1 = Asura_Physics_Environment::GetFaceVertex( pxFace->m_uFaceID, 1 );
		const Asura_AVector3& xV2 = Asura_Physics_Environment::GetFaceVertex( pxFace->m_uFaceID, 2 );

		Asura_Vector_3 xNormal;
		
		xNormal = ((xV1 - xV0)^(xV2 - xV0));
	
		xNormal.Normalise();


		const float fSurfaceAngle = xSurfaceNormal * xNormal;


#if (defined(ASURA_TOOLS) && defined(ASURA_USE_STATIC_DECAL_SYSTEM))
		if (!s_bAllowBackFaceProjection)
		{
			if (fSurfaceAngle<=0.02f) continue;
		}	
#else
		if (fSurfaceAngle<=0.02f) continue;		
#endif		



		Asura_Decal_Vertex xVertexBufferA[9];
		Asura_Decal_Vertex xVertexBufferB[9];

		u_int uNumberOfVertices = 3;

		xVertexBufferA[0].m_xPosition = xV0;
		xVertexBufferA[1].m_xPosition = xV1;
		xVertexBufferA[2].m_xPosition = xV2;

		/// Kevin Lea - 13/11/2002 17:47:42 - left plane
		float fD = xPosition*xUOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferA, uNumberOfVertices, xVertexBufferB, xUOffset, fWidth - fD);
		if (uNumberOfVertices==0) continue;

		/// Kevin Lea - 13/11/2002 17:50:20 - right plane
		Asura_Vector_3 xPlaneNormal = -xUOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferB, uNumberOfVertices, xVertexBufferA, xPlaneNormal, fWidth + fD);
		if (uNumberOfVertices==0) continue;

		/// Kevin Lea - 13/11/2002 17:51:47 - top plane
		fD = xPosition*xVOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferA, uNumberOfVertices, xVertexBufferB, xVOffset, fHeight - fD);
		if (uNumberOfVertices==0) continue;

		/// Kevin Lea - 13/11/2002 17:53:12 - bottom plane
		xPlaneNormal = -xVOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferB, uNumberOfVertices, xVertexBufferA, xPlaneNormal, fHeight + fD);
		if (uNumberOfVertices==0) continue;

		/// Kevin Lea - 13/11/2002 17:54:55 - front plane
		fD = xPosition*xNOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferA, uNumberOfVertices, xVertexBufferB, xNOffset, fDepth - fD);
		if (uNumberOfVertices==0) continue;

		/// Kevin Lea - 13/11/2002 17:56:09 - back plane
		xPlaneNormal = -xNOffset;

		uNumberOfVertices = ClipPolyToPlane( xVertexBufferB, uNumberOfVertices, xVertexBufferA, xPlaneNormal, fDepth + fD);
		if (uNumberOfVertices==0) continue;

        // Set the colour (lighting if required)
        Asura_RGBA xDiffuseRGBA;
        if( pxDecalTemplate->m_uFlags & ASURA_DECAL_LIGHTONCREATION )
        {
            Asura_GI_Solution::CalculateDiffuseAtPoint( xDiffuseRGBA, xPosition, xSurfaceNormal, false );
        }
        else
        {
            xDiffuseRGBA.m_fR = 1.0f;
            xDiffuseRGBA.m_fG = 1.0f;
            xDiffuseRGBA.m_fB = 1.0f;
        }
        xDiffuseRGBA.m_fA = 1.0f;

		for (u_int v=0; v<uNumberOfVertices; v++)
		{
			//xVertexBufferA[v].m_fAlpha = 1.0f - Asura_Maths::Abs((xSurfaceNormal * (xPosition-xVertexBufferA[v].m_xPosition))/fDepth);

			Asura_Vector_3 xDisp = xVertexBufferA[v].m_xPosition - xPosition;

			xVertexBufferA[v].m_fU = (0.5f * (xDisp*xUOffset) / fWidth + 0.5f)*pxDecalTemplate->m_fDeltaU + pxDecalTemplate->m_fU;
			xVertexBufferA[v].m_fV = (0.5f * (xDisp*xVOffset) / fHeight + 0.5f)*pxDecalTemplate->m_fDeltaV + pxDecalTemplate->m_fV;
		}
		/// Kevin Lea - 13/11/2002 17:57:41 - okay, now output stip to platform dependent layer

		// reorder fan into strip
		{
			u_int u = 0;
			u_int v = 0;
			u_int w = uNumberOfVertices-1;

			xVertexBufferB[u++] = xVertexBufferA[v++];

			while(true)
			{
				xVertexBufferB[u++] = xVertexBufferA[v++];
				if (u==uNumberOfVertices) break;

				xVertexBufferB[u++] = xVertexBufferA[w--];
				if (u==uNumberOfVertices) break;
			}
		}

		
#if (defined(ASURA_TOOLS) && defined(ASURA_USE_STATIC_DECAL_SYSTEM))
		if (s_bCreatingStaticDecals)
		{
			Tools_StaticDecal_System::AddStrip( xVertexBufferB, uNumberOfVertices, xDiffuseRGBA, pxDecalTemplate,(pxDecalTemplate->m_uFlags & ASURA_DECAL_LIGHTONCREATION)!=0);
		}
		else
#endif
		{		
			GetCurrentHandler().Platform_AddStrip( xVertexBufferB, uNumberOfVertices, xDiffuseRGBA, pxDecalTemplate );
		}
	}



}

/////////////////////
// ClipPolyToPlane //
/////////////////////
u_int Asura_Decal_System::ClipPolyToPlane(Asura_Decal_Vertex* pxInVertices, u_int uNumberOfVertices, Asura_Decal_Vertex* pxOutVertices, Asura_Vector_3& xPlaneNormal, float fPlaneConstant)
{
	u_int uNewVertexCount = 0;

	float fDot[9];

	{
		bool bAllNegative = true;
		for (u_int u=0; u<uNumberOfVertices; u++)
		{
			fDot[u] = pxInVertices[u].m_xPosition*xPlaneNormal + fPlaneConstant;
			if (fDot[u]>=0.0f)
			{
				bAllNegative = false;
			}
		}

		/// Kevin Lea - 14/11/2002 10:49:43 - early exit if all vertices would be clipped
		if (bAllNegative) return 0;
	}

	for (u_int u=0; u<uNumberOfVertices; u++)
	{
		u_int v = (u!=0) ? u-1 : uNumberOfVertices-1; // previous vertex
		
		if (fDot[u]<0.0f)
		{
			if (fDot[v]>=0.0f)
			{
				// Current vertex is on negative side of plane,
				// but previous vertex is on positive side.
				Asura_Vector_3 xEdge = pxInVertices[v].m_xPosition - pxInVertices[u].m_xPosition;
				float f = xEdge * xPlaneNormal;
				
				Asura_Assert( f!=0.0f, "edge parallel to plane");

				float fLambda = fDot[v] / f;

                // Asura_Assertf( fLambda >= -0.0001f && fLambda <= 1.0001f, ("Out of bounds: %f",Asura_Maths::FtoD(fLambda)));
				if (fLambda < 0.0f) fLambda = 0.0f;
				if (fLambda > 1.0f) fLambda = 1.0f;

				pxOutVertices[uNewVertexCount].m_xPosition = pxInVertices[v].m_xPosition - xEdge * fLambda;
				uNewVertexCount++;	
			}
		}
		else
		{
			if (fDot[v]<0.0f)
			{
				// Current vertex is on positive side of plane,
				// but previous vertex is on negative side.
				const Asura_Vector_3 xEdge = pxInVertices[u].m_xPosition - pxInVertices[v].m_xPosition;
				float f = xEdge * xPlaneNormal;
				
				Asura_Assert( f!=0.0f, "edge parallel to plane");

				float fLambda = fDot[u] / f;

                // Asura_Assertf( fLambda >= -0.0001f && fLambda <= 1.0001f, ("Out of bounds: %f",Asura_Maths::FtoD(fLambda)));
				if (fLambda < 0.0f) fLambda = 0.0f;
				if (fLambda > 1.0f) fLambda = 1.0f;

				pxOutVertices[uNewVertexCount].m_xPosition = pxInVertices[u].m_xPosition - xEdge * fLambda;
				uNewVertexCount++;	
			}

			/// Kevin Lea - 14/11/2002 11:22:04 - include current vertex
			pxOutVertices[uNewVertexCount].m_xPosition = pxInVertices[u].m_xPosition;
			uNewVertexCount++;
		}
	}


	return uNewVertexCount;
}


////////////////////////////////
// Asura_Decal_RenderCallback //
////////////////////////////////
#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
static void Asura_Decal_RenderCallback(Asura_Hash_ID uHash, Asura_Decal_Handler* pxData)
{
	if (pxData)
	{
		pxData->Platform_Render();
	}
}
#endif

////////////
// Render //
////////////
void Asura_Decal_System::Render()
{
	if (!Asura_Render::IsSystemEnabled(ASURA_RENDER_SYSTEM_DECALS))
	{
		return;
	}

#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
	// Loop over all the handlers rendering them.
	s_xHandlerTree.Traverse(Asura_Decal_RenderCallback);
#else
	GetCurrentHandler().Platform_Render();
#endif

}


#ifdef ASURA_USE_STATIC_DECAL_SYSTEM
void Asura_Decal_System::RenderStaticDecals()
{
	if (!Asura_Render::IsSystemEnabled(ASURA_RENDER_SYSTEM_DECALS))
	{
		return;
	}

	// Static decals, need rendering just after the environment
	Platform_RenderStaticDecals();
}
#endif



#if defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)
////////////////////////////////////////////////////////////////////////////////
// Function     : CreateHandler
//
// Description  : Attempts to create a decal-system-handler with the specified hash-ID.
//
// Parameters   : The hash-ID of the handler to create.
//
// Return value : A pointer to the handler created.
////////////////////////////////////////////////////////////////////////////////
Asura_Decal_Handler* Asura_Decal_System::CreateHandler(const Asura_Hash_ID uHash)
{
	Asura_Decal_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHash);
	Asura_Assert(!pxHandler, "Already have an decal-system-handler with this hash-ID.");

	// Do we already have this handler? No - create one...
	if (pxHandler == NULL)
	{
		pxHandler = s_xHandlerTree.AddNode(uHash);
	}

	return pxHandler;
}

////////////////////////////////////////////////////////////////////////////////
// Function     : DestroyHandler
//
// Description  : Attempts to destroy the decal-system-handler with the specified unique hash-ID.
//
// Parameters   : The hash-ID of the handler to destroy.
//
// Return value : "true" if destroyed, "false" if failed.
////////////////////////////////////////////////////////////////////////////////
bool Asura_Decal_System::DestroyHandler(const Asura_Hash_ID uHash)
{
	// Is this the current handler?
	if(s_pxCurrentHandler == s_xHandlerTree.GetDataByHash(uHash))
	{
		s_pxCurrentHandler	= NULL;
	}

	// Call the delete function...
	return s_xHandlerTree.DeleteNodeByHash(uHash);
}
#endif	// defined(ASURA_USE_MULTIPLE_ZONE_HANDLERS)






// Taken straight from Tools_Tangentspace (which isnt' compiled into game builds - we need this in-game for normal-mapped decals)

static void Swap( float& xValue1, float& xValue2 )					 { float 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; }

void Asura_Decal_System::CalculateTangentAndBinormal( const Asura_Vector_3* pxPositions, const Asura_Vector_2* pxTextureCoords,
													  Asura_Vector_3& rxTangent, Asura_Vector_3& rxBinormal )
{
	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
		Asura_Vector_3 xPos = (axPositions [2] - axPositions [0]) * fLambda + axPositions [0];

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

		// make sure tangent points in the right direction
		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
		Asura_Vector_3 xPos = (axPositions [2] - axPositions [0]) * fLambda + axPositions [0];

		// binormal is the ray form the middle vertex to the lerped vertex
		rxBinormal = xPos - axPositions [1];

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

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

#ifdef ASURA_TOOLS
#ifdef ASURA_USE_STATIC_DECAL_SYSTEM

void Asura_Decal_System::BeginStaticDecalCreation(bool bAllowBackFaceProjection)
{
	s_bCreatingStaticDecals = true;
	s_bAllowBackFaceProjection = bAllowBackFaceProjection;
}

void Asura_Decal_System::EndStaticDecalCreation()
{
	s_bCreatingStaticDecals = false;
	s_bAllowBackFaceProjection = false;
}

void Asura_Decal_System::AddDecal(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrient, float fScale, const u_int uDecalTemplateID)
{
	const Asura_Decal_Template* pxDecalTemplate = Project_GetDecalTemplate(uDecalTemplateID);

	if (!pxDecalTemplate)
	{
		Asura_Assert(false,"null decal template");
		return;
	}

	// Input orientation is from an tools object - we have to swap the Z/Y axes to make it work
	Asura_Matrix_3x3 xOrient2 = xOrient;
	Asura_Vector_3 xZ =  Asura_Vector_3(xOrient.e13, xOrient.e23, xOrient.e33);
	Asura_Vector_3 xY = -Asura_Vector_3(xOrient.e12, xOrient.e22, xOrient.e32);
	xOrient2.e13 = xY.x;	xOrient2.e23 = xY.y;	xOrient2.e33 = xY.z;
	xOrient2.e12 = xZ.x;	xOrient2.e22 = xZ.y;	xOrient2.e32 = xZ.z;	
	
	s_xCurrentDecalNormal = Asura_Vector_3(xOrient2.e13, xOrient2.e23, xOrient2.e33);

	AddDecal(xPosition, Asura_Vector_3(xOrient2.e13, xOrient2.e23, xOrient2.e33), pxDecalTemplate, xOrient2, fScale);
}

#endif
#endif








