//////////////////////////////////////////////////////////////////////////////
//
// File: Asura_Collision_Object.cpp
//
// Created by Martin G. Harris.
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

#include "Dynamics/Asura_Collision_Object.h"

#include "Animation/Asura_AnimationAttachment.h"
#include "Core/Asura_LOS.h"
#include "Core/Asura_MaterialFlags.h"
#include "Mesh/Asura_Mesh.h"


////////////////////////////
// Asura_Collision_Object //
////////////////////////////
Asura_Collision_Object::Asura_Collision_Object()
: m_xBoundingBox()
, m_fBoundingRadius(0.0f)
, m_usOverallFlags(0)
#ifdef ASURA_TOOLS
, m_uToolsOverallFlags( 0 )
#endif
, m_sOverallMaterial(ASURA_MATERIAL_NONE)
, m_uOverallMaterialResponse(ASURA_HASH_ID_UNSET)
{
}

/////////////
// ResetBB //
/////////////
void Asura_Collision_Object::ResetBB()
{
	// DWE - No totally flat BB's 
	if (m_xBoundingBox.MinX == m_xBoundingBox.MaxX)
	{
		m_xBoundingBox.MaxX += 0.001f;
	}
	if (m_xBoundingBox.MinY == m_xBoundingBox.MaxY)
	{
		m_xBoundingBox.MaxY += 0.001f;
	}
	if (m_xBoundingBox.MinZ == m_xBoundingBox.MaxZ)
	{
		m_xBoundingBox.MaxZ += 0.001f;
	}
}

///////////////////////////////////////////////////
// FillPolyArray: fill the collision poly array. //
///////////////////////////////////////////////////
int Asura_Collision_Object::FillPolyArray(  int iMaxNumber,
											Asura_Collision_Poly*& pxPolyPtr,
											const Asura_Vector_3& xPosition,
											const Asura_Matrix_3x3* pxOrient) const
{
	// This base class, just fills in the array with the BB polys.
	if (iMaxNumber<=0) return 0; // No free space
	if (!pxPolyPtr) return 0;

	// Indices for the triangles making up the cuboid.
	static int s_iaTriIndex[12][3] = 
		{
            {0,4,5},	{1,0,5}, //0451
			{4,6,7},	{5,4,7}, //4675
			{0,2,6},	{4,0,6}, //0264
			{3,7,6},	{2,3,6}, //3762
			{0,1,3},	{2,0,3}, //0132
            {1,5,7},	{3,1,7}, //1573
		};

	// We need to rotate/displace the BB corners.
	Asura_Vector_3 xCorner[8];
	int iCorner = 0;

	// Loop over the 8 corners
	for (int i=0; i<2; ++i)
	{
		for (int j=0; j<2; ++j)
		{
			for (int k=0; k<2; ++k)
			{
				xCorner[iCorner].x = (i ? m_xBoundingBox.MaxX : m_xBoundingBox.MinX);
				xCorner[iCorner].y = (j ? m_xBoundingBox.MaxY : m_xBoundingBox.MinY);
				xCorner[iCorner].z = (k ? m_xBoundingBox.MaxZ : m_xBoundingBox.MinZ);
				
				// Rotate/displace into world space
				if (pxOrient)
				{
					xCorner[iCorner] = xCorner[iCorner] * (*pxOrient);
				}
				xCorner[iCorner] += xPosition;

				iCorner++;
			}
		}
	}

	// OK, we have the corners in world space, convert to triangles.
	// Faces 0451, 4675, 0264, 3762, 0132, 1573 (anticlockwise)
	int iNumberAdded = 0;

	for (int i=0; i<12; i++)
	{
		if ( iMaxNumber == iNumberAdded )
		{
			break; // Give up, list is full!
		}

		pxPolyPtr->Vertex[0] = xCorner[s_iaTriIndex[i][0]];
		pxPolyPtr->Vertex[1] = xCorner[s_iaTriIndex[i][1]];
		pxPolyPtr->Vertex[2] = xCorner[s_iaTriIndex[i][2]];

		pxPolyPtr->Normal = ((pxPolyPtr->Vertex[1] - pxPolyPtr->Vertex[0])^(pxPolyPtr->Vertex[2] - pxPolyPtr->Vertex[0]));
		pxPolyPtr->Normal.Normalise();

		pxPolyPtr++;
		iNumberAdded++;
	}

	return iNumberAdded;
}

#ifdef ASURA_TOOLS
int Asura_Collision_Object::FillPolyArray(  int iMaxNumber,
											Asura_Collision_Poly*& pxPolyPtr,
                                            u_short*& pusFlagsPtr,
											const Asura_Vector_3& xPosition,
											const Asura_Matrix_3x3* pxOrient) const
{
	// This base class, just fills in the array with the BB polys.
	if (iMaxNumber<=0) return 0; // No free space
	if (!pxPolyPtr) return 0;
    if (!pusFlagsPtr) return 0;

	// Indices for the triangles making up the cuboid.
	static int s_iaTriIndex[12][3] = 
		{
            {0,4,5},	{1,0,5}, //0451
			{4,6,7},	{5,4,7}, //4675
			{0,2,6},	{4,0,6}, //0264
			{3,7,6},	{2,3,6}, //3762
			{0,1,3},	{2,0,3}, //0132
            {1,5,7},	{3,1,7}, //1573
		};

	// We need to rotate/displace the BB corners.
	Asura_Vector_3 xCorner[8];
	int iCorner = 0;

	// Loop over the 8 corners
	for (int i=0; i<2; ++i)
	{
		for (int j=0; j<2; ++j)
		{
			for (int k=0; k<2; ++k)
			{
				xCorner[iCorner].x = (i ? m_xBoundingBox.MaxX : m_xBoundingBox.MinX);
				xCorner[iCorner].y = (j ? m_xBoundingBox.MaxY : m_xBoundingBox.MinY);
				xCorner[iCorner].z = (k ? m_xBoundingBox.MaxZ : m_xBoundingBox.MinZ);
				
				// Rotate/displace into world space
				if (pxOrient)
				{
					xCorner[iCorner] = xCorner[iCorner] * (*pxOrient);
				}
				xCorner[iCorner] += xPosition;

				iCorner++;
			}
		}
	}

	// OK, we have the corners in world space, convert to triangles.
	// Faces 0451, 4675, 0264, 3762, 0132, 1573 (anticlockwise)
	int iNumberAdded = 0;

	for (int i=0; i<12; i++)
	{
		if ( iMaxNumber == iNumberAdded )
		{
			break; // Give up, list is full!
		}

		pxPolyPtr->Vertex[0] = xCorner[s_iaTriIndex[i][0]];
		pxPolyPtr->Vertex[1] = xCorner[s_iaTriIndex[i][1]];
		pxPolyPtr->Vertex[2] = xCorner[s_iaTriIndex[i][2]];

		pxPolyPtr->Normal = ((pxPolyPtr->Vertex[1] - pxPolyPtr->Vertex[0])^(pxPolyPtr->Vertex[2] - pxPolyPtr->Vertex[0]));
		pxPolyPtr->Normal.Normalise();

        *pusFlagsPtr = m_usOverallFlags;

		pxPolyPtr++;
        pusFlagsPtr++;
		iNumberAdded++;
	}

	return iNumberAdded;
}
#endif

/////////////////////////////////////////////////
// LOSTestRotatedBB: LOS test against BB only. //
/////////////////////////////////////////////////
bool Asura_Collision_Object::LOSTestRotatedBB( const Asura_Vector_3& xPosition, 
											   const Asura_Matrix_3x3& xOrient,
											   const Asura_LOS_InputData* pxInput,
											   Asura_LOS_OutputData* pxOutput) const
{
	// Is entire object flagged with one of the flags to ignore?
	if( pxInput->m_uPolyFlagsToIgnore & m_usOverallFlags )
	{
		return false;
	}

	// First, test against the bounding sphere.
	float fDistance;
	Asura_Vector_3 xCentre = xPosition + (m_xBoundingBox.CalculateCentre() * xOrient);
	if (Asura_Maths::RayIntersectsSphere(pxInput->m_xOrigin, pxInput->m_xDirection, xCentre, m_fBoundingRadius, &fDistance) &&
		fDistance < pxOutput->m_fDistanceToIntersection)
	{
		// Convert LOS test from world space to object space
		Asura_Vector_3 xAlpha = pxInput->m_xOrigin;
		Asura_Vector_3 xBeta  = pxInput->m_xDirection;
		xAlpha -= xPosition;
		
		Asura_Matrix_3x3 xMat = xOrient;
		xMat.Transpose();

		xAlpha = xAlpha * xMat;
		xBeta = xBeta * xMat;

		// Now, do a LOS test against the rotated BB.
		Asura_Vector_3 xNormal;
		bool bStartedInside;
		if (Asura_Maths::RayIntersectsBoundingBox(xAlpha, xBeta, m_xBoundingBox, &fDistance, 0, &bStartedInside, &xNormal))
		{
			// Hit the BB
			if (fDistance < pxOutput->m_fDistanceToIntersection)
			{
				// Update output data, note HitEntity Guid is set by caller if needed.
				pxOutput->m_fDistanceToIntersection = fDistance;
				pxOutput->m_iHitBoneID = -1; // Clear this. May be set by caller.
                pxOutput->m_iHitAttachmentID = Asura_AnimationAttachment::INVALID_ID; // And clear this. RAR 28/02/03
				pxOutput->m_usPolyFlags = m_usOverallFlags; // Overall flags for whole object.

				#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
					pxOutput->m_uMaterialResponseHashID = m_uOverallMaterialResponse;
				#endif
				pxOutput->m_sMaterialID = m_sOverallMaterial;

				pxOutput->m_bStartedInside = bStartedInside;

				// Get normal and rotate it.
				pxOutput->m_xNormal = xNormal * xOrient;

				return true;
			}
		}
	}
    
    return false;
}

/////////////////////////////////////////////////
// LOSTestRotatedBB: LOS test against BB only. //
/////////////////////////////////////////////////
bool Asura_Collision_Object::LOSTestSphere (const Asura_Vector_3& xPosition, 
											const Asura_LOS_InputData* pxInput,
											Asura_LOS_OutputData* pxOutput) const
{
	// Is entire object flagged with one of the flags to ignore?
	if( pxInput->m_uPolyFlagsToIgnore & m_usOverallFlags )
	{
		return false;
	}
	else
	{
		const Asura_Vector_3	xCentre(xPosition + m_xBoundingBox.CalculateCentre());
		float					fDistance;

		// First, test against the bounding sphere.
		if( Asura_Maths::RayIntersectsSphere(pxInput->m_xOrigin, pxInput->m_xDirection, xCentre, m_fBoundingRadius, &fDistance) &&
			(fDistance < pxOutput->m_fDistanceToIntersection) )
		{
			const Asura_Vector_3 xSphereHitPosition = pxInput->m_xOrigin + (pxInput->m_xDirection * fDistance);

			// Update output data, note HitEntity Guid is set by caller if needed.
			pxOutput->m_fDistanceToIntersection		= fDistance;
			pxOutput->m_iHitBoneID					= -1; // Clear this. May be set by caller.
			pxOutput->m_iHitAttachmentID			= Asura_AnimationAttachment::INVALID_ID; // And clear this. RAR 28/02/03
			pxOutput->m_usPolyFlags					= m_usOverallFlags; // Overall flags for whole object.

			#if defined ASURA_USE_MATERIAL_RESPONSE_SYSTEM
				pxOutput->m_uMaterialResponseHashID	= m_uOverallMaterialResponse;
			#endif
			pxOutput->m_sMaterialID					= m_sOverallMaterial;

			// Get normal and rotate it.
			pxOutput->m_xNormal = xSphereHitPosition - xCentre;
			pxOutput->m_xNormal.Normalise();
			return true;
		}
		else
		{
			return false;
		}
	}
}


/////////////////////////
// Asura_Collision_Box //
/////////////////////////
Asura_Collision_Box::Asura_Collision_Box(float fMinX, float fMaxX,
										 float fMinY, float fMaxY,
										 float fMinZ, float fMaxZ)
{
	m_xBoundingBox.MinX = fMinX;
	m_xBoundingBox.MaxX = fMaxX;

	m_xBoundingBox.MinY = fMinY;
	m_xBoundingBox.MaxY = fMaxY;

	m_xBoundingBox.MinZ = fMinZ;
	m_xBoundingBox.MaxZ = fMaxZ;

	ResetBB();
	CalculateRadius();
}

Asura_Collision_Box::Asura_Collision_Box(const Asura_Bounding_Box& xBB)
{
	m_xBoundingBox = xBB;
	ResetBB();
	CalculateRadius();
}

//////////////
// ChangeBB //
//////////////
void Asura_Collision_Box::ChangeBB(const Asura_Bounding_Box& xBB)
{
	// MGH 29/4/03: Function added to fix a bug during loading of shapes.
	m_xBoundingBox = xBB;
	ResetBB();
	CalculateRadius();
}

/////////////////////
// CalculateRadius //
/////////////////////
void Asura_Collision_Box::CalculateRadius()
{
	// MGH 4/3/03: Calculate the radius wrt the centre of the BB.
	// NOTE: if you use BBs whose centres are not at the origin (this is the case for
	// most actors), then you will need to take this into account when using the bounding
	// radius.
	Asura_Vector_3 xCentre = m_xBoundingBox.CalculateCentre();
	Asura_Vector_3 xCorner(m_xBoundingBox.MaxX, m_xBoundingBox.MaxY, m_xBoundingBox.MaxZ);

	xCorner -= xCentre;
	m_fBoundingRadius = xCorner.Magnitude();
}


///////////////////////////////
// Asura_Collision_HeightBox //
///////////////////////////////
Asura_Collision_HeightBox::Asura_Collision_HeightBox( float fMinX, float fMaxX,
													  float fMinY, float fMaxY,
													  float fMinZ, float fMaxZ)
: Asura_Collision_Box( fMinX, fMaxX, fMinY, fMaxY, fMinZ, fMaxZ )
{
}

/////////////////////////////////////////////////////////
// SetHeight: Change MinY (fHeight should be positive) //
/////////////////////////////////////////////////////////
void Asura_Collision_HeightBox::SetHeight(float fHeight)
{
	Asura_Assert(fHeight>=0.0f, "Negative height");
	if (fHeight != m_xBoundingBox.MaxY)
	{
		m_xBoundingBox.MaxY = fHeight;
		CalculateRadius();
	}
}


//////////////////////////
// Asura_Collision_Pane //
//////////////////////////
const float g_fPaneThickness = 0.025f;

Asura_Collision_Pane::Asura_Collision_Pane(float fSizeX, float fSizeY)
: Asura_Collision_Box(0.0f, 0.0f, 0.0f, 0.0f, -g_fPaneThickness, g_fPaneThickness)
{
	SetSize(fSizeX, fSizeY);
}

/////////////
// SetSize //
/////////////
bool Asura_Collision_Pane::SetSize(float fSizeX, float fSizeY)
{
	if (fSizeX != m_xBoundingBox.MaxX || fSizeY != m_xBoundingBox.MaxY)
	{
		m_xBoundingBox.MaxX = fSizeX;
		m_xBoundingBox.MaxY = fSizeY;

		CalculateRadius();
		return true;
	}
	return false;
}
