/* model.c - modelloader Copyright (C) 2007 Uncle Mike This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. */ #include "mod_local.h" #include "sprite.h" #include "mathlib.h" #include "alias.h" #include "studio.h" #include "wadfile.h" #include "world.h" #include "gl_local.h" #include "features.h" #include "client.h" #include "server.h" static model_info_t mod_crcinfo[MAX_MODELS]; static model_t mod_known[MAX_MODELS]; static int mod_numknown = 0; byte *com_studiocache; // cache for submodels convar_t *mod_studiocache; convar_t *r_wadtextures; convar_t *r_showhull; model_t *loadmodel; /* =============================================================================== MOD COMMON UTILS =============================================================================== */ /* ================ Mod_Modellist_f ================ */ static void Mod_Modellist_f( void ) { int i, nummodels; model_t *mod; Con_Printf( "\n" ); Con_Printf( "-----------------------------------\n" ); for( i = nummodels = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if( !mod->name[0] ) continue; // free slot Con_Printf( "%s\n", mod->name ); nummodels++; } Con_Printf( "-----------------------------------\n" ); Con_Printf( "%i total models\n", nummodels ); Con_Printf( "\n" ); } /* ================ Mod_FreeUserData ================ */ static void Mod_FreeUserData( model_t *mod ) { // ignore submodels and freed models if( !mod->name[0] || mod->name[0] == '*' ) return; if( host.type == HOST_DEDICATED ) { if( svgame.physFuncs.Mod_ProcessUserData != NULL ) { // let the server.dll free custom data svgame.physFuncs.Mod_ProcessUserData( mod, false, NULL ); } } else { if( clgame.drawFuncs.Mod_ProcessUserData != NULL ) { // let the client.dll free custom data clgame.drawFuncs.Mod_ProcessUserData( mod, false, NULL ); } } } /* ================ Mod_FreeModel ================ */ static void Mod_FreeModel( model_t *mod ) { // already freed? if( !mod || !mod->name[0] ) return; if( mod->name[0] != '*' ) Mod_FreeUserData( mod ); // select the properly unloader switch( mod->type ) { case mod_sprite: Mod_UnloadSpriteModel( mod ); break; case mod_studio: Mod_UnloadStudioModel( mod ); break; case mod_brush: Mod_UnloadBrushModel( mod ); break; case mod_alias: Mod_UnloadAliasModel( mod ); break; } memset( mod, 0, sizeof( *mod )); } /* =============================================================================== MODEL INITIALIZE\SHUTDOWN =============================================================================== */ void Mod_Init( void ) { com_studiocache = Mem_AllocPool( "Studio Cache" ); mod_studiocache = Cvar_Get( "r_studiocache", "1", FCVAR_ARCHIVE, "enables studio cache for speedup tracing hitboxes" ); r_wadtextures = Cvar_Get( "r_wadtextures", "0", 0, "completely ignore textures in the bsp-file if enabled" ); r_showhull = Cvar_Get( "r_showhull", "0", 0, "draw collision hulls 1-3" ); Cmd_AddCommand( "mapstats", Mod_PrintWorldStats_f, "show stats for currently loaded map" ); Cmd_AddCommand( "modellist", Mod_Modellist_f, "display loaded models list" ); Mod_ResetStudioAPI (); Mod_InitStudioHull (); } /* ================ Mod_FreeAll ================ */ void Mod_FreeAll( void ) { int i; Mod_ReleaseHullPolygons(); for( i = 0; i < mod_numknown; i++ ) Mod_FreeModel( &mod_known[i] ); mod_numknown = 0; } /* ================ Mod_ClearUserData ================ */ void Mod_ClearUserData( void ) { int i; for( i = 0; i < mod_numknown; i++ ) Mod_FreeUserData( &mod_known[i] ); } /* ================ Mod_Shutdown ================ */ void Mod_Shutdown( void ) { Mod_FreeAll(); Mem_FreePool( &com_studiocache ); } /* =============================================================================== MODELS MANAGEMENT =============================================================================== */ /* ================== Mod_FindName ================== */ model_t *Mod_FindName( const char *filename, qboolean trackCRC ) { char modname[MAX_QPATH]; model_t *mod; int i; if( !COM_CheckString( filename )) return NULL; Q_strncpy( modname, filename, sizeof( modname )); // search the currently loaded models for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) { if( !Q_stricmp( mod->name, modname )) { if( mod->mempool || mod->name[0] == '*' ) mod->needload = NL_PRESENT; else mod->needload = NL_NEEDS_LOADED; return mod; } } // find a free model slot spot for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) if( !mod->name[0] ) break; // this is a valid spot if( i == mod_numknown ) { if( mod_numknown == MAX_MODELS ) Host_Error( "MAX_MODELS limit exceeded (%d)\n", MAX_MODELS ); mod_numknown++; } // copy name, so model loader can find model file Q_strncpy( mod->name, modname, sizeof( mod->name )); if( trackCRC ) mod_crcinfo[i].flags = FCRC_SHOULD_CHECKSUM; else mod_crcinfo[i].flags = 0; mod->needload = NL_NEEDS_LOADED; mod_crcinfo[i].initialCRC = 0; return mod; } /* ================== Mod_LoadModel Loads a model into the cache ================== */ model_t *Mod_LoadModel( model_t *mod, qboolean crash ) { char tempname[MAX_QPATH]; long length = 0; qboolean loaded; byte *buf; model_info_t *p; ASSERT( mod != NULL ); // check if already loaded (or inline bmodel) if( mod->mempool || mod->name[0] == '*' ) { mod->needload = NL_PRESENT; return mod; } ASSERT( mod->needload == NL_NEEDS_LOADED ); // store modelname to show error Q_strncpy( tempname, mod->name, sizeof( tempname )); COM_FixSlashes( tempname ); buf = FS_LoadFile( tempname, &length, false ); if( !buf ) { memset( mod, 0, sizeof( model_t )); if( crash ) Host_Error( "%s couldn't load\n", tempname ); else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); return NULL; } Con_Reportf( "loading %s\n", mod->name ); mod->needload = NL_PRESENT; mod->type = mod_bad; loadmodel = mod; // call the apropriate loader switch( *(uint *)buf ) { case IDSTUDIOHEADER: Mod_LoadStudioModel( mod, buf, &loaded ); break; case IDSPRITEHEADER: Mod_LoadSpriteModel( mod, buf, &loaded, 0 ); break; case IDALIASHEADER: Mod_LoadAliasModel( mod, buf, &loaded ); break; case Q1BSP_VERSION: case HLBSP_VERSION: case QBSP2_VERSION: Mod_LoadBrushModel( mod, buf, &loaded ); break; default: Mem_Free( buf ); if( crash ) Host_Error( "%s has unknown format\n", tempname ); else Con_Printf( S_ERROR "%s has unknown format\n", tempname ); return NULL; } if( !loaded ) { Mod_FreeModel( mod ); Mem_Free( buf ); if( crash ) Host_Error( "%s couldn't load\n", tempname ); else Con_Printf( S_ERROR "%s couldn't load\n", tempname ); return NULL; } else { if( world.loading ) SetBits( mod->flags, MODEL_WORLD ); // mark worldmodel if( host.type == HOST_DEDICATED ) { if( svgame.physFuncs.Mod_ProcessUserData != NULL ) { // let the server.dll load custom data svgame.physFuncs.Mod_ProcessUserData( mod, true, buf ); } } else { if( clgame.drawFuncs.Mod_ProcessUserData != NULL ) { // let the client.dll load custom data clgame.drawFuncs.Mod_ProcessUserData( mod, true, buf ); } } } p = &mod_crcinfo[mod - mod_known]; mod->needload = NL_PRESENT; if( FBitSet( p->flags, FCRC_SHOULD_CHECKSUM )) { CRC32_t currentCRC; CRC32_Init( ¤tCRC ); CRC32_ProcessBuffer( ¤tCRC, buf, length ); currentCRC = CRC32_Final( currentCRC ); if( FBitSet( p->flags, FCRC_CHECKSUM_DONE )) { if( currentCRC != p->initialCRC ) Host_Error( "%s has a bad checksum\n", tempname ); } else { SetBits( p->flags, FCRC_CHECKSUM_DONE ); p->initialCRC = currentCRC; } } Mem_Free( buf ); return mod; } /* ================== Mod_ForName Loads in a model for the given name ================== */ model_t *Mod_ForName( const char *name, qboolean crash, qboolean trackCRC ) { model_t *mod = Mod_FindName( name, trackCRC ); return Mod_LoadModel( mod, crash ); } /* ================== Mod_PurgeStudioCache free studio cache on change level ================== */ static void Mod_PurgeStudioCache( void ) { int i; // refresh hull data SetBits( r_showhull->flags, FCVAR_CHANGED ); Mod_ReleaseHullPolygons(); // release previois map Mod_FreeModel( mod_known ); // world is stuck on slot #0 always // we should release all the world submodels // and clear studio sequences for( i = 1; i < mod_numknown; i++ ) { if( mod_known[i].type == mod_studio ) mod_known[i].submodels = NULL; if( mod_known[i].name[0] == '*' ) Mod_FreeModel( &mod_known[i] ); mod_known[i].needload = NL_UNREFERENCED; } Mem_EmptyPool( com_studiocache ); Mod_ClearStudioCache(); } /* ================== Mod_LoadWorld Loads in the map and all submodels ================== */ model_t *Mod_LoadWorld( const char *name, qboolean preload ) { model_t *pworld; // already loaded? if( !Q_stricmp( mod_known->name, name )) return mod_known; // free sequence files on studiomodels Mod_PurgeStudioCache(); // load the newmap world.loading = true; pworld = Mod_FindName( name, false ); if( preload ) Mod_LoadModel( pworld, true ); world.loading = false; ASSERT( pworld == mod_known ); return pworld; } /* ================== Mod_FreeUnused Purge all unused models ================== */ void Mod_FreeUnused( void ) { model_t *mod; int i; // never tries to release worldmodel for( i = 1, mod = &mod_known[1]; i < mod_numknown; i++, mod++ ) { if( mod->needload == NL_UNREFERENCED && COM_CheckString( mod->name )) Mod_FreeModel( mod ); } } /* =============================================================================== MODEL ROUTINES =============================================================================== */ /* =============== Mod_Calloc =============== */ void *Mod_Calloc( int number, size_t size ) { cache_user_t *cu; if( number <= 0 || size <= 0 ) return NULL; cu = (cache_user_t *)Mem_Calloc( com_studiocache, sizeof( cache_user_t ) + number * size ); cu->data = (void *)cu; // make sure what cu->data is not NULL return cu; } /* =============== Mod_CacheCheck =============== */ void *Mod_CacheCheck( cache_user_t *c ) { return Cache_Check( com_studiocache, c ); } /* =============== Mod_LoadCacheFile =============== */ void Mod_LoadCacheFile( const char *filename, cache_user_t *cu ) { char modname[MAX_QPATH]; size_t size; byte *buf; Assert( cu != NULL ); if( !COM_CheckString( filename )) return; Q_strncpy( modname, filename, sizeof( modname )); COM_FixSlashes( modname ); buf = FS_LoadFile( modname, &size, false ); if( !buf || !size ) Host_Error( "LoadCacheFile: ^1can't load %s^7\n", filename ); cu->data = Mem_Malloc( com_studiocache, size ); memcpy( cu->data, buf, size ); Mem_Free( buf ); } /* =============== Mod_AliasExtradata =============== */ void *Mod_AliasExtradata( model_t *mod ) { if( mod && mod->type == mod_alias ) return mod->cache.data; return NULL; } /* =============== Mod_StudioExtradata =============== */ void *Mod_StudioExtradata( model_t *mod ) { if( mod && mod->type == mod_studio ) return mod->cache.data; return NULL; } /* ================== Mod_ValidateCRC ================== */ qboolean Mod_ValidateCRC( const char *name, CRC32_t crc ) { model_info_t *p; model_t *mod; mod = Mod_FindName( name, true ); p = &mod_crcinfo[mod - mod_known]; if( !FBitSet( p->flags, FCRC_CHECKSUM_DONE )) return true; if( p->initialCRC == crc ) return true; return false; } /* ================== Mod_NeedCRC ================== */ void Mod_NeedCRC( const char *name, qboolean needCRC ) { model_t *mod; model_info_t *p; mod = Mod_FindName( name, true ); p = &mod_crcinfo[mod - mod_known]; if( needCRC ) SetBits( p->flags, FCRC_SHOULD_CHECKSUM ); else ClearBits( p->flags, FCRC_SHOULD_CHECKSUM ); }