You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1631 lines
48 KiB
1631 lines
48 KiB
//========= 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] ); |
|
} |
|
}
|
|
|