diff --git a/CHANGELOG.md b/CHANGELOG.md index b7cd1932..37060c91 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,3 +6,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/) This project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] + +### Fixed +- Symmetric shadowcasting rounds negative sector edges correctly, preserving visibility when a dungeon is rotated. diff --git a/src/libtcod-fov/fov_symmetric_shadowcast.c b/src/libtcod-fov/fov_symmetric_shadowcast.c index dd81c25b..d124e55d 100644 --- a/src/libtcod-fov/fov_symmetric_shadowcast.c +++ b/src/libtcod-fov/fov_symmetric_shadowcast.c @@ -34,7 +34,6 @@ Based on: https://www.albertford.com/shadowcasting/ */ -#include #include #include @@ -83,11 +82,11 @@ static float slope(int row_depth, int column) { return (2.0f * column - 1.0f) / /** Round half numbers towards infinity. */ -static int round_half_up(float n) { return (int)roundf(n * (1 + FLT_EPSILON)); } +static int round_half_up(float n) { return (int)floorf(n + 0.5f); } /** Round half numbers towards negative infinity. */ -static int round_half_down(float n) { return (int)roundf(n * (1 - FLT_EPSILON)); } +static int round_half_down(float n) { return (int)ceilf(n - 0.5f); } /** Scan a row and recursively scan all of its children. diff --git a/tests/test_fov_symmetric.cpp b/tests/test_fov_symmetric.cpp new file mode 100644 index 00000000..e01d7f53 --- /dev/null +++ b/tests/test_fov_symmetric.cpp @@ -0,0 +1,50 @@ +#include + +#include +#include +#include +#include + +namespace { +std::pair rotate(int x, int y, int turns) { + for (int i = 0; i < turns; ++i) { + const int old_x = x; + x = y; + y = 6 - old_x; + } + return {x, y}; +} +} // namespace + +TEST_CASE("symmetric shadowcasting rounds negative sector edges", "[fov][symmetric]") { + const std::array dungeon = { + "#######", "###...#", "###.#.#", "#.....#", "#.#.#.#", "#.#..##", "#######"}; + // Exact rational-slope reference, with the origin at (3, 1). + const std::array visible = { + "0011111", "0011111", "0011111", "0011100", "0011100", "0011000", "0011100"}; + for (int turns = 0; turns < 4; ++turns) { + for (bool light_walls : {false, true}) { + CAPTURE(turns, light_walls); + TCODFOV_Map* map = TCODFOV_map_new(7, 7); + REQUIRE(map != nullptr); + for (int y = 0; y < 7; ++y) { + for (int x = 0; x < 7; ++x) { + const auto [rx, ry] = rotate(x, y, turns); + const bool floor = dungeon[y][x] == '.'; + TCODFOV_map_set_properties(map, rx, ry, floor, floor); + } + } + const auto [pov_x, pov_y] = rotate(3, 1, turns); + REQUIRE(TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, light_walls, TCODFOV_SYMMETRIC_SHADOWCAST) == TCODFOV_E_OK); + for (int y = 0; y < 7; ++y) { + for (int x = 0; x < 7; ++x) { + CAPTURE(x, y); + const auto [rx, ry] = rotate(x, y, turns); + const bool expected = visible[y][x] == '1' && (light_walls || dungeon[y][x] == '.'); + CHECK(TCODFOV_map_is_in_fov(map, rx, ry) == expected); + } + } + TCODFOV_map_delete(map); + } + } +}