This repository has been archived on 2022-06-27. You can view files and clone it, but cannot push or open issues or pull requests.
Xash3DArchive/engine/common/model.c

641 lines
13 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 "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" // LUMP_ error codes
static model_t *com_models[MAX_MODELS]; // shared replacement modeltable
static model_t cm_models[MAX_MODELS];
static int cm_nummodels = 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;
Msg( "\n" );
Msg( "-----------------------------------\n" );
for( i = nummodels = 0, mod = cm_models; i < cm_nummodels; i++, mod++ )
{
if( !mod->name[0] )
continue; // free slot
Msg( "%s%s\n", mod->name, (mod->type == mod_bad) ? " (DEFAULTED)" : "" );
nummodels++;
}
Msg( "-----------------------------------\n" );
Msg( "%i total models\n", nummodels );
Msg( "\n" );
}
/*
================
Mod_FreeUserData
================
*/
static void Mod_FreeUserData( model_t *mod )
{
// already freed?
if( !mod || !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;
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;
}
}
/*
===============================================================================
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 ();
}
void Mod_ClearAll( qboolean keep_playermodel )
{
model_t *plr = com_models[MAX_MODELS-1];
model_t *mod;
int i;
for( i = 0, mod = cm_models; i < cm_nummodels; i++, mod++ )
{
if( keep_playermodel && mod == plr )
continue;
Mod_FreeModel( mod );
memset( mod, 0, sizeof( *mod ));
}
// g-cont. may be just leave unchanged?
if( !keep_playermodel ) cm_nummodels = 0;
}
void Mod_ClearUserData( void )
{
int i;
for( i = 0; i < cm_nummodels; i++ )
Mod_FreeUserData( &cm_models[i] );
}
void Mod_Shutdown( void )
{
Mod_ClearAll( false );
Mem_FreePool( &com_studiocache );
}
/*
===============================================================================
MODELS MANAGEMENT
===============================================================================
*/
/*
==================
Mod_FindName
==================
*/
model_t *Mod_FindName( const char *filename, qboolean create )
{
model_t *mod;
char name[64];
int i;
if( !filename || !filename[0] )
return NULL;
if( *filename == '!' ) filename++;
Q_strncpy( name, filename, sizeof( name ));
COM_FixSlashes( name );
// search the currently loaded models
for( i = 0, mod = cm_models; i < cm_nummodels; i++, mod++ )
{
if( !mod->name[0] ) continue;
if( !Q_stricmp( mod->name, name ))
{
// prolonge registration
mod->needload = world.load_sequence;
return mod;
}
}
if( !create ) return NULL;
// find a free model slot spot
for( i = 0, mod = cm_models; i < cm_nummodels; i++, mod++ )
if( !mod->name[0] ) break; // this is a valid spot
if( i == cm_nummodels )
{
if( cm_nummodels == MAX_MODELS )
Host_Error( "Mod_ForName: MAX_MODELS limit exceeded\n" );
cm_nummodels++;
}
// copy name, so model loader can find model file
Q_strncpy( mod->name, name, sizeof( mod->name ));
return mod;
}
/*
==================
Mod_LoadModel
Loads a model into the cache
==================
*/
model_t *Mod_LoadModel( model_t *mod, qboolean crash )
{
char tempname[64];
qboolean loaded;
byte *buf;
if( !mod )
{
if( crash ) Host_Error( "Mod_ForName: NULL model\n" );
else MsgDev( D_ERROR, "Mod_ForName: NULL model\n" );
return NULL;
}
// check if already loaded (or inline bmodel)
if( mod->mempool || mod->name[0] == '*' )
return mod;
// store modelname to show error
Q_strncpy( tempname, mod->name, sizeof( tempname ));
COM_FixSlashes( tempname );
buf = FS_LoadFile( tempname, NULL, false );
if( !buf )
{
memset( mod, 0, sizeof( model_t ));
if( crash ) Host_Error( "Mod_ForName: %s couldn't load\n", tempname );
else MsgDev( D_ERROR, "Mod_ForName: %s couldn't load\n", tempname );
return NULL;
}
MsgDev( D_NOTE, "Mod_LoadModel: %s\n", mod->name );
mod->needload = world.load_sequence; // register mod
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( "Mod_ForName: %s unknown format\n", tempname );
else MsgDev( D_ERROR, "Mod_ForName: %s unknown format\n", tempname );
return NULL;
}
if( !loaded )
{
Mod_FreeModel( mod );
Mem_Free( buf );
if( crash ) Host_Error( "Mod_ForName: %s couldn't load\n", tempname );
else MsgDev( D_ERROR, "Mod_ForName: %s couldn't load\n", tempname );
return NULL;
}
else
{
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 );
}
}
}
Mem_Free( buf );
return mod;
}
/*
==================
Mod_ForName
Loads in a model for the given name
==================
*/
model_t *Mod_ForName( const char *name, qboolean crash )
{
model_t *mod;
mod = Mod_FindName( name, true );
return Mod_LoadModel( mod, crash );
}
/*
==================
Mod_LoadWorld
Loads in the map and all submodels
==================
*/
void Mod_LoadWorld( const char *name, uint *checksum, qboolean multiplayer )
{
int i;
// now replacement table is invalidate
memset( com_models, 0, sizeof( com_models ));
com_models[1] = cm_models; // make link to world
if( !Q_stricmp( cm_models[0].name, name ))
{
// recalc the checksum in force-mode
CRC32_MapFile( &world.checksum, cm_models[0].name, multiplayer );
// singleplayer mode: server already loaded map
if( checksum ) *checksum = world.checksum;
// still have the right version
return;
}
// clear all studio submodels on restart
for( i = 1; i < cm_nummodels; i++ )
{
if( cm_models[i].type == mod_studio )
cm_models[i].submodels = NULL;
else if( cm_models[i].type == mod_brush )
Mod_FreeModel( cm_models + i );
}
// purge all submodels
Mod_FreeModel( &cm_models[0] );
Mem_EmptyPool( com_studiocache );
world.load_sequence++; // now all models are invalid
// load the newmap
world.loading = true;
Mod_ForName( name, true );
CRC32_MapFile( &world.checksum, cm_models[0].name, multiplayer );
world.loading = false;
if( checksum ) *checksum = world.checksum;
}
/*
==================
Mod_FreeUnused
Purge all unused models
==================
*/
void Mod_FreeUnused( void )
{
model_t *mod;
int i;
for( i = 0, mod = cm_models; i < cm_nummodels; i++, mod++ )
{
if( !mod->name[0] ) continue;
if( mod->needload != world.load_sequence )
Mod_FreeModel( mod );
}
}
/*
===============================================================================
MODEL ROUTINES
===============================================================================
*/
/*
===================
Mod_GetType
===================
*/
modtype_t Mod_GetType( int handle )
{
model_t *mod = Mod_Handle( handle );
if( !mod ) return mod_bad;
return mod->type;
}
/*
===================
Mod_GetFrames
===================
*/
void Mod_GetFrames( int handle, int *numFrames )
{
model_t *mod = Mod_Handle( handle );
if( !numFrames ) return;
if( !mod )
{
*numFrames = 1;
return;
}
if( mod->type == mod_brush )
*numFrames = 2; // regular and alternate animation
else *numFrames = mod->numframes;
if( *numFrames < 1 ) *numFrames = 1;
}
/*
===================
Mod_FrameCount
model_t as input
===================
*/
int Mod_FrameCount( model_t *mod )
{
if( !mod ) return 1;
switch( mod->type )
{
case mod_sprite:
case mod_studio:
return mod->numframes;
case mod_brush:
return 2; // regular and alternate animation
default:
return 1;
}
}
/*
===================
Mod_GetBounds
===================
*/
void Mod_GetBounds( int handle, vec3_t mins, vec3_t maxs )
{
model_t *cmod;
if( handle <= 0 ) return;
cmod = Mod_Handle( handle );
if( cmod )
{
if( mins ) VectorCopy( cmod->mins, mins );
if( maxs ) VectorCopy( cmod->maxs, maxs );
}
else
{
MsgDev( D_ERROR, "Mod_GetBounds: NULL model %i\n", handle );
if( mins ) VectorClear( mins );
if( maxs ) VectorClear( maxs );
}
}
/*
===============
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 )
{
byte *buf;
string name;
size_t i, j, size;
Assert( cu != NULL );
if( !filename || !filename[0] ) return;
// eliminate '!' symbol (i'm doesn't know what this doing)
for( i = j = 0; i < Q_strlen( filename ); i++ )
{
if( filename[i] == '!' ) continue;
else if( filename[i] == '\\' ) name[j] = '/';
else name[j] = Q_tolower( filename[i] );
j++;
}
name[j] = '\0';
buf = FS_LoadFile( name, &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_RegisterModel
register model with shared index
===================
*/
qboolean Mod_RegisterModel( const char *name, int index )
{
model_t *mod;
if( index < 0 || index > MAX_MODELS )
return false;
// this array used for acess to servermodels
mod = Mod_ForName( name, false );
com_models[index] = mod;
return ( mod != NULL );
}
/*
===============
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_Handle
==================
*/
model_t *Mod_Handle( int handle )
{
if( handle < 0 || handle >= MAX_MODELS )
{
MsgDev( D_NOTE, "Mod_Handle: bad handle #%i\n", handle );
return NULL;
}
return com_models[handle];
}