xash3d-fwgs/engine/common/mod_bmodel.c
Gleb Mazovetskiy 5e0a0765ce Trim all trailing whitespace
The `.editorconfig` file in this repo is configured to trim all trailing
whitespace regardless of whether the line is modified.

Trims all trailing whitespace in the repository to make the codebase easier
to work with in editors that respect `.editorconfig`.

`git blame` becomes less useful on these lines but it already isn't very useful.

Commands:

```
find . -type f -name '*.h' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
find . -type f -name '*.c' -exec sed --in-place 's/[[:space:]]\+$//' {} \+
```
2021-01-04 20:55:10 +03:00

3182 lines
78 KiB
C

/*
mod_bmodel.c - loading & handling world and brushmodels
Copyright (C) 2016 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" // LUMP_ error codes
#include "ref_common.h"
typedef struct wadlist_s
{
char wadnames[MAX_MAP_WADS][32];
int wadusage[MAX_MAP_WADS];
int count;
} wadlist_t;
typedef struct leaflist_s
{
int count;
int maxcount;
qboolean overflowed;
int *list;
vec3_t mins, maxs;
int topnode; // for overflows where each leaf can't be stored individually
} leaflist_t;
typedef struct
{
// generic lumps
dmodel_t *submodels;
size_t numsubmodels;
dvertex_t *vertexes;
size_t numvertexes;
dplane_t *planes;
size_t numplanes;
union
{
dnode_t *nodes;
dnode32_t *nodes32;
};
size_t numnodes;
union
{
dleaf_t *leafs;
dleaf32_t *leafs32;
};
size_t numleafs;
union
{
dclipnode_t *clipnodes;
dclipnode32_t *clipnodes32;
};
size_t numclipnodes;
dtexinfo_t *texinfo;
size_t numtexinfo;
union
{
dmarkface_t *markfaces;
dmarkface32_t *markfaces32;
};
size_t nummarkfaces;
dsurfedge_t *surfedges;
size_t numsurfedges;
union
{
dedge_t *edges;
dedge32_t *edges32;
};
size_t numedges;
union
{
dface_t *surfaces;
dface32_t *surfaces32;
};
size_t numsurfaces;
dfaceinfo_t *faceinfo;
size_t numfaceinfo;
// array lumps
byte *visdata;
size_t visdatasize;
byte *lightdata;
size_t lightdatasize;
byte *deluxdata;
size_t deluxdatasize;
byte *shadowdata;
size_t shadowdatasize;
byte *entdata;
size_t entdatasize;
// lumps that required personal handler
dmiptexlump_t *textures;
size_t texdatasize;
// intermediate arrays (pointers will lost after loading, but keep the data)
color24 *deluxedata_out; // deluxemap data pointer
byte *shadowdata_out; // occlusion data pointer
dclipnode32_t *clipnodes_out; // temporary 32-bit array to hold clipnodes
// misc stuff
wadlist_t wadlist;
int lightmap_samples; // samples per lightmap (1 or 3)
int version; // model version
qboolean isworld;
} dbspmodel_t;
typedef struct
{
const char *lumpname;
size_t entrysize;
size_t maxcount;
size_t count;
} mlumpstat_t;
typedef struct
{
char name[64]; // just for debug
// count errors and warnings
int numerrors;
int numwarnings;
} loadstat_t;
#define CHECK_OVERFLOW BIT( 0 ) // if some of lumps will be overflowed this non fatal for us. But some lumps are critical. mark them
#define USE_EXTRAHEADER BIT( 1 )
#define LUMP_SAVESTATS BIT( 0 )
#define LUMP_TESTONLY BIT( 1 )
#define LUMP_SILENT BIT( 2 )
typedef struct
{
int lumpnumber;
const size_t mincount;
const size_t maxcount;
const int entrysize;
const int entrysize32; // alternative (-1 by default)
const char *loadname;
int flags;
const void **dataptr;
size_t *count;
} mlumpinfo_t;
world_static_t world;
static dbspmodel_t srcmodel;
static loadstat_t loadstat;
static model_t *worldmodel;
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS];
static mlumpinfo_t srclumps[HEADER_LUMPS] =
{
{ LUMP_ENTITIES, 32, MAX_MAP_ENTSTRING, sizeof( byte ), -1, "entities", 0, (const void **)&srcmodel.entdata, &srcmodel.entdatasize },
{ LUMP_PLANES, 1, MAX_MAP_PLANES, sizeof( dplane_t ), -1, "planes", 0, (const void **)&srcmodel.planes, &srcmodel.numplanes },
{ LUMP_TEXTURES, 1, MAX_MAP_MIPTEX, sizeof( byte ), -1, "textures", 0, (const void **)&srcmodel.textures, &srcmodel.texdatasize },
{ LUMP_VERTEXES, 0, MAX_MAP_VERTS, sizeof( dvertex_t ), -1, "vertexes", 0, (const void **)&srcmodel.vertexes, &srcmodel.numvertexes },
{ LUMP_VISIBILITY, 0, MAX_MAP_VISIBILITY, sizeof( byte ), -1, "visibility", 0, (const void **)&srcmodel.visdata, &srcmodel.visdatasize },
{ LUMP_NODES, 1, MAX_MAP_NODES, sizeof( dnode_t ), sizeof( dnode32_t ), "nodes", CHECK_OVERFLOW, (const void **)&srcmodel.nodes, &srcmodel.numnodes },
{ LUMP_TEXINFO, 0, MAX_MAP_TEXINFO, sizeof( dtexinfo_t ), -1, "texinfo", CHECK_OVERFLOW, (const void **)&srcmodel.texinfo, &srcmodel.numtexinfo },
{ LUMP_FACES, 0, MAX_MAP_FACES, sizeof( dface_t ), sizeof( dface32_t ), "faces", CHECK_OVERFLOW, (const void **)&srcmodel.surfaces, &srcmodel.numsurfaces },
{ LUMP_LIGHTING, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "lightmaps", 0, (const void **)&srcmodel.lightdata, &srcmodel.lightdatasize },
{ LUMP_CLIPNODES, 0, MAX_MAP_CLIPNODES, sizeof( dclipnode_t ), sizeof( dclipnode32_t ), "clipnodes", 0, (const void **)&srcmodel.clipnodes, &srcmodel.numclipnodes },
{ LUMP_LEAFS, 1, MAX_MAP_LEAFS, sizeof( dleaf_t ), sizeof( dleaf32_t ), "leafs", CHECK_OVERFLOW, (const void **)&srcmodel.leafs, &srcmodel.numleafs },
{ LUMP_MARKSURFACES, 0, MAX_MAP_MARKSURFACES, sizeof( dmarkface_t ), sizeof( dmarkface32_t ), "markfaces", 0, (const void **)&srcmodel.markfaces, &srcmodel.nummarkfaces },
{ LUMP_EDGES, 0, MAX_MAP_EDGES, sizeof( dedge_t ), sizeof( dedge32_t ), "edges", 0, (const void **)&srcmodel.edges, &srcmodel.numedges },
{ LUMP_SURFEDGES, 0, MAX_MAP_SURFEDGES, sizeof( dsurfedge_t ), -1, "surfedges", 0, (const void **)&srcmodel.surfedges, &srcmodel.numsurfedges },
{ LUMP_MODELS, 1, MAX_MAP_MODELS, sizeof( dmodel_t ), -1, "models", CHECK_OVERFLOW, (const void **)&srcmodel.submodels, &srcmodel.numsubmodels },
};
static mlumpinfo_t extlumps[EXTRA_LUMPS] =
{
{ LUMP_LIGHTVECS, 0, MAX_MAP_LIGHTING, sizeof( byte ), -1, "deluxmaps", USE_EXTRAHEADER, (const void **)&srcmodel.deluxdata, &srcmodel.deluxdatasize },
{ LUMP_FACEINFO, 0, MAX_MAP_FACEINFO, sizeof( dfaceinfo_t ), -1, "faceinfos", CHECK_OVERFLOW|USE_EXTRAHEADER, (const void **)&srcmodel.faceinfo, &srcmodel.numfaceinfo },
{ LUMP_SHADOWMAP, 0, MAX_MAP_LIGHTING / 3, sizeof( byte ), -1, "shadowmap", USE_EXTRAHEADER, (const void **)&srcmodel.shadowdata, &srcmodel.shadowdatasize },
};
/*
===============================================================================
MAP PROCESSING
===============================================================================
*/
/*
=================
Mod_LoadLump
generic loader
=================
*/
static void Mod_LoadLump( const byte *in, mlumpinfo_t *info, mlumpstat_t *stat, int flags )
{
int version = ((dheader_t *)in)->version;
size_t numelems, real_entrysize;
char msg1[32], msg2[32];
dlump_t *l = NULL;
if( FBitSet( info->flags, USE_EXTRAHEADER ))
{
dextrahdr_t *header = (dextrahdr_t *)((byte *)in + sizeof( dheader_t ));
if( header->id != IDEXTRAHEADER || header->version != EXTRA_VERSION )
return;
l = &header->lumps[info->lumpnumber];
}
else
{
dheader_t *header = (dheader_t *)in;
l = &header->lumps[info->lumpnumber];
}
// lump is unused by engine for some reasons ?
if( !l || info->entrysize <= 0 || info->maxcount <= 0 )
return;
real_entrysize = info->entrysize; // default
// analyze real entrysize
if( version == QBSP2_VERSION && info->entrysize32 > 0 )
{
// always use alternate entrysize for BSP2
real_entrysize = info->entrysize32;
}
else if( info->lumpnumber == LUMP_CLIPNODES && version != Q1BSP_VERSION )
{
// never run this check for BSP29 because Arguire QBSP 'broken' clipnodes!
if(( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize ) >= MAX_MAP_CLIPNODES )
{
real_entrysize = info->entrysize32;
SetBits( flags, LUMP_SILENT ); // shut up warning
}
}
// bmodels not required the visibility
if( !FBitSet( flags, LUMP_TESTONLY ) && !world.loading && info->lumpnumber == LUMP_VISIBILITY )
SetBits( flags, LUMP_SILENT ); // shut up warning
// fill the stats for world
if( FBitSet( flags, LUMP_SAVESTATS ))
{
stat->lumpname = info->loadname;
stat->entrysize = real_entrysize;
stat->maxcount = info->maxcount;
if( real_entrysize != 0 )
stat->count = l->filelen / real_entrysize;
}
Q_strncpy( msg1, info->loadname, sizeof( msg1 ));
Q_strncpy( msg2, info->loadname, sizeof( msg2 ));
msg2[0] = Q_toupper( msg2[0] ); // first letter in cap
// lump is not present
if( l->filelen <= 0 )
{
// don't warn about extra lumps - it's optional
if( !FBitSet( info->flags, USE_EXTRAHEADER ))
{
// some data array that may be optional
if( real_entrysize == sizeof( byte ))
{
if( !FBitSet( flags, LUMP_SILENT ))
{
Con_DPrintf( S_WARN "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numwarnings++;
}
}
else if( info->mincount > 0 )
{
// it has the mincount and the lump is completely missed!
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
}
}
return;
}
if( l->filelen % real_entrysize )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %lu bytes\n", msg2, l->filelen, real_entrysize );
loadstat.numerrors++;
return;
}
numelems = l->filelen / real_entrysize;
if( numelems < info->mincount )
{
// it has the mincount and it's smaller than this limit
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
return;
}
if( numelems > info->maxcount )
{
// it has the maxcount and it's overflowed
if( FBitSet( info->flags, CHECK_OVERFLOW ))
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_DPrintf( S_ERROR "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
loadstat.numerrors++;
return;
}
else if( !FBitSet( flags, LUMP_SILENT ))
{
// just throw warning
Con_DPrintf( S_WARN "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
loadstat.numwarnings++;
}
}
if( FBitSet( flags, LUMP_TESTONLY ))
return; // don't fill the intermediate struct
// all checks are passed, store pointers
if( info->dataptr ) *info->dataptr = (void *)(in + l->fileofs);
if( info->count ) *info->count = numelems;
}
/*
================
Mod_ArrayUsage
================
*/
static int Mod_ArrayUsage( const char *szItem, int items, int maxitems, int itemsize )
{
float percentage = maxitems ? (items * 100.0f / maxitems) : 0.0f;
Con_Printf( "%-12s %7i/%-7i %8i/%-8i (%4.1f%%) ", szItem, items, maxitems, items * itemsize, maxitems * itemsize, percentage );
if( percentage > 99.99f )
Con_Printf( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Con_Printf( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Con_Printf( "^2VERY FULL!^7\n" );
else Con_Printf( "\n" );
return items * itemsize;
}
/*
================
Mod_GlobUsage
================
*/
static int Mod_GlobUsage( const char *szItem, int itemstorage, int maxstorage )
{
float percentage = maxstorage ? (itemstorage * 100.0f / maxstorage) : 0.0f;
Con_Printf( "%-15s %-12s %8i/%-8i (%4.1f%%) ", szItem, "[variable]", itemstorage, maxstorage, percentage );
if( percentage > 99.99f )
Con_Printf( "^1SIZE OVERFLOW!!!^7\n" );
else if( percentage > 95.0f )
Con_Printf( "^3SIZE DANGER!^7\n" );
else if( percentage > 80.0f )
Con_Printf( "^2VERY FULL!^7\n" );
else Con_Printf( "\n" );
return itemstorage;
}
/*
=============
Mod_PrintWorldStats_f
Dumps info about world
=============
*/
void Mod_PrintWorldStats_f( void )
{
int i, totalmemory = 0;
model_t *w = worldmodel;
if( !w || !w->numsubmodels )
{
Con_Printf( "No map loaded\n" );
return;
}
Con_Printf( "\n" );
Con_Printf( "Object names Objects/Maxobjs Memory / Maxmem Fullness\n" );
Con_Printf( "------------ --------------- --------------- --------\n" );
for( i = 0; i < ARRAYSIZE( worldstats ); i++ )
{
mlumpstat_t *stat = &worldstats[i];
if( !stat->lumpname || !stat->maxcount || !stat->count )
continue; // unused or lump is empty
if( stat->entrysize == sizeof( byte ))
totalmemory += Mod_GlobUsage( stat->lumpname, stat->count, stat->maxcount );
else totalmemory += Mod_ArrayUsage( stat->lumpname, stat->count, stat->maxcount, stat->entrysize );
}
Con_Printf( "=== Total BSP file data space used: %s ===\n", Q_memprint( totalmemory ));
Con_Printf( "World size ( %g %g %g ) units\n", world.size[0], world.size[1], world.size[2] );
Con_Printf( "Supports transparency world water: %s\n", FBitSet( world.flags, FWORLD_WATERALPHA ) ? "Yes" : "No" );
Con_Printf( "Lighting: %s\n", FBitSet( w->flags, MODEL_COLORED_LIGHTING ) ? "colored" : "monochrome" );
Con_Printf( "World total leafs: %d\n", worldmodel->numleafs + 1 );
Con_Printf( "original name: ^1%s\n", worldmodel->name );
Con_Printf( "internal name: %s\n", (world.message[0]) ? va( "^2%s", world.message ) : "none" );
Con_Printf( "map compiler: %s\n", (world.compiler[0]) ? va( "^3%s", world.compiler ) : "unknown" );
Con_Printf( "map editor: %s\n", (world.generator[0]) ? va( "^2%s", world.generator ) : "unknown" );
}
/*
===============================================================================
COMMON ROUTINES
===============================================================================
*/
/*
===================
Mod_DecompressPVS
===================
*/
byte *Mod_DecompressPVS( const byte *in, int visbytes )
{
byte *out;
int c;
out = g_visdata;
if( !in )
{
// no vis info, so make all visible
while( visbytes )
{
*out++ = 0xff;
visbytes--;
}
return g_visdata;
}
do
{
if( *in )
{
*out++ = *in++;
continue;
}
c = in[1];
in += 2;
while( c )
{
*out++ = 0;
c--;
}
} while( out - g_visdata < visbytes );
return g_visdata;
}
/*
==================
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 Mod_DecompressPVS( leaf->compressed_vis, 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 = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, 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 )
{
int bytes = world.visbytes;
mleaf_t *leaf = NULL;
ASSERT( worldmodel != NULL );
leaf = Mod_PointInLeaf( org, worldmodel->nodes );
bytes = Q_min( bytes, visbytes );
// enable full visibility for some reasons
if( fullvis || !worldmodel->visdata || !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
==================
*/
static int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *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 )
{
int 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, int *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_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 )
return;
if( !origin || !VectorIsNull( 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_CheckWaterAlphaSupport
converted maps potential may don't
support water transparency
==================
*/
static qboolean Mod_CheckWaterAlphaSupport( dbspmodel_t *bmod )
{
mleaf_t *leaf;
int i, j;
const byte *pvs;
if( bmod->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 = Mod_DecompressPVS( leaf->compressed_vis, 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_SampleSizeForFace
return the current lightmap resolution per face
==================
*/
int Mod_SampleSizeForFace( msurface_t *surf )
{
if( !surf || !surf->texinfo )
return LM_SAMPLE_SIZE;
// world luxels has more priority
if( FBitSet( surf->texinfo->flags, TEX_WORLD_LUXELS ))
return 1;
if( FBitSet( surf->texinfo->flags, TEX_EXTRA_LIGHTMAP ))
return LM_SAMPLE_EXTRASIZE;
if( surf->texinfo->faceinfo )
return surf->texinfo->faceinfo->texture_step;
return LM_SAMPLE_SIZE;
}
/*
==================
Mod_GetFaceContents
determine face contents by name
==================
*/
static int Mod_GetFaceContents( const char *name )
{
if( !Q_strnicmp( name, "SKY", 3 ))
return CONTENTS_SKY;
if( name[0] == '!' || name[0] == '*' )
{
if( !Q_strnicmp( name + 1, "lava", 4 ))
return CONTENTS_LAVA;
else if( !Q_strnicmp( name + 1, "slime", 5 ))
return CONTENTS_SLIME;
return CONTENTS_WATER; // otherwise it's water
}
if( !Q_strnicmp( name, "water", 5 ))
return CONTENTS_WATER;
return CONTENTS_SOLID;
}
/*
==================
Mod_GetFaceContents
determine face contents by name
==================
*/
static mvertex_t *Mod_GetVertexByNumber( model_t *mod, int surfedge )
{
int lindex;
medge_t *edge;
lindex = mod->surfedges[surfedge];
if( lindex > 0 )
{
edge = &mod->edges[lindex];
return &mod->vertexes[edge->v[0]];
}
else
{
edge = &mod->edges[-lindex];
return &mod->vertexes[edge->v[1]];
}
}
/*
==================
Mod_MakeNormalAxial
remove jitter from near-axial normals
==================
*/
static void Mod_MakeNormalAxial( vec3_t normal )
{
int i, type;
for( type = 0; type < 3; type++ )
{
if( fabs( normal[type] ) > 0.9999f )
break;
}
// make positive and pure axial
for( i = 0; i < 3 && type != 3; i++ )
{
if( i == type )
normal[i] = 1.0f;
else normal[i] = 0.0f;
}
}
/*
==================
Mod_LightMatrixFromTexMatrix
compute lightmap matrix based on texture matrix
==================
*/
static void Mod_LightMatrixFromTexMatrix( const mtexinfo_t *tx, float lmvecs[2][4] )
{
float lmscale = LM_SAMPLE_SIZE;
int i, j;
// this is can't be possible but who knews
if( FBitSet( tx->flags, TEX_EXTRA_LIGHTMAP ))
lmscale = LM_SAMPLE_EXTRASIZE;
if( tx->faceinfo )
lmscale = tx->faceinfo->texture_step;
// copy texmatrix into lightmap matrix fisrt
for( i = 0; i < 2; i++ )
{
for( j = 0; j < 4; j++ )
{
lmvecs[i][j] = tx->vecs[i][j];
}
}
if( !FBitSet( tx->flags, TEX_WORLD_LUXELS ))
return; // just use texmatrix
VectorNormalize( lmvecs[0] );
VectorNormalize( lmvecs[1] );
if( FBitSet( tx->flags, TEX_AXIAL_LUXELS ))
{
Mod_MakeNormalAxial( lmvecs[0] );
Mod_MakeNormalAxial( lmvecs[1] );
}
// put the lighting origin at center the of poly
VectorScale( lmvecs[0], (1.0f / lmscale), lmvecs[0] );
VectorScale( lmvecs[1], -(1.0f / lmscale), lmvecs[1] );
lmvecs[0][3] = lmscale * 0.5f;
lmvecs[1][3] = -lmscale * 0.5f;
}
/*
=================
Mod_CalcSurfaceExtents
Fills in surf->texturemins[] and surf->extents[]
=================
*/
static void Mod_CalcSurfaceExtents( msurface_t *surf )
{
float mins[2], maxs[2], val;
float lmmins[2], lmmaxs[2];
int bmins[2], bmaxs[2];
int i, j, e, sample_size;
mextrasurf_t *info = surf->info;
int facenum = surf - loadmodel->surfaces;
mtexinfo_t *tex;
mvertex_t *v;
sample_size = Mod_SampleSizeForFace( surf );
tex = surf->texinfo;
Mod_LightMatrixFromTexMatrix( tex, info->lmvecs );
mins[0] = lmmins[0] = mins[1] = lmmins[1] = 999999;
maxs[0] = lmmaxs[0] = maxs[1] = lmmaxs[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];
mins[j] = Q_min( val, mins[j] );
maxs[j] = Q_max( val, maxs[j] );
}
for( j = 0; j < 2; j++ )
{
val = DotProduct( v->position, info->lmvecs[j] ) + info->lmvecs[j][3];
lmmins[j] = Q_min( val, lmmins[j] );
lmmaxs[j] = Q_max( val, lmmaxs[j] );
}
}
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_WORLD_LUXELS ))
{
lmmins[i] = floor( lmmins[i] );
lmmaxs[i] = ceil( lmmaxs[i] );
info->lightmapmins[i] = lmmins[i];
info->lightextents[i] = (lmmaxs[i] - lmmins[i]);
}
else
{
// just copy texturemins
info->lightmapmins[i] = surf->texturemins[i];
info->lightextents[i] = surf->extents[i];
}
#if !XASH_DEDICATED && 0 // REFTODO:
if( !FBitSet( tex->flags, TEX_SPECIAL ) && ( surf->extents[i] > 16384 ) && ( tr.block_size == BLOCK_SIZE_DEFAULT ))
Con_Reportf( S_ERROR "Bad surface extents %i\n", surf->extents[i] );
#endif // XASH_DEDICATED
}
}
/*
=================
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_CreateFaceBevels
=================
*/
static void Mod_CreateFaceBevels( msurface_t *surf )
{
vec3_t delta, edgevec;
byte *facebevel;
vec3_t faceNormal;
mvertex_t *v0, *v1;
int contents;
int i, size;
vec_t radius;
mfacebevel_t *fb;
if( surf->texinfo && surf->texinfo->texture )
contents = Mod_GetFaceContents( surf->texinfo->texture->name );
else contents = CONTENTS_SOLID;
size = sizeof( mfacebevel_t ) + surf->numedges * sizeof( mplane_t );
facebevel = (byte *)Mem_Calloc( loadmodel->mempool, size );
fb = (mfacebevel_t *)facebevel;
facebevel += sizeof( mfacebevel_t );
fb->edges = (mplane_t *)facebevel;
fb->numedges = surf->numedges;
fb->contents = contents;
surf->info->bevel = fb;
if( FBitSet( surf->flags, SURF_PLANEBACK ))
VectorNegate( surf->plane->normal, faceNormal );
else VectorCopy( surf->plane->normal, faceNormal );
// compute face origin and plane edges
for( i = 0; i < surf->numedges; i++ )
{
mplane_t *dest = &fb->edges[i];
v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i );
v1 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + (i + 1) % surf->numedges );
VectorSubtract( v1->position, v0->position, edgevec );
CrossProduct( faceNormal, edgevec, dest->normal );
VectorNormalize( dest->normal );
dest->dist = DotProduct( dest->normal, v0->position );
dest->type = PlaneTypeForNormal( dest->normal );
VectorAdd( fb->origin, v0->position, fb->origin );
}
VectorScale( fb->origin, 1.0f / surf->numedges, fb->origin );
// compute face radius
for( i = 0; i < surf->numedges; i++ )
{
v0 = Mod_GetVertexByNumber( loadmodel, surf->firstedge + i );
VectorSubtract( v0->position, fb->origin, delta );
radius = DotProduct( delta, delta );
fb->radius = Q_max( radius, fb->radius );
}
}
/*
=================
Mod_SetParent
=================
*/
static void Mod_SetParent( mnode_t *node, mnode_t *parent )
{
node->parent = parent;
if( node->contents < 0 ) return; // it's leaf
Mod_SetParent( node->children[0], node );
Mod_SetParent( node->children[1], node );
}
/*
==================
CountClipNodes_r
==================
*/
static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountClipNodes_r( src, hull, src[nodenum].children[0] );
CountClipNodes_r( src, hull, src[nodenum].children[1] );
}
/*
==================
CountClipNodes32_r
==================
*/
static void CountClipNodes32_r( dclipnode32_t *src, hull_t *hull, int nodenum )
{
// leaf?
if( nodenum < 0 ) return;
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
hull->lastclipnode++;
CountClipNodes32_r( src, hull, src[nodenum].children[0] );
CountClipNodes32_r( src, hull, src[nodenum].children[1] );
}
/*
==================
RemapClipNodes_r
==================
*/
static int RemapClipNodes_r( dclipnode32_t *srcnodes, hull_t *hull, int nodenum )
{
dclipnode32_t *src;
mclipnode_t *out;
int i, c;
// leaf?
if( nodenum < 0 )
return nodenum;
// emit a clipnode
if( hull->lastclipnode == MAX_MAP_CLIPNODES )
Host_Error( "MAX_MAP_CLIPNODES limit exceeded\n" );
src = srcnodes + nodenum;
c = hull->lastclipnode;
out = &hull->clipnodes[c];
hull->lastclipnode++;
out->planenum = src->planenum;
for( i = 0; i < 2; i++ )
out->children[i] = RemapClipNodes_r( srcnodes, hull, src->children[i] );
return c;
}
/*
=================
Mod_MakeHull0
Duplicate the drawing hull structure as a clipping hull
=================
*/
static void Mod_MakeHull0( void )
{
mnode_t *in, *child;
mclipnode_t *out;
hull_t *hull;
int i, j;
hull = &loadmodel->hulls[0];
hull->clipnodes = out = Mem_Malloc( loadmodel->mempool, loadmodel->numnodes * sizeof( *out ));
in = loadmodel->nodes;
hull->firstclipnode = 0;
hull->lastclipnode = loadmodel->numnodes - 1;
hull->planes = loadmodel->planes;
for( i = 0; i < loadmodel->numnodes; 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_SetupHull
=================
*/
static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, byte *mempool, int headnode, int hullnum )
{
hull_t *hull = &mod->hulls[hullnum];
int count;
// assume no hull
hull->firstclipnode = hull->lastclipnode = 0;
hull->planes = NULL; // hull is missed
if(( headnode == -1 ) || ( hullnum != 1 && headnode == 0 ))
return; // hull missed
if( headnode >= mod->numclipnodes )
return; // ZHLT weird empty hulls
switch( hullnum )
{
case 1:
VectorCopy( host.player_mins[0], hull->clip_mins ); // copy human hull
VectorCopy( host.player_maxs[0], hull->clip_maxs );
break;
case 2:
VectorCopy( host.player_mins[3], hull->clip_mins ); // copy large hull
VectorCopy( host.player_maxs[3], hull->clip_maxs );
break;
case 3:
VectorCopy( host.player_mins[1], hull->clip_mins ); // copy head hull
VectorCopy( host.player_maxs[1], hull->clip_maxs );
break;
default:
Host_Error( "Mod_SetupHull: bad hull number %i\n", hullnum );
break;
}
if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs ))
return; // no hull specified
CountClipNodes32_r( bmod->clipnodes_out, hull, headnode );
count = hull->lastclipnode;
// fit array to real count
hull->clipnodes = (mclipnode_t *)Mem_Malloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode );
hull->planes = mod->planes; // share planes
hull->lastclipnode = 0; // restart counting
// remap clipnodes to 16-bit indexes
RemapClipNodes_r( bmod->clipnodes_out, hull, headnode );
}
/*
=================
Mod_LoadColoredLighting
=================
*/
static qboolean Mod_LoadColoredLighting( dbspmodel_t *bmod )
{
char modelname[64];
char path[64];
int iCompare;
fs_offset_t litdatasize;
byte *in;
COM_FileBase( loadmodel->name, modelname );
Q_snprintf( path, sizeof( path ), "maps/%s.lit", modelname );
// make sure what deluxemap is actual
if( !COM_CompareFileTime( path, loadmodel->name, &iCompare ))
return false;
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
Con_Printf( S_WARN "%s probably is out of date\n", path );
in = FS_LoadFile( path, &litdatasize, false );
Assert( in != NULL );
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
{
Mem_Free( in );
return false;
}
// skip header bytes
litdatasize -= 8;
if( litdatasize != ( bmod->lightdatasize * 3 ))
{
Con_Printf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, litdatasize, bmod->lightdatasize * 3 );
Mem_Free( in );
return false;
}
loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, litdatasize );
memcpy( loadmodel->lightdata, in + 8, litdatasize );
SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING );
bmod->lightdatasize = litdatasize;
Mem_Free( in );
return true;
}
/*
=================
Mod_LoadDeluxemap
=================
*/
static void Mod_LoadDeluxemap( dbspmodel_t *bmod )
{
char modelname[64];
fs_offset_t deluxdatasize;
char path[64];
int iCompare;
byte *in;
if( !FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
return;
COM_FileBase( loadmodel->name, modelname );
Q_snprintf( path, sizeof( path ), "maps/%s.dlit", modelname );
// make sure what deluxemap is actual
if( !COM_CompareFileTime( path, loadmodel->name, &iCompare ))
return;
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
Con_Printf( S_WARN "%s probably is out of date\n", path );
in = FS_LoadFile( path, &deluxdatasize, false );
Assert( in != NULL );
if( *(uint *)in != IDDELUXEMAPHEADER || *((uint *)in + 1) != DELUXEMAP_VERSION )
{
Mem_Free( in );
return;
}
// skip header bytes
deluxdatasize -= 8;
if( deluxdatasize != bmod->lightdatasize )
{
Con_Reportf( S_ERROR "%s has mismatched size (%li should be %lu)\n", path, deluxdatasize, bmod->lightdatasize );
Mem_Free( in );
return;
}
bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, deluxdatasize );
memcpy( bmod->deluxedata_out, in + 8, deluxdatasize );
bmod->deluxdatasize = deluxdatasize;
Mem_Free( in );
}
/*
=================
Mod_SetupSubmodels
duplicate the basic information
for embedded submodels
=================
*/
static void Mod_SetupSubmodels( dbspmodel_t *bmod )
{
qboolean colored = false;
byte *mempool;
char *ents;
model_t *mod;
dmodel_t *bm;
int i, j;
ents = loadmodel->entities;
mempool = loadmodel->mempool;
if( FBitSet( loadmodel->flags, MODEL_COLORED_LIGHTING ))
colored = true;
mod = loadmodel;
loadmodel->numframes = 2; // regular and alternate animation
// set up the submodels
for( i = 0; i < mod->numsubmodels; i++ )
{
bm = &mod->submodels[i];
// hull 0 is just shared across all bmodels
mod->hulls[0].firstclipnode = bm->headnode[0];
mod->hulls[0].lastclipnode = bm->headnode[0]; // need to be real count
// counting a real number of clipnodes per each submodel
CountClipNodes_r( mod->hulls[0].clipnodes, &mod->hulls[0], bm->headnode[0] );
// but hulls1-3 is build individually for a each given submodel
for( j = 1; j < MAX_MAP_HULLS; j++ )
Mod_SetupHull( bmod, mod, mempool, bm->headnode[j], j );
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;
// this bit will be shared between all the submodels include worldmodel
if( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING );
if( i != 0 )
{
Mod_FindModelOrigin( ents, va( "*%i", i ), bm->origin );
// mark models that have origin brushes
if( !VectorIsNull( bm->origin ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#ifdef HACKS_RELATED_HLMODS
// c2a1 doesn't have origin brush it's just placed at center of the level
if( !Q_stricmp( loadmodel->name, "maps/c2a1.bsp" ) && ( i == 11 ))
SetBits( mod->flags, MODEL_HAS_ORIGIN );
#endif
}
// sets the model flags
for( j = 0; i != 0 && j < mod->nummodelsurfaces; j++ )
{
msurface_t *surf = mod->surfaces + mod->firstmodelsurface + j;
if( FBitSet( surf->flags, SURF_CONVEYOR ))
SetBits( mod->flags, MODEL_CONVEYOR );
if( FBitSet( surf->flags, SURF_TRANSPARENT ))
SetBits( mod->flags, MODEL_TRANSPARENT );
if( FBitSet( surf->flags, SURF_DRAWTURB ))
SetBits( mod->flags, MODEL_LIQUID );
}
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( bmod->clipnodes_out != NULL )
Mem_Free( bmod->clipnodes_out );
}
/*
===============================================================================
MAP LOADING
===============================================================================
*/
/*
=================
Mod_LoadSubmodels
=================
*/
static void Mod_LoadSubmodels( dbspmodel_t *bmod )
{
dmodel_t *in, *out;
int oldmaxfaces;
int i, j;
// allocate extradata for each dmodel_t
out = Mem_Malloc( loadmodel->mempool, bmod->numsubmodels * sizeof( *out ));
loadmodel->numsubmodels = bmod->numsubmodels;
loadmodel->submodels = out;
in = bmod->submodels;
if( bmod->isworld )
refState.max_surfaces = 0;
oldmaxfaces = refState.max_surfaces;
for( i = 0; i < bmod->numsubmodels; i++, in++, out++ )
{
for( j = 0; j < 3; j++ )
{
// reset empty bounds to prevent error
if( in->mins[j] == 999999.0f )
in->mins[j] = 0.0f;
if( in->maxs[j] == -999999.0f)
in->maxs[j] = 0.0f;
// 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 && bmod->isworld )
continue; // skip the world to save mem
oldmaxfaces = Q_max( oldmaxfaces, out->numfaces );
}
// these array used to sort translucent faces in bmodels
if( oldmaxfaces > refState.max_surfaces )
{
refState.draw_surfaces = (sortedface_t *)Z_Realloc( refState.draw_surfaces, oldmaxfaces * sizeof( sortedface_t ));
refState.max_surfaces = oldmaxfaces;
}
}
/*
=================
Mod_LoadEntities
=================
*/
static void Mod_LoadEntities( dbspmodel_t *bmod )
{
byte *entpatch = NULL;
char token[MAX_TOKEN];
char wadstring[MAX_TOKEN];
string keyname;
char *pfile;
if( bmod->isworld )
{
char entfilename[MAX_QPATH];
fs_offset_t entpatchsize;
size_t ft1, ft2;
// world is check for entfile too
Q_strncpy( entfilename, loadmodel->name, sizeof( entfilename ));
COM_ReplaceExtension( entfilename, ".ent" );
// make sure what entity patch is never than bsp
ft1 = FS_FileTime( loadmodel->name, false );
ft2 = FS_FileTime( entfilename, true );
if( ft2 != -1 )
{
if( ft1 > ft2 )
{
Con_Printf( S_WARN "Entity patch is older than bsp. Ignored.\n" );
}
else if(( entpatch = FS_LoadFile( entfilename, &entpatchsize, true )) != NULL )
{
Con_Printf( "^2Read entity patch:^7 %s\n", entfilename );
bmod->entdatasize = entpatchsize;
bmod->entdata = entpatch;
}
}
}
// make sure what we really has terminator
loadmodel->entities = Mem_Calloc( loadmodel->mempool, bmod->entdatasize + 1 );
memcpy( loadmodel->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool
if( entpatch ) Mem_Free( entpatch ); // release entpatch if present
if( !bmod->isworld ) return;
pfile = (char *)loadmodel->entities;
world.generator[0] = '\0';
world.compiler[0] = '\0';
world.message[0] = '\0';
bmod->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, MAX_TOKEN - 2 );
wadstring[MAX_TOKEN - 2] = 0;
if( !Q_strchr( wadstring, ';' ))
Q_strcat( wadstring, ";" );
// parse wad pathes
for( pszWadFile = strtok( wadstring, ";" ); pszWadFile != NULL; pszWadFile = strtok( NULL, ";" ))
{
COM_FixSlashes( pszWadFile );
COM_FileBase( pszWadFile, token );
// make sure what wad is really exist
if( FS_FileExists( va( "%s.wad", token ), false ))
{
int num = bmod->wadlist.count++;
Q_strncpy( bmod->wadlist.wadnames[num], token, sizeof( bmod->wadlist.wadnames[0] ));
bmod->wadlist.wadusage[num] = 0;
}
if( bmod->wadlist.count >= MAX_MAP_WADS )
break; // too many wads...
}
}
else if( !Q_stricmp( keyname, "message" ))
Q_strncpy( world.message, token, sizeof( world.message ));
else if( !Q_stricmp( keyname, "compiler" ) || !Q_stricmp( keyname, "_compiler" ))
Q_strncpy( world.compiler, token, sizeof( world.compiler ));
else if( !Q_stricmp( keyname, "generator" ) || !Q_stricmp( keyname, "_generator" ))
Q_strncpy( world.generator, token, sizeof( world.generator ));
}
return; // all done
}
}
/*
=================
Mod_LoadPlanes
=================
*/
static void Mod_LoadPlanes( dbspmodel_t *bmod )
{
dplane_t *in;
mplane_t *out;
int i, j;
in = bmod->planes;
loadmodel->planes = out = Mem_Malloc( loadmodel->mempool, bmod->numplanes * sizeof( *out ));
loadmodel->numplanes = bmod->numplanes;
for( i = 0; i < bmod->numplanes; i++, in++, out++ )
{
out->signbits = 0;
for( j = 0; j < 3; j++ )
{
out->normal[j] = in->normal[j];
if( out->normal[j] < 0.0f )
SetBits( out->signbits, BIT( j ));
}
if( VectorLength( out->normal ) < 0.5f )
Con_Printf( S_ERROR "bad normal for plane #%i\n", i );
out->dist = in->dist;
out->type = in->type;
}
}
/*
=================
Mod_LoadVertexes
=================
*/
static void Mod_LoadVertexes( dbspmodel_t *bmod )
{
dvertex_t *in;
mvertex_t *out;
int i;
in = bmod->vertexes;
out = loadmodel->vertexes = Mem_Malloc( loadmodel->mempool, bmod->numvertexes * sizeof( mvertex_t ));
loadmodel->numvertexes = bmod->numvertexes;
if( bmod->isworld ) ClearBounds( world.mins, world.maxs );
for( i = 0; i < bmod->numvertexes; i++, in++, out++ )
{
if( bmod->isworld )
AddPointToBounds( in->point, world.mins, world.maxs );
VectorCopy( in->point, out->position );
}
if( !bmod->isworld ) 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( dbspmodel_t *bmod )
{
medge_t *out;
int i;
loadmodel->edges = out = Mem_Malloc( loadmodel->mempool, bmod->numedges * sizeof( medge_t ));
loadmodel->numedges = bmod->numedges;
if( bmod->version == QBSP2_VERSION )
{
dedge32_t *in = (dedge32_t *)bmod->edges32;
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
out->v[0] = in->v[0];
out->v[1] = in->v[1];
}
}
else
{
dedge_t *in = (dedge_t *)bmod->edges;
for( i = 0; i < bmod->numedges; i++, in++, out++ )
{
out->v[0] = (word)in->v[0];
out->v[1] = (word)in->v[1];
}
}
}
/*
=================
Mod_LoadSurfEdges
=================
*/
static void Mod_LoadSurfEdges( dbspmodel_t *bmod )
{
loadmodel->surfedges = Mem_Malloc( loadmodel->mempool, bmod->numsurfedges * sizeof( dsurfedge_t ));
memcpy( loadmodel->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t ));
loadmodel->numsurfedges = bmod->numsurfedges;
}
/*
=================
Mod_LoadMarkSurfaces
=================
*/
static void Mod_LoadMarkSurfaces( dbspmodel_t *bmod )
{
msurface_t **out;
int i;
loadmodel->marksurfaces = out = Mem_Malloc( loadmodel->mempool, bmod->nummarkfaces * sizeof( *out ));
loadmodel->nummarksurfaces = bmod->nummarkfaces;
if( bmod->version == QBSP2_VERSION )
{
dmarkface32_t *in = bmod->markfaces32;
for( i = 0; i < bmod->nummarkfaces; i++, in++ )
{
if( *in < 0 || *in >= loadmodel->numsurfaces )
Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name );
out[i] = loadmodel->surfaces + *in;
}
}
else
{
dmarkface_t *in = bmod->markfaces;
for( i = 0; i < bmod->nummarkfaces; i++, in++ )
{
if( *in < 0 || *in >= loadmodel->numsurfaces )
Host_Error( "Mod_LoadMarkFaces: bad surface number in '%s'\n", loadmodel->name );
out[i] = loadmodel->surfaces + *in;
}
}
}
/*
=================
Mod_LoadTextures
=================
*/
static void Mod_LoadTextures( dbspmodel_t *bmod )
{
dmiptexlump_t *in;
texture_t *tx, *tx2;
texture_t *anims[10];
texture_t *altanims[10];
int num, max, altmax;
qboolean custom_palette;
char texname[64];
mip_t *mt;
int i, j;
if( bmod->isworld )
{
#if !XASH_DEDICATED
// release old sky layers first
if( !Host_IsDedicated() )
{
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ));
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ));
}
#endif
}
if( !bmod->texdatasize )
{
// no textures
loadmodel->textures = NULL;
return;
}
in = bmod->textures;
loadmodel->textures = (texture_t **)Mem_Calloc( loadmodel->mempool, in->nummiptex * sizeof( texture_t* ));
loadmodel->numtextures = in->nummiptex;
for( i = 0; i < loadmodel->numtextures; i++ )
{
int txFlags = 0;
if( in->dataofs[i] == -1 )
{
// create default texture (some mods requires this)
tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx ));
loadmodel->textures[i] = tx;
Q_strncpy( tx->name, "*default", sizeof( tx->name ));
#if !XASH_DEDICATED
if( !Host_IsDedicated() )
{
tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
tx->width = tx->height = 16;
}
#endif
continue; // missed
}
mt = (mip_t *)((byte *)in + in->dataofs[i] );
if( !mt->name[0] )
Q_snprintf( mt->name, sizeof( mt->name ), "miptex_%i", i );
tx = Mem_Calloc( loadmodel->mempool, sizeof( *tx ));
loadmodel->textures[i] = tx;
// convert to lowercase
Q_strncpy( tx->name, mt->name, sizeof( tx->name ));
Q_strnlwr( tx->name, tx->name, sizeof( tx->name ));
custom_palette = false;
tx->width = mt->width;
tx->height = mt->height;
if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mt->name[0] == '{' )
SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing
if( mt->offsets[0] > 0 )
{
int size = (int)sizeof( mip_t ) + ((mt->width * mt->height * 85)>>6);
int next_dataofs = 0, remaining;
// compute next dataofset to determine allocated miptex space
for( j = i + 1; j < loadmodel->numtextures; j++ )
{
next_dataofs = in->dataofs[j];
if( next_dataofs != -1 ) break;
}
if( j == loadmodel->numtextures )
next_dataofs = bmod->texdatasize;
// NOTE: imagelib detect miptex version by size
// 770 additional bytes is indicated custom palette
remaining = next_dataofs - (in->dataofs[i] + size);
if( remaining >= 770 ) custom_palette = true;
}
#if !XASH_DEDICATED
if( !Host_IsDedicated() )
{
// check for multi-layered sky texture (quake1 specific)
if( bmod->isworld && !Q_strncmp( mt->name, "sky", 3 ) && (( mt->width / mt->height ) == 2 ) )
{
ref.dllFuncs.R_InitSkyClouds( mt, tx, custom_palette ); // load quake sky
if( R_GetBuiltinTexture( REF_SOLIDSKY_TEXTURE ) &&
R_GetBuiltinTexture( REF_ALPHASKY_TEXTURE ) )
SetBits( world.flags, FWORLD_SKYSPHERE );
continue;
}
// texture loading order:
// 1. from wad
// 2. internal from map
// trying wad texture (force while r_wadtextures is 1)
if(( r_wadtextures->value && bmod->wadlist.count > 0 ) || ( mt->offsets[0] <= 0 ))
{
Q_snprintf( texname, sizeof( texname ), "%s.mip", mt->name );
// check wads in reverse order
for( j = bmod->wadlist.count - 1; j >= 0; j-- )
{
char *texpath = va( "%s.wad/%s", bmod->wadlist.wadnames[j], texname );
if( FS_FileExists( texpath, false ))
{
tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, TF_ALLOW_EMBOSS|txFlags );
bmod->wadlist.wadusage[j]++; // this wad are really used
break;
}
}
}
// 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( custom_palette ) size += sizeof( short ) + 768;
Q_snprintf( texname, sizeof( texname ), "#%s:%s.mip", loadstat.name, mt->name );
tx->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, TF_ALLOW_EMBOSS|txFlags );
}
// if texture is completely missed
if( !tx->gl_texturenum )
{
Con_DPrintf( S_ERROR "unable to find %s.mip\n", mt->name );
tx->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
}
// check for luma texture
if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, tx->gl_texturenum ), TF_HAS_LUMA ))
{
Q_snprintf( texname, sizeof( texname ), "#%s:%s_luma.mip", loadstat.name, mt->name );
if( mt->offsets[0] > 0 )
{
// 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( custom_palette ) size += sizeof( short ) + 768;
tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, (byte *)mt, size, TF_MAKELUMA );
}
else
{
fs_offset_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
// check wads in reverse order
for( j = bmod->wadlist.count - 1; j >= 0; j-- )
{
char *texpath = va( "%s.wad/%s.mip", bmod->wadlist.wadnames[j], tx->name );
if( FS_FileExists( texpath, false ))
{
src = FS_LoadFile( texpath, &srcSize, false );
bmod->wadlist.wadusage[j]++; // this wad are really used
break;
}
}
// okay, loading it from wad or hi-res version
tx->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texname, src, srcSize, TF_MAKELUMA );
if( src ) Mem_Free( src );
}
}
}
#endif
}
// 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 Con_Printf( S_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 Con_Printf( S_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 )
{
Con_Printf( S_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 )
{
Con_Printf( S_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 void Mod_LoadTexInfo( dbspmodel_t *bmod )
{
mfaceinfo_t *fout, *faceinfo;
int i, j, k, miptex;
dfaceinfo_t *fin;
mtexinfo_t *out;
dtexinfo_t *in;
// trying to load faceinfo
faceinfo = fout = Mem_Calloc( loadmodel->mempool, bmod->numfaceinfo * sizeof( *fout ));
fin = bmod->faceinfo;
for( i = 0; i < bmod->numfaceinfo; i++, fin++, fout++ )
{
Q_strncpy( fout->landname, fin->landname, sizeof( fout->landname ));
fout->texture_step = fin->texture_step;
fout->max_extent = fin->max_extent;
fout->groupid = fin->groupid;
}
loadmodel->texinfo = out = Mem_Calloc( loadmodel->mempool, bmod->numtexinfo * sizeof( *out ));
loadmodel->numtexinfo = bmod->numtexinfo;
in = bmod->texinfo;
for( i = 0; i < bmod->numtexinfo; i++, in++, out++ )
{
for( j = 0; j < 2; j++ )
for( k = 0; k < 4; k++ )
out->vecs[j][k] = in->vecs[j][k];
miptex = in->miptex;
if( miptex < 0 || miptex > loadmodel->numtextures )
miptex = 0; // this is possible?
out->texture = loadmodel->textures[miptex];
out->flags = in->flags;
// make sure what faceinfo is really exist
if( faceinfo != NULL && in->faceinfo != -1 && in->faceinfo < bmod->numfaceinfo )
out->faceinfo = &faceinfo[in->faceinfo];
}
}
/*
=================
Mod_LoadSurfaces
=================
*/
static void Mod_LoadSurfaces( dbspmodel_t *bmod )
{
int test_lightsize = -1;
int next_lightofs = -1;
int prev_lightofs = -1;
int i, j, lightofs;
mextrasurf_t *info;
msurface_t *out;
loadmodel->surfaces = out = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( msurface_t ));
info = Mem_Calloc( loadmodel->mempool, bmod->numsurfaces * sizeof( mextrasurf_t ));
loadmodel->numsurfaces = bmod->numsurfaces;
// predict samplecount based on bspversion
if( bmod->version == Q1BSP_VERSION || bmod->version == QBSP2_VERSION )
bmod->lightmap_samples = 1;
else bmod->lightmap_samples = 3;
for( i = 0; i < bmod->numsurfaces; i++, out++, info++ )
{
texture_t *tex;
// setup crosslinks between two parts of msurface_t
out->info = info;
info->surf = out;
if( bmod->version == QBSP2_VERSION )
{
dface32_t *in = &bmod->surfaces32[i];
if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges )
continue; // corrupted level?
out->firstedge = in->firstedge;
out->numedges = in->numedges;
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
out->plane = loadmodel->planes + in->planenum;
out->texinfo = loadmodel->texinfo + in->texinfo;
for( j = 0; j < MAXLIGHTMAPS; j++ )
out->styles[j] = in->styles[j];
lightofs = in->lightofs;
}
else
{
dface_t *in = &bmod->surfaces[i];
if(( in->firstedge + in->numedges ) > loadmodel->numsurfedges )
{
Con_Reportf( S_ERROR "bad surface %i from %lu\n", i, bmod->numsurfaces );
continue;
}
out->firstedge = in->firstedge;
out->numedges = in->numedges;
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
out->plane = loadmodel->planes + in->planenum;
out->texinfo = loadmodel->texinfo + in->texinfo;
for( j = 0; j < MAXLIGHTMAPS; j++ )
out->styles[j] = in->styles[j];
lightofs = in->lightofs;
}
tex = out->texinfo->texture;
if( !Q_strncmp( tex->name, "sky", 3 ))
SetBits( out->flags, SURF_DRAWSKY );
if(( tex->name[0] == '*' && Q_stricmp( tex->name, "*default" )) || tex->name[0] == '!' )
SetBits( out->flags, SURF_DRAWTURB );
if( !Host_IsQuakeCompatible( ))
{
if( !Q_strncmp( tex->name, "water", 5 ) || !Q_strnicmp( tex->name, "laser", 5 ))
SetBits( out->flags, SURF_DRAWTURB );
}
if( !Q_strncmp( tex->name, "scroll", 6 ))
SetBits( out->flags, SURF_CONVEYOR );
if( FBitSet( out->texinfo->flags, TEX_SCROLL ))
SetBits( out->flags, SURF_CONVEYOR );
// g-cont. added a combined conveyor-transparent
if( !Q_strncmp( tex->name, "{scroll", 7 ))
SetBits( out->flags, SURF_CONVEYOR|SURF_TRANSPARENT );
if( tex->name[0] == '{' )
SetBits( out->flags, SURF_TRANSPARENT );
if( FBitSet( out->texinfo->flags, TEX_SPECIAL ))
SetBits( out->flags, SURF_DRAWTILED );
Mod_CalcSurfaceBounds( out );
Mod_CalcSurfaceExtents( out );
Mod_CreateFaceBevels( out );
// grab the second sample to detect colored lighting
if( test_lightsize > 0 && lightofs != -1 )
{
if( lightofs > prev_lightofs && lightofs < next_lightofs )
next_lightofs = lightofs;
}
// grab the first sample to determine lightmap size
if( lightofs != -1 && test_lightsize == -1 )
{
int sample_size = Mod_SampleSizeForFace( out );
int smax = (info->lightextents[0] / sample_size) + 1;
int tmax = (info->lightextents[1] / sample_size) + 1;
int lightstyles = 0;
test_lightsize = smax * tmax;
// count styles to right compute test_lightsize
for( j = 0; j < MAXLIGHTMAPS && out->styles[j] != 255; j++ )
lightstyles++;
test_lightsize *= lightstyles;
prev_lightofs = lightofs;
next_lightofs = 99999999;
}
#if !XASH_DEDICATED // TODO: Do we need subdivide on server?
if( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() )
ref.dllFuncs.GL_SubdivideSurface( out ); // cut up polygon for warps
#endif
}
// now we have enough data to trying determine samplecount per lightmap pixel
if( test_lightsize > 0 && prev_lightofs != -1 && next_lightofs != -1 && next_lightofs != 99999999 )
{
float samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;
if( samples != (int)samples )
{
test_lightsize = (test_lightsize + 3) & ~3; // align datasize and try again
samples = (float)(next_lightofs - prev_lightofs) / (float)test_lightsize;
}
if( samples == 1 || samples == 3 )
{
bmod->lightmap_samples = (int)samples;
Con_Reportf( "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" );
bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero
}
else Con_DPrintf( S_WARN "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples );
}
}
/*
=================
Mod_LoadNodes
=================
*/
static void Mod_LoadNodes( dbspmodel_t *bmod )
{
mnode_t *out;
int i, j, p;
loadmodel->nodes = out = (mnode_t *)Mem_Calloc( loadmodel->mempool, bmod->numnodes * sizeof( *out ));
loadmodel->numnodes = bmod->numnodes;
for( i = 0; i < loadmodel->numnodes; i++, out++ )
{
if( bmod->version == QBSP2_VERSION )
{
dnode32_t *in = &bmod->nodes32[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = 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 ));
}
}
else
{
dnode_t *in = &bmod->nodes[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = 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( dbspmodel_t *bmod )
{
mleaf_t *out;
int i, j, p;
int visclusters = 0;
loadmodel->leafs = out = (mleaf_t *)Mem_Calloc( loadmodel->mempool, bmod->numleafs * sizeof( *out ));
loadmodel->numleafs = bmod->numleafs;
if( bmod->isworld )
{
visclusters = loadmodel->submodels[0].visleafs;
world.visbytes = (visclusters + 7) >> 3;
world.fatbytes = (visclusters + 31) >> 3;
refState.visbytes = world.visbytes;
}
for( i = 0; i < bmod->numleafs; i++, out++ )
{
if( bmod->version == QBSP2_VERSION )
{
dleaf32_t *in = &bmod->leafs32[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
out->contents = in->contents;
p = in->visofs;
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;
}
else
{
dleaf_t *in = &bmod->leafs[i];
for( j = 0; j < 3; j++ )
{
out->minmaxs[j+0] = in->mins[j];
out->minmaxs[j+3] = in->maxs[j];
}
out->contents = in->contents;
p = in->visofs;
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;
}
if( bmod->isworld )
{
out->cluster = ( i - 1 ); // solid leaf 0 has no visdata
if( out->cluster >= visclusters )
out->cluster = -1;
// ignore visofs errors on leaf 0 (solid)
if( p >= 0 && out->cluster >= 0 && loadmodel->visdata )
{
if( p < bmod->visdatasize )
out->compressed_vis = loadmodel->visdata + p;
else Con_Reportf( S_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;
// gl underwater warp
if( out->contents != CONTENTS_EMPTY )
{
for( j = 0; j < out->nummarksurfaces; j++ )
{
// mark underwater surfaces
SetBits( out->firstmarksurface[j]->flags, SURF_UNDERWATER );
}
}
}
if( bmod->isworld && 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( bmod->isworld && Mod_CheckWaterAlphaSupport( bmod ))
SetBits( world.flags, FWORLD_WATERALPHA );
}
/*
=================
Mod_LoadClipnodes
=================
*/
static void Mod_LoadClipnodes( dbspmodel_t *bmod )
{
dclipnode32_t *out;
int i;
bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( loadmodel->mempool, bmod->numclipnodes * sizeof( *out ));
if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->numclipnodes >= MAX_MAP_CLIPNODES ))
{
dclipnode32_t *in = bmod->clipnodes32;
for( i = 0; i < bmod->numclipnodes; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = in->children[0];
out->children[1] = in->children[1];
}
}
else
{
dclipnode_t *in = bmod->clipnodes;
for( i = 0; i < bmod->numclipnodes; i++, out++, in++ )
{
out->planenum = in->planenum;
out->children[0] = (unsigned short)in->children[0];
out->children[1] = (unsigned short)in->children[1];
// Arguire QBSP 'broken' clipnodes
if( out->children[0] >= bmod->numclipnodes )
out->children[0] -= 65536;
if( out->children[1] >= bmod->numclipnodes )
out->children[1] -= 65536;
}
}
// FIXME: fill loadmodel->clipnodes?
loadmodel->numclipnodes = bmod->numclipnodes;
}
/*
=================
Mod_LoadVisibility
=================
*/
static void Mod_LoadVisibility( dbspmodel_t *bmod )
{
loadmodel->visdata = Mem_Malloc( loadmodel->mempool, bmod->visdatasize );
memcpy( loadmodel->visdata, bmod->visdata, bmod->visdatasize );
}
/*
=================
Mod_LoadLightVecs
=================
*/
static void Mod_LoadLightVecs( dbspmodel_t *bmod )
{
if( bmod->deluxdatasize != bmod->lightdatasize )
{
if( bmod->deluxdatasize > 0 )
Con_Printf( S_ERROR "Mod_LoadLightVecs: has mismatched size (%lu should be %i)\n", bmod->deluxdatasize, bmod->lightdatasize );
else Mod_LoadDeluxemap( bmod ); // old method
return;
}
bmod->deluxedata_out = Mem_Malloc( loadmodel->mempool, bmod->deluxdatasize );
memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize );
}
/*
=================
Mod_LoadShadowmap
=================
*/
static void Mod_LoadShadowmap( dbspmodel_t *bmod )
{
if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 ))
{
if( bmod->shadowdatasize > 0 )
Con_Printf( S_ERROR "Mod_LoadShadowmap: has mismatched size (%i should be %lu)\n", bmod->shadowdatasize, bmod->lightdatasize / 3 );
return;
}
bmod->shadowdata_out = Mem_Malloc( loadmodel->mempool, bmod->shadowdatasize );
memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize );
}
/*
=================
Mod_LoadLighting
=================
*/
static void Mod_LoadLighting( dbspmodel_t *bmod )
{
int i, lightofs;
msurface_t *surf;
color24 *out;
byte *in;
if( !bmod->lightdatasize )
return;
switch( bmod->lightmap_samples )
{
case 1:
if( !Mod_LoadColoredLighting( bmod ))
{
loadmodel->lightdata = out = (color24 *)Mem_Malloc( loadmodel->mempool, bmod->lightdatasize * sizeof( color24 ));
in = bmod->lightdata;
// expand the white lighting data
for( i = 0; i < bmod->lightdatasize; i++, out++ )
out->r = out->g = out->b = *in++;
}
break;
case 3: // load colored lighting
loadmodel->lightdata = Mem_Malloc( loadmodel->mempool, bmod->lightdatasize );
memcpy( loadmodel->lightdata, bmod->lightdata, bmod->lightdatasize );
SetBits( loadmodel->flags, MODEL_COLORED_LIGHTING );
break;
default:
Host_Error( "Mod_LoadLighting: bad lightmap sample count %i\n", bmod->lightmap_samples );
break;
}
// not supposed to be load ?
if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
{
Mod_LoadLightVecs( bmod );
Mod_LoadShadowmap( bmod );
if( bmod->isworld && bmod->deluxdatasize )
SetBits( world.flags, FWORLD_HAS_DELUXEMAP );
}
surf = loadmodel->surfaces;
// setup lightdata pointers
for( i = 0; i < loadmodel->numsurfaces; i++, surf++ )
{
if( bmod->version == QBSP2_VERSION )
lightofs = bmod->surfaces32[i].lightofs;
else lightofs = bmod->surfaces[i].lightofs;
if( loadmodel->lightdata && lightofs != -1 )
{
int offset = (lightofs / bmod->lightmap_samples);
// NOTE: we divide offset by three because lighting and deluxemap keep their pointers
// into three-bytes structs and shadowmap just monochrome
surf->samples = loadmodel->lightdata + offset;
// if deluxemap is present setup it too
if( bmod->deluxedata_out )
surf->info->deluxemap = bmod->deluxedata_out + offset;
// will be used by mods
if( bmod->shadowdata_out )
surf->info->shadowmap = bmod->shadowdata_out + offset;
}
}
}
/*
=================
Mod_LoadBmodelLumps
loading and processing bmodel
=================
*/
qboolean Mod_LoadBmodelLumps( const byte *mod_base, qboolean isworld )
{
dheader_t *header = (dheader_t *)mod_base;
dextrahdr_t *extrahdr = (dextrahdr_t *)((byte *)mod_base + sizeof( dheader_t ));
dbspmodel_t *bmod = &srcmodel;
model_t *mod = loadmodel;
char wadvalue[2048];
int i;
// always reset the intermediate struct
memset( bmod, 0, sizeof( dbspmodel_t ));
memset( &loadstat, 0, sizeof( loadstat_t ));
Q_strncpy( loadstat.name, loadmodel->name, sizeof( loadstat.name ));
wadvalue[0] = '\0';
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, loadmodel->name );
return false;
}
#endif
switch( header->version )
{
case Q1BSP_VERSION:
case HLBSP_VERSION:
case QBSP2_VERSION:
break;
default:
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", loadmodel->name, header->version, HLBSP_VERSION );
loadstat.numerrors++;
return false;
}
bmod->version = header->version; // share up global
if( isworld ) world.flags = 0; // clear world settings
bmod->isworld = isworld;
if( header->version == HLBSP_VERSION &&
header->lumps[LUMP_ENTITIES].fileofs <= 1024 &&
(header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 )
{
// blue-shift swapped lumps
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
}
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], isworld ? (LUMP_SAVESTATS|LUMP_SILENT) : 0 );
if( !bmod->isworld && loadstat.numerrors )
{
Con_DPrintf( "Mod_Load%s: %i error(s), %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numerrors, loadstat.numwarnings );
return false; // there were errors, we can't load this map
}
else if( !bmod->isworld && loadstat.numwarnings )
Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings );
// load into heap
Mod_LoadEntities( bmod );
Mod_LoadPlanes( bmod );
Mod_LoadSubmodels( bmod );
Mod_LoadVertexes( bmod );
Mod_LoadEdges( bmod );
Mod_LoadSurfEdges( bmod );
Mod_LoadTextures( bmod );
Mod_LoadVisibility( bmod );
Mod_LoadTexInfo( bmod );
Mod_LoadSurfaces( bmod );
Mod_LoadLighting( bmod );
Mod_LoadMarkSurfaces( bmod );
Mod_LoadLeafs( bmod );
Mod_LoadNodes( bmod );
Mod_LoadClipnodes( bmod );
// preform some post-initalization
Mod_MakeHull0 ();
Mod_SetupSubmodels( bmod );
if( isworld )
{
loadmodel = mod; // restore pointer to world
#if !XASH_DEDICATED
Mod_InitDebugHulls(); // FIXME: build hulls for separate bmodels (shells, medkits etc)
world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer
world.shadowdata = bmod->shadowdata_out; // occlusion data pointer
#endif // XASH_DEDICATED
}
for( i = 0; i < bmod->wadlist.count; i++ )
{
if( !bmod->wadlist.wadusage[i] )
continue;
Q_strncat( wadvalue, va( "%s.wad; ", bmod->wadlist.wadnames[i] ), sizeof( wadvalue ));
}
if( COM_CheckString( wadvalue ))
{
wadvalue[Q_strlen( wadvalue ) - 2] = '\0'; // kill the last semicolon
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
}
return true;
}
/*
=================
Mod_TestBmodelLumps
check for possible errors
=================
*/
qboolean Mod_TestBmodelLumps( const char *name, const byte *mod_base, qboolean silent )
{
dheader_t *header = (dheader_t *)mod_base;
int i, flags = LUMP_TESTONLY;
// always reset the intermediate struct
memset( &loadstat, 0, sizeof( loadstat_t ));
// store the name to correct show errors and warnings
Q_strncpy( loadstat.name, name, sizeof( loadstat.name ));
if( silent ) SetBits( flags, LUMP_SILENT );
#ifndef SUPPORT_BSP2_FORMAT
if( header->version == QBSP2_VERSION )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, name );
return false;
}
#endif
switch( header->version )
{
case Q1BSP_VERSION:
case HLBSP_VERSION:
case QBSP2_VERSION:
break;
default:
// don't early out: let me analyze errors
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", name, header->version, HLBSP_VERSION );
loadstat.numerrors++;
break;
}
if( header->version == HLBSP_VERSION &&
header->lumps[LUMP_ENTITIES].fileofs <= 1024 &&
(header->lumps[LUMP_ENTITIES].filelen % sizeof( dplane_t )) == 0 )
{
// blue-shift swapped lumps
srclumps[0].lumpnumber = LUMP_PLANES;
srclumps[1].lumpnumber = LUMP_ENTITIES;
}
// loading base lumps
for( i = 0; i < ARRAYSIZE( srclumps ); i++ )
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );
// loading extralumps
for( i = 0; i < ARRAYSIZE( extlumps ); i++ )
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );
if( loadstat.numerrors )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( "Mod_LoadWorld: %i error(s), %i warning(s)\n", loadstat.numerrors, loadstat.numwarnings );
return false; // there were errors, we can't load this map
}
else if( loadstat.numwarnings )
{
if( !FBitSet( flags, LUMP_SILENT ))
Con_Printf( "Mod_LoadWorld: %i warning(s)\n", loadstat.numwarnings );
}
return true;
}
/*
=================
Mod_LoadBrushModel
=================
*/
void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded )
{
if( loaded ) *loaded = false;
loadmodel->mempool = Mem_AllocPool( va( "^2%s^7", loadmodel->name ));
loadmodel->type = mod_brush;
// loading all the lumps into heap
if( !Mod_LoadBmodelLumps( buffer, world.loading ))
return; // there were errors
if( world.loading ) worldmodel = mod;
if( loaded ) *loaded = true; // all done
}
/*
==================
Mod_CheckLump
check lump for existing
==================
*/
int Mod_CheckLump( const char *filename, const int lump, int *lumpsize )
{
file_t *f = FS_Open( filename, "rb", false );
byte buffer[sizeof( dheader_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 )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
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", false );
byte buffer[sizeof( dheader_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 )
{
FS_Close( f );
return LUMP_LOAD_BAD_VERSION;
}
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 )
{
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 )
{
byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];
size_t prefetch_size = sizeof( buffer );
int result, dummy = lumpsize;
dextrahdr_t *extrahdr;
dheader_t *header;
file_t *f;
if( !lumpdata || lumpsize <= 0 )
return LUMP_SAVE_NO_DATA;
// make sure what .bsp is placed into gamedir and not in pak
if( !FS_GetDiskPath( filename, true ))
return LUMP_SAVE_COULDNT_OPEN;
// first we should sure what we allow to rewrite this .bsp
result = Mod_CheckLump( filename, lump, &dummy );
if( result != LUMP_LOAD_NOT_EXIST )
return result;
f = FS_Open( filename, "e+b", true );
if( !f ) return LUMP_SAVE_COULDNT_OPEN;
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
{
FS_Close( f );
return LUMP_SAVE_BAD_HEADER;
}
header = (dheader_t *)buffer;
// these checks below are redundant
if( header->version != HLBSP_VERSION )
{
FS_Close( f );
return LUMP_SAVE_BAD_VERSION;
}
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
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;
}