diff options
Diffstat (limited to 'studiorender/r_studioflex.cpp')
| -rw-r--r-- | studiorender/r_studioflex.cpp | 928 |
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 ); + } +} |