682 lines
20 KiB
C++
682 lines
20 KiB
C++
#include "map/Pathfinder.h"
|
|
|
|
#include "map/TileOrientation.h"
|
|
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <cstdint>
|
|
#include <functional>
|
|
#include <limits>
|
|
#include <queue>
|
|
#include <tuple>
|
|
#include <unordered_set>
|
|
#include <vector>
|
|
|
|
namespace eve::map {
|
|
namespace {
|
|
|
|
constexpr float kInf = std::numeric_limits<float>::infinity();
|
|
constexpr float kSqrt2 = 1.41421356237f;
|
|
|
|
enum class Topology { Ortho4, Ortho8, Hex };
|
|
|
|
using NeighborFn = std::function<void(int nx, int ny, float moveCost)>;
|
|
|
|
Topology parseTopology(const std::string &name, Topology fallback) {
|
|
if (name == "ortho4" || name == "orthogonal4" || name == "4") return Topology::Ortho4;
|
|
if (name == "ortho8" || name == "orthogonal8" || name == "8") return Topology::Ortho8;
|
|
if (name == "hex" || name == "hexagonal" || name == "staggered") return Topology::Hex;
|
|
if (name == "auto") return fallback;
|
|
return fallback;
|
|
}
|
|
|
|
const char *topologyName(Topology t) {
|
|
switch (t) {
|
|
case Topology::Ortho4:
|
|
return "ortho4";
|
|
case Topology::Ortho8:
|
|
return "ortho8";
|
|
case Topology::Hex:
|
|
return "hex";
|
|
}
|
|
return "ortho4";
|
|
}
|
|
|
|
Topology topologyFromOrientation(MapOrientation orientation, bool preferDiagonal) {
|
|
switch (orientation) {
|
|
case MapOrientation::Staggered:
|
|
case MapOrientation::Hexagonal:
|
|
return Topology::Hex;
|
|
case MapOrientation::Orthogonal:
|
|
case MapOrientation::Isometric:
|
|
default:
|
|
return preferDiagonal ? Topology::Ortho8 : Topology::Ortho4;
|
|
}
|
|
}
|
|
|
|
void forEachNeighbor(Topology topology, int x, int y, bool staggerAxisY, bool staggerOdd,
|
|
const NeighborFn &fn) {
|
|
if (!fn) return;
|
|
switch (topology) {
|
|
case Topology::Ortho4: {
|
|
static const int dx[4] = {1, -1, 0, 0};
|
|
static const int dy[4] = {0, 0, 1, -1};
|
|
for (int i = 0; i < 4; ++i) fn(x + dx[i], y + dy[i], 1.f);
|
|
break;
|
|
}
|
|
case Topology::Ortho8: {
|
|
static const int dx[8] = {1, -1, 0, 0, 1, 1, -1, -1};
|
|
static const int dy[8] = {0, 0, 1, -1, 1, -1, 1, -1};
|
|
for (int i = 0; i < 8; ++i) {
|
|
const float c = (i < 4) ? 1.f : kSqrt2;
|
|
fn(x + dx[i], y + dy[i], c);
|
|
}
|
|
break;
|
|
}
|
|
case Topology::Hex: {
|
|
if (staggerAxisY) {
|
|
const bool rowOdd = ((y & 1) != 0);
|
|
const bool shifted = staggerOdd ? rowOdd : !rowOdd;
|
|
if (shifted) {
|
|
fn(x + 1, y, 1.f);
|
|
fn(x - 1, y, 1.f);
|
|
fn(x, y - 1, 1.f);
|
|
fn(x + 1, y - 1, 1.f);
|
|
fn(x, y + 1, 1.f);
|
|
fn(x + 1, y + 1, 1.f);
|
|
} else {
|
|
fn(x + 1, y, 1.f);
|
|
fn(x - 1, y, 1.f);
|
|
fn(x - 1, y - 1, 1.f);
|
|
fn(x, y - 1, 1.f);
|
|
fn(x - 1, y + 1, 1.f);
|
|
fn(x, y + 1, 1.f);
|
|
}
|
|
} else {
|
|
const bool colOdd = ((x & 1) != 0);
|
|
const bool shifted = staggerOdd ? colOdd : !colOdd;
|
|
if (shifted) {
|
|
fn(x, y + 1, 1.f);
|
|
fn(x, y - 1, 1.f);
|
|
fn(x - 1, y, 1.f);
|
|
fn(x - 1, y + 1, 1.f);
|
|
fn(x + 1, y, 1.f);
|
|
fn(x + 1, y + 1, 1.f);
|
|
} else {
|
|
fn(x, y + 1, 1.f);
|
|
fn(x, y - 1, 1.f);
|
|
fn(x - 1, y - 1, 1.f);
|
|
fn(x - 1, y, 1.f);
|
|
fn(x + 1, y - 1, 1.f);
|
|
fn(x + 1, y, 1.f);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
float heuristic(Topology topology, int x0, int y0, int x1, int y1) {
|
|
const int dx = std::abs(x1 - x0);
|
|
const int dy = std::abs(y1 - y0);
|
|
switch (topology) {
|
|
case Topology::Ortho4:
|
|
return float(dx + dy);
|
|
case Topology::Ortho8:
|
|
return float(std::max(dx, dy)) + (kSqrt2 - 1.f) * float(std::min(dx, dy));
|
|
case Topology::Hex: {
|
|
auto toCube = [](int x, int y) {
|
|
const int q = x - (y - (y & 1)) / 2;
|
|
const int r = y;
|
|
const int s = -q - r;
|
|
return std::tuple<int, int, int>{q, r, s};
|
|
};
|
|
auto [q0, r0, s0] = toCube(x0, y0);
|
|
auto [q1, r1, s1] = toCube(x1, y1);
|
|
return float(std::max({std::abs(q0 - q1), std::abs(r0 - r1), std::abs(s0 - s1)}));
|
|
}
|
|
}
|
|
return float(dx + dy);
|
|
}
|
|
|
|
/** Navigable grid — internal linkage to keep Windows export table small. */
|
|
struct Grid {
|
|
int width = 0;
|
|
int height = 0;
|
|
TileLayer *layer = nullptr;
|
|
uint64_t layerRevision = 0;
|
|
Topology topology = Topology::Ortho4;
|
|
bool topologyManual = false;
|
|
bool diagonal = false;
|
|
bool blockEmpty = true;
|
|
bool staggerAxisY = true;
|
|
bool staggerOdd = true;
|
|
bool dirty = true;
|
|
std::vector<float> cost; // ≤0 => blocked
|
|
std::unordered_set<uint32_t> blockedGids;
|
|
|
|
int index(int x, int y) const { return y * width + x; }
|
|
bool inBounds(int x, int y) const { return x >= 0 && y >= 0 && x < width && y < height; }
|
|
|
|
void resize(int w, int h) {
|
|
width = w > 0 ? w : 0;
|
|
height = h > 0 ? h : 0;
|
|
cost.assign(size_t(width * height), 1.f);
|
|
dirty = true;
|
|
}
|
|
|
|
void applyAutoTopologyFromLayer() {
|
|
if (!layer) return;
|
|
topology = topologyFromOrientation(layer->config()->orientation, diagonal);
|
|
}
|
|
|
|
void bindLayer(TileLayer *l) {
|
|
layer = l;
|
|
if (!layer) return;
|
|
auto cfg = layer->config();
|
|
resize(cfg->mapW, cfg->mapH);
|
|
staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
|
|
staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
|
|
if (!topologyManual) applyAutoTopologyFromLayer();
|
|
syncFromLayer();
|
|
}
|
|
|
|
void clearLayer() { layer = nullptr; }
|
|
|
|
void syncFromLayer() {
|
|
if (!layer) return;
|
|
auto cfg = layer->config();
|
|
auto tiles = layer->tiles();
|
|
if (cfg->mapW != width || cfg->mapH != height) resize(cfg->mapW, cfg->mapH);
|
|
staggerAxisY = cfg->staggerAxis == StaggerAxis::Y;
|
|
staggerOdd = cfg->staggerIndex == StaggerIndex::Odd;
|
|
if (!topologyManual) applyAutoTopologyFromLayer();
|
|
|
|
const int n = width * height;
|
|
for (int i = 0; i < n; ++i) {
|
|
const uint32_t gid = (i < int(tiles->gids.size())) ? tileGid(tiles->gids[size_t(i)]) : 0u;
|
|
bool blocked = false;
|
|
if (blockEmpty && gid == 0u) blocked = true;
|
|
if (blockedGids.count(gid)) blocked = true;
|
|
cost[size_t(i)] = blocked ? 0.f : 1.f;
|
|
}
|
|
layerRevision = tiles->revision;
|
|
dirty = true;
|
|
}
|
|
|
|
void setTopology(const std::string &name) {
|
|
if (name == "auto") {
|
|
topologyManual = false;
|
|
if (layer) applyAutoTopologyFromLayer();
|
|
else topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
|
|
} else {
|
|
topologyManual = true;
|
|
topology = parseTopology(name, topology);
|
|
}
|
|
dirty = true;
|
|
}
|
|
|
|
void setDiagonal(bool enable) {
|
|
diagonal = enable;
|
|
if (!topologyManual && layer) applyAutoTopologyFromLayer();
|
|
else if (!topologyManual) topology = diagonal ? Topology::Ortho8 : Topology::Ortho4;
|
|
dirty = true;
|
|
}
|
|
|
|
void blockGid(int gid) {
|
|
if (gid < 0) return;
|
|
blockedGids.insert(uint32_t(gid));
|
|
dirty = true;
|
|
if (layer) syncFromLayer();
|
|
}
|
|
|
|
void unblockGid(int gid) {
|
|
if (gid < 0) return;
|
|
blockedGids.erase(uint32_t(gid));
|
|
dirty = true;
|
|
if (layer) syncFromLayer();
|
|
}
|
|
|
|
void clearBlockedGids() {
|
|
blockedGids.clear();
|
|
dirty = true;
|
|
if (layer) syncFromLayer();
|
|
}
|
|
|
|
void setBlockEmpty(bool enable) {
|
|
blockEmpty = enable;
|
|
dirty = true;
|
|
if (layer) syncFromLayer();
|
|
}
|
|
|
|
bool isWalkable(int x, int y) const {
|
|
if (!inBounds(x, y)) return false;
|
|
return cost[size_t(index(x, y))] > 0.f;
|
|
}
|
|
|
|
void setBlocked(int x, int y, bool blocked) {
|
|
if (!inBounds(x, y)) return;
|
|
cost[size_t(index(x, y))] = blocked ? 0.f : 1.f;
|
|
dirty = true;
|
|
}
|
|
|
|
void setCellCost(int x, int y, float c) {
|
|
if (!inBounds(x, y)) return;
|
|
cost[size_t(index(x, y))] = c;
|
|
dirty = true;
|
|
}
|
|
|
|
float getCellCost(int x, int y) const {
|
|
if (!inBounds(x, y)) return 0.f;
|
|
return cost[size_t(index(x, y))];
|
|
}
|
|
|
|
void forEachWalkableNeighbor(int x, int y, const NeighborFn &fn) const {
|
|
if (!fn || !inBounds(x, y)) return;
|
|
forEachNeighbor(topology, x, y, staggerAxisY, staggerOdd, [&](int nx, int ny, float moveCost) {
|
|
if (!isWalkable(nx, ny)) return;
|
|
if (topology == Topology::Ortho8) {
|
|
const int dx = nx - x;
|
|
const int dy = ny - y;
|
|
if (dx != 0 && dy != 0) {
|
|
if (!isWalkable(x + dx, y) || !isWalkable(x, y + dy)) return;
|
|
}
|
|
}
|
|
fn(nx, ny, moveCost * getCellCost(nx, ny));
|
|
});
|
|
}
|
|
};
|
|
|
|
struct HeapNode {
|
|
float f = 0.f;
|
|
int idx = 0;
|
|
bool operator>(const HeapNode &o) const { return f > o.f; }
|
|
};
|
|
|
|
} // namespace
|
|
|
|
struct Pathfinder::Impl {
|
|
Grid grid;
|
|
bool hasCachedField = false;
|
|
int cachedGoalX = 0;
|
|
int cachedGoalY = 0;
|
|
FlowField cachedField;
|
|
};
|
|
|
|
Pathfinder::Pathfinder() : impl_(std::make_unique<Impl>()) {}
|
|
|
|
Pathfinder::Pathfinder(TileLayer *layer) : Pathfinder() { bindLayer(layer); }
|
|
|
|
Pathfinder::Pathfinder(int width, int height) : Pathfinder() { setSize(width, height); }
|
|
|
|
Pathfinder::~Pathfinder() = default;
|
|
Pathfinder::Pathfinder(Pathfinder &&) noexcept = default;
|
|
Pathfinder &Pathfinder::operator=(Pathfinder &&) noexcept = default;
|
|
|
|
void Pathfinder::bindLayer(TileLayer *layer) {
|
|
impl_->grid.bindLayer(layer);
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::setSize(int width, int height) {
|
|
impl_->grid.clearLayer();
|
|
impl_->grid.resize(width, height);
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::setTopology(const std::string &name) {
|
|
impl_->grid.setTopology(name);
|
|
invalidateCache();
|
|
}
|
|
|
|
std::string Pathfinder::getTopology() const { return topologyName(impl_->grid.topology); }
|
|
|
|
void Pathfinder::setDiagonal(bool enable) {
|
|
impl_->grid.setDiagonal(enable);
|
|
invalidateCache();
|
|
}
|
|
|
|
bool Pathfinder::getDiagonal() const { return impl_->grid.diagonal; }
|
|
|
|
void Pathfinder::blockGid(int gid) {
|
|
impl_->grid.blockGid(gid);
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::unblockGid(int gid) {
|
|
impl_->grid.unblockGid(gid);
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::clearBlockedGids() {
|
|
impl_->grid.clearBlockedGids();
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::setBlockEmpty(bool enable) {
|
|
impl_->grid.setBlockEmpty(enable);
|
|
invalidateCache();
|
|
}
|
|
|
|
bool Pathfinder::getBlockEmpty() const { return impl_->grid.blockEmpty; }
|
|
|
|
void Pathfinder::setBlocked(int x, int y, bool blocked) {
|
|
impl_->grid.setBlocked(x, y, blocked);
|
|
invalidateCache();
|
|
}
|
|
|
|
bool Pathfinder::isWalkable(int x, int y) const { return impl_->grid.isWalkable(x, y); }
|
|
|
|
void Pathfinder::setCellCost(int x, int y, float cost) {
|
|
impl_->grid.setCellCost(x, y, cost);
|
|
invalidateCache();
|
|
}
|
|
|
|
float Pathfinder::getCellCost(int x, int y) const { return impl_->grid.getCellCost(x, y); }
|
|
|
|
void Pathfinder::syncFromLayer() {
|
|
impl_->grid.syncFromLayer();
|
|
invalidateCache();
|
|
}
|
|
|
|
void Pathfinder::invalidateCache() {
|
|
impl_->hasCachedField = false;
|
|
impl_->cachedField.clear();
|
|
}
|
|
|
|
namespace {
|
|
|
|
bool ensureSynced(Grid &grid) {
|
|
if (grid.layer && (grid.dirty || grid.layerRevision != grid.layer->tiles()->revision))
|
|
grid.syncFromLayer();
|
|
return grid.width > 0 && grid.height > 0;
|
|
}
|
|
|
|
FlowField *buildFlowFieldUncached(Grid &grid, int gx, int gy) {
|
|
auto *field = new FlowField();
|
|
if (!ensureSynced(grid) || !grid.isWalkable(gx, gy)) {
|
|
field->resize(grid.width, grid.height);
|
|
field->setGoal(gx, gy);
|
|
return field;
|
|
}
|
|
|
|
const int w = grid.width;
|
|
const int h = grid.height;
|
|
field->resize(w, h);
|
|
field->setGoal(gx, gy);
|
|
|
|
const auto idx = [w](int x, int y) { return y * w + x; };
|
|
std::vector<float> dist(size_t(w * h), kInf);
|
|
std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
|
|
|
|
dist[size_t(idx(gx, gy))] = 0.f;
|
|
field->setCost(gx, gy, 0.f);
|
|
field->setNext(gx, gy, gx, gy);
|
|
open.push(HeapNode{0.f, idx(gx, gy)});
|
|
|
|
while (!open.empty()) {
|
|
const HeapNode cur = open.top();
|
|
open.pop();
|
|
if (cur.f > dist[size_t(cur.idx)]) continue;
|
|
const int cx = cur.idx % w;
|
|
const int cy = cur.idx / w;
|
|
const float enterCur = grid.getCellCost(cx, cy);
|
|
forEachNeighbor(grid.topology, cx, cy, grid.staggerAxisY, grid.staggerOdd,
|
|
[&](int nx, int ny, float moveCost) {
|
|
if (!grid.isWalkable(nx, ny)) return;
|
|
if (grid.topology == Topology::Ortho8) {
|
|
const int dx = nx - cx;
|
|
const int dy = ny - cy;
|
|
if (dx != 0 && dy != 0) {
|
|
if (!grid.isWalkable(cx + dx, cy) || !grid.isWalkable(cx, cy + dy))
|
|
return;
|
|
}
|
|
}
|
|
const int ni = idx(nx, ny);
|
|
const float tentative = dist[size_t(cur.idx)] + moveCost * enterCur;
|
|
if (tentative + 1e-6f >= dist[size_t(ni)]) return;
|
|
dist[size_t(ni)] = tentative;
|
|
field->setCost(nx, ny, tentative);
|
|
field->setNext(nx, ny, cx, cy);
|
|
open.push(HeapNode{tentative, ni});
|
|
});
|
|
}
|
|
return field;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
Path *Pathfinder::findPath(int sx, int sy, int gx, int gy) {
|
|
auto *path = new Path();
|
|
Grid &grid = impl_->grid;
|
|
if (!ensureSynced(grid)) return path;
|
|
if (!grid.isWalkable(sx, sy) || !grid.isWalkable(gx, gy)) return path;
|
|
if (sx == gx && sy == gy) {
|
|
path->add(sx, sy);
|
|
path->setTotalCost(0.f);
|
|
return path;
|
|
}
|
|
|
|
const int w = grid.width;
|
|
const int n = w * grid.height;
|
|
const auto idx = [w](int x, int y) { return y * w + x; };
|
|
|
|
std::vector<float> gScore(size_t(n), kInf);
|
|
std::vector<int> parent(size_t(n), -1);
|
|
std::vector<uint8_t> closed(size_t(n), 0);
|
|
|
|
std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
|
|
const int start = idx(sx, sy);
|
|
const int goal = idx(gx, gy);
|
|
gScore[size_t(start)] = 0.f;
|
|
open.push(HeapNode{heuristic(grid.topology, sx, sy, gx, gy), start});
|
|
|
|
bool found = false;
|
|
while (!open.empty()) {
|
|
const HeapNode cur = open.top();
|
|
open.pop();
|
|
if (closed[size_t(cur.idx)]) continue;
|
|
closed[size_t(cur.idx)] = 1;
|
|
if (cur.idx == goal) {
|
|
found = true;
|
|
break;
|
|
}
|
|
const int cx = cur.idx % w;
|
|
const int cy = cur.idx / w;
|
|
grid.forEachWalkableNeighbor(cx, cy, [&](int nx, int ny, float edgeCost) {
|
|
const int ni = idx(nx, ny);
|
|
if (closed[size_t(ni)]) return;
|
|
const float tentative = gScore[size_t(cur.idx)] + edgeCost;
|
|
if (tentative >= gScore[size_t(ni)]) return;
|
|
gScore[size_t(ni)] = tentative;
|
|
parent[size_t(ni)] = cur.idx;
|
|
open.push(HeapNode{tentative + heuristic(grid.topology, nx, ny, gx, gy), ni});
|
|
});
|
|
}
|
|
|
|
if (!found) return path;
|
|
|
|
int cur = goal;
|
|
while (cur != -1) {
|
|
path->add(cur % w, cur / w);
|
|
if (cur == start) break;
|
|
cur = parent[size_t(cur)];
|
|
}
|
|
path->reverse();
|
|
path->setTotalCost(gScore[size_t(goal)]);
|
|
return path;
|
|
}
|
|
|
|
FlowField *Pathfinder::buildFlowField(int gx, int gy) {
|
|
Grid &grid = impl_->grid;
|
|
if (!ensureSynced(grid)) {
|
|
auto *empty = new FlowField();
|
|
empty->resize(0, 0);
|
|
empty->setGoal(gx, gy);
|
|
return empty;
|
|
}
|
|
|
|
if (impl_->hasCachedField && !grid.dirty && impl_->cachedGoalX == gx && impl_->cachedGoalY == gy &&
|
|
impl_->cachedField.getWidth() == grid.width &&
|
|
impl_->cachedField.getHeight() == grid.height) {
|
|
auto *copy = new FlowField();
|
|
*copy = impl_->cachedField;
|
|
return copy;
|
|
}
|
|
|
|
FlowField *field = buildFlowFieldUncached(grid, gx, gy);
|
|
impl_->cachedField = *field;
|
|
impl_->cachedGoalX = gx;
|
|
impl_->cachedGoalY = gy;
|
|
impl_->hasCachedField = true;
|
|
grid.dirty = false;
|
|
return field;
|
|
}
|
|
|
|
Path *Pathfinder::followFlow(FlowField *field, int sx, int sy) {
|
|
auto *path = new Path();
|
|
Grid &grid = impl_->grid;
|
|
if (!field || !ensureSynced(grid)) return path;
|
|
if (!field->isReachable(sx, sy)) return path;
|
|
|
|
const int gx = field->getGoalX();
|
|
const int gy = field->getGoalY();
|
|
const int maxSteps = grid.width * grid.height + 2;
|
|
int x = sx;
|
|
int y = sy;
|
|
path->add(x, y);
|
|
float total = 0.f;
|
|
for (int step = 0; step < maxSteps; ++step) {
|
|
if (x == gx && y == gy) break;
|
|
const int nx = field->nextX(x, y);
|
|
const int ny = field->nextY(x, y);
|
|
if (nx == x && ny == y) break;
|
|
const float c0 = field->costAt(x, y);
|
|
const float c1 = field->costAt(nx, ny);
|
|
if (c0 < kInf && c1 < kInf && c0 >= c1) total += (c0 - c1);
|
|
x = nx;
|
|
y = ny;
|
|
path->add(x, y);
|
|
}
|
|
path->setTotalCost(total);
|
|
if (path->getLength() > 0) {
|
|
const int lx = path->getX(path->getLength() - 1);
|
|
const int ly = path->getY(path->getLength() - 1);
|
|
if (lx != gx || ly != gy) path->clear();
|
|
}
|
|
return path;
|
|
}
|
|
|
|
Path *Pathfinder::findGroupPath(int sx, int sy, int gx, int gy) {
|
|
Grid &grid = impl_->grid;
|
|
if (!ensureSynced(grid)) return new Path();
|
|
if (!impl_->hasCachedField || grid.dirty || impl_->cachedGoalX != gx || impl_->cachedGoalY != gy ||
|
|
impl_->cachedField.getWidth() != grid.width ||
|
|
impl_->cachedField.getHeight() != grid.height) {
|
|
FlowField *tmp = buildFlowFieldUncached(grid, gx, gy);
|
|
impl_->cachedField = *tmp;
|
|
delete tmp;
|
|
impl_->cachedGoalX = gx;
|
|
impl_->cachedGoalY = gy;
|
|
impl_->hasCachedField = true;
|
|
grid.dirty = false;
|
|
}
|
|
return followFlow(&impl_->cachedField, sx, sy);
|
|
}
|
|
|
|
// --- Path / FlowField (same TU; keep export surface small on Windows) ---
|
|
|
|
void Path::clear() {
|
|
cells_.clear();
|
|
totalCost_ = 0.f;
|
|
}
|
|
|
|
void Path::add(int x, int y) { cells_.push_back(Cell{x, y}); }
|
|
|
|
void Path::reverse() {
|
|
for (size_t i = 0, j = cells_.size(); i + 1 < j; ++i, --j) std::swap(cells_[i], cells_[j - 1]);
|
|
}
|
|
|
|
int Path::getLength() const { return int(cells_.size()); }
|
|
|
|
int Path::getX(int index) const {
|
|
if (index < 0 || index >= int(cells_.size())) return 0;
|
|
return cells_[size_t(index)].x;
|
|
}
|
|
|
|
int Path::getY(int index) const {
|
|
if (index < 0 || index >= int(cells_.size())) return 0;
|
|
return cells_[size_t(index)].y;
|
|
}
|
|
|
|
float Path::getTotalCost() const { return totalCost_; }
|
|
|
|
void Path::setTotalCost(float cost) { totalCost_ = cost; }
|
|
|
|
void FlowField::clear() {
|
|
width_ = height_ = 0;
|
|
goalX_ = goalY_ = 0;
|
|
cost_.clear();
|
|
nextX_.clear();
|
|
nextY_.clear();
|
|
}
|
|
|
|
void FlowField::resize(int width, int height) {
|
|
width_ = width > 0 ? width : 0;
|
|
height_ = height > 0 ? height : 0;
|
|
const int n = width_ * height_;
|
|
cost_.assign(size_t(n), kUnreachable);
|
|
nextX_.assign(size_t(n), 0);
|
|
nextY_.assign(size_t(n), 0);
|
|
for (int y = 0; y < height_; ++y) {
|
|
for (int x = 0; x < width_; ++x) {
|
|
const int i = index(x, y);
|
|
nextX_[size_t(i)] = x;
|
|
nextY_[size_t(i)] = y;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FlowField::setGoal(int x, int y) {
|
|
goalX_ = x;
|
|
goalY_ = y;
|
|
}
|
|
|
|
bool FlowField::inBounds(int x, int y) const {
|
|
return x >= 0 && y >= 0 && x < width_ && y < height_;
|
|
}
|
|
|
|
float FlowField::costAt(int x, int y) const {
|
|
if (!inBounds(x, y)) return kUnreachable;
|
|
return cost_[size_t(index(x, y))];
|
|
}
|
|
|
|
void FlowField::setCost(int x, int y, float cost) {
|
|
if (!inBounds(x, y)) return;
|
|
cost_[size_t(index(x, y))] = cost;
|
|
}
|
|
|
|
int FlowField::nextX(int x, int y) const {
|
|
if (!inBounds(x, y)) return x;
|
|
return nextX_[size_t(index(x, y))];
|
|
}
|
|
|
|
int FlowField::nextY(int x, int y) const {
|
|
if (!inBounds(x, y)) return y;
|
|
return nextY_[size_t(index(x, y))];
|
|
}
|
|
|
|
void FlowField::setNext(int x, int y, int nx, int ny) {
|
|
if (!inBounds(x, y)) return;
|
|
const int i = index(x, y);
|
|
nextX_[size_t(i)] = nx;
|
|
nextY_[size_t(i)] = ny;
|
|
}
|
|
|
|
bool FlowField::isReachable(int x, int y) const {
|
|
const float c = costAt(x, y);
|
|
return c < kUnreachable && c >= 0.f;
|
|
}
|
|
|
|
} // namespace eve::map
|