qbsp: rewrite of q1 contents representation in bspfile.cc

use q2-like bitflags so we can represent mixes like (water | mist), which we need within the compiler
This commit is contained in:
Eric Wasylishen 2022-07-27 02:31:49 -06:00
parent 4e3739c751
commit a2451a41fb
13 changed files with 754 additions and 436 deletions

File diff suppressed because it is too large Load Diff

View File

@ -268,8 +268,6 @@ struct gamedef_t
// FIXME: fix so that we don't have to pass a name here
virtual bool texinfo_is_hintskip(const surfflags_t &flags, const std::string &name) const = 0;
virtual contentflags_t cluster_contents(const contentflags_t &contents0, const contentflags_t &contents1) const = 0;
virtual int32_t contents_priority(const contentflags_t &contents) const = 0;
virtual bool chops(const contentflags_t &) const = 0;
virtual contentflags_t create_empty_contents() const = 0;
virtual contentflags_t create_solid_contents() const = 0;
virtual contentflags_t create_detail_illusionary_contents(const contentflags_t &original) const = 0;
@ -296,11 +294,13 @@ struct gamedef_t
virtual bool contents_seals_map(const contentflags_t &contents) const = 0;
virtual contentflags_t contents_remap_for_export(const contentflags_t &contents) const = 0;
virtual contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const = 0;
virtual contentflags_t visible_contents(const contentflags_t &a, const contentflags_t &b) const = 0;
// counterpart to visible_contents. for a portal with contents from `a` to `b`, returns whether a viewer in `a`
// should see a face
virtual bool directional_visible_contents(const contentflags_t &a, const contentflags_t &b) const = 0;
virtual bool contents_contains(const contentflags_t &a, const contentflags_t &b) const = 0;
// for a portal with contents from `a` to `b`, returns what type of face should be rendered facing `a` and `b`
virtual contentflags_t portal_visible_contents(const contentflags_t &a, const contentflags_t &b) const = 0;
// for a brush with the given contents touching a portal with the required `portal_visible_contents`, as determined by
// portal_visible_contents, should the `brushside_side` of the brushside generate a face?
// e.g. liquids generate front and back sides by default, but for q1 detail_wall/detail_illusionary the back side is opt-in
// with _mirrorinside
virtual bool portal_generates_face(const contentflags_t &portal_visible_contents, const contentflags_t &brushcontents, planeside_t brushside_side) const = 0;
virtual std::string get_contents_display(const contentflags_t &contents) const = 0;
virtual void contents_make_valid(contentflags_t &contents) const = 0;
virtual const std::initializer_list<aabb3d> &get_hull_sizes() const = 0;

View File

@ -40,7 +40,7 @@ struct portal_t
std::optional<winding_t> winding;
bool sidefound; // false if ->side hasn't been checked
side_t *side; // NULL = non-visible
side_t *sides[2]; // [0] = the brush side visible on nodes[0] - it could come from a brush in nodes[1]. NULL = non-visible
face_t *face[2]; // output face in bsp file
};

View File

@ -931,9 +931,9 @@ static std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, std::vector<std::un
return tree;
}
logging::print(logging::flag::STAT, "{:5} brushes\n", c_brushes);
logging::print(logging::flag::STAT, "{:5} visible faces\n", c_faces);
logging::print(logging::flag::STAT, "{:5} nonvisible faces\n", c_nonvisfaces);
logging::print(logging::flag::STAT, " {:8} brushes\n", c_brushes);
logging::print(logging::flag::STAT, " {:8} visible faces\n", c_faces);
logging::print(logging::flag::STAT, " {:8} nonvisible faces\n", c_nonvisfaces);
auto node = std::make_unique<node_t>();
@ -945,9 +945,10 @@ static std::unique_ptr<tree_t> BrushBSP(mapentity_t *entity, std::vector<std::un
stats.leafstats = qbsp_options.target_game->create_content_stats();
BuildTree_r(tree->headnode.get(), std::move(brushlist), stats);
logging::print(logging::flag::STAT, "{:5} visible nodes\n", stats.c_nodes - stats.c_nonvis);
logging::print(logging::flag::STAT, "{:5} nonvis nodes\n", stats.c_nonvis);
logging::print(logging::flag::STAT, "{:5} leafs\n", stats.c_leafs);
logging::print(logging::flag::STAT, " {:8} visible nodes\n", stats.c_nodes - stats.c_nonvis);
logging::print(logging::flag::STAT, " {:8} nonvis nodes\n", stats.c_nonvis);
logging::print(logging::flag::STAT, " {:8} leafs\n", stats.c_leafs);
qbsp_options.target_game->print_content_stats(*stats.leafstats, "leafs");
return tree;
}

View File

@ -477,7 +477,7 @@ see also FindPortalSide which populates p->side
*/
static std::unique_ptr<face_t> FaceFromPortal(portal_t *p, int pside)
{
side_t *side = p->side;
side_t *side = p->sides[pside];
if (!side)
return nullptr; // portal does not bridge different visible contents
@ -489,6 +489,7 @@ static std::unique_ptr<face_t> FaceFromPortal(portal_t *p, int pside)
f->portal = p;
f->lmshift = side->lmshift;
#if 0
bool make_face =
qbsp_options.target_game->directional_visible_contents(p->nodes[pside]->contents, p->nodes[!pside]->contents);
if (!make_face) {
@ -505,7 +506,7 @@ static std::unique_ptr<face_t> FaceFromPortal(portal_t *p, int pside)
}
}
}
#endif
if (pside)
{
@ -590,7 +591,7 @@ void MakeFaces(node_t *node)
MakeFaces_r(node, stats);
logging::print(logging::flag::STAT, "{} makefaces\n", stats.c_nodefaces);
logging::print(logging::flag::STAT, "{} merged\n", stats.c_merge);
logging::print(logging::flag::STAT, "{} subdivided\n", stats.c_subdivide);
logging::print(logging::flag::STAT, " {:8} makefaces\n", stats.c_nodefaces);
logging::print(logging::flag::STAT, " {:8} merged\n", stats.c_merge);
logging::print(logging::flag::STAT, " {:8} subdivided\n", stats.c_subdivide);
}

View File

@ -720,16 +720,20 @@ static void FindPortalSide(portal_t *p)
{
// decide which content change is strongest
// solid > lava > water, etc
// if either is "_noclipfaces" then we don't require a content change
contentflags_t viscontents =
qbsp_options.target_game->visible_contents(p->nodes[0]->contents, p->nodes[1]->contents);
qbsp_options.target_game->portal_visible_contents(p->nodes[0]->contents, p->nodes[1]->contents);
if (viscontents.is_empty(qbsp_options.target_game))
return;
int planenum = p->onnode->planenum;
side_t *bestside = nullptr;
// bestside[0] is the brushside visible on portal side[0] which is the positive side of the plane, always
side_t *bestside[2] = {nullptr, nullptr};
float bestdot = 0;
qbsp_plane_t p1 = map.get_plane(p->onnode->planenum);
// check brushes on both sides of the portal
for (int j = 0; j < 2; j++)
{
node_t *n = p->nodes[j];
@ -739,38 +743,61 @@ static void FindPortalSide(portal_t *p)
for (auto it = n->original_brushes.rbegin(); it != n->original_brushes.rend(); ++it)
{
auto *brush = *it;
if (!qbsp_options.target_game->contents_contains(brush->contents, viscontents))
const bool generate_outside_face = qbsp_options.target_game->portal_generates_face(viscontents, brush->contents, SIDE_FRONT);
const bool generate_inside_face = qbsp_options.target_game->portal_generates_face(viscontents, brush->contents, SIDE_BACK);
if (!(generate_outside_face || generate_inside_face)) {
continue;
}
for (auto &side : brush->sides)
{
// fixme-brushbsp: port these
// if (side.bevel)
// continue;
// if (side.texinfo == TEXINFO_NODE)
if (side.bevel)
continue;
// fixme-brushbsp: restore
// if (!side.visible)
// continue; // non-visible
if (side.planenum == planenum)
{ // exact match
bestside = &side;
goto gotit;
if (side.planenum == planenum) {
// exact match (undirectional)
// because the brush is on j of the positive plane, the brushside must be facing away from j
Q_assert(side.planeside == !j);
// see which way(s) we want to generate faces - we could be a brush on either side of
// the portal, generating either a outward face (common case) or an inward face (liquids) or both.
if (generate_outside_face) {
if (!bestside[!j]) {
bestside[!j] = &side;
}
}
if (generate_inside_face) {
if (!bestside[j]) {
bestside[j] = &side;
}
}
break;
}
// see how close the match is
auto p2 = map.planes.at(side.planenum);
float dot = qv::dot(p1.normal, p2.normal);
if (dot > bestdot)
{
bestdot = dot;
bestside = &side;
}
// fixme-brushbsp: verify that this actually works, restore it
// auto p2 = map.planes.at(side.planenum);
// double dot = qv::dot(p1.normal, p2.normal);
// if (dot > bestdot)
// {
// bestdot = dot;
// bestside[j] = &side;
// }
}
}
}
gotit:
if (!bestside)
if (!bestside[0] && !bestside[1])
logging::print("WARNING: side not found for portal\n");
p->sidefound = true;
p->side = bestside;
for (int i = 0; i < 2; ++i) {
p->sides[i] = bestside[i];
}
}
/*
@ -801,8 +828,11 @@ static void MarkVisibleSides_r(node_t *node)
continue; // edge of world
if (!p->sidefound)
FindPortalSide(p);
if (p->side)
p->side->visible = true;
for (int i = 0; i < 2; ++i) {
if (p->sides[i]) {
p->sides[i]->visible = true;
}
}
}
}

View File

@ -495,6 +495,25 @@ static bool IsTrigger(const mapentity_t *entity)
return trigger_pos == (tex.size() - strlen("trigger"));
}
static void CountLeafs_r(node_t *node, content_stats_base_t& stats)
{
if (node->planenum == PLANENUM_LEAF) {
qbsp_options.target_game->count_contents_in_stats(node->contents, stats);
return;
}
CountLeafs_r(node->children[0].get(), stats);
CountLeafs_r(node->children[1].get(), stats);
}
static void CountLeafs(node_t *headnode)
{
logging::print(logging::flag::PROGRESS, "---- {} ----\n", __func__);
auto stats = qbsp_options.target_game->create_content_stats();
CountLeafs_r(headnode, *stats);
qbsp_options.target_game->print_content_stats(*stats, "leafs");
}
/*
===============
ProcessEntity
@ -596,7 +615,6 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
MakeTreePortals(tree.get());
FillOutside(entity, tree.get(), hullnum);
PruneNodes(tree->headnode.get());
DetailToSolid(tree->headnode.get());
}
}
ExportClipNodes(entity, tree->headnode.get(), hullnum);
@ -652,10 +670,7 @@ static void ProcessEntity(mapentity_t *entity, const int hullnum)
// needs to come after any face creation
MakeMarkFaces(tree->headnode.get());
// convert detail leafs to solid (in case we didn't make the call above)
DetailToSolid(tree->headnode.get());
// fixme-brushbsp: prune nodes
CountLeafs(tree->headnode.get());
// output vertices first, since TJunc needs it
EmitVertices(tree->headnode.get());

View File

@ -79,39 +79,6 @@ static void ConvertNodeToLeaf(node_t *node, const contentflags_t &contents)
Q_assert(node->markfaces.empty());
}
void DetailToSolid(node_t *node)
{
if (node->planenum == PLANENUM_LEAF) {
if (qbsp_options.target_game->id == GAME_QUAKE_II) {
return;
}
// We need to remap CONTENTS_DETAIL to a standard quake content type
if (node->contents.is_detail_solid(qbsp_options.target_game)) {
node->contents = qbsp_options.target_game->create_solid_contents();
} else if (node->contents.is_detail_illusionary(qbsp_options.target_game)) {
node->contents = qbsp_options.target_game->create_empty_contents();
}
/* N.B.: CONTENTS_DETAIL_FENCE is not remapped to CONTENTS_SOLID until the very last moment,
* because we want to generate a leaf (if we set it to CONTENTS_SOLID now it would use leaf 0).
*/
return;
} else {
DetailToSolid(node->children[0].get());
DetailToSolid(node->children[1].get());
// If both children are solid, we can merge the two leafs into one.
// DarkPlaces has an assertion that fails if both children are
// solid.
if (node->children[0]->contents.is_solid(qbsp_options.target_game) &&
node->children[1]->contents.is_solid(qbsp_options.target_game)) {
// This discards any faces on-node. Should be safe (?)
ConvertNodeToLeaf(node, qbsp_options.target_game->create_solid_contents());
}
// fixme-brushbsp: merge with PruneNodes
}
}
static void PruneNodes_R(node_t *node, int &count_pruned)
{
if (node->planenum == PLANENUM_LEAF) {
@ -128,6 +95,13 @@ static void PruneNodes_R(node_t *node, int &count_pruned)
++count_pruned;
}
// DarkPlaces has an assertion that fails if both children are
// solid.
/* N.B.: CONTENTS_DETAIL_FENCE is not remapped to CONTENTS_SOLID until the very last moment,
* because we want to generate a leaf (if we set it to CONTENTS_SOLID now it would use leaf 0).
*/
// fixme-brushbsp: corner case where two solid leafs shouldn't merge is two noclipfaces fence brushes touching
// fixme-brushbsp: also merge other content types
// fixme-brushbsp: maybe merge if same content type, and all faces on node are invisible?

View File

@ -0,0 +1,40 @@
// Game: Quake 2
// Format: Valve
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"_tb_textures" "textures/e1u1"
}
// entity 1
{
"classname" "func_detail_wall"
"_noclipfaces" "1"
// brush 0
{
( 16 16 48 ) ( 16 17 48 ) ( 16 16 49 ) e1u1/wndow1_2 [ 0 -1 0 56 ] [ 0 0 -1 0 ] 0 2 2
( 16 16 48 ) ( 16 16 49 ) ( 17 16 48 ) e1u1/wndow1_2 [ 1 0 0 -40 ] [ 0 0 -1 0 ] 0 2 2
( 16 16 0 ) ( 17 16 0 ) ( 16 17 0 ) e1u1/wndow1_2 [ -1 0 0 40 ] [ 0 -1 0 56 ] 0 2 2
( 96 96 64 ) ( 96 97 64 ) ( 97 96 64 ) e1u1/wndow1_2 [ 1 0 0 -40 ] [ 0 -1 0 56 ] 0 2 2
( 96 96 64 ) ( 97 96 64 ) ( 96 96 65 ) e1u1/wndow1_2 [ -1 0 0 40 ] [ 0 0 -1 0 ] 0 2 2
( 96 96 64 ) ( 96 96 65 ) ( 96 97 64 ) e1u1/wndow1_2 [ 0 1 0 -56 ] [ 0 0 -1 0 ] 0 2 2
}
}
// entity 2
{
"classname" "func_detail_wall"
// brush 0
{
( 96 16 48 ) ( 96 17 48 ) ( 96 16 49 ) e1u1/window1 [ 0 -1 0 56 ] [ 0 0 -1 0 ] 0 2 2
( 96 16 48 ) ( 96 16 49 ) ( 97 16 48 ) e1u1/window1 [ 1 0 0 -40 ] [ 0 0 -1 0 ] 0 2 2
( 96 16 0 ) ( 97 16 0 ) ( 96 17 0 ) e1u1/window1 [ -1 0 0 40 ] [ 0 -1 0 56 ] 0 2 2
( 176 96 64 ) ( 176 97 64 ) ( 177 96 64 ) e1u1/window1 [ 1 0 0 -40 ] [ 0 -1 0 56 ] 0 2 2
( 176 96 64 ) ( 177 96 64 ) ( 176 96 65 ) e1u1/window1 [ -1 0 0 40 ] [ 0 0 -1 0 ] 0 2 2
( 176 96 64 ) ( 176 96 65 ) ( 176 97 64 ) e1u1/window1 [ 0 1 0 -56 ] [ 0 0 -1 0 ] 0 2 2
}
}
// entity 3
{
"classname" "info_player_start"
"origin" "64 48 88"
}

View File

@ -0,0 +1,42 @@
// Game: Quake
// Format: Valve
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"wad" "deprecated/free_wad.wad;deprecated/fence.wad;deprecated/origin.wad;deprecated/hintskip.wad"
"_wateralpha" "0.5"
"_tb_def" "builtin:Quake.fgd"
}
// entity 1
{
"classname" "func_detail_wall"
"_noclipfaces" "1"
// brush 0
{
( 16 16 48 ) ( 16 17 48 ) ( 16 16 49 ) {trigger [ 0 -1 0 56 ] [ 0 0 -1 0 ] 0 2 2
( 16 16 48 ) ( 16 16 49 ) ( 17 16 48 ) {trigger [ 1 0 0 -40 ] [ 0 0 -1 0 ] 0 2 2
( 16 16 0 ) ( 17 16 0 ) ( 16 17 0 ) {trigger [ -1 0 0 40 ] [ 0 -1 0 56 ] 0 2 2
( 96 96 64 ) ( 96 97 64 ) ( 97 96 64 ) {trigger [ 1 0 0 -40 ] [ 0 -1 0 56 ] 0 2 2
( 96 96 64 ) ( 97 96 64 ) ( 96 96 65 ) {trigger [ -1 0 0 40 ] [ 0 0 -1 0 ] 0 2 2
( 96 96 64 ) ( 96 96 65 ) ( 96 97 64 ) {trigger [ 0 1 0 -56 ] [ 0 0 -1 0 ] 0 2 2
}
}
// entity 2
{
"classname" "func_detail_wall"
// brush 0
{
( 96 16 48 ) ( 96 17 48 ) ( 96 16 49 ) blood1 [ 0 -1 0 56 ] [ 0 0 -1 0 ] 0 2 2
( 96 16 48 ) ( 96 16 49 ) ( 97 16 48 ) blood1 [ 1 0 0 -40 ] [ 0 0 -1 0 ] 0 2 2
( 96 16 0 ) ( 97 16 0 ) ( 96 17 0 ) blood1 [ -1 0 0 40 ] [ 0 -1 0 56 ] 0 2 2
( 176 96 64 ) ( 176 97 64 ) ( 177 96 64 ) blood1 [ 1 0 0 -40 ] [ 0 -1 0 56 ] 0 2 2
( 176 96 64 ) ( 177 96 64 ) ( 176 96 65 ) blood1 [ -1 0 0 40 ] [ 0 0 -1 0 ] 0 2 2
( 176 96 64 ) ( 176 96 65 ) ( 176 97 64 ) blood1 [ 0 1 0 -56 ] [ 0 0 -1 0 ] 0 2 2
}
}
// entity 3
{
"classname" "info_player_start"
"origin" "64 48 88"
}

View File

@ -13,7 +13,7 @@ TEST_CASE("StripFilename", "[common]")
REQUIRE("" == fs::path("bar.txt").parent_path());
}
TEST_CASE("q1 contents", "[common][!mayfail]")
TEST_CASE("q1 contents", "[common]")
{
auto* game_q1 = bspver_q1.game;
@ -55,6 +55,16 @@ TEST_CASE("q1 contents", "[common][!mayfail]")
CHECK(combined.native == CONTENTS_WATER);
CHECK(combined.is_detail_illusionary(game_q1));
}
SECTION("detail properties") {
CHECK(detail_solid.is_any_detail(game_q1));
CHECK(detail_fence.is_any_detail(game_q1));
CHECK(detail_illusionary.is_any_detail(game_q1));
CHECK(detail_solid.is_any_solid(game_q1));
CHECK(!detail_fence.is_any_solid(game_q1));
CHECK(!detail_illusionary.is_any_solid(game_q1));
}
}
TEST_CASE("q2 contents", "[common]")

View File

@ -718,12 +718,13 @@ TEST_CASE("simple_worldspawn_sky", "[testmaps_q1]")
// FIXME: unsure what the expected number of visclusters is, does sky get one?
}
TEST_CASE("water_detail_illusionary", "[testmaps_q1][!mayfail]")
TEST_CASE("water_detail_illusionary", "[testmaps_q1]")
{
static const std::string basic_mapname = "qbsp_water_detail_illusionary.map";
static const std::string mirrorinside_mapname = "qbsp_water_detail_illusionary_mirrorinside.map";
auto mapname = GENERATE_REF(basic_mapname, mirrorinside_mapname);
for (const auto& mapname : {basic_mapname, mirrorinside_mapname}) {
DYNAMIC_SECTION("testing " << mapname) {
const auto [bsp, bspx, prt] = LoadTestmapQ1(mapname);
REQUIRE(prt.has_value());
@ -761,6 +762,8 @@ TEST_CASE("water_detail_illusionary", "[testmaps_q1][!mayfail]")
CHECK(above_face_inner == nullptr);
}
}
}
}
TEST_CASE("noclipfaces", "[testmaps_q1]")
{
@ -780,6 +783,46 @@ TEST_CASE("noclipfaces", "[testmaps_q1]")
CHECK(prt->portalleafs == 1);
}
/**
* _noclipfaces 1 detail_wall meeting a _noclipfaces 0 one.
*
* Currently, to simplify the implementation, we're treating that the same as if both had _noclipfaces 1
*/
TEST_CASE("noclipfaces_junction")
{
const std::vector<std::string> maps{
"qbsp_noclipfaces_junction.map",
"q2_noclipfaces_junction.map"
};
for (const auto& map : maps) {
const bool q2 = (map.find("q2") == 0);
DYNAMIC_SECTION(map) {
const auto [bsp, bspx, prt] =
q2 ? LoadTestmapQ2(map) : LoadTestmapQ1(map);
CHECK(bsp.dfaces.size() == 12);
const qvec3d portal_pos {96, 56, 32};
auto *pos_x = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], portal_pos, {1, 0, 0});
auto *neg_x = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], portal_pos, {-1, 0, 0});
REQUIRE(pos_x != nullptr);
REQUIRE(neg_x != nullptr);
if (q2) {
CHECK(std::string("e1u1/wndow1_2") == Face_TextureName(&bsp, pos_x));
CHECK(std::string("e1u1/window1") == Face_TextureName(&bsp, neg_x));
} else {
CHECK(std::string("{trigger") == Face_TextureName(&bsp, pos_x));
CHECK(std::string("blood1") == Face_TextureName(&bsp, neg_x));
}
}
}
}
/**
* Same as previous test, but the T shaped brush entity has _mirrorinside
*/
@ -850,11 +893,14 @@ TEST_CASE("detail_illusionary_noclipfaces_intersecting", "[testmaps_q1]")
CHECK(prt->portalleafs == 1);
}
TEST_CASE("detail_doesnt_seal", "[testmaps_q1]")
/**
* Since moving to a qbsp3 codebase, detail seals by default.
*/
TEST_CASE("detail_seals", "[testmaps_q1]")
{
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_detail_doesnt_seal.map");
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_detail_seals.map");
REQUIRE_FALSE(prt.has_value());
CHECK(prt.has_value());
}
TEST_CASE("detail_doesnt_remove_world_nodes", "[testmaps_q1]")
@ -867,22 +913,29 @@ TEST_CASE("detail_doesnt_remove_world_nodes", "[testmaps_q1]")
// check for a face under the start pos
const qvec3d floor_under_start{-56, -72, 64};
auto *floor_under_start_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_under_start, {0, 0, 1});
REQUIRE(nullptr != floor_under_start_face);
CHECK(nullptr != floor_under_start_face);
}
{
// floor face should be clipped away by detail
const qvec3d floor_inside_detail{64, -72, 64};
auto *floor_inside_detail_face = BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], floor_inside_detail, {0, 0, 1});
REQUIRE(nullptr == floor_inside_detail_face);
CHECK(nullptr == floor_inside_detail_face);
}
// make sure the detail face exists
CHECK(nullptr != BSP_FindFaceAtPoint(&bsp, &bsp.dmodels[0], {32, -72, 136}, {-1, 0, 0}));
#if 0
// fixme-brushbsp: with qbsp3 code, the strucutral node is actually clippped away.
// we could repurpose this test case to test func_detail_wall (q2 window) in which case it would not be clipped away.
{
// but the sturctural nodes/leafs should not be clipped away by detail
const qvec3d covered_by_detail{48, -88, 128};
auto *covered_by_detail_node = BSP_FindNodeAtPoint(&bsp, &bsp.dmodels[0], covered_by_detail, {-1, 0, 0});
REQUIRE(nullptr != covered_by_detail_node);
CHECK(nullptr != covered_by_detail_node);
}
#endif
}
TEST_CASE("merge", "[testmaps_q1]")
@ -905,9 +958,9 @@ TEST_CASE("merge", "[testmaps_q1]")
CHECK(top_winding.bounds().maxs() == exp_bounds.maxs());
}
TEST_CASE("tjunc_many_sided_face", "[testmaps_q1][!mayfail]")
TEST_CASE("tjunc_many_sided_face", "[testmaps_q1]")
{
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_tjunc_many_sided_face.map");
const auto [bsp, bspx, prt] = LoadTestmapQ1("qbsp_tjunc_many_sided_face.map", {"-tjunc", "rotate"});
REQUIRE(prt.has_value());
@ -1029,11 +1082,13 @@ TEST_CASE("q1_cube", "[testmaps_q1]")
// check the empty leafs
for (int i = 1; i < 7; ++i) {
DYNAMIC_SECTION("leaf " << i) {
auto &leaf = bsp.dleafs[i];
CHECK(CONTENTS_EMPTY == leaf.contents);
CHECK(1 == leaf.nummarksurfaces);
}
}
REQUIRE(6 == bsp.dfaces.size());