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:
parent
4e3739c751
commit
a2451a41fb
File diff suppressed because it is too large
Load Diff
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
25
qbsp/qbsp.cc
25
qbsp/qbsp.cc
|
|
@ -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());
|
||||
|
|
|
|||
40
qbsp/tree.cc
40
qbsp/tree.cc
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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"
|
||||
}
|
||||
|
|
@ -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]")
|
||||
|
|
|
|||
|
|
@ -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());
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue