tests: better documentation and test for areaportals

This commit is contained in:
Eric Wasylishen 2025-01-20 00:15:57 -07:00
parent b1ee327092
commit de1e130e4f
3 changed files with 257 additions and 4 deletions

View File

@ -318,10 +318,12 @@ struct mleaf_t
bool operator==(const mleaf_t &other) const;
};
// index of darea_t in dareas *is* the "area number" (unlike for dareaportals).
// 0 is reserved
struct darea_t
{
int32_t numareaportals;
int32_t firstareaportal;
int32_t numareaportals; // number of entries in dareaportals (number of outgoing graph edges)
int32_t firstareaportal; // index into dareaportals of our first outgoing graph edge, *not* the "area portal number"
// serialize for streams
void stream_write(std::ostream &s) const;
@ -334,10 +336,13 @@ struct darea_t
// each area has a list of portals that lead into other areas
// when portals are closed, other areas may not be visible or
// hearable even if the vis info says that it should be
//
// a dareaportal_t is a directed edge in the area graph.
struct dareaportal_t
{
int32_t portalnum;
int32_t otherarea;
int32_t portalnum; // our "area portal number". corresponds to the "style" key on the func_areaportal entity.
// multiple dareaportal_t entries (should be always 2) will have the same portalnum
int32_t otherarea; // area number (index in dareas array) of the other area.
// serialize for streams
void stream_write(std::ostream &s) const;

160
testmaps/q2_areaportals.map Normal file
View File

@ -0,0 +1,160 @@
// Game: Quake 2
// Format: Quake2 (Valve)
// entity 0
{
"mapversion" "220"
"classname" "worldspawn"
"_tb_textures" "textures/e1u1"
// brush 0
{
( -176 -256 64 ) ( -176 -255 64 ) ( -176 -256 65 ) e1u1/ggrat4_2 [ 0 1.0000000000000002 0 -16 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
( -176 -432 64 ) ( -176 -432 65 ) ( -175 -432 64 ) e1u1/ggrat4_2 [ -1 0 0 -16 ] [ 0 0 -1 -32 ] 0 1 1
( -176 -256 96 ) ( -175 -256 96 ) ( -176 -255 96 ) e1u1/ggrat4_2 [ -1.0000000000000002 0 0 -16 ] [ 0 1.0000000000000002 0 -48 ] 0 1 1
( -160 192 352 ) ( -160 193 352 ) ( -159 192 352 ) e1u1/ggrat4_2 [ -1.0000000000000002 0 0 -16 ] [ 0 -1.0000000000000002 0 -80 ] 0 1 1
( -160 176 80 ) ( -159 176 80 ) ( -160 176 81 ) e1u1/ggrat4_2 [ -1 0 0 -16 ] [ 0 0 -1 -32 ] 0 1 1
( -160 192 80 ) ( -160 192 81 ) ( -160 193 80 ) e1u1/ggrat4_2 [ 0 -1.0000000000000002 0 64 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
}
// brush 1
{
( -160 176 88 ) ( -160 177 88 ) ( -160 176 89 ) e1u1/ggrat4_2 [ 0 1.0000000000000002 0 -32 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
( -160 176 88 ) ( -160 176 89 ) ( -159 176 88 ) e1u1/ggrat4_2 [ -1 0 0 -16 ] [ 0 0 -1 -32 ] 0 1 1
( -160 176 96 ) ( -159 176 96 ) ( -160 177 96 ) e1u1/ggrat4_2 [ -1.0000000000000002 0 0 -16 ] [ 0 1.0000000000000002 0 -40 ] 0 1 1
( 288 192 352 ) ( 288 193 352 ) ( 289 192 352 ) e1u1/ggrat4_2 [ -1.0000000000000002 0 0 -16 ] [ 0 -1.0000000000000002 0 48 ] 0 1 1
( 288 192 96 ) ( 289 192 96 ) ( 288 192 97 ) e1u1/ggrat4_2 [ -1 0 0 -16 ] [ 0 0 -1 -32 ] 0 1 1
( 496 192 96 ) ( 496 192 97 ) ( 496 193 96 ) e1u1/ggrat4_2 [ 0 -1.0000000000000002 0 0 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
}
// brush 2
{
( -160 -112 96 ) ( -160 -111 96 ) ( -160 -112 97 ) e1u1/florr1_8 [ 0 1 0 -16 ] [ 0 0 -1 0 ] 0 1 1
( -80 -432 80 ) ( -81 -432 80 ) ( -80 -432 81 ) e1u1/florr1_8 [ -1 0 0 16 ] [ 0 0 -1 0 ] 180 1 1
( -80 -432 80 ) ( -80 -431 80 ) ( -81 -432 80 ) e1u1/florr1_8 [ 1 0 0 -16 ] [ 0 -1 0 16 ] 180 1 1
( -160 -112 96 ) ( -161 -112 96 ) ( -160 -111 96 ) e1u1/florr1_8 [ -1 0 0 16 ] [ 0 -1 0 16 ] 180 1 1
( -160 176 96 ) ( -160 176 97 ) ( -161 176 96 ) e1u1/florr1_8 [ 1 0 0 -16 ] [ 0 0 -1 0 ] 180 1 1
( 496 -432 80 ) ( 496 -432 81 ) ( 496 -431 80 ) e1u1/florr1_8 [ 0 -1 0 16 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 3
{
( -160 -448 88 ) ( -160 -447 88 ) ( -160 -448 89 ) e1u1/ggrat4_2 [ 0 1 0 0 ] [ 0 0 -1 -32 ] 0 1 1
( -160 -448 88 ) ( -160 -448 89 ) ( -159 -448 88 ) e1u1/ggrat4_2 [ -1.0000000000000002 0 0 -32 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
( -160 -448 96 ) ( -159 -448 96 ) ( -160 -447 96 ) e1u1/ggrat4_2 [ 0 1.0000000000000002 0 0 ] [ 1.0000000000000002 0 0 -32 ] 0 1 1
( 288 -432 352 ) ( 288 -431 352 ) ( 289 -432 352 ) e1u1/ggrat4_2 [ 0 1.0000000000000002 0 0 ] [ -1.0000000000000002 0 0 -96 ] 0 1 1
( 288 -432 96 ) ( 289 -432 96 ) ( 288 -432 97 ) e1u1/ggrat4_2 [ 1.0000000000000002 0 0 -80 ] [ 0 0 -1.0000000000000002 -32 ] 0 1 1
( 496 -432 96 ) ( 496 -432 97 ) ( 496 -431 96 ) e1u1/ggrat4_2 [ 0 1 0 0 ] [ 0 0 -1 -32 ] 0 1 1
}
// brush 4
{
( -160 -256 352 ) ( -160 -255 352 ) ( -160 -256 353 ) e1u1/florr1_8 [ 0 0 -1.0000000000000002 0 ] [ 0 -1.0000000000000002 0 0 ] 180 1 1
( 288 -432 360 ) ( 288 -432 361 ) ( 289 -432 360 ) e1u1/florr1_8 [ -1.0000000000000002 0 0 0 ] [ 0 0 1.0000000000000002 -16 ] 180 1 1
( -160 -256 352 ) ( -159 -256 352 ) ( -160 -255 352 ) e1u1/florr1_8 [ -1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( 288 176 368 ) ( 288 177 368 ) ( 289 176 368 ) e1u1/florr1_8 [ -1 0 0 0 ] [ 0 -1 0 0 ] 180 1 1
( 288 176 360 ) ( 289 176 360 ) ( 288 176 361 ) e1u1/florr1_8 [ -1.0000000000000002 0 0 0 ] [ 0 0 1.0000000000000002 -16 ] 180 1 1
( 496 176 360 ) ( 496 176 361 ) ( 496 177 360 ) e1u1/florr1_8 [ 0 0 1.0000000000000002 0 ] [ 0 -1.0000000000000002 0 0 ] 180 1 1
}
// brush 5
{
( 496 192 80 ) ( 496 193 80 ) ( 496 192 81 ) e1u1/ggrat4_2 [ 0 1 0 0 ] [ 0 0 -1 -32 ] 0 1 1
( 512 -432 64 ) ( 511 -432 64 ) ( 512 -432 65 ) e1u1/ggrat4_2 [ -1 0 0 96 ] [ 0 0 -1 -32 ] 180 1 1
( 512 -256 96 ) ( 512 -255 96 ) ( 511 -256 96 ) e1u1/ggrat4_2 [ 1 0 0 -96 ] [ 0 -1 0 0 ] 180 1 1
( 512 -256 352 ) ( 511 -256 352 ) ( 512 -255 352 ) e1u1/ggrat4_2 [ 1 0 0 -96 ] [ 0 -1 0 0 ] 180 1 1
( 496 176 80 ) ( 496 176 81 ) ( 495 176 80 ) e1u1/ggrat4_2 [ 1 0 0 -96 ] [ 0 0 -1 -32 ] 180 1 1
( 512 -256 64 ) ( 512 -256 65 ) ( 512 -255 64 ) e1u1/ggrat4_2 [ 0 -1 0 0 ] [ 0 0 -1 -32 ] 0 1 1
}
// brush 6
{
( 16 -48 96 ) ( 16 -47 96 ) ( 16 -48 97 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 32 -48 96 ) ( 32 -48 97 ) ( 33 -48 96 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 32 -48 96 ) ( 33 -48 96 ) ( 32 -47 96 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 48 -16 192 ) ( 48 -15 192 ) ( 49 -16 192 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 48 176 112 ) ( 49 176 112 ) ( 48 176 113 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 48 -16 112 ) ( 48 -16 113 ) ( 48 -15 112 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 7
{
( 16 -240 96 ) ( 16 -239 96 ) ( 16 -240 97 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 32 -432 96 ) ( 32 -432 97 ) ( 33 -432 96 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 32 -240 96 ) ( 33 -240 96 ) ( 32 -239 96 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 -1 0 -64 ] 0 1 1
( 48 -208 192 ) ( 48 -207 192 ) ( 49 -208 192 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 -1 0 -64 ] 0 1 1
( 48 -208 112 ) ( 49 -208 112 ) ( 48 -208 113 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 48 -208 112 ) ( 48 -208 113 ) ( 48 -207 112 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 8
{
( 16 -48 192 ) ( 16 -47 192 ) ( 16 -48 193 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 96 ] 0 1 1
( 32 -432 192 ) ( 32 -432 193 ) ( 33 -432 192 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 0 -1 96 ] 0 1 1
( 32 -48 192 ) ( 33 -48 192 ) ( 32 -47 192 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 48 -16 352 ) ( 48 -15 352 ) ( 49 -16 352 ) e1u1/caution1_1 [ 1 0 0 0 ] [ 0 -1 0 0 ] 0 1 1
( 48 176 208 ) ( 49 176 208 ) ( 48 176 209 ) e1u1/caution1_1 [ -1 0 0 0 ] [ 0 0 -1 96 ] 0 1 1
( 48 -16 208 ) ( 48 -16 209 ) ( 48 -15 208 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 96 ] 0 1 1
}
// brush 9
{
( 288 -48 96 ) ( 288 -47 96 ) ( 288 -48 97 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 304 -48 96 ) ( 304 -48 97 ) ( 305 -48 96 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 0 -1 0 ] 0 1 1
( 304 -48 96 ) ( 305 -48 96 ) ( 304 -47 96 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 -1 0 0 ] 0 1 1
( 320 -16 192 ) ( 320 -15 192 ) ( 321 -16 192 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 -1 0 0 ] 0 1 1
( 320 176 112 ) ( 321 176 112 ) ( 320 176 113 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 0 -1 0 ] 0 1 1
( 320 -16 112 ) ( 320 -16 113 ) ( 320 -15 112 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
// brush 10
{
( 288 -48 192 ) ( 288 -47 192 ) ( 288 -48 193 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 96 ] 0 1 1
( 304 -432 192 ) ( 304 -432 193 ) ( 305 -432 192 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 0 -1 96 ] 0 1 1
( 304 -48 192 ) ( 305 -48 192 ) ( 304 -47 192 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 -1 0 0 ] 0 1 1
( 320 -16 352 ) ( 320 -15 352 ) ( 321 -16 352 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 -1 0 0 ] 0 1 1
( 320 176 208 ) ( 321 176 208 ) ( 320 176 209 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 0 -1 96 ] 0 1 1
( 320 -16 208 ) ( 320 -16 209 ) ( 320 -15 208 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 96 ] 0 1 1
}
// brush 11
{
( 288 -240 96 ) ( 288 -239 96 ) ( 288 -240 97 ) e1u1/caution1_1 [ 0 -1 0 0 ] [ 0 0 -1 0 ] 0 1 1
( 304 -432 96 ) ( 304 -432 97 ) ( 305 -432 96 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 0 -1 0 ] 0 1 1
( 304 -240 96 ) ( 305 -240 96 ) ( 304 -239 96 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 -1 0 -64 ] 0 1 1
( 320 -208 192 ) ( 320 -207 192 ) ( 321 -208 192 ) e1u1/caution1_1 [ 1 0 0 16 ] [ 0 -1 0 -64 ] 0 1 1
( 320 -208 112 ) ( 321 -208 112 ) ( 320 -208 113 ) e1u1/caution1_1 [ -1 0 0 -16 ] [ 0 0 -1 0 ] 0 1 1
( 320 -208 112 ) ( 320 -208 113 ) ( 320 -207 112 ) e1u1/caution1_1 [ 0 1 0 0 ] [ 0 0 -1 0 ] 0 1 1
}
}
// entity 1
{
"classname" "light"
"origin" "72 -136 168"
"light" "3000"
}
// entity 2
{
"classname" "info_player_start"
"origin" "-88 -112 120"
}
// entity 3
{
"classname" "func_areaportal"
"targetname" "playerstart_portal"
// brush 0
{
( 16 -136 96 ) ( 16 -135 96 ) ( 16 -136 97 ) e1u1/trigger [ 0 0 -1.0000000000000002 16 ] [ 0 -1.0000000000000002 0 -8 ] 180 1 1 65536 128 0
( 80 -208 96 ) ( 80 -208 97 ) ( 81 -208 96 ) e1u1/trigger [ -1.0000000000000002 0 0 0 ] [ 0 0 -1.0000000000000002 16 ] 180 1 1 65536 128 0
( 80 -136 96 ) ( 81 -136 96 ) ( 80 -135 96 ) e1u1/trigger [ -1 0 0 0 ] [ 0 -1 0 -8 ] 180 1 1 65536 128 0
( 144 -56 192 ) ( 144 -55 192 ) ( 145 -56 192 ) e1u1/trigger [ -1 0 0 0 ] [ 0 -1 0 -8 ] 180 1 1 65536 128 0
( 144 -48 112 ) ( 145 -48 112 ) ( 144 -48 113 ) e1u1/trigger [ -1.0000000000000002 0 0 0 ] [ 0 0 1.0000000000000002 -16 ] 180 1 1 65536 128 0
( 48 -56 112 ) ( 48 -56 113 ) ( 48 -55 112 ) e1u1/trigger [ 0 0 1.0000000000000002 -16 ] [ 0 -1.0000000000000002 0 -8 ] 180 1 1 65536 128 0
}
}
// entity 4
{
"classname" "func_areaportal"
"targetname" "grenades_portal"
// brush 0
{
( 288 -136 96 ) ( 288 -135 96 ) ( 288 -136 97 ) e1u1/trigger [ 0 0 -1.0000000000000002 16 ] [ 0 -1.0000000000000002 0 -8 ] 180 1 1 65536 128 0
( 352 -208 96 ) ( 352 -208 97 ) ( 353 -208 96 ) e1u1/trigger [ -1.0000000000000002 0 0 -16 ] [ 0 0 -1.0000000000000002 16 ] 180 1 1 65536 128 0
( 352 -136 96 ) ( 353 -136 96 ) ( 352 -135 96 ) e1u1/trigger [ -1 0 0 -16 ] [ 0 -1 0 -8 ] 180 1 1 65536 128 0
( 416 -56 192 ) ( 416 -55 192 ) ( 417 -56 192 ) e1u1/trigger [ -1 0 0 -16 ] [ 0 -1 0 -8 ] 180 1 1 65536 128 0
( 416 -48 112 ) ( 417 -48 112 ) ( 416 -48 113 ) e1u1/trigger [ -1.0000000000000002 0 0 -16 ] [ 0 0 1.0000000000000002 -16 ] 180 1 1 65536 128 0
( 320 -56 112 ) ( 320 -56 113 ) ( 320 -55 112 ) e1u1/trigger [ 0 0 1.0000000000000002 -16 ] [ 0 -1.0000000000000002 0 -8 ] 180 1 1 65536 128 0
}
}
// entity 5
{
"classname" "ammo_grenades"
"origin" "416 -128 112"
}

View File

@ -256,6 +256,94 @@ TEST(testmapsQ2, areaportalWithDetail)
EXPECT_THAT(bsp.dareas, testing::UnorderedElementsAreArray(std::vector<darea_t>{{0, 0}, {1, 1}, {1, 2}}));
}
/**
* same as q2_areaportal.map but has 2 areaportals
* more clearly shows how areaportal indices work
*
* ap1 ap2
*
* player | light | ammo
* start | | grenades
*
* area area area
* 3 2 1
*
* -- +x -->
*/
TEST(testmapsQ2, areaportals)
{
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_areaportals.map");
ASSERT_EQ(4, bsp.dareas.size()); // 1 reserved + 3 actual = 4
ASSERT_EQ(5, bsp.dareaportals.size()); // 1 reserved + (2 portals * 2 directions) = 5
// check the areaportal numbers from the "style" keys of the func_areaportal entities
auto ents = EntData_Parse(bsp);
auto playerstart_portal_it = std::ranges::find_if(
ents, [](const entdict_t &dict) { return dict.get("targetname") == "playerstart_portal"; });
auto grenades_portal_it =
std::ranges::find_if(ents, [](const entdict_t &dict) { return dict.get("targetname") == "grenades_portal"; });
ASSERT_NE(playerstart_portal_it, ents.end());
ASSERT_NE(grenades_portal_it, ents.end());
const int32_t playerstart_portal_num = playerstart_portal_it->get_int("style");
const int32_t grenades_portal_num = grenades_portal_it->get_int("style");
// may need to be adjusted
ASSERT_EQ(1, playerstart_portal_num);
ASSERT_EQ(2, grenades_portal_num);
// look up the leafs
const qvec3d player_start{-88, -112, 120};
const qvec3d light_pos{72, -136, 168};
const qvec3d grenades_pos{416, -128, 112};
auto *player_start_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], player_start);
auto *light_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], light_pos);
auto *grenades_leaf = BSP_FindLeafAtPoint(&bsp, &bsp.dmodels[0], grenades_pos);
// check leaf areas (may need to be adjusted)
EXPECT_EQ(2, light_leaf->area);
EXPECT_EQ(3, player_start_leaf->area);
EXPECT_EQ(1, grenades_leaf->area);
// inspect player_start_leaf area
{
const darea_t &area = bsp.dareas[player_start_leaf->area];
ASSERT_EQ(area.numareaportals, 1); // to light area
const dareaportal_t &portal = bsp.dareaportals[area.firstareaportal];
EXPECT_EQ(portal.otherarea, light_leaf->area);
EXPECT_EQ(portal.portalnum, playerstart_portal_num);
}
// inspect "light" leaf
{
const darea_t &area = bsp.dareas[light_leaf->area];
ASSERT_EQ(area.numareaportals, 2); // to player start, grenades areas
dareaportal_t portal_x = bsp.dareaportals[area.firstareaportal];
dareaportal_t portal_y = bsp.dareaportals[area.firstareaportal + 1];
EXPECT_THAT((std::vector{portal_x, portal_y}),
testing::UnorderedElementsAre(
dareaportal_t{.portalnum = playerstart_portal_num, .otherarea = player_start_leaf->area},
dareaportal_t{.portalnum = grenades_portal_num, .otherarea = grenades_leaf->area}));
}
// inspect "grenades" leaf
{
const darea_t &area = bsp.dareas[grenades_leaf->area];
ASSERT_EQ(area.numareaportals, 1); // to light leaf
dareaportal_t portal = bsp.dareaportals[area.firstareaportal];
EXPECT_EQ(portal, (dareaportal_t{.portalnum = grenades_portal_num, .otherarea = light_leaf->area}));
}
}
TEST(testmapsQ2, nodrawLight)
{
const auto [bsp, bspx, prt] = LoadTestmapQ2("q2_nodraw_light.map", {"-includeskip"});