qbsp: fix origin brushes in hexen 2. Drop _no_bbox_rotation_expansion

document origin brushes. Never expand the bbox when origin brushes are in
use.
This commit is contained in:
Eric Wasylishen 2020-02-24 01:26:53 -07:00
parent b56afb5988
commit c30a0a46a9
5 changed files with 68 additions and 30 deletions

View File

@ -41,7 +41,11 @@ 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 mapentity_t *src, const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const int hullnum);
enum class rotation_t {
none, hipnotic, origin_brush
};
brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const rotation_t rottype, const int hullnum);
void FreeBrushes(mapentity_t *ent);
int FindPlane(const vec3_t normal, const vec_t dist, int *side);

View File

@ -133,6 +133,30 @@ brush which you don't want to generate bsp splits or portals. All
surfaces of a hint brush must use either the \fIhint\fP or
\fIhintskip\fP texture name.
.SS "ORIGIN"
.PP
An origin brush (all faces textured with "origin") can be added to a brush entity
(but not detail or compiler-internal entities like func_group). Doing so causes all of
the brushes in the brush entitiy to be translated so the center of the origin brush
lines up with 0 0 0. The entity key "origin" is then automatically set on the
brush entity to the original cooridnates of the center of the
"origin" brush before it was translated to 0 0 0.
.PP
In Hexen 2, origin brushes are the native way of marking the center point of the
rotation axis for rotating entities.
.PP
In Quake, origin brushes can be used to make some map hacks easier to set up
that would otherwise require placing brushes at the world origin and
entering an "origin" value by hand.
.PP
Note than, unlike the Hipnotic rotation support in QBSP, using origin brushes
does not cause the model bounds to be expanded. (With Hipnotic rotation this was
to ensure that the model is not vis culled, regardless of its rotated angle.)
Origin brushes are useful for more than just rotation, and doing this bounds
expansion would break some use cases, so if you're going to rotate a model
with an origin brush you might need to expand the bounds of it a bit using
clip brushes so it doesn't get vis culled.
.SH "EXTERNAL MAP PREFAB SUPPORT"
.PP
This qbsp has a prefab system using a point entity named "misc_external_map".

View File

@ -363,8 +363,8 @@ CreateBrushFaces
=================
*/
static face_t *
CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, const vec3_t rotate_offset,
const int hullnum)
CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush,
const vec3_t rotate_offset, const rotation_t rottype, const int hullnum)
{
int i, j, k;
vec_t r;
@ -475,11 +475,20 @@ CreateBrushFaces(const mapentity_t *src, hullbrush_t *hullbrush, const vec3_t ro
// if -wrbrushes is in use, don't do this for the clipping hulls because it depends on having
// the actual non-hacked bbox (it doesn't write axial planes).
const bool noExpand = static_cast<bool>(
atoi(ValueForKey(src, "_no_bbox_rotation_expansion"))
) || (hullnum < 0);
// Hexen2 also doesn't want the bbox expansion, it's handled in engine (see: SV_LinkEdict)
if ((rotate_offset[0] || rotate_offset[1] || rotate_offset[2]) && !noExpand) {
// Only do this for hipnotic rotation. For origin brushes in Quake, it breaks some of their
// uses (e.g. func_train). This means it's up to the mapper to expand the model bounds with
// clip brushes if they're going to rotate a model in vanilla Quake and not use hipnotic rotation.
// The idea behind the bounds expansion was to avoid incorrect vis culling (AFAIK).
const bool shouldExpand =
(rotate_offset[0] != 0.0 || rotate_offset[1] != 0.0 || rotate_offset[2] != 0.0)
&& rottype == rotation_t::hipnotic
&& (hullnum >= 0) // hullnum < 0 corresponds to -wrbrushes clipping hulls
&& !options.hexen2; // never do this in Hexen 2
if (shouldExpand) {
vec_t delta;
delta = fabs(max);
@ -865,7 +874,7 @@ LoadBrush
Converts a mapbrush to a bsp brush
===============
*/
brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const int hullnum)
brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int contents, const vec3_t rotate_offset, const rotation_t rottype, const int hullnum)
{
hullbrush_t hullbrush;
brush_t *brush;
@ -886,11 +895,11 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int conte
if (hullnum <= 0) {
// for hull 0 or BSPX -wrbrushes collision, apply the rotation offset now
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
} else {
// for Quake-style clipping hulls, don't apply rotation offset yet..
// it will be applied below
facelist = CreateBrushFaces(src, &hullbrush, vec3_origin, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, vec3_origin, rottype, hullnum);
}
if (!facelist) {
@ -905,19 +914,19 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int conte
vec3_t size[2] = { {-16, -16, -36}, {16, 16, 36} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
else if (hullnum == 2) {
vec3_t size[2] = { {-32, -32, -32}, {32, 32, 32} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
else if (hullnum == 3) {
vec3_t size[2] = { {-16, -16, -18}, {16, 16, 18} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
}
else if (options.hexen2)
@ -926,19 +935,19 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int conte
vec3_t size[2] = { {-16, -16, -32}, {16, 16, 24} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
else if (hullnum == 2) {
vec3_t size[2] = { {-24, -24, -20}, {24, 24, 20} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
else if (hullnum == 3) {
vec3_t size[2] = { {-16, -16, -12}, {16, 16, 16} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
else if (hullnum == 4) {
#if 0
@ -946,21 +955,21 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int conte
vec3_t size[2] = { {-40, -40, -42}, {40, 40, 42} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
} else
#endif
{ /*mission pack*/
vec3_t size[2] = { {-8, -8, -8}, {8, 8, 8} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
}
else if (hullnum == 5) {
vec3_t size[2] = { {-48, -48, -50}, {48, 48, 50} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
}
else
@ -970,13 +979,13 @@ brush_t *LoadBrush(const mapentity_t *src, const mapbrush_t *mapbrush, int conte
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
} else if (hullnum == 2) {
vec3_t size[2] = { {-32, -32, -64}, {32, 32, 24} };
ExpandBrush(&hullbrush, size, facelist);
FreeBrushFaces(facelist);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, hullnum);
facelist = CreateBrushFaces(src, &hullbrush, rotate_offset, rottype, hullnum);
}
}
@ -1108,7 +1117,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
classname = ValueForKey(src, "classname");
/* Origin brush support */
bool usesOriginBrush = false;
rotation_t rottype = rotation_t::none;
VectorCopy(vec3_origin, rotate_offset);
for (int i = 0; i < src->nummapbrushes; i++) {
@ -1120,7 +1129,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
continue;
}
brush_t *brush = LoadBrush(src, mapbrush, contents, vec3_origin, 0);
brush_t *brush = LoadBrush(src, mapbrush, contents, vec3_origin, rotation_t::none, 0);
if (brush) {
vec3_t origin;
VectorAdd(brush->mins, brush->maxs, origin);
@ -1131,7 +1140,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
SetKeyValue(dst, "origin", value);
VectorCopy(origin, rotate_offset);
usesOriginBrush = true;
rottype = rotation_t::origin_brush;
FreeBrush(brush);
}
@ -1139,10 +1148,11 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
}
/* Hipnotic rotation */
if (!usesOriginBrush) {
if (rottype == rotation_t::none) {
if (!strncmp(classname, "rotate_", 7)) {
FixRotateOrigin(dst);
GetVectorForKey(dst, "origin", rotate_offset);
rottype = rotation_t::hipnotic;
}
}
@ -1245,7 +1255,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
*/
if (contents == CONTENTS_CLIP) {
if (hullnum == 0) {
brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, hullnum);
brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum);
if (brush) {
AddToBounds(dst, brush->mins);
AddToBounds(dst, brush->maxs);
@ -1291,7 +1301,7 @@ Brush_LoadEntity(mapentity_t *dst, const mapentity_t *src, const int hullnum)
if (hullnum > 0 && contents == CONTENTS_SKY)
contents = CONTENTS_SOLID;
brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, hullnum);
brush_t *brush = LoadBrush(src, mapbrush, contents, rotate_offset, rottype, hullnum);
if (!brush)
continue;

View File

@ -2370,7 +2370,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(src, mapbrush, CONTENTS_SOLID, vec3_origin, 1);
brush_t *hull1brush = LoadBrush(src, mapbrush, CONTENTS_SOLID, vec3_origin, rotation_t::none, 1);
if (hull1brush != nullptr)
hull1brushes.push_back(hull1brush);

View File

@ -115,7 +115,7 @@ TEST(qbsp, duplicatePlanes) {
EXPECT_EQ(0, worldspawn.numbrushes);
EXPECT_EQ(6, worldspawn.mapbrush(0).numfaces);
brush_t *brush = LoadBrush(&worldspawn, &worldspawn.mapbrush(0), CONTENTS_SOLID, vec3_origin, 0);
brush_t *brush = LoadBrush(&worldspawn, &worldspawn.mapbrush(0), CONTENTS_SOLID, vec3_origin, rotation_t::none, 0);
ASSERT_NE(nullptr, brush);
EXPECT_EQ(6, Brush_NumFaces(brush));
FreeBrush(brush);
@ -141,7 +141,7 @@ static brush_t *load128x128x32Brush()
mapentity_t worldspawn = LoadMap(map);
Q_assert(1 == worldspawn.nummapbrushes);
brush_t *brush = LoadBrush(&worldspawn, &worldspawn.mapbrush(0), CONTENTS_SOLID, vec3_origin, 0);
brush_t *brush = LoadBrush(&worldspawn, &worldspawn.mapbrush(0), CONTENTS_SOLID, vec3_origin, rotation_t::none, 0);
Q_assert(nullptr != brush);
brush->contents = CONTENTS_SOLID;