///////////////////////////////////////////////////////////////////////////////
//
// File   : AVP_Human_MotionTracker.cpp
// Author : Chris Wallace
// Date   : 8-Jun-2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "Actor/Vertebrates/Human/AVP_Human_MotionTracker.h"

// Asura
#include "Blueprint/Asura_Blueprint_System.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "GUIMenu/Asura_GUIMenu_Element_Image.h"
#include "GUIMenu/Asura_GUIMenu_Menu_Page.h"
#include "Rendering/Asura_Viewport.h"
#include "Tactical/AVP_Tactical_System.h"

// AVP
#include "Actor/AVP_Actor_ObjectiveHandler.h"
#include "Actor/Vertebrates/AVP_Vertebrate_Description.h"
#include "Actor/Vertebrates/Human/AVP_Human_HUDHandler.h"
#include "Blueprint/AVP_Blueprint_HashIDs.h"
#include "Core/AVP_Game_Core.h"
#include "Entity/MotionTrackerBlip/AVP_ServerEntity_MotionTrackerBlip.h"
#include "Entity/PhysicalObject/Actor/AVP_ServerEntity_Actor.h"
#include "Entity/PhysicalObject/Actor/Vertebrates/Human/AVP_ClientActor_Human.h"
#include "Entity/PhysicalObject/AVP_ServerEntity_PhysicalObject.h"
#include "GUIMenu/AVP_GUIMenu_System.h"
#include "Inventory/AVP_Inventory_HashIDs.h"
#include "Inventory/Items/AVP_InventoryItem_Torch.h"
#include "Rendering/AVP_Overlay_Render.h"

DEBUGVAR float dbg_fDisruptTime		= 1.0f;
DEBUGVAR float dbg_fMinFlickerTime	= 0.2f;
DEBUGVAR float dbg_fMaxFlickerTime	= 0.5f;

float dbg_fMaxRange					= 50.0f; //Distance in meters
float dbg_fSpeedThreshold			= 0.5f;                                       // Was 1.5
float dbg_fSpeedThresholdSqrd       = dbg_fSpeedThreshold * dbg_fSpeedThreshold;

float dbg_fDotFadeInParametric		= 0.025f;
float dbg_fDotFadeTimeScale			= 1.0f;

float dbg_fMotionTrackerTimeScale	= 1.0f;

float dbg_fPulseScaleLength			= 0.35f;
float dbg_fStartTime				= 0.0f;
float dbg_fMiddleTime				= dbg_fStartTime + dbg_fPulseScaleLength;
float dbg_fEndTime					= dbg_fMiddleTime+((1.0f-dbg_fMiddleTime)/(3.0f/dbg_fPulseScaleLength));

float dbg_fSecondaryPulseScaleLength= 0.6f;
float dbg_fSecondaryStartTime		= 0.0f;
float dbg_fSecondaryMiddleTime		= dbg_fSecondaryStartTime + dbg_fSecondaryPulseScaleLength;
float dbg_fSecondaryEndTime			= dbg_fSecondaryMiddleTime+((1.0f-dbg_fSecondaryMiddleTime)/(3.0f/dbg_fSecondaryPulseScaleLength));

float dbg_fSecondaryPulseAlphaDiff  = 0.5f;

bool AVP_Human_MotionTracker::s_bShowMotionTrackerScanner = false;
bool AVP_Human_MotionTracker::s_bShowMotionTrackerPulse   = false;
bool AVP_Human_MotionTracker::s_bShowMotionTrackerDot     = false;
bool AVP_Human_MotionTracker::s_bShowMotionTrackerBase    = false;
bool AVP_Human_MotionTracker::s_bMotionTrackerDisrupted    = false;

////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

AVP_Human_MotionTracker::AVP_Human_MotionTracker (Asura_Guid uOwnerGuid)
: m_pxCurrentList(&m_xDotList)
, m_xPosition(0.0f,0.0f)
, m_uOwnerGuid(uOwnerGuid)
, m_fScannerTheta(0.0f)
, m_fMotionTimer(0.0f)
, m_fPulseScale(1.0f)
, m_fPulseAlpha(1.0f)
, m_fFadeParametric(0.0f) // Assume it is off to start with
, m_fDisruptTimer( 0.0f )
, m_fFlickerTimer( 0.0f )
, m_fFlags( 0 )
, m_pxPulseSound( NULL )
, m_pxBleepSound( NULL )
, m_iPulseHandle(ASURA_INVALID_HANDLE)
, m_iBleepHandle(ASURA_INVALID_HANDLE)
{
	// Setting these to true to supress any beeps or pulses for the first scan as a few odd ones seem to play before
	// stuff triggers etc and seems a little untidy.
	m_bBleepedThisPulse = true;
	m_bScannedThisPulse = true;

	RefreshBlueprintValues();

	AVP_ClientActor_Human* pxClient = AVP_ClientActor_Human::FindEntity(m_uOwnerGuid);
	if(pxClient)
	{
		m_pxBleepSound		= pxClient->GetSoundEventFromBlueprints(0x00E35627); //Asura_GetHashID("MarinePlayerMotionTrackerBleep")
		m_pxPulseSound		= pxClient->GetSoundEventFromBlueprints(0x01ACD27A); //Asura_GetHashID("MarinePlayerMotionTrackerPulse")
	}	
}

AVP_Human_MotionTracker::~AVP_Human_MotionTracker()
{
	Asura_Sound_Event_System::Stop(m_iPulseHandle);
	Asura_Sound_Event_System::Stop(m_iBleepHandle);
}

/////////////////////////////////
//	Update
/////////////////////////////////

void AVP_Human_MotionTracker::Update( const AVP_ClientActor_Human& xClient )
{
	if ( !xClient.IsDead() )
	{
		UpdateFadeParametric();
		UpdateDisruption();

		////Only render if the torch is off
		//const AVP_InventoryItem_Torch* pxItem = xClient.GetTorchItem();
		//bool bDisableMotionTracker = pxItem ? pxItem->IsUsing(false) : false;

		bool bDisableMotionTracker = (Asura_Render::GetCutsceneHasCameraControl()) ? true : false;  // Dont update if in cutscene, other wise will play sounds still.

		AVP_Human_HUDHandler::SetMotionTrackerActive( !bDisableMotionTracker );
		AVP_Human_HUDHandler::SetTorchActive( bDisableMotionTracker );

		if(!bDisableMotionTracker)
		{
			m_fMotionTimer += (Asura_Timers::GetGameTimeSlice()*dbg_fMotionTrackerTimeScale);

			if(m_fMotionTimer>1.0f)
			{
				m_fMotionTimer = 0.0f;
				m_bScannedThisPulse = false;
				m_bBleepedThisPulse = false;
			}

			UpdateScanner( xClient );
			UpdatePulse();
			UpdateSecondaryPulse();

			if(!m_bScannedThisPulse)
			{
				UpdateActors( xClient );
				m_bScannedThisPulse = true;
			}

			UpdateDots( xClient, &m_xDotList );
			UpdateDots( xClient, &m_xSecondaryDotList );
		}

		// Make sure we clean up sounds whenever we can.
		if ((m_iPulseHandle != ASURA_INVALID_HANDLE) && !Asura_Sound_Event_System::IsPlaying(m_iPulseHandle))
		{
			Asura_Sound_Event_System::Stop(m_iPulseHandle);
		}
		if ((m_iBleepHandle != ASURA_INVALID_HANDLE) && !Asura_Sound_Event_System::IsPlaying(m_iBleepHandle))
		{
			Asura_Sound_Event_System::Stop(m_iBleepHandle);
		}
	}
}

void AVP_Human_MotionTracker::UpdateScanner( const AVP_ClientActor_Human& xClient )
{
	// Get the players orientation
	Asura_Matrix_3x3 xOrientMatrix;
	Asura_Hash_ID uCameraHashID = AVP_Vertebrate_Description::GetMarkerPointHashID( AVP_Vertebrate_Description::MPID_EYES ); 
	xClient.GetWorldSpaceMarkerPoint(uCameraHashID, NULL, &xOrientMatrix);
	Asura_Euler_3 xOrientEuler;
	xOrientEuler.FromMatrix(xOrientMatrix);

	// Store the angle around Y-Axis
	m_fScannerTheta = xOrientEuler.y;
}

void AVP_Human_MotionTracker::UpdatePulse()
{
	// The pulse starts at time 0.0 and ends the scaling at time 0.35
	// The Alpha-fade takes 1/8th of the remaining time per pulse
	
	float fPulseLength = (dbg_fMiddleTime - dbg_fStartTime);
	float fFadeLength = (dbg_fEndTime - dbg_fMiddleTime);

	if(m_fMotionTimer < dbg_fMiddleTime)
	{
		// If in first stage, calculate scale
		m_fPulseAlpha = 1.0f;
		m_fPulseScale = (fPulseLength) ? ((m_fMotionTimer - dbg_fStartTime) / fPulseLength) : 0.0f;
	}
	else if(m_fMotionTimer < dbg_fEndTime)
	{
		// If in second stage, calculate alpha
		m_fPulseScale = 1.0f;
		m_fPulseAlpha = (fFadeLength>0.0f) ? 1.0f - ((m_fMotionTimer-dbg_fMiddleTime)/fFadeLength) : 0.0f;
	}
	else
	{
		// If finished set to safe/invisible values
		m_fPulseAlpha = 0.0f;
		m_fPulseScale = 0.0f;
	}
}

void AVP_Human_MotionTracker::UpdateSecondaryPulse()
{
	// The pulse starts at time 0.0 and ends the scaling at time 0.35
	// The Alpha-fade takes 1/8th of the remaining time per pulse

	float fPulseLength = (dbg_fSecondaryMiddleTime - dbg_fSecondaryStartTime);
	float fFadeLength = (dbg_fSecondaryEndTime - dbg_fSecondaryMiddleTime);

	if(m_fMotionTimer < dbg_fSecondaryMiddleTime)
	{
		// If in first stage, calculate scale
		m_fSecondaryPulseAlpha = 1.0f;
		m_fSecondaryPulseScale = (fPulseLength) ? ((m_fMotionTimer - dbg_fSecondaryStartTime) / fPulseLength) : 0.0f;
	}
	else if(m_fMotionTimer < dbg_fSecondaryEndTime)
	{
		// If in second stage, calculate alpha
		m_fSecondaryPulseScale = 1.0f;
		m_fSecondaryPulseAlpha = (fFadeLength>0.0f) ? 1.0f - ((m_fMotionTimer-dbg_fSecondaryMiddleTime)/fFadeLength) : 0.0f;
	}
	else
	{
		// If finished set to safe/invisible values
		m_fSecondaryPulseAlpha = 0.0f;
		m_fSecondaryPulseScale = 0.0f;
	}
}

// Creates a list of nearby actors
void AVP_Human_MotionTracker::UpdateActors( const AVP_ClientActor_Human& xClient )
{
	SwapLists(); // swaps over double-buffered list

	// Clean out the original list
	m_pxCurrentList->Clear();

	// Reset closest distance
	m_fClosestDistance = Asura_Maths::LargeFloat;

	Asura_Vector_3 xPlayerPosition = xClient.GetCurrentPosition();

	Asura_Vector_3 xVelocity;
	float          fSpeedSqrd;
	float          fDistance;
	float          fYOmittedDistance;
	Asura_Vector_3 xYOmittedDirectionRotated;
	Asura_Vector_3 xDirection;
	Asura_Vector_3 xDirectionRotated;
	Asura_Vector_3 xActorPosition;
	Asura_Quat	   xQuat;
	TRACKER_DOT    xNewDot;

	// Run through all actors
	Asura_Classified_List_Const_It<AVP_ClientEntity_PhysicalObject> xIterator( AVP_ClientEntity_PhysicalObject::GetList() );
	while(!xIterator.Done())
	{
		const AVP_ClientEntity_PhysicalObject* pxObject = xIterator.GetCurrent();

		if(   pxObject 
		   && !pxObject->IsDontShowUpInMotionTracker() // Don't consider projectiles
		   && pxObject->GetGuid() != m_uOwnerGuid   // Do not add ourselves
		   && pxObject->IsEntityActive() 
		   && pxObject->IsEntityCurrentlyMoving() )		
		{
			// Check the velocity
			pxObject->GetCurrentVelocity( xVelocity );
			fSpeedSqrd = xVelocity.MagnitudeSquared();

			if(fSpeedSqrd > dbg_fSpeedThresholdSqrd)
			{
				// Find out of it is in range
			    xActorPosition = pxObject->GetCurrentPosition();
			
				xDirection     = xActorPosition - xPlayerPosition;

				xQuat.FromMatrix(xClient.GetCurrentOrientation());

				xQuat.Invert();
				xDirectionRotated = xQuat.RotateVector(xDirection);

				xYOmittedDirectionRotated = xDirectionRotated;

				xYOmittedDirectionRotated.y = 0.0f;

				fYOmittedDistance = xYOmittedDirectionRotated.Magnitude();

				fDistance         = xDirectionRotated.Normalise();

				if(fDistance <= dbg_fMaxRange)
				{
					//If it is detected, add it to the list
					xNewDot.uActorGuid = pxObject->GetGuid();

					xNewDot.xInitialPos.x = xDirection.x;
					xNewDot.xInitialPos.z = xDirection.z;
					xNewDot.xInitialPos.y = xDirection.y;

					xNewDot.fLifeTime	= 1.0f;
					
					xNewDot.bActive		= false;

					m_pxCurrentList->Push_Back(xNewDot);

					if(fYOmittedDistance < m_fClosestDistance)
					{
					   m_fClosestDistance = fYOmittedDistance;
					}
				}
			}
		}

		// Next
		xIterator.Next();
	}

	// Iterate over blips
	if ( AVP_Settings::IsSinglePlayer() )
	{
		for (Asura_Hashed_List_It<AVP_ServerEntity_MotionTrackerBlip> it(AVP_ServerEntity_MotionTrackerBlip::GetList()); !it.Done(); it.Next())
		{
			AVP_ServerEntity_MotionTrackerBlip* pxEntity = it.GetCurrent();

			if ( pxEntity && pxEntity->IsEntityActive() )
			{
				fSpeedSqrd = pxEntity->GetVelocity() * pxEntity->GetVelocity();

				if (fSpeedSqrd > dbg_fSpeedThresholdSqrd)
				{
					// Find out of it is in range
					xActorPosition = pxEntity->GetPosition();

					xDirection = xActorPosition - xPlayerPosition;

					xQuat.FromMatrix(xClient.GetCurrentOrientation());

					xQuat.Invert();
					xDirectionRotated = xQuat.RotateVector(xDirection);

					fDistance = xDirectionRotated.Normalise();

					if (fDistance <= dbg_fMaxRange)
					{
						//If it is detected, add it to the list
						xNewDot.uActorGuid = pxEntity->GetGuid();

						xNewDot.xInitialPos.x = xDirection.x;
						xNewDot.xInitialPos.z = xDirection.z;
						xNewDot.xInitialPos.y = xDirection.y;

						xNewDot.fLifeTime	= 1.0f;

						xNewDot.bActive		= false;

						m_pxCurrentList->Push_Back(xNewDot);

						if (fDistance < m_fClosestDistance)
						{
							m_fClosestDistance = fDistance;
						}
					}
				}
			}
		}
	}

	// Play the pulse sound and Get square root of the distance
	// Play it only if something is detected
	//if (m_xDotList.Size() > 0)
	//{
		PlayPulseSound();
	//}
}

void AVP_Human_MotionTracker::UpdateDots( const AVP_ClientActor_Human& xClient, Asura_LinkList<TRACKER_DOT>* pxList )
{
	// Each Dot starts off with an Alpha value of Zero, it is set to 1
	// Once the pulse moves over its position.

	float fCurrentParametric = (dbg_fMiddleTime!=0.0f) ? (m_fMotionTimer/dbg_fMiddleTime) : 0.0f;

	Asura_ListElt<TRACKER_DOT>* pxCurrentElt = pxList->Front();
	while(pxCurrentElt != NULL)
	{
		TRACKER_DOT* pxCurrent = &pxCurrentElt->Data();
		
		if(pxCurrent)
		{
			float fDistance = pxCurrent->xInitialPos.Magnitude();
			
			// If this dot is not yet lit...
			if(pxCurrent->bActive == false)
			{
				//Get the time this should be lit.
				float fLightParametric = (fDistance / dbg_fMaxRange);
					
				if(fCurrentParametric >= fLightParametric)
				{
					pxCurrent->bActive	 = true;

					//Play the bleep sound
					if(!m_bBleepedThisPulse)
					{
						// Don't play on same species as the player
						const AVP_ClientEntity_Actor* const pxDot    = AVP_ClientEntity_Actor::FindEntity(pxCurrent->uActorGuid);
						if ( pxDot && ( AVP_Tactical_System::GetAllegiance(xClient, *pxDot) != AVP_ALLEGIANCE_FRIEND ) )							
						{
							PlayBleepSound(fLightParametric);
							m_bBleepedThisPulse = true;
						}
						else
						{
							const AVP_ServerEntity_MotionTrackerBlip* pxMTBlip = AVP_ServerEntity_MotionTrackerBlip::FindEntity( pxCurrent->uActorGuid );

							if ( pxMTBlip )
							{
								PlayBleepSound(fLightParametric);
								m_bBleepedThisPulse = true;
							}
						}


					}	
				}
			}
			else //Otherwise, fade them out
			{
				pxCurrent->fLifeTime -= Asura_Timers::GetGameTimeSlice()*dbg_fDotFadeTimeScale;
				if(pxCurrent->fLifeTime < 0.0f)
				{
					pxCurrent->fLifeTime = 0.0f;
				}

				// Update the position
				Asura_Vector_3 xDirection =  pxCurrent->xInitialPos;
				Asura_Vector_3 xDirectionRotated;

				Asura_AMatrix3x3 xClientOri(xClient.GetCurrentOrientation());

				Asura_AVector3 xAimDir;
				xClient.GetAimDirection( xAimDir );
				xAimDir = xClientOri.ApplyTranspose( xAimDir );
				xAimDir.SetY(0.0f);
				xAimDir = xAimDir * xClientOri;

				Asura_Matrix_3x3 xAimOri;
				Asura_Maths::OrientationFromDirection( xAimOri, Asura_Vector_3( xAimDir ) );

				xDirectionRotated = xAimOri.ApplyTranspose( xDirection );

				fDistance = xDirectionRotated.Normalise();

				Asura_GUIMenu_Element_Image* pxImageItem = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DOT_CONTAINER));
				if( pxImageItem )
				{
					Asura_Vector_3 xScaledDirection = xDirectionRotated * ((fDistance/dbg_fMaxRange) * pxImageItem->GetScaledWorldBounds().GetHeight() * 0.5f);

					pxCurrent->xPosition.x = xScaledDirection.x;
					//If behind the motion tracker semi circle, move it a little bit behind the semi circle
					float fAlittleBitBehind = - ( pxImageItem->GetScaledWorldBounds().GetHeight() * 0.14f );
					pxCurrent->xPosition.y  = ( xScaledDirection.z >= fAlittleBitBehind ) ? xScaledDirection.z : fAlittleBitBehind ;
				}
			}
		}
		pxCurrentElt = pxCurrentElt->Next();
	}
}

void AVP_Human_MotionTracker::UpdateFadeParametric(void)
{
	// We want to get the fade value from the 'GP_Misc_Elements' HUD Menu Item
	m_fFadeParametric = AVP_GUIMenu_System::GetHUDFadeParamentric();
}

void AVP_Human_MotionTracker::SwapLists(void)
{
	if(m_pxCurrentList != &m_xDotList)
	{
		m_pxCurrentList = &m_xDotList;
	}
	else
	{
		m_pxCurrentList = &m_xSecondaryDotList;
	}
}
/////////////////////////////////
//	Render
/////////////////////////////////

void AVP_Human_MotionTracker::Render( const AVP_ClientActor_Human& xClient )
{
	if ( !xClient.IsDead())
	{
		if ( m_bDisrupted )
		{
			RenderFuzz(m_fFadeParametric);
		}
		else
		{
			RenderScanners();
			RenderPulse(m_fFadeParametric);
			RenderDots(xClient, m_fFadeParametric, &m_xDotList);
			RenderDots(xClient, m_fFadeParametric, &m_xSecondaryDotList);
			RenderDistances();
		}
	}
}

////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_Human_MotionTracker::RenderDistance
// Author       : AJK
// Date         : 05-Nov-2008
////////////////////////////////////////////////////////////////////////////////
void AVP_Human_MotionTracker::RenderDistances()
{
	if ( m_pxCurrentList->Size() > 0 )
	{
		Asura_GUIMenu_Element_Text* pxText1 = static_cast<Asura_GUIMenu_Element_Text*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DISTANCE1));
		Asura_GUIMenu_Element_Text* pxText2 = static_cast<Asura_GUIMenu_Element_Text*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DISTANCE2));
		Asura_GUIMenu_Element_Text* pxText3 = static_cast<Asura_GUIMenu_Element_Text*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DISTANCE3));

		if( pxText1 && pxText2 && pxText3 )
		{
			Asura_Assert(pxText1->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_TEXT && pxText2->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_TEXT && pxText3->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_TEXT, "Casting to wrong type! Will probably crash!");

			RenderDistance( *pxText1 );
			RenderDistance( *pxText2 );
			RenderDistance( *pxText3 );
		}
	}
}

void AVP_Human_MotionTracker::RenderDistance( Asura_GUIMenu_Element_Text& xText )
{
	Asura_UnicodeChar wszBuffer[16];
	Asura_PrintfToBfr( wszBuffer, L"%.1f m", sizeof(wszBuffer), true, m_fClosestDistance);
	xText.SetText( wszBuffer);
}

void AVP_Human_MotionTracker::RenderScanners()
{
	Asura_GUIMenu_Element_Image* pxImageScanner1 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_SCANNER1));
	Asura_GUIMenu_Element_Image* pxImageScanner2 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_SCANNER2));
	Asura_GUIMenu_Element_Image* pxImageScanner3 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_SCANNER3));

	if ( pxImageScanner1 && pxImageScanner2 && pxImageScanner3 )
	{
		Asura_Assert(pxImageScanner1->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_IMAGE && pxImageScanner2->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_IMAGE && pxImageScanner3->GetType() == Asura_GUIMenu_Root::ASURA_GUIMENU_TYPEID_ELEMENT_IMAGE, "Casting to wrong type! Will probably crash!");

		RenderScanner( *pxImageScanner1 );
		RenderScanner( *pxImageScanner2 );
		RenderScanner( *pxImageScanner3 );
	}
}

void AVP_Human_MotionTracker::RenderScanner( Asura_GUIMenu_Element_Image& xImageScanner )
{
	Asura_Vector_3 xUVW[4];
	Asura_Vector_3 xUVWOrigin;

	if ( xImageScanner.GetAnimatorCount() > 0 )
	{
		Asura_GUIMenu_UV_Set xUVSet = xImageScanner.GetAnimator(0)->GetUVSetData();
		float fVOver2 = ( xUVSet.xUV1.v - xUVSet.xUV0.v ) * 0.5f;

		xUVWOrigin.x = xUVSet.xUV0.u + ( ( xUVSet.xUV1.u - xUVSet.xUV0.u ) * 0.5f );
		xUVWOrigin.y = xUVSet.xUV0.v + fVOver2;
		xUVWOrigin.z = 0.0f;

		xUVW[0].x = xUVSet.xUV0.u;
		xUVW[0].y = xUVSet.xUV0.v;
		xUVW[0].z = 0.0f;

		xUVW[1].x = xUVSet.xUV1.u;
		xUVW[1].y = xUVSet.xUV0.v;
		xUVW[1].z = 0.0f;

		xUVW[2].x = xUVSet.xUV1.u;
		xUVW[2].y = xUVSet.xUV1.v - fVOver2;
		xUVW[2].z = 0.0f;

		xUVW[3].x = xUVSet.xUV0.u;
		xUVW[3].y = xUVSet.xUV1.v - fVOver2;
		xUVW[3].z = 0.0f;
	}
	else
	{
		float fVOver2 = ( xImageScanner.GetUV(1).v - xImageScanner.GetUV(0).v ) * 0.5f;

		xUVWOrigin.x = xImageScanner.GetUV(0).u + ( ( xImageScanner.GetUV(1).u - xImageScanner.GetUV(0).u ) * 0.5f );
		xUVWOrigin.y = xImageScanner.GetUV(0).v + fVOver2;
		xUVWOrigin.z = 0.0f;

		xUVW[0].x = xImageScanner.GetUV(0).u;
		xUVW[0].y = xImageScanner.GetUV(0).v;
		xUVW[0].z = 0.0f;

		xUVW[1].x = xImageScanner.GetUV(1).u;
		xUVW[1].y = xImageScanner.GetUV(0).v;
		xUVW[1].z = 0.0f;

		xUVW[2].x = xImageScanner.GetUV(1).u;
		xUVW[2].y = xImageScanner.GetUV(1).v - fVOver2;
		xUVW[2].z = 0.0f;

		xUVW[3].x = xImageScanner.GetUV(0).u;
		xUVW[3].y = xImageScanner.GetUV(1).v - fVOver2;
		xUVW[3].z = 0.0f;
	}

	// Calculate rotation matrix
	Asura_Matrix_3x3 xRotMatrix;
	Asura_Euler_3    xEuler(0.0f, 0.0f, m_fScannerTheta);

	xEuler.ToMatrix(xRotMatrix);

	for(int i=0; i < 4; i++)
	{
		xUVW[i] -= xUVWOrigin;

		xUVW[i] = xUVW[i] * xRotMatrix;

		xUVW[i] += xUVWOrigin;

		Asura_Render::Overlay.U[i] = xUVW[i].x;
		Asura_Render::Overlay.V[i] = xUVW[i].y;
	}

	Asura_Render::Overlay.Colour           = xImageScanner.GetColour().ToUIntARGB();
	Asura_Render::Overlay.TranslucencyMode = xImageScanner.GetTranslucencyMode();
	Asura_Render::Overlay.TextureIndex     = xImageScanner.GetTexture();
	Asura_Render::Overlay.SetRectXY(xImageScanner.GetScaledWorldPos().x, xImageScanner.GetScaledWorldPos().y, xImageScanner.GetScaledWorldBounds().GetWidth(), xImageScanner.GetScaledWorldBounds().GetHeight() * 0.5f);
	Asura_Render::Overlay.Render();
}

void AVP_Human_MotionTracker::RenderPulse(float fAlpha)
{
	Asura_GUIMenu_Element_Image* pxPulseItem1   = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_PULSE1));
	Asura_GUIMenu_Element_Image* pxPulseItem2   = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_PULSE2));
	
	Asura_Render::Overlay.TranslucencyMode   	= ASURA_TRANSLUCENCY_GLOWING;

	if ( pxPulseItem1 && m_fPulseScale >= 0.2f )
	{
		float x = pxPulseItem1->GetScaledWorldPos().x + pxPulseItem1->GetScaledWorldBounds().GetWidth() * 0.5f;
		float y = pxPulseItem1->GetScaledWorldPos().y + pxPulseItem1->GetScaledWorldBounds().GetHeight();

		float fWidth1	= m_fPulseScale * pxPulseItem1->GetScaledWorldBounds().GetWidth() ;
		float fHeight1	= m_fPulseScale * pxPulseItem1->GetScaledWorldBounds().GetHeight();

		float x1        = x - ( fWidth1 * 0.5f );
		float y1        = y - fHeight1;

		Asura_Render::Overlay.TextureIndex      = pxPulseItem1->GetTexture();
		Asura_Render::Overlay.SetRectUV(pxPulseItem1->GetUV(0).u, pxPulseItem1->GetUV(0).v, pxPulseItem1->GetUV(1).u, pxPulseItem1->GetUV(1).v);
		Asura_Render::Overlay.SetRectXY(x1, y1, fWidth1, fHeight1);
		Asura_Render::Overlay.Colour            = AVP_Overlay_Render::MultiplyAlphaValue(0xFFFFFFFF, m_fPulseAlpha * fAlpha);
		Asura_Render::Overlay.Render();
	}

	if (  pxPulseItem2 && m_fSecondaryPulseScale >= 0.2f )
	{
		float x = pxPulseItem2->GetScaledWorldPos().x + pxPulseItem2->GetScaledWorldBounds().GetWidth() * 0.5f;
		float y = pxPulseItem2->GetScaledWorldPos().y + pxPulseItem2->GetScaledWorldBounds().GetHeight();

		float fWidth2	= m_fSecondaryPulseScale * pxPulseItem2->GetScaledWorldBounds().GetWidth();
		float fHeight2	= m_fSecondaryPulseScale * pxPulseItem2->GetScaledWorldBounds().GetHeight();

		float x2 = x - ( fWidth2 * 0.5f );
		float y2 = y - fHeight2;

		Asura_Render::Overlay.TextureIndex      = pxPulseItem2->GetTexture();
		Asura_Render::Overlay.SetRectUV(pxPulseItem2->GetUV(0).u, pxPulseItem2->GetUV(0).v, pxPulseItem2->GetUV(1).u, pxPulseItem2->GetUV(1).v);
		Asura_Render::Overlay.SetRectXY(x2, y2, fWidth2, fHeight2);
		Asura_Render::Overlay.Colour            = AVP_Overlay_Render::MultiplyAlphaValue(0xFFFFFFFF, m_fSecondaryPulseAlpha * fAlpha);
		Asura_Render::Overlay.Render();
	}
}

void AVP_Human_MotionTracker::RenderDots( const AVP_ClientActor_Human& xClient, float fAlpha, Asura_LinkList<TRACKER_DOT>* pxList)
{

	Asura_GUIMenu_Element_Image* pxImageDot       = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DOT));
	Asura_GUIMenu_Element_Image* pxImageDotMarine = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DOT_MARINE));
	Asura_GUIMenu_Element_Image* pxImageScanner   = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_DOT_CONTAINER));

	if ( pxImageDot && pxImageDotMarine && pxImageScanner)
	{
		Asura_Render::Overlay.TranslucencyMode	 = ASURA_TRANSLUCENCY_GLOWING;

		Asura_Vector_2 xScannerCenter (  pxImageScanner->GetScaledWorldPos().x + pxImageScanner->GetScaledWorldBounds().GetWidth() * 0.5f
			                           , pxImageScanner->GetScaledWorldPos().y + pxImageScanner->GetScaledWorldBounds().GetHeight()* 0.5f );
		Asura_ListElt<TRACKER_DOT>* pxCurrentElt = pxList->Front();
		while(pxCurrentElt != NULL)
		{
			TRACKER_DOT* pxCurrent = &pxCurrentElt->Data();

			// Two stages of alpha
			float fDotAlpha = 0.0f;

			float fLifetime			  = pxCurrent->fLifeTime;
			float fLifetimeParametric = 1.0f - fLifetime;

			// If dot is dead or not started, don't render it
			if(fLifetime <= 0.0f || pxCurrent->bActive == false)
			{
				fDotAlpha = 0.0f;
			}
			else
			{
				// If we are fading in....
				if(fLifetimeParametric < dbg_fDotFadeInParametric)
				{
					fDotAlpha = (fLifetimeParametric/dbg_fDotFadeInParametric);
				}
				else // fading out...
				{
					fDotAlpha = (fLifetime/(1.0f-dbg_fDotFadeInParametric));
				}
			}

			const AVP_ClientEntity_Actor* pxDot = AVP_ClientEntity_Actor::FindEntity(pxCurrent->uActorGuid);

			if ( pxDot && ( AVP_Tactical_System::GetAllegiance(xClient, *pxDot) == AVP_ALLEGIANCE_FRIEND ) )
			{
				float fPosX = xScannerCenter.x + ( pxCurrent->xPosition.x - ( pxImageDotMarine->GetScaledWorldBounds().GetWidth()  * 0.5f ));
				float fPosY = xScannerCenter.y - ( pxCurrent->xPosition.y + ( pxImageDotMarine->GetScaledWorldBounds().GetHeight() * 0.5f ));

				Asura_Render::Overlay.TextureIndex = pxImageDotMarine->GetTexture();
				Asura_Render::Overlay.SetRectUV(pxImageDotMarine->GetUV(0).u, pxImageDotMarine->GetUV(0).v, pxImageDotMarine->GetUV(1).u, pxImageDotMarine->GetUV(1).v);
				Asura_Render::Overlay.SetRectXY(fPosX, fPosY, pxImageDotMarine->GetScaledWorldBounds().GetWidth(), pxImageDotMarine->GetScaledWorldBounds().GetHeight());
			}
			else
			{
				float fPosX = xScannerCenter.x + ( pxCurrent->xPosition.x - ( pxImageDot->GetScaledWorldBounds().GetWidth()  * 0.5f ));
				float fPosY = xScannerCenter.y - ( pxCurrent->xPosition.y + ( pxImageDot->GetScaledWorldBounds().GetHeight() * 0.5f ));

				Asura_Render::Overlay.TextureIndex = pxImageDot->GetTexture();
				Asura_Render::Overlay.SetRectUV(pxImageDot->GetUV(0).u, pxImageDot->GetUV(0).v, pxImageDot->GetUV(1).u, pxImageDot->GetUV(1).v);
				Asura_Render::Overlay.SetRectXY(fPosX, fPosY, pxImageDot->GetScaledWorldBounds().GetWidth(), pxImageDot->GetScaledWorldBounds().GetHeight());
			}

			Asura_Render::Overlay.Colour = AVP_Overlay_Render::MultiplyAlphaValue(0xFFFFFFFF, fAlpha * fDotAlpha);
			Asura_Render::Overlay.Render();

			pxCurrentElt = pxCurrentElt->Next();
		}
	}
}

void AVP_Human_MotionTracker::RenderBase()
{
	Asura_GUIMenu_Element_Image* pxImageBase0 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_BASE0));
	Asura_GUIMenu_Element_Image* pxImageBase1 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_BASE1));
	Asura_GUIMenu_Element_Image* pxImageBase2 = static_cast<Asura_GUIMenu_Element_Image*>(AVP_GUIMenu_System::GetHUDElement(AVP_GUIMenu_System::MOTION_TRACKER_BASE2));

	if ( pxImageBase0 && pxImageBase1 && pxImageBase2 )
	{
		RenderImage( *pxImageBase0 );
		RenderImage( *pxImageBase1 );
		RenderImage( *pxImageBase2 );
	}
}

void AVP_Human_MotionTracker::RenderImage(const Asura_GUIMenu_Element_Image& xImage )
{
	Asura_Render::Overlay.Colour           = xImage.GetColour().ToUIntARGB();
	Asura_Render::Overlay.TranslucencyMode = xImage.GetTranslucencyMode();
	Asura_Render::Overlay.TextureIndex     = xImage.GetTexture();
	Asura_Render::Overlay.SetRectUV(xImage.GetUV(0).u, xImage.GetUV(0).v, xImage.GetUV(1).u, xImage.GetUV(1).v);
	Asura_Render::Overlay.SetRectXY(xImage.GetScaledWorldPos().x, xImage.GetScaledWorldPos().y, xImage.GetScaledWorldBounds().GetWidth(), xImage.GetScaledWorldBounds().GetHeight());
	Asura_Render::Overlay.Render();
}

void AVP_Human_MotionTracker::RenderFuzz( float fAlpha )
{
	// TODO
}

void AVP_Human_MotionTracker::RefreshBlueprintValues(void)
{
	Asura_Value xValue;
	Asura_Hash_ID uBlueprintHash;

	uBlueprintHash = 0x3a5f1ec2;	//Asura_GetHashID("MotionTracker");
	Asura_Blueprint* pxBlueprint = Asura_Blueprint_System::GetBlueprintByHashID(uAVP_BLUEPRINT_TYPE_INVENTORY_ITEMS, uBlueprintHash);
	if(pxBlueprint)
	{
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("MaxRange(m)"), xValue))
		{
			dbg_fMaxRange = (xValue.GetFloatValue());
		}
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("DetectableSpeedThreshold"), xValue))
		{
			dbg_fSpeedThreshold = (xValue.GetFloatValue());
		}
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("DotFadeInParametric"), xValue))
		{
			dbg_fDotFadeInParametric = (xValue.GetFloatValue());
		}
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("DotFadeTimeScale"), xValue))
		{
			dbg_fDotFadeTimeScale = (xValue.GetFloatValue());
		}
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("MotionTrackerTimeScale"), xValue))
		{
			dbg_fMotionTrackerTimeScale = (xValue.GetFloatValue());
		}
		if(pxBlueprint->GetParameterValueByHashID(Asura_GetHashID("PulseParametricDuration"), xValue))
		{
			dbg_fPulseScaleLength = (xValue.GetFloatValue());
		}	
	}
	
	dbg_fSecondaryPulseScaleLength = dbg_fPulseScaleLength*1.6f;

	dbg_fStartTime				= 0.0f;
	dbg_fMiddleTime				= dbg_fStartTime + dbg_fPulseScaleLength;
	dbg_fEndTime				= dbg_fMiddleTime+((1.0f-dbg_fMiddleTime)/(3.0f/dbg_fPulseScaleLength));

	dbg_fSecondaryStartTime		= 0.0f;
	dbg_fSecondaryMiddleTime	= dbg_fSecondaryStartTime + dbg_fSecondaryPulseScaleLength;
	dbg_fSecondaryEndTime		= dbg_fSecondaryMiddleTime+((1.0f-dbg_fSecondaryMiddleTime)/(3.0f/dbg_fSecondaryPulseScaleLength));
}

void AVP_Human_MotionTracker::PlayBleepSound(float fParametric)
{
	if(m_pxBleepSound && !m_bDisrupted)
	{
		if (m_iBleepHandle != ASURA_INVALID_HANDLE)
		{
			Asura_Sound_Event_System::FinishedWithHandle(m_iBleepHandle);
		}

		// AJK; 14-04-09 E3, ignore pitch change
		//float	fPitchScale			= 1.4f-(fParametric*0.6f);

		Asura_Sound_Event_System::PlayEvent(m_pxBleepSound, &m_iBleepHandle);
		//if (m_iBleepHandle != ASURA_INVALID_HANDLE)
		//{
		//	Asura_Sound_Event_System::SetPitchScale(m_iBleepHandle, fPitchScale);
		//}
	}
}

void AVP_Human_MotionTracker::PlayPulseSound()
{
	if(m_pxPulseSound && !m_bDisrupted )
	{
		if (m_iPulseHandle != ASURA_INVALID_HANDLE)
		{
			Asura_Sound_Event_System::FinishedWithHandle(m_iPulseHandle);
		}

		Asura_Sound_Event_System::PlayEvent(m_pxPulseSound, &m_iPulseHandle);
	}
}

void AVP_Human_MotionTracker::Disrupt()
{
	m_fDisruptTimer = dbg_fDisruptTime;
	m_bDisrupted = true;
}

void AVP_Human_MotionTracker::UpdateDisruption()
{
	if ( m_fDisruptTimer > 0.0f )
	{
		const float fTimeSlice = Asura_Timers::GetGameTimeSlice();
		m_fDisruptTimer -= fTimeSlice;
		if ( m_fDisruptTimer <= 0.0f )
		{
			m_fDisruptTimer = 0.0f;
			m_bDisrupted = false;
		}
		else if ( m_fFlickerTimer > 0.0f )
		{
			m_fFlickerTimer -= fTimeSlice;
		}
		else if ( m_bDisrupted )
		{
			m_bDisrupted = false;
		}
		else
		{
			m_bDisrupted = true;
			m_fFlickerTimer = Asura_Random::GetFloat( dbg_fMinFlickerTime, dbg_fMaxFlickerTime );
		}
	}

	s_bMotionTrackerDisrupted = m_bDisrupted;
}



