//========= Copyright Valve Corporation, All rights reserved. ============// #include "stdafx.h" #include "physdll.h" #include "vphysics/constraints.h" #include "tier0/icommandline.h" #include "filesystem_tools.h" #include "simplify.h" #include "keyvalues.h" #include "studio.h" IPhysicsCollision *physcollision = NULL; IPhysicsSurfaceProps *physprops = NULL; int g_TotalOut = 0; int g_TotalCompress = 0; bool g_bRecursive = false; bool g_bQuiet = false; KeyValues *g_pModelConfig = NULL; void InitFilesystem( const char *pPath ) { CmdLib_InitFileSystem( pPath ); // This bit of hackery allows us to access files on the harddrive g_pFullFileSystem->AddSearchPath( "", "LOCAL", PATH_ADD_TO_HEAD ); } static bool LoadSurfaceProps( const char *pMaterialFilename ) { if ( !physprops ) return false; FileHandle_t fp = g_pFileSystem->Open( pMaterialFilename, "rb", TOOLS_READ_PATH_ID ); if ( fp == FILESYSTEM_INVALID_HANDLE ) return false; int len = g_pFileSystem->Size( fp ); char *pText = new char[len+1]; g_pFileSystem->Read( pText, len, fp ); g_pFileSystem->Close( fp ); pText[len]=0; physprops->ParseSurfaceData( pMaterialFilename, pText ); delete[] pText; return true; } void LoadSurfacePropsAll() { // already loaded if ( physprops->SurfacePropCount() ) return; const char *SURFACEPROP_MANIFEST_FILE = "scripts/surfaceproperties_manifest.txt"; KeyValues *manifest = new KeyValues( SURFACEPROP_MANIFEST_FILE ); if ( manifest->LoadFromFile( g_pFileSystem, SURFACEPROP_MANIFEST_FILE, "GAME" ) ) { for ( KeyValues *sub = manifest->GetFirstSubKey(); sub != NULL; sub = sub->GetNextKey() ) { if ( !Q_stricmp( sub->GetName(), "file" ) ) { // Add LoadSurfaceProps( sub->GetString() ); continue; } } } manifest->deleteThis(); } void InitVPhysics() { CreateInterfaceFn physicsFactory = GetPhysicsFactory(); physcollision = (IPhysicsCollision *)physicsFactory( VPHYSICS_COLLISION_INTERFACE_VERSION, NULL ); physprops = (IPhysicsSurfaceProps *)physicsFactory( VPHYSICS_SURFACEPROPS_INTERFACE_VERSION, NULL ); LoadSurfacePropsAll(); } struct phyfile_t { phyheader_t header; vcollide_t collide; int fileSize; }; void LoadPHYFile(phyfile_t *pOut, const char *name) { memset( pOut, 0, sizeof(*pOut) ); FileHandle_t file = g_pFullFileSystem->Open( name, "rb" ); if ( !file ) return; g_pFullFileSystem->Read( &pOut->header, sizeof(pOut->header), file ); if ( pOut->header.size != sizeof(pOut->header) || pOut->header.solidCount <= 0 ) return; pOut->fileSize = g_pFullFileSystem->Size( file ); char *buf = (char *)_alloca( pOut->fileSize ); g_pFullFileSystem->Read( buf, pOut->fileSize, file ); g_pFullFileSystem->Close( file ); physcollision->VCollideLoad( &pOut->collide, pOut->header.solidCount, (const char *)buf, pOut->fileSize ); } void OverrideDefaultsForModel( const char *keyname, simplifyparams_t ¶ms ) { KeyValues *pKeys = g_pModelConfig; while ( pKeys ) { if ( !Q_stricmp( pKeys->GetName(), keyname ) ) { for ( KeyValues *pData = pKeys->GetFirstSubKey(); pData; pData = pData->GetNextKey() ) { if ( !Q_stricmp( pData->GetName(), "tolerance" ) ) { params.tolerance = pData->GetFloat(); if (!g_bQuiet) { Msg("%s: tolerance set to %.2f\n", keyname, params.tolerance ); } } else if ( !Q_stricmp( pData->GetName(), "addAABB" ) ) { params.addAABBToSimplifiedHull = pData->GetInt() ? true : false; if (!g_bQuiet) { Msg("%s: AABB %s\n", keyname, params.addAABBToSimplifiedHull ? "on" : "off" ); } } else if ( !Q_stricmp( pData->GetName(), "singleconvex" ) ) { params.forceSingleConvex = pData->GetInt() ? true : false; if (!g_bQuiet) { Msg("%s: Forced to single convex\n", keyname ); } } else if ( !Q_stricmp( pData->GetName(), "mergeconvex" ) ) { params.mergeConvexTolerance = pData->GetFloat(); params.mergeConvexElements = params.mergeConvexTolerance > 0 ? true : false; if (!g_bQuiet) { Msg("%s: Merge convex %.2f\n", keyname, params.mergeConvexTolerance ); } } } return; } pKeys = pKeys->GetNextKey(); } } bool HasMultipleBones( const char *pFilename ) { char outName[1024]; studiohdr_t hdr; Q_strncpy( outName, pFilename, sizeof(outName) ); Q_SetExtension( outName, ".mdl", sizeof(outName) ); FileHandle_t fp = g_pFileSystem->Open( outName, "rb", TOOLS_READ_PATH_ID ); if ( fp == FILESYSTEM_INVALID_HANDLE ) return false; g_pFileSystem->Read( &hdr, sizeof(hdr), fp ); g_pFileSystem->Close( fp ); if ( hdr.numbones > 1 ) return true; return false; } void WritePHXFile( const char *pName, const phyfile_t &file ) { if ( file.header.size != sizeof(file.header) || file.collide.solidCount <= 0 ) return; CUtlBuffer out; char outName[1024]; Q_snprintf( outName, sizeof(outName), "%s", pName ); Q_SetExtension( outName, ".phx", sizeof(outName) ); simplifyparams_t params; params.Defaults(); params.tolerance = (file.collide.solidCount > 1) ? 4.0f : 2.0f; // single solids constraint to AABB for placement help params.addAABBToSimplifiedHull = (file.collide.solidCount == 1) ? true : false; params.mergeConvexElements = true; params.mergeConvexTolerance = 0.025f; Q_FixSlashes(outName); Q_strlower(outName); char *pSearch = Q_strstr( outName,"models\\" ); if ( pSearch ) { char keyname[1024]; pSearch += strlen("models\\"); Q_StripExtension( pSearch, keyname, sizeof(keyname) ); OverrideDefaultsForModel( keyname, params ); } out.Put( &file.header, sizeof(file.header) ); int outSize = 0; bool bStoreSolidNames = file.collide.solidCount > 1 ? true : false; bStoreSolidNames = bStoreSolidNames || HasMultipleBones(outName); vcollide_t *pNewCollide = ConvertVCollideToPHX( &file.collide, params, &outSize, false, bStoreSolidNames); g_TotalOut += file.fileSize; for ( int i = 0; i < pNewCollide->solidCount; i++ ) { int collideSize = physcollision->CollideSize( pNewCollide->solids[i] ); out.PutInt( collideSize ); char *pMem = new char[collideSize]; physcollision->CollideWrite( pMem, pNewCollide->solids[i] ); out.Put( pMem, collideSize ); delete[] pMem; } if (!g_bQuiet) { Msg("%s Compressed %d (%d text) to %d (%d text)\n", outName, file.fileSize, file.collide.descSize, out.TellPut(), pNewCollide->descSize ); } out.Put( pNewCollide->pKeyValues, pNewCollide->descSize ); g_TotalCompress += out.TellPut(); #if 0 //Msg("OLD:\n-----------------------------------\n%s\n", file.collide.pKeyValues ); CPackedPhysicsDescription *pPacked = physcollision->CreatePackedDesc( pNewCollide->pKeyValues, pNewCollide->descSize ); Msg("NEW:\n-----------------------------------\n" ); for ( int i = 0; i < pPacked->m_solidCount; i++ ) { solid_t solid; pPacked->GetSolid( &solid, i ); Msg("index %d\n", solid.index ); Msg("name %s\n", solid.name ); Msg("mass %.2f\n", solid.params.mass ); Msg("surfaceprop %s\n", solid.surfaceprop); Msg("damping %.2f\n", solid.params.damping ); Msg("rotdamping %.2f\n", solid.params.rotdamping ); Msg("drag %.2f\n", solid.params.dragCoefficient ); Msg("inertia %.2f\n", solid.params.inertia ); Msg("volume %.2f\n", solid.params.volume ); } #endif DestroyPHX( pNewCollide ); if ( !g_pFullFileSystem->WriteFile( outName, NULL, out ) ) Warning("Can't write file: %s\n", outName ); } void UnloadPHYFile( phyfile_t *pFile ) { physcollision->VCollideUnload( &pFile->collide ); pFile->header.size = 0; } void MakeFilename( char *pDest, int destSize, const char *pPathname, const char *pFilenameExt ) { Q_strncpy(pDest, pPathname, destSize); Q_AppendSlash(pDest, destSize); Q_strncat(pDest, pFilenameExt, destSize); } void MakeDirname( char *pDest, int destSize, const char *pPathname, const char *pSubdir ) { MakeFilename(pDest, destSize , pPathname, pSubdir); } int main( int argc, char *argv[] ) { if ( argc < 2 ) { Msg("Usage:\nmakephx [options] \ne.g. makephx [-r] *.phy\n"); return 0; } CommandLine()->CreateCmdLine( argc, argv ); g_bRecursive = CommandLine()->FindParm("-r") > 0 ? true : false; g_bQuiet = CommandLine()->FindParm("-quiet") > 0 ? true : false; InitFilesystem( "*.*" ); InitVPhysics(); // disable automatic packing, we want to do this ourselves. physcollision->SetPackOnLoad( false ); MathLib_Init( 2.2f, 2.2f, 0.0f, 2.0f, false, false, false, false ); InstallSpewFunction(); g_pModelConfig = new KeyValues("config"); g_pModelConfig->LoadFromFile( g_pFullFileSystem, "phx.cfg", "GAME" ); g_TotalOut = 0; g_TotalCompress = 0; FileFindHandle_t handle; char fullpath[1024], currentFile[1024], dirName[1024], nameext[256]; strcpy( fullpath, argv[argc-1] ); strcpy( fullpath, ExpandPath( fullpath ) ); strcpy( fullpath, ExpandArg( fullpath ) ); Q_strncpy(dirName, fullpath, sizeof(dirName)); Q_StripFilename(dirName); Q_strncpy(nameext, fullpath + strlen(dirName)+1, sizeof(nameext)); CUtlVector< const char * > directoryList; directoryList.AddToTail( strdup(dirName) ); int current = 0; int count = 0; do { if ( g_bRecursive ) { MakeFilename( currentFile, sizeof(currentFile), directoryList[current], "*.*" ); const char *pFilename = g_pFullFileSystem->FindFirst( currentFile, &handle ); while ( pFilename ) { if ( pFilename[0] != '.' && g_pFullFileSystem->FindIsDirectory( handle ) ) { MakeDirname( currentFile, sizeof(currentFile), directoryList[current], pFilename ); directoryList.AddToTail(strdup(currentFile)); } pFilename = g_pFullFileSystem->FindNext( handle ); } g_pFullFileSystem->FindClose( handle ); } MakeFilename(currentFile, sizeof(currentFile), directoryList[current], nameext); const char *pFilename = g_pFullFileSystem->FindFirst( currentFile, &handle ); while ( pFilename ) { phyfile_t phy; MakeFilename(currentFile, sizeof(currentFile), directoryList[current], pFilename); LoadPHYFile( &phy, currentFile ); if ( phy.collide.isPacked || phy.collide.solidCount < 1 ) { Msg("%s is not a valid PHY file\n", currentFile ); } else { WritePHXFile( currentFile, phy ); count++; } UnloadPHYFile( &phy ); pFilename = g_pFullFileSystem->FindNext( handle ); } g_pFullFileSystem->FindClose( handle ); current++; } while( current < directoryList.Count() ); if ( count ) { if (!g_bQuiet) { Msg("\n------\nTotal %s, %s\nSaved %s\n", Q_pretifymem( g_TotalOut ), Q_pretifymem( g_TotalCompress ), Q_pretifymem( g_TotalOut - g_TotalCompress ) ); Msg("%.2f%% savings\n", ((float)(g_TotalOut-g_TotalCompress) / (float)g_TotalOut) * 100.0f ); } } else { Msg("No files found in %s!\n", directoryList[current] ); } return 0; }