12constexpr float kSqrt2 = 1.41421356237f;
14bool finiteCost(
float c) {
return c < CrowdField::kUnreachable && c >= 0.f; }
19 const bool dimsChanged =
width != width_ ||
height != height_;
20 width_ = std::max(
width, 0);
21 height_ = std::max(
height, 0);
22 cellSize_ = cellSize > 0.f ? cellSize : 0.f;
26 const int n = width_ * height_;
28 cost_.assign(
size_t(
n), 1.f);
32 flowX_.assign(
size_t(
n), 0.f);
33 flowY_.assign(
size_t(
n), 0.f);
40 originX_ = originY_ = 0.f;
50 if (!inBounds(
cx,
cy))
return;
51 cost_[size_t(
index(
cx,
cy))] = blocked ? 0.f : 1.f;
55 if (!inBounds(
cx,
cy))
return true;
56 return cost_[size_t(
index(
cx,
cy))] <= 0.f;
60 if (!inBounds(
cx,
cy))
return;
65 if (!inBounds(
cx,
cy))
return 0.f;
70 cost_.assign(
size_t(width_ * height_),
cost > 0.f ?
cost : 1.f);
79 if (!inBounds(gx, gy))
return;
86void CrowdField::worldToCell(
float wx,
float wy,
float &fx,
float &fy)
const {
87 fx = (
wx - originX_) / cellSize_ - 0.5f;
88 fy = (
wy - originY_) / cellSize_ - 0.5f;
92 const int n = width_ * height_;
94 flowX_.assign(
size_t(
n), 0.f);
95 flowY_.assign(
size_t(
n), 0.f);
98 if (!
valid() || goals_.empty())
return;
100 using HeapNode = std::pair<float, int>;
101 std::priority_queue<HeapNode, std::vector<HeapNode>, std::greater<HeapNode>> open;
102 for (
size_t i = 0; i + 1 < goals_.size(); i += 2) {
103 const int gx = goals_[i];
104 const int gy = goals_[i + 1];
106 const int gi =
index(gx, gy);
107 if (integ_[
size_t(gi)] > 0.f) {
108 integ_[size_t(gi)] = 0.f;
109 open.emplace(0.f, gi);
114 static const int kDx[8] = {1, -1, 0, 0, 1, 1, -1, -1};
115 static const int kDy[8] = {0, 0, 1, -1, 1, -1, 1, -1};
116 static const float kMoveCost[8] = {1.f, 1.f, 1.f, 1.f, kSqrt2, kSqrt2, kSqrt2, kSqrt2};
118 while (!open.empty()) {
119 const HeapNode cur = open.top();
121 const float dist = cur.first;
122 if (dist > integ_[
size_t(cur.second)])
continue;
123 const int cx = cur.second % width_;
124 const int cy = cur.second / width_;
126 for (
int i = 0; i < 8; ++i) {
127 const int nx =
cx + kDx[i];
128 const int ny =
cy + kDy[i];
130 if (kDx[i] != 0 && kDy[i] != 0) {
135 const float tentative = dist + kMoveCost[i] * cost_[size_t(ni)];
136 if (tentative >= integ_[
size_t(ni)])
continue;
137 integ_[size_t(ni)] = tentative;
138 open.emplace(tentative, ni);
144void CrowdField::rebuildFlow() {
145 for (
int cy = 0;
cy < height_; ++
cy) {
146 for (
int cx = 0;
cx < width_; ++
cx) {
148 const float c = integ_[size_t(i)];
149 if (!finiteCost(
c) ||
c == 0.f)
continue;
154 if (
cx > 0 && finiteCost(integ_[
size_t(i - 1)]))
155 gx += integ_[size_t(i - 1)];
158 if (
cx + 1 < width_ && finiteCost(integ_[
size_t(i + 1)]))
159 gx -= integ_[size_t(i + 1)];
164 if (
cy > 0 && finiteCost(integ_[
size_t(i - width_)]))
165 gy += integ_[size_t(i - width_)];
168 if (
cy + 1 < height_ && finiteCost(integ_[
size_t(i + width_)]))
169 gy -= integ_[size_t(i + width_)];
173 const float len = std::sqrt(gx * gx + gy * gy);
175 flowX_[size_t(i)] = gx / len;
176 flowY_[size_t(i)] = gy / len;
193 if (!inBounds(
cx,
cy))
return;
195 dx = flowX_[size_t(i)];
196 dy = flowY_[size_t(i)];
201 float fx = 0.f, fy = 0.f;
202 worldToCell(
wx,
wy, fx, fy);
203 if (fx < -0.5f || fy < -0.5f || fx >
float(width_) - 0.5f || fy >
float(height_) - 0.5f)
206 const int x0 = std::clamp(
int(std::floor(fx)), 0, width_ - 1);
207 const int y0 = std::clamp(
int(std::floor(fy)), 0, height_ - 1);
208 const int x1 = std::min(x0 + 1, width_ - 1);
209 const int y1 = std::min(y0 + 1, height_ - 1);
210 const float tx = std::clamp(fx -
float(x0), 0.f, 1.f);
211 const float ty = std::clamp(fy -
float(y0), 0.f, 1.f);
213 const float c00 = integ_[size_t(
index(x0, y0))];
214 const float c10 = integ_[size_t(
index(x1, y0))];
215 const float c01 = integ_[size_t(
index(x0, y1))];
216 const float c11 = integ_[size_t(
index(x1, y1))];
221 const auto acc = [&](
float c,
float w) {
227 acc(c00, (1.f - tx) * (1.f - ty));
228 acc(c10, tx * (1.f - ty));
229 acc(c01, (1.f - tx) * ty);
236 if (!
valid())
return;
237 float fx = 0.f, fy = 0.f;
238 worldToCell(
wx,
wy, fx, fy);
239 if (fx < -0.5f || fy < -0.5f || fx >
float(width_) - 0.5f || fy >
float(height_) - 0.5f)
242 const int x0 = std::clamp(
int(std::floor(fx)), 0, width_ - 1);
243 const int y0 = std::clamp(
int(std::floor(fy)), 0, height_ - 1);
244 const int x1 = std::min(x0 + 1, width_ - 1);
245 const int y1 = std::min(y0 + 1, height_ - 1);
246 const float tx = std::clamp(fx -
float(x0), 0.f, 1.f);
247 const float ty = std::clamp(fy -
float(y0), 0.f, 1.f);
249 const auto sample = [&](
int x,
int y,
float &
sx,
float &
sy) {
251 sx = finiteCost(integ_[
size_t(i)]) ? flowX_[size_t(i)] : 0.f;
252 sy = finiteCost(integ_[
size_t(i)]) ? flowY_[size_t(i)] : 0.f;
254 float f00x = 0.f, f00y = 0.f, f10x = 0.f, f10y = 0.f;
255 float f01x = 0.f, f01y = 0.f, f11x = 0.f, f11y = 0.f;
256 sample(x0, y0, f00x, f00y);
257 sample(x1, y0, f10x, f10y);
258 sample(x0, y1, f01x, f01y);
259 sample(x1, y1, f11x, f11y);
261 dx = (1.f - ty) * ((1.f - tx) * f00x + tx * f10x) + ty * ((1.f - tx) * f01x + tx * f11x);
262 dy = (1.f - ty) * ((1.f - tx) * f00y + tx * f10y) + ty * ((1.f - tx) * f01y + tx * f11y);
263 const float len = std::sqrt(
dx *
dx +
dy *
dy);
274 const float cs = cellSize_;
275 const int cx0 = std::max(0,
int(std::floor((
wx -
radius - originX_) / cs)));
276 const int cx1 = std::min(width_ - 1,
int(std::floor((
wx +
radius - originX_) / cs)));
277 const int cy0 = std::max(0,
int(std::floor((
wy -
radius - originY_) / cs)));
278 const int cy1 = std::min(height_ - 1,
int(std::floor((
wy +
radius - originY_) / cs)));
280 for (
int cy = cy0;
cy <= cy1; ++
cy) {
281 for (
int cx = cx0;
cx <= cx1; ++
cx) {
283 const float loX = originX_ + float(
cx) * cs, hiX = loX + cs;
284 const float loY = originY_ + float(
cy) * cs, hiY = loY + cs;
285 const float dx =
wx - std::clamp(
wx, loX, hiX);
286 const float dy =
wy - std::clamp(
wy, loY, hiY);
290 const float distance = std::sqrt(d2);
297 const float down =
wy - loY,
up = hiY -
wy;
298 const float nearest = std::min({
left,
right, down,
up});
301 else if (nearest ==
right)
303 else if (nearest == down)
eve::resource::CostSpec cost
std::array< float, 3 > scale
void setGoal(int gx, int gy)
设定唯一目标格(等价 clearGoals + addGoal)。
void flowAtCell(int cx, int cy, float &dx, float &dy) const
查询格级方向向量(单位长度;目标格/阻挡/不可达为 0)。
float costAtWorld(float wx, float wy) const
查询世界坐标处的积分代价(双线性插值;场外返回 kUnreachable)。
void setBlocked(int cx, int cy, bool blocked)
设置/清除某格阻挡。
bool isReachable(int cx, int cy) const
某格是否可达(积分代价有限且非负)。
void setCellCost(int cx, int cy, float cost)
设置某格地形代价(进入该格的移动成本)。
bool resolvePenetration(float &wx, float &wy, float radius) const
圆 vs 阻挡格碰撞消解:把圆心从覆盖到的阻挡格中推出来。
void flowAtWorld(float wx, float wy, float &dx, float &dy) const
查询世界坐标处的跟随方向(双线性插值后归一化)。
void clear()
清空全部数据(回到无效状态)。
bool isBlocked(int cx, int cy) const
查询某格是否阻挡。
void setAllCellCost(float cost)
把全部格子设为同一地形代价(通常 build 前重置用)。
float getCellCost(int cx, int cy) const
查询地形代价(越界/阻挡返回 0)。
static constexpr float kUnreachable
不可达/阻挡的积分代价。
void resize(int width, int height, float cellSize, float originX, float originY)
配置网格(保留原有 cost/goals,若尺寸变化则重置)。
float costAtCell(int cx, int cy) const
查询格级积分代价(未 build 返回 kUnreachable)。
bool valid() const
是否配置了有效网格。
void build()
从目标格做 Dijkstra,生成积分场与方向场。
void addGoal(int gx, int gy)
追加一个目标格(多目标支持)。