summaryrefslogtreecommitdiff
path: root/utils/studiomdl/UnifyLODs.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 /utils/studiomdl/UnifyLODs.cpp
downloadarchived-source-engine-2018-hl2-src-master.tar.xz
archived-source-engine-2018-hl2-src-master.zip
Diffstat (limited to 'utils/studiomdl/UnifyLODs.cpp')
-rw-r--r--utils/studiomdl/UnifyLODs.cpp1631
1 files changed, 1631 insertions, 0 deletions
diff --git a/utils/studiomdl/UnifyLODs.cpp b/utils/studiomdl/UnifyLODs.cpp
new file mode 100644
index 0000000..19bf9d7
--- /dev/null
+++ b/utils/studiomdl/UnifyLODs.cpp
@@ -0,0 +1,1631 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <math.h>
+#include <float.h>
+
+#include "cmdlib.h"
+#include "scriplib.h"
+#include "mathlib/mathlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "bone_setup.h"
+#include "tier1/strtools.h"
+#include "mathlib/vmatrix.h"
+#include "optimize.h"
+
+// debugging only - enabling turns off remapping to create all lod vertexes as unique
+// to ensure remapping logic does not introduce collapse anomalies
+//#define UNIQUE_VERTEXES_FOR_LOD
+
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations local to this file
+//-----------------------------------------------------------------------------
+class CVertexDictionary;
+struct VertexInfo_t;
+static void BuildBoneLODMapping( CUtlVector<int> &boneMap, int lodID );
+
+
+//-----------------------------------------------------------------------------
+// Globals
+//-----------------------------------------------------------------------------
+static int g_NumBonesInLOD[MAX_NUM_LODS];
+
+
+//-----------------------------------------------------------------------------
+// Makes sure all boneweights in a s_boneweight_t are valid
+//-----------------------------------------------------------------------------
+static void ValidateBoneWeight( const s_boneweight_t &boneWeight )
+{
+#ifdef _DEBUG
+ int i;
+ if( boneWeight.weight[0] == 1.0f )
+ {
+ Assert( boneWeight.numbones == 1 );
+ }
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ Assert( boneWeight.bone[i] >= 0 && boneWeight.bone[i] < g_numbones );
+ }
+
+ float weight = 0.0f;
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ weight += boneWeight.weight[i] ;
+ }
+ Assert( fabs( weight - 1.0f ) < 1e-3 );
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Swap bones
+//-----------------------------------------------------------------------------
+static inline void SwapBones( s_boneweight_t &boneWeight, int nBone1, int nBone2 )
+{
+ // swap
+ int nTmpBone = boneWeight.bone[nBone1];
+ float flTmpWeight = boneWeight.weight[nBone1];
+ boneWeight.bone[nBone1] = boneWeight.bone[nBone2];
+ boneWeight.weight[nBone1] = boneWeight.weight[nBone2];
+ boneWeight.bone[nBone2] = nTmpBone;
+ boneWeight.weight[nBone2] = flTmpWeight;
+}
+
+
+//-----------------------------------------------------------------------------
+// Sort the bone weight structure to be sorted by bone weight
+//-----------------------------------------------------------------------------
+static void SortBoneWeightByWeight( s_boneweight_t &boneWeight )
+{
+ // bubble sort the bones by weight. . .put the largest weight first.
+ for( int j = boneWeight.numbones; j > 1; j-- )
+ {
+ for( int k = 0; k < j - 1; k++ )
+ {
+ if( boneWeight.weight[k] >= boneWeight.weight[k+1] )
+ continue;
+
+ SwapBones( boneWeight, k, k+1 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Sort the bone weight structure to be sorted by bone index
+//-----------------------------------------------------------------------------
+static void SortBoneWeightByIndex( s_boneweight_t &boneWeight )
+{
+ // bubble sort the bones by index. . .put the smallest index first.
+ for ( int j = boneWeight.numbones; j > 1; j-- )
+ {
+ for( int k = 0; k < j - 1; k++ )
+ {
+ if( boneWeight.bone[k] <= boneWeight.bone[k+1] )
+ continue;
+
+ SwapBones( boneWeight, k, k+1 );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// A vertex format
+//-----------------------------------------------------------------------------
+struct VertexInfo_t
+{
+ Vector m_Position;
+ Vector m_Normal;
+ Vector2D m_TexCoord;
+ Vector4D m_TangentS;
+ s_boneweight_t m_BoneWeight;
+ int m_nLodFlag;
+};
+
+
+//-----------------------------------------------------------------------------
+// Stores all vertices in the vertex dictionary
+//-----------------------------------------------------------------------------
+class CVertexDictionary
+{
+public:
+ CVertexDictionary();
+
+ // Adds a vertex to the dictionary
+ int AddVertex( const VertexInfo_t &srcVertex );
+ int AddVertexFromSource( const s_source_t *pSrc, int nVertexIndex, int nLod );
+
+ // Iteration
+ int VertexCount() const;
+ VertexInfo_t &Vertex( int i );
+ const VertexInfo_t &Vertex( int i ) const;
+
+ int RootLODVertexStart() const;
+ int RootLODVertexEnd() const;
+
+ // Gets the vertex count for the previous LOD
+ int PrevLODVertexCount() const;
+
+ // Marks the dictionary as starting defining vertices for a new LOD
+ void StartNewLOD();
+
+ void SetRootVertexRange( int start, int end );
+
+private:
+ CUtlVector<VertexInfo_t> m_Verts;
+ int m_nPrevLODCount;
+ int m_nRootLODStart;
+ int m_nRootLODEnd;
+};
+
+
+//-----------------------------------------------------------------------------
+// Copies in a particular vertex from the s_source_t
+//-----------------------------------------------------------------------------
+CVertexDictionary::CVertexDictionary()
+{
+ m_nPrevLODCount = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Accessor
+//-----------------------------------------------------------------------------
+inline VertexInfo_t &CVertexDictionary::Vertex( int i )
+{
+ return m_Verts[i];
+}
+
+inline const VertexInfo_t &CVertexDictionary::Vertex( int i ) const
+{
+ return m_Verts[i];
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets the vertex count for the previous LOD
+//-----------------------------------------------------------------------------
+inline int CVertexDictionary::PrevLODVertexCount() const
+{
+ return m_nPrevLODCount;
+}
+
+
+inline int CVertexDictionary::RootLODVertexStart() const
+{
+ return m_nRootLODStart;
+}
+
+
+inline int CVertexDictionary::RootLODVertexEnd() const
+{
+ return m_nRootLODEnd;
+}
+
+
+//-----------------------------------------------------------------------------
+// Marks the dictionary as starting defining vertices for a new LOD
+//-----------------------------------------------------------------------------
+void CVertexDictionary::StartNewLOD()
+{
+ m_nPrevLODCount = VertexCount();
+}
+
+
+void CVertexDictionary::SetRootVertexRange( int start, int end )
+{
+ m_nRootLODStart = start;
+ m_nRootLODEnd = end;
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds a vertex to the dictionary
+//-----------------------------------------------------------------------------
+int CVertexDictionary::AddVertex( const VertexInfo_t &srcVertex )
+{
+ int nDstVertID = m_Verts.AddToTail( srcVertex );
+ VertexInfo_t &vertex = m_Verts[ nDstVertID ];
+ ValidateBoneWeight( vertex.m_BoneWeight );
+ SortBoneWeightByIndex( vertex.m_BoneWeight );
+ ValidateBoneWeight( vertex.m_BoneWeight );
+
+ return nDstVertID;
+}
+
+
+//-----------------------------------------------------------------------------
+// Copies in a particular vertex from the s_source_t
+//-----------------------------------------------------------------------------
+int CVertexDictionary::AddVertexFromSource( const s_source_t *pSrc, int nVertexIndex, int nLod )
+{
+ int nDstVertID = m_Verts.AddToTail( );
+ VertexInfo_t &vertex = m_Verts[ nDstVertID ];
+
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[nVertexIndex];
+ vertex.m_Position = srcVertex.position;
+ vertex.m_Normal = srcVertex.normal;
+ vertex.m_TexCoord = srcVertex.texcoord;
+ vertex.m_TangentS = srcVertex.tangentS;
+ vertex.m_BoneWeight = srcVertex.boneweight;
+ vertex.m_nLodFlag = 1 << nLod;
+
+ ValidateBoneWeight( vertex.m_BoneWeight );
+ SortBoneWeightByIndex( vertex.m_BoneWeight );
+ ValidateBoneWeight( vertex.m_BoneWeight );
+
+ return nDstVertID;
+}
+
+
+//-----------------------------------------------------------------------------
+// How many vertices in the dictionary?
+//-----------------------------------------------------------------------------
+int CVertexDictionary::VertexCount() const
+{
+ return m_Verts.Count();
+}
+
+
+s_source_t* GetModelLODSource( const char *pModelName,
+ const LodScriptData_t& scriptLOD, bool* pFound )
+{
+ // When doing LOD replacement, ignore all path + extension information
+ char* pTempBuf = (char*)_alloca( Q_strlen(pModelName) + 1 );
+
+ // Strip off extensions for the source...
+ strcpy( pTempBuf, pModelName );
+ char* pDot = strrchr( pTempBuf, '.' );
+ if (pDot)
+ {
+ *pDot = 0;
+ }
+
+ for( int i = 0; i < scriptLOD.modelReplacements.Count(); i++ )
+ {
+ // FIXME: Should we strip off path information?
+// char* pSlash = strrchr( pTempBuf1, '\\' );
+// char* pSlash2 = strrchr( pTempBuf1, '/' );
+// if (pSlash2 > pSlash)
+// pSlash = pSlash2;
+// if (!pSlash)
+// pSlash = pTempBuf1;
+
+ if( !Q_stricmp( pTempBuf, scriptLOD.modelReplacements[i].GetSrcName() ) )
+ {
+ *pFound = true;
+ return scriptLOD.modelReplacements[i].m_pSource;
+ }
+ }
+
+ *pFound = false;
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Tolerances for all fields of the vertex
+//-----------------------------------------------------------------------------
+#define POSITION_EPSILON 0.01f // Was 0.05f
+#define TEXCOORD_EPSILON 0.01f
+#define NORMAL_EPSILON 10.0f // in degrees
+#define TANGENT_EPSILON 10.0f // in degrees
+#define BONEWEIGHT_EPSILON 0.05f
+
+#define UNMATCHED_BONE_WEIGHT 1.0f
+
+//-----------------------------------------------------------------------------
+// Computes error between two positions; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool ComparePositionFuzzy( const Vector &p1, const Vector &p2, float &flError )
+{
+ Vector vecDelta;
+ VectorSubtract( p1, p2, vecDelta );
+ flError = DotProduct( vecDelta, vecDelta );
+ return ( flError <= (POSITION_EPSILON * POSITION_EPSILON) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes error between two normals; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareNormalFuzzy( const Vector &n1, const Vector &n2, float &flError )
+{
+ static float flEpsilon = cos( DEG2RAD( NORMAL_EPSILON ) );
+
+ Vector v1, v2;
+ v1 = n1;
+ v2 = n2;
+ VectorNormalize( v1 );
+ VectorNormalize( v2 );
+ float flDot = DotProduct( v1, v2 );
+ flError = 1.0F - flDot;
+ return ( flDot >= flEpsilon );
+}
+
+//-----------------------------------------------------------------------------
+// Computes error between two tangentS vectors; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareTangentSFuzzy( const Vector4D &n1, const Vector4D &n2, float &flError )
+{
+ static float flEpsilon = cos( DEG2RAD( TANGENT_EPSILON ) );
+
+ Vector4D v1, v2;
+ v1 = n1;
+ v2 = n2;
+
+ if (v1.w != v2.w)
+ {
+ // must match as -1 or 1
+ flError = 2;
+ return false;
+ }
+
+ VectorNormalize( v1.AsVector3D() );
+ VectorNormalize( v2.AsVector3D() );
+ float flDot = DotProduct( v1.AsVector3D(), v2.AsVector3D() );
+
+ // error ranges from [0..2]
+ flError = 1.0F - flDot;
+
+ return ( flDot >= flEpsilon );
+}
+
+//-----------------------------------------------------------------------------
+// Computes error between two texcoords; returns false if the error is too great
+//-----------------------------------------------------------------------------
+bool CompareTexCoordsFuzzy( const Vector2D &t1, const Vector2D &t2, float &flError )
+{
+ Vector2D vecError;
+ vecError[0] = fabs( t2[0] - t1[0] );
+ vecError[1] = fabs( t2[1] - t1[1] );
+ flError = vecError.LengthSqr();
+ return ( flError <= (TEXCOORD_EPSILON * TEXCOORD_EPSILON) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the error between two bone weights, returns false if they are too far
+//-----------------------------------------------------------------------------
+bool CompareBoneWeightsFuzzy( const s_boneweight_t &b1, const s_boneweight_t &b2, float &flError )
+{
+ // This is a list of which bones that exist in b1 also exist in b2.
+ // Use the index to figure out where in the array for b2 that the corresponding bone in b1 is.
+ int nMatchingBones = 0;
+ int pBoneIndexMap1[MAX_NUM_BONES_PER_VERT];
+ int pBoneIndexMap2[MAX_NUM_BONES_PER_VERT];
+
+ int i;
+ for ( i = 0; i < b2.numbones; ++i )
+ {
+ pBoneIndexMap2[i] = -1;
+ }
+
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ pBoneIndexMap1[i] = -1;
+ for ( int j = 0; j < b2.numbones; ++j )
+ {
+ if ( b2.bone[j] == b1.bone[i] )
+ {
+ pBoneIndexMap1[i] = j;
+ pBoneIndexMap2[j] = i;
+ ++nMatchingBones;
+ break;
+ }
+ }
+ }
+
+ // If no bones match, we're done
+ if ( !nMatchingBones )
+ {
+ flError = FLT_MAX;
+ return false;
+ }
+
+ // At least one bone matches, so we're going to consider this vertex as a potential match
+ // This loop will take care of figuring out the error for all bones that exist in
+ // b1 alone, and all bones that exist in b1 and b2
+ flError = 0;
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ // If we didn't find a match for this bone, compute a more expensive weight
+ if ( pBoneIndexMap1[i] == -1 )
+ {
+ flError += b1.weight[i] * b1.weight[i] * UNMATCHED_BONE_WEIGHT;
+ continue;
+ }
+
+ float flDeltaWeight = fabs( b1.weight[i] - b2.weight[ pBoneIndexMap1[i] ] );
+ flError += flDeltaWeight * flDeltaWeight;
+ }
+
+ // This loop will take care of figuring out the error for all bones that exist in b2 alone
+ for ( i = 0; i < b2.numbones; ++i )
+ {
+ // If we didn't find a match for this bone, compute a more expensive weight
+ if ( pBoneIndexMap2[i] == -1 )
+ {
+ flError += b2.weight[i] * b2.weight[i] * UNMATCHED_BONE_WEIGHT;
+ }
+ }
+
+ // This renormalizes the error. The error will become greater with the total
+ // number of bones in the two vertices.
+ flError /= sqrt( (float) (b1.numbones + b2.numbones));
+ return ( flError <= BONEWEIGHT_EPSILON );
+}
+
+
+//-----------------------------------------------------------------------------
+// Searches for a material in the texture list
+//-----------------------------------------------------------------------------
+int FindMaterialByName( const char *pMaterialName )
+{
+ int i;
+ int allocLen = strlen( pMaterialName ) + 1;
+ char *pBaseName = ( char * )_alloca( allocLen );
+ Q_FileBase( ( char * )pMaterialName, pBaseName, allocLen );
+
+ for( i = 0; i < g_numtextures; i++ )
+ {
+ if( stricmp( pBaseName, g_texture[i].name ) == 0 )
+ {
+ return i;
+ }
+ }
+ return -1;
+}
+
+static s_mesh_t *FindMeshByMaterial( s_source_t *pSrc, int nMaterialID )
+{
+ for ( int m = 0; m < pSrc->nummeshes; m++ )
+ {
+ if ( pSrc->meshindex[m] == nMaterialID )
+ return &pSrc->mesh[ pSrc->meshindex[m] ];
+ }
+
+ // this mesh/material doesn't exist at this lod.
+ return NULL;
+}
+
+
+static s_mesh_t *FindOrCullMesh( int nLodID, s_source_t *pSrc, int nMaterialID )
+{
+ char baseMeshName[MAX_PATH];
+ char baseRemovalName[MAX_PATH];
+
+ // possibly marked for removal via $removemesh
+ // determine mesh name
+ int nTextureID = MaterialToTexture( nMaterialID );
+ if (nTextureID == -1)
+ {
+ MdlError( "Unknown Texture for Material %d\n", nMaterialID );
+ }
+
+ Q_FileBase(g_texture[nTextureID].name, baseMeshName, sizeof(baseMeshName)-1);
+ for ( int i = 0; i < g_ScriptLODs[nLodID].meshRemovals.Count(); i++ )
+ {
+ const char *pMeshRemovalName = g_ScriptLODs[nLodID].meshRemovals[i].GetSrcName();
+ Q_FileBase( pMeshRemovalName, baseRemovalName, sizeof(baseRemovalName)-1);
+
+ if (!stricmp( baseRemovalName, baseMeshName ))
+ {
+ // mesh has been marked for removal
+ return NULL;
+ }
+ }
+
+ s_mesh_t *pMesh = FindMeshByMaterial( pSrc, nMaterialID );
+ return pMesh;
+}
+
+
+static void CopyVerts( int nLodID, const s_source_t *pSrc, const s_mesh_t *pSrcMesh, CVertexDictionary &vertexDict, s_mesh_t *pDstMesh, int *pMeshVertIndexMap )
+{
+ // populate the dictionary with the verts
+ for( int srcVertID = 0; srcVertID < pSrcMesh->numvertices; srcVertID++ )
+ {
+ int nVertexIndex = pSrcMesh->vertexoffset + srcVertID;
+ pMeshVertIndexMap[ nVertexIndex ] = vertexDict.AddVertexFromSource( pSrc, nVertexIndex, nLodID ) - pDstMesh->vertexoffset;
+ }
+
+ pDstMesh->numvertices = pSrcMesh->numvertices;
+}
+
+static void CopyFaces( const s_source_t *pSrc, const s_mesh_t *pSrcMesh, CUtlVector<s_face_t> &faces, s_mesh_t *pDstMesh )
+{
+ int srcFaceID;
+ for( srcFaceID = 0; srcFaceID < pSrcMesh->numfaces; srcFaceID++ )
+ {
+ int srcID = srcFaceID + pSrcMesh->faceoffset;
+ s_face_t *pSrcFace = &pSrc->face[srcID];
+ s_face_t *pDstFace = &faces[faces.AddToTail()];
+ pDstFace->a = pSrcFace->a;
+ pDstFace->b = pSrcFace->b;
+ pDstFace->c = pSrcFace->c;
+ pDstMesh->numfaces++;
+ }
+}
+
+#define IGNORE_POSITION 0x01
+#define IGNORE_TEXCOORD 0x02
+#define IGNORE_BONEWEIGHT 0x04
+#define IGNORE_NORMAL 0x08
+#define IGNORE_TANGENTS 0x10
+
+//-----------------------------------------------------------------------------
+// return -1 if there is no match. The index returned is used to index into vertexDict.
+//-----------------------------------------------------------------------------
+static int FindVertexWithinVertexDictionary( const VertexInfo_t &find,
+ const CVertexDictionary &vertexDict, int nStartVert, int nEndVert, int fIgnore )
+{
+ int nBestIndex = -1;
+ float flPositionError = 0.0f;
+ float flNormalError = 0.0f;
+ float flTangentSError = 0.0f;
+ float flTexcoordError = 0.0f;
+ float flBoneWeightError = 0.0f;
+ float flMinPositionError = FLT_MAX;
+ float flMinNormalError = FLT_MAX;
+ float flMinTangentSError = FLT_MAX;
+ float flMinTexcoordError = FLT_MAX;
+ float flMinBoneWeightError = FLT_MAX;
+ bool bFound;
+
+ if (fIgnore & IGNORE_POSITION)
+ {
+ flMinPositionError = 0;
+ flPositionError = 0;
+ }
+
+ if (fIgnore & IGNORE_TEXCOORD)
+ {
+ flMinTexcoordError = 0;
+ flTexcoordError = 0;
+ }
+
+ if (fIgnore & IGNORE_BONEWEIGHT)
+ {
+ flMinBoneWeightError = 0;
+ flBoneWeightError = 0;
+ }
+
+ if (fIgnore & IGNORE_NORMAL)
+ {
+ flMinNormalError = 0;
+ flNormalError = 0;
+ }
+
+ if (fIgnore & IGNORE_TANGENTS)
+ {
+ flMinTangentSError = 0;
+ flTangentSError = 0;
+ }
+
+ for ( int nVertexIndex = nStartVert; nVertexIndex < nEndVert; ++nVertexIndex )
+ {
+ // see if the position is reasonable
+ if ( !(fIgnore & IGNORE_POSITION) && !ComparePositionFuzzy( find.m_Position, vertexDict.Vertex(nVertexIndex).m_Position, flPositionError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_TEXCOORD) && !CompareTexCoordsFuzzy( find.m_TexCoord, vertexDict.Vertex(nVertexIndex).m_TexCoord, flTexcoordError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_BONEWEIGHT) && !CompareBoneWeightsFuzzy( find.m_BoneWeight, vertexDict.Vertex(nVertexIndex).m_BoneWeight, flBoneWeightError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_NORMAL) && !CompareNormalFuzzy( find.m_Normal, vertexDict.Vertex(nVertexIndex).m_Normal, flNormalError ) )
+ continue;
+
+ if ( !(fIgnore & IGNORE_TANGENTS) && !CompareTangentSFuzzy( find.m_TangentS, vertexDict.Vertex(nVertexIndex).m_TangentS, flTangentSError ) )
+ continue;
+
+ // the vert with minimum error is the best or exact candidate
+ bFound = false;
+ if (flMinPositionError > flPositionError)
+ {
+ bFound = true;
+ }
+ else if (flMinPositionError == flPositionError)
+ {
+ if (flMinTexcoordError > flTexcoordError)
+ {
+ bFound = true;
+ }
+ else if (flMinTexcoordError == flTexcoordError)
+ {
+ if (flMinBoneWeightError > flBoneWeightError)
+ {
+ bFound = true;
+ }
+ else if (flMinBoneWeightError == flBoneWeightError)
+ {
+ if (flMinNormalError > flNormalError)
+ {
+ bFound = true;
+ }
+ else if (flMinNormalError == flNormalError)
+ {
+ if (flMinTangentSError >= flTangentSError)
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+ }
+
+ if (!bFound)
+ continue;
+
+ flMinPositionError = flPositionError;
+ flMinTexcoordError = flTexcoordError;
+ flMinBoneWeightError = flBoneWeightError;
+ flMinNormalError = flNormalError;
+ flMinTangentSError = flTangentSError;
+ nBestIndex = nVertexIndex;
+ }
+
+ return nBestIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Use position, normal, and texcoord checks across the entire model to find a boneweight
+//-----------------------------------------------------------------------------
+static void FindBoneWeightWithinModel( const VertexInfo_t &searchVertex, const s_source_t *pSrc, s_boneweight_t &boneWeight, int fIgnore )
+{
+ int nBestIndex = -1;
+ float flPositionError = 0.0f;
+ float flNormalError = 0.0f;
+ float flTangentSError = 0.0f;
+ float flTexcoordError = 0.0f;
+ float flMinPositionError = FLT_MAX;
+ float flMinNormalError = FLT_MAX;
+ float flMinTangentSError = FLT_MAX;
+ float flMinTexcoordError = FLT_MAX;
+ bool bFound;
+
+ if (fIgnore & IGNORE_NORMAL)
+ {
+ flMinNormalError = 0;
+ flNormalError = 0;
+ }
+
+ if (fIgnore & IGNORE_TEXCOORD)
+ {
+ flMinTexcoordError = 0;
+ flTexcoordError = 0;
+ }
+
+ if (fIgnore & IGNORE_TANGENTS)
+ {
+ flMinTangentSError = 0;
+ flTangentSError = 0;
+ }
+
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for ( int i = 0; i < nVertexCount; i++ )
+ {
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[i];
+
+ // Compute error metrics
+ ComparePositionFuzzy( searchVertex.m_Position, srcVertex.position, flPositionError );
+
+ if (!(fIgnore & IGNORE_NORMAL))
+ {
+ CompareNormalFuzzy( searchVertex.m_Normal, srcVertex.normal, flNormalError );
+ }
+
+ if (!(fIgnore & IGNORE_TEXCOORD))
+ {
+ CompareTexCoordsFuzzy( searchVertex.m_TexCoord, srcVertex.texcoord, flTexcoordError );
+ }
+
+ if (!(fIgnore & IGNORE_TANGENTS))
+ {
+ CompareTangentSFuzzy( searchVertex.m_TangentS, srcVertex.tangentS, flTangentSError );
+ }
+
+ // the vert with minimum error is the best or exact candidate
+ bFound = false;
+ if (flMinPositionError > flPositionError)
+ {
+ bFound = true;
+ }
+ else if (flMinPositionError == flPositionError)
+ {
+ if (flMinTexcoordError > flTexcoordError)
+ {
+ bFound = true;
+ }
+ else if (flMinTexcoordError == flTexcoordError)
+ {
+ if (flMinNormalError > flNormalError)
+ {
+ bFound = true;
+ }
+ else if (flMinNormalError == flNormalError)
+ {
+ if (flMinTangentSError >= flTangentSError)
+ {
+ bFound = true;
+ }
+ }
+ }
+ }
+
+ if (bFound)
+ {
+ flMinPositionError = flPositionError;
+ flMinTexcoordError = flTexcoordError;
+ flMinNormalError = flNormalError;
+ flMinTangentSError = flTangentSError;
+ nBestIndex = i;
+ }
+ }
+
+ if ( nBestIndex == -1 )
+ {
+ MdlError( "Encountered a mesh with no vertices!\n" );
+ }
+
+ memcpy( &boneWeight, &pSrc->m_GlobalVertices[ nBestIndex ].boneweight, sizeof(s_boneweight_t) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Modify the bone weights in all of the vertices....
+//-----------------------------------------------------------------------------
+static void RemapBoneWeights( const CUtlVector<int> &boneMap, s_boneweight_t &boneWeight )
+{
+ for( int i = 0; i < boneWeight.numbones; i++ )
+ {
+ Assert( boneWeight.bone[i] >= 0 && boneWeight.bone[i] < boneMap.Count() );
+ boneWeight.bone[i] = boneMap[ boneWeight.bone[i] ];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// After the remapping, we may get multiple instances of the same bone
+// which we want to collapse into a single bone
+//-----------------------------------------------------------------------------
+static void CollapseBoneWeights( s_boneweight_t &boneWeight )
+{
+ // We need the bones to be sorted by bone index for the loop right below
+ SortBoneWeightByIndex( boneWeight );
+
+ for( int i = 0; i < boneWeight.numbones-1; i++ )
+ {
+ if( boneWeight.bone[i] != boneWeight.bone[i+1] )
+ continue;
+
+ // add i+1's weight to i since they have the same bone index
+ boneWeight.weight[i] += boneWeight.weight[i+1];
+
+ // remove i+1
+ for( int j = i+1; j < boneWeight.numbones-1; j++ )
+ {
+ boneWeight.bone[j] = boneWeight.bone[j+1];
+ boneWeight.weight[j] = boneWeight.weight[j+1];
+ }
+ --boneWeight.numbones;
+
+ // Gotta step back one, may have many bones collapsing into one
+ --i;
+ }
+
+ ValidateBoneWeight( boneWeight );
+}
+
+
+//-----------------------------------------------------------------------------
+// Find a matching vertex within the root lod
+//-----------------------------------------------------------------------------
+static void CalculateBoneWeightFromRootLod( const VertexInfo_t &searchVertex, CVertexDictionary &vertexDict,
+ const s_source_t *pRootLODSrc, VertexInfo_t &idealVertex )
+{
+ idealVertex = searchVertex;
+
+ // Look through the part of the vertex dictionary associated with the root LODs for a match
+ // bone weights are not defined properly in SMDs for lower LODs, so don't consider
+ // we can only accept the boneweight from the root LOD
+ int nVertexDictID = FindVertexWithinVertexDictionary( searchVertex, vertexDict,
+ vertexDict.RootLODVertexStart(), vertexDict.RootLODVertexEnd(), IGNORE_BONEWEIGHT|IGNORE_TANGENTS );
+ if ( nVertexDictID != -1 )
+ {
+ Assert( nVertexDictID >= vertexDict.RootLODVertexStart() && nVertexDictID < vertexDict.RootLODVertexEnd() );
+ Assert( nVertexDictID >= 0 && nVertexDictID < vertexDict.VertexCount() );
+
+ // found vertex in dictionary
+#ifdef UNIQUE_VERTEXES_FOR_LOD
+ // keep entry vertex and fill in the missing bone weight attribute
+ idealVertex.m_BoneWeight = vertexDict.Vertex( nVertexDictID ).m_BoneWeight;
+#else
+ // discard entry vertex in favor of best match
+ // this ensures all the attributes, including bone weight are correct for that vertex
+ // the worst case is that the vertex is not an *exact* match for entry attributes just a "close" match
+ idealVertex = vertexDict.Vertex( nVertexDictID );
+#endif
+ return;
+ }
+
+ // In this case, we didn't find anything within the tolerance, so we need to
+ // do a *positional check only* to give us a bone weight to assign to this vertex.
+ FindBoneWeightWithinModel( searchVertex, pRootLODSrc, idealVertex.m_BoneWeight, IGNORE_BONEWEIGHT|IGNORE_TANGENTS );
+}
+
+//-----------------------------------------------------------------------------
+// Find a matching vertex
+//-----------------------------------------------------------------------------
+static void CalculateIdealVert( const VertexInfo_t &searchVertex, CVertexDictionary &vertexDict,
+ const s_mesh_t *pVertexDictMesh, const s_source_t *pRootLODSrc, VertexInfo_t &idealVertex )
+{
+#ifndef UNIQUE_VERTEXES_FOR_LOD
+ // Only look through the part of the vertex dictionary associated with all *higher* LODs for a match
+ int nVertexDictID = FindVertexWithinVertexDictionary( searchVertex, vertexDict,
+ pVertexDictMesh->vertexoffset, vertexDict.PrevLODVertexCount(), 0 );
+ if ( nVertexDictID != -1 )
+ {
+ Assert( nVertexDictID >= pVertexDictMesh->vertexoffset && nVertexDictID < vertexDict.PrevLODVertexCount() );
+ Assert( nVertexDictID >= 0 && nVertexDictID < vertexDict.VertexCount() );
+
+ // found vertex in dictionary
+ idealVertex = vertexDict.Vertex( nVertexDictID );
+ return;
+ }
+#endif
+
+ // could not find a tolerant match
+ // the search vertex is unique
+ idealVertex = searchVertex;
+}
+
+
+static bool FuzzyFloatCompare( float f1, float f2, float epsilon )
+{
+ if( fabs( f1 - f2 ) < epsilon )
+ {
+ return true;
+ }
+ else
+ {
+ return false;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Is this bone weight structure sorted by bone?
+//-----------------------------------------------------------------------------
+static bool IsBoneWeightSortedByBone( const s_boneweight_t &src )
+{
+ for ( int i = 1; i < src.numbones; ++i )
+ {
+ Assert( src.bone[i] != -1 );
+ if ( src.bone[ i-1 ] > src.bone[ i ] )
+ return false;
+ }
+
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Are two bone-weight structures equal?
+//-----------------------------------------------------------------------------
+static bool AreBoneWeightsEqual( const s_boneweight_t &b1, const s_boneweight_t &b2 )
+{
+ // Have to have the same number of bones
+ if ( b1.numbones != b2.numbones )
+ return false;
+
+ // This is a list of which bones that exist in b1 also exist in b2.
+ // Use the index to figure out where in the array for b2 that the corresponding bone in b1 is.
+ int nMatchingBones = 0;
+ int pBoneIndexMap[MAX_NUM_BONES_PER_VERT];
+
+ int i;
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ pBoneIndexMap[i] = -1;
+ for ( int j = 0; j < b2.numbones; ++j )
+ {
+ if ( b2.bone[j] == b1.bone[i] )
+ {
+ pBoneIndexMap[i] = j;
+ ++nMatchingBones;
+ break;
+ }
+ }
+ }
+
+ // If we aren't using the same bone indices, we're done
+ if ( nMatchingBones != b1.numbones )
+ return false;
+
+ // Check to see if the weights are the same
+ for ( i = 0; i < b1.numbones; ++i )
+ {
+ Assert( pBoneIndexMap[i] != -1 );
+ if ( b1.weight[i] != b2.weight[ pBoneIndexMap[i] ] )
+ return false;
+ }
+
+ return true;
+}
+
+
+
+//-----------------------------------------------------------------------------
+// Finds an *exact* requested vertex in the dictionary
+//-----------------------------------------------------------------------------
+static int FindVertexInDictionaryExact( CVertexDictionary &vertexDict, int nStartVert, int nEndVert, const VertexInfo_t &vertex )
+{
+ for ( int nVertID = nStartVert; nVertID < nEndVert; ++nVertID )
+ {
+ if ( vertexDict.Vertex( nVertID ).m_Position != vertex.m_Position )
+ continue;
+
+ if ( !AreBoneWeightsEqual( vertexDict.Vertex( nVertID ).m_BoneWeight, vertex.m_BoneWeight ) )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_TexCoord != vertex.m_TexCoord )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_Normal != vertex.m_Normal )
+ continue;
+
+ if ( vertexDict.Vertex( nVertID ).m_TangentS != vertex.m_TangentS )
+ continue;
+
+ return nVertID;
+ }
+
+ return -1;
+}
+
+
+//-----------------------------------------------------------------------------
+// Finds the *exact* requested vertex in the dictionary or creates it
+//-----------------------------------------------------------------------------
+static int FindOrCreateExactVertexInDictionary( CVertexDictionary &vertexDict,
+ const VertexInfo_t &vertex, s_mesh_t *pDstMesh )
+{
+ int nMeshVertID = FindVertexInDictionaryExact( vertexDict, pDstMesh->vertexoffset, pDstMesh->vertexoffset+pDstMesh->numvertices, vertex );
+ if ( nMeshVertID != -1 )
+ {
+ // flag vertex for what LoD's are using it
+ vertexDict.Vertex( nMeshVertID ).m_nLodFlag |= vertex.m_nLodFlag;
+ return nMeshVertID - pDstMesh->vertexoffset;
+ }
+
+ nMeshVertID = vertexDict.AddVertex( vertex );
+ ++pDstMesh->numvertices;
+ return nMeshVertID - pDstMesh->vertexoffset;
+}
+
+static void PrintBonesUsedInLOD( s_source_t *pSrc )
+{
+ printf( "PrintBonesUsedInLOD\n" );
+
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for( int i = 0; i <nVertexCount; i++ )
+ {
+ Vector &pos = pSrc->m_GlobalVertices[i].position;
+ Vector &norm = pSrc->m_GlobalVertices[i].normal;
+ Vector2D &texcoord = pSrc->m_GlobalVertices[i].texcoord;
+ printf( "pos: %f %f %f norm: %f %f %f texcoord: %f %f\n",
+ pos[0], pos[1], pos[2], norm[0], norm[1], norm[2], texcoord[0], texcoord[1] );
+ s_boneweight_t *pBoneWeight = &pSrc->m_GlobalVertices[i].boneweight;
+ int j;
+ for( j = 0; j < pBoneWeight->numbones; j++ )
+ {
+ int globalBoneID = pBoneWeight->bone[j];
+ const char *pBoneName = g_bonetable[globalBoneID].name;
+ printf( "vert: %d bone: %d boneid: %d weight: %f name: \"%s\"\n", i, ( int )j, ( int )pBoneWeight->bone[j],
+ ( float )pBoneWeight->weight[j], pBoneName );
+ }
+ printf( "\n" );
+ fflush( stdout );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Indicates a particular set of bones is used by a particular LOD
+//-----------------------------------------------------------------------------
+static void MarkBonesUsedByLod( const s_boneweight_t &boneWeight, int nLodID )
+{
+ for( int j = 0; j < boneWeight.numbones; ++j )
+ {
+ int nGlobalBoneID = boneWeight.bone[j];
+ s_bonetable_t *pBone = &g_bonetable[nGlobalBoneID];
+ pBone->flags |= ( BONE_USED_BY_VERTEX_LOD0 << nLodID );
+ }
+}
+
+
+static void PrintSBoneWeight( s_boneweight_t *pBoneWeight, const s_source_t *pSrc )
+{
+ int j;
+ for( j = 0; j < pBoneWeight->numbones; j++ )
+ {
+ int globalBoneID;
+ globalBoneID = pBoneWeight->bone[j];
+ const char *pBoneName = g_bonetable[globalBoneID].name;
+ printf( "bone: %d boneid: %d weight: %f name: \"%s\"\n", ( int )j, ( int )pBoneWeight->bone[j],
+ ( float )pBoneWeight->weight[j], pBoneName );
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+// In the non-top LOD, look for vertices that would be appropriate from the
+// vertex dictionary, and use them if you find them, or add new vertices to the
+// vertex dictionary if not and use those new vertices.
+//-----------------------------------------------------------------------------
+static void CreateLODVertsInDictionary( int nLodID, const s_source_t *pRootLODSrc, s_source_t *pCurrentLODSrc,
+ const s_mesh_t *pCurrLODMesh, s_mesh_t *pVertexDictMesh, CVertexDictionary &vertexDict, int *pMeshVertIndexMap )
+{
+ // this function is specific to lods and not the root
+ Assert( nLodID );
+
+ int nNumCurrentVerts = vertexDict.VertexCount();
+
+ // Used to control where we look for vertices + merging rules
+ vertexDict.StartNewLOD();
+
+ CUtlVector<int> boneMap;
+ BuildBoneLODMapping( boneMap, nLodID );
+
+ for( int nSrcVertID = 0; nSrcVertID < pCurrLODMesh->numvertices; ++nSrcVertID )
+ {
+ int nSrcID = nSrcVertID + pCurrLODMesh->vertexoffset;
+
+ // candidate vertex
+ // vertices at lower LODs have bogus boneweights assigned
+ // must get the boneweight from the nearest or exact vertex at root lod
+ const s_vertexinfo_t& srcVertex = pCurrentLODSrc->m_GlobalVertices[nSrcID];
+ VertexInfo_t vertex;
+ vertex.m_Position = srcVertex.position;
+ vertex.m_Normal = srcVertex.normal;
+ vertex.m_TexCoord = srcVertex.texcoord;
+ vertex.m_TangentS = srcVertex.tangentS;
+
+#ifdef _DEBUG
+ memset( &vertex.m_BoneWeight, 0xDD, sizeof( s_boneweight_t ) );
+#endif
+ // determine the best bone weight for the desired vertex within the root lod only
+ // the root lod contains no bone remappings
+ // this ensures we get a vertex with its matched proper boneweight assignment
+ VertexInfo_t idealVertex;
+ CalculateBoneWeightFromRootLod( vertex, vertexDict, pRootLODSrc, idealVertex );
+
+ // try again to match the candidate vertex
+ // determine the ideal vertex with desired remapped boneweight
+ vertex = idealVertex;
+ CalculateIdealVert( vertex, vertexDict, pVertexDictMesh, pRootLODSrc, idealVertex);
+
+ // remap bone
+ RemapBoneWeights( boneMap, idealVertex.m_BoneWeight );
+ CollapseBoneWeights( idealVertex.m_BoneWeight );
+ SortBoneWeightByWeight( idealVertex.m_BoneWeight );
+
+ // FIXME: this is marking bones based on the slammed vertex data
+ MarkBonesUsedByLod( idealVertex.m_BoneWeight, nLodID );
+
+ // tag ideal vertex as being part of the current lod
+ idealVertex.m_nLodFlag = 1 << nLodID;
+
+ // Find the exact vertex or create it in the dictionary
+ int nMeshVertID = FindOrCreateExactVertexInDictionary( vertexDict, idealVertex, pVertexDictMesh );
+
+ // Indicate where in the higher LODs the vertex we selected resides
+ pMeshVertIndexMap[nSrcID] = nMeshVertID;
+ }
+
+ int nNewVertsCreated = vertexDict.VertexCount() - nNumCurrentVerts;
+ if (!g_quiet && nNewVertsCreated)
+ {
+ printf( "Lod %d: vertexes: %d (%d new)\n", nLodID, vertexDict.VertexCount(), nNewVertsCreated);
+ }
+}
+
+static void PrintSourceVerts( s_source_t *pSrc )
+{
+ int nVertexCount = pSrc->m_GlobalVertices.Count();
+ for( int i = 0; i < nVertexCount; i++ )
+ {
+ const s_vertexinfo_t &srcVertex = pSrc->m_GlobalVertices[i];
+ printf( "v %d ", i );
+ printf( "pos: %f %f %f ", srcVertex.position[0], srcVertex.position[1], srcVertex.position[2] );
+ printf( "norm: %f %f %f ", srcVertex.normal[0], srcVertex.normal[1], srcVertex.normal[2] );
+ printf( "texcoord: %f %f\n", srcVertex.texcoord[0], srcVertex.texcoord[1] );
+ int j;
+ for( j = 0; j < srcVertex.boneweight.numbones; j++ )
+ {
+ printf( "\t%d: %d %f\n", j, ( int )srcVertex.boneweight.bone[j],
+ srcVertex.boneweight.weight[j] );
+ }
+ fflush( stdout );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Copy the vertex dictionary to the finalized processed data
+// Leaves the source data intact, necessary for later processes.
+// Routines can then choose which data they operate on
+//-----------------------------------------------------------------------------
+static void SetProcessedWithDictionary( s_model_t* pSrcModel, CVertexDictionary &vertexDict,
+ CUtlVector<s_face_t> &faces, CUtlVector<s_mesh_t> &meshes, int *pMeshVertIndexMaps[MAX_NUM_LODS] )
+{
+ int i;
+
+ s_loddata_t *pLodData = new s_loddata_t;
+ memset( pLodData, 0, sizeof(s_loddata_t) );
+
+ pSrcModel->m_pLodData = pLodData;
+
+ int nVertexCount = vertexDict.VertexCount();
+
+ pLodData->vertex = (s_lodvertexinfo_t *)kalloc( nVertexCount, sizeof( s_lodvertexinfo_t ) );
+ pLodData->numvertices = nVertexCount;
+ pLodData->face = (s_face_t *)kalloc( faces.Count(), sizeof( s_face_t ));
+ pLodData->numfaces = faces.Count();
+
+ for ( i = 0; i < nVertexCount; ++i )
+ {
+ const VertexInfo_t &srcVertex = vertexDict.Vertex( i );
+ s_lodvertexinfo_t &dstVertex = pLodData->vertex[i];
+
+ dstVertex.boneweight = srcVertex.m_BoneWeight;
+ Assert( dstVertex.boneweight.numbones <= 4 );
+ dstVertex.position = srcVertex.m_Position;
+ dstVertex.normal = srcVertex.m_Normal;
+ dstVertex.texcoord = srcVertex.m_TexCoord;
+ dstVertex.tangentS = srcVertex.m_TangentS;
+ dstVertex.lodFlag = srcVertex.m_nLodFlag;
+ }
+
+ memcpy( pLodData->face, faces.Base(), faces.Count() * sizeof( s_face_t ) );
+ memcpy( pLodData->mesh, meshes.Base(), meshes.Count() * sizeof( s_mesh_t ) );
+
+ for (i=0; i<MAX_NUM_LODS; i++)
+ {
+ pLodData->pMeshVertIndexMaps[i] = pMeshVertIndexMaps[i];
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// This fills out boneMap, which is a mapping from src bone to src bone replacement (or to itself
+// if there is no bone replacement.
+//-----------------------------------------------------------------------------
+static void BuildBoneLODMapping( CUtlVector<int> &boneMap, int lodID )
+{
+ boneMap.AddMultipleToTail( g_numbones );
+
+ Assert( lodID < g_ScriptLODs.Size() );
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+
+ // First, create a direct mapping where no bones are collapsed
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ boneMap[i] = i;
+ }
+
+ for( i = 0; i < scriptLOD.boneReplacements.Size(); i++ )
+ {
+ const char *src, *dst;
+ src = scriptLOD.boneReplacements[i].GetSrcName();
+ dst = scriptLOD.boneReplacements[i].GetDstName();
+ int j = findGlobalBone( src );
+ int k = findGlobalBone( dst );
+
+ if ( j != -1 && k != -1)
+ {
+ boneMap[j] = k;
+ }
+ else if ( j == -1)
+ {
+ // FIXME: is this really an error? It could just be replacement command for bone that doesnt' exist anymore.
+ if (g_verbose)
+ {
+ MdlWarning( "Couldn't replace unknown bone \"%s\" with \"%s\"\n", src, dst );
+ }
+ }
+ else
+ {
+ // FIXME: is this really an error? It could just be replacement command for bone that doesnt' exist anymore.
+ if (g_verbose)
+ {
+ MdlWarning( "Couldn't replace bone \"%s\" with unknown \"%s\"\n", src, dst );
+ }
+ }
+ }
+}
+
+static void MarkRootLODBones( CVertexDictionary &vertexDictionary )
+{
+ // should result in an identity mapping
+ // because their are no bone remaps at the root lod
+ CUtlVector<int> boneMap;
+ BuildBoneLODMapping( boneMap, 0 );
+
+ // iterate and mark bones
+ for (int nVertDictID=vertexDictionary.RootLODVertexStart(); nVertDictID<vertexDictionary.RootLODVertexEnd(); nVertDictID++)
+ {
+ s_boneweight_t &boneWeight = vertexDictionary.Vertex( nVertDictID ).m_BoneWeight;
+
+ RemapBoneWeights( boneMap, boneWeight );
+ CollapseBoneWeights( boneWeight );
+ SortBoneWeightByWeight( boneWeight );
+
+ MarkBonesUsedByLod( boneWeight, 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes LOD vertices for a model piece.
+//-----------------------------------------------------------------------------
+static void UnifyModelLODs( s_model_t *pSrcModel )
+{
+ if ( !Q_stricmp( pSrcModel->name, "blank" ) )
+ return;
+
+ // each lod has a unique vertex mapping table
+ int nNumLODs = pSrcModel->m_LodSources.Count();
+ int nLodID;
+ int *pMeshVertIndexMaps[MAX_NUM_LODS];
+ for ( nLodID = 0; nLodID < MAX_NUM_LODS; nLodID++ )
+ {
+ if ( nLodID < nNumLODs && pSrcModel->m_LodSources[nLodID] )
+ {
+ int nVertexCount = pSrcModel->m_LodSources[nLodID]->m_GlobalVertices.Count();
+ pMeshVertIndexMaps[nLodID] = new int[ nVertexCount ];
+#ifdef _DEBUG
+ memset( pMeshVertIndexMaps[nLodID], 0xDD, nVertexCount * sizeof(int) );
+#endif
+ }
+ else
+ {
+ pMeshVertIndexMaps[nLodID] = NULL;
+ }
+ }
+
+ // These hold the aggregate data for the model that grows as lods are processed
+ CVertexDictionary vertexDictionary;
+ CUtlVector<s_face_t> faces;
+ CUtlVector<s_mesh_t> meshes;
+
+ meshes.AddMultipleToTail( MAXSTUDIOSKINS );
+ Assert( meshes.Count() == MAXSTUDIOSKINS );
+ memset( meshes.Base(), 0, meshes.Count() * sizeof( s_mesh_t ) );
+
+ int nMeshID;
+ for( nMeshID = 0; nMeshID < pSrcModel->source->nummeshes; nMeshID++ )
+ {
+ s_mesh_t *pVertexDictMesh = &meshes[pSrcModel->source->meshindex[nMeshID]];
+
+ pVertexDictMesh->numvertices = 0;
+ pVertexDictMesh->vertexoffset = vertexDictionary.VertexCount();
+ pVertexDictMesh->numfaces = 0;
+ pVertexDictMesh->faceoffset = faces.Count();
+
+ // First build up information for LOD 0
+ if ( !pSrcModel->m_LodSources[0] )
+ continue;
+
+ s_source_t *pLOD0Source = pSrcModel->m_LodSources[0];
+
+ // lookup the material used by this mesh
+ int nMaterialID = pLOD0Source->meshindex[nMeshID];
+ s_mesh_t *pLOD0Mesh = FindMeshByMaterial( pLOD0Source, nMaterialID );
+ if ( !pLOD0Mesh )
+ continue;
+
+ // populate with all vertices from LOD 0
+ int nStart = vertexDictionary.VertexCount();
+ CopyVerts( 0, pLOD0Source, pLOD0Mesh, vertexDictionary, pVertexDictMesh, pMeshVertIndexMaps[0] );
+ vertexDictionary.SetRootVertexRange( nStart, vertexDictionary.VertexCount() );
+
+ MarkRootLODBones( vertexDictionary );
+
+ // only fix up the faces for the highest lod since the lowest ones are going
+ // to be reprocessed later.
+ CopyFaces( pLOD0Source, pLOD0Mesh, faces, pVertexDictMesh );
+
+ // Now, for each LOD, try to build meshes using the vertices in LOD 0.
+ // Ideally, vertices used in an LOD would be in LOD 0 for the benefit of shared vertices.
+ // If we don't find vertices in LOD 0, this code will add vertices into LOD 0's list
+ // of vertices for the next LOD to find
+ for ( nLodID = 1; nLodID < nNumLODs; ++nLodID )
+ {
+ s_source_t *pCurrLOD = pSrcModel->m_LodSources[nLodID];
+ if ( !pCurrLOD )
+ continue;
+
+ // Find the mesh that matches the material
+ // mesh may not be present or could be culled due to $removemesh commands
+ s_mesh_t *pCurrLODMesh = FindOrCullMesh( nLodID, pCurrLOD, nMaterialID );
+ if ( !pCurrLODMesh )
+ continue;
+
+ CreateLODVertsInDictionary( nLodID, pLOD0Source, pCurrLOD, pCurrLODMesh, pVertexDictMesh, vertexDictionary, pMeshVertIndexMaps[nLodID]);
+ }
+ }
+
+#ifdef _DEBUG
+ Msg( "Total vertex count: %d\n", vertexDictionary.VertexCount() );
+#endif
+
+ // save the data we just built into the processed data section
+ // The processed data has all of the verts that are needed for all LODs.
+ SetProcessedWithDictionary( pSrcModel, vertexDictionary, faces, meshes, pMeshVertIndexMaps );
+// PrintSourceVerts( pSrcModel->m_LodModels[0] );
+}
+
+
+//-----------------------------------------------------------------------------
+// Force the vertex array for a model to have all of the vertices that are needed
+// for all of the LODs of the model.
+//-----------------------------------------------------------------------------
+void UnifyLODs( void )
+{
+ // todo: need to fixup the firstref/lastref stuff . . do we really need it anymore?
+ for( int modelID = 0; modelID < g_nummodelsbeforeLOD; modelID++ )
+ {
+ UnifyModelLODs( g_model[modelID] );
+ }
+}
+
+
+static void PrintSpaces( int numSpaces )
+{
+ int i;
+ for( i = 0; i < numSpaces; i++ )
+ {
+ printf( " " );
+ }
+}
+
+static void SpewBoneInfo( int globalBoneID, int depth )
+{
+ s_bonetable_t *pBone = &g_bonetable[globalBoneID];
+ if( g_bPrintBones )
+ {
+ PrintSpaces( depth * 2 );
+ printf( "%d \"%s\" ", depth, pBone->name );
+ }
+ int i;
+ for( i = 0; i < 8; i++ )
+ {
+ if( pBone->flags & ( BONE_USED_BY_VERTEX_LOD0 << i ) )
+ {
+ if( g_bPrintBones )
+ {
+ printf( "lod%d ", i );
+ }
+ g_NumBonesInLOD[i]++;
+ }
+ }
+ if( g_bPrintBones )
+ {
+ printf( "\n" );
+ }
+
+ int j;
+ for( j = 0; j < g_numbones; j++ )
+ {
+ s_bonetable_t *pBone = &g_bonetable[j];
+ if( pBone->parent == globalBoneID )
+ {
+ SpewBoneInfo( j, depth + 1 );
+ }
+ }
+}
+
+void SpewBoneUsageStats( void )
+{
+ memset( g_NumBonesInLOD, 0, sizeof( int ) * MAX_NUM_LODS );
+ if( g_numbones == 0 )
+ {
+ return;
+ }
+ SpewBoneInfo( 0, 0 );
+ if( g_bPrintBones )
+ {
+ int i;
+ for( i = 0; i < g_ScriptLODs.Count(); i++ )
+ {
+ printf( "\t%d bones used in lod %d\n", g_NumBonesInLOD[i], i );
+ }
+ }
+}
+
+void MarkParentBoneLODs( void )
+{
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ int flags = g_bonetable[i].flags;
+ flags &= BONE_USED_BY_VERTEX_MASK;
+ int globalBoneID = g_bonetable[i].parent;
+ while( globalBoneID != -1 )
+ {
+ g_bonetable[globalBoneID].flags |= flags;
+ globalBoneID = g_bonetable[globalBoneID].parent;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the sources associated with the various LODs based on the script commands
+//-----------------------------------------------------------------------------
+static void GetLODSources( CUtlVector< s_source_t * > &lods, const s_model_t *pSrcModel )
+{
+ int nNumLODs = g_ScriptLODs.Count();
+ lods.EnsureCount( nNumLODs );
+ for( int lodID = 0; lodID < nNumLODs; lodID++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+
+ bool bFound;
+ s_source_t* pSource = GetModelLODSource( pSrcModel->filename, scriptLOD, &bFound );
+ if ( !pSource && !bFound )
+ {
+ pSource = pSrcModel->source;
+ }
+
+ lods[lodID] = pSource;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates models to store converted data for the various LODs
+//-----------------------------------------------------------------------------
+void LoadLODSources( void )
+{
+ g_nummodelsbeforeLOD = g_nummodels;
+ for( int modelID = 0; modelID < g_nummodelsbeforeLOD; modelID++ )
+ {
+ if ( !Q_stricmp( g_model[modelID]->name, "blank" ) )
+ {
+ int nNumLODs = g_ScriptLODs.Count();
+ g_model[modelID]->m_LodSources.SetCount( nNumLODs );
+ for ( int i = 0; i < nNumLODs; ++i )
+ {
+ g_model[modelID]->m_LodSources[i] = NULL;
+ }
+ continue;
+ }
+
+ GetLODSources( g_model[modelID]->m_LodSources, g_model[modelID] );
+ }
+}
+
+static void ReplaceBonesRecursive( int globalBoneID, bool replaceThis,
+ CUtlVector<CLodScriptReplacement_t> &boneReplacements,
+ const char *replacementName )
+{
+ if( replaceThis )
+ {
+ CLodScriptReplacement_t &boneReplacement = boneReplacements[boneReplacements.AddToTail()];
+ boneReplacement.SetSrcName( g_bonetable[globalBoneID].name );
+ boneReplacement.SetDstName( replacementName );
+ }
+
+ // find children and recurse.
+ int i;
+ for( i = 0; i < g_numbones; i++ )
+ {
+ if( g_bonetable[i].parent == globalBoneID )
+ {
+ ReplaceBonesRecursive( i, true, boneReplacements, replacementName );
+ }
+ }
+}
+
+static void ConvertSingleBoneTreeCollapseToReplaceBones( CLodScriptReplacement_t &boneTreeCollapse,
+ CUtlVector<CLodScriptReplacement_t> &boneReplacements )
+{
+ // find the bone that we are starting with.
+ int i = findGlobalBone( boneTreeCollapse.GetSrcName() );
+ if (i != -1)
+ {
+ ReplaceBonesRecursive( i, false, boneReplacements, g_bonetable[i].name );
+ return;
+ }
+ MdlWarning( "Couldn't find bone %s for bonetreecollapse, skipping\n", boneTreeCollapse.GetSrcName() );
+}
+
+void ConvertBoneTreeCollapsesToReplaceBones( void )
+{
+ int i;
+ for( i = 0; i < g_ScriptLODs.Size(); i++ )
+ {
+ LodScriptData_t& lod = g_ScriptLODs[i];
+ int j;
+ for( j = 0; j < lod.boneTreeCollapses.Size(); j++ )
+ {
+ ConvertSingleBoneTreeCollapseToReplaceBones( lod.boneTreeCollapses[j],
+ lod.boneReplacements );
+ }
+ }
+}
+
+/*
+static void PrintReplacedBones( LodScriptData_t &lod )
+{
+ int i;
+ for( i = 0; i < lod.boneReplacements.Count(); i++ )
+ {
+ printf( "%s -> %s\n",
+ lod.boneReplacements[i].GetSrcName(),
+ lod.boneReplacements[i].GetDstName() );
+ }
+}
+*/
+
+void FixupReplacedBonesForLOD( LodScriptData_t &lod )
+{
+/*
+ printf( "before:\n" );
+ PrintReplacedBones( lod );
+*/
+ bool changed;
+ int i;
+ int j;
+ do
+ {
+ changed = false;
+ for( i = 0; i < lod.boneReplacements.Count(); i++ )
+ {
+ for( j = 0; j < lod.boneReplacements.Count(); j++ )
+ {
+ if( i == j )
+ {
+ continue;
+ }
+ if( Q_stricmp( lod.boneReplacements[i].GetSrcName(), lod.boneReplacements[j].GetDstName() ) == 0 )
+ {
+ lod.boneReplacements[j].SetDstName( lod.boneReplacements[i].GetDstName() );
+ changed = true;
+ }
+ }
+ }
+ } while( changed );
+/*
+ printf( "after:\n" );
+ PrintReplacedBones( lod );
+*/
+}
+
+void FixupReplacedBones( void )
+{
+ int i;
+ for( i = 0; i < g_ScriptLODs.Size(); i++ )
+ {
+ FixupReplacedBonesForLOD( g_ScriptLODs[i] );
+ }
+}