//
// Asura_PFX_Effect.cpp
//
// Created by KOR - 15/07/2004
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"


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

// Asura headers
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "Rendering/Asura_Module.h"
#include "SpecialFX/Asura_PerlinNoise_System.h"
#include "SpecialFX/PFX/Asura_PFX_Classes.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

#ifdef ASURA_TOOLS
#include "Mesh/Asura_CollisionMesh.h"
#include "Rendering/Asura_Render.h"
#include "Misc/Tools_Render.h"
#include "Particles/Tools_Mode_PFX.h"
#endif

// C O N S T A N T S /////////////////////////////////////////////////////////
#define MAX_RECALCULATIONS_BEFORE_ABANDONMENT 30
#define FRAMES_BEFORE_RESTART                 1200
#if ASURA_MAX_NO_OF_CAMERAS != MAX_CAMERAS_FOR_PFX_EFFECT
#error mismatch in max number of cameras
#endif

Asura_PFX_Effect::Asura_PFX_Effect(u_short usPFXFlags)
: m_eState(ASURA_PFX_EFFECT_RUNNING)
, m_usPFXFlags(usPFXFlags)
, m_ucCameraFlags(0xff)
, m_ucCameraFlagsIgnoringFrustum(0xff)
#ifdef ASURA_TOOLS
, m_bSelectedInTools(false)
#endif
{
	// This will set the ID
	Asura_PFX_System::RegisterEffect(this);

#if !defined( ASURA_USE_NEW_VISIBILITY_SYSTEM )

    for ( int i = 0; i < ASURA_MAX_NO_OF_CAMERAS; ++i )
    {
        m_iLastKnownModule[ i ] = -1;
#if defined(ASURA_USE_STREAMING)
		m_uLastKnownZone[i] = ASURA_HASH_ID_UNSET;
#endif //defined(ASURA_USE_STREAMING)
    }

    m_bAbandonModuleList   = false;
	m_bModuleListValid = false;
	m_uPrevCalcFrameNumber = 0;
    m_uCalcInSuccession    = 0;
    m_uRestartFrameNumber  = 0;
#endif

	m_uCameraFlagsFrameStamp = 0;


	m_xBB.Zero();
	m_fBoundingRadius = 0.0f;
	m_bBoundingBoxValid = false;

	m_xDelta.Zero();

#if !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
    // ensure we calculate this first time through
    InvalidateModuleList();
#endif

	m_fSpeedScale = 1.0f;
	m_fSizeScale = 1.0f;
	m_fReleaseRateScale = 1.0f;
#ifdef ASURA_TOOLS
	m_pxRNG = NULL;
#endif // def ASURA_TOOLS
}

Asura_PFX_Effect::~Asura_PFX_Effect()
{
}

void Asura_PFX_Effect::RenderParticles( const Asura_PFX_Particle* const pxParticles,
                                        u_int uNumParticles,
                                        const Asura_PFX_Particle_Template* pxTemplate,
                                        const Asura_RGBA& xLightColour,
										void* pPlatformRenderData
										)
{
	if(GetCameraFlags(true))
	{
#ifndef ASURA_TOOLS
        // Skip templates with missing textures outside of tools builds since
        // we assume they've been loaded and don't do anything to fix up error
        // cases where they haven't been.
        if( pxTemplate->m_iTextureHandle == ASURA_TEXTURE_NONE )
        {
            // NB: We already complain about the missing texture when loading
            // the chunk so an additional assert is probably unnecessary.
            return;
        }
#endif

#ifdef ASURA_TOOLS
		Tools_Mode_PFX::SetParticleEffectBeingRenderedIsSelected(m_bSelectedInTools);
#endif

#ifdef ASURA_SUPPORT_MULTITHREADED_JOB_QUEUE
		Platform_RenderParticles(pxParticles, uNumParticles, pxTemplate, xLightColour, pPlatformRenderData);
#else
		Platform_RenderParticles(pxParticles, uNumParticles, pxTemplate, xLightColour);
#endif
	}
}


// member function - calls the static function
void Asura_PFX_Effect::UpdateParticles( Asura_PFX_Particle* pxParticles, u_int uNumParticles,
                                        const Asura_PFX_Source_Template* pxSourceTemplate,
										const Asura_PFX_Particle_Template* pxParticleTemplate,
										const Asura_Spline<Asura_Vector_3>* pxSpline) const
{
	if (!uNumParticles)
	{
		return;
	}

	Asura_Vector_3 xCollisionPosition, xCollisionDirection, xGravityPosition;
	bool bUseCollisionPlane = false;
	bool bUsePointGravity = false;
	if(pxSourceTemplate->m_bUseParticleDynamics)
	{
		bUseCollisionPlane = GetCollisionPlane(&xCollisionPosition, &xCollisionDirection);
	}
	if(pxSourceTemplate->m_bPointGravity)
	{
		bUsePointGravity = GetGravityPosition(&xGravityPosition);
	}

	UpdateParticles(pxParticles, uNumParticles, pxSourceTemplate, 
		bUseCollisionPlane, xCollisionPosition, xCollisionDirection, 
		bUsePointGravity, xGravityPosition, 
		GetSizeScale(), Asura_Timers::GetGameTimeSlice(), Asura_Timers::GetLocalScaledTime(),
		pxParticleTemplate, pxSpline );
}


void Asura_PFX_Effect::SetBoundingBox(const Asura_Bounding_Box& xBB)
{
	if(xBB != m_xBB || !m_bBoundingBoxValid)
	{
		m_xBB = xBB;
		m_fBoundingRadius = m_xBB.CalculateExtents().Magnitude()*0.5f;
#if !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
		InvalidateModuleList();
#endif
		m_bBoundingBoxValid = true;

		m_uCameraFlagsFrameStamp = 0;

	}
}

#if !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
void Asura_PFX_Effect::InvalidateModuleList()
{
    m_bModuleListValid = false;
}

bool Asura_PFX_Effect::ModuleListIsInvalid() const
{
    return !m_bModuleListValid;
}

bool Asura_PFX_Effect::ModuleListIsValid() const
{
	return m_bModuleListValid;
}

void Asura_PFX_Effect::CalculateModuleList()
{
#if defined(ASURA_USE_STREAMING)
	m_bModuleListValid = m_xModuleList.CalculateModuleList(m_xBB);
#else
    Asura_Module_System::SetLengthOfVariableLengthCullingList( MAX_VARIABLE_LENGTH_CLIPPING_COUNT );
    Asura_Module_System::TestModuleTree( m_xBB, Asura_Module_System::VariableLengthCullingListCallback );
    m_bModuleListValid = Asura_Module_System::FillVariableLengthModuleList( m_ausModuleList );
#endif //defined(ASURA_USE_STREAMING)
}

bool Asura_PFX_Effect::ModuleListIsVisible() const
{
    Asura_Assert(Asura_Module_System::GetNumberOfModules() < USHRT_MAX, "Assumption in clipping code about number of modules is incorrect");

#if defined(ASURA_USE_STREAMING)
	return m_xModuleList.IsVisible();
#else
    bool bVisible = false;
    for ( int i = 0; i < MAX_VARIABLE_LENGTH_CLIPPING_COUNT; ++i )
    {
        if ( USHRT_MAX == m_ausModuleList[ i ] )
        {
            break;
        }
        else if ( Asura_Module_System::IsModuleVisible( m_ausModuleList[ i ] ) )
        {
            bVisible = true;
            break;
        }
    }
    return bVisible;
#endif //defined(ASURA_USE_STREAMING)
}
#endif

u_int Asura_PFX_Effect::GetCameraFlags(bool bFrustumTest)
{
#if defined( ASURA_USE_NEW_VISIBILITY_SYSTEM )
	const unsigned int uFrameNumber = Asura_Timers::GetFrameNumber();

	//have we determined visibility yet?
	if(m_uCameraFlagsFrameStamp != uFrameNumber)
	{
		m_ucCameraFlags = 0;
		m_ucCameraFlagsIgnoringFrustum = 0;

		//check each camera , to see which can see the module
		for( int i=0; i<Asura_Camera::GetNumberOfCameras(); ++i )
		{
			Asura_Camera::SetCameraIndex( i );

			bool bVisible = false;
			if( Asura_Camera::IsCameraActive() && m_usPFXFlags & (1 << i))
			{
    	        if (m_usPFXFlags & ASURA_PFX_EFFECT_IGNORE_MOD_LINKS)
				{
    	        	//module linking ignored , so always visible
    	        	bVisible = true;
				}
    	        else
    	        {
    	            bVisible = m_xVisibilityQuery.IsAABBVisibleFromCurrentCamera( m_xBB );
    	        }

				if ( bVisible )
				{
				    m_ucCameraFlagsIgnoringFrustum |= (1<<i);
					Asura_Vector_3 xCentre = m_xBB.CalculateCentre();
					if(m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX)
					{
						xCentre += *Asura_Camera::GetPosition();
					}
				    if(Asura_Camera::IsSphereInFrustum(xCentre, m_fBoundingRadius))
					{					
						m_ucCameraFlags |= (1<<i);
					}
				}
			}
		}
		m_uCameraFlagsFrameStamp = uFrameNumber;
	}
	return bFrustumTest ? m_ucCameraFlags : m_ucCameraFlagsIgnoringFrustum;

#else // !defined( ASURA_USE_NEW_VISIBILITY_SYSTEM )
    const unsigned int uFrameNumber = Asura_Timers::GetFrameNumber();
	//have we determined visibility yet?
	if(m_uCameraFlagsFrameStamp != uFrameNumber)
	{
		if(!m_bBoundingBoxValid)
		{
			Asura_Assert(m_bBoundingBoxValid,"No culling bounding box set for this effect yet");
			return 0;
		}

    	if ( uFrameNumber == m_uRestartFrameNumber )
    	{
    	    //Some time has passed since we stopped using the module list. Try using it again
			//in the hope that it will stay valid for longer now.
    	    m_uRestartFrameNumber = 0;
    	    m_bAbandonModuleList  = false;
    	}

    	//check for visible module list becoming invalidated
    	if (m_bAbandonModuleList )
    	{
    	    InvalidateModuleList();
    	}
    	else
    	{
    	    if ( ((m_usPFXFlags & ASURA_PFX_EFFECT_IGNORE_MOD_LINKS) == 0) && 
				 ModuleListIsInvalid() )
    	    {
    	        CalculateModuleList();

    	        if ( uFrameNumber == m_uPrevCalcFrameNumber+1 )
    	        {
    	            //If the module list has been invalidated for too many frames in a row (moving particle
					//system most likely) , then give up on it and resort to doing module visibility tests
					//each 8 frames instead
    	            if ( ++m_uCalcInSuccession == MAX_RECALCULATIONS_BEFORE_ABANDONMENT )
    	            {
    	                m_uCalcInSuccession   = 0;
    	                m_uRestartFrameNumber = uFrameNumber + FRAMES_BEFORE_RESTART;
    	                m_bAbandonModuleList  = true;
    	            }
    	        }

    	        m_uPrevCalcFrameNumber = uFrameNumber;
    	    }
    	}

		unsigned char ucOldFlags = m_ucCameraFlagsIgnoringFrustum;

		m_ucCameraFlags = 0;
		m_ucCameraFlagsIgnoringFrustum = 0;

		//check each camera , to see which can see the module
		for( int i=0; i<Asura_Camera::GetNumberOfCameras(); ++i )
		{
			Asura_Camera::SetCameraIndex( i );

			bool bVisible = false;
			if( Asura_Camera::IsCameraActive() &&
				m_usPFXFlags & (1 << i))
			{
    	        //First check against linked in modules
    	        if (m_usPFXFlags & ASURA_PFX_EFFECT_IGNORE_MOD_LINKS)
				{
    	        	//module linking ignored , so always visible
    	        	bVisible = true;
				}
    	        else if ( ModuleListIsValid() )
    	        {
    	            bVisible = ModuleListIsVisible();
    	        }
    	        else
    	        {
    	            //Given up on the module list , so do a full test every 8 frames
					//For the other frames we just use the last remembered value
    	            if ( ( uFrameNumber & 0x00000007 ) == ( m_uEffectID & 0x00000007 ) )
    	            {
					#if defined(ASURA_USE_STREAMING)
						bVisible = Asura_Module_System::DoesBBIntersectVisibleModule( m_xBB, &m_iLastKnownModule[i], &m_uLastKnownZone[i] );
					#else
    	                bVisible = Asura_Module_System::DoesBBIntersectVisibleModule( m_xBB, &m_iLastKnownModule[ i ] );
					#endif //defined(ASURA_USE_STREAMING)
    	            }
					else
					{
						bVisible = (ucOldFlags & (1<<i)) ? true : false;
					}

    	        }
				if ( bVisible )
				{
					const u_int uFlag = (1<<i);
				    m_ucCameraFlagsIgnoringFrustum |= uFlag;
					Asura_Bounding_Box xBB = m_xBB;
					if(m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX)
					{
						xBB.Displace(*Asura_Camera::GetPosition());
					}
					if(Asura_Camera::IsAABBInFrustum(xBB))
					{
						m_ucCameraFlags |= uFlag;
					}
				}
			}
		}
		m_uCameraFlagsFrameStamp = uFrameNumber;
	}

	return bFrustumTest ? m_ucCameraFlags : m_ucCameraFlagsIgnoringFrustum;
#endif // !defined( ASURA_USE_NEW_VISIBILITY_SYSTEM )
}


void Asura_PFX_Effect::SetIgnoreModuleLinking(bool bIgnore)
{
	if(bIgnore)
	{
		m_usPFXFlags |= ASURA_PFX_EFFECT_IGNORE_MOD_LINKS;
	}
	else
	{
		m_usPFXFlags &=~ ASURA_PFX_EFFECT_IGNORE_MOD_LINKS;
	}
}

#ifdef ASURA_TOOLS
void Asura_PFX_Effect::RenderVisibility()
{
	// Render culling BB
	Asura_Matrix_3x3 xOri;
	xOri.Identity();
	if(m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX)
	{
		Asura_Render::RenderBB(m_xBB, *Asura_Camera::GetPosition(), xOri, 0xff00ff00);
		return;
	}
	Asura_Render::RenderBB(m_xBB, Asura_Vector_3(0.f, 0.f, 0.f), xOri, 0xff00ff00);

#if !defined(ASURA_USE_NEW_VISIBILITY_SYSTEM )
	// Render module list	
	if(ModuleListIsValid())
	{
	#if defined(ASURA_USE_STREAMING)
		m_xModuleList.RenderVisibility();
	#else
		for ( int i = 0; i < MAX_VARIABLE_LENGTH_CLIPPING_COUNT; ++i )
		{
			if ( USHRT_MAX == m_ausModuleList[ i ] )
			{
				break;
			}
			else
			{
				Tools_Render::RenderModuleOutline(m_ausModuleList[ i ], 0xff00ffff, true);
			}
		}
	#endif //defined(ASURA_USE_STREAMING)
	}
#endif
}
#endif //ASURA_TOOLS

void Asura_PFX_Effect::SetDesiredCameraFlags( u_char ucFlags ) 
{
	// blank existing camera flags
	m_usPFXFlags &= ~ASURA_PFX_EFFECT_CAMERA_FLAGS;

	// set the ones we actually want
	m_usPFXFlags |= ucFlags;
}

ASURA_PFX_RENDER_TYPE Asura_PFX_Effect::GetRenderType() const
{
	if(m_usPFXFlags & ASURA_PFX_EFFECT_HUD_EFFECT)
	{
		return ASURA_PFX_RENDER_HUD;
	}
	else if(m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX)
	{
		return ASURA_PFX_RENDER_SKYBOX;
	}
	else if (m_usPFXFlags & ASURA_PFX_EFFECT_PROJECT_EFFECT)
	{
		return ASURA_PFX_RENDER_PROJECT;
	}
	else if (m_usPFXFlags & ASURA_PFX_EFFECT_WATER_DISTORTER)
	{
		return ASURA_PFX_RENDER_WATER_DISTORTER;
	}
	return ASURA_PFX_RENDER_NORMAL;
}

void Asura_PFX_Effect::SetSkyboxEffect(bool bSkybox)
{
	if(bSkybox)
	{
		m_usPFXFlags |= ASURA_PFX_EFFECT_SKYBOX;
	}
	else
	{
		m_usPFXFlags &=~ ASURA_PFX_EFFECT_SKYBOX;
	}
}

void Asura_PFX_Effect::SetHUDEffect(bool bHUD)
{
	if(bHUD)
	{
		m_usPFXFlags |= ASURA_PFX_EFFECT_HUD_EFFECT;
	}
	else
	{
		m_usPFXFlags &=~ ASURA_PFX_EFFECT_HUD_EFFECT;
	}
}

void Asura_PFX_Effect::SetWaterDistorterEffect(bool bWaterDistorter)
{
	if(bWaterDistorter)
	{
		m_usPFXFlags |= ASURA_PFX_EFFECT_WATER_DISTORTER;
	}
	else
	{
		m_usPFXFlags &=~ ASURA_PFX_EFFECT_WATER_DISTORTER;
	}
}

void Asura_PFX_Effect::SetForceRenderOnMainThread(bool bSet)
{
	if(bSet)
	{
		m_usPFXFlags |= ASURA_PFX_EFFECT_FORCE_RENDER_ON_MAIN_THREAD;
	}
	else
	{
		m_usPFXFlags &=~ ASURA_PFX_EFFECT_FORCE_RENDER_ON_MAIN_THREAD;
	}
}

void Asura_PFX_Effect::SetReleaseRateScale(float fScale)
{
	Asura_Assert(fScale>=0.0f && fScale <=1.0f,"Invalid scale value for release rate");	
	m_fReleaseRateScale = Asura_Maths::ClampToRange(fScale,0.0f,1.0f);	
}



/// End of file. ///
