//////////////////////////////////////////////////////////////////////////////
//
// Asura_PlatformHierarchy.cpp
//
// Copyright (c) 2006 Rebellion. All rights reserved.
//
//////////////////////////////////////////////////////////////////////////////

// I N C L U D E S ///////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header.
#include "Animation/Asura_PlatformHierarchy.h"

// Engine independent.
#include "Animation/Asura_Animation.h"
#include "Animation/Asura_Hierarchy.h"
#include "Camera/Asura_Camera.h"
#include "Core/Asura_Material_System.h"
#include "Rendering/Asura_Render.h"
#include "Core/Asura_MaterialResponse_System.h"
#include "Rendering/Asura_DeferredShading.h"

// Engine platform-specific.
#include "Rendering/Asura_PS3_CharacterRenderer.h"
#include "Rendering/Asura_PS3_ObjectHierarchyRenderer.h"
#include "Rendering/Asura_PS3_RenderingOptions.h"
#include "Rendering/Asura_PS3_RSXProfiling.h"

#include "Debugging/Asura_PS3_GPIO.h"
// C L A S S   I M P L E M E N T A T I O N S /////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Hierarchy_System::Platform_RenderSkin
// Author       : KJFL
// Date         :  -Jul-2005
//
// Description  : Handles the platform-specified rendering of a skinned-object.
//
// Parameters   : 
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////
void Asura_Hierarchy_System::Platform_RenderSkin( const Asura_Animation* pxAnim,
                                                  float fAlpha,
                                                  int iTexIDOverride,
                                                  u_int uCamVisFlags,
                                                  u_int uRenderFlags,
                                                  const Asura_Vector_3& xBSCentre,
                                                  float fBSRadius )
{
	if( !Asura_PS3_RenderingOptions::GetOption( Asura_PS3_RenderingOptions::OPTION_DRAW_SMOOTH_SKINS ) ) return;

	// Select LOD mesh based on distance to first bone, since object may be animating far away from its location

	const float fDistance = (GetWorldspaceBonePos(0) - *Asura_Camera::GetPosition()).Magnitude();
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance );

	if( s_bBonesInWorldspace )
	{
		// If the bones are in world space, have to switch them back to model space to avoid skinning inaccuracies
		for(int i = 0; i < s_pxSkin->NumberOfBones; ++i)
		{
			//			s_axBoneTransforms[i]	= s_xInverseBaseTransform * s_axBoneTransforms[i];
			s_xInverseBaseTransform.Apply(s_axBoneTransforms[i], s_axBoneTransforms[i]);
		}
		s_bBonesInWorldspace = false;
	}

	// Remove the bind-pose from the bone-data...
	for( int i = 0; i < s_pxSkin->NumberOfBones; ++i )
	{
		//		s_axBoneTransforms[i]	= s_axBoneTransforms[i] * s_pxSkin->m_pxBoneRestPoseInverseTransforms[i];
		s_axBoneTransforms[i].Apply(s_pxSkin->m_pxBoneRestPoseInverseTransforms[i], s_axBoneTransforms[i]);
	}

	// Time to render the character...
#ifdef ASURA_USE_GCM_REPLAY
	char szTemp[ 512 ];
	if( pxSkinLOD )
	{
		sprintf( szTemp, "Render Character: %s", pxSkinLOD->GetName() );
	}
	else
	{
		sprintf( szTemp, "Render Character" );
	}
	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__MISC, szTemp );
#endif

	if (s_pxSkin != pxAnim->GetSkin()) // check it's the skin we expect (if not project may be messing with things, so just draw it)
	{
		if( ShouldJustRenderForCurCam() )
		{
			Asura_PS3_CharacterRenderer::RenderCharacter( *pxAnim, *pxSkinLOD, s_xBaseTransform, s_xInverseBaseTransform, s_axBoneTransforms, 0 );
		}
		else
		{
			for( int u = 0; u < Asura_Camera::GetNumberOfCameras(); ++u )
			{
				if((uCamVisFlags & (1 << u)) != 0)
				{
					Asura_Camera::SetCameraIndex(u);
					Asura_Render::ApplyCameraSettings();

					Asura_PS3_CharacterRenderer::RenderCharacter( *pxAnim, *pxSkinLOD, s_xBaseTransform, s_xInverseBaseTransform, s_axBoneTransforms, 0 );
				}
			}
		}
	}
	else
	{
		for( int iSkinIndex = 0; iSkinIndex < ASURA_ANIMATION_SKIN_COUNT; ++iSkinIndex )
		{
			const Asura_Hierarchy_Skin *pxSkin = pxAnim->GetSkin(iSkinIndex);

			if (pxSkin)
			{
				const Asura_Hierarchy_Skin* const pxAnimSkinLOD = pxSkin->GetDetailSkinFromDistance( fDistance );

				// Time to render the character...
				if( ShouldJustRenderForCurCam() )
				{
					Asura_PS3_CharacterRenderer::RenderCharacter( *pxAnim, *pxAnimSkinLOD, s_xBaseTransform, s_xInverseBaseTransform, s_axBoneTransforms, iSkinIndex );
				}
				else
				{
					Asura_Assert(Asura_Camera::GetNumberOfCameras() <= 1,"SPU skinning does not support multiple cameras!");
					//BW afaik we don't need this functionality at the moment, if you see this assert let me know.
					//BW This will be fixed soon - I plan to make SPU skinning output in world space, not view space (it already does for shadows, so multiple shadows work)
				
					for( int u = 0; u < Asura_Camera::GetNumberOfCameras(); ++u )
					{
						if((uCamVisFlags & (1 << u)) != 0)
						{
							Asura_Camera::SetCameraIndex(u);
							Asura_Render::ApplyCameraSettings();

							Asura_PS3_CharacterRenderer::RenderCharacter( *pxAnim, *pxAnimSkinLOD, s_xBaseTransform, s_xInverseBaseTransform, s_axBoneTransforms, iSkinIndex );
						}
					}
				}
			}
		}
	}
#ifdef ASURA_USE_GCM_REPLAY
	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__MISC );
#endif
}

void Asura_Hierarchy_System::Platform_RenderHierarchy( const Asura_Animation* pxAnim,
                                                       u_int uCamVisFlags,
                                                       u_int uRenderFlags,
                                                       const Asura_Vector_3& xBSCentre,
                                                       float fBSRadius, 
													   const int iTexIDOverride /*=ASURA_TEXTURE_NONE*/ )
{
    if( !Asura_PS3_RenderingOptions::GetOption(Asura_PS3_RenderingOptions::OPTION_DRAW_HIERS) )
    {
        return;
    }

	Asura_Assert( pxAnim, "NULL parameter passed to Asura_Hierarchy_System::Platform_RenderHierarchy()." );

	// Select LOD mesh based on distance to first bone, since object may be animating far away from its location

	const float fDistance = (GetWorldspaceBonePos(0) - *Asura_Camera::GetPosition()).Magnitude();
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance );

#ifdef ASURA_USE_GCM_REPLAY
	char szTemp[ 512 ];
	if( pxSkinLOD )
	{
		sprintf( szTemp, "Render Hierarchy: %s", pxSkinLOD->GetName() );
	}
	else
	{
		sprintf( szTemp, "Render Hierarchy" );
	}
	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__MISC, szTemp );
#endif
	if( ShouldJustRenderForCurCam() )
	{
		Asura_PS3_ObjectHierarchyRenderer::RenderHierarchy( *pxAnim, *pxSkinLOD );
	}
	else
	{
		for( int u = 0; u < Asura_Camera::GetNumberOfCameras(); ++u )
		{
			if( !(uCamVisFlags & (1 << u)) ) continue;

			Asura_Camera::SetCameraIndex(u);
			Asura_Render::ApplyCameraSettings();

			Asura_PS3_ObjectHierarchyRenderer::RenderHierarchy( *pxAnim, *pxSkinLOD );
		}
	}
#ifdef ASURA_USE_GCM_REPLAY
	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__MISC );
#endif
}

void Asura_Hierarchy_System::Platform_RenderSkinShadow( const Asura_Animation& xAnim, const Asura_Shadow& xShadow, const u_int uCamVisFlags, const u_int uRenderFlags )
{
	if( !Asura_PS3_RenderingOptions::GetOption( Asura_PS3_RenderingOptions::OPTION_ENABLE_SMOOTH_SKIN_SHADOWS ) ) return;

	// Select LOD mesh based on distance to first bone, since object may be animating far away from its location

	const float fDistance = (GetWorldspaceBonePos(0) - *Asura_Camera::GetPosition()).Magnitude();
#ifdef ASURA_USE_LOWER_SKIN_LOD_FOR_SHADOWS
	// Drop LOD an extra level for shadow
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance )->GetRelativeDetailSkin(1);
#else
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance );
#endif

	if(xAnim.GetSkin() == s_pxSkin)
	{
		if( s_bBonesInWorldspace )
		{
			// If the bones are in world space, have to switch them back to model space to avoid skinning inaccuracies
			for(int i = 0; i < pxSkinLOD->NumberOfBones; ++i)
			{
				//			s_axBoneTransforms[i]	= s_xInverseBaseTransform * s_axBoneTransforms[i];
				s_xInverseBaseTransform.Apply(s_axBoneTransforms[i], s_axBoneTransforms[i]);
			}
			s_bBonesInWorldspace = false;

		}

		for( int i = 0; i < pxSkinLOD->NumberOfBones; ++i )
		{
			//		s_axBoneTransforms[i]	= s_axBoneTransforms[i] * pxSkinLOD->m_pxBoneRestPoseInverseTransforms[i];
			s_axBoneTransforms[i].Apply(pxSkinLOD->m_pxBoneRestPoseInverseTransforms[i], s_axBoneTransforms[i]);
		}
	}

#ifdef ASURA_USE_GCM_REPLAY
	char szTemp[ 512 ];
	if( pxSkinLOD )
	{
		sprintf( szTemp, "Render Character Shadow: %s", pxSkinLOD->GetName() );
	}
	else
	{
		sprintf( szTemp, "Render Character Shadow" );
	}
	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__MISC, szTemp );
#endif

	Asura_PS3_CharacterRenderer::RenderShadow(xAnim, *pxSkinLOD, xShadow, s_xBaseTransform, s_axBoneTransforms, s_uSkinIndex);

#ifdef ASURA_USE_GCM_REPLAY
	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__MISC );
#endif
}

void Asura_Hierarchy_System::Platform_RenderHierarchyShadow( const Asura_Animation& xAnim, const Asura_Shadow& xShadow, const u_int uCamVisFlags, const u_int uRenderFlags )
{
	if( !Asura_PS3_RenderingOptions::GetOption( Asura_PS3_RenderingOptions::OPTION_ENABLE_HIER_SHADOWS ) ) return;
	// Select LOD mesh based on distance to first bone, since object may be animating far away from its location

	const float fDistance = (GetWorldspaceBonePos(0) - *Asura_Camera::GetPosition()).Magnitude();
#ifdef ASURA_USE_LOWER_SKIN_LOD_FOR_SHADOWS
	// Drop LOD an extra level for shadow
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance )->GetRelativeDetailSkin(1);
#else
	const Asura_Hierarchy_Skin* const pxSkinLOD = s_pxSkin->GetDetailSkinFromDistance( fDistance );
#endif

#ifdef ASURA_USE_GCM_REPLAY
	char szTemp[ 512 ];
	if( pxSkinLOD )
	{
		sprintf( szTemp, "Render Hierarchy Shadow: %s", pxSkinLOD->GetName() );
	}
	else
	{
		sprintf( szTemp, "Render Hierarchy Shadow" );
	}
	ASURA_PS3_RSX_PROFILE__BEGIN( ASURA_PS3_RSX_PROFILE__MISC, szTemp );
#endif
	Asura_PS3_ObjectHierarchyRenderer::RenderShadow( xAnim, *pxSkinLOD, xShadow );
#ifdef ASURA_USE_GCM_REPLAY
	ASURA_PS3_RSX_PROFILE__END( ASURA_PS3_RSX_PROFILE__MISC );
#endif
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Hierarchy_Skin::Platform_GetTextureIndex()
//
// Returns the texture index associated with the hierarchy skin. Now that we
// allow multiple textures, this doesn't quite make sense, but we return the
// index of the first texture used
//
// Parameters:		None
// Return value:	Texture index
//
////////////////////////////////////////////////////////////////////////////////

int Asura_Hierarchy_Skin::Platform_GetTextureIndex( void ) const
{
	Asura_Hash_ID uMaterialHash	= ASURA_HASH_ID_UNSET;

	if( IsSkinnedModel() )
	{
		const Asura_PS3_CharacterRenderer_Skin* const pxSkin = Asura_PS3_CharacterRenderer::GetCharacter( m_uNameHashID );
		if((pxSkin != NULL) && (pxSkin->GetNumStrips() > 0))
		{
			uMaterialHash = pxSkin->GetStrip(0).m_auMaterialResponseHashIDs[0];
		}
	}
	else
	{
		const Asura_PS3_ObjectHierarchyRenderer::Hierarchy* const pxHierarchy = Asura_PS3_ObjectHierarchyRenderer::GetHierarchyTree().GetDataByHash( m_uNameHashID );
		if((pxHierarchy != NULL) && (pxHierarchy->GetNumberOfStrips() > 0))
		{
			uMaterialHash = pxHierarchy->GetStrip(0).m_auMaterialResponseHashIDs[0];
		}
	}

	const Asura_MaterialResponse* const pxMaterial = Asura_MaterialResponse_System::GetMaterialByHashID( uMaterialHash );
	return pxMaterial ? pxMaterial->GetTexture( Asura_MaterialResponse::ALBEDO_TEXTURE ) : ASURA_TEXTURE_NONE;
}

void Asura_Hierarchy_System::Platform_Initialise( void )
{
}

void Asura_Hierarchy_System::Platform_Reset( void )
{
}

void Asura_Hierarchy_System::Platform_Shutdown( void )
{
}

////////////////////////////////////////////////////////////////////////////////
//
// Asura_Hierarchy_Skin::Platform_IsOpaque()
//
// Queries whether a hierarchy skin is opaque or not. Used by the sorted render
// etc.
//
// Parameters:		None
// Return value:	true if the entire model is opaque
//
////////////////////////////////////////////////////////////////////////////////

bool Asura_Hierarchy_Skin::Platform_IsOpaque( void ) const
{
	if( IsSkinnedModel() )
	{
		const Asura_Hierarchy_Skin*	pxHierarchySkin	= this;

		while(pxHierarchySkin != NULL)
		{
			if(!Asura_PS3_CharacterRenderer::IsCharacterOpaque( m_uNameHashID ))
			{
				// This LOD is transparent - assume that the skin is too...
				return(false);
			}
			pxHierarchySkin	= pxHierarchySkin->m_pxNextLowerSkin;
		}

		// No transparent LODs means the skin is opaque...
		return(true);
	}
	else
	{
		const Asura_Hierarchy_Skin*	pxHierarchySkin	= this;

		while(pxHierarchySkin != NULL)
		{
			if(!Asura_PS3_ObjectHierarchyRenderer::IsHierarchyOpaque( m_uNameHashID ))
			{
				// This LOD is transparent - assume that the skin is too...
				return(false);
			}
			pxHierarchySkin	= pxHierarchySkin->m_pxNextLowerSkin;
		}

		// No transparent LODs means the skin is opaque...
		return(true);
	}
}
