载入中...
搜索中...
未找到
CrowdField.cpp
浏览该文件的文档.
1#include "crowd/CrowdField.h"
2
3#include <algorithm>
4#include <cmath>
5#include <functional>
6#include <queue>
7#include <utility>
8
9namespace eve::crowd {
10namespace {
11
12constexpr float kSqrt2 = 1.41421356237f;
13
14bool finiteCost(float c) { return c < CrowdField::kUnreachable && c >= 0.f; }
15
16} // namespace
17
18void CrowdField::resize(int width, int height, float cellSize, float originX, float originY) {
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;
23 originX_ = originX;
24 originY_ = originY;
25
26 const int n = width_ * height_;
27 if (dimsChanged) {
28 cost_.assign(size_t(n), 1.f);
29 goals_.clear();
30 }
31 integ_.assign(size_t(n), kUnreachable);
32 flowX_.assign(size_t(n), 0.f);
33 flowY_.assign(size_t(n), 0.f);
34 built_ = false;
35}
36
38 width_ = height_ = 0;
39 cellSize_ = 0.f;
40 originX_ = originY_ = 0.f;
41 cost_.clear();
42 integ_.clear();
43 flowX_.clear();
44 flowY_.clear();
45 goals_.clear();
46 built_ = false;
47}
48
49void CrowdField::setBlocked(int cx, int cy, bool blocked) {
50 if (!inBounds(cx, cy)) return;
51 cost_[size_t(index(cx, cy))] = blocked ? 0.f : 1.f;
52}
53
54bool CrowdField::isBlocked(int cx, int cy) const {
55 if (!inBounds(cx, cy)) return true;
56 return cost_[size_t(index(cx, cy))] <= 0.f;
57}
58
59void CrowdField::setCellCost(int cx, int cy, float cost) {
60 if (!inBounds(cx, cy)) return;
61 cost_[size_t(index(cx, cy))] = cost > 0.f ? cost : 0.f;
62}
63
64float CrowdField::getCellCost(int cx, int cy) const {
65 if (!inBounds(cx, cy)) return 0.f;
66 return cost_[size_t(index(cx, cy))];
67}
68
70 cost_.assign(size_t(width_ * height_), cost > 0.f ? cost : 1.f);
71}
72
73void CrowdField::setGoal(int gx, int gy) {
74 goals_.clear();
75 addGoal(gx, gy);
76}
77
78void CrowdField::addGoal(int gx, int gy) {
79 if (!inBounds(gx, gy)) return;
80 goals_.push_back(gx);
81 goals_.push_back(gy);
82}
83
84void CrowdField::clearGoals() { goals_.clear(); }
85
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;
89}
90
92 const int n = width_ * height_;
93 integ_.assign(size_t(n), kUnreachable);
94 flowX_.assign(size_t(n), 0.f);
95 flowY_.assign(size_t(n), 0.f);
96 built_ = true;
97
98 if (!valid() || goals_.empty()) return;
99
100 using HeapNode = std::pair<float, int>; // (cost, idx)
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];
105 if (!isBlocked(gx, gy)) {
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);
110 }
111 }
112 }
113
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};
117
118 while (!open.empty()) {
119 const HeapNode cur = open.top();
120 open.pop();
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_;
125
126 for (int i = 0; i < 8; ++i) {
127 const int nx = cx + kDx[i];
128 const int ny = cy + kDy[i];
129 if (!inBounds(nx, ny) || isBlocked(nx, ny)) continue;
130 if (kDx[i] != 0 && kDy[i] != 0) {
131 // 禁止穿过对角两侧都阻挡的"墙角"。
132 if (isBlocked(cx + kDx[i], cy) || isBlocked(cx, cy + kDy[i])) continue;
133 }
134 const int ni = index(nx, ny);
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);
139 }
140 }
141 rebuildFlow();
142}
143
144void CrowdField::rebuildFlow() {
145 for (int cy = 0; cy < height_; ++cy) {
146 for (int cx = 0; cx < width_; ++cx) {
147 const int i = index(cx, cy);
148 const float c = integ_[size_t(i)];
149 if (!finiteCost(c) || c == 0.f) continue; // 不可达或目标格无方向
150
151 // 中心差分近似 -∇integ(指向代价下降方向);缺失/不可达的邻格
152 // 用本格值代替,形成单侧差分,保证边缘格方向正确。
153 float gx = 0.f;
154 if (cx > 0 && finiteCost(integ_[size_t(i - 1)]))
155 gx += integ_[size_t(i - 1)];
156 else
157 gx += c;
158 if (cx + 1 < width_ && finiteCost(integ_[size_t(i + 1)]))
159 gx -= integ_[size_t(i + 1)];
160 else
161 gx -= c;
162
163 float gy = 0.f;
164 if (cy > 0 && finiteCost(integ_[size_t(i - width_)]))
165 gy += integ_[size_t(i - width_)];
166 else
167 gy += c;
168 if (cy + 1 < height_ && finiteCost(integ_[size_t(i + width_)]))
169 gy -= integ_[size_t(i + width_)];
170 else
171 gy -= c;
172
173 const float len = std::sqrt(gx * gx + gy * gy);
174 if (len > 1e-6f) {
175 flowX_[size_t(i)] = gx / len;
176 flowY_[size_t(i)] = gy / len;
177 }
178 }
179 }
180}
181
182float CrowdField::costAtCell(int cx, int cy) const {
183 if (!inBounds(cx, cy)) return kUnreachable;
184 return integ_[size_t(index(cx, cy))];
185}
186
187bool CrowdField::isReachable(int cx, int cy) const {
188 return finiteCost(costAtCell(cx, cy));
189}
190
191void CrowdField::flowAtCell(int cx, int cy, float &dx, float &dy) const {
192 dx = dy = 0.f;
193 if (!inBounds(cx, cy)) return;
194 const int i = index(cx, cy);
195 dx = flowX_[size_t(i)];
196 dy = flowY_[size_t(i)];
197}
198
199float CrowdField::costAtWorld(float wx, float wy) const {
200 if (!valid()) return kUnreachable;
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)
204 return kUnreachable;
205
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);
212
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))];
217
218 // 只对有限角落插值;全不可达才返回 kUnreachable,避免把单位"吓停"。
219 float sum = 0.f;
220 float wsum = 0.f;
221 const auto acc = [&](float c, float w) {
222 if (finiteCost(c)) {
223 sum += c * w;
224 wsum += w;
225 }
226 };
227 acc(c00, (1.f - tx) * (1.f - ty));
228 acc(c10, tx * (1.f - ty));
229 acc(c01, (1.f - tx) * ty);
230 acc(c11, tx * ty);
231 return wsum > 0.f ? sum / wsum : kUnreachable;
232}
233
234void CrowdField::flowAtWorld(float wx, float wy, float &dx, float &dy) const {
235 dx = dy = 0.f;
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)
240 return;
241
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);
248
249 const auto sample = [&](int x, int y, float &sx, float &sy) {
250 const int i = index(x, y);
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;
253 };
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);
260
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);
264 if (len > 1e-6f) {
265 dx /= len;
266 dy /= len;
267 } else {
268 dx = dy = 0.f;
269 }
270}
271
272bool CrowdField::resolvePenetration(float &wx, float &wy, float radius) const {
273 if (!valid() || radius <= 0.f) return false;
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)));
279 bool pushed = false;
280 for (int cy = cy0; cy <= cy1; ++cy) {
281 for (int cx = cx0; cx <= cx1; ++cx) {
282 if (!isBlocked(cx, cy)) continue;
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);
287 const float d2 = dx * dx + dy * dy;
288 if (d2 >= radius * radius) continue;
289 if (d2 > 1e-12f) {
290 const float distance = std::sqrt(d2);
291 const float scale = (radius - distance) / distance;
292 wx += dx * scale;
293 wy += dy * scale;
294 } else {
295 // Inside the box: choose the nearest expanded face, including radius.
296 const float left = wx - loX, right = hiX - wx;
297 const float down = wy - loY, up = hiY - wy;
298 const float nearest = std::min({left, right, down, up});
299 if (nearest == left)
300 wx = loX - radius;
301 else if (nearest == right)
302 wx = hiX + radius;
303 else if (nearest == down)
304 wy = loY - radius;
305 else
306 wy = hiY + radius;
307 }
308 pushed = true;
309 }
310 }
311 return pushed;
312}
313
314} // namespace eve::crowd
float w
Definition AnimClip.cpp:738
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float cx
Definition CardTypes.cpp:33
float cy
Definition CardTypes.cpp:34
float nx
float ny
eve::resource::CostSpec cost
glm::vec3 n
Definition Grass.cpp:63
HexVec3 up
HexVec3 left
HexVec3 right
std::int32_t c
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
float distance
float radius
float dy
float dx
uint32_t index
float wx
float wy
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
不可达/阻挡的积分代价。
Definition CrowdField.h:25
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
是否配置了有效网格。
Definition CrowdField.h:44
void build()
从目标格做 Dijkstra,生成积分场与方向场。
void addGoal(int gx, int gy)
追加一个目标格(多目标支持)。
void clearGoals()
清空目标列表。