载入中...
搜索中...
未找到
Pathfinder.cpp
浏览该文件的文档.
1#include "map/Pathfinder.h"
2
4
5#include <algorithm>
6#include <cmath>
7#include <cstdint>
8#include <functional>
9#include <limits>
10#include <queue>
11#include <tuple>
12#include <unordered_set>
13#include <vector>
14
15namespace eve::map {
16namespace {
17
18constexpr float kInf = std::numeric_limits<float>::infinity();
19constexpr float kSqrt2 = 1.41421356237f;
20
21enum class Topology { Ortho4, Ortho8, Hex };
22
23using NeighborFn = std::function<void(int nx, int ny, float moveCost)>;
24
25Topology parseTopology(const std::string &name, Topology fallback) {
26 if (name == "ortho4" || name == "orthogonal4" || name == "4") return Topology::Ortho4;
27 if (name == "ortho8" || name == "orthogonal8" || name == "8") return Topology::Ortho8;
28 if (name == "hex" || name == "hexagonal" || name == "staggered") return Topology::Hex;
29 if (name == "auto") return fallback;
30 return fallback;
31}
32
33const char *topologyName(Topology t) {
34 switch (t) {
35 case Topology::Ortho4:
36 return "ortho4";
37 case Topology::Ortho8:
38 return "ortho8";
39 case Topology::Hex:
40 return "hex";
41 }
42 return "ortho4";
43}
44
45Topology topologyFromOrientation(MapOrientation orientation, bool preferDiagonal) {
46 switch (orientation) {
49 return Topology::Hex;
52 default:
53 return preferDiagonal ? Topology::Ortho8 : Topology::Ortho4;
54 }
55}
56
57void forEachNeighbor(Topology topology, int x, int y, bool staggerAxisY, bool staggerOdd,
58 const NeighborFn &fn) {
59 if (!fn) return;
60 switch (topology) {
61 case Topology::Ortho4: {
62 static const int dx[4] = {1, -1, 0, 0};
63 static const int dy[4] = {0, 0, 1, -1};
64 for (int i = 0; i < 4; ++i) fn(x + dx[i], y + dy[i], 1.f);
65 break;
66 }
67 case Topology::Ortho8: {
68 static const int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1};
69 static const int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1};
70 for (int i = 0; i < 8; ++i) {
71 const float c = (i < 4) ? 1.f : kSqrt2;
72 fn(x + dx[i], y + dy[i], c);
73 }
74 break;
75 }
76 case Topology::Hex: {
77 if (staggerAxisY) {
78 const bool rowOdd = ((y & 1) != 0);
79 const bool shifted = staggerOdd ? rowOdd : !rowOdd;
80 if (shifted) {
81 fn(x + 1, y, 1.f);
82 fn(x - 1, y, 1.f);
83 fn(x, y - 1, 1.f);
84 fn(x + 1, y - 1, 1.f);
85 fn(x, y + 1, 1.f);
86 fn(x + 1, y + 1, 1.f);
87 } else {
88 fn(x + 1, y, 1.f);
89 fn(x - 1, y, 1.f);
90 fn(x - 1, y - 1, 1.f);
91 fn(x, y - 1, 1.f);
92 fn(x - 1, y + 1, 1.f);
93 fn(x, y + 1, 1.f);
94 }
95 } else {
96 const bool colOdd = ((x & 1) != 0);
97 const bool shifted = staggerOdd ? colOdd : !colOdd;
98 if (shifted) {
99 fn(x, y + 1, 1.f);
100 fn(x, y - 1, 1.f);
101 fn(x - 1, y, 1.f);
102 fn(x - 1, y + 1, 1.f);
103 fn(x + 1, y, 1.f);
104 fn(x + 1, y + 1, 1.f);
105 } else {
106 fn(x, y + 1, 1.f);
107 fn(x, y - 1, 1.f);
108 fn(x - 1, y - 1, 1.f);
109 fn(x - 1, y, 1.f);
110 fn(x + 1, y - 1, 1.f);
111 fn(x + 1, y, 1.f);
112 }
113 }
114 break;
115 }
116 }
117}
118
119float heuristic(Topology topology, int x0, int y0, int x1, int y1) {
120 const int dx = std::abs(x1 - x0);
121 const int dy = std::abs(y1 - y0);
122 switch (topology) {
123 case Topology::Ortho4:
124 return float(dx + dy);
125 case Topology::Ortho8:
126 return float(std::max(dx, dy)) + (kSqrt2 - 1.f) * float(std::min(dx, dy));
127 case Topology::Hex: {
128 auto toCube = [](int x, int y) {
129 const int q = x - (y - (y & 1)) / 2;
130 const int r = y;
131 const int s = -q - r;
132 return std::tuple<int, int, int>{q, r, s};
133 };
134 auto [q0, r0, s0] = toCube(x0, y0);
135 auto [q1, r1, s1] = toCube(x1, y1);
136 return float(std::max({std::abs(q0 - q1), std::abs(r0 - r1), std::abs(s0 - s1)}));
137 }
138 }
139 return float(dx + dy);
140}
141
143struct Grid {
144 int width = 0;
145 int height = 0;
146 TileLayer *layer = nullptr;
147 uint64_t layerRevision = 0;
148 Topology topology = Topology::Ortho4;
149 bool topologyManual = false;
150 bool diagonal = false;
151 bool blockEmpty = true;
152 bool staggerAxisY = true;
153 bool staggerOdd = true;
154 bool dirty = true;
155 std::vector<float> cost; // ≤0 => blocked
156 std::vector<uint8_t> enterMask;
157 std::vector<uint8_t> exitMask;
158 std::unordered_set<uint32_t> blockedGids;
159
160 int index(int x, int y) const { return y * width + x; }
161 bool inBounds(int x, int y) const { return x >= 0 && y >= 0 && x < width && y < height; }
162
163 void resize(int w, int h) {
164 width = w > 0 ? w : 0;
165 height = h > 0 ? h : 0;
166 cost.assign(size_t(width * height), 1.f);
167 enterMask.assign(size_t(width * height), 0xff);
168 exitMask.assign(size_t(width * height), 0xff);
169 dirty = true;
170 }
171
172 void applyAutoTopologyFromLayer() {
173 if (!layer) return;
174 topology = topologyFromOrientation(layer->config()->orientation, diagonal);
175 }
176
177 void bindLayer(TileLayer *l) {
178 layer = l;
179 if (!layer) return;
180 auto cfg = layer->config();
181 resize(cfg->mapW, cfg->mapH);
182 staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
183 staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
184 if (!topologyManual) applyAutoTopologyFromLayer();
185 syncFromLayer();
186 }
187
188 void clearLayer() { layer = nullptr; }
189
190 void syncFromLayer() {
191 if (!layer) return;
192 auto cfg = layer->config();
193 auto tiles = layer->tiles();
194 if (cfg->mapW != width || cfg->mapH != height) resize(cfg->mapW, cfg->mapH);
195 staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
196 staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
197 if (!topologyManual) applyAutoTopologyFromLayer();
198
199 const auto tileset = layer->tileset();
200 const int n = width * height;
201 for (int i = 0; i < n; ++i) {
202 const uint32_t gid = (i < int(tiles->gids.size())) ? tileGid(tiles->gids[size_t(i)]) : 0u;
203 bool blocked = false;
204 float movementCost = 1.f;
205 uint8_t cellEnterMask = 0xff;
206 uint8_t cellExitMask = 0xff;
207 if (blockEmpty && gid == 0u) blocked = true;
208 if (blockedGids.count(gid)) blocked = true;
209 if (gid != 0u) {
210 const auto visual = std::find_if(
211 tileset->visuals.begin(), tileset->visuals.end(),
212 [gid](const TileLayer::Tileset::Visual &candidate) { return candidate.gid == int(gid); });
213 if (visual != tileset->visuals.end()) {
214 blocked = blocked || !visual->walkable;
215 movementCost = std::max(0.001f, visual->cost);
216 cellEnterMask = visual->enterMask;
217 cellExitMask = visual->exitMask;
218 }
219 }
220 cost[size_t(i)] = blocked ? 0.f : movementCost;
221 enterMask[size_t(i)] = cellEnterMask;
222 exitMask[size_t(i)] = cellExitMask;
223 }
224 layerRevision = tiles->revision;
225 dirty = true;
226 }
227
228 void setTopology(const std::string &name) {
229 if (name == "auto") {
230 topologyManual = false;
231 if (layer) applyAutoTopologyFromLayer();
232 else topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
233 } else {
234 topologyManual = true;
235 topology = parseTopology(name, topology);
236 }
237 dirty = true;
238 }
239
240 void setDiagonal(bool enable) {
241 diagonal = enable;
242 if (!topologyManual && layer) applyAutoTopologyFromLayer();
243 else if (!topologyManual) topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
244 dirty = true;
245 }
246
247 void blockGid(int gid) {
248 if (gid < 0) return;
249 blockedGids.insert(uint32_t(gid));
250 dirty = true;
251 if (layer) syncFromLayer();
252 }
253
254 void unblockGid(int gid) {
255 if (gid < 0) return;
256 blockedGids.erase(uint32_t(gid));
257 dirty = true;
258 if (layer) syncFromLayer();
259 }
260
261 void clearBlockedGids() {
262 blockedGids.clear();
263 dirty = true;
264 if (layer) syncFromLayer();
265 }
266
267 void setBlockEmpty(bool enable) {
268 blockEmpty = enable;
269 dirty = true;
270 if (layer) syncFromLayer();
271 }
272
273 bool isWalkable(int x, int y) const {
274 if (!inBounds(x, y)) return false;
275 return cost[size_t(index(x, y))] > 0.f;
276 }
277
278 void setBlocked(int x, int y, bool blocked) {
279 if (!inBounds(x, y)) return;
280 cost[size_t(index(x, y))] = blocked ? 0.f : 1.f;
281 dirty = true;
282 }
283
284 void setCellCost(int x, int y, float c) {
285 if (!inBounds(x, y)) return;
286 cost[size_t(index(x, y))] = c;
287 dirty = true;
288 }
289
290 float getCellCost(int x, int y) const {
291 if (!inBounds(x, y)) return 0.f;
292 return cost[size_t(index(x, y))];
293 }
294
295 bool canTraverseCardinal(int x, int y, int nx, int ny) const {
296 if (!isWalkable(x, y) || !isWalkable(nx, ny)) return false;
297 uint8_t direction = 0;
298 uint8_t opposite = 0;
299 if (nx == x && ny == y - 1) {
300 direction = 1;
301 opposite = 4;
302 } else if (nx == x + 1 && ny == y) {
303 direction = 2;
304 opposite = 8;
305 } else if (nx == x && ny == y + 1) {
306 direction = 4;
307 opposite = 1;
308 } else if (nx == x - 1 && ny == y) {
309 direction = 8;
310 opposite = 2;
311 } else {
312 return true;
313 }
314 return (exitMask[size_t(index(x, y))] & direction) != 0 && (enterMask[size_t(index(nx, ny))] & opposite) != 0;
315 }
316
317 void forEachWalkableNeighbor(int x, int y, const NeighborFn &fn) const {
318 if (!fn || !inBounds(x, y)) return;
319 forEachNeighbor(topology, x, y, staggerAxisY, staggerOdd, [&](int nx, int ny, float moveCost) {
320 if (!isWalkable(nx, ny)) return;
321 if (!canTraverseCardinal(x, y, nx, ny)) return;
322 if (topology == Topology::Ortho8) {
323 const int dx = nx - x;
324 const int dy = ny - y;
325 if (dx != 0 && dy != 0) {
326 if (!canTraverseCardinal(x, y, x + dx, y) || !canTraverseCardinal(x, y, x, y + dy)) return;
327 }
328 }
329 fn(nx, ny, moveCost * getCellCost(nx, ny));
330 });
331 }
332};
333
334struct HeapNode {
335 float f = 0.f;
336 int idx = 0;
337 bool operator>(const HeapNode &o) const { return f > o.f; }
338};
339
340} // namespace
341
349
350Pathfinder::Pathfinder() : impl_(std::make_unique<Impl>()) {}
351
353
355
356Pathfinder::~Pathfinder() = default;
357Pathfinder::Pathfinder(Pathfinder &&) noexcept = default;
358Pathfinder &Pathfinder::operator=(Pathfinder &&) noexcept = default;
359
360void Pathfinder::bindLayer(TileLayer *layer) {
361 impl_->grid.bindLayer(layer);
362 invalidateCache();
363}
364
366 impl_->grid.clearLayer();
367 impl_->grid.resize(width, height);
369}
370
371void Pathfinder::setTopology(const std::string &name) {
372 impl_->grid.setTopology(name);
374}
375
376std::string Pathfinder::getTopology() const { return topologyName(impl_->grid.topology); }
377
378void Pathfinder::setDiagonal(bool enable) {
379 impl_->grid.setDiagonal(enable);
381}
382
383bool Pathfinder::getDiagonal() const { return impl_->grid.diagonal; }
384
385void Pathfinder::blockGid(int gid) {
386 impl_->grid.blockGid(gid);
388}
389
391 impl_->grid.unblockGid(gid);
393}
394
396 impl_->grid.clearBlockedGids();
398}
399
400void Pathfinder::setBlockEmpty(bool enable) {
401 impl_->grid.setBlockEmpty(enable);
403}
404
405bool Pathfinder::getBlockEmpty() const { return impl_->grid.blockEmpty; }
406
407void Pathfinder::setBlocked(int x, int y, bool blocked) {
408 impl_->grid.setBlocked(x, y, blocked);
410}
411
412bool Pathfinder::isWalkable(int x, int y) const { return impl_->grid.isWalkable(x, y); }
413
414void Pathfinder::setCellCost(int x, int y, float cost) {
415 impl_->grid.setCellCost(x, y, cost);
417}
418
419float Pathfinder::getCellCost(int x, int y) const { return impl_->grid.getCellCost(x, y); }
420
422 impl_->grid.syncFromLayer();
424}
425
427 impl_->hasCachedField = false;
428 impl_->cachedField.clear();
429}
430
431namespace {
432
433bool ensureSynced(Grid &grid) {
434 if (grid.layer && (grid.dirty || grid.layerRevision != grid.layer->tiles()->revision))
435 grid.syncFromLayer();
436 return grid.width > 0 && grid.height > 0;
437}
438
439FlowField *buildFlowFieldUncached(Grid &grid, int gx, int gy) {
440 auto *field = new FlowField();
441 if (!ensureSynced(grid) || !grid.isWalkable(gx, gy)) {
442 field->resize(grid.width, grid.height);
443 field->setGoal(gx, gy);
444 return field;
445 }
446
447 const int w = grid.width;
448 const int h = grid.height;
449 field->resize(w, h);
450 field->setGoal(gx, gy);
451
452 const auto idx = [w](int x, int y) { return y * w + x; };
453 std::vector<float> dist(size_t(w * h), kInf);
454 std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
455
456 dist[size_t(idx(gx, gy))] = 0.f;
457 field->setCost(gx, gy, 0.f);
458 field->setNext(gx, gy, gx, gy);
459 open.push(HeapNode{0.f, idx(gx, gy)});
460
461 while (!open.empty()) {
462 const HeapNode cur = open.top();
463 open.pop();
464 if (cur.f > dist[size_t(cur.idx)]) continue;
465 const int cx = cur.idx % w;
466 const int cy = cur.idx / w;
467 const float enterCur = grid.getCellCost(cx, cy);
468 forEachNeighbor(grid.topology, cx, cy, grid.staggerAxisY, grid.staggerOdd,
469 [&](int nx, int ny, float moveCost) {
470 if (!grid.isWalkable(nx, ny)) return;
471 if (grid.topology == Topology::Ortho8) {
472 const int dx = nx - cx;
473 const int dy = ny - cy;
474 if (dx != 0 && dy != 0) {
475 if (!grid.isWalkable(cx + dx, cy) || !grid.isWalkable(cx, cy + dy))
476 return;
477 }
478 }
479 const int ni = idx(nx, ny);
480 const float tentative = dist[size_t(cur.idx)] + moveCost * enterCur;
481 if (tentative + 1e-6f >= dist[size_t(ni)]) return;
482 dist[size_t(ni)] = tentative;
483 field->setCost(nx, ny, tentative);
484 field->setNext(nx, ny, cx, cy);
485 open.push(HeapNode{tentative, ni});
486 });
487 }
488 return field;
489}
490
491} // namespace
492
493Path *Pathfinder::findPath(int sx, int sy, int gx, int gy) {
494 return findPath(sx, sy, gx, gy, {});
495}
496
497Path *Pathfinder::findPath(int sx, int sy, int gx, int gy,
498 const std::function<float(int, int)> &entryPenalty) {
499 auto *path = new Path();
500 Grid &grid = impl_->grid;
501 if (!ensureSynced(grid)) return path;
502 if (!grid.isWalkable(sx, sy) || !grid.isWalkable(gx, gy)) return path;
503 if (sx == gx && sy == gy) {
504 path->add(sx, sy);
505 path->setTotalCost(0.f);
506 return path;
507 }
508
509 const int w = grid.width;
510 const int n = w * grid.height;
511 const auto idx = [w](int x, int y) { return y * w + x; };
512
513 std::vector<float> gScore(size_t(n), kInf);
514 std::vector<int> parent(size_t(n), -1);
515 std::vector<uint8_t> closed(size_t(n), 0);
516
517 std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
518 const int start = idx(sx, sy);
519 const int goal = idx(gx, gy);
520 gScore[size_t(start)] = 0.f;
521 open.push(HeapNode{heuristic(grid.topology, sx, sy, gx, gy), start});
522
523 bool found = false;
524 while (!open.empty()) {
525 const HeapNode cur = open.top();
526 open.pop();
527 if (closed[size_t(cur.idx)]) continue;
528 closed[size_t(cur.idx)] = 1;
529 if (cur.idx == goal) {
530 found = true;
531 break;
532 }
533 const int cx = cur.idx % w;
534 const int cy = cur.idx / w;
535 grid.forEachWalkableNeighbor(cx, cy, [&](int nx, int ny, float edgeCost) {
536 const int ni = idx(nx, ny);
537 if (closed[size_t(ni)]) return;
538 float penalty = entryPenalty ? entryPenalty(nx, ny) : 0.0f;
539 if (!std::isfinite(penalty)) return;
540 penalty = std::max(0.0f, penalty);
541 const float tentative = gScore[size_t(cur.idx)] + edgeCost + penalty;
542 if (tentative >= gScore[size_t(ni)]) return;
543 gScore[size_t(ni)] = tentative;
544 parent[size_t(ni)] = cur.idx;
545 open.push(HeapNode{tentative + heuristic(grid.topology, nx, ny, gx, gy), ni});
546 });
547 }
548
549 if (!found) return path;
550
551 int cur = goal;
552 while (cur != -1) {
553 path->add(cur % w, cur / w);
554 if (cur == start) break;
555 cur = parent[size_t(cur)];
556 }
557 path->reverse();
558 path->setTotalCost(gScore[size_t(goal)]);
559 return path;
560}
561
562FlowField *Pathfinder::buildFlowField(int gx, int gy) {
563 Grid &grid = impl_->grid;
564 if (!ensureSynced(grid)) {
565 auto *empty = new FlowField();
566 empty->resize(0, 0);
567 empty->setGoal(gx, gy);
568 return empty;
569 }
570
571 if (impl_->hasCachedField && !grid.dirty && impl_->cachedGoalX == gx && impl_->cachedGoalY == gy &&
572 impl_->cachedField.getWidth() == grid.width &&
573 impl_->cachedField.getHeight() == grid.height) {
574 auto *copy = new FlowField();
575 *copy = impl_->cachedField;
576 return copy;
577 }
578
579 FlowField *field = buildFlowFieldUncached(grid, gx, gy);
580 impl_->cachedField = *field;
581 impl_->cachedGoalX = gx;
582 impl_->cachedGoalY = gy;
583 impl_->hasCachedField = true;
584 grid.dirty = false;
585 return field;
586}
587
588Path *Pathfinder::followFlow(FlowField *field, int sx, int sy) {
589 auto *path = new Path();
590 Grid &grid = impl_->grid;
591 if (!field || !ensureSynced(grid)) return path;
592 if (!field->isReachable(sx, sy)) return path;
593
594 const int gx = field->getGoalX();
595 const int gy = field->getGoalY();
596 const int maxSteps = grid.width * grid.height + 2;
597 int x = sx;
598 int y = sy;
599 path->add(x, y);
600 float total = 0.f;
601 for (int step = 0; step < maxSteps; ++step) {
602 if (x == gx && y == gy) break;
603 const int nx = field->nextX(x, y);
604 const int ny = field->nextY(x, y);
605 if (nx == x && ny == y) break;
606 const float c0 = field->costAt(x, y);
607 const float c1 = field->costAt(nx, ny);
608 if (c0 < kInf && c1 < kInf && c0 >= c1) total += (c0 - c1);
609 x = nx;
610 y = ny;
611 path->add(x, y);
612 }
613 path->setTotalCost(total);
614 if (path->getLength() > 0) {
615 const int lx = path->getX(path->getLength() - 1);
616 const int ly = path->getY(path->getLength() - 1);
617 if (lx != gx || ly != gy) path->clear();
618 }
619 return path;
620}
621
622Path *Pathfinder::findGroupPath(int sx, int sy, int gx, int gy) {
623 Grid &grid = impl_->grid;
624 if (!ensureSynced(grid)) return new Path();
625 if (!impl_->hasCachedField || grid.dirty || impl_->cachedGoalX != gx || impl_->cachedGoalY != gy ||
626 impl_->cachedField.getWidth() != grid.width ||
627 impl_->cachedField.getHeight() != grid.height) {
628 FlowField *tmp = buildFlowFieldUncached(grid, gx, gy);
629 impl_->cachedField = *tmp;
630 delete tmp;
631 impl_->cachedGoalX = gx;
632 impl_->cachedGoalY = gy;
633 impl_->hasCachedField = true;
634 grid.dirty = false;
635 }
636 return followFlow(&impl_->cachedField, sx, sy);
637}
638
639// --- Path / FlowField (same TU; keep export surface small on Windows) ---
640
641void Path::clear() {
642 cells_.clear();
643 totalCost_ = 0.f;
644}
645
646void Path::add(int x, int y) { cells_.push_back(Cell{x, y}); }
647
648void Path::reverse() {
649 for (size_t i = 0, j = cells_.size(); i + 1 < j; ++i, --j) std::swap(cells_[i], cells_[j - 1]);
650}
651
652int Path::getLength() const { return int(cells_.size()); }
653
654int Path::getX(int index) const {
655 if (index < 0 || index >= int(cells_.size())) return 0;
656 return cells_[size_t(index)].x;
657}
658
659int Path::getY(int index) const {
660 if (index < 0 || index >= int(cells_.size())) return 0;
661 return cells_[size_t(index)].y;
662}
663
664float Path::getTotalCost() const { return totalCost_; }
665
666void Path::setTotalCost(float cost) { totalCost_ = cost; }
667
668void FlowField::clear() {
669 width_ = height_ = 0;
670 goalX_ = goalY_ = 0;
671 cost_.clear();
672 nextX_.clear();
673 nextY_.clear();
674}
675
676void FlowField::resize(int width, int height) {
677 width_ = width > 0 ? width : 0;
678 height_ = height > 0 ? height : 0;
679 const int n = width_ * height_;
680 cost_.assign(size_t(n), kUnreachable);
681 nextX_.assign(size_t(n), 0);
682 nextY_.assign(size_t(n), 0);
683 for (int y = 0; y < height_; ++y) {
684 for (int x = 0; x < width_; ++x) {
685 const int i = index(x, y);
686 nextX_[size_t(i)] = x;
687 nextY_[size_t(i)] = y;
688 }
689 }
690}
691
692void FlowField::setGoal(int x, int y) {
693 goalX_ = x;
694 goalY_ = y;
695}
696
697bool FlowField::inBounds(int x, int y) const {
698 return x >= 0 && y >= 0 && x < width_ && y < height_;
699}
700
701float FlowField::costAt(int x, int y) const {
702 if (!inBounds(x, y)) return kUnreachable;
703 return cost_[size_t(index(x, y))];
704}
705
706void FlowField::setCost(int x, int y, float cost) {
707 if (!inBounds(x, y)) return;
708 cost_[size_t(index(x, y))] = cost;
709}
710
711int FlowField::nextX(int x, int y) const {
712 if (!inBounds(x, y)) return x;
713 return nextX_[size_t(index(x, y))];
714}
715
716int FlowField::nextY(int x, int y) const {
717 if (!inBounds(x, y)) return y;
718 return nextY_[size_t(index(x, y))];
719}
720
721void FlowField::setNext(int x, int y, int nx, int ny) {
722 if (!inBounds(x, y)) return;
723 const int i = index(x, y);
724 nextX_[size_t(i)] = nx;
725 nextY_[size_t(i)] = ny;
726}
727
728bool FlowField::isReachable(int x, int y) const {
729 const float c = costAt(x, y);
730 return c < kUnreachable && c >= 0.f;
731}
732
733} // namespace eve::map
Duration start
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
const std::string & s
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float nx
float ny
eve::resource::CostSpec cost
float u
Definition Grass.cpp:233
glm::vec3 n
Definition Grass.cpp:63
std::array< double, 10 > q
double r
std::int32_t c
int h
std::uint32_t height
std::uint32_t width
std::int32_t parent
std::string name
Topology topology
bool staggerOdd
std::unordered_set< uint32_t > blockedGids
std::vector< uint8_t > exitMask
bool topologyManual
bool diagonal
bool staggerAxisY
uint64_t layerRevision
bool blockEmpty
TileLayer * layer
bool dirty
std::vector< uint8_t > enterMask
int idx
float f
std::string path
Definition PlayHost.cpp:110
float t
RoadLaneDirection direction
bool found
float dy
float dx
float step
Definition TreeMesh.cpp:314
uint32_t index
std::vector< WfcTile > tiles
Definition WfcSimple.cpp:22
Integration + direction field for group pathfinding to a single goal. nextX/nextY point to the neighb...
Definition FlowField.h:15
Ordered tile-index waypoints from start to goal (inclusive).
Definition Path.h:10
Pathfinding facade. Single-agent: A*. Group (same goal): Flow Field + follow. Grid/topology internals...
Definition Pathfinder.h:20
std::string getTopology() const
Returns the topology.
void invalidateCache()
Invalidate cached flow field (also called when grid dirties).
void setBlockEmpty(bool enable)
Sets the block empty.
void syncFromLayer()
Synchronizes from layer.
float getCellCost(int x, int y) const
Returns the cell cost.
void bindLayer(TileLayer *layer)
Binds layer.
void setCellCost(int x, int y, float cost)
Sets the cell cost.
void setTopology(const std::string &name)
Sets the topology.
bool getBlockEmpty() const
Returns the block empty.
bool isWalkable(int x, int y) const
True when walkable.
bool getDiagonal() const
Returns the diagonal.
Pathfinder()
Pathfinder.
void blockGid(int gid)
Block gid.
void setBlocked(int x, int y, bool blocked)
Sets the blocked.
~Pathfinder()
Pathfinder.
void setSize(int width, int height)
Sets the size.
void clearBlockedGids()
Clears blocked gids.
void unblockGid(int gid)
Unblock gid.
void setDiagonal(bool enable)
Sets the diagonal.
ECS tile layer entity. Script mutates tile GIDs / tileset / draw; TileRenderSystem batch-draws atlas ...
Definition TileLayer.h:32
void bindLayer(Shader *shader, bool alwaysDark)
Binds layer.
Definition Grass.cpp:291
std::int32_t moveCost(const HexMap &map, HexCoordinates from, HexCoordinates to, HexDirection direction, const HexOccupancyQuery &occupied)
Cost of moving between two adjacent cells.
constexpr HexDirection opposite(HexDirection d) noexcept
The direction opposite to d.
Definition HexMetrics.h:65
放置世界:格子占用(多通道)+ 地形语义 + 已放置建筑实例。 行为由 PlacementSystem 提供;本类暴露便于脚本绑定的薄封装方法。 坐标换算统一走 eve::grid(支持 rectang...
MapOrientation
Tile map layout: orthogonal, isometric, staggered, or hexagonal.
uint32_t tileGid(uint32_t raw)
Strip Tiled flip / rotate flags; keep low 28 bits.
Definition TileLayer.h:383
const EditorValue * field(const EditorValue &value, const char *name)
WidgetDesc grid(int columns, std::vector< WidgetDesc > children, std::string id)
Fixed-column grid; children are placed in source order.
Definition Widget.cpp:687
SettlementPipeline::Stage fn
One waypoint.
Definition Path.h:34