qbsp: planning outside filling approach, wip

This commit is contained in:
Eric Wasylishen 2022-04-05 01:31:48 -06:00
parent 449b76db70
commit edad06cc5a
3 changed files with 38 additions and 102 deletions

View File

@ -323,14 +323,17 @@ struct face_t : face_fragment_t
mapentity_t *src_entity; // source entity
face_t *original; // face on node
bool touchesOccupiedLeaf; // internal use in outside.cc
qvec3d origin;
vec_t radius;
// filled by TJunc
std::vector<face_fragment_t> fragments;
// fixme-brushbsp: move to a brush_side_t struct
bool onnode; // has this face been used as a BSP node plane yet?
bool visible; // can any part of this side be seen from non-void parts of the level?
// non-visible means we can discard the brush side
// (avoiding generating a BSP spit, so expanding it outwards)
};
// there is a node_t structure for every node and leaf in the bsp tree

View File

@ -116,6 +116,7 @@ static bool Portal_Passable(const portal_t *p)
// fixme-brushbsp: confirm, why was this not needed before?
// detail separators are treated as non-opaque because detail doesn't block vis
// fixme-brushbsp: should probably move to node_t::opaque()
if (l->detail_separator) {
return false;
}
@ -322,77 +323,24 @@ std::vector<node_t *> FindOccupiedClusters(node_t *headnode)
/*
==================
ResetFacesTouchingOccupiedLeafs
Set f->touchesOccupiedLeaf=false on all faces.
==================
*/
static void ResetFacesTouchingOccupiedLeafs(node_t *node)
{
if (node->planenum == PLANENUM_LEAF) {
return;
}
for (face_t *face : node->facelist) {
face->touchesOccupiedLeaf = false;
}
ResetFacesTouchingOccupiedLeafs(node->children[0]);
ResetFacesTouchingOccupiedLeafs(node->children[1]);
}
/*
==================
MarkFacesTouchingOccupiedLeafs
MarkVisibleBrushSides
Set f->touchesOccupiedLeaf=true on faces that are touching occupied leafs
==================
*/
static void MarkFacesTouchingOccupiedLeafs(node_t *node)
static void MarkVisibleBrushSides(node_t *node)
{
if (node->planenum != PLANENUM_LEAF) {
MarkFacesTouchingOccupiedLeafs(node->children[0]);
MarkFacesTouchingOccupiedLeafs(node->children[1]);
MarkVisibleBrushSides(node->children[0]);
MarkVisibleBrushSides(node->children[1]);
return;
}
// visit the leaf
if (node->outside_distance == -1) {
// This is an occupied leaf, so we need to keep all of the faces touching it.
for (auto &markface : node->markfaces) {
markface->touchesOccupiedLeaf = true;
}
}
}
/*
==================
ClearOutFaces
Deletes (by setting f->w.numpoints=0) faces in solid nodes
==================
*/
static void ClearOutFaces(node_t *node)
{
if (node->planenum != PLANENUM_LEAF) {
ClearOutFaces(node->children[0]);
ClearOutFaces(node->children[1]);
return;
}
// visit the leaf
if (!node->contents.is_solid(options.target_game)) {
return;
}
for (auto &markface : node->markfaces) {
// NOTE: This is how faces are deleted here, kind of ugly
markface->w.clear();
}
// FIXME: Shouldn't be needed here
node->facelist = {};
// fixme-brushbsp: do a flood fill from all empty leafs.
// when we reach a portal to solid, look for a brush side with the same
// planenum. mark it as 'visible'.
}
static void OutLeafsToSolid_r(node_t *node, int *outleafs_count)
@ -411,20 +359,7 @@ static void OutLeafsToSolid_r(node_t *node, int *outleafs_count)
if (node->contents.is_solid(options.target_game) || node->contents.is_sky(options.target_game))
return;
// Now check all faces touching the leaf. If any of them are partially going into the occupied part of the map,
// don't fill the leaf (see comment in FillOutside).
bool skipFill = false;
for (auto &markface : node->markfaces) {
if (markface->touchesOccupiedLeaf) {
skipFill = true;
break;
}
}
if (skipFill) {
return;
}
// Finally, we can fill it in as void.
// Finally, we can fill it in as void.
node->contents = options.target_game->create_solid_contents();
*outleafs_count += 1;
}
@ -442,6 +377,24 @@ static int OutLeafsToSolid(node_t *node)
===========
FillOutside
Goal is to mark brush sides which only touch void as "unbounded" (aka Q2 skip),
so they're not used as BSP splitters in the next phase and basically expand outwards.
Brush sides which cross between void and non-void are problematic for this, so
the process looks like:
1) flood outside -> in from beyond the map bounds and mark these leafs as solid
Now all leafs marked "empty" are actually empty, not void.
2) initialize all original brush sides to "invisible"
2) flood from all empty leafs, mark original brush sides as "visible"
This will handle partially-void, partially-in-bounds sides (they'll be marked visible).
fixme-brushbsp: we'll want to do this for detail as well, which means building another set of
portals for everything (not just structural).
===========
*/
bool FillOutside(node_t *node, const int hullnum)
@ -508,35 +461,11 @@ bool FillOutside(node_t *node, const int hullnum)
return false;
}
// At this point, leafs not reachable from entities have (node->occupied == 0).
// The two final tasks are:
// 1. Mark the leafs that are not reachable as CONTENTS_SOLID (i.e. filling them in as the void).
// 2. Delete faces in those leafs
// An annoying wrinkle here: there may be leafs with (node->occupied == 0), which means they should be filled in as
// void, but they have faces straddling between them and occupied leafs (i.e. leafs which will be CONTENTS_EMPTY
// because they're in playable space). See missing_face_simple.map for an example.
//
// The subtlety is, if we fill these leafs in as solid and delete the inward-facing faces, the only face left
// will be the void-and-non-void-straddling face. This face will mess up LinkConvexFaces, since we need to rebuild
// the BSP and recalculate the leaf contents, unaware of the fact that we wanted this leaf to be void
// (CONTENTS_SOLID), and this face will cause it to be marked as CONTENTS_EMPTY which will manifest as messed up
// hull0 collision in game (weapons shoot through the leaf.)
//
// In order to avoid this scenario, we need to detect those "void-and-non-void-straddling" faces and not fill those
// leafs in as solid. This will keep some extra faces around but keep the content types consistent.
// fixme-brushbsp: the above is no longer relevant
ResetFacesTouchingOccupiedLeafs(node);
MarkFacesTouchingOccupiedLeafs(node);
/* now go back and fill outside with solid contents */
const int outleafs = OutLeafsToSolid(node);
/* remove faces from filled in leafs */
// fixme-brushbsp: don't do this; mark brush sides instead
ClearOutFaces(node);
// See missing_face_simple.map for a test case with a brush that straddles between void and non-void
MarkVisibleBrushSides(node);
logging::print(logging::flag::STAT, " {:8} outleafs\n", outleafs);
return true;

View File

@ -681,6 +681,10 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
// assume non-world bmodels are simple
PortalizeWorld(entity, nodes, hullnum);
// flood fills from the void.
// marks brush sides which are *only* touching void;
// we can skip using them as BSP splitters on the "really good tree"
// (effectively expanding those brush sides outwards).
if (!options.nofill.value() && FillOutside(nodes, hullnum)) {
// fixme-brushbsp: re-add
//FreeNodes(nodes);