//
// Asura_Direct3D_SmoothSkinRenderer.h
//
// Created by RJF - 04 August 2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//

#ifdef ASURA_USE_PRAGMA_ONCE
#pragma once
#endif

#ifndef __ASURA_DIRECT3D_SMOOTHSKINRENDERER_H__
#define __ASURA_DIRECT3D_SMOOTHSKINRENDERER_H__

// Asura Platform-independent
#include "Core/Asura_HalfFloat.h"
#include "Core/Asura_Hash.h"
#include "Core/Asura_HashTree.h"

// Asura PC
#include "Rendering/Asura_Direct3D.h"

#if !defined(ASURA_PC_USE_SHADER_MODEL_3_SMOOTH_SKIN_RENDERER) || !ASURA_PC_USE_SHADER_MODEL_3_SMOOTH_SKIN_RENDERER
#error The SM3 smooth skin renderer is not enabled in the project config; please ensure that its files are removed and not referenced.
#endif

// Forward declarations
class Asura_Animation;
class Asura_Hierarchy_Skin;
class Asura_TextureAnimationController;
class Asura_Vector_3;
class Asura_Matrix_3x3;
class Asura_Direct3D_Effect;
class Tools_SmoothSkin_Vertex;
class Asura_Shadow;

////////////////////////////////////////////////////////////////////////////////
// Smooth skins are skins which support multiple bones per vertex, blended together.
// They are most commonly used for characters.
////////////////////////////////////////////////////////////////////////////////
class Asura_Direct3D_SmoothSkin
{
public:
	Asura_Direct3D_SmoothSkin();
	~Asura_Direct3D_SmoothSkin();

	// One 'strip' is one triangle strip with a given material.
	// The indices used by a strip are laid out sequentially in the index buffer,
	// so we don't need to store the first index - we just start after the previous strip
	struct Strip
	{
		Asura_Hash_ID	m_auMaterialResponseHashID[2];
		u_int			uNumIndices;
		int				iElementID;
		u_int			uBoneMap;
	};

	// Structure of a smooth skin vertex - position, TBN, four bone indices/weights, and one UV channel
	struct Vertex
	{
		Asura_Vector_3	m_xPosition;
		Asura_HalfFloat	m_fUV[2];
		Asura_HalfFloat	m_fUV2[2];
		float			m_fAlpha;

		unsigned char	m_afIndex[4];
		Asura_HalfFloat	m_afWeight[4];
		Asura_Vector_3	m_xNormal;
		Asura_Vector_3  m_xTangent;
		Asura_Vector_3  m_xBinormal;

#ifdef ASURA_TOOLS
		// Normal-mapped character processor format
		// This is what we use for export - easy conversion of processor vertices to our vertex buffer format
		Vertex& operator= (const Tools_SmoothSkin_Vertex& xVertex);
#endif
	};
	struct BoneMap
	{
		u_int  m_uSize;
		u_int* m_puEntries;

		BoneMap() : m_uSize(0), m_puEntries(0) {}
		~BoneMap() {delete[] m_puEntries;}
	};

	// Sets the number of strips, trashing the existing strip data in the process
	void SetNumStrips(u_int uNumStrips);

	// Sets the number of strips, trashing the existing bone maps in the process
	void SetNumBoneMaps(u_int uNumBoneMaps);

	// Gets the number of strips in this smooth skin
	u_int GetNumStrips() const { return m_uNumStrips; }

	// Gets the number of strips in this smooth skin
	u_int GetNumBoneMaps() const { return m_uNumBoneMaps; }

	// Gets the strip at the given index
	Strip& GetStrip(u_int uStripIndex) { Asura_Assert(uStripIndex < m_uNumStrips, "Invalid strip index"); return m_pxStrips[uStripIndex];}
	const Strip& GetStrip(u_int uStripIndex) const { Asura_Assert(uStripIndex < m_uNumStrips, "Invalid strip index"); return m_pxStrips[uStripIndex];}

	// Gets the strip at the given index
	BoneMap& GetBoneMap(u_int uMapIndex) { Asura_Assert(uMapIndex < m_uNumBoneMaps, "Invalid Bonemap index"); return m_pxBoneMaps[uMapIndex];}
	const BoneMap& GetBoneMap(u_int uMapIndex) const { Asura_Assert(uMapIndex < m_uNumBoneMaps, "Invalid Bonemap index"); return m_pxBoneMaps[uMapIndex];}

	// Bind the vertex and index buffer to the device ready for rendering
	void BindBuffers();

	// Gets the number of vertices in the vertex buffer
	u_int GetNumVertices() const { return m_uNumVertices; }

	// Scans all strips to see if any of them are using a transparent material
	bool IsAnyMaterialTransparent() const;

	// For determining whether the skin contains strips or trilists
	D3DPRIMITIVETYPE GetPrimitiveType() const {return m_ePrimitiveType;}

	void SetGeometry(u_int uNumVertices, Direct3D_VertexBuffer* pxVertices, Direct3D_IndexBuffer* pxIndices, D3DPRIMITIVETYPE ePrimitiveType);

protected:
	Strip*								m_pxStrips;
	u_int								m_uNumStrips;
	u_int								m_uNumBoneMaps;

	Direct3D_VertexBuffer*				m_pxVertexBuffer;
	Direct3D_IndexBuffer*				m_pxIndexBuffer;
	u_int								m_uNumVertices;
	D3DPRIMITIVETYPE					m_ePrimitiveType;

	BoneMap *							m_pxBoneMaps;
};

////////////////////////////////////////////////////////////////////////////////
// The smooth skin renderer itself. Takes a given independent-level skin, and
// per-instance data in the form of an Asura_Animation plus a couple of other bits
////////////////////////////////////////////////////////////////////////////////
class Asura_Direct3D_SmoothSkinRenderer
{
public:
	// Isn't self-documenting code great?
	static void Initialise();
	static void Reset();
	static void Shutdown();

	// Create a new, empty smooth skin and register it
	static Asura_Direct3D_SmoothSkin*	CreateSmoothSkin( const Asura_Hash_ID uHash, 
														  const u_int uNumStrips );

	// Look up a smooth skin in the registry and return it if found, else NULL
	static Asura_Direct3D_SmoothSkin*	GetSmoothSkin( const Asura_Hash_ID uHash );

	static Direct3D_VertexDeclaration* GetVertexDeclaration()
	{
		return s_pxSmoothSkinVertexDeclarationObject;
	}

#if defined(ASURA_HIERARCHY_QUAD)
	static void Render( const Asura_Animation& xAnimation,
		const Asura_Hierarchy_Skin& xSkin,
		Asura_AMatrix4x4_InParam xTransform,
		Asura_AMatrix4x4_InParam xInverseTransform,
		const Asura_AMatrix4x4* const pxBoneTransforms );

#if defined ASURA_TOOLS

	static void RenderWireframe( const Asura_Animation& xAnimation,
		const Asura_Hierarchy_Skin& xSkin,
		Asura_AMatrix4x4_InParam xTransform,
		const Asura_AMatrix4x4* const pxBoneTransforms );
#endif // defined ASURA_TOOLS

	static void RenderShadow( const Asura_Animation& xAnimation,
		const Asura_Hierarchy_Skin& xSkin,
		const Asura_Shadow& xShadow,
		Asura_AMatrix4x4_InParam xTransform,
		const Asura_AMatrix4x4* const pxBoneTransforms );
#else
	static void Render( const Asura_Animation& xAnimation,
						const Asura_Hierarchy_Skin& xSkin,
						const Asura_Vector_3& xPos,
						const Asura_Matrix_3x3& xOri,
						const Asura_Vector_3* const pxBonePos,
						const Asura_Matrix_3x3* const pxBoneOri );

#if defined ASURA_TOOLS

	static void RenderWireframe( const Asura_Animation& xAnimation,
								 const Asura_Hierarchy_Skin& xSkin,
								 const Asura_Vector_3& xPos,
								 const Asura_Matrix_3x3& xOri,
								 const Asura_Vector_3* const pxBonePos,
								 const Asura_Matrix_3x3* const pxBoneOri );
#endif // defined ASURA_TOOLS

	static void RenderShadow( const Asura_Animation& xAnimation,
		const Asura_Hierarchy_Skin& xSkin,
		const Asura_Shadow& xShadow,
		const Asura_Vector_3& xPos,
		const Asura_Matrix_3x3& xOri,
		const Asura_Vector_3* const pxBonePos,
		const Asura_Matrix_3x3* const pxBoneOri );
#endif	// defined(ASURA_HIERARCHY_QUAD)

#ifdef ASURA_TOOLS
	static Asura_Direct3D_SmoothSkin* Tools_InitialiseSmoothSkinFromRawData(const Asura_Hierarchy_Skin* pxSkin);
#endif

	//Deletes the render data for all LODs of a model , forcing them to be recalculated the next time it is rendered
	static void ResetAllLODs(const Asura_Hierarchy_Skin* pxSkin);

private:
	// Storage for all registered skins
	static Asura_TemplateHashTree< Asura_Direct3D_SmoothSkin > s_xSkinTree;

	// Vertex decl for smooth skin vertices
	static const Direct3D_VertexElement s_axSmoothSkinVertexDeclaration[];
	static Direct3D_VertexDeclaration* s_pxSmoothSkinVertexDeclarationObject;

	// Pointer to the Shader Model 3 All-Singing All-Dancing Smooth Skin Rendering Ubershader (tm)
	static Asura_Direct3D_Effect* s_pxSmoothSkinUbershader;
	static Asura_Direct3D_Effect* s_pxDamageMappedSmoothSkinUbershader;

	// Pointer to the slightly more sedate and less showy Smooth Skin Shadowing Ubershader
	static Asura_Direct3D_Effect* s_pxSmoothSkinShadowShader;

	// Structure that holds all our effect parameter handles, for nice conceptual grouping purposes
	static struct ShaderParameterHandles
	{
		ShaderParameterHandles() { Release(); }
		void Get(Direct3D_Effect* pxSrcEffect); // Fills out all the handles from the given effect
		void Release(); // NULLs all the handles

		D3DXHANDLE m_xWVPMatrix;
		D3DXHANDLE m_xWorldMatrix;
		D3DXHANDLE m_xBoneMatrices;

		D3DXHANDLE m_xRedSH;
		D3DXHANDLE m_xGreenSH;
		D3DXHANDLE m_xBlueSH;

		D3DXHANDLE m_xTexCoordOffsetAndScale;
		D3DXHANDLE m_xSkinAlpha;
		D3DXHANDLE m_xObjectSpaceCameraPosition;

		D3DXHANDLE m_xNormalMapped;
		D3DXHANDLE m_xSelfIlluminating;
		D3DXHANDLE m_xSpecular;
		D3DXHANDLE m_xTransparent;

	} s_xShaderParameterHandles, s_xDamageMappedShaderParameterHandles;

	// Structure that holds the effect parameter handles for the shadow shader
	static struct ShadowParameterHandles
	{
		ShadowParameterHandles() { Release(); }
		void Get(Direct3D_Effect* pxSrcEffect); // Fills out all the handles from the given effect
		void Release(); // NULLs all the handles

		D3DXHANDLE m_xWVPMatrix;
		D3DXHANDLE m_xBoneMatrices;

	} s_xShadowParameterHandles;

	// Retrieve the effect from the effects system and set up all the parameter handles
	static void CaptureEffect();

	// In tools, alt-tabbing out and back again causes a reload of all shader objects, which invalidates our
	// parameter handles. These callbacks are called when the effect is unloaded/reloaded so that we can get
	// and release the handles appropriately.
	static void OnEffectLoaded(Asura_Direct3D_Effect* pxEffect);
	static void OnEffectUnloaded(Asura_Direct3D_Effect* pxEffect);

	// Sets the values of the parameters, grouped by data flavour
	static void ConfigureForLighting(Asura_Direct3D_Effect* pxEffect, ShaderParameterHandles* pxParams, const Asura_SphericalHarmonics_Order2_RGB& xSH);
	static void ConfigureForMaterial(Asura_Direct3D_Effect* pxEffect, ShaderParameterHandles* pxParams, const int iMaterialID, const Asura_TextureAnimationController& xTexAnimController, const int iTexIDOverride, const float fAlpha);
	static void ConfigureForObjectAndCamera(Asura_Direct3D_Effect* pxEffect,  ShaderParameterHandles *pxParams, const Asura_Vector_3& xPos, const Asura_Matrix_3x3& xOri);
#if defined(ASURA_HIERARCHY_QUAD)
	static void ConfigureForBones(Asura_Direct3D_Effect* pxEffect, ShaderParameterHandles* pxParams, const Asura_Direct3D_SmoothSkin* pxSkin,  const Asura_Animation *pxAnim,const Asura_AMatrix4x4* pxBoneTransforms, const bool bUseDamageMaps);	
	static void ConfigureForShadow(Asura_AMatrix4x4_InParam xTransform, const Asura_Shadow& xShadow);
#else
	static void ConfigureForBones(Asura_Direct3D_Effect* pxEffect, ShaderParameterHandles* pxParams, const Asura_Direct3D_SmoothSkin* pxSkin,  const Asura_Animation *pxAnim,const Asura_Vector_3* xBonePos, const Asura_Matrix_3x3* xBoneOri, const bool bUseDamageMaps);	
	static void ConfigureForShadow(const Asura_Vector_3& xPos, const Asura_Matrix_3x3& xOri, const Asura_Shadow& xShadow);
#endif	// defined(ASURA_HIERARCHY_QUAD)
};

#endif//__ASURA_DIRECT3D_SMOOTHSKINRENDERER_H__