//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $Workfile:     $
// $Date:         $
// $NoKeywords: $
//===========================================================================//

#include "studio.h"
#include "studiorendercontext.h"
#include "bitmap/imageformat.h"
#include "materialsystem/imaterialsystem.h"
#include "materialsystem/imaterial.h"
#include "materialsystem/imaterialvar.h"
#include "materialsystem/itexture.h"
#include "materialsystem/imesh.h"
#include "mathlib/mathlib.h"
#include "studiorender.h"
#include "pixelwriter.h"
#include "vtf/vtf.h"
#include "tier1/convar.h"
#include "tier1/KeyValues.h"
#include "tier0/vprof.h"

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

#define sign( a ) (((a) < 0) ? -1 : (((a) > 0) ? 1 : 0 ))

void CStudioRender::R_StudioEyeballPosition( const mstudioeyeball_t *peyeball, eyeballstate_t *pstate )
{
	// Vector  forward;
	// Vector  org, right, up;

	pstate->peyeball = peyeball;

	Vector tmp;
	// move eyeball into worldspace
	{
		// ConDMsg("%.2f %.2f %.2f\n", peyeball->org[0], peyeball->org[1], peyeball->org[2] );

		VectorCopy( peyeball->org, tmp );

		tmp[0] += m_pRC->m_Config.fEyeShiftX * sign( tmp[0] );
		tmp[1] += m_pRC->m_Config.fEyeShiftY * sign( tmp[1] );
		tmp[2] += m_pRC->m_Config.fEyeShiftZ * sign( tmp[2] );
	}
	VectorTransform( tmp, m_pBoneToWorld[peyeball->bone], pstate->org );
	VectorRotate( peyeball->up, m_pBoneToWorld[peyeball->bone], pstate->up );

	// look directly at target
	VectorSubtract( m_pRC->m_ViewTarget, pstate->org, pstate->forward );
	VectorNormalize( pstate->forward );

	if ( !m_pRC->m_Config.bEyeMove )
	{
		VectorRotate( peyeball->forward, m_pBoneToWorld[peyeball->bone], pstate->forward );
		VectorScale( pstate->forward, -1 ,pstate->forward ); // ???
	}

	CrossProduct( pstate->forward, pstate->up, pstate->right );
	VectorNormalize( pstate->right );

	// shift N degrees off of the target
	float dz;
	dz = peyeball->zoffset;

	VectorMA( pstate->forward, peyeball->zoffset + dz, pstate->right, pstate->forward );

#if 0
	// add random jitter
	VectorMA( forward, RandomFloat( -0.02, 0.02 ), right, forward );
	VectorMA( forward, RandomFloat( -0.02, 0.02 ), up, forward );
#endif

	VectorNormalize( pstate->forward );
	// re-aim eyes 
	CrossProduct( pstate->forward, pstate->up, pstate->right );
	VectorNormalize( pstate->right );

	CrossProduct( pstate->right, pstate->forward, pstate->up );
	VectorNormalize( pstate->up );

	float scale = (1.0 / peyeball->iris_scale) + m_pRC->m_Config.fEyeSize;

	if (scale > 0)
		scale = 1.0 / scale;

	VectorScale( &pstate->right[0], -scale, pstate->mat[0] );
	VectorScale( &pstate->up[0], -scale, pstate->mat[1] );

	pstate->mat[0][3] = -DotProduct( &pstate->org[0], pstate->mat[0] ) + 0.5f;
	pstate->mat[1][3] = -DotProduct( &pstate->org[0], pstate->mat[1] ) + 0.5f;

	// FIXME: push out vertices for cornea
}


//-----------------------------------------------------------------------------
//
//-----------------------------------------------------------------------------
void CStudioRender::R_StudioEyelidFACS( const mstudioeyeball_t *peyeball, const eyeballstate_t *pstate )
{
	if ( peyeball->m_bNonFACS )
		return;

	Vector  headup;
	Vector  headforward;
	Vector	pos;

	float upperlid = DEG2RAD( 9.5 );
	float lowerlid = DEG2RAD( -26.4 );

	// FIXME: Crash workaround
	Vector vecNormTarget;
	vecNormTarget.Init( peyeball->uppertarget[0], peyeball->uppertarget[1], peyeball->uppertarget[2] );
	vecNormTarget /= peyeball->radius;
	vecNormTarget.x = clamp( vecNormTarget.x, -1.0f, 1.0f );
	vecNormTarget.y = clamp( vecNormTarget.y, -1.0f, 1.0f );
	vecNormTarget.z = clamp( vecNormTarget.z, -1.0f, 1.0f );

	// get weighted position of eyeball angles based on the "raiser", "neutral", and "lowerer" controls
	upperlid = m_pFlexWeights[peyeball->upperflexdesc[0]] * asin( vecNormTarget.x );
	upperlid += m_pFlexWeights[peyeball->upperflexdesc[1]] * asin( vecNormTarget.y );
	upperlid += m_pFlexWeights[peyeball->upperflexdesc[2]] * asin( vecNormTarget.z );

	vecNormTarget.Init( peyeball->lowertarget[0], peyeball->lowertarget[1], peyeball->lowertarget[2] );
	vecNormTarget /= peyeball->radius;
	vecNormTarget.x = clamp( vecNormTarget.x, -1.0f, 1.0f );
	vecNormTarget.y = clamp( vecNormTarget.y, -1.0f, 1.0f );
	vecNormTarget.z = clamp( vecNormTarget.z, -1.0f, 1.0f );

	lowerlid = m_pFlexWeights[peyeball->lowerflexdesc[0]] * asin( vecNormTarget.x );
	lowerlid += m_pFlexWeights[peyeball->lowerflexdesc[1]] * asin( vecNormTarget.y );
	lowerlid += m_pFlexWeights[peyeball->lowerflexdesc[2]] * asin( vecNormTarget.z );

	// ConDMsg("%.1f %.1f\n", RAD2DEG( upperlid ), RAD2DEG( lowerlid ) );		

	float sinupper, cosupper, sinlower, coslower;
	SinCos( upperlid, &sinupper, &cosupper );
	SinCos( lowerlid, &sinlower, &coslower );

	// convert to head relative space
	VectorIRotate( pstate->up, m_pBoneToWorld[peyeball->bone], headup );
	VectorIRotate( pstate->forward, m_pBoneToWorld[peyeball->bone], headforward );

	// upper lid
	VectorScale( headup, sinupper * peyeball->radius, pos );
	VectorMA( pos, cosupper * peyeball->radius, headforward, pos );
	m_pFlexWeights[peyeball->upperlidflexdesc] = DotProduct( pos, peyeball->up );

	// lower lid
	VectorScale( headup, sinlower * peyeball->radius, pos );
	VectorMA( pos, coslower * peyeball->radius, headforward, pos );
	m_pFlexWeights[peyeball->lowerlidflexdesc] = DotProduct( pos, peyeball->up );
	// ConDMsg("%.4f %.4f\n", m_pRC->m_FlexWeights[peyeball->upperlidflex], m_pRC->m_FlexWeights[peyeball->lowerlidflex] );
}


void CStudioRender::MaterialPlanerProjection( const matrix3x4_t& mat, int count, const Vector *psrcverts, Vector2D *pdesttexcoords )
{
	for (int i = 0; i < count; i++)
	{
		pdesttexcoords[i][0] = DotProduct( &psrcverts[i].x, mat[0] ) + mat[0][3];
		pdesttexcoords[i][1] = DotProduct( &psrcverts[i].x, mat[1] ) + mat[1][3];
	}
}


//-----------------------------------------------------------------------------
// Ramp and clamp the flex weight
//-----------------------------------------------------------------------------
float CStudioRender::RampFlexWeight( mstudioflex_t &flex, float w )
{
	if (w <= flex.target0 || w >= flex.target3)
	{
		// value outside of range
		w = 0.0;
	}
	else if (w < flex.target1)
	{
		// 0 to 1 ramp
		w = (w - flex.target0) / (flex.target1 - flex.target0);
	}
	else if (w > flex.target2)
	{
		// 1 to 0 ramp
		w = (flex.target3 - w) / (flex.target3 - flex.target2);
	}
	else
	{
		// plat
		w = 1.0;
	}
	return w;
}

//-----------------------------------------------------------------------------
// Setup the flex verts for this rendering
//-----------------------------------------------------------------------------
void CStudioRender::R_StudioFlexVerts( mstudiomesh_t *pmesh, int lod )
{
	VPROF_BUDGET( "CStudioRender::R_StudioFlexVerts", VPROF_BUDGETGROUP_MODEL_RENDERING );

	Assert( pmesh );

	const float flVertAnimFixedPointScale = m_pStudioHdr->VertAnimFixedPointScale();

	// There's a chance we can actually do the flex twice on a single mesh
	// since there's flexed HW + SW portions of the mesh.
	if (m_VertexCache.IsFlexComputationDone())
		return;

	// get pointers to geometry
	if ( !pmesh->pModel()->CacheVertexData( m_pStudioHdr ) )
	{
		// not available yet
		return;
	}
	const mstudio_meshvertexdata_t *vertData = pmesh->GetVertexData( m_pStudioHdr );
	Assert( vertData );
	if ( !vertData )
	{
		static unsigned int warnCount = 0;
		if ( warnCount++ < 20 )
			Warning( "ERROR: R_StudioFlexVerts, model verts have been compressed, cannot render! (use \"-no_compressed_vvds\")" );
		return;
	}

	// The flex data should have been converted to the new (fixed-point) format on load:
	Assert( m_pStudioHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED );
	if ( ( m_pStudioHdr->flags & STUDIOHDR_FLAGS_FLEXES_CONVERTED ) == 0 )
	{
		static unsigned int flexConversionTimesWarned = 0;
		if ( flexConversionTimesWarned++ < 6 )
			Warning( "ERROR: flex verts have not been converted (queued loader refcount bug?) - expect to see 'exploded' faces" );
	}


	mstudiovertex_t *pVertices = vertData->Vertex( 0 );
	Vector4D *pStudioTangentS;
	if ( vertData->HasTangentData() )
	{
		pStudioTangentS	= vertData->TangentS( 0 );
	}
	else
	{
		pStudioTangentS = NULL;
	}

	mstudioflex_t *pflex = pmesh->pFlex( 0 );
	
	m_VertexCache.SetupComputation( pmesh, true );

	// apply flex weights
	int i, j, n;

	for (i = 0; i < pmesh->numflexes; i++)
	{
		float w1 = RampFlexWeight( pflex[i], m_pFlexWeights[ pflex[i].flexdesc ] );
		float w2 = RampFlexWeight( pflex[i], m_pFlexDelayedWeights[ pflex[i].flexdesc ] );

		float w3, w4;
		if ( pflex[i].flexpair != 0)
		{
			w3 = RampFlexWeight( pflex[i], m_pFlexWeights[ pflex[i].flexpair ] );
			w4 = RampFlexWeight( pflex[i], m_pFlexDelayedWeights[ pflex[i].flexpair ] );
		}
		else
		{
			w3 = w1;
			w4 = w2;
		}

		if ( w1 > -0.001 && w1 < 0.001 && w2 > -0.001 && w2 < 0.001 )
		{
			if ( w3 > -0.001 && w3 < 0.001 && w4 > -0.001 && w4 < 0.001 )
			{
				continue;
			}
		}

		// We may have wrinkle information for this flex, but if we're software skinning
		// we're going to ignore it.
		byte *pvanim = pflex[i].pBaseVertanim();
		int nVAnimSizeBytes = pflex[i].VertAnimSizeBytes();

		for (j = 0; j < pflex[i].numverts; j++)
		{
			mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pvanim + j * nVAnimSizeBytes );
			n = pAnim->index;

			// Only flex the indices that are (still) part of this mesh
			// need lod restriction here
			if (n < pmesh->vertexdata.numLODVertexes[lod])
			{
				mstudiovertex_t &vert = pVertices[n];

				CachedPosNormTan_t* pFlexedVertex;
				if (!m_VertexCache.IsVertexFlexed(n))
				{
					// Add a new flexed vert to the flexed vertex list
					pFlexedVertex = m_VertexCache.CreateFlexVertex(n);
					// skip processing if no more flexed verts can be allocated
					if (pFlexedVertex == NULL)
						continue;

					VectorCopy( vert.m_vecPosition, pFlexedVertex->m_Position );
					VectorCopy( vert.m_vecNormal, pFlexedVertex->m_Normal );

					if (pStudioTangentS)
					{
						Vector4DCopy( pStudioTangentS[n], pFlexedVertex->m_TangentS );
						Assert( pFlexedVertex->m_TangentS.w == -1.0f || pFlexedVertex->m_TangentS.w == 1.0f );
					}
				}
				else
				{
					pFlexedVertex = m_VertexCache.GetFlexVertex(n);
				}

				float s = pAnim->speed * (1.0F/255.0F);
				float b = pAnim->side * (1.0F/255.0F);

				float w = (w1 * s + (1.0f - s) * w2) * (1.0f - b) + b * (w3 * s + (1.0f - s) * w4);

				// Accumulate weighted deltas
				pFlexedVertex->m_Position += pAnim->GetDeltaFixed( flVertAnimFixedPointScale ) * w;
				pFlexedVertex->m_Normal += pAnim->GetNDeltaFixed( flVertAnimFixedPointScale ) * w;

				if ( pStudioTangentS )
				{
					pFlexedVertex->m_TangentS.AsVector3D() += pAnim->GetNDeltaFixed( flVertAnimFixedPointScale ) * w;
					Assert( pFlexedVertex->m_TangentS.w == -1.0f || pFlexedVertex->m_TangentS.w == 1.0f );
				}
			}
		}
	}

	m_VertexCache.RenormalizeFlexVertices( vertData->HasTangentData() );
}

// REMOVED!!  Look in version 32 if you need it.
//static void R_StudioEyeballNormals( const mstudioeyeball_t *peyeball, int count, const Vector *psrcverts, Vector *pdestnorms )

#define KERNEL_DIAMETER 2
#define KERNEL_TEXELS (KERNEL_DIAMETER)
#define KERNEL_TEXEL_RADIUS (KERNEL_TEXELS / 2)

inline float GlintGaussSpotCoefficient( float dx, float dy /*, float *table */ )
{
	const float radius = KERNEL_DIAMETER / 2;
	const float rsq = 1.0f / (radius * radius);
	float r2 = (dx * dx + dy * dy) * rsq;
	if (r2 <= 1.0f)
	{
		return exp( -25.0 * r2 );
		// NOTE: This optimization doesn't make much of a difference
		//int index = r2 * (GLINT_TABLE_ENTRIES-1);
		//return table[index];
	}

	return 0;
}

void CStudioRender::AddGlint( CPixelWriter &pixelWriter, float x, float y, const Vector& color )
{
	x = (x + 0.5f) * m_GlintWidth;
	y = (y + 0.5f) * m_GlintHeight;
	const float texelRadius = KERNEL_DIAMETER / 2;

	int x0 = (int)x;
	int y0 = (int)y;
	int x1 = x0 + texelRadius;
	int y1 = y0 + texelRadius;
	x0 -= texelRadius;
	y0 -= texelRadius;

	// clip light to texture
	if ( (x0 >= m_GlintWidth) || (x1 < 0) || (y0 >= m_GlintHeight) || (y1 < 0) )
		return;

	// clamp coordinates
	if ( x0 < 0 )
	{
		x0 = 0;
	}
	if ( y0 < 0 )
	{
		y0 = 0;
	}
	if ( x1 >= m_GlintWidth )
	{
		x1 = m_GlintWidth-1;
	}
	if ( y1 >= m_GlintHeight )
	{
		y1 = m_GlintHeight-1;
	}

	for (int v = y0; v <= y1; ++v )
	{
		pixelWriter.Seek( x0, v );

		for (int u = x0; u <= x1; ++u )
		{
			float fu = ((float)u) - x;
			float fv = ((float)v) - y;
			const float offset = 0.25;
			float intensity =	GlintGaussSpotCoefficient( fu-offset, fv-offset ) + 
								GlintGaussSpotCoefficient( fu+offset, fv-offset ) + 
								5 * GlintGaussSpotCoefficient( fu, fv ) + 
								GlintGaussSpotCoefficient( fu-offset, fv+offset ) + 
								GlintGaussSpotCoefficient( fu+offset, fv+offset );
			
			// NOTE: Old filter code multiplies the signal by 8X, so we will too
			intensity *= (4.0f/9.0f);

			// NOTE: It's much faster to do the work in the dest texture than to touch the memory more
			// or make more buffers
			Vector outColor = intensity * color;
			int r, g, b, a;
			pixelWriter.ReadPixelNoAdvance( r, g, b, a );
			outColor.x += TextureToLinear(r);
			outColor.y += TextureToLinear(g);
			outColor.z += TextureToLinear(b);
			pixelWriter.WritePixel( LinearToTexture(outColor.x), LinearToTexture(outColor.y), LinearToTexture(outColor.z) );
		}
	}
}


//-----------------------------------------------------------------------------
// glint
//-----------------------------------------------------------------------------

// test/stub code
#if 0
class CEmptyTextureRegen : public ITextureRegenerator
{
public:
	virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
	{
		// get the texture
		unsigned char *pTextureData = pVTFTexture->ImageData( 0, 0, 0 );
		int nImageSize = pVTFTexture->ComputeMipSize( 0 );
		memset( pTextureData, 0, nImageSize );
	}

	// We've got a global instance, no need to delete it
	virtual void Release() {}
};
static CEmptyTextureRegen s_GlintTextureRegen;
#endif

class CGlintTextureRegenerator : public ITextureRegenerator
{
public:
	virtual void RegenerateTextureBits( ITexture *pTexture, IVTFTexture *pVTFTexture, Rect_t *pRect )
	{
		// We don't need to reconstitute the bits after a task switch
		// since we reconstitute them every frame they are used anyways
		if ( !m_pStudioRender )
			return;

		if ( ( m_pStudioRender->m_GlintWidth != pVTFTexture->Width() ) || 
			( m_pStudioRender->m_GlintHeight != pVTFTexture->Height() ) )
		{
			m_pStudioRender->m_GlintWidth = pVTFTexture->Width();
			m_pStudioRender->m_GlintHeight = pVTFTexture->Height();
		}

		CStudioRender::GlintRenderData_t pRenderData[16];
		int nGlintCount = m_pStudioRender->BuildGlintRenderData( pRenderData, 
			ARRAYSIZE(pRenderData),	m_pState, *m_pVRight, *m_pVUp, *m_pROrigin );

		// setup glint texture
		unsigned char *pTextureData = pVTFTexture->ImageData( 0, 0, 0 );
		CPixelWriter pixelWriter;
		pixelWriter.SetPixelMemory( pVTFTexture->Format(), pTextureData, pVTFTexture->RowSizeInBytes( 0 ) );
  		int nImageSize = pVTFTexture->ComputeMipSize( 0 );
  		memset( pTextureData, 0, nImageSize );

		// Put in glints due to the lights in the scene
		for ( int i = 0; i < nGlintCount; ++i )
		{
			// NOTE: AddGlint is a more expensive solution but it looks better close-up
			m_pStudioRender->AddGlint( pixelWriter, pRenderData[i].m_vecPosition[0], 
				pRenderData[i].m_vecPosition[1], pRenderData[i].m_vecIntensity );
		}
	}

	// We've got a global instance, no need to delete it
	virtual void Release() {}

	const eyeballstate_t *m_pState;
	const Vector *m_pVRight;
	const Vector *m_pVUp;
	const Vector *m_pROrigin;
	CStudioRender *m_pStudioRender;
};

static CGlintTextureRegenerator s_GlintTextureRegen;

static ITexture *s_pProcGlint = NULL;
void CStudioRender::PrecacheGlint()
{
	if ( !m_pGlintTexture )
	{
		// Begin block in which all render targets should be allocated
		g_pMaterialSystem->BeginRenderTargetAllocation();

		// Get the texture that we are going to be updating procedurally.
		m_pGlintTexture = g_pMaterialSystem->CreateNamedRenderTargetTextureEx2( 
			"_rt_eyeglint", 32, 32, RT_SIZE_NO_CHANGE, IMAGE_FORMAT_BGRA8888, MATERIAL_RT_DEPTH_NONE );
		m_pGlintTexture->IncrementReferenceCount();

		// Begin block in which all render targets should be allocated
		g_pMaterialSystem->EndRenderTargetAllocation();

		if ( !IsX360() )
		{
			// Get the texture that we are going to be updating procedurally.
			s_pProcGlint = g_pMaterialSystem->CreateProceduralTexture( 
				"proc_eyeglint", TEXTURE_GROUP_MODEL, 32, 32, IMAGE_FORMAT_BGRA8888, TEXTUREFLAGS_NOMIP|TEXTUREFLAGS_NOLOD );
			s_pProcGlint->SetTextureRegenerator( &s_GlintTextureRegen );
		}

		// JAY: I don't see this pattern in the code often.  It looks like the material system
		// would rather than I deal exclusively with IMaterials instead.
		// So maybe we should bake the LOD texture into the eyes shader.
		// For now, just hardcode one
		// UNDONE: Add a $lodtexture to the eyes shader.  Maybe add a $lodsize too.
		// UNDONE: Make eyes texture load $lodtexture and switch to that here instead of black
		m_pGlintLODTexture = g_pMaterialSystem->FindTexture( IsX360() ? "black" : "vgui/black", NULL, false );
		m_pGlintLODTexture->IncrementReferenceCount();
	}
}

void CStudioRender::UncacheGlint()
{
	if ( m_pGlintTexture )
	{
		if ( s_pProcGlint )
		{
			s_pProcGlint->SetTextureRegenerator( NULL );
			s_pProcGlint->DecrementReferenceCount();
			s_pProcGlint = NULL;
		}
		m_pGlintTexture->DecrementReferenceCount();
		m_pGlintTexture = NULL;
		m_pGlintLODTexture->DecrementReferenceCount();
		m_pGlintLODTexture = NULL;
	}
}

int CStudioRender::BuildGlintRenderData( GlintRenderData_t *pData, int nMaxGlints,
	const eyeballstate_t *pState, const Vector& vright, const Vector& vup, const Vector& r_origin )
{
	// NOTE: See version 25 for lots of #if 0ed out stuff I removed
	Vector viewdelta;
	VectorSubtract( r_origin, pState->org, viewdelta );
	VectorNormalize( viewdelta );

	// hack cornea position
	float iris_radius = pState->peyeball->radius * (6.0 / 12.0);
	float cornea_radius = pState->peyeball->radius * (8.0 / 12.0);

	Vector cornea;
	// position on eyeball that matches iris radius
	float er = ( iris_radius / pState->peyeball->radius );
	er = FastSqrt( 1 - er * er );

	// position on cornea sphere that matches iris radius
	float cr = ( iris_radius / cornea_radius );
	cr = FastSqrt( 1 - cr * cr );

	float r = ( er * pState->peyeball->radius - cr * cornea_radius );
	VectorScale( pState->forward, r, cornea );

	// get offset for center of cornea
	float dx, dy;
	dx = DotProduct( vright, cornea );
	dy = DotProduct( vup, cornea );

	// move cornea to world space
	VectorAdd( cornea, pState->org, cornea );

	Vector delta, intensity;
	Vector reflection, coord;

	// Put in glints due to the lights in the scene
	int nGlintCount = 0;
	for ( int i = 0; R_LightGlintPosition( i, cornea, delta, intensity ); ++i )
	{
		VectorNormalize( delta );
		if ( DotProduct( delta, pState->forward ) <= 0 )
			continue;

		VectorAdd( delta, viewdelta, reflection );
		VectorNormalize( reflection );

		pData[nGlintCount].m_vecPosition[0] = dx + cornea_radius * DotProduct( vright, reflection );
		pData[nGlintCount].m_vecPosition[1] = dy + cornea_radius * DotProduct( vup, reflection );
		pData[nGlintCount].m_vecIntensity = intensity;
		if ( ++nGlintCount >= nMaxGlints )
			return nMaxGlints;

		if ( !R_LightGlintPosition( i, pState->org, delta, intensity ) )
			continue;

		VectorNormalize( delta );
		if ( DotProduct( delta, pState->forward ) >= er )
			continue;

		pData[nGlintCount].m_vecPosition[0] = pState->peyeball->radius * DotProduct( vright, reflection );
		pData[nGlintCount].m_vecPosition[1] = pState->peyeball->radius * DotProduct( vup, reflection );
		pData[nGlintCount].m_vecIntensity = intensity;
		if ( ++nGlintCount >= nMaxGlints )
			return nMaxGlints;
	}
	return nGlintCount;
}


//-----------------------------------------------------------------------------
// Renders a glint texture procedurally
//-----------------------------------------------------------------------------
ITexture* CStudioRender::RenderGlintTexture( const eyeballstate_t *pState,
	const Vector& vright, const Vector& vup, const Vector& r_origin )
{
	GlintRenderData_t pRenderData[16];
	int nGlintCount = BuildGlintRenderData( pRenderData, ARRAYSIZE(pRenderData),
		pState, vright, vup, r_origin );

	if ( nGlintCount == 0 )
		return m_pGlintLODTexture;

	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	pRenderContext->PushRenderTargetAndViewport( m_pGlintTexture );

	IMaterial *pPrevMaterial = pRenderContext->GetCurrentMaterial();
	void *pPrevProxy = pRenderContext->GetCurrentProxy();
	int nPrevBoneCount = pRenderContext->GetCurrentNumBones();
	MaterialHeightClipMode_t nPrevClipMode = pRenderContext->GetHeightClipMode( );
	bool bPrevClippingEnabled = pRenderContext->EnableClipping( false );
	bool bInFlashlightMode = pRenderContext->GetFlashlightMode();

	if ( bInFlashlightMode )
	{
		DisableScissor();
	}
	pRenderContext->ClearColor4ub( 0, 0, 0, 0 );
	pRenderContext->ClearBuffers( true, false, false );

	pRenderContext->SetFlashlightMode( false );
	pRenderContext->SetHeightClipMode( MATERIAL_HEIGHTCLIPMODE_DISABLE );
	pRenderContext->SetNumBoneWeights( 0 );
	pRenderContext->Bind( m_pGlintBuildMaterial );
	
	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->PushMatrix();
	pRenderContext->LoadIdentity();

	CMeshBuilder meshBuilder;
	IMesh *pMesh = pRenderContext->GetDynamicMesh( );
	meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, nGlintCount * 4, nGlintCount * 6 );

	const float epsilon = 0.5f / 32.0f;
	int nIndex = 0;
	for ( int i = 0; i < nGlintCount; ++i )
	{
		const GlintRenderData_t &glint = pRenderData[i];

		// Position of glint 0..31 range
		float x = (glint.m_vecPosition.x + 0.5f) * m_GlintWidth;
		float y = (glint.m_vecPosition.y + 0.5f) * m_GlintHeight;
		Vector vGlintCenter = Vector( x, y, 0.0f );
		float ooWidth  = 1.0f / (float)m_GlintWidth;
		float ooHeight = 1.0f / (float)m_GlintHeight;

		int x0 = floor(x);
		int y0 = floor(y);
		int x1 = x0 + 1.0f;
		int y1 = y0 + 1.0f;
		x0 -= 2.0f;				// Fill rules make us pad this out more than the procedural version
		y0 -= 2.0f;

		float screenX0 =   x0 * 2 * ooWidth  + epsilon - 1;
		float screenX1 =   x1 * 2 * ooWidth  + epsilon - 1;
		float screenY0 = -(y0 * 2 * ooHeight + epsilon - 1);
		float screenY1 = -(y1 * 2 * ooHeight + epsilon - 1);

		meshBuilder.Position3f( screenX0, screenY0, 0.0f );
		meshBuilder.TexCoord2f(  0, x0, y0 );
		meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() );
		meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( screenX1, screenY0, 0.0f );
		meshBuilder.TexCoord2f(  0, x1, y0 );
		meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() );
		meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( screenX1, screenY1, 0.0f );
		meshBuilder.TexCoord2f(  0, x1, y1 ); 
		meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() );
		meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() );
		meshBuilder.AdvanceVertex();

		meshBuilder.Position3f( screenX0, screenY1, 0.0f );
		meshBuilder.TexCoord2f(  0, x0, y1 ); 
		meshBuilder.TexCoord2fv( 1, vGlintCenter.Base() );
		meshBuilder.TexCoord3fv( 2, glint.m_vecIntensity.Base() );
		meshBuilder.AdvanceVertex();

		meshBuilder.FastIndex( nIndex );
		meshBuilder.FastIndex( nIndex+1 );
		meshBuilder.FastIndex( nIndex+2 );
		meshBuilder.FastIndex( nIndex );
		meshBuilder.FastIndex( nIndex+2 );
		meshBuilder.FastIndex( nIndex+3 );
		nIndex += 4;
	}

	meshBuilder.End();
	pMesh->Draw();

	pRenderContext->MatrixMode( MATERIAL_MODEL );
	pRenderContext->PopMatrix();

	pRenderContext->MatrixMode( MATERIAL_VIEW );
	pRenderContext->PopMatrix();

	pRenderContext->MatrixMode( MATERIAL_PROJECTION );
	pRenderContext->PopMatrix();

	if ( IsX360() )
	{
		pRenderContext->CopyRenderTargetToTextureEx( m_pGlintTexture, 0, NULL, NULL );
	}

	pRenderContext->PopRenderTargetAndViewport( );

	pRenderContext->Bind( pPrevMaterial, pPrevProxy );
	pRenderContext->SetNumBoneWeights( nPrevBoneCount );
	pRenderContext->SetHeightClipMode( nPrevClipMode );
	pRenderContext->EnableClipping( bPrevClippingEnabled );
	pRenderContext->SetFlashlightMode( bInFlashlightMode );

	return m_pGlintTexture;
}

static ConVar r_glint_procedural( "r_glint_procedural", "0" );
static ConVar r_glint_alwaysdraw( "r_glint_alwaysdraw", "0" );

void CStudioRender::R_StudioEyeballGlint( const eyeballstate_t *pstate, IMaterialVar *pGlintVar, 
							const Vector& vright, const Vector& vup, const Vector& r_origin )
{
	// Kick off a PIX event, since this process encompasses a bunch of locks etc...
	CMatRenderContextPtr pRenderContext( g_pMaterialSystem );
	PIXEVENT( pRenderContext, "GenerateEyeballGlint" );

	// Don't do a procedural glint texture if there are enough pixels covered by the eyeball onscreen,
	// and the eye isn't backfaced.
	if ( m_pGlintLODTexture && r_glint_alwaysdraw.GetInt() == 0 )
	{
		// backfaced or too small to bother?
		float pixelArea = pRenderContext->ComputePixelWidthOfSphere( pstate->org, pstate->peyeball->radius );
		if( 
			// FIXME: this backface doesn't work for something that isn't a plane.
			 // DotProduct( pstate->forward, m_ViewPlaneNormal ) > 0.0f ||
			 pixelArea < m_pRC->m_Config.fEyeGlintPixelWidthLODThreshold )
		{
			// use black glint texture
			pGlintVar->SetTextureValue( m_pGlintLODTexture );
			return;
		}
	}

	// Legacy method for DX8
	if ( !IsX360() && ( r_glint_procedural.GetInt() || g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 90 ) )
	{
		// Set up the texture regenerator
		s_GlintTextureRegen.m_pVRight = &vright;
		s_GlintTextureRegen.m_pVUp = &vup;
		s_GlintTextureRegen.m_pROrigin = &r_origin;
		s_GlintTextureRegen.m_pState = pstate;
		s_GlintTextureRegen.m_pStudioRender = this;

		// This will cause the glint texture to be re-generated and then downloaded
		s_pProcGlint->Download( );

		// This is necessary to make sure we don't reconstitute the bits
		// after coming back from a task switch
		s_GlintTextureRegen.m_pStudioRender = NULL;

		// Use the normal glint instead of the black glint
		pGlintVar->SetTextureValue( s_pProcGlint );
	}
	else	// Queued hardware version
	{
		// Make sure we know the correct size of the glint texture
		m_GlintWidth = m_pGlintTexture->GetActualWidth();
		m_GlintHeight = m_pGlintTexture->GetActualHeight();

		// Render glint render target
		ITexture *pUseGlintTexture = RenderGlintTexture( pstate, vright, vup, r_origin );

		// Use the normal glint instead of the black glint
		pGlintVar->SetTextureValue( pUseGlintTexture );
	}
}

void CStudioRender::ComputeGlintTextureProjection( eyeballstate_t const* pState, 
							const Vector& vright, const Vector& vup, matrix3x4_t& mat )
{
	// project eyeball into screenspace texture
	float scale = 1.0 / (pState->peyeball->radius * 2);
	VectorScale( &vright.x, scale, mat[0] );
	VectorScale( &vup.x, scale, mat[1] );

	mat[0][3] = -DotProduct( pState->org.Base(), mat[0] ) + 0.5;
	mat[1][3] = -DotProduct( pState->org.Base(), mat[1] ) + 0.5;
}


/*
void R_MouthLighting( int count, const Vector *psrcverts, const Vector *psrcnorms, Vector4D *pdestlightvalues )
{
	Vector forward;

	if (m_pStudioHdr->nummouths < 1) return;

	mstudiomouth_t *pMouth = r_pstudiohdr->pMouth( 0 ); // FIXME: this needs to get the mouth index from the shader

	float fIllum = m_FlexWeights[pMouth->flexdesc];
	if (fIllum < 0) fIllum = 0;
	if (fIllum > 1) fIllum = 1;
	fIllum = LinearToTexture( fIllum ) / 255.0;


	VectorRotate( pMouth->forward, g_StudioInternalState.boneToWorld[ pMouth->bone ], forward );
	
	for (int i = 0; i < count; i++)
	{
		float dot = -DotProduct( psrcnorms[i], forward );
		if (dot > 0)
		{
			dot = LinearToTexture( dot ) / 255.0; // FIXME: this isn't robust
			VectorScale( pdestlightvalues[i], dot, pdestlightvalues[i] );
		}
		else
			VectorFill( pdestlightvalues[i], 0 );

		VectorScale( pdestlightvalues[i], fIllum, pdestlightvalues[i] );
	}
}
*/

void CStudioRender::R_MouthComputeLightingValues( float& fIllum, Vector& forward )
{
	// FIXME: this needs to get the mouth index from the shader
	mstudiomouth_t *pMouth = m_pStudioHdr->pMouth( 0 ); 

	fIllum = m_pFlexWeights[pMouth->flexdesc];
	if (fIllum < 0) fIllum = 0;
	if (fIllum > 1) fIllum = 1;
	fIllum = LinearToTexture( fIllum ) / 255.0;

	VectorRotate( pMouth->forward, m_pBoneToWorld[ pMouth->bone ], forward );
}

void CStudioRender::R_MouthLighting( float fIllum, const Vector& normal, const Vector& forward, Vector &light )
{
	float dot = -DotProduct( normal, forward );
	if (dot > 0)
	{
		VectorScale( light, dot * fIllum, light );
	}
	else
	{
		VectorFill( light, 0 );
	}
}

static unsigned int illumVarCache = 0;
static unsigned int forwardVarCache = 0;
void CStudioRender::R_MouthSetupVertexShader( IMaterial* pMaterial )
{
	if (!pMaterial)
		return;

	// FIXME: this needs to get the mouth index from the shader
	mstudiomouth_t *pMouth = m_pStudioHdr->pMouth( 0 ); 

	// Don't deal with illum gamma, we apply it at a different point
	// for vertex shaders
	float fIllum = m_pFlexWeights[pMouth->flexdesc];
	if (fIllum < 0) fIllum = 0;
	if (fIllum > 1) fIllum = 1;

	Vector forward;
	VectorRotate( pMouth->forward, m_pBoneToWorld[ pMouth->bone ], forward );
	forward *= -1;

	IMaterialVar* pIllumVar = pMaterial->FindVarFast( "$illumfactor", &illumVarCache );
	if (pIllumVar)
	{
		pIllumVar->SetFloatValue( fIllum );
	}

	IMaterialVar* pFowardVar = pMaterial->FindVarFast( "$forward", &forwardVarCache );
	if (pFowardVar)
	{
		pFowardVar->SetVecValue( forward.Base(), 3 );
	}
}