67 const bool hasField = field.isBuilt();
68 if (avoidance.enabled) {
69 avoidanceMaxSpeed =
n == 0 ? 0.f : *std::max_element(maxSpeeds.begin(), maxSpeeds.end());
70 avoidanceMaxRadius =
n == 0 ? 0.f : *std::max_element(
radii.begin(),
radii.end());
72 nextVxs.resize(
size_t(
n));
73 nextVys.resize(
size_t(
n));
74 preferredVxs.resize(
size_t(
n));
75 preferredVys.resize(
size_t(
n));
76 arriveFactors.resize(
size_t(
n), 1.f);
77 for (
int i = 0; i <
n; ++i) {
78 if (interactions[
size_t(i)].holdPosition) {
79 preferredVxs[size_t(i)] = preferredVys[size_t(i)] = 0.f;
80 arriveFactors[size_t(i)] = 1.f;
83 const float x = xs[size_t(i)];
84 const float y = ys[size_t(i)];
85 const float maxSpeed = maxSpeeds[size_t(i)];
90 float desX = 0.f, desY = 0.f;
92 float fx = 0.f, fy = 0.f;
93 if (hasField) field.flowAtWorld(
x,
y, fx, fy);
97 const float c = field.costAtWorld(
x,
y);
98 const float cell = std::max(field.getCellSize(), 1e-6f);
99 const float arriveCells = arriveRadius /
cell;
100 if (c < CrowdField::kUnreachable && arriveCells > 0.f &&
c < arriveCells) {
107 }
else if (
action == kSeek && hasTargets[
size_t(i)]) {
108 const float tx = targetXs[size_t(i)] -
x;
109 const float ty = targetYs[size_t(i)] -
y;
110 const float d = std::sqrt(tx * tx + ty * ty);
114 if (
d < arriveRadius) {
115 const float k =
d / arriveRadius;
120 }
else if (
action == kBoids && hasTargets[
size_t(i)] && goalWeight > 0.f) {
121 const float tx = targetXs[size_t(i)] -
x;
122 const float ty = targetYs[size_t(i)] -
y;
123 const float d = std::sqrt(tx * tx + ty * ty);
125 desX = tx /
d * goalWeight;
126 desY = ty /
d * goalWeight;
133 if (sepWeight > 0.f || alignWeight > 0.f || cohesionWeight > 0.f) {
134 const float queryR = std::max(sepRadius, perceiveRadius);
135 float sx = 0.f,
sy = 0.f;
136 float axv = 0.f, ayv = 0.f;
137 float cxv = 0.f, cyv = 0.f;
138 int alignN = 0, cohN = 0;
139 forEachNeighbor(
x,
y, queryR, [&](
int j) {
140 if (!canInteract(
size_t(i),
size_t(j)))
return;
141 const float dx = xs[size_t(j)] -
x;
142 const float dy = ys[size_t(j)] -
y;
144 if (d2 <= 0.f)
return;
145 if (sepWeight > 0.f && d2 <= sepRadius * sepRadius) {
146 const float d = std::sqrt(d2);
147 const float fall = 1.f -
d / sepRadius;
148 sx += (
x - xs[size_t(j)]) /
d * fall;
149 sy += (
y - ys[size_t(j)]) /
d * fall;
151 if (alignWeight > 0.f && d2 <= perceiveRadius * perceiveRadius) {
152 axv += vxs[size_t(j)];
153 ayv += vys[size_t(j)];
156 if (cohesionWeight > 0.f && d2 <= perceiveRadius * perceiveRadius) {
157 cxv += xs[size_t(j)];
158 cyv += ys[size_t(j)];
162 desX +=
sx * sepWeight * maxSpeed;
163 desY +=
sy * sepWeight * maxSpeed;
165 const float al = std::sqrt(axv * axv + ayv * ayv);
167 desX += axv / al * alignWeight * maxSpeed;
168 desY += ayv / al * alignWeight * maxSpeed;
172 const float cmx = cxv / float(cohN) -
x;
173 const float cmy = cyv / float(cohN) -
y;
174 const float cl = std::sqrt(cmx * cmx + cmy * cmy);
176 desX += cmx / cl * cohesionWeight * maxSpeed;
177 desY += cmy / cl * cohesionWeight * maxSpeed;
183 if (
action == kBoids && wanderWeight > 0.f) {
184 wanderPhases[size_t(i)] += dt * 2.5f;
185 desX += std::cos(wanderPhases[
size_t(i)]) * wanderWeight * maxSpeed;
186 desY += std::sin(wanderPhases[
size_t(i)]) * wanderWeight * maxSpeed;
190 float dl = std::sqrt(desX * desX + desY * desY);
191 if (dl > maxSpeed && dl > 1e-6f) {
192 desX *= maxSpeed / dl;
193 desY *= maxSpeed / dl;
196 preferredVxs[size_t(i)] = desX;
197 preferredVys[size_t(i)] = desY;
198 arriveFactors[size_t(i)] = arriveK;
200 for (
int i = 0; i <
n; ++i) {
201 if (interactions[
size_t(i)].holdPosition) {
202 nextVxs[size_t(i)] = nextVys[size_t(i)] = speeds[size_t(i)] = 0.f;
205 const float desX = preferredVxs[size_t(i)], desY = preferredVys[size_t(i)];
206 const float maxSpeed = maxSpeeds[size_t(i)];
207 const float arriveK = arriveFactors[size_t(i)];
209 float avx = vxs[size_t(i)];
210 float avy = vys[size_t(i)];
211 float ddx = desX - avx;
212 float ddy = desY - avy;
213 const float ddl = std::sqrt(ddx * ddx + ddy * ddy);
214 const float maxDelta = std::max(maxAccels[
size_t(i)] * dt, 0.f);
215 if (ddl > maxDelta && ddl > 1e-6f) {
216 ddx *= maxDelta / ddl;
217 ddy *= maxDelta / ddl;
222 if (avoidance.enabled) selectAvoidanceVelocity(
size_t(i), dt, avx, avy);
225 float sp = std::sqrt(avx * avx + avy * avy);
226 if (sp > maxSpeed && sp > 1e-6f) {
227 avx *= maxSpeed / sp;
228 avy *= maxSpeed / sp;
231 nextVxs[size_t(i)] = avx;
232 nextVys[size_t(i)] = avy;
233 speeds[size_t(i)] = sp;
237 const float damp = 1.f - (1.f - arriveK) * std::min(1.f, dt * 4.f);
238 nextVxs[size_t(i)] *= damp;
239 nextVys[size_t(i)] *= damp;
240 speeds[size_t(i)] *= damp;
244 const float spAfter = speeds[size_t(i)];
245 if (spAfter > 1e-4f) {
246 const float targetAng = std::atan2(nextVys[
size_t(i)], nextVxs[
size_t(i)]);
247 const float delta = wrapPi(targetAng - headings[
size_t(i)]);
248 const float maxTurn = std::max(turnRates[
size_t(i)] * dt, 0.f);
249 headings[size_t(i)] += std::clamp(delta, -maxTurn, maxTurn);
254 for (
int i = 0; i <
n; ++i) {
255 xs[size_t(i)] += vxs[size_t(i)] * dt;
256 ys[size_t(i)] += vys[size_t(i)] * dt;
262 correctionXs.assign(
size_t(
n), 0.f);
263 correctionYs.assign(
size_t(
n), 0.f);
264 const float maxRadius =
radii.empty() ? 0.f : *std::max_element(
radii.begin(),
radii.end());
265 float maxPenetration = 0.f;
266 for (
int i = 0; i <
n; ++i) {
267 const float xi = xs[size_t(i)];
268 const float yi = ys[size_t(i)];
269 forEachNeighbor(xi, yi,
radii[
size_t(i)] + maxRadius, [&](
int j) {
270 if (j <= i || !canInteract(
size_t(i),
size_t(j)))
return;
271 float dx = xs[size_t(j)] - xi;
272 float dy = ys[size_t(j)] - yi;
274 const float minD =
radii[size_t(i)] +
radii[size_t(j)];
275 if (d2 >= minD * minD)
return;
276 const bool exactOverlap = d2 <= 1e-9f;
278 const std::string
left = stableIds[size_t(i)].empty() ? std::to_string(i) : stableIds[size_t(i)];
279 const std::string
right = stableIds[size_t(j)].empty() ? std::to_string(j) : stableIds[size_t(j)];
282 std::uint64_t
hash = 1469598103934665603ULL;
285 hash *= 1099511628211ULL;
287 const float angle =
static_cast<float>(
hash % 104729ULL) / 104729.0f * 6.28318530717958647692f;
296 const float d = std::sqrt(d2);
297 const float push = minD - (exactOverlap ? 0.0f :
d);
298 maxPenetration = std::max(maxPenetration, push);
299 const float nx =
dx /
d;
300 const float ny =
dy /
d;
301 float leftShare = 0.5f;
302 float rightShare = 0.5f;
303 const auto& leftPolicy = interactions[size_t(i)];
304 const auto& rightPolicy = interactions[size_t(j)];
305 const float leftMobility = leftPolicy.holdPosition ? 0.f : leftPolicy.pushability;
306 const float rightMobility = rightPolicy.holdPosition ? 0.f : rightPolicy.pushability;
307 if (leftMobility + rightMobility == 0.f)
return;
308 leftShare = leftMobility / (leftMobility + rightMobility);
309 rightShare = 1.f - leftShare;
312 if (leftMobility > 0.f && rightMobility > 0.f &&
313 avoidancePriorities[
size_t(i)] > avoidancePriorities[size_t(j)]) {
316 }
else if (leftMobility > 0.f && rightMobility > 0.f &&
317 avoidancePriorities[
size_t(j)] > avoidancePriorities[size_t(i)]) {
321 correctionXs[size_t(i)] -=
nx * push * leftShare;
322 correctionYs[size_t(i)] -=
ny * push * leftShare;
323 correctionXs[size_t(j)] +=
nx * push * rightShare;
324 correctionYs[size_t(j)] +=
ny * push * rightShare;
327 for (
int i = 0; i <
n; ++i) {
329 const float length = std::hypot(correctionXs[
size_t(i)], correctionYs[
size_t(i)]);
330 const float limit = std::max(
radii[
size_t(i)], 1e-4f);
332 xs[size_t(i)] += correctionXs[size_t(i)] *
scale;
333 ys[size_t(i)] += correctionYs[size_t(i)] *
scale;
335 return maxPenetration;
339 if (!field.valid())
return;
340 const float width = float(field.getWidth()) * field.getCellSize();
341 const float height = float(field.getHeight()) * field.getCellSize();
342 for (
size_t i = 0; i < xs.size(); ++i) {
343 for (
int pass = 0; pass < 4; ++pass) {
344 const float oldX = xs[i], oldY = ys[i];
345 field.resolvePenetration(xs[i], ys[i],
radii[i]);
348 const float rx = std::min(
radii[i],
width * 0.5f);
349 const float ry = std::min(
radii[i],
height * 0.5f);
350 xs[i] = std::clamp(xs[i], field.getOriginX() + rx, field.getOriginX() +
width - rx);
351 ys[i] = std::clamp(ys[i], field.getOriginY() + ry, field.getOriginY() +
height - ry);
353 const float dx = xs[i] - oldX,
dy = ys[i] - oldY;
354 const float length2 =
dx *
dx +
dy *
dy;
355 if (length2 == 0.f)
break;
358 const float inward = vxs[i] *
dx + vys[i] *
dy;
360 vxs[i] -=
dx * inward / length2;
361 vys[i] -=
dy * inward / length2;
362 speeds[i] = std::hypot(vxs[i], vys[i]);