//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
//===========================================================================//

#ifndef CSTUDIORENDER_H
#define CSTUDIORENDER_H
#ifdef _WIN32
#pragma once
#endif

#include "istudiorender.h"
#include "studio.h"
#include "materialsystem/imaterialsystem.h" // for LightDesc_t
// wouldn't have to include these if it weren't for inlines.
#include "materialsystem/imaterial.h"
#include "mathlib/mathlib.h"
#include "utllinkedlist.h"
#include "utlvector.h"
#include "tier1/utllinkedlist.h"
#include "flexrenderdata.h"
#include "mathlib/compressed_vector.h"
#include "r_studiolight.h"
#if defined( _WIN32 ) && !defined( _X360 )
#include <xmmintrin.h>
#endif
#include "tier0/dbg.h"


//-----------------------------------------------------------------------------
// Forward declarations
//-----------------------------------------------------------------------------
class ITexture;
class CPixelWriter;
class CMeshBuilder;
class IMaterialVar;
struct mstudioeyeball_t;
struct eyeballstate_t;
struct lightpos_t;
struct dworldlight_t;
struct DecalClipState_t;
class CStudioRender;
struct StudioRenderContext_t;
struct FlexWeights_t;

namespace OptimizedModel
{
	struct FileHeader_t;
	struct MeshHeader_t;
	struct StripGroupHeader_t;
	struct Vertex_t;
	struct ModelLODHeader_t;
}


//-----------------------------------------------------------------------------
// FIXME: Remove
//-----------------------------------------------------------------------------
class IStudioDataCache;
extern IStudioDataCache *g_pStudioDataCache;


//-----------------------------------------------------------------------------
// Singleton
//-----------------------------------------------------------------------------
extern CStudioRender g_StudioRender;


//-----------------------------------------------------------------------------
// Defines + structs
//-----------------------------------------------------------------------------
#define MAXLOCALLIGHTS 4
#define MAXLIGHTCOMPUTE 16

enum StudioModelLighting_t
{
	LIGHTING_HARDWARE = 0,
	LIGHTING_SOFTWARE,
	LIGHTING_MOUTH
};

struct lightpos_t
{
	Vector	delta;		// unit vector from vertex to light
	float	falloff;	// light distance falloff
	float	dot;		// light direction * delta;

	lightpos_t() = default;

private:
	// Copy constructors are not allowed
	lightpos_t( const lightpos_t& src );
};

struct eyeballstate_t
{
	const mstudioeyeball_t *peyeball;

	matrix3x4_t	mat;

	Vector	org;		// world center of eyeball
	Vector	forward;	
	Vector	right;
	Vector	up;
	
	Vector	cornea;		// world center of cornea

	eyeballstate_t() = default;

private:
	// Copy constructors are not allowed
	eyeballstate_t( const eyeballstate_t& src );
};


//-----------------------------------------------------------------------------
// Store decal vertex data here 
//-----------------------------------------------------------------------------
#pragma pack(1)
struct DecalVertex_t
{
	mstudiomesh_t *GetMesh( studiohdr_t *pHdr )
	{
		if ((m_Body == 0xFFFF) || (m_Model == 0xFFFF) || (m_Mesh == 0xFFFF))
			return NULL;

		mstudiobodyparts_t *pBody = pHdr->pBodypart( m_Body );
		mstudiomodel_t *pModel = pBody->pModel( m_Model );
		return pModel->pMesh( m_Mesh );
	}

	IMorph *GetMorph( studiohdr_t *pHdr, studiomeshdata_t *pStudioMeshes )
	{
		if ( (m_Body == 0xFFFF) || (m_Model == 0xFFFF) || (m_Mesh == 0xFFFF) || (m_Group == 0xFFFF) )
			return NULL;

		mstudiobodyparts_t *pBody = pHdr->pBodypart( m_Body );
		mstudiomodel_t *pModel = pBody->pModel( m_Model );
		mstudiomesh_t *pMesh = pModel->pMesh( m_Mesh );
		studiomeshdata_t* pMeshData = &pStudioMeshes[pMesh->meshid];
		studiomeshgroup_t* pGroup = &pMeshData->m_pMeshGroup[m_Group];
		return pGroup->m_pMorph;
	}

	// NOTE: m_Group + m_GroupIndex is necessary only for decals on
	// hardware morphs. If COMPACT_DECAL_VERT is for console, we
	// could remove group index + group
#ifdef COMPACT_DECAL_VERT
	Vector			m_Position;			// 12
	Vector2d32		m_TexCoord;			// 16
	Vector48		m_Normal;			// 22 (packed to m_Body)

	byte			m_Body;				// 24
	byte			m_Model;
	unsigned short	m_MeshVertexIndex;	// index into the mesh's vertex list
	unsigned short	m_Mesh;
	unsigned short	m_GroupIndex;		// index into the mesh's vertex list
	unsigned short	m_Group;
#else
	Vector m_Position;
	Vector m_Normal;
	Vector2D m_TexCoord;

	unsigned short	m_MeshVertexIndex;	// index into the mesh's vertex list
	unsigned short	m_Body;
	unsigned short	m_Model;
	unsigned short	m_Mesh;
	unsigned short	m_GroupIndex;		// index into the group's index list
	unsigned short	m_Group;
#endif

	DecalVertex_t() = default;
	DecalVertex_t( const DecalVertex_t& src )
	{
		m_Position = src.m_Position;
		m_Normal = src.m_Normal;
		m_TexCoord = src.m_TexCoord;
		m_MeshVertexIndex = src.m_MeshVertexIndex;
		m_Body = src.m_Body;
		m_Model = src.m_Model;
		m_Mesh = src.m_Mesh;
		m_GroupIndex = src.m_GroupIndex;
		m_Group = src.m_Group;
	}
};
#pragma pack()


//-----------------------------------------------------------------------------
// Temporary meshes
//-----------------------------------------------------------------------------
struct MeshVertexInfo_t
{
	mstudiomesh_t *m_pMesh;
	int m_nIndex;
};


//-----------------------------------------------------------------------------
// Vertex prefetch count for software skinning
//-----------------------------------------------------------------------------
enum
{
	PREFETCH_VERT_COUNT = 4
};


//-----------------------------------------------------------------------------
// Class that actually renders stuff
//-----------------------------------------------------------------------------
class CStudioRender
{
public:
	CStudioRender();
	~CStudioRender();

	// Init, shutdown
	InitReturnVal_t Init();
	void Shutdown( void );

	void EnableScissor( FlashlightState_t *state );
	void DisableScissor();

	void DrawModel( const DrawModelInfo_t& info, const StudioRenderContext_t& rc, matrix3x4_t *pBoneToWorld, const FlexWeights_t& flex, int flags = STUDIORENDER_DRAW_ENTIRE_MODEL );
	void DrawModelArray( const DrawModelInfo_t &drawInfo, const StudioRenderContext_t &rc, int arrayCount, model_array_instance_t *pInstanceData, int instanceStride, int flags = STUDIORENDER_DRAW_ENTIRE_MODEL );

	// Static-prop related draw methods
	void DrawModelStaticProp( const DrawModelInfo_t& info, const StudioRenderContext_t &rc, const matrix3x4_t &modelToWorld, int flags = STUDIORENDER_DRAW_ENTIRE_MODEL );
	void DrawStaticPropShadows( const DrawModelInfo_t &drawInfo, const StudioRenderContext_t &rc, const matrix3x4_t &modelToWorld, int flags );
	void DrawStaticPropDecals( const DrawModelInfo_t &drawInfo, const StudioRenderContext_t &rc, const matrix3x4_t &modelToWorld );

	void ModelStats( const DrawModelInfo_t& info, const StudioRenderContext_t &rc, matrix3x4_t *pBoneToWorld, const FlexWeights_t &flex, int flags );

	// Create, destroy list of decals for a particular model
	StudioDecalHandle_t CreateDecalList( studiohwdata_t *pHardwareData );
	void DestroyDecalList( StudioDecalHandle_t handle );

	// Add decals to a decal list by doing a planar projection along the ray
	void AddDecal( StudioDecalHandle_t handle, const StudioRenderContext_t& rc, matrix3x4_t *pBoneToWorld, studiohdr_t *pStudioHdr, 
			const Ray_t & ray, const Vector& decalUp, IMaterial* pDecalMaterial, 
			float radius, int body, bool noPokethru, int maxLODToDecal = ADDDECAL_TO_ALL_LODS );

	// Shadow state (affects the models as they are rendered)
	void AddShadow( IMaterial* pMaterial, void* pProxyData, FlashlightState_t *pFlashlightState, VMatrix *pWorldToTexture, ITexture *pFlashlightDepthTexture );
	void ClearAllShadows();

	// Release/restore material system objects
	void PrecacheGlint();
	void UncacheGlint();

	// Get the config
	void R_MouthComputeLightingValues( float& fIllum, Vector& forward );
	void R_MouthLighting( float fIllum, const Vector& normal, const Vector& forward, Vector& light );

	// Performs the lighting computation
	inline void R_ComputeLightAtPoint3( const Vector &pos, const Vector &norm, Vector &color );

#if defined( _WIN32 ) && !defined( _X360 )
	// sse-ized lighting pipeline. lights 4 vertices at once
	inline void R_ComputeLightAtPoints3( const FourVectors &pos, const FourVectors &norm, FourVectors &color );
	void R_MouthLighting( __m128 fIllum, const FourVectors& normal, const FourVectors& forward, FourVectors& light );
#endif

private:
	enum
	{
		DECAL_DYNAMIC = 0x1,
		DECAL_SECONDPASS = 0x2,
	};

	typedef unsigned short DecalId_t;

	struct Decal_t
	{
		int m_IndexCount;
		int m_VertexCount;
		float m_FadeStartTime;
		float m_FadeDuration;
		int	m_Flags;
	};

	struct DecalHistory_t
	{
		unsigned short m_Material;
		unsigned short m_Decal;
		DecalId_t m_nId;
		unsigned short m_nPad;
	};

	typedef CUtlLinkedList<DecalVertex_t, unsigned short> DecalVertexList_t;

	typedef CUtlVector<unsigned short> DecalIndexList_t;
	typedef CUtlLinkedList<Decal_t, unsigned short> DecalList_t;
	typedef CUtlLinkedList<DecalHistory_t, unsigned short> DecalHistoryList_t;

	struct DecalMaterial_t
	{
		IMaterial*			m_pMaterial;
		DecalIndexList_t	m_Indices;
		DecalVertexList_t	m_Vertices;
		DecalList_t			m_Decals;
	};

	struct DecalLod_t
	{
		unsigned short m_FirstMaterial;
		DecalHistoryList_t	m_DecalHistory;
	};

	struct DecalModelList_t
	{
		studiohwdata_t* m_pHardwareData;
		DecalLod_t* m_pLod;
		int m_nLods; // need to retain because hardware data could be flushed
	};

	// A temporary structure used to figure out new decal verts
	struct DecalBuildVertexInfo_t
	{
		enum
		{
			FRONT_FACING = 0x1,
			VALID_AREA = 0x2,		// If you change this, change ProjectDecalOntoMesh
		};

		Vector2D		m_UV;
		unsigned short	m_VertexIndex;	// index into the DecalVertex_t list
		unsigned char	m_UniqueID;
		unsigned char	m_Flags;

	private:
		// No copy constructors
		DecalBuildVertexInfo_t( const DecalBuildVertexInfo_t &src );
	};

	struct DecalBuildInfo_t
	{
		IMaterial						**m_ppMaterials;
		studiohdr_t						*m_pStudioHdr;
		mstudiomesh_t					*m_pMesh;
		studiomeshdata_t				*m_pMeshData;
		DecalMaterial_t					*m_pDecalMaterial;
		MeshVertexInfo_t				*m_pMeshVertices;
		const mstudio_meshvertexdata_t	*m_pMeshVertexData;
		const thinModelVertices_t		*m_pMeshThinVertexData;
		int								m_nGlobalMeshIndex;
		DecalBuildVertexInfo_t			*m_pVertexBuffer;
		float							m_Radius;
		DecalBuildVertexInfo_t			*m_pVertexInfo;
		int								m_Body;
		int								m_Model;
		int								m_Mesh;
		int								m_Group;
		DecalVertexList_t::IndexType_t	m_FirstVertex;
		unsigned short					m_VertexCount;
		bool							m_UseClipVert;
		bool							m_NoPokeThru;
	};

	struct ShadowState_t
	{
		IMaterial*			m_pMaterial;
		void*				m_pProxyData;
		FlashlightState_t * m_pFlashlightState;
		VMatrix *			m_pWorldToTexture;
		ITexture *			m_pFlashlightDepthTexture;
	};

	struct BodyPartInfo_t
	{
		int m_nSubModelIndex;
		mstudiomodel_t *m_pSubModel;
	};

	struct GlintRenderData_t
	{
		Vector2D m_vecPosition;
		Vector m_vecIntensity;
	};

	// Global LRU for model decals
	struct DecalLRU_t
	{
		StudioDecalHandle_t m_hDecalHandle;
		DecalId_t m_nDecalId;
	};

	typedef CUtlFixedLinkedList< DecalLRU_t >::IndexType_t DecalLRUListIndex_t;

private:
	void SetLightingRenderState();

	int R_StudioRenderModel( IMatRenderContext *pRenderContext, int skin, int body, int hitboxset, void /*IClientEntity*/ *pEntity, 
		IMaterial **ppMaterials, int *pMaterialFlags, int flags, int boneMask, int lod, ColorMeshInfo_t *pColorMeshes = NULL );
	IMaterial* R_StudioSetupSkinAndLighting( IMatRenderContext *pRenderContext, int index, IMaterial **ppMaterials, int materialFlags,
		void /*IClientEntity*/ *pClientEntity, ColorMeshInfo_t *pColorMeshes, StudioModelLighting_t &lighting );
	int R_StudioDrawEyeball( IMatRenderContext *pRenderContext, mstudiomesh_t* pmesh,  studiomeshdata_t* pMeshData,
		StudioModelLighting_t lighting, IMaterial *pMaterial, int lod );
	int R_StudioDrawPoints( IMatRenderContext *pRenderContext, int skin, void /*IClientEntity*/ *pClientEntity, 
		IMaterial **ppMaterials, int *pMaterialFlags, int boneMask, int lod, ColorMeshInfo_t *pColorMeshes );
	int R_StudioDrawMesh( IMatRenderContext *pRenderContext, mstudiomesh_t* pmesh, studiomeshdata_t* pMeshData,
		  				   StudioModelLighting_t lighting, IMaterial *pMaterial, ColorMeshInfo_t *pColorMeshes, int lod );
	int R_StudioRenderFinal( IMatRenderContext *pRenderContext, 
		int skin, int nBodyPartCount, BodyPartInfo_t *pBodyPartInfo, void /*IClientEntity*/ *pClientEntity,
		IMaterial **ppMaterials, int *pMaterialFlags, int boneMask, int lod, ColorMeshInfo_t *pColorMeshes = NULL );
	int R_StudioDrawStaticMesh( IMatRenderContext *pRenderContext, mstudiomesh_t* pmesh, 
		studiomeshgroup_t* pGroup, StudioModelLighting_t lighting, float r_blend, IMaterial* pMaterial,
		int lod, ColorMeshInfo_t *pColorMeshes );
	int R_StudioDrawDynamicMesh( IMatRenderContext *pRenderContext, mstudiomesh_t* pmesh, 
				studiomeshgroup_t* pGroup, StudioModelLighting_t lighting, 
				float r_blend, IMaterial* pMaterial, int lod );
	int R_StudioDrawGroupHWSkin( IMatRenderContext *pRenderContext, studiomeshgroup_t* pGroup, IMesh* pMesh, ColorMeshInfo_t *pColorMeshInfo = NULL );
	int R_StudioDrawGroupSWSkin( studiomeshgroup_t* pGroup, IMesh* pMesh );
	void R_StudioDrawHulls( int hitboxset, bool translucent );
	void R_StudioDrawBones (void);
	void R_StudioVertBuffer( void );
	void DrawNormal( const Vector& pos, float scale, const Vector& normal, const Vector& color );
	void BoneMatToMaterialMat( matrix3x4_t& boneMat, float materialMat[4][4] );

	// Various inner-loop methods
	void R_StudioSoftwareProcessMesh( mstudiomesh_t* pmesh, CMeshBuilder& meshBuilder, 
			int numVertices, unsigned short* pGroupToMesh, StudioModelLighting_t lighting, bool doFlex, float r_blend,
			bool bNeedsTangentSpace, bool bDX8Vertex, IMaterial *pMaterial );

	void R_StudioSoftwareProcessMesh_Normals( mstudiomesh_t* pmesh, CMeshBuilder& meshBuilder, 
			int numVertices, unsigned short* pGroupToMesh, StudioModelLighting_t lighting, bool doFlex, float r_blend,
			bool bShowNormals, bool bShowTangentFrame );

	template< class T >
	void ComputeFlexedVertex_StreamOffset( mstudioflex_t *pflex, T *pvanim, int vertCount, float w1, float w2, float w3, float w4 );

	void R_StudioProcessFlexedMesh_StreamOffset( mstudiomesh_t* pmesh, int lod );

	template <VertexCompressionType_t T> void FillFlexMeshGroupVB( CMeshBuilder & meshBuilder, studiomeshgroup_t *pGroup );
	void R_StudioFlexMeshGroup( studiomeshgroup_t *pGroup );

	template<VertexCompressionType_t T> void R_StudioRestoreMesh( mstudiomesh_t* pmesh, studiomeshgroup_t* pMeshData );
	void R_StudioProcessFlexedMesh( mstudiomesh_t* pmesh, CMeshBuilder& meshBuilder, 
		int numVertices, unsigned short* pGroupToMesh );

	// Eye rendering using vertex shaders
	void SetEyeMaterialVars( IMaterial* pMaterial, mstudioeyeball_t* peyeball, 
		const Vector& eyeOrigin, const matrix3x4_t& irisTransform, const matrix3x4_t& glintTransform );

	void ComputeEyelidStateFACS( mstudiomodel_t *pSubModel );

	void R_StudioEyelidFACS( const mstudioeyeball_t *peyeball, const eyeballstate_t *pstate );

	void R_StudioEyeballPosition( const mstudioeyeball_t *peyeball, eyeballstate_t *pstate );

	// Computes the texture projection matrix for the glint texture
	void ComputeGlintTextureProjection( eyeballstate_t const* pState, 
		const Vector& vright, const Vector& vup, matrix3x4_t& mat );

	void R_StudioEyeballGlint( const eyeballstate_t *pstate, IMaterialVar *pGlintTextureVar, 
								const Vector& vright, const Vector& vup, const Vector& r_origin );
	ITexture* RenderGlintTexture( const eyeballstate_t *pstate,
		const Vector& vright, const Vector& vup, const Vector& r_origin );

	int BuildGlintRenderData( GlintRenderData_t *pData, int nMaxGlints,
		const eyeballstate_t *pstate, const Vector& vright, const Vector& vup, const Vector& r_origin );
	void R_MouthSetupVertexShader( IMaterial* pMaterial );

	// Computes a vertex format to use
	VertexFormat_t ComputeSWSkinVertexFormat( IMaterial *pMaterial ) const;

	inline bool R_TeethAreVisible( void )
	{
		return true;
		/*
		// FIXME: commented out until Gary can change them to just draw black
		mstudiomouth_t *pMouth = m_pStudioHdr->pMouth( 0 ); 
		float fIllum = m_FlexWeights[pMouth->flexdesc];
		return fIllum > 0.0f;
		*/
	}

	inline StudioModelLighting_t R_StudioComputeLighting( IMaterial *pMaterial, int materialFlags, ColorMeshInfo_t *pColorMeshes );
	inline void R_StudioTransform( Vector& in1, mstudioboneweight_t *pboneweight, Vector& out1 );
	inline void R_StudioRotate( Vector& in1, mstudioboneweight_t *pboneweight, Vector& out1 );
	inline void R_StudioRotate( Vector4D& in1, mstudioboneweight_t *pboneweight, Vector4D& out1 );
	inline void R_StudioEyeballNormal( mstudioeyeball_t const* peyeball, Vector& org, 
									Vector& pos, Vector& normal );
	void MaterialPlanerProjection( const matrix3x4_t& mat, int count, const Vector *psrcverts, Vector2D *pdesttexcoords );
	void AddGlint( CPixelWriter &pixelWriter, float x, float y, const Vector& color );

	// Methods associated with lighting
	int R_LightGlintPosition( int index, const Vector& org, Vector& delta, Vector& intensity );
	void R_LightEffectsWorld( const lightpos_t *light, const Vector& normal, const Vector &src, Vector &dest );

	void R_GatherStats( studiomeshgroup_t *pGroup, CMeshBuilder &MeshBuilder, IMesh *pMesh, IMaterial *pMaterial );

public:
	// NJS: Messy, but needed for an externally optimized routine to set up the lighting.
	void R_InitLightEffectsWorld3();
	void (FASTCALL *R_LightEffectsWorld3)( const LightDesc_t *pLightDesc, const lightpos_t *light, const Vector& normal, Vector &dest );

private:
	inline float R_WorldLightAngle( const LightDesc_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta );

	void InitDebugMaterials( void );
	void ShutdownDebugMaterials( void );
	int SortMeshes( int* pIndices, IMaterial **ppMaterials, short* pskinref, const Vector& vforward, const Vector& r_origin );

	// Computes pose to decal space transforms for decal creation 
	// returns false if it can't for some reason.
	bool ComputePoseToDecal( Ray_t const& ray, const Vector& up );

	bool AddDecalToModel( DecalBuildInfo_t& buildInfo );

	// Helper methods for decal projection, projects pose space vertex data
	bool			TransformToDecalSpace( DecalBuildInfo_t& build, const Vector& pos, mstudioboneweight_t *pboneweight, Vector2D& uv );
	bool			ProjectDecalOntoMesh( DecalBuildInfo_t& build, DecalBuildVertexInfo_t* pVertexInfo, mstudiomesh_t *pMesh );
	bool			IsFrontFacing( const Vector * norm, const mstudioboneweight_t *pboneweight );
	int				ComputeClipFlags( DecalBuildVertexInfo_t* pVertexInfo, int i );
	void			ConvertMeshVertexToDecalVertex( DecalBuildInfo_t& build, int meshIndex, DecalVertex_t& decalVertex, int nGroupIndex = 0xFFFF );
	unsigned short	AddVertexToDecal( DecalBuildInfo_t& build, int meshIndex, int nGroupIndex = 0xFFFF );
	unsigned short	AddVertexToDecal( DecalBuildInfo_t& build, DecalVertex_t& vert );
	void			AddClippedDecalToTriangle( DecalBuildInfo_t& build, DecalClipState_t& clipState );
	bool			ClipDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int *pClipFlags );
	void			AddTriangleToDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int gi1, int gi2, int gi3 );
	void			AddDecalToMesh( DecalBuildInfo_t& build );
	int				GetDecalMaterial( DecalLod_t& decalLod, IMaterial* pDecalMaterial );
	int				AddDecalToMaterialList( DecalMaterial_t* pMaterial );

	// Total number of meshes we have to deal with
	int ComputeTotalMeshCount( int iRootLOD, int iMaxLOD, int body ) const;

	// Project decals onto all meshes
	void ProjectDecalsOntoMeshes( DecalBuildInfo_t& build, int nMeshCount );

	// Set up the locations for vertices to use
	int ComputeVertexAllocation( int iMaxLOD, int body, studiohwdata_t *pHardwareData, MeshVertexInfo_t *pVertexInfo );

	// Removes a decal and associated vertices + indices from the history list
	void RetireDecal( DecalModelList_t &list, DecalId_t nDecalID, int iLOD, int iMaxLOD );

	// Helper methods related to drawing decals
	void DrawSingleBoneDecals( CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial );
	bool DrawMultiBoneDecals( CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr );
	void DrawSingleBoneFlexedDecals( IMatRenderContext *pRenderContext, CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial );
	bool DrawMultiBoneFlexedDecals( IMatRenderContext *pRenderContext, CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr, studioloddata_t *pStudioLOD );
	void DrawDecalMaterial( IMatRenderContext *pRenderContext, DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr, studioloddata_t *pStudioLOD );
	void DrawDecal( const DrawModelInfo_t &drawInfo, int lod, int body );
	bool PreDrawDecal( IMatRenderContext *pRenderContext, const DrawModelInfo_t &drawInfo );
	
	// Draw shadows
	void DrawShadows( const DrawModelInfo_t& info, int flags, int boneMask );

	// Draw flashlight lighting on decals.
	void DrawFlashlightDecals( const DrawModelInfo_t& info, int lod );
	
	// Helper methods related to extracting and balancing
	float RampFlexWeight( mstudioflex_t &flex, float w );

	// Remove decal from LRU
	void RemoveDecalListFromLRU( StudioDecalHandle_t h );

	// Helper methods related to flexing vertices
	void R_StudioFlexVerts( mstudiomesh_t *pmesh, int lod );

	// Flex stats
	void GetFlexStats( );

	// Sets up the hw flex mesh
	void ComputeFlexWeights( int nFlexCount, mstudioflex_t *pFlex, MorphWeight_t *pWeights );

	// Generate morph accumulator
	void GenerateMorphAccumulator( mstudiomodel_t *pSubModel );

	// Computes eyeball state
	void ComputeEyeballState( mstudiomodel_t *pSubModel );

	// Avoid some warnings...
	CStudioRender( CStudioRender const& );

public:
	// Render context (comes from queue)
	StudioRenderContext_t *m_pRC;

private:
	// Stores all decals for a particular material and lod
	CUtlLinkedList< DecalMaterial_t, unsigned short, true >	m_DecalMaterial;

	// Stores all decal lists that have been made
	CUtlFixedLinkedList< DecalModelList_t >	m_DecalList;
	CThreadFastMutex m_DecalMutex;

	// Stores all shadows to be cast on the current object
	CUtlVector<ShadowState_t>	m_ShadowState;

	matrix3x4_t m_StaticPropRootToWorld;
	matrix3x4_t	*m_pBoneToWorld;	// bone transformation matrix( comes from queue )

	matrix3x4_t *m_PoseToWorld;	// bone transformation matrix
	matrix3x4_t *m_PoseToDecal;	// bone transformation matrix

	// Flex state, comes from queue
	float *m_pFlexWeights;
	float *m_pFlexDelayedWeights;

	studiohdr_t *m_pStudioHdr;
	mstudiomodel_t *m_pSubModel;
	studiomeshdata_t *m_pStudioMeshes;

	eyeballstate_t m_pEyeballState[16]; // MAXSTUDIOEYEBALLS

	// debug materials
	IMaterial		*m_pMaterialMRMWireframe;
	IMaterial		*m_pMaterialMRMWireframeZBuffer;
	IMaterial		*m_pMaterialMRMNormals;
	IMaterial		*m_pMaterialTangentFrame;
	IMaterial		*m_pMaterialTranslucentModelHulls;
	IMaterial		*m_pMaterialSolidModelHulls;
	IMaterial		*m_pMaterialAdditiveVertexColorVertexAlpha;
	IMaterial		*m_pMaterialModelBones;
	IMaterial		*m_pMaterialWorldWireframe;
	IMaterial		*m_pMaterialModelEnvCubemap;

	// Depth override material
	IMaterial		*m_pDepthWrite[2][2];
	IMaterial		*m_pSSAODepthWrite[2][2];

	// GLINT data
	ITexture* m_pGlintTexture;
	ITexture* m_pGlintLODTexture;
	IMaterial *m_pGlintBuildMaterial;
	short	m_GlintWidth;
	short	m_GlintHeight;

	// Flex data
	CCachedRenderData	m_VertexCache;

	// Cached variables:
	bool m_bSkippedMeshes : 1;
	bool m_bDrawTranslucentSubModels : 1;

	DecalId_t	m_nDecalId;
	CUtlFixedLinkedList< DecalLRU_t > m_DecalLRU;

	friend class CGlintTextureRegenerator;
	friend struct mstudiomodel_t;
	friend class CStudioRenderContext;
};


//-----------------------------------------------------------------------------
// Converts matrices to a format material system wants
//-----------------------------------------------------------------------------

/*
================
R_StudioTransform
================
*/
inline void CStudioRender::R_StudioTransform( Vector& in1, mstudioboneweight_t *pboneweight, Vector& out1 )
{
//	MEASURECODE( "R_StudioTransform" );

	Vector out2;
	switch( pboneweight->numbones )
	{
	case 1:
		VectorTransform( in1, m_PoseToWorld[(unsigned)pboneweight->bone[0]], out1 );
		break;
/*
	case 2:
		VectorTransform( in1, m_PoseToWorld[pboneweight->bone[0]], out1 );
		out1 *= pboneweight->weight[0];
		VectorTransform( in1, m_PoseToWorld[pboneweight->bone[1]], out2 );
		VectorMA( out1, pboneweight->weight[1], out2, out1 );
		break;

	case 3:
		VectorTransform( in1, m_PoseToWorld[pboneweight->bone[0]], out1 );
		out1 *= pboneweight->weight[0];
		VectorTransform( in1, m_PoseToWorld[pboneweight->bone[1]], out2 );
		VectorMA( out1, pboneweight->weight[1], out2, out1 );
		VectorTransform( in1, m_PoseToWorld[pboneweight->bone[2]], out2 );
		VectorMA( out1, pboneweight->weight[2], out2, out1 );
		break;
*/
	default:
		VectorFill( out1, 0 );
		for (int i = 0; i < pboneweight->numbones; i++)
		{
			VectorTransform( in1, m_PoseToWorld[(unsigned)pboneweight->bone[i]], out2 );
			VectorMA( out1, pboneweight->weight[i], out2, out1 );
		}
		break;
	}
}


/*
================
R_StudioRotate
================
*/
inline void CStudioRender::R_StudioRotate( Vector& in1, mstudioboneweight_t *pboneweight, Vector& out1 )
{
	// NOTE: This only works to rotate normals if there's no scale in the
	// pose to world transforms. If we ever add scale, we'll need to
	// multiply by the inverse transpose of the pose to world

	if (pboneweight->numbones == 1)
	{
		VectorRotate( in1, m_PoseToWorld[(unsigned)pboneweight->bone[0]], out1 );
	}
	else
	{
		Vector out2;

		VectorFill( out1, 0 );

		for (int i = 0; i < pboneweight->numbones; i++)
		{
			VectorRotate( in1, m_PoseToWorld[(unsigned)pboneweight->bone[i]], out2 );
			VectorMA( out1, pboneweight->weight[i], out2, out1 );
		}
		VectorNormalize( out1 );
	}
}

inline void CStudioRender::R_StudioRotate( Vector4D& realIn1, mstudioboneweight_t *pboneweight, Vector4D& realOut1 )
{
	// garymcthack - god this sucks.
	Vector in1( realIn1[0], realIn1[1], realIn1[2] );
	Vector out1;
	if (pboneweight->numbones == 1)
	{
		VectorRotate( in1, m_PoseToWorld[(unsigned)pboneweight->bone[0]], out1 );
	}
	else
	{
		Vector out2;

		VectorFill( out1, 0 );

		for (int i = 0; i < pboneweight->numbones; i++)
		{
			VectorRotate( in1, m_PoseToWorld[(unsigned)pboneweight->bone[i]], out2 );
			VectorMA( out1, pboneweight->weight[i], out2, out1 );
		}
		VectorNormalize( out1 );
	}
	realOut1.Init( out1[0], out1[1], out1[2], realIn1[3] );
}


//-----------------------------------------------------------------------------
// Compute the contribution of a light depending on it's angle
//-----------------------------------------------------------------------------
/*
  light_normal (lights normal translated to same space as other normals)
  surface_normal
  light_direction_normal | (light_pos - vertex_pos) |
*/

template< int nLightType >
class CWorldLightAngleWrapper
{
public:
	FORCEINLINE static float WorldLightAngle( const LightDesc_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta )
	{
		float dot, dot2, ratio;

		switch (nLightType)	
		{
			case MATERIAL_LIGHT_POINT:
#if 1
				// half-lambert
				dot = DotProduct( snormal, delta );
				if (dot < 0.f)
					return 0.f;
#else
				dot = DotProduct( snormal, delta ) * 0.5 + 0.5;
				dot = dot * dot;
#endif
				return dot;

			case MATERIAL_LIGHT_SPOT:
#if 1
				// half-lambert
				dot = DotProduct( snormal, delta );
				if (dot < 0.)
					return 0.f;
#else
				dot = DotProduct( snormal, delta ) * 0.5 + 0.5;
				dot = dot * dot;
#endif

 				dot2 = -DotProduct (delta, lnormal);
				if (dot2 <= wl->m_PhiDot)
					return 0.f; // outside light cone

				ratio = dot;
				if (dot2 >= wl->m_ThetaDot)
					return ratio;	// inside inner cone

 				if ((wl->m_Falloff == 1.f) || (wl->m_Falloff == 0.f))
 				{
 					ratio *= (dot2 - wl->m_PhiDot) / (wl->m_ThetaDot - wl->m_PhiDot);
 				}
 				else
 				{
 					ratio *= pow((dot2 - wl->m_PhiDot) / (wl->m_ThetaDot - wl->m_PhiDot), wl->m_Falloff );
 				}
				return ratio;

			case MATERIAL_LIGHT_DIRECTIONAL:
#if 1
				// half-lambert
				dot2 = -DotProduct( snormal, lnormal );
				if (dot2 < 0.f)
					return 0.f;
#else
				dot2 = -DotProduct( snormal, lnormal ) * 0.5 + 0.5;
				dot2 = dot2 * dot2;
#endif
				return dot2;

			case MATERIAL_LIGHT_DISABLE:
				return 0.f;

			NO_DEFAULT;
		} 
	}
};

template< int nLightType >
class CWorldLightAngleWrapperConstDirectional
{
public:
	FORCEINLINE static float WorldLightAngle( const LightDesc_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta, float directionalamount )
	{
		float dot, dot2, ratio;

		// directional amount is constant
		dot = directionalamount;
		if (dot < 0.f)
			return 0.f;

		switch (nLightType)	
		{
		case MATERIAL_LIGHT_POINT:
		case MATERIAL_LIGHT_DIRECTIONAL:
			return dot;

		case MATERIAL_LIGHT_SPOT:
			dot2 = -DotProduct (delta, lnormal);
			if (dot2 <= wl->m_PhiDot)
				return 0.f; // outside light cone

			ratio = dot;
			if (dot2 >= wl->m_ThetaDot)
				return ratio;	// inside inner cone

			if ((wl->m_Falloff == 1.f) || (wl->m_Falloff == 0.f))
			{
				ratio *= (dot2 - wl->m_PhiDot) / (wl->m_ThetaDot - wl->m_PhiDot);
			}
			else
			{
				ratio *= pow((dot2 - wl->m_PhiDot) / (wl->m_ThetaDot - wl->m_PhiDot), wl->m_Falloff );
			}
			return ratio;

		case MATERIAL_LIGHT_DISABLE:
			return 0.f;

			NO_DEFAULT;
		} 
	}
};

inline float CStudioRender::R_WorldLightAngle( const LightDesc_t *wl, const Vector& lnormal, const Vector& snormal, const Vector& delta )
{
	switch (wl->m_Type) 
	{
		case MATERIAL_LIGHT_DISABLE:		return CWorldLightAngleWrapper<MATERIAL_LIGHT_DISABLE>::WorldLightAngle( wl, lnormal, snormal, delta );
		case MATERIAL_LIGHT_POINT:			return CWorldLightAngleWrapper<MATERIAL_LIGHT_POINT>::WorldLightAngle( wl, lnormal, snormal, delta );
		case MATERIAL_LIGHT_DIRECTIONAL:	return CWorldLightAngleWrapper<MATERIAL_LIGHT_DIRECTIONAL>::WorldLightAngle( wl, lnormal, snormal, delta );
		case MATERIAL_LIGHT_SPOT:			return CWorldLightAngleWrapper<MATERIAL_LIGHT_SPOT>::WorldLightAngle( wl, lnormal, snormal, delta );
		NO_DEFAULT;
	}
}


//-----------------------------------------------------------------------------
// Draws eyeballs
//-----------------------------------------------------------------------------
inline void CStudioRender::R_StudioEyeballNormal( mstudioeyeball_t const* peyeball, Vector& org, 
									Vector& pos, Vector& normal )
{
	// inside of a flattened torus
	VectorSubtract( pos, org, normal );
	float flUpAmount =  DotProduct( normal, peyeball->up );
	VectorMA( normal, -0.5 * flUpAmount, peyeball->up, normal );
	VectorNormalize( normal );
}


//-----------------------------------------------------------------------------
//
// Stateless utility methods
//
//-----------------------------------------------------------------------------

// Computes the submodel for a specified body + bodypart
int R_StudioSetupModel( int nBodyPart, int nBody, mstudiomodel_t **pSubModel, const studiohdr_t *pStudioHdr );

// Computes PoseToWorld from BoneToWorld
void ComputePoseToWorld( matrix3x4_t *pPoseToWorld, studiohdr_t *pStudioHdr, int boneMask, const Vector& vecViewOrigin, const matrix3x4_t *pBoneToWorld );

// Computes the model LOD
inline int ComputeModelLODAndMetric( studiohwdata_t *pHardwareData, float flUnitSphereSize, float *pMetric )
{
	// NOTE: This function was split off since CStudioRender needs it also.
	float flMetric = pHardwareData->LODMetric( flUnitSphereSize );
	if ( pMetric )
	{
		*pMetric = flMetric;
	}
	return pHardwareData->GetLODForMetric( flMetric );
}



#endif // CSTUDIORENDER_H
