diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /studiorender/r_studiodecal.cpp | |
| download | archived-source-engine-2018-hl2-src-master.tar.xz archived-source-engine-2018-hl2-src-master.zip | |
Diffstat (limited to 'studiorender/r_studiodecal.cpp')
| -rw-r--r-- | studiorender/r_studiodecal.cpp | 1990 |
1 files changed, 1990 insertions, 0 deletions
diff --git a/studiorender/r_studiodecal.cpp b/studiorender/r_studiodecal.cpp new file mode 100644 index 0000000..52ff350 --- /dev/null +++ b/studiorender/r_studiodecal.cpp @@ -0,0 +1,1990 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: +// +//===========================================================================// + +#include "studiorender.h" +#include "studiorendercontext.h" +#include "materialsystem/imaterialsystem.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/imesh.h" +#include "materialsystem/imaterial.h" +#include "mathlib/mathlib.h" +#include "optimize.h" +#include "cmodel.h" +#include "materialsystem/imaterialvar.h" +#include "convar.h" + +#include "tier0/vprof.h" +#include "tier0/minidump.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + + +static int g_nTotalDecalVerts; + +//----------------------------------------------------------------------------- +// Decal triangle clip flags +//----------------------------------------------------------------------------- +enum +{ + DECAL_CLIP_MINUSU = 0x1, + DECAL_CLIP_MINUSV = 0x2, + DECAL_CLIP_PLUSU = 0x4, + DECAL_CLIP_PLUSV = 0x8, +}; + + +#define MAX_DECAL_INDICES_PER_MODEL 2048 + + +//----------------------------------------------------------------------------- +// Triangle clipping state +//----------------------------------------------------------------------------- +struct DecalClipState_t +{ + // Number of used vertices + int m_VertCount; + + // Indices into the clip verts array of the used vertices + int m_Indices[2][7]; + + // Helps us avoid copying the m_Indices array by using double-buffering + bool m_Pass; + + // Add vertices we've started with and had to generate due to clipping + int m_ClipVertCount; + DecalVertex_t m_ClipVerts[16]; + + // Union of the decal triangle clip flags above for each vert + int m_ClipFlags[16]; + + DecalClipState_t() {} + +private: + // Copy constructors are not allowed + DecalClipState_t( const DecalClipState_t& src ); +}; + + +//----------------------------------------------------------------------------- +// +// Lovely decal code begins here... ABANDON ALL HOPE YE WHO ENTER!!! +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Functions to make vertex opaque +//----------------------------------------------------------------------------- + +#ifdef COMPACT_DECAL_VERT +#define GetVecTexCoord( v ) (v.operator Vector2D()) +#define GetVecNormal( v ) (v.operator Vector()) +#else +#define GetVecTexCoord( v ) v +#define GetVecNormal( v ) v +#endif + + +//----------------------------------------------------------------------------- +// Remove decal from LRU +//----------------------------------------------------------------------------- +void CStudioRender::RemoveDecalListFromLRU( StudioDecalHandle_t h ) +{ + DecalLRUListIndex_t i, next; + for ( i = m_DecalLRU.Head(); i != m_DecalLRU.InvalidIndex(); i = next ) + { + next = m_DecalLRU.Next(i); + if ( m_DecalLRU[i].m_hDecalHandle == h ) + { + m_DecalLRU.Remove( i ); + } + } +} + + +//----------------------------------------------------------------------------- +// Create, destroy list of decals for a particular model +//----------------------------------------------------------------------------- +StudioDecalHandle_t CStudioRender::CreateDecalList( studiohwdata_t *pHardwareData ) +{ + if ( !pHardwareData || pHardwareData->m_NumLODs <= 0 ) + return STUDIORENDER_DECAL_INVALID; + + // NOTE: This function is called directly without queueing + m_DecalMutex.Lock(); + int handle = m_DecalList.AddToTail(); + m_DecalMutex.Unlock(); + + m_DecalList[handle].m_pHardwareData = pHardwareData; + m_DecalList[handle].m_pLod = new DecalLod_t[pHardwareData->m_NumLODs]; + m_DecalList[handle].m_nLods = pHardwareData->m_NumLODs; + + for (int i = 0; i < pHardwareData->m_NumLODs; i++) + { + m_DecalList[handle].m_pLod[i].m_FirstMaterial = m_DecalMaterial.InvalidIndex(); + } + + return (StudioDecalHandle_t)handle; +} + +void CStudioRender::DestroyDecalList( StudioDecalHandle_t hDecal ) +{ + if ( hDecal == STUDIORENDER_DECAL_INVALID ) + return; + + RemoveDecalListFromLRU( hDecal ); + + int h = (int)hDecal; + // Clean up + for (int i = 0; i < m_DecalList[h].m_nLods; i++ ) + { + // Blat out all geometry associated with all materials + unsigned short mat = m_DecalList[h].m_pLod[i].m_FirstMaterial; + unsigned short next; + while (mat != m_DecalMaterial.InvalidIndex()) + { + next = m_DecalMaterial.Next(mat); + + g_nTotalDecalVerts -= m_DecalMaterial[mat].m_Vertices.Count(); + + m_DecalMaterial.Free(mat); + + mat = next; + } + } + + delete[] m_DecalList[h].m_pLod; + m_DecalList[h].m_pLod = NULL; + + m_DecalMutex.Lock(); + m_DecalList.Remove( h ); + m_DecalMutex.Unlock(); +} + + +//----------------------------------------------------------------------------- +// Transformation/Rotation for decals +//----------------------------------------------------------------------------- +#define FRONTFACING_EPS 0.1f + +inline bool CStudioRender::IsFrontFacing( const Vector * pnorm, const mstudioboneweight_t * pboneweight ) +{ + // NOTE: This only works to rotate normals if there's no scale in the + // pose to world transforms. If we ever add scale, we'll need to + // multiply by the inverse transpose of the pose to decal + + float z; + if (pboneweight->numbones == 1) + { + z = DotProduct( pnorm->Base(), m_PoseToDecal[(unsigned)pboneweight->bone[0]][2] ); + } + else + { + float zbone; + + z = 0; + for (int i = 0; i < pboneweight->numbones; i++) + { + zbone = DotProduct( pnorm->Base(), m_PoseToDecal[(unsigned)pboneweight->bone[i]][2] ); + z += zbone * pboneweight->weight[i]; + } + } + + return ( z >= FRONTFACING_EPS ); +} + +inline bool CStudioRender::TransformToDecalSpace( DecalBuildInfo_t& build, const Vector& pos, + mstudioboneweight_t *pboneweight, Vector2D& uv ) +{ + // NOTE: This only works to rotate normals if there's no scale in the + // pose to world transforms. If we ever add scale, we'll need to + // multiply by the inverse transpose of the pose to world + + if (pboneweight->numbones == 1) + { + uv.x = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[0]][0] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[0]][0][3]; + uv.y = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[0]][1] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[0]][1][3]; + } + else + { + uv.x = uv.y = 0; + float ubone, vbone; + for (int i = 0; i < pboneweight->numbones; i++) + { + ubone = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[i]][0] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[i]][0][3]; + vbone = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[i]][1] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[i]][1][3]; + + uv.x += ubone * pboneweight->weight[i]; + uv.y += vbone * pboneweight->weight[i]; + } + } + + if (!build.m_NoPokeThru) + return true; + + // No poke thru? do culling.... + float z; + if (pboneweight->numbones == 1) + { + z = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[0]][2] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[0]][2][3]; + } + else + { + z = 0; + float zbone; + for (int i = 0; i < pboneweight->numbones; i++) + { + zbone = DotProduct( pos.Base(), m_PoseToDecal[(unsigned)pboneweight->bone[i]][2] ) + + m_PoseToDecal[(unsigned)pboneweight->bone[i]][2][3]; + z += zbone * pboneweight->weight[i]; + } + } + + return (fabs(z) < build.m_Radius ); +} + + +//----------------------------------------------------------------------------- +// Projects a decal onto a mesh +//----------------------------------------------------------------------------- +bool CStudioRender::ProjectDecalOntoMesh( DecalBuildInfo_t& build, DecalBuildVertexInfo_t* pVertexInfo, mstudiomesh_t *pMesh ) +{ + float invRadius = (build.m_Radius != 0.0f) ? 1.0f / build.m_Radius : 1.0f; + + const mstudio_meshvertexdata_t *vertData = pMesh->GetVertexData( build.m_pStudioHdr ); + const thinModelVertices_t *thinVertData = NULL; + + if ( !vertData ) + { + // For most models (everything that's not got flex data), the vertex data is 'thinned' on load to save memory + thinVertData = pMesh->GetThinVertexData( build.m_pStudioHdr ); + if ( !thinVertData ) + return false; + } + + // For this to work, the plane and intercept must have been transformed + // into pose space. Also, we'll not be bothering with flexes. + for ( int j=0; j < pMesh->numvertices; ++j ) + { + mstudioboneweight_t localBoneWeights; + Vector localPosition; + Vector localNormal; + Vector * vecPosition; + Vector * vecNormal; + mstudioboneweight_t * boneWeights; + + if ( vertData ) + { + mstudiovertex_t &vert = *vertData->Vertex( j ); + vecPosition = &vert.m_vecPosition; + vecNormal = &vert.m_vecNormal; + boneWeights = &vert.m_BoneWeights; + } + else + { + thinVertData->GetMeshPosition( pMesh, j, &localPosition ); + vecPosition = &localPosition; + thinVertData->GetMeshNormal( pMesh, j, &localNormal ); + vecNormal = &localNormal; + thinVertData->GetMeshBoneWeights( pMesh, j, &localBoneWeights ); + boneWeights = &localBoneWeights; + } + + // No decal vertex yet... + pVertexInfo[j].m_VertexIndex = 0xFFFF; + pVertexInfo[j].m_UniqueID = 0xFF; + pVertexInfo[j].m_Flags = 0; + + // We need to know if the normal is pointing in the negative direction + // if so, blow off all triangles connected to that vertex. + if ( !IsFrontFacing( vecNormal, boneWeights ) ) + continue; + + pVertexInfo[j].m_Flags |= DecalBuildVertexInfo_t::FRONT_FACING; + + bool inValidArea = TransformToDecalSpace( build, *vecPosition, boneWeights, pVertexInfo[j].m_UV ); + pVertexInfo[j].m_Flags |= ( inValidArea << 1 ); + + pVertexInfo[j].m_UV *= invRadius * 0.5f; + pVertexInfo[j].m_UV[0] += 0.5f; + pVertexInfo[j].m_UV[1] += 0.5f; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Computes clip flags +//----------------------------------------------------------------------------- +inline int ComputeClipFlags( Vector2D const& uv ) +{ + // Otherwise we gotta do the test + int flags = 0; + + if (uv.x < 0.0f) + flags |= DECAL_CLIP_MINUSU; + else if (uv.x > 1.0f) + flags |= DECAL_CLIP_PLUSU; + + if (uv.y < 0.0f) + flags |= DECAL_CLIP_MINUSV; + else if (uv.y > 1.0f ) + flags |= DECAL_CLIP_PLUSV; + + return flags; +} + +inline int CStudioRender::ComputeClipFlags( DecalBuildVertexInfo_t* pVertexInfo, int i ) +{ + return ::ComputeClipFlags( pVertexInfo[i].m_UV ); +} + + +//----------------------------------------------------------------------------- +// Creates a new vertex where the edge intersects the plane +//----------------------------------------------------------------------------- +static int IntersectPlane( DecalClipState_t& state, int start, int end, + int normalInd, float val ) +{ + DecalVertex_t& startVert = state.m_ClipVerts[start]; + DecalVertex_t& endVert = state.m_ClipVerts[end]; + + Vector2D dir; + Vector2DSubtract( endVert.m_TexCoord, startVert.m_TexCoord, dir ); + Assert( dir[normalInd] != 0.0f ); + float t = (val - GetVecTexCoord( startVert.m_TexCoord )[normalInd]) / dir[normalInd]; + + // Allocate a clipped vertex + DecalVertex_t& out = state.m_ClipVerts[state.m_ClipVertCount]; + int newVert = state.m_ClipVertCount++; + + // The clipped vertex has no analogue in the original mesh + out.m_MeshVertexIndex = 0xFFFF; + out.m_Mesh = 0xFFFF; + out.m_Model = ( sizeof(out.m_Model) == 1 ) ? 0xFF : 0xFFFF; + out.m_Body = ( sizeof(out.m_Body) == 1 ) ? 0xFF : 0xFFFF; + + // Interpolate position + out.m_Position[0] = startVert.m_Position[0] * (1.0 - t) + endVert.m_Position[0] * t; + out.m_Position[1] = startVert.m_Position[1] * (1.0 - t) + endVert.m_Position[1] * t; + out.m_Position[2] = startVert.m_Position[2] * (1.0 - t) + endVert.m_Position[2] * t; + + // Interpolate normal + Vector vNormal; + // FIXME: this is a bug (it's using position data to compute interpolated normals!)... not seeing any obvious artifacts, though + vNormal[0] = startVert.m_Position[0] * (1.0 - t) + endVert.m_Position[0] * t; + vNormal[1] = startVert.m_Position[1] * (1.0 - t) + endVert.m_Position[1] * t; + vNormal[2] = startVert.m_Position[2] * (1.0 - t) + endVert.m_Position[2] * t; + VectorNormalize( vNormal ); + out.m_Normal = vNormal; + + // Interpolate texture coord + Vector2D vTexCoord; + Vector2DLerp( GetVecTexCoord( startVert.m_TexCoord ), GetVecTexCoord( endVert.m_TexCoord ), t, vTexCoord ); + out.m_TexCoord = vTexCoord; + + // Compute the clip flags baby... + state.m_ClipFlags[newVert] = ComputeClipFlags( out.m_TexCoord ); + + return newVert; +} + +//----------------------------------------------------------------------------- +// Clips a triangle against a plane, use clip flags to speed it up +//----------------------------------------------------------------------------- + +static void ClipTriangleAgainstPlane( DecalClipState_t& state, int normalInd, int flag, float val ) +{ + // FIXME: Could compute the & of all the clip flags of all the verts + // as we go through the loop to do another early out + + // Ye Olde Sutherland-Hodgman clipping algorithm + int outVertCount = 0; + int start = state.m_Indices[state.m_Pass][state.m_VertCount - 1]; + bool startInside = (state.m_ClipFlags[start] & flag) == 0; + for (int i = 0; i < state.m_VertCount; ++i) + { + int end = state.m_Indices[state.m_Pass][i]; + + bool endInside = (state.m_ClipFlags[end] & flag) == 0; + if (endInside) + { + if (!startInside) + { + int clipVert = IntersectPlane( state, start, end, normalInd, val ); + state.m_Indices[!state.m_Pass][outVertCount++] = clipVert; + } + state.m_Indices[!state.m_Pass][outVertCount++] = end; + } + else + { + if (startInside) + { + int clipVert = IntersectPlane( state, start, end, normalInd, val ); + state.m_Indices[!state.m_Pass][outVertCount++] = clipVert; + } + } + start = end; + startInside = endInside; + } + + state.m_Pass = !state.m_Pass; + state.m_VertCount = outVertCount; +} + + +//----------------------------------------------------------------------------- +// Converts a mesh index to a DecalVertex_t +//----------------------------------------------------------------------------- +void CStudioRender::ConvertMeshVertexToDecalVertex( DecalBuildInfo_t& build, + int meshIndex, DecalVertex_t& decalVertex, int nGroupIndex ) +{ + // Copy over the data; + // get the texture coords from the decal planar projection + + Assert( meshIndex < MAXSTUDIOVERTS ); + + if ( build.m_pMeshVertexData ) + { + VectorCopy( *build.m_pMeshVertexData->Position( meshIndex ), decalVertex.m_Position ); + VectorCopy( *build.m_pMeshVertexData->Normal( meshIndex ), GetVecNormal( decalVertex.m_Normal ) ); + } + else + { + // At this point in the code, we should definitely have either compressed or uncompressed vertex data + Assert( build.m_pMeshThinVertexData ); + Vector position; + Vector normal; + build.m_pMeshThinVertexData->GetMeshPosition( build.m_pMesh, meshIndex, &position ); + build.m_pMeshThinVertexData->GetMeshNormal( build.m_pMesh, meshIndex, &normal ); + VectorCopy( position, decalVertex.m_Position ); + VectorCopy( normal, GetVecNormal( decalVertex.m_Normal ) ); + } + Vector2DCopy( build.m_pVertexInfo[meshIndex].m_UV, GetVecTexCoord( decalVertex.m_TexCoord ) ); + decalVertex.m_MeshVertexIndex = meshIndex; + decalVertex.m_Mesh = build.m_Mesh; + Assert( decalVertex.m_Mesh < 100 ); + decalVertex.m_Model = build.m_Model; + decalVertex.m_Body = build.m_Body; + decalVertex.m_Group = build.m_Group; + decalVertex.m_GroupIndex = nGroupIndex; +} + + +//----------------------------------------------------------------------------- +// Adds a vertex to the list of vertices for this material +//----------------------------------------------------------------------------- +inline unsigned short CStudioRender::AddVertexToDecal( DecalBuildInfo_t& build, int nMeshIndex, int nGroupIndex ) +{ + DecalBuildVertexInfo_t* pVertexInfo = build.m_pVertexInfo; + + // If we've never seen this vertex before, we need to add a new decal vert + if ( pVertexInfo[nMeshIndex].m_UniqueID != build.m_nGlobalMeshIndex ) + { + pVertexInfo[nMeshIndex].m_UniqueID = build.m_nGlobalMeshIndex; + DecalVertexList_t& decalVertexList = build.m_pDecalMaterial->m_Vertices; + + DecalVertexList_t::IndexType_t v; + v = decalVertexList.AddToTail(); + g_nTotalDecalVerts++; + + // Copy over the data; + ConvertMeshVertexToDecalVertex( build, nMeshIndex, build.m_pDecalMaterial->m_Vertices[v], nGroupIndex ); + +#ifdef _DEBUG + // Make sure clipped vertices are in the right range... + if (build.m_UseClipVert) + { + Assert( (decalVertexList[v].m_TexCoord[0] >= -1e-3) && (decalVertexList[v].m_TexCoord[0] - 1.0f < 1e-3) ); + Assert( (decalVertexList[v].m_TexCoord[1] >= -1e-3) && (decalVertexList[v].m_TexCoord[1] - 1.0f < 1e-3) ); + } +#endif + + // Store off the index of this vertex so we can reference it again + pVertexInfo[nMeshIndex].m_VertexIndex = build.m_VertexCount; + ++build.m_VertexCount; + if (build.m_FirstVertex == decalVertexList.InvalidIndex()) + { + build.m_FirstVertex = v; + } + } + + return pVertexInfo[nMeshIndex].m_VertexIndex; +} + + +//----------------------------------------------------------------------------- +// Adds a vertex to the list of vertices for this material +//----------------------------------------------------------------------------- +inline unsigned short CStudioRender::AddVertexToDecal( DecalBuildInfo_t& build, DecalVertex_t& vert ) +{ + // This creates a unique vertex + DecalVertexList_t& decalVertexList = build.m_pDecalMaterial->m_Vertices; + + // Try to see if the clipped vertex already exists in our decal list... + // Only search for matches with verts appearing in the current decal + DecalVertexList_t::IndexType_t i; + unsigned short vertexCount = 0; + for ( i = build.m_FirstVertex; i != decalVertexList.InvalidIndex(); + i = decalVertexList.Next(i), ++vertexCount ) + { + // Only bother to check against clipped vertices + if ( decalVertexList[i].GetMesh( build.m_pStudioHdr ) ) + continue; + + // They must have the same position, and normal + // texcoord will fall right out if the positions match + Vector temp; + VectorSubtract( decalVertexList[i].m_Position, vert.m_Position, temp ); + if ( (fabs(temp[0]) > 1e-3) || (fabs(temp[1]) > 1e-3) || (fabs(temp[2]) > 1e-3) ) + continue; + + VectorSubtract( decalVertexList[i].m_Normal, vert.m_Normal, temp ); + if ( (fabs(temp[0]) > 1e-3) || (fabs(temp[1]) > 1e-3) || (fabs(temp[2]) > 1e-3) ) + continue; + + return vertexCount; + } + + // This path is the path taken by clipped vertices + Assert( (vert.m_TexCoord[0] >= -1e-3) && (vert.m_TexCoord[0] - 1.0f < 1e-3) ); + Assert( (vert.m_TexCoord[1] >= -1e-3) && (vert.m_TexCoord[1] - 1.0f < 1e-3) ); + + // Must create a new vertex... + DecalVertexList_t::IndexType_t idx = decalVertexList.AddToTail(vert); + g_nTotalDecalVerts++; + if (build.m_FirstVertex == decalVertexList.InvalidIndex()) + build.m_FirstVertex = idx; + Assert( vertexCount == build.m_VertexCount ); + return build.m_VertexCount++; +} + + +//----------------------------------------------------------------------------- +// Adds the clipped triangle to the decal +//----------------------------------------------------------------------------- +void CStudioRender::AddClippedDecalToTriangle( DecalBuildInfo_t& build, DecalClipState_t& clipState ) +{ + // FIXME: Clipped vertices will almost always be shared. We + // need a way of associating clipped vertices with edges so we can share + // the clipped vertices quickly + Assert( clipState.m_VertCount <= 7 ); + + // Yeah baby yeah!! Add this sucka + int i; + unsigned short indices[7]; + for ( i = 0; i < clipState.m_VertCount; ++i) + { + // First add the vertices + int vertIdx = clipState.m_Indices[clipState.m_Pass][i]; + if (vertIdx < 3) + { + indices[i] = AddVertexToDecal( build, clipState.m_ClipVerts[vertIdx].m_MeshVertexIndex ); + } + else + { + indices[i] = AddVertexToDecal( build, clipState.m_ClipVerts[vertIdx] ); + } + } + + // Add a trifan worth of triangles + for ( i = 1; i < clipState.m_VertCount - 1; ++i) + { + MEM_ALLOC_CREDIT(); + build.m_pDecalMaterial->m_Indices.AddToTail( indices[0] ); + build.m_pDecalMaterial->m_Indices.AddToTail( indices[i] ); + build.m_pDecalMaterial->m_Indices.AddToTail( indices[i+1] ); + } +} + + +//----------------------------------------------------------------------------- +// Clips the triangle to +/- radius +//----------------------------------------------------------------------------- +bool CStudioRender::ClipDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int *pClipFlags ) +{ + int i; + + DecalClipState_t clipState; + clipState.m_VertCount = 3; + ConvertMeshVertexToDecalVertex( build, i1, clipState.m_ClipVerts[0] ); + ConvertMeshVertexToDecalVertex( build, i2, clipState.m_ClipVerts[1] ); + ConvertMeshVertexToDecalVertex( build, i3, clipState.m_ClipVerts[2] ); + clipState.m_ClipVertCount = 3; + + for ( i = 0; i < 3; ++i) + { + clipState.m_ClipFlags[i] = pClipFlags[i]; + clipState.m_Indices[0][i] = i; + } + clipState.m_Pass = 0; + + // Clip against each plane + ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_MINUSU, 0.0f ); + if (clipState.m_VertCount < 3) + return false; + + ClipTriangleAgainstPlane( clipState, 0, DECAL_CLIP_PLUSU, 1.0f ); + if (clipState.m_VertCount < 3) + return false; + + ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_MINUSV, 0.0f ); + if (clipState.m_VertCount < 3) + return false; + + ClipTriangleAgainstPlane( clipState, 1, DECAL_CLIP_PLUSV, 1.0f ); + if (clipState.m_VertCount < 3) + return false; + + // Only add the clipped decal to the triangle if it's one bone + // otherwise just return if it was clipped + if ( build.m_UseClipVert ) + { + AddClippedDecalToTriangle( build, clipState ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Adds a decal to a triangle, but only if it should +//----------------------------------------------------------------------------- +void CStudioRender::AddTriangleToDecal( DecalBuildInfo_t& build, int i1, int i2, int i3, int gi1, int gi2, int gi3 ) +{ + DecalBuildVertexInfo_t* pVertexInfo = build.m_pVertexInfo; + + // All must be front-facing for a decal to be added + // FIXME: Could make it work if not all are front-facing, need clipping for that + int nAllFrontFacing = pVertexInfo[i1].m_Flags & pVertexInfo[i2].m_Flags & pVertexInfo[i3].m_Flags; + if ( ( nAllFrontFacing & DecalBuildVertexInfo_t::FRONT_FACING ) == 0 ) + return; + + // This is used to prevent poke through; if the points are too far away + // from the contact point, then don't add the decal + int nAllNotInValidArea = pVertexInfo[i1].m_Flags | pVertexInfo[i2].m_Flags | pVertexInfo[i3].m_Flags; + if ( ( nAllNotInValidArea & DecalBuildVertexInfo_t::VALID_AREA ) == 0 ) + return; + + // Clip to +/- radius + int clipFlags[3]; + + clipFlags[0] = ComputeClipFlags( pVertexInfo, i1 ); + clipFlags[1] = ComputeClipFlags( pVertexInfo, i2 ); + clipFlags[2] = ComputeClipFlags( pVertexInfo, i3 ); + + // Cull... The result is non-zero if they're all outside the same plane + if ( (clipFlags[0] & (clipFlags[1] & clipFlags[2]) ) != 0) + return; + + bool doClip = true; + + // Trivial accept for skinned polys... if even one vert is inside + // the draw region, accept + if ((!build.m_UseClipVert) && ( !clipFlags[0] || !clipFlags[1] || !clipFlags[2] )) + { + doClip = false; + } + + // Trivial accept... no clip flags set means all in + // Don't clip if we have more than one bone... we'll need to do skinning + // and we can't clip the bone indices + // We *do* want to clip in the one bone case though; useful for large + // static props. + if ( doClip && ( clipFlags[0] || clipFlags[1] || clipFlags[2] )) + { + bool validTri = ClipDecal( build, i1, i2, i3, clipFlags ); + + // Don't add the triangle if we culled the triangle or if + // we had one or less bones + if (build.m_UseClipVert || (!validTri)) + return; + } + + // Add the vertices to the decal since there was no clipping + i1 = AddVertexToDecal( build, i1, gi1 ); + i2 = AddVertexToDecal( build, i2, gi2 ); + i3 = AddVertexToDecal( build, i3, gi3 ); + + MEM_ALLOC_CREDIT(); + build.m_pDecalMaterial->m_Indices.AddToTail(i1); + build.m_pDecalMaterial->m_Indices.AddToTail(i2); + build.m_pDecalMaterial->m_Indices.AddToTail(i3); +} + + +//----------------------------------------------------------------------------- +// Adds a decal to a mesh +//----------------------------------------------------------------------------- +void CStudioRender::AddDecalToMesh( DecalBuildInfo_t& build ) +{ + MeshVertexInfo_t &vertexInfo = build.m_pMeshVertices[ build.m_nGlobalMeshIndex ]; + if ( vertexInfo.m_nIndex < 0 ) + return; + + build.m_pVertexInfo = &build.m_pVertexBuffer[ vertexInfo.m_nIndex ]; + + // Draw all the various mesh groups... + for ( int j = 0; j < build.m_pMeshData->m_NumGroup; ++j ) + { + build.m_Group = j; + studiomeshgroup_t* pGroup = &build.m_pMeshData->m_pMeshGroup[j]; + + // Must add decal to each strip in the strip group + // We do this so we can re-use all of the bone state change + // info associated with the strips + for (int k = 0; k < pGroup->m_NumStrips; ++k) + { + OptimizedModel::StripHeader_t* pStrip = &pGroup->m_pStripData[k]; + if (pStrip->flags & OptimizedModel::STRIP_IS_TRISTRIP) + { + for (int i = 0; i < pStrip->numIndices - 2; ++i) + { + bool ccw = (i & 0x1) == 0; + int ti1 = pStrip->indexOffset + i; + int ti2 = ti1+1+ccw; + int ti3 = ti1+2-ccw; + int i1 = pGroup->MeshIndex(ti1); + int i2 = pGroup->MeshIndex(ti2); + int i3 = pGroup->MeshIndex(ti3); + + AddTriangleToDecal( build, i1, i2, i3, pGroup->m_pIndices[ti1], pGroup->m_pIndices[ti2], pGroup->m_pIndices[ti3] ); + } + } + else + { + Assert( pStrip->flags & OptimizedModel::STRIP_IS_TRILIST ); + for (int i = 0; i < pStrip->numIndices; i += 3) + { + int idx = pStrip->indexOffset + i; + + int i1 = pGroup->MeshIndex(idx); + int i2 = pGroup->MeshIndex(idx+1); + int i3 = pGroup->MeshIndex(idx+2); + + AddTriangleToDecal( build, i1, i2, i3, pGroup->m_pIndices[idx], pGroup->m_pIndices[idx+1], pGroup->m_pIndices[idx+2] ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Adds a decal to a mesh +//----------------------------------------------------------------------------- +bool CStudioRender::AddDecalToModel( DecalBuildInfo_t& buildInfo ) +{ + // FIXME: We need to do some high-level culling to figure out exactly + // which meshes we need to add the decals to + // Turns out this solution may also be good for mesh sorting + // we need to know the center of each mesh, could also store a + // bounding radius for each mesh and test the ray against each sphere. + + for ( int i = 0; i < m_pSubModel->nummeshes; ++i) + { + buildInfo.m_Mesh = i; + buildInfo.m_pMesh = m_pSubModel->pMesh(i); + buildInfo.m_pMeshData = &m_pStudioMeshes[buildInfo.m_pMesh->meshid]; + Assert(buildInfo.m_pMeshData); + // Grab either fat or thin vertex data + buildInfo.m_pMeshVertexData = buildInfo.m_pMesh->GetVertexData( buildInfo.m_pStudioHdr ); + if ( buildInfo.m_pMeshVertexData == NULL ) + { + buildInfo.m_pMeshThinVertexData = buildInfo.m_pMesh->GetThinVertexData( buildInfo.m_pStudioHdr ); + if ( !buildInfo.m_pMeshThinVertexData ) + return false; + } + + AddDecalToMesh( buildInfo ); + ++buildInfo.m_nGlobalMeshIndex; + } + return true; +} + + +//----------------------------------------------------------------------------- +// Computes the pose to decal plane transform +//----------------------------------------------------------------------------- +bool CStudioRender::ComputePoseToDecal( const Ray_t& ray, const Vector& up ) +{ + // Create a transform that projects world coordinates into a + // basis for the decal + matrix3x4_t worldToDecal; + Vector decalU, decalV, decalN; + + // Get the z axis + VectorMultiply( ray.m_Delta, -1.0f, decalN ); + if (VectorNormalize( decalN ) == 0.0f) + return false; + + // Deal with the u axis + CrossProduct( up, decalN, decalU ); + if ( VectorNormalize( decalU ) < 1e-3 ) + { + // if up parallel or antiparallel to ray, deal... + Vector fixup( up.y, up.z, up.x ); + CrossProduct( fixup, decalN, decalU ); + if ( VectorNormalize( decalU ) < 1e-3 ) + return false; + } + + CrossProduct( decalN, decalU, decalV ); + + // Since I want world-to-decal, I gotta take the inverse of the decal + // to world. Assuming post-multiplying column vectors, the decal to world = + // [ Ux Vx Nx | ray.m_Start[0] ] + // [ Uy Vy Ny | ray.m_Start[1] ] + // [ Uz Vz Nz | ray.m_Start[2] ] + + VectorCopy( decalU.Base(), worldToDecal[0] ); + VectorCopy( decalV.Base(), worldToDecal[1] ); + VectorCopy( decalN.Base(), worldToDecal[2] ); + + worldToDecal[0][3] = -DotProduct( ray.m_Start.Base(), worldToDecal[0] ); + worldToDecal[1][3] = -DotProduct( ray.m_Start.Base(), worldToDecal[1] ); + worldToDecal[2][3] = -DotProduct( ray.m_Start.Base(), worldToDecal[2] ); + + // Compute transforms from pose space to decal plane space + for ( int i = 0; i < m_pStudioHdr->numbones; i++) + { + ConcatTransforms( worldToDecal, m_PoseToWorld[i], m_PoseToDecal[i] ); + } + + return true; +} + + +//----------------------------------------------------------------------------- +// Gets the list of triangles for a particular material and lod +//----------------------------------------------------------------------------- + +int CStudioRender::GetDecalMaterial( DecalLod_t& decalLod, IMaterial* pDecalMaterial ) +{ + // Grab the material for this lod... + unsigned short j; + for ( j = decalLod.m_FirstMaterial; j != m_DecalMaterial.InvalidIndex(); j = m_DecalMaterial.Next(j) ) + { + if (m_DecalMaterial[j].m_pMaterial == pDecalMaterial) + { + return j; + } + } + + // If we got here, this must be the first time we saw this material + j = m_DecalMaterial.Alloc( true ); + + // Link it into the list of data for this lod + if (decalLod.m_FirstMaterial != m_DecalMaterial.InvalidIndex() ) + m_DecalMaterial.LinkBefore( decalLod.m_FirstMaterial, j ); + decalLod.m_FirstMaterial = j; + + m_DecalMaterial[j].m_pMaterial = pDecalMaterial; + + return j; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CStudioRender::RetireDecal( DecalModelList_t &list, DecalId_t nRetireID, int iLOD, int iMaxLOD ) +{ + // Remove it from the global LRU... + DecalLRUListIndex_t i; + for ( i = m_DecalLRU.Head(); i != m_DecalLRU.InvalidIndex(); i = m_DecalLRU.Next( i ) ) + { + if ( nRetireID == m_DecalLRU[i].m_nDecalId ) + { + m_DecalLRU.Remove( i ); + break; + } + } + Assert( i != m_DecalLRU.InvalidIndex() ); + + // Find the id to retire and retire all the decals with this id across all LODs. + DecalHistoryList_t *pHistoryList = &list.m_pLod[iLOD].m_DecalHistory; + Assert( pHistoryList->Count() ); + if ( !pHistoryList->Count() ) + return; + + DecalHistory_t *pDecalHistory = &pHistoryList->Element( pHistoryList->Head() ); + + // Retire this decal in all lods. + for ( int iLod = ( iMaxLOD - 1 ); iLod >= list.m_pHardwareData->m_RootLOD; --iLod ) + { + pHistoryList = &list.m_pLod[iLod].m_DecalHistory; + if ( !pHistoryList ) + continue; + + unsigned short iList = pHistoryList->Head(); + unsigned short iNext = pHistoryList->InvalidIndex(); + + while ( iList != pHistoryList->InvalidIndex() ) + { + iNext = pHistoryList->Next( iList ); + + pDecalHistory = &pHistoryList->Element( iList ); + if ( !pDecalHistory || pDecalHistory->m_nId != nRetireID ) + { + iList = iNext; + continue; + } + + // Find the decal material for the decal to remove + DecalMaterial_t *pMaterial = &m_DecalMaterial[pDecalHistory->m_Material]; + if ( pMaterial ) + { + // @Note!! Decals must be removed in the reverse order they are added. This code + // assumes that the decal to remove is the oldest one on the model, and therefore + // its vertices start at the head of the list + DecalVertexList_t &vertices = pMaterial->m_Vertices; + Decal_t &decalToRemove = pMaterial->m_Decals[pDecalHistory->m_Decal]; + + // Now clear out the vertices referenced by the indices.... + DecalVertexList_t::IndexType_t next; + DecalVertexList_t::IndexType_t vert = vertices.Head(); + Assert( vertices.Count() >= decalToRemove.m_VertexCount ); + int vertsToRemove = decalToRemove.m_VertexCount; + while ( vertsToRemove > 0 ) + { + // blat out the vertices + next = vertices.Next( vert ); + vertices.Remove( vert ); + vert = next; + g_nTotalDecalVerts--; + + --vertsToRemove; + } + + if ( vertices.Count() == 0 ) + { + vertices.Purge(); + } + + // FIXME: This does a memmove. How expensive is it? + pMaterial->m_Indices.RemoveMultiple( 0, decalToRemove.m_IndexCount ); + if ( pMaterial->m_Indices.Count() == 0) + { + pMaterial->m_Indices.Purge(); + } + + // Remove the decal + pMaterial->m_Decals.Remove( pDecalHistory->m_Decal ); + if ( pMaterial->m_Decals.Count() == 0) + { +#if 1 + pMaterial->m_Decals.Purge(); +#else + if ( list.m_pLod[iLOD].m_FirstMaterial == pDecalHistory->m_Material ) + { + list.m_pLod[iLOD].m_FirstMaterial = m_DecalMaterial.Next( pDecalHistory->m_Material ); + } + m_DecalMaterial.Free( pDecalHistory->m_Material ); +#endif + } + } + + // Clear the decal out of the history + pHistoryList->Remove( iList ); + + // Next element. + iList = iNext; + } + } +} + +//----------------------------------------------------------------------------- +// Adds a decal to the history list +//----------------------------------------------------------------------------- +int CStudioRender::AddDecalToMaterialList( DecalMaterial_t* pMaterial ) +{ + DecalList_t& decalList = pMaterial->m_Decals; + return decalList.AddToTail(); +} + + +//----------------------------------------------------------------------------- +// Total number of meshes we have to deal with +//----------------------------------------------------------------------------- +int CStudioRender::ComputeTotalMeshCount( int iRootLOD, int iMaxLOD, int body ) const +{ + int nMeshCount = 0; + for ( int k=0 ; k < m_pStudioHdr->numbodyparts ; k++) + { + mstudiomodel_t *pSubModel; + R_StudioSetupModel( k, body, &pSubModel, m_pStudioHdr ); + nMeshCount += pSubModel->nummeshes; + } + + nMeshCount *= iMaxLOD-iRootLOD+1; + + return nMeshCount; +} + + +//----------------------------------------------------------------------------- +// Set up the locations for vertices to use +//----------------------------------------------------------------------------- +int CStudioRender::ComputeVertexAllocation( int iMaxLOD, int body, studiohwdata_t *pHardwareData, MeshVertexInfo_t *pMeshVertices ) +{ + bool bSuppressTlucDecal = (m_pStudioHdr->flags & STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS) != 0; + + int nCurrMesh = 0; + int nVertexCount = 0; + for ( int i = iMaxLOD-1; i >= pHardwareData->m_RootLOD; i--) + { + IMaterial **ppMaterials = pHardwareData->m_pLODs[i].ppMaterials; + + for ( int k=0 ; k < m_pStudioHdr->numbodyparts ; k++) + { + mstudiomodel_t *pSubModel; + R_StudioSetupModel( k, body, &pSubModel, m_pStudioHdr ); + + for ( int meshID = 0; meshID < pSubModel->nummeshes; ++meshID, ++nCurrMesh) + { + mstudiomesh_t *pMesh = pSubModel->pMesh(meshID); + + pMeshVertices[nCurrMesh].m_pMesh = pMesh; + + int n; + for ( n = nCurrMesh; --n >= 0; ) + { + if ( pMeshVertices[n].m_pMesh == pMesh ) + { + pMeshVertices[nCurrMesh].m_nIndex = pMeshVertices[n].m_nIndex; + break; + } + } + if ( n >= 0 ) + continue; + + // Don't add to the mesh if the mesh has a translucent material + short *pSkinRef = m_pStudioHdr->pSkinref( 0 ); + IMaterial *pMaterial = ppMaterials[pSkinRef[pMesh->material]]; + if (bSuppressTlucDecal) + { + if (pMaterial->IsTranslucent()) + { + pMeshVertices[nCurrMesh].m_nIndex = -1; + continue; + } + } + + if ( pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SUPPRESS_DECALS ) ) + { + pMeshVertices[nCurrMesh].m_nIndex = -1; + continue; + } + + pMeshVertices[nCurrMesh].m_nIndex = nVertexCount; + nVertexCount += pMesh->numvertices; + } + } + } + + return nVertexCount; +} + + +//----------------------------------------------------------------------------- +// Project decals onto all meshes +//----------------------------------------------------------------------------- +void CStudioRender::ProjectDecalsOntoMeshes( DecalBuildInfo_t& build, int nMeshCount ) +{ + int nMaxVertexIndex = -1; + + for ( int i = 0; i < nMeshCount; ++i ) + { + int nIndex = build.m_pMeshVertices[i].m_nIndex; + + // No mesh, or have we already projected this? + if (( nIndex < 0 ) || ( nIndex <= nMaxVertexIndex )) + continue; + + nMaxVertexIndex = nIndex; + + // Project all vertices for this group into decal space + ProjectDecalOntoMesh( build, &build.m_pVertexBuffer[ nIndex ], build.m_pMeshVertices[i].m_pMesh ); + } +} + + + +//----------------------------------------------------------------------------- +// Add decals to a decal list by doing a planar projection along the ray +//----------------------------------------------------------------------------- +void CStudioRender::AddDecal( StudioDecalHandle_t hDecal, const StudioRenderContext_t& rc, matrix3x4_t *pBoneToWorld, + studiohdr_t *pStudioHdr, const Ray_t& ray, const Vector& decalUp, IMaterial* pDecalMaterial, + float radius, int body, bool noPokethru, int maxLODToDecal ) +{ + VPROF( "CStudioRender::AddDecal" ); + + if ( hDecal == STUDIORENDER_DECAL_INVALID ) + return; + + // For each lod, build the decal list + int h = (int)hDecal; + DecalModelList_t& list = m_DecalList[h]; + + if ( list.m_pHardwareData->m_NumStudioMeshes == 0 ) + return; + + m_pRC = const_cast< StudioRenderContext_t* >( &rc ); + m_pStudioHdr = pStudioHdr; + m_pBoneToWorld = pBoneToWorld; + + // Bone to world must be set before calling AddDecal; it uses that here + // UNDONE: Use current LOD to cull matrices here? + ComputePoseToWorld( m_PoseToWorld, pStudioHdr, BONE_USED_BY_ANYTHING, m_pRC->m_ViewOrigin, m_pBoneToWorld ); + + // Compute transforms from pose space to decal plane space + if (!ComputePoseToDecal( ray, decalUp )) + { + m_pStudioHdr = NULL; + m_pRC = NULL; + m_pBoneToWorld = NULL; + return; + } + + // Get dynamic information from the material (fade start, fade time) + float fadeStartTime = 0.0f; + float fadeDuration = 0.0f; + int flags = 0; + + // This sucker is state needed only when building decals + DecalBuildInfo_t buildInfo; + buildInfo.m_Radius = radius; + buildInfo.m_NoPokeThru = noPokethru; + buildInfo.m_pStudioHdr = pStudioHdr; + buildInfo.m_UseClipVert = ( m_pStudioHdr->numbones <= 1 ) && ( m_pStudioHdr->numflexdesc == 0 ); + buildInfo.m_nGlobalMeshIndex = 0; + buildInfo.m_pMeshVertexData = NULL; + + // Find out which LODs we're defacing + int iMaxLOD; + if ( maxLODToDecal == ADDDECAL_TO_ALL_LODS ) + { + iMaxLOD = list.m_pHardwareData->m_NumLODs; + } + else + { + iMaxLOD = min( list.m_pHardwareData->m_NumLODs, maxLODToDecal ); + } + + // Allocate space for all projected mesh vertices. We do this to prevent + // re-projection of the same meshes when they appear in multiple LODs + int nMeshCount = ComputeTotalMeshCount( list.m_pHardwareData->m_RootLOD, iMaxLOD-1, body ); + + // NOTE: This is a consequence of the sizeof (m_UniqueID) + if ( nMeshCount >= 255 ) + { + Warning("Unable to apply decals to model (%s), it has more than 255 unique meshes!\n", m_pStudioHdr->pszName() ); + m_pStudioHdr = NULL; + m_pRC = NULL; + m_pBoneToWorld = NULL; + return; + } + + if ( !IsX360() ) + { + buildInfo.m_pMeshVertices = (MeshVertexInfo_t*)stackalloc( nMeshCount * sizeof(MeshVertexInfo_t) ); + int nVertexCount = ComputeVertexAllocation( iMaxLOD, body, list.m_pHardwareData, buildInfo.m_pMeshVertices ); + buildInfo.m_pVertexBuffer = (DecalBuildVertexInfo_t*)stackalloc( nVertexCount * sizeof(DecalBuildVertexInfo_t) ); + } + else + { + // Don't allocate on the stack + buildInfo.m_pMeshVertices = (MeshVertexInfo_t*)malloc( nMeshCount * sizeof(MeshVertexInfo_t) ); + int nVertexCount = ComputeVertexAllocation( iMaxLOD, body, list.m_pHardwareData, buildInfo.m_pMeshVertices ); + buildInfo.m_pVertexBuffer = (DecalBuildVertexInfo_t*)malloc( nVertexCount * sizeof(DecalBuildVertexInfo_t) ); + } + + // Project all mesh vertices + ProjectDecalsOntoMeshes( buildInfo, nMeshCount ); + + if ( IsX360() ) + { + while ( g_nTotalDecalVerts * sizeof(DecalVertex_t) > 256*1024 && m_DecalLRU.Head() != m_DecalLRU.InvalidIndex() ) + { + DecalId_t nRetireID = m_DecalLRU[ m_DecalLRU.Head() ].m_nDecalId; + StudioDecalHandle_t hRetire = m_DecalLRU[ m_DecalLRU.Head() ].m_hDecalHandle; + DecalModelList_t &modelList = m_DecalList[(int)hRetire]; + RetireDecal( modelList, nRetireID, modelList.m_pHardwareData->m_RootLOD, modelList.m_pHardwareData->m_NumLODs ); + } + } + + // Check to see if we have too many decals on this model + // This assumes that every decal is applied to the root lod at least + int nRootLOD = list.m_pHardwareData->m_RootLOD; + int nFinalLOD = list.m_pHardwareData->m_NumLODs; + DecalHistoryList_t *pHistoryList = &list.m_pLod[list.m_pHardwareData->m_RootLOD].m_DecalHistory; + if ( m_DecalLRU.Count() >= m_pRC->m_Config.maxDecalsPerModel * 1.5 ) + { + DecalId_t nRetireID = m_DecalLRU[ m_DecalLRU.Head() ].m_nDecalId; + StudioDecalHandle_t hRetire = m_DecalLRU[ m_DecalLRU.Head() ].m_hDecalHandle; + DecalModelList_t &modelList = m_DecalList[(int)hRetire]; + RetireDecal( modelList, nRetireID, modelList.m_pHardwareData->m_RootLOD, modelList.m_pHardwareData->m_NumLODs ); + } + + if ( pHistoryList->Count() >= m_pRC->m_Config.maxDecalsPerModel ) + { + DecalHistory_t *pDecalHistory = &pHistoryList->Element( pHistoryList->Head() ); + DecalId_t nRetireID = pDecalHistory->m_nId; + StudioDecalHandle_t hRetire = hDecal; + RetireDecal( m_DecalList[(int)hRetire], nRetireID, nRootLOD, nFinalLOD ); + } + + // Search all LODs for an overflow condition and retire those also + for ( int i = iMaxLOD-1; i >= list.m_pHardwareData->m_RootLOD; i-- ) + { + // Grab the list of all decals using the same material for this lod... + int materialIdx = GetDecalMaterial( list.m_pLod[i], pDecalMaterial ); + + // Check to see if we should retire the decal + DecalMaterial_t *pDecalMaterial = &m_DecalMaterial[materialIdx]; + while ( pDecalMaterial->m_Indices.Count() > MAX_DECAL_INDICES_PER_MODEL ) + { + DecalHistoryList_t *pHistoryList = &list.m_pLod[i].m_DecalHistory; + DecalHistory_t *pDecalHistory = &pHistoryList->Element( pHistoryList->Head() ); + RetireDecal( list, pDecalHistory->m_nId, nRootLOD, nFinalLOD ); + } + } + + // Gotta do this for all LODs + bool bAddedDecals = false; + for ( int i = iMaxLOD-1; i >= list.m_pHardwareData->m_RootLOD; i-- ) + { + // Grab the list of all decals using the same material for this lod... + int materialIdx = GetDecalMaterial( list.m_pLod[i], pDecalMaterial ); + buildInfo.m_pDecalMaterial = &m_DecalMaterial[materialIdx]; + + // Grab the meshes for this lod + m_pStudioMeshes = list.m_pHardwareData->m_pLODs[i].m_pMeshData; + + // Don't decal on meshes that are translucent if it's twopass + buildInfo.m_ppMaterials = list.m_pHardwareData->m_pLODs[i].ppMaterials; + + // Set up info needed for vertex sharing + buildInfo.m_FirstVertex = buildInfo.m_pDecalMaterial->m_Vertices.InvalidIndex(); + buildInfo.m_VertexCount = 0; + + int prevIndexCount = buildInfo.m_pDecalMaterial->m_Indices.Count(); + + // Step over all body parts + add decals to em all! + int k; + for ( k=0 ; k < m_pStudioHdr->numbodyparts ; k++) + { + // Grab the model for this body part + int model = R_StudioSetupModel( k, body, &m_pSubModel, m_pStudioHdr ); + buildInfo.m_Body = k; + buildInfo.m_Model = model; + if ( !AddDecalToModel( buildInfo ) ) + break; + } + + if ( k != m_pStudioHdr->numbodyparts ) + continue; + + // Add this to the list of decals in this material + if ( buildInfo.m_VertexCount ) + { + bAddedDecals = true; + + int decalIndexCount = buildInfo.m_pDecalMaterial->m_Indices.Count() - prevIndexCount; + Assert(decalIndexCount > 0); + + int decalIndex = AddDecalToMaterialList( buildInfo.m_pDecalMaterial ); + Decal_t& decal = buildInfo.m_pDecalMaterial->m_Decals[decalIndex]; + decal.m_VertexCount = buildInfo.m_VertexCount; + decal.m_IndexCount = decalIndexCount; + decal.m_FadeStartTime = fadeStartTime; + decal.m_FadeDuration = fadeDuration; + decal.m_Flags = flags; + + // Add this decal to the history... + int h = list.m_pLod[i].m_DecalHistory.AddToTail(); + list.m_pLod[i].m_DecalHistory[h].m_Material = materialIdx; + list.m_pLod[i].m_DecalHistory[h].m_Decal = decalIndex; + list.m_pLod[i].m_DecalHistory[h].m_nId = m_nDecalId; + list.m_pLod[i].m_DecalHistory[h].m_nPad = 0; + } + } + + // Add to LRU + if ( bAddedDecals ) + { + DecalLRUListIndex_t h = m_DecalLRU.AddToTail(); + m_DecalLRU[h].m_nDecalId = m_nDecalId; + m_DecalLRU[h].m_hDecalHandle = hDecal; + + // Increment count. + ++m_nDecalId; + } + + if ( IsX360() ) + { + free( buildInfo.m_pMeshVertices ); + free( buildInfo.m_pVertexBuffer ); + } + + m_pStudioHdr = NULL; + m_pRC = NULL; + m_pBoneToWorld = NULL; +} + + +//----------------------------------------------------------------------------- +// +// This code here is all about rendering the decals +// +//----------------------------------------------------------------------------- + +//----------------------------------------------------------------------------- +// Inner loop for rendering decals that have a single bone +//----------------------------------------------------------------------------- + +void CStudioRender::DrawSingleBoneDecals( CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial ) +{ + // We don't got no bones, so yummy yummy yum, just copy the data out + // Static props should go though this code path + + DecalVertexList_t& verts = decalMaterial.m_Vertices; + for ( DecalVertexList_t::IndexLocalType_t i = verts.Head(); i != verts.InvalidIndex(); i = verts.Next(i) ) + { + DecalVertex_t& vertex = verts[i]; + + meshBuilder.Position3fv( vertex.m_Position.Base() ); + meshBuilder.Normal3fv( GetVecNormal( vertex.m_Normal ).Base() ); +#if 0 + if ( decalMaterial.m_pMaterial->InMaterialPage() ) + { + float offset[2], scale[2]; + decalMaterial.m_pMaterial->GetMaterialOffset( offset ); + decalMaterial.m_pMaterial->GetMaterialScale( scale ); + + Vector2D vecTexCoord( vertex.m_TexCoord.x, vertex.m_TexCoord.y ); + vecTexCoord.x = clamp( vecTexCoord.x, 0.0f, 1.0f ); + vecTexCoord.y = clamp( vecTexCoord.y, 0.0f, 1.0f ); + meshBuilder.TexCoordSubRect2f( 0, vecTexCoord.x, vecTexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + +// meshBuilder.TexCoordSubRect2f( 0, vertex.m_TexCoord.x, vertex.m_TexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + } + else +#endif + { + meshBuilder.TexCoord2fv( 0, GetVecTexCoord(vertex.m_TexCoord).Base() ); + } + meshBuilder.Color4ub( 255, 255, 255, 255 ); + + if ( meshBuilder.NumBoneWeights() > 0 ) // bone weight of 0 will not write anything, so these calls would be wasted + { + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneWeight( 1, 0.0f ); + meshBuilder.BoneWeight( 2, 0.0f ); + meshBuilder.BoneWeight( 3, 0.0f ); + } + + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.BoneMatrix( 1, 0 ); + meshBuilder.BoneMatrix( 2, 0 ); + meshBuilder.BoneMatrix( 3, 0 ); + + meshBuilder.AdvanceVertex(); + } +} + +void CStudioRender::DrawSingleBoneFlexedDecals( IMatRenderContext *pRenderContext, CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial ) +{ + // We don't got no bones, so yummy yummy yum, just copy the data out + // Static props should go though this code path + DecalVertexList_t& verts = decalMaterial.m_Vertices; + for ( DecalVertexList_t::IndexLocalType_t i = verts.Head(); i != verts.InvalidIndex(); i = verts.Next(i) ) + { + DecalVertex_t& vertex = verts[i]; + + // Clipped verts shouldn't come through here, only static props should use clipped + Assert ( vertex.m_MeshVertexIndex >= 0 ); + + m_VertexCache.SetBodyModelMesh( vertex.m_Body, vertex.m_Model, vertex.m_Mesh ); + if (m_VertexCache.IsVertexFlexed( vertex.m_MeshVertexIndex )) + { + CachedPosNormTan_t* pFlexedVertex = m_VertexCache.GetFlexVertex( vertex.m_MeshVertexIndex ); + meshBuilder.Position3fv( pFlexedVertex->m_Position.Base() ); + meshBuilder.Normal3fv( pFlexedVertex->m_Normal.Base() ); + } + else + { + meshBuilder.Position3fv( vertex.m_Position.Base() ); + meshBuilder.Normal3fv( GetVecNormal( vertex.m_Normal ).Base() ); + } + +#if 0 + if ( decalMaterial.m_pMaterial->InMaterialPage() ) + { + float offset[2], scale[2]; + decalMaterial.m_pMaterial->GetMaterialOffset( offset ); + decalMaterial.m_pMaterial->GetMaterialScale( scale ); + + Vector2D vecTexCoord( vertex.m_TexCoord.x, vertex.m_TexCoord.y ); + vecTexCoord.x = clamp( vecTexCoord.x, 0.0f, 1.0f ); + vecTexCoord.y = clamp( vecTexCoord.y, 0.0f, 1.0f ); + meshBuilder.TexCoordSubRect2f( 0, vecTexCoord.x, vecTexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + +// meshBuilder.TexCoordSubRect2f( 0, vertex.m_TexCoord.x, vertex.m_TexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + } + else +#endif + { + meshBuilder.TexCoord2fv( 0, GetVecTexCoord(vertex.m_TexCoord).Base() ); + } + + meshBuilder.Color4ub( 255, 255, 255, 255 ); + + if ( meshBuilder.NumBoneWeights() > 0 ) // bone weight of 0 will not write anything, so these calls would be wasted + { + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneWeight( 1, 0.0f ); + meshBuilder.BoneWeight( 2, 0.0f ); + meshBuilder.BoneWeight( 3, 0.0f ); + } + + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.BoneMatrix( 1, 0 ); + meshBuilder.BoneMatrix( 2, 0 ); + meshBuilder.BoneMatrix( 3, 0 ); + + meshBuilder.AdvanceVertex(); + } +} + +//----------------------------------------------------------------------------- +// Inner loop for rendering decals that have multiple bones +//----------------------------------------------------------------------------- +bool CStudioRender::DrawMultiBoneDecals( CMeshBuilder& meshBuilder, DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr ) +{ + const thinModelVertices_t *thinVertData = NULL; + const mstudio_meshvertexdata_t *vertData = NULL; + mstudiomesh_t *pLastMesh = NULL; + + DecalVertexList_t& verts = decalMaterial.m_Vertices; + for ( DecalVertexList_t::IndexLocalType_t i = verts.Head(); i != verts.InvalidIndex(); i = verts.Next(i) ) + { + DecalVertex_t& vertex = verts[i]; + + int n = vertex.m_MeshVertexIndex; + + Assert( n < MAXSTUDIOVERTS ); + + mstudiomesh_t *pMesh = vertex.GetMesh( pStudioHdr ); + Assert( pMesh ); + + m_VertexCache.SetBodyModelMesh( vertex.m_Body, vertex.m_Model, vertex.m_Mesh ); + if (m_VertexCache.IsVertexPositionCached( n )) + { + CachedPosNorm_t* pCachedVert = m_VertexCache.GetWorldVertex( n ); + meshBuilder.Position3fv( pCachedVert->m_Position.Base() ); + meshBuilder.Normal3fv( pCachedVert->m_Normal.Base() ); + } + else + { + // Prevent the computation of this again.... + m_VertexCache.SetupComputation(pMesh); + CachedPosNorm_t* pCachedVert = m_VertexCache.CreateWorldVertex( n ); + + if ( pLastMesh != pMesh ) + { + // only if the mesh changes + pLastMesh = pMesh; + vertData = pMesh->GetVertexData( pStudioHdr ); + if ( vertData ) + thinVertData = NULL; + else + thinVertData = pMesh->GetThinVertexData( pStudioHdr ); + } + + if ( vertData ) + { + mstudioboneweight_t* pBoneWeights = vertData->BoneWeights( n ); + // FIXME: could be faster to blend the matrices and then transform the pos+norm by the same matrix + R_StudioTransform( *vertData->Position( n ), pBoneWeights, pCachedVert->m_Position.AsVector3D() ); + R_StudioRotate( *vertData->Normal( n ), pBoneWeights, pCachedVert->m_Normal.AsVector3D() ); + } + else if ( thinVertData ) + { + // Using compressed vertex data + mstudioboneweight_t boneWeights; + Vector position; + Vector normal; + thinVertData->GetMeshBoneWeights( pMesh, n, &boneWeights ); + thinVertData->GetMeshPosition( pMesh, n, &position ); + thinVertData->GetMeshNormal( pMesh, n, &normal ); + R_StudioTransform( position, &boneWeights, pCachedVert->m_Position.AsVector3D() ); + R_StudioRotate( normal, &boneWeights, pCachedVert->m_Normal.AsVector3D() ); + } + else + { + return false; + } + + // Add a little extra offset for hardware skinning; in that case + // we're doing software skinning for decals and it might not be quite right + VectorMA( pCachedVert->m_Position.AsVector3D(), 0.1, pCachedVert->m_Normal.AsVector3D(), pCachedVert->m_Position.AsVector3D() ); + + meshBuilder.Position3fv( pCachedVert->m_Position.Base() ); + meshBuilder.Normal3fv( pCachedVert->m_Normal.Base() ); + } + +#if 0 + if ( decalMaterial.m_pMaterial->InMaterialPage() ) + { + float offset[2], scale[2]; + decalMaterial.m_pMaterial->GetMaterialOffset( offset ); + decalMaterial.m_pMaterial->GetMaterialScale( scale ); + + Vector2D vecTexCoord( vertex.m_TexCoord.x, vertex.m_TexCoord.y ); + vecTexCoord.x = clamp( vecTexCoord.x, 0.0f, 1.0f ); + vecTexCoord.y = clamp( vecTexCoord.y, 0.0f, 1.0f ); + meshBuilder.TexCoordSubRect2f( 0, vecTexCoord.x, vecTexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + +// meshBuilder.TexCoordSubRect2f( 0, vertex.m_TexCoord.x, vertex.m_TexCoord.y, offset[0], offset[1], scale[0], scale[1] ); + } + else +#endif + { + meshBuilder.TexCoord2fv( 0, GetVecTexCoord(vertex.m_TexCoord).Base() ); + } + + meshBuilder.Color4ub( 255, 255, 255, 255 ); + + if ( meshBuilder.NumBoneWeights() > 0 ) // bone weight of 0 will not write anything, so these calls would be wasted + { + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneWeight( 1, 0.0f ); + meshBuilder.BoneWeight( 2, 0.0f ); + meshBuilder.BoneWeight( 3, 0.0f ); + } + + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.BoneMatrix( 1, 0 ); + meshBuilder.BoneMatrix( 2, 0 ); + meshBuilder.BoneMatrix( 3, 0 ); + + meshBuilder.AdvanceVertex(); + } + return true; +} + +bool CStudioRender::DrawMultiBoneFlexedDecals( IMatRenderContext *pRenderContext, CMeshBuilder& meshBuilder, + DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr, studioloddata_t *pStudioLOD ) +{ + int *pBoneRemap = pStudioLOD ? pStudioLOD->m_pHWMorphDecalBoneRemap : NULL; + + mstudiomesh_t *pLastMesh = NULL; + const mstudio_meshvertexdata_t *vertData = NULL; + + DecalVertexList_t& verts = decalMaterial.m_Vertices; + for ( DecalVertexList_t::IndexLocalType_t i = verts.Head(); i != verts.InvalidIndex(); i = verts.Next(i) ) + { + DecalVertex_t& vertex = verts[i]; + + int n = vertex.m_MeshVertexIndex; + + mstudiomesh_t *pMesh = vertex.GetMesh( pStudioHdr ); + Assert( pMesh ); + + if ( pLastMesh != pMesh ) + { + // only if the mesh changes + pLastMesh = pMesh; + vertData = pMesh->GetVertexData( pStudioHdr ); + } + + if ( !vertData ) + return false; + + IMorph *pMorph = pBoneRemap ? vertex.GetMorph( m_pStudioHdr, m_pStudioMeshes ) : NULL; + Vector2D morphUV; + if ( pMorph ) + { + Assert( pBoneRemap ); + Assert( vertex.m_GroupIndex != 0xFFFF ); + if ( !pRenderContext->GetMorphAccumulatorTexCoord( &morphUV, pMorph, vertex.m_GroupIndex ) ) + { + pMorph = NULL; + } + } + + if ( !pMorph ) + { + mstudioboneweight_t* pBoneWeights = vertData->BoneWeights( n ); + m_VertexCache.SetBodyModelMesh( vertex.m_Body, vertex.m_Model, vertex.m_Mesh ); + + if ( m_VertexCache.IsVertexPositionCached( n ) ) + { + CachedPosNorm_t* pCachedVert = m_VertexCache.GetWorldVertex( n ); + meshBuilder.Position3fv( pCachedVert->m_Position.Base() ); + meshBuilder.Normal3fv( pCachedVert->m_Normal.Base() ); + } + else + { + // Prevent the computation of this again.... + m_VertexCache.SetupComputation(pMesh); + CachedPosNorm_t* pCachedVert = m_VertexCache.CreateWorldVertex( n ); + + if (m_VertexCache.IsThinVertexFlexed( n )) + { + CachedPosNorm_t* pFlexedVertex = m_VertexCache.GetThinFlexVertex( n ); + Vector vecPosition, vecNormal; + VectorAdd( *vertData->Position( n ), pFlexedVertex->m_Position.AsVector3D(), vecPosition ); + VectorAdd( *vertData->Normal( n ), pFlexedVertex->m_Normal.AsVector3D(), vecNormal ); + R_StudioTransform( vecPosition, pBoneWeights, pCachedVert->m_Position.AsVector3D() ); + R_StudioRotate( vecNormal, pBoneWeights, pCachedVert->m_Normal.AsVector3D() ); + VectorNormalize( pCachedVert->m_Normal.AsVector3D() ); + } + else if (m_VertexCache.IsVertexFlexed( n )) + { + CachedPosNormTan_t* pFlexedVertex = m_VertexCache.GetFlexVertex( n ); + R_StudioTransform( pFlexedVertex->m_Position, pBoneWeights, pCachedVert->m_Position.AsVector3D() ); + R_StudioRotate( pFlexedVertex->m_Normal, pBoneWeights, pCachedVert->m_Normal.AsVector3D() ); + } + else + { + Assert( pMesh ); + R_StudioTransform( *vertData->Position( n ), pBoneWeights, pCachedVert->m_Position.AsVector3D() ); + R_StudioRotate( *vertData->Normal( n ), pBoneWeights, pCachedVert->m_Normal.AsVector3D() ); + } + + // Add a little extra offset for hardware skinning; in that case + // we're doing software skinning for decals and it might not be quite right + VectorMA( pCachedVert->m_Position.AsVector3D(), 0.1, pCachedVert->m_Normal.AsVector3D(), pCachedVert->m_Position.AsVector3D() ); + + meshBuilder.Position3fv( pCachedVert->m_Position.Base() ); + meshBuilder.Normal3fv( pCachedVert->m_Normal.Base() ); + } + + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2fv( 0, GetVecTexCoord( vertex.m_TexCoord ).Base() ); + meshBuilder.TexCoord3f( 2, 0.0f, 0.0f, 0.0f ); + + // NOTE: Even if HW morphing is active, since we're using bone 0, it will multiply by identity in the shader + if ( meshBuilder.NumBoneWeights() > 0 ) // bone weight of 0 will not write anything, so these calls would be wasted + { + meshBuilder.BoneWeight( 0, 1.0f ); + meshBuilder.BoneWeight( 1, 0.0f ); + meshBuilder.BoneWeight( 2, 0.0f ); + meshBuilder.BoneWeight( 3, 0.0f ); + } + + meshBuilder.BoneMatrix( 0, 0 ); + meshBuilder.BoneMatrix( 1, 0 ); + meshBuilder.BoneMatrix( 2, 0 ); + meshBuilder.BoneMatrix( 3, 0 ); + } + else + { + meshBuilder.Position3fv( vertData->Position( n )->Base() ); + meshBuilder.Normal3fv( vertData->Normal( n )->Base() ); + meshBuilder.Color4ub( 255, 255, 255, 255 ); + meshBuilder.TexCoord2fv( 0, GetVecTexCoord( vertex.m_TexCoord ).Base() ); + meshBuilder.TexCoord3f( 2, morphUV.x, morphUV.y, 1.0f ); + + // NOTE: We should be renormalizing bone weights here like R_AddVertexToMesh does.. + // It's too expensive. Tough noogies. + mstudioboneweight_t* pBoneWeights = vertData->BoneWeights( n ); + Assert( pBoneWeights->numbones <= 3 ); + meshBuilder.BoneWeight( 0, pBoneWeights->weight[ 0 ] ); + meshBuilder.BoneWeight( 1, pBoneWeights->weight[ 1 ] ); + meshBuilder.BoneWeight( 2, 1.0f - pBoneWeights->weight[ 1 ] - pBoneWeights->weight[ 0 ] ); + meshBuilder.BoneWeight( 3, 0.0f ); + meshBuilder.BoneMatrix( 0, pBoneRemap[ (unsigned)pBoneWeights->bone[0] ] ); + meshBuilder.BoneMatrix( 1, pBoneRemap[ (unsigned)pBoneWeights->bone[1] ] ); + meshBuilder.BoneMatrix( 2, pBoneRemap[ (unsigned)pBoneWeights->bone[2] ] ); + meshBuilder.BoneMatrix( 3, BONE_MATRIX_INDEX_INVALID ); + } + + meshBuilder.AdvanceVertex(); + } + return true; +} + +//----------------------------------------------------------------------------- +// Draws all the decals using a particular material +//----------------------------------------------------------------------------- +void CStudioRender::DrawDecalMaterial( IMatRenderContext *pRenderContext, DecalMaterial_t& decalMaterial, studiohdr_t *pStudioHdr, studioloddata_t *pStudioLOD ) +{ + // Performance analysis. +// VPROF_BUDGET( "Decals", "Decals" ); + VPROF( "DecalsDrawStudio" ); + + // It's possible for the index count to become zero due to decal retirement + int indexCount = decalMaterial.m_Indices.Count(); + if ( indexCount == 0 ) + return; + + if ( !m_pRC->m_Config.m_bEnableHWMorph ) + { + pStudioLOD = NULL; + } + + bool bUseHWMorphing = ( pStudioLOD && ( pStudioLOD->m_pHWMorphDecalBoneRemap != NULL ) ); + if ( bUseHWMorphing ) + { + pRenderContext->BindMorph( MATERIAL_MORPH_DECAL ); + } + + // Bind the decal material + if ( !m_pRC->m_Config.bWireframeDecals ) + { + pRenderContext->Bind( decalMaterial.m_pMaterial ); + } + else + { + pRenderContext->Bind( m_pMaterialMRMWireframe ); + } + + // Use a dynamic mesh... + IMesh* pMesh = pRenderContext->GetDynamicMesh(); + + int vertexCount = decalMaterial.m_Vertices.Count(); + + CMeshBuilder meshBuilder; + meshBuilder.Begin( pMesh, MATERIAL_TRIANGLES, vertexCount, indexCount ); + + // FIXME: Could make static meshes for these? + // But don't make no static meshes for decals that fade, at least + + // Two possibilities: no/one bones, we let the hardware do all transformation + // or, more than one bone, we do software skinning. + bool bDraw = true; + if ( m_pStudioHdr->numbones <= 1 ) + { + if ( m_pStudioHdr->numflexdesc != 0 ) + { + DrawSingleBoneFlexedDecals( pRenderContext, meshBuilder, decalMaterial ); + } + else + { + DrawSingleBoneDecals( meshBuilder, decalMaterial ); + } + } + else + { + if ( m_pStudioHdr->numflexdesc != 0 ) + { + if ( !DrawMultiBoneFlexedDecals( pRenderContext, meshBuilder, decalMaterial, pStudioHdr, pStudioLOD ) ) + { + bDraw = false; + } + } + else + { + if ( !DrawMultiBoneDecals( meshBuilder, decalMaterial, pStudioHdr ) ) + { + bDraw = false; + } + } + } + + // Set the indices + // This is a little tricky. Because we can retire decals, the indices + // for each decal start at 0. We output all the vertices in order of + // each decal, and then fix up the indices based on how many vertices + // we wrote out for the decals + unsigned short decal = decalMaterial.m_Decals.Head(); + int indicesRemaining = decalMaterial.m_Decals[decal].m_IndexCount; + int vertexOffset = 0; + for ( int i = 0; i < indexCount; ++i) + { + meshBuilder.Index( decalMaterial.m_Indices[i] + vertexOffset ); + meshBuilder.AdvanceIndex(); + if (--indicesRemaining <= 0) + { + vertexOffset += decalMaterial.m_Decals[decal].m_VertexCount; + decal = decalMaterial.m_Decals.Next(decal); + if (decal != decalMaterial.m_Decals.InvalidIndex()) + { + indicesRemaining = decalMaterial.m_Decals[decal].m_IndexCount; + } +#ifdef _DEBUG + else + { + Assert( i + 1 == indexCount ); + } +#endif + } + } + + meshBuilder.End(); + if ( bDraw ) + { + pMesh->Draw(); + } + else + { + pMesh->MarkAsDrawn(); + } + + if ( bUseHWMorphing ) + { + pRenderContext->BindMorph( NULL ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: Setup the render state for decals if object has lighting baked. +//----------------------------------------------------------------------------- +static Vector s_pWhite[6] = +{ + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ), + Vector( 1.0, 1.0, 1.0 ) +}; + +bool CStudioRender::PreDrawDecal( IMatRenderContext *pRenderContext, const DrawModelInfo_t &drawInfo ) +{ + if ( !drawInfo.m_bStaticLighting ) + return false; + + // FIXME: This is incredibly bogus, + // it's overwriting lighting state in the context without restoring it! + const Vector *pAmbient; + if ( m_pRC->m_Config.fullbright ) + { + pAmbient = s_pWhite; + m_pRC->m_NumLocalLights = 0; + } + else + { + pAmbient = drawInfo.m_vecAmbientCube; + m_pRC->m_NumLocalLights = CopyLocalLightingState( MAXLOCALLIGHTS, m_pRC->m_LocalLights, + drawInfo.m_nLocalLightCount, drawInfo.m_LocalLightDescs ); + } + + for( int i = 0; i < 6; i++ ) + { + VectorCopy( pAmbient[i], m_pRC->m_LightBoxColors[i].AsVector3D() ); + m_pRC->m_LightBoxColors[i][3] = 1.0f; + } + + SetLightingRenderState(); + return true; +} + + +//----------------------------------------------------------------------------- +// Draws all the decals on a particular model +//----------------------------------------------------------------------------- +void CStudioRender::DrawDecal( const DrawModelInfo_t &drawInfo, int lod, int body ) +{ + StudioDecalHandle_t handle = drawInfo.m_Decals; + if ( handle == STUDIORENDER_DECAL_INVALID ) + return; + + VPROF("CStudioRender::DrawDecal"); + + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + PreDrawDecal( pRenderContext, drawInfo ); + + // All decal vertex data is are stored in pose space + // So as long as the pose-to-world transforms are set, we're all ready! + + // FIXME: Body stuff isn't hooked in at all for decals + + // Get the decal list for this lod + const DecalModelList_t& list = m_DecalList[(int)handle]; + m_pStudioHdr = drawInfo.m_pStudioHdr; + + // Add this fix after I fix the other problem. + studioloddata_t *pStudioLOD = NULL; + if ( m_pStudioHdr->numbones <= 1 ) + { + pRenderContext->SetNumBoneWeights( m_pStudioHdr->numbones ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadMatrix( m_PoseToWorld[0] ); + } + else + { + pStudioLOD = &drawInfo.m_pHardwareData->m_pLODs[lod]; + if ( !m_pRC->m_Config.m_bEnableHWMorph || !pStudioLOD->m_pHWMorphDecalBoneRemap ) + { + pRenderContext->SetNumBoneWeights( 0 ); + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadIdentity( ); + } + else + { + // Set up skinning for decal rendering with hw morphs + pRenderContext->SetNumBoneWeights( pStudioLOD->m_nDecalBoneCount ); + + // Bone 0 is always identity; necessary to multiple against non hw-morphed verts + matrix3x4_t identity; + SetIdentityMatrix( identity ); + pRenderContext->LoadBoneMatrix( 0, identity ); + + // Set up the bone state from the mapping computed in ComputeHWMorphDecalBoneRemap + for ( int i = 0; i < m_pStudioHdr->numbones; ++i ) + { + int nHWBone = pStudioLOD->m_pHWMorphDecalBoneRemap[i]; + if ( nHWBone <= 0 ) + continue; + + pRenderContext->LoadBoneMatrix( nHWBone, m_PoseToWorld[i] ); + } + } + } + + // Gotta do this for all LODs + // Draw each set of decals using a particular material + unsigned short mat = list.m_pLod[lod].m_FirstMaterial; + for ( ; mat != m_DecalMaterial.InvalidIndex(); mat = m_DecalMaterial.Next(mat)) + { + DecalMaterial_t& decalMaterial = m_DecalMaterial[mat]; + DrawDecalMaterial( pRenderContext, decalMaterial, m_pStudioHdr, pStudioLOD ); + } +} + + +void CStudioRender::DrawStaticPropDecals( const DrawModelInfo_t &drawInfo, const StudioRenderContext_t &rc, const matrix3x4_t &modelToWorld ) +{ + StudioDecalHandle_t handle = drawInfo.m_Decals; + if (handle == STUDIORENDER_DECAL_INVALID) + return; + + m_pRC = const_cast< StudioRenderContext_t* >( &rc ); + + VPROF("CStudioRender::DrawStaticPropDecals"); + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + PreDrawDecal( pRenderContext, drawInfo ); + + // All decal vertex data is are stored in pose space + // So as long as the pose-to-world transforms are set, we're all ready! + + // FIXME: Body stuff isn't hooked in at all for decals + + pRenderContext->MatrixMode( MATERIAL_MODEL ); + pRenderContext->LoadMatrix( modelToWorld ); + + const DecalModelList_t& list = m_DecalList[(int)handle]; + // Gotta do this for all LODs + // Draw each set of decals using a particular material + unsigned short mat = list.m_pLod[drawInfo.m_Lod].m_FirstMaterial; + for ( ; mat != m_DecalMaterial.InvalidIndex(); mat = m_DecalMaterial.Next(mat)) + { + DecalMaterial_t& decalMaterial = m_DecalMaterial[mat]; + DrawDecalMaterial( pRenderContext, decalMaterial, drawInfo.m_pStudioHdr, NULL ); + } + + m_pRC = NULL; +} + |