//
// Asura_Platform_PFX.cpp
//
// Created by KOR - 16/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 "Camera/Asura_Camera.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_Timers.h"
#include "Light/Asura_GI_Solution.h"
#include "Rendering/Asura_Colour.h"
#include "Rendering/Asura_PSP_Graphics.h"
#include "Rendering/Asura_Render.h"
#include "SpecialFX/PFX/Asura_PFX_Classes.h"
#include "SpecialFX/PFX/Asura_PFX_System.h"
#include "SpecialFX/PFX/Asura_PFX_Templates.h"

#include "Debugging/Asura_PSP_Profiling.h"
#include "Rendering/Asura_PSP_TextureManagement.h"
#include <kernel.h>

// T Y P E S /////////////////////////////////////////////////////////////////
struct Asura_PSP_RenderParticle_Vertex
{
	// fully 32bit - 24 bytes
	float			m_fU;
	float			m_fV;
	unsigned		m_uColour;
	float			m_afPosition[3];
};

// C O N S T A N T S /////////////////////////////////////////////////////////
const int iASURA_PSP_PARTICLE_VERTEX_FORMAT = SCEGU_TEXTURE_FLOAT | SCEGU_VERTEX_FLOAT | SCEGU_COLOR_PF8888;

// KOR - 30/09/2004 - Dodgy buffer. Yuck. To be replaced with proper class when I get the time (unify with beam renderer)
const u_int uASURA_PSP_MAX_PCLES_PER_FRAME = 1024;
const u_int uASURA_PSP_NUM_VERTS_PER_PARTICLE = 6;
const u_int uASURA_PSP_MAX_PCLE_VERTS_PER_FRAME = uASURA_PSP_MAX_PCLES_PER_FRAME * uASURA_PSP_NUM_VERTS_PER_PARTICLE;
const u_int uASURA_PSP_NUM_PFX_SUBDIV_VERTS = 26;	// see SubdivideParticle for strip composition.

#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 

#ifndef ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD
	// The size beyond which a particle is considered to be suspiciously large.
	#define ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD ( 50.0f )
#endif 

// G L O B A L   D A T A /////////////////////////////////////////////////////

// MH - 1/12/2005 - TODO: use uncached access for the render data. Would need to align to 64 bytes in that case.
#ifdef ASURA_PSP_DEFERREDRENDER
	static Asura_PSP_RenderParticle_Vertex g_axVertices[ 2 ][uASURA_PSP_MAX_PCLE_VERTS_PER_FRAME] __attribute__ ((aligned(16)));
#else
	static Asura_PSP_RenderParticle_Vertex g_axVertices[ 1 ][uASURA_PSP_MAX_PCLE_VERTS_PER_FRAME] __attribute__ ((aligned(16)));
#endif
static u_int						g_uOffset = 0;
static int							g_iTexID = ASURA_TEXTURE_INVALID;
static enum ASURA_TRANSLUCENCY_MODE	g_eTranslucencyMode;
static u_int						g_uNumberOfVertsToRender;
static u_int						g_uStartOfStrip;
static bool							g_bAlwaysOnTop;
static Asura_AVector3				g_xCameraDeltaX __attribute__((aligned(16)));
static Asura_AVector3				g_xCameraDeltaY __attribute__((aligned(16)));

#ifdef ASURA_PSP_TRACK_PFX_USAGE
static u_int g_uNumParticlesThroughFastPathTotal;
static u_int g_uNumParticlesThroughSlowPathTotal;
static u_int g_uNumParticlesThroughFastPathLast;
static u_int g_uNumParticlesThroughSlowPathLast;
static u_int g_uNumParticlesThroughFastPathCur;
static u_int g_uNumParticlesThroughSlowPathCur;
static u_int g_uTimeForParticlePathReset;
#endif // #ifdef ASURA_PSP_TRACK_PFX_USAGE

// I N T E R N A L   F U N C T I O N S ///////////////////////////////////////
static void SubdivideParticle(Asura_PSP_RenderParticle_Vertex* pxVertices, 
							  const Asura_AVector3 axVertices[4], 
							  const u_int uColour);


#ifdef ASURA_PSP_TRACK_PFX_USAGE
static void UpdatePFXRenderStats()
{
	u_int uCurTime = Asura_Timers::GetMSecsSinceStart();
	if (uCurTime > g_uTimeForParticlePathReset)
	{
		g_uTimeForParticlePathReset = uCurTime + 2000;
		g_uNumParticlesThroughFastPathLast = g_uNumParticlesThroughFastPathCur;
		g_uNumParticlesThroughSlowPathLast = g_uNumParticlesThroughSlowPathCur;
		g_uNumParticlesThroughFastPathCur = 0;
		g_uNumParticlesThroughSlowPathCur = 0;
	}
}

static void RecordPFXRendered_Fast(u_int uNumParticles)
{
	g_uNumParticlesThroughFastPathTotal += uNumParticles;
	g_uNumParticlesThroughFastPathCur += uNumParticles;
}

static void RecordPFXRendered_Slow(u_int uNumParticles)
{
	g_uNumParticlesThroughSlowPathTotal += uNumParticles;
	g_uNumParticlesThroughSlowPathCur += uNumParticles;
}
#endif // #ifdef ASURA_PSP_TRACK_PFX_USAGE

void Asura_PFX_System::Platform_BeginRender()
{

#ifdef ASURA_PSP_PROFILING
	Asura_PSP_Profiling::Signal( Asura_PSP_Profiling::ASURA_PSP_SIGNAL_PFX_START ); 
#endif // #ifdef ASURA_PSP_PROFILING

#ifdef ASURA_PSP_TRACK_PFX_USAGE
	UpdatePFXRenderStats();
#endif // #ifdef ASURA_PSP_TRACK_PFX_USAGE

	// Make sure we have a valid particle texture.
	if(g_iTexID == ASURA_TEXTURE_INVALID)
	{
		g_iTexID = Asura_Texture_Management::GetTextureIndex("\\specialfx\\partclfx.tga");
	}
	Asura_Render::CurrentStates.RequestTextureOpMode(ASURA_TEXTUREOP_SINGLE_PASS);
	Asura_PSP_Graphics::SetMaterialType( ASURA_PSP_GRAPHICS_MATERIALTYPE_ENV );

	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
	Asura_PSP_TextureManagement::RequestTextureIndex(g_iTexID);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);

	Asura_PSP_Graphics::SetUseColourDoubling(false);
	Asura_PSP_Graphics::SetUseLighting(false);

	
	const Asura_Matrix_3x3* pxCameraOrientation = Asura_Camera::GetOrientation();
	g_xCameraDeltaX = Asura_AVector3(pxCameraOrientation->e11,pxCameraOrientation->e21,pxCameraOrientation->e31);
	g_xCameraDeltaY = Asura_AVector3(pxCameraOrientation->e12,pxCameraOrientation->e22,pxCameraOrientation->e32);

	g_eTranslucencyMode = ASURA_TRANSLUCENCY_NOT_SET;

	g_uNumberOfVertsToRender = 0;
	
	// JG - 11/07/06: Fix for particles buffer being overwritten, causing all kind of glitches. The problem was Platform_BeginRender 
	// being called several times a frame, clearly this system wasn't designed to do that. This frame check works but is pretty ugly.
	// Don't want to refactorise quite a chunk of independent code to add Begin_Frame/End_Frame methods, were this buffer reset should 
	// be done
	static unsigned int s_uFrame = 0; 
	if (s_uFrame != Asura_Timers::GetFrameNumber())
	{
		g_uStartOfStrip = 0;
		g_uOffset = 0;
		s_uFrame = Asura_Timers::GetFrameNumber(); 
	}
	g_bAlwaysOnTop = false;
}

static void FlushParticles()
{
	if ( g_uNumberOfVertsToRender )
	{
		Asura_Render::CurrentStates.RequestTranslucencyMode(g_eTranslucencyMode);
		Asura_Render::CurrentStates.RequestZBufferCompareMode(g_bAlwaysOnTop ? ASURA_ZBUFFERCMP_ALWAYS : ASURA_ZBUFFERCMP_LESSEQUAL);

		sceKernelDcacheWritebackRange( &g_axVertices[ Asura_PSP_Graphics::GetCurrentDisplayList() ][g_uStartOfStrip], g_uNumberOfVertsToRender * sizeof(Asura_PSP_RenderParticle_Vertex) );

		if (Asura_PFX_System::GetRenderStateOverride() != NULL)
		{
			Asura_PFX_System::GetRenderStateOverride()();
		}

		GUM_DrawArray
		(
			SCEGU_PRIM_TRIANGLE_STRIP, 
			iASURA_PSP_PARTICLE_VERTEX_FORMAT,
			g_uNumberOfVertsToRender, 
			NULL, 
			&g_axVertices[ Asura_PSP_Graphics::GetCurrentDisplayList() ][ g_uStartOfStrip ]
		);
	}
	g_uStartOfStrip = g_uOffset;
	g_uNumberOfVertsToRender = 0;
}

void Asura_PFX_System::Platform_EndRender()
{
	// flush what we have left
	FlushParticles();

#ifdef ASURA_PSP_PROFILING
	Asura_PSP_Profiling::Signal( Asura_PSP_Profiling::ASURA_PSP_SIGNAL_PFX_END ); 
#endif // #ifdef ASURA_PSP_PROFILING

}

// (SB - 01/05/07) Added pxEffect parameter - this can be useful in conjunction with the colour generation callback
static void RenderParticlesComplex(u_char ucCameraFlags, float fSizeScale, const Asura_Matrix_3x3* pxOrientation,
								const Asura_PFX_Particle* const pxParticles, u_int uNumParticles, const Asura_PFX_Particle_Template* pxTemplate, const Asura_RGBA& xLightColour, const Asura_PFX_Effect* pxEffect = NULL)
{
	Asura_AVector3 axVertices[4];
	for( int iCam=0; iCam<Asura_Camera::GetNumberOfCameras(); ++iCam )
	{
		if(!(ucCameraFlags & (1<<iCam))) continue;
		Asura_Camera::SetCameraIndex( iCam );
		const Asura_AVector3 xCameraPos(*Asura_Camera::GetPosition());

		for(u_int uParticle = 0; uParticle < uNumParticles; ++uParticle)
		{
			const Asura_PFX_Particle* pxParticle = &pxParticles[uParticle];
			Asura_Vector_3 xEffectPosition( pxParticle->m_xPosition ); 
			if(pxEffect && pxEffect->GetRenderType() == ASURA_PFX_RENDER_SKYBOX )
			{
				xEffectPosition += *Asura_Camera::GetPosition();
			}
			const Asura_AVector3 xPosition( xEffectPosition );

			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()(pxEffect, pxTemplate, uParticle, xColour);
			}

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

			if(pxTemplate->m_bNearCameraFade || pxTemplate->m_bFarCameraFade)
			{
				const float fDist = (xCameraPos - 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.0f;
					}
					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.0f;
					}
					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)
			{
				continue;
			}

			float fSize = pxTemplate->m_xSize.GetValueAtTime_Discrete(pxParticle->m_fAge);
			fSize *= fSizeScale;

			fSize *= (1.f - pxParticle->m_fSeed * pxTemplate->m_xSizeVariation.GetValueAtTime_Discrete(pxParticle->m_fAge));
			
			Asura_AVector3 xDeltaX __attribute__((aligned(16)));
			Asura_AVector3 xDeltaY __attribute__((aligned(16)));

			float fTheta=0;

			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 : Asura_Random::GetFloat()) * Asura_Maths::Pi * 2.0f;
				}
				else
				{
					fTheta = (pxParticle->m_fSeed + pxTemplate->m_xRotation.GetValueAtTime_Discrete(pxParticle->m_fAge)) * Asura_Maths::Pi * 2.f;
				}
				if(pxTemplate->m_bMirrorRotation)
				{
					const int* piRandom = reinterpret_cast<const int*>(&pxParticle->m_fSeed);
					if(*piRandom % 2)
					{
						fTheta = -fTheta;
					}
				}
			}

			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_AVector3(xMatrix.e11, xMatrix.e21, xMatrix.e31) * fSize;
				xDeltaY = Asura_AVector3(xMatrix.e12, xMatrix.e22, xMatrix.e32) * fSize;
			}
			else if(pxTemplate->m_bAlignEffectX && pxOrientation)
			{
				xDeltaX = Asura_AVector3(pxOrientation->e12, pxOrientation->e22, pxOrientation->e32) * fSize;
				xDeltaY = Asura_AVector3(pxOrientation->e13, pxOrientation->e23, pxOrientation->e33) * fSize;
			}
			else if(pxTemplate->m_bAlignEffectY && pxOrientation)
			{
				xDeltaX = Asura_AVector3(pxOrientation->e11, pxOrientation->e21, pxOrientation->e31) * fSize;
				xDeltaY = Asura_AVector3(pxOrientation->e13, pxOrientation->e23, pxOrientation->e33) * fSize;
			}
			else if(pxTemplate->m_bAlignEffectZ && pxOrientation)
			{
				xDeltaX = Asura_AVector3(pxOrientation->e11, pxOrientation->e21, pxOrientation->e31) * fSize;
				xDeltaY = Asura_AVector3(pxOrientation->e12, pxOrientation->e22, pxOrientation->e32) * fSize;
			}
			else
			{
				xDeltaX = g_xCameraDeltaX; xDeltaX *= fSize;
				xDeltaY = g_xCameraDeltaY; xDeltaY *= fSize;
			}

			Asura_Assert(xPosition.GetX()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetX()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");
			Asura_Assert(xPosition.GetY()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetY()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");
			Asura_Assert(xPosition.GetZ()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetZ()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");

			Asura_Assert(fSize < ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD && fSize > -ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD,"size problems?");

			/// 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 float fAgeInSeconds = pxParticle->m_fAge * pxTemplate->GetLifetimeFromSeed(pxParticle->m_fSeed);
				const float fLength = Asura_Maths::Min(pxTemplate->m_fBlurLength, fAgeInSeconds);
				const Asura_AVector3 xPrevPosition = xPosition - (Asura_AVector3(pxParticle->m_xVelocity) * fLength);

				Asura_AVector3 xBeta;

				xBeta = xPosition - xPrevPosition;
				if( xBeta.MagnitudeSquared() > (0.1f*0.1f) )
				{
					xBeta.Normalise();
					xBeta*=0.1f;
				}
					
				Asura_Assert(xPrevPosition.GetX()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPrevPosition.GetX()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"prev position problem");
				Asura_Assert(xPrevPosition.GetY()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPrevPosition.GetY()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"prev position problem");
				Asura_Assert(xPrevPosition.GetZ()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPrevPosition.GetZ()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"prev position problem");


				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)
				{
					float fSin;
					float fCos;
					
					Asura_Maths::SinCos(fTheta, fSin, fCos);

					Asura_AVector3 xCosDeltaX = xDeltaX * fCos;
					Asura_AVector3 xSinDeltaX = xDeltaX * fSin;
					Asura_AVector3 xCosDeltaY = xDeltaY * fCos;
					Asura_AVector3 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;
				}
			}


			// get the texture frame(s) for this particle
			u_int uTexOrient = 0;
			if(pxTemplate->m_bRandomTextureOrient)
			{
				uTexOrient = Asura_Maths::FloatToInt_Chop(pxParticle->m_fSeed * 4.f);
			}
			const Asura_PFX_TextureFrame* pxTextureFrame;
			const Asura_PFX_TextureFrame* pxTextureFrame2;
			float fWeight;
			const float fAlpha = xColour.m_fA;
			if (pxTemplate->m_bCrossfadeTextureFrames)
			{
				pxTemplate->GetTextureFramesForCrossfadeAtTime(pxParticle->m_fAge, pxTextureFrame, pxTextureFrame2, fWeight);
			}
			else
			{
				pxTextureFrame = pxTemplate->GetTextureFrameAtTime(pxParticle->m_fAge);
				pxTextureFrame2 = NULL;
				fWeight = 1.0f;
			}
			
			do
			{
				Asura_PSP_RenderParticle_Vertex* pxVertices = &g_axVertices[ Asura_PSP_Graphics::GetCurrentDisplayList() ][g_uOffset+1];

				switch(uTexOrient)
				{
					case 0:
					case 1:
						// Normal
						pxVertices[0].m_fU = pxTextureFrame->m_afTextureUVs[0];
						pxVertices[1].m_fU = pxTextureFrame->m_afTextureUVs[2];
						pxVertices[2].m_fU = pxTextureFrame->m_afTextureUVs[2];
						pxVertices[3].m_fU = pxTextureFrame->m_afTextureUVs[0];
						break;
					case 2:
					case 3:
					default:
						// Mirrored
						pxVertices[0].m_fU = pxTextureFrame->m_afTextureUVs[2];
						pxVertices[1].m_fU = pxTextureFrame->m_afTextureUVs[0];
						pxVertices[2].m_fU = pxTextureFrame->m_afTextureUVs[0];
						pxVertices[3].m_fU = pxTextureFrame->m_afTextureUVs[2];
						break;
				}
				switch(uTexOrient)
				{
					case 0:
					case 3:
						// Normal
						pxVertices[0].m_fV = pxTextureFrame->m_afTextureUVs[1];
						pxVertices[1].m_fV = pxTextureFrame->m_afTextureUVs[1];
						pxVertices[2].m_fV = pxTextureFrame->m_afTextureUVs[3];
						pxVertices[3].m_fV = pxTextureFrame->m_afTextureUVs[3];
						break;
					case 1:
					case 2:
					default:
						// Mirrored
						pxVertices[0].m_fV = pxTextureFrame->m_afTextureUVs[3];
						pxVertices[1].m_fV = pxTextureFrame->m_afTextureUVs[3];
						pxVertices[2].m_fV = pxTextureFrame->m_afTextureUVs[1];
						pxVertices[3].m_fV = pxTextureFrame->m_afTextureUVs[1];
						break;
				}
				// set alpha to original alpha * weight
				xColour.m_fA = fAlpha * fWeight;
				const unsigned int uDiffuse = xColour.ToUIntABGR(true);
				if (pxTemplate->m_bCanUseExpensiveClipping)
				{
					SubdivideParticle( pxVertices, axVertices, uDiffuse );
					g_uOffset += uASURA_PSP_NUM_PFX_SUBDIV_VERTS;
					g_uNumberOfVertsToRender += uASURA_PSP_NUM_PFX_SUBDIV_VERTS;
				}
				else
				{
					for (int i=0; i<4; i++)
					{
						pxVertices[i].m_afPosition[0] = axVertices[i].GetX();
						pxVertices[i].m_afPosition[1] = axVertices[i].GetY();
						pxVertices[i].m_afPosition[2] = axVertices[i].GetZ();

						pxVertices[i].m_uColour = uDiffuse;
					}

					// looks like verts are currently arranged clockwise and then remapped 
					// to make a tri-strip with degenerates first and last:
					// 	0----1			 -1,0----1
					// 	|    |	remap->		|    |
					// 	|    |				|    |
					// 	3----2				2----3,4
					pxVertices[-1] = pxVertices[0];
					pxVertices[4] = pxVertices[3];
					pxVertices[3] = pxVertices[2];
					pxVertices[2] = pxVertices[4];
					pxVertices[4] = pxVertices[3];

					g_uOffset += 6;
					g_uNumberOfVertsToRender += 6;
				}


				// setup for crossfade 2nd texture frame. 
				pxTextureFrame = pxTextureFrame2;
				pxTextureFrame2 = NULL;
				fWeight = 1.0f - fWeight;
			}
			while (pxTextureFrame);
		} // end particle loop

		// two tris plus to degenerates
	#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(4 * uNumParticles,1,
			ASURA_RENDER_TTYPE_PFX);
	#else
		Asura_Render::AddToTrianglesRendered(4 * uNumParticles);
	#endif
		Asura_Render::AddToParticlesRendered(uNumParticles);
	}// end camera loop
}

// (SB - 01/05/07) Added pxEffect parameter - this can be useful in conjunction with the colour generation callback
static void RenderParticlesSimple(u_char ucCameraFlags, const Asura_PFX_Particle* const pxParticles, u_int uNumParticles, const Asura_PFX_Particle_Template* pxTemplate, const Asura_PFX_Effect* pxEffect = NULL)
{
	Asura_AVector3 axVertices[4];
	for( int iCam=0; iCam<Asura_Camera::GetNumberOfCameras(); ++iCam )
	{
		if(!(ucCameraFlags & (1<<iCam))) continue;
		Asura_Camera::SetCameraIndex( iCam );
		const Asura_AVector3 xCameraPos(*Asura_Camera::GetPosition());

		for(u_int uParticle = 0; uParticle < uNumParticles; ++uParticle)
		{
			const Asura_PFX_Particle* pxParticle = &pxParticles[uParticle];
			Asura_Vector_3 xEffectPosition( pxParticle->m_xPosition ); 
			if(pxEffect && pxEffect->GetRenderType() == ASURA_PFX_RENDER_SKYBOX )
			{
				xEffectPosition += *Asura_Camera::GetPosition();
			}
			const Asura_AVector3 xPosition( xEffectPosition );

			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()(pxEffect, pxTemplate, uParticle, xColour);
			}

			// KOR - 15/11/2005 - early out on zero alpha
//			if(xColour.m_fA <= 0.f)
//			{
//				continue;
//			}

			float fSize = pxTemplate->m_xSize.GetValueAtTime_Discrete(pxParticle->m_fAge);
			fSize *= (1.f - pxParticle->m_fSeed * pxTemplate->m_xSizeVariation.GetValueAtTime_Discrete(pxParticle->m_fAge));
			
			Asura_AVector3 xDeltaX;
			Asura_AVector3 xDeltaY;

			float fTheta = (pxParticle->m_fSeed + pxTemplate->m_xRotation.GetValueAtTime_Discrete(pxParticle->m_fAge)) * Asura_Maths::Pi * 2.f;

			xDeltaX = g_xCameraDeltaX * fSize;
			xDeltaY = g_xCameraDeltaY * fSize;

			Asura_Assert(xPosition.GetX()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetX()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");
			Asura_Assert(xPosition.GetY()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetY()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");
			Asura_Assert(xPosition.GetZ()<ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD && xPosition.GetZ()>-ASURA_PFX_SYSTEM_SUSPICIOUS_POSITION_THRESHOLD,"position problem");

			Asura_Assert(fSize < ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD && fSize > -ASURA_PFX_SYSTEM_SUSPICIOUS_SIZE_THRESHOLD,"size problems?");

			float fSin;
			float fCos;
			
			Asura_Maths::SinCos(fTheta, fSin, fCos);

			Asura_AVector3 xCosDeltaX = xDeltaX * fCos;
			Asura_AVector3 xSinDeltaX = xDeltaX * fSin;
			Asura_AVector3 xCosDeltaY = xDeltaY * fCos;
			Asura_AVector3 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);
	
#if 0	// old code, fills out 4 render vertices then copies them around to add degenerates and change the order.
			Asura_PSP_RenderParticle_Vertex* pxVertices = &g_axVertices[ Asura_PSP_Graphics::GetCurrentDisplayList() ][g_uOffset+1];
			{
				const Asura_PFX_TextureFrame* pxTextureFrame = pxTemplate->GetTextureFrameAtTime(pxParticle->m_fAge);

				pxVertices[0].m_fU = pxTextureFrame->m_afTextureUVs[0];
				pxVertices[1].m_fU = pxTextureFrame->m_afTextureUVs[2];
				pxVertices[2].m_fU = pxTextureFrame->m_afTextureUVs[2];
				pxVertices[3].m_fU = pxTextureFrame->m_afTextureUVs[0];
				
				pxVertices[0].m_fV = pxTextureFrame->m_afTextureUVs[1];
				pxVertices[1].m_fV = pxTextureFrame->m_afTextureUVs[1];
				pxVertices[2].m_fV = pxTextureFrame->m_afTextureUVs[3];
				pxVertices[3].m_fV = pxTextureFrame->m_afTextureUVs[3];
				
				const unsigned int uDiffuse = xColour.ToUIntABGR(true);
				for (int i=0; i<4; i++)
				{
					pxVertices[i].m_afPosition[0] = axVertices[i].GetX();
					pxVertices[i].m_afPosition[1] = axVertices[i].GetY();
					pxVertices[i].m_afPosition[2] = axVertices[i].GetZ();

					pxVertices[i].m_uColour = uDiffuse;
				}

				pxVertices[-1] = pxVertices[0];
				pxVertices[4] = pxVertices[3];
				pxVertices[3] = pxVertices[2];
				pxVertices[2] = pxVertices[4];
				pxVertices[4] = pxVertices[3];

				g_uOffset += 6;
				g_uNumberOfVertsToRender += 6;
			}
#else	// this produces smaller and faster code by directly writing out what we need.
			Asura_PSP_RenderParticle_Vertex* pxVertices = &g_axVertices[ Asura_PSP_Graphics::GetCurrentDisplayList() ][g_uOffset+1];
			{
				const Asura_PFX_TextureFrame* pxTextureFrame = pxTemplate->GetTextureFrameAtTime(pxParticle->m_fAge);

				float fUV0 = pxTextureFrame->m_afTextureUVs[0];
				float fUV1 = pxTextureFrame->m_afTextureUVs[1];
				float fUV2 = pxTextureFrame->m_afTextureUVs[2];
				float fUV3 = pxTextureFrame->m_afTextureUVs[3];
				pxVertices[-1].m_fU = fUV0;
				pxVertices[0].m_fU = fUV0;
				pxVertices[1].m_fU = fUV2;
				pxVertices[3].m_fU = fUV2;
				pxVertices[2].m_fU = fUV0;
				pxVertices[4].m_fU = fUV2;
				
				pxVertices[-1].m_fV = fUV1;
				pxVertices[0].m_fV = fUV1;
				pxVertices[1].m_fV = fUV1;
				pxVertices[3].m_fV = fUV3;
				pxVertices[2].m_fV = fUV3;
				pxVertices[4].m_fV = fUV3;
				
				const unsigned int uDiffuse = xColour.ToUIntABGR(true);

				float fX0 = axVertices[0].GetX();
				float fY0 = axVertices[0].GetY();
				float fZ0 = axVertices[0].GetZ();
				float fX1 = axVertices[1].GetX();
				float fY1 = axVertices[1].GetY();
				float fZ1 = axVertices[1].GetZ();
				float fX2 = axVertices[2].GetX();
				float fY2 = axVertices[2].GetY();
				float fZ2 = axVertices[2].GetZ();
				float fX3 = axVertices[3].GetX();
				float fY3 = axVertices[3].GetY();
				float fZ3 = axVertices[3].GetZ();
				pxVertices[-1].m_afPosition[0] = fX0;
				pxVertices[-1].m_afPosition[1] = fY0;
				pxVertices[-1].m_afPosition[2] = fZ0;
				pxVertices[-1].m_uColour = uDiffuse;
				pxVertices[0].m_afPosition[0] = fX0;
				pxVertices[0].m_afPosition[1] = fY0;
				pxVertices[0].m_afPosition[2] = fZ0;
				pxVertices[0].m_uColour = uDiffuse;
				pxVertices[1].m_afPosition[0] = fX1;
				pxVertices[1].m_afPosition[1] = fY1;
				pxVertices[1].m_afPosition[2] = fZ1;
				pxVertices[1].m_uColour = uDiffuse;
				pxVertices[3].m_afPosition[0] = fX2;
				pxVertices[3].m_afPosition[1] = fY2;
				pxVertices[3].m_afPosition[2] = fZ2;
				pxVertices[3].m_uColour = uDiffuse;
				pxVertices[2].m_afPosition[0] = fX3;
				pxVertices[2].m_afPosition[1] = fY3;
				pxVertices[2].m_afPosition[2] = fZ3;
				pxVertices[2].m_uColour = uDiffuse;
				pxVertices[4].m_afPosition[0] = fX2;
				pxVertices[4].m_afPosition[1] = fY2;
				pxVertices[4].m_afPosition[2] = fZ2;
				pxVertices[4].m_uColour = uDiffuse;

				g_uOffset += 6;
				g_uNumberOfVertsToRender += 6;
			}
#endif

		} // end particle loop
		// two tris plus to degenerates
	#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(4 * uNumParticles,1,
			ASURA_RENDER_TTYPE_PFX);
	#else
		Asura_Render::AddToTrianglesRendered(4 * uNumParticles);
	#endif
		Asura_Render::AddToParticlesRendered(uNumParticles);
	}// end camera loop
}


void Asura_PFX_Effect::Platform_RenderParticles(const Asura_PFX_Particle* const pxParticles, u_int uNumParticles, const Asura_PFX_Particle_Template* pxTemplate, const Asura_RGBA& xLightColour) const
{
	// crossfade texture anim requires 2 particles rendered for each actual particle
	const u_int uMaxParticlesReq = pxTemplate->m_bCrossfadeTextureFrames ? (uNumParticles * 2) : (uNumParticles);
	const u_int uMaxVertsReq = uMaxParticlesReq * (pxTemplate->m_bCanUseExpensiveClipping ? 
		uASURA_PSP_NUM_PFX_SUBDIV_VERTS : uASURA_PSP_NUM_VERTS_PER_PARTICLE);
	Asura_Assert(g_uOffset + uMaxVertsReq < uASURA_PSP_MAX_PCLE_VERTS_PER_FRAME, "Too many particles this frame");
	if(g_uOffset + uMaxVertsReq >= uASURA_PSP_MAX_PCLE_VERTS_PER_FRAME) return;

	// 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;

	if (g_iTexID != pxTemplate->m_iTextureHandle)
	{
		FlushParticles();
		g_iTexID = pxTemplate->m_iTextureHandle;
		Asura_PSP_TextureManagement::RequestTextureIndex( pxTemplate->m_iTextureHandle );
	}

	const bool bAlwaysOnTop = (m_usPFXFlags & ASURA_PFX_EFFECT_ALWAYS_ON_TOP);

	if ( g_eTranslucencyMode != pxTemplate->m_eTranslucency || g_bAlwaysOnTop != bAlwaysOnTop)
	{
		FlushParticles();
		// get ready for more data
		g_eTranslucencyMode = pxTemplate->m_eTranslucency;
		g_bAlwaysOnTop = bAlwaysOnTop;
	}

	if( pxTemplate->m_bUseLighting
		|| pxTemplate->m_bNearCameraFade 
		|| pxTemplate->m_bFarCameraFade
		|| pxTemplate->m_bFixedRotation
		|| pxTemplate->m_bUseRandomRotation
		|| pxTemplate->m_b3DRotation
		|| pxTemplate->m_bAlignEffectX
		|| pxTemplate->m_bAlignEffectY
		|| pxTemplate->m_bAlignEffectZ
		|| pxTemplate->m_bRandomTextureOrient
		|| pxTemplate->m_bUseSizeScale
		|| pxTemplate->m_bCrossfadeTextureFrames
		|| pxTemplate->m_bCanUseExpensiveClipping
		|| pxTemplate->m_bUseMotionBlur)
	{
#ifdef ASURA_PSP_TRACK_PFX_USAGE
		RecordPFXRendered_Slow(uNumParticles);
#endif
		float fSizeScale = pxTemplate->m_bUseSizeScale ? GetSizeScale() : 1.0f;
		RenderParticlesComplex(m_ucCameraFlags, fSizeScale, GetOrientation(), pxParticles, uNumParticles, pxTemplate, xLightColour, this);
	}
	else
	{
#ifdef ASURA_PSP_TRACK_PFX_USAGE
		RecordPFXRendered_Fast(uNumParticles);
#endif
		RenderParticlesSimple(m_ucCameraFlags, pxParticles, uNumParticles, pxTemplate, this);
	}
}

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

void Asura_PFX_System::Platform_Initialise()
{
}

void Asura_PFX_System::Platform_Reset()
{
	// KOR - 11/01/2006 - particle texture may change
	g_iTexID = ASURA_TEXTURE_INVALID;
}

void Asura_PFX_System::Platform_Shutdown()
{
}


// (MH - 13.01.2006) We need a quick fix for a particular special effect in Miami Vice
// which is used in the front end where the particles get too big and get clipped. I
// have implemented a hardcoded subdivision to work around this. I don't expect this 
// to be used in general as large particles aren't a good idea for fillrate etc.
//
// pxVertices already has texture coordinates arranged clockwise
static void SubdivideParticle(Asura_PSP_RenderParticle_Vertex* pxVertices, 
							  const Asura_AVector3 axVertices[4], 
							  const u_int uColour)
{
	// pxVertices points at the entry after the current place in the vertex buffer 
	// to insert to. Therefore when we actually write out our strip, start at pxVertices[-1].
	//
	// pxVertices has texture coordinates arranged clockwise from entry 0.
	// axVertices has positions arranged clockwise.
	// 	0----1
	// 	|    |
	// 	|    |
	// 	3----2

	// (MH - 13.01.2006) 
	// subdivide and split to a strip as follows.
	// (as we need a fix for this now, I'm hardcoding the subdivision)
	//
    //  A---B---C---D     0,1---2,15--16,17---18
    //  |   |   |   |       |  /  |  \  |  /  |
    //  E---F---G---H       3---4,13--14,19---20
    //  |   |   |   |       |  /  |  \  |  /  |
    //  I---J---K---L       5---6,11--12,21---22
    //  |   |   |   |       |  /  |  \  |  /  |
    //  M---N---O---P       7---8,9---10,23---24,25
	//
	Asura_AVector3 xPosA = axVertices[0];
	Asura_AVector3 xPosB, xPosC;
	Asura_AVector3 xPosD = axVertices[1];
	Asura_AVector3 xPosE, xPosF, xPosG, xPosH, xPosI, xPosJ, xPosK, xPosL;
	Asura_AVector3 xPosM = axVertices[3];
	Asura_AVector3 xPosN, xPosO;
	Asura_AVector3 xPosP = axVertices[2];

	Asura_AVector3 xPosOffsetH = (xPosD - xPosA) * .333333f;	// A->B
	Asura_AVector3 xPosOffsetV = (xPosM - xPosA) * .333333f;	// A->E

	xPosB = xPosA + xPosOffsetH;
	xPosC = xPosB + xPosOffsetH;
	
	xPosE = xPosA + xPosOffsetV;
	xPosF = xPosE + xPosOffsetH;
	xPosG = xPosF + xPosOffsetH;
	xPosH = xPosG + xPosOffsetH;
	
	xPosI = xPosE + xPosOffsetV;
	xPosJ = xPosI + xPosOffsetH;
	xPosK = xPosJ + xPosOffsetH;
	xPosL = xPosK + xPosOffsetH;

	xPosN = xPosM + xPosOffsetH;
	xPosO = xPosN + xPosOffsetH;

	// use Aligned vectors even though texture coords only use 2 elements as it generates
	// smaller code.
	Asura_AVector3 xTexA(pxVertices[0].m_fU, pxVertices[0].m_fV, 0.0f);
	Asura_AVector3 xTexB, xTexC;
	Asura_AVector3 xTexD(pxVertices[1].m_fU, pxVertices[1].m_fV, 0.0f);
	Asura_AVector3 xTexE, xTexF, xTexG, xTexH, xTexI, xTexJ, xTexK, xTexL;
	Asura_AVector3 xTexM(pxVertices[3].m_fU, pxVertices[3].m_fV, 0.0f);
	Asura_AVector3 xTexN, xTexO;
	Asura_AVector3 xTexP(pxVertices[2].m_fU, pxVertices[2].m_fV, 0.0f);

	Asura_AVector3 xTexOffsetH = (xTexD - xTexA) * .333333f;	// A->B
	Asura_AVector3 xTexOffsetV = (xTexM - xTexA) * .333333f;	// A->E

	xTexB = xTexA + xTexOffsetH;
	xTexC = xTexB + xTexOffsetH;
	
	xTexE = xTexA + xTexOffsetV;
	xTexF = xTexE + xTexOffsetH;
	xTexG = xTexF + xTexOffsetH;
	xTexH = xTexG + xTexOffsetH;
	
	xTexI = xTexE + xTexOffsetV;
	xTexJ = xTexI + xTexOffsetH;
	xTexK = xTexJ + xTexOffsetH;
	xTexL = xTexK + xTexOffsetH;

	xTexN = xTexM + xTexOffsetH;
	xTexO = xTexN + xTexOffsetH;

	// start at -1 by decrementing pointer
	--pxVertices;
	pxVertices[0].m_fU = xTexA.GetX();
	pxVertices[0].m_fV = xTexA.GetY();
	pxVertices[0].m_uColour = uColour;
	xPosA.StoreUnaligned(pxVertices[0].m_afPosition);

	pxVertices[1].m_fU = xTexA.GetX();
	pxVertices[1].m_fV = xTexA.GetY();
	pxVertices[1].m_uColour = uColour;
	xPosA.StoreUnaligned(pxVertices[1].m_afPosition);

	pxVertices[2].m_fU = xTexB.GetX();
	pxVertices[2].m_fV = xTexB.GetY();
	pxVertices[2].m_uColour = uColour;
	xPosB.StoreUnaligned(pxVertices[2].m_afPosition);

	pxVertices[3].m_fU = xTexE.GetX();
	pxVertices[3].m_fV = xTexE.GetY();
	pxVertices[3].m_uColour = uColour;
	xPosE.StoreUnaligned(pxVertices[3].m_afPosition);

	pxVertices[4].m_fU = xTexF.GetX();
	pxVertices[4].m_fV = xTexF.GetY();
	pxVertices[4].m_uColour = uColour;
	xPosF.StoreUnaligned(pxVertices[4].m_afPosition);

	pxVertices[5].m_fU = xTexI.GetX();
	pxVertices[5].m_fV = xTexI.GetY();
	pxVertices[5].m_uColour = uColour;
	xPosI.StoreUnaligned(pxVertices[5].m_afPosition);

	pxVertices[6].m_fU = xTexJ.GetX();
	pxVertices[6].m_fV = xTexJ.GetY();
	pxVertices[6].m_uColour = uColour;
	xPosJ.StoreUnaligned(pxVertices[6].m_afPosition);

	pxVertices[7].m_fU = xTexM.GetX();
	pxVertices[7].m_fV = xTexM.GetY();
	pxVertices[7].m_uColour = uColour;
	xPosM.StoreUnaligned(pxVertices[7].m_afPosition);

	pxVertices[8].m_fU = xTexN.GetX();
	pxVertices[8].m_fV = xTexN.GetY();
	pxVertices[8].m_uColour = uColour;
	xPosN.StoreUnaligned(pxVertices[8].m_afPosition);

	pxVertices[9].m_fU = xTexN.GetX();
	pxVertices[9].m_fV = xTexN.GetY();
	pxVertices[9].m_uColour = uColour;
	xPosN.StoreUnaligned(pxVertices[9].m_afPosition);

	pxVertices[10].m_fU = xTexO.GetX();
	pxVertices[10].m_fV = xTexO.GetY();
	pxVertices[10].m_uColour = uColour;
	xPosO.StoreUnaligned(pxVertices[10].m_afPosition);

	pxVertices[11].m_fU = xTexJ.GetX();
	pxVertices[11].m_fV = xTexJ.GetY();
	pxVertices[11].m_uColour = uColour;
	xPosJ.StoreUnaligned(pxVertices[11].m_afPosition);

	pxVertices[12].m_fU = xTexK.GetX();
	pxVertices[12].m_fV = xTexK.GetY();
	pxVertices[12].m_uColour = uColour;
	xPosK.StoreUnaligned(pxVertices[12].m_afPosition);

	pxVertices[13].m_fU = xTexF.GetX();
	pxVertices[13].m_fV = xTexF.GetY();
	pxVertices[13].m_uColour = uColour;
	xPosF.StoreUnaligned(pxVertices[13].m_afPosition);

	pxVertices[14].m_fU = xTexG.GetX();
	pxVertices[14].m_fV = xTexG.GetY();
	pxVertices[14].m_uColour = uColour;
	xPosG.StoreUnaligned(pxVertices[14].m_afPosition);

	pxVertices[15].m_fU = xTexB.GetX();
	pxVertices[15].m_fV = xTexB.GetY();
	pxVertices[15].m_uColour = uColour;
	xPosB.StoreUnaligned(pxVertices[15].m_afPosition);

	pxVertices[16].m_fU = xTexC.GetX();
	pxVertices[16].m_fV = xTexC.GetY();
	pxVertices[16].m_uColour = uColour;
	xPosC.StoreUnaligned(pxVertices[16].m_afPosition);

	pxVertices[17].m_fU = xTexC.GetX();
	pxVertices[17].m_fV = xTexC.GetY();
	pxVertices[17].m_uColour = uColour;
	xPosC.StoreUnaligned(pxVertices[17].m_afPosition);

	pxVertices[18].m_fU = xTexD.GetX();
	pxVertices[18].m_fV = xTexD.GetY();
	pxVertices[18].m_uColour = uColour;
	xPosD.StoreUnaligned(pxVertices[18].m_afPosition);

	pxVertices[19].m_fU = xTexG.GetX();
	pxVertices[19].m_fV = xTexG.GetY();
	pxVertices[19].m_uColour = uColour;
	xPosG.StoreUnaligned(pxVertices[19].m_afPosition);

	pxVertices[20].m_fU = xTexH.GetX();
	pxVertices[20].m_fV = xTexH.GetY();
	pxVertices[20].m_uColour = uColour;
	xPosH.StoreUnaligned(pxVertices[20].m_afPosition);

	pxVertices[21].m_fU = xTexK.GetX();
	pxVertices[21].m_fV = xTexK.GetY();
	pxVertices[21].m_uColour = uColour;
	xPosK.StoreUnaligned(pxVertices[21].m_afPosition);

	pxVertices[22].m_fU = xTexL.GetX();
	pxVertices[22].m_fV = xTexL.GetY();
	pxVertices[22].m_uColour = uColour;
	xPosL.StoreUnaligned(pxVertices[22].m_afPosition);

	pxVertices[23].m_fU = xTexO.GetX();
	pxVertices[23].m_fV = xTexO.GetY();
	pxVertices[23].m_uColour = uColour;
	xPosO.StoreUnaligned(pxVertices[23].m_afPosition);

	pxVertices[24].m_fU = xTexP.GetX();
	pxVertices[24].m_fV = xTexP.GetY();
	pxVertices[24].m_uColour = uColour;
	xPosP.StoreUnaligned(pxVertices[24].m_afPosition);

	pxVertices[25].m_fU = xTexP.GetX();
	pxVertices[25].m_fV = xTexP.GetY();
	pxVertices[25].m_uColour = uColour;
	xPosP.StoreUnaligned(pxVertices[25].m_afPosition);
}
