diff options
| author | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
|---|---|---|
| committer | FluorescentCIAAfricanAmerican <[email protected]> | 2020-04-22 12:56:21 -0400 |
| commit | 3bf9df6b2785fa6d951086978a3e66f49427166a (patch) | |
| tree | 2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/modelloader.cpp | |
| download | archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip | |
Diffstat (limited to 'engine/modelloader.cpp')
| -rw-r--r-- | engine/modelloader.cpp | 6232 |
1 files changed, 6232 insertions, 0 deletions
diff --git a/engine/modelloader.cpp b/engine/modelloader.cpp new file mode 100644 index 0000000..72a479d --- /dev/null +++ b/engine/modelloader.cpp @@ -0,0 +1,6232 @@ +//========= Copyright Valve Corporation, All rights reserved. ============// +// +// Purpose: Model loading / unloading interface +// +// $NoKeywords: $ +//===========================================================================// + +#include "render_pch.h" +#include "common.h" +#include "modelloader.h" +#include "sysexternal.h" +#include "cmd.h" +#include "istudiorender.h" +#include "engine/ivmodelinfo.h" +#include "draw.h" +#include "zone.h" +#include "edict.h" +#include "cmodel_engine.h" +#include "cdll_engine_int.h" +#include "iscratchpad3d.h" +#include "materialsystem/imaterialsystemhardwareconfig.h" +#include "materialsystem/materialsystem_config.h" +#include "gl_rsurf.h" +#include "video/ivideoservices.h" +#include "materialsystem/itexture.h" +#include "Overlay.h" +#include "utldict.h" +#include "mempool.h" +#include "r_decal.h" +#include "l_studio.h" +#include "gl_drawlights.h" +#include "tier0/icommandline.h" +#include "MapReslistGenerator.h" +#ifndef SWDS +#include "vgui_baseui_interface.h" +#endif +#include "engine/ivmodelrender.h" +#include "host.h" +#include "datacache/idatacache.h" +#include "sys_dll.h" +#include "datacache/imdlcache.h" +#include "gl_cvars.h" +#include "vphysics_interface.h" +#include "filesystem/IQueuedLoader.h" +#include "tier2/tier2.h" +#include "lightcache.h" +#include "lumpfiles.h" +#include "tier2/fileutils.h" +#include "UtlSortVector.h" +#include "utlhashtable.h" +#include "tier1/lzmaDecoder.h" +#include "eiface.h" +#include "server.h" +#include "ifilelist.h" +#include "LoadScreenUpdate.h" +#include "optimize.h" +#include "networkstringtable.h" +#include "tier1/callqueue.h" + +// memdbgon must be the last include file in a .cpp file!!! +#include "tier0/memdbgon.h" + +ConVar mat_loadtextures( "mat_loadtextures", "1", FCVAR_CHEAT ); + +// OS X and Linux are blowing up right now due to this. Benefits vs possible regressions on DX less clear. +#if defined( DX_TO_GL_ABSTRACTION ) || defined( STAGING_ONLY ) + #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "1" +#else + #define CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH "0" +#endif +static ConVar mod_offline_hdr_switch( "mod_offline_hdr_switch", CONVAR_DEFAULT_MOD_OFFLINE_HDR_SWITCH, FCVAR_INTERNAL_USE, + "Re-order the HDR/LDR mode switch to do most of the material system " + "reloading with the device offline. This reduces unnecessary device " + "resource uploads and may drastically reduce load time and memory pressure " + "on certain drivers, but may trigger bugs in some very old source engine " + "pathways." ); +static ConVar mod_touchalldata( "mod_touchalldata", "1", 0, "Touch model data during level startup" ); +static ConVar mod_forcetouchdata( "mod_forcetouchdata", "1", 0, "Forces all model file data into cache on model load." ); +ConVar mat_excludetextures( "mat_excludetextures", "0", FCVAR_CHEAT ); + +ConVar r_unloadlightmaps( "r_unloadlightmaps", "0", FCVAR_CHEAT ); +ConVar r_hunkalloclightmaps( "r_hunkalloclightmaps", "1" ); +extern ConVar r_lightcache_zbuffercache; + + +static ConVar mod_dynamicunloadtime( "mod_dynamicunloadtime", "150", FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicunloadtextures( "mod_dynamicunloadtex", "1", FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadpause( "mod_dynamicloadpause", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadthrottle( "mod_dynamicloadthrottle", "0", FCVAR_CHEAT | FCVAR_HIDDEN | FCVAR_DONTRECORD ); +static ConVar mod_dynamicloadspew( "mod_dynamicloadspew", "0", FCVAR_HIDDEN | FCVAR_DONTRECORD ); + +#define DynamicModelDebugMsg(...) ( mod_dynamicloadspew.GetBool() ? Msg(__VA_ARGS__) : (void)0 ) + + +bool g_bHunkAllocLightmaps; + +extern CGlobalVars g_ServerGlobalVariables; +extern IMaterial *g_materialEmpty; +extern ConVar r_rootlod; + +bool g_bLoadedMapHasBakedPropLighting = false; +bool g_bBakedPropLightingNoSeparateHDR = false; // Some maps only have HDR lighting on props, contained in the file for non-hdr light data + +double g_flAccumulatedModelLoadTime; +double g_flAccumulatedModelLoadTimeStudio; +double g_flAccumulatedModelLoadTimeStaticMesh; +double g_flAccumulatedModelLoadTimeBrush; +double g_flAccumulatedModelLoadTimeSprite; +double g_flAccumulatedModelLoadTimeVCollideSync; +double g_flAccumulatedModelLoadTimeVCollideAsync; +double g_flAccumulatedModelLoadTimeVirtualModel; +double g_flAccumulatedModelLoadTimeMaterialNamesOnly; + +//----------------------------------------------------------------------------- +// A dictionary used to store where to find game lump data in the .bsp file +//----------------------------------------------------------------------------- + +// Extended from the on-disk struct to include uncompressed size and stop propagation of bogus signed values +struct dgamelump_internal_t +{ + dgamelump_internal_t( dgamelump_t &other, unsigned int nCompressedSize ) + : id( other.id ) + , flags( other.flags ) + , version( other.version ) + , offset( Max( other.fileofs, 0 ) ) + , uncompressedSize( Max( other.filelen, 0 ) ) + , compressedSize( nCompressedSize ) + {} + GameLumpId_t id; + unsigned short flags; + unsigned short version; + unsigned int offset; + unsigned int uncompressedSize; + unsigned int compressedSize; +}; + +static CUtlVector< dgamelump_internal_t > g_GameLumpDict; +static char g_GameLumpFilename[128] = { 0 }; + +//void NotifyHunkBeginMapLoad( const char *pszMapName ) +//{ +// Hunk_OnMapStart( 32*1024*1024 ); +//} + + +// FIXME/TODO: Right now Host_FreeToLowMark unloads all models including studio +// models that have Cache_Alloc data, too. This needs to be fixed before shipping + +BEGIN_BYTESWAP_DATADESC( lump_t ) + DEFINE_FIELD( fileofs, FIELD_INTEGER ), + DEFINE_FIELD( filelen, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_FIELD( uncompressedSize, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +BEGIN_BYTESWAP_DATADESC( dheader_t ) + DEFINE_FIELD( ident, FIELD_INTEGER ), + DEFINE_FIELD( version, FIELD_INTEGER ), + DEFINE_EMBEDDED_ARRAY( lumps, HEADER_LUMPS ), + DEFINE_FIELD( mapRevision, FIELD_INTEGER ), +END_BYTESWAP_DATADESC() + +bool Model_LessFunc( FileNameHandle_t const &a, FileNameHandle_t const &b ) +{ + return a < b; +} + + + +//----------------------------------------------------------------------------- +// Purpose: Implements IModelLoader +//----------------------------------------------------------------------------- +class CModelLoader : public IModelLoader +{ +// Implement IModelLoader interface +public: + CModelLoader() : m_ModelPool( sizeof( model_t ), MAX_KNOWN_MODELS, CUtlMemoryPool::GROW_FAST, "CModelLoader::m_ModelPool" ), + m_Models( 0, 0, Model_LessFunc ) + { + } + + void Init( void ); + void Shutdown( void ); + + int GetCount( void ); + model_t *GetModelForIndex( int i ); + + // Look up name for model + const char *GetName( model_t const *model ); + + // Check cache for data, reload model if needed + void *GetExtraData( model_t *model ); + + int GetModelFileSize( char const *name ); + + // Finds the model, and loads it if it isn't already present. Updates reference flags + model_t *GetModelForName( const char *name, REFERENCETYPE referencetype ); + // Mark as referenced by name + model_t *ReferenceModel( const char *name, REFERENCETYPE referencetype ); + + // Unmasks the referencetype field for the model + void UnreferenceModel( model_t *model, REFERENCETYPE referencetype ); + // Unmasks the specified reference type across all models + void UnreferenceAllModels( REFERENCETYPE referencetype ); + // Set all models to last loaded on server count -1 + void ResetModelServerCounts(); + + // For any models with referencetype blank, frees all memory associated with the model + // and frees up the models slot + void UnloadUnreferencedModels( void ); + void PurgeUnusedModels( void ); + + bool Map_GetRenderInfoAllocated( void ); + void Map_SetRenderInfoAllocated( bool allocated ); + + virtual void Map_LoadDisplacements( model_t *pModel, bool bRestoring ); + + // Validate version/header of a .bsp file + bool Map_IsValid( char const *mapname, bool bQuiet = false ); + + virtual void RecomputeSurfaceFlags( model_t *mod ); + + virtual void Studio_ReloadModels( ReloadType_t reloadType ); + + void Print( void ); + + // Is a model loaded? + virtual bool IsLoaded( const model_t *mod ); + + virtual bool LastLoadedMapHasHDRLighting(void); + + void DumpVCollideStats(); + + // Returns the map model, otherwise NULL, no load or create + model_t *FindModelNoCreate( const char *pModelName ); + + // Finds the model, builds a model entry if not present + model_t *FindModel( const char *name ); + + modtype_t GetTypeFromName( const char *pModelName ); + + // start with -1, list terminates with -1 + int FindNext( int iIndex, model_t **ppModel ); + + virtual void UnloadModel( model_t *pModel ); + + virtual void ReloadFilesInList( IFileList *pFilesToReload ); + + virtual const char *GetActiveMapName( void ); + + // Called by app system once per frame to poll and update dynamic models + virtual void UpdateDynamicModels() { InternalUpdateDynamicModels(false); } + + // Called by server and client engine code to flush unreferenced dynamic models + virtual void FlushDynamicModels() { InternalUpdateDynamicModels(true); } + + // Called by server and client to force-unload dynamic models regardless of refcount! + virtual void ForceUnloadNonClientDynamicModels(); + + // Called by client code to load dynamic models, instead of GetModelForName. + virtual model_t *GetDynamicModel( const char *name, bool bClientOnly ); + + // Called by client code to query dynamic model state + virtual bool IsDynamicModelLoading( model_t *pModel, bool bClientOnly ); + + // Called by client code to refcount dynamic models + virtual void AddRefDynamicModel( model_t *pModel, bool bClientSideRef ); + virtual void ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ); + + // Called by client code or GetDynamicModel + virtual bool RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ); + + // Called by client code or IModelLoadCallback destructor + virtual void UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ); + + virtual void Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ); + + void DebugPrintDynamicModels(); + +// Internal types +private: + // TODO, flag these and allow for UnloadUnreferencedModels to check for allocation type + // so we don't have to flush all of the studio models when we free the hunk + enum + { + FALLOC_USESHUNKALLOC = (1<<31), + FALLOC_USESCACHEALLOC = (1<<30), + }; + +// Internal methods +private: + // Set reference flags and load model if it's not present already + model_t *LoadModel( model_t *model, REFERENCETYPE *referencetype ); + // Unload models ( won't unload referenced models if checkreferences is true ) + void UnloadAllModels( bool checkreference ); + void SetupSubModels( model_t *model, CUtlVector<mmodel_t> &list ); + + // World/map + void Map_LoadModel( model_t *mod ); + void Map_UnloadModel( model_t *mod ); + void Map_UnloadCubemapSamples( model_t *mod ); + + // World loading helper + void SetWorldModel( model_t *mod ); + void ClearWorldModel( void ); + bool IsWorldModelSet( void ); + int GetNumWorldSubmodels( void ); + + // Sprites + void Sprite_LoadModel( model_t *mod ); + void Sprite_UnloadModel( model_t *mod ); + + // Studio models + void Studio_LoadModel( model_t *mod, bool bTouchAllData ); + void Studio_UnloadModel( model_t *mod ); + + // Byteswap + int UpdateOrCreate( const char *pSourceName, char *pTargetName, int maxLen, bool bForce ); + + // Dynamic load queue + class CDynamicModelInfo; + void QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); + bool CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ); + void UpdateDynamicModelLoadQueue(); + + void FinishDynamicModelLoadIfReady( CDynamicModelInfo *dyn, model_t *mod ); + + void InternalUpdateDynamicModels( bool bIgnoreUpdateTime ); + + // Internal data +private: + enum + { + MAX_KNOWN_MODELS = 1024, + }; + + struct ModelEntry_t + { + model_t *modelpointer; + }; + + CUtlMap< FileNameHandle_t, ModelEntry_t > m_Models; + + CUtlMemoryPool m_ModelPool; + + CUtlVector<model_t> m_InlineModels; + + model_t *m_pWorldModel; + +public: // HACKHACK + worldbrushdata_t m_worldBrushData; + +private: + // local name of current loading model + char m_szLoadName[64]; + + bool m_bMapRenderInfoLoaded; + bool m_bMapHasHDRLighting; + + char m_szActiveMapName[64]; + + // Dynamic model support: + class CDynamicModelInfo + { + public: + enum { QUEUED = 0x01, LOADING = 0x02, CLIENTREADY = 0x04, SERVERLOADING = 0x08, ALLREADY = 0x10, INVALIDFLAG = 0x20 }; // flags + CDynamicModelInfo() : m_iRefCount(0), m_iClientRefCount(0), m_nLoadFlags(INVALIDFLAG), m_uLastTouchedMS_Div256(0) { } + int16 m_iRefCount; + int16 m_iClientRefCount; // also doublecounted in m_iRefCount + uint32 m_nLoadFlags : 8; + uint32 m_uLastTouchedMS_Div256 : 24; + CUtlVector< uintptr_t > m_Callbacks; // low bit = client only + }; + + CUtlHashtable< model_t * , CDynamicModelInfo > m_DynamicModels; + CUtlHashtable< uintptr_t , int > m_RegisteredDynamicCallbacks; + + // Dynamic model load queue + CUtlVector< model_t* > m_DynamicModelLoadQueue; + bool m_bDynamicLoadQueueHeadActive; +}; + +// Expose interface +static CModelLoader g_ModelLoader; +IModelLoader *modelloader = ( IModelLoader * )&g_ModelLoader; + +//----------------------------------------------------------------------------- +// Globals used by the CMapLoadHelper +//----------------------------------------------------------------------------- +static dheader_t s_MapHeader; +static FileHandle_t s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; +static char s_szLoadName[128]; +static char s_szMapName[128]; +static worldbrushdata_t *s_pMap = NULL; +static int s_nMapLoadRecursion = 0; +static CUtlBuffer s_MapBuffer; + +// Lump files are patches for a shipped map +// List of lump files found when map was loaded. Each entry is the lump file index for that lump id. +struct lumpfiles_t +{ + FileHandle_t file; + int lumpfileindex; + lumpfileheader_t header; +}; +static lumpfiles_t s_MapLumpFiles[ HEADER_LUMPS ]; + +CON_COMMAND( mem_vcollide, "Dumps the memory used by vcollides" ) +{ + g_ModelLoader.DumpVCollideStats(); +} + +//----------------------------------------------------------------------------- +// Returns the ref count for this bsp +//----------------------------------------------------------------------------- +int CMapLoadHelper::GetRefCount() +{ + return s_nMapLoadRecursion; +} + +//----------------------------------------------------------------------------- +// Setup a BSP loading context, maintains a ref count. +//----------------------------------------------------------------------------- +void CMapLoadHelper::Init( model_t *pMapModel, const char *loadname ) +{ + if ( ++s_nMapLoadRecursion > 1 ) + { + return; + } + + s_pMap = NULL; + s_szLoadName[ 0 ] = 0; + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + + if ( !pMapModel ) + { + V_strcpy_safe( s_szMapName, loadname ); + } + else + { + V_strcpy_safe( s_szMapName, pMapModel->strName ); + } + + s_MapFileHandle = g_pFileSystem->OpenEx( s_szMapName, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, IsX360() ? "GAME" : NULL ); + if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Host_Error( "CMapLoadHelper::Init, unable to open %s\n", s_szMapName ); + return; + } + + g_pFileSystem->Read( &s_MapHeader, sizeof( dheader_t ), s_MapFileHandle ); + if ( s_MapHeader.ident != IDBSPHEADER ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); + return; + } + + if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, + s_MapHeader.version, BSPVERSION ); + return; + } + + V_strcpy_safe( s_szLoadName, loadname ); + + // Store map version, but only do it once so that the communication between the engine and Hammer isn't broken. The map version + // is incremented whenever a Hammer to Engine session is established so resetting the global map version each time causes a problem. + if ( 0 == g_ServerGlobalVariables.mapversion ) + { + g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; + } + +#ifndef SWDS + InitDLightGlobals( s_MapHeader.version ); +#endif + + s_pMap = &g_ModelLoader.m_worldBrushData; + +#if 0 + // XXX(johns): There are security issues with this system currently. sv_pure doesn't handle unexpected/mismatched + // lumps, so players can create lumps for maps not using them to wallhack/etc.. Currently unused, + // disabling until we have time to make a proper security pass. + if ( IsPC() ) + { + // Now find and open our lump files, and create the master list of them. + for ( int iIndex = 0; iIndex < MAX_LUMPFILES; iIndex++ ) + { + lumpfileheader_t lumpHeader; + char lumpfilename[MAX_PATH]; + + GenerateLumpFileName( s_szMapName, lumpfilename, MAX_PATH, iIndex ); + if ( !g_pFileSystem->FileExists( lumpfilename ) ) + break; + + // Open the lump file + FileHandle_t lumpFile = g_pFileSystem->Open( lumpfilename, "rb" ); + if ( lumpFile == FILESYSTEM_INVALID_HANDLE ) + { + Host_Error( "CMapLoadHelper::Init, failed to load lump file %s\n", lumpfilename ); + return; + } + + // Read the lump header + memset( &lumpHeader, 0, sizeof( lumpHeader ) ); + g_pFileSystem->Read( &lumpHeader, sizeof( lumpfileheader_t ), lumpFile ); + + if ( lumpHeader.lumpID >= 0 && lumpHeader.lumpID < HEADER_LUMPS ) + { + // We may find multiple lump files for the same lump ID. If so, + // close the earlier lump file, because the later one overwrites it. + if ( s_MapLumpFiles[lumpHeader.lumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapLumpFiles[lumpHeader.lumpID].file ); + } + + s_MapLumpFiles[lumpHeader.lumpID].file = lumpFile; + s_MapLumpFiles[lumpHeader.lumpID].lumpfileindex = iIndex; + memcpy( &(s_MapLumpFiles[lumpHeader.lumpID].header), &lumpHeader, sizeof(lumpHeader) ); + } + else + { + Warning("Found invalid lump file '%s'. Lump Id: %d\n", lumpfilename, lumpHeader.lumpID ); + } + } + } +#endif +} + +//----------------------------------------------------------------------------- +// Setup a BSP loading context from a supplied buffer +//----------------------------------------------------------------------------- +void CMapLoadHelper::InitFromMemory( model_t *pMapModel, const void *pData, int nDataSize ) +{ + // valid for 360 only + // 360 has reorganized bsp format and no external lump files + Assert( IsX360() && pData && nDataSize ); + + if ( ++s_nMapLoadRecursion > 1 ) + { + return; + } + + s_pMap = NULL; + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + + V_strcpy_safe( s_szMapName, pMapModel->strName ); + V_FileBase( s_szMapName, s_szLoadName, sizeof( s_szLoadName ) ); + + s_MapBuffer.SetExternalBuffer( (void *)pData, nDataSize, nDataSize ); + + V_memcpy( &s_MapHeader, pData, sizeof( dheader_t ) ); + + if ( s_MapHeader.ident != IDBSPHEADER ) + { + Host_Error( "CMapLoadHelper::Init, map %s has wrong identifier\n", s_szMapName ); + return; + } + + if ( s_MapHeader.version < MINBSPVERSION || s_MapHeader.version > BSPVERSION ) + { + Host_Error( "CMapLoadHelper::Init, map %s has wrong version (%i when expecting %i)\n", s_szMapName, s_MapHeader.version, BSPVERSION ); + return; + } + + // Store map version + g_ServerGlobalVariables.mapversion = s_MapHeader.mapRevision; + +#ifndef SWDS + InitDLightGlobals( s_MapHeader.version ); +#endif + + s_pMap = &g_ModelLoader.m_worldBrushData; +} + +//----------------------------------------------------------------------------- +// Shutdown a BSP loading context. +//----------------------------------------------------------------------------- +void CMapLoadHelper::Shutdown( void ) +{ + if ( --s_nMapLoadRecursion > 0 ) + { + return; + } + + if ( s_MapFileHandle != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapFileHandle ); + s_MapFileHandle = FILESYSTEM_INVALID_HANDLE; + } + + if ( IsPC() ) + { + // Close our open lump files + for ( int i = 0; i < HEADER_LUMPS; i++ ) + { + if ( s_MapLumpFiles[i].file != FILESYSTEM_INVALID_HANDLE ) + { + g_pFileSystem->Close( s_MapLumpFiles[i].file ); + } + } + V_memset( &s_MapLumpFiles, 0, sizeof( s_MapLumpFiles ) ); + } + + s_szLoadName[ 0 ] = 0; + V_memset( &s_MapHeader, 0, sizeof( s_MapHeader ) ); + s_pMap = NULL; + + // discard from memory + if ( s_MapBuffer.Base() ) + { + free( s_MapBuffer.Base() ); + s_MapBuffer.SetExternalBuffer( NULL, 0, 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Free the lighting lump (increases free memory during loading on 360) +//----------------------------------------------------------------------------- +void CMapLoadHelper::FreeLightingLump( void ) +{ + if ( IsX360() && ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) && s_MapBuffer.Base() ) + { + int lightingLump = LumpSize( LUMP_LIGHTING_HDR ) ? LUMP_LIGHTING_HDR : LUMP_LIGHTING; + // Should never have both lighting lumps on 360 + Assert( ( lightingLump == LUMP_LIGHTING ) || ( LumpSize( LUMP_LIGHTING ) == 0 ) ); + + if ( LumpSize( lightingLump ) ) + { + // Check that the lighting lump is the last one in the BSP + int lightingOffset = LumpOffset( lightingLump ); + for ( int i = 0;i < HEADER_LUMPS; i++ ) + { + if ( ( LumpOffset( i ) > lightingOffset ) && ( i != LUMP_PAKFILE ) ) + { + Warning( "CMapLoadHelper: Cannot free lighting lump (should be last before the PAK lump). Regenerate the .360.bsp file with the latest version of makegamedata." ); + return; + } + } + + // Flag the lighting chunk as gone from the BSP (principally, this sets 'filelen' to 0) + V_memset( &s_MapHeader.lumps[ lightingLump ], 0, sizeof( lump_t ) ); + + // Shrink the buffer to free up the space that was used by the lighting lump + void * shrunkBuffer = realloc( s_MapBuffer.Base(), lightingOffset ); + Assert( shrunkBuffer == s_MapBuffer.Base() ); // A shrink would surely never move!!! + s_MapBuffer.SetExternalBuffer( shrunkBuffer, lightingOffset, lightingOffset ); + } + } +} + + +//----------------------------------------------------------------------------- +// Returns the size of a particular lump without loading it... +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpSize( int lumpId ) +{ + // If we have a lump file for this lump, return its length instead + if ( IsPC() && s_MapLumpFiles[lumpId].file != FILESYSTEM_INVALID_HANDLE ) + { + return s_MapLumpFiles[lumpId].header.lumpLength; + } + + lump_t *pLump = &s_MapHeader.lumps[ lumpId ]; + Assert( pLump ); + + // all knowledge of compression is private, they expect and get the original size + int originalSize = s_MapHeader.lumps[lumpId].uncompressedSize; + if ( originalSize != 0 ) + { + return originalSize; + } + + return pLump->filelen; +} + +//----------------------------------------------------------------------------- +// Returns the offset of a particular lump without loading it... +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpOffset( int lumpID ) +{ + // If we have a lump file for this lump, return + // the offset to move past the lump file header. + if ( IsPC() && s_MapLumpFiles[lumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + return s_MapLumpFiles[lumpID].header.lumpOffset; + } + + lump_t *pLump = &s_MapHeader.lumps[ lumpID ]; + Assert( pLump ); + + return pLump->fileofs; +} + +//----------------------------------------------------------------------------- +// Loads one element in a lump. +//----------------------------------------------------------------------------- +void CMapLoadHelper::LoadLumpElement( int nElemIndex, int nElemSize, void *pData ) +{ + if ( !nElemSize || !m_nLumpSize ) + { + return; + } + + // supply from memory + if ( nElemIndex * nElemSize + nElemSize <= m_nLumpSize ) + { + V_memcpy( pData, m_pData + nElemIndex * nElemSize, nElemSize ); + } + else + { + // out of range + Assert( 0 ); + } +} + + +//----------------------------------------------------------------------------- +// Loads one element in a lump. +//----------------------------------------------------------------------------- +void CMapLoadHelper::LoadLumpData( int offset, int size, void *pData ) +{ + if ( !size || !m_nLumpSize ) + { + return; + } + + if ( offset + size <= m_nLumpSize ) + { + V_memcpy( pData, m_pData + offset, size ); + } + else + { + // out of range + Assert( 0 ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mapfile - +// lumpToLoad - +//----------------------------------------------------------------------------- +CMapLoadHelper::CMapLoadHelper( int lumpToLoad ) +{ + if ( lumpToLoad < 0 || lumpToLoad >= HEADER_LUMPS ) + { + Sys_Error( "Can't load lump %i, range is 0 to %i!!!", lumpToLoad, HEADER_LUMPS - 1 ); + } + + m_nLumpID = lumpToLoad; + m_nLumpSize = 0; + m_nLumpOffset = -1; + m_pData = NULL; + m_pRawData = NULL; + m_pUncompressedData = NULL; + + // Load raw lump from disk + lump_t *lump = &s_MapHeader.lumps[ lumpToLoad ]; + Assert( lump ); + + m_nLumpSize = lump->filelen; + m_nLumpOffset = lump->fileofs; + m_nLumpVersion = lump->version; + + FileHandle_t fileToUse = s_MapFileHandle; + + // If we have a lump file for this lump, use it instead + if ( IsPC() && s_MapLumpFiles[lumpToLoad].file != FILESYSTEM_INVALID_HANDLE ) + { + fileToUse = s_MapLumpFiles[lumpToLoad].file; + m_nLumpSize = s_MapLumpFiles[lumpToLoad].header.lumpLength; + m_nLumpOffset = s_MapLumpFiles[lumpToLoad].header.lumpOffset; + m_nLumpVersion = s_MapLumpFiles[lumpToLoad].header.lumpVersion; + + // Store off the lump file name + GenerateLumpFileName( s_szLoadName, m_szLumpFilename, MAX_PATH, s_MapLumpFiles[lumpToLoad].lumpfileindex ); + } + + if ( !m_nLumpSize ) + { + // this lump has no data + return; + } + + if ( s_MapBuffer.Base() ) + { + // bsp is in memory + m_pData = (unsigned char*)s_MapBuffer.Base() + m_nLumpOffset; + } + else + { + if ( s_MapFileHandle == FILESYSTEM_INVALID_HANDLE ) + { + Sys_Error( "Can't load map from invalid handle!!!" ); + } + + unsigned nOffsetAlign, nSizeAlign, nBufferAlign; + g_pFileSystem->GetOptimalIOConstraints( fileToUse, &nOffsetAlign, &nSizeAlign, &nBufferAlign ); + + bool bTryOptimal = ( m_nLumpOffset % 4 == 0 ); // Don't return badly aligned data + unsigned int alignedOffset = m_nLumpOffset; + unsigned int alignedBytesToRead = ( ( m_nLumpSize ) ? m_nLumpSize : 1 ); + + if ( bTryOptimal ) + { + alignedOffset = AlignValue( ( alignedOffset - nOffsetAlign ) + 1, nOffsetAlign ); + alignedBytesToRead = AlignValue( ( m_nLumpOffset - alignedOffset ) + alignedBytesToRead, nSizeAlign ); + } + + m_pRawData = (byte *)g_pFileSystem->AllocOptimalReadBuffer( fileToUse, alignedBytesToRead, alignedOffset ); + if ( !m_pRawData && m_nLumpSize ) + { + Sys_Error( "Can't load lump %i, allocation of %i bytes failed!!!", lumpToLoad, m_nLumpSize + 1 ); + } + + if ( m_nLumpSize ) + { + g_pFileSystem->Seek( fileToUse, alignedOffset, FILESYSTEM_SEEK_HEAD ); + g_pFileSystem->ReadEx( m_pRawData, alignedBytesToRead, alignedBytesToRead, fileToUse ); + m_pData = m_pRawData + ( m_nLumpOffset - alignedOffset ); + } + } + + if ( lump->uncompressedSize != 0 ) + { + // Handle compressed lump -- users of the class see the uncompressed data + AssertMsg( CLZMA::IsCompressed( m_pData ), + "Lump claims to be compressed but is not recognized as LZMA" ); + + m_nLumpSize = CLZMA::GetActualSize( m_pData ); + AssertMsg( lump->uncompressedSize == m_nLumpSize, + "Lump header disagrees with lzma header for compressed lump" ); + + m_pUncompressedData = (unsigned char *)malloc( m_nLumpSize ); + CLZMA::Uncompress( m_pData, m_pUncompressedData ); + + m_pData = m_pUncompressedData; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +CMapLoadHelper::~CMapLoadHelper( void ) +{ + if ( m_pUncompressedData ) + { + free( m_pUncompressedData ); + } + + if ( m_pRawData ) + { + g_pFileSystem->FreeOptimalReadBuffer( m_pRawData ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : model_t +//----------------------------------------------------------------------------- +worldbrushdata_t *CMapLoadHelper::GetMap( void ) +{ + Assert( s_pMap ); + return s_pMap; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +const char *CMapLoadHelper::GetMapName( void ) +{ + return s_szMapName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : const char +//----------------------------------------------------------------------------- +char *CMapLoadHelper::GetLoadName( void ) +{ + // If we have a custom lump file for the lump this helper + // is loading, return it instead. + if ( IsPC() && s_MapLumpFiles[m_nLumpID].file != FILESYSTEM_INVALID_HANDLE ) + { + return m_szLumpFilename; + } + + return s_szLoadName; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : byte +//----------------------------------------------------------------------------- +byte *CMapLoadHelper::LumpBase( void ) +{ + return m_pData; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CMapLoadHelper::LumpSize() +{ + return m_nLumpSize; +} + +int CMapLoadHelper::LumpOffset() +{ + return m_nLumpOffset; +} + +int CMapLoadHelper::LumpVersion() const +{ + return m_nLumpVersion; +} + +void EnableHDR( bool bEnable ) +{ + if ( g_pMaterialSystemHardwareConfig->GetHDREnabled() == bEnable ) + return; + + g_pMaterialSystemHardwareConfig->SetHDREnabled( bEnable ); + + if ( IsX360() ) + { + // cannot do what the pc does and ditch resources, we're loading! + // can safely do the state update only, knowing that the state change won't affect 360 resources + ((MaterialSystem_Config_t *)g_pMaterialSystemConfig)->SetFlag( MATSYS_VIDCFG_FLAGS_ENABLE_HDR, bEnable ); + return; + } + + ShutdownWellKnownRenderTargets(); + InitWellKnownRenderTargets(); + + /// XXX(JohnS): This works around part of the terribleness the comments below discuss by performing + /// UpdateMaterialSystemConfig with the device offline, removing its need to do multiple re-uploads of + /// things. I am not positive my changes to allow that won't introduce terrible regressions or awaken + /// ancient bugs, hence the kill switch. + bool bUpdateOffline = mod_offline_hdr_switch.GetBool(); +#ifndef DEDICATED + extern void V_RenderVGuiOnly(); +#endif + + if ( bUpdateOffline ) + { +#ifndef DEDICATED + V_RenderVGuiOnly(); +#endif + materials->ReleaseResources(); + } + + // Grah. This is terrible. changin mat_hdr_enabled at the commandline + // will by definition break because the release/restore methods don't call + // ShutdownWellKnownRenderTargets/InitWellKnownRenderTargets. + // Also, this forces two alt-tabs, one for InitWellKnownRenderTargets, one + // for UpdateMaterialSystemConfig. + UpdateMaterialSystemConfig(); + + // Worse, since we need to init+shutdown render targets here, we can't + // rely on UpdateMaterialSystemConfig to release + reacquire resources + // because it could be called at any time. We have to precisely control + // when hdr is changed since this is the only time the code can handle it. + if ( !bUpdateOffline ) + { + materials->ReleaseResources(); + } + materials->ReacquireResources(); +#ifndef DEDICATED + if ( bUpdateOffline ) + { + V_RenderVGuiOnly(); + } +#endif +} + +//----------------------------------------------------------------------------- +// Determine feature flags +//----------------------------------------------------------------------------- +void Map_CheckFeatureFlags() +{ + g_bLoadedMapHasBakedPropLighting = false; + g_bBakedPropLightingNoSeparateHDR = false; + + if ( CMapLoadHelper::LumpSize( LUMP_MAP_FLAGS ) > 0 ) + { + CMapLoadHelper lh( LUMP_MAP_FLAGS ); + dflagslump_t flags_lump; + flags_lump = *( (dflagslump_t *)( lh.LumpBase() ) ); + + // check if loaded map has baked static prop lighting + g_bLoadedMapHasBakedPropLighting = + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_NONHDR ) != 0 || + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) != 0; + g_bBakedPropLightingNoSeparateHDR = + ( flags_lump.m_LevelFlags & LVLFLAGS_BAKED_STATIC_PROP_LIGHTING_HDR ) == 0; + } +} + +//----------------------------------------------------------------------------- +// Parse the map header for HDR ability. Returns the presence of HDR data only, +// not the HDR enable state. +//----------------------------------------------------------------------------- +bool Map_CheckForHDR( model_t *pModel, const char *pLoadName ) +{ + // parse the map header only + CMapLoadHelper::Init( pModel, pLoadName ); + + bool bHasHDR = false; + if ( IsX360() ) + { + // If this is true, the 360 MUST use HDR, because the LDR data gets stripped out. + bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0; + } + else + { + // might want to also consider the game lumps GAMELUMP_DETAIL_PROP_LIGHTING_HDR + bHasHDR = CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 && + CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0; + // Mod_GameLumpSize( GAMELUMP_DETAIL_PROP_LIGHTING_HDR ) > 0 // fixme + } + if ( s_MapHeader.version >= 20 && CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) == 0 ) + { + // This lump only exists in version 20 and greater, so don't bother checking for it on earlier versions. + bHasHDR = false; + } + + bool bEnableHDR = ( IsX360() && bHasHDR ) || + ( bHasHDR && ( mat_hdr_level.GetInt() >= 2 ) && + ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() >= 90 ) ); + + EnableHDR( bEnableHDR ); + + // this data really should have been in the header, but it isn't + // establish the features now, before the real bsp load commences + Map_CheckFeatureFlags(); + + CMapLoadHelper::Shutdown(); + + return bHasHDR; +} + +//----------------------------------------------------------------------------- +// Allocates, frees lighting data +//----------------------------------------------------------------------------- +static void AllocateLightingData( worldbrushdata_t *pBrushData, int nSize ) +{ + g_bHunkAllocLightmaps = ( !r_unloadlightmaps.GetBool() && r_hunkalloclightmaps.GetBool() ); + if ( g_bHunkAllocLightmaps ) + { + pBrushData->lightdata = (ColorRGBExp32 *)Hunk_Alloc( nSize, false ); + } + else + { + // Specifically *not* adding it to the hunk. + // If this malloc changes, also change the free in CacheAndUnloadLightmapData() + pBrushData->lightdata = (ColorRGBExp32 *)malloc( nSize ); + } + pBrushData->unloadedlightmaps = false; +} + +static void DeallocateLightingData( worldbrushdata_t *pBrushData ) +{ + if ( pBrushData && pBrushData->lightdata ) + { + if ( !g_bHunkAllocLightmaps ) + { + free( pBrushData->lightdata ); + } + pBrushData->lightdata = NULL; + } +} + +static int ComputeLightmapSize( dface_t *pFace, mtexinfo_t *pTexInfo ) +{ + bool bNeedsBumpmap = false; + if( pTexInfo[pFace->texinfo].flags & SURF_BUMPLIGHT ) + { + bNeedsBumpmap = true; + } + + int lightstyles; + for (lightstyles=0; lightstyles < MAXLIGHTMAPS; lightstyles++ ) + { + if ( pFace->styles[lightstyles] == 255 ) + break; + } + + int nLuxels = (pFace->m_LightmapTextureSizeInLuxels[0]+1) * (pFace->m_LightmapTextureSizeInLuxels[1]+1); + if( bNeedsBumpmap ) + { + return nLuxels * 4 * lightstyles * ( NUM_BUMP_VECTS + 1 ); + } + + return nLuxels * 4 * lightstyles; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLighting( CMapLoadHelper &lh ) +{ + if ( !lh.LumpSize() ) + { + lh.GetMap()->lightdata = NULL; + return; + } + + Assert( lh.LumpSize() % sizeof( ColorRGBExp32 ) == 0 ); + Assert ( lh.LumpVersion() != 0 ); + + AllocateLightingData( lh.GetMap(), lh.LumpSize() ); + memcpy( lh.GetMap()->lightdata, lh.LumpBase(), lh.LumpSize()); + + if ( IsX360() ) + { + // Free the lighting lump, to increase the amount of memory free during the rest of loading + CMapLoadHelper::FreeLightingLump(); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadWorldlights( CMapLoadHelper &lh, bool bIsHDR ) +{ + lh.GetMap()->shadowzbuffers = NULL; + if (!lh.LumpSize()) + { + lh.GetMap()->numworldlights = 0; + lh.GetMap()->worldlights = NULL; + return; + } + lh.GetMap()->numworldlights = lh.LumpSize() / sizeof( dworldlight_t ); + lh.GetMap()->worldlights = (dworldlight_t *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "worldlights" ) ); + memcpy (lh.GetMap()->worldlights, lh.LumpBase(), lh.LumpSize()); +#if !defined( SWDS ) + if ( r_lightcache_zbuffercache.GetInt() ) + { + size_t zbufSize = lh.GetMap()->numworldlights * sizeof( lightzbuffer_t ); + lh.GetMap()->shadowzbuffers = ( lightzbuffer_t *) Hunk_AllocName( zbufSize, va( "%s [%s]", lh.GetLoadName(), "shadowzbuffers" ) ); + memset( lh.GetMap()->shadowzbuffers, 0, zbufSize ); // mark empty + } +#endif + + // Fixup for backward compatability + for ( int i = 0; i < lh.GetMap()->numworldlights; i++ ) + { + if( lh.GetMap()->worldlights[i].type == emit_spotlight) + { + if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && + (lh.GetMap()->worldlights[i].linear_attn == 0.0) && + (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) + { + lh.GetMap()->worldlights[i].quadratic_attn = 1.0; + } + + if (lh.GetMap()->worldlights[i].exponent == 0.0) + lh.GetMap()->worldlights[i].exponent = 1.0; + } + else if( lh.GetMap()->worldlights[i].type == emit_point) + { + // To match earlier lighting, use quadratic... + if ((lh.GetMap()->worldlights[i].constant_attn == 0.0) && + (lh.GetMap()->worldlights[i].linear_attn == 0.0) && + (lh.GetMap()->worldlights[i].quadratic_attn == 0.0)) + { + lh.GetMap()->worldlights[i].quadratic_attn = 1.0; + } + } + + // I replaced the cuttoff_dot field (which took a value from 0 to 1) + // with a max light radius. Radius of less than 1 will never happen, + // so I can get away with this. When I set radius to 0, it'll + // run the old code which computed a radius + if (lh.GetMap()->worldlights[i].radius < 1) + { + lh.GetMap()->worldlights[i].radius = ComputeLightRadius( &lh.GetMap()->worldlights[i], bIsHDR ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadVertices( void ) +{ + dvertex_t *in; + mvertex_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_VERTEXES ); + + in = (dvertex_t *)lh.LumpBase(); + if ( lh.LumpSize() % sizeof(*in) ) + { + Host_Error( "Mod_LoadVertices: funny lump size in %s", lh.GetMapName() ); + } + count = lh.LumpSize() / sizeof(*in); + out = (mvertex_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "vertexes" ) ); + + lh.GetMap()->vertexes = out; + lh.GetMap()->numvertexes = count; + + for ( i=0 ; i<count ; i++, in++, out++) + { + out->position[0] = in->point[0]; + out->position[1] = in->point[1]; + out->position[2] = in->point[2]; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : mins - +// maxs - +// Output : float +//----------------------------------------------------------------------------- +static float RadiusFromBounds (Vector& mins, Vector& maxs) +{ + int i; + Vector corner; + + for (i=0 ; i<3 ; i++) + { + corner[i] = fabs(mins[i]) > fabs(maxs[i]) ? fabs(mins[i]) : fabs(maxs[i]); + } + + return VectorLength( corner ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadSubmodels( CUtlVector<mmodel_t> &submodelList ) +{ + dmodel_t *in; + int i, j, count; + + CMapLoadHelper lh( LUMP_MODELS ); + + in = (dmodel_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error("Mod_LoadSubmodels: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + + submodelList.SetCount( count ); + lh.GetMap()->numsubmodels = count; + + for ( i=0 ; i<count ; i++, in++) + { + for (j=0 ; j<3 ; j++) + { // spread the mins / maxs by a pixel + submodelList[i].mins[j] = in->mins[j] - 1; + submodelList[i].maxs[j] = in->maxs[j] + 1; + submodelList[i].origin[j] = in->origin[j]; + } + submodelList[i].radius = RadiusFromBounds (submodelList[i].mins, submodelList[i].maxs); + submodelList[i].headnode = in->headnode; + submodelList[i].firstface = in->firstface; + submodelList[i].numfaces = in->numfaces; + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : medge_t *Mod_LoadEdges +//----------------------------------------------------------------------------- +medge_t *Mod_LoadEdges ( void ) +{ + dedge_t *in; + medge_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_EDGES ); + + in = (dedge_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadEdges: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + medge_t *pedges = new medge_t[count]; + + out = pedges; + + for ( i=0 ; i<count ; i++, in++, out++) + { + out->v[0] = in->v[0]; + out->v[1] = in->v[1]; + } + + // delete this in the loader + return pedges; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadOcclusion( void ) +{ + CMapLoadHelper lh( LUMP_OCCLUSION ); + + worldbrushdata_t *b = lh.GetMap(); + b->numoccluders = 0; + b->occluders = NULL; + b->numoccluderpolys = 0; + b->occluderpolys = NULL; + b->numoccludervertindices = 0; + b->occludervertindices = NULL; + + if ( !lh.LumpSize() ) + { + return; + } + + CUtlBuffer buf( lh.LumpBase(), lh.LumpSize(), CUtlBuffer::READ_ONLY ); + + switch( lh.LumpVersion() ) + { + case LUMP_OCCLUSION_VERSION: + { + b->numoccluders = buf.GetInt(); + if (b->numoccluders) + { + int nSize = b->numoccluders * sizeof(doccluderdata_t); + b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); + buf.Get( b->occluders, nSize ); + } + + b->numoccluderpolys = buf.GetInt(); + if (b->numoccluderpolys) + { + int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); + b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); + buf.Get( b->occluderpolys, nSize ); + } + + b->numoccludervertindices = buf.GetInt(); + if (b->numoccludervertindices) + { + int nSize = b->numoccludervertindices * sizeof(int); + b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); + buf.Get( b->occludervertindices, nSize ); + } + } + break; + + case 1: + { + b->numoccluders = buf.GetInt(); + if (b->numoccluders) + { + int nSize = b->numoccluders * sizeof(doccluderdata_t); + b->occluders = (doccluderdata_t*)Hunk_AllocName( nSize, "occluder data" ); + + doccluderdataV1_t temp; + for ( int i = 0; i < b->numoccluders; ++i ) + { + buf.Get( &temp, sizeof(doccluderdataV1_t) ); + memcpy( &b->occluders[i], &temp, sizeof(doccluderdataV1_t) ); + b->occluders[i].area = 1; + } + } + + b->numoccluderpolys = buf.GetInt(); + if (b->numoccluderpolys) + { + int nSize = b->numoccluderpolys * sizeof(doccluderpolydata_t); + b->occluderpolys = (doccluderpolydata_t*)Hunk_AllocName( nSize, "occluder poly data" ); + buf.Get( b->occluderpolys, nSize ); + } + + b->numoccludervertindices = buf.GetInt(); + if (b->numoccludervertindices) + { + int nSize = b->numoccludervertindices * sizeof(int); + b->occludervertindices = (int*)Hunk_AllocName( nSize, "occluder vertices" ); + buf.Get( b->occludervertindices, nSize ); + } + } + break; + + case 0: + break; + + default: + Host_Error("Invalid occlusion lump version!\n"); + break; + } +} + + + +// UNDONE: Really, it's stored 2 times because the texture system keeps a +// copy of the name too. I guess we'll get rid of this when we have a material +// system that works without a graphics context. At that point, everyone can +// reference the name in the material, or just the material itself. +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadTexdata( void ) +{ + // Don't bother loading these again; they're already stored in the collision model + // which is guaranteed to be loaded at this point + s_pMap->numtexdata = GetCollisionBSPData()->numtextures; + s_pMap->texdata = GetCollisionBSPData()->map_surfaces.Base(); +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadTexinfo( void ) +{ + texinfo_t *in; + mtexinfo_t *out; + int i, j, count; + // UNDONE: Fix this + + CMapLoadHelper lh( LUMP_TEXINFO ); + + in = (texinfo_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadTexinfo: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mtexinfo_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "texinfo" ) ); + + s_pMap->texinfo = out; + s_pMap->numtexinfo = count; + + bool loadtextures = mat_loadtextures.GetBool(); + + for ( i=0 ; i<count ; ++i, ++in, ++out ) + { + for (j=0; j<2; ++j) + { + for (int k=0 ; k<4 ; ++k) + { + out->textureVecsTexelsPerWorldUnits[j][k] = in->textureVecsTexelsPerWorldUnits[j][k]; + out->lightmapVecsLuxelsPerWorldUnits[j][k] = in->lightmapVecsLuxelsPerWorldUnits[j][k] ; + } + } + + // assume that the scale is the same on both s and t. + out->luxelsPerWorldUnit = VectorLength( out->lightmapVecsLuxelsPerWorldUnits[0].AsVector3D() ); + out->worldUnitsPerLuxel = 1.0f / out->luxelsPerWorldUnit; + + out->flags = in->flags; + out->texinfoFlags = 0; + + if ( loadtextures ) + { + if ( in->texdata >= 0 ) + { + out->material = GL_LoadMaterial( lh.GetMap()->texdata[ in->texdata ].name, TEXTURE_GROUP_WORLD ); + } + else + { + DevMsg( "Mod_LoadTexinfo: texdata < 0 (index==%i/%i)\n", i, count ); + out->material = NULL; + } + if ( !out->material ) + { + out->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + } + else + { + out->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + } +} + +// code to scan the lightmaps for empty lightstyles +static void LinearToGamma( unsigned char *pDstRGB, const float *pSrcRGB ) +{ + pDstRGB[0] = LinearToScreenGamma( pSrcRGB[0] ); + pDstRGB[1] = LinearToScreenGamma( pSrcRGB[1] ); + pDstRGB[2] = LinearToScreenGamma( pSrcRGB[2] ); +} + +static void CheckSurfaceLighting( SurfaceHandle_t surfID, worldbrushdata_t *pBrushData ) +{ +#if !defined( SWDS ) + host_state.worldbrush = pBrushData; + msurfacelighting_t *pLighting = SurfaceLighting( surfID, pBrushData ); + + if( !pLighting->m_pSamples ) + return; + + int smax = ( pLighting->m_LightmapExtents[0] ) + 1; + int tmax = ( pLighting->m_LightmapExtents[1] ) + 1; + int offset = smax * tmax; + if ( SurfHasBumpedLightmaps( surfID ) ) + { + offset *= ( NUM_BUMP_VECTS + 1 ); + } + + + // how many lightmaps does this surface have? + int maxLightmapIndex = 0; + for (int maps = 1 ; maps < MAXLIGHTMAPS && pLighting->m_nStyles[maps] != 255 ; ++maps) + { + maxLightmapIndex = maps; + } + + if ( maxLightmapIndex < 1 ) + return; + + // iterate and test each lightmap + for ( int maps = maxLightmapIndex; maps != 0; maps-- ) + { + ColorRGBExp32 *pLightmap = pLighting->m_pSamples + (maps * offset); + float maxLen = -1; + Vector maxLight; + maxLight.Init(); + for ( int i = 0; i < offset; i++ ) + { + Vector c; + ColorRGBExp32ToVector( pLightmap[i], c ); + if ( c.Length() > maxLen ) + { + maxLight = c; + maxLen = c.Length(); + } + } + unsigned char color[4]; + LinearToGamma( color, maxLight.Base() ); + const int minLightVal = 1; + if ( color[0] <= minLightVal && color[1] <= minLightVal && color[2] <= minLightVal ) + { + // found a lightmap that is too dark, remove it and shift over the subsequent maps/styles + for ( int i = maps; i < maxLightmapIndex; i++ ) + { + ColorRGBExp32 *pLightmapOverwrite = pLighting->m_pSamples + (i * offset); + memcpy( pLightmapOverwrite, pLightmapOverwrite+offset, offset * sizeof(*pLightmapOverwrite) ); + pLighting->m_nStyles[i] = pLighting->m_nStyles[i+1]; + } + // mark end lightstyle as removed, decrement max index + pLighting->m_nStyles[maxLightmapIndex] = 255; + maxLightmapIndex--; + } + } + // we removed all of the lightstyle maps so clear the flag + if ( maxLightmapIndex == 0 ) + { + surfID->flags &= ~SURFDRAW_HASLIGHTSYTLES; + } +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *s - +// Output : void CalcSurfaceExtents +//----------------------------------------------------------------------------- +static void CalcSurfaceExtents ( CMapLoadHelper& lh, SurfaceHandle_t surfID ) +{ + float textureMins[2], textureMaxs[2], val; + int i,j, e; + mvertex_t *v; + mtexinfo_t *tex; + int bmins[2], bmaxs[2]; + + textureMins[0] = textureMins[1] = 999999; + textureMaxs[0] = textureMaxs[1] = -99999; + + worldbrushdata_t *pBrushData = lh.GetMap(); + tex = MSurf_TexInfo( surfID, pBrushData ); + + for (i=0 ; i<MSurf_VertCount( surfID ); i++) + { + e = pBrushData->vertindices[MSurf_FirstVertIndex( surfID )+i]; + v = &pBrushData->vertexes[e]; + + for (j=0 ; j<2 ; j++) + { + val = v->position[0] * tex->textureVecsTexelsPerWorldUnits[j][0] + + v->position[1] * tex->textureVecsTexelsPerWorldUnits[j][1] + + v->position[2] * tex->textureVecsTexelsPerWorldUnits[j][2] + + tex->textureVecsTexelsPerWorldUnits[j][3]; + if (val < textureMins[j]) + textureMins[j] = val; + if (val > textureMaxs[j]) + textureMaxs[j] = val; + } + } + + for (i=0 ; i<2 ; i++) + { + if( MSurf_LightmapExtents( surfID, pBrushData )[i] == 0 && !MSurf_Samples( surfID, pBrushData ) ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; + } + + bmins[i] = Float2Int( textureMins[i] ); + bmaxs[i] = Ceil2Int( textureMaxs[i] ); + MSurf_TextureMins( surfID, pBrushData )[i] = bmins[i]; + MSurf_TextureExtents( surfID, pBrushData )[i] = ( bmaxs[i] - bmins[i] ); + + if ( !(tex->flags & SURF_NOLIGHT) && MSurf_LightmapExtents( surfID, pBrushData )[i] > MSurf_MaxLightmapSizeWithBorder( surfID ) ) + { + Sys_Error ("Bad surface extents on texture %s", tex->material->GetName() ); + } + } + CheckSurfaceLighting( surfID, pBrushData ); +} + +//----------------------------------------------------------------------------- +// Input : *pModel - +// *pLump - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadVertNormals( void ) +{ + CMapLoadHelper lh( LUMP_VERTNORMALS ); + + // get a pointer to the vertex normal data. + Vector *pVertNormals = ( Vector * )lh.LumpBase(); + + // + // verify vertnormals data size + // + if( lh.LumpSize() % sizeof( *pVertNormals ) ) + Host_Error( "Mod_LoadVertNormals: funny lump size in %s!\n", lh.GetMapName() ); + + int count = lh.LumpSize() / sizeof(*pVertNormals); + Vector *out = (Vector *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormals" ) ); + memcpy( out, pVertNormals, lh.LumpSize() ); + + lh.GetMap()->vertnormals = out; + lh.GetMap()->numvertnormals = count; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadVertNormalIndices( void ) +{ + CMapLoadHelper lh( LUMP_VERTNORMALINDICES ); + + // get a pointer to the vertex normal data. + unsigned short *pIndices = ( unsigned short * )lh.LumpBase(); + + int count = lh.LumpSize() / sizeof(*pIndices); + unsigned short *out = (unsigned short *)Hunk_AllocName( lh.LumpSize(), va( "%s [%s]", lh.GetLoadName(), "vertnormalindices" ) ); + memcpy( out, pIndices, lh.LumpSize() ); + + lh.GetMap()->vertnormalindices = out; + lh.GetMap()->numvertnormalindices = count; + + // OPTIMIZE: Water surfaces don't need vertex normals? + int normalIndex = 0; + for( int i = 0; i < lh.GetMap()->numsurfaces; i++ ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, lh.GetMap() ); + MSurf_FirstVertNormal( surfID, lh.GetMap() ) = normalIndex; + normalIndex += MSurf_VertCount( surfID ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimitives( void ) +{ + dprimitive_t *in; + mprimitive_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_PRIMITIVES ); + + in = (dprimitive_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimitives: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mprimitive_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primitives" ) ); + memset( out, 0, count * sizeof( mprimitive_t ) ); + + lh.GetMap()->primitives = out; + lh.GetMap()->numprimitives = count; + for ( i=0 ; i<count ; i++, in++, out++) + { + out->firstIndex = in->firstIndex; + out->firstVert = in->firstVert; + out->indexCount = in->indexCount; + out->type = in->type; + out->vertCount = in->vertCount; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimVerts( void ) +{ + dprimvert_t *in; + mprimvert_t *out; + int i, count; + + CMapLoadHelper lh( LUMP_PRIMVERTS ); + + in = (dprimvert_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimVerts: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mprimvert_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "primverts" ) ); + memset( out, 0, count * sizeof( mprimvert_t ) ); + + lh.GetMap()->primverts = out; + lh.GetMap()->numprimverts = count; + for ( i=0 ; i<count ; i++, in++, out++) + { + out->pos = in->pos; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPrimIndices( void ) +{ + unsigned short *in; + unsigned short *out; + int count; + + CMapLoadHelper lh( LUMP_PRIMINDICES ); + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadPrimIndices: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va("%s [%s]", lh.GetLoadName(), "primindices" ) ); + memset( out, 0, count * sizeof( unsigned short ) ); + + lh.GetMap()->primindices = out; + lh.GetMap()->numprimindices = count; + + memcpy( out, in, count * sizeof( unsigned short ) ); +} + + +// This allocates memory for a lump and copies the lump data in. +void Mod_LoadLump( + model_t *loadmodel, + int iLump, + char *loadname, + int elementSize, + void **ppData, + int *nElements ) +{ + CMapLoadHelper lh( iLump ); + + if ( lh.LumpSize() % elementSize ) + { + Host_Error( "Mod_LoadLump: funny lump size in %s", loadmodel->strName.String() ); + } + + // How many elements? + *nElements = lh.LumpSize() / elementSize; + + // Make room for the data and copy the data in. + *ppData = Hunk_AllocName( lh.LumpSize(), loadname ); + memcpy( *ppData, lh.LumpBase(), lh.LumpSize() ); +} + + +//----------------------------------------------------------------------------- +// Sets up the msurfacelighting_t structure +//----------------------------------------------------------------------------- +bool Mod_LoadSurfaceLightingV1( msurfacelighting_t *pLighting, dface_t *in, ColorRGBExp32 *pBaseLightData ) +{ + // Get lightmap extents from the file. + pLighting->m_LightmapExtents[0] = in->m_LightmapTextureSizeInLuxels[0]; + pLighting->m_LightmapExtents[1] = in->m_LightmapTextureSizeInLuxels[1]; + pLighting->m_LightmapMins[0] = in->m_LightmapTextureMinsInLuxels[0]; + pLighting->m_LightmapMins[1] = in->m_LightmapTextureMinsInLuxels[1]; + + int i = in->lightofs; + if ( (i == -1) || (!pBaseLightData) ) + { + pLighting->m_pSamples = NULL; + + // Can't have *any* lightstyles if we have no samples.... + for ( i=0; i<MAXLIGHTMAPS; ++i) + { + pLighting->m_nStyles[i] = 255; + } + } + else + { + pLighting->m_pSamples = (ColorRGBExp32 *)( ((byte *)pBaseLightData) + i ); + + for (i=0 ; i<MAXLIGHTMAPS; ++i) + { + pLighting->m_nStyles[i] = in->styles[i]; + } + } + + return ((pLighting->m_nStyles[0] != 0) && (pLighting->m_nStyles[0] != 255)) || (pLighting->m_nStyles[1] != 255); +} + +void *Hunk_AllocNameAlignedClear_( int size, int alignment, const char *pHunkName ) +{ + Assert(IsPowerOfTwo(alignment)); + void *pMem = Hunk_AllocName( alignment + size, pHunkName ); + memset( pMem, 0, size + alignment ); + pMem = (void *)( ( ( ( unsigned long )pMem ) + (alignment-1) ) & ~(alignment-1) ); + + return pMem; +} + +// Allocates a block of T from the hunk. Aligns as specified and clears the memory +template< typename T > +T *Hunk_AllocNameAlignedClear( int count, int alignment, const char *pHunkName ) +{ + return (T *)Hunk_AllocNameAlignedClear_( alignment + count * sizeof(T), alignment, pHunkName ); +} +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadFaces( void ) +{ + dface_t *in; + int count, surfnum; + int planenum; + int ti, di; + + int face_lump_to_load = LUMP_FACES; + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_FACES_HDR ) > 0 ) + { + face_lump_to_load = LUMP_FACES_HDR; + } + CMapLoadHelper lh( face_lump_to_load ); + + in = (dface_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadFaces: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + + // align these allocations + // If you trip one of these, you need to rethink the alignment of the struct + Assert( sizeof(msurface1_t) == 16 ); + Assert( sizeof(msurface2_t) == 32 ); + Assert( sizeof(msurfacelighting_t) == 32 ); + + msurface1_t *out1 = Hunk_AllocNameAlignedClear< msurface1_t >( count, 16, va( "%s [%s]", lh.GetLoadName(), "surface1" ) ); + msurface2_t *out2 = Hunk_AllocNameAlignedClear< msurface2_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surface2" ) ); + + msurfacelighting_t *pLighting = Hunk_AllocNameAlignedClear< msurfacelighting_t >( count, 32, va( "%s [%s]", lh.GetLoadName(), "surfacelighting" ) ); + + lh.GetMap()->surfaces1 = out1; + lh.GetMap()->surfaces2 = out2; + lh.GetMap()->surfacelighting = pLighting; + lh.GetMap()->surfacenormals = Hunk_AllocNameAlignedClear< msurfacenormal_t >( count, 2, va( "%s [%s]", lh.GetLoadName(), "surfacenormal" ) ); + lh.GetMap()->numsurfaces = count; + + worldbrushdata_t *pBrushData = lh.GetMap(); + + for ( surfnum=0 ; surfnum<count ; ++surfnum, ++in, ++out1, ++out2, ++pLighting ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( surfnum, pBrushData ); + MSurf_FirstVertIndex( surfID ) = in->firstedge; + + int vertCount = in->numedges; + MSurf_Flags( surfID ) = 0; + Assert( vertCount <= 255 ); + MSurf_SetVertCount( surfID, vertCount ); + + planenum = in->planenum; + + if ( in->onNode ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NODE; + } + if ( in->side ) + { + MSurf_Flags( surfID ) |= SURFDRAW_PLANEBACK; + } + + out2->plane = lh.GetMap()->planes + planenum; + + ti = in->texinfo; + if (ti < 0 || ti >= lh.GetMap()->numtexinfo) + { + Host_Error( "Mod_LoadFaces: bad texinfo number" ); + } + surfID->texinfo = ti; + surfID->m_bDynamicShadowsEnabled = in->AreDynamicShadowsEnabled(); + mtexinfo_t *pTex = lh.GetMap()->texinfo + ti; + + // big hack! + if ( !pTex->material ) + { + pTex->material = g_materialEmpty; + g_materialEmpty->IncrementReferenceCount(); + } + + // lighting info + if ( Mod_LoadSurfaceLightingV1( pLighting, in, lh.GetMap()->lightdata ) ) + { + MSurf_Flags( surfID ) |= SURFDRAW_HASLIGHTSYTLES; + } + + // set the drawing flags flag + if ( pTex->flags & SURF_NOLIGHT ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOLIGHT; + } + + if ( pTex->flags & SURF_NOSHADOWS ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOSHADOWS; + } + + if ( pTex->flags & SURF_WARP ) + { + MSurf_Flags( surfID ) |= SURFDRAW_WATERSURFACE; + } + + if ( pTex->flags & SURF_SKY ) + { + MSurf_Flags( surfID ) |= SURFDRAW_SKY; + } + + di = in->dispinfo; + out2->pDispInfo = NULL; + if( di != -1 ) + { +// out->origSurfaceID = in->origFace; + MSurf_Flags( surfID ) |= SURFDRAW_HAS_DISP; + } + else + { + // non-displacement faces shouldn't come out of VBSP if they have nodraw. + Assert( !(pTex->flags & SURF_NODRAW) ); + + out1->prims.numPrims = in->GetNumPrims(); + out1->prims.firstPrimID = in->firstPrimID; + if ( in->GetNumPrims() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_HAS_PRIMS; + mprimitive_t *pPrim = &pBrushData->primitives[in->firstPrimID]; + if ( pPrim->vertCount > 0 ) + { + MSurf_Flags( surfID ) |= SURFDRAW_DYNAMIC; + } + } + } + + // No shadows on the surface to start with + out2->m_ShadowDecals = SHADOW_DECAL_HANDLE_INVALID; + out2->decals = WORLD_DECAL_HANDLE_INVALID; + + // No overlays on the surface to start with + out2->m_nFirstOverlayFragment = OVERLAY_FRAGMENT_INVALID; + + CalcSurfaceExtents( lh, surfID ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *node - +// *parent - +// Output : void Mod_SetParent +//----------------------------------------------------------------------------- +void Mod_SetParent (mnode_t *node, mnode_t *parent) +{ + node->parent = parent; + if (node->contents >= 0) + return; + Mod_SetParent (node->children[0], node); + Mod_SetParent (node->children[1], node); +} + + +//----------------------------------------------------------------------------- +// Mark an entire subtree as being too small to bother with +//----------------------------------------------------------------------------- +static void MarkSmallNode( mnode_t *node ) +{ + if (node->contents >= 0) + return; + node->contents = -2; + MarkSmallNode (node->children[0]); + MarkSmallNode (node->children[1]); +} + +static void CheckSmallVolumeDifferences( mnode_t *pNode, const Vector &parentSize ) +{ + if (pNode->contents >= 0) + return; + + Vector delta; + VectorSubtract( parentSize, pNode->m_vecHalfDiagonal, delta ); + + if ((delta.x < 5) && (delta.y < 5) && (delta.z < 5)) + { + pNode->contents = -3; + CheckSmallVolumeDifferences( pNode->children[0], parentSize ); + CheckSmallVolumeDifferences( pNode->children[1], parentSize ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadNodes( void ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + int i, j, count, p; + dnode_t *in; + mnode_t *out; + + CMapLoadHelper lh( LUMP_NODES ); + + in = (dnode_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadNodes: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mnode_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "nodes" ) ); + + lh.GetMap()->nodes = out; + lh.GetMap()->numnodes = count; + + for ( i=0 ; i<count ; i++, in++, out++) + { + for (j=0 ; j<3 ; j++) + { + mins[j] = in->mins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + p = in->planenum; + out->plane = lh.GetMap()->planes + p; + + out->firstsurface = in->firstface; + out->numsurfaces = in->numfaces; + out->area = in->area; + out->contents = -1; // differentiate from leafs + + for (j=0 ; j<2 ; j++) + { + p = in->children[j]; + if (p >= 0) + out->children[j] = lh.GetMap()->nodes + p; + else + out->children[j] = (mnode_t *)(lh.GetMap()->leafs + (-1 - p)); + } + } + + Mod_SetParent (lh.GetMap()->nodes, NULL); // sets nodes and leafs + + // Check for small-area parents... no culling below them... + mnode_t *pNode = lh.GetMap()->nodes; + for ( i=0 ; i<count ; ++i, ++pNode) + { + if (pNode->contents == -1) + { + if ((pNode->m_vecHalfDiagonal.x <= 50) && (pNode->m_vecHalfDiagonal.y <= 50) && + (pNode->m_vecHalfDiagonal.z <= 50)) + { + // Mark all children as being too small to bother with... + MarkSmallNode( pNode->children[0] ); + MarkSmallNode( pNode->children[1] ); + } + else + { + CheckSmallVolumeDifferences( pNode->children[0], pNode->m_vecHalfDiagonal ); + CheckSmallVolumeDifferences( pNode->children[1], pNode->m_vecHalfDiagonal ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadLeafs_Version_0( CMapLoadHelper &lh ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + dleaf_version_0_t *in; + mleaf_t *out; + int i, j, count, p; + + in = (dleaf_version_0_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); + + lh.GetMap()->leafs = out; + lh.GetMap()->numleafs = count; + + // one sample per leaf + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); + mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; + mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; + + for ( i=0 ; i<count ; i++, in++, out++) + { + for (j=0 ; j<3 ; j++) + { + mins[j] = in->mins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + pTable[i].ambientSampleCount = 1; + pTable[i].firstAmbientSample = i; + pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; + pSamples[i].pad = 0; + Q_memcpy( &pSamples[i].cube, &in->m_AmbientLighting, sizeof(pSamples[i].cube) ); + + + p = in->contents; + out->contents = p; + + out->cluster = in->cluster; + out->area = in->area; + out->flags = in->flags; +/* + out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; +*/ + out->firstmarksurface = in->firstleafface; + out->nummarksurfaces = in->numleaffaces; + out->parent = NULL; + + out->dispCount = 0; + + out->leafWaterDataID = in->leafWaterDataID; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadLeafs_Version_1( CMapLoadHelper &lh, CMapLoadHelper &ambientLightingLump, CMapLoadHelper &ambientLightingTable ) +{ + Vector mins( 0, 0, 0 ), maxs( 0, 0, 0 ); + dleaf_t *in; + mleaf_t *out; + int i, j, count, p; + + in = (dleaf_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleaf_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafs" ) ); + + lh.GetMap()->leafs = out; + lh.GetMap()->numleafs = count; + + if ( ambientLightingLump.LumpVersion() != LUMP_LEAF_AMBIENT_LIGHTING_VERSION || ambientLightingTable.LumpSize() == 0 ) + { + // convert from previous version + CompressedLightCube *inLightCubes = NULL; + if ( ambientLightingLump.LumpSize() ) + { + inLightCubes = ( CompressedLightCube * )ambientLightingLump.LumpBase(); + Assert( ambientLightingLump.LumpSize() % sizeof( CompressedLightCube ) == 0 ); + Assert( ambientLightingLump.LumpSize() / sizeof( CompressedLightCube ) == lh.LumpSize() / sizeof( dleaf_t ) ); + } + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pLeafAmbient), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( count * sizeof(*lh.GetMap()->m_pAmbientSamples), "LeafAmbientSamples" ); + mleafambientindex_t *pTable = lh.GetMap()->m_pLeafAmbient; + mleafambientlighting_t *pSamples = lh.GetMap()->m_pAmbientSamples; + Vector gray(0.5, 0.5, 0.5); + ColorRGBExp32 grayColor; + VectorToColorRGBExp32( gray, grayColor ); + for ( i = 0; i < count; i++ ) + { + pTable[i].ambientSampleCount = 1; + pTable[i].firstAmbientSample = i; + pSamples[i].x = pSamples[i].y = pSamples[i].z = 128; + pSamples[i].pad = 0; + if ( inLightCubes ) + { + Q_memcpy( &pSamples[i].cube, &inLightCubes[i], sizeof(pSamples[i].cube) ); + } + else + { + for ( j = 0; j < 6; j++ ) + { + pSamples[i].cube.m_Color[j] = grayColor; + } + } + } + } + else + { + Assert( ambientLightingLump.LumpSize() % sizeof( dleafambientlighting_t ) == 0 ); + Assert( ambientLightingTable.LumpSize() % sizeof( dleafambientindex_t ) == 0 ); + Assert((ambientLightingTable.LumpSize() / sizeof(dleafambientindex_t)) == (unsigned)count); // should have one of these per leaf + lh.GetMap()->m_pLeafAmbient = (mleafambientindex_t *)Hunk_AllocName( ambientLightingTable.LumpSize(), "LeafAmbient" ); + lh.GetMap()->m_pAmbientSamples = (mleafambientlighting_t *)Hunk_AllocName( ambientLightingLump.LumpSize(), "LeafAmbientSamples" ); + Q_memcpy( lh.GetMap()->m_pLeafAmbient, ambientLightingTable.LumpBase(), ambientLightingTable.LumpSize() ); + Q_memcpy( lh.GetMap()->m_pAmbientSamples, ambientLightingLump.LumpBase(), ambientLightingLump.LumpSize() ); + } + + + for ( i=0 ; i<count ; i++, in++, out++ ) + { + for (j=0 ; j<3 ; j++) + { + mins[j] = in->mins[j]; + maxs[j] = in->maxs[j]; + } + + VectorAdd( mins, maxs, out->m_vecCenter ); + out->m_vecCenter *= 0.5f; + VectorSubtract( maxs, out->m_vecCenter, out->m_vecHalfDiagonal ); + + p = in->contents; + out->contents = p; + + out->cluster = in->cluster; + out->area = in->area; + out->flags = in->flags; +/* + out->firstmarksurface = lh.GetMap()->marksurfaces + in->firstleafface; +*/ + out->firstmarksurface = in->firstleafface; + out->nummarksurfaces = in->numleaffaces; + out->parent = NULL; + + out->dispCount = 0; + + out->leafWaterDataID = in->leafWaterDataID; + } +} + +void Mod_LoadLeafs( void ) +{ + CMapLoadHelper lh( LUMP_LEAFS ); + + switch( lh.LumpVersion() ) + { + case 0: + Mod_LoadLeafs_Version_0( lh ); + break; + case 1: + if( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_LEAF_AMBIENT_LIGHTING_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING_HDR ); + CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX_HDR ); + Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); + } + else + { + CMapLoadHelper mlh( LUMP_LEAF_AMBIENT_LIGHTING ); + CMapLoadHelper mlhTable( LUMP_LEAF_AMBIENT_INDEX ); + Mod_LoadLeafs_Version_1( lh, mlh, mlhTable ); + } + break; + default: + Assert( 0 ); + Error( "Unknown LUMP_LEAFS version\n" ); + break; + } + + worldbrushdata_t *pMap = lh.GetMap(); + cleaf_t *pCLeaf = GetCollisionBSPData()->map_leafs.Base(); + for ( int i = 0; i < pMap->numleafs; i++ ) + { + pMap->leafs[i].dispCount = pCLeaf[i].dispCount; + pMap->leafs[i].dispListStart = pCLeaf[i].dispListStart; + } + // HACKHACK: Copy over the shared global list here. Hunk_Alloc a copy? + pMap->m_pDispInfoReferences = GetCollisionBSPData()->map_dispList.Base(); + pMap->m_nDispInfoReferences = GetCollisionBSPData()->numdisplist; +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLeafWaterData( void ) +{ + dleafwaterdata_t *in; + mleafwaterdata_t *out; + int count, i; + + CMapLoadHelper lh( LUMP_LEAFWATERDATA ); + + in = (dleafwaterdata_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafs: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mleafwaterdata_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafwaterdata" ) ); + + lh.GetMap()->leafwaterdata = out; + lh.GetMap()->numleafwaterdata = count; + for ( i=0 ; i<count ; i++, in++, out++) + { + out->minZ = in->minZ; + out->surfaceTexInfoID = in->surfaceTexInfoID; + out->surfaceZ = in->surfaceZ; + out->firstLeafIndex = -1; + } + if ( count == 1 ) + { + worldbrushdata_t *brush = lh.GetMap(); + for ( i = 0; i < brush->numleafs; i++ ) + { + if ( brush->leafs[i].leafWaterDataID >= 0 ) + { + brush->leafwaterdata[0].firstLeafIndex = i; + break; + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadCubemapSamples( void ) +{ + char textureName[512]; + char loadName[ MAX_PATH ]; + dcubemapsample_t *in; + mcubemapsample_t *out; + int count, i; + + CMapLoadHelper lh( LUMP_CUBEMAPS ); + + V_strcpy_safe( loadName, lh.GetLoadName() ); + + in = (dcubemapsample_t *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadCubemapSamples: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (mcubemapsample_t *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "cubemapsample" ) ); + + lh.GetMap()->m_pCubemapSamples = out; + lh.GetMap()->m_nCubemapSamples = count; + + bool bHDR = g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE; + int nCreateFlags = bHDR ? 0 : TEXTUREFLAGS_SRGB; + + // We have separate HDR versions of the textures. In order to deal with this, + // we have blahenvmap.hdr.vtf and blahenvmap.vtf. + char *pHDRExtension = ""; + if( bHDR ) + { + pHDRExtension = ".hdr"; + } + + for ( i=0 ; i<count ; i++, in++, out++) + { + out->origin.Init( ( float )in->origin[0], ( float )in->origin[1], ( float )in->origin[2] ); + out->size = in->size; + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d%s", loadName, ( int )in->origin[0], + ( int )in->origin[1], ( int )in->origin[2], pHDRExtension ); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( out->pTexture ) ) + { + if ( bHDR ) + { + Warning( "Couldn't get HDR '%s' -- ", textureName ); + // try non hdr version + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/c%d_%d_%d", loadName, ( int )in->origin[0], + ( int )in->origin[1], ( int )in->origin[2]); + Warning( "Trying non HDR '%s'\n", textureName); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true ); + } + if ( IsErrorTexture( out->pTexture ) ) + { + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); + out->pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( out->pTexture ) ) + { + out->pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + } + Warning( "Failed, using default cubemap '%s'\n", out->pTexture->GetName() ); + } + } + out->pTexture->IncrementReferenceCount(); + } + + CMatRenderContextPtr pRenderContext( materials ); + + if ( count ) + { + pRenderContext->BindLocalCubemap( lh.GetMap()->m_pCubemapSamples[0].pTexture ); + } + else + { + if ( CommandLine()->CheckParm( "-requirecubemaps" ) ) + { + Sys_Error( "Map \"%s\" does not have cubemaps!", lh.GetMapName() ); + } + + ITexture *pTexture; + Q_snprintf( textureName, sizeof( textureName ), "maps/%s/cubemapdefault", loadName ); + pTexture = materials->FindTexture( textureName, TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + if ( IsErrorTexture( pTexture ) ) + { + pTexture = materials->FindTexture( "engine/defaultcubemap", TEXTURE_GROUP_CUBE_MAP, true, nCreateFlags ); + } + pTexture->IncrementReferenceCount(); + pRenderContext->BindLocalCubemap( pTexture ); + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadLeafMinDistToWater( void ) +{ + CMapLoadHelper lh( LUMP_LEAFMINDISTTOWATER ); + + unsigned short *pTmp = ( unsigned short * )lh.LumpBase(); + + int i; + bool foundOne = false; + for( i = 0; i < ( int )( lh.LumpSize() / sizeof( *pTmp ) ); i++ ) + { + if( pTmp[i] != 65535 ) // FIXME: make a marcro for this. + { + foundOne = true; + break; + } + } + + if( !foundOne || lh.LumpSize() == 0 || !g_pMaterialSystemHardwareConfig || !g_pMaterialSystemHardwareConfig->SupportsVertexAndPixelShaders()) + { + // We don't bother keeping this if: + // 1) there is no water in the map + // 2) we don't have this lump in the bsp file (old bsp file) + // 3) we aren't going to use it because we are on old hardware. + lh.GetMap()->m_LeafMinDistToWater = NULL; + } + else + { + int count; + unsigned short *in; + unsigned short *out; + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadLeafMinDistToWater: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "leafmindisttowater" ) ); + + memcpy( out, in, sizeof( out[0] ) * count ); + lh.GetMap()->m_LeafMinDistToWater = out; + } +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void Mod_LoadMarksurfaces( void ) +{ + int i, j, count; + unsigned short *in; + + CMapLoadHelper lh( LUMP_LEAFFACES ); + + in = (unsigned short *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadMarksurfaces: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + SurfaceHandle_t *tempDiskData = new SurfaceHandle_t[count]; + + worldbrushdata_t *pBrushData = lh.GetMap(); + pBrushData->marksurfaces = tempDiskData; + pBrushData->nummarksurfaces = count; + + // read in the mark surfaces, count out how many we'll actually need to store + int realCount = 0; + for ( i=0 ; i<count ; i++) + { + j = in[i]; + if (j >= lh.GetMap()->numsurfaces) + Host_Error ("Mod_LoadMarksurfaces: bad surface number"); + SurfaceHandle_t surfID = SurfaceHandleFromIndex( j, pBrushData ); + tempDiskData[i] = surfID; + if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) + { + realCount++; + } + } + + // now allocate the permanent list, and copy the non-terrain, non-nodraw surfs into it + SurfaceHandle_t *surfList = (SurfaceHandle_t *)Hunk_AllocName( realCount*sizeof(SurfaceHandle_t), va( "%s [%s]", lh.GetLoadName(), "surfacehandle" ) ); + + int outCount = 0; + mleaf_t *pLeaf = pBrushData->leafs; + for ( i = 0; i < pBrushData->numleafs; i++ ) + { + int firstMark = outCount; + int numMark = 0; + bool foundDetail = false; + int numMarkNode = 0; + for ( j = 0; j < pLeaf[i].nummarksurfaces; j++ ) + { + // write a new copy of the mark surfaces for this leaf, strip out the nodraw & terrain + SurfaceHandle_t surfID = tempDiskData[pLeaf[i].firstmarksurface+j]; + if ( !SurfaceHasDispInfo( surfID ) && !(MSurf_Flags(surfID) & SURFDRAW_NODRAW) ) + { + surfList[outCount++] = surfID; + numMark++; + Assert(outCount<=realCount); + if ( MSurf_Flags(surfID) & SURFDRAW_NODE ) + { + // this assert assures that all SURFDRAW_NODE surfs appear coherently + Assert( !foundDetail ); + numMarkNode++; + } + else + { + foundDetail = true; + } + } + } + // update the leaf count + pLeaf[i].nummarksurfaces = numMark; + pLeaf[i].firstmarksurface = firstMark; + pLeaf[i].nummarknodesurfaces = numMarkNode; + } + + // write out the compacted array + pBrushData->marksurfaces = surfList; + pBrushData->nummarksurfaces = realCount; + + // remove the temp copy of the disk data + delete[] tempDiskData; + + //Msg("Must check %d / %d faces\n", checkCount, pModel->brush.numsurfaces ); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *pedges - +// *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadSurfedges( medge_t *pedges ) +{ + int i, count; + int *in; + unsigned short *out; + + CMapLoadHelper lh( LUMP_SURFEDGES ); + + in = (int *)lh.LumpBase(); + if (lh.LumpSize() % sizeof(*in)) + Host_Error ("Mod_LoadSurfedges: funny lump size in %s",lh.GetMapName()); + count = lh.LumpSize() / sizeof(*in); + if (count < 1 || count >= MAX_MAP_SURFEDGES) + Host_Error ("Mod_LoadSurfedges: bad surfedges count in %s: %i", + lh.GetMapName(), count); + out = (unsigned short *)Hunk_AllocName( count*sizeof(*out), va( "%s [%s]", lh.GetLoadName(), "surfedges" ) ); + + lh.GetMap()->vertindices = out; + lh.GetMap()->numvertindices = count; + + for ( i=0 ; i<count ; i++) + { + int edge = in[i]; + int index = 0; + if ( edge < 0 ) + { + edge = -edge; + index = 1; + } + out[i] = pedges[edge].v[index]; + } + + delete[] pedges; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *loadmodel - +// *l - +// *loadname - +//----------------------------------------------------------------------------- +void Mod_LoadPlanes( void ) +{ + // Don't bother loading them, they're already stored + s_pMap->planes = GetCollisionBSPData()->map_planes.Base(); + s_pMap->numplanes = GetCollisionBSPData()->numplanes; +} + + +//----------------------------------------------------------------------------- +// Returns game lump version +//----------------------------------------------------------------------------- +int Mod_GameLumpVersion( int lumpId ) +{ + for ( int i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + return g_GameLumpDict[i].version; + } + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Returns game lump size +//----------------------------------------------------------------------------- +int Mod_GameLumpSize( int lumpId ) +{ + for ( int i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + return g_GameLumpDict[i].uncompressedSize; + } + } + + return 0; +} + + +//----------------------------------------------------------------------------- +// Loads game lumps +//----------------------------------------------------------------------------- +bool Mod_LoadGameLump( int lumpId, void *pOutBuffer, int size ) +{ + int i; + for ( i = g_GameLumpDict.Size(); --i >= 0; ) + { + if ( g_GameLumpDict[i].id == lumpId ) + { + break; + } + } + if ( i < 0 ) + { + // unknown + return false; + } + + byte *pData; + bool bIsCompressed = ( g_GameLumpDict[i].flags & GAMELUMPFLAG_COMPRESSED ); + int dataLength; + int outSize; + if ( bIsCompressed ) + { + // lump data length is always original uncompressed size + // compressed lump data length is determined from next dictionary entry offset + dataLength = g_GameLumpDict[i].compressedSize; + outSize = g_GameLumpDict[i].uncompressedSize; + } + else + { + dataLength = outSize = g_GameLumpDict[i].uncompressedSize; + } + + if ( size < 0 || size < outSize ) + { + // caller must supply a buffer that is large enough to hold the data + return false; + } + + if ( s_MapBuffer.Base() ) + { + // data is in memory + Assert( CMapLoadHelper::GetRefCount() ); + + if ( g_GameLumpDict[i].offset + dataLength > (unsigned int)s_MapBuffer.TellMaxPut() ) + { + // out of range + Assert( 0 ); + return false; + } + + pData = (unsigned char *)s_MapBuffer.Base() + g_GameLumpDict[i].offset; + if ( !bIsCompressed ) + { + V_memcpy( pOutBuffer, pData, outSize ); + return true; + } + } + else + { + // Load file into buffer + FileHandle_t fileHandle = g_pFileSystem->Open( g_GameLumpFilename, "rb" ); + if ( fileHandle == FILESYSTEM_INVALID_HANDLE ) + { + return false; + } + + g_pFileSystem->Seek( fileHandle, g_GameLumpDict[i].offset, FILESYSTEM_SEEK_HEAD ); + + if ( !bIsCompressed ) + { + // read directly into user's buffer + bool bOK = ( g_pFileSystem->Read( pOutBuffer, outSize, fileHandle ) > 0 ); + g_pFileSystem->Close( fileHandle ); + return bOK; + } + else + { + // data is compressed, read into temporary + pData = (byte *)malloc( dataLength ); + bool bOK = ( g_pFileSystem->Read( pData, dataLength, fileHandle ) > 0 ); + g_pFileSystem->Close( fileHandle ); + if ( !bOK ) + { + free( pData ); + return false; + } + } + } + + // We'll fall though to here through here if we're compressed + bool bResult = false; + if ( !CLZMA::IsCompressed( pData ) || CLZMA::GetActualSize( (unsigned char *)pData ) != g_GameLumpDict[i].uncompressedSize ) + { + Warning( "Failed loading game lump %i: lump claims to be compressed but metadata does not match\n", lumpId ); + } + else + { + // uncompress directly into caller's buffer + int outputLength = CLZMA::Uncompress( pData, (unsigned char *)pOutBuffer ); + bResult = ( outputLength > 0 && (unsigned int)outputLength == g_GameLumpDict[i].uncompressedSize ); + } + + if ( !s_MapBuffer.Base() ) + { + // done with temporary buffer + free( pData ); + } + + return bResult; +} + +//----------------------------------------------------------------------------- +// Loads game lump dictionary +//----------------------------------------------------------------------------- +void Mod_LoadGameLumpDict( void ) +{ + CMapLoadHelper lh( LUMP_GAME_LUMP ); + + // FIXME: This is brittle. If we ever try to load two game lumps + // (say, in multiple BSP files), the dictionary info I store here will get whacked + + g_GameLumpDict.RemoveAll(); + V_strcpy_safe( g_GameLumpFilename, lh.GetMapName() ); + + unsigned int lhSize = (unsigned int)Max( lh.LumpSize(), 0 ); + if ( lhSize >= sizeof( dgamelumpheader_t ) ) + { + dgamelumpheader_t* pGameLumpHeader = (dgamelumpheader_t*)lh.LumpBase(); + + // Ensure (lumpsize * numlumps + headersize) doesn't overflow + const int nMaxGameLumps = ( INT_MAX - sizeof( dgamelumpheader_t ) ) / sizeof( dgamelump_t ); + if ( pGameLumpHeader->lumpCount < 0 || + pGameLumpHeader->lumpCount > nMaxGameLumps || + sizeof( dgamelumpheader_t ) + sizeof( dgamelump_t ) * pGameLumpHeader->lumpCount > lhSize ) + { + Warning( "Bogus gamelump header in map, rejecting\n" ); + } + else + { + // Load in lumps + dgamelump_t* pGameLump = (dgamelump_t*)(pGameLumpHeader + 1); + for (int i = 0; i < pGameLumpHeader->lumpCount; ++i ) + { + if ( pGameLump[i].fileofs >= 0 && + (unsigned int)pGameLump[i].fileofs >= (unsigned int)lh.LumpOffset() && + (unsigned int)pGameLump[i].fileofs < (unsigned int)lh.LumpOffset() + lhSize && + pGameLump[i].filelen > 0 ) + { + unsigned int compressedSize = 0; + if ( i + 1 < pGameLumpHeader->lumpCount && + pGameLump[i+1].fileofs > pGameLump[i].fileofs && + pGameLump[i+1].fileofs >= 0 && + (unsigned int)pGameLump[i+1].fileofs <= (unsigned int)lh.LumpOffset() + lhSize ) + { + compressedSize = (unsigned int)pGameLump[i+1].fileofs - (unsigned int)pGameLump[i].fileofs; + } + else + { + compressedSize = (unsigned int)lh.LumpOffset() + lhSize - (unsigned int)pGameLump[i].fileofs; + } + g_GameLumpDict.AddToTail( { pGameLump[i], compressedSize } ); + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Re-Loads all of a model's peer data +//----------------------------------------------------------------------------- +void Mod_TouchAllData( model_t *pModel, int nServerCount ) +{ + double t1 = Plat_FloatTime(); + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + virtualmodel_t *pVirtualModel = g_pMDLCache->GetVirtualModel( pModel->studio ); + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeVirtualModel += ( t2 - t1 ); + + if ( pVirtualModel && nServerCount >= 1 ) + { + // ensure all sub models get current count to avoid purge + // mark first to prevent re-entrant issues during possible reload + // skip self, start at children + for ( int i=1; i<pVirtualModel->m_group.Count(); ++i ) + { + MDLHandle_t childHandle = (MDLHandle_t)(int)pVirtualModel->m_group[i].cache&0xffff; + model_t *pChildModel = (model_t *)g_pMDLCache->GetUserData( childHandle ); + if ( pChildModel ) + { + // child inherits parent reference + pChildModel->nLoadFlags |= ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_REFERENCEMASK ); + pChildModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED; + pChildModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; + pChildModel->nServerCount = nServerCount; + } + } + } + + // don't touch all the data + if ( !mod_forcetouchdata.GetBool() ) + return; + + g_pMDLCache->TouchAllData( pModel->studio ); +} + +//----------------------------------------------------------------------------- +// Callbacks to get called when various data is loaded or unloaded +//----------------------------------------------------------------------------- +class CMDLCacheNotify : public IMDLCacheNotify +{ +public: + virtual void OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ); + virtual void OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ); + +private: + void ComputeModelFlags( model_t* mod, MDLHandle_t handle ); + + // Sets the bounds from the studiohdr + void SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ); +}; +static CMDLCacheNotify s_MDLCacheNotify; + +//----------------------------------------------------------------------------- +// Computes model flags +//----------------------------------------------------------------------------- +void CMDLCacheNotify::ComputeModelFlags( model_t* pModel, MDLHandle_t handle ) +{ + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); + + // Clear out those flags we set... + pModel->flags &= ~(MODELFLAG_TRANSLUCENT_TWOPASS | MODELFLAG_VERTEXLIT | + MODELFLAG_TRANSLUCENT | MODELFLAG_MATERIALPROXY | MODELFLAG_FRAMEBUFFER_TEXTURE | + MODELFLAG_STUDIOHDR_USES_FB_TEXTURE | MODELFLAG_STUDIOHDR_USES_BUMPMAPPING | MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP ); + + bool bForceOpaque = (pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE) != 0; + + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_TRANSLUCENT_TWOPASS ) + { + pModel->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_FB_TEXTURE ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_FB_TEXTURE; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_BUMPMAPPING ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_BUMPMAPPING; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_USES_ENV_CUBEMAP ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_USES_ENV_CUBEMAP; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_AMBIENT_BOOST ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_AMBIENT_BOOST; + } + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_DO_NOT_CAST_SHADOWS ) + { + pModel->flags |= MODELFLAG_STUDIOHDR_DO_NOT_CAST_SHADOWS; + } + + IMaterial *pMaterials[ 128 ]; + int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + for ( int i = 0; i < materialCount; ++i ) + { + IMaterial *pMaterial = pMaterials[ i ]; + if ( !pMaterial ) + continue; + + if ( pMaterial->IsVertexLit() ) + { + pModel->flags |= MODELFLAG_VERTEXLIT; + } + + if ( !bForceOpaque && pMaterial->IsTranslucent() ) + { + //Msg("Translucent material %s for model %s\n", pLODData->ppMaterials[i]->GetName(), pModel->name ); + pModel->flags |= MODELFLAG_TRANSLUCENT; + } + + if ( pMaterial->HasProxy() ) + { + pModel->flags |= MODELFLAG_MATERIALPROXY; + } + + if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame + { + pModel->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; + } + } +} + +//----------------------------------------------------------------------------- +// Sets the bounds from the studiohdr +//----------------------------------------------------------------------------- +void CMDLCacheNotify::SetBoundsFromStudioHdr( model_t *pModel, MDLHandle_t handle ) +{ + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( handle ); + VectorCopy( pStudioHdr->hull_min, pModel->mins ); + VectorCopy( pStudioHdr->hull_max, pModel->maxs ); + pModel->radius = 0.0f; + for ( int i = 0; i < 3; i++ ) + { + if ( fabs(pModel->mins[i]) > pModel->radius ) + { + pModel->radius = fabs(pModel->mins[i]); + } + + if ( fabs(pModel->maxs[i]) > pModel->radius ) + { + pModel->radius = fabs(pModel->maxs[i]); + } + } +} + +//----------------------------------------------------------------------------- +// Callbacks to get called when various data is loaded or unloaded +//----------------------------------------------------------------------------- +void CMDLCacheNotify::OnDataLoaded( MDLCacheDataType_t type, MDLHandle_t handle ) +{ + model_t *pModel = (model_t*)g_pMDLCache->GetUserData( handle ); + + // NOTE: A NULL model can occur for dependent MDLHandle_ts (like .ani files) + if ( !pModel ) + return; + + switch( type ) + { + case MDLCACHE_STUDIOHDR: + { + // FIXME: This code only works because it assumes StudioHdr + // is loaded before VCollide. + SetBoundsFromStudioHdr( pModel, handle ); + } + break; + + case MDLCACHE_VCOLLIDE: + { + SetBoundsFromStudioHdr( pModel, handle ); + + // Expand the model bounds to enclose the collision model (should be done in studiomdl) + vcollide_t *pCollide = g_pMDLCache->GetVCollide( handle ); + if ( pCollide ) + { + Vector mins, maxs; + physcollision->CollideGetAABB( &mins, &maxs, pCollide->solids[0], vec3_origin, vec3_angle ); + AddPointToBounds( mins, pModel->mins, pModel->maxs ); + AddPointToBounds( maxs, pModel->mins, pModel->maxs ); + } + } + break; + + case MDLCACHE_STUDIOHWDATA: + ComputeModelFlags( pModel, handle ); + break; + } +} + +void CMDLCacheNotify::OnDataUnloaded( MDLCacheDataType_t type, MDLHandle_t handle ) +{ +} + +//----------------------------------------------------------------------------- +// Hooks the cache notify into the MDL cache system +//----------------------------------------------------------------------------- +void ConnectMDLCacheNotify( ) +{ + g_pMDLCache->SetCacheNotify( &s_MDLCacheNotify ); +} + +void DisconnectMDLCacheNotify( ) +{ + g_pMDLCache->SetCacheNotify( NULL ); +} + +//----------------------------------------------------------------------------- +// Initialize studiomdl state +//----------------------------------------------------------------------------- +void InitStudioModelState( model_t *pModel ) +{ + Assert( pModel->type == mod_studio ); + + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHDR ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHDR, pModel->studio ); + } + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_STUDIOHWDATA, pModel->studio ); + } + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VCOLLIDE ) ) + { + s_MDLCacheNotify.OnDataLoaded( MDLCACHE_VCOLLIDE, pModel->studio ); + } +} + +//----------------------------------------------------------------------------- +// Resource loading for models +//----------------------------------------------------------------------------- +class CResourcePreloadModel : public CResourcePreload +{ + static void QueuedLoaderMapCallback( void *pContext, void *pContext2, const void *pData, int nSize, LoaderError_t loaderError ) + { + if ( loaderError == LOADERERROR_NONE ) + { + // 360 mounts its bsp entirely into memory + // this data is discarded at the conclusion of the entire load process + Assert( CMapLoadHelper::GetRefCount() == 0 ); + CMapLoadHelper::InitFromMemory( (model_t *)pContext, pData, nSize ); + } + } + + virtual bool CreateResource( const char *pName ) + { + modtype_t modType = g_ModelLoader.GetTypeFromName( pName ); + + // each model type resource has entirely differnt schemes for loading/creating + if ( modType == mod_brush ) + { + // expect to be the map bsp model + MEM_ALLOC_CREDIT_( "CResourcePreloadModel(BSP)" ); + model_t *pMapModel = g_ModelLoader.FindModelNoCreate( pName ); + if ( pMapModel ) + { + Assert( CMapLoadHelper::GetRefCount() == 0 ); + + // 360 reads its specialized bsp into memory, + // up to the pack lump, which is guranateed last + char szLoadName[MAX_PATH]; + V_FileBase( pMapModel->strName, szLoadName, sizeof( szLoadName ) ); + CMapLoadHelper::Init( pMapModel, szLoadName ); + int nBytesToRead = CMapLoadHelper::LumpOffset( LUMP_PAKFILE ); + CMapLoadHelper::Shutdown(); + + // create a loader job to perform i/o operation to mount the .bsp + LoaderJob_t loaderJobBSP; + loaderJobBSP.m_pFilename = pMapModel->strName; + loaderJobBSP.m_pPathID = "GAME"; + loaderJobBSP.m_pCallback = QueuedLoaderMapCallback; + loaderJobBSP.m_pContext = (void *)pMapModel; + loaderJobBSP.m_pTargetData = malloc( nBytesToRead ); + loaderJobBSP.m_nBytesToRead = nBytesToRead; + loaderJobBSP.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + g_pQueuedLoader->AddJob( &loaderJobBSP ); + + // create an anonymous job to perform i/o operation to mount the .ain + // the .ain gets claimed later + char szAINName[MAX_PATH] = { 0 }; + V_snprintf( szAINName, sizeof( szAINName ), "maps/graphs/%s.360.ain", szLoadName ); + LoaderJob_t loaderJobAIN; + loaderJobAIN.m_pFilename = szAINName; + loaderJobAIN.m_pPathID = "GAME"; + loaderJobAIN.m_Priority = LOADERPRIORITY_DURINGPRELOAD; + g_pQueuedLoader->AddJob( &loaderJobAIN ); + + return true; + } + } + else if ( modType == mod_studio ) + { + MEM_ALLOC_CREDIT_( "CResourcePreloadModel(MDL)" ); + + char szFilename[MAX_PATH]; + V_ComposeFileName( "models", pName, szFilename, sizeof( szFilename ) ); + + // find model or create empty entry + model_t *pModel = g_ModelLoader.FindModel( szFilename ); + + // mark as touched + pModel->nLoadFlags |= IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + + if ( pModel->nLoadFlags & ( IModelLoader::FMODELLOADER_LOADED|IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // already loaded or preloaded + return true; + } + + // the model in not supposed to be in memory + Assert( pModel->type == mod_bad ); + + // set its type + pModel->type = mod_studio; + + // mark the model so that the normal studio load path can perform a final fixup + pModel->nLoadFlags |= IModelLoader::FMODELLOADER_LOADED_BY_PRELOAD; + + // setup the new entry for preload to operate + pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); + + // the model is not supposed to be in memory + // if this hits, the mdlcache is out of sync with the modelloder + // if this hits, the mdlcache has the model, but the modelloader doesn't think so + // if the refcounts go haywire, bad evil bugs will occur + Assert( g_pMDLCache->GetRef( pModel->studio ) == 1 ); + + g_pMDLCache->SetUserData( pModel->studio, pModel ); + + // get it into the cache + g_pMDLCache->PreloadModel( pModel->studio ); + + return true; + } + + // unknown + return false; + } + + //----------------------------------------------------------------------------- + // Called before queued loader i/o jobs are actually performed. Must free up memory + // to ensure i/o requests have enough memory to succeed. The models that were + // touched by the CreateResource() are the ones to keep, all others get purged. + //----------------------------------------------------------------------------- + virtual void PurgeUnreferencedResources() + { + bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; + + // purge any model that was not touched by the preload process + int iIndex = -1; + CUtlVector< model_t* > firstList; + CUtlVector< model_t* > otherList; + for ( ;; ) + { + model_t *pModel; + iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); + if ( iIndex == -1 || !pModel ) + { + // end of list + break; + } + if ( pModel->type == mod_studio ) + { + // models that were touched during the preload stay, otherwise purged + if ( pModel->nLoadFlags & IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD ) + { + pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + } + else + { + if ( bSpew ) + { + Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); + } + + // Models that have virtual models have to unload first to + // ensure they properly unreference their virtual models. + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) + { + firstList.AddToTail( pModel ); + } + else + { + otherList.AddToTail( pModel ); + } + } + } + } + + for ( int i=0; i<firstList.Count(); i++ ) + { + g_ModelLoader.UnloadModel( firstList[i] ); + } + for ( int i=0; i<otherList.Count(); i++ ) + { + g_ModelLoader.UnloadModel( otherList[i] ); + } + + if ( !g_pQueuedLoader->IsSameMapLoading() ) + { + g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK ); + } + } + + virtual void PurgeAll() + { + bool bSpew = ( g_pQueuedLoader->GetSpewDetail() & LOADER_DETAIL_PURGES ) != 0; + + // purge any model that was not touched by the preload process + int iIndex = -1; + CUtlVector< model_t* > firstList; + CUtlVector< model_t* > otherList; + for ( ;; ) + { + model_t *pModel; + iIndex = g_ModelLoader.FindNext( iIndex, &pModel ); + if ( iIndex == -1 || !pModel ) + { + // end of list + break; + } + if ( pModel->type == mod_studio ) + { + pModel->nLoadFlags &= ~IModelLoader::FMODELLOADER_TOUCHED_BY_PRELOAD; + if ( bSpew ) + { + Msg( "CResourcePreloadModel: Purging: %s\n", pModel->strName.String() ); + } + + // Models that have virtual models have to unload first to + // ensure they properly unreference their virtual models. + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_VIRTUALMODEL ) ) + { + firstList.AddToTail( pModel ); + } + else + { + otherList.AddToTail( pModel ); + } + } + } + + for ( int i=0; i<firstList.Count(); i++ ) + { + g_ModelLoader.UnloadModel( firstList[i] ); + } + for ( int i=0; i<otherList.Count(); i++ ) + { + g_ModelLoader.UnloadModel( otherList[i] ); + } + + g_pMDLCache->Flush( MDLCACHE_FLUSH_ANIMBLOCK ); + } + + virtual void OnEndMapLoading( bool bAbort ) + { + // discard the memory mounted bsp + CMapLoadHelper::Shutdown(); + Assert( CMapLoadHelper::GetRefCount() == 0 ); + } +}; +static CResourcePreloadModel s_ResourcePreloadModel; + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Init( void ) +{ + m_Models.RemoveAll(); + m_InlineModels.Purge(); + + m_pWorldModel = NULL; + m_bMapRenderInfoLoaded = false; + m_bMapHasHDRLighting = false; + g_bLoadedMapHasBakedPropLighting = false; + + // Make sure we have physcollision and physprop interfaces + CollisionBSPData_LinkPhysics(); + + m_szActiveMapName[0] = '\0'; + + g_pQueuedLoader->InstallLoader( RESOURCEPRELOAD_MODEL, &s_ResourcePreloadModel ); +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Shutdown( void ) +{ + m_pWorldModel = NULL; + + ForceUnloadNonClientDynamicModels(); + + UnloadAllModels( false ); + + m_ModelPool.Clear(); +} + +int CModelLoader::GetCount( void ) +{ + Assert( m_Models.Count() == m_Models.MaxElement() ); + return m_Models.Count(); +} + +model_t *CModelLoader::GetModelForIndex( int i ) +{ + if ( i < 0 || (unsigned)i >= m_Models.Count() ) + { + Assert( !m_Models.IsValidIndex( i ) ); + return NULL; + } + + Assert( m_Models.IsValidIndex( i ) ); + return m_Models[i].modelpointer; +} + +//----------------------------------------------------------------------------- +// Purpose: Look up name for model +// Input : *model - +// Output : const char +//----------------------------------------------------------------------------- +const char *CModelLoader::GetName( const model_t *pModel ) +{ + if ( pModel ) + { + return pModel->strName; + } + return NULL; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the model, builds entry if not present, always returns a model +// Input : *name - +// referencetype - +// Output : model_t +//----------------------------------------------------------------------------- +model_t *CModelLoader::FindModel( const char *pName ) +{ + if ( !pName || !pName[0] ) + { + Sys_Error( "CModelLoader::FindModel: NULL name" ); + } + + // inline models are grabbed only from worldmodel + if ( pName[0] == '*' ) + { + int modelNum = atoi( pName + 1 ); + if ( !IsWorldModelSet() ) + { + Sys_Error( "bad inline model number %i, worldmodel not yet setup", modelNum ); + } + + if ( modelNum < 1 || modelNum >= GetNumWorldSubmodels() ) + { + Sys_Error( "bad inline model number %i", modelNum ); + } + return &m_InlineModels[modelNum]; + } + + model_t *pModel = NULL; + + // get a handle suitable to use as the model key + // handles are insensitive to case and slashes + FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pName ); + + int i = m_Models.Find( fnHandle ); + if ( i == m_Models.InvalidIndex() ) + { + pModel = (model_t *)m_ModelPool.Alloc(); + Assert( pModel ); + memset( pModel, 0, sizeof( model_t ) ); + + pModel->fnHandle = fnHandle; + + // Mark that we should load from disk + pModel->nLoadFlags = FMODELLOADER_NOTLOADEDORREFERENCED; + + // Copy in name and normalize! + // Various other subsystems fetch this 'object' name to do dictionary lookups, + // which are usually case insensitive, but not to slashes or dotslashes. + pModel->strName = pName; + V_RemoveDotSlashes( pModel->strName.GetForModify(), '/' ); + + ModelEntry_t entry; + entry.modelpointer = pModel; + m_Models.Insert( fnHandle, entry ); + } + else + { + pModel = m_Models[i].modelpointer; + } + + // notify the reslist generator that this model may be referenced later in the level + // (does nothing if reslist generation is not enabled) + MapReslistGenerator().OnModelPrecached( pName ); + + Assert( pModel ); + + return pModel; +} + +//----------------------------------------------------------------------------- +// Purpose: Finds the model, and loads it if it isn't already present. Updates reference flags +// Input : *name - +// referencetype - +// Output : model_t +//----------------------------------------------------------------------------- +model_t *CModelLoader::GetModelForName( const char *name, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "GetModelForName: dynamic models must use GetDynamicModel" ); + + // find or build new entry + model_t *model = FindModel( name ); + + // touch and load if not present + model_t *retval = LoadModel( model, &referencetype ); + + return retval; +} + + +//----------------------------------------------------------------------------- +// Purpose: Add a reference to the model in question +// Input : *name - +// referencetype - +//----------------------------------------------------------------------------- +model_t *CModelLoader::ReferenceModel( const char *name, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "ReferenceModel: do not use for dynamic models" ); + + model_t *model = FindModel( name ); + + model->nLoadFlags |= referencetype; + + return model; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *entry - +// referencetype - +//----------------------------------------------------------------------------- +model_t *CModelLoader::LoadModel( model_t *mod, REFERENCETYPE *pReferencetype ) +{ + if ( pReferencetype ) + { + mod->nLoadFlags |= *pReferencetype; + } + + // during initial load mark the model with an unique session ticket + // at load end, models that have a mismatch count are considered candidates for purge + // models that get marked, touch *all* their sub data to ensure the cache is pre-populated + // and hitches less during gameplay + bool bTouchAllData = false; + int nServerCount = Host_GetServerCount(); + if ( mod->nServerCount != nServerCount ) + { + // server has changed + mod->nServerCount = nServerCount; + bTouchAllData = true; + } + + // Check if the studio model is in cache. + // The model type will not be set for first time models that need to fall through to the load path. + // A model that needs a post precache fixup will fall through to the load path. + if ( mod->type == mod_studio && !( mod->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // in cache + Verify( g_pMDLCache->GetStudioHdr( mod->studio ) != 0 ); + Assert( FMODELLOADER_LOADED & mod->nLoadFlags ); + + if ( bTouchAllData ) + { + // Touch all related .ani files and sub/dependent models + // only touches once, when server changes + Mod_TouchAllData( mod, nServerCount ); + } + + return mod; + } + + // Check if brushes or sprites are loaded + if ( FMODELLOADER_LOADED & mod->nLoadFlags ) + { + return mod; + } + + // model needs to be loaded + double st = Plat_FloatTime(); + + // Set the name of the current model we are loading + Q_FileBase( mod->strName, m_szLoadName, sizeof( m_szLoadName ) ); + + // load the file + if ( developer.GetInt() > 1 ) + { + DevMsg( "Loading: %s\n", mod->strName.String() ); + } + + mod->type = GetTypeFromName( mod->strName ); + if ( mod->type == mod_bad ) + { + mod->type = mod_studio; + } + + // finalize the model data + switch ( mod->type ) + { + case mod_sprite: + { + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + double t1 = Plat_FloatTime(); + Sprite_LoadModel( mod ); + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeSprite += ( t2 - t1 ); + } + break; + + case mod_studio: + { + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + double t1 = Plat_FloatTime(); + Studio_LoadModel( mod, bTouchAllData ); + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeStudio += ( t2 - t1 ); + } + break; + + case mod_brush: + { + double t1 = Plat_FloatTime(); + + // This is necessary on dedicated clients. On listen + dedicated servers, it's called twice. + // The second invocation is harmless. + // Add to file system before loading so referenced objects in map can use the filename. + g_pFileSystem->AddSearchPath( mod->strName, "GAME", PATH_ADD_TO_HEAD ); + + // the map may have explicit texture exclusion + // the texture state needs to be established before any loading work + if ( IsX360() || mat_excludetextures.GetBool() ) + { + char szExcludePath[MAX_PATH]; + sprintf( szExcludePath, "//MOD/maps/%s_exclude.lst", m_szLoadName ); + g_pMaterialSystem->SetExcludedTextures( szExcludePath ); + } + + // need this before queued loader starts, various systems use this as a cheap map changed state + V_strncpy( m_szActiveMapName, mod->strName, sizeof( m_szActiveMapName ) ); + + //NotifyHunkBeginMapLoad( m_szActiveMapName ); + + bool bQueuedLoader = false; + if ( IsX360() ) + { + // must establish the bsp feature set first to ensure proper state during queued loading + Map_CheckForHDR( mod, m_szLoadName ); + + // Do not optimize map-to-same-map loading in TF + // FIXME/HACK: this fixes a bug (when shipping Orange Box) where static props would sometimes + // disappear when a client disconnects and reconnects to the same map+server + // (static prop lighting data persists when loading map A after map A) + bool bIsTF = !V_stricmp( COM_GetModDirectory(), "tf" ); + bool bOptimizeMapReload = !bIsTF; + + // start the queued loading process + bQueuedLoader = g_pQueuedLoader->BeginMapLoading( mod->strName, g_pMaterialSystemHardwareConfig->GetHDREnabled(), bOptimizeMapReload ); + } + + // the queued loader process needs to own the actual texture update + if ( !bQueuedLoader && ( IsX360() || mat_excludetextures.GetBool() ) ) + { + g_pMaterialSystem->UpdateExcludedTextures(); + } + + BeginLoadingUpdates( MATERIAL_NON_INTERACTIVE_MODE_LEVEL_LOAD ); + g_pFileSystem->BeginMapAccess(); + Map_LoadModel( mod ); + g_pFileSystem->EndMapAccess(); + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeBrush += (t2 - t1); + } + break; + + default: + Assert( 0 ); + break; + }; + + float dt = ( Plat_FloatTime() - st ); + COM_TimestampedLog( "Load of %s took %.3f msec", mod->strName.String(), 1000.0f * dt ); + g_flAccumulatedModelLoadTime += dt; + + return mod; +} + +//----------------------------------------------------------------------------- +// Purpose: Creates the name of the sprite +//----------------------------------------------------------------------------- +//static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsAVI, bool &bIsBIK ) +static void BuildSpriteLoadName( const char *pName, char *pOut, int outLen, bool &bIsVideo ) +{ + // If it's a .vmt and they put a path in there, then use the path. + // Otherwise, use the old method of prepending the sprites directory. + Assert( pName != NULL && pOut != NULL ); + + bIsVideo = false; + bool bIsVMT = false; + const char *pExt = V_GetFileExtension( pName ); + if ( pExt != NULL ) + { + bIsVMT = !Q_stricmp( pExt, "vmt" ); + if ( !bIsVMT ) + { + if ( g_pVideo ) + { + bIsVideo = ( g_pVideo->LocateVideoSystemForPlayingFile( pName ) != VideoSystem::NONE ); + } + } + } + + if ( ( bIsVideo || bIsVMT ) && ( strchr( pName, '/' ) || strchr( pName, '\\' ) ) ) + { + // The material system cannot handle a prepended "materials" dir + // Keep .avi extensions on the material to load avi-based materials + if ( bIsVMT ) + { + const char *pNameStart = pName; + if ( Q_stristr( pName, "materials/" ) == pName || + Q_stristr( pName, "materials\\" ) == pName ) + { + // skip past materials/ + pNameStart = &pName[10]; + } + Q_StripExtension( pNameStart, pOut, outLen ); + } + else + { + // name is good as is + Q_strncpy( pOut, pName, outLen ); + } + } + else + { + char szBase[MAX_PATH]; + Q_FileBase( pName, szBase, sizeof( szBase ) ); + Q_snprintf( pOut, outLen, "sprites/%s", szBase ); + } + + return; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *name - +// Output : int +//----------------------------------------------------------------------------- +int CModelLoader::GetModelFileSize( char const *name ) +{ + if ( !name || !name[ 0 ] ) + return -1; + + model_t *model = FindModel( name ); + + int size = -1; + if ( Q_stristr( model->strName, ".spr" ) || Q_stristr( model->strName, ".vmt" ) ) + { + char spritename[ MAX_PATH ]; + Q_StripExtension( va( "materials/%s", model->strName.String() ), spritename, MAX_PATH ); + Q_DefaultExtension( spritename, ".vmt", sizeof( spritename ) ); + + size = COM_FileSize( spritename ); + } + else + { + size = COM_FileSize( name ); + } + + return size; +} + +//----------------------------------------------------------------------------- +// Purpose: Unmasks the referencetype field for the model +// Input : *model - +// referencetype - +//----------------------------------------------------------------------------- +void CModelLoader::UnreferenceModel( model_t *model, REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceModel: do not use for dynamic models" ); + model->nLoadFlags &= ~referencetype; +} + +//----------------------------------------------------------------------------- +// Purpose: Unmasks the specified reference type across all models +// Input : referencetype - +//----------------------------------------------------------------------------- +void CModelLoader::UnreferenceAllModels( REFERENCETYPE referencetype ) +{ + AssertMsg( !(referencetype & FMODELLOADER_DYNAMIC), "UnreferenceAllModels: do not use for dynamic models" ); + + // UNDONE: If we ever free a studio model, write code to free the collision data + // UNDONE: Reference count collision data? + + FOR_EACH_MAP_FAST( m_Models, i ) + { + m_Models[ i ].modelpointer->nLoadFlags &= ~referencetype; + } +} + +//----------------------------------------------------------------------------- +// Purpose: When changing servers the old servercount number is bogus. This +// marks all models as loaded from -1 (e.g. a server count from the +// before time.) +//----------------------------------------------------------------------------- +void CModelLoader::ResetModelServerCounts() +{ + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + pModel->nServerCount = -1; + } +} + + +void CModelLoader::ReloadFilesInList( IFileList *pFilesToReload ) +{ + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + + if ( pModel->type != mod_studio ) + continue; + + if ( !IsLoaded( pModel ) ) + continue; + + if ( pModel->type != mod_studio ) + continue; + + if ( pFilesToReload->IsFileInList( pModel->strName ) ) + { + #ifdef PURE_SERVER_DEBUG_SPEW + Msg( "Reloading model %s\n", pModel->strName.String() ); + #endif + + // Flush out the model cache + // Don't flush vcollides since the vphysics system currently + // has no way of indicating they refer to vcollides + g_pMDLCache->Flush( pModel->studio, (int)(MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + // Get the studiohdr into the cache + g_pMDLCache->GetStudioHdr( pModel->studio ); + +#ifndef _XBOX + // force the collision to load + g_pMDLCache->GetVCollide( pModel->studio ); +#endif + } + else + { + if ( g_pMDLCache->IsDataLoaded( pModel->studio, MDLCACHE_STUDIOHWDATA ) ) + { + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + if ( pStudioHdr ) + { + // Ok, we didn't have to do a full reload, but if any of our materials changed, flush out the studiohwdata because the + // vertex format may have changed. + IMaterial *pMaterials[128]; + int nMaterials = g_pStudioRender->GetMaterialList( pStudioHdr, ARRAYSIZE( pMaterials ), &pMaterials[0] ); + + for ( int iMat=0; iMat < nMaterials; iMat++ ) + { + if ( pMaterials[iMat] && pMaterials[iMat]->WasReloadedFromWhitelist() ) + { + #ifdef PURE_SERVER_DEBUG_SPEW + Msg( "Reloading model %s because material %s was reloaded\n", pModel->strName.String(), pMaterials[iMat]->GetName() ); + #endif + g_pMDLCache->Flush( pModel->studio, MDLCACHE_FLUSH_STUDIOHWDATA ); + break; + } + } + } + } + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model +// and frees up the models slot +//----------------------------------------------------------------------------- +void CModelLoader::UnloadAllModels( bool bCheckReference ) +{ + model_t *model; + + FOR_EACH_MAP_FAST( m_Models, i ) + { + model = m_Models[ i ].modelpointer; + if ( bCheckReference ) + { + if ( model->nLoadFlags & FMODELLOADER_REFERENCEMASK ) + { + if ( model->type == mod_studio ) + { + g_pMDLCache->MarkAsLoaded(model->studio); + } + continue; + } + } + else + { + // Wipe current flags + model->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; + } + + if ( IsX360() && g_pQueuedLoader->IsMapLoading() && ( model->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + // models preloaded by the queued loader are not initially claimed and MUST remain until the end of the load process + // unclaimed models get unloaded during the post load purge + continue; + } + + if ( model->nLoadFlags & ( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ) ) + { + UnloadModel( model ); + } + } +} + + +//----------------------------------------------------------------------------- +// Purpose: For any models with referencetype blank (if checking), frees all memory associated with the model +// and frees up the models slot +//----------------------------------------------------------------------------- +void CModelLoader::UnloadUnreferencedModels( void ) +{ + // unload all unreferenced models + UnloadAllModels( true ); +} + + +//----------------------------------------------------------------------------- +// Called at the conclusion of loading. +// Frees all memory associated with models (and their materials) that are not +// marked with the current session. +//----------------------------------------------------------------------------- +void CModelLoader::PurgeUnusedModels( void ) +{ + int nServerCount = Host_GetServerCount(); + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + if ( ( pModel->nLoadFlags & FMODELLOADER_LOADED ) && ( pModel->nServerCount != nServerCount ) ) + { + // mark as unreferenced + // do not unload dynamic models + pModel->nLoadFlags &= (~FMODELLOADER_REFERENCEMASK) | FMODELLOADER_DYNAMIC; + } + } + + // flush dynamic models that have no refcount + FlushDynamicModels(); + + // unload unreferenced models only + UnloadAllModels( true ); + + // now purge unreferenced materials + materials->UncacheUnusedMaterials( true ); +} + +//----------------------------------------------------------------------------- +// Compute whether this submodel uses material proxies or not +//----------------------------------------------------------------------------- +static void Mod_ComputeBrushModelFlags( model_t *mod ) +{ + Assert( mod ); + + worldbrushdata_t *pBrushData = mod->brush.pShared; + // Clear out flags we're going to set + mod->flags &= ~(MODELFLAG_MATERIALPROXY | MODELFLAG_TRANSLUCENT | MODELFLAG_FRAMEBUFFER_TEXTURE | MODELFLAG_TRANSLUCENT_TWOPASS ); + mod->flags = MODELFLAG_HAS_DLIGHT; // force this check the first time + + int i; + int scount = mod->brush.nummodelsurfaces; + bool bHasOpaqueSurfaces = false; + bool bHasTranslucentSurfaces = false; + for ( i = 0; i < scount; ++i ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, pBrushData ); + + // Clear out flags we're going to set + MSurf_Flags( surfID ) &= ~(SURFDRAW_NOCULL | SURFDRAW_TRANS | SURFDRAW_ALPHATEST | SURFDRAW_NODECALS); + + mtexinfo_t *pTex = MSurf_TexInfo( surfID, pBrushData ); + IMaterial* pMaterial = pTex->material; + + if ( pMaterial->HasProxy() ) + { + mod->flags |= MODELFLAG_MATERIALPROXY; + } + + if ( pMaterial->NeedsPowerOfTwoFrameBufferTexture( false ) ) // The false checks if it will ever need the frame buffer, not just this frame + { + mod->flags |= MODELFLAG_FRAMEBUFFER_TEXTURE; + } + + // Deactivate culling if the material is two sided + if ( pMaterial->IsTwoSided() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NOCULL; + } + + if ( (pTex->flags & SURF_TRANS) || pMaterial->IsTranslucent() ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + MSurf_Flags( surfID ) |= SURFDRAW_TRANS; + bHasTranslucentSurfaces = true; + } + else + { + bHasOpaqueSurfaces = true; + } + + // Certain surfaces don't want decals at all + if ( (pTex->flags & SURF_NODECALS) || pMaterial->GetMaterialVarFlag( MATERIAL_VAR_SUPPRESS_DECALS ) || pMaterial->IsAlphaTested() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_NODECALS; + } + + if ( pMaterial->IsAlphaTested() ) + { + MSurf_Flags( surfID ) |= SURFDRAW_ALPHATEST; + } + } + + if ( bHasOpaqueSurfaces && bHasTranslucentSurfaces ) + { + mod->flags |= MODELFLAG_TRANSLUCENT_TWOPASS; + } +} + + +//----------------------------------------------------------------------------- +// Recomputes translucency for the model... +//----------------------------------------------------------------------------- +void Mod_RecomputeTranslucency( model_t* mod, int nSkin, int nBody, void /*IClientRenderable*/ *pClientRenderable, float fInstanceAlphaModulate ) +{ + if (fInstanceAlphaModulate < 1.0f) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + return; + } + + mod->flags &= ~MODELFLAG_TRANSLUCENT; + + switch( mod->type ) + { + case mod_brush: + { + for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface+i, mod->brush.pShared ); + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* material = MSurf_TexInfo( surfID, mod->brush.pShared )->material; + if ( material->IsTranslucent() ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + break; + } + } + } + break; + + case mod_studio: + { + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( mod->studio ); + if ( pStudioHdr->flags & STUDIOHDR_FLAGS_FORCE_OPAQUE ) + return; + + IMaterial *pMaterials[ 128 ]; + int materialCount = g_pStudioRender->GetMaterialListFromBodyAndSkin( mod->studio, nSkin, nBody, ARRAYSIZE( pMaterials ), pMaterials ); + for ( int i = 0; i < materialCount; i++ ) + { + if ( pMaterials[i] != NULL ) + { + // Bind material first so all material proxies execute + CMatRenderContextPtr pRenderContext( g_pMaterialSystem ); + pRenderContext->Bind( pMaterials[i], pClientRenderable ); + bool bIsTranslucent = pMaterials[i]->IsTranslucent(); + + if ( bIsTranslucent ) + { + mod->flags |= MODELFLAG_TRANSLUCENT; + break; + } + } + } + } + break; + } +} + + +//----------------------------------------------------------------------------- +// returns the material count... +//----------------------------------------------------------------------------- +int Mod_GetMaterialCount( model_t* mod ) +{ + switch( mod->type ) + { + case mod_brush: + { + CUtlVector<IMaterial*> uniqueMaterials( 0, 32 ); + + for (int i = 0; i < mod->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( mod->brush.firstmodelsurface + i, mod->brush.pShared ); + + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* pMaterial = MSurf_TexInfo( surfID, mod->brush.pShared )->material; + + // Try to find the material in the unique list of materials + // if it's not there, then add it + if (uniqueMaterials.Find(pMaterial) < 0) + uniqueMaterials.AddToTail(pMaterial); + } + + return uniqueMaterials.Size(); + } + break; + + case mod_studio: + { + // FIXME: This should return the list of all materials + // across all LODs if we every decide to implement this + Assert(0); + } + break; + + default: + // unimplemented + Assert(0); + break; + } + + return 0; +} + +//----------------------------------------------------------------------------- +// returns the first n materials. +//----------------------------------------------------------------------------- +int Mod_GetModelMaterials( model_t* pModel, int count, IMaterial** ppMaterials ) +{ + studiohdr_t *pStudioHdr; + int found = 0; + int i; + + switch( pModel->type ) + { + case mod_brush: + { + for ( i = 0; i < pModel->brush.nummodelsurfaces; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( pModel->brush.firstmodelsurface + i, pModel->brush.pShared ); + if ( MSurf_Flags( surfID ) & SURFDRAW_NODRAW ) + continue; + + IMaterial* pMaterial = MSurf_TexInfo( surfID, pModel->brush.pShared )->material; + + // Try to find the material in the unique list of materials + // if it's not there, then add it + int j = found; + while ( --j >= 0 ) + { + if ( ppMaterials[j] == pMaterial ) + break; + } + if (j < 0) + ppMaterials[found++] = pMaterial; + + // Stop when we've gotten count materials + if ( found >= count ) + return found; + } + } + break; + + case mod_studio: + if ( pModel->ppMaterials ) + { + int nMaterials = ((intptr_t*)(pModel->ppMaterials))[-1]; + found = MIN( count, nMaterials ); + memcpy( ppMaterials, pModel->ppMaterials, found * sizeof( IMaterial* ) ); + } + else + { + // Get the studiohdr into the cache + pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + // Get the list of materials + found = g_pStudioRender->GetMaterialList( pStudioHdr, count, ppMaterials ); + } + break; + + default: + // unimplemented + Assert( 0 ); + break; + } + + return found; +} + + +void Mod_SetMaterialVarFlag( model_t *pModel, unsigned int uiFlag, bool on ) +{ + MaterialVarFlags_t flag = (MaterialVarFlags_t)uiFlag; + IMaterial *pMaterials[ 128 ]; + if ( pModel ) + { + int materialCount = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + for ( int i = 0; i < materialCount; ++i ) + { + IMaterial *pMaterial = pMaterials[ i ]; + if ( pMaterial ) + { + pMaterial->SetMaterialVarFlag( flag, on ); + } + } + } +} + +//----------------------------------------------------------------------------- +// Used to compute which surfaces are in water or not +//----------------------------------------------------------------------------- + +static void MarkWaterSurfaces_ProcessLeafNode( mleaf_t *pLeaf ) +{ + int i; + + int flags = ( pLeaf->leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; + + SurfaceHandle_t *pHandle = &host_state.worldbrush->marksurfaces[pLeaf->firstmarksurface]; + + for( i = 0; i < pLeaf->nummarksurfaces; i++ ) + { + SurfaceHandle_t surfID = pHandle[i]; + ASSERT_SURF_VALID( surfID ); + if( MSurf_Flags( surfID ) & SURFDRAW_WATERSURFACE ) + continue; + + if (SurfaceHasDispInfo( surfID )) + continue; + + MSurf_Flags( surfID ) |= flags; + } + + // FIXME: This is somewhat bogus, but I can do it quickly, and it's + // not clear I need to solve the harder problem. + + // If any portion of a displacement surface hits a water surface, + // I'm going to mark it as being in water, and vice versa. + for ( i = 0; i < pLeaf->dispCount; i++ ) + { + IDispInfo *pDispInfo = MLeaf_Disaplcement( pLeaf, i ); + + if ( pDispInfo ) + { + SurfaceHandle_t parentSurfID = pDispInfo->GetParent(); + MSurf_Flags( parentSurfID ) |= flags; + } + } +} + + +void MarkWaterSurfaces_r( mnode_t *node ) +{ + // no polygons in solid nodes + if (node->contents == CONTENTS_SOLID) + return; // solid + + // if a leaf node, . .mark all the polys as to whether or not they are in water. + if (node->contents >= 0) + { + MarkWaterSurfaces_ProcessLeafNode( (mleaf_t *)node ); + return; + } + + MarkWaterSurfaces_r( node->children[0] ); + MarkWaterSurfaces_r( node->children[1] ); +} + + +//----------------------------------------------------------------------------- +// Computes the sort group for a particular face +//----------------------------------------------------------------------------- +static int SurfFlagsToSortGroup( SurfaceHandle_t surfID, int flags ) +{ + // If we're on the low end, stick everything into the same sort group + if ( g_pMaterialSystemHardwareConfig->GetDXSupportLevel() < 80 ) + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; + + if( flags & SURFDRAW_WATERSURFACE ) + return MAT_SORT_GROUP_WATERSURFACE; + + if( ( flags & ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) == ( SURFDRAW_UNDERWATER | SURFDRAW_ABOVEWATER ) ) + return MAT_SORT_GROUP_INTERSECTS_WATER_SURFACE; + + if( flags & SURFDRAW_UNDERWATER ) + return MAT_SORT_GROUP_STRICTLY_UNDERWATER; + + if( flags & SURFDRAW_ABOVEWATER ) + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; + + static int warningcount = 0; + if ( ++warningcount < 10 ) + { + Vector vecCenter; + Surf_ComputeCentroid( surfID, &vecCenter ); + DevWarning( "SurfFlagsToSortGroup: unhandled flags (%X) (%s)!\n", flags, MSurf_TexInfo(surfID)->material->GetName() ); + DevWarning( "- This implies you have a surface (usually a displacement) embedded in solid.\n" ); + DevWarning( "- Look near (%.1f, %.1f, %.1f)\n", vecCenter.x, vecCenter.y, vecCenter.z ); + } + //Assert( 0 ); + return MAT_SORT_GROUP_STRICTLY_ABOVEWATER; +} + + + +//----------------------------------------------------------------------------- +// Computes sort group +//----------------------------------------------------------------------------- +bool Mod_MarkWaterSurfaces( model_t *pModel ) +{ + bool bHasWaterSurfaces = false; + model_t *pSaveModel = host_state.worldmodel; + + // garymcthack!!!!!!!! + // host_state.worldmodel isn't set at this point, so. . . . + host_state.SetWorldModel( pModel ); + MarkWaterSurfaces_r( pModel->brush.pShared->nodes ); + for ( int i = 0; i < pModel->brush.pShared->numsurfaces; i++ ) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( i, pModel->brush.pShared ); + + int sortGroup = SurfFlagsToSortGroup( surfID, MSurf_Flags( surfID ) ); + if ( sortGroup == MAT_SORT_GROUP_WATERSURFACE ) + { + bHasWaterSurfaces = true; + } + MSurf_SetSortGroup( surfID, sortGroup ); + } + host_state.SetWorldModel( pSaveModel ); + + return bHasWaterSurfaces; +} + + +//----------------------------------------------------------------------------- +// Marks identity brushes as being in fog volumes or not +//----------------------------------------------------------------------------- +class CBrushBSPIterator : public ISpatialLeafEnumerator +{ +public: + CBrushBSPIterator( model_t *pWorld, model_t *pBrush ) + { + m_pWorld = pWorld; + m_pBrush = pBrush; + m_pShared = pBrush->brush.pShared; + m_count = 0; + } + bool EnumerateLeaf( int leaf, int ) + { + // garymcthack - need to test identity brush models + int flags = ( m_pShared->leafs[leaf].leafWaterDataID == -1 ) ? SURFDRAW_ABOVEWATER : SURFDRAW_UNDERWATER; + MarkModelSurfaces( flags ); + m_count++; + return true; + } + + void MarkModelSurfaces( int flags ) + { + // Iterate over all this models surfaces + int surfaceCount = m_pBrush->brush.nummodelsurfaces; + for (int i = 0; i < surfaceCount; ++i) + { + SurfaceHandle_t surfID = SurfaceHandleFromIndex( m_pBrush->brush.firstmodelsurface + i, m_pShared ); + MSurf_Flags( surfID ) &= ~(SURFDRAW_ABOVEWATER | SURFDRAW_UNDERWATER); + MSurf_Flags( surfID ) |= flags; + } + } + + void CheckSurfaces() + { + if ( !m_count ) + { + MarkModelSurfaces( SURFDRAW_ABOVEWATER ); + } + } + + model_t* m_pWorld; + model_t* m_pBrush; + worldbrushdata_t *m_pShared; + int m_count; +}; + +static void MarkBrushModelWaterSurfaces( model_t* world, + Vector const& mins, Vector const& maxs, model_t* brush ) +{ + // HACK: This is a totally brutal hack dealing with initialization order issues. + // I want to use the same box enumeration code so I don't have multiple + // copies, but I want to use it from modelloader. host_state.worldmodel isn't + // set up at that time however, so I have to fly through these crazy hoops. + // Massive suckage. + + model_t* pTemp = host_state.worldmodel; + CBrushBSPIterator brushIterator( world, brush ); + host_state.SetWorldModel( world ); + g_pToolBSPTree->EnumerateLeavesInBox( mins, maxs, &brushIterator, (int)brush ); + brushIterator.CheckSurfaces(); + host_state.SetWorldModel( pTemp ); +} + +int g_nMapLoadCount = 0; +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +// *buffer - +//----------------------------------------------------------------------------- +void CModelLoader::Map_LoadModel( model_t *mod ) +{ + ++g_nMapLoadCount; + + MEM_ALLOC_CREDIT(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); + + COM_TimestampedLog( "Map_LoadModel: Start" ); + + double startTime = Plat_FloatTime(); + + SetWorldModel( mod ); + + // point at the shared world/brush data + mod->brush.pShared = &m_worldBrushData; + mod->brush.renderHandle = 0; + + // HDR and features must be established first + COM_TimestampedLog( " Map_CheckForHDR" ); + m_bMapHasHDRLighting = Map_CheckForHDR( mod, m_szLoadName ); + if ( IsX360() && !m_bMapHasHDRLighting ) + { + Warning( "Map '%s' lacks exepected HDR data! 360 does not support accurate LDR visuals.", m_szLoadName ); + } + + // Load the collision model + COM_TimestampedLog( " CM_LoadMap" ); + unsigned int checksum; + CM_LoadMap( mod->strName, false, &checksum ); + + // Load the map + mod->type = mod_brush; + mod->nLoadFlags |= FMODELLOADER_LOADED; + CMapLoadHelper::Init( mod, m_szLoadName ); + + COM_TimestampedLog( " Mod_LoadVertices" ); + Mod_LoadVertices(); + + COM_TimestampedLog( " Mod_LoadEdges" ); + medge_t *pedges = Mod_LoadEdges(); + + COM_TimestampedLog( " Mod_LoadSurfedges" ); + Mod_LoadSurfedges( pedges ); + + COM_TimestampedLog( " Mod_LoadPlanes" ); + Mod_LoadPlanes(); + + COM_TimestampedLog( " Mod_LoadOcclusion" ); + Mod_LoadOcclusion(); + + // texdata needs to load before texinfo + COM_TimestampedLog( " Mod_LoadTexdata" ); + Mod_LoadTexdata(); + + COM_TimestampedLog( " Mod_LoadTexinfo" ); + Mod_LoadTexinfo(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // Until BSP version 19, this must occur after loading texinfo + COM_TimestampedLog( " Mod_LoadLighting" ); + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_LIGHTING_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_LIGHTING_HDR ); + Mod_LoadLighting( mlh ); + } + else + { + CMapLoadHelper mlh( LUMP_LIGHTING ); + Mod_LoadLighting( mlh ); + } + + COM_TimestampedLog( " Mod_LoadPrimitives" ); + Mod_LoadPrimitives(); + + COM_TimestampedLog( " Mod_LoadPrimVerts" ); + Mod_LoadPrimVerts(); + + COM_TimestampedLog( " Mod_LoadPrimIndices" ); + Mod_LoadPrimIndices(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // faces need to be loaded before vertnormals + COM_TimestampedLog( " Mod_LoadFaces" ); + Mod_LoadFaces(); + + COM_TimestampedLog( " Mod_LoadVertNormals" ); + Mod_LoadVertNormals(); + + COM_TimestampedLog( " Mod_LoadVertNormalIndices" ); + Mod_LoadVertNormalIndices(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + // note leafs must load befor marksurfaces + COM_TimestampedLog( " Mod_LoadLeafs" ); + Mod_LoadLeafs(); + + COM_TimestampedLog( " Mod_LoadMarksurfaces" ); + Mod_LoadMarksurfaces(); + + COM_TimestampedLog( " Mod_LoadNodes" ); + Mod_LoadNodes(); + + COM_TimestampedLog( " Mod_LoadLeafWaterData" ); + Mod_LoadLeafWaterData(); + + COM_TimestampedLog( " Mod_LoadCubemapSamples" ); + Mod_LoadCubemapSamples(); + +#ifndef SWDS + // UNDONE: Does the cmodel need worldlights? + COM_TimestampedLog( " OverlayMgr()->LoadOverlays" ); + OverlayMgr()->LoadOverlays(); +#endif + + COM_TimestampedLog( " Mod_LoadLeafMinDistToWater" ); + Mod_LoadLeafMinDistToWater(); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " LUMP_CLIPPORTALVERTS" ); + Mod_LoadLump( mod, + LUMP_CLIPPORTALVERTS, + va( "%s [%s]", m_szLoadName, "clipportalverts" ), + sizeof(m_worldBrushData.m_pClipPortalVerts[0]), + (void**)&m_worldBrushData.m_pClipPortalVerts, + &m_worldBrushData.m_nClipPortalVerts ); + + COM_TimestampedLog( " LUMP_AREAPORTALS" ); + Mod_LoadLump( mod, + LUMP_AREAPORTALS, + va( "%s [%s]", m_szLoadName, "areaportals" ), + sizeof(m_worldBrushData.m_pAreaPortals[0]), + (void**)&m_worldBrushData.m_pAreaPortals, + &m_worldBrushData.m_nAreaPortals ); + + COM_TimestampedLog( " LUMP_AREAS" ); + Mod_LoadLump( mod, + LUMP_AREAS, + va( "%s [%s]", m_szLoadName, "areas" ), + sizeof(m_worldBrushData.m_pAreas[0]), + (void**)&m_worldBrushData.m_pAreas, + &m_worldBrushData.m_nAreas ); + + COM_TimestampedLog( " Mod_LoadWorldlights" ); + if ( g_pMaterialSystemHardwareConfig->GetHDRType() != HDR_TYPE_NONE && + CMapLoadHelper::LumpSize( LUMP_WORLDLIGHTS_HDR ) > 0 ) + { + CMapLoadHelper mlh( LUMP_WORLDLIGHTS_HDR ); + Mod_LoadWorldlights( mlh, true ); + } + else + { + CMapLoadHelper mlh( LUMP_WORLDLIGHTS ); + Mod_LoadWorldlights( mlh, false ); + } + + COM_TimestampedLog( " Mod_LoadGameLumpDict" ); + Mod_LoadGameLumpDict(); + + // load the portal information + // JAY: Disabled until we need this information. +#if 0 + Mod_LoadPortalVerts(); + Mod_LoadClusterPortals(); + Mod_LoadClusters(); + Mod_LoadPortals(); +#endif + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " Mod_LoadSubmodels" ); + CUtlVector<mmodel_t> submodelList; + Mod_LoadSubmodels( submodelList ); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " SetupSubModels" ); + SetupSubModels( mod, submodelList ); + + COM_TimestampedLog( " RecomputeSurfaceFlags" ); + RecomputeSurfaceFlags( mod ); + +#ifndef SWDS + EngineVGui()->UpdateProgressBar(PROGRESS_LOADWORLDMODEL); +#endif + + COM_TimestampedLog( " Map_VisClear" ); + Map_VisClear(); + + COM_TimestampedLog( " Map_SetRenderInfoAllocated" ); + Map_SetRenderInfoAllocated( false ); + + // Close map file, etc. + CMapLoadHelper::Shutdown(); + + double elapsed = Plat_FloatTime() - startTime; + COM_TimestampedLog( "Map_LoadModel: Finish - loading took %.4f seconds", elapsed ); +} + +void CModelLoader::Map_UnloadCubemapSamples( model_t *mod ) +{ + int i; + for ( i=0 ; i < mod->brush.pShared->m_nCubemapSamples ; i++ ) + { + mcubemapsample_t *pSample = &mod->brush.pShared->m_pCubemapSamples[i]; + pSample->pTexture->DecrementReferenceCount(); + } +} + + +//----------------------------------------------------------------------------- +// Recomputes surface flags +//----------------------------------------------------------------------------- +void CModelLoader::RecomputeSurfaceFlags( model_t *mod ) +{ + for (int i=0 ; i<mod->brush.pShared->numsubmodels ; i++) + { + model_t *pSubModel = &m_InlineModels[i]; + + // Compute whether this submodel uses material proxies or not + Mod_ComputeBrushModelFlags( pSubModel ); + + // Mark if brush models are in water or not; we'll use this + // for identity brushes. If the brush is not an identity brush, + // then we'll not have to worry. + if ( i != 0 ) + { + MarkBrushModelWaterSurfaces( mod, pSubModel->mins, pSubModel->maxs, pSubModel ); + } + } +} + +//----------------------------------------------------------------------------- +// Setup sub models +//----------------------------------------------------------------------------- +void CModelLoader::SetupSubModels( model_t *mod, CUtlVector<mmodel_t> &list ) +{ + int i; + + m_InlineModels.SetCount( m_worldBrushData.numsubmodels ); + + for (i=0 ; i<m_worldBrushData.numsubmodels ; i++) + { + model_t *starmod; + mmodel_t *bm; + + bm = &list[i]; + starmod = &m_InlineModels[i]; + + *starmod = *mod; + + starmod->brush.firstmodelsurface = bm->firstface; + starmod->brush.nummodelsurfaces = bm->numfaces; + starmod->brush.firstnode = bm->headnode; + if ( starmod->brush.firstnode >= m_worldBrushData.numnodes ) + { + Sys_Error( "Inline model %i has bad firstnode", i ); + } + + VectorCopy(bm->maxs, starmod->maxs); + VectorCopy(bm->mins, starmod->mins); + starmod->radius = bm->radius; + + if (i == 0) + { + *mod = *starmod; + } + else + { + starmod->strName.Format( "*%d", i ); + starmod->fnHandle = g_pFileSystem->FindOrAddFileName( starmod->strName ); + } + } +} + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Map_UnloadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); + mod->nLoadFlags &= ~FMODELLOADER_LOADED; + +#ifndef SWDS + OverlayMgr()->UnloadOverlays(); +#endif + + DeallocateLightingData( &m_worldBrushData ); + +#ifndef SWDS + DispInfo_ReleaseMaterialSystemObjects( mod ); +#endif + + Map_UnloadCubemapSamples( mod ); + +#ifndef SWDS + // Free decals in displacements. + R_DecalTerm( &m_worldBrushData, true ); +#endif + + if ( m_worldBrushData.hDispInfos ) + { + DispInfo_DeleteArray( m_worldBrushData.hDispInfos ); + m_worldBrushData.hDispInfos = NULL; + } + + // Model loader loads world model materials, unload them here + for( int texinfoID = 0; texinfoID < m_worldBrushData.numtexinfo; texinfoID++ ) + { + mtexinfo_t *pTexinfo = &m_worldBrushData.texinfo[texinfoID]; + if ( pTexinfo ) + { + GL_UnloadMaterial( pTexinfo->material ); + } + } + + MaterialSystem_DestroySortinfo(); + + // Don't store any reference to it here + ClearWorldModel(); + Map_SetRenderInfoAllocated( false ); +} + + +//----------------------------------------------------------------------------- +// Computes dimensions + frame count of a material +//----------------------------------------------------------------------------- +static void GetSpriteInfo( const char *pName, bool bIsVideo, int &nWidth, int &nHeight, int &nFrameCount ) +{ + nFrameCount = 1; + nWidth = nHeight = 1; + + // FIXME: The reason we are putting logic related to AVIs here, + // logic which is duplicated in the client DLL related to loading sprites, + // is that this code gets run on dedicated servers also. + IMaterial *pMaterial = NULL; + IVideoMaterial *pVideoMaterial = NULL; + if ( bIsVideo && g_pVideo != NULL ) + { + pVideoMaterial = g_pVideo->CreateVideoMaterial( pName, pName, "GAME", VideoPlaybackFlags::DEFAULT_MATERIAL_OPTIONS, VideoSystem::DETERMINE_FROM_FILE_EXTENSION, false ); + if ( pVideoMaterial ) + { + pVideoMaterial->GetVideoImageSize( &nWidth, &nHeight ); + nFrameCount = pVideoMaterial->GetFrameCount(); + pMaterial = pVideoMaterial->GetMaterial(); + + g_pVideo->DestroyVideoMaterial( pVideoMaterial ); + } + } + else + { + pMaterial = GL_LoadMaterial( pName, TEXTURE_GROUP_OTHER ); + if ( pMaterial ) + { + // Store off our source height, width, frame count + nWidth = pMaterial->GetMappingWidth(); + nHeight = pMaterial->GetMappingHeight(); + nFrameCount = pMaterial->GetNumAnimationFrames(); + } + } + + if ( pMaterial == g_materialEmpty ) + { + DevMsg( "Missing sprite material %s\n", pName ); + } + +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Sprite_LoadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_LOADED ) ); + + mod->nLoadFlags |= FMODELLOADER_LOADED; + + // The hunk data is not used on the server + byte* pSprite = NULL; + +#ifndef SWDS + if ( g_ClientDLL ) + { + int nSize = g_ClientDLL->GetSpriteSize(); + if ( nSize ) + { + pSprite = ( byte * )new byte[ nSize ]; + } + } +#endif + + mod->type = mod_sprite; + mod->sprite.sprite = (CEngineSprite *)pSprite; + + // Fake the bounding box. We need it for PVS culling, and we don't + // know the scale at which the sprite is going to be rendered at + // when we load it + mod->mins = mod->maxs = Vector(0,0,0); + + // Figure out the real load name.. + char loadName[MAX_PATH]; + bool bIsVideo; + BuildSpriteLoadName( mod->strName, loadName, MAX_PATH, bIsVideo ); + GetSpriteInfo( loadName, bIsVideo, mod->sprite.width, mod->sprite.height, mod->sprite.numframes ); + +#ifndef SWDS + if ( g_ClientDLL && mod->sprite.sprite ) + { + g_ClientDLL->InitSprite( mod->sprite.sprite, loadName ); + } +#endif +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Sprite_UnloadModel( model_t *mod ) +{ + Assert( !( mod->nLoadFlags & FMODELLOADER_REFERENCEMASK ) ); + mod->nLoadFlags &= ~FMODELLOADER_LOADED; + + char loadName[MAX_PATH]; + bool bIsVideo; + BuildSpriteLoadName( mod->strName, loadName, sizeof( loadName ), bIsVideo ); + + IMaterial *mat = materials->FindMaterial( loadName, TEXTURE_GROUP_OTHER ); + if ( !IsErrorMaterial( mat ) ) + { + GL_UnloadMaterial( mat ); + } + +#ifndef SWDS + if ( g_ClientDLL && mod->sprite.sprite ) + { + g_ClientDLL->ShutdownSprite( mod->sprite.sprite ); + } +#endif + + delete[] (byte *)mod->sprite.sprite; + mod->sprite.sprite = 0; + mod->sprite.numframes = 0; +} + + +//----------------------------------------------------------------------------- +// Purpose: Flush and reload models. Intended for use when lod changes. +//----------------------------------------------------------------------------- +void CModelLoader::Studio_ReloadModels( CModelLoader::ReloadType_t reloadType ) +{ +#if !defined( SWDS ) + if ( g_ClientDLL ) + g_ClientDLL->InvalidateMdlCache(); +#endif // SWDS + if ( serverGameDLL ) + serverGameDLL->InvalidateMdlCache(); + + // ensure decals have no stale references to invalid lods + modelrender->RemoveAllDecalsFromAllModels(); + + // ensure static props have no stale references to invalid lods + modelrender->ReleaseAllStaticPropColorData(); + + // Flush out the model cache + // Don't flush vcollides since the vphysics system currently + // has no way of indicating they refer to vcollides + g_pMDLCache->Flush( (MDLCacheFlush_t) (MDLCACHE_FLUSH_ALL & (~MDLCACHE_FLUSH_VCOLLIDE)) ); + + // Load the critical pieces now + // The model cache will re-populate as models render + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[ i ].modelpointer; + if ( !IsLoaded( pModel ) ) + continue; + + if ( pModel->type != mod_studio ) + continue; + + MDLCACHE_CRITICAL_SECTION_( g_pMDLCache ); + + // Get the studiohdr into the cache + g_pMDLCache->GetStudioHdr( pModel->studio ); + + // force the collision to load + g_pMDLCache->GetVCollide( pModel->studio ); + } +} + +struct modelsize_t +{ + const char *pName; + int size; +}; + +class CModelsize_Less +{ +public: + bool Less( const modelsize_t& src1, const modelsize_t& src2, void *pCtx ) + { + return ( src1.size < src2.size ); + } +}; + +void CModelLoader::DumpVCollideStats() +{ + int i; + CUtlSortVector< modelsize_t, CModelsize_Less > list; + for ( i = 0; (m_Models).IsUtlMap && i < (m_Models).MaxElement(); ++i ) if ( !(m_Models).IsValidIndex( i ) ) continue; else + { + model_t *pModel = m_Models[ i ].modelpointer; + if ( pModel && pModel->type == mod_studio ) + { + int size = 0; + bool loaded = g_pMDLCache->GetVCollideSize( pModel->studio, &size ); + if ( loaded && size ) + { + modelsize_t elem; + elem.pName = pModel->strName; + elem.size = size; + list.Insert( elem ); + } + } + } + for ( i = m_InlineModels.Count(); --i >= 0; ) + { + vcollide_t *pCollide = CM_VCollideForModel( i+1, &m_InlineModels[i] ); + if ( pCollide ) + { + int size = 0; + for ( int j = 0; j < pCollide->solidCount; j++ ) + { + size += physcollision->CollideSize( pCollide->solids[j] ); + } + size += pCollide->descSize; + if ( size ) + { + modelsize_t elem; + elem.pName = m_InlineModels[i].strName; + elem.size = size; + list.Insert( elem ); + } + } + } + + Msg("VCollides loaded: %d\n", list.Count() ); + int totalVCollideMemory = 0; + for ( i = 0; i < list.Count(); i++ ) + { + Msg("%8d bytes:%s\n", list[i].size, list[i].pName); + totalVCollideMemory += list[i].size; + } + int bboxCount, bboxSize; + physcollision->GetBBoxCacheSize( &bboxSize, &bboxCount ); + Msg( "%8d bytes BBox physics: %d boxes\n", bboxSize, bboxCount ); + totalVCollideMemory += bboxSize; + Msg( "--------------\n%8d bytes total VCollide Memory\n", totalVCollideMemory ); +} + + +//----------------------------------------------------------------------------- +// Is the model loaded? +//----------------------------------------------------------------------------- +bool CModelLoader::IsLoaded( const model_t *mod ) +{ + return (mod->nLoadFlags & FMODELLOADER_LOADED) != 0; +} + +bool CModelLoader::LastLoadedMapHasHDRLighting(void) +{ + return m_bMapHasHDRLighting; +} + +//----------------------------------------------------------------------------- +// Loads a studio model +//----------------------------------------------------------------------------- +void CModelLoader::Studio_LoadModel( model_t *pModel, bool bTouchAllData ) +{ + if ( !mod_touchalldata.GetBool() ) + { + bTouchAllData = false; + } + + // a preloaded model requires specific fixup behavior + bool bPreLoaded = ( pModel->nLoadFlags & FMODELLOADER_LOADED_BY_PRELOAD ) != 0; + + bool bLoadPhysics = true; + if ( pModel->nLoadFlags == FMODELLOADER_STATICPROP ) + { + // this is the first call in loading as a static prop (load bit not set), don't load physics yet + // the next call in causes the physics to load + bLoadPhysics = false; + } + + // mark as loaded and fixed up + pModel->nLoadFlags |= FMODELLOADER_LOADED; + pModel->nLoadFlags &= ~FMODELLOADER_LOADED_BY_PRELOAD; + + if ( !bPreLoaded ) + { + pModel->studio = g_pMDLCache->FindMDL( pModel->strName ); + g_pMDLCache->SetUserData( pModel->studio, pModel ); + + InitStudioModelState( pModel ); + } + + // Get the studiohdr into the cache + studiohdr_t *pStudioHdr = g_pMDLCache->GetStudioHdr( pModel->studio ); + (void) pStudioHdr; + + // a preloaded model alrady has its physics data resident + if ( bLoadPhysics && !bPreLoaded ) + { + // load the collision data now + bool bSynchronous = bTouchAllData; + double t1 = Plat_FloatTime(); + g_pMDLCache->GetVCollideEx( pModel->studio, bSynchronous ); + + double t2 = Plat_FloatTime(); + if ( bSynchronous ) + { + g_flAccumulatedModelLoadTimeVCollideSync += ( t2 - t1 ); + } + else + { + g_flAccumulatedModelLoadTimeVCollideAsync += ( t2 - t1 ); + } + } + + // this forces sync setup operations (materials/shaders) to build out now during load and not at runtime + double t1 = Plat_FloatTime(); + + // should already be NULL, but better safe than sorry + if ( pModel->ppMaterials ) + { + free( pModel->ppMaterials - 1 ); + pModel->ppMaterials = NULL; + } + + IMaterial *pMaterials[128]; + int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), pMaterials ); + + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + // Cache the material pointers so that we don't re-scan all the VMTs on dynamic unload + COMPILE_TIME_ASSERT( sizeof( intptr_t ) == sizeof( IMaterial * ) ); + IMaterial **pMem = (IMaterial**) malloc( (1 + nMaterials) * sizeof( IMaterial* ) ); + *(intptr_t*)pMem = nMaterials; + pModel->ppMaterials = pMem + 1; + for ( int i=0; i<nMaterials; i++ ) + { + pModel->ppMaterials[i] = pMaterials[i]; + } + } + + if ( nMaterials ) + { + for ( int i=0; i<nMaterials; i++ ) + { + pMaterials[i]->IncrementReferenceCount(); + } + // track the refcount bump + pModel->nLoadFlags |= FMODELLOADER_TOUCHED_MATERIALS; + } + + double t2 = Plat_FloatTime(); + g_flAccumulatedModelLoadTimeMaterialNamesOnly += ( t2 - t1 ); + + // a preloaded model must touch its children + if ( bTouchAllData || bPreLoaded ) + { + Mod_TouchAllData( pModel, Host_GetServerCount() ); + } +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Studio_UnloadModel( model_t *pModel ) +{ + // Do not unload models that are still referenced by the dynamic system + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + return; + } + + if ( pModel->nLoadFlags & FMODELLOADER_TOUCHED_MATERIALS ) + { + IMaterial *pMaterials[128]; + int nMaterials = Mod_GetModelMaterials( pModel, ARRAYSIZE( pMaterials ), &pMaterials[0] ); + for ( int j=0; j<nMaterials; j++ ) + { + pMaterials[j]->DecrementReferenceCount(); + } + pModel->nLoadFlags &= ~FMODELLOADER_TOUCHED_MATERIALS; + } + + // leave these flags alone since we are going to return from alt-tab at some point. + // Assert( !( mod->needload & FMODELLOADER_REFERENCEMASK ) ); + pModel->nLoadFlags &= ~( FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD ); + if ( IsX360() ) + { + // 360 doesn't need to keep the reference flags, but the PC does + pModel->nLoadFlags &= ~FMODELLOADER_REFERENCEMASK; + } + +#ifdef DBGFLAG_ASSERT + int nRef = +#endif + g_pMDLCache->Release( pModel->studio ); + + // the refcounts must be as expected, or evil latent bugs will occur + Assert( InEditMode() || ( nRef == 0 ) ); + + if ( pModel->ppMaterials ) + { + free( pModel->ppMaterials - 1 ); + pModel->ppMaterials = NULL; + } + + pModel->studio = MDLHANDLE_INVALID; + pModel->type = mod_bad; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::SetWorldModel( model_t *mod ) +{ + Assert( mod ); + m_pWorldModel = mod; +// host_state.SetWorldModel( mod ); // garymcthack +} + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::ClearWorldModel( void ) +{ + m_pWorldModel = NULL; + memset( &m_worldBrushData, 0, sizeof(m_worldBrushData) ); + m_InlineModels.Purge(); +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::IsWorldModelSet( void ) +{ + return m_pWorldModel ? true : false; +} + +//----------------------------------------------------------------------------- +// Purpose: +// Output : int +//----------------------------------------------------------------------------- +int CModelLoader::GetNumWorldSubmodels( void ) +{ + if ( !IsWorldModelSet() ) + return 0; + + return m_worldBrushData.numsubmodels; +} + +//----------------------------------------------------------------------------- +// Purpose: Check cache or union data for info, reload studio model if needed +// Input : *model - +//----------------------------------------------------------------------------- +void *CModelLoader::GetExtraData( model_t *model ) +{ + if ( !model ) + { + return NULL; + } + + switch ( model->type ) + { + case mod_sprite: + { + // sprites don't use the real cache yet + if ( model->type == mod_sprite ) + { + // The sprite got unloaded. + if ( !( FMODELLOADER_LOADED & model->nLoadFlags ) ) + { + return NULL; + } + + return model->sprite.sprite; + } + } + break; + + case mod_studio: + return g_pMDLCache->GetStudioHdr( model->studio ); + + default: + case mod_brush: + // Should never happen + Assert( 0 ); + break; + }; + + return NULL; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::Map_GetRenderInfoAllocated( void ) +{ + return m_bMapRenderInfoLoaded; +} + + +//----------------------------------------------------------------------------- +// Purpose: +//----------------------------------------------------------------------------- +void CModelLoader::Map_SetRenderInfoAllocated( bool allocated ) +{ + m_bMapRenderInfoLoaded = allocated; +} + + +//----------------------------------------------------------------------------- +// Purpose: +// Input : *mod - +//----------------------------------------------------------------------------- +void CModelLoader::Map_LoadDisplacements( model_t *pModel, bool bRestoring ) +{ + if ( !pModel ) + { + Assert( false ); + return; + } + + Q_FileBase( pModel->strName, m_szLoadName, sizeof( m_szLoadName ) ); + CMapLoadHelper::Init( pModel, m_szLoadName ); + + DispInfo_LoadDisplacements( pModel, bRestoring ); + + CMapLoadHelper::Shutdown(); +} + + +//----------------------------------------------------------------------------- +// Purpose: List the model dictionary +//----------------------------------------------------------------------------- +void CModelLoader::Print( void ) +{ + ConMsg( "Models:\n" ); + FOR_EACH_MAP_FAST( m_Models, i ) + { + model_t *pModel = m_Models[i].modelpointer; + if ( pModel->type == mod_studio || pModel->type == mod_bad ) + { + // studio models have ref counts + // bad models are unloaded models which need to be listed + int refCount = ( pModel->type == mod_studio ) ? g_pMDLCache->GetRef( pModel->studio ) : 0; + ConMsg( "%4d: Flags:0x%8.8x RefCount:%2d %s\n", i, pModel->nLoadFlags, refCount, pModel->strName.String() ); + } + else + { + ConMsg( "%4d: Flags:0x%8.8x %s\n", i, pModel->nLoadFlags, pModel->strName.String() ); + } + } +} + +//----------------------------------------------------------------------------- +// Callback for UpdateOrCreate utility function - swaps a bsp. +//----------------------------------------------------------------------------- +#if defined( _X360 ) +static bool BSPCreateCallback( const char *pSourceName, const char *pTargetName, const char *pPathID, void *pExtraData ) +{ + // load the bsppack dll + IBSPPack *iBSPPack = NULL; + CSysModule *pmodule = g_pFullFileSystem->LoadModule( "bsppack" ); + if ( pmodule ) + { + CreateInterfaceFn factory = Sys_GetFactory( pmodule ); + if ( factory ) + { + iBSPPack = ( IBSPPack * )factory( IBSPPACK_VERSION_STRING, NULL ); + } + } + if( !iBSPPack ) + { + Warning( "Can't load bsppack.dll - unable to swap bsp.\n" ); + return false; + } + + bool bOk = true; + if ( !iBSPPack->SwapBSPFile( g_pFileSystem, pSourceName, pTargetName, IsX360(), ConvertVTFTo360Format, NULL, NULL ) ) + { + bOk = false; + Warning( "Failed to create %s\n", pTargetName ); + } + + Sys_UnloadModule( pmodule ); + + return bOk; +} +#endif + +//----------------------------------------------------------------------------- +// Calls utility function to create .360 version of a file. +//----------------------------------------------------------------------------- +int CModelLoader::UpdateOrCreate( const char *pSourceName, char *pTargetName, int targetLen, bool bForce ) +{ +#if defined( _X360 ) + return ::UpdateOrCreate( pSourceName, pTargetName, targetLen, NULL, BSPCreateCallback, bForce ); +#else + return UOC_NOT_CREATED; +#endif +} + +//----------------------------------------------------------------------------- +// Purpose: Determine if specified .bsp is valid +// Input : *mapname - +// Output : Returns true on success, false on failure. +//----------------------------------------------------------------------------- +bool CModelLoader::Map_IsValid( char const *pMapFile, bool bQuiet /* = false */ ) +{ + static char s_szLastMapFile[MAX_PATH] = { 0 }; + + if ( !pMapFile || !pMapFile[0] ) + { + if ( !bQuiet ) + { + ConMsg( "CModelLoader::Map_IsValid: Empty mapname!!!\n" ); + } + return false; + } + + char szMapFile[MAX_PATH] = { 0 }; + V_strncpy( szMapFile, pMapFile, sizeof( szMapFile ) ); + + if ( IsX360() && !V_stricmp( szMapFile, s_szLastMapFile ) ) + { + // already been checked, no reason to do multiple i/o validations + return true; + } + + // Blacklist some characters + // - Don't allow characters not allowed on all supported platforms for consistency + // - Don't allow quotes or ;"' as defense-in-depth against script abuses (and, no real reason for mapnames to use these) + const char *pBaseFileName = V_UnqualifiedFileName( pMapFile ); + bool bIllegalChar = false; + for (; pBaseFileName && *pBaseFileName; pBaseFileName++ ) + { + // ASCII control characters (codepoints <= 31) illegal in windows filenames + if ( *pBaseFileName <= (char)31 ) + bIllegalChar = true; + + switch ( *pBaseFileName ) + { + // Illegal in windows filenames, don't allow on any platform + case '<': case '>': case ':': case '"': case '/': case '\\': + case '|': case '?': case '*': + bIllegalChar = true; + // Additional special characters in source engine commands, defense-in-depth against things that might be + // composing commands with map names (though they really shouldn't be) + case ';': case '\'': + bIllegalChar = true; + default: break; + } + } + + if ( bIllegalChar ) + { + Assert( !"Map with illegal characters in filename" ); + Warning( "Map with illegal characters in filename\n" ); + return false; + } + + FileHandle_t mapfile; + + if ( IsX360() ) + { + char szMapName360[MAX_PATH]; + UpdateOrCreate( szMapFile, szMapName360, sizeof( szMapName360 ), false ); + V_strcpy_safe( szMapFile, szMapName360 ); + } + + mapfile = g_pFileSystem->OpenEx( szMapFile, "rb", IsX360() ? FSOPEN_NEVERINPACK : 0, "GAME" ); + if ( mapfile != FILESYSTEM_INVALID_HANDLE ) + { + dheader_t header; + memset( &header, 0, sizeof( header ) ); + g_pFileSystem->Read( &header, sizeof( dheader_t ), mapfile ); + g_pFileSystem->Close( mapfile ); + + if ( header.ident == IDBSPHEADER ) + { + if ( header.version >= MINBSPVERSION && header.version <= BSPVERSION ) + { + V_strncpy( s_szLastMapFile, szMapFile, sizeof( s_szLastMapFile ) ); + return true; + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: Map '%s' bsp version %i, expecting %i\n", szMapFile, header.version, BSPVERSION ); + } + + } + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: '%s' is not a valid BSP file\n", szMapFile ); + } + } + } + else + { + if ( !bQuiet ) + { + Warning( "CModelLoader::Map_IsValid: No such map '%s'\n", szMapFile ); + } + } + + // Get outta here if we are checking vidmemstats. + if ( CommandLine()->CheckParm( "-dumpvidmemstats" ) ) + { + Cbuf_AddText( "quit\n" ); + } + + return false; +} + +model_t *CModelLoader::FindModelNoCreate( const char *pModelName ) +{ + FileNameHandle_t fnHandle = g_pFileSystem->FindOrAddFileName( pModelName ); + int i = m_Models.Find( fnHandle ); + if ( i != m_Models.InvalidIndex() ) + { + return m_Models[i].modelpointer; + } + + // not found + return NULL; +} + +modtype_t CModelLoader::GetTypeFromName( const char *pModelName ) +{ + // HACK HACK, force sprites to correctly + const char *pExt = V_GetFileExtension( pModelName ); + if ( pExt ) + { + if ( !V_stricmp( pExt, "spr" ) || !V_stricmp( pExt, "vmt" ) ) + { + return mod_sprite; + } + else if ( !V_stricmp( pExt, "bsp" ) ) + { + return mod_brush; + } + else if ( !V_stricmp( pExt, "mdl" ) ) + { + return mod_studio; + } + else if ( g_pVideo != NULL && g_pVideo->LocateVideoSystemForPlayingFile( pModelName) != VideoSystem::NONE ) // video sprite + { + return mod_sprite; + } + } + + return mod_bad; +} + +int CModelLoader::FindNext( int iIndex, model_t **ppModel ) +{ + if ( iIndex == -1 && m_Models.Count() ) + { + iIndex = m_Models.FirstInorder(); + } + else if ( !m_Models.Count() || !m_Models.IsValidIndex( iIndex ) ) + { + *ppModel = NULL; + return -1; + } + + *ppModel = m_Models[iIndex].modelpointer; + + iIndex = m_Models.NextInorder( iIndex ); + if ( iIndex == m_Models.InvalidIndex() ) + { + // end of list + iIndex = -1; + } + + return iIndex; +} + +void CModelLoader::UnloadModel( model_t *pModel ) +{ + switch ( pModel->type ) + { + case mod_brush: + // Let it free data or call destructors.. + Map_UnloadModel( pModel ); + + // Remove from file system + g_pFileSystem->RemoveSearchPath( pModel->strName, "GAME" ); + + m_szActiveMapName[0] = '\0'; + break; + + case mod_studio: + Studio_UnloadModel( pModel ); + break; + + case mod_sprite: + Sprite_UnloadModel( pModel ); + break; + } +} + +const char *CModelLoader::GetActiveMapName( void ) +{ + return m_szActiveMapName; +} + +model_t *CModelLoader::GetDynamicModel( const char *name, bool bClientOnly ) +{ + if ( !name || !name[0] ) + { + name = "models/empty.mdl"; + } + + Assert( V_strnicmp( name, "models/", 7 ) == 0 && V_strstr( name, ".mdl" ) != NULL ); + + model_t *pModel = FindModel( name ); + Assert( pModel ); + + CDynamicModelInfo &dyn = m_DynamicModels[ m_DynamicModels.Insert( pModel ) ]; // Insert returns existing if key is already set + if ( dyn.m_nLoadFlags == CDynamicModelInfo::INVALIDFLAG ) + { + dyn.m_nLoadFlags = 0; + DynamicModelDebugMsg( "model %p [%s] registered\n", pModel, pModel->strName.String() ); + } + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + + return pModel; +} + +void CModelLoader::UpdateDynamicModelLoadQueue() +{ + if ( mod_dynamicloadpause.GetBool() ) + return; + + static double s_LastDynamicLoadTime = 0.0; + if ( mod_dynamicloadthrottle.GetFloat() > 0 && Plat_FloatTime() < s_LastDynamicLoadTime + mod_dynamicloadthrottle.GetFloat() ) + return; + + if ( m_bDynamicLoadQueueHeadActive ) + { + Assert( m_DynamicModelLoadQueue.Count() >= 1 ); + MaterialLock_t matLock = g_pMaterialSystem->Lock(); // ASDFADFASFASEGAafliejsfjaslaslgsaigas + bool bComplete = g_pQueuedLoader->CompleteDynamicLoad(); + g_pMaterialSystem->Unlock(matLock); + + if ( bComplete ) + { + model_t *pModel = m_DynamicModelLoadQueue[0]; + m_DynamicModelLoadQueue.Remove(0); + m_bDynamicLoadQueueHeadActive = false; + + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + Assert( pModel->type == mod_bad || ( pModel->nLoadFlags & (FMODELLOADER_LOADED | FMODELLOADER_LOADED_BY_PRELOAD) ) ); + (void) LoadModel( pModel, NULL ); + Assert( pModel->type == mod_studio ); + + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::LOADING ); + + dyn.m_nLoadFlags &= ~( CDynamicModelInfo::QUEUED | CDynamicModelInfo::LOADING ); + + g_pMDLCache->LockStudioHdr( pModel->studio ); + dyn.m_nLoadFlags |= CDynamicModelInfo::CLIENTREADY; + + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + + FinishDynamicModelLoadIfReady( &dyn, pModel ); + } + + // do the clean up after we're actually done + // we keep some file cache around to make sure that LoadModel doesn't do blocking load + g_pQueuedLoader->CleanupDynamicLoad(); + + s_LastDynamicLoadTime = Plat_FloatTime(); + } + } + + // If we're not working, and we have work to do, and the queued loader is open for business... + if ( !m_bDynamicLoadQueueHeadActive && m_DynamicModelLoadQueue.Count() > 0 && g_pQueuedLoader->IsFinished() ) + { + model_t *pModel = m_DynamicModelLoadQueue[0]; + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + m_bDynamicLoadQueueHeadActive = true; + + CDynamicModelInfo &dyn = m_DynamicModels[hDyn]; + Assert( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ); + Assert( !(dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ); + dyn.m_nLoadFlags |= CDynamicModelInfo::LOADING; + + // the queued loader is very ... particular about path names. it doesn't like leading "models/" + const char* pName = pModel->strName; + if ( V_strnicmp( pName, "models", 6 ) == 0 && ( pName[6] == '/' || pName[6] == '\\' ) ) + { + pName += 7; + } + + MaterialLock_t matLock = g_pMaterialSystem->Lock(); + g_pQueuedLoader->DynamicLoadMapResource( pName, NULL, NULL, NULL ); + g_pMaterialSystem->Unlock(matLock); + } + else + { + m_DynamicModelLoadQueue.Remove(0); + } + } +} + +void CModelLoader::FinishDynamicModelLoadIfReady( CDynamicModelInfo *pDyn, model_t *pModel ) +{ + CDynamicModelInfo &dyn = *pDyn; + if ( ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) + { + if ( !( dyn.m_nLoadFlags & CDynamicModelInfo::SERVERLOADING ) ) + { + // There ought to be a better way to plumb this through, but this should be ok... + if ( sv.GetDynamicModelsTable() ) + { + int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); + if ( netidx != INVALID_STRING_INDEX ) + { + char nIsLoaded = 1; + sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); + } + } + + DynamicModelDebugMsg( "model %p [%s] loaded\n", pModel, pModel->strName.String() ); + + dyn.m_nLoadFlags |= CDynamicModelInfo::ALLREADY; + + // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back + for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) + { + uintptr_t callbackID = dyn.m_Callbacks[ i ]; + bool bClientOnly = (bool)(callbackID & 1); + IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); + UnregisterModelLoadCallback( pModel, bClientOnly, pCallback ); + pCallback->OnModelLoadComplete( pModel ); + } + } + else + { + // Reverse order; UnregisterModelLoadCallback does a FastRemove that swaps from back + for ( int i = dyn.m_Callbacks.Count()-1; i >= 0; --i ) + { + uintptr_t callbackID = dyn.m_Callbacks[ i ]; + bool bClientOnly = (bool)(callbackID & 1); + IModelLoadCallback* pCallback = ( IModelLoadCallback* )( callbackID & ~1 ); + if ( bClientOnly ) + { + UnregisterModelLoadCallback( pModel, true, pCallback ); + pCallback->OnModelLoadComplete( pModel ); + } + } + } + } +} + +bool CModelLoader::RegisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback, bool bCallImmediatelyIfLoaded ) +{ + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn == m_DynamicModels.InvalidHandle() ) + return false; + + Assert( ((uintptr_t)pCallback & 1) == 0 ); + uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; + + int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + AssertMsg( dyn.m_iRefCount > 0, "RegisterModelLoadCallback requires non-zero model refcount" ); + if ( dyn.m_nLoadFlags & readyFlag ) + { + if ( !bCallImmediatelyIfLoaded ) + return false; + + pCallback->OnModelLoadComplete( pModel ); + } + else + { + if ( !dyn.m_Callbacks.HasElement( callbackID ) ) + { + dyn.m_Callbacks.AddToTail( callbackID ); + // Set registration count for callback pointer + m_RegisteredDynamicCallbacks[ m_RegisteredDynamicCallbacks.Insert( callbackID, 0 ) ]++; + } + } + + return true; +} + +bool CModelLoader::IsDynamicModelLoading( model_t *pModel, bool bClientOnly ) +{ + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + AssertMsg( dyn.m_iRefCount > 0, "dynamic model state cannot be queried with zero refcount" ); + if ( dyn.m_iRefCount > 0 ) + { + int readyFlag = bClientOnly ? CDynamicModelInfo::CLIENTREADY : CDynamicModelInfo::ALLREADY; + return !( dyn.m_nLoadFlags & readyFlag ); + } + } + return false; +} + +void CModelLoader::AddRefDynamicModel( model_t *pModel, bool bClientSideRef ) +{ + extern IVModelInfo* modelinfo; + + UtlHashHandle_t hDyn = m_DynamicModels.Insert( pModel ); + CDynamicModelInfo& dyn = m_DynamicModels[ hDyn ]; + dyn.m_iRefCount++; + dyn.m_iClientRefCount += ( bClientSideRef ? 1 : 0 ); + Assert( dyn.m_iRefCount > 0 ); + + DynamicModelDebugMsg( "model %p [%s] addref %d (%d)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); + + if ( !( dyn.m_nLoadFlags & ( CDynamicModelInfo::QUEUED | CDynamicModelInfo::CLIENTREADY ) ) ) + { + QueueDynamicModelLoad( &dyn, pModel ); + + // Try to kick it off asap if we aren't already busy. + if ( !m_bDynamicLoadQueueHeadActive ) + { + UpdateDynamicModelLoadQueue(); + } + } +} + +void CModelLoader::ReleaseDynamicModel( model_t *pModel, bool bClientSideRef ) +{ + Assert( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ); + UtlHashHandle_t hDyn = m_DynamicModels.Find( pModel ); + Assert( hDyn != m_DynamicModels.InvalidHandle() ); + if ( hDyn != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ hDyn ]; + Assert( dyn.m_iRefCount > 0 ); + if ( dyn.m_iRefCount > 0 ) + { + DynamicModelDebugMsg( "model %p [%s] release %d (%dc)\n", pModel, pModel->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount ); + dyn.m_iRefCount--; + dyn.m_iClientRefCount -= ( bClientSideRef ? 1 : 0 ); + Assert( dyn.m_iClientRefCount >= 0 ); + if ( dyn.m_iClientRefCount < 0 ) + dyn.m_iClientRefCount = 0; + dyn.m_uLastTouchedMS_Div256 = Plat_MSTime() >> 8; + } + } +} + +void CModelLoader::UnregisterModelLoadCallback( model_t *pModel, bool bClientOnly, IModelLoadCallback *pCallback ) +{ + Assert( ((uintptr_t)pCallback & 1) == 0 ); + uintptr_t callbackID = (uintptr_t)pCallback | (uintptr_t)bClientOnly; + if ( int *pCallbackRegistrationCount = m_RegisteredDynamicCallbacks.GetPtr( callbackID ) ) + { + if ( pModel ) + { + UtlHashHandle_t i = m_DynamicModels.Find( pModel ); + if ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ i ]; + if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) + { + if ( dyn.m_Callbacks.Count() == 0 ) + { + dyn.m_Callbacks.Purge(); + } + if ( --(*pCallbackRegistrationCount) == 0 ) + { + m_RegisteredDynamicCallbacks.Remove( callbackID ); + return; + } + } + } + } + else + { + for ( UtlHashHandle_t i = m_DynamicModels.FirstHandle(); i != m_DynamicModels.InvalidHandle(); i = m_DynamicModels.NextHandle(i) ) + { + CDynamicModelInfo &dyn = m_DynamicModels[ i ]; + if ( dyn.m_Callbacks.FindAndFastRemove( callbackID ) ) + { + if ( dyn.m_Callbacks.Count() == 0 ) + { + dyn.m_Callbacks.Purge(); + } + if ( --(*pCallbackRegistrationCount) == 0 ) + { + m_RegisteredDynamicCallbacks.Remove( callbackID ); + return; + } + } + } + } + } +} + +void CModelLoader::QueueDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) +{ + Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); + // Client-side entities have priority over server-side entities + // because they are more likely to be used in UI elements. --henryg + if ( dyn->m_iClientRefCount > 0 && m_DynamicModelLoadQueue.Count() > 1 ) + { + m_DynamicModelLoadQueue.InsertAfter( 0, mod ); + } + else + { + m_DynamicModelLoadQueue.AddToTail( mod ); + } + dyn->m_nLoadFlags |= CDynamicModelInfo::QUEUED; + mod->nLoadFlags |= ( dyn->m_iClientRefCount > 0 ? FMODELLOADER_DYNCLIENT : FMODELLOADER_DYNSERVER ); +} + +bool CModelLoader::CancelDynamicModelLoad( CDynamicModelInfo *dyn, model_t *mod ) +{ + int i = m_DynamicModelLoadQueue.Find( mod ); + Assert( (i < 0) == !(dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED) ); + if ( i >= 0 ) + { + if ( i == 0 && m_bDynamicLoadQueueHeadActive ) + { + Assert( dyn->m_nLoadFlags & CDynamicModelInfo::LOADING ); + // can't remove head of queue + return false; + } + else + { + Assert( dyn->m_nLoadFlags & CDynamicModelInfo::QUEUED ); + Assert( !(dyn->m_nLoadFlags & CDynamicModelInfo::LOADING) ); + m_DynamicModelLoadQueue.Remove( i ); + dyn->m_nLoadFlags &= ~CDynamicModelInfo::QUEUED; + mod->nLoadFlags &= ~FMODELLOADER_DYNAMIC; + return true; + } + } + return false; +} + +void CModelLoader::InternalUpdateDynamicModels( bool bIgnoreTime ) +{ + const uint now = Plat_MSTime(); + const uint delay = bIgnoreTime ? 0 : (int)( clamp( mod_dynamicunloadtime.GetFloat(), 1.f, 600.f ) * 1000 ); + + UpdateDynamicModelLoadQueue(); + +#ifdef _DEBUG + extern CNetworkStringTableContainer *networkStringTableContainerServer; + bool bPrevStringTableLockState = networkStringTableContainerServer->Lock( false ); +#endif + + // Scan for models to unload. TODO: accelerate with a "models to potentially unload" list? + UtlHashHandle_t i = m_DynamicModels.FirstHandle(); + while ( i != m_DynamicModels.InvalidHandle() ) + { + model_t *pModel = m_DynamicModels.Key( i ); + CDynamicModelInfo& dyn = m_DynamicModels[ i ]; + + // UNLOAD THIS MODEL if zero refcount and not currently loading, and either timed out or never loaded + if ( dyn.m_iRefCount <= 0 && !(dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) && + ( ( now - (dyn.m_uLastTouchedMS_Div256 << 8) ) >= delay || !( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) ) ) + { + // Remove from load queue + if ( dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED ) + { + if ( !CancelDynamicModelLoad( &dyn, pModel ) ) + { + // Couldn't remove from queue, advance to next entry and do not remove + i = m_DynamicModels.NextHandle(i); + continue; + } + } + + // Unlock studiohdr_t + if ( dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY ) + { + g_pMDLCache->UnlockStudioHdr( pModel->studio ); + } + + // There ought to be a better way to plumb this through, but this should be ok... + if ( sv.GetDynamicModelsTable() ) + { + int netidx = sv.GetDynamicModelsTable()->FindStringIndex( pModel->strName ); + if ( netidx != INVALID_STRING_INDEX ) + { + char nIsLoaded = 0; + sv.GetDynamicModelsTable()->SetStringUserData( netidx, 1, &nIsLoaded ); + } + } + + if ( pModel->nLoadFlags & FMODELLOADER_DYNAMIC ) + { + pModel->nLoadFlags &= ~FMODELLOADER_DYNAMIC; + // Actually unload the model if all system references are gone + if ( pModel->nLoadFlags & FMODELLOADER_REFERENCEMASK ) + { + DynamicModelDebugMsg( "model %p [%s] unload - deferred: non-dynamic reference\n", pModel, pModel->strName.String() ); + } + else + { + DynamicModelDebugMsg( "model %p [%s] unload\n", pModel, pModel->strName.String() ); + + Studio_UnloadModel( pModel ); + + if ( mod_dynamicunloadtextures.GetBool() ) + { + materials->UncacheUnusedMaterials( false ); + } + } + } + + // Remove from table, advance to next entry + i = m_DynamicModels.RemoveAndAdvance(i); + continue; + } + + // Advance to next entry in table + i = m_DynamicModels.NextHandle(i); + } + +#ifdef _DEBUG + networkStringTableContainerServer->Lock( bPrevStringTableLockState ); +#endif +} + +void CModelLoader::Client_OnServerModelStateChanged( model_t *pModel, bool bServerLoaded ) +{ +#ifndef SWDS + // Listen server don't distinguish between server and client ready, never use SERVERLOADING flag + if ( sv.IsActive() ) + return; + + UtlHashHandle_t i = m_DynamicModels.Find( pModel ); + if ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[i]; + if ( !bServerLoaded ) + { + if ( dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY ) + DynamicModelDebugMsg( "dynamic model [%s] loaded on client but not server! is this bad? unknown...", pModel->strName.String() ); + dyn.m_nLoadFlags &= ~CDynamicModelInfo::ALLREADY; + dyn.m_nLoadFlags |= CDynamicModelInfo::SERVERLOADING; + } + else + { + dyn.m_nLoadFlags &= ~CDynamicModelInfo::SERVERLOADING; + FinishDynamicModelLoadIfReady( &dyn, pModel ); + } + } +#endif +} + +void CModelLoader::ForceUnloadNonClientDynamicModels() +{ + UtlHashHandle_t i = m_DynamicModels.FirstHandle(); + while ( i != m_DynamicModels.InvalidHandle() ) + { + CDynamicModelInfo &dyn = m_DynamicModels[i]; + dyn.m_iRefCount = dyn.m_iClientRefCount; + i = m_DynamicModels.NextHandle( i ); + } + + // Flush everything + InternalUpdateDynamicModels( true ); +} + + +// reconstruct the ambient lighting for a leaf at the given position in worldspace +void Mod_LeafAmbientColorAtPos( Vector *pOut, const Vector &pos, int leafIndex ) +{ + for ( int i = 0; i < 6; i++ ) + { + pOut[i].Init(); + } + mleafambientindex_t *pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; + if ( !pAmbient->ambientSampleCount && pAmbient->firstAmbientSample ) + { + // this leaf references another leaf, move there (this leaf is a solid leaf so it borrows samples from a neighbor) + leafIndex = pAmbient->firstAmbientSample; + pAmbient = &host_state.worldbrush->m_pLeafAmbient[leafIndex]; + } + int count = pAmbient->ambientSampleCount; + if ( count > 0 ) + { + int start = host_state.worldbrush->m_pLeafAmbient[leafIndex].firstAmbientSample; + mleafambientlighting_t *pSamples = host_state.worldbrush->m_pAmbientSamples + start; + mleaf_t *pLeaf = &host_state.worldbrush->leafs[leafIndex]; + float totalFactor = 0; + for ( int i = 0; i < count; i++ ) + { + // do an inverse squared distance weighted average of the samples to reconstruct + // the original function + + // the sample positions are packed as leaf bounds fractions, compute + Vector samplePos = pLeaf->m_vecCenter - pLeaf->m_vecHalfDiagonal; + samplePos.x += float(pSamples[i].x) * pLeaf->m_vecHalfDiagonal.x * (2.0f / 255.0f); + samplePos.y += float(pSamples[i].y) * pLeaf->m_vecHalfDiagonal.y * (2.0f / 255.0f); + samplePos.z += float(pSamples[i].z) * pLeaf->m_vecHalfDiagonal.z * (2.0f / 255.0f); + + float dist = (samplePos - pos).LengthSqr(); + float factor = 1.0f / (dist + 1.0f); + totalFactor += factor; + for ( int j = 0; j < 6; j++ ) + { + Vector v; + ColorRGBExp32ToVector( pSamples[i].cube.m_Color[j], v ); + pOut[j] += v * factor; + } + } + for ( int i = 0; i < 6; i++ ) + { + pOut[i] *= (1.0f / totalFactor); + } + } +} + +#if defined( WIN32 ) +int ComputeSize( studiohwdata_t *hwData, int *numVerts, int *pTriCount, bool onlyTopLod = false ) +{ + unsigned size = 0; + Assert(hwData && numVerts); + int max_lod = (onlyTopLod ? 1 : hwData->m_NumLODs); + *pTriCount = 0; + for ( int i=0; i < max_lod; i++ ) + { + studioloddata_t *pLOD = &hwData->m_pLODs[i]; + for ( int j = 0; j < hwData->m_NumStudioMeshes; j++ ) + { + studiomeshdata_t *pMeshData = &pLOD->m_pMeshData[j]; + for ( int k = 0; k < pMeshData->m_NumGroup; k++ ) + { + studiomeshgroup_t *pMeshGroup = &pMeshData->m_pMeshGroup[k]; + IMesh* mesh = pMeshGroup->m_pMesh; + size += mesh->ComputeMemoryUsed(); // Size of VB and IB + size += 2*pMeshGroup->m_NumVertices; // Size of m_pGroupIndexToMeshIndex[] array + *numVerts += mesh->VertexCount(); + Assert( mesh->VertexCount() == pMeshGroup->m_NumVertices ); + for ( int l = 0; l < pMeshGroup->m_NumStrips; ++l ) + { + OptimizedModel::StripHeader_t *pStripData = &pMeshGroup->m_pStripData[l]; + *pTriCount += pStripData->numIndices / 3; + } + } + } + } + return size; +} + +// APSFIXME: needs to only do models that are resident, sizes might be wrong, i.e lacking compressed vert state? +CON_COMMAND_F( model_list, "Dump model list to file", FCVAR_CHEAT | FCVAR_DONTRECORD ) +{ + // don't run this on dedicated servers + if ( sv.IsDedicated() ) + return; + + if ( g_pFileSystem ) + { + FileHandle_t fileHandle = g_pFileSystem->Open( "model_list.csv", "wt", "GAME" ); + + if ( fileHandle ) + { + const char *substring = NULL; + if ( args.ArgC() > 1 ) + { + substring = args[1]; + } + + g_pFileSystem->FPrintf( fileHandle, "name,dataSize,numVerts,nTriCount,dataSizeLod0,numVertsLod0,nTriCountLod0,numBones,numParts,numLODs,numMeshes\n" ); + + for ( int i = 0; i < modelloader->GetCount(); i++ ) + { + const char* name = "Unknown"; + int dataSizeLod0 = 0; + int dataSize = 0; + int numParts = 0; + int numBones = 0; + int numVertsLod0 = 0; + int numVerts = 0; + int numLODs = 0; + int numMeshes = 0; + int nTriCount = 0; + int nTriCountLod0 = 0; + + model_t* model = modelloader->GetModelForIndex( i ); + if ( model ) + { + // other model types are not interesting + if ( model->type != mod_studio ) + continue; + + name = model->strName; + + if ( substring && substring[0] ) + { + if ( Q_stristr( name, substring ) == NULL ) + continue; + } + + studiohwdata_t *hwData = g_pMDLCache->GetHardwareData( model->studio ); + if ( hwData ) + { + numMeshes = hwData->m_NumStudioMeshes; + numLODs = hwData->m_NumLODs; + dataSize = ComputeSize( hwData, &numVerts, &nTriCount, false ); // Size of vertex data + dataSizeLod0 = ComputeSize( hwData, &numVertsLod0, &nTriCountLod0, true ); + } + + studiohdr_t *pStudioHdr = (studiohdr_t *)modelloader->GetExtraData( model ); + dataSize += pStudioHdr->length; // Size of MDL file + numBones = pStudioHdr->numbones; + numParts = pStudioHdr->numbodyparts; + + g_pFileSystem->FPrintf( fileHandle, "%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d\n", + name, dataSize, numVerts, nTriCount, dataSizeLod0, numVertsLod0, nTriCountLod0, numBones, numParts, numLODs, numMeshes ); + } + } + + g_pFileSystem->Close( fileHandle ); + Msg( "Created \"model_list.csv\" in the game folder.\n" ); + } + } +} +#endif // WIN32 + + + +CON_COMMAND_F( mod_dynamicmodeldebug, "debug spew for dynamic model loading", FCVAR_HIDDEN | FCVAR_DONTRECORD ) +{ + ((CModelLoader*)modelloader)->DebugPrintDynamicModels(); +} + +#include "server.h" +#ifndef SWDS +#include "client.h" +#endif +void CModelLoader::DebugPrintDynamicModels() +{ + Msg( "network table (server):\n" ); + if ( sv.GetDynamicModelsTable() ) + { + for ( int i = 0; i < sv.GetDynamicModelsTable()->GetNumStrings(); ++i ) + { + int dummy = 0; + char* data = (char*) sv.GetDynamicModelsTable()->GetStringUserData( i, &dummy ); + bool bLoadedOnServer = !(data && dummy && data[0] == 0); + Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', sv.GetDynamicModelsTable()->GetString(i) ); + } + } + +#ifndef SWDS + Msg( "\nnetwork table (client):\n" ); + if ( cl.m_pDynamicModelsTable ) + { + for ( int i = 0; i < cl.m_pDynamicModelsTable->GetNumStrings(); ++i ) + { + int dummy = 0; + char* data = (char*) cl.m_pDynamicModelsTable->GetStringUserData( i, &dummy ); + bool bLoadedOnServer = !(data && dummy && data[0] == 0); + Msg( "%3i: %c %s\n", i, bLoadedOnServer ? '*' : ' ', cl.m_pDynamicModelsTable->GetString(i) ); + } + } +#endif + + extern IVModelInfo *modelinfo; + extern IVModelInfoClient *modelinfoclient; + Msg( "\ndynamic models:\n" ); + for ( UtlHashHandle_t h = m_DynamicModels.FirstHandle(); h != m_DynamicModels.InvalidHandle(); h = m_DynamicModels.NextHandle(h) ) + { + CDynamicModelInfo &dyn = m_DynamicModels[h]; + int idx = modelinfo->GetModelIndex( m_DynamicModels.Key(h)->strName ); +#ifndef SWDS + if ( idx == -1 ) idx = modelinfoclient->GetModelIndex( m_DynamicModels.Key(h)->strName ); +#endif + Msg( "%d (%d%c): %s [ref: %d (%dc)] %s%s%s%s\n", idx, ((-2 - idx) >> 1), (idx & 1) ? 'c' : 's', + m_DynamicModels.Key(h)->strName.String(), dyn.m_iRefCount, dyn.m_iClientRefCount, + (dyn.m_nLoadFlags & CDynamicModelInfo::QUEUED) ? " QUEUED" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::LOADING) ? " LOADING" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::CLIENTREADY) ? " CLIENTREADY" : "", + (dyn.m_nLoadFlags & CDynamicModelInfo::ALLREADY) ? " ALLREADY" : "" ); + } +} |