//
// Asura_PFX_Code_Effects.cpp
//
// Created by KOR - 09/09/2004
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"

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

// Asura headers
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "Core/Asura_Timers.h"
#include "Light/Asura_Photon.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

// constructor
Asura_PFX_Entity_Fire::Asura_PFX_Entity_Fire(const Asura_Vector_3& xPosition, const Asura_Guid uOwnerGuid)
{
	m_uNumberOfActiveParticles = 0;
	m_fReleaseTimer = 0.0f;
	m_xPosition = xPosition;
	m_xOffset.Zero();
	m_bDieOut = false;
	
	m_uBone = 0;
	m_uNumberOfBones   = 0;
	m_pxBoneDirections = NULL;
	m_pxBonePositions  = NULL;

	m_pxPhoton = Asura_Photon_System::CreatePhoton();	
	if (m_pxPhoton)
	{
		Asura_Vector_3 xPhotonPos = xPosition;
		m_pxPhoton->SetColours( 1.5f, 0.75f, 0.375f);
		m_pxPhoton->SetRange( 2.0f );
		m_pxPhoton->SetPosition( xPhotonPos );
	}

//	RenderIfCameraDoesntIgnoreThisEntityGuid = true;
//	AssociatedEntityGuid = uOwnerGuid;
}

// destructor
Asura_PFX_Entity_Fire::~Asura_PFX_Entity_Fire()
{
	if(m_pxPhoton) 
	{
		Asura_Photon_System::DestroyPhoton(m_pxPhoton);
	}

	delete [] m_pxBonePositions;
	delete [] m_pxBoneDirections;
}

// public factory function
int Asura_PFX_Entity_Fire::Create(const Asura_Vector_3& xPosition)
{
	if(Asura_PFX_System::CanCreateEffect())
	{
		Asura_PFX_Entity_Fire* pxFire = new Asura_PFX_Entity_Fire(xPosition, ASURA_GUID_UNREGISTERED);
        if ( pxFire )
        {
			return pxFire->GetID();
        }
	}

	return -1;
}

int Asura_PFX_Entity_Fire::Create(const Asura_Vector_3& xPosition, const Asura_Guid uOwnerGuid)
{
	if(Asura_PFX_System::CanCreateEffect())
	{
		Asura_PFX_Entity_Fire* pxFire = new Asura_PFX_Entity_Fire(xPosition, uOwnerGuid);
        if ( pxFire )
        {
		    return pxFire->GetID();
        }
	}

	return -1;
}

// update function
void Asura_PFX_Entity_Fire::Update()
{
	float timeslice = Asura_Timers::GetGameTimeSlice();
	float decaytime = timeslice * 2.f;

	
	Asura_Bounding_Box xBB;
	xBB.Reset();

	// update all active particles
	for( u_int u=0; u<m_uNumberOfActiveParticles; )
	{
		m_axParticles[u].m_xPosition+=m_xOffset;

		m_axParticles[u].m_xPosition += m_axParticles[u].m_xVelocity * timeslice;
		m_axParticles[u].m_fAge += decaytime;

		// remove any timed-out particles
		if (m_axParticles[u].m_fAge > 1.f)
		{
			m_axParticles[u] = m_axParticles[--m_uNumberOfActiveParticles];			
		}
		else
		{
			xBB.Set(m_axParticles[u].m_xPosition);

			u++;
		}
	}
	if(m_uNumberOfActiveParticles)
	{
		// If there are no particles then there are no positions to set the BB from.
		SetBoundingBox(xBB);
	}

	if (m_uNumberOfBones!=0)
	{
		if (m_uNumberOfActiveParticles!=ASURA_PFX_MAXFIREPARTICLES)
		{
			m_fReleaseTimer -= timeslice;
		}

		// replace any timed-out particles
		while( (m_uNumberOfActiveParticles < ASURA_PFX_MAXFIREPARTICLES) && (m_fReleaseTimer <= 0.0f) && !m_bDieOut )
		{
			Asura_Vector_3 xVel( (Asura_Random::GetFloat()-0.5f)*0.5f, -1.0f+(Asura_Random::GetFloat()-0.5f)*0.5f, (Asura_Random::GetFloat()-0.5f)*0.5f);
			
			m_uBone = (m_uBone + 1) % m_uNumberOfBones;
				
			m_axParticles[m_uNumberOfActiveParticles].m_xPosition  = m_pxBonePositions[m_uBone];
			m_axParticles[m_uNumberOfActiveParticles].m_xPosition += m_pxBoneDirections[m_uBone] * Asura_Random::GetFloat( -0.1f, 0.25f );
			m_axParticles[m_uNumberOfActiveParticles].m_xVelocity  = xVel;			
			m_axParticles[m_uNumberOfActiveParticles].m_fAge = 0.0f;
			m_axParticles[m_uNumberOfActiveParticles].m_fSeed = Asura_Random::GetFloat();

			m_uNumberOfActiveParticles++;
			m_fReleaseTimer += 0.004f;
		}
	}

	if(m_pxPhoton)
	{
		m_pxPhoton->SetRange( Asura_Random::GetFloat(8.f, 12.f) * m_uNumberOfActiveParticles/(float)ASURA_PFX_MAXFIREPARTICLES );
	}

	if( m_bDieOut && (!m_uNumberOfActiveParticles) ) 
	{
		Destroy();
	}

}

void Asura_PFX_Entity_Fire::UpdatePosition(const Asura_Animation* pxAnimation, const Asura_Vector_3 xPosition)
{
	const Asura_Hierarchy_Skin* pxSkin = pxAnimation->GetSkin();

	if (pxSkin==NULL) return;

	if( !m_pxBonePositions || !m_pxBoneDirections )
	{
		m_pxBonePositions  = new Asura_Vector_3[pxSkin->NumberOfBones];
		m_pxBoneDirections = new Asura_Vector_3[pxSkin->NumberOfBones];
		m_uNumberOfBones = pxSkin->NumberOfBones;
	}
	else
	{
		Asura_Assert( m_uNumberOfBones == (u_int)pxSkin->NumberOfBones, "Bone numbers have changed?!" );
		if( m_uNumberOfBones < (u_int)pxSkin->NumberOfBones )
		{
			// gah, need to remake bone arrays...
			delete [] m_pxBonePositions;
			delete [] m_pxBoneDirections;

			m_pxBonePositions  = new Asura_Vector_3[pxSkin->NumberOfBones];
			m_pxBoneDirections = new Asura_Vector_3[pxSkin->NumberOfBones];

			m_uNumberOfBones = pxSkin->NumberOfBones;
		}
	}

	for( u_int u=0; u<m_uNumberOfBones; u++)
	{
        m_pxBonePositions[u] = Asura_Hierarchy_System::GetWorldspaceBonePos( u );
		
		Asura_Matrix_3x3 xBoneOrient = Asura_Hierarchy_System::GetWorldspaceBoneOri( u );
		Asura_Vector_3 xForward( 0.0f, 0.0f, 1.0f );		
		
		m_pxBoneDirections[u] = xForward * xBoneOrient;
	}

    const unsigned int uFrameNumber = Asura_Timers::GetFrameNumber();
    if ( uFrameNumber - m_uLastFramePosWasUpdated > 1 )
    {
        // if we've not been updated for a while we need to update the particle positions
        m_xOffset   = xPosition;
        m_xPosition = xPosition;
        Update();
    }
    else
    {
	    m_xOffset   = (xPosition - m_xPosition) * 0.9f;
	    m_xPosition = xPosition;
    }
    m_uLastFramePosWasUpdated = uFrameNumber;

	UpdatePhotonPosition(xPosition);
}

void Asura_PFX_Entity_Fire::Render(void)
{
	Asura_PFX_Particle_Template*	pxParticleTemplate	= Asura_PFX_System::GetParticleTempByHash(0x48E6F853); // Hash 0x48E6F853 == "entity fire"

	if(pxParticleTemplate == NULL)
	{
		Asura_Assert(false, "Could not get fire template");
	}
	else
	{
		const Asura_RGBA		xColour(1.0f, 1.0f, 1.0f, 1.0f);
		Asura_PFX_RenderQueue&	xRenderQueue		= Asura_PFX_System::GetRenderQueue();
		void*					pPlatformRenderData	= NULL;

		xRenderQueue.AddParticles(*pxParticleTemplate, m_axParticles, m_uNumberOfActiveParticles, xColour, *this, pPlatformRenderData);
	}
}

void Asura_PFX_Entity_Fire::UpdatePhotonPosition(const Asura_Vector_3& xPosition)
{
	if(m_pxPhoton)
	{
		// KOR - 12/06/2003 10:53:35 - xPosition is at the character's feet,
		// so move it up to a vaguely central position, ensuring floor is lit
		Asura_Vector_3 xPhotonPosition(m_xPosition);
		xPhotonPosition.y -= 1.0f;
		m_pxPhoton->SetPosition(xPhotonPosition);
	}
}

// constructor
Asura_PFX_Entity_SoulWind::Asura_PFX_Entity_SoulWind(const Asura_Vector_3& xPosition, const Asura_Guid uOwnerGuid)
{
	m_uNumberOfActiveParticles = 0;
	m_fReleaseTimer = 0.0f;
	m_xPosition = xPosition;
	m_bDieOut = false;
	
	m_uNumberOfBones = 0;

	m_bRenderThisFrame = false;
}

// destructor
Asura_PFX_Entity_SoulWind::~Asura_PFX_Entity_SoulWind()
{
}

// public factory function
int Asura_PFX_Entity_SoulWind::Create(const Asura_Vector_3& xPosition)
{
	if(Asura_PFX_System::CanCreateEffect())
	{
		Asura_PFX_Entity_SoulWind* pxEffect = new Asura_PFX_Entity_SoulWind(xPosition, ASURA_GUID_UNREGISTERED);
        if ( pxEffect )
        {
			return pxEffect->GetID();
        }
	}

	return -1;
}

int Asura_PFX_Entity_SoulWind::Create(const Asura_Vector_3& xPosition, const Asura_Guid uOwnerGuid)
{
	if(Asura_PFX_System::CanCreateEffect())
	{
		Asura_PFX_Entity_SoulWind* pxEffect = new Asura_PFX_Entity_SoulWind(xPosition, uOwnerGuid);
        if ( pxEffect )
        {
		    return pxEffect->GetID();
        }
	}

	return -1;
}

DEBUGVAR float dbg_fGravity = 0.2f;

// update function
void Asura_PFX_Entity_SoulWind::Update()
{
	float timeslice = Asura_Timers::GetGameTimeSlice();
	float decaytime = timeslice * 2.f;

	Asura_Bounding_Box xBB;
	xBB.Reset();
	// update all active particles
	for( u_int u=0; u<m_uNumberOfActiveParticles; )
	{
		m_axParticles[u].m_xVelocity += (m_axBonePosition[u % m_uNumberOfBones] - m_axParticles[u].m_xPosition) * dbg_fGravity;
		m_axParticles[u].m_xPosition += m_axParticles[u].m_xVelocity * timeslice;
		m_axParticles[u].m_fAge += decaytime;
		
		xBB.Set(m_axParticles[u].m_xPosition);
		// remove any timed-out particles
		if (m_axParticles[u].m_fAge > 1.f)
		{
			m_axParticles[u] = m_axParticles[--m_uNumberOfActiveParticles];			
		}
		else
		{
			u++;
		}
	}
	SetBoundingBox(xBB);

	if (m_uNumberOfBones!=0)
	{
		if (m_uNumberOfActiveParticles!=ASURA_PFX_MAXSOULWINDPARTICLES)
		{
			m_fReleaseTimer -= timeslice;
		}

		// replace any timed-out particles
		while( (m_uNumberOfActiveParticles < ASURA_PFX_MAXSOULWINDPARTICLES) && (m_fReleaseTimer <= 0.0f) && !m_bDieOut )
		{
			// make a new particle
			Asura_Vector_3 xVel( 0.f, 0.f, 0.f );
			
			u_int uBone = Asura_Random::GetIntU() % m_uNumberOfBones;
				
			m_axParticles[m_uNumberOfActiveParticles].m_xPosition = m_axBonePosition[uBone];
			m_axParticles[m_uNumberOfActiveParticles].m_xVelocity = xVel;			
			m_axParticles[m_uNumberOfActiveParticles].m_fAge = 0.0f;
			
			m_uNumberOfActiveParticles++;
			m_fReleaseTimer += 0.01f;
		}
	}

	if( m_bDieOut && (!m_uNumberOfActiveParticles) ) 
	{
		Destroy();
	}

}

void Asura_PFX_Entity_SoulWind::UpdatePosition(const Asura_Animation* pxAnimation, const Asura_Vector_3 xPosition)
{
	const Asura_Hierarchy_Skin* pxSkin = pxAnimation->GetSkin();

	if (pxSkin==NULL) return;

	m_uNumberOfBones = pxSkin->NumberOfBones;


	for( u_int u=0; u<m_uNumberOfBones; u++)
	{
        m_axBonePosition[u] = Asura_Hierarchy_System::GetWorldspaceBonePos( u );
	}

    const unsigned int uFrameNumber = Asura_Timers::GetFrameNumber();
    if ( uFrameNumber - m_uLastFramePosWasUpdated > 1 )
    {
        // if we've not been updated for a while we need to update the particle positions
        m_xPosition = xPosition;
        Update();
    }
    else
    {
	    m_xPosition = xPosition;
    }
    m_uLastFramePosWasUpdated = uFrameNumber;

	m_bRenderThisFrame = true;
}

void Asura_PFX_Entity_SoulWind::Render(void)
{
	if (m_bRenderThisFrame)
	{
		Asura_PFX_Particle_Template*	pxParticleTemplate	= Asura_PFX_System::GetParticleTempByHash(0xDC52944E);	// 0xDC52944E == soul wind pcl

		m_bRenderThisFrame = false;

		if(pxParticleTemplate == NULL)
		{
			Asura_Assert(false, "Could not get soul wind pcl template");
		}
		else
		{
			const Asura_RGBA		xColour(1.0f, 1.0f, 1.0f, 1.0f);
			Asura_PFX_RenderQueue&	xRenderQueue		= Asura_PFX_System::GetRenderQueue();
			void*					pPlatformRenderData	= NULL;

			xRenderQueue.AddParticles(*pxParticleTemplate, m_axParticles, m_uNumberOfActiveParticles, xColour, *this, pPlatformRenderData);
		}
	}
}
