qbsp: q2: adding structrual solid to a leaf now only clears detail bit, not other bits

fixes #420
This commit is contained in:
Eric Wasylishen 2024-04-24 23:46:40 -06:00
parent 59ee3fd0c9
commit e61630d4d9
4 changed files with 79 additions and 56 deletions

View File

@ -1211,15 +1211,10 @@ struct gamedef_q2_t : public gamedef_t
contentflags_t contents_remap_for_export(const contentflags_t &contents, remap_type_t type) const override contentflags_t contents_remap_for_export(const contentflags_t &contents, remap_type_t type) const override
{ {
if (contents.flags & EWT_VISCONTENTS_DETAIL_WALL) { if (contents.flags & EWT_VISCONTENTS_DETAIL_WALL) {
return create_solid_contents(); contents_int_t result = contents.flags;
} result &= (~EWT_VISCONTENTS_DETAIL_WALL);
// Solid wipes out any other contents result |= EWT_VISCONTENTS_SOLID;
// Previously, this was done in LeafNode but we've changed to detail-solid being return contentflags_t::make(result);
// non-sealing.
if (type == remap_type_t::leaf) {
if (contents.flags & EWT_VISCONTENTS_SOLID) {
return create_solid_contents();
}
} }
return contents; return contents;
@ -1227,13 +1222,14 @@ struct gamedef_q2_t : public gamedef_t
contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const override contentflags_t combine_contents(const contentflags_t &a, const contentflags_t &b) const override
{ {
// structural solid (but not detail solid) eats any other contents contents_int_t bits_a = a.flags;
if (contents_are_solid(a) || contents_are_solid(b)) { contents_int_t bits_b = b.flags;
return create_solid_contents();
}
auto bits_a = a.flags; // structural solid eats detail flags
auto bits_b = b.flags; if (contents_are_solid(a) || contents_are_solid(b)) {
bits_a &= ~EWT_CFLAG_DETAIL;
bits_b &= ~EWT_CFLAG_DETAIL;
}
return contentflags_t::make(bits_a | bits_b); return contentflags_t::make(bits_a | bits_b);
} }

View File

@ -135,8 +135,11 @@ static void PruneNodes_R(node_t *node, prune_stats_t &stats)
// fixme-brushbsp: is it correct to strip off detail flags here? // fixme-brushbsp: is it correct to strip off detail flags here?
if (IsAnySolidLeaf(nodedata->children[0]) && IsAnySolidLeaf(nodedata->children[1])) { if (IsAnySolidLeaf(nodedata->children[0]) && IsAnySolidLeaf(nodedata->children[1])) {
contentflags_t merged_contents = qbsp_options.target_game->combine_contents(nodedata->children[0]->get_leafdata()->contents,
nodedata->children[1]->get_leafdata()->contents);
// This discards any faces on-node. Should be safe (?) // This discards any faces on-node. Should be safe (?)
ConvertNodeToLeaf(node, qbsp_options.target_game->create_solid_contents()); ConvertNodeToLeaf(node, merged_contents);
stats.nodes_pruned++; stats.nodes_pruned++;
} }

View File

@ -222,37 +222,44 @@ TEST_SUITE("common")
{ {
auto *game_q2 = bspver_q2.game; auto *game_q2 = bspver_q2.game;
const std::array test_contents{ struct before_after_t {
game_q2->create_contents_from_native(Q2_CONTENTS_EMPTY), int32_t before;
game_q2->create_contents_from_native(Q2_CONTENTS_SOLID), int32_t after;
game_q2->create_contents_from_native(Q2_CONTENTS_WINDOW),
game_q2->create_contents_from_native(Q2_CONTENTS_AUX),
game_q2->create_contents_from_native(Q2_CONTENTS_LAVA),
game_q2->create_contents_from_native(Q2_CONTENTS_SLIME),
game_q2->create_contents_from_native(Q2_CONTENTS_WATER),
game_q2->create_contents_from_native(Q2_CONTENTS_MIST),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER),
game_q2->create_contents_from_native(Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST)
}; };
SUBCASE("solid combined with others") SUBCASE("solid combined with others")
{ {
const std::vector<before_after_t> before_after_adding_solid {
{Q2_CONTENTS_EMPTY, Q2_CONTENTS_SOLID},
{Q2_CONTENTS_SOLID, Q2_CONTENTS_SOLID},
{Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER, Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER},
{Q2_CONTENTS_WINDOW, Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW},
{Q2_CONTENTS_AUX, Q2_CONTENTS_SOLID | Q2_CONTENTS_AUX},
{Q2_CONTENTS_LAVA, Q2_CONTENTS_SOLID | Q2_CONTENTS_LAVA},
{Q2_CONTENTS_SLIME, Q2_CONTENTS_SOLID | Q2_CONTENTS_SLIME},
{Q2_CONTENTS_WATER, Q2_CONTENTS_SOLID | Q2_CONTENTS_WATER},
{Q2_CONTENTS_MIST, Q2_CONTENTS_SOLID | Q2_CONTENTS_MIST},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID, Q2_CONTENTS_SOLID}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW, Q2_CONTENTS_SOLID | Q2_CONTENTS_WINDOW}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX, Q2_CONTENTS_SOLID |Q2_CONTENTS_AUX}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA, Q2_CONTENTS_SOLID |Q2_CONTENTS_LAVA}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME, Q2_CONTENTS_SOLID |Q2_CONTENTS_SLIME}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER, Q2_CONTENTS_SOLID | Q2_CONTENTS_WATER}, // detail flag gets erased
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST, Q2_CONTENTS_SOLID | Q2_CONTENTS_MIST} // detail flag gets erased
};
auto solid = game_q2->create_solid_contents(); auto solid = game_q2->create_solid_contents();
CHECK(game_q2->contents_to_native(solid) == Q2_CONTENTS_SOLID); CHECK(game_q2->contents_to_native(solid) == Q2_CONTENTS_SOLID);
for (const auto &c : test_contents) { for (const auto &[before, after] : before_after_adding_solid) {
// solid is treated specially in Q2 and wipes out any other content
// flags when combined
auto combined = game_q2->contents_remap_for_export(
game_q2->combine_contents(solid, c), gamedef_t::remap_type_t::leaf);
CHECK(game_q2->contents_to_native(combined) == Q2_CONTENTS_SOLID); auto combined = game_q2->contents_remap_for_export(
game_q2->combine_contents(game_q2->create_contents_from_native(before),
solid), gamedef_t::remap_type_t::leaf);
CHECK(game_q2->contents_to_native(combined) == after);
CHECK(combined.is_solid(game_q2)); CHECK(combined.is_solid(game_q2));
CHECK(!combined.is_any_detail(game_q2));
} }
} }
@ -260,14 +267,30 @@ TEST_SUITE("common")
{ {
contentflags_t water = game_q2->create_contents_from_native(Q2_CONTENTS_WATER); contentflags_t water = game_q2->create_contents_from_native(Q2_CONTENTS_WATER);
for (const auto &c : test_contents) { const std::vector<before_after_t> before_after_adding_water {
auto combined = game_q2->combine_contents(water, c); {Q2_CONTENTS_EMPTY, Q2_CONTENTS_WATER},
{Q2_CONTENTS_SOLID, Q2_CONTENTS_WATER | Q2_CONTENTS_SOLID},
{Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER, Q2_CONTENTS_WATER | Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER},
{Q2_CONTENTS_WINDOW, Q2_CONTENTS_WATER | Q2_CONTENTS_WINDOW},
{Q2_CONTENTS_AUX, Q2_CONTENTS_WATER | Q2_CONTENTS_AUX},
{Q2_CONTENTS_LAVA, Q2_CONTENTS_WATER | Q2_CONTENTS_LAVA},
{Q2_CONTENTS_SLIME, Q2_CONTENTS_WATER | Q2_CONTENTS_SLIME},
{Q2_CONTENTS_WATER, Q2_CONTENTS_WATER},
{Q2_CONTENTS_MIST, Q2_CONTENTS_WATER | Q2_CONTENTS_MIST},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_SOLID},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_WINDOW},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_AUX},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_LAVA},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_SLIME},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_WATER, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL},
{Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST, Q2_CONTENTS_WATER | Q2_CONTENTS_DETAIL | Q2_CONTENTS_MIST}
};
for (const auto &[before, after] : before_after_adding_water) {
auto combined = game_q2->combine_contents(game_q2->create_contents_from_native(before), water);
SUBCASE(fmt::format("water combined with {}", c.to_string(game_q2)).c_str()) SUBCASE(fmt::format("water combined with {}", game_q2->create_contents_from_native(before).to_string(game_q2)).c_str())
{ {
if (!(game_q2->contents_to_native(c) & Q2_CONTENTS_SOLID)) { CHECK(game_q2->contents_to_native(combined) == after);
CHECK(game_q2->contents_to_native(combined) == (Q2_CONTENTS_WATER | game_q2->contents_to_native(c)));
}
} }
} }
} }

View File

@ -48,12 +48,11 @@ TEST_CASE("detail" * doctest::test_suite("testmaps_q2"))
for (size_t i = 1; i < bsp.dleafs.size(); ++i) { for (size_t i = 1; i < bsp.dleafs.size(); ++i) {
++counts_by_contents[bsp.dleafs[i].contents]; ++counts_by_contents[bsp.dleafs[i].contents];
} }
CHECK(2 == counts_by_contents.size()); // number of types CHECK(3 == counts_by_contents.size()); // number of types
CHECK(counts_by_contents.find(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == CHECK(1 == counts_by_contents.at(Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL)); // detail leafs
counts_by_contents.end()); // the detail bit gets cleared
CHECK(8 == counts_by_contents.at(0)); // empty leafs CHECK(8 == counts_by_contents.at(0)); // empty leafs
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) >= 8); CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) >= 6);
CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) <= 12); CHECK(counts_by_contents.at(Q2_CONTENTS_SOLID) <= 12);
// clusters: // clusters:
@ -84,7 +83,7 @@ TEST_CASE("detail" * doctest::test_suite("testmaps_q2"))
// check for correct contents // check for correct contents
auto *detail_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_button); auto *detail_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], inside_button);
CHECK(Q2_CONTENTS_SOLID == detail_leaf->contents); CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == detail_leaf->contents);
CHECK(-1 == detail_leaf->cluster); CHECK(-1 == detail_leaf->cluster);
CHECK(0 == detail_leaf->area); // solid leafs get the invalid area 0 CHECK(0 == detail_leaf->area); // solid leafs get the invalid area 0
@ -541,7 +540,7 @@ TEST_CASE("q2_detail_overlapping_solid_sealing" * doctest::test_suite("testmaps_
// check leaf contents // check leaf contents
CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents); CHECK(Q2_CONTENTS_EMPTY == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_start_room)->contents);
CHECK(Q2_CONTENTS_SOLID == BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_void)->contents); CHECK((Q2_CONTENTS_SOLID & BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], in_void)->contents) == Q2_CONTENTS_SOLID);
} }
/** /**
@ -719,8 +718,10 @@ TEST_CASE("q2_ladder" * doctest::test_suite("testmaps_q2"))
auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], point_in_ladder); auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], point_in_ladder);
// the brush lacked a visible contents, so it became solid. solid leafs wipe out any other content bits // the brush lacked a visible contents, so it became solid.
CHECK(leaf->contents == (Q2_CONTENTS_SOLID)); // ladder and detail flags are preseved now.
// (previously we were wiping them out and just writing out leafs as Q2_CONTENTS_SOLID).
CHECK(leaf->contents == (Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER | Q2_CONTENTS_DETAIL));
CHECK(1 == Leaf_Brushes(&bsp, leaf).size()); CHECK(1 == Leaf_Brushes(&bsp, leaf).size());
CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER | Q2_CONTENTS_DETAIL) == Leaf_Brushes(&bsp, leaf).at(0)->contents); CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_LADDER | Q2_CONTENTS_DETAIL) == Leaf_Brushes(&bsp, leaf).at(0)->contents);
@ -774,13 +775,13 @@ TEST_CASE("q2_detail_wall" * doctest::test_suite("testmaps_q2"))
INFO("check leaf / brush contents"); INFO("check leaf / brush contents");
CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game)); CAPTURE(game->create_contents_from_native(detail_wall_leaf->contents).to_string(game));
CHECK(Q2_CONTENTS_SOLID == detail_wall_leaf->contents); CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == detail_wall_leaf->contents);
REQUIRE(1 == Leaf_Brushes(&bsp, detail_wall_leaf).size()); REQUIRE(1 == Leaf_Brushes(&bsp, detail_wall_leaf).size());
auto *brush = Leaf_Brushes(&bsp, detail_wall_leaf).at(0); auto *brush = Leaf_Brushes(&bsp, detail_wall_leaf).at(0);
CAPTURE(game->create_contents_from_native(brush->contents).to_string(game)); CAPTURE(game->create_contents_from_native(brush->contents).to_string(game));
CHECK(Q2_CONTENTS_SOLID == brush->contents); CHECK((Q2_CONTENTS_SOLID | Q2_CONTENTS_DETAIL) == brush->contents);
} }
{ {
@ -1059,7 +1060,7 @@ TEST_CASE("q2_unknown_contents" * doctest::test_suite("testmaps_q2"))
auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0}); auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], {0, 0, 0});
// FIXME: should the unknown contents get converted to SOLID in the leaf? // FIXME: should the unknown contents get converted to SOLID in the leaf?
CHECK(leaf->contents == (Q2_CONTENTS_SOLID)); CHECK(leaf->contents == (Q2_CONTENTS_SOLID | 1024));
CHECK(1 == Leaf_Brushes(&bsp, leaf).size()); CHECK(1 == Leaf_Brushes(&bsp, leaf).size());
// FIXME: should the unknown contents have SOLID added in the brush? // FIXME: should the unknown contents have SOLID added in the brush?
@ -1073,7 +1074,7 @@ TEST_CASE("q2_unknown_contents" * doctest::test_suite("testmaps_q2"))
auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], {64, 0, 0}); auto *leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], {64, 0, 0});
// FIXME: should the unknown contents get converted to SOLID in the leaf? // FIXME: should the unknown contents get converted to SOLID in the leaf?
CHECK(leaf->contents == (Q2_CONTENTS_SOLID)); CHECK(leaf->contents == (Q2_CONTENTS_SOLID | nth_bit(30)));
CHECK(1 == Leaf_Brushes(&bsp, leaf).size()); CHECK(1 == Leaf_Brushes(&bsp, leaf).size());
// FIXME: should the unknown contents have SOLID added in the brush? // FIXME: should the unknown contents have SOLID added in the brush?