You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
182 lines
4.5 KiB
182 lines
4.5 KiB
#include <gtest/gtest.h> |
|
|
|
#include "lighting.h" |
|
|
|
namespace devilution { |
|
|
|
namespace { |
|
|
|
const uint8_t ENV_WIDTH = 25; |
|
const uint8_t ENV_HEIGHT = 25; |
|
|
|
// Real environment |
|
char env[ENV_WIDTH][ENV_HEIGHT]; |
|
// Visible environment |
|
char vis[ENV_WIDTH][ENV_HEIGHT]; |
|
// Observer position in the center of the environment |
|
const Point pos(ENV_WIDTH / 2, ENV_HEIGHT / 2); |
|
// Walls (box) around the observer with the specified radius |
|
const int box_radius = 4; |
|
// Objects around the observer: point, visible-to-observer flag |
|
const std::pair<Point, bool> objects[] = { |
|
{ { 15, 12 }, true }, |
|
{ { 13, 15 }, true }, |
|
{ { 10, 11 }, true }, |
|
{ { 11, 13 }, true }, |
|
{ { 9, 15 }, false }, // Invisible to the observer, because of the {11,13} |
|
}; |
|
|
|
// Build walls around with diagonally adjacent corners |
|
void buildWallsAround(char env[ENV_WIDTH][ENV_HEIGHT], Point p, int radius) |
|
{ |
|
for (int i = -radius + 1; i < radius; i++) { |
|
env[p.x + radius][p.y + i] = '#'; |
|
env[p.x - radius][p.y + i] = '#'; |
|
env[p.x - i][p.y + radius] = '#'; |
|
env[p.x - i][p.y - radius] = '#'; |
|
} |
|
} |
|
|
|
void initEnvironment() |
|
{ |
|
memset(env, ' ', sizeof(env)); |
|
memset(vis, ' ', sizeof(vis)); |
|
|
|
// Build walls around with diagonally adjacent corners |
|
buildWallsAround(env, pos, box_radius); |
|
|
|
// Place objects |
|
for (auto &o : objects) { |
|
env[o.first.x][o.first.y] = '#'; |
|
} |
|
|
|
// Place observer |
|
env[pos.x][pos.y] = 'x'; |
|
} |
|
|
|
void doVision() |
|
{ |
|
initEnvironment(); |
|
|
|
auto markVisibleFn = [](Point p) { |
|
if (env[p.x][p.y] == ' ') |
|
// Mark as hit by the ray |
|
vis[p.x][p.y] = '.'; |
|
else |
|
// Copy visible object |
|
vis[p.x][p.y] = env[p.x][p.y]; |
|
}; |
|
auto markTransparentFn = [](Point p) {}; |
|
auto passesLightFn = [](Point p) { |
|
return env[p.x][p.y] != '#'; |
|
}; |
|
auto inBoundsFn = [](Point p) { return true; }; |
|
|
|
DoVision(pos, 15, markVisibleFn, markTransparentFn, passesLightFn, inBoundsFn); |
|
} |
|
|
|
[[maybe_unused]] |
|
void dumpVisibleEnv() |
|
{ |
|
char buf[4096]; |
|
int sz = 0; |
|
for (int i = 0; i < ENV_HEIGHT; i++) { |
|
for (int j = 0; j < ENV_WIDTH; j++) { |
|
sz += snprintf(buf + sz, sizeof(buf) - sz, "%c ", vis[i][j]); |
|
} |
|
sz += snprintf(buf + sz, sizeof(buf) - sz, "\n"); |
|
} |
|
write(2, buf, sz); |
|
} |
|
|
|
// This test case checks the visibility of surrounding objects |
|
TEST(VisionTest, VisibleObjects) |
|
{ |
|
doVision(); |
|
|
|
for (auto &o : objects) { |
|
if (o.second) |
|
// Visible object |
|
EXPECT_EQ(vis[o.first.x][o.first.y], '#') << "Expext visible wall or object"; |
|
else |
|
// Invisible object |
|
EXPECT_EQ(vis[o.first.x][o.first.y], ' ') << "Expect invisible tile"; |
|
} |
|
} |
|
|
|
// This test case checks the visibility of objects in a straight line |
|
// of sight parallel to the X or Y coordinate lines: |
|
// https://github.com/diasurgical/DevilutionX/pull/7901 |
|
TEST(VisionTest, VisibilityInStraightLineOfSight) |
|
{ |
|
doVision(); |
|
|
|
Displacement displacements[] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; |
|
|
|
for (auto &d : displacements) { |
|
Point p = pos; |
|
bool found = false; |
|
|
|
// Move along the XY coordinate lines until a visible object is hit |
|
while (p.x >= 0 && p.y >= 0 && p.x < ENV_WIDTH && p.y < ENV_HEIGHT) { |
|
p += d; |
|
|
|
if (vis[p.x][p.y] == '#') { |
|
found = true; |
|
break; |
|
} |
|
} |
|
EXPECT_TRUE(found) << "Expect visible wall or object in a straight line of sight"; |
|
} |
|
} |
|
|
|
// This test case checks that nothing is visible through the |
|
// diagonally adjacent tiles: |
|
// https://github.com/diasurgical/DevilutionX/pull/7920 |
|
TEST(VisionTest, NoVisibilityThroughAdjacentTiles) |
|
{ |
|
char mask[ENV_WIDTH][ENV_HEIGHT]; |
|
|
|
doVision(); |
|
|
|
memset(mask, ' ', sizeof(mask)); |
|
buildWallsAround(mask, pos, box_radius); |
|
|
|
enum State { |
|
BehindWall = 0, |
|
HitWall, |
|
OnWall, |
|
InsideBox, |
|
} state |
|
= BehindWall; |
|
|
|
// Goes over each tile and compares the mask with the visible |
|
// environment that is behind the wall |
|
for (int i = 0; i < ENV_HEIGHT; i++) { |
|
EXPECT_EQ(state, BehindWall); |
|
for (int j = 0; j < ENV_WIDTH; j++) { |
|
if (state == BehindWall) { |
|
// Mask and environment are compared strictly behind |
|
// the wall |
|
EXPECT_EQ(mask[i][j], vis[i][j]) << "Expect no \"leaked\" light through adjacent tiles"; |
|
} |
|
|
|
if (mask[i][j] == '#') { |
|
if (state == BehindWall) |
|
state = HitWall; |
|
else if (state == HitWall) |
|
state = OnWall; |
|
else if (state == InsideBox) |
|
state = BehindWall; |
|
} else { |
|
if (state == HitWall) |
|
state = InsideBox; |
|
else if (state == OnWall) |
|
state = BehindWall; |
|
} |
|
} |
|
} |
|
} |
|
|
|
} // namespace |
|
} // namespace devilution
|
|
|