summaryrefslogtreecommitdiff
path: root/studiorender/r_studiodecal.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /studiorender/r_studiodecal.cpp
downloadarchived-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.cpp1990
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;
+}
+