//
// Asura_PSP_CharacterRenderer.cpp
//
// Created by KJFL - Wednesday, March 03, 2004
//
// Copyright (C) 2004 Rebellion, All Rights Reserved.
//

#include "Core/Asura.h"
#include "Core/Memory/Asura_Mem.h"
#include <kernel.h>
#include <libvfpu.h>

// This file's header.
#include "Rendering/Asura_PSP_CharacterRenderer.h"
#include "Rendering/Asura_PSP_CharacterRenderer_Internal.h"

// Asura headers

#include "Rendering/Asura_PSP_Filter.h"
#include "Animation/Asura_Hierarchy.h"
#include "Animation/Asura_TextureAnimation.h"
#include "Core/Asura_HashTree.h"
#include "Core/Asura_Stream.h"
#include "Core/Asura_Material_System.h"
#include "Core/Asura_TextureManagement.h"
#include "Rendering/Asura_Render.h"
#include "Rendering/Asura_PSP_Graphics.h"
#include "Rendering/Asura_PSP_RenderStates.h"
#include "Rendering/Asura_PSP_TextureManagement.h"

#include "Camera/Asura_Camera.h"

DEBUGVAR bool dbg_bCharacterShadows = false;
DEBUGVAR bool dbg_bNoAnims = false;

DEBUGVAR bool dbg_bCharacterRendererFastDrawArray = true; 

////////////////////////////////////////////////////////////////////////////////
//
// Asura_PSP_CharacterRender_Handler
//
////////////////////////////////////////////////////////////////////////////////

void Asura_PSP_CharacterRender_Handler::Reset (void)
{
	m_xHashTree.Reset();
}

Asura_PSP_SmoothSkin* Asura_PSP_CharacterRender_Handler::AddCharacter( Asura_Hash_ID uHash )						
{
	bool bDidExist = true;
	Asura_PSP_SmoothSkin* pxSmoothSkin = m_xHashTree.AddOrFindNode(uHash,&bDidExist,true);

	Asura_Assert(pxSmoothSkin,"Out of memory creating new hash tree node");
	Asura_Assertf(!(pxSmoothSkin && bDidExist),("Duplicate item 0x08%x",uHash));

	return (bDidExist) ? NULL : pxSmoothSkin;
}

int Asura_PSP_CharacterRender_Handler::GetMaterialIndex( const Asura_Hierarchy_Skin* pxSkin ) const
{
	const Asura_PSP_SmoothSkin*	pxPSPSkin	= GetCharacter(pxSkin);

	// Return the material...
	return((pxPSPSkin == NULL) ? ASURA_MATERIAL_NONE : pxPSPSkin->m_pxGroups[0].m_iMaterial);
}



////////////////////////////////////////////////////////////////////////////////
//
// Asura_PSP_CharacterRenderer
//
////////////////////////////////////////////////////////////////////////////////

#if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)
Asura_TemplateHashTree<Asura_PSP_CharacterRender_Handler>	Asura_PSP_CharacterRenderer::s_xHandlerTree;
Asura_PSP_CharacterRender_Handler*							Asura_PSP_CharacterRenderer::s_pxCurrentHandler	= NULL;
#else
Asura_PSP_CharacterRender_Handler							Asura_PSP_CharacterRenderer::s_xHandler;
#endif

#ifdef ASURA_HIERARCHY_QUAD
	Asura_AMatrix4x4										Asura_PSP_CharacterRenderer::s_xView;
	Asura_AMatrix4x4										Asura_PSP_CharacterRenderer::s_axBonePosAndOri[ ASURA_MAX_NO_OF_BONES_PER_MODEL ];
#else
	Asura_PSP_Matrix4										Asura_PSP_CharacterRenderer::s_axBonePosAndOri[ASURA_MAX_NO_OF_BONES_PER_MODEL];
#endif

int															Asura_PSP_CharacterRenderer::s_iLastMaterial					= ASURA_MATERIAL_FIRSTID - 1;

Asura_PSP_CharacterRenderer::FnBeginRendering*				Asura_PSP_CharacterRenderer::s_pxBeginRenderingCharacter		= NULL;
Asura_PSP_CharacterRenderer::FnSetRenderingStates*			Asura_PSP_CharacterRenderer::s_pxSetRenderingStatesCharacter	= NULL;
Asura_PSP_CharacterRenderer::FnEndRendering*				Asura_PSP_CharacterRenderer::s_pxEndRenderingCharacter			= NULL;

void Asura_PSP_CharacterRenderer::Initialise(void)
{
	// Nothing to initialise...
}

void Asura_PSP_CharacterRenderer::Reset()
{
#if defined(ASURA_USE_STREAMING)
	s_xHandlerTree.Reset();
	s_pxCurrentHandler	= NULL;
#else
	GetCurrentHandler().Reset();
#endif
}

void Asura_PSP_CharacterRenderer::Shutdown(void)
{
#if defined(ASURA_USE_STREAMING) || defined(ASURA_USE_MULTIPLE_HANDLERS)
	s_xHandlerTree.Reset();
	s_pxCurrentHandler	= NULL;
#else
	s_xHandler.Reset();
#endif
}

#if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)

void Asura_PSP_CharacterRenderer::ReparentHandler(Asura_PSP_CharacterRender_Handler& xSourceHandler, const Asura_Hash_ID uNewParentHash)
{
	Asura_PSP_CharacterRender_Handler*	pxParentHandler	= s_xHandlerTree.GetDataByHash(uNewParentHash);

	if(pxParentHandler != NULL)
	{
		xSourceHandler.m_xHashTree.SetParent(&(pxParentHandler->m_xHashTree));
	}
}

void Asura_PSP_CharacterRenderer::ReparentHandler(const Asura_Hash_ID uSourceHash, const Asura_Hash_ID uNewParentHash)
{
	Asura_PSP_CharacterRender_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uSourceHash);

	if(pxHandler != NULL)
	{
		ReparentHandler(*pxHandler, uNewParentHash);
	}
}

Asura_PSP_CharacterRender_Handler* Asura_PSP_CharacterRenderer::CreateHandler (const Asura_Hash_ID uHash, const Asura_Hash_ID uParentHandlerHash)
{
	Asura_PSP_CharacterRender_Handler*	pxHandler	= s_xHandlerTree.GetDataByHash(uHash);

	// Do we already have this zone?
	if(pxHandler == NULL)
	{
		// No - create one...
		pxHandler	= s_xHandlerTree.AddNode(uHash);

		if(pxHandler != NULL)
		{
			ReparentHandler( *pxHandler, uParentHandlerHash );
		}
	}
	else
	{
		// Yes - this shouldn't happen...
		Asura_Assert(false, "Already have a character handler with this hash-ID.");
	}

	// Return the zone...
	return(pxHandler);
}


bool Asura_PSP_CharacterRenderer::DestroyHandler (const Asura_Hash_ID uHash)
{
	// Call the delete function...
	return(s_xHandlerTree.DeleteNodeByHash(uHash));
}


void Asura_PSP_CharacterRenderer::WriteHandlerReport(u_int uHandler, Asura_Stream& xLog)
{
	const u_int uLOGBUFFERSIZE = 64;
	char szLogBuffer[uLOGBUFFERSIZE];

	Asura_PSP_CharacterRender_Handler* pxHandler = s_xHandlerTree.GetDataByHash(uHandler);
	if (pxHandler)
	{
		if (!pxHandler->m_xHashTree.GetNumberOfNodes())
		{
			xLog << "PSP SmoothSkin:\t\t\tNone\n";
		}
		else
		{
			Asura_PrintfToBfr( szLogBuffer, "PSP SmoothSkin:\t\t\t%d Item(s)\n", uLOGBUFFERSIZE, true, pxHandler->m_xHashTree.GetNumberOfNodes() );
			xLog << szLogBuffer;
		}
	}
	else
	{
		xLog << "PSP SmoothSkin:\t\t\tNo Handler\n";
	}
}

#endif	//__if defined(ASURA_USE_MULTIPLE_HANDLERS) || defined(ASURA_USE_STREAMING)__


//#define USE_SEB_BONEMATRIX

#ifdef USE_SEB_BONEMATRIX
//#include<kernel.h>
void SebGuBoneMatrix(u_int uBone, const Asura_PSP_Matrix4* pxMatrix)
{
	//sceKernelDcacheWritebackAll();
	u_int *pData;
	pData = (u_int *)GU_GetMemory( 11*sizeof(u_int) ) ; // Allocate 13 commands 11+2 from the branch)
	//pData -= 2;   // To write over the branch (CMD_BASE+CMD_BRANCH)
	pData[-2] = 0x2A000000 | (uBone * 12);
	pData[-1] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->x.x) >> 8);
	pData[ 0] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->x.y) >> 8);
	pData[ 1] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->x.z) >> 8);
	pData[ 2] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->y.x) >> 8);
	pData[ 3] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->y.y) >> 8);
	pData[ 4] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->y.z) >> 8);
	pData[ 5] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->z.x) >> 8);
	pData[ 6] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->z.y) >> 8);
	pData[ 7] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->z.z) >> 8);
	pData[ 8] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->w.x) >> 8);
	pData[ 9] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->w.y) >> 8);
	pData[10] = 0x2B000000 | (*reinterpret_cast<const u_int*>(&pxMatrix->w.z) >> 8);
}
#endif

void Asura_PSP_CharacterRenderer::DrawArray(unsigned int uPrimType, unsigned int uVertexFormat, unsigned int uVertices, void* pIndices, void* pBuffer) 
{
#if 0
	if (dbg_bCharacterRendererFastDrawArray)
	{
		// Get a pointer to the display list buffer
		u_int	puDisplayListAddress =	(u_int)	GU_GetMemory(2 * sizeof(u_int));
		
		// Use uncached memory access
		u_int*	puDisplayList =			(u_int*)(puDisplayListAddress | 0x40000000); 

		// Write ONLY the commands needed
		puDisplayList[-2]	= (0x12000000) | ((0x00FFFFFF) & uVertexFormat);					// CMD_VTYPE: Set the vertex format (could be set less often, i.e. first pass, dual pass)
		puDisplayList[-1]	= (0x10000000) | ((0x00FF0000) & ((unsigned int)pBuffer >> 8));		// CMD_BASE: Set the vertex buffer base address higher bits
		puDisplayList[ 0]	= (0x01000000) | ((0x00FFFFFF) & (unsigned int)pBuffer);			// CMD_VADR: Set the vertex buffer base address lower bits 
		puDisplayList[ 1]	= (0x04000000) | (uPrimType << 16) | ((0x0000FFFF) & uVertices);	// CMD_PRIM: Set the primitive to be render (actual draw function)
	}
	else
#endif
	{
		GUM_DrawArray(uPrimType, uVertexFormat, uVertices, pIndices, pBuffer); 
	}
}

int Asura_PSP_CharacterRenderer::SetDefaultRenderingStates(const Asura_PSP_SmoothSkin_Group& xGroup, const float fAlpha, const int iTextureOverride )
{
	if(s_iLastMaterial != xGroup.m_iMaterial)
	{
		const int iTexture = (iTextureOverride != ASURA_TEXTURE_NONE) ? iTextureOverride : Asura_Material_System::GetTextureID( xGroup.m_iMaterial );

		Asura_TextureAnimationOutput xTextureAnim;
		if(Asura_Material_System::GetTextureAnimationState(xGroup.m_iMaterial,0,&xTextureAnim))
		{
			Asura_PSP_RenderStates::RequestTexOffset(xTextureAnim.m_fUOffset, xTextureAnim.m_fVOffset);
		}
		else
		{
			Asura_PSP_RenderStates::RequestTexOffset(0.f, 0.f);
		}

		const u_int uTextureFlags = Asura_Texture_Management::GetTextureFlags(iTexture);
		const u_int uMaterialFlags = Asura_Material_System::GetMaterialFlags(xGroup.m_iMaterial);

		Asura_PSP_Graphics::SetupEntityAlphaStates(uMaterialFlags, uTextureFlags, (fAlpha < 0.999f), Asura_Material_System::GetProjectFlags( xGroup.m_iMaterial ));

		Asura_PSP_TextureManagement::RequestTextureIndex( iTexture );

		// Update last material
		return xGroup.m_iMaterial;
	}

	return s_iLastMaterial;
}

#ifndef ASURA_HIERARCHY_QUAD
void Asura_PSP_CharacterRenderer::Render( const Asura_Hierarchy_Skin* pxSkin, const Asura_Bounding_Box& xLightingBB, const float fAlpha, const int iTextureOverride )
{
	// Current character skin
	const Asura_PSP_CharacterRender_Handler&	xHandler	= GetCurrentHandler();
	const Asura_PSP_SmoothSkin*					pxPSPSkin	= xHandler.GetCharacter(pxSkin);

	if (!pxPSPSkin) return;

	// Vertex buffer 
	u_char* pxVertexBufferStart = pxPSPSkin->m_pxVertices;

	Asura_PSP_Matrix4 xMatrix;
	Asura_PSP_Graphics::SetUseColourDoubling(true);
	Asura_PSP_Graphics::SetupEntityLights( fAlpha, Asura_Hierarchy_System::s_bBonesInWorldspace );
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CW);

	if(Asura_Hierarchy_System::s_bBonesInWorldspace)
	{
		GUM_MatrixMode(SCEGU_MATRIX_WORLD);
		GUM_PushMatrix();
		GUM_LoadIdentity();
		GUM_SetCurMatrix();
		if(s_pxBeginRenderingCharacter)
		{
			Asura_PSP_Matrix4 xViewMatrix;
			GUM_MatrixMode(SCEGU_MATRIX_VIEW);
			GUM_PushMatrix();
			GUM_StoreMatrix(&xViewMatrix);
			GUM_MatrixMode(SCEGU_MATRIX_WORLD);

			Asura_Mem::Set(&xMatrix, 0, sizeof(Asura_PSP_Matrix4));
			xMatrix.x.x	= 1.0f;
			xMatrix.y.y	= 1.0f;
			xMatrix.z.z	= 1.0f;
			xMatrix.w.w	= 1.0f;

			s_pxBeginRenderingCharacter(&xViewMatrix, &xMatrix);
		}
	}
	else
	{
		// KOR - 3/3/2006 - entity renderers now premultiply view matrix
		// into world matrix, to get around PSP's 24-bit precision rubbishness.
		Asura_PSP_Matrix4 xViewMatrix;
		GUM_MatrixMode(SCEGU_MATRIX_VIEW);
		GUM_PushMatrix();
		GUM_StoreMatrix(&xViewMatrix);
		GUM_LoadIdentity();
		GUM_SetCurMatrix(); 

		// Set up pos & ori matrix
		const Asura_Matrix_3x3 xOri = *Asura_Hierarchy_System::s_pxOri;
		const Asura_Vector_3 xPos = *Asura_Hierarchy_System::s_pxPos;
		xMatrix.x.x = xOri.e11;
		xMatrix.x.y = xOri.e21;
		xMatrix.x.z = xOri.e31;
		xMatrix.y.x = xOri.e12;
		xMatrix.y.y = xOri.e22;
		xMatrix.y.z = xOri.e32;
		xMatrix.z.x = xOri.e13;
		xMatrix.z.y = xOri.e23;
		xMatrix.z.z = xOri.e33;
		xMatrix.w.x = xPos.x;
		xMatrix.w.y = xPos.y;
		xMatrix.w.z = xPos.z;	
		xMatrix.w.w = 1.0f;
		xMatrix.x.w = 0.f;
		xMatrix.y.w = 0.f;
		xMatrix.z.w = 0.f;
		GUM_MatrixMode(SCEGU_MATRIX_WORLD);
		GUM_PushMatrix();
		GUM_LoadMatrix(&xViewMatrix);
		GUM_MultMatrix(&xMatrix);
		GUM_SetCurMatrix(); 

		if(s_pxBeginRenderingCharacter)
		{
			s_pxBeginRenderingCharacter(&xViewMatrix, &xMatrix);
		}
	}

	const u_int uVertexFormat = 
		pxPSPSkin->m_bColoursReplaceNormals ?
			(SCEGU_WEIGHT_4|
			SCEGU_WEIGHT_UBYTE|
			SCEGU_COLOR_PF8888|
			SCEGU_TEXTURE_USHORT|
			SCEGU_VERTEX_SHORT)
			:
			(SCEGU_WEIGHT_4|
			SCEGU_WEIGHT_UBYTE|
			SCEGU_COLOR_NONE|
			SCEGU_TEXTURE_USHORT|
			SCEGU_NORMAL_SHORT|
			SCEGU_VERTEX_SHORT)
			;

	u_int auCurrentBoneIndex[4] = { 0, 0, 0, 0 };

	s_iLastMaterial = ASURA_MATERIAL_FIRSTID - 1;

	for (u_int u=0; u<pxPSPSkin->m_uNumberOfGroups; u++)
	{
		const Asura_PSP_SmoothSkin_Group& xGroup = pxPSPSkin->m_pxGroups[u];

		if(s_pxSetRenderingStatesCharacter)
		{
			s_iLastMaterial = s_pxSetRenderingStatesCharacter(xGroup, fAlpha, iTextureOverride);
		}
		else
		{
			s_iLastMaterial = SetDefaultRenderingStates(xGroup, fAlpha, iTextureOverride);
		}

		const u_char* pxVertices = pxVertexBufferStart + (xGroup.m_uStartVertex * pxPSPSkin->m_xVertexSize);
		
        for(u_int b=0; b<4; b++)
		{
			const u_int uBoneIndex = dbg_bNoAnims ? 0 : xGroup.m_aucBoneIDs[b];
		
			if (uBoneIndex && auCurrentBoneIndex[b] != uBoneIndex)
			{
				// The platform hierarchy now sets up all the values, including the scaling, for us.
#ifdef USE_SEB_BONEMATRIX
				SebGuBoneMatrix( b, &s_axBonePosAndOri[uBoneIndex] );
#else
				GU_BoneMatrix( b, &s_axBonePosAndOri[uBoneIndex] );
#endif
				auCurrentBoneIndex[b] = uBoneIndex;
			}
		}
		DrawArray
		(
			SCEGU_PRIM_TRIANGLE_STRIP,
			uVertexFormat,
			xGroup.m_uNumberOfVertices,
			0,
			(void*)pxVertices
		);

	#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2,1,
			ASURA_RENDER_TTYPE_SKIN);
	#else
		Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2);
	#endif
	}

	// JG - 11/09/07: Set UV offsets to zero by default
	Asura_PSP_RenderStates::RequestTexOffset( 0.0f, 0.0f ); 

	if(s_pxEndRenderingCharacter)
	{
		s_pxEndRenderingCharacter();
	}

	if(dbg_bCharacterShadows)
	{
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);

		Asura_PSP_Graphics::SetMaterialType(ASURA_PSP_GRAPHICS_MATERIALTYPE_SHADOW);
		Asura_PSP_Graphics::SetNumberOfLights(0);

		Asura_PSP_RenderStates::RequestAmbient(0xff000000);

		Asura_PSP_TextureManagement::RequestTextureIndex( ASURA_TEXTURE_NONE );
		
		// Shadow
		for (u_int u=0; u<pxPSPSkin->m_uNumberOfGroups; u++)
		{
			Asura_PSP_SmoothSkin_Group& xGroup = pxPSPSkin->m_pxGroups[u];

			//int iTexture = Asura_Material_System::GetTextureID( xGroup.m_iMaterial );


			void* pxVertices = pxPSPSkin->m_pxVertices + (xGroup.m_uStartVertex * pxPSPSkin->m_xVertexSize);
		
			for(u_int b=0; b<4; b++)
			{
				u_int uBoneIndex = xGroup.m_aucBoneIDs[b];

				const Asura_Matrix_3x3& xOri = Asura_Hierarchy_System::s_axBoneOri[uBoneIndex];
				const Asura_Vector_3& xPos = Asura_Hierarchy_System::s_axBonePos[uBoneIndex];

				xMatrix.x.x = xOri.e11;
				xMatrix.x.y = 0;//xOri.e21;
				xMatrix.x.z = xOri.e31;
				xMatrix.y.x = xOri.e12;
				xMatrix.y.y = 0;//xOri.e22;
				xMatrix.y.z = xOri.e32;
				xMatrix.z.x = xOri.e13;
				xMatrix.z.y = 0;//xOri.e23;
				xMatrix.z.z = xOri.e33;

				xMatrix.w.x = xPos.x;
				xMatrix.w.y = Asura_Hierarchy_System::s_pxPos->y;//xPos.y;
				xMatrix.w.z = xPos.z;

				xMatrix.w.w = 1.0f;

				xMatrix.x.w = 0;
				xMatrix.y.w = 0;
				xMatrix.z.w = 0;

			/*	xMatrix.x.x = 1.0f;
				xMatrix.x.y = 0.0f;
				xMatrix.x.z = 0.0f;
				xMatrix.y.x = 0.0f;
				xMatrix.y.y = 1.0f;
				xMatrix.y.z = 0.0f;
				xMatrix.z.x = 0.0f;
				xMatrix.z.y = 0.0f;
				xMatrix.z.z = 1.0f;*/
				
				GU_BoneMatrix( b, &xMatrix );
			}

			DrawArray
			(
				SCEGU_PRIM_TRIANGLE_STRIP,
				uVertexFormat,
				xGroup.m_uNumberOfVertices,
				0,
				(void*)pxVertices
			);

		#if defined ASURA_RENDER_TTYPES
			Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2,1,
				ASURA_RENDER_TTYPE_SHA);
		#else
			Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2);
		#endif
		}
	}
	GUM_MatrixMode	(SCEGU_MATRIX_WORLD);
	GUM_PopMatrix		();
	GUM_SetCurMatrix(); 

	if(!Asura_Hierarchy_System::s_bBonesInWorldspace)
	{
		GUM_MatrixMode(SCEGU_MATRIX_VIEW);
		GUM_PopMatrix();
		GUM_SetCurMatrix(); 
	}
}

#else	// ASURA_HIERARCHY_QUAD

void Asura_PSP_CharacterRenderer::SetViewMatrix( const Asura_Matrix_3x3& rxOri, const Asura_Vector_3& rxPos )
{
	// RJT TODO: Could save a few cycles here
	s_xView.SetColumn0(Asura_AVector4( rxOri.e11,	-rxOri.e12, -rxOri.e13, 0.0f ) );
	s_xView.SetColumn1(Asura_AVector4( rxOri.e21,	-rxOri.e22, -rxOri.e23, 0.0f ) );
	s_xView.SetColumn2(Asura_AVector4( rxOri.e31,	-rxOri.e32, -rxOri.e33, 0.0f ) );
	s_xView.SetColumn3(Asura_AVector4( 0.0f,			0.0f,		0.0f,		1.0f ) );
	s_xView.SetColumn3(Asura_AVector4( -rxPos.x,		-rxPos.y,	-rxPos.z,	1.0f ) * s_xView );

	// RJT TODO: Find static form of this (and move to higher level?)
	Asura_AMatrix4x4 xIdentity; xIdentity.Identity();
	GU_SetMatrix( SCEGU_MATRIX_VIEW, reinterpret_cast< const ScePspFMatrix4 * >( &xIdentity ) );
}

void Asura_PSP_CharacterRenderer::Render( const Asura_Hierarchy_Skin* pxSkin, const Asura_Bounding_Box& xLightingBB, const float fAlpha, const int iTextureOverride )
{
	// Current character skin
	const Asura_PSP_CharacterRender_Handler&	xHandler	= GetCurrentHandler();
	const Asura_PSP_SmoothSkin*					pxPSPSkin	= xHandler.GetCharacter(pxSkin);
	if (!pxPSPSkin) return;

	// Vertex buffer 
	u_char* pxVertexBufferStart = pxPSPSkin->m_pxVertices;

	Asura_PSP_Graphics::SetUseColourDoubling(true);
	Asura_PSP_Graphics::SetupEntityLights( fAlpha, Asura_Hierarchy_System::s_bBonesInWorldspace );
	Asura_Render::CurrentStates.RequestZBufferEnabled(true);
	Asura_Render::CurrentStates.RequestCullMode(ASURA_CULLMODE_CW);

	if( Asura_Hierarchy_System::s_bBonesInWorldspace )
	{
		// RJT TODO: Find static form of this (and move to higher level?)
		Asura_AMatrix4x4 xIdentity; xIdentity.Identity();
		GU_SetMatrix( SCEGU_MATRIX_WORLD, reinterpret_cast< const ScePspFMatrix4 * >( &xIdentity ) );

		if( s_pxBeginRenderingCharacter )
		{
			// RJT TODO: Cleanup!
			s_pxBeginRenderingCharacter( 
				reinterpret_cast< Asura_PSP_Matrix4 * >( &s_xView ), 
				const_cast< Asura_PSP_Matrix4 * >( reinterpret_cast< const Asura_PSP_Matrix4 * >( &xIdentity ) )
			);
		}
	}
	else
	{
		// KOR - 3/3/2006 - entity renderers now premultiply view matrix
		// into world matrix, to get around PSP's 24-bit precision rubbishness.
		Asura_AMatrix4x4	xWorldView;
			
//		xWorldView	= Asura_Hierarchy_System::s_xBaseTransform * s_xView;
		s_xView.Apply(Asura_Hierarchy_System::s_xBaseTransform, xWorldView);
		GU_SetMatrix( SCEGU_MATRIX_WORLD, reinterpret_cast< const ScePspFMatrix4 * >( &xWorldView ) );

		if( s_pxBeginRenderingCharacter )
		{
			// RJT TODO: Cleanup!
			s_pxBeginRenderingCharacter( 
				reinterpret_cast< Asura_PSP_Matrix4 * >( &s_xView ),
				const_cast< Asura_PSP_Matrix4 * >( reinterpret_cast< const Asura_PSP_Matrix4 * >( &Asura_Hierarchy_System::s_xBaseTransform ) )
			);
		}
	}

	const u_int uVertexFormat = pxPSPSkin->m_bColoursReplaceNormals ?
		( SCEGU_WEIGHT_4 | SCEGU_WEIGHT_UBYTE | SCEGU_COLOR_PF8888 | SCEGU_TEXTURE_USHORT | SCEGU_VERTEX_SHORT ) :
		( SCEGU_WEIGHT_4 | SCEGU_WEIGHT_UBYTE | SCEGU_COLOR_NONE | SCEGU_TEXTURE_USHORT | SCEGU_NORMAL_SHORT | SCEGU_VERTEX_SHORT );

	u_int auCurrentBoneIndex[ 4 ] = { 0, 0, 0, 0 };
	s_iLastMaterial = ASURA_MATERIAL_FIRSTID - 1;
	for( u_int u = 0; u < pxPSPSkin->m_uNumberOfGroups; ++u )
	{
		const Asura_PSP_SmoothSkin_Group& xGroup = pxPSPSkin->m_pxGroups[ u ];

		s_iLastMaterial = s_pxSetRenderingStatesCharacter ?
			s_pxSetRenderingStatesCharacter(xGroup, fAlpha, iTextureOverride) :
			SetDefaultRenderingStates(xGroup, fAlpha, iTextureOverride);

		const u_char* pxVertices = pxVertexBufferStart + ( xGroup.m_uStartVertex * pxPSPSkin->m_xVertexSize );
		for( u_int b = 0; b < 4; ++b )
		{
			const u_int uBoneIndex = dbg_bNoAnims ? 0 : xGroup.m_aucBoneIDs[ b ];
			if( uBoneIndex && ( auCurrentBoneIndex[ b ] != uBoneIndex ) )
			{
				// The platform hierarchy now sets up all the values, including the scaling, for us.
				GU_BoneMatrix( b, reinterpret_cast< const ScePspFMatrix4 * >( &s_axBonePosAndOri[ uBoneIndex ] ) );
				auCurrentBoneIndex[b] = uBoneIndex;
			}
		}

		GU_DrawArray(
			SCEGU_PRIM_TRIANGLE_STRIP,
			uVertexFormat,
			xGroup.m_uNumberOfVertices,
			0,
			pxVertices
		);

#if defined ASURA_RENDER_TTYPES
		Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2,1,ASURA_RENDER_TTYPE_SKIN);
#else
		Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2);
#endif
	}

	// JG - 11/09/07: Set UV offsets to zero by default
	Asura_PSP_RenderStates::RequestTexOffset( 0.0f, 0.0f ); 

	if( s_pxEndRenderingCharacter )
	{
		s_pxEndRenderingCharacter();
	}

	if( dbg_bCharacterShadows )
	{
		Asura_Render::CurrentStates.RequestZBufferWriteEnabled(false);
		Asura_PSP_Graphics::SetMaterialType(ASURA_PSP_GRAPHICS_MATERIALTYPE_SHADOW);
		Asura_PSP_Graphics::SetNumberOfLights(0);
		Asura_PSP_RenderStates::RequestAmbient(0xff000000);
		Asura_PSP_TextureManagement::RequestTextureIndex( ASURA_TEXTURE_NONE );

		// Shadow
		for( u_int u = 0; u < pxPSPSkin->m_uNumberOfGroups; ++u )
		{
			Asura_PSP_SmoothSkin_Group& xGroup = pxPSPSkin->m_pxGroups[ u ];

			void* pxVertices = pxPSPSkin->m_pxVertices + ( xGroup.m_uStartVertex * pxPSPSkin->m_xVertexSize );
			for( u_int b = 0; b < 4; ++b )
			{
				u_int uBoneIndex = xGroup.m_aucBoneIDs[ b ];

				// RJT TODO: Improve
				Asura_AMatrix4x4	xTransform( s_axBonePosAndOri[ uBoneIndex ] );
				Asura_AVector4		xRow;
				Asura_AVector4		xColumn = Asura_Hierarchy_System::s_xBaseTransform.GetColumn3();
				xRow.SetElements(0.0f, 0.0f, 0.0f, xColumn.GetY());
				xTransform.SetRow1( xRow );

				GU_BoneMatrix( b, reinterpret_cast< const ScePspFMatrix4 * >( &xTransform ) );
			}

			GU_DrawArray(
				SCEGU_PRIM_TRIANGLE_STRIP,
				uVertexFormat,
				xGroup.m_uNumberOfVertices,
				0,
				pxVertices
			);

#if defined ASURA_RENDER_TTYPES
			Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2,1,ASURA_RENDER_TTYPE_SHA);
#else
			Asura_Render::AddToTrianglesRendered(xGroup.m_uNumberOfVertices-2);
#endif
		}
	}

	// RJT TODO: Get rid (requires complete removal of GUM)
	GUM_MatrixMode( SCEGU_MATRIX_WORLD );
	GUM_PushMatrix();
	GUM_PopMatrix();
	GUM_MatrixMode( SCEGU_MATRIX_VIEW );
	GUM_PushMatrix();
	GUM_PopMatrix();
}
#endif	// ASURA_HIERARCHY_QUAD

/// End of file. ///