///////////////////////////////////////////////////////////////////////////////
//
// File: AVP_CloakEffect.cpp
//
// Created by Andrew Southgate 04/12/2007
// 
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
// Handles rendering of the predators cloak
//
///////////////////////////////////////////////////////////////////////////////


#include "Core/Asura.h"

#include "AVP_CloakEffect.h"

#include "Animation/Asura_Animation.h"
#include "Core/Asura_Timers.h"
#include "Core/Memory/Asura_Mem.h"
#include "Interface/Asura_CommandConsole.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_SortedRender.h"
#include "Shapes/Asura_Shapes.h"

#include "Entity/PhysicalObject/AVP_ClientEntity_PhysicalObject.h"
#include "SpecialFX/AVP_PerlinNoise.h"

DVSVAR bool			dvs_bAnimateTransitionValue = false;
DVSVAR float		dvs_fCloakEffect_TransitionTime = 2.0f;
DVSVAR float 		dvs_fEffectScale = 2.5f;
DVSVAR float 		dvs_fLightningScale = 0.04f;
DVSVAR float 		dvs_fVariationSpeed = 0.08f;
DVSVAR float 		dvs_fVariationScale = 3.2f;
DVSVAR float 		dvs_fTransitionValue = 0.0f;
DVSVAR bool 		dvs_bOverride = false;
DVSVAR bool 		dvs_bOverrideTransitionValue = false;
DVSVAR Asura_RGBA	dvs_xLightningColour = Asura_RGBA(4.0f, 10.0f, 11.0f, 1.0f);

static const u_int g_uMaxNumRenderableObjects = 100;


class AVP_CloakSortedItem
{
	AVP_CloakSortedItem *m_pxNext;
	AVP_CloakSortedItem *m_pxPrev;
	const AVP_CloakEffect *m_pxCloakEffect;
	mutable Asura_SortedRenderItem_Animation m_xSortedRenderItem;
public:
	AVP_CloakSortedItem *GetNext() const { return m_pxNext; }
	void Initialise(AVP_CloakSortedItem *&pxHead, AVP_CloakSortedItem *&pxTail, const AVP_CloakEffect *pxCloakEffect)
	{
		m_pxNext = NULL;
		m_pxPrev = pxTail;
		if (pxTail) pxTail->m_pxNext = this;
		else pxHead = this;
		pxTail = this;

		m_pxCloakEffect = pxCloakEffect;
	}
	Asura_SortedRenderItem_Animation &GetSortedItem() const { return m_xSortedRenderItem;}
};

static AVP_CloakSortedItem *g_pxFirstRenderItem;
static AVP_CloakSortedItem *g_pxLastRenderItem;

static Asura_Mempool *g_pxCloakSortedItemsPool;





AVP_CloakEffect::AVP_CloakEffect()
: m_fEffectScale(dvs_fEffectScale)
, m_fLightningScale(dvs_fLightningScale)
, m_fVariationSpeed(dvs_fVariationSpeed)
, m_fVariationScale(dvs_fVariationScale)
, m_fTransitionTime(0.0f)
, m_fTransitionValue(1.0f)
, m_fMaxVisibilityDistance(30.0f)
, m_fMaxVisibilityAngle(45.0f * Asura_Maths::DegToRadConstant)
, m_xLightningColour(dvs_xLightningColour)
, m_fMaxBoneValue(0.0f)
, m_fMinBoneValue(0.0f)
, m_uNumBones(0)
, m_pfBoneOffsets(0)
, m_uSeed(Asura_Random::GetInt(0, NUM_CLOAK_EFFECT_PERMUTATIONS-1))
, m_eState(UNCLOAKED)
{

#ifdef ASURA_DEBUG_VARIABLE_SYSTEM

 	if(!Asura_CommandConsole::VarExists(L"AVP.CloakEffect.Override"))
 	{
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.Override", &dvs_bOverride);

		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.Scale", &dvs_fEffectScale, 0.0f, FLT_MAX);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.LightningScale", &dvs_fLightningScale, 0.0001f, 1.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.VariationSpeed", &dvs_fVariationSpeed, 0.0f, FLT_MAX);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.VariationScale", &dvs_fVariationScale, 0.0f, FLT_MAX);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.LightningColour.r", &dvs_xLightningColour.m_fR, 0.0f, 50.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.LightningColour.g", &dvs_xLightningColour.m_fG, 0.0f, 50.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.LightningColour.b", &dvs_xLightningColour.m_fB, 0.0f, 50.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.LightningColour.a", &dvs_xLightningColour.m_fA, 0.0f, 50.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.OverrideTransitionValue", &dvs_bOverrideTransitionValue);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.TransitionValue", &dvs_fTransitionValue, -2.0f, 2.0f);

		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.TransitionTime", &dvs_fCloakEffect_TransitionTime, 0.001f, 10.0f);
		Asura_CommandConsole::AddVar(L"AVP.CloakEffect.ConstantlyTransition", &dvs_bAnimateTransitionValue);
	}
#endif	// ASURA_DEBUG_VARIABLE_SYSTEM

}

void AVP_CloakEffect::Initialise()
{
	Platform_Initialise();
	g_pxCloakSortedItemsPool = new Asura_Mempool(sizeof(AVP_CloakSortedItem), g_uMaxNumRenderableObjects);
}
void AVP_CloakEffect::Shutdown()
{
	Platform_Shutdown();
	delete g_pxCloakSortedItemsPool;

}


AVP_CloakEffect::~AVP_CloakEffect()
{
	delete[] m_pfBoneOffsets;
}

void AVP_CloakEffect::SetMaxBones(unsigned int uNumBones)
{
	if (uNumBones != m_uNumBones)
	{
		float *pfBoneValues = new float[uNumBones];

		if (m_pfBoneOffsets && m_uNumBones)
		{
			Asura_Mem::Copy(pfBoneValues, m_pfBoneOffsets, Asura_Maths::Min(m_uNumBones, uNumBones) * sizeof(float));
		}
		if (uNumBones > m_uNumBones)
		{
			Asura_Mem::Set(pfBoneValues + m_uNumBones, 0, (uNumBones - m_uNumBones) * sizeof(float));
		}
		delete [] m_pfBoneOffsets;
		m_pfBoneOffsets = pfBoneValues;
		m_uNumBones = uNumBones;
	}
}

void AVP_CloakEffect::CalcMaxMinBoneOffsets()
{
	m_fMinBoneValue = FLT_MAX;
	m_fMaxBoneValue = FLT_MIN;
	for (unsigned int i = 0; i < m_uNumBones; ++i)
	{
		float fValue = m_pfBoneOffsets[i];
		if (m_fMaxBoneValue < fValue)
		{
			m_fMaxBoneValue = fValue;
		}
		if (m_fMinBoneValue > fValue)
		{
			m_fMinBoneValue = fValue;
		}
	}
}

void AVP_CloakEffect::SetBoneOffset(unsigned int uBoneIdx, float fValue)
{
	if (uBoneIdx >= m_uNumBones)
	{
		SetMaxBones(uBoneIdx + 1);
	}
	bool bNeedToRecalcMaxMin = false;
	if (m_pfBoneOffsets[uBoneIdx] == m_fMaxBoneValue)
	{
		if (m_fMaxBoneValue < fValue)
		{
			m_fMaxBoneValue = fValue;
		} 
		else 
		{
			bNeedToRecalcMaxMin = true;
		}
	}
	if (m_pfBoneOffsets[uBoneIdx] == m_fMinBoneValue)
	{
		if (m_fMinBoneValue > fValue)
		{
			m_fMinBoneValue = fValue;
		} 
		else 
		{
			bNeedToRecalcMaxMin = true;
		}
	}
	if (bNeedToRecalcMaxMin)
	{
		CalcMaxMinBoneOffsets();
	}

	m_pfBoneOffsets[uBoneIdx] = fValue;
}

bool AVP_CloakEffect::BeginCloaking(const bool bInstant)
{
	switch(m_eState)
	{
		case UNCLOAKED:
		{
			if(NUM_CLOAK_EFFECT_PERMUTATIONS > 2)
			{
				u_int uSeed(Asura_Random::GetInt(0, NUM_CLOAK_EFFECT_PERMUTATIONS-2));

				m_uSeed = uSeed >= m_uSeed ? uSeed + 1 : uSeed;
			}

			m_eState	= (bInstant) ? CLOAKED : CLOAKING;
			return(true);
		}

		case UNCLOAKING:
		{
			m_eState	= (bInstant) ? CLOAKED : CLOAKING;
			return(true);
		}
		
		default:
		{
			Asura_Assert(false, "Cannot begin cloaking since since the effect is already in use.");
			return(false);
		}
	}		// End of switch.
}

bool AVP_CloakEffect::BeginUncloaking(const bool bInstant)
{
	switch(m_eState)
	{
		case CLOAKING:
		case CLOAKED:
		{
			m_eState	= (bInstant) ? UNCLOAKED : UNCLOAKING;
			return(true);
		}
		
		default:
		{
			Asura_Assert(false, "Cannot begin uncloaking since the effect is already in use.");
			return(false);
		}
	}		// End of switch.
}

bool AVP_CloakEffect::Update(AVP_ClientEntity_PhysicalObject& xClient)
{
	const float				fTimeStep		= Asura_Timers::GetGameTimeSlice();
	Asura_Shape_Instance&	xShapeInstance	= xClient.GetShapeInstance();

	if (dvs_bOverride)
	{
		m_fEffectScale = dvs_fEffectScale;
		m_fLightningScale = dvs_fLightningScale;
		m_fVariationSpeed = dvs_fVariationSpeed;
		m_fVariationScale = dvs_fVariationScale;
	}
	if (dvs_bAnimateTransitionValue)
	{
		m_fTransitionTime = Asura_Timers::GetLocalScaledTime() * (1.0f / dvs_fCloakEffect_TransitionTime);
		m_fTransitionTime = Asura_Maths::FloatMod(m_fTransitionTime, 2.0f);
		if (m_fTransitionTime > 1.0f)
		{
			m_fTransitionTime = 2.0f - m_fTransitionTime;
		}
	}
	else
	{
		switch(m_eState)
		{
			case UNCLOAKED:
			{
				m_fTransitionTime	= 0.0f;
				break;
			}

			case CLOAKING:
			{
				if (m_fTransitionTime < 1.0f)
				{
					m_fTransitionTime += fTimeStep * (1.0f / dvs_fCloakEffect_TransitionTime);
				}
				if (m_fTransitionTime >= 1.0f)
				{
					m_eState			= CLOAKED;
					m_fTransitionTime	= 1.0f;
				}
				break;
			}

			case CLOAKED:
			{
				m_fTransitionTime	= 1.0f;
				break;
			}

			case UNCLOAKING:
			{
				if (m_fTransitionTime > 0.0f)
				{
					m_fTransitionTime -= fTimeStep * (1.0f / dvs_fCloakEffect_TransitionTime);
				}
				if (m_fTransitionTime <= 0.0f)
				{
					m_eState			= UNCLOAKED;
					m_fTransitionTime	= 0.0f;
				}
				break;
			}

			default:
			{
				Asura_Assert(false, "Unexpected state for cloak-effect.");
				break;
			}
		}		// End of switch.
	}

	UpdateTransitionValueAndApplyAlpha(xShapeInstance);

	// Return whether we're currently (at least partially) cloaked...
	return(m_eState != UNCLOAKED);
}

void AVP_CloakEffect::SetTransitionTimeAndState(float fTransTime, State eState, Asura_Animation& xAsuraAnim)
{
	m_eState          = eState;
	m_fTransitionTime = fTransTime;

	UpdateTransitionValueAndApplyAlpha(xAsuraAnim);
}

void AVP_CloakEffect::UpdateTransitionValueAndAlpha(float& fAlpha)
{
	float	fTransitionValue	= Asura_Maths::Lerp(-1.0f, 1.0f, m_fTransitionTime);
	fAlpha			         	= (m_eState == UNCLOAKED) ? 1.0f : 0.5f;

	SetTransitionValue(fTransitionValue * (1.0f + m_fLightningScale + 0.0001f));

	if (dvs_bOverrideTransitionValue)
	{
		SetTransitionValue(dvs_fTransitionValue);
	} 
}

void AVP_CloakEffect::UpdateTransitionValueAndApplyAlpha(Asura_Shape_Instance& xShapeInstance)
{
	float fAlpha;
	UpdateTransitionValueAndAlpha(fAlpha);
	if (fAlpha < 1.0f)
	{
		xShapeInstance.AddFlags(ASURA_SHAPE_INSTANCE_NO_SHADOW);
	}
	else
	{
		xShapeInstance.RemoveFlags(ASURA_SHAPE_INSTANCE_NO_SHADOW);
	}
}

void AVP_CloakEffect::UpdateTransitionValueAndApplyAlpha(Asura_Animation& xAsuraAnim)
{
	float fAlpha;
	UpdateTransitionValueAndAlpha(fAlpha);
}
static bool s_bPredCloakTransparentPass = false;
void AVP_CloakEffect::RenderTransparentPasses()
{
	Asura_SortedRender::SetTransparentPass(true);
	s_bPredCloakTransparentPass = true;
	for (AVP_CloakSortedItem *pxItem = g_pxFirstRenderItem; pxItem; pxItem = g_pxFirstRenderItem)
	{
		// this is horrible but allows us to get around the privateness Asura_Hierarchy_System::Render
		pxItem->GetSortedItem().Render();

		g_pxFirstRenderItem = pxItem->GetNext();
		g_pxCloakSortedItemsPool->Deallocate(reinterpret_cast<void*>(pxItem));
	}
	g_pxLastRenderItem = NULL;
	s_bPredCloakTransparentPass = false;
	Asura_SortedRender::SetTransparentPass(false);
}


////////////////////////////////////////////////////////////////////////////////
// Function     : AVP_CloakEffect::Render
// Author       : Andy Tate
// Date         : 8-Apr-2008
////////////////////////////////////////////////////////////////////////////////

void AVP_CloakEffect::Render(const Asura_Animation& xAnimation,
							 const u_int uCamVisFlags,
							 const u_int uRenderFlags,
							 const Asura_Vector_3& xBSCentre,
							 const float fBSRadius) const
{
	bool bFullyCloaked = (m_fMaxBoneValue + m_fTransitionValue) - 1.0f  >= (m_fLightningScale - 0.00001f);

	if (!Asura_SortedRender::IsTransparentPass())
	{
		if ((xBSCentre - *Asura_Camera::GetPosition()).MagnitudeSquared() <=
			(fBSRadius + GetMaxVisibilityDistance()) * (fBSRadius + GetMaxVisibilityDistance()))
		{
			AVP_CloakSortedItem *pxItem = reinterpret_cast<AVP_CloakSortedItem *>(g_pxCloakSortedItemsPool->Allocate());
			Asura_Assertf(pxItem, ("Increase g_uMaxNumRenderableObjects in AVP_CloakEffect, trying to render too many cloaked items at once, (currently %d)", g_uMaxNumRenderableObjects));
			if (pxItem)
			{
				pxItem->Initialise(g_pxFirstRenderItem, g_pxLastRenderItem, this);

				pxItem->GetSortedItem().m_eType = ASURA_SORTEDRENDERITEM_TYPE_ANIMATION_SMOOTH_SKIN;
				pxItem->GetSortedItem().m_pxAnimation = const_cast<Asura_Animation*>(&xAnimation);
				pxItem->GetSortedItem().m_uCamVisFlags = uCamVisFlags;
				pxItem->GetSortedItem().m_xBSCentre = xBSCentre;
				pxItem->GetSortedItem().m_fBSRadius = fBSRadius;
				pxItem->GetSortedItem().m_uRenderFlags = uRenderFlags;

				Asura_AMatrix4x4 xBone0Transform;
				Asura_AMatrix4x4 xTransform;
				Asura_Hierarchy_System::s_pxTweenStorage->GetBoneData(0).GetTransform(xBone0Transform);
				xBone0Transform.InvertOrthogonal();
				Asura_Hierarchy_System::s_xBaseTransform.Apply(xBone0Transform, xTransform);

				pxItem->GetSortedItem().m_xPosition = xTransform.GetTranslation();
				xTransform.ToMatrix(pxItem->GetSortedItem().m_xOrient);
			}
		}
	} 
	if ((!Asura_SortedRender::IsTransparentPass() && !bFullyCloaked) || (Asura_SortedRender::IsTransparentPass() && s_bPredCloakTransparentPass))
	{
		Platform_BeginRender();

#if defined ASURA_HIERARCHY_QUAD
		const Asura_Hierarchy_Skin&	xSkin	= *xAnimation.GetSkin();

		if( Asura_Hierarchy_System::s_bBonesInWorldspace )
		{
			Asura_Hierarchy_System::s_xInverseBaseTransform.ApplyToArray
				(
				Asura_Hierarchy_System::s_axBoneTransforms,
				xSkin.NumberOfBones,
				Asura_Hierarchy_System::s_axBoneTransforms
				);
		}

		for( int i = 0; i < xSkin.NumberOfBones; ++i )
		{
			Asura_Hierarchy_System::s_axBoneTransforms[i].Apply
				(
				xSkin.m_pxBoneRestPoseInverseTransforms[i],
				Asura_Hierarchy_System::s_axBoneTransforms[i]
			);
		}

		if( Asura_Hierarchy_System::ShouldJustRenderForCurCam() )
		{
		}
		else
		{
			for( int i = 0; i < Asura_Camera::GetNumberOfCameras(); ++i )
			{
				if((uCamVisFlags & (1 << i)) != 0)
				{
					if(Asura_Camera::IsCameraActive(i))
					{
						Asura_Camera::SetCameraIndex(i);
						Asura_Render::ApplyCameraSettings();

						// Select LOD mesh based on distance to first bone, since object may be animating far away from its location
						const Asura_Vector_3				xDifference(Asura_Hierarchy_System::GetWorldspaceBonePos(0) - *Asura_Camera::GetPosition());
						const float							fLODDistance	= xDifference.Magnitude();
						Asura_Hierarchy_Skin const* const	pxSkin			= xAnimation.GetSkin()->GetDetailSkinFromDistance( fLODDistance );

						if(pxSkin == NULL)
						{
							Asura_Assertf(false, ("Couldn't retrieve LOD skin for '%s' at distance %.02f.", xSkin.GetName(), fLODDistance));
						}
						else
						{
							Platform_RenderSkin(xAnimation, *pxSkin);
						}
					}
				}
			}
		}
#else
#error Can't be bothered to make this work without ASURA_HIERARCHY_QUAD at the moment
#endif

		Platform_EndRender();

	}
}

/// End of function 'AVP_CloakEffect::Render'. ///


void AVP_CloakEffect::SetTransitionValue(float fTransitionValue) 
{ 
	m_fTransitionValue = fTransitionValue;
}


/// End of file. ///
