diff --git a/demos/2DDungeon/main.cpp b/demos/2DDungeon/main.cpp index 59d8856..21dada5 100644 --- a/demos/2DDungeon/main.cpp +++ b/demos/2DDungeon/main.cpp @@ -47,27 +47,39 @@ int main() { auto [x, y] = world.getCoord(index); + // enable walls in 3x3 area around floor (without center) + // must come before Exclude to avoid collapsing then un-collapsing cells + for (int dy = -1; dy <= 1; ++dy) { + for (int dx = -1; dx <= 1; ++dx) { + if (dx == 0 && dy == 0) continue; + constrainer.template Include(world.getCoordOffset(x, y, dx, dy)); + } + } + // floor cannot be adjacent to empty space constrainer.template Exclude(world.getCoordOffset(x, y, -1, 0)); // Left constrainer.template Exclude(world.getCoordOffset(x, y, 1, 0)); // Right constrainer.template Exclude(world.getCoordOffset(x, y, 0, -1)); // Up constrainer.template Exclude(world.getCoordOffset(x, y, 0, 1)); // Down })> - ::SetInitialState(i); + } // make it impossible for the edge to be floor for (size_t x = 0; x < world.width(); ++x) { constrainer.template Exclude(world.getId({static_cast(x), 0})); constrainer.template Exclude(world.getId({static_cast(x), static_cast(world.height() - 1)})); } - for (size_t y = 0; y < world.height(); ++y) { - constrainer.template Exclude(world.getId({0, static_cast(y)})); - constrainer.template Exclude(world.getId({static_cast(world.width() - 1), static_cast(y)})); - } + // seed floor tiles to kick-start dungeon generation + constrainer.template Only(world.getId({2, 2})); })> + ::SetRandomSelector> ::Build; World world{}; - bool success = WFC::Run(world, 0xDEADBEEF); + bool success = WFC::Run(world, std::random_device{}()); if (!success) { std::cout << "WFC solver failed!\n"; } diff --git a/include/nd-wfc/wfc_constrainer.hpp b/include/nd-wfc/wfc_constrainer.hpp index 8372d07..8137dae 100644 --- a/include/nd-wfc/wfc_constrainer.hpp +++ b/include/nd-wfc/wfc_constrainer.hpp @@ -117,6 +117,23 @@ public: ApplyMask(cellId, 1 << value.InternalIndex); } + /** + * @brief Re-enable specific values for a cell (OR bits back in) + * @param cellId The ID of the cell to modify + */ + template + void Include(size_t cellId) { + static_assert(sizeof...(IncludedValues) > 0, "At least one included value must be provided"); + if (m_wave.IsCollapsed(cellId)) return; // don't un-collapse decided cells + auto indices = IDMapT::template ValuesToIndices(); + m_wave.Enable(cellId, BitContainerT::GetMask(indices)); + } + + void Include(WorldValue value, size_t cellId) { + if (m_wave.IsCollapsed(cellId)) return; + m_wave.Enable(cellId, 1 << value.InternalIndex); + } + private: void ApplyMask(size_t cellId, MaskType mask) { bool wasCollapsed = m_wave.IsCollapsed(cellId); diff --git a/include/nd-wfc/wfc_random.hpp b/include/nd-wfc/wfc_random.hpp index 5876580..e00d34f 100644 --- a/include/nd-wfc/wfc_random.hpp +++ b/include/nd-wfc/wfc_random.hpp @@ -1,5 +1,7 @@ #pragma once +#include + namespace WFC { /** @@ -27,14 +29,14 @@ public: template class AdvancedRandomSelector { private: - std::mt19937& m_rng; + mutable std::mt19937 m_rng; public: - explicit AdvancedRandomSelector(std::mt19937& rng) : m_rng(rng) {} + explicit AdvancedRandomSelector(uint32_t seed = 0x12345678) : m_rng(seed) {} - uint32_t rng(uint32_t max) const { - std::uniform_int_distribution dist(0, max); - return dist(m_rng); + uint32_t rng(uint32_t max) const { + std::uniform_int_distribution dist(0, max - 1); + return dist(m_rng); } }; diff --git a/include/nd-wfc/wfc_wave.hpp b/include/nd-wfc/wfc_wave.hpp index 06b25c6..9247982 100644 --- a/include/nd-wfc/wfc_wave.hpp +++ b/include/nd-wfc/wfc_wave.hpp @@ -27,6 +27,7 @@ public: public: void Collapse(size_t index, ElementT mask) { m_data[index] &= mask; } + void Enable(size_t index, ElementT mask) { m_data[index] |= mask; } size_t size() const { return m_data.size(); } size_t Entropy(size_t index) const { return std::popcount(m_data[index]); } bool IsCollapsed(size_t index) const { return Entropy(index) == 1; }