//========= Copyright Valve Corporation, All rights reserved. ============//
//
// r_studio.cpp: routines for setting up to draw 3DStudio models 
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//===========================================================================//


#include "studio.h"
#include "studiorender.h"
#include "studiorendercontext.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "tier0/vprof.h"
#include "tier3/tier3.h"
#include "datacache/imdlcache.h"

// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"

//-----------------------------------------------------------------------------
// Figures out what kind of lighting we're gonna want
//-----------------------------------------------------------------------------
FORCEINLINE StudioModelLighting_t CStudioRender::R_StudioComputeLighting( IMaterial *pMaterial, int materialFlags, ColorMeshInfo_t *pColorMeshes )
{
	// Here, we only do software lighting when the following conditions are met.
	// 1) The material is vertex lit and we don't have hardware lighting
	// 2) We're drawing an eyeball
	// 3) We're drawing mouth-lit stuff

	// FIXME: When we move software lighting into the material system, only need to
	// test if it's vertex lit

	Assert( pMaterial );
	bool doMouthLighting = materialFlags && (m_pStudioHdr->nummouths >= 1);

	if ( IsX360() )
	{
		// 360 does not do software lighting
		return doMouthLighting ? LIGHTING_MOUTH : LIGHTING_HARDWARE;
	}

	bool doSoftwareLighting = doMouthLighting ||
		(pMaterial->IsVertexLit() && pMaterial->NeedsSoftwareLighting() );

	if ( !m_pRC->m_Config.m_bSupportsVertexAndPixelShaders )
	{
		if ( !doSoftwareLighting && pColorMeshes )
		{
			pMaterial->SetUseFixedFunctionBakedLighting( true );
		}
		else
		{
			doSoftwareLighting = true;
			pMaterial->SetUseFixedFunctionBakedLighting( false );
		}
	}

	StudioModelLighting_t lighting = LIGHTING_HARDWARE;
	if ( doMouthLighting )
		lighting = LIGHTING_MOUTH;
	else if ( doSoftwareLighting )
		lighting = LIGHTING_SOFTWARE;

	return lighting;
}


IMaterial* CStudioRender::R_StudioSetupSkinAndLighting( IMatRenderContext *pRenderContext, int index, IMaterial **ppMaterials, int materialFlags,  
	void /*IClientRenderable*/ *pClientRenderable, ColorMeshInfo_t *pColorMeshes, StudioModelLighting_t &lighting )
{
	VPROF( "R_StudioSetupSkin" );
	IMaterial *pMaterial = NULL;
	bool bCheckForConVarDrawTranslucentSubModels = false;
	if( m_pRC->m_Config.bWireframe && !m_pRC->m_pForcedMaterial )
	{
		if ( m_pRC->m_Config.bDrawZBufferedWireframe )
			pMaterial = m_pMaterialMRMWireframeZBuffer;
		else
			pMaterial = m_pMaterialMRMWireframe;
	}
	else if( m_pRC->m_Config.bShowEnvCubemapOnly )
	{
		pMaterial = m_pMaterialModelEnvCubemap;
	}
	else
	{
		if ( !m_pRC->m_pForcedMaterial && ( m_pRC->m_nForcedMaterialType != OVERRIDE_DEPTH_WRITE && m_pRC->m_nForcedMaterialType != OVERRIDE_SSAO_DEPTH_WRITE ) )
		{
			pMaterial = ppMaterials[index];
			if ( !pMaterial )
			{
				Assert( 0 );
				return 0;
			}
		}
		else
		{
			materialFlags = 0;
			pMaterial = m_pRC->m_pForcedMaterial;
			if (m_pRC->m_nForcedMaterialType == OVERRIDE_BUILD_SHADOWS)
			{
				// Connect the original material up to the shadow building material
				// Also bind the original material so its proxies are in the correct state
				static unsigned int translucentCache = 0;
				IMaterialVar* pOriginalMaterialVar = pMaterial->FindVarFast( "$translucent_material", &translucentCache );
				Assert( pOriginalMaterialVar );
				IMaterial *pOriginalMaterial = ppMaterials[index];
				if ( pOriginalMaterial )
				{
					// Disable any alpha modulation on the original material that was left over from when it was last rendered
					pOriginalMaterial->AlphaModulate( 1.0f );
					pRenderContext->Bind( pOriginalMaterial, pClientRenderable );
					if ( pOriginalMaterial->IsTranslucent() || pOriginalMaterial->IsAlphaTested() )
					{
						if ( pOriginalMaterialVar )
							pOriginalMaterialVar->SetMaterialValue( pOriginalMaterial );
					}
					else
					{
						if ( pOriginalMaterialVar )
							pOriginalMaterialVar->SetMaterialValue( NULL );
					}
				}
				else
				{
					if ( pOriginalMaterialVar )
						pOriginalMaterialVar->SetMaterialValue( NULL );
				}
			}
			else if ( m_pRC->m_nForcedMaterialType == OVERRIDE_DEPTH_WRITE || m_pRC->m_nForcedMaterialType == OVERRIDE_SSAO_DEPTH_WRITE )
			{
				// Disable any alpha modulation on the original material that was left over from when it was last rendered
				ppMaterials[index]->AlphaModulate( 1.0f );

				// Bail if the material is still considered translucent after setting the AlphaModulate to 1.0
				if ( ppMaterials[index]->IsTranslucent() )
				{
					return NULL;
				}

				static unsigned int originalTextureVarCache = 0;
				IMaterialVar *pOriginalTextureVar = ppMaterials[index]->FindVarFast( "$basetexture", &originalTextureVarCache );

				// Select proper override material
				int nAlphaTest = (int) ( ppMaterials[index]->IsAlphaTested() && pOriginalTextureVar->IsTexture() ); // alpha tested base texture
				int nNoCull = (int) ppMaterials[index]->IsTwoSided();
				if ( m_pRC->m_nForcedMaterialType == OVERRIDE_SSAO_DEPTH_WRITE )
				{
					pMaterial = m_pSSAODepthWrite[nAlphaTest][nNoCull];
				}
				else
				{
					pMaterial = m_pDepthWrite[nAlphaTest][nNoCull];
				}

				// If we're alpha tested, we should set up the texture variables from the original material
				if ( nAlphaTest != 0 )
				{
					static unsigned int originalTextureFrameVarCache = 0;
					IMaterialVar *pOriginalTextureFrameVar = ppMaterials[index]->FindVarFast( "$frame", &originalTextureFrameVarCache );
					static unsigned int originalAlphaRefCache = 0;
					IMaterialVar *pOriginalAlphaRefVar = ppMaterials[index]->FindVarFast( "$AlphaTestReference", &originalAlphaRefCache );

					static unsigned int textureVarCache = 0;
					IMaterialVar *pTextureVar = pMaterial->FindVarFast( "$basetexture", &textureVarCache );
					static unsigned int textureFrameVarCache = 0;
					IMaterialVar *pTextureFrameVar = pMaterial->FindVarFast( "$frame", &textureFrameVarCache );
					static unsigned int alphaRefCache = 0;
					IMaterialVar *pAlphaRefVar = pMaterial->FindVarFast( "$AlphaTestReference", &alphaRefCache );

					if ( pOriginalTextureVar->IsTexture() ) // If $basetexture is defined
					{
						if( pTextureVar && pOriginalTextureVar )
						{
							pTextureVar->SetTextureValue( pOriginalTextureVar->GetTextureValue() );
						}

						if( pTextureFrameVar && pOriginalTextureFrameVar )
						{
							pTextureFrameVar->SetIntValue( pOriginalTextureFrameVar->GetIntValue() );
						}

						if( pAlphaRefVar && pOriginalAlphaRefVar )
						{
							pAlphaRefVar->SetFloatValue( pOriginalAlphaRefVar->GetFloatValue() );
						}
					}
				}
			}
		}

		// Set this bool to check after the bind below
		bCheckForConVarDrawTranslucentSubModels = true;

		if ( m_pRC->m_nForcedMaterialType != OVERRIDE_DEPTH_WRITE && m_pRC->m_nForcedMaterialType != OVERRIDE_SSAO_DEPTH_WRITE)
		{
			// Try to set the alpha based on the blend
			pMaterial->AlphaModulate( m_pRC->m_AlphaMod );

			// Try to set the color based on the colormod
			pMaterial->ColorModulate( m_pRC->m_ColorMod[0], m_pRC->m_ColorMod[1], m_pRC->m_ColorMod[2] );
		}
	}

	lighting = R_StudioComputeLighting( pMaterial, materialFlags, pColorMeshes );
	if ( lighting == LIGHTING_MOUTH )
	{
		if ( !m_pRC->m_Config.bTeeth || !R_TeethAreVisible() )
			return NULL;
		// skin it and light it, but only if we need to.
		if ( m_pRC->m_Config.m_bSupportsVertexAndPixelShaders )
		{
			R_MouthSetupVertexShader( pMaterial );
		}
	}

	// TODO: It's possible we don't want to use the color texels--for example because of a convar. 
	// We should check that here in addition to whether or not we have the data available.
	static unsigned int lightmapVarCache = 0;
	IMaterialVar *pLightmapVar = pMaterial->FindVarFast( "$lightmap", &lightmapVarCache );
	if ( pLightmapVar )
	{
		ITexture* newTex = pColorMeshes ? pColorMeshes->m_pLightmap : NULL;

		if (newTex)
			pLightmapVar->SetTextureValue(newTex);
		else 
			pLightmapVar->SetUndefined();
	}
	
	pRenderContext->Bind( pMaterial, pClientRenderable );

	if ( bCheckForConVarDrawTranslucentSubModels )
	{
		bool translucent = pMaterial->IsTranslucent();

		if (( m_bDrawTranslucentSubModels && !translucent ) ||
			( !m_bDrawTranslucentSubModels && translucent ))
		{
			m_bSkippedMeshes = true;
			return NULL;
		}
	}

	return pMaterial;
}



//=============================================================================


/*
=================
R_StudioSetupModel
	based on the body part, figure out which mesh it should be using.
inputs:
outputs:
	pstudiomesh
	pmdl
=================
*/
int R_StudioSetupModel( int bodypart, int entity_body, mstudiomodel_t **ppSubModel, 
	const studiohdr_t *pStudioHdr )
{
	int index;
	mstudiobodyparts_t   *pbodypart;

	if (bodypart > pStudioHdr->numbodyparts)
	{
		ConDMsg ("R_StudioSetupModel: no such bodypart %d\n", bodypart);
		bodypart = 0;
	}

	pbodypart = pStudioHdr->pBodypart( bodypart );

	if ( pbodypart->base == 0 )
	{
		Warning( "Model has missing body part: %s\n", pStudioHdr->pszName() );
		Assert( 0 );
	}
	index = entity_body / pbodypart->base;
	index = index % pbodypart->nummodels;

	Assert( ppSubModel );
	*ppSubModel = pbodypart->pModel( index );
	return index;
}



//-----------------------------------------------------------------------------
// Generates the PoseToBone Matrix nessecary to align the given bone with the 
// world.
//-----------------------------------------------------------------------------
static void ScreenAlignBone( matrix3x4_t *pPoseToWorld, mstudiobone_t *pCurBone, 
	const Vector& vecViewOrigin, const matrix3x4_t &boneToWorld )
{
	// Grab the world translation:
	Vector vT( boneToWorld[0][3], boneToWorld[1][3], boneToWorld[2][3] );

	// Construct the coordinate frame:
	// Initialized to get rid of compiler 
	Vector vX, vY, vZ;

	if( pCurBone->flags & BONE_SCREEN_ALIGN_SPHERE )
	{
		vX = vecViewOrigin - vT;		    
		VectorNormalize(vX);
		vZ = Vector(0,0,1);
		vY = vZ.Cross(vX);				
		VectorNormalize(vY);
		vZ = vX.Cross(vY);				
		VectorNormalize(vZ);
	} 
	else
	{
		Assert( pCurBone->flags & BONE_SCREEN_ALIGN_CYLINDER );
		vX.Init( boneToWorld[0][0], boneToWorld[1][0], boneToWorld[2][0] );
		vZ = vecViewOrigin - vT;			
		VectorNormalize(vZ);
		vY = vZ.Cross(vX);				
		VectorNormalize(vY);
		vZ = vX.Cross(vY);				
		VectorNormalize(vZ);
	}

	matrix3x4_t matBoneBillboard( 
		vX.x, vY.x, vZ.x, vT.x, 
		vX.y, vY.y, vZ.y, vT.y, 
		vX.z, vY.z, vZ.z, vT.z );
	ConcatTransforms( matBoneBillboard, pCurBone->poseToBone, *pPoseToWorld );
}


//-----------------------------------------------------------------------------
// Computes PoseToWorld from BoneToWorld
//-----------------------------------------------------------------------------
void ComputePoseToWorld( matrix3x4_t *pPoseToWorld, studiohdr_t *pStudioHdr, int boneMask, const Vector& vecViewOrigin, const matrix3x4_t *pBoneToWorld )
{ 
	if ( pStudioHdr->flags & STUDIOHDR_FLAGS_STATIC_PROP )
	{
		// by definition, these always have an identity poseToBone transform
		MatrixCopy( pBoneToWorld[ 0 ], pPoseToWorld[ 0 ] );
		return;
	}

	if ( !pStudioHdr->pLinearBones() )
	{
		// convert bone to world transformations into pose to world transformations
		for (int i = 0; i < pStudioHdr->numbones; i++)
		{
			mstudiobone_t *pCurBone = pStudioHdr->pBone( i );
			if ( !(pCurBone->flags & boneMask) )
				continue;

			ConcatTransforms( pBoneToWorld[ i ], pCurBone->poseToBone, pPoseToWorld[ i ] );
		}
	}
	else
	{
		mstudiolinearbone_t *pLinearBones = pStudioHdr->pLinearBones();

		// convert bone to world transformations into pose to world transformations
		for (int i = 0; i < pStudioHdr->numbones; i++)
		{
			if ( !(pLinearBones->flags(i) & boneMask) )
				continue;

			ConcatTransforms( pBoneToWorld[ i ], pLinearBones->poseToBone(i), pPoseToWorld[ i ] );
		}
	}

#if 0
			// These don't seem to be used in any existing QC file, re-enable in a future project?
			// Pretransform
			if( !( pCurBone->flags & ( BONE_SCREEN_ALIGN_SPHERE | BONE_SCREEN_ALIGN_CYLINDER )))
			{
				ConcatTransforms( pBoneToWorld[ i ], pCurBone->poseToBone, pPoseToWorld[ i ] );
			}
			else 
			{
				// If this bone is screen aligned, then generate a PoseToWorld matrix that billboards the bone
				ScreenAlignBone( &pPoseToWorld[i], pCurBone, vecViewOrigin, pBoneToWorld[i] );
			} 	
#endif
}