diff --git a/demos/2DDungeon/main.cpp b/demos/2DDungeon/main.cpp index 0fbbe29..59d8856 100644 --- a/demos/2DDungeon/main.cpp +++ b/demos/2DDungeon/main.cpp @@ -4,10 +4,11 @@ #include // Tile types for the dungeon +// Values start at 1 so default-initialized cells (0) don't match any tile enum class Tile : int { - Empty = 0, - Wall = 1, - Floor = 2 + Empty = 1, + Wall = 2, + Floor = 3 }; constexpr size_t DungeonWidth = 16; @@ -15,6 +16,13 @@ constexpr size_t DungeonHeight = 16; using World = WFC::Array2D; +// Concrete types needed for constrainer lambda signatures +using IDMap = WFC::VariableIDMap; +constexpr size_t WorldSize = DungeonWidth * DungeonHeight; +using WaveT = WFC::Wave; +using QueueT = WFC::WFCQueue; +using ConstrainerT = WFC::Constrainer; + void printDungeon(const World& world) { for (size_t y = 0; y < DungeonHeight; ++y) { for (size_t x = 0; x < DungeonWidth; ++x) { @@ -33,33 +41,36 @@ int main() { std::cout << "Dungeon size: " << DungeonWidth << "x" << DungeonHeight << "\n\n"; using DungeonBuilder = WFC::Builder - ::DefineIDs - ::Constrain val, auto& constrainer) constexpr { + ::DefineIDs + ::Variable + ::Constrain val, ConstrainerT& constrainer) { auto [x, y] = world.getCoord(index); // floor cannot be adjacent to empty space - constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, -1, 0)); // Left - constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 1, 0)); // Right - constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, -1)); // Up - constrainer.Exclude(Tile::Empty, world.getCoordOffset(x, y, 0, 1)); // Down + 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 })> - ::DefineIDs ::SetInitialState(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.Exclude(world.getId({0, y}), Tile::Wall); - constrainer.Exclude(world.getId({world.width() - 1, y}), Tile::Wall); + constrainer.template Exclude(world.getId({0, static_cast(y)})); + constrainer.template Exclude(world.getId({static_cast(world.width() - 1), static_cast(y)})); } })> ::Build; World world{}; - DungeonBuilder::Solve(world, 0xDEADBEEF); + bool success = WFC::Run(world, 0xDEADBEEF); + if (!success) { + std::cout << "WFC solver failed!\n"; + } printDungeon(world); return 0; diff --git a/include/nd-wfc/wfc_bit_container.hpp b/include/nd-wfc/wfc_bit_container.hpp index 1ba5df6..f21257d 100644 --- a/include/nd-wfc/wfc_bit_container.hpp +++ b/include/nd-wfc/wfc_bit_container.hpp @@ -142,28 +142,31 @@ public: public: // Sub byte struct SubTypeAccess { - constexpr SubTypeAccess(uint8_t& data, uint8_t subIndex) : Data{ data }, Shift{ StorageBits * subIndex } {}; - + constexpr SubTypeAccess(uint8_t& data, uint8_t subIndex) : Data{ data }, Shift{ static_cast(StorageBits * subIndex) } {}; + constexpr uint8_t GetValue() const { return ((Data >> Shift) & Mask); } - constexpr uint8_t SetValue(uint8_t val) { Clear(); return Data |= ((val & Mask) << Shift); } - constexpr void Clear() { Data &= ~Mask; } + constexpr uint8_t SetValue(uint8_t val) { Clear(); Data |= ((val & Mask) << Shift); return GetValue(); } + constexpr void Clear() { Data &= ~(static_cast(Mask) << Shift); } - constexpr SubTypeAccess& operator=(uint8_t other) { return SetValue(other); } + constexpr SubTypeAccess& operator=(uint8_t other) { SetValue(other); return *this; } constexpr operator uint8_t() const { return GetValue(); } - constexpr SubTypeAccess& operator&=(uint8_t other) { return SetValue(GetValue() & other); } - constexpr SubTypeAccess& operator|=(uint8_t other) { return SetValue(GetValue() | other); } - constexpr SubTypeAccess& operator^=(uint8_t other) { return SetValue(GetValue() ^ other); } - constexpr SubTypeAccess& operator<<=(uint8_t other) { return SetValue(GetValue() << other); } - constexpr SubTypeAccess& operator>>=(uint8_t other) { return SetValue(GetValue() >> other); } + constexpr SubTypeAccess& operator&=(uint8_t other) { SetValue(GetValue() & other); return *this; } + constexpr SubTypeAccess& operator|=(uint8_t other) { SetValue(GetValue() | other); return *this; } + constexpr SubTypeAccess& operator^=(uint8_t other) { SetValue(GetValue() ^ other); return *this; } + constexpr SubTypeAccess& operator<<=(uint8_t other) { SetValue(GetValue() << other); return *this; } + constexpr SubTypeAccess& operator>>=(uint8_t other) { SetValue(GetValue() >> other); return *this; } uint8_t& Data; uint8_t Shift; }; - constexpr const SubTypeAccess operator[](size_t index) const requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; } - constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], index & ElementsPerByte }; } + constexpr StorageType operator[](size_t index) const requires(IsSubByte) { + uint8_t shift = static_cast(StorageBits * (index % ElementsPerByte)); + return (data()[index / ElementsPerByte] >> shift) & static_cast(Mask); + } + constexpr SubTypeAccess operator[](size_t index) requires(IsSubByte) { return SubTypeAccess{data()[index / ElementsPerByte], static_cast(index % ElementsPerByte) }; } public: // default constexpr const StorageType& operator[](size_t index) const requires(!IsSubByte) { return data()[index]; } diff --git a/include/nd-wfc/wfc_constrainer.hpp b/include/nd-wfc/wfc_constrainer.hpp index 8c9a737..8372d07 100644 --- a/include/nd-wfc/wfc_constrainer.hpp +++ b/include/nd-wfc/wfc_constrainer.hpp @@ -8,7 +8,11 @@ namespace WFC { template struct EmptyConstrainerFunction { + static void invoke(WorldT&, WorldSizeT, WorldValue, ConstainerType&) {} void operator()(WorldT&, WorldSizeT, WorldValue, ConstainerType&) const {} + + using FuncPtrType = void(*)(WorldT&, WorldSizeT, WorldValue, ConstainerType&); + operator FuncPtrType() const { return &invoke; } }; template diff --git a/include/nd-wfc/wfc_variable_map.hpp b/include/nd-wfc/wfc_variable_map.hpp index 267e95e..ed705b5 100644 --- a/include/nd-wfc/wfc_variable_map.hpp +++ b/include/nd-wfc/wfc_variable_map.hpp @@ -61,7 +61,8 @@ public: static constexpr VarT GetValue(size_t index) { constexpr_assert(index < size()); - return GetAllValues()[index]; + constexpr VarT arr[] = {Values...}; + return arr[index]; } static consteval size_t size() { return sizeof...(Values); } diff --git a/include/nd-wfc/wfc_wave.hpp b/include/nd-wfc/wfc_wave.hpp index 5e146eb..06b25c6 100644 --- a/include/nd-wfc/wfc_wave.hpp +++ b/include/nd-wfc/wfc_wave.hpp @@ -18,9 +18,9 @@ public: public: Wave() = default; - Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, allocator) + Wave(size_t size, size_t variableAmount, WFCStackAllocator& allocator) : m_data(size, allocator) { - for (auto& wave : m_data) wave = (1 << variableAmount) - 1; + for (size_t i = 0; i < m_data.size(); ++i) m_data[i] = (1 << variableAmount) - 1; } Wave(const Wave& other) = default; @@ -30,8 +30,16 @@ public: 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; } - bool IsFullyCollapsed() const { return std::all_of(m_data.begin(), m_data.end(), [](ElementT value) { return std::popcount(value) == 1; }); } - bool HasContradiction() const { return std::any_of(m_data.begin(), m_data.end(), [](ElementT value) { return value == 0; }); } + bool IsFullyCollapsed() const { + for (size_t i = 0; i < m_data.size(); ++i) + if (std::popcount(static_cast(m_data[i])) != 1) return false; + return true; + } + bool HasContradiction() const { + for (size_t i = 0; i < m_data.size(); ++i) + if (static_cast(m_data[i]) == 0) return true; + return false; + } bool IsContradicted(size_t index) const { return m_data[index] == 0; } uint16_t GetVariableID(size_t index) const { return static_cast(std::countr_zero(m_data[index])); } ElementT GetMask(size_t index) const { return m_data[index]; }