|
| 1 | +// Copyright 2018 Wouter van Oortmerssen. All rights reserved. |
| 2 | +// |
| 3 | +// Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | +// you may not use this file except in compliance with the License. |
| 5 | +// You may obtain a copy of the License at |
| 6 | +// |
| 7 | +// http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | +// |
| 9 | +// Unless required by applicable law or agreed to in writing, software |
| 10 | +// distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | +// See the License for the specific language governing permissions and |
| 13 | +// limitations under the License. |
| 14 | + |
| 15 | + |
| 16 | +// Very simple tile based Wave Function Collapse ("Simple Tiled Model") implementation. |
| 17 | +// See: https://github.com/mxgmn/WaveFunctionCollapse |
| 18 | +// Derives adjacencies from an example rather than explicitly specified neighbors. |
| 19 | +// Does not do any symmetries/rotations unless they're in the example. |
| 20 | + |
| 21 | +// Algorithm has a lot of similarities to A* in how its implemented. |
| 22 | +// Uses bitmasks to store the set of possible tiles, which currently limits the number of |
| 23 | +// unique tiles to 64. This restriction cool be lifted by using std::bitset instead. |
| 24 | + |
| 25 | +// In my testing, generates a 50x50 tile map in <1 msec. 58% of such maps are conflict free. |
| 26 | +// At 100x100 that is 3 msec and 34%. |
| 27 | +// At 200x200 that is 24 msec and 13% |
| 28 | +// At 400x400 that is 205 msec and ~1% |
| 29 | +// Algorithm may need to extended to flood more than 2 neighbor levels to make it suitable |
| 30 | +// for really gigantic maps. |
| 31 | + |
| 32 | +// inmap & outmap must point to row-major 2D arrays of the given size. |
| 33 | +// each in tile char must be in range 0..127, of which max 64 may actually be in use (may be |
| 34 | +// sparse). |
| 35 | +// Returns false if too many unique tiles in input. |
| 36 | +template<typename T> bool WaveFunctionCollapse(const int2 &insize, const char **inmap, |
| 37 | + const int2 &outsize, char **outmap, |
| 38 | + RandomNumberGenerator<T> &rnd, |
| 39 | + int &num_contradictions) { |
| 40 | + num_contradictions = 0; |
| 41 | + typedef uint64_t bitmask_t; |
| 42 | + const auto nbits = sizeof(bitmask_t) * 8; |
| 43 | + array<int, 256> tile_lookup; |
| 44 | + tile_lookup.fill(-1); |
| 45 | + struct Tile { bitmask_t sides[4] = {}; size_t freq = 0; char tidx = 0; }; |
| 46 | + vector<Tile> tiles; |
| 47 | + int2 neighbors[] = { { 0, 1 }, { 1, 0 }, { 0, -1 }, { -1, 0 } }; |
| 48 | + // Collect unique tiles and their frequency of occurrence. |
| 49 | + for (int iny = 0; iny < insize.y; iny++) { |
| 50 | + for (int inx = 0; inx < insize.x; inx++) { |
| 51 | + auto t = inmap[iny][inx]; |
| 52 | + if (tile_lookup[t] < 0) { |
| 53 | + // We use a bitmask_t mask for valid neighbors. |
| 54 | + if (tiles.size() == nbits - 1) return false; |
| 55 | + tile_lookup[t] = (int)tiles.size(); |
| 56 | + tiles.push_back(Tile()); |
| 57 | + } |
| 58 | + auto &tile = tiles[tile_lookup[t]]; |
| 59 | + tile.freq++; |
| 60 | + tile.tidx = t; |
| 61 | + } |
| 62 | + } |
| 63 | + // Construct valid neighbor bitmasks. |
| 64 | + for (int iny = 0; iny < insize.y; iny++) { |
| 65 | + for (int inx = 0; inx < insize.x; inx++) { |
| 66 | + auto t = inmap[iny][inx]; |
| 67 | + auto &tile = tiles[tile_lookup[t]]; |
| 68 | + int ni = 0; |
| 69 | + for (auto n : neighbors) { |
| 70 | + auto p = (n + int2(inx, iny) + insize) % insize; |
| 71 | + auto tn = inmap[p.y][p.x]; |
| 72 | + assert(tile_lookup[tn] >= 0); |
| 73 | + tile.sides[ni] |= 1 << tile_lookup[tn]; |
| 74 | + ni++; |
| 75 | + } |
| 76 | + } |
| 77 | + } |
| 78 | + size_t most_common_tile_id = 0; |
| 79 | + size_t most_common_tile_freq = 0; |
| 80 | + for (auto &tile : tiles) if (tile.freq > most_common_tile_freq) { |
| 81 | + most_common_tile_freq = tile.freq; |
| 82 | + most_common_tile_id = &tile - &tiles[0]; |
| 83 | + } |
| 84 | + // Track an open list (much like A*) of next options, sorted by best candidate at the end. |
| 85 | + list<pair<int2, int>> open, temp; |
| 86 | + // Store a bitmask per output cell of remaining possible choices. |
| 87 | + auto max_bitmask = (1 << tiles.size()) - 1; |
| 88 | + enum class State : uchar { NEW, OPEN, CLOSED }; |
| 89 | + struct Cell { |
| 90 | + bitmask_t wf; |
| 91 | + uchar popcnt = 0; |
| 92 | + State state = State::NEW; |
| 93 | + decltype(open)::iterator it; |
| 94 | + Cell(bitmask_t wf, uchar popcnt) : wf(wf), popcnt(popcnt) {} |
| 95 | + }; |
| 96 | + vector<vector<Cell>> cells(outsize.y, vector<Cell>(outsize.x, Cell(max_bitmask, tiles.size()))); |
| 97 | + auto start = rndivec<int, 2>(rnd, outsize); |
| 98 | + open.push_back({ start, 0 }); // Start. |
| 99 | + auto &scell = cells[start.y][start.x]; |
| 100 | + scell.state = State::OPEN; |
| 101 | + scell.it = open.begin(); |
| 102 | + // Pick tiles until no more possible. |
| 103 | + while (!open.empty()) { |
| 104 | + // Simply picking the first list item results in the same chance of conflicts as |
| 105 | + // random picks over equal options, but it is assumed the latter could generate more |
| 106 | + // interesting maps. |
| 107 | + size_t num_candidates = 1; |
| 108 | + auto numopts_0 = cells[open.back().first.y][open.back().first.x].popcnt; |
| 109 | + for (auto it = ++open.rbegin(); it != open.rend(); ++it) |
| 110 | + if (numopts_0 == cells[it->first.y][it->first.x].popcnt && |
| 111 | + open.back().second == it->second) |
| 112 | + num_candidates++; |
| 113 | + else |
| 114 | + break; |
| 115 | + auto candidate_i = rnd(num_candidates); |
| 116 | + auto candidate_it = --open.end(); |
| 117 | + for (int i = 0; i < candidate_i; i++) --candidate_it; |
| 118 | + auto cur = candidate_it->first; |
| 119 | + temp.splice(temp.end(), open, candidate_it); |
| 120 | + auto &cell = cells[cur.y][cur.x]; |
| 121 | + assert(cell.state == State::OPEN); |
| 122 | + cell.state = State::CLOSED; |
| 123 | + bool contradiction = !cell.popcnt; |
| 124 | + if (contradiction) { |
| 125 | + num_contradictions++; |
| 126 | + // Rather than failing right here, fill in the whole map as best as possible just in |
| 127 | + // case a map with bad tile neighbors is still useful to the caller. |
| 128 | + // As a heuristic lets just use the most common tile, as that will likely have the |
| 129 | + // most neighbor options. |
| 130 | + cell.wf = 1 << most_common_tile_id; |
| 131 | + cell.popcnt = 1; |
| 132 | + } |
| 133 | + // From our options, pick one randomly, weighted by frequency of tile occurrence. |
| 134 | + // First find total frequency. |
| 135 | + size_t total_freq = 0; |
| 136 | + for (size_t i = 0; i < tiles.size(); i++) if (cell.wf & (1 << i)) total_freq += tiles[i].freq; |
| 137 | + auto freqpick = rnd(total_freq); |
| 138 | + // Now pick. |
| 139 | + size_t picked = 0; |
| 140 | + for (size_t i = 0; i < tiles.size(); i++) if (cell.wf & (1 << i)) { |
| 141 | + picked = i; |
| 142 | + if ((freqpick -= tiles[i].freq) <= 0) break; |
| 143 | + } |
| 144 | + assert(freqpick <= 0); |
| 145 | + // Modify the picked tile. |
| 146 | + auto &tile = tiles[picked]; |
| 147 | + outmap[cur.y][cur.x] = tile.tidx; |
| 148 | + cell.wf = 1 << picked; // Exactly one option remains. |
| 149 | + cell.popcnt = 1; |
| 150 | + // Now lets cycle thru neighbors, reduce their options (and maybe their neighbors options), |
| 151 | + // and add them to the open list for next pick. |
| 152 | + int ni = 0; |
| 153 | + for (auto n : neighbors) { |
| 154 | + auto p = (cur + n + outsize) % outsize; |
| 155 | + auto &ncell = cells[p.y][p.x]; |
| 156 | + if (ncell.state != State::CLOSED) { |
| 157 | + ncell.wf &= tile.sides[ni]; // Reduce options. |
| 158 | + ncell.popcnt = PopCount(ncell.wf); |
| 159 | + int totalnnumopts = 0; |
| 160 | + if (!contradiction) { |
| 161 | + // Hardcoded second level of neighbors of neighbors, to reduce chance of |
| 162 | + // contradiction. |
| 163 | + // Only do this when our current tile isn't a contradiction, to avoid |
| 164 | + // artificially shrinking options. |
| 165 | + int nni = 0; |
| 166 | + for (auto nn : neighbors) { |
| 167 | + auto pnn = (p + nn + outsize) % outsize; |
| 168 | + auto &nncell = cells[pnn.y][pnn.x]; |
| 169 | + if (nncell.state != State::CLOSED) { |
| 170 | + // Collect the superset of possible options. If we remove anything but |
| 171 | + // these, we are guaranteed the direct neigbor always has a possible |
| 172 | + //pick. |
| 173 | + bitmask_t superopts = 0; |
| 174 | + for (size_t i = 0; i < tiles.size(); i++) |
| 175 | + if (ncell.wf & (1 << i)) |
| 176 | + superopts |= tiles[i].sides[nni]; |
| 177 | + nncell.wf &= superopts; |
| 178 | + nncell.popcnt = PopCount(nncell.wf); |
| 179 | + } |
| 180 | + totalnnumopts += nncell.popcnt; |
| 181 | + nni++; |
| 182 | + } |
| 183 | + } |
| 184 | + if (ncell.state == State::OPEN) { |
| 185 | + // Already in the open list, remove it for it to be re-added just in case |
| 186 | + // its location is not optimal anymore. |
| 187 | + totalnnumopts = min(totalnnumopts, ncell.it->second); |
| 188 | + temp.splice(temp.end(), open, ncell.it); // Avoid alloc. |
| 189 | + } |
| 190 | + // Insert this neighbor, sorted by lowest possibilities. |
| 191 | + // Use total possibilities of neighbors as a tie-breaker to avoid causing |
| 192 | + // contradictions by needless surrounding of tiles. |
| 193 | + decltype(open)::iterator dit = open.begin(); |
| 194 | + for (auto it = open.rbegin(); it != open.rend(); ++it) { |
| 195 | + auto onumopts = cells[it->first.y][it->first.x].popcnt; |
| 196 | + if (onumopts > ncell.popcnt || |
| 197 | + (onumopts == ncell.popcnt && it->second >= totalnnumopts)) { |
| 198 | + dit = it.base(); |
| 199 | + break; |
| 200 | + } |
| 201 | + } |
| 202 | + if (temp.empty()) temp.push_back({}); |
| 203 | + open.splice(dit, temp, ncell.it = temp.begin()); |
| 204 | + *ncell.it = { p, totalnnumopts }; |
| 205 | + ncell.state = State::OPEN; |
| 206 | + } |
| 207 | + ni++; |
| 208 | + } |
| 209 | + } |
| 210 | + return true; |
| 211 | +} |
0 commit comments