//============ Copyright (c) Valve Corporation, All rights reserved. ==========++; // //============================================================================= // Valve includes #include "appframework/appframework.h" #include "appframework/tier3app.h" #include "datamodel/dmelement.h" #include "datamodel/dmelementfactoryhelper.h" #include "datamodel/idatamodel.h" #include "fbxsystem/ifbxsystem.h" #include "fbxutils/dmfbxserializer.h" #include "filesystem.h" #include "icommandline.h" #include "mathlib/mathlib.h" #include "movieobjects/dmeaxissystem.h" #include "movieobjects/dmefaceset.h" #include "movieobjects/dmematerial.h" #include "movieobjects/dmemesh.h" #include "movieobjects/dmemodel.h" #include "movieobjects/dmobjserializer.h" #include "dmserializers/idmserializers.h" #include "istudiorender.h" #ifdef SOURCE2 #include "resourcesystem/resourcehandletypes.h" #endif #include "tier1/tier1.h" #include "tier2/tier2.h" #include "tier2/tier2dm.h" #include "tier3/tier3.h" #include "tier2/p4helpers.h" #include "p4lib/ip4.h" // Last include #include "tier0/memdbgon.h" class CStudioDataCache : public CBaseAppSystem < IStudioDataCache > { public: bool VerifyHeaders( studiohdr_t *pStudioHdr ); vertexFileHeader_t *CacheVertexData( studiohdr_t *pStudioHdr ); }; static CStudioDataCache g_StudioDataCache; EXPOSE_SINGLE_INTERFACE_GLOBALVAR( CStudioDataCache, IStudioDataCache, STUDIO_DATA_CACHE_INTERFACE_VERSION, g_StudioDataCache ); /* ================= VerifyHeaders Minimal presence and header validation, no data loads Return true if successful, false otherwise. ================= */ bool CStudioDataCache::VerifyHeaders( studiohdr_t *pStudioHdr ) { // default valid return true; } /* ================= CacheVertexData Cache model's specified dynamic data ================= */ vertexFileHeader_t *CStudioDataCache::CacheVertexData( studiohdr_t *pStudioHdr ) { // minimal implementation - return persisted data return ( vertexFileHeader_t* )pStudioHdr->VertexBase(); } //----------------------------------------------------------------------------- // // Search for a material by name by generating resource names and seeing if // the resource exists on disk in content and then game and then on user // specified material search paths // // If a material file cannot be found, the original name is copied and false // is returned // //----------------------------------------------------------------------------- template bool FindMaterialResource( OUT_Z_ARRAY char (&szMaterialResourceName)[maxLenInChars], const char *pszMaterialName, const CUtlVector< CUtlString > &materialSearchPathList ) { #ifdef SOURCE2 char szResourceName[MAX_PATH] = { 0 }; char szResourceFullPath[MAX_PATH] = { 0 }; char szMaterialPath[MAX_PATH] = { 0 }; ResourcePathGenerationType_t pSearchPaths[] = { RESOURCE_PATH_CONTENT, RESOURCE_PATH_GAME }; for ( int s = 0; s < ARRAYSIZE( pSearchPaths ); ++s ) { // Check if current material is valid FixupResourceName( pszMaterialName, RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); if ( GenerateStandardFullPathForResourceName( szResourceName, pSearchPaths[s], szResourceFullPath, ARRAYSIZE( szResourceFullPath ) ) ) { V_strcpy_safe( szMaterialResourceName, szResourceName ); return true; } } // Loop through material search paths and try to find the material for ( int s = 0; s < ARRAYSIZE( pSearchPaths ); s++ ) { for ( int i = 0; i < materialSearchPathList.Count(); ++i ) { V_ComposeFileName( materialSearchPathList[i].Get(), pszMaterialName, szMaterialPath, ARRAYSIZE( szMaterialPath ) ); FixupResourceName( szMaterialPath, RESOURCE_TYPE_MATERIAL, szResourceName, ARRAYSIZE( szResourceName ) ); if ( GenerateStandardFullPathForResourceName( szResourceName, pSearchPaths[s], szResourceFullPath, ARRAYSIZE( szResourceFullPath ) ) ) { V_strcpy_safe( szMaterialResourceName, szResourceName ); return true; } } } V_strcpy_safe( szMaterialResourceName, pszMaterialName ); #endif return false; } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static void RemapMaterials( const CUtlVector< CUtlString > &materialSearchPathList ) { char szResourceName[MAX_PATH] = {}; char szMaterialName[MAX_PATH] = {}; // Loop through all nodes in the for ( DmElementHandle_t hElement = g_pDataModel->FirstAllocatedElement(); hElement != DMELEMENT_HANDLE_INVALID; hElement = g_pDataModel->NextAllocatedElement( hElement ) ) { CDmeMesh *pDmeMesh = CastElement< CDmeMesh >( g_pDataModel->GetElement( hElement ) ); if ( !pDmeMesh ) continue; for ( int i = 0; i < pDmeMesh->FaceSetCount(); ++i ) { CDmeFaceSet *pDmeFaceSet = pDmeMesh->GetFaceSet( i ); CDmeMaterial *pDmeMaterial = pDmeFaceSet->GetMaterial(); // Check if current material is valid, and just set the standard name found anyway if ( FindMaterialResource( szResourceName, pDmeMaterial->GetMaterialName(), materialSearchPathList ) ) { V_FileBase( szResourceName, szMaterialName, ARRAYSIZE( szMaterialName ) ); pDmeMaterial->SetName( szMaterialName ); pDmeMaterial->SetMaterial( szResourceName ); continue; } { bool bFound = false; const char *szSuffixes[] = { "_color." }; FbxString sTmpResourceName = pDmeMaterial->GetMaterialName(); for ( int i = 0; i < ARRAYSIZE( szSuffixes ); ++i ) { if ( sTmpResourceName.FindAndReplace( szSuffixes[i], "." ) ) { if ( FindMaterialResource( szResourceName, sTmpResourceName.Buffer(), materialSearchPathList ) ) { V_FileBase( szResourceName, szMaterialName, ARRAYSIZE( szMaterialName ) ); pDmeMaterial->SetName( szMaterialName ); pDmeMaterial->SetMaterial( szResourceName ); bFound = 1; break; } } } if ( bFound ) continue; } Warning( "Warning! Cannot find a material resource for material \"%s\" on mesh \"%s\"\n", pDmeMaterial->GetMaterialName(), pDmeMesh->GetName() ); } } } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- int DoIt( CDmElement *pDmRoot, const char *pszOutFilename, const CUtlVector< CUtlString > &materialSearchPathList ) { RemapMaterials( materialSearchPathList ); CP4AutoAddFile outfile( pszOutFilename ); char szDmxFilename[MAX_PATH]; V_strncpy( szDmxFilename, pszOutFilename, ARRAYSIZE( szDmxFilename ) ); V_SetExtension( szDmxFilename, ".dmx", ARRAYSIZE( szDmxFilename ) ); CP4AutoEditAddFile dmxFile( szDmxFilename ); CDmeModel *pDmeModel = pDmRoot->GetValueElement< CDmeModel >( "model" ); if ( !pDmeModel ) { pDmeModel = pDmRoot->GetValueElement< CDmeModel >( "skeleton" ); } Assert( pDmeModel ); CDmeAxisSystem *pDmeAxisSystem = pDmeModel->GetValueElement< CDmeAxisSystem >( "axisSystem" ); Assert( pDmeAxisSystem ); const bool bReturn = g_pDataModel->SaveToFile( szDmxFilename, NULL, "keyvalues2", "model", pDmRoot ); g_pDataModel->UnloadFile( pDmRoot->GetFileId() ); return bReturn ? 0 : -1; } bool ProcessAxisSystem( CDmFbxSerializer &dmFbxSerializer ); //----------------------------------------------------------------------------- // DEFINE_CONSOLE_APPLICATION in Source2 //----------------------------------------------------------------------------- class CFbx2DmxApp : public CDefaultAppSystemGroup < CSteamAppSystemGroup > { typedef CDefaultAppSystemGroup< CSteamAppSystemGroup > BaseClass; public: CFbx2DmxApp() { m_pszOptForceMod = NULL; m_bOptUFC = false; m_nOptVerbosity = 0; m_bOptAnimation = false; m_bOptPrintSearchPaths = false; m_pszOptFilename = NULL; m_pszOptOutFilename = NULL; } virtual bool Create() OVERRIDE; virtual int Main() OVERRIDE; virtual bool PreInit() OVERRIDE { CreateInterfaceFn factory = GetFactory(); ConnectTier1Libraries( &factory, 1 ); ConnectTier2Libraries( &factory, 1 ); if ( !g_pFullFileSystem ) return false; if ( !g_pCVar ) return false; ConVar_Register(); return true; } virtual void PostShutdown() OVERRIDE { ConVar_Unregister(); DisconnectTier2Libraries(); DisconnectTier1Libraries(); } protected: const char *m_pszOptFilename; int m_nOptVerbosity; bool m_bOptAnimation; bool m_bOptPrintSearchPaths; bool m_bOptUFC; const char *m_pszOptForceMod; CUtlVector< CUtlString > m_sOptMaterialSearchPaths; const char *m_pszOptOutFilename; protected: bool ParseArguments(); }; static bool CStudioMDLApp_SuggestGameInfoDirFn( CFSSteamSetupInfo const *pFsSteamSetupInfo, char *pchPathBuffer, int nBufferLength, bool *pbBubbleDirectories ) { const char *pProcessFileName = NULL; int nParmCount = CommandLine()->ParmCount(); if ( nParmCount > 1 ) { pProcessFileName = CommandLine()->GetParm( nParmCount - 1 ); } if ( pProcessFileName ) { Q_MakeAbsolutePath( pchPathBuffer, nBufferLength, pProcessFileName ); if ( pbBubbleDirectories ) *pbBubbleDirectories = true; return true; } return false; } int main( int argc, char **argv ) { SetSuggestGameInfoDirFn( CStudioMDLApp_SuggestGameInfoDirFn ); CFbx2DmxApp s_ApplicationObject; CSteamApplication s_SteamApplicationObject( &s_ApplicationObject ); return AppMain( argc, argv, &s_SteamApplicationObject ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- static void PrintUsage() { Msg( "\n" ); Msg( "NAME\n" ); Msg( " fbx2dmx - Converts an FBX file to a DMX file\n" ); Msg( "\n" ); Msg( "SYNOPSIS\n" ); Msg( " fbx2dmx [ opts ... ] < filename.fbx >\n" ); Msg( "\n" ); Msg( " -h | -help . . . . . . . . . . . Prints this information\n" ); Msg( " -nop4 . . . . . . . . . . . . . Turns off Perforce integration\n" ); Msg( " -i | -input <$> . . . . . . . . Specifies the input filename\n" ); Msg( " -o | -output <$> . . . . . . . Specifies the output filename\n" ); Msg( " -ufc . . . . . . . . . . . . . . _'s in delta names means they are\n" " corrective states\n" ); Msg( " -v . . . . . . . . . . . . . . . Each -v increases verbosity\n" ); Msg( " -a . . . . . . . . . . . . . . . Convert animation, normally models are\n" " converted\n" ); Msg( " -msp | -materialSearchPath <$> . Specify a material search path to remap\n" " materials to\n" ); Msg( " -psp . . . . . . . . . . . . . . Print the search paths that will be used\n" ); Msg( " -up . . . . . . . . . . . . . . One of [ x, y, z, -x, -y, -z ], Def: y\n" ); Msg( " -fp | -forwardParity . . . . . One of [ even, odd, -even, -odd ], Def: x\n" ); Msg( "\n" ); Msg( "DESCRIPTION\n" ); Msg( " Converts an FBX file to a DMX file. File is saved in same location as FBX\n" " file with extension changed\n" ); Msg( "\n" ); Msg( " If -i isn't specified then any argument that isn't associated with a command\n" " line switch is considered to be the input.\n" ); Msg( "\n" ); Msg( " If -o isn't specified then the extension of the input is changed to be .fbx\n" " and used as the output filename.\n" ); Msg( "\n" ); Msg( "AXIS SYSTEM\n" ); Msg( " To specify the axis system the resulting DMX file will contain, the\n" " combination of -up & -parity is used. -up specifies which axis is to be\n" " the up axis, prefix the x, y, or z with a minus to specify the negative\n" " axis. -forwardParity specifies the forward axis. Of the remaining two\n" " axes left after up is specified, parity specifies which of them,\n" " alphabetically, will be the forward, even being the first odd being the\n" " second. i.e. If Y is up, then even means X, odd means Z. Again prefix\n" " with a minus for the negative axis.\n" "\n" " e.g. To specify Maya Y Up which is +Y up and +Z forward: -up y -fp odd\n" " To specify Valve Engine which is +Z up and +X forward: -up z -fp even\n" ); Msg( "\n" ); Msg( "\n" ); } //----------------------------------------------------------------------------- // //----------------------------------------------------------------------------- bool ProcessAxisSystem( CDmFbxSerializer &dmFbxSerializer ) { // Defaults dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_Y; dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_ODD; const int nParmCount = CommandLine()->ParmCount(); const int nUpIndex = CommandLine()->FindParm( "-up" ); if ( nUpIndex > 0 ) { if ( nUpIndex < ( nParmCount - 1 ) ) // Ensure there's a parameter value after -up { const char *pszUp = CommandLine()->GetParm( nUpIndex + 1 ); if ( pszUp ) { if ( StringHasPrefix( pszUp, "x" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_X; } else if ( StringHasPrefix( pszUp, "y" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_Y; } else if ( StringHasPrefix( pszUp, "z" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_Z; } else if ( StringHasPrefix( pszUp, "-x" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_NX; } else if ( StringHasPrefix( pszUp, "-y" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_NY; } else if ( StringHasPrefix( pszUp, "-z" ) ) { dmFbxSerializer.m_eOptUpAxis = CDmeAxisSystem::AS_AXIS_NZ; } else { Warning( "Error! Invalid -up value specified, must be one of -up [ x, y, z, -x, -y, -z ]: \"%s\"\n", pszUp ); return false; } } else { Warning( "Error! No parameter specified after -up, must be one of -up [ x, y, z, -x, -y, -z ]\n" ); return false; } } else { Warning( "Error! No parameter specified after -up, must be one of -up [ x, y, z, -x, -y, -z ]\n" ); return false; } } int nFpIndex = 0; const char *szFp[] = { "-fp", "-forwardParity", "-forwardParity" }; for ( int i = 0; i < ARRAYSIZE( szFp ); ++i ) { const int nTmpFpIndex = CommandLine()->FindParm( szFp[i] ); if ( nTmpFpIndex > 0 ) { if ( nTmpFpIndex < nParmCount - 1 ) { nFpIndex = nTmpFpIndex; break; } else { Warning( "Error! No parameter specified after %s, must be one of %s [ even, odd, -even, -odd ]\n", CommandLine()->GetParm( nTmpFpIndex ), CommandLine()->GetParm( nTmpFpIndex ) ); return false; } } } if ( nFpIndex > 0 ) { const char *pszFp = CommandLine()->GetParm( nFpIndex + 1 ); if ( pszFp ) { if ( StringHasPrefix( pszFp, "e" ) ) { dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_EVEN; } else if ( StringHasPrefix( pszFp, "o" ) ) { dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_ODD; } else if ( StringHasPrefix( pszFp, "-e" ) ) { dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_NEVEN; } else if ( StringHasPrefix( pszFp, "-o" ) ) { dmFbxSerializer.m_eOptForwardParity = CDmeAxisSystem::AS_PARITY_NODD; } else { Warning( "Error! Invalid -forwardParity value specified, must be one of [ even, odd, -even, -odd ]: \"%s\"\n", pszFp ); return false; } } else { Warning( "Error! No parameter specified after %s, must be one of %s [ even, odd, -even, -odd ]\n", CommandLine()->GetParm( nFpIndex ), CommandLine()->GetParm( nFpIndex ) ); return false; } } return true; } bool CFbx2DmxApp::Create() { MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); if ( !ParseArguments() ) { return false; } AppSystemInfo_t appSystems[] = { { "vstdlib.dll", PROCESS_UTILS_INTERFACE_VERSION }, { "materialsystem.dll", MATERIAL_SYSTEM_INTERFACE_VERSION }, { "studiorender.dll", STUDIO_RENDER_INTERFACE_VERSION }, { "mdllib.dll", MDLLIB_INTERFACE_VERSION }, { "filesystem_stdio.dll", FILESYSTEM_INTERFACE_VERSION }, { "p4lib.dll", P4_INTERFACE_VERSION }, { "", "" } // Required to terminate the list }; AddSystem( g_pDataModel, VDATAMODEL_INTERFACE_VERSION ); AddSystem( g_pDmElementFramework, VDMELEMENTFRAMEWORK_VERSION ); AddSystem( g_pDmSerializers, DMSERIALIZERS_INTERFACE_VERSION ); // Add in the locally-defined studio data cache AppModule_t studioDataCacheModule = LoadModule( Sys_GetFactoryThis() ); AddSystem( studioDataCacheModule, STUDIO_DATA_CACHE_INTERFACE_VERSION ); // Add the P4 module separately so that if it is absent (say in the SDK) then the other system will initialize properly if ( !CommandLine()->FindParm( "-nop4" ) ) { AppModule_t p4Module = LoadModule( "p4lib.dll" ); AddSystem( p4Module, P4_INTERFACE_VERSION ); } AddSystem( g_pFbx, FBX_INTERFACE_VERSION ); bool bOk = AddSystems( appSystems ); if ( !bOk ) return false; IMaterialSystem *pMaterialSystem = ( IMaterialSystem* )FindSystem( MATERIAL_SYSTEM_INTERFACE_VERSION ); if ( !pMaterialSystem ) return false; pMaterialSystem->SetShaderAPI( "shaderapiempty.dll" ); return true; } int CFbx2DmxApp::Main() { char szOptFilename[ MAX_PATH ]; // Globally disable DMX undo g_pDataModel->SetUndoEnabled( false ); // This bit of hackery allows us to access files on the harddrive if ( m_bOptPrintSearchPaths ) { g_pFullFileSystem->PrintSearchPaths(); } //p4->SetVerbose( false ); p4->SetOpenFileChangeList( "fbx2dmx" ); int nRetVal = -1; char szExt[ MAX_PATH ] = { }; V_ExtractFileExtension( szOptFilename, szExt, ARRAYSIZE( szExt ) ); CDisableUndoScopeGuard noUndo; CDmElement *pDmRoot = NULL; if ( !V_stricmp( szExt, "obj" ) ) { CDmObjSerializer dmObjSerializer; pDmRoot = dmObjSerializer.ReadOBJ( szOptFilename ); if ( !pDmRoot ) { Warning( "Couldn't load OBJ file: %s\n", szOptFilename ); } } else { CDmFbxSerializer dmFbxSerializer; dmFbxSerializer.m_bOptUnderscoreForCorrectors = m_bOptUFC; dmFbxSerializer.m_nOptVerbosity = m_nOptVerbosity; dmFbxSerializer.m_bAnimation = m_bOptAnimation; dmFbxSerializer.m_sOptMaterialSearchPathList = m_sOptMaterialSearchPaths; if ( ProcessAxisSystem( dmFbxSerializer ) ) { pDmRoot = dmFbxSerializer.ReadFBX( m_pszOptFilename ); if ( !pDmRoot ) { Warning( "Couldn't load FBX file: %s\n", szOptFilename ); } } } if ( pDmRoot ) { if ( m_pszOptOutFilename ) { nRetVal = DoIt( pDmRoot, m_pszOptOutFilename, m_sOptMaterialSearchPaths ); } else { nRetVal = DoIt( pDmRoot, m_pszOptFilename, m_sOptMaterialSearchPaths ); } } return nRetVal; } bool CFbx2DmxApp::ParseArguments() { if ( CommandLine()->CheckParm( "-h" ) || CommandLine()->CheckParm( "--help" ) || CommandLine()->CheckParm( "-help" ) ) { PrintUsage(); return false; } for ( int i = 1; i < CommandLine()->ParmCount(); ++i ) { const char *pszParam = CommandLine()->GetParm( i ); if ( !V_stricmp( pszParam, "-game" ) ) { m_pszOptForceMod = CommandLine()->GetParm( ++i ); continue; } if ( !V_stricmp( pszParam, "-nop4" ) ) { continue; } if ( !V_stricmp( pszParam, "-ufc" ) ) { m_bOptUFC = true; continue; } if ( !V_stricmp( pszParam, "-v" ) ) { ++m_nOptVerbosity; continue; } if ( !V_stricmp( pszParam, "-a" ) ) { m_bOptAnimation = true; continue; } if ( !V_stricmp( pszParam, "-msp" ) || !V_stricmp( pszParam, "-materialSearchPath" ) ) { m_sOptMaterialSearchPaths.AddToTail( CommandLine()->GetParm( ++i ) ); continue; } if ( !V_stricmp( pszParam, "-psp" ) ) { m_bOptPrintSearchPaths = true; continue; } if ( !V_stricmp( pszParam, "-up" ) ) { ++i; continue; } if ( !V_stricmp( pszParam, "-fp" ) || !V_stricmp( pszParam, "-forwardParity" ) || !V_stricmp( pszParam, "-forwardparity" ) ) { ++i; continue; } if ( !V_stricmp( pszParam, "-i" ) || !V_stricmp( pszParam, "-input" ) ) { ++i; pszParam = CommandLine()->GetParm( i ); if ( m_pszOptFilename ) { Warning( "Warning! Filename was already specified as: \"%s\", using -i \"%s\"\n", m_pszOptFilename, pszParam ); } m_pszOptFilename = pszParam; continue; } if ( !V_stricmp( pszParam, "-o" ) || !V_stricmp( pszParam, "-output" ) ) { ++i; pszParam = CommandLine()->GetParm( i ); if ( m_pszOptOutFilename ) { Warning( "Warning! Output filename was already specified as: \"%s\", using -o \"%s\"\n", m_pszOptOutFilename, pszParam ); } m_pszOptOutFilename = pszParam; continue; } if ( StringHasPrefix( pszParam, "-" ) ) { Warning( "Warning! Unknown command line switch \"%s\"\n", pszParam ); continue; } if ( m_pszOptFilename ) { Warning( "Warning! Filename already specified: \"%s\", ignoring \"%s\"\n", m_pszOptFilename, pszParam ); } else { m_pszOptFilename = pszParam; } } if ( !m_pszOptFilename ) { Warning( "Error! Cannot find any file to execute from passed command line arguments\n\n" ); PrintUsage(); return false; } return true; } //----------------------------------------------------------------------------- // Purpose: bind studiohdr_t support functions to fbx2dmx utility // FIXME: This should be moved into studio.cpp? //----------------------------------------------------------------------------- const studiohdr_t *studiohdr_t::FindModel( void **cache, char const *pModelName ) const { MDLHandle_t handle = g_pMDLCache->FindMDL( pModelName ); *cache = ( void* )( uintp )handle; return g_pMDLCache->GetStudioHdr( handle ); } virtualmodel_t *studiohdr_t::GetVirtualModel( void ) const { if ( numincludemodels == 0 ) return NULL; return g_pMDLCache->GetVirtualModelFast( this, VoidPtrToMDLHandle( VirtualModel() ) ); } byte *studiohdr_t::GetAnimBlock( int i, bool preloadIfMissing ) const { return g_pMDLCache->GetAnimBlock( VoidPtrToMDLHandle( VirtualModel() ), i, preloadIfMissing ); } bool studiohdr_t::hasAnimBlockBeenPreloaded( int i ) const { return g_pMDLCache->HasAnimBlockBeenPreloaded( VoidPtrToMDLHandle( VirtualModel() ), i ); } int studiohdr_t::GetAutoplayList( unsigned short **pOut ) const { return g_pMDLCache->GetAutoplayList( VoidPtrToMDLHandle( VirtualModel() ), pOut ); } const studiohdr_t *virtualgroup_t::GetStudioHdr( void ) const { return g_pMDLCache->GetStudioHdr( VoidPtrToMDLHandle( cache ) ); }