summaryrefslogtreecommitdiff
path: root/utils/studiomdl/optimize.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'utils/studiomdl/optimize.cpp')
-rw-r--r--utils/studiomdl/optimize.cpp4017
1 files changed, 4017 insertions, 0 deletions
diff --git a/utils/studiomdl/optimize.cpp b/utils/studiomdl/optimize.cpp
new file mode 100644
index 0000000..88f6d0f
--- /dev/null
+++ b/utils/studiomdl/optimize.cpp
@@ -0,0 +1,4017 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//=============================================================================//
+
+#pragma warning( disable : 4786 )
+
+#pragma warning( disable : 4748 ) // buffer overrun with optimizations off
+// This file has tons of problems with global optimizations. . turn 'em off.
+// NOTE: Would be nice to have a test case for this! - not verified in vs2005
+#pragma optimize( "g", off )
+
+// use this much memory to build an output file in memory.
+#define FILEBUFFER_SIZE ( 4 * 1024 * 1024 )
+
+//#define IGNORE_BONES
+
+#define NVTRISTRIP
+
+#define EMIT_TRILISTS
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <float.h>
+#include "mathlib/mathlib.h"
+#include "cmdlib.h"
+#include "studio.h"
+#include "studiomdl.h"
+#include "HardwareMatrixState.h"
+#include "HardwareVertexCache.h"
+#include "optimize.h"
+#include <malloc.h>
+#include <nvtristrip.h>
+#include "FileBuffer.h"
+#include "tier1/utlvector.h"
+#include "materialsystem/imaterial.h"
+#include "tier1/utllinkedlist.h"
+
+#include "tier1/smartptr.h"
+#include "tier2/p4helpers.h"
+
+bool g_bDumpGLViewFiles;
+extern bool g_IHVTest;
+
+// flush bones between strips rather than deallocating the LRU.
+#define USE_FLUSH
+
+extern int FindMaterialByName( const char *pMaterialName );
+
+namespace OptimizedModel
+{
+
+//-----------------------------------------------------------------------------
+// Defines which debugging output file we will see
+//-----------------------------------------------------------------------------
+
+enum
+{
+ WRITEGLVIEW_SHOWMESH = 0x00000001,
+ WRITEGLVIEW_SHOWSTRIPGROUP = 0x00000002,
+ WRITEGLVIEW_SHOWSTRIP = 0x00000004,
+ WRITEGLVIEW_SHOWSUBSTRIP = 0x00000008,
+ WRITEGLVIEW_SHOWFLEXED = 0x00000010,
+ WRITEGLVIEW_SHOWSW = 0x00000020,
+ WRITEGLVIEW_SHOWMESHPROPS = 0x00000040,
+ WRITEGLVIEW_SHOWVERTNUMBONES = 0x00000080,
+ WRITEGLVIEW_SHOWSTRIPNUMBONES = 0x00000100,
+ WRITEGLVIEW_FORCEUINT = 0xffffffff
+};
+
+//-----------------------------------------------------------------------------
+// This is used to help us figure out where in the file data should go
+// and also to display stats
+//-----------------------------------------------------------------------------
+struct TotalMeshStats_t
+{
+ int m_TotalBodyParts;
+ int m_TotalModels;
+ int m_TotalModelLODs;
+ int m_TotalMeshes;
+ int m_TotalStrips;
+ int m_TotalStripGroups;
+ int m_TotalVerts;
+ int m_TotalIndices;
+ int m_TotalBoneStateChanges;
+ int m_TotalMaterialReplacements;
+};
+
+struct Triangle_t
+{
+ Triangle_t()
+ {
+ touched = false;
+ neighborTriID[0] = neighborTriID[1] = neighborTriID[2] = -1;
+ }
+ int vertID[3];
+ int neighborTriID[3];
+ int boneID[MAX_NUM_BONES_PER_TRI];
+ int numBones;
+ bool touched;
+};
+
+
+//-----------------------------------------------------------------------------
+// Associates software bone indices with hardware bone indices
+//-----------------------------------------------------------------------------
+struct BoneStateChange_t
+{
+ int hardwareID;
+ int newBoneID;
+};
+
+struct Strip_t
+{
+ // these are the verts and indices that are used while building.
+ // (there may be verts in here that aren't use in the stripGroup.)
+ CUtlVector<Vertex_t> verts;
+ int numIndices;
+ unsigned short *pIndices;
+
+ unsigned int flags;
+
+ int numBones;
+
+ // These are the final, sorted verts as they appear in the strip group.
+ int stripGroupIndexOffset; // offset into stripGroup's indices
+ int numStripGroupIndices;
+ int stripGroupVertexOffset; // offset into stripGroup's verts
+ int numStripGroupVerts;
+
+ int numBoneStateChanges;
+ BoneStateChange_t boneStateChanges[MAX_NUM_BONES_PER_STRIP];
+};
+
+
+//-----------------------------------------------------------------------------
+// a list of vertices + triangles for a strip group
+//-----------------------------------------------------------------------------
+
+typedef CUtlVector<Vertex_t> VertexList_t;
+typedef CUtlVector<Triangle_t> TriangleList_t;
+typedef CUtlVector<unsigned short> VertexIndexList_t;
+typedef CUtlVector<Strip_t> StripList_t;
+typedef CUtlVector<bool> TriangleProcessedList_t;
+
+//-----------------------------------------------------------------------------
+// String table
+//-----------------------------------------------------------------------------
+class CStringTable
+{
+public:
+ int StringTableOffset( const char *string )
+ {
+ int i;
+ int size = 0;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ if( stricmp( m_Strings[i].Base(), string ) == 0 )
+ {
+ return size;
+ }
+ size += m_Strings[i].Size();
+ }
+ return -1;
+ }
+ bool StringPresent( const char *string )
+ {
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ if( stricmp( m_Strings[i].Base(), string ) == 0 )
+ {
+ return true;
+ }
+ }
+ return false;
+ }
+ void AddString( const char *newString )
+ {
+ if( StringPresent( newString ) )
+ {
+ return;
+ }
+ CUtlVector<char> &s = m_Strings[m_Strings.AddToTail()];
+ int size = strlen( newString ) + 1;
+ s.AddMultipleToTail( size );
+ strcpy( s.Base(), newString );
+ }
+ void Purge()
+ {
+ m_Strings.Purge();
+ }
+ int CalcSize( void )
+ {
+ int size = 0;
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+ size += m_Strings[i].Size();
+ }
+ return size;
+ }
+ void WriteToMem( char *pDst )
+ {
+ int size = 0;
+ int i;
+ for( i = 0; i < m_Strings.Size(); i++ )
+ {
+#ifdef _DEBUG
+ int j = Q_strlen( m_Strings[i].Base() ) + 1;
+ int k = m_Strings[i].Size();
+ Assert( j == k );
+#endif
+ memcpy( pDst + size, m_Strings[i].Base(), m_Strings[i].Size() );
+ size += m_Strings[i].Size();
+ }
+ }
+private:
+ typedef CUtlVector<char> CharVector_t;
+ CUtlVector<CharVector_t> m_Strings;
+};
+
+// global string table for the whole vtx file.
+static CStringTable s_StringTable;
+
+//-----------------------------------------------------------------------------
+// This is all the indices, vertices, and strips that make up this group
+// a group can be rendered all in one call to the material system
+//-----------------------------------------------------------------------------
+
+struct StripGroup_t
+{
+ VertexIndexList_t indices;
+ VertexList_t verts;
+ StripList_t strips;
+ unsigned int flags;
+};
+
+struct Mesh_t
+{
+ CUtlVector<StripGroup_t> stripGroups;
+ unsigned int flags;
+};
+
+struct ModelLOD_t
+{
+ CUtlVector<Mesh_t> meshes;
+ float switchPoint;
+};
+
+struct Model_t
+{
+ CUtlVector<ModelLOD_t> modelLODs;
+};
+
+//-----------------------------------------------------------------------------
+// Main class that does all the dirty work to stripy + groupify
+//-----------------------------------------------------------------------------
+
+class COptimizedModel
+{
+public:
+ bool OptimizeFromStudioHdr( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts, int vertCacheSize,
+ bool usesFixedFunction, bool bForceSoftwareSkin, bool bHWFlex, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName, const char *glViewFileName );
+
+private:
+ void CleanupEverything();
+
+ // Setup to get the ball rolling
+ void SetupMeshProcessing( studiohdr_t *phdr, int vertCacheSize,
+ bool usesFixedFunction, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName );
+
+ //
+ // Methods associated with pre-processing the mesh
+ //
+
+ //
+ // Methods associated with mesh processing
+ //
+
+ void SourceMeshToTriangleList( s_model_t *pSrcModel, s_mesh_t *pSrcMesh, CUtlVector<mstudioiface_t> &meshTriangleList );
+ void CreateLODTriangleList( s_model_t *pSrcModel, int nLodID, s_source_t* pLODSource,
+ mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList, bool writeDebug );
+
+ // This processes the model + breaks it into strips
+ void ProcessModel( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts, TotalMeshStats_t& stats,
+ bool bForceSoftwareSkin, bool bHWFlex );
+
+ // processes a single mesh within the model
+ void ProcessMesh( Mesh_t *pMesh, studiohdr_t *pStudioHeader, CUtlVector<mstudioiface_t> &srcFaces,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, bool ForceNoFlex,
+ bool bForceSoftwareSkin, bool bHWFlex );
+
+ // Processes a single strip group
+ void ProcessStripGroup( StripGroup_t *pStripGroup, bool isHWSkinned, bool isFlexed,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ TriangleProcessedList_t& trianglesProcessed,
+ int maxBonesPerVert, int maxBonesPerTri, int maxBonesPerStrip,
+ bool forceNoFlex, bool bHWFlex );
+
+ // Constructs vertices appropriate for a strip group based on source face data
+ bool GenerateStripGroupVerticesFromFace( mstudioiface_t* pFace,
+ mstudiomesh_t *pStudioMesh, int maxPreferredBones, Vertex_t* pStripGroupVert );
+
+ // Count the number of unique bones in a set of vertices
+ int CountUniqueBones( int count, Vertex_t *pVertex ) const;
+ int CountMaxVertBones( int count, Vertex_t *pVertex ) const;
+
+ // Counts the unique # of bones in a strip
+ int CountUniqueBonesInStrip( StripGroup_t *pStripGroup, Strip_t *pStrip );
+
+ // Builds SW + HW skinned strips
+ void BuildSWSkinnedStrips( TriangleList_t& tris,
+ VertexList_t const& verts, StripGroup_t *pStripGroup );
+ void BuildHWSkinnedStrips( TriangleList_t& tris, VertexList_t& verts,
+ StripGroup_t *pStripGroup, int maxBonesPerStrip );
+
+ // These methods deal with finding another triangle to batch together
+ // in a similar matrix state group
+ int ComputeNewBonesNeeded( Triangle_t const& triangle ) const;
+ bool AllocateHardwareBonesForTriangle( Triangle_t *tri );
+ Triangle_t* GetNextTriangle( TriangleList_t& triangles, bool allowNewStrip );
+ Triangle_t* GetNextUntouchedWithoutBoneStateChange( TriangleList_t& triangles );
+ Triangle_t* GetNextUntouchedWithLeastBoneStateChanges( TriangleList_t& triangles );
+
+ // Actually does the stripification
+ void Stripify( VertexIndexList_t const& sourceIndices, bool isHWSkinned,
+ int* pNumIndices, unsigned short** ppIndices );
+
+ // Makes sure our vertices are using the correct bones
+ void SanityCheckVertBones( VertexIndexList_t const& list, VertexList_t const& vertices );
+
+ // Sets the flags associated with a particular strip group + mesh
+ void ComputeStripGroupFlags( StripGroup_t *pStripGroup, bool isHWSkinned, bool isFlexed );
+ void ComputeMeshFlags( Mesh_t *pMesh, studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh );
+
+ bool MeshIsTeeth( studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh );
+
+ // Tries to add neighboring vertices that'll fit into the matrix transform state
+ void BuildStripsRecursive( VertexIndexList_t& indices, TriangleList_t& list, Triangle_t *triangle );
+
+ // Figures out all bones affecting a particular triangle
+ void BuildTriangleBoneData( VertexList_t& list, Triangle_t& tri );
+
+ // Memory optimize the strip data
+ void PostProcessStripGroup( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, StripGroup_t *pStripGroup );
+
+ void COptimizedModel::ZeroNumBones( void );
+
+ //
+ // Methods associated with writing VTX files
+ //
+
+ // This writes the strip data out to a VTX file
+ void WriteVTXFile( studiohdr_t *pHdr, char const* pFileName,
+ TotalMeshStats_t const& stats );
+
+
+ //
+ // Methods associated with writing GL View files
+ //
+
+
+ // This writes the GL debugging files
+ void WriteGLViewFiles( studiohdr_t *pHdr, const char *glViewFileName );
+
+ void OutputMemoryUsage( void );
+ bool IsVertexFlexed( mstudiomesh_t *pStudioMesh, int vertID ) const;
+ void BuildNeighborInfo( TriangleList_t& list, int nMaxVertexId );
+ void ClearTouched( void );
+ void PrintVert( Vertex_t *v, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh );
+ void SanityCheckAgainstStudioHDR( studiohdr_t *phdr );
+ void WriteStringTable( int stringTableOffset );
+ void WriteMaterialReplacements( int materialReplacementsOffset );
+ void WriteMaterialReplacementLists( int materialReplacementsOffset, int materialReplacementListOffset );
+ void WriteHeader( int vertCacheSize, int maxBonesPerVert,
+ int maxBonesPerTri, int maxBonesPerStrip, int numBodyParts, long checkSum );
+ void WriteBodyPart( int bodyPartID, mstudiobodyparts_t *pBodyPart, int modelID );
+ void WriteModel( int modelID, mstudiomodel_t *pModel, int lodID );
+ void WriteModelLOD( int lodID, ModelLOD_t *pLOD, int meshID );
+ void WriteMesh( int meshID, Mesh_t *pMesh, int stripGroupID );
+ void WriteStripGroup( int stripGroupID, StripGroup_t *pStripGroup,
+ int vertID, int indexID, int stripID );
+ int WriteVerts( int vertID, StripGroup_t *pStripGroup );
+ int WriteIndices( int indexID, StripGroup_t *pStripGroup );
+ void WriteStrip( int stripID, Strip_t *pStrip, int indexID, int vertID, int boneID );
+ void WriteBoneStateChange( int boneID, BoneStateChange_t *boneStateChange );
+
+ void DrawGLViewTriangle( FILE *fp, Vector& pos1, Vector& pos2, Vector& pos3,
+ Vector& color1, Vector& color2, Vector& color3 );
+ void WriteGLViewFile( studiohdr_t *phdr, const char *pFileName, unsigned int flags, float shrinkFactor );
+ void ShrinkVerts( float shrinkFactor );
+ void GLViewDrawBegin( int mode );
+ void CheckVertBoneWeights( Vertex_t *pVert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh );
+ void GLViewVert( FILE *fp, Vertex_t vert, int index, Vector& color, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, bool showSubStrips, float shrinkFraction );
+ void GLViewDrawEnd( void );
+ void SetMeshPropsColor( unsigned int meshFlags, Vector& color );
+ void SetFlexedAndSkinColor( unsigned int glViewFlags, unsigned int stripGroupFlags, Vector& color );
+ void SetColorFromNumVertexBones( int numBones, Vector& color );
+ Vector& GetOrigVertPosition( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert );
+ float GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ mstudioboneweight_t &GetOrigVertBoneWeight( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert );
+ int GetOrigVertBoneIndex( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ void ShowStats( void );
+ void MapGlobalBonesToHardwareBoneIDsAndSortBones( studiohdr_t *phdr );
+ void RemoveRedundantBoneStateChanges( void );
+ void CheckVert( Vertex_t *pVert, int maxBonesPerTri, int maxBonesPerVert );
+ void CheckAllVerts( int maxBonesPerTri, int maxBonesPerVert );
+ void SortBonesWithinVertex( bool flexed, Vertex_t *vert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, int *globalToHardwareBoneIndex, int *hardwareToGlobalBoneIndex, int maxBonesPerTri, int maxBonesPerVert );
+ int GetTotalVertsForMesh( Mesh_t *pMesh );
+ int GetTotalIndicesForMesh( Mesh_t *pMesh );
+ int GetTotalStripsForMesh( Mesh_t *pMesh );
+ int GetTotalStripGroupsForMesh( Mesh_t *pMesh );
+ int GetTotalBoneStateChangesForMesh( Mesh_t *pMesh );
+ bool MeshNeedsRemoval( studiohdr_t *pHdr, mstudiomesh_t *pStudioMesh,
+ LodScriptData_t& scriptLOD );
+
+ void PrintBoneStateChanges( studiohdr_t *pHdr, int lod );
+ void PrintVerts( studiohdr_t *phdr, int lod );
+ void SanityCheckVertexBoneLODFlags( studiohdr_t *pStudioHdr, FileHeader_t *pVtxHeader );
+
+ //
+ // SOURCE DATA
+ //
+
+ // These are all the built models
+ CUtlVector<Model_t> m_Models;
+
+ // total number of bones in the studio model
+ int m_NumBones;
+
+ // information about the hardware
+ int m_VertexCacheSize;
+ int m_MaxBonesPerTri;
+ int m_MaxBonesPerVert;
+ int m_MaxBonesPerStrip;
+ bool m_UsesFixedFunction;
+
+ // stats
+ int m_NumSkinnedAndFlexedVerts;
+
+ CHardwareMatrixState m_HardwareMatrixState;
+
+ // a place to stick file output.
+ CFileBuffer *m_FileBuffer;
+
+ // offset for different items in the output file.
+ int m_BodyPartsOffset;
+ int m_ModelsOffset;
+ int m_ModelLODsOffset;
+ int m_MeshesOffset;
+ int m_StripGroupsOffset;
+ int m_StripsOffset;
+ int m_VertsOffset;
+ int m_IndicesOffset;
+ int m_BoneStageChangesOffset;
+ int m_StringTableOffset;
+ int m_MaterialReplacementsOffset;
+ int m_MaterialReplacementsListOffset;
+ int m_EndOfFileOffset;
+};
+
+//-----------------------------------------------------------------------------
+// Singleton instance
+//-----------------------------------------------------------------------------
+
+static COptimizedModel s_OptimizedModel;
+
+
+//-----------------------------------------------------------------------------
+// Cleanup method
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::CleanupEverything()
+{
+}
+
+
+
+void COptimizedModel::OutputMemoryUsage( void )
+{
+ printf( "body parts: %7d bytes\n", ( int )( m_ModelsOffset - m_BodyPartsOffset ) );
+ printf( "models: %7d bytes\n", ( int )( m_MeshesOffset - m_ModelsOffset ) );
+ printf( "model LODs: %7d bytes\n", ( int )( m_MeshesOffset - m_ModelLODsOffset ) );
+ printf( "meshes: %7d bytes\n", ( int )( m_StripGroupsOffset - m_MeshesOffset ) );
+ printf( "strip groups: %7d bytes\n", ( int )( m_StripsOffset - m_StripGroupsOffset ) );
+ printf( "strips: %7d bytes\n", ( int )( m_VertsOffset - m_StripsOffset ) );
+ printf( "verts: %7d bytes\n", ( int )( m_IndicesOffset - m_VertsOffset ) );
+ printf( "indices: %7d bytes\n", ( int )( m_BoneStageChangesOffset - m_IndicesOffset ) );
+ printf( "bone changes: %7d bytes\n", ( int )( m_EndOfFileOffset - m_BoneStageChangesOffset ) );
+ printf( "everything: %7d bytes\n", ( int )( m_EndOfFileOffset ) );
+}
+
+void COptimizedModel::SanityCheckAgainstStudioHDR( studiohdr_t *phdr )
+{
+#if 0 // garymcthack
+ printf( "SanityCheckAgainstStudioHDR\n" );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ Assert( header->numBodyParts == phdr->numbodyparts );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ Assert( bodyPart->numModels == pStudioBodyPart->nummodels );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ Assert( model->numMeshes == pStudioModel->nummeshes );
+ for( int meshID = 0; meshID < model->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = model->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *stripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < stripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *strip = stripGroup->pStrip( stripID );
+ }
+ }
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are all related to creating groups of meshes to strip
+//
+//-----------------------------------------------------------------------------
+
+
+
+//-----------------------------------------------------------------------------
+// pick any triangle that hasn't been used yet to start with.
+//-----------------------------------------------------------------------------
+
+static Triangle_t *GetNextUntouched( TriangleList_t& triangles )
+{
+ int i;
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ if( !triangles[i].touched )
+ return &triangles[i];
+ }
+ return 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns the number of bones that are not represented in the current hardware state
+//-----------------------------------------------------------------------------
+
+inline int COptimizedModel::ComputeNewBonesNeeded( Triangle_t const& triangle ) const
+{
+ int numNewBones = 0;
+ for( int i = 0; i < triangle.numBones; ++i )
+ {
+ if( !m_HardwareMatrixState.IsMatrixAllocated( triangle.boneID[i] ) )
+ ++numNewBones;
+ }
+ return numNewBones;
+}
+
+
+//-----------------------------------------------------------------------------
+// returns triangle index
+// Find the next triangle without a bone state change
+// May add new hardware bones if there is space and is necessary.
+//-----------------------------------------------------------------------------
+
+Triangle_t* COptimizedModel::GetNextUntouchedWithoutBoneStateChange( TriangleList_t& triangles )
+{
+ Triangle_t *bestTriangle = 0;
+ int bestNumNewBones = MAX_NUM_BONES_PER_TRI + 1;
+
+ int i;
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ // We haven't processed this one, so let's try it
+ if( !triangles[i].touched )
+ {
+ // How many bones are not represented in the current state?
+ int numNewBones = ComputeNewBonesNeeded( triangles[i] );
+
+ // if this triangle fit and if it's the best so far, save it.
+ if ( (numNewBones <= m_HardwareMatrixState.FreeMatrixCount()) &&
+ (numNewBones < bestNumNewBones ) )
+ {
+ bestNumNewBones = numNewBones;
+ bestTriangle = &triangles[i];
+
+ // Can't get any better than this!
+ if (bestNumNewBones == 0)
+ break;
+ }
+ }
+ }
+ return bestTriangle;
+}
+
+
+//-----------------------------------------------------------------------------
+// This will returns the triangle that requires the last number of bone
+// state changes
+//-----------------------------------------------------------------------------
+
+Triangle_t *COptimizedModel::GetNextUntouchedWithLeastBoneStateChanges( TriangleList_t& triangles )
+{
+ Triangle_t *bestTriangle = 0;
+ int bestNumNewBones = MAX_NUM_BONES_PER_TRI + 1;
+ int i;
+
+ // For this one, just find the triangle that needs the least number
+ // of new bones. That way, we'll not have to change too many states
+ for( i = 0; i < triangles.Size(); i++ )
+ {
+ if( !triangles[i].touched )
+ {
+ int numNewBones = ComputeNewBonesNeeded( triangles[i] );
+
+ if( numNewBones < bestNumNewBones )
+ {
+ bestNumNewBones = numNewBones;
+ bestTriangle = &triangles[i];
+ }
+ }
+ }
+
+ // This only happens if there are no triangles untouched
+ if( !bestTriangle )
+ return 0;
+
+#ifdef USE_FLUSH
+ m_HardwareMatrixState.DeallocateAll();
+#else
+ // Remove bones until we have enough space...
+ int numToRemove = bestNumNewBones - m_HardwareMatrixState.FreeMatrixCount();
+ Assert( numToRemove > 0 );
+ m_HardwareMatrixState.DeallocateLRU(numToRemove);
+#endif
+
+ return bestTriangle;
+}
+
+
+//-----------------------------------------------------------------------------
+// Allocate bones for a triangle from the hardware matrix state
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::AllocateHardwareBonesForTriangle( Triangle_t *tri )
+{
+ for( int i = 0; i < tri->numBones; ++i )
+ {
+ int bone = tri->boneID[i];
+ if( !m_HardwareMatrixState.IsMatrixAllocated( bone ) )
+ {
+ if( !m_HardwareMatrixState.AllocateMatrix( bone ) )
+ return false;
+ }
+ }
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Get the next triangle.
+// Try to find one that doesn't cause a bone state change by jumping to
+// a new location in the model that works with the bones that we have allocated.
+// If we can't find one, and allowNewStrip is true, then flush and pick the next
+// best triangle that is close to the current hardware bone state.
+//-----------------------------------------------------------------------------
+
+Triangle_t *COptimizedModel::GetNextTriangle( TriangleList_t& tris, bool allowNewStrip )
+{
+ // First try to get a triangle that doesn't involve changing matrix state
+ Triangle_t *tri;
+ tri = GetNextUntouchedWithoutBoneStateChange( tris );
+
+ // If that didn't work, pick the triangle that changes the state the least
+ if( !tri && allowNewStrip )
+ {
+ tri = GetNextUntouchedWithLeastBoneStateChanges( tris );
+ }
+
+ // Return the triangle we found
+ return tri;
+}
+
+
+//-----------------------------------------------------------------------------
+// Make sure all vertices we've added up to now use bones in the matrix list
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::SanityCheckVertBones( VertexIndexList_t const& list, VertexList_t const& vertices )
+{
+#ifdef _DEBUG
+ Vertex_t const *pVert;
+ int i;
+ for( i = 0; i < list.Size(); i++ )
+ {
+ pVert = &vertices[list[i]];
+ if( !g_staticprop )
+ {
+ Assert( pVert->numBones != 0 );
+ }
+ int j;
+ for( j = 0; j < pVert->numBones; j++ )
+ {
+ if( pVert->boneID[j] == -1 )
+ {
+ continue;
+ }
+ if( !m_HardwareMatrixState.IsMatrixAllocated( pVert->boneID[j] ) )
+ {
+ Assert( 0 );
+ }
+ }
+ }
+#endif
+}
+
+
+//-----------------------------------------------------------------------------
+// Make sure all vertices we've added up to now use bones in the matrix list
+//-----------------------------------------------------------------------------
+void COptimizedModel::Stripify( VertexIndexList_t const& sourceIndices, bool isHWSkinned,
+ int* pNumIndices, unsigned short** ppIndices )
+{
+ if( sourceIndices.Size() == 0 )
+ {
+ *ppIndices = 0;
+ *pNumIndices = 0;
+ return;
+ }
+
+ // Skip the tristripping phase if we're building in preview mode
+ if ( g_bBuildPreview || isHWSkinned == false )
+ {
+ *pNumIndices = sourceIndices.Count();
+ *ppIndices = new unsigned short[*pNumIndices];
+ memcpy( *ppIndices, sourceIndices.Base(), (*pNumIndices) * sizeof(unsigned short) );
+ return;
+ }
+
+/*
+ printf( "Stripify\n" );
+ int i;
+ for( i = 0; i < sourceIndices.Size(); i++ )
+ {
+ printf( "stripindex: %d\n", sourceIndices[i] );
+ }
+*/
+
+#ifdef NVTRISTRIP
+ PrimitiveGroup *primGroups;
+ unsigned short numPrimGroups;
+
+ // Be sure to call delete[] on the returned primGroups to avoid leaking mem
+ GenerateStrips( &sourceIndices[0], sourceIndices.Size(),
+ &primGroups, &numPrimGroups );
+ Assert( numPrimGroups == 1 );
+ *pNumIndices = primGroups->numIndices;
+ *ppIndices = new unsigned short[*pNumIndices];
+ memcpy( *ppIndices, primGroups->indices, sizeof( unsigned short ) * *pNumIndices );
+ delete [] primGroups;
+#endif
+}
+
+//-----------------------------------------------------------------------------
+// eat up triangle recursively by flood-filling around the model until
+// we run out of bones on the hardware.
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildStripsRecursive( VertexIndexList_t& indices,
+ TriangleList_t& tris, Triangle_t *triangle )
+{
+ Assert( triangle );
+
+ // Don't process the triangle if it's already been processed
+ if( triangle->touched )
+ return;
+
+ // Only suck in triangles that need no state change
+ if ( ComputeNewBonesNeeded( *triangle ) )
+ return;
+
+ // We've got enough hardware bones. Lets add this triangle's vertices, and
+ // then add the vertices of all the neighboring triangles.
+ triangle->touched = true;
+
+ indices.AddToTail( ( unsigned short )triangle->vertID[0] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[1] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[2] );
+
+ // Try to add our neighbors
+ if( triangle->neighborTriID[0] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[0]] );
+ }
+ if( triangle->neighborTriID[1] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[1]] );
+ }
+ if( triangle->neighborTriID[2] != -1 )
+ {
+ BuildStripsRecursive( indices, tris, &tris[triangle->neighborTriID[2]] );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a HW-skinned strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildHWSkinnedStrips( TriangleList_t& triangles,
+ VertexList_t& vertices, StripGroup_t *pStripGroup, int maxBonesPerStrip )
+{
+ // Set up the hardware matrix state
+ m_HardwareMatrixState.Init( maxBonesPerStrip );
+
+ // Empty out the list of triangles to be stripified.
+ VertexIndexList_t trianglesToStrip;
+ trianglesToStrip.EnsureCapacity( triangles.Size() * 3 );
+
+ // pick any old unused triangle to start with.
+ Triangle_t *pSeedTri = GetNextUntouched( triangles );
+ while( pSeedTri )
+ {
+ // Make sure we've got out transforms allocated
+#ifdef DBGFLAG_ASSERT
+ bool ok =
+#endif
+ AllocateHardwareBonesForTriangle( pSeedTri );
+ Assert( ok );
+
+ // eat up triangle recursively by flood-filling around the model until
+ // we run out of bones on the hardware.
+ BuildStripsRecursive( trianglesToStrip, triangles, pSeedTri );
+
+ // Try to jump to a new location in the mesh without
+ // causing a hardware bone state overflow or flush.
+ pSeedTri = GetNextTriangle( triangles, false );
+ if( pSeedTri )
+ continue;
+
+ // Save the results of the generated strip.
+ int stripIdx = pStripGroup->strips.AddToTail( );
+ Strip_t& newStrip = pStripGroup->strips[stripIdx];
+
+ // Compute the strip flags
+ newStrip.flags = 0;
+#ifdef EMIT_TRILISTS
+ newStrip.flags |= STRIP_IS_TRILIST;
+#else
+ newStrip.flags |= STRIP_IS_TRISTRIP;
+#endif
+
+ // Sanity check the indices of the bones.
+ SanityCheckVertBones( trianglesToStrip, vertices );
+
+ // There are no more triangles to eat up without causing a flush, so
+ // go ahead and stripify what we have and flush.
+ // NOTE: This allocates space for stripIndices.pIndices.
+ Stripify( trianglesToStrip, true, &newStrip.numIndices, &newStrip.pIndices );
+
+ // hack - should just build directly into newStrip.verts instead of using a global.
+ int i;
+ for( i = 0; i < vertices.Size(); i++ )
+ {
+ newStrip.verts.AddToTail( vertices[i] );
+ }
+
+ // Compute the number of bones in this strip
+ newStrip.numBoneStateChanges = m_HardwareMatrixState.AllocatedMatrixCount();
+ Assert( newStrip.numBoneStateChanges <= maxBonesPerStrip );
+
+ // Save off the bones used for this strip.
+ for( i = 0; i < m_HardwareMatrixState.AllocatedMatrixCount(); i++ )
+ {
+ newStrip.boneStateChanges[i].hardwareID = i;
+ newStrip.boneStateChanges[i].newBoneID = m_HardwareMatrixState.GetNthBoneGlobalID( i );
+ }
+
+ // Empty out the triangles to strip so that we can start again with a new strip.
+ trianglesToStrip.RemoveAll();
+
+ // Get the next best triangle, allowing for a bone state flushes.
+ pSeedTri = GetNextTriangle( triangles, true );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a SW-skinned strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::BuildSWSkinnedStrips( TriangleList_t& triangles,
+ VertexList_t const& vertices, StripGroup_t *pStripGroup )
+{
+ // Save the results of the generated strip.
+ int stripIdx = pStripGroup->strips.AddToTail( );
+ Strip_t& newStrip = pStripGroup->strips[stripIdx];
+
+ // Set the strip flags
+ newStrip.flags = 0;
+#ifdef EMIT_TRILISTS
+ newStrip.flags |= STRIP_IS_TRILIST;
+#else
+ newStrip.flags |= STRIP_IS_TRISTRIP;
+#endif
+
+ int nTriangleCount = triangles.Count();
+
+ VertexIndexList_t indices;
+ indices.EnsureCapacity( nTriangleCount * 3 );
+
+ for( int i = 0; i < nTriangleCount; i++ )
+ {
+ Triangle_t* triangle = &triangles[i];
+ triangle->touched = true;
+ indices.AddToTail( ( unsigned short )triangle->vertID[0] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[1] );
+ indices.AddToTail( ( unsigned short )triangle->vertID[2] );
+ }
+
+ Stripify( indices, false, &newStrip.numIndices, &newStrip.pIndices );
+
+ // hack - should just build directly into newStrip.verts instead of using a global.
+ for( int i = 0; i < vertices.Size(); i++ )
+ {
+ newStrip.verts.AddToTail( vertices[i] );
+ }
+
+ newStrip.numBoneStateChanges = 0;
+ for( int i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ newStrip.boneStateChanges[i].hardwareID = -1;
+ newStrip.boneStateChanges[i].newBoneID = -1;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns true if a particular vertex is part of a flex
+//-----------------------------------------------------------------------------
+bool COptimizedModel::IsVertexFlexed( mstudiomesh_t *pStudioMesh, int vertID ) const
+{
+ mstudioflex_t *pflex = pStudioMesh->pFlex( 0 );
+
+ int i, j, n;
+
+ // Iterate through all the flexes
+ // figure out if the vertex is part of the flex
+ for (i = 0; i < pStudioMesh->numflexes; i++)
+ {
+ byte *pvanim = pflex[i].pBaseVertanim();
+ int nVAnimSizeBytes = pflex[i].VertAnimSizeBytes();
+
+ for (j = 0; j < pflex[i].numverts; j++, pvanim += nVAnimSizeBytes )
+ {
+ mstudiovertanim_t *pAnim = (mstudiovertanim_t*)( pvanim );
+
+ n = pAnim->index;
+ if ( n == vertID )
+ return true;
+ }
+ }
+ return false;
+}
+
+//-----------------------------------------------------------------------------
+// Computes flags for the strip group
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ComputeStripGroupFlags( StripGroup_t *pStripGroup,
+ bool isHWSkinned, bool isFlexed )
+{
+ pStripGroup->flags = 0;
+ if( isFlexed )
+ {
+ pStripGroup->flags |= STRIPGROUP_IS_FLEXED;
+ pStripGroup->flags |= STRIPGROUP_IS_DELTA_FLEXED; // Going forward, DX9 models are delta flexed
+ }
+ if( isHWSkinned )
+ {
+ pStripGroup->flags |= STRIPGROUP_IS_HWSKINNED;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Try to reduce the number of bones affecting this vert so hardware can deal
+//-----------------------------------------------------------------------------
+
+#define MIN_BONE_INFLUENCE 1.0f
+
+static void TryToReduceBoneInfluence( Vertex_t& stripGroupVert,
+ mstudioboneweight_t const& boneWeights, int maxBones )
+{
+ int i;
+ while ( stripGroupVert.numBones > maxBones)
+ {
+ // Find the minimum bone weight...
+ float minWeight = 2.0;
+ int minIndex = -1;
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; ++i )
+ {
+ if (stripGroupVert.boneID[i] != -1)
+ {
+ float weight = boneWeights.weight[ stripGroupVert.boneWeightIndex[i] ];
+ if (weight < minWeight)
+ {
+ minWeight = weight;
+ minIndex = i;
+ }
+ }
+ }
+ Assert( minIndex >= 0 );
+
+ // Now that we got it, remove that bone influence if it's small enough
+ if (minWeight >= MIN_BONE_INFLUENCE)
+ return;
+
+ // Blat out that bone!
+ for( i = minIndex; i < MAX_NUM_BONES_PER_VERT - 1; ++i )
+ {
+ stripGroupVert.boneID[i] = stripGroupVert.boneID[i+1];
+ stripGroupVert.boneWeightIndex[i] = stripGroupVert.boneWeightIndex[i+1];
+ }
+ stripGroupVert.boneID[ MAX_NUM_BONES_PER_VERT - 1] = -1;
+ stripGroupVert.boneWeightIndex[ MAX_NUM_BONES_PER_VERT - 1] = 0;
+
+ --stripGroupVert.numBones;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Generate three strip-group ready vertices from source mesh data
+// returns true if the face contants a flexed vertex
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::GenerateStripGroupVerticesFromFace( mstudioiface_t* pFace,
+ mstudiomesh_t *pStudioMesh,
+ int maxPreferredBones,
+ Vertex_t* pStripGroupVert )
+{
+ int vertIDs[3];
+ vertIDs[0] = pFace->a;
+ vertIDs[1] = pFace->b;
+ vertIDs[2] = pFace->c;
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+
+ bool triangleIsFlexed = false;
+ for( int faceIndex = 0; faceIndex < 3; ++faceIndex )
+ {
+ // Get the original source vertex id
+ int vertex = vertIDs[faceIndex];
+
+ // Check the verts of the triangle to see if they are flexed
+ triangleIsFlexed = triangleIsFlexed || IsVertexFlexed( pStudioMesh, vertex );
+
+ // How many bones affect this vertex
+ mstudioboneweight_t *pBoneWeight = vertData->BoneWeights(vertex);
+
+ int bonesAffectingVertex = pBoneWeight->numbones;
+ if( !g_staticprop && bonesAffectingVertex <= 0 )
+ {
+ MdlWarning( "too few bones/vert (%d) : it has no bones!\n", bonesAffectingVertex );
+ }
+ else if( bonesAffectingVertex > MAX_NUM_BONES_PER_VERT )
+ {
+ MdlError( "too many bones/vert (%d) : MAX_NUM_BONES_PER_VERT needs to be upped\n", bonesAffectingVertex );
+ }
+
+ // Set the fields of the strip group's vert
+ pStripGroupVert[faceIndex].origMeshVertID = vertex;
+
+ pStripGroupVert[faceIndex].numBones = bonesAffectingVertex;
+
+ int boneID;
+
+#ifndef IGNORE_BONES
+ for( boneID = 0; boneID < bonesAffectingVertex; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = pBoneWeight->bone[boneID];
+ pStripGroupVert[faceIndex].boneWeightIndex[boneID] = boneID;
+ }
+ for( ; boneID < MAX_NUM_BONES_PER_VERT; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = -1;
+ pStripGroupVert[faceIndex].boneWeightIndex[boneID] = boneID;
+ }
+#else
+ // don't let bones have any influence.
+ for( boneID = 0; boneID < MAX_NUM_BONES_PER_VERT; boneID++ )
+ {
+ pStripGroupVert[faceIndex].boneID[boneID] = -1;
+ pStripGroupVert[faceIndex].boneWeights[boneID] = 0;
+ }
+#endif
+
+ // For hardware skinning, we want to try to reduce the number of
+ // bone influences, which we'll do here if we can.
+ // At the moment, we can't do this for fixed function because
+ // we get seam problems at transitions between SW and HW rendered
+ if (!m_UsesFixedFunction)
+ {
+ if ((maxPreferredBones > 0) && (bonesAffectingVertex > maxPreferredBones))
+ {
+ TryToReduceBoneInfluence( pStripGroupVert[faceIndex], *pBoneWeight,
+ maxPreferredBones );
+ }
+ }
+ }
+
+ return triangleIsFlexed;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the unique number of bones in a specified set of vertices
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::CountUniqueBones( int count, Vertex_t *pVertex ) const
+{
+ int uniqueBoneCount = 0;
+ int uniqueBoneList[MAX_NUM_BONES_PER_STRIP];
+ while ( --count >= 0 )
+ {
+ for (int i = 0; i < pVertex[count].numBones; ++i)
+ {
+ int boneID = pVertex[count].boneID[i];
+ int j = uniqueBoneCount;
+ while ( --j >= 0 )
+ {
+ if (uniqueBoneList[j] == boneID)
+ break;
+ }
+
+ // Didn't find a match!
+ if (j < 0)
+ {
+ Assert( uniqueBoneCount < MAX_NUM_BONES_PER_STRIP );
+ uniqueBoneList[uniqueBoneCount++] = boneID;
+ }
+ }
+ }
+
+ return uniqueBoneCount;
+}
+
+int COptimizedModel::CountMaxVertBones( int count, Vertex_t *pVertex ) const
+{
+ int maxBones = 0;
+ while ( --count >= 0 )
+ {
+ if (maxBones < pVertex[count].numBones)
+ maxBones = pVertex[count].numBones;
+ }
+
+ return maxBones;
+}
+
+//-----------------------------------------------------------------------------
+// Adds a vertex to the list of vertices to be added to the strip group
+//-----------------------------------------------------------------------------
+
+static int FindOrCreateVertex( VertexList_t& list, Vertex_t const& vert )
+{
+ int i;
+ for( i = 0; i < list.Size(); i++ )
+ {
+ if (list[i].origMeshVertID == vert.origMeshVertID)
+ {
+ // If this is the case, then everything else should be too!
+ Assert( !memcmp( &list[i], &vert, sizeof( vert )) );
+ return i;
+ }
+ }
+
+ list.AddToTail( vert );
+ return i;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the bones used by the triangle
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::BuildTriangleBoneData( VertexList_t& list, Triangle_t& tri )
+{
+ int j, k, l;
+
+ // Blat out the bone ID state
+ tri.numBones = 0;
+ for( j = 0; j < MAX_NUM_BONES_PER_TRI; j++ )
+ {
+ tri.boneID[j] = -1;
+ }
+
+ // Iterate through the vertices in the triangle
+ for( j = 0; j < 3; j++ )
+ {
+ // Iterate over the bones influencing the vertex
+ Vertex_t& vert = list[tri.vertID[j]];
+ for( k = 0; k < vert.numBones; ++k )
+ {
+ int bone = vert.boneID[k];
+ Assert( (bone >= 0) && (bone < m_NumBones) );
+
+ // Look for matches with previously found bones
+ for ( l = tri.numBones; --l >= 0; )
+ {
+ if ( bone == tri.boneID[l] )
+ break;
+ }
+
+ // No match, add it to our list
+ if ( l < 0 )
+ tri.boneID[tri.numBones++] = bone;
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes neighboring triangles along each face of a triangle
+//-----------------------------------------------------------------------------
+struct EdgeInfo_t
+{
+ int m_nConnectedVertId;
+ int m_nEdgeIndex;
+ int m_nTriangleId;
+};
+
+static void FindMatchingEdge( TriangleList_t& list, int nTriangleId, int nEdgeIndex,
+ const int *pVertIds, CUtlVector< int >& vertexToEdges, CUtlFixedLinkedList< EdgeInfo_t >& edges )
+{
+ // Have we already attached this edge to something?
+ Triangle_t &tri = list[nTriangleId];
+ if ( tri.neighborTriID[nEdgeIndex] != -1 )
+ return;
+
+ int nVertIndex = ( pVertIds[0] < pVertIds[1] ) ? 0 : 1;
+ int nConnectedVertId = pVertIds[ 1-nVertIndex ];
+ int hFirstEdge = vertexToEdges[nVertIndex];
+ for ( int hEdge = hFirstEdge; hEdge != edges.InvalidIndex(); hEdge = edges.Next(hEdge) )
+ {
+ EdgeInfo_t &edge = edges[hEdge];
+ if ( edge.m_nConnectedVertId != nConnectedVertId )
+ continue;
+
+ // Can't attach triangles to themselves
+ if ( edge.m_nTriangleId == nTriangleId )
+ continue;
+
+ // Found a match! Mark the two triangles as sharing an edge
+ tri.neighborTriID[nEdgeIndex] = edge.m_nTriangleId;
+ list[ edge.m_nTriangleId ].neighborTriID[ edge.m_nEdgeIndex ] = nTriangleId;
+ if ( hEdge == hFirstEdge )
+ {
+ vertexToEdges[nVertIndex] = edges.Next( hFirstEdge );
+ }
+ edges.Free( hEdge );
+ return;
+ }
+
+ // No match! Insert the disconnected edge into the edge list
+ int hNewEdge = edges.Alloc( true );
+ EdgeInfo_t &newEdge = edges[hNewEdge];
+ newEdge.m_nConnectedVertId = nConnectedVertId;
+ newEdge.m_nEdgeIndex = nEdgeIndex;
+ newEdge.m_nTriangleId = nTriangleId;
+ edges.LinkBefore( hFirstEdge, hNewEdge );
+ vertexToEdges[nVertIndex] = hNewEdge;
+}
+
+void COptimizedModel::BuildNeighborInfo( TriangleList_t& list, int nMaxVertexId )
+{
+ // NOTE: vertexToEdges[vertId] contains the index of the head of a linked list stored in edges
+ CUtlFixedLinkedList< EdgeInfo_t > edges;
+ CUtlVector< int > vertexToEdges;
+ vertexToEdges.SetCount( nMaxVertexId );
+ memset( vertexToEdges.Base(), 0, nMaxVertexId * sizeof(int) );
+
+ int pEdgeVertIds[2];
+ int nTriCount = list.Count();
+ for ( int i = 0; i < nTriCount; ++i )
+ {
+ Triangle_t &tri = list[i];
+
+ // Add the three edges for this tri into a lookup table indexed by the lower vertID
+ pEdgeVertIds[0] = tri.vertID[0]; pEdgeVertIds[1] = tri.vertID[1];
+ FindMatchingEdge( list, i, 0, pEdgeVertIds, vertexToEdges, edges );
+
+ pEdgeVertIds[0] = tri.vertID[1]; pEdgeVertIds[1] = tri.vertID[2];
+ FindMatchingEdge( list, i, 1, pEdgeVertIds, vertexToEdges, edges );
+
+ pEdgeVertIds[0] = tri.vertID[2]; pEdgeVertIds[1] = tri.vertID[0];
+ FindMatchingEdge( list, i, 2, pEdgeVertIds, vertexToEdges, edges );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Processes a single strip group
+//-----------------------------------------------------------------------------
+void COptimizedModel::ProcessStripGroup( StripGroup_t *pStripGroup, bool isHWSkinned,
+ bool isFlexed, mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ TriangleProcessedList_t& trianglesProcessed,
+ int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, bool forceNoFlex, bool bHWFlex )
+{
+ ComputeStripGroupFlags( pStripGroup, isHWSkinned, isFlexed );
+
+ // all of the triangles before stripping for the current stripgroup.
+ TriangleList_t stripGroupSourceTriangles;
+ VertexList_t stripGroupVertices;
+
+ // FIXME: Flexed/HWSkinned state of faces don't change with each pass.
+ // We could precompute those flags just once (instead of doing it 4 times)
+
+ // Add each face to the stripgroup, if it's appropriate
+ for( int n=0; n < srcFaces.Size(); ++n )
+ {
+ // Don't bother processing a triangle that's already been done
+ if (trianglesProcessed[n])
+ continue;
+
+ mstudioiface_t *pFace = &srcFaces[n];
+
+ int preferredBones = isHWSkinned && (bHWFlex || !isFlexed) ? maxBonesPerVert : 0;
+ // start a new strip group header.
+ Vertex_t stripGroupVert[3];
+ bool triangleIsFlexed = GenerateStripGroupVerticesFromFace(
+ pFace, pStudioMesh,
+ preferredBones,
+ stripGroupVert );
+
+ if( forceNoFlex )
+ {
+ triangleIsFlexed = false;
+ }
+
+ // Don't bother to add the vertices to our strip-group vertex list
+ // if we don't need to.
+ if ( triangleIsFlexed != isFlexed )
+ continue;
+
+ // If we're doing software skinning, then always accept, even if
+ // this triangle should have been a HW skinned triangle.
+ if (isHWSkinned)
+ {
+ // Check how many unique bones were in that face, and how many vertices
+ // maximally in a vertex
+ int numVertexBones = CountMaxVertBones( 3, stripGroupVert );
+ int numTriangleBones = CountUniqueBones( 3, stripGroupVert );
+
+ // If we have too many, we'll be skinning in software
+ bool triangleIsHWSkinned = ( numTriangleBones <= maxBonesPerTri ) &&
+ ( numVertexBones <= maxBonesPerVert );
+
+ // Don't bother to add vertices which aren't gonna be used in this pass
+ if (!triangleIsHWSkinned)
+ continue;
+ }
+
+ // Add a new triangle to our list of triangles
+ int triIndex = stripGroupSourceTriangles.AddToTail( );
+
+ Triangle_t& newTri = stripGroupSourceTriangles[triIndex];
+ newTri.vertID[0] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[0] );
+ newTri.vertID[1] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[1] );
+ newTri.vertID[2] = FindOrCreateVertex( stripGroupVertices, stripGroupVert[2] );
+ BuildTriangleBoneData( stripGroupVertices, newTri );
+
+ // By default, this processes a triangle
+ // Later on below, we may decide to un-process triangles
+ // if tristrips that are too small are generated
+ trianglesProcessed[n] = true;
+ }
+
+ // No mesh? bye bye
+ if (stripGroupSourceTriangles.Size() == 0)
+ return;
+
+ // Figure out neighboring triangles
+ BuildNeighborInfo( stripGroupSourceTriangles, stripGroupVertices.Count() );
+
+ // Build the actual strips
+ if( isHWSkinned )
+ {
+ BuildHWSkinnedStrips( stripGroupSourceTriangles, stripGroupVertices, pStripGroup, maxBonesPerStrip );
+
+ // Check to see if any strips were produced that are too small
+ // If so, remove them, and let the software pass take care of them.
+ //if (stripGroupSourceTriangles.Size() < m_MinimumGroupSize
+ // trianglesProcessed[n] = false;
+ }
+ else
+ {
+ BuildSWSkinnedStrips( stripGroupSourceTriangles, stripGroupVertices, pStripGroup );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// How many unique bones are in a strip?
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::CountUniqueBonesInStrip( StripGroup_t *pStripGroup, Strip_t *pStrip )
+{
+ int *boneUsageCounts = ( int * )_alloca( m_NumBones * sizeof( int ) );
+ memset( boneUsageCounts, 0, sizeof( int ) * m_NumBones );
+ int i;
+ for( i = 0; i < pStrip->numStripGroupVerts; i++ )
+ {
+ Vertex_t *pVert = &pStripGroup->verts[i+pStrip->stripGroupVertexOffset];
+ if( !g_staticprop )
+ {
+ Assert( pVert->numBones != 0 );
+ }
+ int j;
+ for( j = 0; j < pVert->numBones; j++ )
+ {
+ int boneID;
+ boneID = pVert->boneID[j];
+ Assert( boneID != -1 );
+ boneUsageCounts[boneID]++;
+ }
+ }
+ int numBones = 0;
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ if( boneUsageCounts[i] )
+ {
+ numBones++;
+ }
+ }
+ return numBones;
+}
+
+
+//-----------------------------------------------------------------------------
+// A little work to be done after we construct the strip groups
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::PostProcessStripGroup( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, StripGroup_t *pStripGroup )
+{
+ int i;
+
+ // We're gonna compile all of the vertices in the current strip into
+ // the current strip group's vertex list
+ for( i = 0; i < pStripGroup->strips.Size(); i++ )
+ {
+ // create sorted strip verts and indices in the stripgroup
+ Strip_t *pStrip = &pStripGroup->strips[i];
+ int vertOffset = pStripGroup->verts.Size();
+ pStrip->stripGroupVertexOffset = vertOffset;
+ pStrip->stripGroupIndexOffset = pStripGroup->indices.Size();
+
+ // make sure we have enough memory allocated
+ pStripGroup->indices.EnsureCapacity( pStripGroup->indices.Size() + pStrip->numIndices );
+
+ // Try to find each of the strip's vertices in the strip group
+ int maxNumBones = 0;
+ int j;
+ for( j = 0; j < pStrip->numIndices; j++ )
+ {
+ int newIndex = -1;
+ int index = pStrip->pIndices[j];
+ Vertex_t *pVert = &pStrip->verts[index];
+
+ // Does this vertex exist in the strip group?
+ int k;
+ for( k = vertOffset; k < pStripGroup->verts.Size(); k++ )
+ {
+ if( pVert->origMeshVertID == pStripGroup->verts[k].origMeshVertID )
+ {
+ newIndex = k;
+ break;
+ }
+ }
+
+ // Didn't find it? Add the vertex to the list
+ if( newIndex == -1 )
+ {
+ newIndex = pStripGroup->verts.AddToTail( *pVert );
+ }
+ pStripGroup->indices.AddToTail( newIndex );
+
+#ifdef _DEBUG
+ // float GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID );
+ int i;
+ float *pWeight = ( float * )_alloca( sizeof( float ) * pVert->numBones );
+ for( i = 0; i < pVert->numBones; i++ )
+ {
+ pWeight[i] = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, pVert, i );
+ }
+#endif
+
+ // Keep track of the max # of bones in a vert
+ if (pVert->numBones > maxNumBones)
+ maxNumBones = pVert->numBones;
+ }
+ pStrip->numStripGroupIndices = pStripGroup->indices.Size() - pStrip->stripGroupIndexOffset;
+ pStrip->numStripGroupVerts = pStripGroup->verts.Size() - pStrip->stripGroupVertexOffset;
+
+ // The number of bones in a strip is the max number of
+ // bones in a vertex in this strip for vertex shaders, and it's the
+ // number of unique bones in the strip for fixed-function
+ if (!m_UsesFixedFunction)
+ pStrip->numBones = maxNumBones;
+ else
+ pStrip->numBones = CountUniqueBonesInStrip(pStripGroup, pStrip);
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Returns true if a particular mesh is teeth
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::MeshIsTeeth( studiohdr_t *pStudioHeader, mstudiomesh_t *pStudioMesh )
+{
+ // The mesh is teeth if it's got a skin whose material has a non-zero flags
+ int i;
+ for( i = 0; i < pStudioHeader->numskinfamilies; i++ )
+ {
+ short *pskinref = pStudioHeader->pSkinref( 0 );
+ pskinref += ( i * pStudioHeader->numskinref );
+ mstudiotexture_t *ptexture;
+ ptexture = pStudioHeader->pTexture( pskinref[ pStudioMesh->material ] );
+ if( ptexture->flags )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Computes the flags for a mesh
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ComputeMeshFlags( Mesh_t *pMesh, studiohdr_t *pStudioHeader,
+ mstudiomesh_t *pStudioMesh )
+{
+ // eyeball?
+ pMesh->flags = 0;
+ if( pStudioMesh->materialtype != 0 )
+ {
+ pMesh->flags |= MESH_IS_EYES;
+ }
+
+ // teeth?
+ if( MeshIsTeeth( pStudioHeader, pStudioMesh ) )
+ {
+ pMesh->flags |= MESH_IS_TEETH;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates 4 strip groups for a mesh, combinations of flexed + hwskinned
+// A mesh has a single material
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::ProcessMesh( Mesh_t *pMesh, studiohdr_t *pStudioHeader,
+ CUtlVector<mstudioiface_t> &srcFaces,
+ mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ bool forceNoFlex, bool bForceSoftwareSkin, bool bHWFlex )
+{
+ // Compute the mesh flags
+ ComputeMeshFlags( pMesh, pStudioHeader, pStudioMesh );
+
+ // We're gonna keep track of which ones we haven't processed
+ // because we're gonna add all unprocessed faces to the software
+ // lists if for some reason they don't get added to the hardware lists
+ TriangleProcessedList_t trianglesProcessed;
+ trianglesProcessed.AddMultipleToTail( srcFaces.Size() );
+ memset( trianglesProcessed.Base(), 0, trianglesProcessed.Size() );
+
+ // there are up to 4 stripgroups per mesh
+ // Note that we're gonna do the HW skinned versions first
+ // because they have the option of deciding not to be hardware after all
+ int isHWSkinned, isFlexed;
+ for( isHWSkinned = bForceSoftwareSkin ? 0 : 1; isHWSkinned >= 0; --isHWSkinned )
+ {
+ for( isFlexed = 1; isFlexed >= 0; --isFlexed )
+ {
+ int realMaxBonesPerTri, realMaxBonesPerVert, realMaxBonesPerStrip;
+
+ if( isFlexed && !bHWFlex )
+ {
+ realMaxBonesPerTri = 1;
+ realMaxBonesPerVert = 1;
+ realMaxBonesPerStrip = 1;
+ }
+ else
+ {
+ realMaxBonesPerTri = m_MaxBonesPerTri;
+ realMaxBonesPerVert = m_MaxBonesPerVert;
+ realMaxBonesPerStrip = m_MaxBonesPerStrip;
+ }
+
+ int newStripGroupIndex = pMesh->stripGroups.AddToTail( );
+ StripGroup_t& newStripGroup = pMesh->stripGroups[newStripGroupIndex];
+ ProcessStripGroup( &newStripGroup,
+ isHWSkinned ? true : false,
+ isFlexed ? true : false,
+ pStudioModel, pStudioMesh, srcFaces, trianglesProcessed,
+ realMaxBonesPerVert, realMaxBonesPerTri, realMaxBonesPerStrip, forceNoFlex, bHWFlex );
+
+ PostProcessStripGroup( pStudioModel, pStudioMesh, &newStripGroup );
+
+ // Clear out the strip group if there wasn't anything in it
+ if( !newStripGroup.indices.Size() )
+ pMesh->stripGroups.FastRemove( newStripGroupIndex );
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// some setup required before we really get into it
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::SetupMeshProcessing( studiohdr_t *pHdr, int vertexCacheSize,
+ bool usesFixedFunction, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *fileName )
+{
+#ifdef NVTRISTRIP
+ // tell nvtristrip all of it's params
+ SetCacheSize( vertexCacheSize );
+ SetStitchStrips( true );
+ SetMinStripSize( 0 );
+# ifdef EMIT_TRILISTS
+ SetListsOnly( true );
+# else
+ SetListsOnly( false );
+# endif
+#endif // NVTRISTRIP
+
+ if( !g_quiet )
+ {
+ printf( "---------------------\n" );
+ printf( "Generating optimized mesh \"%s\":\n", fileName );
+#ifdef _DEBUG
+ printf( "\tvertex cache size: %d\n", vertexCacheSize );
+ printf( "\tmax bones/tri: %d\n", maxBonesPerTri );
+ printf( "\tmax bones/vert: %d\n", maxBonesPerVert );
+ printf( "\tmax bones/strip: %d\n", maxBonesPerStrip );
+#endif
+ }
+
+ CleanupEverything();
+
+ // Total number of bones in the original model
+ m_NumBones = pHdr->numbones;
+
+ // Hardware limitations
+ m_MaxBonesPerVert = maxBonesPerVert;
+ m_MaxBonesPerTri = maxBonesPerTri;
+ m_MaxBonesPerStrip = maxBonesPerStrip;
+ m_UsesFixedFunction = usesFixedFunction;
+ m_VertexCacheSize = vertexCacheSize;
+
+ // stats
+ m_NumSkinnedAndFlexedVerts = 0;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the entire model, return stats...
+//-----------------------------------------------------------------------------
+
+int COptimizedModel::GetTotalVertsForMesh( Mesh_t *pMesh )
+{
+ int numVerts = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numVerts += pStripGroup->verts.Size();
+ }
+ return numVerts;
+}
+
+int COptimizedModel::GetTotalIndicesForMesh( Mesh_t *pMesh )
+{
+ int numIndices = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numIndices += pStripGroup->indices.Size();
+ }
+ return numIndices;
+}
+
+int COptimizedModel::GetTotalStripsForMesh( Mesh_t *pMesh )
+{
+ int numStrips = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ numStrips += pStripGroup->strips.Size();
+ }
+ return numStrips;
+}
+
+int COptimizedModel::GetTotalStripGroupsForMesh( Mesh_t *pMesh )
+{
+ return pMesh->stripGroups.Size();
+}
+
+int COptimizedModel::GetTotalBoneStateChangesForMesh( Mesh_t *pMesh )
+{
+ int numBoneStateChanges = 0;
+ int i;
+ for( i = 0; i < pMesh->stripGroups.Size(); i++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[i];
+ int j;
+ for( j = 0; j < pStripGroup->strips.Size(); j++ )
+ {
+ Strip_t *pStrip = &pStripGroup->strips[j];
+ numBoneStateChanges += pStrip->numBoneStateChanges;
+ }
+ }
+ return numBoneStateChanges;
+}
+
+
+
+/*
+static void WriteDebugFile( const char *fileName, const char *outFileName, float red, float grn, float blu )
+{
+ char *tmpName = ( char * )_alloca( strlen( fileName ) + 1 );
+ strcpy( tmpName, fileName );
+
+ s_source_t *pSrc = Load_Source( tmpName, "SMD" );
+ Assert( pSrc );
+
+ int i, j;
+
+ FILE *fp;
+ fp = fopen( outFileName, "w" );
+ Assert( fp );
+
+ for( i = 0; i < pSrc->nummeshes; i++ )
+ {
+ s_mesh_t *pMesh = &pSrc->mesh[i];
+ for( j = 0; j < pMesh->numfaces; j++ )
+ {
+ s_face_t *pFace = &pSrc->face[pMesh->faceoffset + j];
+ Vector &a = pSrc->vertex[pMesh->vertexoffset + pFace->a];
+ Vector &b = pSrc->vertex[pMesh->vertexoffset + pFace->b];
+ Vector &c = pSrc->vertex[pMesh->vertexoffset + pFace->c];
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( a[0] ), ( float )( a[1] ), ( float )( a[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( b[0] ), ( float )( b[1] ), ( float )( b[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", ( float )( c[0] ), ( float )( c[1] ), ( float )( c[2] ),
+ ( float )red, ( float )grn, ( float )blu );
+ }
+
+ }
+ fclose( fp );
+}
+*/
+
+void COptimizedModel::SourceMeshToTriangleList( s_model_t *pSrcModel, s_mesh_t *pSrcMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList )
+{
+ s_face_t *pFaces = pSrcModel->source->face + pSrcMesh->faceoffset;
+ int i;
+ for ( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ const s_face_t &pFace = pFaces[i];
+ int j = meshTriangleList.AddToTail();
+ mstudioiface_t &newTriangle = meshTriangleList[j];
+ newTriangle.a = pFace.a;
+ newTriangle.b = pFace.b;
+ newTriangle.c = pFace.c;
+ }
+}
+
+/*
+static void WriteSourceMesh( s_model_t *pSrcModel, s_mesh_t *pSrcMesh, int red_, int grn_, int blu_ )
+{
+ FILE *fp;
+ fp = fopen( "blah.glv", "a+" );
+ float red, grn, blu;
+
+ red = ( float )rand() / ( float )VALVE_RAND_MAX;
+ grn = ( float )rand() / ( float )VALVE_RAND_MAX;
+ blu = ( float )rand() / ( float )VALVE_RAND_MAX;
+ float len = red * red + grn * grn + blu * blu;
+ len = sqrt( len );
+ red *= 255.0f / len;
+ grn *= 255.0f / len;
+ blu *= 255.0f / len;
+
+ s_face_t *pFaces = pSrcModel->source->face + pSrcMesh->faceoffset;
+ int i;
+ for( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ const s_face_t &face = pFaces[i];
+ Vector a, b, c;
+ a = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.a];
+ b = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.b];
+ c = pSrcModel->source->vertex[pSrcMesh->vertexoffset + face.c];
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", a[0], a[1], a[2], red, grn, blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", c[0], c[1], c[2], red, grn, blu );
+ fprintf( fp, "%f %f %f %f %f %f\n", b[0], b[1], b[2], red, grn, blu );
+ }
+ fclose( fp );
+}
+*/
+
+static void RandomColor( Vector& color )
+{
+ color[0] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ color[1] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ color[2] = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ VectorNormalize( color );
+}
+
+/*
+static void GLViewCube( Vector pos, float size, FILE *fp )
+{
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] - size, pos[1] - size, pos[2] + size );
+
+ fprintf( fp, "4\n" );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] - size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] + size, pos[2] + size );
+ fprintf( fp, "%f %f %f 255 0 0\n", pos[0] + size, pos[1] - size, pos[2] + size );
+}
+*/
+
+#if 0
+static void MStudioBoneWeightToSBoneWeight( s_boneweight_t &sbone, const mstudioboneweight_t &mbone,
+ const s_source_t *pSrc )
+{
+ int i;
+ for( i = 0; i < mbone.numbones; i++ )
+ {
+ sbone.bone[i] = pSrc->boneimap[mbone.bone[i]];
+// sbone.bone[i] = mbone.bone[i];
+ sbone.weight[i] = mbone.weight[i];
+ }
+ sbone.numbones = mbone.numbones;
+}
+#endif
+
+void COptimizedModel::CreateLODTriangleList( s_model_t *pSrcModel, int nLodID, s_source_t* pSrc,
+ mstudiomodel_t *pStudioModel,
+ mstudiomesh_t *pStudioMesh,
+ CUtlVector<mstudioiface_t> &meshTriangleList,
+ bool writeDebug )
+{
+ if ( !pSrc || !pSrcModel )
+ return;
+
+#ifdef _DEBUG
+// const mstudio_modelvertexdata_t *vertData = pStudioModel->GetVertexData();
+// Assert( vertData ); // This can only return NULL on X360 for now
+// mstudiovertex_t *modelFirstVert = vertData->Vertex( 0 );
+// mstudiovertex_t *modelLastVert = vertData->Vertex( pStudioModel->numvertices - 1 );
+// mstudiovertex_t *meshFirstVert = vertData->Vertex( 0 );
+// mstudiovertex_t *meshLastVert = vertData->Vertex( pStudioMesh->numvertices - 1 );
+#endif
+
+/*
+ if( writeDebug )
+ {
+ printf( "MODEL VERTS:\n" );
+ int i;
+ for( i = 0; i < pStudioModel->numvertices; i++ )
+ {
+ Vector &v = *pStudioModel->pVertex( i );
+ Vector &n = *pStudioModel->pNormal( i );
+ Vector2D &t = *pStudioModel->pTexcoord( i );
+ printf( "model %d: p %f %f %f n: %f %f %f t: %f %f\n",
+ i, v[0], v[1], v[2], n[0], n[1], n[2], t[0], t[1] );
+ }
+ printf( "MESH VERTS:\n" );
+ for( i = 0; i < pStudioMesh->numvertices; i++ )
+ {
+ Vector &v = *pStudioMesh->pVertex( i );
+ Vector &n = *pStudioMesh->pNormal( i );
+ Vector2D &t = *pStudioMesh->pTexcoord( i );
+ printf( "mesh %d: p %f %f %f n: %f %f %f t: %f %f\n",
+ i, v[0], v[1], v[2], n[0], n[1], n[2], t[0], t[1] );
+ }
+ }
+*/
+ // need to find the mesh in the lod model that matches the original model.
+ int i;
+ int textureSearchID = MaterialToTexture( pStudioMesh->material );
+
+ // this is icky.. . pSrc->nummeshes really refers to the total number of non-empty meshes
+ // in the model. In pSrc->meshes, there are some empty meshes in the middle if they
+ // don't exist for the material, so you have to go through all of the meshes to find
+ // the non-empty ones.
+ s_mesh_t *pSrcMesh = NULL;
+ for ( i = 0; i < pStudioModel->nummeshes; i++ )
+ {
+ if ( pSrc->texmap[pSrc->meshindex[i]] == textureSearchID )
+ {
+ // woo hoo! found it.
+ pSrcMesh = &pSrc->mesh[pSrc->meshindex[i]];
+ break;
+ }
+ }
+ if ( !pSrcMesh )
+ {
+ //printf( "%s doesn't have material %d\n", lodSMDName, textureSearchID );
+ // There aren't any triangles in this lower lod with this material on it.
+ return;
+ }
+
+ CUtlVector<int> indexMapping;
+ indexMapping.AddMultipleToTail( pSrcMesh->numvertices );
+
+ for ( i = 0; i < pSrcMesh->numvertices; i++ )
+ {
+ // get the mapping between indices in the lod and their real pool location.
+ indexMapping[i] = pSrcModel->m_pLodData->pMeshVertIndexMaps[nLodID][pSrcMesh->vertexoffset + i];
+ }
+
+ // build the lod's faces so indexes map to remapped vertexes
+ for ( i = 0; i < pSrcMesh->numfaces; i++ )
+ {
+ int index = meshTriangleList.AddToTail();
+ mstudioiface_t& newFace = meshTriangleList[index];
+ const s_face_t &srcFace = pSrc->face[pSrcMesh->faceoffset + i];
+ newFace.a = indexMapping[srcFace.a];
+ newFace.b = indexMapping[srcFace.b];
+ newFace.c = indexMapping[srcFace.c];
+ }
+}
+
+
+bool ComparePath( const char *a, const char *b )
+{
+ if ( strlen( a ) != strlen( b ) )
+ {
+ return false;
+ }
+
+ // case and seperator invariant
+ for ( ; *a; a++, b++ )
+ {
+ if ( *a == *b )
+ {
+ continue;
+ }
+ if ( tolower( *a ) == tolower( *b ) )
+ {
+ continue;
+ }
+ if ( ( *a == '/' || *a == '\\' ) &&
+ ( *b == '/' || *b == '\\' ) )
+ {
+ continue;
+ }
+ return false;
+ }
+ return true;
+}
+
+bool COptimizedModel::MeshNeedsRemoval( studiohdr_t *pHdr, mstudiomesh_t *pStudioMesh,
+ LodScriptData_t& scriptLOD )
+{
+ mstudiotexture_t *ptexture;
+ short *pskinref = pHdr->pSkinref( 0 );
+ ptexture = pHdr->pTexture( pskinref[ pStudioMesh->material ] );
+ const char *meshName = ptexture->material->GetName();
+ int i;
+ for( i = 0; i < scriptLOD.meshRemovals.Size(); i++ )
+ {
+ const char *meshRemovalName = scriptLOD.meshRemovals[i].GetSrcName();
+ if ( ComparePath( meshName, meshRemovalName ) )
+ {
+ return true;
+ }
+ }
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Process the entire model, return stats...
+//-----------------------------------------------------------------------------
+void COptimizedModel::ProcessModel( studiohdr_t *pHdr, s_bodypart_t *pSrcBodyParts,
+ TotalMeshStats_t& stats, bool bForceSoftwareSkin, bool bHWFlex )
+{
+ memset( &stats, 0, sizeof(stats) );
+ m_Models.RemoveAll();
+
+ int bodyPartID, modelID, meshID, lodID;
+ for ( bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++, stats.m_TotalBodyParts++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ s_bodypart_t *pSrcBodyPart = &pSrcBodyParts[bodyPartID];
+ for ( modelID = 0; modelID < pBodyPart->nummodels; modelID++, stats.m_TotalModels++ )
+ {
+ int i = m_Models.AddToTail();
+ Model_t& newModel = m_Models[i];
+ mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID );
+ s_model_t *pSrcModel = pSrcBodyPart->pmodel[modelID];
+ for ( lodID = 0; lodID < g_ScriptLODs.Count(); lodID++, stats.m_TotalModelLODs++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+ s_source_t *pLODSource = pSrcModel->m_LodSources[lodID];
+
+ int i = newModel.modelLODs.AddToTail();
+ Assert( i == lodID );
+ ModelLOD_t& newLOD = newModel.modelLODs[i];
+ newLOD.switchPoint = scriptLOD.switchValue;
+
+ // In this case, we've been told to remove the model
+ if ( !pLODSource )
+ {
+ if ( pSrcModel && stricmp( pSrcModel->name, "blank" ) != 0)
+ {
+ // This is nonsensical
+ Assert( lodID != 0 );
+ }
+ continue;
+ }
+
+ for ( meshID = 0; meshID < pStudioModel->nummeshes; meshID++, stats.m_TotalMeshes++ )
+ {
+#ifdef _DEBUG
+// printf( "bodyPart: %d model: %d modellod: %d mesh: %d\n", bodyPartID, modelID, lodID, meshID );
+#endif
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ s_mesh_t *pSrcMesh = &pSrcModel->source->mesh[pSrcModel->source->meshindex[meshID]];
+
+ int i = newLOD.meshes.AddToTail();
+ Assert( i == meshID );
+ Mesh_t& newMesh = newLOD.meshes[i];
+
+ if ( MeshNeedsRemoval( pHdr, pStudioMesh, scriptLOD ) )
+ continue;
+#ifdef _DEBUG
+// int textureSearchID = material_to_texture( pStudioMesh->material );
+// const char *pDebugName = pHdr->pTexture( textureSearchID )->pszName( );
+#endif
+ CUtlVector<mstudioiface_t> meshTriangleList;
+ if ( pLODSource )
+ {
+ // map the lod data to triangles
+ // uses the original mesh redirected through a mapping table
+ // this expects built per lod-to-root mapping tables to generate faces
+ CreateLODTriangleList( pSrcModel, lodID, pLODSource, pStudioModel, pStudioMesh, meshTriangleList, false );
+ }
+ else
+ {
+ // build the triangle list from the unmapped source
+ SourceMeshToTriangleList( pSrcModel, pSrcMesh, meshTriangleList );
+ }
+
+ ProcessMesh( &newMesh, pHdr, meshTriangleList, pStudioModel, pStudioMesh,
+ !scriptLOD.GetFacialAnimationEnabled(), bForceSoftwareSkin, bHWFlex );
+
+ stats.m_TotalVerts += GetTotalVertsForMesh( &newMesh );
+ stats.m_TotalIndices += GetTotalIndicesForMesh( &newMesh );
+ stats.m_TotalStrips += GetTotalStripsForMesh( &newMesh );
+ stats.m_TotalStripGroups += GetTotalStripGroupsForMesh( &newMesh );
+ stats.m_TotalBoneStateChanges += GetTotalBoneStateChangesForMesh( &newMesh );
+ }
+ }
+ }
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Some processing that happens at the end
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::MapGlobalBonesToHardwareBoneIDsAndSortBones( studiohdr_t *phdr )
+{
+ // garymcthack: holy jesus is this function long!
+#ifdef IGNORE_BONES
+ return;
+#endif
+
+ int *globalToHardwareBoneIndex;
+ int hardwareToGlobalBoneIndex[MAX_NUM_BONES_PER_STRIP];
+
+ globalToHardwareBoneIndex = ( int * )_alloca( m_NumBones * sizeof( int ) );
+ Assert( globalToHardwareBoneIndex );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ {
+ // Dear LORD! This needs to be in another function! (garymcthack)
+ int i;
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ globalToHardwareBoneIndex[i] = -1;
+ }
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ hardwareToGlobalBoneIndex[i] = -1;
+ }
+
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ // Only do this to HW-skinned meshes
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ if( !( pStripGroup->flags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ continue;
+ }
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ // printf( "UPDATING BONE STATE\n" );
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+
+ // Generate mapping back and forth between hardware IDs and original bone IDs
+ int boneStateChangeID;
+ for( boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *pBoneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ globalToHardwareBoneIndex[pBoneStateChange->newBoneID] = pBoneStateChange->hardwareID;
+ if( pBoneStateChange->hardwareID != -1 )
+ {
+ hardwareToGlobalBoneIndex[pBoneStateChange->hardwareID] = pBoneStateChange->newBoneID;
+ }
+ }
+ int vertID;
+ for( vertID = 0; vertID < pStrip->numVerts; vertID++ )
+ {
+ Vertex_t *vert = pStripGroup->pVertex( vertID + pStrip->vertOffset );
+ int boneID;
+ for( boneID = 0; boneID < header->maxBonesPerVert; boneID++ )
+ {
+ int globalBoneID = vert->boneID[boneID];
+ if( globalBoneID == -1 )
+ {
+ // this index isn't used.
+ vert->boneID[boneID] = 0;
+ // make sure it's associated weight is zero.
+ // Assert( vert->boneWeights[boneID] == 0 );
+ continue;
+ }
+ Assert( globalBoneID >= 0 && globalBoneID < m_NumBones );
+ vert->boneID[boneID] = globalToHardwareBoneIndex[globalBoneID];
+ Assert( vert->boneID[boneID] >= 0 && vert->boneID[boneID] < header->maxBonesPerStrip );
+ }
+
+ // We only do index palette skinning when we're not doing fixed function
+ if (m_UsesFixedFunction)
+ {
+// bool flexed = pStripGroup->flags & STRIPGROUP_IS_FLEXED;
+ SortBonesWithinVertex( false /* flexed */, vert, pStudioModel,
+ pStudioMesh, globalToHardwareBoneIndex, hardwareToGlobalBoneIndex,
+ m_MaxBonesPerTri, m_MaxBonesPerVert );
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are associated with writing a .vtx file
+//
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::WriteHeader( int vertCacheSize, int maxBonesPerVert,
+ int maxBonesPerTri, int maxBonesPerStrip, int numBodyParts, long checkSum )
+{
+ FileHeader_t fileHeader;
+
+ fileHeader.version = OPTIMIZED_MODEL_FILE_VERSION;
+ fileHeader.vertCacheSize = vertCacheSize;
+ fileHeader.maxBonesPerTri = IsUShort( maxBonesPerTri );
+ fileHeader.maxBonesPerVert = maxBonesPerVert;
+ fileHeader.maxBonesPerStrip = IsUShort( maxBonesPerStrip );
+ fileHeader.numBodyParts = numBodyParts;
+ fileHeader.bodyPartOffset = sizeof( FileHeader_t );
+ fileHeader.checkSum = checkSum;
+ fileHeader.numLODs = g_ScriptLODs.Size();
+ fileHeader.materialReplacementListOffset = m_MaterialReplacementsListOffset;
+ m_FileBuffer->WriteAt( 0, &fileHeader, sizeof( FileHeader_t ), "header" );
+#ifdef _DEBUG
+// FileHeader_t *debug = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+// BodyPartHeader_t *pBodyPart = debug->pBodyPart( 0 );
+#endif
+}
+
+
+void COptimizedModel::WriteBodyPart( int bodyPartID, mstudiobodyparts_t *pBodyPart, int modelID )
+{
+ BodyPartHeader_t bodyPart;
+ bodyPart.numModels = pBodyPart->nummodels;
+ int bodyPartOffset = m_BodyPartsOffset + bodyPartID * sizeof( BodyPartHeader_t );
+ int modelFileOffset = m_ModelsOffset + modelID * sizeof( ModelHeader_t );
+ bodyPart.modelOffset = modelFileOffset - bodyPartOffset;
+ m_FileBuffer->WriteAt( bodyPartOffset, &bodyPart, sizeof( BodyPartHeader_t ), "bodypart" );
+#ifdef _DEBUG
+// BodyPartHeader_t *debug = ( BodyPartHeader_t * )m_FileBuffer->GetPointer( bodyPartOffset );
+// ModelHeader_t *pModel = debug->pModel( 0 );
+#endif
+}
+
+void COptimizedModel::WriteModel( int modelID, mstudiomodel_t *pModel, int lodID )
+{
+ ModelHeader_t model;
+ model.numLODs = IsChar( g_ScriptLODs.Size() );
+ int modelFileOffset = m_ModelsOffset + modelID * sizeof( ModelHeader_t );
+ int lodFileOffset = m_ModelLODsOffset + lodID * sizeof( ModelLODHeader_t );
+ model.lodOffset = lodFileOffset - modelFileOffset;
+ m_FileBuffer->WriteAt( modelFileOffset, &model, sizeof( ModelHeader_t ), "model" );
+#ifdef _DEBUG
+// ModelHeader_t *debug = ( ModelHeader_t * )m_FileBuffer->GetPointer( modelFileOffset );
+// ModelLODHeader_t *pLOD = debug->pLOD( 0 );
+#endif
+}
+
+void COptimizedModel::WriteModelLOD( int lodID, ModelLOD_t *pLOD, int meshID )
+{
+ ModelLODHeader_t lod;
+ int lodFileOffset = m_ModelLODsOffset + lodID * sizeof( ModelLODHeader_t );
+ int meshFileOffset = m_MeshesOffset + meshID * sizeof( MeshHeader_t );
+ lod.meshOffset = meshFileOffset - lodFileOffset;
+ lod.numMeshes = pLOD->meshes.Size();
+ lod.switchPoint = pLOD->switchPoint;
+ m_FileBuffer->WriteAt( lodFileOffset, &lod, sizeof( ModelLODHeader_t ), "modellod" );
+#ifdef _DEBUG
+// ModelLODHeader_t *debug = ( ModelLODHeader_t * )m_FileBuffer->GetPointer( lodFileOffset );
+// MeshHeader_t *pMesh = debug->pMesh( 0 );
+#endif
+}
+
+void COptimizedModel::WriteMesh( int meshID, Mesh_t *pMesh, int stripGroupID )
+{
+ MeshHeader_t mesh;
+ mesh.numStripGroups = IsChar( pMesh->stripGroups.Size() );
+ int meshFileOffset = m_MeshesOffset + meshID * sizeof( MeshHeader_t );
+ int stripGroupFileOffset = m_StripGroupsOffset + stripGroupID * sizeof( StripGroupHeader_t );
+ mesh.stripGroupHeaderOffset = stripGroupFileOffset - meshFileOffset;
+ mesh.flags = pMesh->flags;
+ m_FileBuffer->WriteAt( meshFileOffset, &mesh, sizeof( MeshHeader_t ), "mesh" );
+#ifdef _DEBUG
+// MeshHeader_t *debug = ( MeshHeader_t * )m_FileBuffer->GetPointer( meshFileOffset );
+// StripGroupHeader_t *pStripGroup = debug->pStripGroup( 0 );
+#endif
+}
+
+void COptimizedModel::WriteStripGroup( int stripGroupID, StripGroup_t *pStripGroup,
+ int vertID, int indexID, int stripID )
+{
+ StripGroupHeader_t stripGroup;
+ stripGroup.numVerts = pStripGroup->verts.Size();
+ stripGroup.numIndices = pStripGroup->indices.Size();
+ stripGroup.numStrips = pStripGroup->strips.Size();
+ stripGroup.flags = IsByte( pStripGroup->flags );
+ int stripGroupFileOffset = m_StripGroupsOffset + stripGroupID * sizeof( StripGroupHeader_t );
+ int vertsFileOffset = m_VertsOffset + vertID * sizeof( Vertex_t );
+ int indicesFileOffset = m_IndicesOffset + indexID * sizeof( unsigned short );
+ int stripsFileOffset = m_StripsOffset + stripID * sizeof( StripHeader_t );
+ stripGroup.vertOffset = vertsFileOffset - stripGroupFileOffset;
+ stripGroup.indexOffset = indicesFileOffset - stripGroupFileOffset;
+ stripGroup.stripOffset = stripsFileOffset - stripGroupFileOffset;
+ m_FileBuffer->WriteAt( stripGroupFileOffset, &stripGroup, sizeof( StripGroupHeader_t), "strip group" );
+#ifdef _DEBUG
+// StripGroupHeader_t *debug = ( StripGroupHeader_t * )m_FileBuffer->GetPointer( stripGroupFileOffset );
+// unsigned short *pIndex = debug->pIndex( 0 );
+// Vertex_t *pVert = debug->pVertex( 0 );
+// StripHeader_t *pStripHeader = debug->pStrip( 0 );
+#endif
+}
+
+int COptimizedModel::WriteVerts( int vertID, StripGroup_t *pStripGroup )
+{
+ int vertFileOffset = m_VertsOffset + vertID * sizeof( Vertex_t );
+ int numVerts = pStripGroup->verts.Size();
+ m_FileBuffer->WriteAt( vertFileOffset, &pStripGroup->verts[0], sizeof( Vertex_t ) * numVerts, "verts" );
+#ifdef _DEBUG
+// Vertex_t *debug = ( Vertex_t * )m_FileBuffer->GetPointer( vertFileOffset );
+#endif
+ return numVerts;
+}
+
+int COptimizedModel::WriteIndices( int indexID, StripGroup_t *pStripGroup )
+{
+ int indexFileOffset = m_IndicesOffset + indexID * sizeof( unsigned short );
+ int numIndices = pStripGroup->indices.Size();
+ m_FileBuffer->WriteAt( indexFileOffset, &pStripGroup->indices[0], sizeof( unsigned short ) * numIndices, "indices" );
+#ifdef _DEBUG
+// unsigned short *debug = ( unsigned short * )m_FileBuffer->GetPointer( indexFileOffset );
+#endif
+ return numIndices;
+}
+
+void COptimizedModel::WriteStrip( int stripID, Strip_t *pStrip, int indexID, int vertID, int boneID )
+{
+ StripHeader_t stripHeader;
+
+ stripHeader.numIndices = pStrip->numStripGroupIndices;
+ stripHeader.indexOffset = pStrip->stripGroupIndexOffset;
+ stripHeader.numVerts = pStrip->numStripGroupVerts;
+ stripHeader.vertOffset = pStrip->stripGroupVertexOffset;
+ stripHeader.numBoneStateChanges = pStrip->numBoneStateChanges;
+ stripHeader.numBones = IsShort( pStrip->numBones );
+ stripHeader.flags = IsByte( pStrip->flags );
+ int boneFileOffset = m_BoneStageChangesOffset + boneID * sizeof( BoneStateChangeHeader_t );
+ int stripFileOffset = m_StripsOffset + stripID * sizeof( StripHeader_t );
+ stripHeader.boneStateChangeOffset = IsInt24( boneFileOffset - stripFileOffset );
+ m_FileBuffer->WriteAt( stripFileOffset, &stripHeader, sizeof( StripHeader_t ), "strip" );
+#ifdef _DEBUG
+// StripHeader_t *debug = ( StripHeader_t* )m_FileBuffer->GetPointer( stripFileOffset );
+// BoneStateChangeHeader_t *pBoneStateChange = debug->pBoneStateChange( 0 );
+#endif
+}
+
+void COptimizedModel::WriteBoneStateChange( int boneID, BoneStateChange_t *boneStateChange )
+{
+ BoneStateChangeHeader_t boneHeader;
+ boneHeader.hardwareID = boneStateChange->hardwareID;
+ boneHeader.newBoneID = boneStateChange->newBoneID;
+ int boneFileOffset = m_BoneStageChangesOffset + boneID * sizeof( BoneStateChangeHeader_t );
+#if 0
+ printf( "\tboneStateChange: hwid: %d boneID %d\n", ( int )boneHeader.hardwareID,
+ ( int )boneHeader.newBoneID );
+#endif
+ m_FileBuffer->WriteAt( boneFileOffset, &boneHeader, sizeof( BoneStateChangeHeader_t ), "bone" );
+#ifdef _DEBUG
+// BoneStateChangeHeader_t *debug = ( BoneStateChangeHeader_t * )m_FileBuffer->GetPointer( boneFileOffset );
+#endif
+}
+
+void COptimizedModel::ZeroNumBones( void )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ int bodyPartID, modelID, lodID, meshID, stripGroupID, vertID, stripID;
+ for( bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *pBodyPart = header->pBodyPart( bodyPartID );
+ for( modelID = 0; modelID < pBodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *pModel = pBodyPart->pModel( modelID );
+ for( lodID = 0; lodID < pModel->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = pModel->pLOD( lodID );
+ for( meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *pMesh = pLOD->pMesh( meshID );
+ for( stripGroupID = 0; stripGroupID < pMesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = pMesh->pStripGroup( stripGroupID );
+ for( vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ Vertex_t *pVert = pStripGroup->pVertex( vertID );
+ pVert->numBones = 0;
+ }
+ for( stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ pStrip->numBones = 0;
+ pStrip->numBoneStateChanges = 0;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::WriteStringTable( int stringTableOffset )
+{
+ int stringTableSize = s_StringTable.CalcSize();
+ if( stringTableSize == 0 )
+ {
+ return;
+ }
+ char *pTmp = new char[stringTableSize];
+ s_StringTable.WriteToMem( pTmp );
+ m_FileBuffer->WriteAt( stringTableOffset, pTmp, stringTableSize, "string table" );
+// char *pDebug = ( char * )m_FileBuffer->GetPointer( stringTableOffset );
+ delete [] pTmp;
+}
+
+void COptimizedModel::WriteMaterialReplacements( int materialReplacementsOffset )
+{
+ int offset = materialReplacementsOffset;
+ int i, j;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ for( j = 0; j < scriptLOD.materialReplacements.Size(); j++ )
+ {
+ CLodScriptReplacement_t &materialReplacement = scriptLOD.materialReplacements[j];
+ MaterialReplacementHeader_t tmpHeader;
+ tmpHeader.materialID = FindMaterialByName( materialReplacement.GetSrcName() );
+ tmpHeader.replacementMaterialNameOffset = m_StringTableOffset +
+ s_StringTable.StringTableOffset( materialReplacement.GetDstName() ) - offset;
+ m_FileBuffer->WriteAt( offset, &tmpHeader, sizeof( tmpHeader ), "material replacements" );
+ offset += sizeof( MaterialReplacementHeader_t );
+ }
+ }
+}
+
+void COptimizedModel::WriteMaterialReplacementLists( int materialReplacementsOffset, int materialReplacementListOffset )
+{
+ int replacementOffset = materialReplacementsOffset;
+ int offset = materialReplacementListOffset;
+ int i;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ MaterialReplacementListHeader_t tmpHeader;
+ tmpHeader.numReplacements = IsChar( scriptLOD.materialReplacements.Size() );
+ tmpHeader.replacementOffset = IsInt24( replacementOffset - offset );
+ m_FileBuffer->WriteAt( offset, &tmpHeader, sizeof( tmpHeader ), "material replacement headers" );
+ MaterialReplacementListHeader_t *pDebugList =
+ ( MaterialReplacementListHeader_t * )m_FileBuffer->GetPointer( offset );
+ if( pDebugList->numReplacements )
+ {
+// MaterialReplacementHeader_t *pDebug = pDebugList->pMaterialReplacement( 0 );
+// const char *string = pDebug->pMaterialReplacementName();
+ }
+ replacementOffset += tmpHeader.numReplacements * sizeof( MaterialReplacementHeader_t );
+ offset += sizeof( MaterialReplacementListHeader_t );
+ }
+}
+
+void COptimizedModel::SanityCheckVertexBoneLODFlags( studiohdr_t *pStudioHdr, FileHeader_t *pVtxHeader )
+{
+ int bodyPartID;
+ for( bodyPartID = 0; bodyPartID < pStudioHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pStudioHdr->pBodypart( bodyPartID );
+ BodyPartHeader_t *pVtxBodyPart = pVtxHeader->pBodyPart( bodyPartID );
+ int modelID;
+ for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pModel = pBodyPart->pModel( modelID );
+ ModelHeader_t *pVtxModel = pVtxBodyPart->pModel( modelID );
+ int lodID;
+ for( lodID = 0; lodID < pVtxModel->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pVtxLOD = pVtxModel->pLOD( lodID );
+ int meshID;
+ Assert( pVtxLOD->numMeshes == pModel->nummeshes );
+ for( meshID = 0; meshID < pVtxLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *pVtxMesh = pVtxLOD->pMesh( meshID );
+ mstudiomesh_t *pMesh = pModel->pMesh( meshID );
+ int stripGroupID;
+ for( stripGroupID = 0; stripGroupID < pVtxMesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = pVtxMesh->pStripGroup( stripGroupID );
+ int vertID;
+ for( vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ Vertex_t *pVertex = pStripGroup->pVertex( vertID );
+ Vector pos = GetOrigVertPosition( pModel, pMesh, pVertex );
+ const mstudioboneweight_t &boneWeight = GetOrigVertBoneWeight( pModel, pMesh, pVertex );
+ int i;
+
+ for( i = 0; i < boneWeight.numbones; i++ )
+ {
+ mstudiobone_t *pBone = pStudioHdr->pBone( boneWeight.bone[i] );
+// const char *pBoneName = pBone->pszName();
+ if (!( pBone->flags & ( BONE_USED_BY_VERTEX_LOD0 << lodID ) ))
+ {
+ MdlError("Mismarked Bone flag");
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::WriteVTXFile( studiohdr_t *pHdr, const char *pFileName,
+ TotalMeshStats_t const& stats )
+{
+
+ // calculate file offsets
+ m_FileBuffer = new CFileBuffer( FILEBUFFER_SIZE );
+ m_BodyPartsOffset = sizeof( FileHeader_t );
+ m_ModelsOffset = m_BodyPartsOffset + sizeof( BodyPartHeader_t ) * stats.m_TotalBodyParts;
+ m_ModelLODsOffset = m_ModelsOffset + sizeof( ModelHeader_t ) * stats.m_TotalModels;
+ m_MeshesOffset = m_ModelLODsOffset + sizeof( ModelLODHeader_t ) * stats.m_TotalModelLODs;
+ m_StripGroupsOffset = m_MeshesOffset + sizeof( MeshHeader_t ) * stats.m_TotalMeshes;
+ m_StripsOffset = m_StripGroupsOffset + sizeof( StripGroupHeader_t ) * stats.m_TotalStripGroups;
+ m_VertsOffset = m_StripsOffset + sizeof( StripHeader_t ) * stats.m_TotalStrips;
+ m_IndicesOffset = m_VertsOffset + sizeof( Vertex_t ) * stats.m_TotalVerts;
+ m_BoneStageChangesOffset = m_IndicesOffset + sizeof( unsigned short ) * stats.m_TotalIndices;
+ m_StringTableOffset = m_BoneStageChangesOffset + sizeof( BoneStateChangeHeader_t ) * stats.m_TotalBoneStateChanges;
+ m_MaterialReplacementsOffset = m_StringTableOffset + s_StringTable.CalcSize();
+ m_MaterialReplacementsListOffset = m_MaterialReplacementsOffset + stats.m_TotalMaterialReplacements * sizeof( MaterialReplacementHeader_t );
+ m_EndOfFileOffset = m_MaterialReplacementsListOffset + g_ScriptLODs.Count() * sizeof( MaterialReplacementListHeader_t );
+
+ int curModel = 0;
+ int curLOD = 0;
+ int curMesh = 0;
+ int curStrip = 0;
+ int curStripGroup = 0;
+ int curVert = 0;
+ int curIndex = 0;
+ int curBoneStateChange = 0;
+ int deltaModel = 0;
+ int deltaLOD = 0;
+ int deltaMesh = 0;
+ int deltaStrip = 0;
+ int deltaStripGroup = 0;
+ int deltaVert = 0;
+ int deltaIndex = 0;
+ int deltaBoneStateChange = 0;
+
+ WriteStringTable( m_StringTableOffset );
+ WriteMaterialReplacements( m_MaterialReplacementsOffset );
+ WriteMaterialReplacementLists( m_MaterialReplacementsOffset, m_MaterialReplacementsListOffset );
+
+ for( int bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pStudioModel = pBodyPart->pModel( modelID );
+ Model_t *pModel = &m_Models[curModel + deltaModel];
+ for( int lodID = 0; lodID < g_ScriptLODs.Count(); lodID++ )
+ {
+// printf( "lod: %d\n", lodID );
+ ModelLOD_t *pLOD = &pModel->modelLODs[lodID];
+ for( int meshID = 0; meshID < pStudioModel->nummeshes; meshID++ )
+ {
+ Mesh_t *pMesh = &pLOD->meshes[meshID];
+ for( int stripGroupID = 0; stripGroupID < pMesh->stripGroups.Count(); stripGroupID++ )
+ {
+ StripGroup_t *pStripGroup = &pMesh->stripGroups[stripGroupID];
+ deltaVert += WriteVerts( curVert + deltaVert, pStripGroup );
+ deltaIndex += WriteIndices( curIndex + deltaIndex, pStripGroup );
+
+ int nStripCount = pStripGroup->strips.Count();
+ for( int stripID = 0; stripID < nStripCount; stripID++ )
+ {
+ Strip_t *pStrip = &pStripGroup->strips[stripID];
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ WriteBoneStateChange( curBoneStateChange + deltaBoneStateChange, &pStrip->boneStateChanges[boneStateChangeID] );
+ deltaBoneStateChange++;
+ }
+ WriteStrip( curStrip + deltaStrip, pStrip, curIndex, curVert, curBoneStateChange );
+ deltaStrip++;
+ curBoneStateChange += deltaBoneStateChange;
+ deltaBoneStateChange = 0;
+ }
+ WriteStripGroup( curStripGroup + deltaStripGroup, pStripGroup, curVert, curIndex, curStrip );
+ deltaStripGroup++;
+ curStrip += deltaStrip;
+ deltaStrip = 0;
+ curVert += deltaVert;
+ deltaVert = 0;
+ curIndex += deltaIndex;
+ deltaIndex = 0;
+ }
+ WriteMesh( curMesh + deltaMesh, pMesh, curStripGroup );
+ deltaMesh++;
+ curStripGroup += deltaStripGroup;
+ deltaStripGroup = 0;
+ }
+ WriteModelLOD( curLOD + deltaLOD, pLOD, curMesh );
+ deltaLOD++;
+ curMesh += deltaMesh;
+ deltaMesh = 0;
+ }
+ WriteModel( curModel + deltaModel, pStudioModel, curLOD );
+ deltaModel++;
+ curLOD += deltaLOD;
+ deltaLOD = 0;
+ }
+ WriteBodyPart( bodyPartID, pBodyPart, curModel );
+ curModel += deltaModel;
+ deltaModel = 0;
+ }
+
+ WriteHeader( m_VertexCacheSize, m_MaxBonesPerVert, m_MaxBonesPerTri,
+ m_MaxBonesPerStrip, pHdr->numbodyparts, pHdr->checksum );
+
+#ifdef _DEBUG
+ m_FileBuffer->TestWritten( m_EndOfFileOffset );
+#endif
+
+ MapGlobalBonesToHardwareBoneIDsAndSortBones( pHdr );
+
+// DebugCompareVerts( phdr );
+ SanityCheckAgainstStudioHDR( pHdr );
+
+ if ( !g_quiet )
+ {
+ OutputMemoryUsage();
+ }
+
+ RemoveRedundantBoneStateChanges();
+ if( g_staticprop )
+ {
+ ZeroNumBones();
+ }
+
+ // Show statistics
+#ifdef _DEBUG
+// ShowStats();
+#endif
+
+ m_FileBuffer->WriteToFile( pFileName, m_EndOfFileOffset );
+
+ FileHeader_t *pVtxHeader = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ SanityCheckVertexBoneLODFlags( pHdr, pVtxHeader );
+}
+
+
+
+static void MergeLikeBoneIndicesWithinVert( mstudioboneweight_t *pBoneWeight )
+{
+ if( pBoneWeight->numbones == 1 )
+ {
+ return;
+ }
+
+ int i, j;
+ int realNumBones = pBoneWeight->numbones;
+ for( i = 0; i < pBoneWeight->numbones; i++ )
+ {
+ for( j = i+1; j < pBoneWeight->numbones; j++ )
+ {
+ if( ( pBoneWeight->bone[i] == pBoneWeight->bone[j] ) && ( pBoneWeight->weight[i] != 0.0f ) )
+ {
+ pBoneWeight->weight[i] += pBoneWeight->weight[j];
+ pBoneWeight->weight[j] = 0.0f;
+ realNumBones--;
+ }
+ }
+ }
+
+ // force all of the -1's at the end with a bubble sort
+
+ float tmpWeight;
+ int tmpIndex;
+ // bubble sort the bones.
+ for( j = pBoneWeight->numbones; j > 1; j-- )
+ {
+ int k;
+ for( k = 0; k < j - 1; k++ )
+ {
+ if( ( pBoneWeight->weight[k] == 0.0f ) && ( pBoneWeight->weight[k+1] != 0.0f ) )
+ {
+ // swap
+ tmpIndex = pBoneWeight->bone[k];
+ tmpWeight = pBoneWeight->weight[k];
+ pBoneWeight->bone[k] = pBoneWeight->bone[k+1];
+ pBoneWeight->weight[k] = pBoneWeight->weight[k+1];
+ pBoneWeight->bone[k+1] = tmpIndex;
+ pBoneWeight->weight[k+1] = tmpWeight;
+ }
+ }
+ }
+ pBoneWeight->numbones = realNumBones;
+}
+
+static void MergeLikeBoneIndicesWithinVerts( studiohdr_t *pHdr )
+{
+ int bodyPartID, modelID, vertID;
+ for( bodyPartID = 0; bodyPartID < pHdr->numbodyparts; bodyPartID++ )
+ {
+ mstudiobodyparts_t *pBodyPart = pHdr->pBodypart( bodyPartID );
+ for( modelID = 0; modelID < pBodyPart->nummodels; modelID++ )
+ {
+ mstudiomodel_t *pModel = pBodyPart->pModel( modelID );
+ for( vertID = 0; vertID < pModel->numvertices; vertID++ )
+ {
+ const mstudio_modelvertexdata_t *vertData = pModel->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ mstudioboneweight_t *pBoneWeight = vertData->BoneWeights( vertID );
+ MergeLikeBoneIndicesWithinVert( pBoneWeight );
+ }
+ }
+ }
+}
+
+void COptimizedModel::PrintBoneStateChanges( studiohdr_t *phdr, int lod )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+// mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+// for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ int lodID = lod;
+ {
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+// mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+// mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *pBoneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ printf( "bone change: hwid: %d boneid: %d (%s)\n",
+ ( int )pBoneStateChange->hardwareID,
+ ( int )pBoneStateChange->newBoneID,
+ g_bonetable[pBoneStateChange->newBoneID].name);
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+void COptimizedModel::PrintVerts( studiohdr_t *phdr, int lod )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+// for( int lodID = 0; lodID < header->numLODs; lodID++ )
+ int lodID = lod;
+ {
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int vertID = 0; vertID < pStripGroup->numVerts; vertID++ )
+ {
+ PrintVert( pStripGroup->pVertex( vertID ), pStudioModel, pStudioMesh );
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static int CalcNumMaterialReplacements()
+{
+ int i;
+ int numReplacements = 0;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ numReplacements += scriptLOD.materialReplacements.Size();
+ }
+ return numReplacements;
+}
+
+//-----------------------------------------------------------------------------
+//
+// Main entry point
+//
+//-----------------------------------------------------------------------------
+
+bool COptimizedModel::OptimizeFromStudioHdr( studiohdr_t *pHdr, s_bodypart_t *pSrcBodyParts,
+ int vertCacheSize,
+ bool usesFixedFunction, bool bForceSoftwareSkin, bool bHWFlex, int maxBonesPerVert, int maxBonesPerTri,
+ int maxBonesPerStrip, const char *pFileName, const char *glViewFileName )
+{
+ Assert( maxBonesPerVert <= MAX_NUM_BONES_PER_VERT );
+ Assert( maxBonesPerTri <= MAX_NUM_BONES_PER_TRI );
+ Assert( maxBonesPerStrip <= MAX_NUM_BONES_PER_STRIP );
+
+ MergeLikeBoneIndicesWithinVerts( pHdr );
+
+ // Some initialization shite
+ SetupMeshProcessing( pHdr, vertCacheSize, usesFixedFunction, maxBonesPerVert,
+ maxBonesPerTri, maxBonesPerStrip, pFileName );
+
+ // The dude that does it all
+ TotalMeshStats_t stats;
+ ProcessModel( pHdr, pSrcBodyParts, stats, bForceSoftwareSkin, bHWFlex );
+ stats.m_TotalMaterialReplacements = CalcNumMaterialReplacements();
+
+ // Write it out to disk
+ WriteVTXFile( pHdr, pFileName, stats );
+
+ // Write out debugging files....
+ WriteGLViewFiles( pHdr, glViewFileName );
+
+ // DebugCrap( pHdr );
+
+// PrintBoneStateChanges( pHdr, 1 );
+// PrintVerts( pHdr, 1 );
+
+ delete m_FileBuffer;
+ m_FileBuffer = NULL;
+
+ if( m_NumSkinnedAndFlexedVerts != 0 )
+ {
+ MdlWarning( "!!!!WARNING!!!!: %d flexed verts had more than one bone influence. . will use SLOW path in engine\n", m_NumSkinnedAndFlexedVerts );
+ }
+
+ CleanupEverything();
+
+ return true;
+}
+
+
+static int numGLViewTrangles = 0;
+static int numGLViewSWDegenerates = 0;
+static int numGLViewHWDegenerates = 0;
+
+
+enum
+{
+ GLVIEWDRAW_TRILIST,
+ GLVIEWDRAW_TRISTRIP,
+ GLVIEWDRAW_NONE
+};
+
+static int s_DrawMode;
+static int s_LastThreeIndices[3];
+static Vertex_t s_LastThreeVerts[3];
+static Vector s_LastThreePositions[3];
+static int s_ListID = 0;
+static bool s_Shrunk[3];
+
+//-----------------------------------------------------------------------------
+//
+// The following methods are used in writing out GL View files
+//
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::PrintVert( Vertex_t *v, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh )
+{
+ printf( "vert:\n" );
+#if 0
+ printf( "\tposition: %f %f %f\n",
+ v->position[0], v->position[1], v->position[2] );
+ printf( "\tnormal: %f %f %f\n",
+ v->normal[0], v->normal[1], v->normal[2] );
+ printf( "\ttexcoord: %f %f\n",
+ v->texCoord[0], v->texCoord[1] );
+#endif
+ printf( "\torigMeshVertID: %d\n", v->origMeshVertID );
+ printf( "\tnumBones: %d\n", v->numBones );
+ int i;
+// for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ for( i = 0; i < v->numBones; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, v, i );
+ printf( "\tboneID[%d]: %d weight: %f (%s)\n", i, ( int )v->boneID[i], boneWeight,
+ g_bonetable[v->boneID[i]].name );
+ }
+}
+
+
+static float RandomFloat( float min, float max )
+{
+ float ret;
+
+ ret = ( ( float )rand() ) / ( float )VALVE_RAND_MAX;
+ ret *= max - min;
+ ret += min;
+ return ret;
+}
+
+Vector& COptimizedModel::GetOrigVertPosition( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return *vertData->Position( pVert->origMeshVertID );
+}
+
+float COptimizedModel::GetOrigVertBoneWeightValue( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return vertData->BoneWeights( pVert->origMeshVertID )->weight[pVert->boneWeightIndex[boneID]];
+}
+
+mstudioboneweight_t &COptimizedModel::GetOrigVertBoneWeight( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return *vertData->BoneWeights( pVert->origMeshVertID );
+}
+
+int COptimizedModel::GetOrigVertBoneIndex( mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, Vertex_t *pVert, int boneID )
+{
+ Assert( pStudioMesh->pModel() == pStudioModel );
+
+ const mstudio_meshvertexdata_t *vertData = pStudioMesh->GetVertexData();
+ Assert( vertData ); // This can only return NULL on X360 for now
+ return vertData->BoneWeights( pVert->origMeshVertID )->bone[pVert->boneWeightIndex[boneID]];
+}
+
+void COptimizedModel::SetMeshPropsColor( unsigned int meshFlags, Vector& color )
+{
+ if( meshFlags & MESH_IS_TEETH )
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else if( meshFlags & MESH_IS_EYES )
+ {
+ color[0] = 1.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+}
+
+void COptimizedModel::SetFlexedAndSkinColor( unsigned int glViewFlags, unsigned int stripGroupFlags, Vector& color )
+{
+ if( ( glViewFlags & WRITEGLVIEW_SHOWFLEXED ) && ( glViewFlags & WRITEGLVIEW_SHOWSW ) )
+ {
+ if( ( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ ( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // flexed and hw skinned = yellow
+ color[0] = 1.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else if( !( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ ( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // not flexed and hw skinned = green
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else if( !( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ !( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // not flexed and sw skinned = blue
+ color[0] = 0.0f; color[1] = 0.0f; color[2] = 1.0f;
+ }
+ else if( ( stripGroupFlags & STRIPGROUP_IS_FLEXED ) &&
+ !( stripGroupFlags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+ // flexed and sw skinned = red
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ Assert( 0 );
+ }
+
+ }
+ else if( glViewFlags & WRITEGLVIEW_SHOWFLEXED )
+ {
+ if( stripGroupFlags & STRIPGROUP_IS_FLEXED )
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ }
+ else if( glViewFlags & WRITEGLVIEW_SHOWSW )
+ {
+ if( stripGroupFlags & STRIPGROUP_IS_HWSKINNED )
+ {
+ color[0] = 0.0f; color[1] = 1.0f; color[2] = 0.0f;
+ }
+ else
+ {
+ color[0] = 1.0f; color[1] = 0.0f; color[2] = 0.0f;
+ }
+ }
+}
+
+void COptimizedModel::SetColorFromNumVertexBones( int numBones, Vector& color )
+{
+ Vector numBonesColor[5] = {
+ Vector( 0.0f, 0.0f, 0.0f ), // 0 bones = black
+ Vector( 0.0f, 1.0f, 0.0f ), // 1 bone = green
+ Vector( 1.0f, 1.0f, 0.0f ), // 2 bones = yellow
+ Vector( 0.0f, 0.0f, 1.0f ), // 3 bones = blue
+ Vector( 1.0f, 0.0f, 0.0f ) // 4 bones = red
+ };
+ Assert( numBones >= 0 && numBones <= 4 );
+ VectorCopy( numBonesColor[numBones], color );
+}
+
+
+void COptimizedModel::DrawGLViewTriangle( FILE *fp, Vector& pos1, Vector& pos2, Vector& pos3,
+ Vector& color1, Vector& color2, Vector& color3 )
+{
+ numGLViewTrangles++;
+ fprintf( fp, "3\n" );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos1[0], pos1[1], pos1[2], color1[0], color1[1], color1[2] );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos2[0], pos2[1], pos2[2], color2[0], color2[1], color2[2] );
+ fprintf( fp, "%f %f %f %f %f %f\n", pos3[0], pos3[1], pos3[2], color3[0], color3[1], color3[2] );
+}
+
+//-----------------------------------------------------------------------------
+// use index to test for degenerates. . isn't used for anything else.
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::GLViewVert( FILE *fp, Vertex_t vert, int index,
+ Vector& color, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh,
+ bool showSubStrips, float shrinkFactor )
+{
+// CheckVertBoneWeights( &vert, pStudioModel, pStudioMesh );
+ Assert( s_DrawMode != GLVIEWDRAW_NONE );
+ int id = s_ListID % 3;
+ s_LastThreeIndices[id] = index;
+ s_LastThreeVerts[id] = vert;
+ s_Shrunk[id] = false;
+ VectorCopy( GetOrigVertPosition( pStudioModel, pStudioMesh, &s_LastThreeVerts[id] ), s_LastThreePositions[id] );
+ if( s_DrawMode == GLVIEWDRAW_TRILIST )
+ {
+ // trilist
+ if( id == 2 )
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[0],
+ s_LastThreePositions[1],
+ s_LastThreePositions[2],
+ color, color, color );
+ }
+ }
+ else
+ {
+ // tristrip
+ if( s_ListID >= 2 )
+ {
+ // spit out the triangle with both facings. . doesn't matter if we
+ // get the facing right for glview
+ if( s_LastThreeIndices[0] == s_LastThreeIndices[1] ||
+ s_LastThreeIndices[1] == s_LastThreeIndices[2] ||
+ s_LastThreeIndices[0] == s_LastThreeIndices[2] )
+ {
+ // skip degenerate triangles
+ numGLViewHWDegenerates++;
+ if( showSubStrips )
+ {
+ RandomColor( color );
+ }
+ }
+ else if( !( s_ListID & 1 ) )
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[(id+0-2+3)%3],
+ s_LastThreePositions[(id+1-2+3)%3],
+ s_LastThreePositions[(id+2-2+3)%3],
+ color, color, color );
+ }
+ else
+ {
+ ShrinkVerts( shrinkFactor );
+ DrawGLViewTriangle( fp,
+ s_LastThreePositions[(id+2-2+3)%3],
+ s_LastThreePositions[(id+1-2+3)%3],
+ s_LastThreePositions[(id+0-2+3)%3],
+ color, color, color );
+ }
+ }
+ }
+
+ s_ListID++;
+}
+
+void COptimizedModel::GLViewDrawEnd( void )
+{
+ s_DrawMode = GLVIEWDRAW_NONE;
+}
+
+/*
+void COptimizedModel::DebugCrap( studiohdr_t *phdr )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ char tmp[256];
+ sprintf( tmp, "crap.lod%d", lodID );
+ printf( "writing %s\n", tmp );
+ FILE *fp = fopen( tmp, "w" );
+ if( !fp )
+ {
+ printf( "can't write crap file %s\n", tmp );
+ return;
+ }
+
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int id = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ Vertex_t& vert = *pStripGroup->pVertex( id );
+ Vector& vertPos = GetOrigVertPosition( pStudioModel, pStudioMesh, &vert );
+ fprintf( fp, "mesh: %04d origvertid: %04d pos: %0.2f %0.2f %0.2f ", meshID, vert.origMeshVertID, vertPos[0], vertPos[1], vertPos[2] );
+ int i;
+ for( i = 0; i < vert.numBones; i++ )
+ {
+ float boneWeight;
+ boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, &vert, i );
+ int boneID;
+ boneID = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, &vert, i );
+ fprintf( fp, "bone: %d %0.1f ", boneID, boneWeight );
+ }
+ fprintf( fp, "\n" );
+ }
+ }
+ }
+ }
+ fclose( fp );
+ }
+ }
+ }
+}
+*/
+
+
+void COptimizedModel::WriteGLViewFile( studiohdr_t *phdr, const char *pFileName, unsigned int flags, float shrinkFactor )
+{
+ Vector color;
+ RandomColor( color );
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ mstudiobodyparts_t *pStudioBodyPart = phdr->pBodypart( bodyPartID );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ mstudiomodel_t *pStudioModel = pStudioBodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ char tmp[256];
+ sprintf( tmp, "%s.lod%d", pFileName, lodID );
+ printf( "writing %s\n", tmp );
+ CPlainAutoPtr< CP4File > spFile( g_p4factory->AccessFile( tmp ) );
+ spFile->Edit();
+ FILE *fp = fopen( tmp, "w" );
+ if( !fp )
+ {
+ printf( "can't write glview file %s\n", tmp );
+ return;
+ }
+
+/*
+ // write out tangent space vectors
+ int vertID;
+ for( vertID = 0; vertID < pStudioModel->numvertices; vertID++ )
+ {
+ const Vector &pos = *pStudioModel->pVertex( vertID );
+ const Vector &norm = *pStudioModel->pNormal( vertID );
+ const Vector4D &sVect = *pStudioModel->pTangentS( vertID );
+ Vector tmpVect;
+ tmpVect = pos + norm * .15f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 0.0 0.0 1.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ Vector tmpSVect( sVect[0], sVect[1], sVect[2] );
+ tmpVect = pos + tmpSVect * .15f;
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 1.0 0.0 0.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ Vector tmpTVect;
+ CrossProduct( norm, tmpSVect, tmpTVect );
+ tmpTVect *= sVect[3];
+ tmpVect = pos + tmpTVect * .15f;
+
+ fprintf( fp, "2\n" );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", pos[0], pos[1], pos[2] );
+ fprintf( fp, "%f %f %f 0.0 1.0 0.0\n", tmpVect[0], tmpVect[1], tmpVect[2] );
+
+ }
+ continue;
+ */
+
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ mstudiomesh_t *pStudioMesh = pStudioModel->pMesh( meshID );
+ if( flags & WRITEGLVIEW_SHOWMESH )
+ {
+ RandomColor( color );
+ }
+ if( flags & WRITEGLVIEW_SHOWMESHPROPS )
+ {
+ SetMeshPropsColor( mesh->flags, color );
+ }
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ if( flags & WRITEGLVIEW_SHOWSTRIPGROUP )
+ {
+ RandomColor( color );
+ }
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+
+ SetFlexedAndSkinColor( flags, pStripGroup->flags, color );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ if( flags & WRITEGLVIEW_SHOWSTRIP )
+ {
+ RandomColor( color );
+ }
+
+ if( flags & WRITEGLVIEW_SHOWSTRIPNUMBONES )
+ {
+ switch (pStrip->numBones)
+ {
+ case 0:
+ case 1:
+ color.Init( 0, 0, 255 );
+ break;
+
+ case 2:
+ color.Init( 0, 255, 0 );
+ break;
+
+ case 3:
+ color.Init( 255, 255, 0 );
+ break;
+
+ case 4:
+ color.Init( 255, 0, 0 );
+ break;
+ }
+ }
+
+ GLViewDrawBegin( ( pStrip->flags & STRIP_IS_TRILIST ) ? GLVIEWDRAW_TRILIST : GLVIEWDRAW_TRISTRIP );
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int id = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ Vertex_t& vert = *pStripGroup->pVertex( id );
+
+ if( flags & WRITEGLVIEW_SHOWVERTNUMBONES )
+ {
+ switch (vert.numBones)
+ {
+ case 0:
+ case 1:
+ color.Init( 0, 0, 255 );
+ break;
+
+ case 2:
+ color.Init( 0, 255, 0 );
+ break;
+
+ case 3:
+ color.Init( 255, 255, 0 );
+ break;
+
+ case 4:
+ color.Init( 255, 0, 0 );
+ break;
+ }
+ }
+
+ GLViewVert( fp, vert, id, color, pStudioModel, pStudioMesh,
+ ( flags & WRITEGLVIEW_SHOWSUBSTRIP ) ? true : false, shrinkFactor );
+ }
+ GLViewDrawEnd();
+ }
+ }
+ }
+ fclose( fp );
+ spFile->Add();
+ }
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Write out all GL View files
+//-----------------------------------------------------------------------------
+
+void COptimizedModel::WriteGLViewFiles( studiohdr_t *pHdr, char const* glViewFileName )
+{
+ if( !g_bDumpGLViewFiles )
+ return;
+
+ char tmpFileName[128];
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".mesh" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWMESH, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".stripgroup" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIPGROUP, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".strip" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIP, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".substrip" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSUBSTRIP, .97f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".flexed" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWFLEXED, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".sw" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSW, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".flexedandsw" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSW | WRITEGLVIEW_SHOWFLEXED, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".meshprops" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWMESHPROPS, .8f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".vertnumbones" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWVERTNUMBONES, 1.0f );
+ strcpy( tmpFileName, glViewFileName );
+ strcat( tmpFileName, ".stripnumbones" );
+ WriteGLViewFile( pHdr, tmpFileName, WRITEGLVIEW_SHOWSTRIPNUMBONES, 1.0f );
+}
+
+
+void COptimizedModel::GLViewDrawBegin( int mode )
+{
+ s_DrawMode = mode;
+ s_ListID = 0;
+}
+
+void COptimizedModel::ShrinkVerts( float shrinkFactor )
+{
+ Vector center;
+ Vector delta;
+
+ VectorCopy( s_LastThreePositions[0], center );
+ VectorAdd( center, s_LastThreePositions[1], center );
+ VectorAdd( center, s_LastThreePositions[2], center );
+ VectorScale( center, 1.0f / 3.0f, center );
+
+ int i;
+ for( i = 0; i < 3; i++ )
+ {
+ if( s_Shrunk[i] )
+ {
+ continue;
+ }
+ VectorSubtract( s_LastThreePositions[i], center, delta );
+ VectorScale( delta, shrinkFactor, delta );
+ VectorAdd( center, delta, s_LastThreePositions[i] );
+ s_Shrunk[i] = true;
+ }
+}
+
+void COptimizedModel::CheckVertBoneWeights( Vertex_t *pVert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh )
+{
+ int i;
+ float sum = 0;
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, pVert, i );
+ sum += boneWeight;
+ }
+ Assert( sum > 0.95f && sum < 1.1f );
+}
+
+
+#define CACHE_INEFFICIENCY 6
+void COptimizedModel::ShowStats( void )
+{
+ int totalHWTriangles = 0;
+ int totalHWDegenerates = 0;
+ int totalHWIndices = 0;
+ int totalHWVertexCacheHits = 0;
+ int totalHWVertexCacheMisses = 0;
+ int totalSWTriangles = 0;
+ int totalSWDegenerates = 0;
+ int totalSWIndices = 0;
+ int totalSWVertexCacheHits = 0;
+ int totalSWVertexCacheMisses = 0;
+
+ CHardwareVertexCache hardwareVertexCache;
+ hardwareVertexCache.Init( m_VertexCacheSize - CACHE_INEFFICIENCY );
+
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ printf( "header: %d body parts\n", header->numBodyParts );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+ printf( " bodyPart %d: %d models\n", bodyPartID, bodyPart->numModels );
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ printf( " model: %d lods\n", model->numLODs );
+ for( int lodID = 0; lodID < 1; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ printf( " lod: %d meshes\n", pLOD->numMeshes );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ printf( " mesh %d: %d stripsgroups\n", meshID, mesh->numStripGroups );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ int lastThreeIndices[3];
+
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+ printf( " strip: %d numIndices: %d indexOffset: %d\n", stripID, pStrip->numIndices, pStrip->indexOffset );
+
+ hardwareVertexCache.Flush();
+ if( pStrip->flags & STRIP_IS_TRISTRIP )
+ {
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ totalHWIndices++;
+ int newVertOffset = indexID % 3;
+ lastThreeIndices[newVertOffset] = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+ if( !hardwareVertexCache.IsPresent( lastThreeIndices[newVertOffset] ) )
+ {
+ totalHWVertexCacheMisses++;
+ hardwareVertexCache.Insert( lastThreeIndices[newVertOffset] );
+ }
+ else
+ {
+ totalHWVertexCacheHits++;
+ }
+ if( indexID >= 2 )
+ {
+ totalHWTriangles++;
+ if( lastThreeIndices[0] != lastThreeIndices[1] &&
+ lastThreeIndices[1] != lastThreeIndices[2] )
+ {
+
+ }
+ else
+ {
+ totalHWDegenerates++;
+ }
+ }
+ }
+ }
+ else
+ {
+ Assert( pStrip->numIndices % 3 == 0 );
+ totalHWTriangles += pStrip->numIndices / 3;
+ for( int indexID = 0; indexID < pStrip->numIndices; indexID++ )
+ {
+ int newVertOffset = indexID % 3;
+ int index = *pStripGroup->pIndex( indexID + pStrip->indexOffset );
+// printf( "%d\n", index );
+ lastThreeIndices[newVertOffset] = index;
+ if( newVertOffset == 2 )
+ {
+ if( lastThreeIndices[0] == lastThreeIndices[1] ||
+ lastThreeIndices[1] == lastThreeIndices[2] ||
+ lastThreeIndices[0] == lastThreeIndices[2] )
+ {
+// printf( "degenerate triangle!!!! %d %d %d\n", lastThreeIndices[0], lastThreeIndices[1], lastThreeIndices[2] );
+ totalHWDegenerates++;
+ }
+ }
+ totalHWIndices++;
+ if( !hardwareVertexCache.IsPresent( index ) )
+ {
+ totalHWVertexCacheMisses++;
+ hardwareVertexCache.Insert( index );
+ }
+ else
+ {
+ totalHWVertexCacheHits++;
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+ int totalRealHWTriangles = totalHWTriangles - totalHWDegenerates;
+ int totalRealSWTriangles = totalSWTriangles - totalSWDegenerates;
+ printf( "TotalHWTriangles: %d\n", totalHWTriangles );
+ printf( "TotalHWDegenerates: %d\n", totalHWDegenerates );
+ printf( "TotalRealHWTriangles: %d\n", totalRealHWTriangles );
+ printf( "TotalHWIndices: %d\n", totalHWIndices );
+ printf( "HW real tris/index: %f\n", ( float )totalRealHWTriangles / ( float )totalHWIndices );
+ printf( "totalHWVertexCacheHits: %d\n", totalHWVertexCacheHits );
+ printf( "totalHWVertexCacheMisses: %d\n", totalHWVertexCacheMisses );
+ printf( "HW vertex cache hit/miss ratio: %f\n", ( float )totalHWVertexCacheHits / ( float )totalHWVertexCacheMisses );
+
+ printf( "TotalSWTriangles: %d\n", totalSWTriangles );
+ printf( "TotalSWDegenerates: %d\n", totalSWDegenerates );
+ printf( "TotalRealSWTriangles: %d\n", totalRealSWTriangles );
+ printf( "TotalSWIndices: %d\n", totalSWIndices );
+ printf( "SW real tris/index: %f\n", ( float )totalRealSWTriangles / ( float )totalSWIndices );
+ printf( "totalSWVertexCacheHits: %d\n", totalSWVertexCacheHits );
+ printf( "totalSWVertexCacheMisses: %d\n", totalSWVertexCacheMisses );
+ printf( "SW vertex cache hit/miss ratio: %f\n", ( float )totalSWVertexCacheHits / ( float )totalSWVertexCacheMisses );
+}
+
+void COptimizedModel::CheckVert( Vertex_t *pVert, int maxBonesPerTri, int maxBonesPerVert )
+{
+#ifndef IGNORE_BONES
+
+#ifdef _DEBUG
+ int offset = ( int )( ( unsigned char * )pVert - ( unsigned char * )m_FileBuffer->GetPointer( 0 ) );
+ Assert( offset >= m_VertsOffset && offset < m_IndicesOffset );
+ Assert( ( ( offset - m_VertsOffset ) % sizeof( Vertex_t ) ) == 0 );
+#endif
+
+ int j;
+ for( j = 0; j < maxBonesPerVert; j++ )
+ {
+ if( pVert->boneID[j] != -1 )
+ {
+ Assert( pVert->boneID[j] >= 0 && pVert->boneID[j] < maxBonesPerTri );
+ }
+#if 0
+ if( pVert->boneWeights[j] != 0 )
+ {
+ Assert( pVert->boneID[j] != -1 );
+ }
+#endif
+ }
+ // Test to make sure we are sorted.
+ for( j = 0; j < maxBonesPerVert-1; j++ )
+ {
+#if 1
+// if( pVert->boneWeights[j] != 0 && pVert->boneWeights[j+1] != 0 )
+ {
+ Assert( pVert->boneID[j] < pVert->boneID[j+1] );
+ }
+#endif
+ }
+#if 0
+ // Make sure that all the non-zero weights are first.
+ bool foundZero = false;
+ for( j = 0; j < maxBonesPerVert; j++ )
+ {
+ if( !foundZero )
+ {
+ if( pVert->boneWeights[j] == 0.0f )
+ {
+ foundZero = true;
+ }
+ }
+ else
+ {
+ Assert( pVert->boneWeights[j] == 0.0f );
+ }
+ }
+#endif
+#endif
+}
+
+void COptimizedModel::CheckAllVerts( int maxBonesPerTri, int maxBonesPerVert )
+{
+ int i;
+ for( i = m_VertsOffset; i < m_IndicesOffset; i += sizeof( Vertex_t ) )
+ {
+ Vertex_t *vert = ( Vertex_t * )m_FileBuffer->GetPointer( i );
+ CheckVert( vert, maxBonesPerTri, maxBonesPerVert );
+ }
+}
+
+void COptimizedModel::SortBonesWithinVertex( bool flexed, Vertex_t *vert, mstudiomodel_t *pStudioModel, mstudiomesh_t *pStudioMesh, int *globalToHardwareBoneIndex, int *hardwareToGlobalBoneIndex, int maxBonesPerTri, int maxBonesPerVert )
+{
+ int i;
+/*
+ for( i = 0; i < m_NumBones; i++ )
+ {
+ if( globalToHardwareBoneIndex[i] != -1 )
+ {
+ if( flexed )
+ {
+ printf( "global bone id: %d hardware bone id: %d\n",
+ i, globalToHardwareBoneIndex[i] );
+ }
+ }
+ }
+*/
+#if 0
+ unsigned char tmpWeightIndex;
+ int tmpIndex;
+ int j;
+ // bubble sort the bones.
+ for( j = m_MaxBonesPerVert; j > 1; j-- )
+ {
+ int k;
+ for( k = 0; k < j - 1; k++ )
+ {
+ if( vert->boneID[k] > vert->boneID[k+1] )
+ {
+ // swap
+ tmpIndex = vert->boneID[k];
+ tmpWeightIndex = vert->boneWeightIndex[k];
+ vert->boneID[k] = vert->boneID[k+1];
+ vert->boneWeightIndex[k] = vert->boneWeightIndex[k+1];
+ vert->boneID[k+1] = tmpIndex;
+ vert->boneWeightIndex[k+1] = tmpWeightIndex;
+ }
+ }
+ }
+#else
+
+ int origBoneWeightIndex[MAX_NUM_BONES_PER_VERT];
+ int zeroWeightIndex = -1;
+ // find a orig vert bone index that has a zero weight
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ if( boneWeight == 0.0f )
+ {
+ zeroWeightIndex = i;
+ break;
+ }
+ }
+
+ for( i = 0; i < MAX_NUM_BONES_PER_VERT; i++ )
+ {
+ origBoneWeightIndex[i] = zeroWeightIndex;
+ }
+ for( i = 0; i < vert->numBones; i++ )
+ {
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ int globalBoneIndex = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, vert, i );
+// if( vert->numBones > 1 )
+ {
+ if( flexed )
+ {
+ printf( "boneWeight: %f\n", boneWeight );
+ printf( "globalBoneIndex: %d\n", globalBoneIndex );
+ }
+ }
+ if( boneWeight > 0.0f )
+ {
+ int hardwareBoneIndex = globalToHardwareBoneIndex[globalBoneIndex];
+ Assert( globalBoneIndex != -1 );
+ origBoneWeightIndex[hardwareBoneIndex] = vert->boneWeightIndex[i];
+ }
+ else
+ {
+ int hardwareBoneIndex = globalToHardwareBoneIndex[globalBoneIndex];
+ origBoneWeightIndex[hardwareBoneIndex] = zeroWeightIndex;
+ Assert( zeroWeightIndex != -1 );
+ Assert( globalBoneIndex == -1 );
+ }
+ }
+// if( vert->numBones > 1 )
+ for( i = 0; i < maxBonesPerTri; i++ )
+ {
+ vert->boneID[i] = i;
+ vert->boneWeightIndex[i] = origBoneWeightIndex[i];
+ float boneWeight = GetOrigVertBoneWeightValue( pStudioModel, pStudioMesh, vert, i );
+ int globalBoneIndex = GetOrigVertBoneIndex( pStudioModel, pStudioMesh, vert, i );
+ if( flexed )
+ {
+ Assert( boneWeight >= 0.0f && boneWeight <= 1.0f );
+ printf( "boneWeight: %f ", boneWeight );
+ printf( "globalBoneIndex: %d ", globalBoneIndex );
+ printf( "hardwareBoneID: %d\n", i );
+ }
+ }
+ vert->numBones = maxBonesPerTri; // this may be different for software t&l stuff
+#endif
+}
+
+void COptimizedModel::RemoveRedundantBoneStateChanges( void )
+{
+ FileHeader_t *header = ( FileHeader_t * )m_FileBuffer->GetPointer( 0 );
+ for( int bodyPartID = 0; bodyPartID < header->numBodyParts; bodyPartID++ )
+ {
+ BodyPartHeader_t *bodyPart = header->pBodyPart( bodyPartID );
+
+ bool allocated[MAX_NUM_BONES_PER_STRIP];
+ int hardwareBoneState[MAX_NUM_BONES_PER_STRIP];
+ bool changed[MAX_NUM_BONES_PER_STRIP];
+
+ // start anew with each body part
+// printf( "START BODY PARTY - RESETTING BONE MATRIX STATE\n" );
+ int i;
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ hardwareBoneState[i] = -1;
+ allocated[i] = false;
+ }
+
+ for( int modelID = 0; modelID < bodyPart->numModels; modelID++ )
+ {
+ ModelHeader_t *model = bodyPart->pModel( modelID );
+ for( int lodID = 0; lodID < model->numLODs; lodID++ )
+ {
+ ModelLODHeader_t *pLOD = model->pLOD( lodID );
+ for( int meshID = 0; meshID < pLOD->numMeshes; meshID++ )
+ {
+ MeshHeader_t *mesh = pLOD->pMesh( meshID );
+ for( int stripGroupID = 0; stripGroupID < mesh->numStripGroups; stripGroupID++ )
+ {
+ StripGroupHeader_t *pStripGroup = mesh->pStripGroup( stripGroupID );
+ if( !( pStripGroup->flags & STRIPGROUP_IS_HWSKINNED ) )
+ {
+// printf( "skipping! software skinned stripgroup\n" );
+ continue;
+ }
+ for( int stripID = 0; stripID < pStripGroup->numStrips; stripID++ )
+ {
+ StripHeader_t *pStrip = pStripGroup->pStrip( stripID );
+// int startNumBoneChanges = pStrip->numBoneStateChanges;
+/*
+ printf( "HARDWARE BONE STATE\n" );
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ if( allocated[i] )
+ {
+ printf( "\thw: %d global: %d\n", i, hardwareBoneState[i] );
+ }
+ }
+
+ printf( "before optimization\n" );
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ printf( "\thw: %d global: %d\n",
+ ( int )pStrip->pBoneStateChange( i )->hardwareID,
+ ( int )pStrip->pBoneStateChange( i )->newBoneID );
+ }
+*/
+
+ for( i = 0; i < MAX_NUM_BONES_PER_STRIP; i++ )
+ {
+ changed[i] = false;
+ }
+ for( int boneStateChangeID = 0; boneStateChangeID < pStrip->numBoneStateChanges; boneStateChangeID++ )
+ {
+ BoneStateChangeHeader_t *boneStateChange = pStrip->pBoneStateChange( boneStateChangeID );
+ Assert( boneStateChange->hardwareID >= 0 && boneStateChange->hardwareID < MAX_NUM_BONES_PER_STRIP );
+ if( allocated[boneStateChange->hardwareID] &&
+ hardwareBoneState[boneStateChange->hardwareID] == boneStateChange->newBoneID )
+ {
+ // already got this one!
+ }
+ else
+ {
+ changed[boneStateChange->hardwareID] = true;
+ allocated[boneStateChange->hardwareID] = true;
+ hardwareBoneState[boneStateChange->hardwareID] = boneStateChange->newBoneID;
+ }
+ }
+/*
+ // now "changed" should tell us which ones we care about.
+ int curOutBoneID = 0;
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ // hack . . going to stomp over what is already there with new data.
+ if( changed[i] )
+ {
+ BoneStateChangeHeader_t *boneStateChange = pStrip->pBoneStateChange( curOutBoneID );
+ boneStateChange->hardwareID = i;
+ boneStateChange->newBoneID = hardwareBoneState[i];
+ curOutBoneID++;
+ }
+ }
+ pStrip->numBoneStateChanges = curOutBoneID;
+ printf( "start bone changes: %d end bone changes: %d\n", startNumBoneChanges, pStrip->numBoneStateChanges );
+ printf( "after optimization\n" );
+ for( i = 0; i < pStrip->numBoneStateChanges; i++ )
+ {
+ printf( "\thw: %d global: %d\n",
+ ( int )pStrip->pBoneStateChange( i )->hardwareID,
+ ( int )pStrip->pBoneStateChange( i )->newBoneID );
+ }
+
+ */
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+static void AddMaterialReplacementsToStringTable( void )
+{
+ int i, j;
+ int numLODs = g_ScriptLODs.Size();
+ for( i = 0; i < numLODs; i++ )
+ {
+ LodScriptData_t &scriptLOD = g_ScriptLODs[i];
+ for( j = 0; j < scriptLOD.materialReplacements.Size(); j++ )
+ {
+ CLodScriptReplacement_t &materialReplacement = scriptLOD.materialReplacements[j];
+ s_StringTable.AddString( materialReplacement.GetDstName() );
+ }
+ }
+}
+
+
+// Check that all replacematerial/removemesh commands map to valid source materials
+void ValidateLODReplacements( studiohdr_t *pHdr )
+{
+ bool failed = false;
+ int lodID;
+ for( lodID = 0; lodID < g_ScriptLODs.Size(); lodID++ )
+ {
+ LodScriptData_t& scriptLOD = g_ScriptLODs[lodID];
+ int j;
+ for( j = 0; j < scriptLOD.meshRemovals.Count(); j++ )
+ {
+ const char *pName1 = scriptLOD.meshRemovals[j].GetSrcName();
+ int i;
+ for( i = 0; i < pHdr->numtextures; i++ )
+ {
+ const char *pName2 = pHdr->pTexture( i )->material->GetName();
+ if( ComparePath( pName1, pName2 ) )
+ {
+ goto got_one;
+ }
+ }
+
+ // no match
+ MdlWarning( "\"%s\" doesn't match any of the materals in the model\n", pName1 );
+ failed = true;
+got_one:
+ ;
+ }
+ }
+ if( failed )
+ {
+ MdlWarning( "possible materials in model:\n" );
+ int i;
+ for( i = 0; i < pHdr->numtextures; i++ )
+ {
+ MdlWarning( "\t\"%s\"\n", pHdr->pTexture( i )->material->GetName() );
+ }
+ MdlError( "Exiting due to errors\n" );
+ }
+}
+
+void WriteOptimizedFiles( studiohdr_t *phdr, s_bodypart_t *pSrcBodyParts )
+{
+ char filename[MAX_PATH];
+ char tmpFileName[MAX_PATH];
+ char glViewFilename[MAX_PATH];
+
+ ValidateLODReplacements( phdr );
+
+ s_StringTable.Purge();
+
+ // hack! This should really go in the mdl file since it's common to all LODs.
+ AddMaterialReplacementsToStringTable();
+
+ V_strcpy_safe( filename, gamedir );
+// if( *g_pPlatformName )
+// {
+// strcat( filename, "platform_" );
+// strcat( filename, g_pPlatformName );
+// strcat( filename, "/" );
+// }
+ V_strcat_safe( filename, "models/" );
+ V_strcat_safe( filename, outname );
+ Q_StripExtension( filename, filename, sizeof( filename ) );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".sw.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".sw.glview" );
+ bool bForceSoftwareSkinning = phdr->numbones > 0 && !g_staticprop;
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 512, //vert cache size FIXME: figure out the correct size for L1
+ false, /* doesn't use fixed function */
+ bForceSoftwareSkinning, // force software skinning if not static prop
+ false, // No hardware flex
+ 3, // bones/vert
+ 3*3, // bones/tri
+ 512, // bones/strip
+ tmpFileName, glViewFilename );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".dx80.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".dx80.glview" );
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 24 /* vert cache size (real size, not effective!)*/,
+ false, /* doesn't use fixed function */
+ false, // don't force software skinning
+ false, // No hardware flex
+ 3 /* bones/vert */,
+ 9 /* bones/tri */,
+ 16 /* bones/strip */,
+ tmpFileName, glViewFilename );
+
+ V_strcpy_safe( tmpFileName, filename );
+ V_strcat_safe( tmpFileName, ".dx90.vtx" );
+ V_strcpy_safe( glViewFilename, filename );
+ V_strcat_safe( glViewFilename, ".dx90.glview" );
+ s_OptimizedModel.OptimizeFromStudioHdr( phdr, pSrcBodyParts,
+ 24 /* vert cache size (real size, not effective!)*/,
+ false, /* doesn't use fixed function */
+ false, // don't force software skinning
+ true, // Hardware flex on DX9 parts
+ 3 /* bones/vert */,
+ 9 /* bones/tri */,
+ 53 /* bones/strip */,
+ tmpFileName, glViewFilename );
+
+ s_StringTable.Purge();
+}
+
+}; // namespace OptimizedModel
+#pragma optimize( "", on )