596 lines
12 KiB
596 lines
12 KiB
/* |
|
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 "enginefeatures.h" |
|
#include "client.h" |
|
#include "server.h" // LUMP_ error codes |
|
|
|
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; |
|
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 INITALIZE\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" ); |
|
|
|
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 ) |
|
{ |
|
model_t *mod; |
|
int i; |
|
|
|
for( i = 0, mod = mod_known; i < mod_numknown; i++, mod++ ) |
|
Mod_FreeModel( mod ); |
|
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( "Mod_ForName: MAX_MODELS limit exceeded\n" ); |
|
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[64]; |
|
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( "Mod_ForName: %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 |
|
================== |
|
*/ |
|
void Mod_PurgeStudioCache( void ) |
|
{ |
|
int i; |
|
|
|
// 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_Alloc( 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_Alloc( 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 ); |
|
} |