Misc HLBSP tweaks

Added support for multiple -wadpath args.
Added -xwadpath (for eg valve/*.wad to avoid bloat/license issues).
Reworked hint brushes - the non-hint surfaces can use any name, just so long as they're not 'hint', for compat with zhlt.
Support 'bevel' and 'null' textures for compat with zhlt.
This commit is contained in:
Shpoike 2019-03-28 02:18:16 +00:00
parent 7fb22c7367
commit 347a455102
11 changed files with 135 additions and 68 deletions

View File

@ -41,7 +41,7 @@ int Brush_ListCountWithCFlags(const brush_t *brush, int cflags);
int Brush_ListCount(const brush_t *brush);
int Brush_NumFaces(const brush_t *brush);
brush_t *LoadBrush(const mapbrush_t *mapbrush, const vec3_t rotate_offset, const int hullnum);
brush_t *LoadBrush(const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const int hullnum);
void FreeBrushes(mapentity_t *ent);
int FindPlane(const vec3_t normal, const vec_t dist, int *side);

View File

@ -71,6 +71,7 @@ public:
int firstface;
int numfaces;
brushformat_t format;
int contents;
mapbrush_t() : firstface(0), numfaces(0), format(brushformat_t::NORMAL) {}
const mapface_t &face(int i) const;

View File

@ -111,6 +111,8 @@
#define CONTENTS_DETAIL_ILLUSIONARY -11 /* compiler internal use only */
#define CONTENTS_DETAIL_FENCE -12 /* compiler internal use only */
#define CONTENTS_ILLUSIONARY_VISBLOCKER -13
#define CONTENTS_FENCE -15 /* compiler internal use only */
#define CONTENTS_LADDER -16 /* reserved for engine use */
// Special contents flags for the compiler only
#define CFLAGS_STRUCTURAL_COVERED_BY_DETAIL (1U << 0)
@ -139,6 +141,7 @@
#define TEX_PHONG_ANGLE_CONCAVE_SHIFT 45
#define TEX_PHONG_ANGLE_CONCAVE_MASK (255ULL << TEX_PHONG_ANGLE_CONCAVE_SHIFT) /* 8 bit value. if non zero, overrides _phong_angle for concave joints. */
#define TEX_NOBOUNCE (1ULL << 53) /* light doesn't bounce off this face */
#define TEX_NOEXPAND (1ULL << 54) /* don't expand this face for larger clip hulls */
/*
* The quality of the bsp output is highly sensitive to these epsilon values.
@ -367,7 +370,11 @@ public:
float midsplitSurfFraction;
char szMapName[512];
char szBSPName[512];
char wadPath[512];
struct
{
char *path;
bool external; //wads from this path are not to be embedded into the bsp, but will instead require the engine to load them from elsewhere. strongly recommended for eg halflife.wad
} wadPaths[16];
vec_t on_epsilon;
bool fObjExport;
bool fOmitDetail;
@ -379,7 +386,15 @@ public:
bool fLeakTest;
bool fContentHack;
vec_t worldExtent;
~options_t() {
for (int i = 0; i < sizeof(wadPaths)/sizeof(wadPaths[0]); i++)
{
free(wadPaths[i].path);
wadPaths[i].path = nullptr;
}
}
options_t() {
memset(this, 0, sizeof(options_t));
@ -389,7 +404,6 @@ public:
this->fVerbose = true;
this->szMapName[0] = 0;
this->szBSPName[0] = 0;
this->wadPath[0] = 0;
/* Default to the original Quake BSP Version... */
this->BSPVersion = BSPVERSION;

View File

@ -65,7 +65,7 @@ typedef struct wad_s {
struct wad_s *next;
} wad_t;
wad_t *WADList_AddWad(const char *fpath, wad_t *current_wadlist);
wad_t *WADList_AddWad(const char *fpath, bool external, wad_t *current_wadlist);
wad_t *WADList_Init(const char *wadstring);
void WADList_Process(const wad_t *wadlist);
void WADList_Free(wad_t *wadlist);

View File

@ -30,7 +30,8 @@ Print out more .map information
.IP "\fB-noverbose\fP"
Print out almost no information at all
.IP "\fB-splitspecial\fP"
Doesn't combine sky and water faces into one large face
Doesn't combine sky and water faces into one large face.
This allows for statically lit water.
.IP "\fB-transwater\fP"
Computes portal information for transparent water (default)
.IP "\fB-notranswater\fP"
@ -53,19 +54,27 @@ Create an old-style QBSP .PTS file (default is new)
Makes it a compile error if a leak is detected.
.IP "\fB-nopercent\fP"
Prevents output of percent completion information
.IP "\fB-hexen2\fP"
Generate a hexen2 bsp. This can be used in addition to -bsp2 to avoid clipnode issues.
.IP "\fB-bsp2\fP"
Create the output BSP file in BSP2 format. Allows the creation of much larger
and more complex maps than the original BSP 29 format).
.IP "\fB-2psb\fP"
Create the output BSP file in 2PSB format. This an earlier version of the
BSP2 format, supported by the RMQ engine (and thus is also known as the
BSP2rmq or RMQe bsp format). original BSP 29 format).
BSP2rmq or RMQe bsp format).
.IP "\fB-hlbsp\fP"
Create the output BSP file in Half-Life's format.
Note that the hull size differences prevent this from being generally usable for the vanilla quake gamecode.
This cannot be used in combination with the -bsp2 argument.
.IP "\fB-leakdist [n]\fP"
Space between leakfile points (default 2)
.IP "\fB-subdivide [n]\fP"
Use different texture subdivision (default 240)
Use different texture subdivision (default 240). Lower values will harm framerates. Higher values may not be supported. DP+FTEQW+QSS support up to 4080 (unless lightmap scaling is in use), but such values will cause other engines to crash-to-console.
.IP "\fB-wadpath <dir>\fP"
Search this directory for wad files (default is cwd)
Search this directory for wad files (default is cwd). Multiple -wadpath args may be used. This argument is ignored for wads specified using an absolute path.
.IP "\fB-xwadpath <dir>\fP"
Like -wadpath, except textures found using the specified path will NOT be embedded into the bsp (equivelent to -notex, but for only textures from specific wads). You should use this for wads like halflife's standard wad files, but q1bsps require an engine extension and players are not nearly as likely to have the same wad version.
.IP "\fB-oldrottex\fP"
Use old method of texturing rotate_ brushes where the mapper aligns
textures for the object at (0 0 0).
@ -74,14 +83,13 @@ Switch to the cheap spatial subdivion bsp heuristic when splitting nodes
of this size (in any dimension). This gives much faster qbsp processing
times on large maps and should generate better bsp trees as well.
From txqbsp-xt, thanks rebb. (default 1024, 0 to disable)
.IP "\fB-hexen2\fP"
Generate a hexen2 bsp.
.IP "\fB-wrbrushes\fP"
(bspx) Includes a list of brushes for brush-based collision.
This allows for arbitrary collision sizes in engines that support it, currently only FTEQW.
.IP "\fB-wrbrushesonly\fP"
"-wrbrushes" combined with "-noclip" argument.
"-wrbrushes" combined with "-noclip" argument. This is NOT backwards compatible.
.IP "\fB-notex\fP"
Write only placeholder textures, to depend upon replacements.
Write only placeholder textures, to depend upon replacements. This avoids inclusion of third-party copyrighted images inside your maps, but is not backwards compatible but will work in FTEQW and QSS.
.IP "\fB-omitdetail\fP"
Detail brushes are omitted from the compile.
.IP "\fB-convert <fmt>\fP"

View File

@ -33,6 +33,7 @@
typedef struct hullbrush_s {
const mapbrush_t *srcbrush;
int contents;
int numfaces;
vec3_t mins;
vec3_t maxs;
@ -382,11 +383,11 @@ CreateBrushFaces(hullbrush_t *hullbrush, const vec3_t rotate_offset,
mapface = hullbrush->faces;
for (i = 0; i < hullbrush->numfaces; i++, mapface++) {
if (!hullnum) {
if (!hullnum && hullbrush->contents == CONTENTS_HINT) {
/* Don't generate hintskip faces */
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface->texinfo);
const char *texname = map.miptex.at(texinfo.miptex).c_str();
if (!Q_strcasecmp(texname, "hintskip"))
if (Q_strcasecmp(texname, "hint"))
continue;
}
@ -764,6 +765,8 @@ ExpandBrush(hullbrush_t *hullbrush, vec3_t hull_size[2], face_t *facelist)
// expand all of the planes
mapface = hullbrush->faces;
for (i = 0; i < hullbrush->numfaces; i++, mapface++) {
if (mapface->flags & TEX_NOEXPAND)
continue;
VectorCopy(vec3_origin, corner);
for (x = 0; x < 3; x++) {
if (mapface->plane.normal[x] > 0)
@ -814,28 +817,33 @@ static int
Brush_GetContents(const mapbrush_t *mapbrush)
{
const char *texname;
const mapface_t &mapface = mapbrush->face(0);
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo);
texname = map.miptex.at(texinfo.miptex).c_str();
if (!Q_strcasecmp(texname, "origin"))
return CONTENTS_ORIGIN;
if (!Q_strcasecmp(texname, "hint") || !Q_strcasecmp(texname, "hintskip"))
return CONTENTS_HINT;
if (!Q_strcasecmp(texname, "clip"))
return CONTENTS_CLIP;
//check for strong content indicators
for (int i = 0; i < mapbrush->numfaces; i++)
{
const mapface_t &mapface = mapbrush->face(i);
const mtexinfo_t &texinfo = map.mtexinfos.at(mapface.texinfo);
texname = map.miptex.at(texinfo.miptex).c_str();
if (texname[0] == '*') {
if (!Q_strncasecmp(texname + 1, "lava", 4))
return CONTENTS_LAVA;
if (!Q_strncasecmp(texname + 1, "slime", 5))
return CONTENTS_SLIME;
return CONTENTS_WATER;
if (!Q_strcasecmp(texname, "origin"))
return CONTENTS_ORIGIN;
if (!Q_strcasecmp(texname, "hint"))
return CONTENTS_HINT;
if (!Q_strcasecmp(texname, "clip"))
return CONTENTS_CLIP;
if (texname[0] == '*') {
if (!Q_strncasecmp(texname + 1, "lava", 4))
return CONTENTS_LAVA;
if (!Q_strncasecmp(texname + 1, "slime", 5))
return CONTENTS_SLIME;
return CONTENTS_WATER;
}
if (!Q_strncasecmp(texname, "sky", 3))
return CONTENTS_SKY;
}
if (!Q_strncasecmp(texname, "sky", 3))
return CONTENTS_SKY;
//and anything else is assumed to be a regular solid.
return CONTENTS_SOLID;
}
@ -847,7 +855,7 @@ LoadBrush
Converts a mapbrush to a bsp brush
===============
*/
brush_t *LoadBrush(const mapbrush_t *mapbrush, const vec3_t rotate_offset, const int hullnum)
brush_t *LoadBrush(const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const int hullnum)
{
hullbrush_t hullbrush;
brush_t *brush;
@ -858,6 +866,7 @@ brush_t *LoadBrush(const mapbrush_t *mapbrush, const vec3_t rotate_offset, const
Error("brush->faces >= MAX_FACES (%d), source brush on line %d",
MAX_FACES, mapbrush->face(0).linenum);
hullbrush.contents = contents;
hullbrush.srcbrush = mapbrush;
hullbrush.numfaces = mapbrush->numfaces;
for (int i=0; i<mapbrush->numfaces; i++)
@ -961,6 +970,7 @@ brush_t *LoadBrush(const mapbrush_t *mapbrush, const vec3_t rotate_offset, const
// create the brush
brush = (brush_t *)AllocMem(BRUSH, 1, true);
brush->contents = contents;
brush->faces = facelist;
VectorCopy(hullbrush.mins, brush->mins);
VectorCopy(hullbrush.maxs, brush->maxs);
@ -1097,7 +1107,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
continue;
}
brush_t *brush = LoadBrush(mapbrush, vec3_origin, 0);
brush_t *brush = LoadBrush(mapbrush, contents, vec3_origin, 0);
if (brush) {
vec3_t origin;
VectorAdd(brush->mins, brush->maxs, origin);
@ -1221,7 +1231,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
*/
if (contents == CONTENTS_CLIP) {
if (hullnum <= 0) {
brush_t *brush = LoadBrush(mapbrush, rotate_offset, hullnum);
brush_t *brush = LoadBrush(mapbrush, contents, rotate_offset, hullnum);
if (brush) {
AddToBounds(dst, brush->mins);
AddToBounds(dst, brush->maxs);
@ -1263,12 +1273,11 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
if (hullnum && contents == CONTENTS_SKY)
contents = CONTENTS_SOLID;
brush_t *brush = LoadBrush(mapbrush, rotate_offset, hullnum);
brush_t *brush = LoadBrush(mapbrush, contents, rotate_offset, hullnum);
if (!brush)
continue;
dst->numbrushes++;
brush->contents = contents;
brush->lmshift = lmshift;
brush->cflags = cflags;

View File

@ -132,7 +132,7 @@ const char *rgszWarnings[cWarnings] = {
"Point (%.3f %.3f %.3f) off plane by %2.4f",
"Couldn't create brush faces",
"Reached occupant at (%.0f %.0f %.0f), no filling performed.",
"Reached occupant \"%s\" at (%.0f %.0f %.0f), no filling performed.",
"Portal siding direction is wrong",
"New portal was clipped away in CutNodePortals_r near (%.3f %.3f %.3f)",
"Winding outside node",

View File

@ -204,6 +204,18 @@ IsSkipName(const char *name)
return true;
if (!Q_strcasecmp(name, "*lavaskip"))
return true;
if (!Q_strcasecmp(name, "bevel")) //zhlt compat
return true;
if (!Q_strcasecmp(name, "null")) //zhlt compat
return true;
return false;
}
static bool
IsNoExpandName(const char *name)
{
if (!Q_strcasecmp(name, "bevel")) //zhlt compat
return true;
return false;
}
@ -292,6 +304,8 @@ FindTexinfoEnt(mtexinfo_t *texinfo, const mapentity_t *entity)
flags |= TEX_HINT;
if (IsSpecialName(texname))
flags |= TEX_SPECIAL;
if (IsNoExpandName(texname))
flags |= TEX_NOEXPAND;
if (atoi(ValueForKey(entity, "_dirt")) == -1)
flags |= TEX_NODIRT;
if (atoi(ValueForKey(entity, "_bounce")) == -1)
@ -2323,7 +2337,7 @@ TestExpandBrushes(const mapentity_t *src)
for (int i = 0; i < src->nummapbrushes; i++) {
const mapbrush_t *mapbrush = &src->mapbrush(i);
brush_t *hull1brush = LoadBrush(mapbrush, vec3_origin, 1);
brush_t *hull1brush = LoadBrush(mapbrush, CONTENTS_SOLID, vec3_origin, 1);
if (hull1brush != nullptr)
hull1brushes.push_back(hull1brush);

View File

@ -436,7 +436,7 @@ FillOutside(node_t *node, const int hullnum)
Q_assert(leakentity != nullptr);
const vec_t *origin = leakentity->origin;
Message(msgWarning, warnMapLeak, origin[0], origin[1], origin[2]);
Message(msgWarning, warnMapLeak, ValueForKey(leakentity, "classname"), origin[0], origin[1], origin[2]);
if (map.leakfile)
return false;

View File

@ -682,15 +682,16 @@ PrintOptions(void)
" -nooldaxis Uses alternate texture alignment which was default in tyrutils-ericw v0.15.1 and older\n"
" -forcegoodtree Force use of expensive processing for SolidBSP stage\n"
" -nopercent Prevents output of percent completion information\n"
" -hexen2 Generate a BSP compatible with hexen2 engines\n"
" -wrbrushes (bspx) Includes a list of brushes for brush-based collision\n"
" -wrbrushesonly -wrbrushes with -noclip\n"
" -hexen2 Generate a BSP compatible with hexen2 engines\n"
" -hlbsp Request output in Half-Life bsp format\n"
" -bsp2 Request output in bsp2 format\n"
" -2psb Request output in 2psb format (RMQ compatible)\n"
" -leakdist [n] Space between leakfile points (default 2)\n"
" -subdivide [n] Use different texture subdivision (default 240)\n"
" -wadpath <dir> Search this directory for wad files\n"
" -wadpath <dir> Search this directory for wad files (mips will be embedded unless -notex)\n"
" -xwadpath <dir> Search this directory for wad files (mips will NOT be embedded, avoiding texture license issues)\n"
" -oldrottex Use old rotate_ brush texturing aligned at (0 0 0)\n"
" -maxnodesize [n]Triggers simpler BSP Splitting when node exceeds size (default 1024, 0 to disable)\n"
" -epsilon [n] Customize ON_EPSILON (default 0.0001)\n"
@ -849,15 +850,25 @@ ParseOptions(char *szOptions)
Error("Invalid argument to option %s", szTok);
options.dxSubdivide = atoi(szTok2);
szTok = szTok2;
} else if (!Q_strcasecmp(szTok, "wadpath")) {
} else if (!Q_strcasecmp(szTok, "wadpath") || !Q_strcasecmp(szTok, "xwadpath")) {
szTok2 = GetTok(szTok + strlen(szTok) + 1, szEnd);
if (!szTok2)
Error("Invalid argument to option %s", szTok);
strcpy(options.wadPath, szTok2);
int i;
for (i = 0; i < sizeof(options.wadPaths)/sizeof(options.wadPaths[0]); i++)
{
if (options.wadPaths[i].path)
continue;
options.wadPaths[i].external = !!Q_strcasecmp(szTok, "wadpath");
options.wadPaths[i].path = strdup(szTok2);
/* Remove trailing /, if any */
if (options.wadPaths[i].path[strlen(options.wadPaths[i].path) - 1] == '/')
options.wadPaths[i].path[strlen(options.wadPaths[i].path) - 1] = 0;
break;
}
if (i == sizeof(options.wadPaths)/sizeof(options.wadPaths[0]))
Error("too many -wadpath args");
szTok = szTok2;
/* Remove trailing /, if any */
if (options.wadPath[strlen(options.wadPath) - 1] == '/')
options.wadPath[strlen(options.wadPath) - 1] = 0;
} else if (!Q_strcasecmp(szTok, "oldrottex")) {
options.fixRotateObjTexture = false;
} else if (!Q_strcasecmp(szTok, "maxnodesize")) {
@ -996,9 +1007,10 @@ InitQBSP(int argc, const char **argv)
Message(msgFile, IntroString);
/* If no wadpath given, default to the map directory */
if (options.wadPath[0] == 0) {
strcpy(options.wadPath, options.szMapName);
StripFilename(options.wadPath);
if (!options.wadPaths[0].path) {
options.wadPaths[0].external = false;
options.wadPaths[0].path = strdup(options.szMapName);
StripFilename(options.wadPaths[0].path);
}
// Remove already existing files

View File

@ -42,13 +42,15 @@ byte thepalette[768] = // Quake palette
};
static bool
WAD_LoadInfo(wad_t *wad)
WAD_LoadInfo(wad_t *wad, bool external)
{
wadinfo_t *hdr = &wad->header;
int i, len, lumpinfosize, disksize;
int i, len, lumpinfosize;
dmiptex_t miptex;
texture_t *tex;
external |= options.fNoTextures;
len = fread(hdr, 1, sizeof(wadinfo_t), wad->file);
if (len != sizeof(wadinfo_t))
return false;
@ -80,6 +82,7 @@ WAD_LoadInfo(wad_t *wad)
wad->lumps[i].size = sizeof(miptex) + (w>>0)*(h>>0) + (w>>1)*(h>>1) + (w>>2)*(h>>2) + (w>>3)*(h>>3);
if (options.BSPVersion == BSPHLVERSION)
wad->lumps[i].size += 2+3*256; //palette size+palette data
wad->lumps[i].size = (wad->lumps[i].size+3) & ~3; //keep things aligned if we can.
tex = (texture_t *)AllocMem(OTHER, sizeof(texture_t), true);
tex->next = textures;
@ -89,6 +92,10 @@ WAD_LoadInfo(wad_t *wad)
tex->width = miptex.width;
tex->height = miptex.height;
//if we're not going to embed it into the bsp, set its size now so we know how much to actually store.
if (external)
wad->lumps[i].size = wad->lumps[i].disksize = sizeof(dmiptex_t);
//printf("Created texture_t %s %d %d\n", tex->name, tex->width, tex->height);
}
else
@ -98,7 +105,7 @@ WAD_LoadInfo(wad_t *wad)
return true;
}
wad_t *WADList_AddWad(const char *fpath, wad_t *current_wadlist)
wad_t *WADList_AddWad(const char *fpath, bool external, wad_t *current_wadlist)
{
wad_t wad = {0};
@ -106,12 +113,13 @@ wad_t *WADList_AddWad(const char *fpath, wad_t *current_wadlist)
if (wad.file) {
if (options.fVerbose)
Message(msgLiteral, "Opened WAD: %s\n", fpath);
if (WAD_LoadInfo(&wad)) {
if (WAD_LoadInfo(&wad, external)) {
wad_t *newwad = (wad_t *)AllocMem(OTHER, sizeof(wad), true);
memcpy(newwad, &wad, sizeof(wad));
newwad->next = current_wadlist;
// FIXME: leaves file open?
// (currently needed so that mips can be loaded later, as needed)
return newwad;
} else {
@ -143,18 +151,22 @@ WADList_Init(const char *wadstring)
while (*pos && *pos != ';')
pos++;
if (!options.wadPath[0] || IsAbsolutePath(fname)) {
if (!options.wadPaths[0].path || IsAbsolutePath(fname)) {
fpath = (char *)AllocMem(OTHER, (pos - fname) + 1, false);
q_snprintf(fpath, (pos - fname) + 1, "%s", fname);
wadlist = WADList_AddWad(fpath, false, wadlist);
FreeMem(fpath, OTHER, strlen(fpath) + 1);
} else {
pathlen = strlen(options.wadPath) + 1 + (pos - fname);
fpath = (char *)AllocMem(OTHER, pathlen + 1, true);
q_snprintf(fpath, pathlen + 1, "%s/%s", options.wadPath, fname);
for (int i = 0; i < sizeof(options.wadPaths)/sizeof(options.wadPaths[0]) && options.wadPaths[i].path; i++)
{
pathlen = strlen(options.wadPaths[i].path) + 1 + (pos - fname);
fpath = (char *)AllocMem(OTHER, pathlen + 1, true);
q_snprintf(fpath, pathlen + 1, "%s/%s", options.wadPaths[i].path, fname);
wadlist = WADList_AddWad(fpath, options.wadPaths[i].external, wadlist);
FreeMem(fpath, OTHER, strlen(fpath) + 1);
}
}
wadlist = WADList_AddWad(fpath, wadlist);
FreeMem(fpath, OTHER, strlen(fpath) + 1);
pos++;
}
@ -206,10 +218,7 @@ WADList_Process(const wad_t *wadlist)
for (i = 0; i < map.nummiptex(); i++) {
texture = WADList_FindTexture(wadlist, map.miptex.at(i).c_str());
if (texture) {
if (options.fNoTextures)
texdata->count += sizeof(dmiptex_t);
else
texdata->count += texture->size;
texdata->count += texture->size;
}
}
@ -267,7 +276,7 @@ WAD_LoadLump(const wad_t *wad, const char *name, byte *dest)
for (i = 0; i < wad->header.numlumps; i++) {
if (!Q_strcasecmp(name, wad->lumps[i].name)) {
fseek(wad->file, wad->lumps[i].filepos, SEEK_SET);
if (options.fNoTextures || wad->lumps[i].disksize == sizeof(dmiptex_t))
if (wad->lumps[i].disksize == sizeof(dmiptex_t))
{
size = fread(dest, 1, sizeof(dmiptex_t), wad->file);
if (size != sizeof(dmiptex_t))