载入中...
搜索中...
未找到
GridGraphAlgorithms.cpp
浏览该文件的文档.
2
3#include "common/Diagnostic.h"
4#include "procgen/Semantic.h"
5
6#include <algorithm>
7#include <cmath>
8#include <queue>
9#include <random>
10#include <sstream>
11
13namespace {
14
15bool occupied(const Grid2D& grid, int x, int y) { return grid.getCell(x, y) != int(Semantic::Empty); }
16
17Grid2D makeGrid(const GenerateSettings& settings) {
18 Grid2D grid;
19 grid.resize(settings.width, settings.height);
20 return grid;
21}
22
23void setOccupied(Grid2D& grid, int x, int y, int semantic) {
24 if (x >= 0 && y >= 0 && x < grid.getWidth() && y < grid.getHeight()) grid.setCell(x, y, semantic);
25}
26
27std::vector<std::pair<int, int>> cells(const Grid2D& grid) {
28 std::vector<std::pair<int, int>> result;
29 for (int y = 0; y < grid.getHeight(); ++y)
30 for (int x = 0; x < grid.getWidth(); ++x)
31 if (occupied(grid, x, y)) result.emplace_back(x, y);
32 return result;
33}
34
35std::vector<std::vector<std::pair<int, int>>> islands(const Grid2D& grid) {
36 std::vector<std::vector<std::pair<int, int>>> result;
37 std::vector<unsigned char> visited(std::size_t(grid.getWidth() * grid.getHeight()));
38 constexpr int dx[] = {-1, 1, 0, 0};
39 constexpr int dy[] = {0, 0, -1, 1};
40 for (int y = 0; y < grid.getHeight(); ++y) {
41 for (int x = 0; x < grid.getWidth(); ++x) {
42 const int start = y * grid.getWidth() + x;
43 if (!occupied(grid, x, y) || visited[std::size_t(start)]) continue;
44 result.emplace_back();
45 std::queue<std::pair<int, int>> pending;
46 pending.emplace(x, y);
47 visited[std::size_t(start)] = 1;
48 while (!pending.empty()) {
49 const auto current = pending.front();
50 pending.pop();
51 result.back().push_back(current);
52 for (int direction = 0; direction < 4; ++direction) {
53 const int nx = current.first + dx[direction];
54 const int ny = current.second + dy[direction];
55 if (nx < 0 || ny < 0 || nx >= grid.getWidth() || ny >= grid.getHeight()) continue;
56 const int index = ny * grid.getWidth() + nx;
57 if (occupied(grid, nx, ny) && !visited[std::size_t(index)]) {
58 visited[std::size_t(index)] = 1;
59 pending.emplace(nx, ny);
60 }
61 }
62 }
63 }
64 }
65 return result;
66}
67
68Grid2D cellular(const GenerateSettings& settings) {
69 Grid2D grid = makeGrid(settings);
70 std::mt19937_64 rng(settings.seed);
71 std::uniform_real_distribution<float> probability(0.f, 1.f);
72 for (int y = 0; y < settings.height; ++y)
73 for (int x = 0; x < settings.width; ++x)
74 if (probability(rng) < settings.x) grid.setCell(x, y, settings.semantic);
75 for (int step = 0; step < settings.a; ++step) {
76 Grid2D next = grid;
77 for (int y = 0; y < settings.height; ++y) {
78 for (int x = 0; x < settings.width; ++x) {
79 int neighbors = 0;
80 for (int oy = -1; oy <= 1; ++oy)
81 for (int ox = -1; ox <= 1; ++ox)
82 if ((ox != 0 || oy != 0) && occupied(grid, x + ox, y + oy)) ++neighbors;
83 next.setCell(
84 x, y,
85 neighbors > 4 ? settings.semantic : (neighbors < 4 ? int(Semantic::Empty) : grid.getCell(x, y)));
86 }
87 }
88 grid = std::move(next);
89 }
90 return grid;
91}
92
93Grid2D randomWalk(const GenerateSettings& settings) {
94 Grid2D grid = makeGrid(settings);
95 std::mt19937_64 rng(settings.seed);
96 std::uniform_int_distribution<int> startX(0, settings.width - 1);
97 std::uniform_int_distribution<int> startY(0, settings.height - 1);
98 std::uniform_int_distribution<int> direction(0, 3);
99 int x = settings.b != 0 ? std::clamp(settings.b, 0, settings.width - 1) : startX(rng);
100 int y = settings.c != 0 ? std::clamp(settings.c, 0, settings.height - 1) : startY(rng);
101 const int iterations = std::max(1, settings.a);
102 const int length = std::max(1, int(settings.x));
103 constexpr int dx[] = {-1, 1, 0, 0};
104 constexpr int dy[] = {0, 0, -1, 1};
105 for (int iteration = 0; iteration < iterations; ++iteration) {
106 for (int step = 0; step < length; ++step) {
107 setOccupied(grid, x, y, settings.semantic);
108 const int d = direction(rng);
109 x = std::clamp(x + dx[d], 0, settings.width - 1);
110 y = std::clamp(y + dy[d], 0, settings.height - 1);
111 }
112 if (settings.y > 0.5f) {
113 x = startX(rng);
114 y = startY(rng);
115 }
116 }
117 return grid;
118}
119
120Grid2D maze(const GenerateSettings& settings) {
121 Grid2D grid = makeGrid(settings);
122 if (settings.width < 3 || settings.height < 3) return grid;
123 std::mt19937_64 rng(settings.seed);
124 std::vector<std::pair<int, int>> stack{{1, 1}};
125 setOccupied(grid, 1, 1, settings.semantic);
126 constexpr int dx[] = {-2, 2, 0, 0};
127 constexpr int dy[] = {0, 0, -2, 2};
128 while (!stack.empty()) {
129 const auto current = stack.back();
130 std::vector<int> candidates;
131 for (int d = 0; d < 4; ++d) {
132 const int nx = current.first + dx[d];
133 const int ny = current.second + dy[d];
134 if (nx > 0 && ny > 0 && nx < settings.width - 1 && ny < settings.height - 1 && !occupied(grid, nx, ny))
135 candidates.push_back(d);
136 }
137 if (candidates.empty()) {
138 stack.pop_back();
139 continue;
140 }
141 std::shuffle(candidates.begin(), candidates.end(), rng);
142 const int d = candidates.front();
143 const int nx = current.first + dx[d];
144 const int ny = current.second + dy[d];
145 setOccupied(grid, current.first + dx[d] / 2, current.second + dy[d] / 2, settings.semantic);
146 setOccupied(grid, nx, ny, settings.semantic);
147 stack.emplace_back(nx, ny);
148 }
149 return grid;
150}
151
152} // namespace
153
155 if (settings.width <= 0 || settings.height <= 0)
157 DiagnosticCode::InvalidArgument, "generator dimensions must be positive", {}, {}, "procgen.gridGraph"));
158 Grid2D grid = makeGrid(settings);
159 std::mt19937_64 rng(settings.seed);
160 if (operation == "generate.fill") {
161 grid.fill(settings.semantic);
162 } else if (operation == "generate.random_noise") {
163 std::uniform_real_distribution<float> probability(0.f, 1.f);
164 for (int y = 0; y < settings.height; ++y)
165 for (int x = 0; x < settings.width; ++x)
166 if (probability(rng) < settings.x) grid.setCell(x, y, settings.semantic);
167 } else if (operation == "generate.checkerboard" || operation == "generate.dot_grid") {
168 const int spacing = std::max(1, settings.a);
169 for (int y = 0; y < settings.height; ++y)
170 for (int x = 0; x < settings.width; ++x)
171 if (operation == "generate.checkerboard" ? ((x / spacing + y / spacing) % 2 == 0)
172 : (x % spacing == 0 && y % spacing == 0))
173 grid.setCell(x, y, settings.semantic);
174 } else if (operation == "generate.shape") {
175 const int cx = settings.a;
176 const int cy = settings.b;
177 const int radius = std::max(1, settings.c);
178 for (int y = 0; y < settings.height; ++y)
179 for (int x = 0; x < settings.width; ++x) {
180 bool inside = settings.x < 0.5f ? ((x - cx) * (x - cx) + (y - cy) * (y - cy) <= radius * radius)
181 : (std::abs(x - cx) <= radius && std::abs(y - cy) <= radius);
182 if (inside) grid.setCell(x, y, settings.semantic);
183 }
184 } else if (operation == "generate.cellular") {
185 grid = cellular(settings);
186 } else if (operation == "generate.random_walk") {
187 grid = randomWalk(settings);
188 } else if (operation == "generate.maze") {
189 grid = maze(settings);
190 } else if (operation == "generate.poisson") {
191 const float radius = std::max(1.f, settings.x);
192 std::vector<std::pair<int, int>> accepted;
193 std::vector<std::pair<int, int>> candidates;
194 for (int y = 0; y < settings.height; ++y)
195 for (int x = 0; x < settings.width; ++x) candidates.emplace_back(x, y);
196 std::shuffle(candidates.begin(), candidates.end(), rng);
197 for (const auto& candidate : candidates) {
198 const bool clear = std::none_of(accepted.begin(), accepted.end(), [&](const auto& point) {
199 const float dx = float(point.first - candidate.first);
200 const float dy = float(point.second - candidate.second);
201 return dx * dx + dy * dy < radius * radius;
202 });
203 if (clear) {
204 accepted.push_back(candidate);
205 grid.setCell(candidate.first, candidate.second, settings.semantic);
206 }
207 }
208 } else {
210 "unsupported generator: " + std::string(operation), {}, {},
211 "procgen.gridGraph"));
212 }
213 return Result<Grid2D>::success(std::move(grid));
214}
215
216Result<Grid2D> select(const Grid2D& input, std::string_view operation, int mode, int count, float weight,
217 std::uint64_t seed, std::string_view rule) {
218 Grid2D out;
219 out.resize(input.getWidth(), input.getHeight());
220 auto active = cells(input);
221 if (operation == "select.random") {
222 std::mt19937_64 rng(seed);
223 std::shuffle(active.begin(), active.end(), rng);
224 const int amount = count > 0 ? std::min(count, int(active.size()))
225 : int(std::round(std::clamp(weight, 0.f, 1.f) * float(active.size())));
226 active.resize(std::size_t(amount));
227 for (const auto& point : active)
228 out.setCell(point.first, point.second, input.getCell(point.first, point.second));
229 } else if (operation == "select.border" || operation == "select.fill") {
230 for (const auto& point : active) {
231 bool border = false;
232 constexpr int dx[] = {-1, 1, 0, 0};
233 constexpr int dy[] = {0, 0, -1, 1};
234 for (int d = 0; d < 4; ++d) border = border || !occupied(input, point.first + dx[d], point.second + dy[d]);
235 if ((operation == "select.border" && border) || (operation == "select.fill" && !border))
236 out.setCell(point.first, point.second, input.getCell(point.first, point.second));
237 }
238 } else if (operation == "select.neighbors") {
239 for (const auto& point : active) {
240 int neighbors = 0;
241 for (int oy = -1; oy <= 1; ++oy)
242 for (int ox = -1; ox <= 1; ++ox)
243 if ((ox != 0 || oy != 0) && occupied(input, point.first + ox, point.second + oy)) ++neighbors;
244 if ((mode == 0 && neighbors == count) || (mode == 1 && neighbors >= count) ||
245 (mode == 2 && neighbors <= count))
246 out.setCell(point.first, point.second, input.getCell(point.first, point.second));
247 }
248 } else if (operation == "select.rule") {
249 if (rule.size() != 9)
251 "rule must contain exactly nine characters (0/1/*)", {},
252 {}, "procgen.gridGraph"));
253 for (const auto& point : active) {
254 bool match = true;
255 for (int oy = -1; oy <= 1 && match; ++oy)
256 for (int ox = -1; ox <= 1; ++ox) {
257 const char expected = rule[std::size_t((oy + 1) * 3 + ox + 1)];
258 if (expected != '*' &&
259 (occupied(input, point.first + ox, point.second + oy) != (expected == '1'))) {
260 match = false;
261 break;
262 }
263 }
264 if (match) out.setCell(point.first, point.second, input.getCell(point.first, point.second));
265 }
266 } else if (operation == "select.islands" || operation == "select.island_centers") {
267 for (const auto& island : islands(input)) {
268 const bool keep = mode == 0 ? int(island.size()) < count
269 : (mode == 1 ? int(island.size()) > count : int(island.size()) == count);
270 if (!keep) continue;
271 if (operation == "select.island_centers") {
272 long long sx = 0;
273 long long sy = 0;
274 for (const auto& point : island) {
275 sx += point.first;
276 sy += point.second;
277 }
278 const int x = int(std::llround(double(sx) / double(island.size())));
279 const int y = int(std::llround(double(sy) / double(island.size())));
280 out.setCell(x, y, input.getCell(island.front().first, island.front().second));
281 } else {
282 for (const auto& point : island)
283 out.setCell(point.first, point.second, input.getCell(point.first, point.second));
284 }
285 }
286 } else {
288 "unsupported selector: " + std::string(operation), {}, {},
289 "procgen.gridGraph"));
290 }
291 return Result<Grid2D>::success(std::move(out));
292}
293
294Result<Grid2D> findPath(const Grid2D& navigation, const Grid2D& starts, const Grid2D& targets, int semantic) {
295 if (navigation.getWidth() != starts.getWidth() || navigation.getHeight() != starts.getHeight() ||
296 navigation.getWidth() != targets.getWidth() || navigation.getHeight() != targets.getHeight())
298 "pathfinding grids must have equal dimensions", {}, {},
299 "procgen.gridGraph"));
300 const auto startCells = cells(starts);
301 const auto targetCells = cells(targets);
302 if (startCells.empty() || targetCells.empty())
304 "pathfinding requires start and target cells", {}, {},
305 "procgen.gridGraph"));
306 const int width = navigation.getWidth();
307 const int height = navigation.getHeight();
308 std::vector<int> previous(std::size_t(width * height), -1);
309 std::queue<int> pending;
310 const int start = startCells.front().second * width + startCells.front().first;
311 pending.push(start);
312 previous[std::size_t(start)] = start;
313 int destination = -1;
314 while (!pending.empty() && destination < 0) {
315 const int current = pending.front();
316 pending.pop();
317 const int x = current % width;
318 const int y = current / width;
319 if (occupied(targets, x, y)) {
320 destination = current;
321 break;
322 }
323 constexpr int dx[] = {-1, 1, 0, 0};
324 constexpr int dy[] = {0, 0, -1, 1};
325 for (int d = 0; d < 4; ++d) {
326 const int nx = x + dx[d];
327 const int ny = y + dy[d];
328 if (nx < 0 || ny < 0 || nx >= width || ny >= height || !occupied(navigation, nx, ny)) continue;
329 const int next = ny * width + nx;
330 if (previous[std::size_t(next)] < 0) {
331 previous[std::size_t(next)] = current;
332 pending.push(next);
333 }
334 }
335 }
336 if (destination < 0)
338 Diagnostic::error(DiagnosticCode::InvalidArgument, "no path found", {}, {}, "procgen.gridGraph"));
339 Grid2D out;
340 out.resize(width, height);
341 for (int current = destination;; current = previous[std::size_t(current)]) {
343 if (current == start) break;
344 }
345 return Result<Grid2D>::success(std::move(out));
346}
347
348} // namespace eve::procgen::gridgraph
ActionParameterOperation operation
Duration start
bool & active
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
std::vector< QuestEvent > pending
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float length
Definition CaveMesh.cpp:94
int island
float nx
float ny
Stable, structured diagnostics shared by engine modules.
EvpackChunkInput input
Definition Evpack.cpp:170
bool border
std::uint32_t height
std::uint32_t width
graphics::Canvas * previous
std::array< PixelCell, kPixelChunkSize *kPixelChunkSize > cells
float radius
std::uint32_t seed
Definition PointSet.cpp:807
float d
RoadLaneDirection direction
double current
float dy
float dx
bool occupied
std::uint32_t count
TerrainThermalSettings settings
int spacing
int iterations
Definition TreeMesh.cpp:311
float step
Definition TreeMesh.cpp:314
uint32_t index
double oy
std::vector< char > inside
double ox
glm::vec3 point
uint32_t semantic
Definition WfcSimple.cpp:15
static Diagnostic error(DiagnosticCode code, std::string message, std::string path={}, DiagnosticDetails details={}, std::string source={})
Construct an error diagnostic with the standard error severity.
Definition Diagnostic.h:125
Move-only operation result carrying either a value or Status.
Definition Result.h:155
static Result success(T value)
Construct a successful result owning value.
Definition Result.h:164
static Result failure(Status status)
Construct a failed result from a structured status.
Definition Result.h:175
Intermediate 2D generation result. cells store semantic ids (see Semantic.h), not tile GIDs — convert...
Definition Grid2D.h:36
int getWidth() const
Returns the width.
Definition Grid2D.cpp:20
void resize(int width, int height)
Resize.
Definition Grid2D.cpp:13
void setCell(int x, int y, int semantic)
Sets the cell.
Definition Grid2D.cpp:23
int getHeight() const
Returns the height.
Definition Grid2D.cpp:21
constexpr uint32_t Empty
Definition Semantic.h:12
Result< Grid2D > findPath(const Grid2D &navigation, const Grid2D &starts, const Grid2D &targets, int semantic)
Finds path.
Result< Grid2D > generate(std::string_view operation, const GenerateSettings &settings)
Generate.
Result< Grid2D > select(const Grid2D &input, std::string_view operation, int mode, int count, float weight, std::uint64_t seed, std::string_view rule)
Select.