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

3042 lines
75 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
#define MAX_SIDE_VERTS 512 // per one polygon
world_static_t world;
byte *mod_base;
byte *com_studiocache; // cache for submodels
static model_t *com_models[MAX_MODELS]; // shared replacement modeltable
static model_t cm_models[MAX_MODELS];
static int cm_nummodels = 0;
static byte visdata[MAX_MAP_LEAFS/8]; // intermediate buffer
int bmodel_version; // global stuff to detect bsp version
char modelname[64]; // short model name (without path and ext)
convar_t *mod_studiocache;
convar_t *mod_allow_materials;
convar_t *r_wadtextures;
static wadlist_t wadlist;
model_t *loadmodel;
model_t *worldmodel;
/*
===============================================================================
MOD COMMON UTILS
===============================================================================
*/
/*
================
Mod_SetupHulls
================
*/
int Mod_ArrayUsage( const char *szItem, int items, int maxitems, int itemsize )
{
float percentage = maxitems ? (items * 100.0f / maxitems) : 0.0f;
Msg( "%-12s %7i/%-7i %7i/%-7i (%4.1f%%)", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage );
if( percentage > 99.9f )
Msg( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Msg( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Msg( "^2VERY FULL!^7\n" );
else Msg( "\n" );
return items * itemsize;
}
/*
================
Mod_SetupHulls
================
*/
int Mod_GlobUsage( const char *szItem, int itemstorage, int maxstorage )
{
float percentage = maxstorage ? (itemstorage * 100.0f / maxstorage) : 0.0f;
Msg( "%-12s [variable] %7i/%-7i (%4.1f%%)", szItem, itemstorage, maxstorage, percentage );
if( percentage > 99.9f )
Msg( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Msg( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Msg( "^2VERY FULL!^7\n" );
else Msg( "\n" );
return itemstorage;
}
/*
=============
Mod_PrintBSPFileSizes
Dumps info about current file
=============
*/
void Mod_PrintBSPFileSizes_f( void )
{
int totalmemory = 0;
model_t *w = worldmodel;
if( !w || !w->numsubmodels )
{
Msg( "No map loaded\n" );
return;
}
Msg( "\n" );
Msg( "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" );
Msg( "------------ --------------- --------------- --------\n" );
totalmemory += Mod_ArrayUsage( "models", w->numsubmodels, MAX_MAP_MODELS, sizeof( dmodel_t ));
totalmemory += Mod_ArrayUsage( "planes", w->numplanes, MAX_MAP_PLANES, sizeof( dplane_t ));
totalmemory += Mod_ArrayUsage( "vertexes", w->numvertexes, MAX_MAP_VERTS, sizeof( dvertex_t ));
totalmemory += Mod_ArrayUsage( "nodes", w->numnodes, MAX_MAP_NODES, sizeof( dnode_t ));
totalmemory += Mod_ArrayUsage( "texinfos", w->numtexinfo, MAX_MAP_TEXINFO, sizeof( dtexinfo_t ));
totalmemory += Mod_ArrayUsage( "faces", w->numsurfaces, MAX_MAP_FACES, sizeof( dface_t ));
if( world.version == XTBSP_VERSION )
totalmemory += Mod_ArrayUsage( "clipnodes", w->numclipnodes, MAX_MAP_CLIPNODES * 3, sizeof( dclipnode_t ));
else
totalmemory += Mod_ArrayUsage( "clipnodes", w->numclipnodes, MAX_MAP_CLIPNODES, sizeof( dclipnode_t ));
totalmemory += Mod_ArrayUsage( "leaves", w->numleafs, MAX_MAP_LEAFS, sizeof( dleaf_t ));
totalmemory += Mod_ArrayUsage( "marksurfaces", w->nummarksurfaces, MAX_MAP_MARKSURFACES, sizeof( dmarkface_t ));
totalmemory += Mod_ArrayUsage( "surfedges", w->numsurfedges, MAX_MAP_SURFEDGES, sizeof( dsurfedge_t ));
totalmemory += Mod_ArrayUsage( "edges", w->numedges, MAX_MAP_EDGES, sizeof( dedge_t ));
totalmemory += Mod_GlobUsage( "texdata", world.texdatasize, MAX_MAP_MIPTEX );
totalmemory += Mod_GlobUsage( "lightdata", world.litdatasize, MAX_MAP_LIGHTING );
totalmemory += Mod_GlobUsage( "deluxemap", world.vecdatasize, MAX_MAP_LIGHTING );
totalmemory += Mod_GlobUsage( "visdata", world.visdatasize, MAX_MAP_VISIBILITY );
totalmemory += Mod_GlobUsage( "entdata", world.entdatasize, MAX_MAP_ENTSTRING );
Msg( "=== Total BSP file data space used: %s ===\n", Q_memprint( totalmemory ));
Msg( "World size ( %g %g %g ) units\n", world.size[0], world.size[1], world.size[2] );
Msg( "Supports transparency world water: %s\n", world.water_alpha ? "Yes" : "No" );
Msg( "original name: ^1%s\n", worldmodel->name );
Msg( "internal name: %s\n", (world.message[0]) ? va( "^2%s", world.message ) : "none" );
}
/*
================
Mod_Modellist_f
================
*/
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_DecompressVis
===================
*/
static void Mod_DecompressVis( const byte *in, const byte *inend, byte *out, byte *outend )
{
byte *outstart = out;
int c;
while( out < outend )
{
if( in == inend )
{
MsgDev( D_WARN, "Mod_DecompressVis: input underrun (decompressed %i of %i output bytes)\n",
(int)(out - outstart), (int)(outend - outstart));
return;
}
c = *in++;
if( c )
{
*out++ = c;
}
else
{
if( in == inend )
{
MsgDev( D_NOTE, "Mod_DecompressVis: input underrun (during zero-run) (decompressed %i of %i output bytes)\n",
(int)(out - outstart), (int)(outend - outstart));
return;
}
for( c = *in++; c > 0; c-- )
{
if( out == outend )
{
MsgDev( D_NOTE, "Mod_DecompressVis: output overrun (decompressed %i of %i output bytes)\n",
(int)(out - outstart), (int)(outend - outstart));
return;
}
*out++ = 0;
}
}
}
}
/*
==================
Mod_PointInLeaf
==================
*/
mleaf_t *Mod_PointInLeaf( const vec3_t p, mnode_t *node )
{
ASSERT( node != NULL );
while( 1 )
{
if( node->contents < 0 )
return (mleaf_t *)node;
node = node->children[PlaneDiff( p, node->plane ) < 0];
}
// never reached
return NULL;
}
/*
==================
Mod_GetPVSForPoint
Returns PVS data for a given point
NOTE: can return NULL
==================
*/
byte *Mod_GetPVSForPoint( const vec3_t p )
{
mnode_t *node;
mleaf_t *leaf = NULL;
ASSERT( worldmodel != NULL );
node = worldmodel->nodes;
while( 1 )
{
if( node->contents < 0 )
{
leaf = (mleaf_t *)node;
break; // we found a leaf
}
node = node->children[PlaneDiff( p, node->plane ) < 0];
}
if( leaf && leaf->cluster >= 0 )
return world.visdata + leaf->cluster * world.visbytes;
return NULL;
}
/*
==================
Mod_FatPVS_RecursiveBSPNode
==================
*/
static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node )
{
int i;
while( node->contents >= 0 )
{
float d = PlaneDiff( org, node->plane );
if( d > radius )
node = node->children[0];
else if( d < -radius )
node = node->children[1];
else
{
// go down both sides
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0] );
node = node->children[1];
}
}
// if this leaf is in a cluster, accumulate the vis bits
if(((mleaf_t *)node)->cluster >= 0 )
{
byte *vis = world.visdata + ((mleaf_t *)node)->cluster * world.visbytes;
for( i = 0; i < visbytes; i++ )
visbuffer[i] |= vis[i];
}
}
/*
==================
Mod_FatPVS_RecursiveBSPNode
Calculates a PVS that is the inclusive or of all leafs
within radius pixels of the given point.
==================
*/
int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis )
{
mleaf_t *leaf = Mod_PointInLeaf( org, worldmodel->nodes );
int bytes = world.visbytes;
bytes = Q_min( bytes, visbytes );
// enable full visibility for some reasons
if( fullvis || !world.visclusters || !leaf || leaf->cluster < 0 )
{
memset( visbuffer, 0xFF, bytes );
return bytes;
}
if( !merge ) memset( visbuffer, 0x00, bytes );
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes );
return bytes;
}
/*
======================================================================
LEAF LISTING
======================================================================
*/
static void Mod_BoxLeafnums_r( leaflist_t *ll, mnode_t *node )
{
int sides;
while( 1 )
{
if( node->contents == CONTENTS_SOLID )
return;
if( node->contents < 0 )
{
mleaf_t *leaf = (mleaf_t *)node;
// it's a leaf!
if( ll->count >= ll->maxcount )
{
ll->overflowed = true;
return;
}
ll->list[ll->count++] = leaf->cluster;
return;
}
sides = BOX_ON_PLANE_SIDE( ll->mins, ll->maxs, node->plane );
if( sides == 1 )
{
node = node->children[0];
}
else if( sides == 2 )
{
node = node->children[1];
}
else
{
// go down both
if( ll->topnode == -1 )
ll->topnode = node - worldmodel->nodes;
Mod_BoxLeafnums_r( ll, node->children[0] );
node = node->children[1];
}
}
}
/*
==================
Mod_BoxLeafnums
==================
*/
int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, short *list, int listsize, int *topnode )
{
leaflist_t ll;
if( !worldmodel ) return 0;
VectorCopy( mins, ll.mins );
VectorCopy( maxs, ll.maxs );
ll.maxcount = listsize;
ll.overflowed = false;
ll.topnode = -1;
ll.list = list;
ll.count = 0;
Mod_BoxLeafnums_r( &ll, worldmodel->nodes );
if( topnode ) *topnode = ll.topnode;
return ll.count;
}
/*
=============
Mod_BoxVisible
Returns true if any leaf in boxspace
is potentially visible
=============
*/
qboolean Mod_BoxVisible( const vec3_t mins, const vec3_t maxs, const byte *visbits )
{
short leafList[MAX_BOX_LEAFS];
int i, count;
if( !visbits || !mins || !maxs )
return true;
count = Mod_BoxLeafnums( mins, maxs, leafList, MAX_BOX_LEAFS, NULL );
for( i = 0; i < count; i++ )
{
if( CHECKVISBIT( visbits, leafList[i] ))
return true;
}
return false;
}
/*
=============
Mod_HeadnodeVisible
=============
*/
qboolean Mod_HeadnodeVisible( mnode_t *node, const byte *visbits, short *lastleaf )
{
if( !node || node->contents == CONTENTS_SOLID )
return false;
if( node->contents < 0 )
{
if( !CHECKVISBIT( visbits, ((mleaf_t *)node)->cluster ))
return false;
if( lastleaf )
*lastleaf = ((mleaf_t *)node)->cluster;
return true;
}
if( Mod_HeadnodeVisible( node->children[0], visbits, lastleaf ))
return true;
if( Mod_HeadnodeVisible( node->children[1], visbits, lastleaf ))
return true;
return false;
}
/*
==================
Mod_AmbientLevels
grab the ambient sound levels for current point
==================
*/
void Mod_AmbientLevels( const vec3_t p, byte *pvolumes )
{
mleaf_t *leaf;
if( !worldmodel || !p || !pvolumes )
return;
leaf = Mod_PointInLeaf( p, worldmodel->nodes );
*(int *)pvolumes = *(int *)leaf->ambient_sound_level;
}
/*
==================
Mod_SampleSizeForFace
return the current lightmap resolution per face
==================
*/
int Mod_SampleSizeForFace( msurface_t *surf )
{
if( !surf || !surf->texinfo || !surf->texinfo->faceinfo )
return LM_SAMPLE_SIZE;
return surf->texinfo->faceinfo->texture_step;
}
/*
==================
Mod_CheckWaterAlphaSupport
converted maps potential may don't
support water transparency
==================
*/
static qboolean Mod_CheckWaterAlphaSupport( void )
{
mleaf_t *leaf;
int i, j;
const byte *pvs;
if( world.visdatasize <= 0 ) return true;
// check all liquid leafs to see if they can see into empty leafs, if any
// can we can assume this map supports r_wateralpha
for( i = 0, leaf = loadmodel->leafs; i < loadmodel->numleafs; i++, leaf++ )
{
if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 )
{
pvs = world.visdata + leaf->cluster * world.visbytes;
for( j = 0; j < loadmodel->numleafs; j++ )
{
if( CHECKVISBIT( pvs, loadmodel->leafs[j].cluster ) && loadmodel->leafs[j].contents == CONTENTS_EMPTY )
return true;
}
}
}
return false;
}
/*
================
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", "1", FCVAR_ARCHIVE, "completely ignore textures in the wad-files if disabled" );
if( host.type == HOST_NORMAL )
mod_allow_materials = Cvar_Get( "host_allow_materials", "0", FCVAR_LATCH|FCVAR_ARCHIVE, "allow HD textures" );
else mod_allow_materials = NULL; // no reason to load HD-textures for dedicated server
Cmd_AddCommand( "mapstats", Mod_PrintBSPFileSizes_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 );
}
/*
===============================================================================
MAP LOADING
===============================================================================
*/
/*
=================
Mod_LoadSubmodels
=================
*/
static void Mod_LoadSubmodels( const dlump_t *l )
{
dmodel_t *in;
dmodel_t *out;
int i, j, count;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadBModel: funny lump size\n" );
count = l->filelen / sizeof( *in );
if( count < 1 ) Host_Error( "Map %s without models\n", loadmodel->name );
if( count > MAX_MAP_MODELS ) Host_Error( "Map %s has too many models\n", loadmodel->name );
// allocate extradata
out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->submodels = out;
loadmodel->numsubmodels = count;
if( world.loading ) world.max_surfaces = 0;
for( i = 0; i < count; i++, in++, out++ )
{
for( j = 0; j < 3; j++ )
{
// spread the mins / maxs by a unit
out->mins[j] = in->mins[j] - 1.0f;
out->maxs[j] = in->maxs[j] + 1.0f;
out->origin[j] = in->origin[j];
}
for( j = 0; j < MAX_MAP_HULLS; j++ )
out->headnode[j] = in->headnode[j];
out->visleafs = in->visleafs;
out->firstface = in->firstface;
out->numfaces = in->numfaces;
if( i == 0 || !world.loading )
continue; // skip the world
if( VectorIsNull( out->origin ))
{
// NOTE: zero origin after recalculating is indicated included origin brush
VectorAverage( out->mins, out->maxs, out->origin );
}
world.max_surfaces = Q_max( world.max_surfaces, out->numfaces );
}
if( world.loading )
world.draw_surfaces = Mem_Alloc( loadmodel->mempool, world.max_surfaces * sizeof( msurface_t* ));
}
/*
=================
Mod_LoadTextures
=================
*/
static void Mod_LoadTextures( const dlump_t *l )
{
dmiptexlump_t *in;
texture_t *tx, *tx2;
texture_t *anims[10];
texture_t *altanims[10];
int num, max, altmax;
char texname[64];
imgfilter_t *filter;
mip_t *mt;
int i, j;
if( world.loading )
{
// release old sky layers first
GL_FreeTexture( tr.solidskyTexture );
GL_FreeTexture( tr.alphaskyTexture );
tr.solidskyTexture = tr.alphaskyTexture = 0;
world.texdatasize = l->filelen;
world.custom_skybox = false;
world.has_mirrors = false;
world.sky_sphere = false;
}
if( !l->filelen )
{
// no textures
loadmodel->textures = NULL;
return;
}
in = (void *)(mod_base + l->fileofs);
loadmodel->numtextures = in->nummiptex;
loadmodel->textures = (texture_t **)Mem_Alloc( loadmodel->mempool, loadmodel->numtextures * sizeof( texture_t* ));
for( i = 0; i < loadmodel->numtextures; i++ )
{
qboolean load_external = false;
qboolean load_external_luma = false;
if( in->dataofs[i] == -1 )
{
// create default texture (some mods requires this)
tx = Mem_Alloc( loadmodel->mempool, sizeof( *tx ));
loadmodel->textures[i] = tx;
Q_strncpy( tx->name, "*default", sizeof( tx->name ));
tx->gl_texturenum = tr.defaultTexture;
tx->width = tx->height = 16;
continue; // missed
}
mt = (mip_t *)((byte *)in + in->dataofs[i] );
if( !mt->name[0] )
{
MsgDev( D_WARN, "unnamed texture in %s\n", loadmodel->name );
Q_snprintf( mt->name, sizeof( mt->name ), "miptex_%i", i );
}
tx = Mem_Alloc( loadmodel->mempool, sizeof( *tx ));
loadmodel->textures[i] = tx;
// convert to lowercase
Q_strnlwr( mt->name, mt->name, sizeof( mt->name ));
Q_strncpy( tx->name, mt->name, sizeof( tx->name ));
filter = R_FindTexFilter( tx->name ); // grab texture filter
tx->width = mt->width;
tx->height = mt->height;
// check for multi-layered sky texture
if( world.loading && !Q_strncmp( mt->name, "sky", 3 ) && mt->width == 256 && mt->height == 128 )
{
if( Mod_AllowMaterials( ))
{
// build standard path: "materials/mapname/texname_solid.tga"
Q_snprintf( texname, sizeof( texname ), "materials/%s/%s_solid.tga", modelname, mt->name );
if( !FS_FileExists( texname, false ))
{
// build common path: "materials/mapname/texname_solid.tga"
Q_snprintf( texname, sizeof( texname ), "materials/common/%s_solid.tga", mt->name );
if( FS_FileExists( texname, false ))
load_external = true;
}
else load_external = true;
if( load_external )
{
tr.solidskyTexture = GL_LoadTexture( texname, NULL, 0, TF_UNCOMPRESSED|TF_NOMIPMAP, NULL );
load_external = false;
}
if( tr.solidskyTexture )
{
// build standard path: "materials/mapname/texname_alpha.tga"
Q_snprintf( texname, sizeof( texname ), "materials/%s/%s_alpha.tga", modelname, mt->name );
if( !FS_FileExists( texname, false ))
{
// build common path: "materials/mapname/texname_alpha.tga"
Q_snprintf( texname, sizeof( texname ), "materials/common/%s_alpha.tga", mt->name );
if( FS_FileExists( texname, false ))
load_external = true;
}
else load_external = true;
if( load_external )
{
tr.alphaskyTexture = GL_LoadTexture( texname, NULL, 0, TF_UNCOMPRESSED|TF_NOMIPMAP, NULL );
load_external = false;
}
}
if( !tr.solidskyTexture || !tr.alphaskyTexture )
{
// couldn't find one of layer
GL_FreeTexture( tr.solidskyTexture );
GL_FreeTexture( tr.alphaskyTexture );
tr.solidskyTexture = tr.alphaskyTexture = 0;
}
}
if( !tr.solidskyTexture && !tr.alphaskyTexture )
R_InitSky( mt, tx ); // fallback to standard sky
if( tr.solidskyTexture && tr.alphaskyTexture )
world.sky_sphere = true;
}
else
{
// texture loading order:
// 1. HQ from disk
// 2. from wad
// 3. internal from map
if( Mod_AllowMaterials( ))
{
if( mt->name[0] == '*' ) mt->name[0] = '!'; // replace unexpected symbol
// build standard path: "materials/mapname/texname.tga"
Q_snprintf( texname, sizeof( texname ), "materials/%s/%s.tga", modelname, mt->name );
if( !FS_FileExists( texname, false ))
{
// build common path: "materials/mapname/texname.tga"
Q_snprintf( texname, sizeof( texname ), "materials/common/%s.tga", mt->name );
if( FS_FileExists( texname, false ))
load_external = true;
}
else load_external = true;
}
if( load_external )
{
tx->gl_texturenum = GL_LoadTexture( texname, NULL, 0, 0, filter );
if( !tx->gl_texturenum ) load_external = false; // for some reasons we can't load HQ texture
else MsgDev( D_NOTE, "loading HQ: %s\n", texname );
}
// trying wad texture (force while r_wadtextures is 1)
if( !load_external && ( r_wadtextures->value || mt->offsets[0] <= 0 ))
{
Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name );
// check wads in reverse order
for( j = wadlist.count - 1; j >= 0; j-- )
{
char *texpath = va( "%s.wad/%s", wadlist.wadnames[j], texname );
if( FS_FileExists( texpath, false ))
{
tx->gl_texturenum = GL_LoadTexture( texpath, NULL, 0, 0, filter );
break;
}
}
}
// HQ failed, wad failed, so use internal texture (if present)
if( mt->offsets[0] > 0 && !tx->gl_texturenum )
{
// NOTE: imagelib detect miptex version by size
// 770 additional bytes is indicated custom palette
int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6);
if( bmodel_version >= HLBSP_VERSION ) size += sizeof( short ) + 768;
Q_snprintf( texname, sizeof( texname ), "#%s.mip", mt->name );
tx->gl_texturenum = GL_LoadTexture( texname, (byte *)mt, size, 0, filter );
}
}
// set the emo-texture for missed
if( !tx->gl_texturenum ) tx->gl_texturenum = tr.defaultTexture;
if( load_external )
{
// build standard luma path: "materials/mapname/texname_luma.tga"
Q_snprintf( texname, sizeof( texname ), "materials/%s/%s_luma.tga", modelname, mt->name );
if( !FS_FileExists( texname, false ))
{
// build common path: "materials/mapname/texname_luma.tga"
Q_snprintf( texname, sizeof( texname ), "materials/common/%s_luma.tga", mt->name );
if( FS_FileExists( texname, false ))
load_external_luma = true;
}
else load_external_luma = true;
}
// check for luma texture
if( R_GetTexture( tx->gl_texturenum )->flags & TF_HAS_LUMA || load_external_luma )
{
if( !load_external_luma )
Q_snprintf( texname, sizeof( texname ), "#%s_luma.mip", mt->name );
else MsgDev( D_NOTE, "loading luma HQ: %s\n", texname );
if( mt->offsets[0] > 0 && !load_external_luma )
{
// NOTE: imagelib detect miptex version by size
// 770 additional bytes is indicated custom palette
int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6);
if( bmodel_version >= HLBSP_VERSION ) size += sizeof( short ) + 768;
tx->fb_texturenum = GL_LoadTexture( texname, (byte *)mt, size, TF_MAKELUMA, NULL );
}
else
{
size_t srcSize = 0;
byte *src = NULL;
// NOTE: we can't loading it from wad as normal because _luma texture doesn't exist
// and not be loaded. But original texture is already loaded and can't be modified
// So load original texture manually and convert it to luma
if( !load_external_luma )
{
// check wads in reverse order
for( j = wadlist.count - 1; j >= 0; j-- )
{
char *texpath = va( "%s.wad/%s.mip", wadlist.wadnames[j], tx->name );
if( FS_FileExists( texpath, false ))
{
src = FS_LoadFile( texpath, &srcSize, false );
break;
}
}
}
// okay, loading it from wad or hi-res version
tx->fb_texturenum = GL_LoadTexture( texname, src, srcSize, TF_MAKELUMA, NULL );
if( src ) Mem_Free( src );
if( !tx->fb_texturenum && load_external_luma )
{
// in case we failed to loading 32-bit luma texture
MsgDev( D_ERROR, "Couldn't load %s\n", texname );
}
}
}
}
// sequence the animations and detail textures
for( i = 0; i < loadmodel->numtextures; i++ )
{
tx = loadmodel->textures[i];
if( !tx || ( tx->name[0] != '-' && tx->name[0] != '+' ))
continue;
if( tx->anim_next )
continue; // already sequenced
// find the number of frames in the animation
memset( anims, 0, sizeof( anims ));
memset( altanims, 0, sizeof( altanims ));
max = tx->name[1];
altmax = 0;
if( max >= '0' && max <= '9' )
{
max -= '0';
altmax = 0;
anims[max] = tx;
max++;
}
else if( max >= 'a' && max <= 'j' )
{
altmax = max - 'a';
max = 0;
altanims[altmax] = tx;
altmax++;
}
else MsgDev( D_ERROR, "Mod_LoadTextures: bad animating texture %s\n", tx->name );
for( j = i + 1; j < loadmodel->numtextures; j++ )
{
tx2 = loadmodel->textures[j];
if( !tx2 || ( tx2->name[0] != '-' && tx2->name[0] != '+' ))
continue;
if( Q_strcmp( tx2->name + 2, tx->name + 2 ))
continue;
num = tx2->name[1];
if( num >= '0' && num <= '9' )
{
num -= '0';
anims[num] = tx2;
if( num + 1 > max )
max = num + 1;
}
else if( num >= 'a' && num <= 'j' )
{
num = num - 'a';
altanims[num] = tx2;
if( num + 1 > altmax )
altmax = num + 1;
}
else MsgDev( D_ERROR, "Mod_LoadTextures: bad animating texture %s\n", tx->name );
}
// link them all together
for( j = 0; j < max; j++ )
{
tx2 = anims[j];
if( !tx2 )
{
MsgDev( D_ERROR, "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name );
tx->anim_total = 0;
break;
}
tx2->anim_total = max * ANIM_CYCLE;
tx2->anim_min = j * ANIM_CYCLE;
tx2->anim_max = (j + 1) * ANIM_CYCLE;
tx2->anim_next = anims[(j + 1) % max];
if( altmax ) tx2->alternate_anims = altanims[0];
}
for( j = 0; j < altmax; j++ )
{
tx2 = altanims[j];
if( !tx2 )
{
MsgDev( D_ERROR, "Mod_LoadTextures: missing frame %i of %s\n", j, tx->name );
tx->anim_total = 0;
break;
}
tx2->anim_total = altmax * ANIM_CYCLE;
tx2->anim_min = j * ANIM_CYCLE;
tx2->anim_max = (j+1) * ANIM_CYCLE;
tx2->anim_next = altanims[(j + 1) % altmax];
if( max ) tx2->alternate_anims = anims[0];
}
}
}
/*
=================
Mod_LoadTexInfo
=================
*/
static mfaceinfo_t *Mod_LoadFaceInfo( const dlump_t *l, int *numfaceinfo )
{
dfaceinfo_t *in;
mfaceinfo_t *out, *faceinfo;
int i, count;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadFaceInfo: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( *in );
faceinfo = out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
for( i = 0; i < count; i++, in++, out++ )
{
Q_strncpy( out->landname, in->landname, sizeof( out->landname ));
out->texture_step = in->texture_step;
out->max_extent = in->max_extent;
out->groupid = in->groupid;
}
*numfaceinfo = count;
return faceinfo;
}
/*
=================
Mod_LoadTexInfo
=================
*/
static void Mod_LoadTexInfo( const dlump_t *l, dextrahdr_t *extrahdr )
{
dtexinfo_t *in;
mtexinfo_t *out;
int miptex;
mfaceinfo_t *fi = NULL;
int fi_count;
int i, j, count;
if( extrahdr != NULL )
fi = Mod_LoadFaceInfo( &extrahdr->lumps[LUMP_FACEINFO], &fi_count );
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadTexInfo: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( *in );
out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->texinfo = out;
loadmodel->numtexinfo = count;
for( i = 0; i < count; i++, in++, out++ )
{
for( j = 0; j < 8; j++ )
out->vecs[0][j] = in->vecs[0][j];
miptex = in->miptex;
if( miptex < 0 || miptex > loadmodel->numtextures )
Host_Error( "Mod_LoadTexInfo: bad miptex number in '%s'\n", loadmodel->name );
out->texture = loadmodel->textures[miptex];
out->flags = in->flags;
// make sure what faceinfo is really exist
if( extrahdr != NULL && in->faceinfo != -1 && in->faceinfo < fi_count )
out->faceinfo = &fi[in->faceinfo];
}
}
/*
=================
Mod_LoadDeluxemap
=================
*/
static void Mod_LoadDeluxemap( void )
{
char path[64];
int iCompare;
byte *in;
if( !world.loading ) return; // only world can have deluxedata
if( !( host.features & ENGINE_LOAD_DELUXEDATA ))
{
world.deluxedata = NULL;
world.vecdatasize = 0;
return;
}
Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname );
// make sure what deluxemap is actual
if( !COM_CompareFileTime( path, loadmodel->name, &iCompare ))
{
world.deluxedata = NULL;
world.vecdatasize = 0;
return;
}
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
MsgDev( D_WARN, "Mod_LoadDeluxemap: %s probably is out of date\n", path );
in = FS_LoadFile( path, &world.vecdatasize, false );
ASSERT( in != NULL );
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
{
MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s is not a deluxemap file\n", path );
world.deluxedata = NULL;
world.vecdatasize = 0;
Mem_Free( in );
return;
}
// skip header bytes
world.vecdatasize -= 8;
if( world.vecdatasize != world.litdatasize )
{
MsgDev( D_ERROR, "Mod_LoadDeluxemap: %s has mismatched size (%i should be %i)\n", path, world.vecdatasize, world.litdatasize );
world.deluxedata = NULL;
world.vecdatasize = 0;
Mem_Free( in );
return;
}
MsgDev( D_INFO, "Mod_LoadDeluxemap: %s loaded\n", path );
world.deluxedata = Mem_Alloc( loadmodel->mempool, world.vecdatasize );
memcpy( world.deluxedata, in + 8, world.vecdatasize );
Mem_Free( in );
}
/*
=================
Mod_LoadLightVecs
=================
*/
static void Mod_LoadLightVecs( const dlump_t *l )
{
byte *in;
in = (void *)(mod_base + l->fileofs);
world.vecdatasize = l->filelen;
if( world.vecdatasize != world.litdatasize )
{
MsgDev( D_ERROR, "Mod_LoadLightVecs: has mismatched size (%i should be %i)\n", world.vecdatasize, world.litdatasize );
world.deluxedata = NULL;
world.vecdatasize = 0;
return;
}
world.deluxedata = Mem_Alloc( loadmodel->mempool, world.vecdatasize );
memcpy( world.deluxedata, in, world.vecdatasize );
MsgDev( D_INFO, "Mod_LoadLightVecs: loaded\n" );
}
/*
=================
Mod_LoadLighting
=================
*/
static void Mod_LoadLighting( const dlump_t *l, dextrahdr_t *extrahdr )
{
byte d, *in;
color24 *out;
int i;
if( !l->filelen )
{
if( world.loading )
{
MsgDev( D_WARN, "map ^2%s^7 has no lighting\n", loadmodel->name );
loadmodel->lightdata = NULL;
world.deluxedata = NULL;
world.vecdatasize = 0;
world.litdatasize = 0;
}
return;
}
in = (void *)(mod_base + l->fileofs);
if( world.loading ) world.litdatasize = l->filelen;
switch( bmodel_version )
{
case Q1BSP_VERSION:
// expand the white lighting data
loadmodel->lightdata = (color24 *)Mem_Alloc( loadmodel->mempool, l->filelen * sizeof( color24 ));
out = loadmodel->lightdata;
for( i = 0; i < l->filelen; i++, out++ )
{
d = *in++;
out->r = d;
out->g = d;
out->b = d;
}
break;
case HLBSP_VERSION:
case XTBSP_VERSION:
// load colored lighting
loadmodel->lightdata = Mem_Alloc( loadmodel->mempool, l->filelen );
memcpy( loadmodel->lightdata, in, l->filelen );
break;
}
if( !world.loading ) return; // only world can have deluxedata (FIXME: what about quake models?)
// not supposed to be load ?
if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
{
world.deluxedata = NULL;
world.vecdatasize = 0;
return;
}
// try to loading deluxemap too
if( extrahdr != NULL )
Mod_LoadLightVecs( &extrahdr->lumps[LUMP_LIGHTVECS] );
else Mod_LoadDeluxemap (); // old method
}
/*
=================
Mod_CalcSurfaceExtents
Fills in surf->texturemins[] and surf->extents[]
=================
*/
static void Mod_CalcSurfaceExtents( msurface_t *surf )
{
float mins[2], maxs[2], val;
int bmins[2], bmaxs[2];
int i, j, e, sample_size;
mtexinfo_t *tex;
mvertex_t *v;
sample_size = Mod_SampleSizeForFace( surf );
tex = surf->texinfo;
mins[0] = mins[1] = 999999;
maxs[0] = maxs[1] = -999999;
for( i = 0; i < surf->numedges; i++ )
{
e = loadmodel->surfedges[surf->firstedge + i];
if( e >= loadmodel->numedges || e <= -loadmodel->numedges )
Host_Error( "Mod_CalcSurfaceExtents: bad edge\n" );
if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
for( j = 0; j < 2; j++ )
{
val = DotProduct( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3];
if( val < mins[j] ) mins[j] = val;
if( val > maxs[j] ) maxs[j] = val;
}
}
for( i = 0; i < 2; i++ )
{
bmins[i] = floor( mins[i] / sample_size );
bmaxs[i] = ceil( maxs[i] / sample_size );
surf->texturemins[i] = bmins[i] * sample_size;
surf->extents[i] = (bmaxs[i] - bmins[i]) * sample_size;
if( !FBitSet( tex->flags, TEX_SPECIAL ) && surf->extents[i] > 4096 )
MsgDev( D_ERROR, "Bad surface extents %i\n", surf->extents[i] );
}
}
/*
=================
Mod_CalcSurfaceBounds
fills in surf->mins and surf->maxs
=================
*/
static void Mod_CalcSurfaceBounds( msurface_t *surf )
{
int i, e;
mvertex_t *v;
ClearBounds( surf->info->mins, surf->info->maxs );
for( i = 0; i < surf->numedges; i++ )
{
e = loadmodel->surfedges[surf->firstedge + i];
if( e >= loadmodel->numedges || e <= -loadmodel->numedges )
Host_Error( "Mod_CalcSurfaceBounds: bad edge\n" );
if( e >= 0 ) v = &loadmodel->vertexes[loadmodel->edges[e].v[0]];
else v = &loadmodel->vertexes[loadmodel->edges[-e].v[1]];
AddPointToBounds( v->position, surf->info->mins, surf->info->maxs );
}
VectorAverage( surf->info->mins, surf->info->maxs, surf->info->origin );
}
/*
=================
Mod_LoadSurfaces
=================
*/
static void Mod_LoadSurfaces( const dlump_t *l )
{
dface_t *in;
msurface_t *out;
mextrasurf_t *info;
int i, j;
int count;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadSurfaces: funny lump size in '%s'\n", loadmodel->name );
count = l->filelen / sizeof( *in );
loadmodel->numsurfaces = count;
loadmodel->surfaces = out = Mem_Alloc( loadmodel->mempool, count * sizeof( msurface_t ));
info = Mem_Alloc( loadmodel->mempool, count * sizeof( mextrasurf_t ));
for( i = 0; i < count; i++, in++, out++, info++ )
{
texture_t *tex;
// setup crosslinks between two parts of msurface_t
out->info = info;
info->surf = out;
if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges )
{
MsgDev( D_ERROR, "Bad surface %i from %i\n", i, count );
continue;
}
out->firstedge = in->firstedge;
out->numedges = in->numedges;
out->flags = 0;
if( in->side ) out->flags |= SURF_PLANEBACK;
out->plane = loadmodel->planes + in->planenum;
out->texinfo = loadmodel->texinfo + in->texinfo;
tex = out->texinfo->texture;
if( !Q_strncmp( tex->name, "sky", 3 ))
out->flags |= (SURF_DRAWTILED|SURF_DRAWSKY);
if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' )
out->flags |= (SURF_DRAWTURB|SURF_DRAWTILED|SURF_NOCULL);
if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 ))
out->flags |= (SURF_DRAWTURB|SURF_DRAWTILED|SURF_NOCULL);
if( !Q_strncmp( tex->name, "scroll", 6 ))
out->flags |= SURF_CONVEYOR;
// g-cont. added a combined conveyor-transparent
if( !Q_strncmp( tex->name, "{scroll", 7 ))
out->flags |= SURF_CONVEYOR|SURF_TRANSPARENT;
// g-cont this texture from decals.wad he-he
// support !reflect for reflected water
if( !Q_strcmp( tex->name, "reflect1" ) || !Q_strncmp( tex->name, "!reflect", 8 ))
{
out->flags |= SURF_REFLECT;
world.has_mirrors = true;
}
if( tex->name[0] == '{' )
out->flags |= SURF_TRANSPARENT;
if( out->texinfo->flags & TEX_SPECIAL )
out->flags |= SURF_DRAWTILED;
Mod_CalcSurfaceBounds( out );
Mod_CalcSurfaceExtents( out );
if( loadmodel->lightdata && in->lightofs != -1 )
{
if( bmodel_version >= HLBSP_VERSION )
out->samples = loadmodel->lightdata + (in->lightofs / 3);
else out->samples = loadmodel->lightdata + in->lightofs;
// if deluxemap is present setup it too
if( world.deluxedata )
out->info->deluxemap = world.deluxedata + (in->lightofs / 3);
}
for( j = 0; j < MAXLIGHTMAPS; j++ )
out->styles[j] = in->styles[j];
if( out->flags & SURF_DRAWTURB )
GL_SubdivideSurface( out ); // cut up polygon for warps
}
}
/*
=================
Mod_LoadVertexes
=================
*/
static void Mod_LoadVertexes( const dlump_t *l )
{
dvertex_t *in;
mvertex_t *out;
int i, count;
in = (void *)( mod_base + l->fileofs );
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadVertexes: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( *in );
loadmodel->numvertexes = count;
out = loadmodel->vertexes = Mem_Alloc( loadmodel->mempool, count * sizeof( mvertex_t ));
if( world.loading ) ClearBounds( world.mins, world.maxs );
for( i = 0; i < count; i++, in++, out++ )
{
VectorCopy( in->point, out->position );
if( world.loading ) AddPointToBounds( in->point, world.mins, world.maxs );
}
if( !world.loading ) return;
VectorSubtract( world.maxs, world.mins, world.size );
for( i = 0; i < 3; i++ )
{
// spread the mins / maxs by a pixel
world.mins[i] -= 1.0f;
world.maxs[i] += 1.0f;
}
}
/*
=================
Mod_LoadEdges
=================
*/
static void Mod_LoadEdges( const dlump_t *l )
{
dedge_t *in;
medge_t *out;
int i, count;
in = (void *)( mod_base + l->fileofs );
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadEdges: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( *in );
loadmodel->edges = out = Mem_Alloc( loadmodel->mempool, count * sizeof( medge_t ));
loadmodel->numedges = count;
for( i = 0; i < count; i++, in++, out++ )
{
out->v[0] = (word)in->v[0];
out->v[1] = (word)in->v[1];
}
}
/*
=================
Mod_LoadSurfEdges
=================
*/
static void Mod_LoadSurfEdges( const dlump_t *l )
{
dsurfedge_t *in;
int count;
in = (void *)( mod_base + l->fileofs );
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadSurfEdges: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( dsurfedge_t );
loadmodel->surfedges = Mem_Alloc( loadmodel->mempool, count * sizeof( dsurfedge_t ));
loadmodel->numsurfedges = count;
memcpy( loadmodel->surfedges, in, count * sizeof( dsurfedge_t ));
}
/*
=================
Mod_LoadMarkSurfaces
=================
*/
static void Mod_LoadMarkSurfaces( const dlump_t *l )
{
dmarkface_t *in;
int i, j, count;
msurface_t **out;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in ))
Host_Error( "Mod_LoadMarkFaces: funny lump size in %s\n", loadmodel->name );
count = l->filelen / sizeof( *in );
loadmodel->marksurfaces = out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->nummarksurfaces = count;
for( i = 0; i < count; i++ )
{
j = in[i];
if( j < 0 || j >= loadmodel->numsurfaces )
Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name );
out[i] = loadmodel->surfaces + j;
}
}
/*
=================
Mod_SetParent
=================
*/
static void Mod_SetParent( mnode_t *node, mnode_t *parent )
{
node->parent = parent;
if( node->contents < 0 ) return; // it's node
Mod_SetParent( node->children[0], node );
Mod_SetParent( node->children[1], node );
}
/*
=================
Mod_LoadNodes
=================
*/
static void Mod_LoadNodes( const dlump_t *l )
{
dnode_t *in;
mnode_t *out;
int i, j, p;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadNodes: funny lump size\n" );
loadmodel->numnodes = l->filelen / sizeof( *in );
if( loadmodel->numnodes < 1 ) Host_Error( "Map %s has no nodes\n", loadmodel->name );
out = loadmodel->nodes = (mnode_t *)Mem_Alloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out ));
for( i = 0; i < loadmodel->numnodes; i++, out++, in++ )
{
for( j = 0; j < 3; j++ )
{
out->minmaxs[j] = in->mins[j];
out->minmaxs[3+j] = in->maxs[j];
}
p = in->planenum;
out->plane = loadmodel->planes + p;
out->firstsurface = in->firstface;
out->numsurfaces = in->numfaces;
for( j = 0; j < 2; j++ )
{
p = in->children[j];
if( p >= 0 ) out->children[j] = loadmodel->nodes + p;
else out->children[j] = (mnode_t *)(loadmodel->leafs + ( -1 - p ));
}
}
// sets nodes and leafs
Mod_SetParent( loadmodel->nodes, NULL );
}
/*
=================
Mod_LoadLeafs
=================
*/
static void Mod_LoadLeafs( const dlump_t *l )
{
dleaf_t *in;
mleaf_t *out;
int i, j, p, count;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadLeafs: funny lump size\n" );
count = l->filelen / sizeof( *in );
if( count < 1 ) Host_Error( "Map %s has no leafs\n", loadmodel->name );
out = (mleaf_t *)Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->leafs = out;
loadmodel->numleafs = count;
if( world.loading )
{
// get visleafs from the submodel data
world.visclusters = loadmodel->submodels[0].visleafs;
world.visbytes = (world.visclusters + 7) >> 3;
world.visdata = (byte *)Mem_Alloc( loadmodel->mempool, world.visclusters * world.visbytes );
// enable full visibility as default
memset( world.visdata, 0xFF, world.visclusters * world.visbytes );
}
for( i = 0; i < count; i++, in++, out++ )
{
for( j = 0; j < 3; j++ )
{
out->minmaxs[j] = in->mins[j];
out->minmaxs[3+j] = in->maxs[j];
}
out->contents = in->contents;
p = in->visofs;
if( world.loading )
{
out->cluster = ( i - 1 ); // solid leaf 0 has no visdata
if( out->cluster >= world.visclusters )
out->cluster = -1;
// ignore visofs errors on leaf 0 (solid)
if( p >= 0 && out->cluster >= 0 && loadmodel->visdata )
{
if( p < world.visdatasize )
{
byte *inrow = loadmodel->visdata + p;
byte *inrowend = loadmodel->visdata + world.visdatasize;
byte *outrow = world.visdata + out->cluster * world.visbytes;
byte *outrowend = world.visdata + (out->cluster + 1) * world.visbytes;
Mod_DecompressVis( inrow, inrowend, outrow, outrowend );
}
else MsgDev( D_WARN, "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i );
}
}
else out->cluster = -1; // no visclusters on bmodels
if( p == -1 ) out->compressed_vis = NULL;
else out->compressed_vis = loadmodel->visdata + p;
for( j = 0; j < 4; j++ )
out->ambient_sound_level[j] = in->ambient_level[j];
out->firstmarksurface = loadmodel->marksurfaces + in->firstmarksurface;
out->nummarksurfaces = in->nummarksurfaces;
// gl underwater warp
if( out->contents != CONTENTS_EMPTY )
{
for( j = 0; j < out->nummarksurfaces; j++ )
{
// underwater surfaces can't have reflection (perfomance)
out->firstmarksurface[j]->flags |= SURF_UNDERWATER;
out->firstmarksurface[j]->flags &= ~SURF_REFLECT;
}
}
}
if( loadmodel->leafs[0].contents != CONTENTS_SOLID )
Host_Error( "Mod_LoadLeafs: Map %s has leaf 0 is not CONTENTS_SOLID\n", loadmodel->name );
// do some final things for world
if( world.loading )
{
// store size of fat pvs
world.fatbytes = (world.visclusters + 31) >> 3;
world.water_alpha = Mod_CheckWaterAlphaSupport();
}
}
/*
=================
Mod_LoadPlanes
=================
*/
static void Mod_LoadPlanes( const dlump_t *l )
{
dplane_t *in;
mplane_t *out;
int i, j, count;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadPlanes: funny lump size\n" );
count = l->filelen / sizeof( *in );
if( count < 1 ) Host_Error( "Map %s has no planes\n", loadmodel->name );
out = (mplane_t *)Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->planes = out;
loadmodel->numplanes = count;
for( i = 0; i < count; i++, in++, out++ )
{
for( j = 0; j < 3; j++ )
{
out->normal[j] = in->normal[j];
if( out->normal[j] < 0.0f )
out->signbits |= 1<<j;
}
if( VectorIsNull( out->normal ))
Host_Error( "Mod_LoadPlanes: bad normal for plane #%i\n", i );
out->dist = in->dist;
out->type = in->type;
}
}
/*
=================
Mod_LoadVisibility
=================
*/
static void Mod_LoadVisibility( const dlump_t *l )
{
// bmodels has no visibility
if( !world.loading ) return;
if( !l->filelen )
{
MsgDev( D_WARN, "map ^2%s^7 has no visibility\n", loadmodel->name );
loadmodel->visdata = NULL;
world.visdatasize = 0;
return;
}
loadmodel->visdata = Mem_Alloc( loadmodel->mempool, l->filelen );
memcpy( loadmodel->visdata, (void *)(mod_base + l->fileofs), l->filelen );
world.visdatasize = l->filelen; // save it for PHS allocation
}
/*
=================
Mod_LoadEntities
=================
*/
static void Mod_LoadEntities( const dlump_t *l )
{
char *pfile;
string keyname;
char token[2048];
char wadstring[2048];
// make sure what we really has terminator
loadmodel->entities = Mem_Alloc( loadmodel->mempool, l->filelen + 1 );
memcpy( loadmodel->entities, mod_base + l->fileofs, l->filelen );
if( !world.loading ) return;
world.entdatasize = l->filelen;
pfile = (char *)loadmodel->entities;
world.message[0] = '\0';
wadlist.count = 0;
// parse all the wads for loading textures in right ordering
while(( pfile = COM_ParseFile( pfile, token )) != NULL )
{
if( token[0] != '{' )
Host_Error( "Mod_LoadEntities: found %s when expecting {\n", token );
while( 1 )
{
// parse key
if(( pfile = COM_ParseFile( pfile, token )) == NULL )
Host_Error( "Mod_LoadEntities: EOF without closing brace\n" );
if( token[0] == '}' ) break; // end of desc
Q_strncpy( keyname, token, sizeof( keyname ));
// parse value
if(( pfile = COM_ParseFile( pfile, token )) == NULL )
Host_Error( "Mod_LoadEntities: EOF without closing brace\n" );
if( token[0] == '}' )
Host_Error( "Mod_LoadEntities: closing brace without data\n" );
if( !Q_stricmp( keyname, "wad" ))
{
char *pszWadFile;
Q_strncpy( wadstring, token, 2046 );
wadstring[2046] = 0;
if( !Q_strchr( wadstring, ';' ))
Q_strcat( wadstring, ";" );
// parse wad pathes
for (pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" ))
{
COM_FixSlashes( pszWadFile );
FS_FileBase( pszWadFile, wadlist.wadnames[wadlist.count++] );
if( wadlist.count >= 256 ) break; // too many wads...
}
}
else if( !Q_stricmp( keyname, "mapversion" ))
world.mapversion = Q_atoi( token );
else if( !Q_stricmp( keyname, "message" ))
Q_strncpy( world.message, token, sizeof( world.message ));
}
return; // all done
}
}
/*
=================
Mod_LoadClipnodes
=================
*/
static void Mod_LoadClipnodes( const dlump_t *l )
{
dclipnode_t *in, *out;
int i, count;
hull_t *hull;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadClipnodes: funny lump size\n" );
count = l->filelen / sizeof( *in );
out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
loadmodel->clipnodes = out;
loadmodel->numclipnodes = count;
// hulls[0] is a point hull, always zeroed
hull = &loadmodel->hulls[1];
hull->clipnodes = out;
hull->firstclipnode = 0;
hull->lastclipnode = count - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull
VectorCopy( host.player_maxs[0], hull->clip_maxs );
hull = &loadmodel->hulls[2];
hull->clipnodes = out;
hull->firstclipnode = 0;
hull->lastclipnode = count - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull
VectorCopy( host.player_maxs[3], hull->clip_maxs );
hull = &loadmodel->hulls[3];
hull->clipnodes = out;
hull->firstclipnode = 0;
hull->lastclipnode = count - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull
VectorCopy( host.player_maxs[1], hull->clip_maxs );
for( i = 0; i < count; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = in->children[0];
out->children[1] = in->children[1];
}
}
/*
=================
Mod_LoadClipnodes31
=================
*/
static void Mod_LoadClipnodes31( const dlump_t *l, const dlump_t *l2, const dlump_t *l3 )
{
dclipnode_t *in, *in2, *in3, *out, *out2, *out3;
int i, count, count2, count3;
hull_t *hull;
in = (void *)(mod_base + l->fileofs);
if( l->filelen % sizeof( *in )) Host_Error( "Mod_LoadClipnodes: funny lump size\n" );
count = l->filelen / sizeof( *in );
out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
in2 = (void *)(mod_base + l2->fileofs);
if( l2->filelen % sizeof( *in2 )) Host_Error( "Mod_LoadClipnodes2: funny lump size\n" );
count2 = l2->filelen / sizeof( *in2 );
out2 = Mem_Alloc( loadmodel->mempool, count2 * sizeof( *out2 ));
in3 = (void *)(mod_base + l3->fileofs);
if( l3->filelen % sizeof( *in3 )) Host_Error( "Mod_LoadClipnodes3: funny lump size\n" );
count3 = l3->filelen / sizeof( *in3 );
out3 = Mem_Alloc( loadmodel->mempool, count3 * sizeof( *out3 ));
loadmodel->clipnodes = out;
loadmodel->numclipnodes = count + count2 + count3;
// hulls[0] is a point hull, always zeroed
hull = &loadmodel->hulls[1];
hull->clipnodes = out;
hull->firstclipnode = 0;
hull->lastclipnode = count - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull
VectorCopy( host.player_maxs[0], hull->clip_maxs );
hull = &loadmodel->hulls[2];
hull->clipnodes = out2;
hull->firstclipnode = 0;
hull->lastclipnode = count2 - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull
VectorCopy( host.player_maxs[3], hull->clip_maxs );
hull = &loadmodel->hulls[3];
hull->clipnodes = out3;
hull->firstclipnode = 0;
hull->lastclipnode = count3 - 1;
hull->planes = loadmodel->planes;
VectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull
VectorCopy( host.player_maxs[1], hull->clip_maxs );
for( i = 0; i < count; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = in->children[0];
out->children[1] = in->children[1];
}
for( i = 0; i < count2; i++, out2++, in2++ )
{
out2->planenum = in2->planenum;
out2->children[0] = in2->children[0];
out2->children[1] = in2->children[1];
}
for( i = 0; i < count3; i++, out3++, in3++ )
{
out3->planenum = in3->planenum;
out3->children[0] = in3->children[0];
out3->children[1] = in3->children[1];
}
}
/*
=================
Mod_FindModelOrigin
routine to detect bmodels with origin-brush
=================
*/
static void Mod_FindModelOrigin( const char *entities, const char *modelname, vec3_t origin )
{
char *pfile;
string keyname;
char token[2048];
qboolean model_found;
qboolean origin_found;
if( !entities || !modelname || !*modelname || !origin )
return;
pfile = (char *)entities;
while(( pfile = COM_ParseFile( pfile, token )) != NULL )
{
if( token[0] != '{' )
Host_Error( "Mod_FindModelOrigin: found %s when expecting {\n", token );
model_found = origin_found = false;
VectorClear( origin );
while( 1 )
{
// parse key
if(( pfile = COM_ParseFile( pfile, token )) == NULL )
Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" );
if( token[0] == '}' ) break; // end of desc
Q_strncpy( keyname, token, sizeof( keyname ));
// parse value
if(( pfile = COM_ParseFile( pfile, token )) == NULL )
Host_Error( "Mod_FindModelOrigin: EOF without closing brace\n" );
if( token[0] == '}' )
Host_Error( "Mod_FindModelOrigin: closing brace without data\n" );
if( !Q_stricmp( keyname, "model" ) && !Q_stricmp( modelname, token ))
model_found = true;
if( !Q_stricmp( keyname, "origin" ))
{
Q_atov( origin, token, 3 );
origin_found = true;
}
}
if( model_found ) break;
}
}
/*
=================
Mod_MakeHull0
Duplicate the drawing hull structure as a clipping hull
=================
*/
static void Mod_MakeHull0( void )
{
mnode_t *in, *child;
dclipnode_t *out;
hull_t *hull;
int i, j, count;
hull = &loadmodel->hulls[0];
in = loadmodel->nodes;
count = loadmodel->numnodes;
out = Mem_Alloc( loadmodel->mempool, count * sizeof( *out ));
hull->clipnodes = out;
hull->firstclipnode = 0;
hull->lastclipnode = count - 1;
hull->planes = loadmodel->planes;
for( i = 0; i < count; i++, out++, in++ )
{
out->planenum = in->plane - loadmodel->planes;
for( j = 0; j < 2; j++ )
{
child = in->children[j];
if( child->contents < 0 )
out->children[j] = child->contents;
else out->children[j] = child - loadmodel->nodes;
}
}
}
/*
=================
Mod_UnloadBrushModel
Release all uploaded textures
=================
*/
void Mod_UnloadBrushModel( model_t *mod )
{
texture_t *tx;
int i;
ASSERT( mod != NULL );
if( mod->type != mod_brush )
return; // not a bmodel
if( mod->name[0] != '*' )
{
for( i = 0; i < mod->numtextures; i++ )
{
tx = mod->textures[i];
if( !tx || tx->gl_texturenum == tr.defaultTexture )
continue; // free slot
GL_FreeTexture( tx->gl_texturenum ); // main texture
GL_FreeTexture( tx->fb_texturenum ); // luma texture
}
Mem_FreePool( &mod->mempool );
}
memset( mod, 0, sizeof( *mod ));
}
/*
=================
Mod_LoadBrushModel
=================
*/
static void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded )
{
int i, j;
int sample_size;
char *ents;
dheader_t *header;
dextrahdr_t *extrahdr;
dmodel_t *bm;
if( loaded ) *loaded = false;
header = (dheader_t *)buffer;
loadmodel->type = mod_brush;
i = header->version;
// BSP31 and BSP30 have different offsets
if( i == XTBSP_VERSION )
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader31_t ));
else extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
switch( i )
{
case 28: // get support for quake1 beta
i = Q1BSP_VERSION;
sample_size = 16;
break;
case Q1BSP_VERSION:
case HLBSP_VERSION:
sample_size = 16;
break;
case XTBSP_VERSION:
sample_size = 8;
break;
default:
MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)", loadmodel->name, i, HLBSP_VERSION );
return;
}
// will be merged later
if( world.loading )
{
world.lm_sample_size = sample_size;
world.version = i;
}
else if( world.lm_sample_size != sample_size )
{
// can't mixing world and bmodels with different sample sizes!
MsgDev( D_ERROR, "%s has wrong version number (%i should be %i)", loadmodel->name, i, world.version );
return;
}
loadmodel->numframes = sample_size; // NOTE: world store sample size into model_t->numframes
bmodel_version = i; // share it
// swap all the lumps
mod_base = (byte *)header;
loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name ));
// make sure what extrahdr is valid
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
extrahdr = NULL; // no extra header
// load into heap
if( header->lumps[LUMP_ENTITIES].fileofs <= 1024 && (header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 )
{
// blue-shift swapped lumps
Mod_LoadEntities( &header->lumps[LUMP_PLANES] );
Mod_LoadPlanes( &header->lumps[LUMP_ENTITIES] );
}
else
{
// normal half-life lumps
Mod_LoadEntities( &header->lumps[LUMP_ENTITIES] );
Mod_LoadPlanes( &header->lumps[LUMP_PLANES] );
}
// Half-Life: alpha version has BSP version 29 and map version 220 (and lightdata is RGB)
if( world.version <= 29 && world.mapversion == 220 && (header->lumps[LUMP_LIGHTING].filelen % 3) == 0 )
world.version = bmodel_version = HLBSP_VERSION;
Mod_LoadSubmodels( &header->lumps[LUMP_MODELS] );
Mod_LoadVertexes( &header->lumps[LUMP_VERTEXES] );
Mod_LoadEdges( &header->lumps[LUMP_EDGES] );
Mod_LoadSurfEdges( &header->lumps[LUMP_SURFEDGES] );
Mod_LoadTextures( &header->lumps[LUMP_TEXTURES] );
Mod_LoadLighting( &header->lumps[LUMP_LIGHTING], extrahdr );
Mod_LoadVisibility( &header->lumps[LUMP_VISIBILITY] );
Mod_LoadTexInfo( &header->lumps[LUMP_TEXINFO], extrahdr );
Mod_LoadSurfaces( &header->lumps[LUMP_FACES] );
Mod_LoadMarkSurfaces( &header->lumps[LUMP_MARKSURFACES] );
Mod_LoadLeafs( &header->lumps[LUMP_LEAFS] );
Mod_LoadNodes( &header->lumps[LUMP_NODES] );
if( bmodel_version == XTBSP_VERSION )
Mod_LoadClipnodes31( &header->lumps[LUMP_CLIPNODES], &header->lumps[LUMP_CLIPNODES2], &header->lumps[LUMP_CLIPNODES3] );
else Mod_LoadClipnodes( &header->lumps[LUMP_CLIPNODES] );
Mod_MakeHull0 ();
ents = loadmodel->entities;
// set up the submodels
for( i = 0; i < mod->numsubmodels; i++ )
{
bm = &mod->submodels[i];
mod->hulls[0].firstclipnode = bm->headnode[0];
for( j = 1; j < MAX_MAP_HULLS; j++ )
{
mod->hulls[j].firstclipnode = bm->headnode[j];
// mod->hulls[j].lastclipnode = mod->numclipnodes - 1;
}
mod->firstmodelsurface = bm->firstface;
mod->nummodelsurfaces = bm->numfaces;
VectorCopy( bm->mins, mod->mins );
VectorCopy( bm->maxs, mod->maxs );
mod->radius = RadiusFromBounds( mod->mins, mod->maxs );
mod->numleafs = bm->visleafs;
mod->flags = 0;
if( i != 0 )
{
// HACKHACK: c2a1 issues
if( !bm->origin[0] && !bm->origin[1] )
SetBits( mod->flags, MODEL_HAS_ORIGIN );
Mod_FindModelOrigin( ents, va( "*%i", i ), bm->origin );
// flag 2 is indicated model with origin brush!
if( !VectorIsNull( bm->origin ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
}
for( j = 0; i != 0 && j < mod->nummodelsurfaces; j++ )
{
msurface_t *surf = mod->surfaces + mod->firstmodelsurface + j;
if( surf->flags & SURF_CONVEYOR )
mod->flags |= MODEL_CONVEYOR;
if( surf->flags & SURF_TRANSPARENT )
mod->flags |= MODEL_TRANSPARENT;
// kill water backplanes for submodels (half-life rules)
if( surf->flags & SURF_DRAWTURB )
{
mod->flags |= MODEL_LIQUID;
if( surf->plane->type == PLANE_Z )
{
// kill bottom plane too
if( surf->info->mins[2] == bm->mins[2] + 1.0f )
surf->flags |= SURF_WATERCSG;
}
else
{
// kill side planes
surf->flags |= SURF_WATERCSG;
}
}
}
if( i < mod->numsubmodels - 1 )
{
char name[8];
// duplicate the basic information
Q_snprintf( name, sizeof( name ), "*%i", i + 1 );
loadmodel = Mod_FindName( name, true );
*loadmodel = *mod;
Q_strncpy( loadmodel->name, name, sizeof( loadmodel->name ));
loadmodel->mempool = NULL;
mod = loadmodel;
}
}
if( loaded ) *loaded = true; // all done
}
/*
==================
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 )
{
byte *buf;
char tempname[64];
qboolean loaded;
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;
}
FS_FileBase( mod->name, modelname );
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 XTBSP_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
// update the lightmap blocksize
if( host.features & ENGINE_LARGE_LIGHTMAPS )
world.block_size = BLOCK_SIZE_MAX;
else world.block_size = BLOCK_SIZE_DEFAULT;
if( !Q_stricmp( cm_models[0].name, name ))
{
// recalc the checksum in force-mode
CRC32_MapFile( &world.checksum, worldmodel->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;
worldmodel = Mod_ForName( name, true );
CRC32_MapFile( &world.checksum, worldmodel->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 );
}
}
/*
===================
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];
}
/*
==================
Mod_CheckLump
check lump for existing
==================
*/
int Mod_CheckLump( const char *filename, const int lump, int *lumpsize )
{
file_t *f = FS_Open( filename, "rb", true );
byte buffer[sizeof( dheader31_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
dextrahdr_t *extrahdr;
dheader_t *header;
if( !f ) return LUMP_LOAD_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_LOAD_BAD_HEADER;
}
header = (dheader_t *)buffer;
if( header->version != HLBSP_VERSION && header->version != XTBSP_VERSION )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
// BSP31 and BSP30 have different offsets
if( header->version == XTBSP_VERSION )
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader31_t ));
else extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_LOAD_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_LOAD_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen <= 0 )
{
FS_Close( f );
return LUMP_LOAD_NOT_EXIST;
}
if( lumpsize )
*lumpsize = extrahdr->lumps[lump].filelen;
FS_Close( f );
return LUMP_LOAD_OK;
}
/*
==================
Mod_ReadLump
reading random lump by user request
==================
*/
int Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize )
{
file_t *f = FS_Open( filename, "rb", true );
byte buffer[sizeof( dheader31_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
dextrahdr_t *extrahdr;
dheader_t *header;
byte *data;
int length;
if( !f ) return LUMP_LOAD_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_LOAD_BAD_HEADER;
}
header = (dheader_t *)buffer;
if( header->version != HLBSP_VERSION && header->version != XTBSP_VERSION )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
// BSP31 and BSP30 have different offsets
if( header->version == XTBSP_VERSION )
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader31_t ));
else extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_LOAD_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_LOAD_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen <= 0 )
{
FS_Close( f );
return LUMP_LOAD_NOT_EXIST;
}
data = malloc( extrahdr->lumps[lump].filelen + 1 );
length = extrahdr->lumps[lump].filelen;
if( !data )
{
FS_Close( f );
return LUMP_LOAD_MEM_FAILED;
}
FS_Seek( f, extrahdr->lumps[lump].fileofs, SEEK_SET );
if( FS_Read( f, data, length ) != length )
{
Mem_Free( data );
FS_Close( f );
return LUMP_LOAD_CORRUPTED;
}
data[length] = 0; // write term
FS_Close( f );
if( lumpsize )
*lumpsize = length;
*lumpdata = data;
return LUMP_LOAD_OK;
}
/*
==================
Mod_SaveLump
writing lump by user request
only empty lumps is allows
==================
*/
int Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize )
{
file_t *f = FS_Open( filename, "e+b", true );
byte buffer[sizeof( dheader31_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
dextrahdr_t *extrahdr;
dheader_t *header;
if( !f ) return LUMP_SAVE_COULDNT_OPEN;
if( !lumpdata || lumpsize <= 0 )
return LUMP_SAVE_NO_DATA;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_SAVE_BAD_HEADER;
}
header = (dheader_t *)buffer;
if( header->version != HLBSP_VERSION && header->version != XTBSP_VERSION )
{
FS_Close( f );
return LUMP_SAVE_BAD_VERSION;
}
// BSP31 and BSP30 have different offsets
if( header->version == XTBSP_VERSION )
extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader31_t ));
else extrahdr = (dextrahdr_t *)((byte *)buffer + sizeof( dheader_t ));
if( extrahdr->id != IDEXTRAHEADER || extrahdr->version != EXTRA_VERSION )
{
FS_Close( f );
return LUMP_SAVE_NO_EXTRADATA;
}
if( lump < 0 || lump >= EXTRA_LUMPS )
{
FS_Close( f );
return LUMP_SAVE_INVALID_NUM;
}
if( extrahdr->lumps[lump].filelen != 0 )
{
FS_Close( f );
return LUMP_SAVE_ALREADY_EXIST;
}
FS_Seek( f, 0, SEEK_END );
// will be saved later
extrahdr->lumps[lump].fileofs = FS_Tell( f );
extrahdr->lumps[lump].filelen = lumpsize;
if( FS_Write( f, lumpdata, lumpsize ) != lumpsize )
{
FS_Close( f );
return LUMP_SAVE_CORRUPTED;
}
// update the header
if( header->version == XTBSP_VERSION )
FS_Seek( f, sizeof( dheader31_t ), SEEK_SET );
else FS_Seek( f, sizeof( dheader_t ), SEEK_SET );
if( FS_Write( f, extrahdr, sizeof( dextrahdr_t )) != sizeof( dextrahdr_t ))
{
FS_Close( f );
return LUMP_SAVE_CORRUPTED;
}
FS_Close( f );
return LUMP_SAVE_OK;
}