summaryrefslogtreecommitdiff
path: root/studiorender/r_studioflex.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /studiorender/r_studioflex.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'studiorender/r_studioflex.cpp')
-rw-r--r--studiorender/r_studioflex.cpp928
1 files changed, 928 insertions, 0 deletions
diff --git a/studiorender/r_studioflex.cpp b/studiorender/r_studioflex.cpp
new file mode 100644
index 0000000..0aea6b1
--- /dev/null
+++ b/studiorender/r_studioflex.cpp
@@ -0,0 +1,928 @@
+//========= 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 );
+ }
+}