//////////////////////////////////////////////////////////////////////
// Asura_Volume_Cylinder											//
// SJL 19/11/07														//
// Copyright (C) 2007 Rebellion, All Rights Reserved.			    //
//																	//
// An oriented cylinder volume class								//
//////////////////////////////////////////////////////////////////////

// Main asura file
#include "Core/Asura.h"

// This file's header.
#include "Volume/Asura_Volume_Cylinder.h"

// Asura headers
#include "ChunkSystem/Asura_Chunk_Stream.h"
#include "Core/Asura_OrientedVolume.h"
#if !defined(ASURA_NO_DEBUG_OUTPUT)
#include "Debugging/Asura_DebugLineRender.h"
#endif	//!defined(ASURA_NO_DEBUG_OUTPUT)

// Tools headers
#ifdef ASURA_TOOLS
#include "EntityTemplate/Tools_EntityTemplate_System.h"
#endif


// Static class members
const u_int Asura_Volume_Cylinder::s_uCHUNKVERSION = 0;

Asura_Volume_Cylinder::Asura_Volume_Cylinder()
{
	m_iClassification = ASURA_VOLUME_CLASSIFICATION_CYLINDER;

	m_fRadius = 1.0f;
	m_fHeight = 1.0f;
	
	m_xPosition.Zero();
	m_xOrient.Identity();
}

Asura_Volume_Cylinder::Asura_Volume_Cylinder(const Asura_Vector_3& xPosition,
											 const Asura_Quat& xOrientation,
											 const float fRadius,
											 const float fHeight)
: m_xOrient(xOrientation)
, m_xPosition(xPosition)
, m_fRadius(fRadius)
, m_fHeight(fHeight)
{
	m_iClassification = ASURA_VOLUME_CLASSIFICATION_CYLINDER;
}

Asura_Volume_Cylinder::~Asura_Volume_Cylinder()
{

}

void Asura_Volume_Cylinder::ReadFromChunkStream(Asura_Chunk_Stream& xStream)
{
	// Classification should be read out already by the container class / whatever code created this object

	// Read and check version number
	u_int uVersion;
	xStream >> uVersion;
	Asura_Assert(uVersion == s_uCHUNKVERSION, "Unknown chunk version loading Asura_Volume_Cylinder");
	if (uVersion != s_uCHUNKVERSION ) return;

	// Read data
	xStream >> m_xPosition;
	xStream >> m_fRadius;
	xStream >> m_fHeight;
	xStream >> m_xOrient;
}

void Asura_Volume_Cylinder::WriteToChunkStream (Asura_Chunk_Stream& xStream) const
{
	// Write classification
	xStream << m_iClassification;

	// Write version number
	xStream << s_uCHUNKVERSION;

	// Write data
	xStream << m_xPosition;
	xStream << m_fRadius;
	xStream << m_fHeight;
	xStream << m_xOrient;
}

bool Asura_Volume_Cylinder::Contains(const Asura_Vector_3& xPosition) const
{
	//see if the orientation is being used
	if(m_xOrient.w == 1.0f)
	{
		// Check y in range.
		if (xPosition.y > m_xPosition.y) return false;
		if (xPosition.y < m_xPosition.y - m_fHeight) return false;

		const float fRadiusSqr = m_fRadius* m_fRadius;		
		Asura_Vector_3 xToSqr	 = (m_xPosition - xPosition);
		xToSqr.y = 0.f;
		const float fToSqr = xToSqr.MagnitudeSquared();

		if (fToSqr < fRadiusSqr) 
		{
			return true;
		}
		else
		{
			return false;
		}

	}
	else
	{
		//need to get position relative to volume
		Asura_Vector_3 xRelative = xPosition - m_xPosition;

		//and rotate into the space of the volume
		Asura_Quat xOrientInverse = m_xOrient;
		xOrientInverse.Invert();
		xRelative = xOrientInverse.RotateVector(xRelative);

		Asura_Vector_3 xLoc = xRelative+m_xPosition;

		// Check y in range.
		if (xLoc.y > m_xPosition.y) return false;
		if (xLoc.y < m_xPosition.y - m_fHeight) return false;

		const float fRadiusSqr = m_fRadius* m_fRadius;		
		Asura_Vector_3 xToSqr	 = (m_xPosition - xLoc);
		xToSqr.y = 0.f;
		const float fToSqr = xToSqr.MagnitudeSquared();

		if (fToSqr < fRadiusSqr) 
		{
			return true;
		}
		else
		{
			return false;
		}
	}
}

bool Asura_Volume_Cylinder::Overlaps(const Asura_Bounding_Box& xAABB) const
{
	//Don't have a proper cylinder vs AABB test , so just test against a box around the cylinder instead
	//RAR 22/02/2008
	Asura_Bounding_Box xCylinderBB;
	xCylinderBB.MinX = m_xPosition.x - m_fRadius;	
	xCylinderBB.MaxX = m_xPosition.x + m_fRadius;	
	xCylinderBB.MinY = m_xPosition.y - m_fHeight;
	xCylinderBB.MaxY = m_xPosition.y;
	xCylinderBB.MinZ = m_xPosition.z - m_fRadius;	
	xCylinderBB.MaxZ = m_xPosition.z + m_fRadius;
	
	if(m_xOrient.w != 1.0f)
	{
		//oriented cuboid is rotated about the centre , but the cylinder is about the base , so need
		//to apply an offset
		Asura_Vector_3 xCentreToBase(0.0f,m_fHeight/2.0f,0.0f);

		Asura_Vector_3 xRotatedBaseToCentre = m_xOrient.RotateVector(-xCentreToBase);

		xCylinderBB.Displace(xCentreToBase+xRotatedBaseToCentre);
	}	

	return Asura_OrientedVolume::Overlaps(m_xOrient, xCylinderBB, xAABB);
}


Asura_Volume* Asura_Volume_Cylinder::Duplicate() const
{
	Asura_Volume_Cylinder* pxNew = new Asura_Volume_Cylinder();
	pxNew->Copy(this);
	return static_cast<Asura_Volume*>(pxNew);
}

void Asura_Volume_Cylinder::Copy(const Asura_Volume_Cylinder* pxFrom)
{
	m_xPosition = pxFrom->m_xPosition;
	m_fRadius = pxFrom->m_fRadius;
	m_fHeight = pxFrom->m_fHeight;
	m_xOrient = pxFrom->m_xOrient;
}

void Asura_Volume_Cylinder::GetAABB(Asura_Bounding_Box& xBB) const
{
	// Calculate a bounding box for a cylinder oriented up the y-axis.
	Asura_Bounding_Box xOBB;
	xOBB.MinX = -m_fRadius;
	xOBB.MaxX =  m_fRadius;
	xOBB.MinY = -m_fHeight;
	xOBB.MaxY = 0.f;
	xOBB.MinZ = -m_fRadius;
	xOBB.MaxZ =  m_fRadius;
	// Rotate it.
	Asura_Matrix_3x3 xOrient;
	m_xOrient.ToMatrix(xOrient);
	xBB.FromOBB(xOBB, xOrient);
	// Displace to cylinder position.
	xBB.Displace(m_xPosition);
}

#if !defined(ASURA_NO_DEBUG_OUTPUT)
void Asura_Volume_Cylinder::AddToLineSet(const u_int uARGB,
										 Asura_DebugLineRender_LineSet& xLineSet) const
{
	xLineSet.AddCylinder(m_xPosition, m_xOrient, m_fRadius, -m_fHeight, uARGB, 8);
}
#endif	//!defined(ASURA_NO_DEBUG_OUTPUT)

#ifdef ASURA_TOOLS
void Asura_Volume_Cylinder::ApplyChangesForTemplateInstance(class Tools_EntityTemplateInstance* pxInstance)
{
   	m_xPosition = pxInstance->TransformPosition( m_xPosition );
    m_xOrient = pxInstance->TransformQuat( m_xOrient );
}
#endif //ASURA_TOOLS

void Asura_Volume_Cylinder::RotateAbout(const Asura_Vector_3& xCentre, const Asura_Matrix_3x3& xOri)
{
	Asura_Matrix_3x3 xCur;
	m_xOrient.ToMatrix(xCur);
	xCur = xOri * xCur;
	m_xOrient.FromMatrix(xCur);

	m_xPosition -= xCentre;
	m_xPosition = m_xPosition * xOri;
	m_xPosition += xCentre;
}
