2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
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.
|
|
|
|
*/
|
2019-03-16 05:15:06 +01:00
|
|
|
#include "common.h"
|
2018-04-13 18:23:45 +02:00
|
|
|
#include "mod_local.h"
|
|
|
|
#include "sprite.h"
|
2020-02-10 11:07:06 +01:00
|
|
|
#include "xash3d_mathlib.h"
|
2018-04-13 18:23:45 +02:00
|
|
|
#include "alias.h"
|
|
|
|
#include "studio.h"
|
|
|
|
#include "wadfile.h"
|
|
|
|
#include "world.h"
|
2018-04-13 18:26:48 +02:00
|
|
|
#include "enginefeatures.h"
|
2018-04-13 18:23:45 +02:00
|
|
|
#include "client.h"
|
|
|
|
#include "server.h" // LUMP_ error codes
|
2019-02-27 00:23:03 +01:00
|
|
|
#include "ref_common.h"
|
2024-05-29 04:16:02 +02:00
|
|
|
#if defined( HAVE_OPENMP )
|
|
|
|
#include <omp.h>
|
|
|
|
#endif // HAVE_OPENMP
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
#define MIPTEX_CUSTOM_PALETTE_SIZE_BYTES ( sizeof( int16_t ) + 768 )
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
typedef struct leaflist_s
|
|
|
|
{
|
|
|
|
int count;
|
|
|
|
int maxcount;
|
|
|
|
qboolean overflowed;
|
2019-05-19 14:01:23 +02:00
|
|
|
int *list;
|
2018-04-13 18:23:45 +02:00
|
|
|
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;
|
|
|
|
};
|
2019-11-28 17:18:50 +01:00
|
|
|
size_t numleafs;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
union
|
|
|
|
{
|
|
|
|
dclipnode_t *clipnodes;
|
|
|
|
dclipnode32_t *clipnodes32;
|
|
|
|
};
|
2019-11-28 17:18:50 +01:00
|
|
|
size_t numclipnodes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
int lightmap_samples; // samples per lightmap (1 or 3)
|
|
|
|
int version; // model version
|
|
|
|
qboolean isworld;
|
2023-01-03 05:50:50 +01:00
|
|
|
qboolean isbsp30ext;
|
2018-04-13 18:23:45 +02:00
|
|
|
} 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 )
|
2023-01-03 05:50:50 +01:00
|
|
|
#define LUMP_BSP30EXT BIT( 3 ) // extra marker for Mod_LoadLump
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
typedef struct
|
|
|
|
{
|
2024-11-05 22:33:08 +01:00
|
|
|
int lumpnumber;
|
|
|
|
int flags;
|
|
|
|
const size_t mincount;
|
|
|
|
const size_t maxcount;
|
|
|
|
const int entrysize;
|
|
|
|
const int entrysize32; // alternative (-1 by default)
|
|
|
|
const char *loadname;
|
|
|
|
const void **dataptr;
|
|
|
|
size_t *count;
|
2018-04-13 18:23:45 +02:00
|
|
|
} mlumpinfo_t;
|
|
|
|
|
|
|
|
world_static_t world;
|
|
|
|
static dbspmodel_t srcmodel;
|
|
|
|
static loadstat_t loadstat;
|
|
|
|
static model_t *worldmodel;
|
2018-10-04 08:08:48 +02:00
|
|
|
static byte g_visdata[(MAX_MAP_LEAFS+7)/8]; // intermediate buffer
|
2024-11-05 22:33:08 +01:00
|
|
|
static mlumpstat_t worldstats[HEADER_LUMPS+EXTRA_LUMPS];
|
|
|
|
static mlumpinfo_t srclumps[HEADER_LUMPS] =
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-05 22:33:08 +01:00
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_ENTITIES,
|
|
|
|
.mincount = 32,
|
|
|
|
.maxcount = MAX_MAP_ENTSTRING,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "entities",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.entdata,
|
|
|
|
.count = &srcmodel.entdatasize,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_PLANES,
|
|
|
|
.mincount = 1,
|
|
|
|
.maxcount = MAX_MAP_PLANES,
|
|
|
|
.entrysize = sizeof( dplane_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "planes",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.planes,
|
|
|
|
.count = &srcmodel.numplanes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_TEXTURES,
|
|
|
|
.mincount = 1,
|
|
|
|
.maxcount = MAX_MAP_MIPTEX,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "textures",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.textures,
|
|
|
|
.count = &srcmodel.texdatasize,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_VERTEXES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_VERTS,
|
|
|
|
.entrysize = sizeof( dvertex_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "vertexes",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.vertexes,
|
|
|
|
.count = &srcmodel.numvertexes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_VISIBILITY,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_VISIBILITY,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "visibility",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.visdata,
|
|
|
|
.count = &srcmodel.visdatasize,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_NODES,
|
|
|
|
.mincount = 1,
|
|
|
|
.maxcount = MAX_MAP_NODES,
|
|
|
|
.entrysize = sizeof( dnode_t ),
|
|
|
|
.entrysize32 = sizeof( dnode32_t ),
|
|
|
|
.loadname = "nodes",
|
|
|
|
.flags = CHECK_OVERFLOW,
|
|
|
|
.dataptr = (const void **)&srcmodel.nodes,
|
|
|
|
.count = &srcmodel.numnodes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_TEXINFO,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_TEXINFO,
|
|
|
|
.entrysize = sizeof( dtexinfo_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "texinfo",
|
|
|
|
.flags = CHECK_OVERFLOW,
|
|
|
|
.dataptr = (const void **)&srcmodel.texinfo,
|
|
|
|
.count = &srcmodel.numtexinfo,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_FACES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_FACES,
|
|
|
|
.entrysize = sizeof( dface_t ),
|
|
|
|
.entrysize32 = sizeof( dface32_t ),
|
|
|
|
.loadname = "faces",
|
|
|
|
.flags = CHECK_OVERFLOW,
|
|
|
|
.dataptr = (const void **)&srcmodel.surfaces,
|
|
|
|
.count = &srcmodel.numsurfaces,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_LIGHTING,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_LIGHTING,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "lightmaps",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.lightdata,
|
|
|
|
.count = &srcmodel.lightdatasize,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_CLIPNODES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_CLIPNODES,
|
|
|
|
.entrysize = sizeof( dclipnode_t ),
|
|
|
|
.entrysize32 = sizeof( dclipnode32_t ),
|
|
|
|
.loadname = "clipnodes",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.clipnodes,
|
|
|
|
.count = &srcmodel.numclipnodes,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_LEAFS,
|
|
|
|
.mincount = 1,
|
|
|
|
.maxcount = MAX_MAP_LEAFS,
|
|
|
|
.entrysize = sizeof( dleaf_t ),
|
|
|
|
.entrysize32 = sizeof( dleaf32_t ),
|
|
|
|
.loadname = "leafs",
|
|
|
|
.flags = CHECK_OVERFLOW,
|
|
|
|
.dataptr = (const void **)&srcmodel.leafs,
|
|
|
|
.count = &srcmodel.numleafs,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_MARKSURFACES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_MARKSURFACES,
|
|
|
|
.entrysize = sizeof( dmarkface_t ),
|
|
|
|
.entrysize32 = sizeof( dmarkface32_t ),
|
|
|
|
.loadname = "markfaces",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.markfaces,
|
|
|
|
.count = &srcmodel.nummarkfaces,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_EDGES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_EDGES,
|
|
|
|
.entrysize = sizeof( dedge_t ),
|
|
|
|
.entrysize32 = sizeof( dedge32_t ),
|
|
|
|
.loadname = "edges",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.edges,
|
|
|
|
.count = &srcmodel.numedges,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_SURFEDGES,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_SURFEDGES,
|
|
|
|
.entrysize = sizeof( dsurfedge_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "surfedges",
|
|
|
|
.flags = 0,
|
|
|
|
.dataptr = (const void **)&srcmodel.surfedges,
|
|
|
|
.count = &srcmodel.numsurfedges,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_MODELS,
|
|
|
|
.mincount = 1,
|
|
|
|
.maxcount = MAX_MAP_MODELS,
|
|
|
|
.entrysize = sizeof( dmodel_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "models",
|
|
|
|
.flags = CHECK_OVERFLOW,
|
|
|
|
.dataptr = (const void **)&srcmodel.submodels,
|
|
|
|
.count = &srcmodel.numsubmodels,
|
|
|
|
},
|
2018-04-13 18:23:45 +02:00
|
|
|
};
|
|
|
|
|
2024-11-05 22:33:08 +01:00
|
|
|
static const mlumpinfo_t extlumps[EXTRA_LUMPS] =
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-05 22:33:08 +01:00
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_LIGHTVECS,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_LIGHTING,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "deluxmaps",
|
|
|
|
.flags = USE_EXTRAHEADER,
|
|
|
|
.dataptr = (const void **)&srcmodel.deluxdata,
|
|
|
|
.count = &srcmodel.deluxdatasize,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_FACEINFO,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_FACEINFO,
|
|
|
|
.entrysize = sizeof( dfaceinfo_t ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "faceinfos",
|
|
|
|
.flags = CHECK_OVERFLOW|USE_EXTRAHEADER,
|
|
|
|
.dataptr = (const void **)&srcmodel.faceinfo,
|
|
|
|
.count = &srcmodel.numfaceinfo,
|
|
|
|
},
|
|
|
|
{
|
|
|
|
.lumpnumber = LUMP_SHADOWMAP,
|
|
|
|
.mincount = 0,
|
|
|
|
.maxcount = MAX_MAP_LIGHTING / 3,
|
|
|
|
.entrysize = sizeof( byte ),
|
|
|
|
.entrysize32 = -1,
|
|
|
|
.loadname = "shadowmap",
|
|
|
|
.flags = USE_EXTRAHEADER,
|
|
|
|
.dataptr = (const void **)&srcmodel.shadowdata,
|
|
|
|
.count = &srcmodel.shadowdatasize,
|
|
|
|
},
|
2018-04-13 18:23:45 +02:00
|
|
|
};
|
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
Static helper functions
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
static mip_t *Mod_GetMipTexForTexture( dbspmodel_t *bmod, int i )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
if( i < 0 || i >= bmod->textures->nummiptex )
|
|
|
|
return NULL;
|
|
|
|
|
|
|
|
if( bmod->textures->dataofs[i] == -1 )
|
2023-04-07 15:34:41 +02:00
|
|
|
return NULL;
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
return (mip_t *)((byte *)bmod->textures + bmod->textures->dataofs[i] );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// Returns index of WAD that texture was found in, or -1 if not found.
|
2024-11-23 13:02:49 +01:00
|
|
|
static int Mod_LoadTextureFromWadList( wadlist_t *list, const char *name, rgbdata_t **pic, char *texpath, size_t texpathlen )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
int i;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !list || !COM_CheckString( name ))
|
2023-04-07 15:34:41 +02:00
|
|
|
return -1;
|
|
|
|
|
|
|
|
// check wads in reverse order
|
2023-04-11 01:29:17 +02:00
|
|
|
for( i = list->count - 1; i >= 0; i-- )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2024-11-23 13:02:49 +01:00
|
|
|
searchpath_t *sp = NULL;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2024-11-23 13:02:49 +01:00
|
|
|
while(( sp = g_fsapi.GetArchiveByName( list->wadnames[i], sp )))
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2024-11-23 13:02:49 +01:00
|
|
|
fs_offset_t len;
|
|
|
|
byte *buf;
|
|
|
|
char file[MAX_VA_STRING];
|
|
|
|
int pack_ind;
|
|
|
|
|
|
|
|
Q_snprintf( file, sizeof( file ), "%s.mip", name );
|
|
|
|
pack_ind = g_fsapi.FindFileInArchive( sp, file, NULL, 0 );
|
|
|
|
|
|
|
|
if( pack_ind < 0 )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
if( texpath != NULL )
|
|
|
|
Q_snprintf( texpath, texpathlen, "%s/%s.mip", list->wadnames[i], name );
|
|
|
|
|
|
|
|
if( pic == NULL )
|
|
|
|
return i; // dedicated server don't want to load the textures (why?)
|
|
|
|
|
|
|
|
if( !( buf = g_fsapi.LoadFileFromArchive( sp, file, pack_ind, &len, false )))
|
|
|
|
{
|
|
|
|
*pic = NULL;
|
|
|
|
return i; // corrupted file, don't ignore it
|
|
|
|
}
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2024-11-23 13:02:49 +01:00
|
|
|
// tell imagelib to directly load this texture to save time
|
|
|
|
Q_snprintf( file, sizeof( file ), "#%s/%s.mip", list->wadnames[i], name );
|
|
|
|
*pic = FS_LoadImage( file, buf, len );
|
|
|
|
Mem_Free( buf );
|
|
|
|
return i; // if file is corrupted, it's fine, we want to tell the user about it
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
static fs_offset_t Mod_CalculateMipTexSize( mip_t *mt, qboolean palette )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !mt )
|
2023-04-07 15:34:41 +02:00
|
|
|
return 0;
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
return sizeof( *mt ) + (( mt->width * mt->height * 85 ) >> 6 ) +
|
|
|
|
( palette ? MIPTEX_CUSTOM_PALETTE_SIZE_BYTES : 0 );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static qboolean Mod_CalcMipTexUsesCustomPalette( model_t *mod, dbspmodel_t *bmod, int textureIndex )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
|
|
|
int nextTextureIndex = 0;
|
2023-04-11 01:29:17 +02:00
|
|
|
mip_t *mipTex;
|
|
|
|
fs_offset_t size, remainingBytes;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
|
|
|
|
|
|
|
|
if( !mipTex || mipTex->offsets[0] <= 0 )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Calculate the size assuming we are not using a custom palette.
|
2023-04-11 01:29:17 +02:00
|
|
|
size = Mod_CalculateMipTexSize( mipTex, false );
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
// Compute next data offset to determine allocated miptex space
|
2023-10-28 15:06:00 +02:00
|
|
|
for( nextTextureIndex = textureIndex + 1; nextTextureIndex < mod->numtextures; nextTextureIndex++ )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
|
|
|
int nextOffset = bmod->textures->dataofs[nextTextureIndex];
|
|
|
|
|
|
|
|
if( nextOffset != -1 )
|
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
remainingBytes = nextOffset - ( bmod->textures->dataofs[textureIndex] + size );
|
2023-04-07 15:34:41 +02:00
|
|
|
return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// There was no other miptex after this one.
|
|
|
|
// See if there is enough space between the end and our offset.
|
2023-04-11 01:29:17 +02:00
|
|
|
remainingBytes = bmod->texdatasize - ( bmod->textures->dataofs[textureIndex] + size );
|
|
|
|
return remainingBytes >= MIPTEX_CUSTOM_PALETTE_SIZE_BYTES;
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
static qboolean Mod_NameImpliesTextureIsAnimated( texture_t *tex )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
|
|
|
if( !tex )
|
|
|
|
return false;
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
// Not an animated texture name
|
2023-04-07 15:34:41 +02:00
|
|
|
if( tex->name[0] != '-' && tex->name[0] != '+' )
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// Name implies texture is animated - check second character is valid.
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !( tex->name[1] >= '0' && tex->name[1] <= '9' ) &&
|
|
|
|
!( tex->name[1] >= 'a' && tex->name[1] <= 'j' ))
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: animating texture \"%s\" has invalid name\n", __func__, tex->name );
|
2023-04-07 15:34:41 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_CreateDefaultTexture( model_t *mod, texture_t **texture )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *tex;
|
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Pointer must be valid, and value pointed to must be null.
|
|
|
|
if( !texture || *texture != NULL )
|
|
|
|
return;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
*texture = tex = Mem_Calloc( mod->mempool, sizeof( *tex ));
|
2023-04-11 01:29:17 +02:00
|
|
|
Q_strncpy( tex->name, REF_DEFAULT_TEXTURE, sizeof( tex->name ));
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
#if !XASH_DEDICATED
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !Host_IsDedicated( ))
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
tex->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
|
|
|
|
tex->width = 16;
|
|
|
|
tex->height = 16;
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
#endif // XASH_DEDICATED
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
MAP PROCESSING
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadLump
|
|
|
|
|
|
|
|
generic loader
|
|
|
|
=================
|
|
|
|
*/
|
2024-10-14 18:36:59 +02:00
|
|
|
static void Mod_LoadLump( const byte *in, const mlumpinfo_t *info, mlumpstat_t *stat, int flags )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
2023-01-03 05:50:50 +01:00
|
|
|
else if( version == HLBSP_VERSION && FBitSet( flags, LUMP_BSP30EXT ) && info->lumpnumber == LUMP_CLIPNODES )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-01-03 05:50:50 +01:00
|
|
|
// if this map is bsp30ext, try to guess extended clipnodes
|
|
|
|
if((( l->filelen % info->entrysize ) || ( l->filelen / info->entrysize32 ) >= MAX_MAP_CLIPNODES_HLBSP ))
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
real_entrysize = info->entrysize32;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 ))
|
|
|
|
{
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_DPrintf( S_WARN "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
|
2018-04-13 18:23:45 +02:00
|
|
|
loadstat.numwarnings++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else if( info->mincount > 0 )
|
|
|
|
{
|
|
|
|
// it has the mincount and the lump is completely missed!
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
|
2018-04-13 18:23:45 +02:00
|
|
|
loadstat.numerrors++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( l->filelen % real_entrysize )
|
|
|
|
{
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
2023-04-09 22:56:28 +02:00
|
|
|
Con_DPrintf( S_ERROR "Mod_Load%s: Lump size %d was not a multiple of %zu bytes\n", msg2, l->filelen, real_entrysize );
|
2018-04-13 18:23:45 +02:00
|
|
|
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 ))
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_DPrintf( S_ERROR "map ^2%s^7 has no %s\n", loadstat.name, msg1 );
|
2018-04-13 18:23:45 +02:00
|
|
|
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 ))
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_DPrintf( S_ERROR "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
|
2018-04-13 18:23:45 +02:00
|
|
|
loadstat.numerrors++;
|
|
|
|
return;
|
|
|
|
}
|
2018-06-12 11:14:56 +02:00
|
|
|
else if( !FBitSet( flags, LUMP_SILENT ))
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
// just throw warning
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_DPrintf( S_WARN "map ^2%s^7 has too many %s\n", loadstat.name, msg1 );
|
2018-04-13 18:23:45 +02:00
|
|
|
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 );
|
|
|
|
|
2018-06-09 00:28:35 +02:00
|
|
|
if( percentage > 99.99f )
|
2018-04-13 18:23:45 +02:00
|
|
|
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 );
|
|
|
|
|
2018-06-09 00:28:35 +02:00
|
|
|
if( percentage > 99.99f )
|
2018-04-13 18:23:45 +02:00
|
|
|
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 );
|
2022-12-20 03:39:08 +01:00
|
|
|
Con_Printf( "internal name: ^2%s\n", world.message[0] ? world.message : "none" );
|
|
|
|
Con_Printf( "map compiler: ^3%s\n", world.compiler[0] ? world.compiler : "unknown" );
|
|
|
|
Con_Printf( "map editor: ^2%s\n", world.generator[0] ? world.generator : "unknown" );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
COMMON ROUTINES
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
===================
|
2018-10-04 08:08:48 +02:00
|
|
|
Mod_DecompressPVS
|
2024-05-26 22:12:16 +02:00
|
|
|
|
|
|
|
TODO: replace all Mod_DecompressPVS calls by this
|
2018-04-13 18:23:45 +02:00
|
|
|
===================
|
|
|
|
*/
|
2024-05-26 22:12:16 +02:00
|
|
|
static void Mod_DecompressPVSTo( byte *const out, const byte *in, size_t visbytes )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-05-26 22:12:16 +02:00
|
|
|
byte *dst = out;
|
2018-10-04 08:08:48 +02:00
|
|
|
|
2024-05-26 22:12:16 +02:00
|
|
|
if( !in ) // no visinfo, make all visible
|
2021-01-03 02:28:45 +01:00
|
|
|
{
|
2024-05-26 22:12:16 +02:00
|
|
|
memset( out, 0xFF, visbytes );
|
|
|
|
return;
|
2018-10-04 08:08:48 +02:00
|
|
|
}
|
|
|
|
|
2024-05-26 22:12:16 +02:00
|
|
|
while( dst < out + visbytes )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-05-26 22:12:16 +02:00
|
|
|
if( *in ) // uncompressed
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-05-26 22:12:16 +02:00
|
|
|
*dst++ = *in++;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2024-05-26 22:12:16 +02:00
|
|
|
else // zero repeated `c` times
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-05-26 22:12:16 +02:00
|
|
|
size_t c = in[1];
|
|
|
|
if( c > out + visbytes - dst )
|
|
|
|
c = out + visbytes - dst;
|
|
|
|
|
|
|
|
memset( dst, 0, c );
|
|
|
|
in += 2;
|
|
|
|
dst += c;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2024-05-26 22:12:16 +02:00
|
|
|
}
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-05-26 22:12:16 +02:00
|
|
|
/*
|
|
|
|
===================
|
|
|
|
Mod_DecompressPVS
|
|
|
|
===================
|
|
|
|
*/
|
|
|
|
static byte *Mod_DecompressPVS( const byte *in, int visbytes )
|
|
|
|
{
|
|
|
|
Mod_DecompressPVSTo( g_visdata, in, visbytes );
|
2018-10-04 08:08:48 +02:00
|
|
|
return g_visdata;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
2024-05-29 04:16:02 +02:00
|
|
|
static size_t Mod_CompressPVS( byte *const out, const byte *in, size_t inbytes )
|
2024-05-27 12:18:05 +02:00
|
|
|
{
|
|
|
|
size_t i;
|
|
|
|
byte *dst = out;
|
|
|
|
|
|
|
|
for( i = 0; i < inbytes; i++ )
|
|
|
|
{
|
|
|
|
size_t j = i + 1, rep = 1;
|
|
|
|
|
|
|
|
*dst++ = in[i];
|
|
|
|
|
|
|
|
// only compress zeros
|
|
|
|
if( in[i] )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for( ; j < inbytes && rep != 255; j++, rep++ )
|
|
|
|
{
|
|
|
|
if( in[j] )
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
*dst++ = rep;
|
|
|
|
i = j - 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
return dst - out;
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
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 )
|
|
|
|
{
|
2024-05-27 12:21:04 +02:00
|
|
|
mleaf_t *leaf;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
ASSERT( worldmodel != NULL );
|
|
|
|
|
2024-05-27 12:21:04 +02:00
|
|
|
leaf = Mod_PointInLeaf( p, worldmodel->nodes );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( leaf && leaf->cluster >= 0 )
|
2018-10-04 08:08:48 +02:00
|
|
|
return Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );
|
2018-04-13 18:23:45 +02:00
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
Mod_FatPVS_RecursiveBSPNode
|
|
|
|
|
|
|
|
==================
|
|
|
|
*/
|
2024-05-27 12:17:43 +02:00
|
|
|
static void Mod_FatPVS_RecursiveBSPNode( const vec3_t org, float radius, byte *visbuffer, int visbytes, mnode_t *node, qboolean phs )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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
|
2024-05-27 12:17:43 +02:00
|
|
|
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, visbytes, node->children[0], phs );
|
2018-04-13 18:23:45 +02:00
|
|
|
node = node->children[1];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// if this leaf is in a cluster, accumulate the vis bits
|
|
|
|
if(((mleaf_t *)node)->cluster >= 0 )
|
|
|
|
{
|
2024-05-29 04:16:02 +02:00
|
|
|
byte *vis;
|
|
|
|
|
|
|
|
if( phs )
|
|
|
|
{
|
|
|
|
int i = ((mleaf_t *)node)->cluster + 1;
|
|
|
|
vis = Mod_DecompressPVS( &world.compressed_phs[world.phsofs[i]], world.visbytes );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
vis = Mod_DecompressPVS( ((mleaf_t *)node)->compressed_vis, world.visbytes );
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-05-27 12:17:43 +02:00
|
|
|
Q_memor( visbuffer, vis, visbytes );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
Mod_FatPVS_RecursiveBSPNode
|
|
|
|
|
|
|
|
Calculates a PVS that is the inclusive or of all leafs
|
|
|
|
within radius pixels of the given point.
|
|
|
|
==================
|
|
|
|
*/
|
2024-05-27 12:17:43 +02:00
|
|
|
int Mod_FatPVS( const vec3_t org, float radius, byte *visbuffer, int visbytes, qboolean merge, qboolean fullvis, qboolean phs )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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
|
2018-10-04 08:08:48 +02:00
|
|
|
if( fullvis || !worldmodel->visdata || !leaf || leaf->cluster < 0 )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
memset( visbuffer, 0xFF, bytes );
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2024-05-29 04:16:02 +02:00
|
|
|
// requested PHS but we don't have PHS for some reason
|
|
|
|
// enable full visibility
|
|
|
|
if( phs && !( world.compressed_phs && world.phsofs ))
|
|
|
|
{
|
|
|
|
memset( visbuffer, 0xFF, bytes );
|
|
|
|
return bytes;
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
if( !merge ) memset( visbuffer, 0x00, bytes );
|
|
|
|
|
2024-05-27 12:17:43 +02:00
|
|
|
Mod_FatPVS_RecursiveBSPNode( org, radius, visbuffer, bytes, worldmodel->nodes, phs );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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;
|
|
|
|
}
|
2021-01-03 02:28:45 +01:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
==================
|
|
|
|
*/
|
2019-05-19 14:01:23 +02:00
|
|
|
static int Mod_BoxLeafnums( const vec3_t mins, const vec3_t maxs, int *list, int listsize, int *topnode )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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 )
|
|
|
|
{
|
2019-05-19 14:01:23 +02:00
|
|
|
int leafList[MAX_BOX_LEAFS];
|
2018-04-13 18:23:45 +02:00
|
|
|
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_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;
|
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !entities || !COM_CheckString( modelname ))
|
2018-04-13 18:23:45 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
if( !origin || !VectorIsNull( origin ))
|
|
|
|
return;
|
|
|
|
|
|
|
|
pfile = (char *)entities;
|
|
|
|
|
2021-10-01 19:40:36 +02:00
|
|
|
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if( token[0] != '{' )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: found %s when expecting {\n", __func__, token );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
model_found = origin_found = false;
|
|
|
|
VectorClear( origin );
|
|
|
|
|
|
|
|
while( 1 )
|
|
|
|
{
|
|
|
|
// parse key
|
2021-10-01 19:40:36 +02:00
|
|
|
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: EOF without closing brace\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
if( token[0] == '}' ) break; // end of desc
|
|
|
|
|
|
|
|
Q_strncpy( keyname, token, sizeof( keyname ));
|
|
|
|
|
2021-01-03 02:28:45 +01:00
|
|
|
// parse value
|
2021-10-01 19:40:36 +02:00
|
|
|
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: EOF without closing brace\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( token[0] == '}' )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: closing brace without data\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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;
|
2021-01-03 02:28:45 +01:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
Mod_CheckWaterAlphaSupport
|
|
|
|
|
|
|
|
converted maps potential may don't
|
|
|
|
support water transparency
|
|
|
|
==================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static qboolean Mod_CheckWaterAlphaSupport( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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
|
2023-10-28 15:06:00 +02:00
|
|
|
for( i = 0, leaf = mod->leafs; i < mod->numleafs; i++, leaf++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if(( leaf->contents == CONTENTS_WATER || leaf->contents == CONTENTS_SLIME ) && leaf->cluster >= 0 )
|
|
|
|
{
|
2018-10-04 08:08:48 +02:00
|
|
|
pvs = Mod_DecompressPVS( leaf->compressed_vis, world.visbytes );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
for( j = 0; j < mod->numleafs; j++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
if( CHECKVISBIT( pvs, mod->leafs[j].cluster ) && mod->leafs[j].contents == CONTENTS_EMPTY )
|
2018-04-13 18:23:45 +02:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
Mod_SampleSizeForFace
|
|
|
|
|
|
|
|
return the current lightmap resolution per face
|
|
|
|
==================
|
|
|
|
*/
|
2023-12-02 15:25:18 +01:00
|
|
|
int Mod_SampleSizeForFace( const msurface_t *surf )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2019-05-19 14:01:23 +02:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
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]];
|
|
|
|
}
|
|
|
|
}
|
2021-01-03 02:28:45 +01:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
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
|
2019-10-06 05:50:32 +02:00
|
|
|
VectorScale( lmvecs[0], (1.0f / lmscale), lmvecs[0] );
|
|
|
|
VectorScale( lmvecs[1], -(1.0f / lmscale), lmvecs[1] );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2019-10-06 05:50:32 +02:00
|
|
|
lmvecs[0][3] = lmscale * 0.5f;
|
|
|
|
lmvecs[1][3] = -lmscale * 0.5f;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_CalcSurfaceExtents
|
|
|
|
|
|
|
|
Fills in surf->texturemins[] and surf->extents[]
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_CalcSurfaceExtents( model_t *mod, msurface_t *surf )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2022-02-28 16:46:19 +01:00
|
|
|
// this place is VERY critical to precision
|
|
|
|
// keep it as float, don't use double, because it causes issues with lightmap
|
|
|
|
float mins[2], maxs[2], val;
|
|
|
|
float lmmins[2], lmmaxs[2];
|
2018-04-13 18:23:45 +02:00
|
|
|
int bmins[2], bmaxs[2];
|
|
|
|
int i, j, e, sample_size;
|
|
|
|
mextrasurf_t *info = surf->info;
|
|
|
|
mtexinfo_t *tex;
|
|
|
|
mvertex_t *v;
|
|
|
|
|
|
|
|
sample_size = Mod_SampleSizeForFace( surf );
|
|
|
|
tex = surf->texinfo;
|
|
|
|
|
2021-01-03 02:28:45 +01:00
|
|
|
Mod_LightMatrixFromTexMatrix( tex, info->lmvecs );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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++ )
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
e = mod->surfedges[surf->firstedge + i];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( e >= mod->numedges || e <= -mod->numedges )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: bad edge\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]];
|
|
|
|
else v = &mod->vertexes[mod->edges[-e].v[1]];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
{
|
2022-02-28 14:37:44 +01:00
|
|
|
val = DotProductPrecise( v->position, surf->texinfo->vecs[j] ) + surf->texinfo->vecs[j][3];
|
2018-04-13 18:23:45 +02:00
|
|
|
mins[j] = Q_min( val, mins[j] );
|
|
|
|
maxs[j] = Q_max( val, maxs[j] );
|
|
|
|
}
|
|
|
|
|
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
{
|
2022-02-28 14:37:44 +01:00
|
|
|
val = DotProductPrecise( v->position, info->lmvecs[j] ) + info->lmvecs[j][3];
|
2018-04-13 18:23:45 +02:00
|
|
|
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];
|
|
|
|
}
|
|
|
|
|
2019-11-24 01:52:08 +01:00
|
|
|
#if !XASH_DEDICATED && 0 // REFTODO:
|
2018-10-04 08:08:48 +02:00
|
|
|
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] );
|
2018-11-01 21:31:16 +01:00
|
|
|
#endif // XASH_DEDICATED
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_CalcSurfaceBounds
|
|
|
|
|
|
|
|
fills in surf->mins and surf->maxs
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_CalcSurfaceBounds( model_t *mod, msurface_t *surf )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
int i, e;
|
|
|
|
mvertex_t *v;
|
|
|
|
|
|
|
|
ClearBounds( surf->info->mins, surf->info->maxs );
|
|
|
|
|
|
|
|
for( i = 0; i < surf->numedges; i++ )
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
e = mod->surfedges[surf->firstedge + i];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( e >= mod->numedges || e <= -mod->numedges )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: bad edge\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( e >= 0 ) v = &mod->vertexes[mod->edges[e].v[0]];
|
|
|
|
else v = &mod->vertexes[mod->edges[-e].v[1]];
|
2018-04-13 18:23:45 +02:00
|
|
|
AddPointToBounds( v->position, surf->info->mins, surf->info->maxs );
|
|
|
|
}
|
|
|
|
|
|
|
|
VectorAverage( surf->info->mins, surf->info->maxs, surf->info->origin );
|
|
|
|
}
|
|
|
|
|
2019-05-19 14:01:23 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_CreateFaceBevels
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_CreateFaceBevels( model_t *mod, msurface_t *surf )
|
2019-05-19 14:01:23 +02:00
|
|
|
{
|
|
|
|
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 );
|
2023-10-28 15:06:00 +02:00
|
|
|
facebevel = (byte *)Mem_Calloc( mod->mempool, size );
|
2019-05-19 14:01:23 +02:00
|
|
|
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];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i );
|
|
|
|
v1 = Mod_GetVertexByNumber( mod, surf->firstedge + (i + 1) % surf->numedges );
|
2019-05-19 14:01:23 +02:00
|
|
|
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++ )
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
v0 = Mod_GetVertexByNumber( mod, surf->firstedge + i );
|
2019-05-19 14:01:23 +02:00
|
|
|
VectorSubtract( v0->position, fb->origin, delta );
|
|
|
|
radius = DotProduct( delta, delta );
|
|
|
|
fb->radius = Q_max( radius, fb->radius );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
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
|
|
|
|
==================
|
|
|
|
*/
|
2018-11-27 14:11:26 +01:00
|
|
|
static void CountClipNodes_r( mclipnode_t *src, hull_t *hull, int nodenum )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
// 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] );
|
|
|
|
}
|
|
|
|
|
2018-11-27 14:11:26 +01:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
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] );
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
==================
|
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_MakeHull0( model_t *mod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
mnode_t *in, *child;
|
|
|
|
mclipnode_t *out;
|
|
|
|
hull_t *hull;
|
|
|
|
int i, j;
|
2021-01-03 02:28:45 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
hull = &mod->hulls[0];
|
|
|
|
hull->clipnodes = out = Mem_Malloc( mod->mempool, mod->numnodes * sizeof( *out ));
|
|
|
|
in = mod->nodes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
hull->firstclipnode = 0;
|
2023-10-28 15:06:00 +02:00
|
|
|
hull->lastclipnode = mod->numnodes - 1;
|
|
|
|
hull->planes = mod->planes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
for( i = 0; i < mod->numnodes; i++, out++, in++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
out->planenum = in->plane - mod->planes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
{
|
|
|
|
child = in->children[j];
|
|
|
|
|
|
|
|
if( child->contents < 0 )
|
|
|
|
out->children[j] = child->contents;
|
2023-10-28 15:06:00 +02:00
|
|
|
else out->children[j] = child - mod->nodes;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_SetupHull
|
|
|
|
=================
|
|
|
|
*/
|
2021-06-01 18:28:52 +02:00
|
|
|
static void Mod_SetupHull( dbspmodel_t *bmod, model_t *mod, poolhandle_t mempool, int headnode, int hullnum )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
hull_t *hull = &mod->hulls[hullnum];
|
|
|
|
|
|
|
|
// 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:
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: bad hull number %i\n", __func__, hullnum );
|
2018-04-13 18:23:45 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( VectorIsNull( hull->clip_mins ) && VectorIsNull( hull->clip_maxs ))
|
|
|
|
return; // no hull specified
|
|
|
|
|
2018-11-27 14:11:26 +01:00
|
|
|
CountClipNodes32_r( bmod->clipnodes_out, hull, headnode );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// fit array to real count
|
2018-06-09 00:28:35 +02:00
|
|
|
hull->clipnodes = (mclipnode_t *)Mem_Malloc( mempool, sizeof( mclipnode_t ) * hull->lastclipnode );
|
2018-04-13 18:23:45 +02:00
|
|
|
hull->planes = mod->planes; // share planes
|
|
|
|
hull->lastclipnode = 0; // restart counting
|
|
|
|
|
|
|
|
// remap clipnodes to 16-bit indexes
|
|
|
|
RemapClipNodes_r( bmod->clipnodes_out, hull, headnode );
|
|
|
|
}
|
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
static qboolean Mod_LoadLitfile( model_t *mod, const char *ext, size_t expected_size, color24 **out, size_t *outsize )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
char modelname[64], path[64];
|
|
|
|
int iCompare;
|
|
|
|
fs_offset_t datasize;
|
|
|
|
byte *in;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
COM_FileBase( mod->name, modelname, sizeof( modelname ));
|
2024-11-17 02:35:20 +01:00
|
|
|
Q_snprintf( path, sizeof( path ), "maps/%s.%s", modelname, ext );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:33:13 +01:00
|
|
|
if( !pfnCompareFileTime( path, mod->name, &iCompare ))
|
2018-04-13 18:23:45 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
if( iCompare < 0 ) // this may happens if level-designer used -onlyents key for hlcsg
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_Printf( S_WARN "%s probably is out of date\n", path );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
in = FS_LoadFile( path, &datasize, false );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( !in )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
Con_Printf( S_ERROR "couldn't load %s\n", path );
|
2018-04-13 18:23:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( datasize <= 8 ) // header + version
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
Con_Printf( S_ERROR "%s is too short\n", path );
|
|
|
|
goto cleanup_and_error;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( LittleLong( ((uint *)in)[0] ) != IDDELUXEMAPHEADER )
|
|
|
|
{
|
|
|
|
Con_Printf( S_ERROR "%s is corrupted\n", path );
|
|
|
|
goto cleanup_and_error;
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( LittleLong( ((uint *)in)[1] ) != DELUXEMAP_VERSION )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
Con_Printf( S_ERROR "has %s mismatched version (%u should be %u)\n", path, LittleLong( ((uint *)in)[1] ), DELUXEMAP_VERSION );
|
|
|
|
goto cleanup_and_error;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// skip header bytes
|
2024-11-17 02:35:20 +01:00
|
|
|
datasize -= 8;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( datasize != expected_size )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
Con_Printf( S_ERROR "%s has mismatched size (%li should be %zu)\n", path, (long)datasize, expected_size );
|
|
|
|
goto cleanup_and_error;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
*out = Mem_Malloc( mod->mempool, datasize );
|
|
|
|
memcpy( *out, in + 8, datasize );
|
|
|
|
*outsize = datasize;
|
|
|
|
Mem_Free( in );
|
|
|
|
return true;
|
|
|
|
|
|
|
|
cleanup_and_error:
|
2018-04-13 18:23:45 +02:00
|
|
|
Mem_Free( in );
|
2024-11-17 02:35:20 +01:00
|
|
|
return false;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_SetupSubmodels
|
|
|
|
|
|
|
|
duplicate the basic information
|
|
|
|
for embedded submodels
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_SetupSubmodels( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2018-04-20 10:41:02 +02:00
|
|
|
qboolean colored = false;
|
2021-06-01 18:28:52 +02:00
|
|
|
poolhandle_t mempool;
|
2018-04-13 18:23:45 +02:00
|
|
|
char *ents;
|
|
|
|
dmodel_t *bm;
|
2023-10-28 15:06:00 +02:00
|
|
|
const char *name = mod->name;
|
2018-04-13 18:23:45 +02:00
|
|
|
int i, j;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
ents = mod->entities;
|
|
|
|
mempool = mod->mempool;
|
|
|
|
if( FBitSet( mod->flags, MODEL_COLORED_LIGHTING ))
|
2018-04-20 10:41:02 +02:00
|
|
|
colored = true;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->numframes = 2; // regular and alternate animation
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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];
|
2018-11-27 14:11:26 +01:00
|
|
|
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] );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
|
2021-01-03 02:28:45 +01:00
|
|
|
VectorCopy( bm->mins, mod->mins );
|
2018-04-13 18:23:45 +02:00
|
|
|
VectorCopy( bm->maxs, mod->maxs );
|
|
|
|
|
|
|
|
mod->radius = RadiusFromBounds( mod->mins, mod->maxs );
|
|
|
|
mod->numleafs = bm->visleafs;
|
2018-04-20 10:41:02 +02:00
|
|
|
mod->flags = 0;
|
|
|
|
|
|
|
|
// this bit will be shared between all the submodels include worldmodel
|
|
|
|
if( colored ) SetBits( mod->flags, MODEL_COLORED_LIGHTING );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( i != 0 )
|
|
|
|
{
|
2023-03-13 04:25:57 +01:00
|
|
|
char temp[MAX_VA_STRING];
|
|
|
|
|
|
|
|
Q_snprintf( temp, sizeof( temp ), "*%i", i );
|
|
|
|
Mod_FindModelOrigin( ents, temp, bm->origin );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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
|
2023-10-28 15:06:00 +02:00
|
|
|
if( !Q_stricmp( name, "maps/c2a1.bsp" ) && ( i == 11 ))
|
2018-04-13 18:23:45 +02:00
|
|
|
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];
|
2023-10-28 15:06:00 +02:00
|
|
|
model_t *submod;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// duplicate the basic information
|
|
|
|
Q_snprintf( name, sizeof( name ), "*%i", i + 1 );
|
2023-10-28 15:06:00 +02:00
|
|
|
submod = Mod_FindName( name, true );
|
|
|
|
*submod = *mod;
|
|
|
|
Q_strncpy( submod->name, name, sizeof( submod->name ));
|
|
|
|
submod->mempool = 0;
|
|
|
|
mod = submod;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2018-06-09 00:28:35 +02:00
|
|
|
if( bmod->clipnodes_out != NULL )
|
|
|
|
Mem_Free( bmod->clipnodes_out );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
===============================================================================
|
|
|
|
|
|
|
|
MAP LOADING
|
|
|
|
|
|
|
|
===============================================================================
|
|
|
|
*/
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadSubmodels
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadSubmodels( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
dmodel_t *in, *out;
|
|
|
|
int oldmaxfaces;
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
// allocate extradata for each dmodel_t
|
2023-10-28 15:06:00 +02:00
|
|
|
out = Mem_Malloc( mod->mempool, bmod->numsubmodels * sizeof( *out ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->numsubmodels = bmod->numsubmodels;
|
|
|
|
mod->submodels = out;
|
2018-04-13 18:23:45 +02:00
|
|
|
in = bmod->submodels;
|
|
|
|
|
|
|
|
if( bmod->isworld )
|
2019-02-27 00:23:03 +01:00
|
|
|
refState.max_surfaces = 0;
|
|
|
|
oldmaxfaces = refState.max_surfaces;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
for( i = 0; i < bmod->numsubmodels; i++, in++, out++ )
|
|
|
|
{
|
|
|
|
for( j = 0; j < 3; j++ )
|
|
|
|
{
|
2018-06-19 15:22:30 +02:00
|
|
|
// reset empty bounds to prevent error
|
|
|
|
if( in->mins[j] == 999999.0f )
|
|
|
|
in->mins[j] = 0.0f;
|
|
|
|
if( in->maxs[j] == -999999.0f)
|
2021-01-03 02:28:45 +01:00
|
|
|
in->maxs[j] = 0.0f;
|
2018-06-19 15:22:30 +02:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
// 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
|
2021-01-03 02:28:45 +01:00
|
|
|
oldmaxfaces = Q_max( oldmaxfaces, out->numfaces );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// these array used to sort translucent faces in bmodels
|
2019-02-27 00:23:03 +01:00
|
|
|
if( oldmaxfaces > refState.max_surfaces )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2019-02-27 00:23:03 +01:00
|
|
|
refState.draw_surfaces = (sortedface_t *)Z_Realloc( refState.draw_surfaces, oldmaxfaces * sizeof( sortedface_t ));
|
|
|
|
refState.max_surfaces = oldmaxfaces;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 03:35:36 +01:00
|
|
|
static int Mod_LoadEntities_splitstr_handler( char *prev, char *next, void *userdata )
|
|
|
|
{
|
2024-11-17 04:28:02 +01:00
|
|
|
const char *wad;
|
|
|
|
wadlist_t *wadlist = userdata;
|
2024-11-17 03:35:36 +01:00
|
|
|
|
|
|
|
*next = '\0';
|
|
|
|
|
|
|
|
if( !COM_CheckStringEmpty( prev ))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
COM_FixSlashes( prev );
|
2024-11-17 04:28:02 +01:00
|
|
|
wad = COM_FileWithoutPath( prev );
|
|
|
|
|
|
|
|
if( Q_stricmp( COM_FileExtension( wad ), "wad" ))
|
|
|
|
return 0;
|
2024-11-17 03:35:36 +01:00
|
|
|
|
|
|
|
// make sure that wad is really exist
|
2024-11-17 04:28:02 +01:00
|
|
|
if( FS_FileExists( wad, false ))
|
2024-11-17 03:35:36 +01:00
|
|
|
{
|
2024-11-17 04:28:02 +01:00
|
|
|
int num = wadlist->count++;
|
|
|
|
Q_strncpy( wadlist->wadnames[num], wad, sizeof( wadlist->wadnames[0] ));
|
|
|
|
wadlist->wadusage[num] = 0;
|
2024-11-17 03:35:36 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 04:28:02 +01:00
|
|
|
if( wadlist->count >= ARRAYSIZE( wadlist->wadnames ))
|
2024-11-17 03:35:36 +01:00
|
|
|
return 1;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadEntities
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadEntities( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 03:35:36 +01:00
|
|
|
byte *entpatch = NULL;
|
|
|
|
char token[MAX_TOKEN];
|
|
|
|
string keyname;
|
|
|
|
char *pfile;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( bmod->isworld )
|
|
|
|
{
|
2024-11-17 03:35:36 +01:00
|
|
|
char entfilename[MAX_QPATH];
|
2019-05-02 18:12:23 +02:00
|
|
|
fs_offset_t entpatchsize;
|
2024-11-17 03:35:36 +01:00
|
|
|
int ft1, ft2;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 03:35:36 +01:00
|
|
|
// if world check for entfile too
|
2023-10-28 15:06:00 +02:00
|
|
|
Q_strncpy( entfilename, mod->name, sizeof( entfilename ));
|
2023-04-26 03:57:28 +02:00
|
|
|
COM_ReplaceExtension( entfilename, ".ent", sizeof( entfilename ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 03:35:36 +01:00
|
|
|
// make sure that entity patch is never than bsp
|
2023-10-28 15:06:00 +02:00
|
|
|
ft1 = FS_FileTime( mod->name, false );
|
2018-04-13 18:23:45 +02:00
|
|
|
ft2 = FS_FileTime( entfilename, true );
|
|
|
|
|
|
|
|
if( ft2 != -1 )
|
|
|
|
{
|
|
|
|
if( ft1 > ft2 )
|
|
|
|
{
|
2018-04-26 02:23:00 +02:00
|
|
|
Con_Printf( S_WARN "Entity patch is older than bsp. Ignored.\n" );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
else if(( entpatch = FS_LoadFile( entfilename, &entpatchsize, true )) != NULL )
|
|
|
|
{
|
|
|
|
Con_Printf( "^2Read entity patch:^7 %s\n", entfilename );
|
|
|
|
bmod->entdatasize = entpatchsize;
|
|
|
|
bmod->entdata = entpatch;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 03:35:36 +01:00
|
|
|
// make sure that we really have null terminator
|
|
|
|
mod->entities = Mem_Malloc( mod->mempool, bmod->entdatasize + 1 );
|
2023-10-28 15:06:00 +02:00
|
|
|
memcpy( mod->entities, bmod->entdata, bmod->entdatasize ); // moving to private model pool
|
2024-11-17 03:35:36 +01:00
|
|
|
mod->entities[bmod->entdatasize] = 0;
|
|
|
|
|
|
|
|
if( entpatch )
|
|
|
|
{
|
|
|
|
Mem_Free( entpatch ); // release entpatch if present
|
|
|
|
entpatch = NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !bmod->isworld )
|
|
|
|
return;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
pfile = (char *)mod->entities;
|
2018-04-13 18:23:45 +02:00
|
|
|
world.generator[0] = '\0';
|
|
|
|
world.compiler[0] = '\0';
|
|
|
|
world.message[0] = '\0';
|
2024-09-30 17:42:02 +02:00
|
|
|
world.wadlist.count = 0;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// parse all the wads for loading textures in right ordering
|
2021-10-01 19:40:36 +02:00
|
|
|
while(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) != NULL )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if( token[0] != '{' )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: found %s when expecting {\n", __func__, token );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
while( 1 )
|
|
|
|
{
|
|
|
|
// parse key
|
2021-10-01 19:40:36 +02:00
|
|
|
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: EOF without closing brace\n", __func__ );
|
2024-11-17 03:35:36 +01:00
|
|
|
|
|
|
|
if( token[0] == '}' )
|
|
|
|
break; // end of desc
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
Q_strncpy( keyname, token, sizeof( keyname ));
|
|
|
|
|
2021-01-03 02:28:45 +01:00
|
|
|
// parse value
|
2021-10-01 19:40:36 +02:00
|
|
|
if(( pfile = COM_ParseFile( pfile, token, sizeof( token ))) == NULL )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: EOF without closing brace\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( token[0] == '}' )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: closing brace without data\n", __func__ );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( !Q_stricmp( keyname, "wad" ))
|
2024-11-17 04:28:02 +01:00
|
|
|
Q_splitstr( token, ';', &world.wadlist, Mod_LoadEntities_splitstr_handler );
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadPlanes( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
dplane_t *in;
|
|
|
|
mplane_t *out;
|
|
|
|
int i, j;
|
|
|
|
|
|
|
|
in = bmod->planes;
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->planes = out = Mem_Malloc( mod->mempool, bmod->numplanes * sizeof( *out ));
|
|
|
|
mod->numplanes = bmod->numplanes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
for( i = 0; i < bmod->numplanes; i++, in++, out++ )
|
|
|
|
{
|
2018-06-09 00:28:35 +02:00
|
|
|
out->signbits = 0;
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadVertexes( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
dvertex_t *in;
|
|
|
|
mvertex_t *out;
|
|
|
|
int i;
|
|
|
|
|
|
|
|
in = bmod->vertexes;
|
2023-10-28 15:06:00 +02:00
|
|
|
out = mod->vertexes = Mem_Malloc( mod->mempool, bmod->numvertexes * sizeof( mvertex_t ));
|
|
|
|
mod->numvertexes = bmod->numvertexes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadEdges( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
medge_t *out;
|
|
|
|
int i;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->edges = out = Mem_Malloc( mod->mempool, bmod->numedges * sizeof( medge_t ));
|
|
|
|
mod->numedges = bmod->numedges;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadSurfEdges( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->surfedges = Mem_Malloc( mod->mempool, bmod->numsurfedges * sizeof( dsurfedge_t ));
|
|
|
|
memcpy( mod->surfedges, bmod->surfedges, bmod->numsurfedges * sizeof( dsurfedge_t ));
|
|
|
|
mod->numsurfedges = bmod->numsurfedges;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadMarkSurfaces
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadMarkSurfaces( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
msurface_t **out;
|
|
|
|
int i;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->marksurfaces = out = Mem_Malloc( mod->mempool, bmod->nummarkfaces * sizeof( *out ));
|
|
|
|
mod->nummarksurfaces = bmod->nummarkfaces;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( bmod->version == QBSP2_VERSION )
|
|
|
|
{
|
2024-09-05 14:23:23 +02:00
|
|
|
const dmarkface32_t *in = bmod->markfaces32;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-09-05 14:23:23 +02:00
|
|
|
for( i = 0; i < bmod->nummarkfaces; i++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-09-05 14:23:23 +02:00
|
|
|
if( in[i] < 0 || in[i] >= mod->numsurfaces )
|
|
|
|
Host_Error( "%s: bad surface number %i at %i (max %i) in '%s'\n", __func__, in[i], i, mod->numsurfaces, mod->name );
|
|
|
|
out[i] = mod->surfaces + in[i];
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2024-09-05 14:23:23 +02:00
|
|
|
const dmarkface_t *in = bmod->markfaces;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-09-05 14:23:23 +02:00
|
|
|
for( i = 0; i < bmod->nummarkfaces; i++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-09-05 14:23:23 +02:00
|
|
|
// NOTE: some of the buggy compilers have written a broken BSP file
|
|
|
|
// with marksurface pointing at negative surface, for example darkf6.bsp
|
|
|
|
// and darkf26.bsp in darkfuture mod. GoldSrc straight up writes
|
|
|
|
// invalid pointer to a surface. Try to fix up these cases...
|
|
|
|
if( mod->numsurfaces <= INT16_MAX && (int16_t)in[i] < 0 )
|
|
|
|
{
|
|
|
|
Con_Printf( S_WARN "%s: fixing up bad surface number %i at %i (max %i) in '%s'\n", __func__, in[i], i, mod->numsurfaces, mod->name );
|
|
|
|
out[i] = mod->surfaces;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( in[i] < 0 || in[i] >= mod->numsurfaces )
|
|
|
|
Host_Error( "%s: bad surface number %i at %i (max %i) in '%s'\n", __func__, in[i], i, mod->numsurfaces, mod->name );
|
|
|
|
out[i] = mod->surfaces + in[i];
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-30 04:27:35 +01:00
|
|
|
static qboolean Mod_LooksLikeWaterTexture( const char *name )
|
|
|
|
{
|
|
|
|
if(( name[0] == '*' && Q_stricmp( name, REF_DEFAULT_TEXTURE )) || name[0] == '!' )
|
|
|
|
return true;
|
|
|
|
|
|
|
|
if( !Host_IsQuakeCompatible( ))
|
|
|
|
{
|
|
|
|
if( !Q_strncmp( name, "water", 5 ) || !Q_strnicmp( name, "laser", 5 ))
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2024-06-11 05:16:21 +02:00
|
|
|
static void Mod_TextureReplacementReport( const char *modelname, const char *texname, const char *type, int gl_texturenum, const char *foundpath )
|
|
|
|
{
|
|
|
|
if( host_allow_materials.value != 2.0f )
|
|
|
|
return;
|
|
|
|
|
|
|
|
if( gl_texturenum > 0 ) // found and loaded successfully
|
|
|
|
Con_Printf( "Looking for %s:%s%s tex replacement..." S_GREEN "OK (%s)\n", modelname, texname, type, foundpath );
|
|
|
|
else if( gl_texturenum < 0 ) // not found
|
|
|
|
Con_Printf( "Looking for %s:%s%s tex replacement..." S_YELLOW "MISS (%s)\n", modelname, texname, type, foundpath );
|
|
|
|
else // found but not loaded
|
|
|
|
Con_Printf( "Looking for %s:%s%s tex replacement..." S_RED "FAIL (%s)\n", modelname, texname, type, foundpath );
|
|
|
|
}
|
|
|
|
|
|
|
|
static qboolean Mod_SearchForTextureReplacement( char *out, size_t size, const char *modelname, const char *texname, const char *type )
|
|
|
|
{
|
|
|
|
const char *subdirs[] = { modelname, "common" };
|
|
|
|
int i;
|
|
|
|
|
|
|
|
for( i = 0; i < ARRAYSIZE( subdirs ); i++ )
|
|
|
|
{
|
|
|
|
if( Q_snprintf( out, size, "materials/%s/%s%s.tga", subdirs[i], texname, type ) < 0 )
|
|
|
|
continue; // truncated name
|
|
|
|
|
|
|
|
if( g_fsapi.FileExists( out, false ))
|
|
|
|
return true; // found, load it
|
|
|
|
}
|
|
|
|
|
|
|
|
Mod_TextureReplacementReport( modelname, texname, type, -1, "not found" );
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void Mod_InitSkyClouds( model_t *mod, mip_t *mt, texture_t *tx, qboolean custom_palette )
|
2024-06-10 21:00:44 +02:00
|
|
|
{
|
|
|
|
#if !XASH_DEDICATED
|
|
|
|
rgbdata_t r_temp, *r_sky;
|
|
|
|
uint *trans, *rgba;
|
|
|
|
uint transpix;
|
|
|
|
int r, g, b;
|
|
|
|
int i, j, p;
|
2024-06-11 05:16:21 +02:00
|
|
|
string texname;
|
|
|
|
int solidskyTexture = 0, alphaskyTexture = 0;
|
2024-06-10 21:00:44 +02:00
|
|
|
|
|
|
|
if( !ref.initialized )
|
|
|
|
return;
|
|
|
|
|
2024-06-11 05:16:21 +02:00
|
|
|
if( Mod_AllowMaterials( ))
|
|
|
|
{
|
|
|
|
rgbdata_t *pic;
|
|
|
|
|
|
|
|
if( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, "_solid" ))
|
|
|
|
{
|
|
|
|
pic = FS_LoadImage( texname, NULL, 0 );
|
|
|
|
if( pic )
|
|
|
|
{
|
|
|
|
// need to do rename texture to properly cleanup these textures on reload
|
|
|
|
solidskyTexture = GL_LoadTextureInternal( "solid_sky", pic, TF_NOMIPMAP );
|
|
|
|
Mod_TextureReplacementReport( mod->name, mt->name, "_solid", solidskyTexture, texname );
|
|
|
|
FS_FreeImage( pic );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( Mod_SearchForTextureReplacement( texname, sizeof( texname ), mod->name, mt->name, "_alpha" ))
|
|
|
|
{
|
|
|
|
pic = FS_LoadImage( texname, NULL, 0 );
|
|
|
|
if( pic )
|
|
|
|
{
|
|
|
|
alphaskyTexture = GL_LoadTextureInternal( "alpha_sky", pic, TF_NOMIPMAP );
|
|
|
|
Mod_TextureReplacementReport( mod->name, mt->name, "_alpha", alphaskyTexture, texname );
|
|
|
|
FS_FreeImage( pic );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !solidskyTexture || !alphaskyTexture )
|
|
|
|
{
|
|
|
|
ref.dllFuncs.GL_FreeTexture( solidskyTexture );
|
|
|
|
ref.dllFuncs.GL_FreeTexture( alphaskyTexture );
|
|
|
|
}
|
|
|
|
else goto done; // replacements found, notify the renderer and exit
|
|
|
|
}
|
|
|
|
|
2024-06-10 21:00:44 +02:00
|
|
|
Q_snprintf( texname, sizeof( texname ), "%s%s.mip", ( mt->offsets[0] > 0 ) ? "#" : "", tx->name );
|
|
|
|
|
|
|
|
if( mt->offsets[0] > 0 )
|
|
|
|
{
|
|
|
|
size_t size = sizeof( mip_t ) + (( mt->width * mt->height * 85 ) >> 6 );
|
|
|
|
|
|
|
|
if( custom_palette )
|
|
|
|
size += sizeof( short ) + 768;
|
|
|
|
|
|
|
|
r_sky = FS_LoadImage( texname, (byte *)mt, size );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// okay loading it from wad
|
|
|
|
r_sky = FS_LoadImage( texname, NULL, 0 );
|
|
|
|
}
|
|
|
|
|
|
|
|
if( !r_sky || !r_sky->palette || r_sky->type != PF_INDEXED_32 || r_sky->height == 0 )
|
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: unable to load sky texture %s\n", __func__, tx->name );
|
2024-06-10 21:00:44 +02:00
|
|
|
|
|
|
|
if( r_sky )
|
|
|
|
FS_FreeImage( r_sky );
|
|
|
|
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
// make an average value for the back to avoid
|
|
|
|
// a fringe on the top level
|
|
|
|
trans = Mem_Malloc( host.mempool, r_sky->height * r_sky->height * sizeof( *trans ));
|
|
|
|
r = g = b = 0;
|
|
|
|
|
|
|
|
for( i = 0; i < r_sky->width >> 1; i++ )
|
|
|
|
{
|
|
|
|
for( j = 0; j < r_sky->height; j++ )
|
|
|
|
{
|
|
|
|
p = r_sky->buffer[i * r_sky->width + j + r_sky->height];
|
|
|
|
rgba = (uint *)r_sky->palette + p;
|
|
|
|
trans[(i * r_sky->height) + j] = *rgba;
|
|
|
|
r += ((byte *)rgba)[0];
|
|
|
|
g += ((byte *)rgba)[1];
|
|
|
|
b += ((byte *)rgba)[2];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
((byte *)&transpix)[0] = r / ( r_sky->height * r_sky->height );
|
|
|
|
((byte *)&transpix)[1] = g / ( r_sky->height * r_sky->height );
|
|
|
|
((byte *)&transpix)[2] = b / ( r_sky->height * r_sky->height );
|
|
|
|
((byte *)&transpix)[3] = 0;
|
|
|
|
|
|
|
|
// build a temporary image
|
|
|
|
r_temp = *r_sky;
|
|
|
|
r_temp.width = r_sky->width >> 1;
|
|
|
|
r_temp.height = r_sky->height;
|
|
|
|
r_temp.type = PF_RGBA_32;
|
|
|
|
r_temp.flags = IMAGE_HAS_COLOR;
|
|
|
|
r_temp.size = r_temp.width * r_temp.height * 4;
|
|
|
|
r_temp.buffer = (byte *)trans;
|
|
|
|
r_temp.palette = NULL;
|
|
|
|
|
|
|
|
// load it in
|
|
|
|
solidskyTexture = GL_LoadTextureInternal( "solid_sky", &r_temp, TF_NOMIPMAP );
|
|
|
|
|
|
|
|
for( i = 0; i < r_sky->width >> 1; i++ )
|
|
|
|
{
|
|
|
|
for( j = 0; j < r_sky->height; j++ )
|
|
|
|
{
|
|
|
|
p = r_sky->buffer[i * r_sky->width + j];
|
|
|
|
|
|
|
|
if( p == 0 )
|
|
|
|
{
|
|
|
|
trans[(i * r_sky->height) + j] = transpix;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
rgba = (uint *)r_sky->palette + p;
|
|
|
|
trans[(i * r_sky->height) + j] = *rgba;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
r_temp.flags = IMAGE_HAS_COLOR|IMAGE_HAS_ALPHA;
|
|
|
|
|
|
|
|
// load it in
|
|
|
|
alphaskyTexture = GL_LoadTextureInternal( "alpha_sky", &r_temp, TF_NOMIPMAP );
|
|
|
|
|
|
|
|
// clean up
|
|
|
|
FS_FreeImage( r_sky );
|
|
|
|
Mem_Free( trans );
|
|
|
|
|
2024-06-11 05:16:21 +02:00
|
|
|
if( !solidskyTexture || !alphaskyTexture )
|
|
|
|
{
|
|
|
|
ref.dllFuncs.GL_FreeTexture( solidskyTexture );
|
|
|
|
ref.dllFuncs.GL_FreeTexture( alphaskyTexture );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
done:
|
2024-06-10 21:00:44 +02:00
|
|
|
// notify the renderer
|
|
|
|
ref.dllFuncs.R_SetSkyCloudsTextures( solidskyTexture, alphaskyTexture );
|
|
|
|
|
|
|
|
if( solidskyTexture && alphaskyTexture )
|
|
|
|
SetBits( world.flags, FWORLD_SKYSPHERE );
|
|
|
|
#endif // !XASH_DEDICATED
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadTextureData( model_t *mod, dbspmodel_t *bmod, int textureIndex )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *texture = NULL;
|
|
|
|
mip_t *mipTex = NULL;
|
2023-04-07 15:34:41 +02:00
|
|
|
qboolean usesCustomPalette = false;
|
|
|
|
uint32_t txFlags = 0;
|
2024-06-11 05:16:21 +02:00
|
|
|
char texpath[MAX_VA_STRING];
|
|
|
|
char safemtname[16]; // only for external textures
|
|
|
|
qboolean load_external = false;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-05-30 05:18:10 +02:00
|
|
|
// don't load texture data on dedicated server, as there is no renderer.
|
|
|
|
// but count the wadusage for automatic precache
|
|
|
|
//
|
2023-04-11 01:29:17 +02:00
|
|
|
// FIXME: for ENGINE_IMPROVED_LINETRACE we need to load textures on server too
|
|
|
|
// but there is no facility for this yet
|
2023-10-28 15:06:00 +02:00
|
|
|
texture = mod->textures[textureIndex];
|
2023-04-07 15:34:41 +02:00
|
|
|
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
|
2024-06-11 05:16:21 +02:00
|
|
|
usesCustomPalette = Mod_CalcMipTexUsesCustomPalette( mod, bmod, textureIndex );
|
|
|
|
|
|
|
|
// check for multi-layered sky texture (quake1 specific)
|
|
|
|
if( bmod->isworld && Q_strncmp( mipTex->name, "sky", 3 ) == 0 && ( mipTex->width / mipTex->height ) == 2 )
|
|
|
|
{
|
|
|
|
Mod_InitSkyClouds( mod, mipTex, texture, usesCustomPalette ); // load quake sky
|
|
|
|
return;
|
|
|
|
}
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
if( FBitSet( host.features, ENGINE_IMPROVED_LINETRACE ) && mipTex->name[0] == '{' )
|
|
|
|
SetBits( txFlags, TF_KEEP_SOURCE ); // Paranoia2 texture alpha-tracing
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-30 04:27:35 +01:00
|
|
|
// check if this is water to keep the source texture and expand it to RGBA (so ripple effect works)
|
|
|
|
if( Mod_LooksLikeWaterTexture( mipTex->name ))
|
|
|
|
SetBits( txFlags, TF_KEEP_SOURCE | TF_EXPAND_SOURCE );
|
|
|
|
|
2024-06-11 05:16:21 +02:00
|
|
|
// Texture loading order:
|
|
|
|
// 1. HQ from disk
|
|
|
|
// 2. From WAD
|
|
|
|
// 3. Internal from map
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-06-11 05:16:21 +02:00
|
|
|
texture->gl_texturenum = 0;
|
|
|
|
Q_strncpy( safemtname, mipTex->name, sizeof( safemtname ));
|
|
|
|
if( safemtname[0] == '*' )
|
|
|
|
safemtname[0] = '!'; // replace unexpected symbol
|
|
|
|
|
|
|
|
if( Mod_AllowMaterials( ))
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-06-11 05:16:21 +02:00
|
|
|
#if !XASH_DEDICATED
|
|
|
|
if( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, "" ))
|
|
|
|
{
|
|
|
|
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, txFlags );
|
|
|
|
load_external = texture->gl_texturenum != 0;
|
|
|
|
Mod_TextureReplacementReport( mod->name, safemtname, "", texture->gl_texturenum, texpath );
|
|
|
|
}
|
|
|
|
#endif // !XASH_DEDICATED
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Try WAD texture (force while r_wadtextures is 1)
|
2024-09-30 17:42:02 +02:00
|
|
|
if( !texture->gl_texturenum && (( r_wadtextures.value && world.wadlist.count > 0 ) || mipTex->offsets[0] <= 0 ))
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2024-11-23 13:02:49 +01:00
|
|
|
rgbdata_t *pic = NULL;
|
|
|
|
int wadIndex = Mod_LoadTextureFromWadList( &world.wadlist, mipTex->name, Host_IsDedicated() ? NULL : &pic, texpath, sizeof( texpath ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( wadIndex >= 0 )
|
|
|
|
{
|
2024-05-30 05:18:10 +02:00
|
|
|
#if !XASH_DEDICATED
|
2024-11-23 13:02:49 +01:00
|
|
|
if( !Host_IsDedicated( ) && pic != NULL )
|
|
|
|
{
|
|
|
|
texture->gl_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texpath, pic, txFlags, false );
|
|
|
|
FS_FreeImage( pic );
|
|
|
|
}
|
2024-05-30 05:18:10 +02:00
|
|
|
#endif // !XASH_DEDICATED
|
2024-09-30 17:42:02 +02:00
|
|
|
world.wadlist.wadusage[wadIndex]++;
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-05-30 05:18:10 +02:00
|
|
|
#if !XASH_DEDICATED
|
|
|
|
if( Host_IsDedicated( ))
|
|
|
|
return;
|
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// WAD failed, so use internal texture (if present)
|
|
|
|
if( mipTex->offsets[0] > 0 && texture->gl_texturenum == 0 )
|
|
|
|
{
|
|
|
|
char texName[64];
|
|
|
|
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
|
2019-05-19 14:01:23 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
Q_snprintf( texName, sizeof( texName ), "#%s:%s.mip", loadstat.name, mipTex->name );
|
|
|
|
texture->gl_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, txFlags );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// If texture is completely missed:
|
|
|
|
if( texture->gl_texturenum == 0 )
|
|
|
|
{
|
|
|
|
Con_DPrintf( S_ERROR "Unable to find %s.mip\n", mipTex->name );
|
|
|
|
texture->gl_texturenum = R_GetBuiltinTexture( REF_DEFAULT_TEXTURE );
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Check for luma texture
|
2024-06-11 05:16:21 +02:00
|
|
|
texture->fb_texturenum = 0;
|
|
|
|
if( load_external ) // external textures will not have TF_HAS_LUMA flag because it set only from WAD images loader
|
|
|
|
{
|
|
|
|
if( Mod_SearchForTextureReplacement( texpath, sizeof( texpath ), mod->name, safemtname, "_luma" ))
|
|
|
|
{
|
|
|
|
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texpath, NULL, 0, TF_MAKELUMA );
|
|
|
|
Mod_TextureReplacementReport( mod->name, safemtname, "_luma", texture->fb_texturenum, texpath );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( FBitSet( REF_GET_PARM( PARM_TEX_FLAGS, texture->gl_texturenum ), TF_HAS_LUMA ) && !texture->fb_texturenum )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
|
|
|
char texName[64];
|
2024-06-11 05:16:21 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
Q_snprintf( texName, sizeof( texName ), "#%s:%s_luma.mip", loadstat.name, mipTex->name );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( mipTex->offsets[0] > 0 )
|
|
|
|
{
|
|
|
|
const size_t size = Mod_CalculateMipTexSize( mipTex, usesCustomPalette );
|
2023-04-11 01:29:17 +02:00
|
|
|
texture->fb_texturenum = ref.dllFuncs.GL_LoadTexture( texName, (byte *)mipTex, size, TF_MAKELUMA );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2023-04-07 15:34:41 +02:00
|
|
|
else
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
int wadIndex;
|
2024-11-23 13:02:49 +01:00
|
|
|
rgbdata_t *pic = NULL;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
// NOTE: We can't load the _luma texture from the WAD as normal because it
|
|
|
|
// doesn't exist there. The original texture is already loaded, but cannot be modified.
|
|
|
|
// Instead, load the original texture again and convert it to luma.
|
2024-11-23 13:02:49 +01:00
|
|
|
wadIndex = Mod_LoadTextureFromWadList( &world.wadlist, texture->name, &pic, NULL, 0 );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-23 13:02:49 +01:00
|
|
|
if( wadIndex >= 0 && pic != NULL )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2024-11-23 13:02:49 +01:00
|
|
|
// OK, loading it from wad or hi-res(??) version
|
|
|
|
texture->fb_texturenum = ref.dllFuncs.GL_LoadTextureFromBuffer( texName, pic, TF_MAKELUMA, false );
|
|
|
|
FS_FreeImage( pic );
|
2024-09-30 17:42:02 +02:00
|
|
|
world.wadlist.wadusage[wadIndex]++;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
#endif // !XASH_DEDICATED
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadTexture( model_t *mod, dbspmodel_t *bmod, int textureIndex )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *texture;
|
|
|
|
mip_t *mipTex;
|
2023-03-13 04:25:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( textureIndex < 0 || textureIndex >= mod->numtextures )
|
2023-04-07 15:34:41 +02:00
|
|
|
return;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
mipTex = Mod_GetMipTexForTexture( bmod, textureIndex );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( !mipTex )
|
|
|
|
{
|
|
|
|
// No data for this texture.
|
|
|
|
// Create default texture (some mods require this).
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_CreateDefaultTexture( mod, &mod->textures[textureIndex] );
|
2023-04-07 15:34:41 +02:00
|
|
|
return;
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( mipTex->name[0] == '\0' )
|
2023-04-11 01:29:17 +02:00
|
|
|
Q_snprintf( mipTex->name, sizeof( mipTex->name ), "miptex_%i", textureIndex );
|
2019-03-22 16:41:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
texture = (texture_t *)Mem_Calloc( mod->mempool, sizeof( *texture ));
|
|
|
|
mod->textures[textureIndex] = texture;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Ensure texture name is lowercase.
|
2023-04-15 01:56:14 +02:00
|
|
|
Q_strnlwr( mipTex->name, texture->name, sizeof( texture->name ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
texture->width = mipTex->width;
|
|
|
|
texture->height = mipTex->height;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_LoadTextureData( mod, bmod, textureIndex );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
2019-03-22 16:41:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadAllTextures( model_t *mod, dbspmodel_t *bmod )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
int i;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
for( i = 0; i < mod->numtextures; i++ )
|
|
|
|
Mod_LoadTexture( mod, bmod, i );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
2019-03-22 16:41:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_SequenceAnimatedTexture( model_t *mod, int baseTextureIndex )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *anims[10];
|
|
|
|
texture_t *altanims[10];
|
|
|
|
texture_t *baseTexture;
|
2023-04-07 15:34:41 +02:00
|
|
|
int max = 0;
|
|
|
|
int altmax = 0;
|
2023-04-11 01:29:17 +02:00
|
|
|
int candidateIndex;
|
2023-03-13 04:25:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( baseTextureIndex < 0 || baseTextureIndex >= mod->numtextures )
|
2023-04-07 15:34:41 +02:00
|
|
|
return;
|
2019-03-22 16:41:57 +01:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
baseTexture = mod->textures[baseTextureIndex];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !Mod_NameImpliesTextureIsAnimated( baseTexture ))
|
2023-04-07 15:34:41 +02:00
|
|
|
return;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
// Already sequenced
|
2023-04-07 15:34:41 +02:00
|
|
|
if( baseTexture->anim_next )
|
|
|
|
return;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// find the number of frames in the animation
|
2023-04-11 01:29:17 +02:00
|
|
|
memset( anims, 0, sizeof( anims ));
|
|
|
|
memset( altanims, 0, sizeof( altanims ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( baseTexture->name[1] >= '0' && baseTexture->name[1] <= '9' )
|
|
|
|
{
|
|
|
|
// This texture is a standard animation frame.
|
|
|
|
int frameIndex = (int)baseTexture->name[1] - (int)'0';
|
|
|
|
|
|
|
|
anims[frameIndex] = baseTexture;
|
|
|
|
max = frameIndex + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This texture is an alternate animation frame.
|
|
|
|
int frameIndex = (int)baseTexture->name[1] - (int)'a';
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
altanims[frameIndex] = baseTexture;
|
|
|
|
altmax = frameIndex + 1;
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Now search the rest of the textures to find all other frames.
|
2023-10-28 15:06:00 +02:00
|
|
|
for( candidateIndex = baseTextureIndex + 1; candidateIndex < mod->numtextures; candidateIndex++ )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
texture_t *altTexture = mod->textures[candidateIndex];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-11 01:29:17 +02:00
|
|
|
if( !Mod_NameImpliesTextureIsAnimated( altTexture ))
|
2023-04-07 15:34:41 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
// This texture is animated, but is it part of the same group as
|
|
|
|
// the original texture we encountered? Check that the rest of
|
|
|
|
// the name matches the original (both will be valid for at least
|
|
|
|
// string index 2).
|
2023-04-11 01:29:17 +02:00
|
|
|
if( Q_strcmp( altTexture->name + 2, baseTexture->name + 2 ) != 0 )
|
2023-04-07 15:34:41 +02:00
|
|
|
continue;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( altTexture->name[1] >= '0' && altTexture->name[1] <= '9' )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-04-07 15:34:41 +02:00
|
|
|
// This texture is a standard frame.
|
|
|
|
int frameIndex = (int)altTexture->name[1] - (int)'0';
|
|
|
|
anims[frameIndex] = altTexture;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( frameIndex >= max )
|
|
|
|
max = frameIndex + 1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// This texture is an alternate frame.
|
|
|
|
int frameIndex = (int)altTexture->name[1] - (int)'a';
|
|
|
|
altanims[frameIndex] = altTexture;
|
|
|
|
|
|
|
|
if( frameIndex >= altmax )
|
|
|
|
altmax = frameIndex + 1;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
// Link all standard animated frames together.
|
2023-04-11 01:29:17 +02:00
|
|
|
for( candidateIndex = 0; candidateIndex < max; candidateIndex++ )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *tex = anims[candidateIndex];
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
if( !tex )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: missing frame %i of animated texture \"%s\"\n",
|
|
|
|
__func__,
|
2023-04-07 15:34:41 +02:00
|
|
|
candidateIndex,
|
|
|
|
baseTexture->name );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
baseTexture->anim_total = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
tex->anim_total = max * ANIM_CYCLE;
|
|
|
|
tex->anim_min = candidateIndex * ANIM_CYCLE;
|
2023-04-11 01:29:17 +02:00
|
|
|
tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;
|
|
|
|
tex->anim_next = anims[( candidateIndex + 1 ) % max];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( altmax > 0 )
|
|
|
|
tex->alternate_anims = altanims[0];
|
|
|
|
}
|
|
|
|
|
|
|
|
// Link all alternate animated frames together.
|
2023-04-11 01:29:17 +02:00
|
|
|
for( candidateIndex = 0; candidateIndex < altmax; candidateIndex++ )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
texture_t *tex = altanims[candidateIndex];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
if( !tex )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: missing alternate frame %i of animated texture \"%s\"\n",
|
|
|
|
__func__,
|
2023-04-07 15:34:41 +02:00
|
|
|
candidateIndex,
|
|
|
|
baseTexture->name );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
baseTexture->anim_total = 0;
|
|
|
|
break;
|
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-04-07 15:34:41 +02:00
|
|
|
tex->anim_total = altmax * ANIM_CYCLE;
|
|
|
|
tex->anim_min = candidateIndex * ANIM_CYCLE;
|
2023-04-11 01:29:17 +02:00
|
|
|
tex->anim_max = ( candidateIndex + 1 ) * ANIM_CYCLE;
|
|
|
|
tex->anim_next = altanims[( candidateIndex + 1 ) % altmax];
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
if( max > 0 )
|
|
|
|
tex->alternate_anims = anims[0];
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_SequenceAllAnimatedTextures( model_t *mod )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
int i;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
for( i = 0; i < mod->numtextures; i++ )
|
|
|
|
Mod_SequenceAnimatedTexture( mod, i );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadTextures
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadTextures( model_t *mod, dbspmodel_t *bmod )
|
2023-04-07 15:34:41 +02:00
|
|
|
{
|
2023-04-11 01:29:17 +02:00
|
|
|
dmiptexlump_t *lump;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
|
|
|
#if !XASH_DEDICATED
|
|
|
|
// release old sky layers first
|
|
|
|
if( !Host_IsDedicated() && bmod->isworld )
|
|
|
|
{
|
2024-06-10 21:00:44 +02:00
|
|
|
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( "alpha_sky" ));
|
|
|
|
ref.dllFuncs.GL_FreeTexture( R_GetBuiltinTexture( "solid_sky" ));
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
#endif
|
|
|
|
|
|
|
|
lump = bmod->textures;
|
|
|
|
|
|
|
|
if( bmod->texdatasize < 1 || !lump || lump->nummiptex < 1 )
|
|
|
|
{
|
|
|
|
// no textures
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->textures = NULL;
|
2023-04-07 15:34:41 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->textures = (texture_t **)Mem_Calloc( mod->mempool, lump->nummiptex * sizeof( texture_t * ));
|
|
|
|
mod->numtextures = lump->nummiptex;
|
2023-04-07 15:34:41 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_LoadAllTextures( mod, bmod );
|
|
|
|
Mod_SequenceAllAnimatedTextures( mod );
|
2023-04-07 15:34:41 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadTexInfo
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadTexInfo( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
mfaceinfo_t *fout, *faceinfo;
|
2019-05-02 18:12:23 +02:00
|
|
|
int i, j, k, miptex;
|
2018-04-13 18:23:45 +02:00
|
|
|
dfaceinfo_t *fin;
|
|
|
|
mtexinfo_t *out;
|
|
|
|
dtexinfo_t *in;
|
|
|
|
|
|
|
|
// trying to load faceinfo
|
2023-10-28 15:06:00 +02:00
|
|
|
faceinfo = fout = Mem_Calloc( mod->mempool, bmod->numfaceinfo * sizeof( *fout ));
|
2018-04-13 18:23:45 +02:00
|
|
|
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;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->texinfo = out = Mem_Calloc( mod->mempool, bmod->numtexinfo * sizeof( *out ));
|
|
|
|
mod->numtexinfo = bmod->numtexinfo;
|
2018-04-13 18:23:45 +02:00
|
|
|
in = bmod->texinfo;
|
|
|
|
|
|
|
|
for( i = 0; i < bmod->numtexinfo; i++, in++, out++ )
|
|
|
|
{
|
2019-05-02 18:12:23 +02:00
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
for( k = 0; k < 4; k++ )
|
|
|
|
out->vecs[j][k] = in->vecs[j][k];
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
miptex = in->miptex;
|
2024-11-17 11:04:20 +01:00
|
|
|
if( miptex < 0 || miptex >= mod->numtextures )
|
2018-10-04 08:08:48 +02:00
|
|
|
miptex = 0; // this is possible?
|
2023-10-28 15:06:00 +02:00
|
|
|
out->texture = mod->textures[miptex];
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadSurfaces( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
int test_lightsize = -1;
|
|
|
|
int next_lightofs = -1;
|
|
|
|
int prev_lightofs = -1;
|
|
|
|
int i, j, lightofs;
|
|
|
|
mextrasurf_t *info;
|
|
|
|
msurface_t *out;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->surfaces = out = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( msurface_t ));
|
|
|
|
info = Mem_Calloc( mod->mempool, bmod->numsurfaces * sizeof( mextrasurf_t ));
|
|
|
|
mod->numsurfaces = bmod->numsurfaces;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if(( in->firstedge + in->numedges ) > mod->numsurfedges )
|
2018-10-04 08:08:48 +02:00
|
|
|
continue; // corrupted level?
|
2018-04-13 18:23:45 +02:00
|
|
|
out->firstedge = in->firstedge;
|
|
|
|
out->numedges = in->numedges;
|
|
|
|
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
|
2023-10-28 15:06:00 +02:00
|
|
|
out->plane = mod->planes + in->planenum;
|
|
|
|
out->texinfo = mod->texinfo + in->texinfo;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
for( j = 0; j < MAXLIGHTMAPS; j++ )
|
|
|
|
out->styles[j] = in->styles[j];
|
|
|
|
lightofs = in->lightofs;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
dface_t *in = &bmod->surfaces[i];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if(( in->firstedge + in->numedges ) > mod->numsurfedges )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-04-09 22:56:28 +02:00
|
|
|
Con_Reportf( S_ERROR "bad surface %i from %zu\n", i, bmod->numsurfaces );
|
2018-04-13 18:23:45 +02:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
out->firstedge = in->firstedge;
|
|
|
|
out->numedges = in->numedges;
|
|
|
|
if( in->side ) SetBits( out->flags, SURF_PLANEBACK );
|
2023-10-28 15:06:00 +02:00
|
|
|
out->plane = mod->planes + in->planenum;
|
|
|
|
out->texinfo = mod->texinfo + in->texinfo;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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 ))
|
2018-10-04 08:08:48 +02:00
|
|
|
SetBits( out->flags, SURF_DRAWSKY );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-30 04:27:35 +01:00
|
|
|
if( Mod_LooksLikeWaterTexture( tex->name ))
|
2018-10-04 08:08:48 +02:00
|
|
|
SetBits( out->flags, SURF_DRAWTURB );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( !Q_strncmp( tex->name, "scroll", 6 ))
|
|
|
|
SetBits( out->flags, SURF_CONVEYOR );
|
|
|
|
|
2018-12-29 23:43:07 +01:00
|
|
|
if( FBitSet( out->texinfo->flags, TEX_SCROLL ))
|
|
|
|
SetBits( out->flags, SURF_CONVEYOR );
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
// 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 );
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_CalcSurfaceBounds( mod, out );
|
|
|
|
Mod_CalcSurfaceExtents( mod, out );
|
|
|
|
Mod_CreateFaceBevels( mod, out );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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;
|
|
|
|
}
|
|
|
|
|
2019-11-24 01:52:08 +01:00
|
|
|
#if !XASH_DEDICATED // TODO: Do we need subdivide on server?
|
2019-03-22 16:41:57 +01:00
|
|
|
if( FBitSet( out->flags, SURF_DRAWTURB ) && !Host_IsDedicated() )
|
2023-10-28 15:06:00 +02:00
|
|
|
ref.dllFuncs.GL_SubdivideSurface( mod, out ); // cut up polygon for warps
|
2018-04-18 17:32:30 +02:00
|
|
|
#endif
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// 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;
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_Reportf( "lighting: %s\n", (bmod->lightmap_samples == 1) ? "monochrome" : "colored" );
|
2018-04-13 18:23:45 +02:00
|
|
|
bmod->lightmap_samples = Q_max( bmod->lightmap_samples, 1 ); // avoid division by zero
|
|
|
|
}
|
2018-10-04 08:08:48 +02:00
|
|
|
else Con_DPrintf( S_WARN "lighting invalid samplecount: %g, defaulting to %i\n", samples, bmod->lightmap_samples );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadNodes
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadNodes( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
mnode_t *out;
|
|
|
|
int i, j, p;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->nodes = out = (mnode_t *)Mem_Calloc( mod->mempool, bmod->numnodes * sizeof( *out ));
|
|
|
|
mod->numnodes = bmod->numnodes;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
for( i = 0; i < mod->numnodes; i++, out++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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;
|
2023-10-28 15:06:00 +02:00
|
|
|
out->plane = mod->planes + p;
|
2018-04-13 18:23:45 +02:00
|
|
|
out->firstsurface = in->firstface;
|
|
|
|
out->numsurfaces = in->numfaces;
|
|
|
|
|
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
{
|
|
|
|
p = in->children[j];
|
2023-10-28 15:06:00 +02:00
|
|
|
if( p >= 0 ) out->children[j] = mod->nodes + p;
|
|
|
|
else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
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;
|
2023-10-28 15:06:00 +02:00
|
|
|
out->plane = mod->planes + p;
|
2018-04-13 18:23:45 +02:00
|
|
|
out->firstsurface = in->firstface;
|
|
|
|
out->numsurfaces = in->numfaces;
|
|
|
|
|
|
|
|
for( j = 0; j < 2; j++ )
|
|
|
|
{
|
|
|
|
p = in->children[j];
|
2023-10-28 15:06:00 +02:00
|
|
|
if( p >= 0 ) out->children[j] = mod->nodes + p;
|
|
|
|
else out->children[j] = (mnode_t *)(mod->leafs + ( -1 - p ));
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// sets nodes and leafs
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_SetParent( mod->nodes, NULL );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadLeafs
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadLeafs( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
mleaf_t *out;
|
|
|
|
int i, j, p;
|
2018-10-04 08:08:48 +02:00
|
|
|
int visclusters = 0;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->leafs = out = (mleaf_t *)Mem_Calloc( mod->mempool, bmod->numleafs * sizeof( *out ));
|
|
|
|
mod->numleafs = bmod->numleafs;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( bmod->isworld )
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
visclusters = mod->submodels[0].visleafs;
|
2018-10-04 08:08:48 +02:00
|
|
|
world.visbytes = (visclusters + 7) >> 3;
|
|
|
|
world.fatbytes = (visclusters + 31) >> 3;
|
2019-02-27 00:23:03 +01:00
|
|
|
refState.visbytes = world.visbytes;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
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];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
out->firstmarksurface = mod->marksurfaces + in->firstmarksurface;
|
2018-04-13 18:23:45 +02:00
|
|
|
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];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
out->firstmarksurface = mod->marksurfaces + in->firstmarksurface;
|
2018-04-13 18:23:45 +02:00
|
|
|
out->nummarksurfaces = in->nummarksurfaces;
|
|
|
|
}
|
|
|
|
|
|
|
|
if( bmod->isworld )
|
|
|
|
{
|
|
|
|
out->cluster = ( i - 1 ); // solid leaf 0 has no visdata
|
|
|
|
|
2018-10-04 08:08:48 +02:00
|
|
|
if( out->cluster >= visclusters )
|
2018-04-13 18:23:45 +02:00
|
|
|
out->cluster = -1;
|
|
|
|
|
|
|
|
// ignore visofs errors on leaf 0 (solid)
|
2023-10-28 15:06:00 +02:00
|
|
|
if( p >= 0 && out->cluster >= 0 && mod->visdata )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if( p < bmod->visdatasize )
|
2023-10-28 15:06:00 +02:00
|
|
|
out->compressed_vis = mod->visdata + p;
|
2018-10-04 08:08:48 +02:00
|
|
|
else Con_Reportf( S_WARN "Mod_LoadLeafs: invalid visofs for leaf #%i\n", i );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2019-07-19 19:23:08 +02:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
else out->cluster = -1; // no visclusters on bmodels
|
|
|
|
|
|
|
|
if( p == -1 ) out->compressed_vis = NULL;
|
2023-10-28 15:06:00 +02:00
|
|
|
else out->compressed_vis = mod->visdata + p;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// 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 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
if( bmod->isworld && mod->leafs[0].contents != CONTENTS_SOLID )
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: Map %s has leaf 0 is not CONTENTS_SOLID\n", __func__, mod->name );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// do some final things for world
|
2023-10-28 15:06:00 +02:00
|
|
|
if( bmod->isworld && Mod_CheckWaterAlphaSupport( mod, bmod ))
|
2018-04-13 18:23:45 +02:00
|
|
|
SetBits( world.flags, FWORLD_WATERALPHA );
|
|
|
|
}
|
|
|
|
|
2024-05-29 04:16:02 +02:00
|
|
|
/*
|
|
|
|
===========
|
|
|
|
Mod_CalcPHS
|
|
|
|
|
|
|
|
To be called while loading world for multiplayer game server
|
|
|
|
===========
|
|
|
|
*/
|
|
|
|
static void Mod_CalcPHS( model_t *mod )
|
|
|
|
{
|
|
|
|
const qboolean vis_stats = host_developer.value >= DEV_EXTENDED;
|
|
|
|
const size_t rowbytes = ALIGN( world.visbytes, 4 ); // force align rows by 32-bit boundary
|
|
|
|
const size_t count = mod->numleafs + 1; // same as mod->submodels[0].visleafs + 1
|
|
|
|
double t1;
|
|
|
|
double t2;
|
|
|
|
size_t total_compressed_size = 0;
|
|
|
|
size_t hcount = 0;
|
|
|
|
size_t vcount = 0;
|
2024-06-02 09:34:10 +02:00
|
|
|
int i;
|
2024-05-29 04:16:02 +02:00
|
|
|
byte *uncompressed_pvs;
|
|
|
|
byte *uncompressed_phs;
|
|
|
|
|
|
|
|
if( !mod->visdata )
|
|
|
|
return;
|
|
|
|
|
|
|
|
#if defined( HAVE_OPENMP )
|
|
|
|
Con_Reportf( "Building PHS in %d threads...\n", omp_get_max_threads( ));
|
|
|
|
#else
|
|
|
|
Con_Reportf( "Building PHS...\n" );
|
|
|
|
#endif
|
|
|
|
|
|
|
|
uncompressed_pvs = Mem_Calloc( mod->mempool, rowbytes * count * 2 );
|
|
|
|
uncompressed_phs = &uncompressed_pvs[rowbytes * count];
|
|
|
|
|
|
|
|
world.phsofs = Mem_Calloc( mod->mempool, sizeof( size_t ) * count );
|
|
|
|
world.compressed_phs = NULL;
|
|
|
|
|
|
|
|
t1 = Platform_DoubleTime();
|
|
|
|
|
|
|
|
#pragma omp parallel
|
|
|
|
{
|
|
|
|
// uncompress pvs first
|
|
|
|
#pragma omp for schedule( static, 256 ) // there might be thousands of leafs, split by 256
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
Mod_DecompressPVSTo( &uncompressed_pvs[rowbytes * i], mod->leafs[i].compressed_vis, world.visbytes );
|
|
|
|
|
|
|
|
// now create phs
|
|
|
|
#pragma omp for schedule( static, 256 ) reduction( + : vcount, hcount )
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
{
|
|
|
|
const byte *scan = &uncompressed_pvs[rowbytes * i];
|
|
|
|
byte *dst = &uncompressed_phs[rowbytes * i]; // rowbytes, not rowwords!
|
|
|
|
size_t j;
|
|
|
|
|
|
|
|
memcpy( dst, scan, rowbytes );
|
|
|
|
|
|
|
|
for( j = 0; j < rowbytes; j++ )
|
|
|
|
{
|
|
|
|
size_t k;
|
|
|
|
uint bitbyte = scan[j];
|
|
|
|
|
|
|
|
if( bitbyte == 0 )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
for( k = 0; k < 8; k++ )
|
|
|
|
{
|
|
|
|
size_t index;
|
|
|
|
|
|
|
|
if( !FBitSet( bitbyte, BIT( k )))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// OR this pvs row into the phs
|
|
|
|
// +1 because pvs is 1 based
|
|
|
|
index = (( j * 8 ) + k + 1 );
|
|
|
|
if( index >= count )
|
|
|
|
continue;
|
|
|
|
|
|
|
|
Q_memor( dst, &uncompressed_pvs[rowbytes * index], rowbytes );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if( vis_stats && i != 0 )
|
|
|
|
{
|
|
|
|
size_t j;
|
|
|
|
|
|
|
|
for( j = 0; j < count; j++ )
|
|
|
|
{
|
|
|
|
if( CHECKVISBIT( scan, j ))
|
|
|
|
vcount++;
|
|
|
|
|
|
|
|
if( CHECKVISBIT( dst, j ))
|
|
|
|
hcount++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// since I can't predict at which spot compressed array
|
|
|
|
// should be put, this loop is single threaded
|
|
|
|
for( i = 0; i < count; i++ )
|
|
|
|
{
|
|
|
|
const byte *src = &uncompressed_phs[rowbytes * i];
|
|
|
|
byte temp_compressed_row[(MAX_MAP_LEAFS+1)/4]; // compression for this row might be ineffective
|
|
|
|
size_t compressed_size;
|
|
|
|
|
|
|
|
compressed_size = Mod_CompressPVS( temp_compressed_row, src, rowbytes );
|
|
|
|
|
|
|
|
world.compressed_phs = Mem_Realloc( mod->mempool, world.compressed_phs, total_compressed_size + compressed_size );
|
|
|
|
memcpy( &world.compressed_phs[total_compressed_size], temp_compressed_row, compressed_size );
|
|
|
|
world.phsofs[i] = total_compressed_size;
|
|
|
|
|
|
|
|
total_compressed_size += compressed_size;
|
|
|
|
}
|
|
|
|
|
|
|
|
t2 = Platform_DoubleTime();
|
|
|
|
|
|
|
|
if( vis_stats )
|
2024-06-22 06:31:37 +02:00
|
|
|
Con_Reportf( "Average leaves visible / audible / total: %zu / %zu / %zu\n", vcount / count, hcount / count, count );
|
2024-05-29 04:16:02 +02:00
|
|
|
Con_Reportf( "Uncompressed PHS size: %s\n", Q_memprint( rowbytes * count ));
|
|
|
|
Con_Reportf( "Compressed PHS size: %s\n", Q_memprint( total_compressed_size + sizeof( *world.phsofs ) * count ));
|
|
|
|
Con_Reportf( "PHS building time: %.2f ms\n", ( t2 - t1 ) * 1000.0f );
|
|
|
|
|
|
|
|
// TODO: rewrite this into a unit test
|
|
|
|
// NOTE: how to get GoldSrc fat PHS and PVS data
|
|
|
|
// start a multiplayer server with some op4_bootcamp (for example)
|
|
|
|
// attach to process with GDB:
|
|
|
|
// (gdb) p gPAS[0]
|
|
|
|
// $0 = (byte *) ...
|
|
|
|
// (gdb) p gPAS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]
|
|
|
|
// $1 = (byte *) ...
|
|
|
|
// (gdb) dump binary memory op4_bootcamp_gs.phs $0 $1
|
|
|
|
// (gdb) p gPVS[0]
|
|
|
|
// $2 = (byte *) ...
|
|
|
|
// (gdb) p gPVS[gPVSRowBytes * (cl.worldmodel->numleafs + 1)]
|
|
|
|
// $3 = (byte *) ...
|
|
|
|
// (gdb) dump binary memory op4_bootcamp_gs.pvs $0 $1
|
|
|
|
//
|
|
|
|
// NOTE: as of writing, uncompressed PVS and PHS data do match! hooray!
|
|
|
|
//
|
|
|
|
// FS_WriteFile( "op4_bootcamp.pvs", uncompressed_pvs, rowbytes * count );
|
|
|
|
// FS_WriteFile( "op4_bootcamp.phs", uncompressed_phs, rowbytes * count );
|
|
|
|
|
|
|
|
// release uncompressed data
|
|
|
|
Mem_Free( uncompressed_pvs );
|
|
|
|
|
|
|
|
// TODO: cache the PHS somewhere, it might take a long time on giant maps
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadClipnodes
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadClipnodes( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
dclipnode32_t *out;
|
|
|
|
int i;
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
bmod->clipnodes_out = out = (dclipnode32_t *)Mem_Malloc( mod->mempool, bmod->numclipnodes * sizeof( *out ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-01-03 05:50:50 +01:00
|
|
|
if(( bmod->version == QBSP2_VERSION ) || ( bmod->version == HLBSP_VERSION && bmod->isbsp30ext && bmod->numclipnodes >= MAX_MAP_CLIPNODES_HLBSP ))
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
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;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
// FIXME: fill mod->clipnodes?
|
|
|
|
mod->numclipnodes = bmod->numclipnodes;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadVisibility
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadVisibility( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->visdata = Mem_Malloc( mod->mempool, bmod->visdatasize );
|
|
|
|
memcpy( mod->visdata, bmod->visdata, bmod->visdatasize );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadLightVecs
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadLightVecs( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if( bmod->deluxdatasize != bmod->lightdatasize )
|
|
|
|
{
|
|
|
|
if( bmod->deluxdatasize > 0 )
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: has mismatched size (%zu should be %zu)\n", __func__, bmod->deluxdatasize, bmod->lightdatasize );
|
2024-11-17 02:35:20 +01:00
|
|
|
else
|
|
|
|
Mod_LoadLitfile( mod, "dlit", bmod->lightdatasize, &bmod->deluxedata_out, &bmod->deluxdatasize ); // old method
|
2018-04-13 18:23:45 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
bmod->deluxedata_out = Mem_Malloc( mod->mempool, bmod->deluxdatasize );
|
2018-04-13 18:23:45 +02:00
|
|
|
memcpy( bmod->deluxedata_out, bmod->deluxdata, bmod->deluxdatasize );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadShadowmap
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadShadowmap( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
if( bmod->shadowdatasize != ( bmod->lightdatasize / 3 ))
|
|
|
|
{
|
|
|
|
if( bmod->shadowdatasize > 0 )
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( S_ERROR "%s: has mismatched size (%zu should be %zu)\n", __func__, bmod->shadowdatasize, bmod->lightdatasize / 3 );
|
2018-04-13 18:23:45 +02:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
bmod->shadowdata_out = Mem_Malloc( mod->mempool, bmod->shadowdatasize );
|
2018-04-13 18:23:45 +02:00
|
|
|
memcpy( bmod->shadowdata_out, bmod->shadowdata, bmod->shadowdatasize );
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadLighting
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static void Mod_LoadLighting( model_t *mod, dbspmodel_t *bmod )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
int i;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( !bmod->lightdatasize )
|
|
|
|
return;
|
|
|
|
|
|
|
|
switch( bmod->lightmap_samples )
|
|
|
|
{
|
|
|
|
case 1:
|
2024-11-17 02:35:20 +01:00
|
|
|
if( !Mod_LoadLitfile( mod, "lit", bmod->lightdatasize * 3, &mod->lightdata, &bmod->lightdatasize ))
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
mod->lightdata = (color24 *)Mem_Malloc( mod->mempool, bmod->lightdatasize * sizeof( color24 ));
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// expand the white lighting data
|
2024-11-17 02:35:20 +01:00
|
|
|
for( i = 0; i < bmod->lightdatasize; i++ )
|
|
|
|
mod->lightdata[i].r = mod->lightdata[i].g = mod->lightdata[i].b = bmod->lightdata[i];
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
2024-11-17 02:35:20 +01:00
|
|
|
else SetBits( mod->flags, MODEL_COLORED_LIGHTING );
|
2018-04-13 18:23:45 +02:00
|
|
|
break;
|
|
|
|
case 3: // load colored lighting
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->lightdata = Mem_Malloc( mod->mempool, bmod->lightdatasize );
|
|
|
|
memcpy( mod->lightdata, bmod->lightdata, bmod->lightdatasize );
|
|
|
|
SetBits( mod->flags, MODEL_COLORED_LIGHTING );
|
2018-04-13 18:23:45 +02:00
|
|
|
break;
|
|
|
|
default:
|
2024-06-19 05:46:08 +02:00
|
|
|
Host_Error( "%s: bad lightmap sample count %i\n", __func__, bmod->lightmap_samples );
|
2018-04-13 18:23:45 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// not supposed to be load ?
|
|
|
|
if( FBitSet( host.features, ENGINE_LOAD_DELUXEDATA ))
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_LoadLightVecs( mod, bmod );
|
|
|
|
Mod_LoadShadowmap( mod, bmod );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( bmod->isworld && bmod->deluxdatasize )
|
|
|
|
SetBits( world.flags, FWORLD_HAS_DELUXEMAP );
|
|
|
|
}
|
|
|
|
|
|
|
|
// setup lightdata pointers
|
2024-11-17 02:35:20 +01:00
|
|
|
if( !mod->lightdata )
|
|
|
|
return;
|
|
|
|
|
|
|
|
for( i = 0; i < mod->numsurfaces; i++ )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
int lightofs;
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
if( bmod->version == QBSP2_VERSION )
|
|
|
|
lightofs = bmod->surfaces32[i].lightofs;
|
2024-11-17 02:35:20 +01:00
|
|
|
else
|
|
|
|
lightofs = bmod->surfaces[i].lightofs;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2024-11-17 02:35:20 +01:00
|
|
|
if( lightofs != -1 )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2024-11-17 02:35:20 +01:00
|
|
|
int offset = lightofs / bmod->lightmap_samples;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// NOTE: we divide offset by three because lighting and deluxemap keep their pointers
|
|
|
|
// into three-bytes structs and shadowmap just monochrome
|
2024-11-17 02:35:20 +01:00
|
|
|
mod->surfaces[i].samples = mod->lightdata + offset;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// if deluxemap is present setup it too
|
|
|
|
if( bmod->deluxedata_out )
|
2024-11-17 02:35:20 +01:00
|
|
|
mod->surfaces[i].info->deluxemap = bmod->deluxedata_out + offset;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// will be used by mods
|
|
|
|
if( bmod->shadowdata_out )
|
2024-11-17 02:35:20 +01:00
|
|
|
mod->surfaces[i].info->shadowmap = bmod->shadowdata_out + offset;
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2022-05-05 03:04:24 +02:00
|
|
|
/*
|
|
|
|
=================
|
2023-01-03 05:01:36 +01:00
|
|
|
Mod_LumpLooksLikeEntities
|
2022-05-05 03:04:24 +02:00
|
|
|
|
|
|
|
=================
|
|
|
|
*/
|
2023-01-03 05:01:36 +01:00
|
|
|
static int Mod_LumpLooksLikeEntities( const char *lump, const size_t lumplen )
|
2022-05-05 03:04:24 +02:00
|
|
|
{
|
2023-01-03 05:01:36 +01:00
|
|
|
// look for "classname" string
|
|
|
|
return Q_memmem( lump, lumplen, "\"classname\"", sizeof( "\"classname\"" ) - 1 ) != NULL ? 1 : 0;
|
2022-05-05 03:04:24 +02:00
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadBmodelLumps
|
|
|
|
|
|
|
|
loading and processing bmodel
|
|
|
|
=================
|
|
|
|
*/
|
2023-10-28 15:06:00 +02:00
|
|
|
static qboolean Mod_LoadBmodelLumps( model_t *mod, const byte *mod_base, qboolean isworld )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-01-03 05:50:50 +01:00
|
|
|
const dheader_t *header = (const dheader_t *)mod_base;
|
|
|
|
const dextrahdr_t *extrahdr = (const dextrahdr_t *)(mod_base + sizeof( dheader_t ));
|
2018-04-13 18:23:45 +02:00
|
|
|
dbspmodel_t *bmod = &srcmodel;
|
|
|
|
char wadvalue[2048];
|
2022-12-20 03:39:08 +01:00
|
|
|
size_t len = 0;
|
2023-01-03 05:50:50 +01:00
|
|
|
int i, ret, flags = 0;
|
2024-05-30 04:58:31 +02:00
|
|
|
qboolean wadlist_warn = false;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// always reset the intermediate struct
|
|
|
|
memset( bmod, 0, sizeof( dbspmodel_t ));
|
|
|
|
memset( &loadstat, 0, sizeof( loadstat_t ));
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
Q_strncpy( loadstat.name, mod->name, sizeof( loadstat.name ));
|
2018-04-13 18:23:45 +02:00
|
|
|
wadvalue[0] = '\0';
|
|
|
|
|
|
|
|
#ifndef SUPPORT_BSP2_FORMAT
|
|
|
|
if( header->version == QBSP2_VERSION )
|
|
|
|
{
|
2023-10-28 15:06:00 +02:00
|
|
|
Con_Printf( S_ERROR DEFAULT_BSP_BUILD_ERROR, mod->name );
|
2018-04-13 18:23:45 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
#endif
|
|
|
|
switch( header->version )
|
|
|
|
{
|
|
|
|
case HLBSP_VERSION:
|
2023-01-03 05:50:50 +01:00
|
|
|
if( extrahdr->id == IDEXTRAHEADER )
|
|
|
|
{
|
|
|
|
SetBits( flags, LUMP_BSP30EXT );
|
|
|
|
}
|
2021-11-29 14:28:42 +01:00
|
|
|
// only relevant for half-life maps
|
2023-01-03 05:50:50 +01:00
|
|
|
else if( !Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_ENTITIES].fileofs, header->lumps[LUMP_ENTITIES].filelen ) &&
|
2023-01-03 05:01:36 +01:00
|
|
|
Mod_LumpLooksLikeEntities( mod_base + header->lumps[LUMP_PLANES].fileofs, header->lumps[LUMP_PLANES].filelen ))
|
2021-11-29 14:28:42 +01:00
|
|
|
{
|
|
|
|
// blue-shift swapped lumps
|
|
|
|
srclumps[0].lumpnumber = LUMP_PLANES;
|
|
|
|
srclumps[1].lumpnumber = LUMP_ENTITIES;
|
2023-01-03 05:01:36 +01:00
|
|
|
break;
|
2021-11-29 14:28:42 +01:00
|
|
|
}
|
2023-01-03 05:01:36 +01:00
|
|
|
// intended fallthrough
|
|
|
|
case Q1BSP_VERSION:
|
|
|
|
case QBSP2_VERSION:
|
2021-11-29 14:28:42 +01:00
|
|
|
// everything else
|
|
|
|
srclumps[0].lumpnumber = LUMP_ENTITIES;
|
|
|
|
srclumps[1].lumpnumber = LUMP_PLANES;
|
2023-01-03 05:01:36 +01:00
|
|
|
break;
|
|
|
|
default:
|
2023-10-28 15:06:00 +02:00
|
|
|
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", mod->name, header->version, HLBSP_VERSION );
|
2023-01-03 16:26:18 +01:00
|
|
|
loadstat.numerrors++;
|
|
|
|
return false;
|
2019-12-02 00:14:02 +01:00
|
|
|
}
|
|
|
|
|
2023-01-03 05:01:36 +01:00
|
|
|
bmod->version = header->version; // share up global
|
2023-01-03 05:50:50 +01:00
|
|
|
if( isworld )
|
|
|
|
{
|
|
|
|
world.flags = 0; // clear world settings
|
|
|
|
SetBits( flags, LUMP_SAVESTATS|LUMP_SILENT );
|
|
|
|
}
|
2023-01-03 05:01:36 +01:00
|
|
|
bmod->isworld = isworld;
|
2023-01-03 05:50:50 +01:00
|
|
|
bmod->isbsp30ext = FBitSet( flags, LUMP_BSP30EXT );
|
2023-01-03 05:01:36 +01:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
// loading base lumps
|
|
|
|
for( i = 0; i < ARRAYSIZE( srclumps ); i++ )
|
2023-01-03 05:50:50 +01:00
|
|
|
Mod_LoadLump( mod_base, &srclumps[i], &worldstats[i], flags );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// loading extralumps
|
|
|
|
for( i = 0; i < ARRAYSIZE( extlumps ); i++ )
|
2023-01-03 05:50:50 +01:00
|
|
|
Mod_LoadLump( mod_base, &extlumps[i], &worldstats[ARRAYSIZE( srclumps ) + i], flags );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
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
|
2021-01-03 02:28:45 +01:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
else if( !bmod->isworld && loadstat.numwarnings )
|
|
|
|
Con_DPrintf( "Mod_Load%s: %i warning(s)\n", isworld ? "World" : "Brush", loadstat.numwarnings );
|
|
|
|
|
|
|
|
// load into heap
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_LoadEntities( mod, bmod );
|
|
|
|
Mod_LoadPlanes( mod, bmod );
|
|
|
|
Mod_LoadSubmodels( mod, bmod );
|
|
|
|
Mod_LoadVertexes( mod, bmod );
|
|
|
|
Mod_LoadEdges( mod, bmod );
|
|
|
|
Mod_LoadSurfEdges( mod, bmod );
|
|
|
|
Mod_LoadTextures( mod, bmod );
|
|
|
|
Mod_LoadVisibility( mod, bmod );
|
|
|
|
Mod_LoadTexInfo( mod, bmod );
|
|
|
|
Mod_LoadSurfaces( mod, bmod );
|
|
|
|
Mod_LoadLighting( mod, bmod );
|
|
|
|
Mod_LoadMarkSurfaces( mod, bmod );
|
|
|
|
Mod_LoadLeafs( mod, bmod );
|
|
|
|
Mod_LoadNodes( mod, bmod );
|
|
|
|
Mod_LoadClipnodes( mod, bmod );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// preform some post-initalization
|
2023-10-28 15:06:00 +02:00
|
|
|
Mod_MakeHull0( mod );
|
|
|
|
Mod_SetupSubmodels( mod, bmod );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2018-06-09 00:28:35 +02:00
|
|
|
if( isworld )
|
|
|
|
{
|
2023-12-15 05:37:40 +01:00
|
|
|
world.version = bmod->version;
|
2019-11-24 01:52:08 +01:00
|
|
|
#if !XASH_DEDICATED
|
2019-06-17 06:05:36 +02:00
|
|
|
world.deluxedata = bmod->deluxedata_out; // deluxemap data pointer
|
|
|
|
world.shadowdata = bmod->shadowdata_out; // occlusion data pointer
|
2018-06-14 19:31:46 +02:00
|
|
|
#endif // XASH_DEDICATED
|
2024-05-29 04:16:02 +02:00
|
|
|
|
|
|
|
if( SV_Active() && svs.maxclients > 1 )
|
|
|
|
Mod_CalcPHS( mod );
|
2018-06-09 00:28:35 +02:00
|
|
|
}
|
|
|
|
|
2024-09-30 17:42:02 +02:00
|
|
|
for( i = 0; i < world.wadlist.count; i++ )
|
2018-06-12 11:14:56 +02:00
|
|
|
{
|
2024-09-30 17:42:02 +02:00
|
|
|
if( !world.wadlist.wadusage[i] )
|
2018-06-12 11:14:56 +02:00
|
|
|
continue;
|
2024-05-30 04:58:31 +02:00
|
|
|
|
|
|
|
if( !wadlist_warn )
|
2022-12-20 03:39:08 +01:00
|
|
|
{
|
2024-11-23 12:01:09 +01:00
|
|
|
ret = Q_snprintf( &wadvalue[len], sizeof( wadvalue ), "%s; ", world.wadlist.wadnames[i] );
|
2024-05-30 04:58:31 +02:00
|
|
|
if( ret == -1 )
|
|
|
|
{
|
|
|
|
Con_DPrintf( S_WARN "Too many wad files for output!\n" );
|
|
|
|
wadlist_warn = true;
|
|
|
|
}
|
|
|
|
len += ret;
|
2022-12-20 03:39:08 +01:00
|
|
|
}
|
2018-06-12 11:14:56 +02:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( COM_CheckString( wadvalue ))
|
|
|
|
{
|
2018-06-12 11:14:56 +02:00
|
|
|
wadvalue[Q_strlen( wadvalue ) - 2] = '\0'; // kill the last semicolon
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_Reportf( "Wad files required to run the map: \"%s\"\n", wadvalue );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
2023-01-04 15:55:59 +01:00
|
|
|
static int Mod_LumpLooksLikeEntitiesFile( file_t *f, const dlump_t *l, int flags, const char *msg )
|
2023-01-03 05:01:36 +01:00
|
|
|
{
|
|
|
|
char *buf;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if( FS_Seek( f, l->fileofs, SEEK_SET ) < 0 )
|
|
|
|
{
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
|
|
|
Con_DPrintf( S_ERROR "map ^2%s^7 %s lump past end of file\n", loadstat.name, msg );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
buf = Z_Malloc( l->filelen + 1 );
|
|
|
|
if( FS_Read( f, buf, l->filelen ) != l->filelen )
|
|
|
|
{
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
|
|
|
Con_DPrintf( S_ERROR "can't read %s lump of map ^2%s^7", msg, loadstat.name );
|
|
|
|
Z_Free( buf );
|
|
|
|
return -1;
|
|
|
|
}
|
|
|
|
|
|
|
|
ret = Mod_LumpLooksLikeEntities( buf, l->filelen );
|
|
|
|
|
|
|
|
Z_Free( buf );
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_TestBmodelLumps
|
|
|
|
|
|
|
|
check for possible errors
|
2023-01-03 05:01:36 +01:00
|
|
|
return real entities lump (for bshift swapped lumps)
|
2018-04-13 18:23:45 +02:00
|
|
|
=================
|
|
|
|
*/
|
2023-01-03 05:01:36 +01:00
|
|
|
qboolean Mod_TestBmodelLumps( file_t *f, const char *name, const byte *mod_base, qboolean silent, dlump_t *entities )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2023-01-03 05:50:50 +01:00
|
|
|
const dheader_t *header = (const dheader_t *)mod_base;
|
|
|
|
const dextrahdr_t *extrahdr = (const dextrahdr_t *)( mod_base + sizeof( dheader_t ));
|
2018-04-13 18:23:45 +02:00
|
|
|
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 ));
|
2023-01-03 05:50:50 +01:00
|
|
|
if( silent )
|
|
|
|
SetBits( flags, LUMP_SILENT );
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
#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
|
2023-01-03 05:01:36 +01:00
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
switch( header->version )
|
|
|
|
{
|
|
|
|
case HLBSP_VERSION:
|
2023-01-03 05:50:50 +01:00
|
|
|
if( extrahdr->id == IDEXTRAHEADER )
|
2023-01-03 05:01:36 +01:00
|
|
|
{
|
2023-01-03 05:50:50 +01:00
|
|
|
SetBits( flags, LUMP_BSP30EXT );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
// only relevant for half-life maps
|
|
|
|
int ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_ENTITIES], flags, "entities" );
|
|
|
|
if( ret < 0 ) return false;
|
|
|
|
if( !ret )
|
2023-01-03 05:01:36 +01:00
|
|
|
{
|
2023-01-03 05:50:50 +01:00
|
|
|
ret = Mod_LumpLooksLikeEntitiesFile( f, &header->lumps[LUMP_PLANES], flags, "planes" );
|
|
|
|
if( ret < 0 ) return false;
|
|
|
|
if( ret )
|
|
|
|
{
|
|
|
|
// blue-shift swapped lumps
|
|
|
|
*entities = header->lumps[LUMP_PLANES];
|
2023-01-03 05:01:36 +01:00
|
|
|
|
2023-01-03 05:50:50 +01:00
|
|
|
srclumps[0].lumpnumber = LUMP_PLANES;
|
|
|
|
srclumps[1].lumpnumber = LUMP_ENTITIES;
|
|
|
|
break;
|
|
|
|
}
|
2023-01-03 05:01:36 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
// intended fallthrough
|
|
|
|
case Q1BSP_VERSION:
|
2018-04-13 18:23:45 +02:00
|
|
|
case QBSP2_VERSION:
|
2023-01-03 05:01:36 +01:00
|
|
|
// everything else
|
|
|
|
*entities = header->lumps[LUMP_ENTITIES];
|
|
|
|
|
|
|
|
srclumps[0].lumpnumber = LUMP_ENTITIES;
|
|
|
|
srclumps[1].lumpnumber = LUMP_PLANES;
|
2018-04-13 18:23:45 +02:00
|
|
|
break;
|
|
|
|
default:
|
|
|
|
// don't early out: let me analyze errors
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
2018-10-04 08:08:48 +02:00
|
|
|
Con_Printf( S_ERROR "%s has wrong version number (%i should be %i)\n", name, header->version, HLBSP_VERSION );
|
2018-04-13 18:23:45 +02:00
|
|
|
loadstat.numerrors++;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
|
|
|
// 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 ))
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( "%s: %i error(s), %i warning(s)\n", __func__, loadstat.numerrors, loadstat.numwarnings );
|
2018-04-13 18:23:45 +02:00
|
|
|
return false; // there were errors, we can't load this map
|
2021-01-03 02:28:45 +01:00
|
|
|
}
|
2018-04-13 18:23:45 +02:00
|
|
|
else if( loadstat.numwarnings )
|
|
|
|
{
|
|
|
|
if( !FBitSet( flags, LUMP_SILENT ))
|
2024-06-19 05:46:08 +02:00
|
|
|
Con_Printf( "%s: %i warning(s)\n", __func__, loadstat.numwarnings );
|
2018-04-13 18:23:45 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
=================
|
|
|
|
Mod_LoadBrushModel
|
|
|
|
=================
|
|
|
|
*/
|
|
|
|
void Mod_LoadBrushModel( model_t *mod, const void *buffer, qboolean *loaded )
|
|
|
|
{
|
2023-03-13 04:08:36 +01:00
|
|
|
char poolname[MAX_VA_STRING];
|
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
Q_snprintf( poolname, sizeof( poolname ), "^2%s^7", mod->name );
|
2023-03-13 04:08:36 +01:00
|
|
|
|
2021-01-03 02:28:45 +01:00
|
|
|
if( loaded ) *loaded = false;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
2023-10-28 15:06:00 +02:00
|
|
|
mod->mempool = Mem_AllocPool( poolname );
|
|
|
|
mod->type = mod_brush;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
// loading all the lumps into heap
|
2023-10-28 15:06:00 +02:00
|
|
|
if( !Mod_LoadBmodelLumps( mod, buffer, world.loading ))
|
2018-04-13 18:23:45 +02:00
|
|
|
return; // there were errors
|
|
|
|
|
|
|
|
if( world.loading ) worldmodel = mod;
|
|
|
|
|
|
|
|
if( loaded ) *loaded = true; // all done
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
==================
|
|
|
|
Mod_CheckLump
|
|
|
|
|
|
|
|
check lump for existing
|
|
|
|
==================
|
|
|
|
*/
|
2023-06-19 06:32:22 +02:00
|
|
|
int GAME_EXPORT Mod_CheckLump( const char *filename, const int lump, int *lumpsize )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2019-05-19 14:01:23 +02:00
|
|
|
file_t *f = FS_Open( filename, "rb", false );
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
==================
|
|
|
|
*/
|
2023-06-19 06:32:22 +02:00
|
|
|
int GAME_EXPORT Mod_ReadLump( const char *filename, const int lump, void **lumpdata, int *lumpsize )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
2019-05-19 14:01:23 +02:00
|
|
|
file_t *f = FS_Open( filename, "rb", false );
|
2018-04-13 18:23:45 +02:00
|
|
|
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
|
|
|
|
==================
|
|
|
|
*/
|
2023-06-19 06:32:22 +02:00
|
|
|
int GAME_EXPORT Mod_SaveLump( const char *filename, const int lump, void *lumpdata, int lumpsize )
|
2018-04-13 18:23:45 +02:00
|
|
|
{
|
|
|
|
byte buffer[sizeof( dheader_t ) + sizeof( dextrahdr_t )];
|
|
|
|
size_t prefetch_size = sizeof( buffer );
|
2018-06-12 11:14:56 +02:00
|
|
|
int result, dummy = lumpsize;
|
2018-04-13 18:23:45 +02:00
|
|
|
dextrahdr_t *extrahdr;
|
|
|
|
dheader_t *header;
|
2018-06-12 11:14:56 +02:00
|
|
|
file_t *f;
|
2018-04-13 18:23:45 +02:00
|
|
|
|
|
|
|
if( !lumpdata || lumpsize <= 0 )
|
|
|
|
return LUMP_SAVE_NO_DATA;
|
|
|
|
|
2018-06-12 11:14:56 +02:00
|
|
|
// 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;
|
|
|
|
|
2018-04-13 18:23:45 +02:00
|
|
|
if( FS_Read( f, buffer, prefetch_size ) != prefetch_size )
|
|
|
|
{
|
|
|
|
FS_Close( f );
|
|
|
|
return LUMP_SAVE_BAD_HEADER;
|
|
|
|
}
|
|
|
|
|
|
|
|
header = (dheader_t *)buffer;
|
|
|
|
|
2018-06-12 11:14:56 +02:00
|
|
|
// these checks below are redundant
|
2018-04-13 18:23:45 +02:00
|
|
|
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;
|
2018-04-23 20:49:37 +02:00
|
|
|
}
|