You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2893 lines
106 KiB
2893 lines
106 KiB
#include "datacache/imdlcache.h"
|
|
#include "optimize.h"
|
|
#include "mdlcombine.h"
|
|
#include "filesystem.h"
|
|
#include "studio.h"
|
|
#include "mathlib/mathlib.h"
|
|
#include <math.h>
|
|
#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; i<m_pCombinedStudioHdr->numbodyparts; i++)
|
|
{
|
|
pStudioBodyPart = m_pCombinedStudioHdr->pBodypart(i);
|
|
for (j=0; j<pStudioBodyPart->nummodels; j++)
|
|
{
|
|
pStudioModel = pStudioBodyPart->pModel(j);
|
|
for (m=0; m<pStudioModel->nummeshes; 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 );
|
|
}
|