
//
// Asura_Platform_PFX.cpp
//
// Created by KOR - 16/07/2004
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//
#include "Core/Asura.h"

#include "Core/Asura_HalfFloat.h"

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

// Asura headers
#include "Debugging/Asura_Debug_Timers.h"
#include "Debugging/Asura_Logfile.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_Viewport.h"
#include "Light/Asura_GI_Solution.h"
#include "SpecialFX/PFX/Asura_PFX_Classes.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

#include "Rendering/Asura_Direct3D.h"
#include "Rendering/Asura_Direct3D_Caps.h"
#include "Rendering/Asura_Direct3D_Shaders.h"
#include "Rendering/Asura_Direct3D_BackBufferCapture.h"
#include "Rendering/Asura_Direct3D_RenderTargetManager.h"
#include "Rendering/Asura_Direct3D_Effects_System.h"
#include "Rendering/Asura_Direct3D_Fog.h"

#include "Camera/Asura_Camera.h"
#include "Core/Asura_Timers.h"
#include "Core/Asura_TextureManagement.h"

#ifdef ASURA_USE_FASTPATH_PFX
#include "SpecialFX/Asura_Platform_PFX_System_FastPath.h"
#endif

#ifdef ASURA_TARGET_SM3


#ifdef ASURA_TOOLS
#include "Particles/Tools_Mode_PFX.h"
#endif

static float g_fHEAT_HAZE_OFFSET_V = 0.0f;

#ifndef ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD
// The position beyond which a particle position is considered to be suspicious.
#define	ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD  ( 6000.0f )
#endif 

// Texture used when we need an intermediate power of two render target
// rather than using the backbuffer as a texture directly. (Only needed
// with certain hardware.)
static Direct3D_Texture* g_pxPowerOfTwoBackbufferTexture = NULL;

#ifdef ASURA_TOOLS
static Asura_RandomGenerator* g_pxHeatHazeRNG = 0;
#endif

static void CreatePowerOfTwoBackbufferTexture()
{
	Asura_Assert( !g_pxPowerOfTwoBackbufferTexture, "Create called when texture already exists" );

	u_int uRTWidth = 1 << ((int)(logf((float)Asura_Direct3D::GetPresentParameters().BackBufferWidth) / logf(2.f)));
	u_int uRTHeight = 1 << ((int)(logf((float)Asura_Direct3D::GetPresentParameters().BackBufferWidth) / logf(2.f)));

	Asura_Direct3D::D3DDevice->CreateTexture( uRTWidth, uRTHeight, 1, D3DUSAGE_RENDERTARGET, Asura_Direct3D::GetPresentParameters().BackBufferFormat, D3DPOOL_DEFAULT, &g_pxPowerOfTwoBackbufferTexture, NULL );

	Asura_Assert( g_pxPowerOfTwoBackbufferTexture, "Could not create texture" );
}

static void DestroyPowerOfTwoBackbufferTexture()
{
	if( g_pxPowerOfTwoBackbufferTexture )
	{
		g_pxPowerOfTwoBackbufferTexture->Release();
		g_pxPowerOfTwoBackbufferTexture = NULL;
	}
}


struct PFXVertex
{
	float X, Y, Z, CrossFadeBlendWeight;
	D3DXCOLOR Diffuse;
	float U, V;
	float U2, V2;
};


// Decls/Effects/Handles for the old (slow path) PFX renderer
// ------------------------------------------------------------------------------------------
const Direct3D_VertexElement axVERTEX_DECLARATION[] =
{
	{ 0,  0, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_POSITION, 0 },
	{ 0, 16, D3DDECLTYPE_FLOAT4, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_COLOR,    0 },
	{ 0, 32, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 0 },
	{ 0, 40, D3DDECLTYPE_FLOAT2, D3DDECLMETHOD_DEFAULT, D3DDECLUSAGE_TEXCOORD, 1 },
	D3DDECL_END()
};

static Direct3D_VertexDeclaration* g_pxPFXVertexDecl = NULL;

static Asura_Direct3D_Effect* g_pxPFXEffect = NULL;

static D3DXHANDLE g_xTextureHandle = 0;
static D3DXHANDLE g_xCrossfadeTextureFramesHandle = 0;
static D3DXHANDLE g_xHeatHazeCaptureTextureHandle = 0;
static D3DXHANDLE g_xOneOverScreenDimensionsHandle = 0;

static Asura_Direct3D_FogEffectParameters g_xFogParameters;

static void CaptureEffect(void);

#if ASURA_LOAD_EFFECT_FILES == 1
static void OnEffectLoaded(Asura_Direct3D_Effect* pxEffect)
{
	CaptureEffect();
}

static void OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect)
{
	g_pxPFXEffect = NULL;	
}
#endif


static void CaptureEffect(void)
{
	g_pxPFXEffect = Asura_Direct3D_Effects_System::GetEffect("PFX_SlowPath");	;

	if (g_pxPFXEffect)
	{

#if ASURA_LOAD_EFFECT_FILES == 1
		g_pxPFXEffect->SetOnLoadCallback(OnEffectLoaded);
		g_pxPFXEffect->SetOnUnloadCallback(OnEffectUnloaded);
#endif	

		g_xTextureHandle				= g_pxPFXEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xTexture" );
		g_xCrossfadeTextureFramesHandle = g_pxPFXEffect->GetD3DEffect()->GetParameterByName( NULL, "g_bCrossfadeTextureFrames" );		
		g_xHeatHazeCaptureTextureHandle = g_pxPFXEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xHeatHazeCaptureTexture" );	
		g_xOneOverScreenDimensionsHandle= g_pxPFXEffect->GetD3DEffect()->GetParameterByName( NULL, "g_xOneOverScreenDimensions" );

		g_xFogParameters.Capture(g_pxPFXEffect);
	}
}

// Number of particles in slow path Vertex Buffer
static int const g_iParticleVertsNumOrig = 256;
static LPDIRECT3DVERTEXBUFFER9 g_pxParticleVertexBuffer = NULL;

static bool PostD3DReset( void* )
{

	// Create a vertex buffer for batching up slow path particles
	HRESULT xResult = Asura_Direct3D::D3DDevice->CreateVertexBuffer( g_iParticleVertsNumOrig * sizeof( PFXVertex ) * 6, D3DUSAGE_WRITEONLY | D3DUSAGE_DYNAMIC, 0, D3DPOOL_DEFAULT, &g_pxParticleVertexBuffer, NULL );
	ASURA_D3D_OK( xResult );
	(void) xResult;	// Prevent warning if ASURA_D3D_OK is not enabled

	return true;

}

static bool PreD3DReset( void* )
{

	// Delete the batching vertex buffer
	if ( g_pxParticleVertexBuffer )
	{
		g_pxParticleVertexBuffer->Release();
		g_pxParticleVertexBuffer = NULL;
	}

	return true;

}

void Asura_PFX_System::Platform_Initialise( void )
{
	ASURA_CPU_PROFILE_SCOPED_TIMER( "Asura_PFX_System::Platform_Initialise");

#ifdef ASURA_USE_FASTPATH_PFX
	Asura_Platform_PFX_System_FastPath::Initialise();
#endif

	// JAT 01/10/07 - when doing batch exporting of levels sometimes we don't have a D3D device to use
	if ( NULL == Asura_Direct3D::D3DDevice )
	{
		return;
	}

	// Do we need an intermediate power of two texture to use the backbuffer as a texture?
	if( Asura_Direct3D_Caps::IsNonPow2Conditional() )
	{
		// create the texture, and add callbacks to cope with Direct3D being reset
		CreatePowerOfTwoBackbufferTexture();
		Asura_Direct3D_RenderTargetManager::AddResetCallbacks( DestroyPowerOfTwoBackbufferTexture, CreatePowerOfTwoBackbufferTexture );
	}


#if defined ASURA_ASSERT
	const HRESULT xResult =
#endif
		Asura_Direct3D::D3DDevice->CreateVertexDeclaration( axVERTEX_DECLARATION, &g_pxPFXVertexDecl );

	Asura_Assert( SUCCEEDED( xResult ) && g_pxPFXVertexDecl, "Failed to create PFX vertex declaration." );

	CaptureEffect();

	// Register D3D reset callbacks
	Asura_Direct3D::AddPreResetCallback( PreD3DReset, NULL);
	Asura_Direct3D::AddPostResetCallback( PostD3DReset, NULL);

	// Allocate vertex buffer
	PreD3DReset( NULL );
	PostD3DReset( NULL );

}

void Asura_PFX_System::Platform_Reset( void )
{

}

void Asura_PFX_System::Platform_Shutdown( void )
{
#ifdef ASURA_USE_FASTPATH_PFX
	Asura_Platform_PFX_System_FastPath::Shutdown();
#endif

	if( Asura_Direct3D_Caps::IsNonPow2Conditional() )
	{
		Asura_Direct3D_RenderTargetManager::RemoveResetCallbacks( DestroyPowerOfTwoBackbufferTexture, CreatePowerOfTwoBackbufferTexture );
		DestroyPowerOfTwoBackbufferTexture();
	}

	// JAT 02/10/07 - stop it crashing if the vertex declaration didn't get created e.g. during a batch export of lighting
	if ( g_pxPFXVertexDecl )
	{	
		g_pxPFXVertexDecl->Release();
		g_pxPFXVertexDecl = NULL;
	}

	// Effect system destroys the shader
	g_pxPFXEffect = NULL;

	// Unregister D3D reset callbacks
	Asura_Direct3D::RemovePostResetCallback( PostD3DReset, NULL);
	Asura_Direct3D::RemovePreResetCallback( PreD3DReset, NULL);

	// Delete vertex buffer
	PreD3DReset( NULL );

}


DEBUGVAR u_int		dbg_uAlphaTestRefValue = 1;

void Asura_PFX_System::Platform_BeginRender()
{
#if ASURA_D3D_ENABLE_PIX
	const wchar_t* const wszString = s_bIsRenderingHeatHazeSources ? L"PFX Render (Heat Haze)"
		: GetCurrentRenderType() == ASURA_PFX_RENDER_HUD ? L"PFX Render (HUD)"
		: GetCurrentRenderType() == ASURA_PFX_RENDER_SKYBOX ? L"PFX Render (Skybox)"
		: GetCurrentRenderType() == ASURA_PFX_RENDER_PROJECT ? L"PFX Render (Project)"
		: GetCurrentRenderType() == ASURA_PFX_RENDER_WATER_DISTORTER ? L"PFX Render (Water Distorter)"
		: L"PFX Render (Normal)";
#endif
	D3DPERFCALL(BeginEvent(0xff885577, wszString));

	Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE1, 0 );

	Asura_Render::CurrentStates.RequestAlphaTest(true, dbg_uAlphaTestRefValue);
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

	if( s_bIsRenderingHeatHazeSources )
	{
		// Assume that the backbuffer capture is dirty - we just rendered a bunch of particles to it
		Asura_Direct3D_BackBufferCapture::CaptureCurrentBackBuffer();
		Asura_Direct3D_BackBufferCapture::AdviseNextFrame();
		Asura_Render::AllowFullScreenRendering();
		Asura_Render::CurrentStates.RequestTranslucencyMode(ASURA_TRANSLUCENCY_NORMAL);
	}
}

void Asura_PFX_System::Platform_EndRender()
{
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(true);

	Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE1, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA);
	Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED | D3DCOLORWRITEENABLE_GREEN | D3DCOLORWRITEENABLE_BLUE | D3DCOLORWRITEENABLE_ALPHA);

	if( GetCurrentRenderType() == ASURA_PFX_RENDER_SKYBOX )
	{
		// If we were rendering skybox particles, ensure we rebind all the deferred render targets.
		// Everything except the prelighting buffer will have been switched off by the fast path
		// renderer
		if( Asura_Direct3D::GetDeferredRenderingTarget() )
		{
			Asura_Direct3D::GetDeferredRenderingTarget()->Bind();
		}
	}
	D3DPERFCALL(EndEvent());
}

bool Asura_PFX_System::Platform_SupportsHeatHaze()
{
	return true;
}



// (SB - 22/02/2008) Now we are using a vertex buffer to batch up particles this is used for locking the
// vertex buffer
static PFXVertex* xParticleVertsOrig = NULL;

void Asura_PFX_Effect::Platform_RenderParticles( const Asura_PFX_Particle* pxParticles,
												u_int uNumParticles,
												const Asura_PFX_Particle_Template* pxTemplate,
												const Asura_RGBA& xLightColour 
#ifdef ASURA_SUPPORT_MULTITHREADED_JOB_QUEUE
												,void* pPlatformRenderData
#endif
												) const
{
	// It is valid to pass in zero particles (source could just have started or ended. Simply return)
	if(!uNumParticles) return;

	Asura_Assert(pxParticles, "Null particle pointer given");
	Asura_Assert(pxTemplate, "Null particle template pointer given");
	if(!pxTemplate || !pxParticles) return;

	// Make sure vertex buffer is valid
	Asura_Assert( g_pxParticleVertexBuffer, "Failed to allocate slow path PFX vertex buffer." );
	if (!g_pxParticleVertexBuffer) return;


#ifdef ASURA_USE_FASTPATH_PFX
	// Handle custom PFX effects, that have custom update functions so won't be handled
	// by the multithreaded update, but can be rendered using the fast path...

#ifdef ASURA_SUPPORT_MULTITHREADED_JOB_QUEUE
	if (pPlatformRenderData == NULL)
	{			
		pPlatformRenderData = Asura_Platform_PFX_System_FastPath::GetInstanceBufferArea(uNumParticles);			

		if (pPlatformRenderData)
		{
			Asura_Platform_PFX_System_FastPath::UpdateInstanceBuffer(pxTemplate, pxParticles, (Asura_Platform_PFXInstanceData *)pPlatformRenderData, uNumParticles);				
		}
	}
#endif

	// Can we use the fast path renderer? 	
	// DMR - 02/08/07 - Skybox particles forced through the slow path, as they're rendered early, before we're finished with MRT rendering
	if  (!pxTemplate->m_bUseMotionBlur &&
		!pxTemplate->m_b3DRotation &&
		!pxTemplate->m_bSpriteRotation &&
		!(m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX) &&
		!(m_usPFXFlags & ASURA_PFX_EFFECT_FORCE_RENDER_ON_MAIN_THREAD) &&
		!(pxTemplate->m_eTranslucency == ASURA_TRANSLUCENCY_SPECIAL_HEATHAZE) &&
		pPlatformRenderData)
	{
		Asura_Platform_PFX_System_FastPath::RenderParticles(this,
			pxParticles,
			uNumParticles,
			pxTemplate,
			xLightColour,
			m_usPFXFlags,
			m_ucCameraFlags,
			pPlatformRenderData);
		return;	
	}
#endif

	Asura_Direct3D::D3DDevice->SetRenderState(D3DRS_COLORWRITEENABLE, D3DCOLORWRITEENABLE_RED|D3DCOLORWRITEENABLE_GREEN|D3DCOLORWRITEENABLE_BLUE);
	Asura_Direct3D::D3DDevice->SetRenderState( D3DRS_COLORWRITEENABLE1, 0 );


	Direct3D_BaseTexture* pxTexture = Asura_Direct3D::GetDirect3DTextureByIndex( pxTemplate->m_iTextureHandle);
	Asura_Assert( NULL != pxTexture, "Particle Texture not found");
	if( NULL != pxTexture)
	{
		D3DRESOURCETYPE ResType = pxTexture->GetType();
		if (ResType == D3DRTYPE_TEXTURE)
		{
			g_pxPFXEffect->GetD3DEffect()->SetTexture( g_xTextureHandle, pxTexture );
		}
	}


	g_pxPFXEffect->GetD3DEffect()->SetBool( g_xCrossfadeTextureFramesHandle, pxTemplate->m_bCrossfadeTextureFrames );



	// Set up heat haze params
	if (Asura_PFX_System::IsRenderingHeatHazeSources())
	{	
		D3DXVECTOR4 xVec;
		xVec.x = 1.0f / static_cast<float>(Asura_Viewport::GetWidth());
		xVec.y = 1.0f / static_cast<float>(Asura_Viewport::GetHeight());
		g_pxPFXEffect->GetD3DEffect()->SetVector(g_xOneOverScreenDimensionsHandle, &xVec);
		g_pxPFXEffect->GetD3DEffect()->SetTexture( g_xHeatHazeCaptureTextureHandle, Asura_Direct3D_BackBufferCapture::GetCaptureTexture());
	}

	// Set up shader params for fogging
	g_xFogParameters.Apply(pxTemplate->m_bUseFogging, pxTemplate->m_eTranslucency );




	Asura_Render::CurrentStates.RequestZBufferCompareMode((m_usPFXFlags & ASURA_PFX_EFFECT_ALWAYS_ON_TOP) != 0 ? ASURA_ZBUFFERCMP_ALWAYS : ASURA_ZBUFFERCMP_LESSEQUAL);

	Asura_Render::CurrentStates.RequestTranslucencyMode(pxTemplate->m_eTranslucency);
	// Solid particles write to the z-buffer.
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(pxTemplate->m_eTranslucency == ASURA_TRANSLUCENCY_OFF);

	Asura_AVector4 axVertices[4];

	D3DXMATRIX xMatView, xMatProj;
	Asura_Direct3D::D3DDevice->GetTransform(D3DTS_VIEW, &xMatView);
	Asura_Direct3D::D3DDevice->GetTransform(D3DTS_PROJECTION, &xMatProj);


	Asura_Direct3D::SetViewingArea();

	Asura_AMatrix4x4 m44ViewProj, m44View, m44Proj;

	m44View.LoadColsUnaligned(&Asura_Direct3D::GetViewMatrix().m[0][0]);
	m44Proj.LoadColsUnaligned(&Asura_Direct3D::GetProjectionMatrix().m[0][0]);

	m44ViewProj.LoadColsUnaligned(&Asura_Direct3D::GetViewProjectionMatrix().m[0][0]);

	for( int iCam=0; iCam<Asura_Camera::GetNumberOfCameras(); ++iCam )
	{
		if((m_ucCameraFlags & (1<<iCam)) != 0)
		{
			Asura_Camera::SetCameraIndex( iCam );

			if(Asura_Camera::IsCameraActive())
			{
				Asura_Render::ApplyCameraSettings();

				const Asura_Vector_3& xCameraPos3 = *Asura_Camera::GetPosition();
				const Asura_AVector4 xCameraPos4(xCameraPos3, 1.0f);
				PFXVertex*	pxParticleVerts;
				u_int				uParticlesLeft	= uNumParticles;
				u_int				uParticlesToRender;
				u_int				uParticleCount;

				while ( uParticlesLeft )
				{
					uParticleCount = uParticlesLeft;
					if ( uParticleCount > g_iParticleVertsNumOrig )
					{
						uParticleCount	= g_iParticleVertsNumOrig;
					}

					uParticlesToRender	= uParticleCount;
					g_pxParticleVertexBuffer->Lock( 0, 0, (void**)(&xParticleVertsOrig), D3DLOCK_DISCARD );
					pxParticleVerts		= &xParticleVertsOrig[0];

					for(u_int uParticle = 0 ; uParticle < uParticleCount ; ++uParticle)
					{
						const	Asura_PFX_Particle*	pxParticle = &pxParticles[uParticle];
						Asura_AVector4		xPosition(pxParticle->m_xPosition, 1.0f);

						if ( m_usPFXFlags & ASURA_PFX_EFFECT_SKYBOX ) xPosition += xCameraPos4;

						Asura_RGBA xColour = pxTemplate->m_xColour.GetValueAtTime_Discrete(pxParticle->m_fAge);

						// (SB - 01/05/07) Allow project to override particle colour
						if (Asura_PFX_System::GetParticleColourOverride() != NULL)
						{
							Asura_PFX_System::GetParticleColourOverride()(this, pxTemplate, uParticle, xColour);
						}

						if(pxTemplate->m_bUseLighting)
						{
							xColour.m_fR *= xLightColour.m_fR;
							xColour.m_fG *= xLightColour.m_fG;
							xColour.m_fB *= xLightColour.m_fB;
						}

						if(pxTemplate->m_bNearCameraFade || pxTemplate->m_bFarCameraFade)
						{
							float fDist = (xCameraPos4 - xPosition).Magnitude();
							//fade when near camera
							if(fDist < pxTemplate->m_fNearFadeDistance && pxTemplate->m_bNearCameraFade)
							{
								if( (pxTemplate->m_fNearFadeDistance - pxTemplate->m_fNearZeroDistance) == 0.0f )
								{
									xColour.m_fA = 0.f;
								}
								else
								{
									xColour.m_fA *= (fDist - pxTemplate->m_fNearZeroDistance) / (pxTemplate->m_fNearFadeDistance - pxTemplate->m_fNearZeroDistance);
								}						
							}
							//fade when far from camera
							if(fDist > pxTemplate->m_fFarFadeDistance && pxTemplate->m_bFarCameraFade)
							{
								if( (pxTemplate->m_fFarFadeDistance - pxTemplate->m_fFarZeroDistance) == 0.0f )
								{
									xColour.m_fA = 0.f;
								}
								else
								{
									xColour.m_fA *= (fDist - pxTemplate->m_fFarZeroDistance) / (pxTemplate->m_fFarFadeDistance - pxTemplate->m_fFarZeroDistance);
								}				
							}
						}
						// KOR - 15/11/2005 - early out on zero alpha
						if(xColour.m_fA <= 0.f)
						{
							--uParticlesToRender;
							continue;
						}

						float fSize = pxTemplate->m_xSize.GetValueAtTime_Discrete(pxParticle->m_fAge);
						if( pxTemplate->m_bUseSizeScale )
						{
							fSize *= GetSizeScale();
						}

						fSize *= (1.f - pxParticle->m_fSeed * pxTemplate->m_xSizeVariation.GetValueAtTime_Discrete(pxParticle->m_fAge));
						const float fAgeInSeconds = pxParticle->m_fAge * pxTemplate->GetLifetimeFromSeed(pxParticle->m_fSeed);
						const float fLength = Asura_Maths::Min(pxTemplate->m_fBlurLength, fAgeInSeconds);

						Asura_AVector4 xDeltaX;
						Asura_AVector4 xDeltaY;

						float fTheta;

						if(pxTemplate->m_bFixedRotation)
						{
							fTheta = Asura_Maths::DegToRad(pxTemplate->m_fFixedRotation);
							if(pxTemplate->m_uNumFixedRotations > 1)
							{
								int iRotation = Asura_Maths::FloatToInt_Chop(pxParticle->m_fSeed * pxTemplate->m_uNumFixedRotations);
								fTheta += (iRotation * Asura_Maths::Pi * 2.f) / pxTemplate->m_uNumFixedRotations;
							}
						}
						else if(pxTemplate->m_b3DRotation || !pxTemplate->m_bUseMotionBlur)
						{
							// Need a rotation
							if(pxTemplate->m_bUseRandomRotation)
							{
								fTheta = ((Asura_Timers::GetGameTimeScale() == 0.0f) ? 0.0f : GetRNG()->GetFloat()) * Asura_Maths::Pi * 2.0f;
							}
							else
							{
#ifdef ASURA_TOOLS
								fTheta = (pxParticle->m_fSeed + pxTemplate->m_xRotation.GetAreaUnderGraph(pxParticle->m_fAge)) * Asura_Maths::Pi * 2.f;
#else
								fTheta = (pxParticle->m_fSeed + pxTemplate->m_xRotation.GetValueAtTime_Discrete(pxParticle->m_fAge)) * Asura_Maths::Pi * 2.f;
#endif
							}

							if(pxTemplate->m_bMirrorRotation)
							{
								const int* piRandom = reinterpret_cast<const int*>(&pxParticle->m_fSeed);
								if(*piRandom % 2)
								{
									fTheta = -fTheta;
								}
							}
						}

						const Asura_Matrix_3x3* pxOrientation = GetOrientation();
						if(pxTemplate->m_b3DRotation)
						{
							// KOR - 9/9/2004 - not optimal, and possibly not the way we want to do 3D rotation
							// (intended for effects such as glass shards or scattering debris). Need artist feedback to improve.
							const Asura_Euler_3 xEuler(fTheta, fTheta, 0.f);
							Asura_Matrix_3x3 xMatrix;
							xEuler.ToMatrix(xMatrix);
							xDeltaX = Asura_AVector4(xMatrix.e11, xMatrix.e21, xMatrix.e31, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(xMatrix.e12, xMatrix.e22, xMatrix.e32, 0.0f) * fSize;
						}
						else if(pxTemplate->m_bAlignEffectX && pxOrientation)
						{
							xDeltaX = Asura_AVector4(pxOrientation->e12, pxOrientation->e22, pxOrientation->e32, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxOrientation->e13, pxOrientation->e23, pxOrientation->e33, 0.0f) * fSize;
						}
						else if(pxTemplate->m_bAlignEffectY && pxOrientation)
						{
							xDeltaX = Asura_AVector4(pxOrientation->e11, pxOrientation->e21, pxOrientation->e31, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxOrientation->e13, pxOrientation->e23, pxOrientation->e33, 0.0f) * fSize;
						}
						else if(pxTemplate->m_bAlignEffectZ && pxOrientation)
						{
							xDeltaX = Asura_AVector4(pxOrientation->e11, pxOrientation->e21, pxOrientation->e31, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxOrientation->e12, pxOrientation->e22, pxOrientation->e32, 0.0f) * fSize;
						}
						else if(pxTemplate->m_bBillboardJustY && pxOrientation)
						{
							const Asura_Matrix_3x3* pxCameraOrientation = Asura_Camera::GetOrientation();
							xDeltaX = Asura_AVector4(pxCameraOrientation->e11,pxCameraOrientation->e21,pxCameraOrientation->e31, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxOrientation->e13, pxOrientation->e23, pxOrientation->e33, 0.0f) * fSize;
						}
						else if(pxTemplate->m_bBillboardJustX && pxOrientation)
						{
							const Asura_Matrix_3x3* pxCameraOrientation = Asura_Camera::GetOrientation();
							xDeltaX = Asura_AVector4(pxOrientation->e12, pxOrientation->e22, pxOrientation->e32, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxCameraOrientation->e12,pxCameraOrientation->e22,pxCameraOrientation->e32, 0.0f) * fSize;
						}
						else
						{
							const Asura_Matrix_3x3* pxCameraOrientation = Asura_Camera::GetOrientation();
							xDeltaX = Asura_AVector4(pxCameraOrientation->e11,pxCameraOrientation->e21,pxCameraOrientation->e31, 0.0f) * fSize;
							xDeltaY = Asura_AVector4(pxCameraOrientation->e12,pxCameraOrientation->e22,pxCameraOrientation->e32, 0.0f) * fSize;
						}

						// JGP - moved the getting of frames to here


						// get the texture frame(s) for this particle
						const Asura_PFX_TextureFrame* pxTextureFrame;
						const Asura_PFX_TextureFrame* pxTextureFrame2;
						float fCrossFadeBlendWeight = 0.0f;
						if (pxTemplate->m_bCrossfadeTextureFrames)
						{
							pxTemplate->GetTextureFramesForCrossfadeAtTime(pxParticle->m_fAge, pxTextureFrame, pxTextureFrame2, fCrossFadeBlendWeight);

						}
						else
						{
							fCrossFadeBlendWeight = 0.0f;
							pxTextureFrame = pxTemplate->GetTextureFrameAtTime(pxParticle->m_fAge);
							pxTextureFrame2 = NULL;
						}

						// JGP aspect ratio from UV

						if ( pxTemplate->m_bUseUVAsAspect )
						{

							const float fDU = pxTextureFrame->m_afTextureUVs[ 2 ] - pxTextureFrame->m_afTextureUVs[ 0 ];
							const float fDV = pxTextureFrame->m_afTextureUVs[ 3 ] - pxTextureFrame->m_afTextureUVs[ 1 ];
							const float fAspect = ( fDV ) ? fDU / fDV : 1.0f;
							xDeltaX *= fAspect;
						}
						/// Kevin Lea - 27/03/2002 11:43:15 - code to draw particle stretched along its path
						/// ucky and needs rewriting
						if (pxTemplate->m_bUseMotionBlur)
						{
							const Asura_AVector4 xPrevPosition = xPosition - (Asura_AVector4(pxParticle->m_xVelocity, 0.0f) * fLength);
							Asura_AVector4 xBeta;

							xBeta = xPosition - xPrevPosition;
							if( xBeta.MagnitudeSquared() > (0.1f*0.1f) )
							{
								xBeta.Normalise();
								xBeta*=0.1f;
							}

							if (Asura_Maths::Abs(xBeta * xDeltaX) > Asura_Maths::Abs(xBeta*xDeltaY))
							{
								if (xBeta * xDeltaX > 0)
								{
									axVertices[0] = xPrevPosition	- xBeta - xDeltaX - xDeltaY; 
									axVertices[1] = xPosition		+ xBeta + xDeltaX - xDeltaY;
									axVertices[2] = xPosition		+ xBeta + xDeltaX + xDeltaY;
									axVertices[3] = xPrevPosition	- xBeta - xDeltaX + xDeltaY;
								}
								else
								{
									axVertices[0] = xPosition		+ xBeta - xDeltaX - xDeltaY; 
									axVertices[1] = xPrevPosition	- xBeta + xDeltaX - xDeltaY;
									axVertices[2] = xPrevPosition	- xBeta + xDeltaX + xDeltaY;
									axVertices[3] = xPosition		+ xBeta - xDeltaX + xDeltaY;
								}
							}
							else
							{
								if (xBeta * xDeltaY > 0)
								{
									axVertices[0] = xPrevPosition	- xBeta - xDeltaX - xDeltaY; 
									axVertices[1] = xPrevPosition	- xBeta + xDeltaX - xDeltaY;
									axVertices[2] = xPosition		+ xBeta + xDeltaX + xDeltaY;
									axVertices[3] = xPosition		+ xBeta - xDeltaX + xDeltaY;
								}
								else
								{
									axVertices[0] = xPosition		+ xBeta - xDeltaX - xDeltaY; 
									axVertices[1] = xPosition		+ xBeta + xDeltaX - xDeltaY;
									axVertices[2] = xPrevPosition	- xBeta + xDeltaX + xDeltaY;
									axVertices[3] = xPrevPosition	- xBeta - xDeltaX + xDeltaY;
								}
							}
						}
						else 
						{
							if (fTheta!=0.f)
							{
								const float fSin = Asura_Maths::Sine(fTheta);
								const float fCos = Asura_Maths::Cosine(fTheta);

								Asura_AVector4 xCosDeltaX = xDeltaX * fCos;
								Asura_AVector4 xSinDeltaX = xDeltaX * fSin;
								Asura_AVector4 xCosDeltaY = xDeltaY * fCos;
								Asura_AVector4 xSinDeltaY = xDeltaY * fSin;

								axVertices[0] = xPosition + (-xCosDeltaX + xSinDeltaX) + (-xSinDeltaY - xCosDeltaY);		
								axVertices[1] = xPosition + ( xCosDeltaX + xSinDeltaX) + ( xSinDeltaY - xCosDeltaY);
								axVertices[2] = xPosition + ( xCosDeltaX - xSinDeltaX) + ( xSinDeltaY + xCosDeltaY);
								axVertices[3] = xPosition + (-xCosDeltaX - xSinDeltaX) + (-xSinDeltaY + xCosDeltaY);
							}
							else
							{
								axVertices[0] = xPosition - xDeltaX - xDeltaY; 
								axVertices[1] = xPosition + xDeltaX - xDeltaY;
								axVertices[2] = xPosition + xDeltaX + xDeltaY;
								axVertices[3] = xPosition - xDeltaX + xDeltaY;
							}
						}

						{		
							// are we changing texture orient?
							u_int uTexOrient = 0;
							if(pxTemplate->m_bRandomTextureOrient)
							{
								uTexOrient = Asura_Maths::FloatToInt_Chop(pxParticle->m_fSeed * 4.f);
							}

							float fVOffset = 0.f;
							if(pxTemplate->m_bSpriteRotation)
							{
								Asura_Vector_3 xParticleDirection = pxParticle->m_xVelocity;
								Asura_Vector_3 xCameraDirection = pxParticle->m_xPosition - xCameraPos3;
								// Flatten it out to the X-Z plane.
								xParticleDirection.y = 0.f;
								xCameraDirection.y = 0.f;
								if(xParticleDirection.Normalise() > 0.f && xCameraDirection.Normalise() > 0.f)
								{
									const float fParticleYAngle = Asura_Maths::ArcTangent(xParticleDirection.x, xParticleDirection.z);
									const float fCameraYAngle = Asura_Maths::ArcTangent(xCameraDirection.x, xCameraDirection.z);
									const float fAngleDiff = Asura_Maths::RadiansToZeroMinimumRange(fParticleYAngle + Asura_Maths::DegToRad(22.5f) - fCameraYAngle);
									const u_int uFrameOffset = Asura_Maths::FloatToInt_Near(fAngleDiff / Asura_Maths::DegToRad(45.f));
									fVOffset = uFrameOffset * pxTextureFrame->m_afTextureUVs[3] - pxTextureFrame->m_afTextureUVs[1];
								}
							}

							const float fFrame0U0 = pxTextureFrame->m_afTextureUVs[0];
							const float fFrame0U1 = pxTextureFrame->m_afTextureUVs[2];
							const float fFrame1U0 = pxTextureFrame2 ? (pxTextureFrame2->m_afTextureUVs[0]) : 0.f;
							const float fFrame1U1 = pxTextureFrame2 ? (pxTextureFrame2->m_afTextureUVs[2]) : 0.f;
							switch(uTexOrient)
							{
							case 0:
							case 1:
								// Normal
								pxParticleVerts[0].U = pxParticleVerts[3].U	= pxParticleVerts[5].U = fFrame0U0;
								pxParticleVerts[1].U = pxParticleVerts[2].U = pxParticleVerts[4].U = fFrame0U1;
								if (pxTextureFrame2)
								{
									pxParticleVerts[0].U2 = pxParticleVerts[3].U2 = pxParticleVerts[5].U2 = fFrame1U0;
									pxParticleVerts[1].U2 = pxParticleVerts[2].U2 = pxParticleVerts[4].U2 = fFrame1U1;
								}
								break;
							case 2:
							case 3:
							default:
								// Mirrored
								pxParticleVerts[0].U = pxParticleVerts[3].U	= pxParticleVerts[5].U = fFrame0U1;
								pxParticleVerts[1].U = pxParticleVerts[2].U = pxParticleVerts[4].U = fFrame0U0;
								if (pxTextureFrame2)
								{
									pxParticleVerts[0].U2 = pxParticleVerts[3].U2 = pxParticleVerts[5].U2 = fFrame1U1;
									pxParticleVerts[1].U2 = pxParticleVerts[2].U2 = pxParticleVerts[4].U2 = fFrame1U0;
								}
								break;
							}
							const float fFrame0V0 = pxTextureFrame->m_afTextureUVs[1] + fVOffset;
							const float fFrame0V1 = pxTextureFrame->m_afTextureUVs[3] + fVOffset;
							const float fFrame1V0 = pxTextureFrame2 ? (pxTextureFrame2->m_afTextureUVs[1] + fVOffset) : 0.f;
							const float fFrame1V1 = pxTextureFrame2 ? (pxTextureFrame2->m_afTextureUVs[3] + fVOffset) : 0.f;
							switch(uTexOrient)
							{
							case 0:
							case 3:
								// Normal
								pxParticleVerts[0].V = pxParticleVerts[1].V = pxParticleVerts[3].V = fFrame0V0;
								pxParticleVerts[1].V								= pxTextureFrame->m_afTextureUVs[1];
								pxParticleVerts[2].V = pxParticleVerts[4].V = pxParticleVerts[5].V = fFrame0V1;
								pxParticleVerts[5].V								= pxTextureFrame->m_afTextureUVs[3];
								if (pxTextureFrame2)
								{
									pxParticleVerts[0].V2 = pxParticleVerts[1].V2 = pxParticleVerts[3].V2 = fFrame1V0;
									pxParticleVerts[1].V2							= pxTextureFrame2->m_afTextureUVs[1];
									pxParticleVerts[2].V2 = pxParticleVerts[4].V2 = pxParticleVerts[5].V2 = fFrame1V1;
									pxParticleVerts[5].V2							= pxTextureFrame2->m_afTextureUVs[3];
								}
								break;
							case 1:
							case 2:
							default:
								// Mirrored
								pxParticleVerts[0].V = pxParticleVerts[1].V = pxParticleVerts[3].V = fFrame0V1;
								pxParticleVerts[1].V								= pxTextureFrame->m_afTextureUVs[3];
								pxParticleVerts[2].V = pxParticleVerts[4].V = pxParticleVerts[5].V = fFrame0V0;
								pxParticleVerts[5].V								= pxTextureFrame->m_afTextureUVs[1];
								if (pxTextureFrame2)
								{
									pxParticleVerts[0].V2 = pxParticleVerts[1].V2 = pxParticleVerts[3].V2 = fFrame1V1;
									pxParticleVerts[1].V2							= pxTextureFrame2->m_afTextureUVs[3];
									pxParticleVerts[2].V2 = pxParticleVerts[4].V2 = pxParticleVerts[5].V2 = fFrame1V0;
									pxParticleVerts[5].V2							= pxTextureFrame2->m_afTextureUVs[1];
								}
								break;
							}

							// 1st Tri
							pxParticleVerts[0].X			= axVertices[0].GetX();
							pxParticleVerts[0].Y			= axVertices[0].GetY();
							pxParticleVerts[0].Z			= axVertices[0].GetZ();

							pxParticleVerts[1].X			= axVertices[1].GetX();
							pxParticleVerts[1].Y			= axVertices[1].GetY();
							pxParticleVerts[1].Z			= axVertices[1].GetZ();

							pxParticleVerts[2].X			= axVertices[2].GetX();
							pxParticleVerts[2].Y			= axVertices[2].GetY();
							pxParticleVerts[2].Z			= axVertices[2].GetZ();

							// 2nd Tri
							pxParticleVerts[3].X			= axVertices[0].GetX();
							pxParticleVerts[3].Y			= axVertices[0].GetY();
							pxParticleVerts[3].Z			= axVertices[0].GetZ();

							pxParticleVerts[4].X			= axVertices[2].GetX();
							pxParticleVerts[4].Y			= axVertices[2].GetY();
							pxParticleVerts[4].Z			= axVertices[2].GetZ();

							pxParticleVerts[5].X			= axVertices[3].GetX();
							pxParticleVerts[5].Y			= axVertices[3].GetY();
							pxParticleVerts[5].Z			= axVertices[3].GetZ();

							pxParticleVerts[0].Diffuse	= pxParticleVerts[1].Diffuse = pxParticleVerts[2].Diffuse	=
								pxParticleVerts[3].Diffuse	= pxParticleVerts[4].Diffuse = pxParticleVerts[5].Diffuse = D3DXCOLOR(xColour.m_fR, xColour.m_fG, xColour.m_fB, xColour.m_fA);
							pxParticleVerts[0].CrossFadeBlendWeight	= pxParticleVerts[1].CrossFadeBlendWeight = pxParticleVerts[2].CrossFadeBlendWeight	=
								pxParticleVerts[3].CrossFadeBlendWeight	= pxParticleVerts[4].CrossFadeBlendWeight = pxParticleVerts[5].CrossFadeBlendWeight	= fCrossFadeBlendWeight;

							pxParticleVerts += 6;
						}
					}

					g_pxParticleVertexBuffer->Unlock();

					if ( uParticlesToRender )
					{
#ifdef ASURA_TOOLS
						Tools_Mode_PFX::AddToParticlesRendered(uParticlesToRender);
#endif
						g_pxPFXEffect->Begin(false, Asura_PFX_System::IsRenderingHeatHazeSources() ? 1 : 0);
						g_pxPFXEffect->BeginPass(0);
						g_pxPFXEffect->GetD3DEffect()->CommitChanges();

						// (SB - 18/05/07) Setup render states for particle effect
						if (Asura_PFX_System::GetRenderStateOverride() != NULL)
						{
							Asura_PFX_System::GetRenderStateOverride()();
						}

						// Set the decl
						Asura_Direct3D::D3DDevice->SetVertexDeclaration( g_pxPFXVertexDecl );

						// Setup stream source, render and reset
						Asura_Direct3D::D3DDevice->SetStreamSource( 0, g_pxParticleVertexBuffer, 0, sizeof( PFXVertex ) );
						Asura_Direct3D::D3DDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, uParticlesToRender * 2 );
						Asura_Direct3D::D3DDevice->SetStreamSource(0,NULL,0,0);

						g_pxPFXEffect->EndPass();
						g_pxPFXEffect->End();
						// Flush any effects we may still be using
						Asura_Direct3D_Effects_System::FlushEffects	();

						Asura_Render::AddToTrianglesRendered( uParticlesToRender*2 );
					}
					pxParticles		+= uParticleCount;
					uParticlesLeft	-= uParticleCount;
				}
			}
		}
	}
	Asura_Render::CurrentStates.RequestZBufferCompareMode(ASURA_ZBUFFERCMP_LESSEQUAL);


	if (Asura_PFX_System::IsRenderingHeatHazeSources())
	{	
		g_pxPFXEffect->GetD3DEffect()->SetTexture( g_xHeatHazeCaptureTextureHandle, NULL );
	}

	for( u_int u = 0; u < 16; ++u )
	{
		Asura_Direct3D::D3DDevice->SetSamplerState( u, D3DSAMP_SRGBTEXTURE, FALSE );
	}
}

#ifdef ASURA_TOOLS
void Asura_PFX_System::SetHeatHazeRNG(Asura_RandomGenerator* pxRNG)
{
	g_pxHeatHazeRNG = pxRNG;
	//reset the offset at the same time , since this needs to start at a known value for avi recording to be consistent.
	//RAR 23/05/2006
	g_fHEAT_HAZE_OFFSET_V = 0.0f;
}

#endif



#ifdef ASURA_SUPPORT_MULTITHREADED_JOB_QUEUE

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PFX_System::Platform_BeginUpdate
// Author       : Dave Reed
// Date         : 13-Nov-2006
//
// Description  : Prepares vertex buffers for updating during Update.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////
void Asura_PFX_System::Platform_BeginUpdate (void)
{
#ifdef ASURA_USE_FASTPATH_PFX
	Asura_Platform_PFX_System_FastPath::UpdateInstanceBuffersForNewFrame();
#endif
}

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PFX_System::Platform_GetRenderData
// Author       : Dave Reed
// Date         : 13-Nov-2006
//
// Description  : returns area of vertex buffer big enough for given number of 
//                particles - for multithreaded updates.
//
//			      Returns NULL if we're not doing this via the job queue 
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////
void* Asura_PFX_System::Platform_GetRenderData(u_int uNumParticles)
{
#ifdef ASURA_USE_FASTPATH_PFX
	// Todo - Don't call this with slow-path effects - this is wasting buffer space at the moment!
	if (Asura_Platform_PFX_System_FastPath::IsEnabled())
	{	
		return Asura_Platform_PFX_System_FastPath::GetInstanceBufferArea(uNumParticles);
	}
#endif	

	return NULL;
}

#endif // ASURA_SUPPORT_MULTITHREADED_JOB_QUEUE

#endif // defined(ASURA_TARGET_SM3)



/// End of file. ///
