Skip to content

Commit 7a767e9

Browse files
committed
qbsp: fix func_detail_fence frustum culling issue,
reported by hemebond. Fixes q1_detail_fence2.map.
1 parent 43037bf commit 7a767e9

3 files changed

Lines changed: 77 additions & 0 deletions

File tree

qbsp/faces.cc

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -407,6 +407,24 @@ static node_t *FixupMarkFaces_ProcessCluster_FindStorageLeaf(node_t *node)
407407
return node;
408408
}
409409

410+
static const aabb3d BoundFaces(const std::set<face_t *> &marfaces_to_add)
411+
{
412+
aabb3d result;
413+
for (const face_t *face : marfaces_to_add) {
414+
for (const qvec3d &pt : face->w) {
415+
result += pt;
416+
}
417+
}
418+
return result;
419+
}
420+
421+
static void AddBoundsToNode(node_t *start, const aabb3d &bounds_to_add)
422+
{
423+
for (node_t *node = start; node; node = node->parent) {
424+
node->bounds += bounds_to_add;
425+
}
426+
}
427+
410428
static void FixupMarkFaces_AddFacesToLeaf(node_t *node, const std::set<face_t *> &marfaces_to_add)
411429
{
412430
Q_assert(FixupMarkFaces_IsUsableLeaf(node));
@@ -421,6 +439,16 @@ static void FixupMarkFaces_AddFacesToLeaf(node_t *node, const std::set<face_t *>
421439

422440
auto new_markfaces = std::vector<face_t *>(current_markfaces.begin(), current_markfaces.end());
423441
leafdata->markfaces = new_markfaces;
442+
443+
// expand the bounds of `node` (plus all ancestors) to encompass `markfaces_to_add`, to prevent frustum culling
444+
// of the storage leaf when the markfaces_to_add are in view (fixes q1_detail_fence2.map test case).
445+
//
446+
// This does mean we "lie" about the node bounds. Alternative to this would be adding the marfaces to _every_
447+
// eligible leaf in the storage clusters, which would potentially waste a ton of marksurfaces
448+
aabb3d added_bounds = BoundFaces(marfaces_to_add);
449+
//logging::print("old storage node bounds: {}, want to add faces bounds: {}\n", node->bounds, added_bounds);
450+
451+
AddBoundsToNode(node, added_bounds);
424452
}
425453

426454
/**

testmaps/q1_detail_fence.map

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -285,3 +285,13 @@
285285
"origin" "120 -72 104"
286286
"wait" "0.3"
287287
}
288+
// entity 6
289+
{
290+
"classname" "ambient_thunder"
291+
"origin" "-128 -320 64"
292+
}
293+
// entity 7
294+
{
295+
"classname" "ambient_suck_wind"
296+
"origin" "224 16 192"
297+
}

tests/test_qbsp.cc

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1499,6 +1499,10 @@ TEST(testmapsQ1, detailFence)
14991499
EXPECT_EQ(player_start_leaf->contents, CONTENTS_EMPTY);
15001500
auto markfaces = Leaf_Markfaces(&bsp, player_start_leaf);
15011501
EXPECT_THAT(markfaces, testing::Contains(back_of_pillar_face));
1502+
// check that its bounds were extended to encompass the faces inside the func_detail_fence -
1503+
// they're using the player start leaf as "storage".
1504+
EXPECT_EQ(qvec3f(-128, -320, 64), player_start_leaf->mins);
1505+
EXPECT_EQ(qvec3f(224, 16, 192), player_start_leaf->maxs);
15021506

15031507
// check the cubby off to the side - it _shouldn't_ have got the back_of_pillar_face added to its marksurfaces
15041508
// (make sure the flood fill in FixupDetailFence() isn't propagating them excessively)
@@ -1509,6 +1513,41 @@ TEST(testmapsQ1, detailFence)
15091513
EXPECT_THAT(cubby_leaf_markfaces, testing::Not(testing::Contains(back_of_pillar_face)));
15101514
}
15111515

1516+
TEST(testmapsQ1, detailFence2)
1517+
{
1518+
const auto [bsp, bspx, prt] = LoadTestmapQ1("q1_detail_fence2.map");
1519+
1520+
// similar to above.. this one repro's an issue where the "faces inside the func_detail_fence volume" get
1521+
// added to a leaf that can get frustum culled, so we expand the bounds.
1522+
1523+
const aabb3f func_detail_fence_box = aabb3f({-80, -192, -48}, {-16, -48, 64});
1524+
1525+
// grab a random face inside the detail_fence
1526+
const auto in_fence_pos = qvec3d(-80, -104, 8);
1527+
auto *in_fence_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], in_fence_pos);
1528+
1529+
// find the "storage leaf", that is an empty leaf that has in_fence_face in its marksurfaces
1530+
const mleaf_t *storage_leaf = nullptr;
1531+
for (const mleaf_t &l : bsp.dleafs) {
1532+
auto markfaces = Leaf_Markfaces(&bsp, &l);
1533+
if (l.contents != CONTENTS_EMPTY) {
1534+
continue;
1535+
}
1536+
if (std::ranges::find(markfaces, in_fence_face) != markfaces.end()) {
1537+
storage_leaf = &l;
1538+
break;
1539+
}
1540+
}
1541+
1542+
ASSERT_NE(nullptr, storage_leaf);
1543+
1544+
const aabb3f storage_leaf_bounds = aabb3f(storage_leaf->mins, storage_leaf->maxs);
1545+
1546+
SCOPED_TRACE(testing::Message() << "storage_leaf_bounds: " << storage_leaf_bounds
1547+
<< " func_detail_fence_box: " << func_detail_fence_box);
1548+
ASSERT_TRUE(storage_leaf_bounds.contains(func_detail_fence_box));
1549+
}
1550+
15121551
TEST(testmapsQ1, detailFenceWithoutExtendedContents)
15131552
{
15141553
const auto [bsp, bspx, prt] = LoadTestmapQ1("q1_detail_fence.map", {"-noextendedcontentflags"});

0 commit comments

Comments
 (0)