diff --git a/modules/wfc/array_2d.h b/modules/wfc/array_2d.h index def37d558..7e8a1154e 100644 --- a/modules/wfc/array_2d.h +++ b/modules/wfc/array_2d.h @@ -11,6 +11,11 @@ public: Vector data; + Array2D() { + height = 0; + width = 0; + } + Array2D(uint32_t p_height, uint32_t p_width) { height = p_height; width = p_width; @@ -24,6 +29,19 @@ public: data.fill(p_value); } + void resize(uint32_t p_height, uint32_t p_width) { + height = p_height; + width = p_width; + data.resize(width * height); + } + + void resize_fill(uint32_t p_height, uint32_t p_width, T p_value) { + height = p_height; + width = p_width; + data.resize(width * height); + data.fill(p_value); + } + const T &get(uint32_t i, uint32_t j) const { CRASH_BAD_INDEX(i, height); CRASH_BAD_INDEX(j, width); @@ -34,7 +52,7 @@ public: T &get(uint32_t i, uint32_t j) { CRASH_BAD_INDEX(i, height); CRASH_BAD_INDEX(j, width); - + return data.write[j + i * width]; } diff --git a/modules/wfc/array_3d.h b/modules/wfc/array_3d.h index c25011795..9d39f602a 100644 --- a/modules/wfc/array_3d.h +++ b/modules/wfc/array_3d.h @@ -12,6 +12,12 @@ public: Vector data; + Array3D() { + height = 0; + width = 0; + depth = 0; + } + Array3D(uint32_t p_height, uint32_t p_width, uint32_t p_depth) { height = p_height; width = p_width; @@ -27,6 +33,21 @@ public: data.fill(value); } + void resize(uint32_t p_height, uint32_t p_width, uint32_t p_depth) { + height = p_height; + width = p_width; + depth = p_depth; + data.resize(width * height * depth); + } + + void resize_fill(uint32_t p_height, uint32_t p_width, uint32_t p_depth, T value) { + height = p_height; + width = p_width; + depth = p_depth; + data.resize(width * height * depth); + data.fill(value); + } + const T &get(uint32_t i, uint32_t j, uint32_t k) const { CRASH_BAD_INDEX(i, height); CRASH_BAD_INDEX(j, width); diff --git a/modules/wfc/overlapping_wfc.h b/modules/wfc/overlapping_wfc.h index dff3ed269..c80749557 100644 --- a/modules/wfc/overlapping_wfc.h +++ b/modules/wfc/overlapping_wfc.h @@ -46,7 +46,7 @@ private: const Array2D &input, const OverlappingWFCOptions &options, const int &seed, const std::pair>, Vector> &patterns, - const Vector &propagator) : + const Vector &propagator) : input(input), options(options), patterns(patterns.first), wfc(options.periodic_output, seed, patterns.second, propagator, options.get_wave_height(), options.get_wave_width()) { // If necessary, the ground is set. if (options.ground) { @@ -159,8 +159,8 @@ private: // If agrees(pattern1, pattern2, dy, dx), then compatible[pattern1][direction] // contains pattern2, where direction is the direction defined by (dy, dx) // (see direction.hpp). - static Vector generate_compatible(const Vector> &patterns) { - Vector compatible; + static Vector generate_compatible(const Vector> &patterns) { + Vector compatible; compatible.resize(patterns.size()); // Iterate on every dy, dx, pattern1 and pattern2 diff --git a/modules/wfc/propagator.cpp b/modules/wfc/propagator.cpp deleted file mode 100644 index db8615ffb..000000000 --- a/modules/wfc/propagator.cpp +++ /dev/null @@ -1,80 +0,0 @@ -#include "propagator.h" -#include "wave.h" - -void Propagator::init_compatible() { - CompatibilityEntry value; - // We compute the number of pattern compatible in all directions. - for (uint32_t y = 0; y < wave_height; y++) { - for (uint32_t x = 0; x < wave_width; x++) { - for (uint32_t pattern = 0; pattern < patterns_size; pattern++) { - for (int direction = 0; direction < 4; direction++) { - value.direction[direction] = static_cast(propagator_state[pattern].directions[get_opposite_direction(direction)].size()); - } - - compatible.get(y, x, pattern) = value; - } - } - } -} - -void Propagator::propagate(Wave &wave) { - // We propagate every element while there is element to propagate. - while (propagating.size() != 0) { - // The cell and pattern that has been set to false. - - const PropagatingEntry &e = propagating[propagating.size() - 1]; - - uint32_t y1 = e.data[0]; - uint32_t x1 = e.data[1]; - uint32_t pattern = e.data[2]; - - propagating.resize(propagating.size() - 1); - - // We propagate the information in all 4 directions. - for (uint32_t direction = 0; direction < 4; direction++) { - // We get the next cell in the direction direction. - int dx = directions_x[direction]; - int dy = directions_y[direction]; - int x2, y2; - if (periodic_output) { - x2 = ((int)x1 + dx + (int)wave.width) % wave.width; - y2 = ((int)y1 + dy + (int)wave.height) % wave.height; - } else { - x2 = x1 + dx; - y2 = y1 + dy; - - if (x2 < 0 || x2 >= (int)wave.width) { - continue; - } - - if (y2 < 0 || y2 >= (int)wave.height) { - continue; - } - } - - // The index of the second cell, and the patterns compatible - uint32_t i2 = x2 + y2 * wave.width; - const Vector &patterns = propagator_state[pattern].directions[direction]; - - // For every pattern that could be placed in that cell without being in - // contradiction with pattern1 - int size = patterns.size(); - for (int i = 0; i < size; ++i) { - uint32_t pattern_entry = patterns[i]; - - // We decrease the number of compatible patterns in the opposite - // direction If the pattern was discarded from the wave, the element - // is still negative, which is not a problem - CompatibilityEntry &value = compatible.get(y2, x2, pattern_entry); - value.direction[direction]--; - - // If the element was set to 0 with this operation, we need to remove - // the pattern from the wave, and propagate the information - if (value.direction[direction] == 0) { - add_to_propagator(y2, x2, pattern_entry); - wave.set(i2, pattern_entry, false); - } - } - } - } -} diff --git a/modules/wfc/propagator.h b/modules/wfc/propagator.h deleted file mode 100644 index 3673016c5..000000000 --- a/modules/wfc/propagator.h +++ /dev/null @@ -1,88 +0,0 @@ -#ifndef FAST_WFC_PROPAGATOR_HPP_ -#define FAST_WFC_PROPAGATOR_HPP_ - -#include "array_3d.h" -#include "core/vector.h" -#include "direction.h" - -class Wave; - -class Propagator { -public: - struct PropagatorEntry { - Vector directions[4]; - }; - -private: - const uint32_t patterns_size; - - Vector propagator_state; - - const uint32_t wave_width; - const uint32_t wave_height; - - const bool periodic_output; - - struct PropagatingEntry { - uint32_t data[3]; - - PropagatingEntry() { - for (int i = 0; i < 3; ++i) { - data[i] = 0; - } - } - - PropagatingEntry(uint32_t x, uint32_t y, uint32_t z) { - data[0] = x; - data[1] = y; - data[2] = z; - } - }; - - // All the tuples (y, x, pattern) that should be propagated. - // The tuple should be propagated when wave.get(y, x, pattern) is set to - // false. - Vector propagating; - - struct CompatibilityEntry { - int direction[4]; - - CompatibilityEntry() { - for (int i = 0; i < 4; ++i) { - direction[i] = 0; - } - } - }; - - // compatible.get(y, x, pattern)[direction] contains the number of patterns - // present in the wave that can be placed in the cell next to (y,x) in the - // opposite direction of direction without being in contradiction with pattern - // placed in (y,x). If wave.get(y, x, pattern) is set to false, then - // compatible.get(y, x, pattern) has every element negative or null - Array3D compatible; - - void init_compatible(); - -public: - Propagator(uint32_t wave_height, uint32_t wave_width, bool periodic_output, Vector propagator_state) : - patterns_size(propagator_state.size()), - propagator_state(propagator_state), - wave_width(wave_width), - wave_height(wave_height), - periodic_output(periodic_output), - compatible(wave_height, wave_width, patterns_size) { - init_compatible(); - } - - void add_to_propagator(uint32_t y, uint32_t x, uint32_t pattern) { - // All the direction are set to 0, since the pattern cannot be set in (y,x). - CompatibilityEntry temp; - compatible.get(y, x, pattern) = temp; - - propagating.push_back(PropagatingEntry(y, x, pattern)); - } - - void propagate(Wave &wave); -}; - -#endif // FAST_WFC_PROPAGATOR_HPP_ diff --git a/modules/wfc/tiling_wfc.h b/modules/wfc/tiling_wfc.h index f785b2f21..78225fb17 100644 --- a/modules/wfc/tiling_wfc.h +++ b/modules/wfc/tiling_wfc.h @@ -208,7 +208,7 @@ private: }; // Generate the propagator which will be used in the wfc algorithm. - static Vector generate_propagator( + static Vector generate_propagator( const Vector &neighbors, Vector> tiles, Vector> id_to_oriented_tile, @@ -250,7 +250,7 @@ private: add(7, 0); } - Vector propagator(nb_oriented_tiles); + Vector propagator(nb_oriented_tiles); for (size_t i = 0; i < nb_oriented_tiles; ++i) { for (size_t j = 0; j < nb_oriented_tiles; ++j) { diff --git a/modules/wfc/wave.cpp b/modules/wfc/wave.cpp deleted file mode 100644 index 5d39ad046..000000000 --- a/modules/wfc/wave.cpp +++ /dev/null @@ -1,132 +0,0 @@ -#include "wave.h" - -#include - -namespace { - -// Return distribution * log(distribution). -Vector get_plogp(const Vector &distribution) { - Vector plogp; - - for (int i = 0; i < distribution.size(); i++) { - plogp.push_back(distribution[i] * log(distribution[i])); - } - - return plogp; -} - -// Return min(v) / 2. -double get_min_abs_half(const Vector &v) { - double min_abs_half = std::numeric_limits::infinity(); - - for (int i = 0; i < v.size(); i++) { - min_abs_half = std::min(min_abs_half, std::abs(v[i] / 2.0)); - } - - return min_abs_half; -} - -} // namespace - -Wave::Wave(uint32_t height, uint32_t width, - const Vector &patterns_frequencies) : - patterns_frequencies(patterns_frequencies), - plogp_patterns_frequencies(get_plogp(patterns_frequencies)), - min_abs_half_plogp(get_min_abs_half(plogp_patterns_frequencies)), - is_impossible(false), - nb_patterns(patterns_frequencies.size()), - data(width * height, nb_patterns, 1), - width(width), - height(height), - size(height * width) { - - // Initialize the memoisation of entropy. - double base_entropy = 0; - double base_s = 0; - - for (uint32_t i = 0; i < nb_patterns; i++) { - base_entropy += plogp_patterns_frequencies[i]; - base_s += patterns_frequencies[i]; - } - - double log_base_s = log(base_s); - double entropy_base = log_base_s - base_entropy / base_s; - - memoisation_plogp_sum.resize(width * height); - memoisation_plogp_sum.fill(base_entropy); - - memoisation_sum.resize(width * height); - memoisation_sum.fill(base_s); - - memoisation_log_sum.resize(width * height); - memoisation_log_sum.fill(log_base_s); - - memoisation_nb_patterns.resize(width * height); - memoisation_nb_patterns.fill(static_cast(nb_patterns)); - - memoisation_entropy.resize(width * height); - memoisation_entropy.fill(entropy_base); -} - -void Wave::set(uint32_t index, uint32_t pattern, bool value) { - bool old_value = data.get(index, pattern); - - // If the value isn't changed, nothing needs to be done. - if (old_value == value) { - return; - } - - // Otherwise, the memoisation should be updated. - data.get(index, pattern) = value; - memoisation_plogp_sum.write[index] -= plogp_patterns_frequencies[pattern]; - memoisation_sum.write[index] -= patterns_frequencies[pattern]; - memoisation_log_sum.write[index] = log(memoisation_sum[index]); - memoisation_nb_patterns.write[index]--; - memoisation_entropy.write[index] = memoisation_log_sum[index] - memoisation_plogp_sum[index] / memoisation_sum[index]; - - // If there is no patterns possible in the cell, then there is a contradiction. - if (memoisation_nb_patterns[index] == 0) { - is_impossible = true; - } -} - -int Wave::get_min_entropy(std::minstd_rand &gen) const { - if (is_impossible) { - return -2; - } - - std::uniform_real_distribution<> dis(0, min_abs_half_plogp); - - // The minimum entropy (plus a small noise) - double min = std::numeric_limits::infinity(); - int argmin = -1; - - for (uint32_t i = 0; i < size; i++) { - // If the cell is decided, we do not compute the entropy (which is equal - // to 0). - double nb_patterns_local = memoisation_nb_patterns[i]; - - if (nb_patterns_local == 1) { - continue; - } - - // Otherwise, we take the memoised entropy. - double entropy = memoisation_entropy[i]; - - // We first check if the entropy is less than the minimum. - // This is important to reduce noise computation (which is not - // negligible). - if (entropy <= min) { - // Then, we add noise to decide randomly which will be chosen. - // noise is smaller than the smallest p * log(p), so the minimum entropy - // will always be chosen. - double noise = dis(gen); - if (entropy + noise < min) { - min = entropy + noise; - argmin = i; - } - } - } - - return argmin; -} diff --git a/modules/wfc/wave.h b/modules/wfc/wave.h deleted file mode 100644 index b1dba6db9..000000000 --- a/modules/wfc/wave.h +++ /dev/null @@ -1,71 +0,0 @@ -#ifndef FAST_WFC_WAVE_HPP_ -#define FAST_WFC_WAVE_HPP_ - -#include "array_2d.h" -#include -#include "core/vector.h" - -// Contains the pattern possibilities in every cell. -// Also contains information about cell entropy. -class Wave { -private: - // The patterns frequencies p given to wfc. - const Vector patterns_frequencies; - - // The precomputation of p * log(p). - const Vector plogp_patterns_frequencies; - - // The precomputation of min (p * log(p)) / 2. - // This is used to define the maximum value of the noise. - const double min_abs_half_plogp; - - Vector memoisation_plogp_sum; // The sum of p'(pattern)// log(p'(pattern)). - Vector memoisation_sum; // The sum of p'(pattern). - Vector memoisation_log_sum; // The log of sum. - Vector memoisation_nb_patterns; // The number of patterns present - Vector memoisation_entropy; // The entropy of the cell. - - // This value is set to true if there is a contradiction in the wave (all elements set to false in a cell). - bool is_impossible; - - // The number of distinct patterns. - const size_t nb_patterns; - - // The actual wave. data.get(index, pattern) is equal to 0 if the pattern can - // be placed in the cell index. - Array2D data; - -public: - // The size of the wave. - const uint32_t width; - const uint32_t height; - const uint32_t size; - - // Initialize the wave with every cell being able to have every pattern. - Wave(uint32_t height, uint32_t width, const Vector &patterns_frequencies); - - // Return true if pattern can be placed in cell index. - bool get(uint32_t index, uint32_t pattern) const { - return data.get(index, pattern); - } - - // Return true if pattern can be placed in cell (i,j) - bool get(uint32_t i, uint32_t j, uint32_t pattern) const { - return get(i * width + j, pattern); - } - - // Set the value of pattern in cell index. - void set(uint32_t index, uint32_t pattern, bool value); - - // Set the value of pattern in cell (i,j). - void set(uint32_t i, uint32_t j, uint32_t pattern, bool value) { - set(i * width + j, pattern, value); - } - - // Return the index of the cell with lowest entropy different of 0. - // If there is a contradiction in the wave, return -2. - // If every cell is decided, return -1. - int get_min_entropy(std::minstd_rand &gen) const; -}; - -#endif // FAST_WFC_WAVE_HPP_ diff --git a/modules/wfc/wave_form_collapse.cpp b/modules/wfc/wave_form_collapse.cpp index 40089c9ca..c1ab63679 100644 --- a/modules/wfc/wave_form_collapse.cpp +++ b/modules/wfc/wave_form_collapse.cpp @@ -4,7 +4,7 @@ namespace { // Normalize a vector so the sum of its elements is equal to 1.0f -Vector &normalize(Vector &v) { +void normalize(Vector &v) { double sum_weights = 0.0; int size = v.size(); const double *vpr = v.ptr(); @@ -17,31 +17,59 @@ Vector &normalize(Vector &v) { for (int i = 0; i < size; ++i) { vpw[i] *= inv_sum_weights; } - - return v; } + +// Return distribution * log(distribution). +Vector get_plogp(const Vector &distribution) { + Vector plogp; + + for (int i = 0; i < distribution.size(); i++) { + plogp.push_back(distribution[i] * log(distribution[i])); + } + + return plogp; +} + +// Return min(v) / 2. +double get_min_abs_half(const Vector &v) { + double min_abs_half = std::numeric_limits::infinity(); + + for (int i = 0; i < v.size(); i++) { + min_abs_half = std::min(min_abs_half, std::abs(v[i] / 2.0)); + } + + return min_abs_half; +} + } //namespace -Array2D WaveFormCollapse::wave_to_output() const { - Array2D output_patterns(wave.height, wave.width); - - for (uint32_t i = 0; i < wave.size; i++) { - for (uint32_t k = 0; k < nb_patterns; k++) { - if (wave.get(i, k)) { - output_patterns.data.write[i] = k; - } - } - } - return output_patterns; +bool WaveFormCollapse::get_eriodic_output() const { + return is_impossible; +} +void WaveFormCollapse::set_periodic_output(const bool val) { + is_impossible = val; } -//WaveFormCollapse::WaveFormCollapse() { -//} +void WaveFormCollapse::set_seed(const int seed) { + gen.seed(seed); +} -WaveFormCollapse::WaveFormCollapse(bool periodic_output, int seed, - Vector patterns_frequencies, - Vector propagator, uint32_t wave_height, uint32_t wave_width) : - gen(seed), patterns_frequencies(normalize(patterns_frequencies)), wave(wave_height, wave_width, patterns_frequencies), nb_patterns(propagator.size()), propagator(wave.height, wave.width, periodic_output, propagator) { +void WaveFormCollapse::set_size(uint32_t p_width, uint32_t p_height) { + wave_width = p_width; + wave_height = p_height; + wave_size = p_height * p_width; +} + +void WaveFormCollapse::set_propagator_state(const Vector &p_propagator_state) { + propagator_state = p_propagator_state; +} + +void WaveFormCollapse::set_pattern_frequencies(const Vector &p_patterns_frequencies, const bool p_normalize) { + patterns_frequencies = p_patterns_frequencies; + + if (p_normalize) { + normalize(patterns_frequencies); + } } Array2D WaveFormCollapse::run() { @@ -56,14 +84,13 @@ Array2D WaveFormCollapse::run() { return wave_to_output(); } - // Propagate the information. - propagator.propagate(wave); + propagate(); } } WaveFormCollapse::ObserveStatus WaveFormCollapse::observe() { // Get the cell with lowest entropy. - int argmin = wave.get_min_entropy(gen); + int argmin = wave_get_min_entropy(); // If there is a contradiction, the algorithm has failed. if (argmin == -2) { @@ -79,16 +106,16 @@ WaveFormCollapse::ObserveStatus WaveFormCollapse::observe() { // Choose an element according to the pattern distribution double s = 0; - for (uint32_t k = 0; k < nb_patterns; k++) { - s += wave.get(argmin, k) ? patterns_frequencies[k] : 0; + for (int k = 0; k < patterns_frequencies.size(); k++) { + s += wave_get(argmin, k) ? patterns_frequencies[k] : 0; } - std::uniform_real_distribution<> dis(0, s); - double random_value = dis(gen); - size_t chosen_value = nb_patterns - 1; + double random_value = gen.random(0.0, s); - for (uint32_t k = 0; k < nb_patterns; k++) { - random_value -= wave.get(argmin, k) ? patterns_frequencies[k] : 0; + size_t chosen_value = patterns_frequencies.size() - 1; + + for (int k = 0; k < patterns_frequencies.size(); k++) { + random_value -= wave_get(argmin, k) ? patterns_frequencies[k] : 0; if (random_value <= 0) { chosen_value = k; break; @@ -96,15 +123,224 @@ WaveFormCollapse::ObserveStatus WaveFormCollapse::observe() { } // And define the cell with the pattern. - for (uint32_t k = 0; k < nb_patterns; k++) { - if (wave.get(argmin, k) != (k == chosen_value)) { - propagator.add_to_propagator(argmin / wave.width, argmin % wave.width, k); - wave.set(argmin, k, false); + for (int k = 0; k < patterns_frequencies.size(); k++) { + if (wave_get(argmin, k) != (k == chosen_value)) { + add_to_propagator(argmin / wave_width, argmin % wave_width, k); + wave_set(argmin, k, false); } } return OBSERVE_STATUS_TO_CONTINUE; } +Array2D WaveFormCollapse::wave_to_output() const { + Array2D output_patterns(wave_height, wave_width); + + for (uint32_t i = 0; i < wave_size; i++) { + for (int k = 0; k < patterns_frequencies.size(); k++) { + if (wave_get(i, k)) { + output_patterns.data.write[i] = k; + } + } + } + + return output_patterns; +} + +void WaveFormCollapse::wave_set(uint32_t index, uint32_t pattern, bool value) { + bool old_value = data.get(index, pattern); + + // If the value isn't changed, nothing needs to be done. + if (old_value == value) { + return; + } + + // Otherwise, the memoisation should be updated. + data.get(index, pattern) = value; + + memoisation_plogp_sum.write[index] -= plogp_patterns_frequencies[pattern]; + memoisation_sum.write[index] -= patterns_frequencies[pattern]; + memoisation_log_sum.write[index] = log(memoisation_sum[index]); + memoisation_nb_patterns.write[index]--; + memoisation_entropy.write[index] = memoisation_log_sum[index] - memoisation_plogp_sum[index] / memoisation_sum[index]; + + // If there is no patterns possible in the cell, then there is a contradiction. + if (memoisation_nb_patterns[index] == 0) { + is_impossible = true; + } +} + +int WaveFormCollapse::wave_get_min_entropy() const { + if (is_impossible) { + return -2; + } + + RandomPCG pcg; + + // The minimum entropy (plus a small noise) + double min = std::numeric_limits::infinity(); + int argmin = -1; + + for (uint32_t i = 0; i < wave_size; i++) { + // If the cell is decided, we do not compute the entropy (which is equal + // to 0). + double nb_patterns_local = memoisation_nb_patterns[i]; + + if (nb_patterns_local == 1) { + continue; + } + + // Otherwise, we take the memoised entropy. + double entropy = memoisation_entropy[i]; + + // We first check if the entropy is less than the minimum. + // This is important to reduce noise computation (which is not + // negligible). + if (entropy <= min) { + // Then, we add noise to decide randomly which will be chosen. + // noise is smaller than the smallest p * log(p), so the minimum entropy + // will always be chosen. + double noise = pcg.random(0.0, min_abs_half_plogp); + if (entropy + noise < min) { + min = entropy + noise; + argmin = i; + } + } + } + + return argmin; +} + +void WaveFormCollapse::init_compatible() { + CompatibilityEntry value; + + // We compute the number of pattern compatible in all directions. + for (uint32_t y = 0; y < wave_height; y++) { + for (uint32_t x = 0; x < wave_width; x++) { + for (int pattern = 0; pattern < propagator_state.size(); pattern++) { + for (int direction = 0; direction < 4; direction++) { + value.direction[direction] = static_cast(propagator_state[pattern].directions[get_opposite_direction(direction)].size()); + } + + compatible.get(y, x, pattern) = value; + } + } + } +} + +void WaveFormCollapse::propagate() { + // We propagate every element while there is element to propagate. + while (propagating.size() != 0) { + // The cell and pattern that has been set to false. + + const PropagatingEntry &e = propagating[propagating.size() - 1]; + + uint32_t y1 = e.data[0]; + uint32_t x1 = e.data[1]; + uint32_t pattern = e.data[2]; + + propagating.resize(propagating.size() - 1); + + // We propagate the information in all 4 directions. + for (uint32_t direction = 0; direction < 4; direction++) { + // We get the next cell in the direction direction. + int dx = directions_x[direction]; + int dy = directions_y[direction]; + int x2, y2; + + if (periodic_output) { + x2 = ((int)x1 + dx + (int)wave_width) % wave_width; + y2 = ((int)y1 + dy + (int)wave_height) % wave_height; + } else { + x2 = x1 + dx; + y2 = y1 + dy; + + if (x2 < 0 || x2 >= (int)wave_width) { + continue; + } + + if (y2 < 0 || y2 >= (int)wave_height) { + continue; + } + } + + // The index of the second cell, and the patterns compatible + uint32_t i2 = x2 + y2 * wave_width; + const Vector &patterns = propagator_state[pattern].directions[direction]; + + // For every pattern that could be placed in that cell without being in + // contradiction with pattern1 + int size = patterns.size(); + for (int i = 0; i < size; ++i) { + uint32_t pattern_entry = patterns[i]; + + // We decrease the number of compatible patterns in the opposite + // direction If the pattern was discarded from the wave, the element + // is still negative, which is not a problem + CompatibilityEntry &value = compatible.get(y2, x2, pattern_entry); + value.direction[direction]--; + + // If the element was set to 0 with this operation, we need to remove + // the pattern from the wave, and propagate the information + if (value.direction[direction] == 0) { + add_to_propagator(y2, x2, pattern_entry); + wave_set(i2, pattern_entry, false); + } + } + } + } +} + +void WaveFormCollapse::initialize() { + //wave + data.resize_fill(wave_width * wave_height, patterns_frequencies.size(), 1); + + plogp_patterns_frequencies = get_plogp(patterns_frequencies); + min_abs_half_plogp = get_min_abs_half(plogp_patterns_frequencies); + + is_impossible = false; + + // Initialize the memoisation of entropy. + double base_entropy = 0; + double base_s = 0; + + for (uint32_t i = 0; i < patterns_frequencies.size(); i++) { + base_entropy += plogp_patterns_frequencies[i]; + base_s += patterns_frequencies[i]; + } + + double log_base_s = log(base_s); + double entropy_base = log_base_s - base_entropy / base_s; + + memoisation_plogp_sum.resize(wave_width * wave_height); + memoisation_plogp_sum.fill(base_entropy); + + memoisation_sum.resize(wave_width * wave_height); + memoisation_sum.fill(base_s); + + memoisation_log_sum.resize(wave_width * wave_height); + memoisation_log_sum.fill(log_base_s); + + memoisation_nb_patterns.resize(wave_width * wave_height); + memoisation_nb_patterns.fill(static_cast(patterns_frequencies.size())); + + memoisation_entropy.resize(wave_width * wave_height); + memoisation_entropy.fill(entropy_base); + + //propagator + compatible.resize(wave_height, wave_width, propagator_state.size()); + init_compatible(); +} + +WaveFormCollapse::WaveFormCollapse() { + //todo maybe it should be better as true? + periodic_output = false; + is_impossible = false; +} + +WaveFormCollapse::~WaveFormCollapse() { + +} + void WaveFormCollapse::bind_methods() { } \ No newline at end of file diff --git a/modules/wfc/wave_form_collapse.h b/modules/wfc/wave_form_collapse.h index 335c189c2..94af15df5 100644 --- a/modules/wfc/wave_form_collapse.h +++ b/modules/wfc/wave_form_collapse.h @@ -1,14 +1,16 @@ #ifndef WAVE_FORM_COLLAPSE_H #define WAVE_FORM_COLLAPSE_H -#include "core/reference.h" - -#include -#include +#include "core/int_types.h" +#include "core/math/random_pcg.h" #include "array_2d.h" -#include "propagator.h" -#include "wave.h" +#include "array_3d.h" +#include "core/vector.h" + +#include "core/reference.h" + +#include "direction.h" class WaveFormCollapse : public Reference { GDCLASS(WaveFormCollapse, Reference); @@ -20,43 +22,157 @@ public: OBSERVE_STATUS_TO_CONTINUE }; + struct PropagatorStateEntry { + Vector directions[4]; + }; + + struct PropagatingEntry { + uint32_t data[3]; + + PropagatingEntry() { + for (int i = 0; i < 3; ++i) { + data[i] = 0; + } + } + + PropagatingEntry(uint32_t x, uint32_t y, uint32_t z) { + data[0] = x; + data[1] = y; + data[2] = z; + } + }; + + struct CompatibilityEntry { + int direction[4]; + + CompatibilityEntry() { + for (int i = 0; i < 4; ++i) { + direction[i] = 0; + } + } + }; + +public: + bool get_eriodic_output() const; + void set_periodic_output(const bool val); + + void set_seed(const int seed); + + void set_size(uint32_t p_width, uint32_t p_height); + + void set_propagator_state(const Vector &p_propagator_state); + void set_pattern_frequencies(const Vector &p_patterns_frequencies, const bool p_normalize = true); + Array2D run(); ObserveStatus observe(); - void propagate() { propagator.propagate(wave); } + //dvoid propagate() { propagator.propagate(wave); } void remove_wave_pattern(uint32_t i, uint32_t j, uint32_t pattern) { - if (wave.get(i, j, pattern)) { - wave.set(i, j, pattern, false); - propagator.add_to_propagator(i, j, pattern); + if (wave_get(i, j, pattern)) { + wave_set(i, j, pattern, false); + add_to_propagator(i, j, pattern); } } + // Return true if pattern can be placed in cell index. + bool wave_get(uint32_t index, uint32_t pattern) const { + return data.get(index, pattern); + } + + // Return true if pattern can be placed in cell (i,j) + bool wave_get(uint32_t i, uint32_t j, uint32_t pattern) const { + return wave_get(i * wave_width + j, pattern); + } + + // Set the value of pattern in cell index. + void wave_set(uint32_t index, uint32_t pattern, bool value); + + // Set the value of pattern in cell (i,j). + void wave_set(uint32_t i, uint32_t j, uint32_t pattern, bool value) { + wave_set(i * wave_width + j, pattern, value); + } + + // Return the index of the cell with lowest entropy different of 0. + // If there is a contradiction in the wave, return -2. + // If every cell is decided, return -1. + int wave_get_min_entropy() const; + + void add_to_propagator(uint32_t y, uint32_t x, uint32_t pattern) { + // All the direction are set to 0, since the pattern cannot be set in (y,x). + CompatibilityEntry temp; + compatible.get(y, x, pattern) = temp; + + propagating.push_back(PropagatingEntry(y, x, pattern)); + } + + void propagate(); + + void initialize(); + WaveFormCollapse(); - WaveFormCollapse(bool periodic_output, int seed, Vector patterns_frequencies, - Vector propagator, uint32_t wave_height, - uint32_t wave_width); + ~WaveFormCollapse(); protected: static void bind_methods(); private: - std::minstd_rand gen; - - const Vector patterns_frequencies; - - Wave wave; + RandomPCG gen; // The number of distinct patterns. - const size_t nb_patterns; - - Propagator propagator; + size_t nb_patterns; // Transform the wave to a valid output (a 2d array of patterns that aren't in // contradiction). This function should be used only when all cell of the wave // are defined. Array2D wave_to_output() const; + + //Wave + uint32_t wave_width; + uint32_t wave_height; + uint32_t wave_size; + + // The patterns frequencies p given to wfc. + Vector patterns_frequencies; + + // The precomputation of p * log(p). + Vector plogp_patterns_frequencies; + + // The precomputation of min (p * log(p)) / 2. + // This is used to define the maximum value of the noise. + double min_abs_half_plogp; + + Vector memoisation_plogp_sum; // The sum of p'(pattern)// log(p'(pattern)). + Vector memoisation_sum; // The sum of p'(pattern). + Vector memoisation_log_sum; // The log of sum. + Vector memoisation_nb_patterns; // The number of patterns present + Vector memoisation_entropy; // The entropy of the cell. + + // This value is set to true if there is a contradiction in the wave (all elements set to false in a cell). + bool is_impossible; + + // The actual wave. data.get(index, pattern) is equal to 0 if the pattern can + // be placed in the cell index. + Array2D data; + + //Propagator + Vector propagator_state; + + bool periodic_output; + + // All the tuples (y, x, pattern) that should be propagated. + // The tuple should be propagated when wave.get(y, x, pattern) is set to false. + Vector propagating; + + // compatible.get(y, x, pattern)[direction] contains the number of patterns + // present in the wave that can be placed in the cell next to (y,x) in the + // opposite direction of direction without being in contradiction with pattern + // placed in (y,x). If wave.get(y, x, pattern) is set to false, then + // compatible.get(y, x, pattern) has every element negative or null + Array3D compatible; + + void init_compatible(); }; #endif