#include "datacache/imdlcache.h" #include "optimize.h" #include "mdlcombine.h" #include "filesystem.h" #include "studio.h" #include "mathlib/mathlib.h" #include #include "tier1/characterset.h" #include "keyvalues.h" #include "vtfcombine.h" // beware: this does not do any type of byte swapping for handling of endian issues! CCombinerMemoryWriter g_CombinerWriter; CModelCombine g_ModelCombiner; static CModelCombine *s_pCurrentCombine = &g_ModelCombiner; static characterset_t s_BreakSet; unsigned int CModelCombine::m_nNextAssetID = 0; #ifdef DEBUG_COMBINE #define DebugCombineMsg( ... ) Msg( __VA_ARGS__ ) //#define TEST_MDL_COMBINE 1 //#define TEST_VTX_COMBINE 1 #else #define DebugCombineMsg( ... ) #endif CCombinerMemoryWriter::CCombinerMemoryWriter( ) { m_pWorkBuffer = NULL; } CCombinerMemoryWriter::~CCombinerMemoryWriter( ) { free( m_pWorkBuffer ); } void CCombinerMemoryWriter::Init( ) { if ( m_pWorkBuffer == NULL ) { CharacterSetBuild( &s_BreakSet, "{}()':" ); m_pWorkBuffer = ( char * )malloc( COMBINER_WORK_BUFFER_SIZE ); m_pEndBuffer = m_pWorkBuffer + COMBINER_WORK_BUFFER_SIZE - 1; } memset( m_pWorkBuffer, 0, COMBINER_WORK_BUFFER_SIZE ); m_pWriteArea = m_pWritePos = NULL; #ifdef DEBUG_COMBINE m_pErrorPos = NULL; #endif // DEBUG_COMBINE m_nWriteArea = -1; InitWriteArea( WRITE_AREA_VVD, m_pWorkBuffer ); SetWriteArea( WRITE_AREA_VVD ); } void CCombinerMemoryWriter::InitWriteArea( int nArea, char *pPosition ) { pPosition = ( char * )( ( intp )( pPosition + 15 ) & ( ~15 ) ); m_pSaveWriteArea[ nArea ] = m_pSaveWritePos[ nArea ] = pPosition; } void CCombinerMemoryWriter::SetWriteArea( int nArea ) { if ( m_nWriteArea != -1 ) { m_pSaveWriteArea[ m_nWriteArea ] = m_pWriteArea; m_pSaveWritePos[ m_nWriteArea ] = m_pWritePos; } m_nWriteArea = nArea; if ( m_nWriteArea != -1 ) { m_pWriteArea = m_pSaveWriteArea[ m_nWriteArea ]; m_pWritePos = m_pSaveWritePos[ m_nWriteArea ]; } } char *CCombinerMemoryWriter::AllocWrite( int nSize ) { char *pOrigPos = m_pWritePos; #ifdef DEBUG_COMBINE if ( m_pErrorPos >= m_pWritePos && m_pErrorPos < m_pWritePos + nSize ) { Msg( "AllocFailure: %d offset %d\n", m_pErrorPos - m_pWriteArea, m_pErrorPos - m_pWritePos ); Assert( 0 ); } #endif // DEBUG_COMBINE m_pWritePos += nSize; if ( m_pWritePos > m_pEndBuffer ) { AssertMsg( false, "Internal model combiner buffer not large enough" ); V_sprintf_safe( g_ModelCombiner.GetResults()->m_szErrorMessage, "Internal model combiner buffer not large enough" ); throw( COMBINE_RESULT_FLAG_OUT_OF_MEMORY ); } return pOrigPos; } char *CCombinerMemoryWriter::WriteOffset( int &nOffsetIndex ) { nOffsetIndex = ( m_pWritePos - m_pWriteArea ); return m_pWritePos; } char *CCombinerMemoryWriter::WriteOffset( int &nOffsetIndex, void *pBasePtr ) { nOffsetIndex = ( ( byte * )m_pWritePos - ( byte * )pBasePtr ); return m_pWritePos; } char *CCombinerMemoryWriter::WriteOffset( short &nOffsetIndex, void *pBasePtr ) { nOffsetIndex = ( ( byte * )m_pWritePos - ( byte * )pBasePtr ); return m_pWritePos; } char *CCombinerMemoryWriter::WriteBuffer( const void *pData, int nSize ) { char *pOrigPos = AllocWrite( nSize ); memcpy( pOrigPos, pData, nSize ); return pOrigPos; } char *CCombinerMemoryWriter::WriteBufferWithOffset( const void *pData, int nSize, int &nOffsetIndex ) { WriteOffset( nOffsetIndex ); return WriteBuffer( pData, nSize ); } char *CCombinerMemoryWriter::WriteString( const char *pszString ) { return WriteBuffer( pszString, strlen( pszString ) + 1 ); } char *CCombinerMemoryWriter::WriteText( const char *pszString ) { return WriteBuffer( pszString, strlen( pszString ) ); } void CCombinerMemoryWriter::AlignWrite( int nAlignSize ) { #ifdef DEBUG_COMBINE char *pPriorPos = m_pWritePos; #endif // DEBUG_COMBINE nAlignSize--; m_pWritePos = ( char * )( ( intp )( m_pWritePos + nAlignSize ) & ( ~nAlignSize ) ); #ifdef DEBUG_COMBINE if ( m_pErrorPos >= pPriorPos && m_pErrorPos < m_pWritePos ) { DebugCombineMsg( "AlignFailure: %d offset %d\n", m_pErrorPos - m_pWriteArea, m_pErrorPos - m_pWritePos ); Assert( 0 ); } #endif // DEBUG_COMBINE if ( m_pWritePos > m_pEndBuffer ) { Assert( 0 ); V_sprintf_safe( g_ModelCombiner.GetResults()->m_szErrorMessage, "Internal model combiner buffer not large enough" ); throw( COMBINE_RESULT_FLAG_OUT_OF_MEMORY ); } } CModelCombine::CModelCombine( ) { } CModelCombine::~CModelCombine( ) { } void CModelCombine::Init( TCombinedStudioData *pCombinedStudioData ) { m_pCombinedStudioData = pCombinedStudioData; memset( &m_pCombinedStudioData->m_Results, 0, sizeof( m_pCombinedStudioData->m_Results ) ); for ( int nGroup = 0; nGroup < COMBINER_MAX_ATLAS_GROUPS; nGroup++ ) { memset( &m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_pCombinedTextures, 0, sizeof( m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_pCombinedTextures ) ); memset( &m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_nCombinedTextureSizes, 0, sizeof( m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_nCombinedTextureSizes ) ); m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_pCombinedMaterial = NULL; memset( &m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_szCombinedMaterialName, 0, sizeof( m_pCombinedStudioData->m_AtlasGroups[ nGroup ].m_szCombinedMaterialName ) ); } // we need to make sure we don't have any periods, as other routines may strip off those as an extension for( char *pszTestChar = m_pCombinedStudioData->m_szCombinedModelName; *pszTestChar; pszTestChar++ ) { if ( *pszTestChar == '.' ) { *pszTestChar = '_'; } } g_CombinerWriter.Init(); } void CModelCombine::BeginStringTable( ) { strings[ 0 ].base = NULL; strings[ 0 ].ptr = NULL; strings[ 0 ].string = ""; strings[ 0 ].dupindex = -1; numStrings = 1; } //----------------------------------------------------------------------------- // Purpose: add a string to the file-global string table. // Keep track of fixup locations //----------------------------------------------------------------------------- void CModelCombine::AddToStringTable( void *base, int *ptr, const char *string ) { if ( numStrings >= COMBINER_MAX_STRINGS ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Too many strings for string table of size %d", COMBINER_MAX_STRINGS ); throw( COMBINE_RESULT_FLAG_TOO_MANY_STRINGS ); } for ( int i = 0; i < numStrings; i++ ) { if ( !string || !strcmp( string, strings[ i ].string ) ) { strings[ numStrings ].base = ( byte * )base; strings[ numStrings ].ptr = ptr; strings[ numStrings ].string = string; strings[ numStrings ].dupindex = i; numStrings++; return; } } strings[ numStrings ].base = ( byte * )base; strings[ numStrings ].ptr = ptr; strings[ numStrings ].string = string; strings[ numStrings ].dupindex = -1; numStrings++; } //----------------------------------------------------------------------------- // Purpose: Write out stringtable // fixup local pointers //----------------------------------------------------------------------------- void CModelCombine::WriteStringTable( ) { // force null at first address strings[ 0 ].addr = ( byte * )g_CombinerWriter.GetWritePos(); g_CombinerWriter.WriteBuffer( "", 1 ); // null string // save all the rest for ( int i = 1; i < numStrings; i++ ) { if ( strings[ i ].dupindex == -2 ) { // initial filler to match string tables up strings[ i ].addr = ( byte * )g_CombinerWriter.GetWritePos(); g_CombinerWriter.WriteBuffer( strings[ i ].string, strlen( strings[ i ].string ) + 1 ); } else if ( strings[ i ].dupindex == -1 ) { // not in table yet // calc offset relative to local base *strings[ i ].ptr = ( byte * )g_CombinerWriter.GetWritePos() - strings[ i ].base; // keep track of address in case of duplication strings[ i ].addr = ( byte * )g_CombinerWriter.GetWritePos(); // copy string data, add a terminating \0 g_CombinerWriter.WriteBuffer( strings[ i ].string, strlen( strings[ i ].string ) + 1 ); } else { // already in table, calc offset of existing string relative to local base *strings[ i ].ptr = strings[ strings[ i ].dupindex ].addr - strings[ i ].base; } } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::VerifyField( int nField, const char *pszDescription ) { #ifdef DEBUG_COMBINE int *pOriginal = ( int * )( ( ( byte * )m_pStudioHdr[ 0 ] ) + nField ); int *pCombined = ( int * )( ( ( byte * )m_pCombinedStudioHdr ) + nField ); DebugCombineMsg( "Verify Field %s: %d / %d\n", pszDescription, *pOriginal, *pCombined ); #endif // DEBUG_COMBINE } void CModelCombine::VerifyField2( int nField, const char *pszDescription ) { #ifdef DEBUG_COMBINE int *pOriginal = ( int * )( ( ( byte * )m_pStudioHdr2[ 0 ] ) + nField ); int *pCombined = ( int * )( ( ( byte * )m_pCombinedStudioHdr2 ) + nField ); DebugCombineMsg( "Verify Field2 %s: %d / %d\n", pszDescription, *pOriginal, *pCombined ); #endif // DEBUG_COMBINE } void CModelCombine::VerifyOffset( void *pPtr, const char *pszDescription, void *pWritePos ) { #ifdef DEBUG_COMBINE int pV1 = ( ( char * )pPtr ) - ( ( char * ) m_pStudioHdr[ 0 ] ); if ( pWritePos == 0 ) { pWritePos = m_pWritePos; } int pV2 = ( char * )pWritePos - m_pWriteArea; DebugCombineMsg( "Verify Offset %s: %d / %d ( %d )\n", pszDescription, pV1, pV2, pV2 - pV1 ); #endif // DEBUG_COMBINE } bool CModelCombine::Resolve( ) { char szFileName[ MAX_PATH ]; m_pCombinedStudioData->m_Results.m_nCombinedResults = COMBINE_RESULT_FLAG_OK; m_pCombinedStudioData->m_Results.m_szErrorMessage[ 0 ] = 0; m_pCombinedStudioData->m_Results.m_szErrorDetails[ 0 ] = 0; m_pCombinedStudioData->m_Results.m_nDetailedError = COMBINED_DETAIL_ERROR_NOT_SPECIFIED; memset( MDL_Data, 0, sizeof( MDL_Data ) ); memset( VTX_Data, 0, sizeof( VTX_Data ) ); memset( VVD_Data, 0, sizeof( VVD_Data ) ); try { if ( STRING(m_pCombinedStudioData->m_ModelInputData[ 0 ].m_iszModelName)[ 0 ] == 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "No primary model specified" ); throw( COMBINE_RESULT_FLAG_MISSING_ASSET_FILE ); } double flStartLoadTime = Plat_FloatTime(); for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { bool bResult; MDL_Data[ nModel ] = new CUtlBuffer(); V_strcpy_safe( szFileName, STRING(m_pCombinedStudioData->m_ModelInputData[ nModel ].m_iszModelName) ); Q_SetExtension( szFileName, ".mdl", sizeof( szFileName ) ); bResult = g_pFullFileSystem->ReadFile( szFileName, "GAME", *MDL_Data[ nModel ], 0 ); if ( !bResult ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Missing asset file: %s", szFileName ); throw( COMBINE_RESULT_FLAG_MISSING_ASSET_FILE ); } m_pStudioHdr[ nModel ] = ( studiohdr_t * )MDL_Data[ nModel ]->PeekGet(); m_pStudioHdr2[ nModel ] = ( studiohdr2_t * )MDL_Data[ nModel ]->PeekGet( m_pStudioHdr[ nModel ]->studiohdr2index ); VTX_Data[ nModel ] = new CUtlBuffer(); V_strcpy_safe( szFileName, STRING(m_pCombinedStudioData->m_ModelInputData[ nModel ].m_iszModelName) ); Q_SetExtension( szFileName, ".dx90.vtx", sizeof( szFileName ) ); // GetVTXExtension() private :( bResult = g_pFullFileSystem->ReadFile( szFileName, "GAME", *VTX_Data[ nModel ], 0 ); if ( !bResult ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Missing asset file: %s", szFileName ); throw( COMBINE_RESULT_FLAG_MISSING_ASSET_FILE ); } VVD_Data[ nModel ] = new CUtlBuffer(); V_strcpy_safe( szFileName, STRING(m_pCombinedStudioData->m_ModelInputData[ nModel ].m_iszModelName) ); Q_SetExtension( szFileName, ".vvd", sizeof( szFileName ) ); bResult = g_pFullFileSystem->ReadFile( szFileName, "GAME", *VVD_Data[ nModel ], 0 ); if ( !bResult ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Missing asset file: %s", szFileName ); throw( COMBINE_RESULT_FLAG_MISSING_ASSET_FILE ); } m_pVertexFileHeader[ nModel ] = ( vertexFileHeader_t * )VVD_Data[ nModel ]->PeekGet(); } m_pCombinedStudioData->m_Results.m_flModelLoadDuration += ( float )( Plat_FloatTime() - flStartLoadTime ); CombineTextures(); double flStartCombineTime = Plat_FloatTime(); DetermineMasterBoneList(); CombineVVD(); CombineVTX(); #ifdef TEST_MDL_COMBINE CombineMDL( true ); TestCombineMDL(); #else CombineMDL( false ); #endif g_CombinerWriter.SetWriteArea( -1 ); m_pCombinedStudioData->m_Results.m_flModelCombineDuration += ( float )( Plat_FloatTime() - flStartCombineTime ); m_pCombinedStudioData->m_Results.m_nCombinedResults = COMBINE_RESULT_FLAG_OK; } catch( ECombinedResult nFlags ) { m_pCombinedStudioData->m_Results.m_nCombinedResults = nFlags; } GetTextureCombiner().Cleanup(); for( int i = 0; i < m_pCombinedStudioData->m_nNumModels; i++ ) { delete MDL_Data[ i ]; delete VTX_Data[ i ]; delete VVD_Data[ i ]; } return ( m_pCombinedStudioData->m_Results.m_nCombinedResults == COMBINE_RESULT_FLAG_OK ); } void CModelCombine::DetermineMasterBoneList( ) { studiohdr_t *pPrimaryStudioHdr = m_pStudioHdr[ 0 ]; if ( pPrimaryStudioHdr->numbones > COMBINER_MAX_BONES ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Too many bones in primary model: %d > %d", pPrimaryStudioHdr->numbones, COMBINER_MAX_BONES ); throw( COMBINE_RESULT_FLAG_TOO_MANY_BONES ); } memset( m_nMasterToLocalBoneRemap, -1, sizeof( m_nMasterToLocalBoneRemap ) ); m_nNumMasterBones = 0; for( int nBone = 0; nBone < pPrimaryStudioHdr->numbones; nBone++ ) { const mstudiobone_t *pOrigBone = pPrimaryStudioHdr->pBone( nBone ); m_pMasterBoneList[ m_nNumMasterBones ] = pOrigBone; m_nBoneModelOwner[ m_nNumMasterBones ] = 0; m_nBoneRemap[ 0 ][ nBone ] = m_nNumMasterBones; m_nMasterToLocalBoneRemap[ 0 ][ m_nNumMasterBones ] = nBone; m_nNumMasterBones++; } for( int nModel = 1; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { studiohdr_t *pSecondaryStudioHdr = m_pStudioHdr[ nModel ]; for( int nBone = 0; nBone < pSecondaryStudioHdr->numbones; nBone++ ) { const mstudiobone_t *pOrigBone = pSecondaryStudioHdr->pBone( nBone ); char *pszName = pOrigBone->pszName(); bool bFound = false; for ( int nBoneSearch = 0 ; nBoneSearch < m_nNumMasterBones; nBoneSearch++ ) { if ( strcmpi( m_pMasterBoneList[ nBoneSearch ]->pszName(), pszName ) == 0 ) { m_nBoneRemap[ nModel ][ nBone ] = nBoneSearch; m_nMasterToLocalBoneRemap[ nModel ][ nBoneSearch ] = nBone; bFound = true; break; } } if ( bFound == false ) { if ( m_nNumMasterBones >= COMBINER_MAX_BONES ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Too many bones from secondary model %s", pSecondaryStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_TOO_MANY_BONES ); } m_pMasterBoneList[ m_nNumMasterBones ] = pOrigBone; m_nBoneModelOwner[ m_nNumMasterBones ] = nModel; m_nBoneRemap[ nModel ][ nBone ] = m_nNumMasterBones; m_nMasterToLocalBoneRemap[ nModel ][ m_nNumMasterBones ] = nBone; m_nNumMasterBones++; } } } } void CModelCombine::CombineMDL_PreintStrings( ) { char *pStringData = m_pStudioHdr2[ 0 ]->pszName(); // this is the first entry in the string table; char *pEndData = ( ( char * ) m_pStudioHdr[ 0 ] ) + m_pStudioHdr[ 0 ]->length; while( pStringData < pEndData ) { if ( numStrings >= COMBINER_MAX_STRINGS ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "Too many initial strings %d > %d", numStrings, COMBINER_MAX_STRINGS ); throw( COMBINE_RESULT_FLAG_TOO_MANY_STRINGS ); } strings[ numStrings ].base = NULL; strings[ numStrings ].ptr = NULL; strings[ numStrings ].string = pStringData; strings[ numStrings ].dupindex = -2; numStrings++; pStringData += strlen( pStringData ) + 1; } } void CModelCombine::WriteBoneProc( int nSize, int nType1, int nType2 ) { for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { const mstudiobone_t *pOrigBone = m_pMasterBoneList[ nBone ]; mstudiobone_t *pNewBone = (mstudiobone_t *)m_pCombinedStudioHdr->pBone( nBone ); if ( pNewBone->proctype == nType1 || pNewBone->proctype == nType2 ) { char *pPos = g_CombinerWriter.WriteBuffer( pOrigBone->pProcedure(), nSize ); pNewBone->procindex = (byte *)pPos - (byte *)pNewBone; } } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::WriteBoneQuatInterp( ) { for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { const mstudiobone_t *pOrigBone = m_pMasterBoneList[ nBone ]; mstudiobone_t *pNewBone = (mstudiobone_t *)m_pCombinedStudioHdr->pBone( nBone ); if ( pNewBone->proctype == STUDIO_PROC_QUATINTERP ) { mstudioquatinterpbone_t *pOrigProc = ( mstudioquatinterpbone_t * )pOrigBone->pProcedure(); mstudioquatinterpbone_t *pNewProc = ( mstudioquatinterpbone_t * )pNewBone->pProcedure(); char *pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTrigger( 0 ), pOrigProc->numtriggers * sizeof( mstudioquatinterpinfo_t ) ); pNewProc->triggerindex = (byte *)pPos - (byte *)pNewProc; } } // AlignWrite( 4 ); } void CModelCombine::WriteBoneTwist( ) { for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { const mstudiobone_t *pOrigBone = m_pMasterBoneList[ nBone ]; mstudiobone_t *pNewBone = (mstudiobone_t *)m_pCombinedStudioHdr->pBone( nBone ); if ( pNewBone->proctype == STUDIO_PROC_TWIST_MASTER || pNewBone->proctype == STUDIO_PROC_TWIST_SLAVE ) { mstudiotwistbone_t *pOrigProc = ( mstudiotwistbone_t * )pOrigBone->pProcedure(); mstudiotwistbone_t *pNewProc = ( mstudiotwistbone_t * )pNewBone->pProcedure(); char *pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTarget( 0 ), pOrigProc->m_nTargetCount * sizeof( mstudiotwistbonetarget_t ) ); pNewProc->m_nTargetIndex = (byte *)pPos - (byte *)pNewProc; g_CombinerWriter.AlignWrite( 4 ); } } // AlignWrite( 4 ); } void CModelCombine::WriteBoneConstraints( ) { for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { const mstudiobone_t *pOrigBone = m_pMasterBoneList[ nBone ]; mstudiobone_t *pNewBone = (mstudiobone_t *)m_pCombinedStudioHdr->pBone( nBone ); // make this not repeat the same code switch( pNewBone->proctype ) { case STUDIO_PROC_POINT_CONSTRAINT: { char *pPos = g_CombinerWriter.WriteBuffer( pOrigBone->pProcedure(), sizeof( mstudiopointconstraint_t ) ); pNewBone->procindex = (byte *)pPos - (byte *)pNewBone; g_CombinerWriter.AlignWrite( 4 ); mstudiopointconstraint_t *pOrigProc = ( mstudiopointconstraint_t * )pOrigBone->pProcedure(); mstudiopointconstraint_t *pNewProc = ( mstudiopointconstraint_t * )pNewBone->pProcedure(); pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTarget( 0 ), pOrigProc->m_nTargetCount * sizeof( mstudioconstrainttarget_t ) ); pNewProc->m_nTargetIndex = (byte *)pPos - (byte *)pNewProc; g_CombinerWriter.AlignWrite( 4 ); } break; case STUDIO_PROC_ORIENT_CONSTRAINT: { char *pPos = g_CombinerWriter.WriteBuffer( pOrigBone->pProcedure(), sizeof( mstudioorientconstraint_t ) ); pNewBone->procindex = (byte *)pPos - (byte *)pNewBone; g_CombinerWriter.AlignWrite( 4 ); mstudioorientconstraint_t *pOrigProc = ( mstudioorientconstraint_t * )pOrigBone->pProcedure(); mstudioorientconstraint_t *pNewProc = ( mstudioorientconstraint_t * )pNewBone->pProcedure(); pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTarget( 0 ), pOrigProc->m_nTargetCount * sizeof( mstudioconstrainttarget_t ) ); pNewProc->m_nTargetIndex = (byte *)pPos - (byte *)pNewProc; g_CombinerWriter.AlignWrite( 4 ); } break; case STUDIO_PROC_AIM_CONSTRAINT: { char *pPos = g_CombinerWriter.WriteBuffer( pOrigBone->pProcedure(), sizeof( mstudioaimconstraint_t ) ); pNewBone->procindex = (byte *)pPos - (byte *)pNewBone; g_CombinerWriter.AlignWrite( 4 ); mstudioaimconstraint_t *pOrigProc = ( mstudioaimconstraint_t * )pOrigBone->pProcedure(); mstudioaimconstraint_t *pNewProc = ( mstudioaimconstraint_t * )pNewBone->pProcedure(); pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTarget( 0 ), pOrigProc->m_nTargetCount * sizeof( mstudioconstrainttarget_t ) ); pNewProc->m_nTargetIndex = (byte *)pPos - (byte *)pNewProc; g_CombinerWriter.AlignWrite( 4 ); } break; case STUDIO_PROC_PARENT_CONSTRAINT: { char *pPos = g_CombinerWriter.WriteBuffer( pOrigBone->pProcedure(), sizeof( mstudioparentconstraint_t ) ); pNewBone->procindex = (byte *)pPos - (byte *)pNewBone; g_CombinerWriter.AlignWrite( 4 ); mstudioparentconstraint_t *pOrigProc = ( mstudioparentconstraint_t * )pOrigBone->pProcedure(); mstudioparentconstraint_t *pNewProc = ( mstudioparentconstraint_t * )pNewBone->pProcedure(); pPos = g_CombinerWriter.WriteBuffer( pOrigProc->pTarget( 0 ), pOrigProc->m_nTargetCount * sizeof( mstudioconstrainttarget_t ) ); pNewProc->m_nTargetIndex = (byte *)pPos - (byte *)pNewProc; g_CombinerWriter.AlignWrite( 4 ); } break; } } } void CModelCombine::WriteBoneAttachments( ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->localattachmentindex ); m_pCombinedStudioHdr->numlocalattachments = 0; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ nModel ]; int nNumAttachments = pStudioHdr->numlocalattachments; for( int nAttachment = 0; nAttachment < nNumAttachments; nAttachment++ ) { m_pCombinedStudioHdr->numlocalattachments++; mstudioattachment_t *pOrigAttachment = pStudioHdr->pLocalAttachment( nAttachment ); mstudioattachment_t *pNewAttachment = m_pCombinedStudioHdr->pLocalAttachment( m_pCombinedStudioHdr->numlocalattachments - 1 ); g_CombinerWriter.WriteBuffer( pOrigAttachment, sizeof( *pOrigAttachment ) ); AddToStringTable( pNewAttachment, &pNewAttachment->sznameindex, pOrigAttachment->pszName() ); RemapBone( nModel, pNewAttachment->localbone ); } } g_CombinerWriter.AlignWrite( 4 ); VerifyField( offsetof( studiohdr_t, localattachmentindex ), "BoneAttachments" ); } void CModelCombine::WriteHitBoxes( ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->hitboxsetindex ); m_pCombinedStudioHdr->numhitboxsets = 0; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { if ( nModel > 0 ) { // for now, we are only interested in the hitbox sets of the primary model break; } studiohdr_t *pStudioHdr = m_pStudioHdr[ nModel ]; int nNumHitBoxSets = pStudioHdr->numhitboxsets; for( int nHitBoxSet = 0; nHitBoxSet < nNumHitBoxSets; nHitBoxSet++ ) { m_pCombinedStudioHdr->numhitboxsets++; mstudiohitboxset_t *pOrigHitBoxSet = pStudioHdr->pHitboxSet( nHitBoxSet ); mstudiohitboxset_t *pNewHitBoxSet = m_pCombinedStudioHdr->pHitboxSet( m_pCombinedStudioHdr->numhitboxsets - 1 ); g_CombinerWriter.WriteBuffer( pOrigHitBoxSet, sizeof( *pOrigHitBoxSet ) ); AddToStringTable( pNewHitBoxSet, &pNewHitBoxSet->sznameindex, pOrigHitBoxSet->pszName() ); } } g_CombinerWriter.AlignWrite( 4 ); for( int nHitBoxSet = 0; nHitBoxSet < m_pCombinedStudioHdr->numhitboxsets; nHitBoxSet++ ) { mstudiohitboxset_t *pNewHitBoxSet = m_pCombinedStudioHdr->pHitboxSet( nHitBoxSet ); pNewHitBoxSet->numhitboxes = 0; g_CombinerWriter.WriteOffset( pNewHitBoxSet->hitboxindex, pNewHitBoxSet ); for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ nModel ]; if ( nModel > 0 ) { break; } if ( nHitBoxSet < pStudioHdr->numhitboxsets ) { mstudiohitboxset_t *pOrigHitBoxSet = pStudioHdr->pHitboxSet( nHitBoxSet ); for( int nHitBox = 0; nHitBox < pOrigHitBoxSet->numhitboxes; nHitBox++ ) { pNewHitBoxSet->numhitboxes++; mstudiobbox_t *pOrigHitBox = pOrigHitBoxSet->pHitbox( nHitBox ); mstudiobbox_t *pNewHitBox = pNewHitBoxSet->pHitbox( pNewHitBoxSet->numhitboxes - 1 ); g_CombinerWriter.WriteBuffer( pOrigHitBox, sizeof( *pOrigHitBox ) ); AddToStringTable( pNewHitBox, &pNewHitBox->szhitboxnameindex, pOrigHitBox->pszHitboxName() ); RemapBone( nModel, pNewHitBox->bone ); } } } g_CombinerWriter.AlignWrite( 4 ); } VerifyField( offsetof( studiohdr_t, hitboxsetindex ), "HitBoxes" ); } // compare function for qsort below int CModelCombine::BoneNameCompare( const void *elem1, const void *elem2 ) { int index1 = *(byte *)elem1; int index2 = *(byte *)elem2; // compare bones by name return strcmpi( s_pCurrentCombine->m_pMasterBoneList[ index1 ]->pszName(), s_pCurrentCombine->m_pMasterBoneList[ index2 ]->pszName() ); } void CModelCombine::WriteBoneTable( ) { byte *pBoneTable = ( byte * )g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->bonetablebynameindex ); g_CombinerWriter.AllocWrite( sizeof( *pBoneTable ) * m_pCombinedStudioHdr->numbones ); for ( int i = 0; i < m_pCombinedStudioHdr->numbones; i++ ) { pBoneTable[ i ] = i; } qsort( pBoneTable, m_pCombinedStudioHdr->numbones, sizeof( byte ), BoneNameCompare ); } void CModelCombine::CombineMDL_Bones( ) { studiohdr_t *pPrimaryStudioHdr = m_pStudioHdr[ 0 ]; AddToStringTable( m_pCombinedStudioHdr, &m_pCombinedStudioHdr->surfacepropindex, pPrimaryStudioHdr->pszSurfaceProp() ); int *nFlags = ( int * )stackalloc( sizeof( int ) * m_nNumMasterBones ); memset( nFlags, 0, sizeof( int ) * m_nNumMasterBones ); for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { OptimizedModel::FileHeader_t *pOrigHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ nModel ]->PeekGet(); int nBoneTest = 0; int nBoneProp = 0; if ( nModel > 0 ) { nBoneTest = BONE_USED_BY_VERTEX_AT_LOD( pOrigHeader->numLODs - 1 ); for( int nLOD = pOrigHeader->numLODs; nLOD < m_pCombinedHardwareHeader->numLODs; nLOD++ ) { nBoneProp |= BONE_USED_BY_VERTEX_AT_LOD( nLOD ); } } for( int nBone = 0; nBone < m_pStudioHdr[ nModel ]->numbones; nBone++ ) { nFlags[ m_nBoneRemap[ nModel ][ nBone ] ] |= m_pStudioHdr[ nModel ]->pBone( nBone )->flags; if ( nModel > 0 && pOrigHeader->numLODs < m_pCombinedHardwareHeader->numLODs ) { if ( ( m_pStudioHdr[ nModel ]->pBone( nBone )->flags & nBoneTest ) != 0 ) { nFlags[ m_nBoneRemap[ nModel ][ nBone ] ] |= nBoneProp; } } } } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->boneindex ); m_pCombinedStudioHdr->numbones = 0; for( int nBone = 0; nBone < pPrimaryStudioHdr->numbones; nBone++ ) { const mstudiobone_t *pOrigBone = pPrimaryStudioHdr->pBone( nBone ); mstudiobone_t *pNewBone = ( mstudiobone_t * )g_CombinerWriter.WriteBuffer( pOrigBone, sizeof( mstudiobone_t ) ); AddToStringTable( pNewBone, &pNewBone->sznameindex, pOrigBone->pszName() ); AddToStringTable( pNewBone, &pNewBone->surfacepropidx, pOrigBone->pszSurfaceProp() ); pNewBone->flags = nFlags[ nBone ]; m_pCombinedStudioHdr->numbones++; } for( int nBone = m_pCombinedStudioHdr->numbones; nBone < m_nNumMasterBones; nBone++ ) { const mstudiobone_t *pOrigBone = m_pMasterBoneList[ nBone ]; mstudiobone_t *pNewBone = ( mstudiobone_t * )g_CombinerWriter.WriteBuffer( pOrigBone, sizeof( mstudiobone_t ) ); AddToStringTable( pNewBone, &pNewBone->sznameindex, pOrigBone->pszName() ); AddToStringTable( pNewBone, &pNewBone->surfacepropidx, pOrigBone->pszSurfaceProp() ); if ( pNewBone->parent >= 0 ) { pNewBone->parent = m_nBoneRemap[ m_nBoneModelOwner[ nBone ] ][ pNewBone->parent ]; } pNewBone->flags = nFlags[ nBone ]; m_pCombinedStudioHdr->numbones++; } g_CombinerWriter.AlignWrite( 4 ); WriteBoneProc( sizeof( mstudioaxisinterpbone_t ), STUDIO_PROC_AXISINTERP ); WriteBoneProc( sizeof( mstudioquatinterpbone_t ), STUDIO_PROC_QUATINTERP ); WriteBoneQuatInterp(); WriteBoneProc( sizeof( mstudiojigglebone_t ), STUDIO_PROC_JIGGLE ); WriteBoneProc( sizeof( mstudioaimatbone_t ), STUDIO_PROC_AIMATBONE, STUDIO_PROC_AIMATATTACH ); // aimAttach needs fixup WriteBoneProc( sizeof( mstudiotwistbone_t ), STUDIO_PROC_TWIST_MASTER, STUDIO_PROC_TWIST_SLAVE ); WriteBoneTwist(); WriteBoneConstraints(); if ( pPrimaryStudioHdr->numbonecontrollers > 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model has bone controllers: %s", pPrimaryStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->bonecontrollerindex ); g_CombinerWriter.AlignWrite( 4 ); WriteBoneAttachments(); WriteHitBoxes(); WriteBoneTable(); g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::WriteAnimation( mstudioanimdesc_t *pOrigAnim, void *pAnimData, int nFrameSize ) { if ( ( pOrigAnim->flags & STUDIO_FRAMEANIM ) != 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model has anim frames" ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } else { mstudio_rle_anim_t *pOrigFrame = ( mstudio_rle_anim_t * )( pAnimData ); while( 1 ) { int nSize = sizeof( mstudio_rle_anim_t ); if ( pOrigFrame->flags & STUDIO_ANIM_RAWROT2 ) { nSize += sizeof( Quaternion64 ); } if ( pOrigFrame->flags & STUDIO_ANIM_RAWPOS ) { nSize += sizeof( Vector48 ); } if ( pOrigFrame->flags & STUDIO_ANIM_ANIMROT ) { nSize += sizeof( mstudioanim_valueptr_t ); } if ( pOrigFrame->flags & STUDIO_ANIM_ANIMPOS ) { nSize += sizeof( mstudioanim_valueptr_t ); } if ( pOrigFrame->flags & STUDIO_ANIM_ANIMROT ) { mstudioanim_valueptr_t *rotvptr = pOrigFrame->pRotV(); for (int k = 0; k < 3; k++) { mstudioanimvalue_t *pAnimValue = rotvptr->pAnimvalue( k ); if ( pAnimValue != NULL ) { int nCount = nFrameSize; while( nCount > 0 ) { nSize += sizeof( *pAnimValue ) * ( pAnimValue->num.valid + 1 ); nCount -= pAnimValue->num.total; pAnimValue += pAnimValue->num.valid + 1; } } } } if ( pOrigFrame->flags & STUDIO_ANIM_ANIMPOS ) { mstudioanim_valueptr_t *posvptr = pOrigFrame->pPosV(); for (int k = 0; k < 3; k++) { mstudioanimvalue_t *pAnimValue = posvptr->pAnimvalue( k ); if ( pAnimValue != NULL ) { int nCount = nFrameSize; while( nCount > 0 ) { nSize += sizeof( *pAnimValue ) * ( pAnimValue->num.valid + 1 ); nCount -= pAnimValue->num.total; pAnimValue += pAnimValue->num.valid + 1; } } } } // all of the indexes are relative, so a direct copy should be safe g_CombinerWriter.WriteBuffer( pOrigFrame, nSize ); // DebugCombineMsg( "%d: %d\n", pOrigFrame->bone, pOrigFrame->nextoffset - nSize ); if ( pOrigFrame->nextoffset == 0 ) { break; } else { pOrigFrame = pOrigFrame->pNext(); VerifyOffset( pOrigFrame, "Start rle_anim" ); } } g_CombinerWriter.AllocWrite( sizeof( mstudio_rle_anim_t ) ); // bug in studiomdl which adds an extra one of these } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::CombineMDL_Anims( ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ 0 ]; VerifyOffset( pStudioHdr->pLocalAnimdesc( 0 ), "Start Animations" ); g_CombinerWriter.WriteBufferWithOffset( pStudioHdr->pLocalAnimdesc( 0 ), sizeof( mstudioanimdesc_t ) * pStudioHdr->numlocalanim, m_pCombinedStudioHdr->localanimindex ); g_CombinerWriter.AlignWrite( 4 ); m_pCombinedStudioHdr->numlocalanim = pStudioHdr->numlocalanim; for( int nAnim = 0; nAnim < m_pCombinedStudioHdr->numlocalanim; nAnim++ ) { mstudioanimdesc_t *pOrigAnim = pStudioHdr->pLocalAnimdesc( nAnim ); mstudioanimdesc_t *pNewAnim = m_pCombinedStudioHdr->pLocalAnimdesc( nAnim ); pNewAnim->baseptr = ( byte * )g_CombinerWriter.GetWriteArea() - ( byte * )pNewAnim; AddToStringTable( pNewAnim, &pNewAnim->sznameindex, pOrigAnim->pszName() ); int nNumSections = 0; if ( pOrigAnim->sectionframes > 1 ) { nNumSections = ( pOrigAnim->numframes / pOrigAnim->sectionframes ) + 2; // studio.cpp line 113 pNewAnim->sectionindex = ( byte * )g_CombinerWriter.AllocWrite( nNumSections * sizeof( mstudioanimsections_t ) ) - (byte *)pNewAnim; } g_CombinerWriter.AlignWrite( 16 ); pNewAnim->sectionframes = pOrigAnim->sectionframes; g_CombinerWriter.WriteOffset( pNewAnim->animindex, pNewAnim ); DebugCombineMsg( "Anim %d: %d / %d ( %d )\n", nAnim, pOrigAnim->animindex, pNewAnim->animindex, pNewAnim->animindex - pOrigAnim->animindex ); if ( nNumSections > 1 ) { int nRemainingFrames = pOrigAnim->numframes; for( int nSection = 0; nSection < nNumSections; nSection++ ) { mstudioanimsections_t *pOrigSection = pOrigAnim->pSection( nSection ); mstudioanimsections_t *pNewSection = pNewAnim->pSection( nSection ); VerifyOffset( pOrigSection, "Start mstudioanimsections_t", pNewSection ); *pNewSection = *pOrigSection; g_CombinerWriter.WriteOffset( pNewSection->animindex, pNewAnim ); int nFrames; if ( nRemainingFrames > pOrigAnim->sectionframes ) { nRemainingFrames -= pOrigAnim->sectionframes; nFrames = pOrigAnim->sectionframes + 1; } else { nFrames = nRemainingFrames; nRemainingFrames = 0; } WriteAnimation( pOrigAnim, ( ( byte * )pOrigAnim ) + pOrigSection->animindex, nFrames ); } } else { WriteAnimation( pOrigAnim, pOrigAnim->pAnimBlock( pOrigAnim->animblock, pOrigAnim->animindex ), pOrigAnim->numframes ); } g_CombinerWriter.AlignWrite( 4 ); if ( pOrigAnim->numikrules > 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model has ik rules: %s", pStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } g_CombinerWriter.AlignWrite( 4 ); if ( pOrigAnim->numlocalhierarchy > 0 ) { g_CombinerWriter.WriteOffset( pNewAnim->localhierarchyindex, pNewAnim ); g_CombinerWriter.WriteBuffer( pOrigAnim->pHierarchy( 0 ), pOrigAnim->numlocalhierarchy * sizeof( mstudiolocalhierarchy_t ) ); for( int nLocalHierarchy = 0; nLocalHierarchy < pOrigAnim->numlocalhierarchy; nLocalHierarchy++ ) { mstudiolocalhierarchy_t *pOrigLocalHierarchy = pOrigAnim->pHierarchy( nLocalHierarchy ); mstudiolocalhierarchy_t *pNewLocalHierarchy = pNewAnim->pHierarchy( nLocalHierarchy ); g_CombinerWriter.WriteOffset( pNewLocalHierarchy->localanimindex, pNewLocalHierarchy ); mstudiocompressedikerror_t *pOrigCompressedIKError = pOrigLocalHierarchy->pLocalAnim(); mstudiocompressedikerror_t *pNewCompressedIKError = pNewLocalHierarchy->pLocalAnim(); g_CombinerWriter.WriteBuffer( pOrigCompressedIKError, sizeof( *pOrigCompressedIKError ) ); for( int nAnim = 0; nAnim < 6; nAnim++ ) { g_CombinerWriter.WriteOffset( pNewCompressedIKError->offset[ nAnim ], pNewCompressedIKError ); int nSize = 0; mstudioanimvalue_t *pAnimValue = pOrigCompressedIKError->pAnimvalue( nAnim ); if ( pAnimValue != NULL ) { int nCount = pOrigAnim->numframes; while( nCount > 0 ) { nSize += sizeof( *pAnimValue ) * ( pAnimValue->num.valid + 1 ); nCount -= pAnimValue->num.total; pAnimValue += pAnimValue->num.valid + 1; } } g_CombinerWriter.WriteBuffer( pOrigCompressedIKError->pAnimvalue( nAnim ), nSize ); } } } g_CombinerWriter.AlignWrite( 4 ); } for( int nAnim = 0; nAnim < m_pCombinedStudioHdr->numlocalanim; nAnim++ ) { mstudioanimdesc_t *pOrigAnim = pStudioHdr->pLocalAnimdesc( nAnim ); mstudioanimdesc_t *pNewAnim = m_pCombinedStudioHdr->pLocalAnimdesc( nAnim ); if ( pOrigAnim->nummovements > 0 ) { g_CombinerWriter.WriteOffset( pNewAnim->movementindex, pNewAnim ); VerifyOffset( pOrigAnim->pMovement( 0 ), "Start Movement" ); g_CombinerWriter.WriteBuffer( pOrigAnim->pMovement( 0 ), sizeof( mstudiomovement_t ) * pOrigAnim->nummovements ); g_CombinerWriter.AlignWrite( 4 ); } } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::CombineMDL_SequenceInfo( ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ 0 ]; VerifyField( offsetof( studiohdr_t, localseqindex ), "Sequence Info" ); m_pCombinedStudioHdr->numlocalseq = pStudioHdr->numlocalseq; g_CombinerWriter.WriteBufferWithOffset( pStudioHdr->pLocalSeqdesc( 0 ), sizeof( mstudioseqdesc_t ) * pStudioHdr->numlocalseq, m_pCombinedStudioHdr->localseqindex ); for( int nSequence = 0; nSequence < pStudioHdr->numlocalseq; nSequence++ ) { mstudioseqdesc_t *pOrigSequence = pStudioHdr->pLocalSeqdesc( nSequence ); mstudioseqdesc_t *pNewSequence = m_pCombinedStudioHdr->pLocalSeqdesc( nSequence ); AddToStringTable( pNewSequence, &pNewSequence->szlabelindex, pOrigSequence->pszLabel() ); AddToStringTable( pNewSequence, &pNewSequence->szactivitynameindex, pOrigSequence->pszActivityName() ); pNewSequence->baseptr = ( byte * )g_CombinerWriter.GetWriteArea() - ( byte * )pNewSequence; if ( pOrigSequence->groupsize[ 0 ] > 1 || pOrigSequence->groupsize[ 1 ] > 1 ) { g_CombinerWriter.WriteOffset( pNewSequence->posekeyindex, pNewSequence ); int nSize = ( pOrigSequence->groupsize[ 0 ] + pOrigSequence->groupsize[ 1 ] ) * sizeof( float ); g_CombinerWriter.WriteBuffer( pOrigSequence->pPoseKey( 0, 0 ), nSize ); } g_CombinerWriter.WriteOffset( pNewSequence->eventindex, pNewSequence ); if ( pOrigSequence->numevents > 0 ) { VerifyOffset( pOrigSequence->pEvent( 0 ), "Sequence Event" ); g_CombinerWriter.WriteBuffer( pOrigSequence->pEvent( 0 ), pOrigSequence->numevents * sizeof( mstudioevent_t ) ); for ( int nEvent = 0; nEvent < pOrigSequence->numevents; nEvent++ ) { mstudioevent_t *pOrigEvent = pOrigSequence->pEvent( nEvent ); mstudioevent_t *pNewEvent = pNewSequence->pEvent( nEvent ); if ( pOrigEvent->type == NEW_EVENT_STYLE ) { AddToStringTable( pNewEvent, &pNewEvent->szeventindex, pOrigEvent->pszEventName() ); } } } g_CombinerWriter.AlignWrite( 4 ); g_CombinerWriter.WriteOffset( pNewSequence->autolayerindex, pNewSequence ); if ( pOrigSequence->numautolayers > 0 ) { VerifyOffset( pOrigSequence->pAutolayer( 0 ), "Auto Layer" ); g_CombinerWriter.WriteBuffer( pOrigSequence->pAutolayer( 0 ), pOrigSequence->numautolayers * sizeof( mstudioautolayer_t ) ); } if ( pOrigSequence->weightlistindex < pOrigSequence->eventindex ) { // we are using an existing weight int nFound = -1; for( int nPreviousSequence = 0; nPreviousSequence < nSequence; nPreviousSequence++ ) { mstudioseqdesc_t *pOrigPreviousSequence = pStudioHdr->pLocalSeqdesc( nPreviousSequence ); if ( memcmp( pOrigPreviousSequence->pBoneweight( 0 ), pOrigSequence->pBoneweight( 0 ), pStudioHdr->numbones * sizeof( float ) ) == 0 ) { nFound = nPreviousSequence; break; } } if ( nFound != -1 ) { mstudioseqdesc_t *pNewPreviousSequence = m_pCombinedStudioHdr->pLocalSeqdesc( nFound ); pNewSequence->weightlistindex = ( ( byte * )pNewPreviousSequence->pBoneweight( 0 ) - ( byte * )pNewSequence); } else { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "was not able to find existing weight in model: %s", pStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_UNHANDLED_ISSUE ); } } else { g_CombinerWriter.WriteOffset( pNewSequence->weightlistindex, pNewSequence ); // Assert( m_nNumMasterBones == pStudioHdr->numbones ); // need to handle bone merge! g_CombinerWriter.WriteBuffer( pOrigSequence->pBoneweight( 0 ), pStudioHdr->numbones * sizeof( float ) ); float flTempWeight = 1.0f; for( int nNewBones = pStudioHdr->numbones; nNewBones < m_nNumMasterBones; nNewBones++ ) { // more efficient ways to do this, but for now, not knowing if I can just give them all 1.0f, I'll leave it g_CombinerWriter.WriteBuffer( &flTempWeight, sizeof( flTempWeight ) ); } } g_CombinerWriter.WriteOffset( pNewSequence->iklockindex, pNewSequence ); if ( pOrigSequence->numiklocks > 0 ) { VerifyOffset( pOrigSequence->pIKLock( 0 ), "IK Lock" ); g_CombinerWriter.WriteBuffer( pOrigSequence->pIKLock( 0 ), pOrigSequence->numiklocks * sizeof( mstudioiklock_t ) ); } g_CombinerWriter.AlignWrite( 4 ); g_CombinerWriter.WriteOffset( pNewSequence->animindexindex, pNewSequence ); int nSize = ( pOrigSequence->groupsize[ 0 ] * pOrigSequence->groupsize[ 1 ] ) * sizeof( short ); if ( nSize > 0 ) { VerifyOffset( ( ( ( byte * )pOrigSequence ) + pOrigSequence->animindexindex ), "AnimIndexIndex" ); g_CombinerWriter.WriteBuffer( ( ( ( byte * )pOrigSequence ) + pOrigSequence->animindexindex ), nSize ); } g_CombinerWriter.AlignWrite( 4 ); g_CombinerWriter.WriteOffset( pNewSequence->keyvalueindex, pNewSequence ); if( pOrigSequence->keyvaluesize > 0 ) { VerifyOffset( ( void * )pOrigSequence->KeyValueText(), "Sequence KV" ); g_CombinerWriter.WriteBuffer( ( void * )pOrigSequence->KeyValueText(), pOrigSequence->keyvaluesize * sizeof( char ) ); } g_CombinerWriter.AlignWrite( 4 ); g_CombinerWriter.WriteOffset( pNewSequence->activitymodifierindex, pNewSequence ); if ( pOrigSequence->numactivitymodifiers > 0 ) { g_CombinerWriter.WriteBuffer( pOrigSequence->pActivityModifier( 0 ), pOrigSequence->numactivitymodifiers * sizeof( mstudioactivitymodifier_t ) ); } g_CombinerWriter.AlignWrite( 4 ); for ( int nActivityModifier = 0; nActivityModifier < pOrigSequence->numactivitymodifiers; nActivityModifier++ ) { mstudioactivitymodifier_t *pOrigActivityModifier = pOrigSequence->pActivityModifier( nActivityModifier ); mstudioactivitymodifier_t *pNewActivityModifier = pNewSequence->pActivityModifier( nActivityModifier ); AddToStringTable( pNewActivityModifier, &pNewActivityModifier->sznameindex, pOrigActivityModifier->pszName() ); } nSize = pOrigSequence->numactivitymodifiers * sizeof( mstudioactivitymodifier_t ); byte *pTestPtr = ( ( ( byte * )pOrigSequence ) + pOrigSequence->activitymodifierindex ) + nSize; VerifyOffset( pTestPtr, "Final Sequence Write" ); } int *pNodeName = ( int * )g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->localnodenameindex ); g_CombinerWriter.AllocWrite( pStudioHdr->numlocalnodes * sizeof( *pNodeName ) ); g_CombinerWriter.AlignWrite( 4 ); for ( int nNodeIndex = 0; nNodeIndex < pStudioHdr->numlocalnodes; nNodeIndex++ ) { AddToStringTable( m_pCombinedStudioHdr, pNodeName, pStudioHdr->pszLocalNodeName( nNodeIndex ) ); pNodeName++; } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->localnodeindex ); if ( pStudioHdr->numlocalnodes > 0 ) { g_CombinerWriter.WriteBuffer( pStudioHdr->pLocalTransition( 0 ), pStudioHdr->numlocalnodes * pStudioHdr->numlocalnodes * sizeof( byte ) ); } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::WriteModel( int nModel ) { mstudiomodel_t *pOrigModel = m_pMasterModels[ 0 ][ nModel ]; mstudiomodel_t *pNewModel = &m_pCombinedModels[ nModel ]; g_CombinerWriter.WriteOffset( pNewModel->meshindex, pNewModel ); g_CombinerWriter.WriteBuffer( pOrigModel->pMesh( 0 ), pOrigModel->nummeshes * sizeof( mstudiomesh_t ) ); g_CombinerWriter.AlignWrite( 4 ); for ( int nMesh = 0; nMesh < pOrigModel->nummeshes; nMesh++ ) { // mstudiomesh_t *pOrigMesh = pOrigModel->pMesh( nMesh ); mstudiomesh_t *pNewMesh = pNewModel->pMesh( nMesh ); pNewMesh->numvertices = m_pCombinedVertex->numLODVertexes[ 0 ]; for( int nLOD = 0; nLOD < m_pCombinedVertex->numLODs; nLOD++ ) { pNewMesh->vertexdata.numLODVertexes[ nLOD ] = m_pCombinedVertex->numLODVertexes[ nLOD ]; } pNewMesh->modelindex = ( byte * )pNewModel - ( byte * )pNewMesh; } // pNewModel->vertexindex = pOrigModel->vertexindex; // externalVertexIndex += pmodel[i].numvertices * sizeof(mstudiovertex_t); // ALIGN4( externalTangentsIndex ); // pmodel[i].tangentsindex = (int)externalTangentsIndex; // externalTangentsIndex += pmodel[i].numvertices * sizeof( Vector4D ); g_CombinerWriter.WriteOffset( pNewModel->eyeballindex, pNewModel ); if ( pOrigModel->numeyeballs > 0 ) { g_CombinerWriter.WriteBuffer( pOrigModel->pEyeball( 0 ), pOrigModel->numeyeballs * sizeof( mstudioeyeball_t ) ); } for ( int nMesh = 0; nMesh < pOrigModel->nummeshes; nMesh++ ) { // mstudiomesh_t *pOrigMesh = pOrigModel->pMesh( nMesh ); mstudiomesh_t *pNewMesh = pNewModel->pMesh( nMesh ); pNewMesh->numflexes = 0; g_CombinerWriter.WriteOffset( pNewMesh->flexindex, pNewMesh ); for( int nSubModel = 0; nSubModel < m_pCombinedStudioData->m_nNumModels; nSubModel++ ) { if ( !m_pMasterModels[ nSubModel ][ nModel ] ) { continue; } mstudiomesh_t *pOrigFlexMesh = m_pMasterModels[ nSubModel ][ nModel ]->pMesh( nMesh ); if ( pOrigFlexMesh->numflexes ) { // figure out the total number of flex entries for this mesh and reserve space pNewMesh->numflexes += pOrigFlexMesh->numflexes; g_CombinerWriter.WriteBuffer( pOrigFlexMesh->pFlex( 0 ), pOrigFlexMesh->numflexes * sizeof( mstudioflex_t ) ); } } int nTotalFlexes = 0; if ( pNewMesh->numflexes > 0 ) { g_CombinerWriter.AlignWrite( 4 ); for( int nSubModel = 0; nSubModel < m_pCombinedStudioData->m_nNumModels; nSubModel++ ) { if ( !m_pMasterModels[ nSubModel ][ nModel ] ) { continue; } mstudiomesh_t *pOrigFlexMesh = m_pMasterModels[ nSubModel ][ nModel ]->pMesh( nMesh ); if ( pOrigFlexMesh->numflexes ) { for( int nFlex = 0; nFlex < pOrigFlexMesh->numflexes; nFlex++, nTotalFlexes++ ) { mstudioflex_t *pOrigFlex = pOrigFlexMesh->pFlex( nFlex ); mstudioflex_t *pNewFlex = pNewMesh->pFlex( nTotalFlexes ); bool bWrinkleVAnim = ( pOrigFlex->vertanimtype == STUDIO_VERT_ANIM_WRINKLE ); int nVAnimDeltaSize = bWrinkleVAnim ? sizeof( mstudiovertanim_wrinkle_t ) : sizeof( mstudiovertanim_t ); g_CombinerWriter.WriteOffset( pNewFlex->vertindex, pNewFlex ); g_CombinerWriter.WriteBuffer( pOrigFlex->pBaseVertanim(), pOrigFlex->numverts * nVAnimDeltaSize ); if ( bWrinkleVAnim ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model has anim wrinkle" ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); #if 0 for( int nVert = 0; nVert < pOrigFlex->numverts; nVert++ ) { mstudiovertanim_wrinkle_t *pNewVertAnimWrinkle = pNewFlex->pVertanimWrinkle( nVert ); } #endif } else { float flDestAnimFixPointScale = m_pCombinedStudioHdr->VertAnimFixedPointScale(); float flSourceAnimFixPointScale = m_pStudioHdr[ nSubModel ]->VertAnimFixedPointScale(); Vector vUpdate; for( int nVert = 0; nVert < pOrigFlex->numverts; nVert++ ) { mstudiovertanim_t *pOrigVertAnim = pOrigFlex->pVertanim( nVert ); mstudiovertanim_t *pNewVertAnim = pNewFlex->pVertanim( nVert ); int nVertIndex = pOrigVertAnim->index; nVertIndex += pOrigFlexMesh->vertexoffset; vUpdate = pNewVertAnim->GetDeltaFloat(); vUpdate /= flSourceAnimFixPointScale; vUpdate *= flDestAnimFixPointScale; pNewVertAnim->SetDeltaFloat( vUpdate ); vUpdate = pNewVertAnim->GetNDeltaFloat(); vUpdate /= flSourceAnimFixPointScale; vUpdate *= flDestAnimFixPointScale; pNewVertAnim->SetNDeltaFloat( vUpdate ); pNewVertAnim->index = m_nVertexRemap[ nSubModel ][ nVertIndex ] - pNewMesh->vertexoffset; } } } } } } } } void CModelCombine::CombineMDL_Model( ) { studiohdr_t *pPrimaryStudioHdr = m_pStudioHdr[ 0 ]; studiohdr_t *pFlexStudioHdr = NULL; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ nModel ]; if ( pStudioHdr->numflexdesc ) { #if 0 if ( pFlexStudioHdr ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model %s and %s both have flex specifications", pFlexStudioHdr->pszName(), pStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } #endif pFlexStudioHdr = pStudioHdr; m_nFlexModelSource = nModel; break; } } if ( !pFlexStudioHdr ) { pFlexStudioHdr = pPrimaryStudioHdr; m_nFlexModelSource = 0; } VerifyField( offsetof( studiohdr_t, bodypartindex ), "Body Part Sequence" ); g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->bodypartindex ); g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pBodypart( 0 ), pPrimaryStudioHdr->numbodyparts * sizeof( mstudiobodyparts_t ) ); m_pCombinedModels = ( mstudiomodel_t * )g_CombinerWriter.GetWritePos(); int nTotalModels = 0; for ( int nBodyPart = 0; nBodyPart < pPrimaryStudioHdr->numbodyparts; nBodyPart++ ) { mstudiobodyparts_t *pOrigBodyPart = pPrimaryStudioHdr->pBodypart( nBodyPart ); mstudiobodyparts_t *pNewBodyPart = m_pCombinedStudioHdr->pBodypart( nBodyPart ); AddToStringTable( pNewBodyPart, &pNewBodyPart->sznameindex, pOrigBodyPart->pszName() ); g_CombinerWriter.WriteOffset( pNewBodyPart->modelindex, pNewBodyPart ); for( int nModel = 0; nModel < pOrigBodyPart->nummodels; nModel++ ) { // build our list of master models m_pMasterModels[ 0 ][ nTotalModels ] = m_pMasterFlexModels[ nTotalModels ] = pOrigBodyPart->pModel( nModel ); g_CombinerWriter.WriteBuffer( m_pMasterModels[ 0 ][ nTotalModels ], sizeof( mstudiomodel_t ) ); for( int nSubModels = 1; nSubModels < m_pCombinedStudioData->m_nNumModels; nSubModels++ ) { m_pMasterModels[ nSubModels ][ nTotalModels ] = NULL; studiohdr_t *pSubStudioHdr = m_pStudioHdr[ nSubModels ]; if ( nBodyPart < pSubStudioHdr->numbodyparts ) { mstudiobodyparts_t *pSubBodyPart = pSubStudioHdr->pBodypart( nBodyPart ); if ( nModel < pSubBodyPart->nummodels ) { // if a sub model matches up to the body part m_pCombinedModels[ nTotalModels ].numvertices += pSubBodyPart->pModel( nModel )->numvertices; if ( nSubModels == m_nFlexModelSource ) { // if it is part of the primary flex specification m_pMasterFlexModels[ nTotalModels ] = pSubBodyPart->pModel( nModel ); } m_pMasterModels[ nSubModels ][ nTotalModels ] = pSubBodyPart->pModel( nModel ); } } } nTotalModels++; } } g_CombinerWriter.AlignWrite( 4 ); m_pCombinedStudioHdr->numflexdesc = pFlexStudioHdr->numflexdesc; m_pCombinedStudioHdr->numflexcontrollers = pFlexStudioHdr->numflexcontrollers; m_pCombinedStudioHdr->numflexrules = pFlexStudioHdr->numflexrules; m_pCombinedStudioHdr->numflexcontrollerui = pFlexStudioHdr->numflexcontrollerui; g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->flexdescindex ); VerifyField( offsetof( studiohdr_t, flexdescindex ), "Flex Desc" ); if ( pFlexStudioHdr->numflexdesc > 0 ) { g_CombinerWriter.WriteBuffer( pFlexStudioHdr->pFlexdesc( 0 ), pFlexStudioHdr->numflexdesc * sizeof( mstudioflexdesc_t ) ); } g_CombinerWriter.AlignWrite( 4 ); for ( int nFlexDesc = 0; nFlexDesc < pFlexStudioHdr->numflexdesc; nFlexDesc++ ) { mstudioflexdesc_t *pOrigFlexDesc = pFlexStudioHdr->pFlexdesc( nFlexDesc ); mstudioflexdesc_t *pNewFlexDesc = m_pCombinedStudioHdr->pFlexdesc( nFlexDesc ); AddToStringTable( pNewFlexDesc, &pNewFlexDesc->szFACSindex, pOrigFlexDesc->pszFACS() ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->flexcontrollerindex ); VerifyField( offsetof( studiohdr_t, flexdescindex ), "Flex Controller" ); if ( pFlexStudioHdr->numflexcontrollers > 0 ) { g_CombinerWriter.WriteBuffer( pFlexStudioHdr->pFlexcontroller( ( LocalFlexController_t )0 ), pFlexStudioHdr->numflexcontrollers * sizeof( mstudioflexcontroller_t ) ); for( int nFlexController = 0; nFlexController < pFlexStudioHdr->numflexcontrollers; nFlexController++ ) { mstudioflexcontroller_t *pOrigFlexController = pFlexStudioHdr->pFlexcontroller( ( LocalFlexController_t )nFlexController ); mstudioflexcontroller_t *pNewFlexController = m_pCombinedStudioHdr->pFlexcontroller( ( LocalFlexController_t )nFlexController ); AddToStringTable( pNewFlexController, &pNewFlexController->sznameindex, pOrigFlexController->pszName() ); AddToStringTable( pNewFlexController, &pNewFlexController->sztypeindex, pOrigFlexController->pszType() ); } } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->flexruleindex ); VerifyField( offsetof( studiohdr_t, flexruleindex ), "Flex Rules" ); if ( pFlexStudioHdr->numflexrules > 0 ) { g_CombinerWriter.WriteBuffer( pFlexStudioHdr->pFlexRule( 0 ), pFlexStudioHdr->numflexrules * sizeof( mstudioflexrule_t ) ); g_CombinerWriter.AlignWrite( 4 ); for( int nFlexRule = 0; nFlexRule < pFlexStudioHdr->numflexrules; nFlexRule++ ) { mstudioflexrule_t *pOrigFlexRule = pFlexStudioHdr->pFlexRule( nFlexRule ); mstudioflexrule_t *pNewFlexRule = m_pCombinedStudioHdr->pFlexRule( nFlexRule ); g_CombinerWriter.WriteOffset( pNewFlexRule->opindex, pNewFlexRule ); g_CombinerWriter.WriteBuffer( pOrigFlexRule->iFlexOp( 0 ), pOrigFlexRule->numops * sizeof( mstudioflexop_t ) ); g_CombinerWriter.AlignWrite( 4 ); } } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->flexcontrolleruiindex ); VerifyField( offsetof( studiohdr_t, flexcontrolleruiindex ), "Flex Controller UI" ); if ( pFlexStudioHdr->numflexcontrollerui > 0 ) { g_CombinerWriter.WriteBuffer( pFlexStudioHdr->pFlexControllerUI( 0 ), pFlexStudioHdr->numflexcontrollerui * sizeof( mstudioflexcontrollerui_t ) ); for( int nFlexControllerUI = 0; nFlexControllerUI < pFlexStudioHdr->numflexcontrollerui; nFlexControllerUI++ ) { mstudioflexcontrollerui_t *pOrigFlexControllerUI = pFlexStudioHdr->pFlexControllerUI( nFlexControllerUI ); mstudioflexcontrollerui_t *pNewFlexControllerUI = m_pCombinedStudioHdr->pFlexControllerUI( nFlexControllerUI ); AddToStringTable( pNewFlexControllerUI, &pNewFlexControllerUI->sznameindex, pOrigFlexControllerUI->pszName() ); // not worrying about remapping the controller indexes here yet } g_CombinerWriter.AlignWrite( 4 ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->ikchainindex ); VerifyField( offsetof( studiohdr_t, ikchainindex ), "IK Chain" ); if ( pPrimaryStudioHdr->numikchains > 0 ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pIKChain( 0 ), pPrimaryStudioHdr->numikchains * sizeof( mstudioikchain_t ) ); g_CombinerWriter.AlignWrite( 4 ); for( int nIKChain = 0; nIKChain < pPrimaryStudioHdr->numikchains; nIKChain++ ) { mstudioikchain_t *pOrigIKChain = pPrimaryStudioHdr->pIKChain( nIKChain ); mstudioikchain_t *pNewIKChain = m_pCombinedStudioHdr->pIKChain( nIKChain ); AddToStringTable( pNewIKChain, &pNewIKChain->sznameindex, pNewIKChain->pszName() ); g_CombinerWriter.WriteOffset( pNewIKChain->linkindex, pNewIKChain ); g_CombinerWriter.WriteBuffer( pOrigIKChain->pLink( 0 ), pOrigIKChain->numlinks * sizeof( mstudioiklink_t ) ); } } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->localikautoplaylockindex ); VerifyField( offsetof( studiohdr_t, localikautoplaylockindex ), "IK Autoplay Lock" ); if ( pPrimaryStudioHdr->numlocalikautoplaylocks > 0 ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pLocalIKAutoplayLock( 0 ), pPrimaryStudioHdr->numlocalikautoplaylocks * sizeof( mstudioiklock_t ) ); g_CombinerWriter.AlignWrite( 4 ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->mouthindex ); VerifyField( offsetof( studiohdr_t, mouthindex ), "Mouth" ); if ( pPrimaryStudioHdr->nummouths ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pMouth( 0 ), pPrimaryStudioHdr->nummouths * sizeof( mstudiomouth_t ) ); g_CombinerWriter.AlignWrite( 4 ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->localposeparamindex ); VerifyField( offsetof( studiohdr_t, localposeparamindex ), "Pose Param" ); if ( pPrimaryStudioHdr->numlocalposeparameters > 0 ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pLocalPoseParameter( 0 ), pPrimaryStudioHdr->numlocalposeparameters * sizeof( mstudioposeparamdesc_t ) ); g_CombinerWriter.AlignWrite( 4 ); for( int nPoseParam = 0; nPoseParam < pPrimaryStudioHdr->numlocalposeparameters; nPoseParam++ ) { mstudioposeparamdesc_t *pOrigPoseParam = pPrimaryStudioHdr->pLocalPoseParameter( nPoseParam ); mstudioposeparamdesc_t *pNewPoseParam = m_pCombinedStudioHdr->pLocalPoseParameter( nPoseParam ); AddToStringTable( pNewPoseParam, &pNewPoseParam->sznameindex, pOrigPoseParam->pszName() ); } } for( int nModel = 0; nModel < nTotalModels; nModel++ ) { WriteModel( nModel ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->includemodelindex ); VerifyField( offsetof( studiohdr_t, includemodelindex ), "Model Groups" ); if ( pPrimaryStudioHdr->numincludemodels > 0 ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pModelGroup( 0 ), pPrimaryStudioHdr->numincludemodels * sizeof( mstudiomodelgroup_t ) ); for( int nModelGroup = 0; nModelGroup < pPrimaryStudioHdr->numincludemodels; nModelGroup++ ) { mstudiomodelgroup_t *pOrigModelGroup = pPrimaryStudioHdr->pModelGroup( nModelGroup ); mstudiomodelgroup_t *pNewModelGroup = m_pCombinedStudioHdr->pModelGroup( nModelGroup ); AddToStringTable( pNewModelGroup, &pNewModelGroup->sznameindex, pOrigModelGroup->pszName() ); } } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->animblockindex ); VerifyField( offsetof( studiohdr_t, animblockindex ), "Anim Blocks" ); if ( pPrimaryStudioHdr->numanimblocks > 0 ) { g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr->pAnimBlock( 0 ), pPrimaryStudioHdr->numanimblocks * sizeof( mstudioanimblock_t ) ); Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model has anim blocks: %s", pPrimaryStudioHdr->pszName() ); throw( COMBINE_RESULT_FLAG_UNHANDLED_ISSUE ); } g_CombinerWriter.AlignWrite( 4 ); AddToStringTable( m_pCombinedStudioHdr, &m_pCombinedStudioHdr->szanimblocknameindex, pPrimaryStudioHdr->pszAnimBlockName() ); } void CModelCombine::CombineMDL_Textures( ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->textureindex ); m_pCombinedStudioHdr->numtextures = m_pCombinedStudioData->m_nNumAtlasGroups + m_pCombinedStudioData->m_nNumNonAtlasedMaterialBaseNames; // first allocate/write out "blank" texture entries (materials) mstudiotexture_t NewTexture; memset( &NewTexture, 0, sizeof( NewTexture ) ); NewTexture.sznameindex = 0; NewTexture.used = 1; for ( int nAtlasGroup = 0; nAtlasGroup < m_pCombinedStudioHdr->numtextures; nAtlasGroup++ ) { g_CombinerWriter.WriteBuffer( &NewTexture, sizeof( mstudiotexture_t ) ); } g_CombinerWriter.AlignWrite( 4 ); // then fix up those written entries with proper name index values from the string table for ( int nAtlasGroup = 0; nAtlasGroup < m_pCombinedStudioData->m_nNumAtlasGroups; nAtlasGroup++ ) { mstudiotexture_t *pNewTexture = m_pCombinedStudioHdr->pTexture( nAtlasGroup ); // we use a specialized name here that other systems won't understand V_sprintf_safe( m_pCombinedStudioData->m_AtlasGroups[ nAtlasGroup ].m_szCombinedMaterialName, "!%s|%d|%hu|%d!", m_pCombinedStudioData->m_szCombinedModelName, nAtlasGroup, m_pCombinedStudioData->m_FinalHandle, GetNextAssetID() ); AddToStringTable( pNewTexture, &pNewTexture->sznameindex, m_pCombinedStudioData->m_AtlasGroups[ nAtlasGroup ].m_szCombinedMaterialName ); } for ( int nNonAtlasedMaterial = 0; nNonAtlasedMaterial < m_pCombinedStudioData->m_nNumNonAtlasedMaterialBaseNames; nNonAtlasedMaterial++ ) { int nMaterial = m_pCombinedStudioData->m_nNumAtlasGroups + nNonAtlasedMaterial; mstudiotexture_t *pNewTexture = m_pCombinedStudioHdr->pTexture( nMaterial ); AddToStringTable( pNewTexture, &pNewTexture->sznameindex, m_pCombinedStudioData->m_szNonAtlasedMaterialBaseName[ nNonAtlasedMaterial ] ); } // write out material paths (used for non atlased materials) int *pNewCDTexture = ( int * )g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->cdtextureindex ); m_pCombinedStudioHdr->numcdtextures = m_pCombinedStudioData->m_nNumNonAtlasedMaterialPaths; g_CombinerWriter.AllocWrite( m_pCombinedStudioHdr->numcdtextures * sizeof( int ) ); g_CombinerWriter.AlignWrite( 4 ); for ( int nMaterialPaths = 0; nMaterialPaths < m_pCombinedStudioData->m_nNumNonAtlasedMaterialPaths; nMaterialPaths++ ) { AddToStringTable( m_pCombinedStudioHdr, &pNewCDTexture[ nMaterialPaths ], m_pCombinedStudioData->m_szNonAtlasedMaterialPaths[ nMaterialPaths ] ); } g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->skinindex ); m_pCombinedStudioHdr->numskinref = m_pCombinedStudioHdr->numtextures; m_pCombinedStudioHdr->numskinfamilies = 1; for ( short nTexture = 0; nTexture < static_cast< short >( m_pCombinedStudioHdr->numtextures ); nTexture++ ) { g_CombinerWriter.WriteBuffer( &nTexture, sizeof( short ) ); } g_CombinerWriter.AlignWrite( 4 ); } void CModelCombine::CombineMDL_KeyValues( ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ 0 ]; g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr->keyvalueindex ); VerifyField( offsetof( studiohdr_t, keyvalueindex ), "KeyValues" ); if ( pStudioHdr->keyvaluesize > 0 ) { g_CombinerWriter.WriteBuffer( pStudioHdr->KeyValueText(), pStudioHdr->keyvaluesize ); } g_CombinerWriter.AlignWrite( 4 ); } #define WRITE_BONE_BLOCK( type, srcfield, dest, destindex ) \ g_CombinerWriter.WriteOffset( pNewLinearBone->destindex, pNewLinearBone ); \ type *dest = (type *)g_CombinerWriter.AllocWrite( m_nNumMasterBones * sizeof( type ) ); \ g_CombinerWriter.AlignWrite( 4 ); \ for ( int i = 0; i < m_nNumMasterBones; i++) \ { \ dest[ i ] = m_pMasterBoneList[ i ]->srcfield; \ } void CModelCombine::CombineMDL_BoneTransforms( ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr2->srcbonetransformindex ); VerifyField2( offsetof( studiohdr2_t, srcbonetransformindex ), "Bone Transforms" ); // need to merge bone transforms from the sub models? if ( m_pStudioHdr2[ 0 ]->numsrcbonetransform > 0 ) { g_CombinerWriter.WriteBuffer( m_pStudioHdr[ 0 ]->SrcBoneTransform( 0 ), m_pStudioHdr2[ 0 ]->numsrcbonetransform * sizeof( mstudiosrcbonetransform_t ) ); for( int nBoneTransform = 0; nBoneTransform < m_pStudioHdr2[ 0 ]->numsrcbonetransform; nBoneTransform++ ) { mstudiosrcbonetransform_t *pOrigBoneTransform = ( mstudiosrcbonetransform_t * )m_pStudioHdr[ 0 ]->SrcBoneTransform( nBoneTransform ); mstudiosrcbonetransform_t *pNewBoneTransform = ( mstudiosrcbonetransform_t * )m_pCombinedStudioHdr->SrcBoneTransform( nBoneTransform ); AddToStringTable( pNewBoneTransform, &pNewBoneTransform->sznameindex, pOrigBoneTransform->pszName() ); } } g_CombinerWriter.AlignWrite( 4 ); if ( m_pStudioHdr2[ 0 ]->pLinearBones() != NULL ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr2->linearboneindex, m_pCombinedStudioHdr2 ); VerifyField2( offsetof( studiohdr2_t, linearboneindex ), "Linear Bone Index" ); mstudiolinearbone_t *pNewLinearBone = ( mstudiolinearbone_t * )g_CombinerWriter.WriteBuffer( m_pStudioHdr2[ 0 ]->pLinearBones(), sizeof( mstudiolinearbone_t ) ); pNewLinearBone->numbones = m_nNumMasterBones; WRITE_BONE_BLOCK( int, flags, pFlags, flagsindex ); WRITE_BONE_BLOCK( int, parent, pParent, parentindex ); WRITE_BONE_BLOCK( Vector, pos, pPos, posindex ); WRITE_BONE_BLOCK( Quaternion, quat, pQuat, quatindex ); WRITE_BONE_BLOCK( RadianEuler, rot, pRot, rotindex ); WRITE_BONE_BLOCK( matrix3x4_t, poseToBone, pPoseToBone, posetoboneindex ); WRITE_BONE_BLOCK( Vector, posscale, pPoseScale, posscaleindex ); WRITE_BONE_BLOCK( Vector, rotscale, pRotScale, rotscaleindex ); WRITE_BONE_BLOCK( Quaternion, qAlignment, pQAlignment, qalignmentindex ); for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { mstudiobone_t *pNewBone = (mstudiobone_t *)m_pCombinedStudioHdr->pBone( nBone ); *pNewLinearBone->pflags( nBone ) = pNewBone->flags; } } } void CModelCombine::CombineMDL_BoneFlexDrivers( ) { m_pCombinedStudioHdr2->m_nBoneFlexDriverCount = m_pStudioHdr2[ m_nFlexModelSource ]->m_nBoneFlexDriverCount; if ( m_pStudioHdr2[ m_nFlexModelSource ]->m_nBoneFlexDriverCount > 0 ) { g_CombinerWriter.WriteOffset( m_pCombinedStudioHdr2->m_nBoneFlexDriverIndex, m_pCombinedStudioHdr2 ); VerifyField2( offsetof( studiohdr2_t, m_nBoneFlexDriverIndex ), "Bone Flex Drivers" ); g_CombinerWriter.WriteBuffer( m_pStudioHdr2[ m_nFlexModelSource ]->pBoneFlexDriver( 0 ), m_pStudioHdr2[ m_nFlexModelSource ]->m_nBoneFlexDriverCount * sizeof( mstudioboneflexdriver_t ) ); g_CombinerWriter.AlignWrite( 4 ); for( int nBoneFlexDriver = 0; nBoneFlexDriver < m_pStudioHdr2[ m_nFlexModelSource ]->m_nBoneFlexDriverCount; nBoneFlexDriver++ ) { mstudioboneflexdriver_t *pOrigBoneFlexDriver = m_pStudioHdr2[ m_nFlexModelSource ]->pBoneFlexDriver( nBoneFlexDriver ); mstudioboneflexdriver_t *pNewBoneFlexDriver = m_pCombinedStudioHdr2->pBoneFlexDriver( nBoneFlexDriver ); g_CombinerWriter.WriteOffset( pNewBoneFlexDriver->m_nControlIndex, pNewBoneFlexDriver ); g_CombinerWriter.WriteBuffer( pOrigBoneFlexDriver->pBoneFlexDriverControl( 0 ), pOrigBoneFlexDriver->m_nControlCount * sizeof( mstudioboneflexdrivercontrol_t ) ); /* for( int nBoneFlexDriverControl = 0; nBoneFlexDriverControl < pOrigBoneFlexDriver->m_nControlCount; nBoneFlexDriverControl++ ) { mstudioboneflexdrivercontrol_t *pOrigBoneFlexDriverControl = pOrigBoneFlexDriver->pBoneFlexDriverControl( nBoneFlexDriverControl ); mstudioboneflexdrivercontrol_t *pNewBoneFlexDriverControl = pNewBoneFlexDriver->pBoneFlexDriverControl( nBoneFlexDriver ); }*/ g_CombinerWriter.AlignWrite( 4 ); } } } void CModelCombine::CombineMDL_AssignMeshIDs( ) { int i; int j; int m; int numMeshes; mstudiobodyparts_t *pStudioBodyPart; mstudiomodel_t *pStudioModel; mstudiomesh_t *pStudioMesh; numMeshes = 0; for (i=0; inumbodyparts; i++) { pStudioBodyPart = m_pCombinedStudioHdr->pBodypart(i); for (j=0; jnummodels; j++) { pStudioModel = pStudioBodyPart->pModel(j); for (m=0; mnummeshes; m++) { // get each mesh pStudioMesh = pStudioModel->pMesh(m); pStudioMesh->meshid = numMeshes + m; } numMeshes += pStudioModel->nummeshes; } } } void CModelCombine::CombineMDL( bool bNoStringTable ) { g_CombinerWriter.AllocWrite( 256 ); g_CombinerWriter.AlignWrite( 16 ); g_CombinerWriter.InitWriteArea( WRITE_AREA_MDL, g_CombinerWriter.GetWritePos() ); g_CombinerWriter.SetWriteArea( WRITE_AREA_MDL ); m_nFlexModelSource = -1; studiohdr_t *pPrimaryStudioHdr = m_pStudioHdr[ 0 ]; m_pCombinedStudioHdr = ( studiohdr_t * )g_CombinerWriter.WriteBuffer( pPrimaryStudioHdr, sizeof( *pPrimaryStudioHdr ) ); m_pCombinedStudioHdr2 = ( studiohdr2_t * )g_CombinerWriter.WriteBufferWithOffset( pPrimaryStudioHdr->pStudioHdr2(), sizeof( studiohdr2_t ), m_pCombinedStudioHdr->studiohdr2index ); m_pCombinedStudioHdr->flags |= STUDIOHDR_FLAGS_COMBINED; BeginStringTable(); CombineMDL_PreintStrings(); AddToStringTable( m_pCombinedStudioHdr2, &m_pCombinedStudioHdr2->sznameindex, m_pStudioHdr2[ 0 ]->pszName() ); CombineMDL_Bones(); CombineMDL_Anims(); CombineMDL_SequenceInfo(); CombineMDL_Model(); CombineMDL_Textures(); CombineMDL_KeyValues(); CombineMDL_BoneTransforms(); CombineMDL_BoneFlexDrivers(); // int nStringOffset = m_pWritePos - m_pWriteArea; // const char *pszOrigStrings = ( ( char * ) m_pStudioHdr[ 0 ] ) + nStringOffset; // const char *pszNewStrings = ( char * )m_pWritePos; if ( !bNoStringTable ) { WriteStringTable(); } int nTotal = g_CombinerWriter.GetWritePos() - g_CombinerWriter.GetWriteArea(); m_pCombinedStudioHdr->length = nTotal; m_pCombinedStudioHdr->checksum = 0; for ( int i = 0; i < nTotal; i += 4 ) { // TODO: does this need something more than a simple shift left and add checksum? m_pCombinedStudioHdr->checksum = ( m_pCombinedStudioHdr->checksum << 1 ) + ( ( m_pCombinedStudioHdr->checksum & 0x8000000 ) ? 1 : 0 ) + *( ( long * )( g_CombinerWriter.GetWriteArea() + i ) ); } m_pCombinedHardwareHeader->checkSum = m_pCombinedStudioHdr->checksum; m_pCombinedVertex->checksum = m_pCombinedStudioHdr->checksum; CombineMDL_AssignMeshIDs(); } #ifdef DEBUG_COMBINE void CModelCombine::TestCombineMDL( ) { int nLength = m_pCombinedStudioHdr->length; char *pOrig = ( char * )m_pStudioHdr[ 0 ]; char *pNew = ( char * )m_pCombinedStudioHdr; char *pStart = pNew; if ( nLength != m_pStudioHdr[ 0 ]->length ) { Msg( "Test Failure: Original Length: %d, New Length: %d\n", m_pStudioHdr[ 0 ]->length, nLength ); if ( nLength > m_pStudioHdr[ 0 ]->length ) { nLength = m_pStudioHdr[ 0 ]->length; } } int nOffset = sizeof( studiohdr_t ) + sizeof( studiohdr2_t ); nLength -= nOffset; pOrig += nOffset; pNew += nOffset; char *pErrorPos = NULL; for( ; nLength; pOrig++, pNew++, nLength-- ) { if ( ( *pOrig ) != ( *pNew ) ) { pErrorPos = pNew; break; } } if ( pErrorPos != NULL ) { Msg( "Test Failure: Offset %d\n", pNew - pStart ); Init( m_pCombinedStudioData ); m_pErrorPos = pErrorPos; // need to do VVD and VTX to recreate memory CombineVVD(); CombineVTX(); CombineMDL( true ); } } #endif // DEBUG_COMBINE void CModelCombine::CalcVTXInfo() { for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { OptimizedModel::FileHeader_t *pOrigHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ nModel ]->PeekGet(); if ( pOrigHeader->numBodyParts > m_MaxHardwareData.m_nMaxBodyParts ) { m_MaxHardwareData.m_nMaxBodyParts = pOrigHeader->numBodyParts; } for( int nBodyPart = 0; nBodyPart < pOrigHeader->numBodyParts; nBodyPart++ ) { OptimizedModel::BodyPartHeader_t *pOrigBodyPart = pOrigHeader->pBodyPart( nBodyPart ); int nSubModelToUse = m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection; for( int nBodyModel = 0; nBodyModel < pOrigBodyPart->numModels; nBodyModel++ ) { // skip over sub models that are not the selected one (-1 no selection, so no skipping) if ( nSubModelToUse != -1 && nBodyModel != nSubModelToUse ) { continue; } OptimizedModel::ModelHeader_t *pOrigModel = pOrigBodyPart->pModel( nBodyModel ); for ( int nLOD = 0; nLOD < pOrigModel->numLODs; nLOD++ ) { OptimizedModel::ModelLODHeader_t *pOrigLOD = pOrigModel->pLOD( nLOD ); unsigned char nStripGroupFlags = OptimizedModel::STRIPGROUP_IS_HWSKINNED; for (int i = 0; i < 2; i++ ) { if ( i > 0 ) { nStripGroupFlags |= OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED; } for( int nMesh = 0; nMesh < pOrigLOD->numMeshes; nMesh++ ) { OptimizedModel::MeshHeader_t *pOrigMesh = pOrigLOD->pMesh( nMesh ); bool bUsed = false; for( int nStripGroup = 0; nStripGroup < pOrigMesh->numStripGroups; nStripGroup++ ) { OptimizedModel::StripGroupHeader_t *pOrigStripGroup = pOrigMesh->pStripGroup( nStripGroup ); if ( pOrigStripGroup->flags == nStripGroupFlags ) { // we are compatible for( int nStrip = 0; nStrip < pOrigStripGroup->numStrips; nStrip++ ) { OptimizedModel::StripHeader_t *pOrigStrip = pOrigStripGroup->pStrip( nStrip ); if ( pOrigStrip->numTopologyIndices > 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "strip has topology indices" ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } m_MaxHardwareData.m_nBoneStateChanges += pOrigStrip->numBoneStateChanges; m_MaxHardwareData.m_nIndices += pOrigStrip->numIndices; m_MaxHardwareData.m_nVerts += pOrigStrip->numVerts; m_MaxHardwareData.m_nStrips++; } bUsed = true; } } if ( bUsed ) { // we put things into this strip group m_MaxHardwareData.m_nStripGroups++; } } } m_MaxHardwareData.m_nModelLODs++; } } m_MaxHardwareData.m_nBodyParts++; } } } void CModelCombine::WriteStrip( OptimizedModel::StripGroupHeader_t *pNewStripGroup, int nModel, OptimizedModel::StripGroupHeader_t *pOrigStripGroup, OptimizedModel::StripHeader_t *pOrigStrip ) { OptimizedModel::Vertex_t *pNewVert = pNewStripGroup->pVertex( pNewStripGroup->numVerts ); // OptimizedModel::Vertex_t *pOrigVert = pOrigStripGroup->pVertex( pOrigStrip->vertOffset ); unsigned short *pNewIndex = pNewStripGroup->pIndex( pNewStripGroup->numIndices ); unsigned short *pOrigIndex = pOrigStripGroup->pIndex( pOrigStrip->indexOffset ); OptimizedModel::StripHeader_t *pNewStrip = pNewStripGroup->pStrip( pNewStripGroup->numStrips ); if ( pOrigStrip->numTopologyIndices > 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "strip has topology indices" ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } Assert( m_CurrentHardwareData.m_nStrips < m_MaxHardwareData.m_nStrips ); pNewStrip->numIndices = pOrigStrip->numIndices; pNewStrip->numTopologyIndices = pOrigStrip->numTopologyIndices; pNewStrip->indexOffset = pNewStripGroup->numIndices; pNewStrip->topologyOffset = pNewStripGroup->numTopologyIndices; pNewStrip->numVerts = pOrigStrip->numVerts; pNewStrip->vertOffset = pNewStripGroup->numVerts; pNewStrip->numBoneStateChanges = pOrigStrip->numBoneStateChanges; pNewStrip->numBones = pOrigStrip->numBones; pNewStrip->flags = pOrigStrip->flags; int boneFileOffset = m_HardwareOffsets.m_nBoneStateChanges + m_CurrentHardwareData.m_nBoneStateChanges * sizeof( OptimizedModel::BoneStateChangeHeader_t ); int stripFileOffset = m_HardwareOffsets.m_nStrips + m_CurrentHardwareData.m_nStrips * sizeof( OptimizedModel::StripHeader_t ); pNewStrip->boneStateChangeOffset = boneFileOffset - stripFileOffset; int nOffset = pNewStripGroup->numVerts - pOrigStrip->vertOffset; for( int nIndex = 0; nIndex < pOrigStrip->numIndices; nIndex++ ) { *pNewIndex = ( *pOrigIndex ) + nOffset; Assert( ( *pNewIndex ) >= pNewStrip->vertOffset ); Assert( ( *pNewIndex ) < pNewStrip->vertOffset + pNewStrip->numVerts ); pOrigIndex++; pNewIndex++; } // OptimizedModel::Vertex_t *pTestVert = pOrigStripGroup->pVertex( 0 ); // OptimizedModel::FileHeader_t *pPrimaryHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ 0 ]->PeekGet(); // Msg( "%d / %d\n", ( char * )pNewVert - ( char * )m_pCombinedHardwareHeader, ( char * )pTestVert - ( char * )pPrimaryHeader ); for( int nVert = 0; nVert < pOrigStrip->numVerts; nVert++ ) { OptimizedModel::Vertex_t *pOrigVert = pOrigStripGroup->pVertex( nVert + pOrigStrip->vertOffset ); *pNewVert = *pOrigVert; pNewVert->origMeshVertID = m_nVertexRemap[ nModel ][ pOrigVert->origMeshVertID ]; pNewVert++; } // pTestVert = pOrigStripGroup->pVertex( pOrigStrip->numVerts ); // Msg( "%d / %d\n", ( char * )pNewVert - ( char * )m_pCombinedHardwareHeader, ( char * )pTestVert - ( char * )pPrimaryHeader ); for( int nBone = 0; nBone < pOrigStrip->numBoneStateChanges; nBone++ ) { OptimizedModel::BoneStateChangeHeader_t *pNewBone = pNewStrip->pBoneStateChange( nBone ); OptimizedModel::BoneStateChangeHeader_t *pOrigBone = pOrigStrip->pBoneStateChange( nBone ); pNewBone->hardwareID = pOrigBone->hardwareID; pNewBone->newBoneID = m_nBoneRemap[ nModel ][ pOrigBone->newBoneID ]; } m_CurrentHardwareData.m_nBoneStateChanges += pNewStrip->numBoneStateChanges; pNewStripGroup->numIndices += pOrigStrip->numIndices; m_CurrentHardwareData.m_nIndices += pOrigStrip->numIndices; pNewStripGroup->numVerts += pOrigStrip->numVerts; m_CurrentHardwareData.m_nVerts += pOrigStrip->numVerts; pNewStripGroup->numStrips++; m_CurrentHardwareData.m_nStrips++; } void CModelCombine::MergeStripGroup( int nLOD, OptimizedModel::StripGroupHeader_t *pNewStripGroup, int nModel, OptimizedModel::StripGroupHeader_t *pOrigStripGroup ) { // OptimizedModel::Vertex_t *pNewVert = pNewStripGroup->pVertex( pNewStripGroup->numVerts ); // unsigned short *pNewIndex = pNewStripGroup->pIndex( pNewStripGroup->numIndices ); // OptimizedModel::StripHeader_t *pNewStrip = pNewStripGroup->pStrip( pNewStripGroup->numStrips ); for( int nStrip = 0; nStrip < pOrigStripGroup->numStrips; nStrip++ ) { OptimizedModel::StripHeader_t *pOrigStrip = pOrigStripGroup->pStrip( nStrip ); WriteStrip( pNewStripGroup, nModel, pOrigStripGroup, pOrigStrip ); m_pCombinedStudioData->m_Results.m_nNumIndexes[ nModel ][ nLOD ] += pOrigStrip->numIndices; } m_pCombinedStudioData->m_Results.m_nBatches[ nModel ][ nLOD ] += pOrigStripGroup->numStrips; m_pCombinedStudioData->m_Results.m_nCombinedBatches[ nLOD ] += pOrigStripGroup->numStrips; } void CModelCombine::WriteStripGroup( int nLOD, int nMaterialIndex, unsigned char nStripGroupFlags, OptimizedModel::MeshHeader_t *pNewMesh ) { OptimizedModel::StripGroupHeader_t *pNewStripGroup = pNewMesh->pStripGroup( pNewMesh->numStripGroups ); pNewStripGroup->numVerts = 0; pNewStripGroup->numIndices = 0; pNewStripGroup->numTopologyIndices = 0; pNewStripGroup->numStrips = 0; pNewStripGroup->flags = nStripGroupFlags; int stripGroupFileOffset = m_HardwareOffsets.m_nStripGroups + m_CurrentHardwareData.m_nStripGroups * sizeof( OptimizedModel::StripGroupHeader_t ); int vertsFileOffset = m_HardwareOffsets.m_nVerts + m_CurrentHardwareData.m_nVerts * sizeof( OptimizedModel::Vertex_t ); int indicesFileOffset = m_HardwareOffsets.m_nIndices + m_CurrentHardwareData.m_nIndices * sizeof( unsigned short ); int topologyFileOffset = m_HardwareOffsets.m_nTopology + m_CurrentHardwareData.m_nTopology * sizeof( unsigned short ); int stripsFileOffset = m_HardwareOffsets.m_nStrips + m_CurrentHardwareData.m_nStrips * sizeof( OptimizedModel::StripHeader_t ); pNewStripGroup->vertOffset = vertsFileOffset - stripGroupFileOffset; pNewStripGroup->indexOffset = indicesFileOffset - stripGroupFileOffset; pNewStripGroup->topologyOffset = topologyFileOffset - stripGroupFileOffset; pNewStripGroup->stripOffset = stripsFileOffset - stripGroupFileOffset; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { OptimizedModel::FileHeader_t *pOrigHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ nModel ]->PeekGet(); for( int nBodyPart = 0; nBodyPart < pOrigHeader->numBodyParts; nBodyPart++ ) { OptimizedModel::BodyPartHeader_t *pOrigBodyPart = pOrigHeader->pBodyPart( nBodyPart ); int nSubModelToUse = m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection; for( int nBodyModel = 0; nBodyModel < pOrigBodyPart->numModels; nBodyModel++ ) { // skip over sub models that are not the selected one (-1 no selection, so no skipping) if ( nSubModelToUse != -1 && nBodyModel != nSubModelToUse ) { continue; } OptimizedModel::ModelHeader_t *pOrigModel = pOrigBodyPart->pModel( nBodyModel ); int nPickLOD = nLOD; if ( nLOD >= pOrigModel->numLODs ) { nPickLOD = pOrigModel->numLODs - 1; } OptimizedModel::ModelLODHeader_t *pOrigLOD = pOrigModel->pLOD( nPickLOD ); for( int nMesh = 0; nMesh < pOrigLOD->numMeshes; nMesh++ ) { OptimizedModel::MeshHeader_t *pOrigMesh = pOrigLOD->pMesh( nMesh ); // determine the new material index int nOriginalMaterialIndex = m_pCombinedStudioData->m_MeshToMaterialMap[ nModel ][ nBodyPart ][ nMesh ]; int nNewMaterialIndex = GetTextureCombiner().GetAtlasGroupIndex( nOriginalMaterialIndex ); if ( nNewMaterialIndex == -1 ) { nNewMaterialIndex = m_pCombinedStudioData->m_nNumAtlasGroups + GetTextureCombiner().GetAtlasGroupMaterialIndex( nOriginalMaterialIndex ); } if ( nMaterialIndex == nNewMaterialIndex ) { for( int nStripGroup = 0; nStripGroup < pOrigMesh->numStripGroups; nStripGroup++ ) { OptimizedModel::StripGroupHeader_t *pOrigStripGroup = pOrigMesh->pStripGroup( nStripGroup ); if ( pOrigStripGroup->flags == pNewStripGroup->flags ) { // we are compatible MergeStripGroup( nLOD, pNewStripGroup, nModel, pOrigStripGroup ); } } } } } } } m_pCombinedStudioData->m_Results.m_nCombinedNumIndexes[ nLOD ] += pNewStripGroup->numIndices; if ( pNewStripGroup->numIndices ) { // we put things into this strip group pNewMesh->numStripGroups++; m_CurrentHardwareData.m_nStripGroups++; } } void CModelCombine::WriteMeshes( int nLOD, int nMaterialIndex, OptimizedModel::ModelLODHeader_t *pNewModelLOD, OptimizedModel::ModelLODHeader_t *pOrigModelLOD ) { OptimizedModel::MeshHeader_t *pNewMesh = pNewModelLOD->pMesh( pNewModelLOD->numMeshes ); OptimizedModel::MeshHeader_t *pOrigMesh = pOrigModelLOD->pMesh( 0 ); int meshFileOffset = m_HardwareOffsets.m_nMeshes + m_CurrentHardwareData.m_nMeshes * sizeof( OptimizedModel::MeshHeader_t ); int stripGroupFileOffset = m_HardwareOffsets.m_nStripGroups + m_CurrentHardwareData.m_nStripGroups * sizeof( OptimizedModel::StripGroupHeader_t ); pNewMesh->stripGroupHeaderOffset = stripGroupFileOffset - meshFileOffset; pNewMesh->flags = pOrigMesh->flags; WriteStripGroup( nLOD, nMaterialIndex, OptimizedModel::STRIPGROUP_IS_HWSKINNED, pNewMesh ); WriteStripGroup( nLOD, nMaterialIndex, OptimizedModel::STRIPGROUP_IS_HWSKINNED | OptimizedModel::STRIPGROUP_IS_DELTA_FLEXED, pNewMesh ); pNewModelLOD->numMeshes++; m_CurrentHardwareData.m_nMeshes++; } void CModelCombine::WriteModelLOD( int nLOD, OptimizedModel::ModelHeader_t *pNewModel, OptimizedModel::ModelHeader_t *pOrigModel ) { OptimizedModel::ModelLODHeader_t *pNewModelLOD = pNewModel->pLOD( m_CurrentHardwareData.m_nModelLODs ); OptimizedModel::ModelLODHeader_t *pOrigModelLOD = pOrigModel->pLOD( nLOD ); int lodFileOffset = m_HardwareOffsets.m_nModelLODs + m_CurrentHardwareData.m_nModelLODs * sizeof( OptimizedModel::ModelLODHeader_t ); int meshFileOffset = m_HardwareOffsets.m_nMeshes + m_CurrentHardwareData.m_nMeshes * sizeof( OptimizedModel::MeshHeader_t ); pNewModelLOD->meshOffset = meshFileOffset - lodFileOffset; int nTotalMaterials = m_pCombinedStudioData->m_nNumAtlasGroups + m_pCombinedStudioData->m_nNumNonAtlasedMaterialBaseNames; for ( int nMaterialIndex = 0; nMaterialIndex < nTotalMaterials; nMaterialIndex++ ) { WriteMeshes( nLOD, nMaterialIndex, pNewModelLOD, pOrigModelLOD ); } m_CurrentHardwareData.m_nModelLODs++; } void CModelCombine::WriteModel( int nBodyModel, OptimizedModel::BodyPartHeader_t *pNewBodyPart, OptimizedModel::BodyPartHeader_t *pOrigBodyPart ) { OptimizedModel::ModelHeader_t *pNewModel = pNewBodyPart->pModel( m_CurrentHardwareData.m_nModels ); OptimizedModel::ModelHeader_t *pOrigModel = pOrigBodyPart->pModel( nBodyModel ); pNewModel->numLODs = pOrigModel->numLODs; int modelFileOffset = m_HardwareOffsets.m_nModels + m_CurrentHardwareData.m_nModels * sizeof( OptimizedModel::ModelHeader_t ); int lodFileOffset = m_HardwareOffsets.m_nModelLODs + m_CurrentHardwareData.m_nModelLODs * sizeof( OptimizedModel::ModelLODHeader_t ); pNewModel->lodOffset = lodFileOffset - modelFileOffset; for( int nLOD = 0; nLOD < pOrigModel->numLODs; nLOD++ ) { WriteModelLOD( nLOD, pNewModel, pOrigModel ); } m_CurrentHardwareData.m_nModels++; pNewBodyPart->numModels++; } void CModelCombine::WriteBodyPart( int nBodyPart ) { for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { OptimizedModel::FileHeader_t *pOrigHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ nModel ]->PeekGet(); if ( pOrigHeader->numBodyParts > nBodyPart ) { OptimizedModel::BodyPartHeader_t *pOrigBodyPart = pOrigHeader->pBodyPart( nBodyPart ); OptimizedModel::BodyPartHeader_t *pNewBodyPart = m_pCombinedHardwareHeader->pBodyPart( nBodyPart ); pNewBodyPart->numModels = 0; int bodyPartOffset = m_HardwareOffsets.m_nBodyParts + m_CurrentHardwareData.m_nBodyParts * sizeof( OptimizedModel::BodyPartHeader_t ); int modelFileOffset = m_HardwareOffsets.m_nModels + m_CurrentHardwareData.m_nModels * sizeof( OptimizedModel::ModelHeader_t ); pNewBodyPart->modelOffset = modelFileOffset - bodyPartOffset; int nBodyModel = ( m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection != -1 ) ? m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection : 0; WriteModel( nBodyModel, pNewBodyPart, pOrigBodyPart ); break; } } m_CurrentHardwareData.m_nBodyParts++; } void CModelCombine::CombineVTX( ) { int nMaxSize = 0; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { nMaxSize += VTX_Data[ nModel ]->TellMaxPut(); } g_CombinerWriter.AllocWrite( 256 ); g_CombinerWriter.AlignWrite( 16 ); g_CombinerWriter.InitWriteArea( WRITE_AREA_VTX, g_CombinerWriter.GetWritePos() ); g_CombinerWriter.SetWriteArea( WRITE_AREA_VTX ); OptimizedModel::FileHeader_t *pPrimaryHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ 0 ]->PeekGet(); // m_pCombinedHardwareHeader = ( OptimizedModel::FileHeader_t * )g_CombinerWriter.WriteBuffer( pPrimaryHeader, VTX_Data[ 0 ]->TellMaxPut() ); // return; m_pCombinedHardwareHeader = ( OptimizedModel::FileHeader_t * )g_CombinerWriter.AllocWrite( sizeof( *m_pCombinedHardwareHeader ) ); m_pCombinedHardwareHeader->version = pPrimaryHeader->version; m_pCombinedHardwareHeader->vertCacheSize = pPrimaryHeader->vertCacheSize; m_pCombinedHardwareHeader->maxBonesPerStrip = pPrimaryHeader->maxBonesPerStrip; m_pCombinedHardwareHeader->maxBonesPerFace = pPrimaryHeader->maxBonesPerFace; m_pCombinedHardwareHeader->maxBonesPerVert = pPrimaryHeader->maxBonesPerVert; m_pCombinedHardwareHeader->numLODs = pPrimaryHeader->numLODs; // figure out the worst case scenario - we'll have some blank spots in the file, but should be minimal memset( &m_MaxHardwareData, 0, sizeof( m_MaxHardwareData ) ); memset( &m_CurrentHardwareData, 0, sizeof( m_CurrentHardwareData ) ); memset( &m_HardwareOffsets, 0, sizeof( m_HardwareOffsets ) ); CalcVTXInfo(); m_pCombinedHardwareHeader->numBodyParts = m_MaxHardwareData.m_nMaxBodyParts; m_MaxHardwareData.m_nStripGroups++; // we need to reserve space for temp writing m_HardwareOffsets.m_nBodyParts = sizeof( OptimizedModel::FileHeader_t ); m_HardwareOffsets.m_nModels = m_HardwareOffsets.m_nBodyParts + sizeof( OptimizedModel::BodyPartHeader_t ) * m_MaxHardwareData.m_nBodyParts; m_HardwareOffsets.m_nModelLODs = m_HardwareOffsets.m_nModels + sizeof( OptimizedModel::ModelHeader_t ) * m_MaxHardwareData.m_nModels; m_HardwareOffsets.m_nMeshes = m_HardwareOffsets.m_nModelLODs + sizeof( OptimizedModel::ModelLODHeader_t ) * m_MaxHardwareData.m_nModelLODs; m_HardwareOffsets.m_nStripGroups = m_HardwareOffsets.m_nMeshes + sizeof( OptimizedModel::MeshHeader_t ) * m_MaxHardwareData.m_nMeshes; m_HardwareOffsets.m_nStrips = m_HardwareOffsets.m_nStripGroups + sizeof( OptimizedModel::StripGroupHeader_t ) * m_MaxHardwareData.m_nStripGroups; m_HardwareOffsets.m_nVerts = m_HardwareOffsets.m_nStrips + sizeof( OptimizedModel::StripHeader_t ) * m_MaxHardwareData.m_nStrips; m_HardwareOffsets.m_nIndices = m_HardwareOffsets.m_nVerts + sizeof( OptimizedModel::Vertex_t ) * m_MaxHardwareData.m_nVerts; m_HardwareOffsets.m_nBoneStateChanges = m_HardwareOffsets.m_nIndices + sizeof( unsigned short ) * m_MaxHardwareData.m_nIndices; m_HardwareOffsets.m_nStringTable = m_HardwareOffsets.m_nBoneStateChanges + sizeof( OptimizedModel::BoneStateChangeHeader_t ) * m_MaxHardwareData.m_nBoneStateChanges; m_HardwareOffsets.m_nMaterialReplacements = m_HardwareOffsets.m_nStringTable + 0; g_CombinerWriter.AllocWrite( m_HardwareOffsets.m_nMaterialReplacements - m_HardwareOffsets.m_nModels ); m_pCombinedHardwareHeader->bodyPartOffset = m_HardwareOffsets.m_nBodyParts; for( int nBodyPart = 0; nBodyPart < m_MaxHardwareData.m_nMaxBodyParts; nBodyPart++ ) { WriteBodyPart( nBodyPart ); } for( int nLOD = 1; nLOD < m_pCombinedVertex->numLODs; nLOD++ ) { for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { if ( m_pCombinedStudioData->m_Results.m_nNumIndexes[ nModel ][ nLOD ] > m_pCombinedStudioData->m_Results.m_nNumIndexes[ nModel ][ nLOD - 1 ] ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "%s has lower LOD which has higher triangle count\n", m_pStudioHdr[ nModel ]->pszName() ); m_pCombinedStudioData->m_Results.m_nDetailedError = COMBINED_DETAIL_ERROR_MODEL_LOWER_LOD_HIGHER_TRI_COUNT; V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorDetails, "Model %s\nLOD %d Tris %d\nLOD %d Tris %d\n", m_pStudioHdr[ nModel ]->pszName(), nLOD - 1, m_pCombinedStudioData->m_Results.m_nNumIndexes[ nModel ][ nLOD - 1 ] / 3, nLOD, m_pCombinedStudioData->m_Results.m_nNumIndexes[ nModel ][ nLOD ] / 3 ); throw( COMBINE_RESULT_FLAG_FAILED_GOOD_PRACTICE ); } } } m_pCombinedHardwareHeader->materialReplacementListOffset = m_HardwareOffsets.m_nMaterialReplacements; g_CombinerWriter.AllocWrite( pPrimaryHeader->numLODs * sizeof( OptimizedModel::MaterialReplacementListHeader_t ) ); for( int nLOD = 0; nLOD < pPrimaryHeader->numLODs; nLOD++ ) { OptimizedModel::MaterialReplacementListHeader_t *pNewMaterialReplacementList = m_pCombinedHardwareHeader->pMaterialReplacementList( nLOD ); OptimizedModel::MaterialReplacementListHeader_t *pOrigMaterialReplacementList = pPrimaryHeader->pMaterialReplacementList( nLOD ); pNewMaterialReplacementList->numReplacements = pOrigMaterialReplacementList->numReplacements; g_CombinerWriter.WriteOffset( pNewMaterialReplacementList->replacementOffset, pNewMaterialReplacementList ); if ( pOrigMaterialReplacementList->numReplacements > 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "material has replacements" ); throw( COMBINE_RESULT_FLAG_UNSUPPORTED_FEATURE ); } } #ifdef TEST_VTX_COMBINE TestCombineVTX(); #endif // TEST_VTX_COMBINE // int nSize = Min( 48916, VTX_Data[ 0 ]->TellMaxPut() ); // memcpy( m_pCombinedHardwareHeader, VTX_Data[ 0 ]->PeekGet(), nSize ); // memcpy( ( char * )m_pCombinedHardwareHeader + 48916, ( char * )VTX_Data[ 0 ]->PeekGet() + 48916, 688 ); // g_CombinerWriter.AlignWrite( 128 ); // m_pCombinedHardwareHeader = ( OptimizedModel::FileHeader_t * )g_CombinerWriter.WriteBuffer( VTX_Data[ 0 ]->PeekGet(), VTX_Data[ 0 ]->TellMaxPut() ); } #ifdef DEBUG_COMBINE void CModelCombine::TestCombineVTX( ) { OptimizedModel::FileHeader_t *pPrimaryHeader = ( OptimizedModel::FileHeader_t * )VTX_Data[ 0 ]->PeekGet(); Assert( pPrimaryHeader->version == m_pCombinedHardwareHeader->version ); Assert( pPrimaryHeader->vertCacheSize == m_pCombinedHardwareHeader->vertCacheSize ); Assert( pPrimaryHeader->maxBonesPerStrip == m_pCombinedHardwareHeader->maxBonesPerStrip ); Assert( pPrimaryHeader->maxBonesPerFace == m_pCombinedHardwareHeader->maxBonesPerFace ); Assert( pPrimaryHeader->maxBonesPerVert == m_pCombinedHardwareHeader->maxBonesPerVert ); Assert( pPrimaryHeader->numLODs == m_pCombinedHardwareHeader->numLODs ); Assert( pPrimaryHeader->numBodyParts == m_pCombinedHardwareHeader->numBodyParts ); for( int nBodyPart = 0; nBodyPart < pPrimaryHeader->numBodyParts; nBodyPart++ ) { OptimizedModel::BodyPartHeader_t *pOrigBodyPart = pPrimaryHeader->pBodyPart( nBodyPart ); OptimizedModel::BodyPartHeader_t *pNewBodyPart = m_pCombinedHardwareHeader->pBodyPart( nBodyPart ); Assert( pOrigBodyPart->numModels == pNewBodyPart->numModels ); for( int nModel = 0; nModel < pOrigBodyPart->numModels; nModel++ ) { OptimizedModel::ModelHeader_t *pOrigModel = pOrigBodyPart->pModel( nModel ); OptimizedModel::ModelHeader_t *pNewModel = pNewBodyPart->pModel( nModel ); Assert( pOrigModel->numLODs == pNewModel->numLODs ); for( int nLOD = 0; nLOD < pOrigModel->numLODs; nLOD++ ) { OptimizedModel::ModelLODHeader_t *pOrigLOD = pOrigModel->pLOD( nLOD ); OptimizedModel::ModelLODHeader_t *pNewLOD = pNewModel->pLOD( nLOD ); Assert( pOrigLOD->numMeshes == pNewLOD->numMeshes ); Assert( pOrigLOD->switchPoint == pNewLOD->switchPoint ); for( int nMesh = 0; nMesh < pOrigLOD->numMeshes; nMesh++ ) { OptimizedModel::MeshHeader_t *pOrigMesh = pOrigLOD->pMesh( nMesh ); OptimizedModel::MeshHeader_t *pNewMesh = pNewLOD->pMesh( nMesh ); Assert( pOrigMesh->numStripGroups == pNewMesh->numStripGroups ); Assert( pOrigMesh->flags == pNewMesh->flags ); for( int nStripGroup = 0; nStripGroup < pOrigMesh->numStripGroups; nStripGroup++ ) { OptimizedModel::StripGroupHeader_t *pOrigStripGroup = pOrigMesh->pStripGroup( nStripGroup ); OptimizedModel::StripGroupHeader_t *pNewStripGroup = pNewMesh->pStripGroup( nStripGroup ); Assert( pOrigStripGroup->numVerts == pNewStripGroup->numVerts ); Assert( pOrigStripGroup->numIndices == pNewStripGroup->numIndices ); Assert( pOrigStripGroup->numStrips == pNewStripGroup->numStrips ); Assert( pOrigStripGroup->flags == pNewStripGroup->flags ); Assert( pOrigStripGroup->numTopologyIndices == pNewStripGroup->numTopologyIndices ); for( int nNumVerts = 0; nNumVerts < pOrigStripGroup->numVerts; nNumVerts++ ) { OptimizedModel::Vertex_t *pOrigVertex = pOrigStripGroup->pVertex( nNumVerts ); OptimizedModel::Vertex_t *pNewVertex = pNewStripGroup->pVertex( nNumVerts ); Assert( memcmp( pOrigVertex, pNewVertex, sizeof( *pOrigVertex ) ) == 0 ); } for( int nStrip = 0; nStrip < pOrigStripGroup->numStrips; nStrip++ ) { OptimizedModel::StripHeader_t *pOrigStrip = pOrigStripGroup->pStrip( nStrip ); OptimizedModel::StripHeader_t *pNewStrip = pNewStripGroup->pStrip( nStrip ); Assert( pOrigStrip->numIndices == pNewStrip->numIndices ); Assert( pOrigStrip->numVerts == pNewStrip->numVerts ); Assert( pOrigStrip->numBones == pNewStrip->numBones ); Assert( pOrigStrip->flags == pNewStrip->flags ); Assert( pOrigStrip->numBoneStateChanges == pNewStrip->numBoneStateChanges ); Assert( pOrigStrip->numTopologyIndices == pNewStrip->numTopologyIndices ); for( int nBoneStateChange = 0; nBoneStateChange < pOrigStrip->numBoneStateChanges; nBoneStateChange++ ) { OptimizedModel::BoneStateChangeHeader_t *pOrigBoneStateChange = pOrigStrip->pBoneStateChange( nBoneStateChange ); OptimizedModel::BoneStateChangeHeader_t *pNewBoneStateChange = pNewStrip->pBoneStateChange( nBoneStateChange ); Assert( pOrigBoneStateChange->hardwareID == pNewBoneStateChange->hardwareID ); Assert( pOrigBoneStateChange->newBoneID == pNewBoneStateChange->newBoneID ); } } } } } } } } #endif // DEBUG_COMBINE // this will move a vert belonging to a secondary model from pose space of that secondary model // into that secondary model's bone space, then transform the vert into pose space of the primary model. void CModelCombine::CombineVVD_OffsetVerts( ) { matrix3x4_t VertToPrimaryPose[ COMBINER_MAX_BONES ]; for( int nBone = 0; nBone < m_nNumMasterBones; nBone++ ) { matrix3x4_t VertToPrimaryBone; VertToPrimaryBone = m_pMasterBoneList[ nBone ]->poseToBone; MatrixInvert( VertToPrimaryBone, VertToPrimaryPose[ nBone ] ); } for( int nModel = 1; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { int nNumVerts = m_pVertexFileHeader[ nModel ]->numLODVertexes[ 0 ]; for( int nVert = 0; nVert < nNumVerts; nVert++ ) { int nRealVert = m_nVertexRemap[ nModel ][ nVert ]; mstudiovertex_t *pVert = ( mstudiovertex_t * )m_pCombinedVertex->GetVertexData() + nRealVert; Vector vPosition, vPos, vFinalVert, vFinalNormal, vFinalTangent; bool bHasTangent = m_pVertexFileHeader[ nModel ]->tangentDataStart != NULL; vFinalVert.Init(); vFinalNormal.Init(); vFinalTangent.Init(); for( int nBone = 0; nBone < pVert->m_BoneWeights.numbones; nBone++ ) { int nBoneIndex = pVert->m_BoneWeights.bone[ nBone ]; VectorTransform( pVert->m_vecPosition, m_pStudioHdr[ nModel ]->pBone( m_nMasterToLocalBoneRemap[ nModel ][ nBoneIndex ] )->poseToBone, vPosition ); VectorTransform( vPosition, VertToPrimaryPose[ nBoneIndex ], vPos ); vFinalVert += vPos * pVert->m_BoneWeights.weight[ nBone ]; VectorRotate( pVert->m_vecNormal, m_pStudioHdr[ nModel ]->pBone( m_nMasterToLocalBoneRemap[ nModel ][ nBoneIndex ] )->poseToBone, vPosition ); VectorRotate( vPosition, VertToPrimaryPose[ nBoneIndex ], vPos ); vFinalNormal += vPos * pVert->m_BoneWeights.weight[ nBone ]; if ( bHasTangent ) { Vector4D vTangent = *( m_pCombinedVertex->GetTangentData() + nRealVert ); VectorRotate( vTangent.AsVector3D(), m_pStudioHdr[ nModel ]->pBone( m_nMasterToLocalBoneRemap[ nModel ][ nBoneIndex ] )->poseToBone, vPosition ); VectorRotate( vPosition, VertToPrimaryPose[ nBoneIndex ], vPos ); vFinalTangent += vPos * pVert->m_BoneWeights.weight[ nBone ]; } } pVert->m_vecPosition = vFinalVert; pVert->m_vecNormal = vFinalNormal; if ( bHasTangent ) { Vector *pTangent = ( Vector * )( m_pCombinedVertex->GetTangentData() + nRealVert ); *pTangent = vFinalTangent; } } } } void CModelCombine::CombineVVD( ) { int nVertsWritten[ COMBINER_MAX_MODELS ]; for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { int nNumVerts = m_pVertexFileHeader[ nModel ]->numLODVertexes[ 0 ]; m_nVertexRemap[ nModel ] = ( int * )g_CombinerWriter.AllocWrite( sizeof( int ) * nNumVerts ); nVertsWritten[ nModel ] = 0; } g_CombinerWriter.AlignWrite( 16 ); g_CombinerWriter.InitWriteArea( WRITE_AREA_VVD, g_CombinerWriter.GetWritePos() ); g_CombinerWriter.SetWriteArea( WRITE_AREA_VVD ); m_pCombinedVertex = ( vertexFileHeader_t * )g_CombinerWriter.AllocWrite( sizeof( *m_pCombinedVertex ) ); m_pCombinedVertex->id = MODEL_VERTEX_FILE_ID; m_pCombinedVertex->version = MODEL_VERTEX_FILE_VERSION; m_pCombinedVertex->numLODs = m_pVertexFileHeader[ 0 ]->numLODs; g_CombinerWriter.WriteOffset( m_pCombinedVertex->vertexDataStart, m_pCombinedVertex ); int nVertOffset = 0; for( int nLOD = m_pCombinedVertex->numLODs - 1; nLOD >= 0; nLOD-- ) { for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { int nNumVerts = m_pVertexFileHeader[ nModel ]->numLODVertexes[ nLOD ]; m_pCombinedVertex->numLODVertexes[ nLOD ] += m_pVertexFileHeader[ nModel ]->numLODVertexes[ nLOD ]; nNumVerts -= nVertsWritten[ nModel ]; mstudiovertex_t *pNewVertex = ( mstudiovertex_t * )g_CombinerWriter.GetWritePos(); if ( m_pVertexFileHeader[ nModel ]->numFixups > 0 ) { int nCount = nNumVerts; int nOffset = nVertsWritten[ nModel ]; int nNumWritten = 0; const mstudiovertex_t *pOrigVertex = m_pVertexFileHeader[ nModel ]->GetVertexData(); for( int nFixup = 0; nFixup < m_pVertexFileHeader[ nModel ]->numFixups; nFixup++ ) { vertexFileFixup_t *pOrigFixup = ( vertexFileFixup_t * )( ( byte * )m_pVertexFileHeader[ nModel ] + m_pVertexFileHeader[ nModel ]->fixupTableStart) + nFixup; if ( pOrigFixup->numVertexes < nOffset ) { nOffset -= pOrigFixup->numVertexes; } else { int nSize = pOrigFixup->numVertexes - nOffset; if ( nSize > nCount ) { nSize = nCount; } g_CombinerWriter.WriteBuffer( pOrigVertex + nOffset, nSize * sizeof( mstudiovertex_t ) ); nOffset = 0; nCount -= nSize; nNumWritten += nSize; if ( nCount == 0 ) { break; } } } Assert( nNumWritten == nNumVerts ); } else { g_CombinerWriter.WriteBuffer( m_pVertexFileHeader[ nModel ]->GetVertexData() + nVertsWritten[ nModel ], nNumVerts * sizeof( mstudiovertex_t ) ); } if ( nModel > 0 ) { // primary one doesn't need bone remap mstudiovertex_t *pBoneVertex = pNewVertex; for( int nVert = 0, nVertIndex = nVertsWritten[ nModel ]; nVert < nNumVerts; nVert++, pBoneVertex++, nVertIndex++ ) { for( int nBone = 0; nBone < pBoneVertex->m_BoneWeights.numbones; nBone++ ) { pBoneVertex->m_BoneWeights.bone[ nBone ] = m_nBoneRemap[ nModel ][ pBoneVertex->m_BoneWeights.bone[ nBone ] ]; } } } for( int nBodyBart = 0; nBodyBart < m_pStudioHdr[ nModel ]->numbodyparts; nBodyBart++ ) { mstudiobodyparts_t *pOrigBodyPart = m_pStudioHdr[ nModel ]->pBodypart( nBodyBart ); int nSubModelToUse = m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection; for( int nBodyPartModel = 0; nBodyPartModel < pOrigBodyPart->nummodels; nBodyPartModel++ ) { // skip over sub models that are not the selected one (-1 no selection, so no skipping) if ( nSubModelToUse != -1 && nBodyPartModel != nSubModelToUse ) { continue; } mstudiomodel_t *pOrigModel = pOrigBodyPart->pModel( nBodyPartModel ); for( int nMesh = 0; nMesh < pOrigModel->nummeshes; nMesh++ ) { mstudiomesh_t *pOrigMesh = pOrigModel->pMesh( nMesh ); Vector2D vStartST, vSizeST, vPixelSize; int nTextureIndex = m_pCombinedStudioData->m_nModelMaterialIndices[ nModel ][ nMesh ]; GetTextureCombiner().GetTextureInfo( nTextureIndex, vStartST, vSizeST, vPixelSize ); for( int nVert = 0, nVertIndex = nVertsWritten[ nModel ] + pOrigMesh->vertexoffset; nVert < pOrigMesh->numvertices; nVert++, pNewVertex++, nVertIndex++ ) { double flInteger; pNewVertex->m_vecTexCoord.x = ( float )modf( ( double )pNewVertex->m_vecTexCoord.x, &flInteger ); if ( pNewVertex->m_vecTexCoord.x < 0.0f ) { pNewVertex->m_vecTexCoord.x += 1.0f; } pNewVertex->m_vecTexCoord.y = ( float )modf( ( double )pNewVertex->m_vecTexCoord.y, &flInteger ); if ( pNewVertex->m_vecTexCoord.y < 0.0f ) { pNewVertex->m_vecTexCoord.y += 1.0f; } pNewVertex->m_vecTexCoord = ( pNewVertex->m_vecTexCoord * vSizeST ); pNewVertex->m_vecTexCoord += vStartST; m_nVertexRemap[ nModel ][ nVertIndex ] = nVertOffset + nVert; } } } } nVertsWritten[ nModel ] += nNumVerts; nVertOffset += nNumVerts; m_pCombinedStudioData->m_Results.m_nNumLODs[ nModel ] = m_pVertexFileHeader[ nModel ]->numLODs; if ( nLOD < m_pCombinedVertex->numLODs - 1 ) { m_pCombinedStudioData->m_Results.m_nNumVerts[ nModel ][ nLOD ] = m_pCombinedStudioData->m_Results.m_nNumVerts[ nModel ][ nLOD + 1 ] + nNumVerts; } else { m_pCombinedStudioData->m_Results.m_nNumVerts[ nModel ][ nLOD ] = nNumVerts; } m_pCombinedStudioData->m_Results.m_nCombinedNumVerts[ nLOD ] += nNumVerts; } } m_pCombinedStudioData->m_Results.m_nCombinedNumLODs = m_pCombinedVertex->numLODs; int nLastLODSize = m_pCombinedVertex->numLODVertexes[ m_pCombinedVertex->numLODs - 1 ]; for( int nLOD = m_pCombinedVertex->numLODs; nLOD < MAX_NUM_LODS; nLOD++ ) { m_pCombinedVertex->numLODVertexes[ nLOD ] = nLastLODSize; } Vector4D *pData = ( Vector4D * )g_CombinerWriter.WriteOffset( m_pCombinedVertex->tangentDataStart, m_pCombinedVertex ); g_CombinerWriter.AllocWrite( m_pCombinedVertex->numLODVertexes[ 0 ] * sizeof( Vector4D ) ); for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { if ( m_pVertexFileHeader[ nModel ]->tangentDataStart == 0 ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "vertex file has tangent data" ); throw( COMBINE_RESULT_FLAG_UNHANDLED_ISSUE ); } const Vector4D *vData = m_pVertexFileHeader[ nModel ]->GetTangentData(); for( int nIndex = 0; nIndex < m_pVertexFileHeader[ nModel ]->numLODVertexes[ 0 ]; nIndex++ ) { pData[ m_nVertexRemap[ nModel ][ nIndex ] ] = *vData; vData++; } } CombineVVD_OffsetVerts(); } #if 0 void CModelCombine::Test( ) { studiohdr_t *pStudioHdr = m_pStudioHdr[ 0 ]; vertexFileHeader_t *pVertexHeader = ( vertexFileHeader_t * )VVD_Data[ 0 ]->PeekGet(); const mstudiovertex_t *pVertex = pVertexHeader->GetVertexData(); int nTotalModels = 0; for ( int nBodyPart = 0; nBodyPart < pStudioHdr->numbodyparts; nBodyPart++ ) { mstudiobodyparts_t *pOrigBodyPart = pStudioHdr->pBodypart( nBodyPart ); for( int nModel = 0; nModel < pOrigBodyPart->nummodels; nModel++ ) { mstudiomodel_t *pOrigModel = pOrigBodyPart->pModel( nModel ); for( int nMesh = 0; nMesh < pOrigModel->nummeshes; nMesh++ ) { mstudiomesh_t *pOrigMesh = pOrigModel->pMesh( nMesh ); for( int nVert = 0; nVert < pOrigMesh->numvertices; nVert++ ) { const mstudiovertex_t *pOrigVert = &pVertex[ nVert + pOrigMesh->vertexoffset ]; Msg(""); } } } } } #endif void CModelCombine::CombineTextures( ) { GetTextureCombiner().Init( m_pCombinedStudioData ); for( int nModel = 0; nModel < m_pCombinedStudioData->m_nNumModels; nModel++ ) { #if 0 for( int nSkin = 0; nSkin < m_pStudioHdr[ nModel ]->numskinfamilies; nSkin++ ) { short *pSkinRef = m_pStudioHdr[ nModel ]->pSkinref( 0 ); pSkinRef += ( nSkin * m_pStudioHdr[ nModel ]->numskinref ); for( int nReference = 0; nReference < m_pStudioHdr[ nModel ]->numskinref; nReference++, pSkinRef++ ) { int nTextureIndex = *pSkinRef; const char *pszTextureName = m_pStudioHdr[ nModel ]->pTexture( nTextureIndex )->pszName(); Msg( "SKin %d, Reference %d: %s\n", nSkin, nReference, pszTextureName ); } } #endif int nSkin = m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nSkinFamily; short *pSkinRef = m_pStudioHdr[ nModel ]->pSkinref( 0 ); if ( nSkin > 0 && nSkin < m_pStudioHdr[ nModel ]->numskinfamilies ) { pSkinRef += ( nSkin * m_pStudioHdr[ nModel ]->numskinref ); } for( int nBodyPart = 0; nBodyPart < m_pStudioHdr[ nModel ]->numbodyparts; nBodyPart++ ) { mstudiobodyparts_t *pOrigBodyPart = m_pStudioHdr[ nModel ]->pBodypart( nBodyPart ); int nSubModelToUse = m_pCombinedStudioData->m_ModelInputData[ nModel ].m_nBodyGroupSubModelSelection; for( int nBodyPartModel = 0; nBodyPartModel < pOrigBodyPart->nummodels; nBodyPartModel++ ) { // skip over sub models that are not the selected one (-1 no selection, so no skipping) if ( nSubModelToUse != -1 && nBodyPartModel != nSubModelToUse ) { continue; } mstudiomodel_t *pOrigModel = pOrigBodyPart->pModel( nBodyPartModel ); m_pCombinedStudioData->m_nModelMaterialCounts[ nModel ] = pOrigModel->nummeshes; for( int nMesh = 0; nMesh < pOrigModel->nummeshes; nMesh++ ) { mstudiomesh_t *pOrigMesh = pOrigModel->pMesh( nMesh ); int nTextureIndex = pSkinRef[ pOrigMesh->material ]; m_pCombinedStudioData->m_MeshToMaterialMap[ nModel ][ nBodyPart ][ nMesh ] = nTextureIndex; m_pCombinedStudioData->m_nModelMaterialIndices[ nModel ][ nMesh ] = AddMaterialToTextureCombiner( nTextureIndex, nModel, pOrigMesh->material ); } } } } GetTextureCombiner().Resolve(); } int CModelCombine::AddMaterialToTextureCombiner( int nTextureIndex, int nModel, int nModelMaterialIndex ) { char szPath[ MAX_PATH ]; char szFinalPath[ MAX_PATH ]; bool bFound = false; // If we don't do this, we get filenames like "materials\\blah.vmt". const char *pszTextureName = m_pStudioHdr[ nModel ]->pTexture( nTextureIndex )->pszName(); if ( pszTextureName[ 0 ] == CORRECT_PATH_SEPARATOR || pszTextureName[ 0 ] == INCORRECT_PATH_SEPARATOR ) { pszTextureName++; } // search through all specified directories until a valid material is found for ( int nSearch = 0; nSearch < m_pStudioHdr[ nModel ]->numcdtextures; nSearch++ ) { // This prevents filenames like /models/blah.vmt. const char *pszCDTexture = m_pStudioHdr[ nModel ]->pCdtexture( nSearch ); if ( pszCDTexture[ 0 ] == CORRECT_PATH_SEPARATOR || pszCDTexture[ 0 ] == INCORRECT_PATH_SEPARATOR ) { pszCDTexture++; } V_ComposeFileName( pszCDTexture, pszTextureName, szPath, sizeof( szPath ) ); V_ComposeFileName( "materials/", szPath, szFinalPath, sizeof( szFinalPath ) ); char szCheckPath[ MAX_PATH ]; V_strcpy_safe( szCheckPath, szFinalPath ); V_strcat_safe( szCheckPath, ".vmt" ); if ( g_pFullFileSystem->FileExists( szCheckPath ) ) { bFound = true; break; } } if ( bFound == false ) { Assert( 0 ); V_sprintf_safe( m_pCombinedStudioData->m_Results.m_szErrorMessage, "model %s has missing material %s", m_pStudioHdr[ nModel ]->pszName(), pszTextureName ); throw( COMBINE_RESULT_FLAG_MISSING_ASSET_FILE ); } return GetTextureCombiner().AddMaterial( szFinalPath ); }