载入中...
搜索中...
未找到
CrowdSpawn.cpp
浏览该文件的文档.
2
3#include <unordered_set>
4
5namespace eve::crowd {
6namespace {
7constexpr float tolerance = 0.001f;
8
9bool isTerrainClear(const CrowdField& field, bool clamp, float x, float y, float radius) {
10 if (!field.valid()) return true;
11 if (clamp && (x - radius < field.getOriginX() || y - radius < field.getOriginY() ||
12 x + radius > field.getOriginX() + float(field.getWidth()) * field.getCellSize() ||
13 y + radius > field.getOriginY() + float(field.getHeight()) * field.getCellSize()))
14 return false;
15 float projectedX = x, projectedY = y;
16 field.resolvePenetration(projectedX, projectedY, radius);
17 return std::hypot(projectedX - x, projectedY - y) <= tolerance;
18}
19
20void projectTerrain(const CrowdField& field, bool clamp, float& x, float& y, float radius) {
21 if (!field.valid()) return;
22 for (int pass = 0; pass < 4; ++pass) {
23 field.resolvePenetration(x, y, radius);
24 if (clamp) {
25 const float width = float(field.getWidth()) * field.getCellSize();
26 const float height = float(field.getHeight()) * field.getCellSize();
27 const float rx = std::min(radius, width * 0.5f), ry = std::min(radius, height * 0.5f);
28 x = std::clamp(x, field.getOriginX() + rx, field.getOriginX() + width - rx);
29 y = std::clamp(y, field.getOriginY() + ry, field.getOriginY() + height - ry);
30 }
31 }
32}
33} // namespace
34
36 const auto failure = [](DiagnosticCode code, const char* message) {
38 };
39 if (!std::isfinite(batch.maxDistance) || batch.maxDistance < 0.f || !std::isfinite(batch.searchSpacing) ||
40 batch.searchSpacing <= 0.f || batch.maxPasses < 1 || batch.maxPasses > 256 || batch.maxChecks < 1 ||
41 batch.maxChecks > 10000000 || batch.agents.size() > 1024 ||
44 return failure(DiagnosticCode::InvalidArgument, "Invalid spawn policy or work budget");
45 SpawnReceipt receipt;
46 if (batch.agents.empty())
48 if (impl_->xs.size() + batch.agents.size() > size_t(std::max(impl_->maxAgents, 0)))
49 return failure(DiagnosticCode::PreconditionViolation, "Crowd capacity cannot fit the batch");
50 std::unordered_set<std::string> names;
51 for (size_t i = 0; i < impl_->xs.size(); ++i)
52 if (!std::isfinite(impl_->xs[i]) || !std::isfinite(impl_->ys[i]) || !std::isfinite(impl_->radii[i]) ||
53 impl_->radii[i] < 0.f)
54 return failure(DiagnosticCode::PreconditionViolation, "Existing crowd geometry is invalid");
55 for (const auto& request : batch.agents) {
56 const auto& policy = request.interaction;
57 if (request.stableId.empty() || !std::isfinite(request.x) || !std::isfinite(request.y) ||
58 !std::isfinite(request.heading) || !std::isfinite(request.radius) || request.radius <= 0.f ||
59 !std::isfinite(policy.pushability) || policy.pushability < 0.f || policy.pushability > 1.f ||
60 policy.layer < 0 || policy.mask < 0)
61 return failure(DiagnosticCode::InvalidArgument, "Invalid spawn agent");
62 if (impl_->namedAgents.contains(request.stableId) || !names.insert(request.stableId).second)
63 return failure(DiagnosticCode::Conflict, "Spawn stable identifiers must be unique");
64 }
65
66 // No callbacks or external mutations occur between the snapshot and swap.
67 Crowd staged;
68 staged.impl_ = std::make_unique<Impl>(*impl_);
69 auto& d = *staged.impl_;
70 const size_t originalCount = d.xs.size();
71 bool exhausted = false;
72 const auto consume = [&]() {
73 if (receipt.checks >= batch.maxChecks) {
74 exhausted = true;
75 return false;
76 }
77 ++receipt.checks;
78 return true;
79 };
80 const auto isClear = [&](const SpawnRequest& request, float x, float y, bool checkAgents) {
81 if (!consume() || !std::isfinite(x) || !std::isfinite(y) ||
82 !isTerrainClear(d.field, d.clampToField, x, y, request.radius))
83 return false;
84 if (!checkAgents) return true;
85 for (size_t i = 0; i < d.xs.size(); ++i) {
86 if (!consume()) return false;
87 if ((request.interaction.layer & d.interactions[i].mask) == 0 ||
88 (d.interactions[i].layer & request.interaction.mask) == 0)
89 continue;
90 if (std::hypot(double(x) - d.xs[i], double(y) - d.ys[i]) < double(request.radius) + d.radii[i] - tolerance)
91 return false;
92 }
93 return true;
94 };
95 for (const auto& request : batch.agents) {
96 float x = request.x, y = request.y;
97 bool clear = isClear(request, x, y, batch.policy != SpawnPolicy::PushNeighbors);
98 if (!clear && batch.policy == SpawnPolicy::NearestFree) {
99 // Double loop counters avoid integer overflow for tiny spacing/huge radii.
100 const double rings = std::ceil(double(batch.maxDistance) / double(batch.searchSpacing));
101 for (double ring = 1; ring <= rings && !clear && !exhausted; ++ring) {
102 const double radius = std::min(ring * double(batch.searchSpacing), double(batch.maxDistance));
103 const double samples = std::max(8.0, std::ceil(6.283185307179586 * radius / batch.searchSpacing));
104 for (double sample = 0; sample < samples && !exhausted; ++sample) {
105 const double angle = sample / samples * 6.283185307179586;
106 x = request.x + float(std::cos(angle) * radius);
107 y = request.y + float(std::sin(angle) * radius);
108 if (isClear(request, x, y, true)) {
109 clear = true;
110 break;
111 }
112 }
113 }
114 }
115 if (!clear)
116 return failure(
118 exhausted ? "Spawn work budget exhausted" : "No valid spawn placement within search distance");
119 const int slot = staged.addNamedAgent(request.stableId, x, y, request.heading, request.radius);
120 if (slot < 0) return failure(DiagnosticCode::InvariantViolation, "Validated spawn allocation rejected");
121 auto policy = staged.setAgentInteraction(slot, request.interaction);
122 if (!policy) return Result<SpawnReceipt>::failure(policy.status());
123 receipt.created.push_back({request.stableId, x, y});
124 }
125
126 if (batch.policy == SpawnPolicy::PushNeighbors) {
127 const size_t count = d.xs.size();
128 std::vector<uint8_t> active(count, 0);
129 for (size_t i = originalCount; i < count; ++i) active[i] = 1;
130 std::vector<double> correctionsX(count), correctionsY(count);
131 bool solved = false;
132 for (int pass = 0; pass <= batch.maxPasses; ++pass) {
133 std::fill(correctionsX.begin(), correctionsX.end(), 0.f);
134 std::fill(correctionsY.begin(), correctionsY.end(), 0.f);
135 bool overlap = false;
136 for (size_t i = 0; i < count; ++i) {
137 if (!active[i]) continue;
138 for (size_t j = 0; j < count; ++j) {
139 if (j == i || (j < i && active[j])) continue;
140 if (!consume())
141 return failure(DiagnosticCode::PreconditionViolation, "Spawn work budget exhausted");
142 if (!d.canInteract(i, j)) continue;
143 double dx = double(d.xs[j]) - d.xs[i], dy = double(d.ys[j]) - d.ys[i];
144 double distance = std::hypot(dx, dy);
145 const double depth = double(d.radii[i]) + d.radii[j] - distance;
146 if (depth <= tolerance) continue;
147 overlap = true;
148 active[j] = 1;
149 const auto mobility = [&](size_t slot) {
150 return slot >= originalCount || d.interactions[slot].holdPosition
151 ? 0.f
152 : d.interactions[slot].pushability;
153 };
154 const float left = mobility(i), right = mobility(j), total = left + right;
155 if (total == 0.f)
156 return failure(DiagnosticCode::Conflict, "Spawn contact is blocked by fixed agents");
157 if (distance < 1e-6f) {
158 // Slot order only selects the tie direction. The operation is repeatable
159 // for identical world and request ordering; no random stream is consumed.
160 dx = i < j ? 1.f : -1.f;
161 dy = 0.f;
162 distance = 1.f;
163 }
164 correctionsX[i] -= dx / distance * depth * left / total;
165 correctionsY[i] -= dy / distance * depth * left / total;
166 correctionsX[j] += dx / distance * depth * right / total;
167 correctionsY[j] += dy / distance * depth * right / total;
168 }
169 }
170 if (!overlap) {
171 solved = true;
172 break;
173 }
174 if (pass == batch.maxPasses) break;
175 for (size_t i = 0; i < originalCount; ++i) {
176 if (!active[i] || d.interactions[i].holdPosition || d.interactions[i].pushability == 0.f) continue;
177 const double length = std::hypot(correctionsX[i], correctionsY[i]);
178 const float limit = d.field.valid() ? std::min(d.radii[i], d.field.getCellSize() * 0.5f) : d.radii[i];
179 const double scale = length > limit && length > 0.f ? limit / length : 1.0;
180 d.xs[i] += float(correctionsX[i] * scale);
181 d.ys[i] += float(correctionsY[i] * scale);
182 if (!std::isfinite(d.xs[i]) || !std::isfinite(d.ys[i]))
183 return failure(DiagnosticCode::PreconditionViolation, "Spawn displacement exceeds numeric range");
184 projectTerrain(d.field, d.clampToField, d.xs[i], d.ys[i], d.radii[i]);
185 if (!isTerrainClear(d.field, d.clampToField, d.xs[i], d.ys[i], d.radii[i]) ||
186 std::hypot(d.xs[i] - impl_->xs[i], d.ys[i] - impl_->ys[i]) > batch.maxDistance + tolerance)
188 "Spawn displacement violates terrain or distance budget");
189 }
190 }
191 if (!solved)
192 return failure(DiagnosticCode::PreconditionViolation, "Spawn relaxation did not converge within budget");
193 for (size_t i = 0; i < originalCount; ++i)
194 if (d.xs[i] != impl_->xs[i] || d.ys[i] != impl_->ys[i]) ++receipt.displacedAgents;
195 }
196 // Cached broadphase refers to pre-transaction positions; rebuild on the next advance.
197 d.gridW = d.gridH = 0;
198 impl_.swap(staged.impl_);
200}
201
202} // namespace eve::crowd
bool & active
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float length
Definition CaveMesh.cpp:94
std::string message
DiagnosticCode code
const GltfImportRequest & request
HexVec3 left
HexVec3 right
std::uint32_t height
std::uint32_t width
std::array< float, 3 > scale
float distance
float radius
float d
float dy
float dx
std::uint32_t count
int limit
Definition TreeMesh.cpp:164
std::uint32_t depth
float angle
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
static Status success(StatusCode code=StatusCode::Ok)
Construct a successful status with an explicit non-error outcome.
Definition Status.h:81
群体行为模块:连续流场寻路 + 海量单位移动/转向/行动 + Boids 鸟群。
Definition Crowd.h:116
Result< void > setAgentInteraction(int id, AgentInteraction policy)
Atomically replace the local interaction policy for a current compact slot.
int addNamedAgent(const std::string &stableId, float x, float y, float heading, float radius)
Add an agent with an editor/game-stable logical identifier.
Definition Crowd.cpp:125
Result< SpawnReceipt > applySpawnBatch(const SpawnBatch &batch)
Apply a caller-built spawn batch atomically, including neighbor displacement.
const EditorValue * field(const EditorValue &value, const char *name)
DiagnosticCode
Stable machine-readable diagnostic codes.
Definition Diagnostic.h:47
Caller-built transaction; positive budgets bound search and relaxation. @cost Linear in requested age...
Definition Crowd.h:77
int maxChecks
Combined candidate/neighbor work budget, at most ten million.
Definition Crowd.h:83
float searchSpacing
NearestFree samples concentric rings at this spacing.
Definition Crowd.h:81
float maxDistance
Maximum relocation from each requested/original position.
Definition Crowd.h:80
std::vector< SpawnRequest > agents
Definition Crowd.h:78
int maxPasses
Contact relaxation limit, at most 256.
Definition Crowd.h:82
SpawnPolicy policy
Definition Crowd.h:79
Observations from a committed spawn transaction. @cost Linear in created placement count; returned by...
Definition Crowd.h:96
std::vector< SpawnPlacement > created
Definition Crowd.h:97
One named agent to create; all fields are owned values.
Definition Crowd.h:67