You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
6295 lines
187 KiB
6295 lines
187 KiB
//========= 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 |
|
//----------------------------------------------------------------------------- |
|
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; |
|
|
|
int s_MapVersion = 0; |
|
|
|
// 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; |
|
} |
|
|
|
s_MapVersion = s_MapHeader.version; |
|
|
|
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; |
|
|
|
// nillerusr: Fuck you johns |
|
|
|
// 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 ); |
|
} |
|
} |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// 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_RenderSwapBuffers(); |
|
#endif |
|
|
|
if ( bUpdateOffline ) |
|
{ |
|
#ifndef DEDICATED |
|
V_RenderSwapBuffers(); |
|
#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_RenderSwapBuffers(); |
|
} |
|
#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 && |
|
( 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; |
|
} |
|
|
|
switch ( lh.LumpVersion() ) |
|
{ |
|
case LUMP_WORLDLIGHTS_VERSION: |
|
{ |
|
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() ); |
|
break; |
|
} |
|
|
|
case 0: |
|
{ |
|
int nNumWorldLights = lh.LumpSize() / sizeof( dworldlight_version0_t ); |
|
lh.GetMap()->numworldlights = nNumWorldLights; |
|
lh.GetMap()->worldlights = (dworldlight_t *)Hunk_AllocName( nNumWorldLights * sizeof( dworldlight_t ), va( "%s [%s]", lh.GetLoadName(), "worldlights" ) ); |
|
dworldlight_version0_t* RESTRICT pOldWorldLight = reinterpret_cast<dworldlight_version0_t*>( lh.LumpBase() ); |
|
dworldlight_t* RESTRICT pNewWorldLight = lh.GetMap()->worldlights; |
|
|
|
for ( int i = 0; i < nNumWorldLights; i++ ) |
|
{ |
|
pNewWorldLight->origin = pOldWorldLight->origin; |
|
pNewWorldLight->intensity = pOldWorldLight->intensity; |
|
pNewWorldLight->normal = pOldWorldLight->normal; |
|
pNewWorldLight->shadow_cast_offset.Init( 0.0f, 0.0f, 0.0f ); |
|
pNewWorldLight->cluster = pOldWorldLight->cluster; |
|
pNewWorldLight->type = pOldWorldLight->type; |
|
pNewWorldLight->style = pOldWorldLight->style; |
|
pNewWorldLight->stopdot = pOldWorldLight->stopdot; |
|
pNewWorldLight->stopdot2 = pOldWorldLight->stopdot2; |
|
pNewWorldLight->exponent = pOldWorldLight->exponent; |
|
pNewWorldLight->radius = pOldWorldLight->radius; |
|
pNewWorldLight->constant_attn = pOldWorldLight->constant_attn; |
|
pNewWorldLight->linear_attn = pOldWorldLight->linear_attn; |
|
pNewWorldLight->quadratic_attn = pOldWorldLight->quadratic_attn; |
|
pNewWorldLight->flags = pOldWorldLight->flags; |
|
pNewWorldLight->texinfo = pOldWorldLight->texinfo; |
|
pNewWorldLight->owner = pOldWorldLight->owner; |
|
pNewWorldLight++; |
|
pOldWorldLight++; |
|
} |
|
break; |
|
} |
|
|
|
default: |
|
Host_Error( "Invalid worldlight lump version!\n" ); |
|
break; |
|
} |
|
|
|
#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 *)( ( ( ( uintp )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->GetHDREnabled() && 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 |
|
#ifdef PLATFORM_64BITS |
|
msurface1_t *out1 = Hunk_AllocNameAlignedClear< msurface1_t >( count, alignof(msurface1_t), va( "%s [%s]", lh.GetLoadName(), "surface1" ) ); |
|
msurface2_t *out2 = Hunk_AllocNameAlignedClear< msurface2_t >( count, alignof(msurface2_t), va( "%s [%s]", lh.GetLoadName(), "surface2" ) ); |
|
|
|
msurfacelighting_t *pLighting = Hunk_AllocNameAlignedClear< msurfacelighting_t >( count, alignof(msurfacelighting_t), va( "%s [%s]", lh.GetLoadName(), "surfacelighting" ) ); |
|
#else |
|
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" ) ); |
|
#endif |
|
|
|
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->GetHDREnabled() && 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->GetHDREnabled(); //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)(intp)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, intp ) |
|
{ |
|
// 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, (intp)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->GetHDREnabled() && 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->GetHDREnabled() && 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 ); |
|
} |
|
|
|
bool bHaveBspFormatInPath = strstr(szMapFile, ".bsp"); |
|
bool bHaveMapsInPath = strstr(szMapFile, "maps/"); |
|
|
|
if( !bHaveMapsInPath ) |
|
snprintf(szMapFile, sizeof(szMapFile), "maps/%s", pMapFile); |
|
|
|
if( !bHaveBspFormatInPath ) |
|
strncat(szMapFile, ".bsp", sizeof(szMapFile)); |
|
|
|
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" : "" ); |
|
} |
|
}
|
|
|