Skip to content

Commit 084b5e4

Browse files
roumingTrihedraf
authored andcommitted
test: add vision_test
This test covers a few vision bugs: 1. VisibilityInStraightLineOfSight - test case checks the visibility of objects in a straight line of sight parallel to the X or Y coordinate lines: diasurgical#7901 2. NoVisibilityThroughAdjacentTiles - test case checks that nothing is visible through the diagonally adjacent tiles: diasurgical#7920 3. VisibleObjects - generic test, which makes sure some objects are visible, but some - are not. Signed-off-by: Roman Penyaev <r.peniaev@gmail.com>
1 parent a9db2ec commit 084b5e4

2 files changed

Lines changed: 184 additions & 0 deletions

File tree

test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ set(standalone_tests
4444
ini_test
4545
parse_int_test
4646
path_test
47+
vision_test
4748
random_test
4849
rectangle_test
4950
static_vector_test
@@ -105,6 +106,7 @@ target_link_dependencies(format_int_test PRIVATE libdevilutionx_format_int langu
105106
target_link_dependencies(ini_test PRIVATE libdevilutionx_ini app_fatal_for_testing)
106107
target_link_dependencies(parse_int_test PRIVATE libdevilutionx_parse_int)
107108
target_link_dependencies(path_test PRIVATE libdevilutionx_pathfinding libdevilutionx_direction app_fatal_for_testing)
109+
target_link_dependencies(vision_test PRIVATE DevilutionX::SDL tl libdevilutionx_so app_fatal_for_testing)
108110
target_link_dependencies(path_benchmark PRIVATE libdevilutionx_pathfinding app_fatal_for_testing)
109111
target_link_dependencies(random_test PRIVATE libdevilutionx_random)
110112
target_link_dependencies(static_vector_test PRIVATE libdevilutionx_random app_fatal_for_testing)

test/vision_test.cpp

Lines changed: 182 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,182 @@
1+
#include <gtest/gtest.h>
2+
3+
#include "lighting.h"
4+
5+
namespace devilution {
6+
7+
namespace {
8+
9+
const uint8_t ENV_WIDTH = 25;
10+
const uint8_t ENV_HEIGHT = 25;
11+
12+
// Real environment
13+
char env[ENV_WIDTH][ENV_HEIGHT];
14+
// Visible environment
15+
char vis[ENV_WIDTH][ENV_HEIGHT];
16+
// Observer position in the center of the environment
17+
const Point pos(ENV_WIDTH / 2, ENV_HEIGHT / 2);
18+
// Walls (box) around the observer with the specified radius
19+
const int box_radius = 4;
20+
// Objects around the observer: point, visible-to-observer flag
21+
const std::pair<Point, bool> objects[] = {
22+
{ { 15, 12 }, true },
23+
{ { 13, 15 }, true },
24+
{ { 10, 11 }, true },
25+
{ { 11, 13 }, true },
26+
{ { 9, 15 }, false }, // Invisible to the observer, because of the {11,13}
27+
};
28+
29+
// Build walls around with diagonally adjacent corners
30+
void buildWallsAround(char env[ENV_WIDTH][ENV_HEIGHT], Point p, int radius)
31+
{
32+
for (int i = -radius + 1; i < radius; i++) {
33+
env[p.x + radius][p.y + i] = '#';
34+
env[p.x - radius][p.y + i] = '#';
35+
env[p.x - i][p.y + radius] = '#';
36+
env[p.x - i][p.y - radius] = '#';
37+
}
38+
}
39+
40+
void initEnvironment()
41+
{
42+
memset(env, ' ', sizeof(env));
43+
memset(vis, ' ', sizeof(vis));
44+
45+
// Build walls around with diagonally adjacent corners
46+
buildWallsAround(env, pos, box_radius);
47+
48+
// Place objects
49+
for (auto &o : objects) {
50+
env[o.first.x][o.first.y] = '#';
51+
}
52+
53+
// Place observer
54+
env[pos.x][pos.y] = 'x';
55+
}
56+
57+
void doVision()
58+
{
59+
initEnvironment();
60+
61+
auto markVisibleFn = [](Point p) {
62+
if (env[p.x][p.y] == ' ')
63+
// Mark as hit by the ray
64+
vis[p.x][p.y] = '.';
65+
else
66+
// Copy visible object
67+
vis[p.x][p.y] = env[p.x][p.y];
68+
};
69+
auto markTransparentFn = [](Point p) {};
70+
auto passesLightFn = [](Point p) {
71+
return env[p.x][p.y] != '#';
72+
};
73+
auto inBoundsFn = [](Point p) { return true; };
74+
75+
DoVision(pos, 15, markVisibleFn, markTransparentFn, passesLightFn, inBoundsFn);
76+
}
77+
78+
[[maybe_unused]]
79+
void dumpVisibleEnv()
80+
{
81+
char buf[4096];
82+
int sz = 0;
83+
for (int i = 0; i < ENV_HEIGHT; i++) {
84+
for (int j = 0; j < ENV_WIDTH; j++) {
85+
sz += snprintf(buf + sz, sizeof(buf) - sz, "%c ", vis[i][j]);
86+
}
87+
sz += snprintf(buf + sz, sizeof(buf) - sz, "\n");
88+
}
89+
write(2, buf, sz);
90+
}
91+
92+
// This test case checks the visibility of surrounding objects
93+
TEST(VisionTest, VisibleObjects)
94+
{
95+
doVision();
96+
97+
for (auto &o : objects) {
98+
if (o.second)
99+
// Visible object
100+
EXPECT_EQ(vis[o.first.x][o.first.y], '#') << "Expext visible wall or object";
101+
else
102+
// Invisible object
103+
EXPECT_EQ(vis[o.first.x][o.first.y], ' ') << "Expect invisible tile";
104+
}
105+
}
106+
107+
// This test case checks the visibility of objects in a straight line
108+
// of sight parallel to the X or Y coordinate lines:
109+
// https://github.com/diasurgical/DevilutionX/pull/7901
110+
TEST(VisionTest, VisibilityInStraightLineOfSight)
111+
{
112+
doVision();
113+
114+
Displacement displacements[] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } };
115+
116+
for (auto &d : displacements) {
117+
Point p = pos;
118+
bool found = false;
119+
120+
// Move along the XY coordinate lines until a visible object is hit
121+
while (p.x >= 0 && p.y >= 0 && p.x < ENV_WIDTH && p.y < ENV_HEIGHT) {
122+
p += d;
123+
124+
if (vis[p.x][p.y] == '#') {
125+
found = true;
126+
break;
127+
}
128+
}
129+
EXPECT_TRUE(found) << "Expect visible wall or object in a straight line of sight";
130+
}
131+
}
132+
133+
// This test case checks that nothing is visible through the
134+
// diagonally adjacent tiles:
135+
// https://github.com/diasurgical/DevilutionX/pull/7920
136+
TEST(VisionTest, NoVisibilityThroughAdjacentTiles)
137+
{
138+
char mask[ENV_WIDTH][ENV_HEIGHT];
139+
140+
doVision();
141+
142+
memset(mask, ' ', sizeof(mask));
143+
buildWallsAround(mask, pos, box_radius);
144+
145+
enum State {
146+
BehindWall = 0,
147+
HitWall,
148+
OnWall,
149+
InsideBox,
150+
} state
151+
= BehindWall;
152+
153+
// Goes over each tile and compares the mask with the visible
154+
// environment that is behind the wall
155+
for (int i = 0; i < ENV_HEIGHT; i++) {
156+
EXPECT_EQ(state, BehindWall);
157+
for (int j = 0; j < ENV_WIDTH; j++) {
158+
if (state == BehindWall) {
159+
// Mask and environment are compared strictly behind
160+
// the wall
161+
EXPECT_EQ(mask[i][j], vis[i][j]) << "Expect no \"leaked\" light through adjacent tiles";
162+
}
163+
164+
if (mask[i][j] == '#') {
165+
if (state == BehindWall)
166+
state = HitWall;
167+
else if (state == HitWall)
168+
state = OnWall;
169+
else if (state == InsideBox)
170+
state = BehindWall;
171+
} else {
172+
if (state == HitWall)
173+
state = InsideBox;
174+
else if (state == OnWall)
175+
state = BehindWall;
176+
}
177+
}
178+
}
179+
}
180+
181+
} // namespace
182+
} // namespace devilution

0 commit comments

Comments
 (0)