//////////////////////////////////////////////////////////////////////////////
//
// Asura_Entity_Renderable.cpp
//
// Copyright (c) 2003 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

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

#include "Core/Asura.h"

#include "Entity/Asura_Entity_Renderable.h"

#include "Core/Asura_Timers.h"
#include "Debugging/Asura_Logfile.h"
#include "Entity/Asura_PosAndOriInterpolator.h"
#include "Network/Asura_Network.h"
#if defined(ASURA_USE_STREAMING)
#include "Streaming/Asura_Streaming_System.h"
#include "Streaming/Asura_Streaming_ZoneEntities.h"
#endif	// defined(ASURA_USE_STREAMING)


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

Asura_Combined_List<Asura_Entity_Renderable> Asura_Entity_Renderable::s_xList( 5 );
Asura_Classified_List<Asura_Entity_Renderable> Asura_Entity_Renderable::s_xRenderableList;

// KJFL - 5 November 2007 - only entities in this list will be queried in LoS 
// tests versus entities. Entities are added/removed from the list by the 
// protected methods in this class.
Asura_Classified_List<Asura_Entity_Renderable> Asura_Entity_Renderable::s_xLineOfSightList;
u_int Asura_Entity_Renderable::s_uLineOfSightUpdateStamp = 0;

Asura_Entity_Renderable::Asura_Entity_Renderable()
: Asura_Entity_Client()
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	m_pxPosAndOriInterpolator = new Asura_PosAndOriInterpolator;
	m_uCurrentPositionFrameStamp = 0xffffffff;
    m_uCurrentOrientationFrameStamp = 0xffffffff;
// (MH - 05.09.2003) init to rubbish values to highlight problems?
//	m_xCurrentPosition(Asura_Maths::LargeFloat, Asura_Maths::LargeFloat, Asura_Maths::LargeFloat);
//	m_xCurrentOrientation.Zero();
//	m_xCurrentVelocity(Asura_Maths::LargeFloat, Asura_Maths::LargeFloat, Asura_Maths::LargeFloat);
#else
	m_fLastReportedServerTimestamp = 0.0f;

#endif// ASURA_NO_NETWORK_LAG_COMPENSATION
	
	// MGH 17/7/2006: Moved out of #ifdef to initialise these variables in both cases.
	// The failure to initialise them was breaking Gun. Not initialising variables is just silly.
	m_xCurrentVelocity.Zero();
	m_xCurrentPosition.Zero(); // AJG - 05/07/04 - Initialised to prevent errors seen occasionally in 100 Bullets. Maybe should initialise to something to highlight problems, as above.
	m_xCurrentOrientation.Identity(); // KOR - 17/10/2005 - orientation wasn't getting initialised either.

	m_ucInvisibleFlags = 0;
	m_bIgnoreVisibilityChecks = false;
	m_bIsEntityCurrentlyMoving = true;
	m_bEntityAlwaysMoves = false;
    m_bRemoveFromRenderableListIfInvisible = true;

	AddToList();
	AddToRenderableList();
}


Asura_Entity_Renderable::~Asura_Entity_Renderable()
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (m_pxPosAndOriInterpolator)
	{
		delete m_pxPosAndOriInterpolator;
	}
#endif

	RemoveFromList();

	if( !m_bRemoveFromRenderableListIfInvisible || !IsInvisible() )
	{
		RemoveFromRenderableList(false);
	}

	// KJFL - 5 November 2007 - Currently always play safe and try to remove
	// the entity from the line-of-sight list, even though it may not be present.
	RemoveFromLineOfSightList();
}

const Asura_Vector_3& Asura_Entity_Renderable::GetCurrentPosition( void ) const
{
#ifdef ASURA_NO_NETWORK_LAG_COMPENSATION
	return m_xCurrentPosition;
#else
    
	if (!m_pxPosAndOriInterpolator)
	{
		return m_xCurrentPosition;
	}

	// check to see if the interpolated position was already computed this frame
	if (m_uCurrentPositionFrameStamp == Asura_Timers::GetFrameNumber())
	{
		return m_xCurrentPosition;
	}

	m_uCurrentPositionFrameStamp = Asura_Timers::GetFrameNumber();

#if _DEBUG
	extern u_int g_uCurGuidForTest;
	g_uCurGuidForTest = GetGuid();
#endif

	m_pxPosAndOriInterpolator->GetCurrentPosition(m_xCurrentPosition, m_xCurrentVelocity);

#if _DEBUG
	g_uCurGuidForTest = 0;
#endif
	
	return m_xCurrentPosition;
#endif
}

const Asura_Matrix_3x3& Asura_Entity_Renderable::GetCurrentOrientation( void ) const
{
#ifdef ASURA_NO_NETWORK_LAG_COMPENSATION
	return m_xCurrentOrientation;
#else
	if (!m_pxPosAndOriInterpolator)
	{
		return m_xCurrentOrientation;
	}

	if (m_uCurrentOrientationFrameStamp == Asura_Timers::GetFrameNumber())
	{
		return m_xCurrentOrientation;
	}

	m_uCurrentOrientationFrameStamp = Asura_Timers::GetFrameNumber();

	m_pxPosAndOriInterpolator->GetCurrentOrientation(m_xCurrentOrientation);

	return m_xCurrentOrientation;
#endif
}

void Asura_Entity_Renderable::GetCurrentVelocity( Asura_Vector_3& xVelocity ) const
{
#ifdef ASURA_NO_NETWORK_LAG_COMPENSATION
	xVelocity = m_xCurrentVelocity;
#else
	if (!m_pxPosAndOriInterpolator)
	{
		xVelocity = Asura_Maths::ZeroVector3;
		return;
	}

	// check to see if the pos and velocity were already computed this frame
	if (m_uCurrentPositionFrameStamp != Asura_Timers::GetFrameNumber())
	{
		m_uCurrentPositionFrameStamp = Asura_Timers::GetFrameNumber();

		m_pxPosAndOriInterpolator->GetCurrentPosition(m_xCurrentPosition, m_xCurrentVelocity);
	}
	xVelocity = m_xCurrentVelocity;
#endif
}


void Asura_Entity_Renderable::GetCurrentAngularVelocity(Asura_Euler_3& xEuler) const
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (!m_pxPosAndOriInterpolator)
	{
		xEuler.Zero();
		return;
	}
	m_pxPosAndOriInterpolator->GetCurrentAngularVelocity(xEuler);
#else
	xEuler = m_xCurrentAngularVelocity;
#endif //ASURA_NO_NETWORK_LAG_COMPENSATION
}


void Asura_Entity_Renderable::TeleportTo(const Asura_Vector_3& xPosition, const Asura_Matrix_3x3& xOrientation)
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (m_pxPosAndOriInterpolator)
	{
		m_pxPosAndOriInterpolator->TeleportTo(xPosition, xOrientation);
	}
	m_uCurrentPositionFrameStamp = Asura_Timers::GetFrameNumber();
	m_uCurrentOrientationFrameStamp = Asura_Timers::GetFrameNumber();
#endif //ASURA_NO_NETWORK_LAG_COMPENSATION
	m_xCurrentPosition = xPosition;
	m_xCurrentOrientation = xOrientation;
	m_xCurrentVelocity.Zero();
}


#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
// (MH - 28.08.2003) attempt to improve projectiles
void Asura_Entity_Renderable::InitialiseMovingObject(const Asura_Vector_3& xPosition, 
													const Asura_Quat& xOrientQuat,
													const Asura_Vector_3& xVelocity,
													float fServerTime)
{
	Asura_Assert(m_pxPosAndOriInterpolator, "no interpolator - object can't move");
	if (m_pxPosAndOriInterpolator)
	{
		m_pxPosAndOriInterpolator->InitialiseMovingObject(xPosition, xOrientQuat, xVelocity, fServerTime);
	}
}
#endif

const Asura_Vector_3& Asura_Entity_Renderable::GetLastReportedPosition() const
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (m_pxPosAndOriInterpolator)
	{
		return m_pxPosAndOriInterpolator->GetLastReportedPosition();
	}
	else
	{
		return m_xCurrentPosition;
	}
#else
	return m_xCurrentPosition;
#endif
}


const Asura_Quat& Asura_Entity_Renderable::GetLastReportedOrientation() const
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (m_pxPosAndOriInterpolator)
	{
		return m_pxPosAndOriInterpolator->GetLastReportedOrientation();
	}
	else
	{
		Asura_Assert(false, "can't return last reported orient quat for non-moving objects. Use GetLastReportedOrientationMatrix()");
		return Asura_Maths::IdentityQuat;
	}
#else
	Asura_Assert(false, "This function only implemented when ASURA_NO_NETWORK_LAG_COMPENSATION is not defined");
	return Asura_Maths::IdentityQuat;
#endif
}
		

void Asura_Entity_Renderable::GetLastReportedOrientationMatrix(Asura_Matrix_3x3& xMat) const
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	if (m_pxPosAndOriInterpolator)
	{
		m_pxPosAndOriInterpolator->GetLastReportedOrientation().ToMatrix(xMat);
	}
	else
	{
		xMat = m_xCurrentOrientation;
	}
#else
	Asura_Assert(false, "This function only implemented when ASURA_NO_NETWORK_LAG_COMPENSATION is not defined");
	xMat = Asura_Maths::IdentityMatrix3x3;
#endif
}
		
void Asura_Entity_Renderable::ProcessNewPositionAndOrientationData(const Asura_Vector_3& xPosition, 
                                                                   const Asura_Quat& xOrientation,
                                                                   float fServerTimestamp)
{
	bool bMoving = false;
#ifdef ASURA_NO_NETWORK_LAG_COMPENSATION
	// Convert old orientation to a Quat, we'll need it later
	Asura_Quat xOldOrientation;
	xOldOrientation.FromMatrix(m_xCurrentOrientation);
	const bool bRotating = (!xOldOrientation.ApproximatelyEquals(xOrientation));
	const bool bMoved = (m_xCurrentPosition != xPosition);
	bMoving = (m_bEntityAlwaysMoves || bRotating || bMoved);
	// Update velocities
	float fTimeDiff = (fServerTimestamp - m_fLastReportedServerTimestamp);
	if (fTimeDiff > 0)
	{	
		if (bMoved)
		{	// Update current velocity and position
			m_xCurrentVelocity = (xPosition - m_xCurrentPosition) / fTimeDiff;
		}
		else
		{
			m_xCurrentVelocity.Zero();
		}

		if (bRotating)
		{	//	Update current angular velocity and orientation
			Asura_Euler_3 xEuler0, xEuler1;
			xOrientation.ToEuler(xEuler1);
			xOldOrientation.ToEuler(xEuler0);
			m_xCurrentAngularVelocity = (xEuler1 - xEuler0);
			// assume velocity is within +/- 180 deg per frame, so shift into range
			if (m_xCurrentAngularVelocity.x < -Asura_Maths::Deg180)		m_xCurrentAngularVelocity.x += Asura_Maths::Deg360;
			else if (m_xCurrentAngularVelocity.x > Asura_Maths::Deg180) m_xCurrentAngularVelocity.x -= Asura_Maths::Deg360;
			if (m_xCurrentAngularVelocity.y < -Asura_Maths::Deg180)		m_xCurrentAngularVelocity.y += Asura_Maths::Deg360;
			else if (m_xCurrentAngularVelocity.y > Asura_Maths::Deg180) m_xCurrentAngularVelocity.y -= Asura_Maths::Deg360;
			if (m_xCurrentAngularVelocity.z < -Asura_Maths::Deg180)		m_xCurrentAngularVelocity.z += Asura_Maths::Deg360;
			else if (m_xCurrentAngularVelocity.z > Asura_Maths::Deg180) m_xCurrentAngularVelocity.z -= Asura_Maths::Deg360;
			m_xCurrentAngularVelocity /= fTimeDiff;
		}
		else
		{
			m_xCurrentAngularVelocity.Zero();
		}
	}
	else
	{	// First time update
		m_xCurrentVelocity.Zero();
		m_xCurrentAngularVelocity.Zero();
	}

	// Update current position and orientation
	m_xCurrentPosition = xPosition;
	xOrientation.ToMatrix(m_xCurrentOrientation);	
	
	// AW - 21/03/03 - Added timestamp and prev position record to implement
	m_fLastReportedServerTimestamp = fServerTimestamp;
#else
	if (!m_pxPosAndOriInterpolator)
	{
		m_xCurrentPosition = xPosition;
		xOrientation.ToMatrix(m_xCurrentOrientation);
		m_bIsEntityCurrentlyMoving = m_bEntityAlwaysMoves;
		return;
	}
	else
	{
#if _DEBUG
		extern u_int g_uCurGuidForTest;
		g_uCurGuidForTest = GetGuid();
#endif
		m_pxPosAndOriInterpolator->ProcessNewData(xPosition, xOrientation, fServerTimestamp);
#if _DEBUG
		g_uCurGuidForTest = 0;
#endif
	}

	if (m_bEntityAlwaysMoves || m_pxPosAndOriInterpolator->IsMoving())
	{
		bMoving = true;
	}

#endif
	if (m_bIsEntityCurrentlyMoving != bMoving)
	{
		SetEntityCurrentlyMoving(bMoving);
	}
}


void Asura_Entity_Renderable::SetTimeLagType(TimeLagType eType)
{
#ifndef ASURA_NO_NETWORK_LAG_COMPENSATION
	Asura_Assert(m_pxPosAndOriInterpolator, "");
	if (m_pxPosAndOriInterpolator)
	{
		m_pxPosAndOriInterpolator->SetTimeLagType(eType);
	}
#endif
}

bool Asura_Entity_Renderable::GetPositionForDialogue(Asura_Vector_3& xPosition) const
{
	xPosition = GetCurrentPosition();

	return true;
}

void Asura_Entity_Renderable::SetInvisibleFlag(bool bInvisible)
{
	bool bWasInvisible = IsInvisible();
	m_bInvisibleGeneral = bInvisible;

	SetInvisibleInternal( bInvisible, bWasInvisible );
}

void Asura_Entity_Renderable::SetInvisibleByPhysicalFlag(bool bInvisible)
{
	bool bWasInvisible = IsInvisible();
	m_bInvisibleByPhysicalFlag = bInvisible;

	SetInvisibleInternal( bInvisible, bWasInvisible );
}

void Asura_Entity_Renderable::SetInvisibleByCutscene(bool bInvisible)
{
	bool bWasInvisible = IsInvisible();
	m_bInvisibleByCutscene = bInvisible;

	SetInvisibleInternal( bInvisible, bWasInvisible );
}

void Asura_Entity_Renderable::SetInvisibleByDeactivation(bool bInvisible)
{
	bool bWasInvisible = IsInvisible();
	m_bInvisibleByDeactivation = bInvisible;

	SetInvisibleInternal( bInvisible, bWasInvisible );
}

// private internal function - replaces 4 copies of same code in calling functions.
void Asura_Entity_Renderable::SetInvisibleInternal( bool bInvisible, bool bWasInvisible )
{
	//Has this changed the overall invisibility state?
	if( m_bRemoveFromRenderableListIfInvisible && bWasInvisible != IsInvisible() )
	{
		if(bInvisible)
		{
			RemoveFromRenderableList(true);
		}
		else
		{
			AddToRenderableList();
		}
		OnVisibilityChanged();
	}
}


void Asura_Entity_Renderable::AddToList(void)
{
	// Add to the standard list...
	s_xList.AddToList(this);
}

void Asura_Entity_Renderable::RemoveFromList(void)
{
	// Remove from the standard list...
	s_xList.RemoveFromList(this);
}

void Asura_Entity_Renderable::AddToRenderableList(void)
{
	// Add to the standard list...
	s_xRenderableList.AddToList(this);

#if defined(ASURA_USE_STREAMING)
	Asura_Streaming_Zone*			pxZone		= Asura_Streaming_System::FindMasterZone(m_uZoneHashID);
	Asura_Streaming_ZoneEntities*	pxEntities	= pxZone->GetEntities();

	// Register with the streaming system...
	pxEntities->AddRenderableEntityToList(*this);
#endif	// defined(ASURA_USE_STREAMING)
}

void Asura_Entity_Renderable::RemoveFromRenderableList(const bool bShouldAlwaysBePresent)
{
	// Remove from the standard list...
	s_xRenderableList.RemoveFromList(this, bShouldAlwaysBePresent);

#if defined(ASURA_USE_STREAMING)
	Asura_Streaming_Zone*			pxZone		= Asura_Streaming_System::FindMasterZone(m_uZoneHashID);
	Asura_Streaming_ZoneEntities*	pxEntities	= pxZone->GetEntities();

	// Register with the streaming system...
	pxEntities->RemoveRenderableEntityFromList(*this, bShouldAlwaysBePresent);
#endif	// defined(ASURA_USE_STREAMING)
}

void Asura_Entity_Renderable::GetLineOfSightAABB( Asura_Bounding_Box& xAABB ) 
{
	Asura_Assert( false, "Code bug: the derived class does not implement GetLineOfSightAABB()" );
}

void Asura_Entity_Renderable::AddToLineOfSightList( void )
{
	s_xLineOfSightList.AddToList( this );
	
	++s_uLineOfSightUpdateStamp;
}

void Asura_Entity_Renderable::RemoveFromLineOfSightList( void )
{
	// The "false" parameter below means RemoveFromList won't assert if the entity
	// is already not in the list.
	s_xLineOfSightList.RemoveFromList( this, false );

	++s_uLineOfSightUpdateStamp;
}

#ifdef ASURA_USE_STREAMING
Asura_Hash_ID Asura_Entity_Renderable::CheckForZoneChange()
{
	Asura_Hash_ID uNewHash = UpdateContainingZone(GetCurrentPosition());
	return uNewHash;
}

void Asura_Entity_Renderable::MigrateZone(Asura_Streaming_ZoneEntities* pxOldEntities, Asura_Streaming_ZoneEntities* pxNewEntities)
{
	Asura_Streaming_ZoneEntities::MigrateEntity(this, pxOldEntities, pxNewEntities);
}
#endif //#ifdef ASURA_USE_STREAMING


// overrides of OnEntityActivate()/OnEntityDeactivate() - called when active status changes or is initialised. 
// Derived classes should always call parent versions if they override them. 
// We remove the entity from the renderable lists as appropriate on deactivation and put it back in on activation.
void Asura_Entity_Renderable::OnEntityActivate(void) 
{
	PARENT::OnEntityActivate();

	SetInvisibleByDeactivation(false);
}

void Asura_Entity_Renderable::OnEntityDeactivate(void) 
{
	PARENT::OnEntityDeactivate();

	SetInvisibleByDeactivation(true);

	// Catch all to remove entities from line of sight when deactivated
	RemoveFromLineOfSightList();
}

/// End of file. ///
