///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_PSP_BotanicalsElement.cpp
// Author : Catalin Arsenescu
// Date   : 09-March-2007
//
// Copyright (C) 2007 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////
#include "Core\Asura.h"

#ifdef ASURA_USE_BOTANICALS

// This file's header
#include "Botanicals/Asura_PSP_BotanicalsElement.h"

// Asura independent headers
#include "Camera/Asura_Camera.h"
#include "Rendering/Asura_Render.h"
#include "Core/Asura_TextureManagement.h"
#include "Core/Asura_Material_System.h"

// PSP headers
#include "Botanicals/Asura_PSP_BotanicalsTemplate.h"
#include "Rendering/Asura_PSP_Graphics.h"
#include "Botanicals/Asura_PSP_BotanicalsSystem.h"
#include "Rendering/Asura_PSP_TextureManagement.h"

//PSP Sony include
#include "kernel.h"

//////////////////////////////////////////////////////////////////////////////////////////////////
//TODO:																							//
//Remove all the calls to the sceKernelDcacheWritebackAll(); use double buffering for example	//
//////////////////////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsElement::Asura_PSP_BotanicalsElement
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_BotanicalsElement::Asura_PSP_BotanicalsElement()
: m_fCentre(0.0f)
, m_fRadius(0.0f)
, m_iNumLODs(0)
, m_pxLODData(NULL)
#if defined( ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS) && 1 == ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS
, m_xSectionID(0)
, m_xVersionID(0)
#endif
{
}
/// End of function 'Asura_PSP_BotanicalsElement::Asura_PSP_BotanicalsElement'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsElement::~Asura_PSP_BotanicalsElement
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_BotanicalsElement::~Asura_PSP_BotanicalsElement()
{
	delete[] m_pxLODData;
	m_pxLODData = NULL;
	m_iNumLODs = 0;
}
/// End of function 'Asura_PSP_BotanicalsElement::~Asura_PSP_BotanicalsElement'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsElement::Render
// Author       : Catalin Arsenescu
// Date         : 14-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_BotanicalsElement::Render(Asura_PSP_BotanicalsTemplate*	pxTemplate
										,Asura_PSP_BotanicalsShape*		pxShape
										,Asura_Vector_3					xInstancePosition
										,int							iInstanceID
										,float							fDistance
										,float							fInstanceScale
#if defined( ASURA_BOTANICAL_SUPPORT_DYNAMIC_DATA) && 1 == ASURA_BOTANICAL_SUPPORT_DYNAMIC_DATA
										,Asura_BGR_DynamicInstance*		pxDynamicInstanceData
#endif
										)
{
	// It appears that this is being applied twice, effectively; once here and once in the offset for each individual billboard
	// It should be safe to remove the occurrence here; I'm seeing no problems in the output botanicals. However, I've retained
	// the line in case it turns out that that assessment is incorrect.
//	xInstancePosition.y += m_fCentre; 		
	if (!Asura_Camera::IsSphereInFrustum(xInstancePosition, m_fRadius))
	{
		return;
	}

#if defined( ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS) && 1 == ASURA_BOTANICAL_SUPPORT_SECTION_VERSIONS
	const Asura_BGR_Section_ID xCurrElementSectionID = m_xSectionID;
	const Asura_BGR_Version_ID xCurrElementVersionID = m_xVersionID;

	if( true == pxTemplate->HasDynamicSectionVersionData() && NULL != pxDynamicInstanceData)
	{
		const Asura_BGR_Version_ID xInstanceVersionID = pxTemplate->GetDynamicSectionVersion( pxDynamicInstanceData, xCurrElementSectionID);
		if( xCurrElementVersionID != xInstanceVersionID)
		{
			return;
		}
	}
#endif

	for( unsigned int uLOD = 0; uLOD < m_iNumLODs; ++uLOD )
	{
		m_pxLODData[uLOD].Render(xInstancePosition, fDistance, fInstanceScale);
	}
}
/// End of function 'Asura_PSP_BotanicalsElement::Render'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsLODData::Asura_PSP_BotanicalsLODData
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_BotanicalsLODData::Asura_PSP_BotanicalsLODData()
: m_eMeshType(ASURA_PSP_BOTANICALS_NORMAL_MESH)
, m_fMinDistance(0.0f)
, m_fMinFullAlphaDistance(0.0f)
, m_fMaxFullAlphaDistance(0.0f)
, m_fMaxDistance(0.0f)
, m_uNumVisibleInstances(0)
, m_pxGeometryLODs(NULL)
{
}
/// End of function 'Asura_PSP_BotanicalsLODData::Asura_PSP_BotanicalsLODData'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsLODData::~Asura_PSP_BotanicalsLODData
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_BotanicalsLODData::~Asura_PSP_BotanicalsLODData()
{
	switch( m_eMeshType )
	{
		case ASURA_PSP_BOTANICALS_NORMAL_MESH:
		case ASURA_PSP_BOTANICALS_2SIDED_MESH:
		{
			delete m_pxGeometryLODs;
			m_pxGeometryLODs = NULL;
			break;
		}
		case ASURA_PSP_BOTANICALS_BILLBOARDS:
		{
			delete m_pxBillboardLODs;
			m_pxBillboardLODs = NULL;
			break;
		}
		case ASURA_PSP_BOTANICALS_IMPOSTER:
		{
			delete m_pxImpostersLODs;
			m_pxImpostersLODs = NULL;
			break;
		}
		default:
		{
			Asura_Assert( false, "Unknown botanicals element type." );
			break;
		}
	}
}
/// End of function 'Asura_PSP_BotanicalsLODData::~Asura_PSP_BotanicalsLODData'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsLODData::Render
// Author       : Catalin Arsenescu
// Date         : 14-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_BotanicalsLODData::Render(Asura_Vector_3 xInstancePosition, float fDistance, float fInstanceScale)
{
	float fMin = m_fMinDistance; 
	float fMinFull = m_fMinFullAlphaDistance; 
	float fMaxFull = m_fMaxFullAlphaDistance; 
	float fMax = m_fMaxDistance;

	float fFadeValue = 1.0f;

	// Are we in range for this LOD to be rendered?
	if (fDistance >= fMin)
	{
		if (fDistance < fMax)
		{
			// Is this the only LOD that needs rendering?
			if ((fDistance >= fMinFull) && (fDistance < fMaxFull))
			{
				GU_AlphaFunc(SCEGU_GREATER, 0x80, 0xff);
			}
			else
			{
				// Blending in this LoD 
				if (fDistance < fMinFull)
				{
					// Interpolate from 0 to 1
					float fPixelFraction = (fDistance - fMin) / (fMinFull - fMin); 

					fFadeValue = fPixelFraction;
				}
				// Dissolving this LoD
				else
				{
					// Interpolate from 1 to 0
					float fPixelFraction = (fDistance - fMaxFull) / (fMax - fMaxFull); 

					fFadeValue = 1.0f - fPixelFraction;
				}

				int iAlpha = Asura_Maths::FloatToInt_Near(255 / (1.0f + fFadeValue));
				GU_AlphaFunc(SCEGU_GREATER, iAlpha, 0xff);
			}
		}
		else
		{
			// Out of range, don't keep checking
			if(m_eMeshType == ASURA_PSP_BOTANICALS_IMPOSTER)
			{
				m_pxImpostersLODs->Render(xInstancePosition, fInstanceScale, 1.0f);
			}
			return;
		}
	}					
	else
	{
		// Not far away enough, but a nearest LOD could be in range, keep checking
		return;
	}

	switch(m_eMeshType)
	{
	case ASURA_PSP_BOTANICALS_2SIDED_MESH:
		{
			Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_NONE);
			m_pxGeometryLODs->Render();
			Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CW);
			break;
		}
	case ASURA_PSP_BOTANICALS_NORMAL_MESH:
		{
			m_pxGeometryLODs->Render();
			break;
		}
	case ASURA_PSP_BOTANICALS_BILLBOARDS:
		{
			m_pxBillboardLODs->Render(xInstancePosition, fInstanceScale);
			break;
		}
	case ASURA_PSP_BOTANICALS_IMPOSTER:
		{
			m_pxImpostersLODs->Render(xInstancePosition, fInstanceScale, fFadeValue);
			break;
		}
	default:
		{
			break;
		}
	}
	GU_AlphaFunc(SCEGU_GREATER, 0x04, 0xff);
}
/// End of function 'Asura_PSP_BotanicalsLODData::Render'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_BotanicalsLODData::Initialise
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_BotanicalsLODData::Initialise(Asura_PSP_Botanicals_Mesh_Type eType)
{
	m_eMeshType				= eType;

	switch( m_eMeshType )
	{
		case ASURA_PSP_BOTANICALS_NORMAL_MESH:
		case ASURA_PSP_BOTANICALS_2SIDED_MESH:
		{
			m_pxGeometryLODs = new Asura_PSP_Botanicals_LOD_Geometry();
			break;
		}
		case ASURA_PSP_BOTANICALS_BILLBOARDS:
		{
			m_pxBillboardLODs = new Asura_PSP_Botanicals_LOD_Billboards();
			break;
		}
		case ASURA_PSP_BOTANICALS_IMPOSTER:
		{
			m_pxImpostersLODs = new Asura_PSP_Botanicals_LOD_Imposters();
			break;
		}
		default:
		{
			Asura_Assert( false, "Unknown botanicals element type." );
			break;
		}
	}
}
/// End of function 'Asura_PSP_BotanicalsLODData::Initialise'. ///


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Geometry::Asura_PSP_Botanicals_LOD_Geometry
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Geometry::Asura_PSP_Botanicals_LOD_Geometry()
: m_iMaterial(0)
, m_uNumIndices(0)
, m_uNumTriangles(0)
, m_uNumVertices(0)
, m_pxVertices(NULL)
, m_pusIndices(NULL)
{
}
/// End of function 'Asura_PSP_Botanicals_LOD_Geometry::Asura_PSP_Botanicals_LOD_Geometry'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Geometry::~Asura_PSP_Botanicals_LOD_Geometry
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Geometry::~Asura_PSP_Botanicals_LOD_Geometry()
{
	Asura_ResourcePushType(ASURA_RESOURCE_BOTANICAL_MESH);
	{
		Asura_ResourceDeallocateArray(m_pusIndices);
		Asura_ResourceDeallocateArray(m_pxVertices);
		m_pusIndices = NULL;
		m_pxVertices = NULL;
		m_uNumVertices = 0;
		m_uNumTriangles = 0;
		m_uNumIndices = 0;
	}
	Asura_ResourcePopType();
}
/// End of function 'Asura_PSP_Botanicals_LOD_Geometry::~Asura_PSP_Botanicals_LOD_Geometry'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Geometry::Render
// Author       : Catalin Arsenescu
// Date         : 14-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_Botanicals_LOD_Geometry::Render(void)
{
	const int iTexture = Asura_Material_System::GetTextureID( m_iMaterial );
	u_int uMaterialFlags = Asura_Material_System::GetMaterialFlags( m_iMaterial );
	const u_int uTextureFlags = Asura_Texture_Management::GetTextureFlags( iTexture );

	// Renders as 1-bit alpha, so this is safe
	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( true );		

	Asura_PSP_Graphics::SetupEntityAlphaStates( uMaterialFlags, uTextureFlags, false );
	Asura_PSP_TextureManagement::RequestTextureIndex( iTexture );

	GU_CallList( m_uDrawArrayDisplayList ); 

	// Dome debug info
#if defined ASURA_RENDER_TTYPES
	Asura_Render::AddToTrianglesRendered(m_uNumTriangles,1,
		ASURA_RENDER_TTYPE_OBJ);
#else
	Asura_Render::AddToTrianglesRendered(m_uNumTriangles);
#endif
	
	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( false );		
}
/// End of function 'Asura_PSP_Botanicals_LOD_Geometry::Render'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Billboards::Asura_PSP_Botanicals_LOD_Billboards
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Billboards::Asura_PSP_Botanicals_LOD_Billboards()
: m_iMaterial(0)
, m_uNumberOfRectangles(0)
, m_uNumberOfVertices(0)
, m_pxVertices(NULL)
, m_pxPositions(NULL)
, m_pxNormals(NULL)
{
}
/// End of function 'Asura_PSP_Botanicals_LOD_Billboards::Asura_PSP_Botanicals_LOD_Billboards'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Billboards::~Asura_PSP_Botanicals_LOD_Billboards
// Author       : Catalin Arsenescu
// Date         : 13-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Billboards::~Asura_PSP_Botanicals_LOD_Billboards()
{
	Asura_ResourcePushType(ASURA_RESOURCE_BOTANICAL_MESH);
	{
		Asura_ResourceDeallocateArray(m_pxVertices);
		delete[] m_pxPositions; 
		delete[] m_pxNormals; 
		m_pxPositions = NULL;
		m_pxNormals = NULL;
		m_pxVertices = NULL;
		m_uNumberOfRectangles = 0;
		m_uNumberOfVertices = 0;
	}
	Asura_ResourcePopType();
}
/// End of function 'Asura_PSP_Botanicals_LOD_Billboards::~Asura_PSP_Botanicals_LOD_Billboards'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Billboards::Render
// Author       : Catalin Arsenescu
// Date         : 15-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_Botanicals_LOD_Billboards::Render(Asura_Vector_3& xInstancePosition, float fInstanceScale)
{
	const int iTexture = Asura_Material_System::GetTextureID( m_iMaterial );
	u_int uMaterialFlags = Asura_Material_System::GetMaterialFlags( m_iMaterial );
	const u_int uTextureFlags = Asura_Texture_Management::GetTextureFlags( iTexture );
	
	// Renders as 1-bit alpha, so this is safe
	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( true );
		
	Asura_PSP_Graphics::SetupEntityAlphaStates( uMaterialFlags, uTextureFlags, false );
	Asura_PSP_TextureManagement::RequestTextureIndex( iTexture );

	// Billboard orientation matrix manually built into the billboard cluster
	// Need to nullify the hardware one
	GUM_MatrixMode(SCEGU_MATRIX_WORLD);
	GUM_PushMatrix();
	GUM_LoadIdentity();

	// Built an orientation matrix for the cluster, facing the camera, rotating around the vertical axis
	// Calculate the cluster center -> camera position vector
	const Asura_Vector_3* xCameraPosition = Asura_Camera::GetPosition(); 

	Asura_Vector_3 xInstanceCameraDirection = *xCameraPosition - xInstancePosition; 
	// Build a coordinate system for the cluster
	xInstanceCameraDirection.Normalise(); 
	Asura_Vector_3 xCameraUp(0.0f, 1.0f, 0.0f);
	Asura_Vector_3 xCameraRight = xCameraUp ^ xInstanceCameraDirection;

#if defined INDIVIDUAL_BILLBOARD_LIGHTING
	// Need this bit to rotate the normal
	Asura_Matrix_3x3 xBillboardOrientation(	-xCameraRight.x, xCameraUp.x, xInstanceCameraDirection.x,
											-xCameraRight.y, xCameraUp.y, xInstanceCameraDirection.y, 
											-xCameraRight.z, xCameraUp.z, xInstanceCameraDirection.z);
#endif // defined INDIVIDUAL_BILLBOARD_LIGHTING
	for (unsigned int uBillboard = 0; uBillboard < m_uNumberOfRectangles; uBillboard++)
	{
		// TODO: using a static buffer to render the billboards to. Need to think how to do this (move to VRAM?)

		// I don't see any security on this; what's stopping it just overflowing? Just to reassure myself, I've added in an assert and a quick-out.
		if(Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset >= g_uASURA_PSP_BOTANICALRENDER_BILLBOARD_BUFFER_SIZE)
		{
			Asura_Assert(0, "Billboard Buffer full");
			break;
		}

		Asura_PSP_BotanicalBillboard_Vertex* pxVertexBuffer = &(Asura_PSP_BotanicalsSystem::s_pxBillboardBuffer[Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset]); 

		// Create each individual billboard using the previously calculated coordinate system
		for (unsigned int uVertex = 0; uVertex < 4; uVertex++)
		{					
			// Offset into the billboard buffer for the current billboard
			unsigned int uBillboardVertexOffset = 4 * uBillboard + uVertex; 
		
			// Quad size
			float fHorizontalSize	= m_pxVertices[uBillboardVertexOffset].m_fWidthOffset;
			float fVerticalSize		= m_pxVertices[uBillboardVertexOffset].m_fHeightOffset;

			Asura_Vector_3 xDeltaX = xCameraRight * fHorizontalSize * fInstanceScale;
			Asura_Vector_3 xDeltaY = xCameraUp * fVerticalSize * fInstanceScale;

			// Translate the quad to its position
			Asura_Vector_3 xBillboardPosition(xInstancePosition + m_pxPositions[uBillboard]); 
			pxVertexBuffer[uVertex].m_xPosition = xBillboardPosition + xDeltaX + xDeltaY; 

			// Texture coordinates
			pxVertexBuffer[uVertex].m_fU = m_pxVertices[uBillboardVertexOffset].m_fU; 
			pxVertexBuffer[uVertex].m_fV = m_pxVertices[uBillboardVertexOffset].m_fV; 

		#if defined INDIVIDUAL_BILLBOARD_LIGHTING
			pxVertexBuffer[uVertex].m_xNormal = xBillboardOrientation.ApplyTranspose( m_pxNormals[uBillboard] ); 
		#else
			pxVertexBuffer[uVertex].m_xNormal = xInstanceCameraDirection;
		#endif // defined INDIVIDUAL_BILLBOARD_LIGHTING
		}

		// Next billboard 
		Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset += 4;
		sceKernelDcacheWritebackAll();
	
		// Draw the last billboard
		GUM_DrawArray
		(
			uBOTANICAL_BILLBOARD_PRIM_TYPE,
			uBOTANICAL_BILLBOARD_VERTEX_FORMAT,
			4,
			0,
			(void*)pxVertexBuffer
		);

		// Some debug info
	#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(2,1,
			ASURA_RENDER_TTYPE_OBJ);
	#else
		Asura_Render::AddToTrianglesRendered(2);
	#endif
	}

	// End using the cluster local coordinate, back to object space
	GUM_PopMatrix();
	
	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( false );
}
/// End of function 'Asura_PSP_Botanicals_LOD_Billboards::Render'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Imposters::Asura_PSP_Botanicals_LOD_Imposters
// Author       : Catalin Arsenescu
// Date         : 15-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Imposters::Asura_PSP_Botanicals_LOD_Imposters()
: m_iMaterial(0)
, m_iNumAngles(0)
, m_pxAngles(NULL)
{
}
/// End of function 'Asura_PSP_Botanicals_LOD_Imposters::Asura_PSP_Botanicals_LOD_Imposters'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Imposters::~Asura_PSP_Botanicals_LOD_Imposters
// Author       : Catalin Arsenescu
// Date         : 15-March-2007
////////////////////////////////////////////////////////////////////////////////
Asura_PSP_Botanicals_LOD_Imposters::~Asura_PSP_Botanicals_LOD_Imposters()
{
	Asura_ResourcePushType(ASURA_RESOURCE_BOTANICAL_MESH);
	{
		delete[] m_pxAngles;
		m_pxAngles = NULL;
		m_iNumAngles = 0;
	}
	Asura_ResourcePopType();
}
/// End of function 'Asura_PSP_Botanicals_LOD_Imposters::~Asura_PSP_Botanicals_LOD_Imposters'. ///

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_PSP_Botanicals_LOD_Imposters::Render
// Author       : Catalin Arsenescu
// Date         : 15-March-2007
////////////////////////////////////////////////////////////////////////////////
void Asura_PSP_Botanicals_LOD_Imposters::Render(Asura_Vector_3& xInstancePosition, float fInstanceScale, float fFadeValue)
{

	const int iTexture = Asura_Material_System::GetTextureID( m_iMaterial );
	u_int uMaterialFlags = Asura_Material_System::GetMaterialFlags( m_iMaterial );
	const u_int uTextureFlags = Asura_Texture_Management::GetTextureFlags( iTexture );

	// Renders as 1-bit alpha, so this is safe
	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( true );		

	Asura_PSP_Graphics::SetupEntityAlphaStates( uMaterialFlags, uTextureFlags, false );
	Asura_PSP_TextureManagement::RequestTextureIndex( iTexture );

	// Billboard orientation matrix manually built into the billboard cluster
	// Need to nullify the hardware one
	GUM_MatrixMode(SCEGU_MATRIX_WORLD);
	GUM_PushMatrix();
	GUM_LoadIdentity();

	// Built an orientation matrix for the cluster, facing the camera, rotating around the vertical axis
	// Calculate the cluster center -> camera position vector
	const Asura_Vector_3* xCameraPosition = Asura_Camera::GetPosition(); 
	Asura_Vector_3 xInstanceCameraDirection = *xCameraPosition - xInstancePosition; 
	// Build a coordinate system for the cluster
	xInstanceCameraDirection.Normalise(); 
	Asura_Vector_3 xCameraUp(0.0f, 1.0f, 0.0f);
	Asura_Vector_3 xCameraRight = xCameraUp ^ xInstanceCameraDirection;

#if defined INDIVIDUAL_BILLBOARD_LIGHTING
	// Need this bit to rotate the normal
	Asura_Matrix_3x3 xBillboardOrientation(	-xCameraRight.x, xCameraUp.x, xInstanceCameraDirection.x,
											-xCameraRight.y, xCameraUp.y, xInstanceCameraDirection.y, 
											-xCameraRight.z, xCameraUp.z, xInstanceCameraDirection.z);
#endif // defined INDIVIDUAL_BILLBOARD_LIGHTING

	for(int iAngle=0; iAngle<m_iNumAngles; iAngle++)
	{
		// I don't see any security on this; what's stopping it just overflowing? Just to reassure myself, I've added in an assert and a quick-out.
		if(Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset >= g_uASURA_PSP_BOTANICALRENDER_BILLBOARD_BUFFER_SIZE)
		{
			Asura_Assert(0, "Billboard Buffer full");
			break;
		}

		Asura_PSP_BotanicalBillboard_Vertex* pxVertexBuffer = &(Asura_PSP_BotanicalsSystem::s_pxBillboardBuffer[Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset]); 

		// Determine the fade value for this imposter, and whether to render it at all.

		// TODO: While this change now takes in the four different angles for the billboards, it does not bear in mind the orientation of the mesh 
		// when trying to determine which of the available billboards to use, because the orientation isn't available in any convenient form to 
		// work with, just as a prebaked value saved out in a display list. We'd need to expand the PSP botanical chunk to cater for this.
		Asura_Vector_3 xNormal = m_pxAngles[iAngle].m_xNormal;			
		
		xNormal.y=0;
		xNormal.Normalise();

		float fDot= xNormal*xInstanceCameraDirection;
		float fAng= Asura_Maths::ArcCosine(fDot)/(Asura_Maths::Pi/180.0f);
		float fMax= m_pxAngles[iAngle].m_fAngleRange*0.5f;
		float fFadeStart=fMax-m_pxAngles[iAngle].m_fFadeRange;

		if (fAng>fMax)
		{		
			// Out of range; don't render this one.
			continue;
		}

		float fImposterFade=1.0f;
		if (fAng>fFadeStart)
		{
			fImposterFade=fAng-fFadeStart;
			fImposterFade=1.0f-(fImposterFade/m_pxAngles[iAngle].m_fFadeRange);
		}

		int nFade=(int)((1.0f-(fFadeValue*fImposterFade))*127.0f);
		Asura_Render::CurrentStates.RequestAlphaTest( true );
		GU_AlphaFunc(SCEGU_GREATER, 0x80+nFade, 0xff);

		// Create each individual billboard using the previously calculated coordinate system
		const float fHalfH = (m_pxAngles[iAngle]).m_fWidth / 2.0f;
		const float fHalfV = (m_pxAngles[iAngle]).m_fHeight / 1.0f;
		const float fHSize[4] = {-fHalfH, fHalfH, -fHalfH, fHalfH};
		const float fVSize[4] = {-fHalfV, -fHalfV, 0.0f, 0.0f, };
		const float fU[4] = {(m_pxAngles[iAngle]).m_fU[1], (m_pxAngles[iAngle]).m_fU[0], (m_pxAngles[iAngle]).m_fU[1], (m_pxAngles[iAngle]).m_fU[0]};
		const float fV[4] = {(m_pxAngles[iAngle]).m_fV[0], (m_pxAngles[iAngle]).m_fV[0], (m_pxAngles[iAngle]).m_fV[1], (m_pxAngles[iAngle]).m_fV[1]};
		for (unsigned int uVertex = 0; uVertex < 4; uVertex++)
		{					
			// Offset into the billboard buffer for the current billboard
			unsigned int uBillboardVertexOffset = uVertex; 
		
			// Quad size
			float fHorizontalSize	= fHSize[uVertex];
			float fVerticalSize		= fVSize[uVertex];
	
			Asura_Vector_3 xDeltaX = xCameraRight * fHorizontalSize * fInstanceScale;
			Asura_Vector_3 xDeltaY = xCameraUp * fVerticalSize * fInstanceScale;
	
			// Translate the quad to its position
//			Asura_Vector_3 xBillboardPosition(xInstancePosition + m_pxPositions[uBillboard]);
			Asura_Vector_3 xBillboardPosition(xInstancePosition);
			pxVertexBuffer[uVertex].m_xPosition = xBillboardPosition + xDeltaX + xDeltaY;
	
			// Texture coordinates
			pxVertexBuffer[uVertex].m_fU = fU[uVertex];
			pxVertexBuffer[uVertex].m_fV = fV[uVertex]; 
		#if defined INDIVIDUAL_BILLBOARD_LIGHTING
			pxVertexBuffer[uVertex].m_xNormal = xBillboardOrientation.ApplyTranspose( (m_pxAngles[iAngle]).m_xNormal ); 
		#else
			pxVertexBuffer[uVertex].m_xNormal = xInstanceCameraDirection;
		#endif // defined INDIVIDUAL_BILLBOARD_LIGHTING
		}

		// Next billboard 
		Asura_PSP_BotanicalsSystem::s_uBillboardBufferOffset += 4; 

		sceKernelDcacheWritebackAll();


		// Draw the last billboard
		GUM_DrawArray
		(
			uBOTANICAL_BILLBOARD_PRIM_TYPE,
			uBOTANICAL_BILLBOARD_VERTEX_FORMAT,
			4,
			0,
			(void*)pxVertexBuffer
		);

		// Some debug info
#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(2,1,
			ASURA_RENDER_TTYPE_OBJ);
#else
		Asura_Render::AddToTrianglesRendered(2);
#endif
	}

	// End using the cluster local coordinate, back to object space
	GUM_PopMatrix();

	Asura_PSP_Graphics::SetTransparentEntityWriteToZBuffer( false );		
}
/// End of function 'Asura_PSP_Botanicals_LOD_Imposters::Render'. ///

#endif //ASURA_USE_BOTANICALS
