Skip to content

Commit bfea46c

Browse files
committed
tests: add a test that -wrbrushes is getting the hull expansion capping
1 parent 3bf69ff commit bfea46c

File tree

3 files changed

+144
-0
lines changed

3 files changed

+144
-0
lines changed

include/common/qvec.hh

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,6 +775,20 @@ public:
775775
{
776776
}
777777

778+
// TODO: this is repeated serveral times in the codebase
779+
static qplane3 from_points(const std::array<qvec<T, 3>, 3> &planepts)
780+
{
781+
/* calculate the normal/dist plane equation */
782+
qvec<T, 3> ab = planepts[0] - planepts[1];
783+
qvec<T, 3> cb = planepts[2] - planepts[1];
784+
785+
T length;
786+
qvec<T, 3> normal = qv::normalize(qv::cross(ab, cb), length);
787+
T dist = qv::dot(planepts[1], normal);
788+
789+
return qplane3(normal, dist);
790+
}
791+
778792
public:
779793
// Sort support
780794
[[nodiscard]] constexpr auto operator<=>(const qplane3 &other) const = default;
@@ -809,6 +823,13 @@ struct fmt::formatter<qplane3<T>> : formatter<qvec<T, 3>>
809823
}
810824
};
811825

826+
// gtest support
827+
template<typename T>
828+
std::ostream &operator<<(std::ostream &os, const qplane3<T> &p)
829+
{
830+
return os << fmt::format("{}", p);
831+
}
832+
812833
using qplane3f = qplane3<float>;
813834
using qplane3d = qplane3<double>;
814835

testmaps/q1_hull_expansion.map

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
// Game: Quake
2+
// Format: Valve
3+
// entity 0
4+
{
5+
"mapversion" "220"
6+
"classname" "worldspawn"
7+
"wad" "deprecated/free_wad.wad;deprecated/hintskip.wad"
8+
// brush 0
9+
{
10+
( -336 -64 -16 ) ( -336 -63 -16 ) ( -336 -64 -15 ) bally [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
11+
( -64 -128 -16 ) ( -64 -128 -15 ) ( -63 -128 -16 ) bally [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
12+
( -64 -64 -16 ) ( -63 -64 -16 ) ( -64 -63 -16 ) bally [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
13+
( 64 64 16 ) ( 64 65 16 ) ( 65 64 16 ) bally [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
14+
( 64 816 16 ) ( 65 816 16 ) ( 64 816 17 ) bally [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
15+
( 432 64 16 ) ( 432 64 17 ) ( 432 65 16 ) bally [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
16+
}
17+
// brush 1
18+
{
19+
( 416 816 368 ) ( 416 -128 368 ) ( 416 816 16 ) bally [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
20+
( 432 -128 16 ) ( 416 -128 16 ) ( 432 -128 368 ) bally [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
21+
( 432 816 16 ) ( 416 816 16 ) ( 432 -128 16 ) bally [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
22+
( 432 -128 368 ) ( 416 -128 368 ) ( 432 816 368 ) bally [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
23+
( 432 816 368 ) ( 416 816 368 ) ( 432 816 16 ) bally [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
24+
( 432 -128 368 ) ( 432 816 368 ) ( 432 -128 16 ) bally [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
25+
}
26+
// brush 2
27+
{
28+
( -336 -128 16 ) ( -336 816 16 ) ( -336 -128 368 ) bally [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
29+
( -336 -128 368 ) ( -320 -128 368 ) ( -336 -128 16 ) bally [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
30+
( -336 -128 16 ) ( -320 -128 16 ) ( -336 816 16 ) bally [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
31+
( -336 816 368 ) ( -320 816 368 ) ( -336 -128 368 ) bally [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
32+
( -336 816 16 ) ( -320 816 16 ) ( -336 816 368 ) bally [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
33+
( -320 -128 16 ) ( -320 -128 368 ) ( -320 816 16 ) bally [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
34+
}
35+
// brush 3
36+
{
37+
( -320 816 368 ) ( -320 800 368 ) ( -320 816 16 ) bally [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
38+
( 416 800 368 ) ( 416 800 16 ) ( -320 800 368 ) bally [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
39+
( -320 816 16 ) ( -320 800 16 ) ( 416 816 16 ) bally [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
40+
( 416 816 368 ) ( 416 800 368 ) ( -320 816 368 ) bally [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
41+
( 416 816 368 ) ( -320 816 368 ) ( 416 816 16 ) bally [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
42+
( 416 816 16 ) ( 416 800 16 ) ( 416 816 368 ) bally [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
43+
}
44+
// brush 4
45+
{
46+
( -320 -128 16 ) ( -320 -112 16 ) ( -320 -128 368 ) bally [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
47+
( 416 -128 16 ) ( -320 -128 16 ) ( 416 -128 368 ) bally [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
48+
( 416 -128 16 ) ( 416 -112 16 ) ( -320 -128 16 ) bally [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
49+
( -320 -128 368 ) ( -320 -112 368 ) ( 416 -128 368 ) bally [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
50+
( -320 -112 16 ) ( 416 -112 16 ) ( -320 -112 368 ) bally [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
51+
( 416 -128 368 ) ( 416 -112 368 ) ( 416 -128 16 ) bally [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
52+
}
53+
// brush 5
54+
{
55+
( -320 -112 368 ) ( -320 -112 352 ) ( -320 800 368 ) sky3 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
56+
( 416 -112 368 ) ( 416 -112 352 ) ( -320 -112 368 ) sky3 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
57+
( 416 800 352 ) ( -320 800 352 ) ( 416 -112 352 ) sky3 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
58+
( 416 800 368 ) ( 416 -112 368 ) ( -320 800 368 ) sky3 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
59+
( -320 800 368 ) ( -320 800 352 ) ( 416 800 368 ) sky3 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
60+
( 416 800 368 ) ( 416 800 352 ) ( 416 -112 368 ) sky3 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
61+
}
62+
}
63+
// entity 1
64+
{
65+
"classname" "info_player_start"
66+
"origin" "128 16 75"
67+
"angle" "0"
68+
}
69+
// entity 2
70+
{
71+
"classname" "func_wall"
72+
// brush 0
73+
{
74+
( -56 360 56 ) ( -56 232 48 ) ( -72 232 16 ) archeo1 [ 0 1.0000000000000002 0 0 ] [ 0 0 -1.0000000000000002 0 ] 0 1 1
75+
( -56 -64 48 ) ( -40 -64 16 ) ( -72 -64 16 ) archeo1 [ -1.0000000000000002 0 0 0 ] [ 0 0 -1.0000000000000002 0 ] 0 1 1
76+
( -184 376 16 ) ( -184 375 16 ) ( -183 376 16 ) archeo1 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
77+
( -56 800 56 ) ( -72 800 24 ) ( -40 800 24 ) archeo1 [ 1.0000000000000002 0 0 0 ] [ 0 0 -1.0000000000000002 0 ] 0 1 1
78+
( -40 360 24 ) ( -40 232 16 ) ( -56 232 48 ) archeo1 [ 0 -1.0000000000000002 0 0 ] [ 0 0 -1.0000000000000002 0 ] 0 1 1
79+
}
80+
}

tests/test_qbsp.cc

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1961,6 +1961,49 @@ int CountClipnodeNodes(const mbsp_t &bsp, int hullnum)
19611961
return CountClipnodeNodes_r(bsp, headnode);
19621962
}
19631963

1964+
TEST(testmapsQ1, hullExpansionBasic)
1965+
{
1966+
// this has a func_wall with a triangular prism (5 sides):
1967+
//
1968+
// ^
1969+
// | ^-------\ this end is sheared upwards a bit
1970+
// +Z /_\_______\
1971+
// |
1972+
// ---- +Y -------->
1973+
//
1974+
// The way the BRUSHLIST bspx lump makes the AABB of the brush implicit
1975+
// makes it hard to come up with examples for testing that the "cap" planes
1976+
// are being inserted.
1977+
//
1978+
// this one is completely broken if you try to walk on the top edge of the prism,
1979+
// and the cap planes are disabled (e.g. return at the start of AddBrushBevels)
1980+
1981+
const auto [bsp, bspx, prt] = LoadTestmapQ1("q1_hull_expansion.map", {"-wrbrushes"});
1982+
1983+
const bspxbrushes lump = deserialize<bspxbrushes>(bspx.at("BRUSHLIST"));
1984+
ASSERT_EQ(lump.models.size(), 2); // world + 1x func_wall
1985+
1986+
const auto &funcwall = lump.models.at(1);
1987+
ASSERT_EQ(funcwall.brushes.size(), 1);
1988+
1989+
const auto &prism = funcwall.brushes.at(0);
1990+
ASSERT_GE(prism.faces.size(), 3); // 2 non-axial faces, the sloped sides, plus the cap
1991+
1992+
const qplane3d prism_top_cap_plane =
1993+
qplane3d::from_points({qvec3d(-49.25, -64, 29.5), qvec3d(-62.75, -64, 29.5), qvec3d(-56, 800, 83.5)});
1994+
1995+
// conver to qplane3d's
1996+
std::vector<qplane3d> prism_planes;
1997+
for (auto &prism_face : prism.faces) {
1998+
prism_planes.push_back(qplane3d(prism_face.normal, prism_face.dist));
1999+
}
2000+
2001+
// check for presence to top cap
2002+
using namespace testing;
2003+
EXPECT_THAT(prism_planes,
2004+
Contains(Truly([=](const qplane3d &inp) -> bool { return qv::epsilonEqual(prism_top_cap_plane, inp); })));
2005+
}
2006+
19642007
/**
19652008
* Tests a bad hull expansion
19662009
*/

0 commit comments

Comments
 (0)