///////////////////////////////////////////////////////////////////////////////
//
// File: Asura_CollisionFX_System.cpp
//
// Created by Martin G.Harris (28/09/2007)
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
// Play SFX/PFX according to collision information from the physics system.
//
///////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"
#include "SpecialFX/Asura_CollisionFX_System.h"

// Only compile this code if the symbol is defined.
#ifdef ASURA_USE_COLLISIONFX

#include "Core/Asura_BitPacker.h"
#include "Core/Asura_LOS.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Debugging/Asura_Assert.h"
#include "Debugging/Asura_Logfile.h"
#include "Network/Asura_Message.h"
#include "Network/Asura_MessageHeader.h"
#include "Network/Asura_NetMsg.h"
#include "Network/Asura_Network.h"
#include "Physics/Asura_Physics_CollisionFX_Report.h"
#include "Rendering/Asura_Primitive_Render.h"
#include "SpecialFX/Asura_CFX_Effect.h"
#include "SpecialFX/Asura_CollisionFX_Messages.h"
#include "SpecialFX/Asura_Decal_Manager.h"
#include "SpecialFX/PFX/Asura_PFX_Enums.h"

/////////////////
// Static data //
/////////////////

// Default number of elements per pool.
const u_int uDEFAULT_MEMPOOL_SIZE = 16;

// Memory pools for keeping track of the collisions.
Asura_ResizingMempool Asura_CollisionFX_System::s_xListNodeMempool( uDEFAULT_MEMPOOL_SIZE, Asura_CollisionFX_List::GetMemoryElementSize(), __FILE__, __LINE__ );
Asura_ResizingMempool Asura_CollisionFX_System::s_xEntryMempool( uDEFAULT_MEMPOOL_SIZE, sizeof( Asura_CollisionFX_Entry ), __FILE__, __LINE__ );

// List of collisions that are in progress.
Asura_CollisionFX_List Asura_CollisionFX_System::s_xCollisionList;

float Asura_CollisionFX_System::s_fNextDestroyTime = Asura_Maths::LargeFloat;
float Asura_CollisionFX_System::s_fLastClientUpdateTimestamp = 0.0f;

Asura_CollisionFX_CreateEffectFuncPtr* Asura_CollisionFX_System::s_pfnCreateEffectFuncPtr = 0;

// Parameters that can be set by the project.
Asura_CollisionFX_Param Asura_CollisionFX_System::s_xParam;

// Enable the update messages for remote client machines by default.
bool Asura_CollisionFX_System::s_bEnableCFXClientUpdateMessages = true;

//////////////////////////////////////////////////////////////////////////////
// Asura_CollisionFX_Param
//////////////////////////////////////////////////////////////////////////////

// Default values for the parameters.
Asura_CollisionFX_Param::Asura_CollisionFX_Param()
: m_fThresholdMinSpeed(0.5f)
, m_fThresholdMinDotProduct(0.2588f)// 75 degrees
, m_fSlideDelayBeforePlaying(0.5f)
, m_fSlideDelayStopWaiting(0.2f)
, m_fSlideDelayStopPlaying(0.4f)
, m_fSoundEventFadeOutTime(0.3f)
, m_bEnableSlidingCollisions(false)	// Sliding turned off by default.
{
}


//////////////////////////////////////////////////////////////////////////////
// Asura_CollisionFX_Entry 
//////////////////////////////////////////////////////////////////////////////

/////////////////////////////
// Asura_CollisionFX_Entry //
/////////////////////////////
Asura_CollisionFX_Entry::Asura_CollisionFX_Entry(Asura_Guid uGuid1, Asura_Guid uGuid2, int iType)
: m_uGuid1(uGuid1)
, m_uGuid2(uGuid2)
, m_iType(iType)
, m_fStartTime(Asura_Timers::GetLocalScaledTime())
, m_fLastCollisionTime(Asura_Timers::GetLocalScaledTime())
, m_fSlideStartTime(0.0f)
, m_fDestroyTime(0.0f)
, m_pxEffect(0)
{
	// When two entities collide in the physics system, only one collision FX report is generated;
	// so we need to impose some definite ordering. Also we always want uGuid2 to be
	// ASURA_GUID_SERVER (i.e. zero) for collisions with the environment.
	if (IsPhysicsCollision() || uGuid1 == ASURA_GUID_SERVER)
	{
		m_uGuid1 = Asura_Maths::Max(uGuid1, uGuid2);
		m_uGuid2 = Asura_Maths::Min(uGuid1, uGuid2);
	}
}

Asura_CollisionFX_Entry::Asura_CollisionFX_Entry(const Asura_CollisionFX_Entry& xOther)
{
	*this = xOther;
}

// Copy operator
Asura_CollisionFX_Entry& Asura_CollisionFX_Entry::operator=(const Asura_CollisionFX_Entry& xRight)
{
	m_uGuid1 = xRight.m_uGuid1;
	m_uGuid2 = xRight.m_uGuid2;
	m_iType  = xRight.m_iType;

	m_fStartTime		 = xRight.m_fStartTime;
	m_fLastCollisionTime = xRight.m_fLastCollisionTime;
	m_fSlideStartTime	 = xRight.m_fSlideStartTime;
	m_fDestroyTime		 = xRight.m_fDestroyTime;

	m_pxEffect = 0;

	// Copying is only done for entries that don't have an effect pointer set yet.
	Asura_Assert(xRight.m_pxEffect == 0, "Tried to copy collision FX entry with non-null effect pointer");

	return *this;
}

///////////////////////
// UpdateDestroyTime //
///////////////////////
void Asura_CollisionFX_Entry::UpdateDestroyTime(float fDelay)
{
	m_fLastCollisionTime = Asura_Timers::GetLocalScaledTime();
	
	m_fDestroyTime = m_fLastCollisionTime + fDelay;

	// Update the cached time.
	Asura_CollisionFX_System::UpdateDestroyedTime(m_fDestroyTime);
}

///////////////
// SetEffect //
///////////////
void Asura_CollisionFX_Entry::SetEffect(Asura_CFX_Effect* pxEffect)
{
	m_pxEffect = pxEffect;
	if (m_pxEffect)
	{
		m_pxEffect->SetEntry(this);
	}
}


///////////////////////////////////////////////////////////////////////////////
// Asura_CollisionFX_List
///////////////////////////////////////////////////////////////////////////////

// Size of hash table in collision FX list. This value may need tuning.
const u_int uASURA_CFX_HASH_TABLE_SIZE = 16;

Asura_CollisionFX_List::Asura_CollisionFX_List()
: Asura_HashTable<Asura_CollisionFX_Entry>(uASURA_CFX_HASH_TABLE_SIZE)
{
	// List nodes should be allocated from the mempool.
	SetMemorySupplyFunction(Asura_CollisionFX_System::AllocateFromListNodeMempool);
	SetMemoryReturnFunction(Asura_CollisionFX_System::DeallocateToListNodeMempool);
}

///////////////////////////////////////////////////////////////////////////////
// Asura_CollisionFX_System
///////////////////////////////////////////////////////////////////////////////

////////////////
// Initialise //
////////////////
void Asura_CollisionFX_System::Initialise()
{
	s_fNextDestroyTime = Asura_Maths::LargeFloat;
	s_fLastClientUpdateTimestamp = 0.0f;

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	// If the compressed position settings haven't been set up yet, then just set them to some default value.
	const Asura_NetMsg_CompressedPositionSettings& xSettings = Asura_NetMsg_System::GetPositionCompression();
	if (xSettings.m_uBitsX == 0)
	{
		Asura_NetMsg_System::SetPositionCompression(Asura_NetMsg_CompressedPositionSettings_Normal);
	}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

#ifdef ASURA_USE_PRIMITIVE_RENDER
	const Asura_Hash_ID uCatHash = Asura_Primitive_Render::RegisterCategory("CollisionFX");
	Asura_Primitive_Render::EnableCategory(uCatHash, false);
#endif //ASURA_USE_PRIMITIVE_RENDER
}

///////////
// Reset //
///////////
void Asura_CollisionFX_System::Reset()
{
	// Stop any effects that are still playing.
	for (Asura_CollisionFX_List::Iterator xIt(s_xCollisionList.GetIterator()); !xIt.Done(); xIt.Next())
	{
		Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
	
		// Stop the PFX or SFX effect immediately.
		StopEffect(pxEntry, true);

		// Delete the CFX effect.
		DeleteEffect(pxEntry->m_pxEffect);
	}

	// Clear the list.
	s_xCollisionList.Clear();

	// Dump all of the memory.
	s_xListNodeMempool.ForceReset();
	s_xEntryMempool.ForceReset();

	s_fNextDestroyTime = Asura_Maths::LargeFloat;
	s_fLastClientUpdateTimestamp = 0.0f;
}

//////////////
// Shutdown //
//////////////
void Asura_CollisionFX_System::Shutdown()
{
	Reset();
}

////////////////////
// StopAllEffects //
////////////////////
void Asura_CollisionFX_System::StopAllEffects(bool bImmediate)
{
	for (Asura_CollisionFX_List::Iterator xIt(s_xCollisionList.GetIterator()); !xIt.Done(); xIt.Next())
	{
		Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
		StopEffect(pxEntry, bImmediate);
	}
}

////////////////
// StopEffect //
////////////////
void Asura_CollisionFX_System::StopEffect(Asura_CollisionFX_Entry* pxEntry, bool bImmediate)
{
	if (!pxEntry) return;

	Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
//	Asura_Assert(pxEffect, "Null collision entry effect");
	if (pxEffect)
	{
		pxEffect->Stop(bImmediate);
	}
}

//////////////
// AddEntry //
//////////////
// NOTE: you should check whether an entry already exists before adding a new one.
Asura_CollisionFX_Entry* Asura_CollisionFX_System::AddEntry(const Asura_CollisionFX_Entry& xEntryToAdd)
{
	Asura_CollisionFX_Entry* pxEntry = static_cast<Asura_CollisionFX_Entry*>(s_xEntryMempool.Allocate());
	Asura_Assert(pxEntry, "Failed to allocate collision entry from mempool.");
	if (pxEntry)
	{
		// Copy entry and add to the list.
		*pxEntry = xEntryToAdd;
		s_xCollisionList.AddObject(pxEntry);
	}

	return pxEntry;
}


////////////
// Update //
////////////
void Asura_CollisionFX_System::Update()
{
#ifdef ASURA_USE_PRIMITIVE_RENDER
	if (Asura_Primitive_Render::IsActive())
	{
		RenderDebugInfo();
	}
#endif //ASURA_USE_PRIMITIVE_RENDER

	if (!s_pfnCreateEffectFuncPtr)
	{
		// No callback registered. Not much point updating if the system isn't being used.
		Asura_Assert(0, "Asura_CollisionFX_System: no CreateEffect callback has been registered");
		return;
	}

	if (Asura_Timers::GetGameTimeSlice() == 0.0f)
	{
		// The game is paused. We shouldn't be getting any physics reports and no entries
		// are going to time out as the game time isn't advancing. Nothing to do.
		return;
	}

	// For now assume that only the server processes the collision reports.
	// TODO fix this if we want client-side effects such as gibs and ragdolls to work so
	// that the sounds sync with the local client positions rather than the server positions.
	if (Asura_Network::IsServer())
	{
		UpdateProcessCollisionReports();
	}

	UpdateDestroyOldEntries();
}

///////////////////////////////////
// UpdateProcessCollisionReports //
///////////////////////////////////
void Asura_CollisionFX_System::UpdateProcessCollisionReports()
{
	// Loop over the physics collisions. If collision already exists then update, otherwise add a new entry.
	// NOTE: this assumes that only a single report is generated for each object-environmental collision
	// and for each object-object collision pair.

	Asura_Physics_CollisionFX_Report_Iterator xIt;

	for (xIt.Start(); xIt.NotFinished(); xIt.Next())
	{
		const Asura_Physics_CollisionFX_Report* pxCollInfo = xIt.GetCurrentReport();
		if (pxCollInfo)
		{
/*
			Asura_OutputToDebugger(("%u: Report  (%u %u)  %u  p=(%f %f %f)  n=(%f %f %f) time=%f\n",
				Asura_Timers::GetFrameNumber(),
				pxCollInfo->GetObject1EntityGuid(), pxCollInfo->GetObject2EntityGuid(),
				pxCollInfo->GetEnvironmentFaceID(),
				pxCollInfo->GetPosition().GetX(), pxCollInfo->GetPosition().GetY(), pxCollInfo->GetPosition().GetZ(),
				pxCollInfo->GetNormal().GetX(), pxCollInfo->GetNormal().GetY(), pxCollInfo->GetNormal().GetZ(),
				Asura_Timers::GetLocalScaledTime()));
*/

			// Implement a minimum velocity to exclude collisions from one object resting on another.
			const float fSpeed = pxCollInfo->GetRelVelocity().Magnitude();
			if (fSpeed < s_xParam.m_fThresholdMinSpeed)
			{
				// This collision has a very low velocity, so exclude it.
				continue;
			}

			// Test for sliding impacts.
			bool bSliding = false;

			if (fSpeed>0.0f)
			{
				const float fDot = (pxCollInfo->GetNormal() * pxCollInfo->GetRelVelocity())/fSpeed;

				if (Asura_Maths::Abs(fDot) < s_xParam.m_fThresholdMinDotProduct)
				{
					// Typically 75 degrees or more between relative velocity and normal:
					// a sliding or glancing impact with the environment.
					if (s_xParam.m_bEnableSlidingCollisions)
					{
						bSliding = true;
					}
					else
					{
						// Ignore it.
						continue;
					}
				}
			}

			// There are two types of report: one for direct impacts and another for glancing/sliding collisions. 
			const int iType = (bSliding ? iASURA_CFX_TYPE_PHYSICS_SLIDE : iASURA_CFX_TYPE_PHYSICS_IMPACT);
			RegisterCollision(*pxCollInfo, iType);
		}
	}
}

/////////////////////////////
// UpdateDestroyOldEntries //
/////////////////////////////
void Asura_CollisionFX_System::UpdateDestroyOldEntries()
{
	// Check the on-going collisions for those that have timed out (these are deleted),
	// also check for sliding collisions for which the playing effect has timed out
	// (in this case the effect is stopped).
	const float fTime = Asura_Timers::GetLocalScaledTime();

	if (fTime >= s_fNextDestroyTime)
	{
		s_fNextDestroyTime = Asura_Maths::LargeFloat;

		for (Asura_CollisionFX_List::Iterator xIt(s_xCollisionList.GetIterator()); !xIt.Done(); )
		{
			Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
			if (pxEntry)
			{
				if (pxEntry->m_fDestroyTime <= fTime)
				{
					// Stop PFX/SFX effect, delete CFX, deallocate entry.
					DeleteEntry(pxEntry, false);

					// Delete the current list node and advance to the next one.
					xIt.DeleteCurrent();
					continue;
				}
				else 
				{
					UpdateDestroyedTime(pxEntry->m_fDestroyTime);

					// Check for a sliding effect that has timed out.
					if ( pxEntry->m_iType == iASURA_CFX_TYPE_PHYSICS_SLIDE ||
						(pxEntry->GetEffect() && pxEntry->GetEffect()->GetSlidingState()))
					{
						UpdateCollision_Sliding(pxEntry);
					}
				}
			}
			
			xIt.Next();
		}
	}

	// MGH 10/10/2007: We have finished with the collision FX reports. Clear them.
	// MGH 12/10/2007: This breaks Asura_Physics_CollisionFX_Report_System::DebugRender so I'll let the
	// physics system clear the reports.
	//Asura_Physics_CollisionFX_Report_System::ClearAllReports();
}

//////////////////
// CreateEffect //
//////////////////
Asura_CFX_Effect* Asura_CollisionFX_System::CreateEffect(const Asura_Physics_CollisionFX_Report& xCollInfo, int iType)
{
	if (s_pfnCreateEffectFuncPtr)
	{
		return s_pfnCreateEffectFuncPtr(xCollInfo, iType);
	}
	
	Asura_Assert(0, "No Collision FX callback has been registered");
	return 0;
}

//////////////////
// DeleteEffect //
//////////////////
// Delete effect and set the pointer to null.
void Asura_CollisionFX_System::DeleteEffect(Asura_CFX_Effect*& pxEffect)
{
	if (pxEffect)
	{
		delete pxEffect;
		pxEffect = 0;
	}
}

/////////////////
// DeleteEntry //
/////////////////
// Stop effect, delete CFX, deallocate entry. Doesn't delete the list node though.
void Asura_CollisionFX_System::DeleteEntry(Asura_CollisionFX_Entry* pxEntry, bool bImmediate)
{
	if (pxEntry)
	{
		// Stop the PFX or SFX effect.
		StopEffect(pxEntry, bImmediate);

		// Delete the CFX effect.
		DeleteEffect(pxEntry->m_pxEffect);

		// Delete the entry.
		s_xEntryMempool.Deallocate(pxEntry);
	}
}


///////////////////////
// RegisterCollision //
///////////////////////
// Register collision from physics or other source (e.g. using data from Line-Of-Sight tests).
void Asura_CollisionFX_System::RegisterCollision(const Asura_Physics_CollisionFX_Report& xCollInfo,
												 int iTypeID,
												 bool bForceSlide /*= false*/)
{
	// NOTE: assumes that m_uEntity2Guid == ASURA_GUID_SERVER if it is an environmental polygon.
	const Asura_CollisionFX_Entry xTestEntry(xCollInfo.GetObject1EntityGuid(), xCollInfo.GetObject2EntityGuid(), iTypeID);

	// MGH 5/10/2007: If the guid hasn't been set then we can't look up the surface type.
	// We may need to comment out this assert if it becomes too annoying.
	Asura_Assert(xCollInfo.GetObject1EntityGuid() != ASURA_GUID_UNREGISTERED && 
				 xCollInfo.GetObject2EntityGuid() != ASURA_GUID_UNREGISTERED, "Unregistered guid in collision FX report");

	// Search for the collision in the table.
	Asura_CollisionFX_Entry* pxEntry = s_xCollisionList.FindObject( xTestEntry );
	
	if (pxEntry && pxEntry->m_fLastCollisionTime == Asura_Timers::GetLocalScaledTime())
	{
		// Duplicate collision report (i.e. with same entities and same timestamp). Ignore it.
		// Note: the physics system is still letting through some duplicate object-object collisions.
		//Asura_Assert(0, "Duplicate collision FX report. Ignoring it.");
		return;
	}

	//
	// Deal with the sliding case.
	//
	if (iTypeID == iASURA_CFX_TYPE_PHYSICS_SLIDE || bForceSlide)
	{
		if (s_xParam.m_bEnableSlidingCollisions)
		{
			RegisterCollision_Sliding(xTestEntry, pxEntry, xCollInfo, iTypeID);
		}
		return;
	}

	//
	// Normal non-sliding case.
	//
	if (pxEntry)
	{
		// Update existing entry.
		Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
		if (pxEffect)
		{
			pxEntry->UpdateDestroyTime(pxEffect->GetDestroyDelay());
			pxEffect->Update(xCollInfo);
		}
	}
	else
	{
		// Get effect for new entry.
		Asura_CFX_Effect* pxEffect = CreateEffect(xCollInfo, iTypeID);
		if (pxEffect)
		{
			// Add new entry.
			Asura_CollisionFX_Entry* pxNewEntry = AddEntry(xTestEntry);
			if (pxNewEntry)
			{
				pxNewEntry->SetEffect(pxEffect);

				pxNewEntry->UpdateDestroyTime(pxEffect->GetDestroyDelay());
				pxEffect->Start(xCollInfo);
			}
			else
			{
				// Delete the CFX effect.
				DeleteEffect(pxEffect);
			}
		}
	}
}

///////////////////////////////
// RegisterCollision_Sliding //
///////////////////////////////
// Register a sliding collision.
// TODO handle looping effects better (esp. sliding on to new surface type).

void Asura_CollisionFX_System::RegisterCollision_Sliding( const Asura_CollisionFX_Entry& xTestEntry,
														  Asura_CollisionFX_Entry* pxEntry,
														  const Asura_Physics_CollisionFX_Report& xCollInfo,
														  int iTypeID )
{
	// For sliding case we don't start playing the effect until we have
	// had a number of glancing collisions over a certain period of time.
	if (!pxEntry)
	{
		// Add a new entry, but with no effect for now.
		Asura_CollisionFX_Entry* pxNewEntry = AddEntry(xTestEntry);
		if (pxNewEntry)
		{
			pxNewEntry->m_pxEffect = 0;

			pxNewEntry->UpdateDestroyTime(fASURA_CFX_DESTROY_DELAY);
			pxNewEntry->m_fSlideStartTime = Asura_Timers::GetLocalScaledTime();
		}
		return;
	}

	// We already have an entry. Update it.
	const float fTime = Asura_Timers::GetLocalScaledTime();
	const float fLastCollisionTime = pxEntry->m_fLastCollisionTime;
	const float fSlideStartTime = pxEntry->m_fSlideStartTime;

	Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
	const bool bPlaying = (pxEffect ? pxEffect->GetStartedState() : false);

	const float fDestroyDelay = (pxEffect ? pxEffect->GetDestroyDelay() : fASURA_CFX_DESTROY_DELAY);
	pxEntry->UpdateDestroyTime(fDestroyDelay);

	if (!bPlaying)
	{
		// Still waiting to start the effect.
		if (fTime > fLastCollisionTime + s_xParam.m_fSlideDelayStopWaiting)
		{
			// No collisions for a while. Restart the delay before playing the slide effect.
			pxEntry->m_fSlideStartTime = fTime;
		}
		else if (fSlideStartTime > fLastCollisionTime)
		{
			// Invalid slide start time. Effect was stopped by a timeout. Restart the delay.
			pxEntry->m_fSlideStartTime = fTime;
		}
		else if (fTime >= fSlideStartTime + s_xParam.m_fSlideDelayBeforePlaying)
		{
			// We have waited long enough. Play the slide effect.
			if (!pxEffect)
			{
				pxEffect = CreateEffect(xCollInfo, iTypeID);
				if (pxEffect)
				{
					// Start the effect.
					pxEntry->SetEffect(pxEffect);

					pxEffect->SetSlidingState();
					pxEntry->UpdateDestroyTime(pxEffect->GetDestroyDelay());
					pxEffect->Start(xCollInfo);
				}
			}
			else
			{
				// Restart the effect.
				pxEffect->Restart(xCollInfo);
			}
		}
	}
	else
	{
		// Currently playing the effect.
		if (fTime >= fLastCollisionTime + s_xParam.m_fSlideDelayStopPlaying)
		{
			// No collisions for a while. Stop the slide effect.
			if (pxEffect)
			{
				pxEffect->Stop(false);
			}

			// Restart the delay.
			pxEntry->m_fSlideStartTime = fTime;
		}
		else
		{
			// Continue the slide effect.
			if (pxEffect)
			{
				pxEffect->Update(xCollInfo);

				// When a sliding effect is followed by no collisions for a period of time (e.g. 0.5 seconds),
				// we must stop the effect elsewhere; this is done in the destroy loop in Update().
				// We need to modify the cached destroy time to make sure that it runs through the loop.
				UpdateDestroyedTime(fTime + s_xParam.m_fSlideDelayStopPlaying);
			}
		}
	}
}

/////////////////////////////
// UpdateCollision_Sliding //
/////////////////////////////
// Update a sliding collision, to stop the effect if it has timed out.
void Asura_CollisionFX_System::UpdateCollision_Sliding(Asura_CollisionFX_Entry* pxEntry)
{
	if (!pxEntry) return;

	//Asura_Assert(pxEntry->m_iType == iASURA_CFX_TYPE_PHYSICS_SLIDE, "Wrong type");

	Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
	const bool bPlaying = (pxEffect && pxEffect->GetStartedState());
	if (!bPlaying)
	{
		// Not playing an effect, no need to check for a timeout.
		return;
	}

	const float fTime = Asura_Timers::GetLocalScaledTime();
	const float fLastCollisionTime = pxEntry->m_fLastCollisionTime;

	if (fTime >= fLastCollisionTime + s_xParam.m_fSlideDelayStopPlaying)
	{
		// No collisions for a while. Stop the slide effect.
		if (pxEffect)
		{
			pxEffect->Stop(false);
		}

		// Mark slide time as invalid (it can never be greater than m_fLastCollisionTime normally).
		pxEntry->m_fSlideStartTime = fLastCollisionTime + 0.1f;
	}
	else
	{
		// Make sure that we check again at the correct time.
		UpdateDestroyedTime(fLastCollisionTime + s_xParam.m_fSlideDelayStopPlaying);
	}
}

///////////////////////
// RegisterCollision //
///////////////////////
// Helper function to register a collision based on LOS data.
void Asura_CollisionFX_System::RegisterCollision( const Asura_LOS_InputData& xLOSInput,
												  const Asura_LOS_OutputData& xLOSOutput,
												  Asura_Guid uEntity1Guid,
												  const Asura_Vector_3& xRelVelocity,
												  int iTypeID,
												  bool bForceSlide /*= false*/)
{
	Asura_Assert(iTypeID != iASURA_CFX_TYPE_PHYSICS_IMPACT && iTypeID != iASURA_CFX_TYPE_PHYSICS_SLIDE, "RegisterCollision: type set wrongly as physics.");

	Asura_Physics_CollisionFX_Report xInfo;

	if (xLOSOutput.m_bHitEnvironment)
	{
		// Hit environment.
		xInfo.SetEnvironmentCollision(uEntity1Guid, xLOSOutput.m_uEnvironmentFaceIndex);
	}
	else if (xLOSOutput.m_uHitEntity != 0)
	{
		// Hit entity.
		xInfo.SetObjectObjectCollision(uEntity1Guid, xLOSOutput.m_uHitEntity);
	}
	else
	{
		// No LOS hit. Nothing to do.
		return;
	}

	const Asura_Vector_3 xPosition = (xLOSInput.m_xOrigin + xLOSInput.m_xDirection * xLOSOutput.m_fDistanceToIntersection);
	xInfo.SetPosition(xPosition);
	xInfo.SetRelVelocity(xRelVelocity);
	xInfo.SetNormal(xLOSOutput.m_xNormal);


	RegisterCollision(xInfo, iTypeID, bForceSlide);
}



/////////////////////
// RemoveCollision //
/////////////////////
// Remove collision entry manually. Return true if an entry was removed.
bool Asura_CollisionFX_System::RemoveCollision(Asura_Guid uGuid1, Asura_Guid uGuid2, int iTypeID, bool bImmediate)
{
	const Asura_CollisionFX_Entry xTestEntry(uGuid1, uGuid2, iTypeID);

	Asura_CollisionFX_Entry* pxEntry = s_xCollisionList.RemoveObject(xTestEntry);
	if (pxEntry)
	{
		// Stop PFX/SFX effect, delete CFX, deallocate entry.
		DeleteEntry(pxEntry, bImmediate);
		return true;
	}

	return false;
}

/////////////////////////
// RemoveAllCollisions //
/////////////////////////
// Remove all collisions associated with this entity.
void Asura_CollisionFX_System::RemoveAllCollisions(Asura_Guid uGuid, bool bImmediate)
{
	for (Asura_CollisionFX_List::Iterator xIt(s_xCollisionList.GetIterator()); !xIt.Done(); )
	{
		Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
		if (pxEntry)	
		{
			if (pxEntry->m_uGuid1 == uGuid || pxEntry->m_uGuid2 == uGuid)
			{
				// Stop PFX/SFX effect, delete CFX, deallocate entry.
				DeleteEntry(pxEntry, bImmediate);

				// Delete the current list node and advance to the next one.
				xIt.DeleteCurrent();
				continue;
			}
		}

		xIt.Next();
	}
}

////////////////////
// DebugDumpTable //
////////////////////
#ifdef _DEBUG
// Dump a copy of the table to the debugger.
void Asura_CollisionFX_System::DebugDumpTable()
{
	Asura_OutputToDebugger(("Collision FX Table:  Number of entries: %u   (time = %.2f)\n", s_xCollisionList.GetNumEntries(), Asura_Timers::GetLocalScaledTime()));

	if (s_xCollisionList.GetNumEntries()>0)
	{
		Asura_OutputToDebugger(("Num    Guid 1   Guid 2   Type      Start       Last      Slide    Destroy\n"));
	}

	int u=0;
	for (Asura_HashTable_ConstIt<Asura_CollisionFX_Entry> xIt(&s_xCollisionList); !xIt.Done(); xIt.Next() )
	{
		const Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
		if (pxEntry)
		{
			Asura_OutputToDebugger(("%3u: %8u %8u %6d %10.2f %10.2f %10.2f %10.2f\n", u, pxEntry->m_uGuid1, pxEntry->m_uGuid2, pxEntry->m_iType, pxEntry->m_fStartTime, pxEntry->m_fLastCollisionTime, pxEntry->m_fSlideStartTime, pxEntry->m_fDestroyTime));
			++u;
		}
	}
	Asura_OutputToDebugger(("\n"));
}
#endif //_DEBUG

/////////////////////
// RenderDebugInfo //
/////////////////////
void Asura_CollisionFX_System::RenderDebugInfo()
{
#ifdef ASURA_USE_PRIMITIVE_RENDER
	static const Asura_Hash_ID s_uCatHash = Asura_GetHashID("CollisionFX");
	Asura_Primitive_Render::SetRenderParams(0.0f, s_uCatHash);

	Asura_Primitive_Render::DrawConsoleText("Collision FX Table:  Number of entries: %u   (time = %.2f)", s_xCollisionList.GetNumEntries(), Asura_Timers::GetLocalScaledTime());

	if (s_xCollisionList.GetNumEntries()>0)
	{
		Asura_Primitive_Render::DrawConsoleText("");
		Asura_Primitive_Render::DrawConsoleText("Num    Guid 1   Guid 2  Type      Start       Last      Slide    Destroy Started        SE       PFX");
	}

	int u=0;
	for (Asura_HashTable_ConstIt<Asura_CollisionFX_Entry> xIt(&s_xCollisionList); !xIt.Done(); xIt.Next() )
	{
		const Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
		if (pxEntry)
		{
			Asura_CFX_Effect* const pxEffect = pxEntry->m_pxEffect;
			const bool bStarted = (pxEffect ? pxEffect->GetStartedState() : false);
			const Asura_CFX_PFX_Handle* const pxPFXHandle = (pxEffect ? pxEffect->GetPFXHandle() : 0);
			const Asura_CFX_SE_Handle*  const pxSEHandle  = (pxEffect ? pxEffect->GetSEHandle()  : 0);

			const Asura_Hash_ID uPFXHash = (pxPFXHandle ? pxPFXHandle->m_uPFXHash : 0);
			const Asura_Hash_ID uSEHash  = (pxSEHandle  ? pxSEHandle->m_uSEHash   : 0);

			Asura_Primitive_Render::DrawConsoleText("%3u: %8u %8u %5d %10.2f %10.2f %10.2f %10.2f %7u  %08x  %08x", u, pxEntry->m_uGuid1, pxEntry->m_uGuid2, pxEntry->m_iType, pxEntry->m_fStartTime, pxEntry->m_fLastCollisionTime, pxEntry->m_fSlideStartTime, pxEntry->m_fDestroyTime, (bStarted?1:0), uSEHash, uPFXHash);
			++u;
		}
	}
#endif //ASURA_USE_PRIMITIVE_RENDER
}

////////////////////////////////
// ShouldSendMessageToClients //
////////////////////////////////
bool Asura_CollisionFX_System::ShouldSendMessageToClients()
{
	return s_bEnableCFXClientUpdateMessages && Asura_Network::IsExternalNetworkActive() && Asura_Network::IsServer();
}

/////////////////////////////
// SendClientUpdateMessage //
/////////////////////////////
void Asura_CollisionFX_System::SendClientUpdateMessage()
{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	if (!ShouldSendMessageToClients()) return;

	// Send a message to update the CFX system on remote machines.
	const int iNumEntries = s_xCollisionList.GetNumEntries();
	if (iNumEntries <= 0)
	{
		return;
	}

	// Collect the data that needs to be sent.
	Asura_Message_CFX_ClientUpdate xTemp;

	for (Asura_CollisionFX_List::Iterator xIt = s_xCollisionList.GetIterator();
		 !xIt.Done() && xTemp.m_uNumEntries < uASURA_CFX_MAX_NUM_ENTRIES_FOR_CLIENT;
		 xIt.Next())
	{
		const Asura_CollisionFX_Entry* const pxEntry = xIt.GetCurrent();
		if (!pxEntry) continue;
		
		Asura_CFX_Effect* const pxEffect = pxEntry->m_pxEffect;
		if (!pxEffect) continue;

		if (pxEffect->GetStartedState() == false)
		{
			continue; // Effect may have been stopped.
		}

		if (pxEffect->GetLocalEffect())
		{
			continue; // Local effect, don't send to remote machines.
		}

		Asura_Message_CFX_ClientUpdate_Entry& xData = xTemp.m_axEntry[xTemp.m_uNumEntries];

		xData.m_uGuid1		= pxEntry->GetGuid1();
		xData.m_uGuid2		= pxEntry->GetGuid2();
		xData.m_iType		= pxEntry->GetType();
		xData.m_uStateFlags = pxEffect->GetStateFlags();
		xData.m_xPosition	= pxEffect->GetLastPosition();

		const Asura_CFX_PFX_Handle* const pxPFXHandle = pxEffect->GetPFXHandle();
		const Asura_CFX_SE_Handle*  const pxSEHandle  = pxEffect->GetSEHandle();
		
		if (pxSEHandle)
		{
			xData.m_uSEHash = pxSEHandle->m_uSEHash;
			xData.m_fVolumeScale = pxSEHandle->GetVolumeScale();
		}

		if (pxPFXHandle)
		{
			xData.m_uPFXHash  = pxPFXHandle->m_uPFXHash;
			xData.m_uPFXFlags = pxPFXHandle->m_uPFXFlags;
			xData.m_xNormal	  = pxPFXHandle->GetLastNormal();
		}

		pxEffect->ClearCreatedUpdatedFlags();
		
		++xTemp.m_uNumEntries;
	}
	
	u_int uDataSize = 0;
	const void* pCompressedData = 0;

	const u_int uBufferSize = xTemp.m_uNumEntries*sizeof(Asura_Message_CFX_ClientUpdate_Entry) + sizeof(u_int); // Uncompressed size
	Asura_BitPacker xBitPacker(uBufferSize);

	if (xTemp.m_uNumEntries > 0)
	{
		// Compress using bitpacker
		xTemp.Pack(xBitPacker);
		pCompressedData = xBitPacker.GetResult(uDataSize);
	}

	// Note that if there are no relevant entries we still send a message with no data, in order to prod
	// the client into stopping the effects and cleaning up the CFX table.
	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_COLLISIONFX_CLIENTUPDATE, uDataSize);
	if (pxMessage)
	{
		pxMessage->SetFromGuid(ASURA_GUID_SERVER);
		pxMessage->SetToGuid(ASURA_GUID_ALL_OTHER_CLIENTMACHINES);

		if (uDataSize > 0)
		{
			void* pData = pxMessage->GetData();
			if (pData)
			{
				Asura_Mem::Copy(pData, pCompressedData, uDataSize);
			}
		}

		Asura_Network::Send(pxMessage);
	}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM
}

///////////////////////////////
// HandleClientUpdateMessage //
///////////////////////////////
void Asura_CollisionFX_System::HandleClientUpdateMessage(const Asura_Message* pxMessage)
{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	if (Asura_Network::IsServer())
	{
		Asura_Assert(0, "Server shouldn't be receiving CFX update messages");
		return;
	}

	if (!pxMessage) return;

	if (pxMessage->GetMsgID() != ASURA_MSG_COLLISIONFX_CLIENTUPDATE)
	{
		Asura_Assert(0, "Wrong message ID");
		return;
	}

	const float fSentTime = pxMessage->GetSentTime();
	const float fDiffServerTime = fSentTime - Asura_Timers::GetServerAbsoluteTime();
	if (fDiffServerTime < -1.0f)
	{
		// Very old message (more than 1 second), just ignore it.
		return;
	}

	const bool bOldMessage = (fSentTime < s_fLastClientUpdateTimestamp);
	if (!bOldMessage)
	{
		s_fLastClientUpdateTimestamp = fSentTime;
	}

	if (pxMessage->GetData())
	{
		// Unpack the message data.
		Asura_Message_CFX_ClientUpdate xTemp;
		Asura_BitUnpacker xUnpacker(pxMessage->GetData(), pxMessage->GetDataSize());
		xTemp.Unpack(xUnpacker);

		// Sanity check.
		Asura_Assert(xUnpacker.GetNumBitsRemaining() < 8, "Didn't read all of the bitpacked message data");
		Asura_Assert(xUnpacker.Unpack(xUnpacker.GetNumBitsRemaining()) == 0, "Didn't read all of the bitpacked message data");

		// Process the message.
		for (u_int u=0; u<xTemp.m_uNumEntries; ++u)
		{
			const Asura_Message_CFX_ClientUpdate_Entry& xDataEntry = xTemp.m_axEntry[u];
			HandleClientUpdateMessageData(xDataEntry, bOldMessage);
		}
	}


	if (!bOldMessage)
	{
		// Any updated/created entries should have had their m_fLastCollisionTime variable set.
		// Search through the table for any other remote-client effects and stop them.
		for (Asura_CollisionFX_List::Iterator xIt(s_xCollisionList.GetIterator()); !xIt.Done(); xIt.Next())
		{
			Asura_CollisionFX_Entry* pxEntry = xIt.GetCurrent();
			if (pxEntry->GetLastCollisionTime() != Asura_Timers::GetLocalScaledTime())
			{
				// Not included in the message packet, so it must have been stopped on server.
				Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
				if (pxEffect)
				{
					// Stop the effect on the remote-client machine.
					pxEffect->StopClient();
				}
			}
		}
	}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM
}

///////////////////////////////////
// HandleClientUpdateMessageData //
///////////////////////////////////
void Asura_CollisionFX_System::HandleClientUpdateMessageData(const Asura_Message_CFX_ClientUpdate_Entry& xData, bool bOldMessage)
{
	const Asura_CollisionFX_Entry xTestEntry(xData.m_uGuid1, xData.m_uGuid2, xData.m_iType);
	Asura_CollisionFX_Entry* pxEntry = s_xCollisionList.FindObject( xTestEntry );
	if (pxEntry)
	{
		// Entry already exists. Update it, but ignore old out-of-order messages.
		if (!bOldMessage)
		{
			Asura_CFX_Effect* pxEffect = pxEntry->m_pxEffect;
			if (pxEffect)
			{
				if (xData.GetCreatedSE() || xData.GetCreatedPFX())
				{
					// Restart the effect, updating the data first.
					Asura_CFX_SE_Handle* pxSEHandle = pxEffect->GetSEHandle();
					if (xData.GetCreatedSE() && pxSEHandle)
					{
						pxSEHandle->m_uSEHash = xData.m_uSEHash;
						pxSEHandle->SetVolumeScale(*pxEffect, xData.m_fVolumeScale, false);
					}

					Asura_CFX_PFX_Handle* pxPFXHandle = pxEffect->GetPFXHandle();
					if (xData.GetCreatedPFX() && pxPFXHandle)
					{
						pxPFXHandle->m_uPFXHash  = xData.m_uPFXHash;
						pxPFXHandle->m_uPFXFlags = xData.m_uPFXFlags;
					}

					pxEffect->StopClient();
					pxEffect->StartClient(xData.m_xPosition, xData.m_xNormal);
				}
				else
				{
					// Update any data that has changed.
					const Asura_Vector_3* pxPosition = (xData.GetUpdatedPosition() ? &xData.m_xPosition : 0);
					const Asura_Vector_3* pxNormal = (xData.GetUpdatedNormal() ? &xData.m_xNormal : 0);
					const float* pfVolumeScale = (xData.GetUpdatedVolume() ? &xData.m_fVolumeScale : 0);

					pxEffect->UpdateClient(pxPosition, pxNormal, pfVolumeScale);
				}

				pxEntry->UpdateDestroyTime(pxEffect->GetDestroyDelay());
			}				
			else
			{
				pxEntry->UpdateDestroyTime(1.0f);
			}
		}
		else
		{
			pxEntry->UpdateDestroyTime(1.0f);
		}
	}
	else if (xData.GetCreatedSE() || xData.GetCreatedPFX())
	{
		// Create the entry if it has been flagged for creation (rather than just being an update message).
		pxEntry = AddEntry(xTestEntry);
		if (pxEntry)
		{
			Asura_CFX_Effect_PFX_SE_RemoteClient* pxClientEffect = CreateRemoteClientEffect(xData);
			if (pxClientEffect)
			{
				pxClientEffect->SetPFXFlag(xData.m_uPFXFlags);
				pxClientEffect->StartClient(xData.m_xPosition, xData.m_xNormal);

				pxEntry->SetEffect(pxClientEffect);			
				pxEntry->UpdateDestroyTime(pxClientEffect->GetDestroyDelay());
			}
			else
			{
				pxEntry->UpdateDestroyTime(1.0f);
			}
		}
	}		
}

//////////////////////////////
// CreateRemoteClientEffect //
//////////////////////////////
// Create an effect for a remote client machine from the message data.
Asura_CFX_Effect_PFX_SE_RemoteClient* Asura_CollisionFX_System::CreateRemoteClientEffect(const Asura_Message_CFX_ClientUpdate_Entry& xData)
{
	// TODO allow projects to override this default remote-client effect.
	return new Asura_CFX_Effect_PFX_SE_RemoteClient(xData.m_uPFXHash, xData.m_uSEHash, ASURA_CFX_FLAG_UPDATE_POSITION | ASURA_CFX_FLAG_UPDATE_DIRECTION, xData.m_fVolumeScale);
}

//////////////////////////////////
// SendClientCreateDecalMessage //
//////////////////////////////////
// Send a message to the remote client machines to create a decal.
void Asura_CollisionFX_System::SendClientCreateDecalMessage(const Asura_CFX_Decal_Handle& xHandle,
															const Asura_Vector_3& xPosition,
															const Asura_Vector_3& xNormal,
															const Asura_Vector_3* pxTopDirection)
{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	if (!ShouldSendMessageToClients()) return;

	// Fill out the message data
	Asura_Message_CFX_ClientCreateDecal xTemp;

	xTemp.m_uDecalSetHash	= xHandle.m_uDecalSetHash;
	xTemp.m_iDecalIndex		= xHandle.m_iDecalIndex;
	xTemp.m_fScale			= xHandle.m_fScale;
	xTemp.m_fOrientation	= xHandle.m_fOrientation;
	xTemp.m_xPosition		= xPosition;
	xTemp.m_xNormal			= xNormal;

	if (pxTopDirection)
	{
		xTemp.m_bAlignWithVelocity = true;
		xTemp.m_xTopDirection = *pxTopDirection;
		xTemp.m_xTopDirection.Normalise();
	}
	else
	{
		xTemp.m_bAlignWithVelocity = false;
	}

	// Compress using bitpacker
	const u_int uBufferSize = sizeof(Asura_Message_CFX_ClientCreateDecal); // Uncompressed size
	u_int uDataSize = 0;
	Asura_BitPacker xBitPacker(uBufferSize);
	xTemp.Pack(xBitPacker);
	const void* pCompressedData = xBitPacker.GetResult(uDataSize);

	Asura_Message* pxMessage = Asura_Network::CreateMessage(ASURA_MSG_COLLISIONFX_CLIENTCREATEDECAL, uDataSize);
	if (pxMessage)
	{
		pxMessage->SetFromGuid(ASURA_GUID_SERVER);
		pxMessage->SetToGuid(ASURA_GUID_ALL_OTHER_CLIENTMACHINES);

		if (uDataSize > 0)
		{
			void* pData = pxMessage->GetData();
			if (pData)
			{
				Asura_Mem::Copy(pData, pCompressedData, uDataSize);
			}
		}

		Asura_Network::Send(pxMessage);
	}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM
}

////////////////////////////////////
// HandleClientCreateDecalMessage //
////////////////////////////////////
// Handle message from server machine to create a decal.
void Asura_CollisionFX_System::HandleClientCreateDecalMessage(const Asura_Message* pxMessage)
{
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
	if (Asura_Network::IsServer())
	{
		Asura_Assert(0, "Server shouldn't be receiving CFX create decal messages");
		return;
	}

	if (!pxMessage) return;

	if (pxMessage->GetMsgID() != ASURA_MSG_COLLISIONFX_CLIENTCREATEDECAL)
	{
		Asura_Assert(0, "Wrong message ID");
		return;
	}

	if (pxMessage->GetData())
	{
		// Unpack the message data.
		Asura_Message_CFX_ClientCreateDecal xTemp;
		Asura_BitUnpacker xUnpacker(pxMessage->GetData(), pxMessage->GetDataSize());
		xTemp.Unpack(xUnpacker);

		// Sanity check.
		Asura_Assert(xUnpacker.GetNumBitsRemaining() < 8, "Didn't read all of the bitpacked message data");
		Asura_Assert(xUnpacker.Unpack(xUnpacker.GetNumBitsRemaining()) == 0, "Didn't read all of the bitpacked message data");

		// Process the message. Create a decal.
		if (xTemp.m_bAlignWithVelocity)
		{
			Asura_Decal_Manager::AddDecal(xTemp.m_xPosition, xTemp.m_xNormal, xTemp.m_xTopDirection,
										  xTemp.m_uDecalSetHash, xTemp.m_iDecalIndex,
										  xTemp.m_fScale, xTemp.m_fOrientation);
		}
		else
		{
			Asura_Decal_Manager::AddDecal(xTemp.m_xPosition, xTemp.m_xNormal,
										  xTemp.m_uDecalSetHash, xTemp.m_iDecalIndex,
										  xTemp.m_fScale, xTemp.m_fOrientation);
		}
	}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

}


///////////////////////////////////////////////////////////////////////////////
// Asura_Message_CFX_ClientUpdate
///////////////////////////////////////////////////////////////////////////////
#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Message_CFX_ClientUpdate::Pack(Asura_BitPacker& xPacker) const
{
	Asura_Assert(m_uNumEntries>0, "Error: number of entries is zero");
	xPacker.Pack(m_uNumEntries-1, uASURA_CFX_MAX_NUM_ENTRIES_FOR_CLIENT_NUMBITS);
	for (u_int u=0; u<m_uNumEntries; ++u)
	{
		m_axEntry[u].Pack(xPacker);
	}
}

void Asura_Message_CFX_ClientUpdate::Unpack(Asura_BitUnpacker& xUnpacker)
{
	m_uNumEntries = 1 + xUnpacker.Unpack(uASURA_CFX_MAX_NUM_ENTRIES_FOR_CLIENT_NUMBITS);
	for (u_int u=0; u<m_uNumEntries; ++u)
	{
		m_axEntry[u].Unpack(xUnpacker);
	}
}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

///////////////////////////////////////////////////////////////////////////////
// Asura_Message_CFX_ClientUpdate_Entry
///////////////////////////////////////////////////////////////////////////////

// Note: we really need to compress the message data as much as possible,
// to avoid wasting a lot of bandwidth in networked games when there are
// a lot of CFX effects active.

// The maximum number of types.
const int iASURA_CFX_MAX_NUM_TYPES = 7;

// The number of bits required to store the maximum number of types.
const u_int uASURA_CFX_NUM_BITS_FOR_TYPE = 3;

// The number of bits required to store the volume.
const u_int uASURA_CFX_NUM_BITS_FOR_VOLUME = 7;

// The number of bits required to store guid 1 in the short ID format.
const u_int uASURA_CFX_NUM_BITS_FOR_SHORT_ID_GUID = 14;

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Message_CFX_ClientUpdate_Entry::Pack(Asura_BitPacker& xPacker) const
{
	// Can we compress the ID in to the short format?
	const bool bShortID = (m_uGuid2 == 0) &&
						  (m_iType == 0 || m_iType == 1) &&
						  (m_uGuid1 >= ASURA_GUID_TOOLSCREATEDENTITY_START) &&
						  (m_uGuid1 < ASURA_GUID_TOOLSCREATEDENTITY_START + (1u<<uASURA_CFX_NUM_BITS_FOR_SHORT_ID_GUID));

	// Short format?
	xPacker.PackBool(bShortID);

	if (bShortID)
	{
		// Short Identification Format
		// This covers the most common case of a tools created object colliding with the environment.
		const u_int uGuidOffset = (m_uGuid1 - ASURA_GUID_TOOLSCREATEDENTITY_START);
		xPacker.Pack(uGuidOffset, uASURA_CFX_NUM_BITS_FOR_SHORT_ID_GUID);
		xPacker.PackBool(m_iType == 1);
	}
	else
	{
		// Long Identification Format
		// m_uGuid1, m_uGuid2
		Asura_MessagePacking::PackGuid(m_uGuid1, &xPacker);
		Asura_MessagePacking::PackGuid(m_uGuid2, &xPacker);

		// Clamp the type to range [0,7]; it is unlikely to be much more than this anyway.
		// m_iType
		const int iType = Asura_Maths::ClampToRange(m_iType, 0, iASURA_CFX_MAX_NUM_TYPES);
		Asura_Assert(iType == m_iType, "Clamped CFX entry type to fit range");
		xPacker.Pack(iType, uASURA_CFX_NUM_BITS_FOR_TYPE);
	}

	// State flags
	// m_uStateFlags
	xPacker.PackBool(GetCreatedSE());
	xPacker.PackBool(GetCreatedPFX());

	// Creation flags imply that position flags etc are set, so no need to send them.
	if (!GetCreatedSE() && !GetCreatedPFX())
	{
		xPacker.PackBool(GetUpdatedPosition());
		xPacker.PackBool(GetUpdatedNormal());
		xPacker.PackBool(GetUpdatedVolume());
	}

	// Sound event information
	// m_uSEHash
	if (GetCreatedSE())
	{
		xPacker.Pack(m_uSEHash, 32);
	}

	// m_fVolumeScale
	if (GetCreatedSE() || GetUpdatedVolume())
	{
		if (m_fVolumeScale >= 0.99f)
		{
			xPacker.PackBool(false);
		}
		else
		{
			xPacker.PackBool(true);
			Asura_Compressed_Float xFloat(0.0f, 1.0f, uASURA_CFX_NUM_BITS_FOR_VOLUME);
			xFloat.FromFloat(m_fVolumeScale, false);
			xFloat.Pack(xPacker);
		}
	}

	// PFX information
	// m_uPFXHash, m_uPFXFlags
	if (GetCreatedPFX())
	{
		xPacker.Pack(m_uPFXHash, 32);

		if (m_uPFXFlags == ASURA_PFX_EFFECT_CAMERA_FLAGS)
		{
			xPacker.PackBool(false); // Most common case.
		}
		else
		{
			// Currently m_uPFXFlags is actually a u_short.
			xPacker.PackBool(true);
			xPacker.Pack(static_cast<u_short>(m_uPFXFlags), 16);
		}
	}

	// Misc data
	// m_xPosition
	if (GetCreatedSE() || GetCreatedPFX() || GetUpdatedPosition())
	{
		Asura_NetMsg_System::CompressPosition(m_xPosition, xPacker);
	}

	// m_xNormal
	if (GetCreatedPFX() || GetUpdatedNormal())
	{
		u_short usNormal = 0;
		Asura_NetMsg_Data_CompressedNormal::To16Bits(m_xNormal, &usNormal);
		xPacker.Pack(usNormal, 16);
	}
}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Message_CFX_ClientUpdate_Entry::Unpack(Asura_BitUnpacker& xUnpacker)
{
	// Short format?
	const bool bShortID = xUnpacker.UnpackBool();

	if (bShortID)
	{
		// Short Identification Format
		// This covers the most common case of a tools created object colliding with the environment.
		const u_int uGuidOffset = xUnpacker.Unpack(uASURA_CFX_NUM_BITS_FOR_SHORT_ID_GUID);

		m_uGuid1 = ASURA_GUID_TOOLSCREATEDENTITY_START + uGuidOffset;
		m_uGuid2 = 0;

		const bool bTypeOne = xUnpacker.UnpackBool();
		m_iType = (bTypeOne ? 1 : 0);
	}
	else
	{
		// Long Identification Format
		// m_uGuid1, m_uGuid2
		m_uGuid1 = Asura_MessagePacking::UnpackGuid(&xUnpacker);
		m_uGuid2 = Asura_MessagePacking::UnpackGuid(&xUnpacker);

		// Clamped the type to range [0,7]; it is unlikely to be much more than this anyway.
		// m_iType
		m_iType = static_cast<int>(xUnpacker.Unpack(uASURA_CFX_NUM_BITS_FOR_TYPE));
	}

	// State flags
	// m_uStateFlags
	m_uStateFlags = 0;

	if (xUnpacker.UnpackBool())
	{
		m_uStateFlags |= ASURA_CFX_FLAG_STATE_CREATED_SE;
	}
	if (xUnpacker.UnpackBool())
	{
		m_uStateFlags |= ASURA_CFX_FLAG_STATE_CREATED_PFX;
	}

	// Creation flags imply that position flags etc are set, so there was no need to send them.
	if (!GetCreatedSE() && !GetCreatedPFX())
	{
		if (xUnpacker.UnpackBool())
		{
			m_uStateFlags |= ASURA_CFX_FLAG_STATE_UPDATED_POSITION;
		}
		if (xUnpacker.UnpackBool())
		{
			m_uStateFlags |= ASURA_CFX_FLAG_STATE_UPDATED_NORMAL;
		}
		if (xUnpacker.UnpackBool())
		{
			m_uStateFlags |= ASURA_CFX_FLAG_STATE_UPDATED_VOLUME;
		}
	}

	// Sound event information
	// m_uSEHash
	if (GetCreatedSE())
	{
		m_uSEHash = xUnpacker.Unpack(32);
	}

	// m_fVolumeScale
	if (GetCreatedSE() || GetUpdatedVolume())
	{
		const bool bVolumeSent = xUnpacker.UnpackBool();
		if (!bVolumeSent)
		{
			m_fVolumeScale = 1.0f;
		}
		else
		{
			Asura_Compressed_Float xFloat(0.0f, 1.0f, uASURA_CFX_NUM_BITS_FOR_VOLUME);
			xFloat.Unpack(xUnpacker);
			xFloat.ToFloat(m_fVolumeScale);
		}
	}

	// PFX information
	// m_uPFXHash, m_uPFXFlags
	if (GetCreatedPFX())
	{
		m_uPFXHash = xUnpacker.Unpack(32);
	
		const bool bFlagsSent = xUnpacker.UnpackBool();
		if (!bFlagsSent)
		{
			m_uPFXFlags = ASURA_PFX_EFFECT_CAMERA_FLAGS; // Most common case.
		}
		else
		{
			// Currently m_uPFXFlags is actually a u_short.
			m_uPFXFlags = xUnpacker.Unpack(16);
		}
	}

	// Misc data
	// m_xPosition
	if (GetCreatedSE() || GetCreatedPFX() || GetUpdatedPosition())
	{
		Asura_NetMsg_System::UncompressPosition(m_xPosition, xUnpacker);
	}

	// m_xNormal
	if (GetCreatedPFX() || GetUpdatedNormal())
	{
		const u_short usNormal = static_cast<u_short>(xUnpacker.Unpack(16));
		Asura_NetMsg_Data_CompressedNormal::From16Bits( usNormal, &m_xNormal );
		m_xNormal.Normalise();
	}
}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM


///////////////////////////////////////////////////////////////////////////////
// Asura_Message_CFX_ClientCreateDecal
///////////////////////////////////////////////////////////////////////////////

// The number of bits required to send the decal index.
const u_int uASURA_NUM_BITS_FOR_DECAL_INDEX = 7;

// The number of bits required to send the decal scale.
const u_int uASURA_NUM_BITS_FOR_DECAL_SCALE = 8;

// The number of bits required to send the decal orientation.
const u_int uASURA_NUM_BITS_FOR_DECAL_ORIENTATION = 8;


#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Message_CFX_ClientCreateDecal::Pack(Asura_BitPacker& xPacker) const
{
	// Asura_Hash_ID m_uDecalSetHash
	xPacker.Pack(m_uDecalSetHash, 32);

	// int m_iDecalIndex [0, 127]
	xPacker.Pack(m_iDecalIndex, uASURA_NUM_BITS_FOR_DECAL_INDEX);

	// float m_fScale [0.0, 10.0]
	if (m_fScale >= 0.99f && m_fScale <= 1.01f)
	{
		// Default value of scale is 1.0f, no need to send it.
		xPacker.PackBool(false);
	}
	else
	{
		// Send the scale.
		xPacker.PackBool(true);
		Asura_Compressed_Float xFloat(0.0f, 10.0f, uASURA_NUM_BITS_FOR_DECAL_SCALE);
		xFloat.FromFloat(m_fScale, true);
		xFloat.Pack(xPacker);
	}

	// float m_fOrientation [0, 2.pi]
	const float fOrientation = Asura_Maths::RadiansToZeroMinimumRange(m_fOrientation);
	if (fOrientation == 0.0f)
	{
		// Default value of 0.0f
		xPacker.PackBool(false);
	}
	else
	{
		// Send the orientation
		xPacker.PackBool(true);
		Asura_Compressed_Float xFloat(0.0f, Asura_Maths::Deg360, uASURA_NUM_BITS_FOR_DECAL_ORIENTATION);
		xFloat.FromFloat(fOrientation, true);
		xFloat.Pack(xPacker);
	}

	// Asura_Vector_3 m_xPosition
	Asura_NetMsg_System::CompressPosition(m_xPosition, xPacker);

	// Asura_Vector_3 m_xNormal
	u_short usNormal = 0;
	Asura_NetMsg_Data_CompressedNormal::To16Bits(m_xNormal, &usNormal);
	xPacker.Pack(usNormal, 16);

	// bool m_bAlignWithVelocity
	xPacker.PackBool(m_bAlignWithVelocity);

	// Asura_Vector_3 m_xTopDirection
	if (m_bAlignWithVelocity)
	{
		u_short usTopDirection = 0;
		Asura_NetMsg_Data_CompressedNormal::To16Bits(m_xTopDirection, &usTopDirection);
		xPacker.Pack(usTopDirection, 16);
	}
}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

#ifdef ASURA_SUPPORT_NETMSG_SYSTEM
void Asura_Message_CFX_ClientCreateDecal::Unpack(Asura_BitUnpacker& xUnpacker)
{
	// Asura_Hash_ID m_uDecalSetHash
	m_uDecalSetHash = xUnpacker.Unpack(32);
	
	// int m_iDecalIndex [0, 127]
	m_iDecalIndex = xUnpacker.Unpack(uASURA_NUM_BITS_FOR_DECAL_INDEX);
	
	// float m_fScale [0.0, 10.0]
	const bool bSentScale = xUnpacker.UnpackBool();
	if (!bSentScale)
	{
		m_fScale = 1.0f;
	}
	else
	{
		Asura_Compressed_Float xFloat(0.0f, 10.0f, uASURA_NUM_BITS_FOR_DECAL_SCALE);
		xFloat.Unpack(xUnpacker);
		xFloat.ToFloat(m_fScale);
	}

	// float m_fOrientation [0, 2.pi]
	const bool bSentOrientation = xUnpacker.UnpackBool();
	if (!bSentOrientation)
	{
		m_fOrientation = 0.0f;
	}
	else
	{
		Asura_Compressed_Float xFloat(0.0f, Asura_Maths::Deg360, uASURA_NUM_BITS_FOR_DECAL_ORIENTATION);
		xFloat.Unpack(xUnpacker);
		xFloat.ToFloat(m_fOrientation);
	}

	// Asura_Vector_3 m_xPosition
	Asura_NetMsg_System::UncompressPosition(m_xPosition, xUnpacker);

	// Asura_Vector_3 m_xNormal
	const u_short usNormal = static_cast<u_short>(xUnpacker.Unpack(16));
	Asura_NetMsg_Data_CompressedNormal::From16Bits( usNormal, &m_xNormal );
	m_xNormal.Normalise();

	// bool m_bAlignWithVelocity
	m_bAlignWithVelocity = xUnpacker.UnpackBool();
	
	// Asura_Vector_3 m_xTopDirection
	if (m_bAlignWithVelocity)
	{
		const u_short usTopDirection = static_cast<u_short>(xUnpacker.Unpack(16));
		Asura_NetMsg_Data_CompressedNormal::From16Bits( usTopDirection, &m_xTopDirection );
		m_xTopDirection.Normalise();
	}
}
#endif //ASURA_SUPPORT_NETMSG_SYSTEM

///////////////////////////////////////////////////////////////////////////////
// Asura_Compressed_Float
///////////////////////////////////////////////////////////////////////////////
void Asura_Compressed_Float::FromFloat(float fValue, bool bAssertIfOutOfRange /*= true*/)
{
	const u_int uNumBins = GetNumBins();
	const float fRange = GetMax() - GetMin();

	Asura_Assert(uNumBins>2,  "Asura_Compressed_Float: Too few bins!");
	Asura_Assert(fRange>0.0f, "Asura_Compressed_Float: Range must be positive!");

	Asura_Assert((fValue>=GetMin() && fValue<=GetMax()) || !bAssertIfOutOfRange, "Asura_Compressed_Float: Float out of range!");

	if (fValue<=GetMin())
	{
		m_uData = 0;
	}
	else if (fValue>=GetMax())
	{
		m_uData = uNumBins - 1;
	}
	else
	{
		const float fFraction = (fValue - GetMin())/fRange;
		m_uData = Asura_Maths::FloatToInt_Chop(fFraction*(uNumBins - 2) + 1);
	}
}

void Asura_Compressed_Float::ToFloat(float& fValue) const
{
	const u_int uNumBins = GetNumBins();
	const float fRange = GetMax() - GetMin();

	Asura_Assert(m_uData<uNumBins, "Asura_Compressed_Float: Invalid bin number!");
	
	if (m_uData == 0)
	{
		fValue = GetMin();
	}
	else if (m_uData >= uNumBins - 1)
	{
		fValue = GetMax();
	}
	else
	{
		const float fFraction = (static_cast<float>(m_uData) - 0.5f)/(uNumBins-2);
		fValue = fFraction*fRange + GetMin();
	}
}


#endif //ASURA_USE_COLLISIONFX
