载入中...
搜索中...
未找到
WfcSimple.cpp
浏览该文件的文档.
2#include "procgen/Semantic.h"
3
4#include <algorithm>
5#include <cstdint>
6#include <limits>
7#include <random>
8#include <string>
9#include <vector>
10
11namespace eve::procgen {
12namespace {
13
14struct WfcTile {
16 // Orthogonal adjacency: bit i set ⇒ may neighbor tile i (N/E/S/W all share same rule).
17 uint64_t compat = 0;
18};
19
20struct WfcPreset {
21 const char *name;
22 std::vector<WfcTile> tiles;
23};
24
25WfcPreset makeDungeonPreset() {
26 // 0 Wall, 1 Floor, 2 Corridor, 3 Door
27 WfcPreset p;
28 p.name = "dungeon";
29 p.tiles = {
30 {Semantic::Wall, 0},
31 {Semantic::Floor, 0},
33 {Semantic::Door, 0},
34 };
35 auto allow = [&](int a, int b) {
36 p.tiles[size_t(a)].compat |= (1ull << b);
37 p.tiles[size_t(b)].compat |= (1ull << a);
38 };
39 allow(0, 0); // wall-wall
40 allow(0, 1); // wall-floor
41 allow(0, 2); // wall-corridor
42 allow(0, 3); // wall-door
43 allow(1, 1); // floor-floor
44 allow(1, 2); // floor-corridor
45 allow(1, 3); // floor-door
46 allow(2, 2); // corridor-corridor
47 // door-door disallowed (keeps doors sparse)
48 return p;
49}
50
51WfcPreset makeCavePreset() {
52 WfcPreset p;
53 p.name = "cave";
54 p.tiles = {
55 {Semantic::Wall, 0},
56 {Semantic::Floor, 0},
57 };
58 auto allow = [&](int a, int b) {
59 p.tiles[size_t(a)].compat |= (1ull << b);
60 p.tiles[size_t(b)].compat |= (1ull << a);
61 };
62 allow(0, 0);
63 allow(0, 1);
64 allow(1, 1);
65 return p;
66}
67
68WfcPreset makeTerrainPreset() {
69 // Adjacent biomes only (ordered elevation band).
70 // 0 Water 1 Sand 2 Grass 3 Dirt 4 Stone 5 Snow
71 WfcPreset p;
72 p.name = "terrain";
73 p.tiles = {
76 };
77 auto allow = [&](int a, int b) {
78 p.tiles[size_t(a)].compat |= (1ull << b);
79 p.tiles[size_t(b)].compat |= (1ull << a);
80 };
81 for (int i = 0; i < 6; ++i) {
82 allow(i, i);
83 if (i + 1 < 6) allow(i, i + 1);
84 }
85 return p;
86}
87
88const WfcPreset &presetByName(const std::string &name) {
89 static const WfcPreset dungeon = makeDungeonPreset();
90 static const WfcPreset cave = makeCavePreset();
91 static const WfcPreset terrain = makeTerrainPreset();
92 if (name == "cave") return cave;
93 if (name == "terrain") return terrain;
94 return dungeon;
95}
96
97bool genWfcSimple(const Params &params, Grid2D &out, std::string &error) {
98 const int w = params.getWidth();
99 const int h = params.getHeight();
100 if (w < 4 || h < 4) {
101 error = "wfc.simple: size must be at least 4x4";
102 return false;
103 }
104 if (w > 256 || h > 256) {
105 error = "wfc.simple: size capped at 256x256";
106 return false;
107 }
108
109 const std::string presetName = params.getString("preset", "dungeon");
110 if (presetName != "dungeon" && presetName != "cave" && presetName != "terrain") {
111 error = "wfc.simple: unknown preset '" + presetName + "' (use dungeon|cave|terrain)";
112 return false;
113 }
114 const WfcPreset &preset = presetByName(presetName);
115 const int tileCount = int(preset.tiles.size());
116 if (tileCount <= 0 || tileCount > 63) {
117 error = "wfc.simple: invalid tile set";
118 return false;
119 }
120
121 const int maxAttempts = std::max(1, params.getInt("maxAttempts", 32));
122 const uint64_t fullMask = (tileCount >= 63) ? ~0ull : ((1ull << tileCount) - 1ull);
123
124 std::mt19937 rng(params.getSeed());
125
126 auto trySolve = [&](Grid2D &grid) -> bool {
127 std::vector<uint64_t> wave(size_t(w * h), fullMask);
128 auto idx = [&](int x, int y) { return size_t(y * w + x); };
129 auto countBits = [](uint64_t m) {
130 int c = 0;
131 while (m) {
132 m &= m - 1;
133 ++c;
134 }
135 return c;
136 };
137
138 auto pickTile = [&](uint64_t mask) -> int {
139 int options[64];
140 int n = 0;
141 for (int t = 0; t < tileCount; ++t) {
142 if (mask & (1ull << t)) options[n++] = t;
143 }
144 if (n <= 0) return -1;
145 std::uniform_int_distribution<int> dist(0, n - 1);
146 return options[dist(rng)];
147 };
148
149 // Border: force walls for dungeon/cave (keeps maps enclosed).
150 if (presetName != "terrain") {
151 for (int x = 0; x < w; ++x) {
152 wave[idx(x, 0)] = 1ull << 0;
153 wave[idx(x, h - 1)] = 1ull << 0;
154 }
155 for (int y = 0; y < h; ++y) {
156 wave[idx(0, y)] = 1ull << 0;
157 wave[idx(w - 1, y)] = 1ull << 0;
158 }
159 }
160
161 auto propagate = [&](int sx, int sy) -> bool {
162 std::vector<std::pair<int, int>> stack;
163 stack.emplace_back(sx, sy);
164 while (!stack.empty()) {
165 const auto [cx, cy] = stack.back();
166 stack.pop_back();
167 const uint64_t self = wave[idx(cx, cy)];
168 if (self == 0) return false;
169
170 uint64_t allowedNeighbor = 0;
171 for (int t = 0; t < tileCount; ++t) {
172 if (self & (1ull << t)) allowedNeighbor |= preset.tiles[size_t(t)].compat;
173 }
174
175 const int dirs[4][2] = {{0, -1}, {1, 0}, {0, 1}, {-1, 0}};
176 for (const auto &d : dirs) {
177 const int nx = cx + d[0];
178 const int ny = cy + d[1];
179 if (nx < 0 || ny < 0 || nx >= w || ny >= h) continue;
180 uint64_t &nb = wave[idx(nx, ny)];
181 const uint64_t before = nb;
182 nb &= allowedNeighbor;
183 if (nb == 0) return false;
184 if (nb != before) stack.emplace_back(nx, ny);
185 }
186 }
187 return true;
188 };
189
190 // Seed propagate from border constraints.
191 if (presetName != "terrain") {
192 for (int x = 0; x < w; ++x) {
193 if (!propagate(x, 0) || !propagate(x, h - 1)) return false;
194 }
195 for (int y = 0; y < h; ++y) {
196 if (!propagate(0, y) || !propagate(w - 1, y)) return false;
197 }
198 }
199
200 for (;;) {
201 int bestX = -1, bestY = -1;
202 int bestEntropy = std::numeric_limits<int>::max();
203 bool anyUncollapsed = false;
204 for (int y = 0; y < h; ++y) {
205 for (int x = 0; x < w; ++x) {
206 const int e = countBits(wave[idx(x, y)]);
207 if (e == 0) return false;
208 if (e == 1) continue;
209 anyUncollapsed = true;
210 // Prefer lower entropy; break ties randomly via salt.
211 const int salted = e * 1000 + int(rng() % 1000u);
212 if (salted < bestEntropy) {
213 bestEntropy = salted;
214 bestX = x;
215 bestY = y;
216 }
217 }
218 }
219 if (!anyUncollapsed) break;
220 if (bestX < 0) return false;
221
222 const int chosen = pickTile(wave[idx(bestX, bestY)]);
223 if (chosen < 0) return false;
224 wave[idx(bestX, bestY)] = 1ull << chosen;
225 if (!propagate(bestX, bestY)) return false;
226 }
227
228 grid.resize(w, h);
229 for (int y = 0; y < h; ++y) {
230 for (int x = 0; x < w; ++x) {
231 const uint64_t m = wave[idx(x, y)];
232 int tile = 0;
233 for (int t = 0; t < tileCount; ++t) {
234 if (m & (1ull << t)) {
235 tile = t;
236 break;
237 }
238 }
239 grid.setCell(x, y, int(preset.tiles[size_t(tile)].semantic));
240 }
241 }
242 return true;
243 };
244
245 bool ok = false;
246 for (int attempt = 0; attempt < maxAttempts; ++attempt) {
247 // Advance RNG between attempts for a different collapse order.
248 if (attempt > 0) (void)rng();
249 Grid2D candidate;
250 if (trySolve(candidate)) {
251 out = std::move(candidate);
252 ok = true;
253 break;
254 }
255 }
256 if (!ok) {
257 error = "wfc.simple: failed to collapse (try different seed/preset or raise maxAttempts)";
258 return false;
259 }
260
261 out.setMeta("algorithm", "wfc.simple");
262 out.setMeta("preset", presetName);
263
264 // Spawn/stairs on walkable cells when present.
265 std::vector<std::pair<int, int>> walkable;
266 for (int y = 0; y < h; ++y) {
267 for (int x = 0; x < w; ++x) {
268 const uint32_t c = uint32_t(out.getCell(x, y));
270 c == Semantic::Dirt || c == Semantic::Sand) {
271 walkable.emplace_back(x, y);
272 }
273 }
274 }
275 if (!walkable.empty()) {
276 std::uniform_int_distribution<size_t> pick(0, walkable.size() - 1);
277 auto a = walkable[pick(rng)];
278 auto b = walkable[pick(rng)];
279 if (a == b && walkable.size() > 1) b = walkable[(pick(rng) + 1) % walkable.size()];
280 out.clearObjects();
281 out.addObjectAt("spawn", "spawn", float(a.first), float(a.second));
282 out.addObjectAt("stairs", "stairs", float(b.first), float(b.second));
283 }
284 return true;
285}
286
287} // namespace
288
290 registry.registerAlgorithm("wfc.simple", genWfcSimple);
291}
292
293} // namespace eve::procgen
float cx
Definition CardTypes.cpp:31
float cy
Definition CardTypes.cpp:32
int y
Definition Grass.cpp:135
int x
Definition Grass.cpp:135
glm::vec3 n
Definition Grass.cpp:64
int h
int w
std::string error
uint32_t a
uint32_t b
uint32_t c
int idx
glm::vec4 p[6]
const char * name
Definition RockMesh.cpp:21
int d
float m[16]
std::vector< WfcTile > tiles
Definition WfcSimple.cpp:22
uint64_t compat
Definition WfcSimple.cpp:17
uint32_t semantic
Definition WfcSimple.cpp:15
void registerAlgorithm(const std::string &id, GeneratorFn fn)
constexpr uint32_t Floor
Definition Semantic.h:12
constexpr uint32_t Grass
Definition Semantic.h:16
constexpr uint32_t Stone
Definition Semantic.h:18
constexpr uint32_t Wall
Definition Semantic.h:11
constexpr uint32_t Sand
Definition Semantic.h:15
constexpr uint32_t Dirt
Definition Semantic.h:17
constexpr uint32_t Corridor
Definition Semantic.h:13
constexpr uint32_t Empty
Definition Semantic.h:10
constexpr uint32_t Door
Definition Semantic.h:20
constexpr uint32_t Water
Definition Semantic.h:14
constexpr uint32_t Snow
Definition Semantic.h:19
void registerWfcSimple(GeneratorRegistry &registry)