summaryrefslogtreecommitdiff
path: root/engine/modelloader.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /engine/modelloader.cpp
downloadarchived-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.cpp6232
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" : "" );
+ }
+}