mirror of
https://github.com/FWGS/xash3d-fwgs
synced 2024-12-03 15:41:05 +01:00
603 lines
12 KiB
C
603 lines
12 KiB
C
/*
|
|
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 "common.h"
|
|
#include "mod_local.h"
|
|
#include "sprite.h"
|
|
#include "xash3d_mathlib.h"
|
|
#include "alias.h"
|
|
#include "studio.h"
|
|
#include "wadfile.h"
|
|
#include "world.h"
|
|
#include "enginefeatures.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;
|
|
poolhandle_t 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( !COM_CheckStringEmpty( mod->name ) )
|
|
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( !COM_CheckStringEmpty( mod->name ) || mod->name[0] == '*' )
|
|
return;
|
|
|
|
if( Host_IsDedicated() )
|
|
{
|
|
if( svgame.physFuncs.Mod_ProcessUserData != NULL )
|
|
{
|
|
// let the server.dll free custom data
|
|
svgame.physFuncs.Mod_ProcessUserData( mod, false, NULL );
|
|
}
|
|
}
|
|
#if !XASH_DEDICATED
|
|
else
|
|
{
|
|
ref.dllFuncs.Mod_ProcessRenderData( mod, false, NULL );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
================
|
|
Mod_FreeModel
|
|
================
|
|
*/
|
|
void Mod_FreeModel( model_t *mod )
|
|
{
|
|
// already freed?
|
|
if( !mod || !COM_CheckStringEmpty( mod->name ) )
|
|
return;
|
|
|
|
if( mod->type != mod_brush || mod->name[0] != '*' )
|
|
{
|
|
Mod_FreeUserData( mod );
|
|
Mem_FreePool( &mod->mempool );
|
|
}
|
|
|
|
if( mod->type == mod_brush && FBitSet( mod->flags, MODEL_WORLD ) )
|
|
{
|
|
world.shadowdata = NULL;
|
|
world.deluxedata = NULL;
|
|
}
|
|
|
|
memset( mod, 0, sizeof( *mod ));
|
|
}
|
|
|
|
/*
|
|
===============================================================================
|
|
|
|
MODEL INITIALIZE\SHUTDOWN
|
|
|
|
===============================================================================
|
|
*/
|
|
/*
|
|
================
|
|
Mod_Init
|
|
================
|
|
*/
|
|
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;
|
|
|
|
#if !XASH_DEDICATED
|
|
Mod_ReleaseHullPolygons();
|
|
#endif
|
|
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
|
|
|
|
never return NULL
|
|
==================
|
|
*/
|
|
model_t *Mod_FindName( const char *filename, qboolean trackCRC )
|
|
{
|
|
char modname[MAX_QPATH];
|
|
model_t *mod;
|
|
int i;
|
|
|
|
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( !COM_CheckStringEmpty( mod->name ) ) 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];
|
|
fs_offset_t 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( "Could not load model %s from disk\n", tempname );
|
|
else Con_Printf( S_ERROR "Could not load model %s from disk\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:
|
|
// REFTODO: move server-related code here
|
|
loaded = true;
|
|
break;
|
|
case Q1BSP_VERSION:
|
|
case HLBSP_VERSION:
|
|
case QBSP2_VERSION:
|
|
Mod_LoadBrushModel( mod, buf, &loaded );
|
|
// ref.dllFuncs.Mod_LoadModel( mod_brush, mod, buf, &loaded, 0 );
|
|
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 )
|
|
{
|
|
if( world.loading )
|
|
SetBits( mod->flags, MODEL_WORLD ); // mark worldmodel
|
|
|
|
if( Host_IsDedicated() )
|
|
{
|
|
if( svgame.physFuncs.Mod_ProcessUserData != NULL )
|
|
{
|
|
// let the server.dll load custom data
|
|
svgame.physFuncs.Mod_ProcessUserData( mod, true, buf );
|
|
}
|
|
}
|
|
#if !XASH_DEDICATED
|
|
else
|
|
{
|
|
loaded = ref.dllFuncs.Mod_ProcessRenderData( mod, true, buf );
|
|
}
|
|
#endif
|
|
}
|
|
|
|
if( !loaded )
|
|
{
|
|
Mod_FreeModel( mod );
|
|
Mem_Free( buf );
|
|
|
|
if( crash ) Host_Error( "Could not load model %s\n", tempname );
|
|
else Con_Printf( S_ERROR "Could not load model %s\n", tempname );
|
|
|
|
return NULL;
|
|
}
|
|
|
|
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;
|
|
|
|
if( !COM_CheckString( name ))
|
|
return NULL;
|
|
|
|
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 );
|
|
#if !XASH_DEDICATED
|
|
Mod_ReleaseHullPolygons();
|
|
#endif
|
|
// 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];
|
|
fs_offset_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 );
|
|
}
|