/**
*
* \file Asura_Shard_Effect.cpp
*
* Created by Andrew Weinkove - 03/12/02
*
* Copyright (C) 2002 Rebellion, All Rights Reserved.
*
**/

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

#include "Core/Asura.h"

// This file's header.
#include "SpecialFX/Asura_Shard_Effect.h"

// Engine independent.
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "SpecialFX/Asura_Shard_System.h"

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

Asura_Shard_Effect::Asura_Shard_Effect()
{
    m_iNumberOfActiveShards = 0;
}

Asura_Shard_Effect::~Asura_Shard_Effect()
{
	#ifdef ASURA_ASSERT
    int iNumShards = 
	#endif
	Asura_Shard_System::GetNumActiveShards();
	for (int i=0; i<m_iNumberOfActiveShards; i++)
	{
		Asura_Assert(m_apxShards[i]!=0,"");
		Asura_Shard_System::DeallocateShard(m_apxShards[i]);
	}
    Asura_Assert(iNumShards == Asura_Shard_System::GetNumActiveShards() + m_iNumberOfActiveShards, "MH");
}

void Asura_Shard_Effect::Initialise(Asura_Shard **ppxShards, const int iNumberOfShards, const float fAlphaOutTime, const unsigned uFlags, const unsigned uColour, const int iTextureIndex)
{
	m_fTimeSinceLastUpdate = 0.0f;
	m_fAlpha = 0.0f;
	m_iNumberOfActiveShards = iNumberOfShards;
	m_fAlphaOutTime = fAlphaOutTime;
	m_uFlags = uFlags;
	Asura_Assert(!IsReadyToBeDestroyed(), "Error, created a shard effect that is ready to be destroyed immediately, check the flags you sent.");
	m_uColour = uColour;
	m_iTextureIndex = iTextureIndex;
	Asura_Assert(m_iNumberOfActiveShards, "Error, created a shard effect with no shards");
	Asura_Assert(m_iNumberOfActiveShards <= MAX_SHARDS_PER_EFFECT, "Error, created a shard effect with too many shards");
	if (m_iNumberOfActiveShards > MAX_SHARDS_PER_EFFECT)
		m_iNumberOfActiveShards = MAX_SHARDS_PER_EFFECT;
	m_xBB.Reset();
	for (int i=0;i<m_iNumberOfActiveShards;i++)
	{
		m_apxShards[i] = ppxShards[i];
		for (int v=0;v<3;v++)
		{
			m_xBB.Set(m_apxShards[i]->m_axVertices[v]);
		}
	}
}


void Asura_Shard_Effect::Render(void)
{
	//extern void Render_Sphere( const Asura_Vector_3& xCentre, float fRadius, int iColour );

    Asura_Assert(m_iNumberOfActiveShards, "no shards to render!");

    const Asura_Vector_3 xBSCentre = m_xBB.CalculateCentre();
    const float          fBSRadius = 0.5f * m_xBB.CalculateExtents().Magnitude();

	u_int uCamVisFlags = 0;
	for (int i=0;i<Asura_Camera::GetNumberOfCameras();i++)
	{
		Asura_Camera::SetCameraIndex(i);

        if( !Asura_Camera::IsCameraActive() ) { continue; }

        if( !Asura_Camera::IsSphereInFrustum(xBSCentre, fBSRadius) ) { continue; }

		uCamVisFlags |= (1<<i);
	}

	//Render_Sphere(xBSCentre, fBSRadius, 0x80ffffff);
    if( uCamVisFlags ) { Platform_Render( uCamVisFlags ); }
}


void Asura_Shard_Effect::Update(void)
{
    Asura_Assert(m_iNumberOfActiveShards, "no shards to update!");

	// See if it's time to do an update yet.
	// If the updates are done too frequently, odd things tend to happen (probably
	// due to floating point inaccuracy) and shards fly off for miles.
	// This is a serious issue if game speed is low or frame rate is v.high.aw.
	m_fTimeSinceLastUpdate += Asura_Timers::GetGameTimeSlice();
	if (m_fTimeSinceLastUpdate < 0.04f * Asura_Timers::GetGameTimeScale())
		return;
	float fTimeslice = m_fTimeSinceLastUpdate;
	if (fTimeslice == 0.0f)
		return;
	m_fTimeSinceLastUpdate = 0.0f;

	// Pre-calculate inverse now for speed.
	float fInvTimeslice = 1.0f / fTimeslice;

	m_xBB.Reset();
	for (int i=0;i<m_iNumberOfActiveShards;)
	{
		if (m_apxShards[i]->m_fTimeToStayStill <= 0.0f)
		{
			// Update position of each vertex.
			Asura_Vector_3 axOldPosition[3];
			bool abCollided[3];
			for (int v=0;v<3;v++)
			{
				axOldPosition[v] = m_apxShards[i]->m_axVertices[v];

				// KJFL - Friday, February 11, 2005 - I'm not sure we want to support shards in the new Physics system - or at least, in their current form
				// They only seemed to be used for one glass effect atm, which isn't used on all projects.


				abCollided[v] = !m_apxShards[i]->m_axVertices[v].ApproximatelyEquals(axOldPosition[v] + (m_apxShards[i]->m_axVelocities[v] * fTimeslice));
			}

			// Resolve the positions of the vertices so that the triangle remains the same shape.
			int iRecursions = 1;
			while (iRecursions--)
			{
				for (int v=0;v<3;v++)
				{
					int iOtherVertex = v + 1;
					if (iOtherVertex == 3)
						iOtherVertex = 0;

					Asura_Vector_3 xDiff = m_apxShards[i]->m_axVertices[iOtherVertex] - m_apxShards[i]->m_axVertices[v];
					Asura_Vector_3 xCentrePos = m_apxShards[i]->m_axVertices[v] + (xDiff * 0.5f);
					xDiff.Normalise();

					Asura_Vector_3 xRequiredVelocityToGetToPosition = ((xCentrePos - (xDiff * m_apxShards[i]->m_afEdgeLengths[v] * 0.5f)) - m_apxShards[i]->m_axVertices[v]) * fInvTimeslice;
					Asura_Vector_3 xRequiredVelocityToGetToPosition2 = ((xCentrePos + (xDiff * m_apxShards[i]->m_afEdgeLengths[v] * 0.5f)) - m_apxShards[i]->m_axVertices[iOtherVertex]) * fInvTimeslice;


					// KJFL - Friday, February 11, 2005 - I'm not sure we want to support shards in the new Physics system - or at least, in their current form
					// They only seemed to be used for one glass effect atm, which isn't used on all projects.

				}
			}

			// Update velocities according to what happened.
			for (int v=0;v<3;v++)
			{
				if (!abCollided[v])
				{
					float fMag = m_apxShards[i]->m_axVelocities[v].Magnitude();
					fMag *= 1.0f - (0.025f * fTimeslice);
					m_apxShards[i]->m_axVelocities[v] = m_apxShards[i]->m_axVertices[v] - axOldPosition[v];
					m_apxShards[i]->m_axVelocities[v].Normalise();
					m_apxShards[i]->m_axVelocities[v] *= fMag;
				}
				else
				{
					m_apxShards[i]->m_axVelocities[v].x *= 0.5f;
					m_apxShards[i]->m_axVelocities[v].z *= 0.5f;
				}
			}

			// When the velocities of the vertices are very different, strange things
			// start to happen, and they fly and spin like paper planes.
			// Adjust the velocities here if they are very different.
			for (int v=0;v<3;v++)
			{
				int iOtherVertex = v + 1;
				if (iOtherVertex == 3)
					iOtherVertex = 0;

				Asura_Vector_3 xVelocityDiff = m_apxShards[i]->m_axVelocities[iOtherVertex] - m_apxShards[i]->m_axVelocities[v];
				if (xVelocityDiff.Magnitude() > 10.0f)
				{
					// Half the difference between the velocities.
					m_apxShards[i]->m_axVelocities[v] += xVelocityDiff * 0.25f;
					m_apxShards[i]->m_axVelocities[iOtherVertex] -= xVelocityDiff * 0.25f;
				}
			}

			// See if we should freeze this shard. (otherwise they creep along the ground
			// and look bad).
			float fTotalVelocity = 0.0f;
			unsigned uCollisionCount = 0;
			static const unsigned uNumberOfCollisionsRequired = 2;
			for (int v=0;v<3;v++)
			{
				if (abCollided[v] &&
					uCollisionCount < uNumberOfCollisionsRequired)
				{
					fTotalVelocity += m_apxShards[i]->m_axVelocities[v] * m_apxShards[i]->m_axVelocities[v];
					uCollisionCount++;
				}
			}
			if (uCollisionCount >= uNumberOfCollisionsRequired &&
				fTotalVelocity < 0.75f)
				m_apxShards[i]->m_fTimeToStayStill = m_apxShards[i]->m_fLifeTimeRemaining;

			// Apply gravity
			for (int v=0;v<3;v++)
			{
				// Little extra gravity, because resolving positions sometimes makes the 
				// shards a bit too floaty.
				m_apxShards[i]->m_axVelocities[v].y += 13.0f * fTimeslice;
			}
		}

		for (int v=0;v<3;v++)
		{
			// Set the bounding box here
			m_xBB.Set(m_apxShards[i]->m_axVertices[v]);
		}
		// Update timers.
		m_apxShards[i]->m_fLifeTimeRemaining -= fTimeslice;
		m_apxShards[i]->m_fTimeToStayStill -= fTimeslice;
		// Destroy shard if necessary.
		if (m_apxShards[i]->m_fLifeTimeRemaining <= 0)
		{
			Asura_Shard_System::DeallocateShard(m_apxShards[i]);
			m_apxShards[i] = m_apxShards[--m_iNumberOfActiveShards];
		}
		else
		{
			i++;
		}
	}

	if (m_iNumberOfActiveShards==0)
	{
		SetAsDestroyed();
	}
	else
	{
		Asura_Assert(m_apxShards[0], "");
		if (m_apxShards[0]->m_fLifeTimeRemaining < m_fAlphaOutTime)
			m_fAlpha = (m_fAlphaOutTime - m_apxShards[0]->m_fLifeTimeRemaining) / m_fAlphaOutTime;
		Asura_Assert(m_fAlpha >= 0.0f, "");
		Asura_Assert(m_fAlpha <= 1.0f, "");
	}
}
