diff --git a/.vscode/cspell.json b/.vscode/cspell.json index f3efd33a..6fb3482b 100644 --- a/.vscode/cspell.json +++ b/.vscode/cspell.json @@ -39,6 +39,7 @@ "bhex", "bitdepth", "bitfield", + "BITPACKED", "BKGND", "blambot", "BLENDMODE", diff --git a/buildsys/autotools/sources.am b/buildsys/autotools/sources.am index fcf25c3b..8a786c18 100644 --- a/buildsys/autotools/sources.am +++ b/buildsys/autotools/sources.am @@ -10,7 +10,9 @@ libtcod_fov_include_HEADERS = \ ../../include/libtcod-fov/fov.h \ ../../include/libtcod-fov/fov.hpp \ ../../include/libtcod-fov/fov_types.h \ + ../../include/libtcod-fov/libtcod_int.h \ ../../include/libtcod-fov/logging.h \ + ../../include/libtcod-fov/map.hpp \ ../../include/libtcod-fov/map_inline.h \ ../../include/libtcod-fov/map_types.h \ ../../include/libtcod-fov/version.h diff --git a/include/libtcod-fov.h b/include/libtcod-fov.h index 3750a680..343f2d4c 100644 --- a/include/libtcod-fov.h +++ b/include/libtcod-fov.h @@ -37,12 +37,14 @@ #include "libtcod-fov/error.h" #include "libtcod-fov/fov.h" #include "libtcod-fov/logging.h" +#include "libtcod-fov/map_inline.h" #include "libtcod-fov/map_types.h" #include "libtcod-fov/version.h" #ifdef __cplusplus #include "libtcod-fov/bresenham.hpp" #include "libtcod-fov/fov.hpp" +#include "libtcod-fov/map.hpp" #endif // __cplusplus #endif // TCODFOV_H_ diff --git a/src/libtcod-fov/libtcod_int.h b/include/libtcod-fov/libtcod_int.h similarity index 86% rename from src/libtcod-fov/libtcod_int.h rename to include/libtcod-fov/libtcod_int.h index 899d7d66..312d1f66 100644 --- a/src/libtcod-fov/libtcod_int.h +++ b/include/libtcod-fov/libtcod_int.h @@ -46,28 +46,28 @@ extern "C" { #endif /* fov internal stuff */ -TCODFOV_Error TCODFOV_map_compute_fov_circular_raycasting( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_circular_raycasting( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int max_radius, bool light_walls); -TCODFOV_Error TCODFOV_map_compute_fov_diamond_raycasting( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_diamond_raycasting( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int max_radius, bool light_walls); -TCODFOV_Error TCODFOV_map_compute_fov_recursive_shadowcasting( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_recursive_shadowcasting( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int max_radius, bool light_walls); -TCODFOV_Error TCODFOV_map_compute_fov_permissive2( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_permissive2( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, @@ -75,21 +75,21 @@ TCODFOV_Error TCODFOV_map_compute_fov_permissive2( int max_radius, bool light_walls, int permissiveness); -TCODFOV_Error TCODFOV_map_compute_fov_restrictive_shadowcasting( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_restrictive_shadowcasting( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int max_radius, bool light_walls); -TCODFOV_Error TCODFOV_map_compute_fov_symmetric_shadowcast( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_compute_fov_symmetric_shadowcast( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int max_radius, bool light_walls); -TCODFOV_Error TCODFOV_map_postprocess( +TCODFOV_PUBLIC TCODFOV_Error TCODFOV_map_postprocess( const TCODFOV_Map2D* __restrict transparent, TCODFOV_Map2D* __restrict fov, int pov_x, int pov_y, int radius); /** Return true if `x` and `y` are in the boundaries of `map`. diff --git a/include/libtcod-fov/map.hpp b/include/libtcod-fov/map.hpp new file mode 100644 index 00000000..d2d20067 --- /dev/null +++ b/include/libtcod-fov/map.hpp @@ -0,0 +1,14 @@ +#include + +#include "map_inline.h" +#include "map_types.h" + +namespace tcod::fov { + +struct MapDeleter { + void operator()(TCODFOV_Map2D* map) const { TCODFOV_map2d_delete(map); } +}; + +typedef std::unique_ptr Map2DPtr; + +} // namespace tcod::fov diff --git a/include/libtcod-fov/map_inline.h b/include/libtcod-fov/map_inline.h index ae117ebb..ef7bf63d 100644 --- a/include/libtcod-fov/map_inline.h +++ b/include/libtcod-fov/map_inline.h @@ -1,9 +1,33 @@ #pragma once #ifndef TCODFOV_MAP_INLINE_H_ #define TCODFOV_MAP_INLINE_H_ +#include +#include + #include "fov_types.h" #include "map_types.h" +/// @brief Return minimum byte length which can hold the given number of bits. +static inline ptrdiff_t TCODFOV_round_to_byte_(ptrdiff_t bits) { return (((bits - 1) | 7) + 1) / 8; } + +/// @brief Return a new bitpacked map of the given size. +/// @return The new map, or NULL if memory could not be allocated. +static inline TCODFOV_Map2D* TCODFOV_map2d_new_bitpacked(int width, int height) { + const ptrdiff_t byte_width = TCODFOV_round_to_byte_(width); + TCODFOV_Map2D* map = (TCODFOV_Map2D*)calloc(1, sizeof(*map) + byte_width * height); + if (!map) return NULL; + map->bitpacked.shape[0] = height; + map->bitpacked.shape[1] = width; + map->bitpacked.y_stride = byte_width; + map->bitpacked.data = (uint8_t*)map + sizeof(*map); + return map; +} + +/// @brief Delete a map created by any TCODFOV_map2d_new function. +static inline void TCODFOV_map2d_delete(TCODFOV_Map2D* map) { + if (map) free(map); +} + /// @brief Return the width of a 2D map. /// @param map Map union pointer, can be NULL. /// @return The map width in tiles, or zero if `map` is NULL. @@ -11,7 +35,9 @@ static inline int TCODFOV_map2d_get_width(const TCODFOV_Map2D* __restrict map) { if (!map) return 0; switch (map->type) { case TCODFOV_MAP2D_CALLBACK: - return map->bool_callback.width; + case TCODFOV_MAP2D_BITPACKED: + // Multiple structs share the same shape format + return map->bool_callback.shape[1]; case TCODFOV_MAP2D_DEPRECATED: return map->deprecated_map.map.width; default: @@ -25,7 +51,8 @@ static inline int TCODFOV_map2d_get_height(const TCODFOV_Map2D* __restrict map) if (!map) return 0; switch (map->type) { case TCODFOV_MAP2D_CALLBACK: - return map->bool_callback.height; + case TCODFOV_MAP2D_BITPACKED: + return map->bool_callback.shape[0]; case TCODFOV_MAP2D_DEPRECATED: return map->deprecated_map.map.height; default: @@ -67,10 +94,15 @@ static inline bool TCODFOV_map2d_get_bool(const TCODFOV_Map2D* __restrict map, i return cell->fov; } } + case TCODFOV_MAP2D_BITPACKED: { + const uint8_t active_bit = 1 << (x % 8); + return (map->bitpacked.data[map->bitpacked.y_stride * y + (x / 8)] & active_bit) != 0; + } default: return 0; } } + /// @brief Assign the boolean `value` to `{x, y}` on `map`. Out-of-bounds writes are ignored. /// @param map Map union pointer, can be NULL. /// @param x X coordinate. @@ -99,6 +131,12 @@ static inline void TCODFOV_map2d_set_bool(TCODFOV_Map2D* __restrict map, int x, return; } } + case TCODFOV_MAP2D_BITPACKED: { + const uint8_t active_bit = 1 << (x % 8); + const ptrdiff_t index = map->bitpacked.y_stride * y + (x / 8); + map->bitpacked.data[index] = (map->bitpacked.data[index] & ~active_bit) | (value ? active_bit : 0); + return; + } default: return; } diff --git a/include/libtcod-fov/map_types.h b/include/libtcod-fov/map_types.h index 687b32e0..506a0908 100644 --- a/include/libtcod-fov/map_types.h +++ b/include/libtcod-fov/map_types.h @@ -3,6 +3,8 @@ #define TCODFOV_MAP_TYPES_H_ #include +#include +#include #include "fov_types.h" @@ -11,13 +13,13 @@ typedef enum TCODFOV_Map2DType { TCODFOV_MAP2D_UNDEFINED = 0, TCODFOV_MAP2D_CALLBACK = 1, TCODFOV_MAP2D_DEPRECATED = 2, + TCODFOV_MAP2D_BITPACKED = 3, } TCODFOV_Map2DType; /// @brief Callbacks to get/set on 2D grids. struct TCODFOV_Map2DCallback { TCODFOV_Map2DType type; // Must be TCODFOV_MAP2D_CALLBACK - int width; - int height; + int shape[2]; // {height, width} void* userdata; bool (*get)(void* userdata, int x, int y); // Get callback void (*set)(void* userdata, int x, int y, bool v); // Set callback @@ -30,10 +32,19 @@ struct TCODFOV_Map2DDeprecated { TCODFOV_Map map; }; +/// @brief Bitpacked 2D grid. +struct TCODFOV_Map2DBitpacked { + TCODFOV_Map2DType type; // Must be TCODFOV_MAP2D_BITPACKED + int shape[2]; // {height, width} + uint8_t* __restrict data; // Boolean data packed into bytes + ptrdiff_t y_stride; // Array stride along the y-axis +}; + /// @brief Union type for 2D maps. typedef union TCODFOV_Map2D { TCODFOV_Map2DType type; struct TCODFOV_Map2DCallback bool_callback; struct TCODFOV_Map2DDeprecated deprecated_map; + struct TCODFOV_Map2DBitpacked bitpacked; } TCODFOV_Map2D; #endif // TCODFOV_MAP_TYPES_H_ diff --git a/src/sources.cmake b/src/sources.cmake index f435078a..3fda8b98 100644 --- a/src/sources.cmake +++ b/src/sources.cmake @@ -9,7 +9,6 @@ target_sources(${PROJECT_NAME} PRIVATE libtcod-fov/fov_recursive_shadowcasting.c libtcod-fov/fov_restrictive.c libtcod-fov/fov_symmetric_shadowcast.c - libtcod-fov/libtcod_int.h libtcod-fov/logging.c libtcod-fov/utility.h ) @@ -27,7 +26,9 @@ install(FILES ../include/libtcod-fov/fov.h ../include/libtcod-fov/fov.hpp ../include/libtcod-fov/fov_types.h + ../include/libtcod-fov/libtcod_int.h ../include/libtcod-fov/logging.h + ../include/libtcod-fov/map.hpp ../include/libtcod-fov/map_inline.h ../include/libtcod-fov/map_types.h ../include/libtcod-fov/version.h diff --git a/tests/test_fov.cpp b/tests/test_fov.cpp index c1678b6b..8069da48 100644 --- a/tests/test_fov.cpp +++ b/tests/test_fov.cpp @@ -5,15 +5,19 @@ #include #include #include -#include +#include #include #include #include #include +#include "libtcod-fov/fov.hpp" +#include "libtcod-fov/libtcod_int.h" +#include "libtcod-fov/map.hpp" + struct MapInfo { std::string name{}; - tcod::fov::MapPtr_ data{}; // Map tile data + tcod::fov::Map2DPtr data{}; // Map tile data std::tuple pov{0, 0}; }; @@ -32,7 +36,7 @@ static auto load_map(std::string name, const std::filesystem::path& path) { std::ranges::max(lines | std::views::transform([](const auto& line) { return gsl::narrow(line.size()); })); auto map = MapInfo{ .name = std::move(name), - .data = tcod::fov::MapPtr_{TCODFOV_map_new(map_width, map_height)}, + .data = tcod::fov::Map2DPtr{TCODFOV_map2d_new_bitpacked(map_width, map_height)}, }; for (int y = 0; y < gsl::narrow(lines.size()); ++y) { const auto& line = lines.at(y); @@ -40,7 +44,7 @@ static auto load_map(std::string name, const std::filesystem::path& path) { static constexpr auto DEFAULT_CH = '.'; const auto ch = (x < gsl::narrow(line.size()) ? line.at(x) : DEFAULT_CH); const bool transparent = ch != '#'; - TCODFOV_map_set_properties(map.data.get(), x, y, transparent, false); + TCODFOV_map2d_set_bool(map.data.get(), x, y, transparent); if (ch == '@') { map.pov = {x, y}; } @@ -49,30 +53,34 @@ static auto load_map(std::string name, const std::filesystem::path& path) { return map; } -static auto new_map_with_radius(int radius, bool start_transparent) -> tcod::fov::MapPtr_ { +static auto new_map_with_radius(int radius, bool start_transparent) -> tcod::fov::Map2DPtr { const int size = radius * 2 + 1; - tcod::fov::MapPtr_ map{TCODFOV_map_new(size, size)}; - TCODFOV_map_clear(map.get(), start_transparent, 0); + tcod::fov::Map2DPtr map{TCODFOV_map2d_new_bitpacked(size, size)}; + for (int y = 0; y < TCODFOV_map2d_get_height(map.get()); ++y) { + for (int x = 0; x < TCODFOV_map2d_get_height(map.get()); ++x) { + TCODFOV_map2d_set_bool(map.get(), x, y, start_transparent); + } + } return map; } -static auto new_empty_map(int radius) -> tcod::fov::MapPtr_ { return new_map_with_radius(radius, true); } -static auto new_opaque_map(int radius) -> tcod::fov::MapPtr_ { return new_map_with_radius(radius, false); } -static auto new_corridor_map(int radius) -> tcod::fov::MapPtr_ { - tcod::fov::MapPtr_ map{new_map_with_radius(radius, false)}; +static auto new_empty_map(int radius) -> tcod::fov::Map2DPtr { return new_map_with_radius(radius, true); } +static auto new_opaque_map(int radius) -> tcod::fov::Map2DPtr { return new_map_with_radius(radius, false); } +static auto new_corridor_map(int radius) -> tcod::fov::Map2DPtr { + tcod::fov::Map2DPtr map{new_map_with_radius(radius, false)}; for (int i = 0; i < radius * 2 + 1; ++i) { - TCODFOV_map_set_properties(map.get(), radius, i, true, true); - TCODFOV_map_set_properties(map.get(), i, radius, true, true); + TCODFOV_map2d_set_bool(map.get(), radius, i, true); + TCODFOV_map2d_set_bool(map.get(), i, radius, true); } return map; } -static auto new_forest_map(int radius) -> tcod::fov::MapPtr_ { +static auto new_forest_map(int radius) -> tcod::fov::Map2DPtr { // Forest map with 1 in 4 chance of a blocking tile. std::mt19937 rng(0); std::uniform_int_distribution chance(0, 3); - tcod::fov::MapPtr_ map{new_map_with_radius(radius, true)}; - for (int i = 0; i < map->nbcells; ++i) { - if (chance(rng) == 0) { - map->cells[i].transparent = false; + tcod::fov::Map2DPtr map{new_map_with_radius(radius, true)}; + for (int y = 0; y < TCODFOV_map2d_get_height(map.get()); ++y) { + for (int x = 0; x < TCODFOV_map2d_get_height(map.get()); ++x) { + if (chance(rng) == 0) TCODFOV_map2d_set_bool(map.get(), x, y, false); } } return map; @@ -102,25 +110,27 @@ TEST_CASE("FOV Benchmarks", "[.benchmark]") { }; for (auto& active_test : test_maps) { const std::string& map_name = active_test.name; - TCODFOV_Map* map = active_test.data.get(); + tcod::fov::Map2DPtr& map = active_test.data; + tcod::fov::Map2DPtr out = tcod::fov::Map2DPtr{ + TCODFOV_map2d_new_bitpacked(TCODFOV_map2d_get_width(map.get()), TCODFOV_map2d_get_height(map.get()))}; const auto [pov_x, pov_y] = active_test.pov; BENCHMARK(map_name + " TCODFOV_BASIC") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_BASIC); + (void)!TCODFOV_map_compute_fov_circular_raycasting(map.get(), out.get(), pov_x, pov_y, 0, true); }; BENCHMARK(map_name + " TCODFOV_DIAMOND") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_DIAMOND); + (void)!TCODFOV_map_compute_fov_diamond_raycasting(map.get(), out.get(), pov_x, pov_y, 0, true); }; BENCHMARK(map_name + " TCODFOV_SHADOW") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_SHADOW); + (void)!TCODFOV_map_compute_fov_recursive_shadowcasting(map.get(), out.get(), pov_x, pov_y, 0, true); }; BENCHMARK(map_name + " TCODFOV_RESTRICTIVE") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_RESTRICTIVE); + (void)!TCODFOV_map_compute_fov_restrictive_shadowcasting(map.get(), out.get(), pov_x, pov_y, 0, true); }; BENCHMARK(map_name + " TCODFOV_PERMISSIVE_8") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_PERMISSIVE_8); + (void)!TCODFOV_map_compute_fov_permissive2(map.get(), out.get(), pov_x, pov_y, 0, true, 8); }; BENCHMARK(map_name + " TCODFOV_SYMMETRIC_SHADOWCAST") { - (void)!TCODFOV_map_compute_fov(map, pov_x, pov_y, 0, true, TCODFOV_SYMMETRIC_SHADOWCAST); + (void)!TCODFOV_map_compute_fov_symmetric_shadowcast(map.get(), out.get(), pov_x, pov_y, 0, true); }; } }