载入中...
搜索中...
未找到
DistanceField3D.cpp
浏览该文件的文档.
2
3#include "common/Exception.h"
4
5#include <algorithm>
6#include <cmath>
7#include <limits>
8
9namespace eve::physics {
10
11namespace {
12constexpr double maxQueryIntervals = 1000000.0;
13
14int queryIntervals(double length, double interval, const char *operation) {
15 const double count = std::ceil(length / interval);
16 if (!std::isfinite(count) || count > maxQueryIntervals)
17 throw eve::Exception("%s: sweep or capsule is too long", operation);
18 return std::max(1, static_cast<int>(count));
19}
20} // namespace
21
22DistanceField3D::DistanceField3D(int width, int height, int depth, float cellSize,
23 float originX, float originY, float originZ,
24 float outsideDistance)
25 : width_(width), height_(height), depth_(depth), cellSize_(cellSize), originX_(originX),
26 originY_(originY), originZ_(originZ), outsideDistance_(outsideDistance) {
27 if (width < 2 || height < 2 || depth < 2 || !(cellSize > 0.f) || !std::isfinite(cellSize) ||
28 !std::isfinite(originX) || !std::isfinite(originY) || !std::isfinite(originZ) ||
29 !std::isfinite(outsideDistance))
30 throw eve::Exception("DistanceField3D: dimensions must be >= 2 and cellSize finite > 0");
31 constexpr size_t maxSamples = 64u * 1024u * 1024u;
32 const size_t wh = static_cast<size_t>(width) * static_cast<size_t>(height);
33 if (static_cast<size_t>(width) > maxSamples / static_cast<size_t>(height) ||
34 wh > maxSamples / static_cast<size_t>(depth))
35 throw eve::Exception("DistanceField3D: grid is too large");
36 const size_t count = wh * static_cast<size_t>(depth);
37 distances_.assign(count, outsideDistance);
38 moverSkinWidth_ = std::max(1e-4f, cellSize_ * 1e-3f);
39}
40
41bool DistanceField3D::validIndex(int x, int y, int z) const {
42 return x >= 0 && y >= 0 && z >= 0 && x < width_ && y < height_ && z < depth_;
43}
44
45bool DistanceField3D::validRegion(int x, int y, int z, int width, int height, int depth) const {
46 return x >= 0 && y >= 0 && z >= 0 && width > 0 && height > 0 && depth > 0 &&
47 width <= width_ - x && height <= height_ - y && depth <= depth_ - z;
48}
49
50size_t DistanceField3D::index(int x, int y, int z) const {
51 return (static_cast<size_t>(z) * static_cast<size_t>(height_) + static_cast<size_t>(y)) *
52 static_cast<size_t>(width_) +
53 static_cast<size_t>(x);
54}
55
56void DistanceField3D::setDistances(const std::vector<float> &distances) {
57 if (distances.size() != distances_.size())
58 throw eve::Exception("DistanceField3D.setDistances: sample count does not match grid");
59 if (!std::all_of(distances.begin(), distances.end(),
60 [](float distance) { return std::isfinite(distance); }))
61 throw eve::Exception("DistanceField3D.setDistances: all distances must be finite");
62 distances_ = distances;
63 ++revision_;
64}
65
67 if (!std::isfinite(distance))
68 throw eve::Exception("DistanceField3D.fill: distance must be finite");
69 std::fill(distances_.begin(), distances_.end(), distance);
70 ++revision_;
71}
72
73void DistanceField3D::setDistanceRegion(int x, int y, int z, int width, int height, int depth,
74 const std::vector<float> &distances) {
75 if (!validRegion(x, y, z, width, height, depth))
76 throw eve::Exception("DistanceField3D.setDistanceRegion: region is out of range");
77 const size_t count = static_cast<size_t>(width) * static_cast<size_t>(height) *
78 static_cast<size_t>(depth);
79 if (distances.size() != count)
80 throw eve::Exception(
81 "DistanceField3D.setDistanceRegion: sample count does not match region");
82 if (!std::all_of(distances.begin(), distances.end(),
83 [](float distance) { return std::isfinite(distance); }))
84 throw eve::Exception(
85 "DistanceField3D.setDistanceRegion: all distances must be finite");
86 size_t source = 0;
87 for (int localZ = 0; localZ < depth; ++localZ) {
88 for (int localY = 0; localY < height; ++localY) {
89 const size_t destination = index(x, y + localY, z + localZ);
90 std::copy_n(distances.begin() + static_cast<std::ptrdiff_t>(source), width,
91 distances_.begin() + static_cast<std::ptrdiff_t>(destination));
92 source += static_cast<size_t>(width);
93 }
94 }
95 ++revision_;
96}
97
98void DistanceField3D::fillRegion(int x, int y, int z, int width, int height, int depth,
99 float distance) {
100 if (!validRegion(x, y, z, width, height, depth))
101 throw eve::Exception("DistanceField3D.fillRegion: region is out of range");
102 if (!std::isfinite(distance))
103 throw eve::Exception("DistanceField3D.fillRegion: distance must be finite");
104 for (int localZ = 0; localZ < depth; ++localZ) {
105 for (int localY = 0; localY < height; ++localY) {
106 const size_t destination = index(x, y + localY, z + localZ);
107 std::fill_n(distances_.begin() + static_cast<std::ptrdiff_t>(destination), width,
108 distance);
109 }
110 }
111 ++revision_;
112}
113
114void DistanceField3D::setDistance(int x, int y, int z, float distance) {
115 if (!validIndex(x, y, z)) throw eve::Exception("DistanceField3D.setDistance: index out of range");
116 if (!std::isfinite(distance))
117 throw eve::Exception("DistanceField3D.setDistance: distance must be finite");
118 distances_[index(x, y, z)] = distance;
119 ++revision_;
120}
121
122float DistanceField3D::getDistance(int x, int y, int z) const {
123 return validIndex(x, y, z) ? distances_[index(x, y, z)] : outsideDistance_;
124}
125
126float DistanceField3D::sample(float x, float y, float z) const {
127 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
128 throw eve::Exception("DistanceField3D.sample: coordinates must be finite");
129 const float gx = (x - originX_) / cellSize_;
130 const float gy = (y - originY_) / cellSize_;
131 const float gz = (z - originZ_) / cellSize_;
132 if (gx < 0.f || gy < 0.f || gz < 0.f || gx > width_ - 1.f || gy > height_ - 1.f ||
133 gz > depth_ - 1.f)
134 return outsideDistance_;
135
136 const int x0 = std::min(static_cast<int>(std::floor(gx)), width_ - 2);
137 const int y0 = std::min(static_cast<int>(std::floor(gy)), height_ - 2);
138 const int z0 = std::min(static_cast<int>(std::floor(gz)), depth_ - 2);
139 const float tx = std::clamp(gx - x0, 0.f, 1.f);
140 const float ty = std::clamp(gy - y0, 0.f, 1.f);
141 const float tz = std::clamp(gz - z0, 0.f, 1.f);
142 auto mix = [](float a, float b, float t) { return a + (b - a) * t; };
143 const float c00 = mix(getDistance(x0, y0, z0), getDistance(x0 + 1, y0, z0), tx);
144 const float c10 = mix(getDistance(x0, y0 + 1, z0), getDistance(x0 + 1, y0 + 1, z0), tx);
145 const float c01 = mix(getDistance(x0, y0, z0 + 1), getDistance(x0 + 1, y0, z0 + 1), tx);
146 const float c11 = mix(getDistance(x0, y0 + 1, z0 + 1),
147 getDistance(x0 + 1, y0 + 1, z0 + 1), tx);
148 return mix(mix(c00, c10, ty), mix(c01, c11, ty), tz);
149}
150
151void DistanceField3D::sampleNormal(float x, float y, float z) {
152 const float h = cellSize_ * 0.5f;
153 const float maxX = originX_ + cellSize_ * static_cast<float>(width_ - 1);
154 const float maxY = originY_ + cellSize_ * static_cast<float>(height_ - 1);
155 const float maxZ = originZ_ + cellSize_ * static_cast<float>(depth_ - 1);
156 if (x < originX_ || y < originY_ || z < originZ_ || x > maxX || y > maxY || z > maxZ) {
157 normalX_ = 0.f;
158 normalY_ = 1.f;
159 normalZ_ = 0.f;
160 return;
161 }
162 const float center = sample(x, y, z);
163 const float nx = x - h < originX_ ? sample(x + h, y, z) - center
164 : x + h > maxX ? center - sample(x - h, y, z)
165 : sample(x + h, y, z) - sample(x - h, y, z);
166 const float ny = y - h < originY_ ? sample(x, y + h, z) - center
167 : y + h > maxY ? center - sample(x, y - h, z)
168 : sample(x, y + h, z) - sample(x, y - h, z);
169 const float nz = z - h < originZ_ ? sample(x, y, z + h) - center
170 : z + h > maxZ ? center - sample(x, y, z - h)
171 : sample(x, y, z + h) - sample(x, y, z - h);
172 const float length = std::sqrt(nx * nx + ny * ny + nz * nz);
173 if (length > 1e-6f) {
174 normalX_ = nx / length;
175 normalY_ = ny / length;
176 normalZ_ = nz / length;
177 } else {
178 normalX_ = 0.f;
179 normalY_ = 1.f;
180 normalZ_ = 0.f;
181 }
182}
183
184void DistanceField3D::setCollisionResult(float x, float y, float z, float radius) {
185 collisionX_ = x;
186 collisionY_ = y;
187 collisionZ_ = z;
188 collisionDistance_ = sample(x, y, z) - radius;
189 sampleNormal(x, y, z);
190 updateContactPoints(x, y, z, radius);
191}
192
193void DistanceField3D::updateContactPoints(float x, float y, float z, float radius) {
194 const float fieldDistance = collisionDistance_ + radius;
195 surfaceX_ = x - normalX_ * fieldDistance;
196 surfaceY_ = y - normalY_ * fieldDistance;
197 surfaceZ_ = z - normalZ_ * fieldDistance;
198 shapeContactX_ = x - normalX_ * radius;
199 shapeContactY_ = y - normalY_ * radius;
200 shapeContactZ_ = z - normalZ_ * radius;
201}
202
203bool DistanceField3D::checkSphere(float x, float y, float z, float radius) {
204 if (!(radius >= 0.f) || !std::isfinite(radius))
205 throw eve::Exception("DistanceField3D.checkSphere: radius must be finite and >= 0");
206 setCollisionResult(x, y, z, radius);
207 return collisionDistance_ <= 0.f;
208}
209
210bool DistanceField3D::checkCapsule(float ax, float ay, float az, float bx, float by, float bz,
211 float radius) {
212 if (!std::isfinite(ax) || !std::isfinite(ay) || !std::isfinite(az) || !std::isfinite(bx) ||
213 !std::isfinite(by) || !std::isfinite(bz) || !(radius >= 0.f) || !std::isfinite(radius))
214 throw eve::Exception(
215 "DistanceField3D.checkCapsule: coordinates and radius must be finite; radius >= 0");
216 float bestX, bestY, bestZ;
217 const float best = capsuleClearance(ax, ay, az, bx, by, bz, radius, bestX, bestY, bestZ);
218 collisionX_ = bestX;
219 collisionY_ = bestY;
220 collisionZ_ = bestZ;
221 collisionDistance_ = best;
222 sampleNormal(bestX, bestY, bestZ);
223 updateContactPoints(bestX, bestY, bestZ, radius);
224 return best <= 0.f;
225}
226
227float DistanceField3D::capsuleClearance(float ax, float ay, float az, float bx, float by,
228 float bz, float radius, float &closestX, float &closestY,
229 float &closestZ) const {
230 const float dx = bx - ax, dy = by - ay, dz = bz - az;
231 const float length = std::hypot(dx, dy, dz);
232 // A half-cell maximum interval is a conservative practical resolution for a sampled SDF.
233 const int steps = queryIntervals(length, cellSize_ * 0.5, "DistanceField3D.checkCapsule");
234 float best = std::numeric_limits<float>::infinity();
235 closestX = ax;
236 closestY = ay;
237 closestZ = az;
238 for (int i = 0; i <= steps; ++i) {
239 const float t = static_cast<float>(i) / static_cast<float>(steps);
240 const float x = ax + dx * t, y = ay + dy * t, z = az + dz * t;
241 const float clearance = sample(x, y, z) - radius;
242 if (clearance < best) {
243 best = clearance;
244 closestX = x;
245 closestY = y;
246 closestZ = z;
247 }
248 }
249 return best;
250}
251
252void DistanceField3D::setCastResult(float fraction, float travelLength, float x, float y, float z,
253 float clearance, float radius, bool startedInside) {
254 castFraction_ = fraction;
255 castDistance_ = fraction * travelLength;
256 castStartedInside_ = startedInside;
257 collisionX_ = x;
258 collisionY_ = y;
259 collisionZ_ = z;
260 collisionDistance_ = clearance;
261 sampleNormal(x, y, z);
262 updateContactPoints(x, y, z, radius);
263}
264
265bool DistanceField3D::castSphere(float x, float y, float z, float radius, float deltaX,
266 float deltaY, float deltaZ) {
267 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z) ||
268 !(radius >= 0.f) || !std::isfinite(radius) || !std::isfinite(deltaX) ||
269 !std::isfinite(deltaY) || !std::isfinite(deltaZ))
270 throw eve::Exception(
271 "DistanceField3D.castSphere: coordinates, radius, and delta must be finite; radius >= 0");
272 const float travel = std::hypot(deltaX, deltaY, deltaZ);
273 const float initial = sample(x, y, z) - radius;
274 if (initial <= 0.f) {
275 setCastResult(0.f, travel, x, y, z, initial, radius, true);
276 return true;
277 }
278 castFraction_ = 1.f;
279 castDistance_ = travel;
280 castStartedInside_ = false;
281 if (travel <= 1e-7f) return false;
282
283 const int steps = queryIntervals(travel, cellSize_ * 0.25, "DistanceField3D.castSphere");
284 float previous = 0.f;
285 for (int i = 1; i <= steps; ++i) {
286 const float fraction = static_cast<float>(i) / static_cast<float>(steps);
287 const float px = x + deltaX * fraction;
288 const float py = y + deltaY * fraction;
289 const float pz = z + deltaZ * fraction;
290 if (sample(px, py, pz) - radius > 0.f) {
291 previous = fraction;
292 continue;
293 }
294 float low = previous, high = fraction;
295 for (int iteration = 0; iteration < 14; ++iteration) {
296 const float middle = 0.5f * (low + high);
297 if (sample(x + deltaX * middle, y + deltaY * middle, z + deltaZ * middle) -
298 radius <=
299 0.f)
300 high = middle;
301 else
302 low = middle;
303 }
304 const float hx = x + deltaX * high;
305 const float hy = y + deltaY * high;
306 const float hz = z + deltaZ * high;
307 setCastResult(high, travel, hx, hy, hz, sample(hx, hy, hz) - radius, radius, false);
308 return true;
309 }
310 return false;
311}
312
313bool DistanceField3D::castCapsule(float ax, float ay, float az, float bx, float by, float bz,
314 float radius, float deltaX, float deltaY, float deltaZ) {
315 if (!std::isfinite(ax) || !std::isfinite(ay) || !std::isfinite(az) ||
316 !std::isfinite(bx) || !std::isfinite(by) || !std::isfinite(bz) ||
317 !(radius >= 0.f) || !std::isfinite(radius) || !std::isfinite(deltaX) ||
318 !std::isfinite(deltaY) || !std::isfinite(deltaZ))
319 throw eve::Exception(
320 "DistanceField3D.castCapsule: coordinates, radius, and delta must be finite; radius >= 0");
321 const float travel = std::hypot(deltaX, deltaY, deltaZ);
322 float hitX, hitY, hitZ;
323 const float initial =
324 capsuleClearance(ax, ay, az, bx, by, bz, radius, hitX, hitY, hitZ);
325 if (initial <= 0.f) {
326 setCastResult(0.f, travel, hitX, hitY, hitZ, initial, radius, true);
327 return true;
328 }
329 castFraction_ = 1.f;
330 castDistance_ = travel;
331 castStartedInside_ = false;
332 if (travel <= 1e-7f) return false;
333
334 const int steps = queryIntervals(travel, cellSize_ * 0.25, "DistanceField3D.castCapsule");
335 float previous = 0.f;
336 for (int i = 1; i <= steps; ++i) {
337 const float fraction = static_cast<float>(i) / static_cast<float>(steps);
338 float closestX, closestY, closestZ;
339 const float clearance = capsuleClearance(
340 ax + deltaX * fraction, ay + deltaY * fraction, az + deltaZ * fraction,
341 bx + deltaX * fraction, by + deltaY * fraction, bz + deltaZ * fraction, radius,
342 closestX, closestY, closestZ);
343 if (clearance > 0.f) {
344 previous = fraction;
345 continue;
346 }
347 float low = previous, high = fraction;
348 for (int iteration = 0; iteration < 14; ++iteration) {
349 const float middle = 0.5f * (low + high);
350 float ignoredX, ignoredY, ignoredZ;
351 if (capsuleClearance(
352 ax + deltaX * middle, ay + deltaY * middle, az + deltaZ * middle,
353 bx + deltaX * middle, by + deltaY * middle, bz + deltaZ * middle, radius,
354 ignoredX, ignoredY, ignoredZ) <= 0.f)
355 high = middle;
356 else
357 low = middle;
358 }
359 const float finalClearance = capsuleClearance(
360 ax + deltaX * high, ay + deltaY * high, az + deltaZ * high,
361 bx + deltaX * high, by + deltaY * high, bz + deltaZ * high, radius, hitX, hitY,
362 hitZ);
363 setCastResult(high, travel, hitX, hitY, hitZ, finalClearance, radius, false);
364 return true;
365 }
366 return false;
367}
368
369void DistanceField3D::setMoverUp(float x, float y, float z) {
370 if (!std::isfinite(x) || !std::isfinite(y) || !std::isfinite(z))
371 throw eve::Exception("DistanceField3D.setMoverUp: direction must be finite");
372 const float magnitude = std::hypot(x, y, z);
373 if (!(magnitude > 1e-6f))
374 throw eve::Exception("DistanceField3D.setMoverUp: direction must be non-zero");
375 moverUpX_ = x / magnitude;
376 moverUpY_ = y / magnitude;
377 moverUpZ_ = z / magnitude;
378}
379
381 if (!std::isfinite(degrees))
382 throw eve::Exception("DistanceField3D.setMoverSlopeLimit: degrees must be finite");
383 constexpr float radiansPerDegree = 0.01745329251994329577f;
384 moverSlopeCos_ = std::cos(std::clamp(degrees, 0.f, 89.9f) * radiansPerDegree);
385}
386
388 if (!(width >= 0.f) || !std::isfinite(width))
389 throw eve::Exception("DistanceField3D.setMoverSkinWidth: width must be finite and >= 0");
390 moverSkinWidth_ = width;
391}
392
394 if (!(distance >= 0.f) || !std::isfinite(distance))
395 throw eve::Exception(
396 "DistanceField3D.setMoverGroundSnap: distance must be finite and >= 0");
397 moverGroundSnap_ = distance;
398}
399
401 if (!(height >= 0.f) || !std::isfinite(height))
402 throw eve::Exception(
403 "DistanceField3D.setMoverStepHeight: height must be finite and >= 0");
404 moverStepHeight_ = height;
405}
406
407bool DistanceField3D::moveCapsule(float ax, float ay, float az, float bx, float by, float bz,
408 float radius, float deltaX, float deltaY, float deltaZ) {
409 // Validation and pathological capsule-length protection are shared with the overlap query.
410 checkCapsule(ax, ay, az, bx, by, bz, radius);
411 if (!std::isfinite(deltaX) || !std::isfinite(deltaY) || !std::isfinite(deltaZ))
412 throw eve::Exception("DistanceField3D.moveCapsule: delta must be finite");
413
414 struct Vec3 {
415 float x, y, z;
416 };
417 auto dot = [](Vec3 a, Vec3 b) { return a.x * b.x + a.y * b.y + a.z * b.z; };
418 auto length = [&](Vec3 value) { return std::sqrt(dot(value, value)); };
419
420 constexpr int maxIterations = 6;
421 constexpr int maxPlanes = 4;
422 // Retain a numerical epsilon even when the caller requests zero visible skin, otherwise an
423 // exact distance==radius contact would be classified as penetration on every solver pass.
424 const float skin = std::max(moverSkinWidth_, cellSize_ * 1e-6f);
425 Vec3 position{0.f, 0.f, 0.f};
426 Vec3 remaining{deltaX, deltaY, deltaZ};
427 Vec3 planes[maxPlanes]{};
428 int planeCount = 0;
429 bool constrained = false;
430 moverIterations_ = 0;
431 moverNormalX_ = 0.f;
432 moverNormalY_ = 1.f;
433 moverNormalZ_ = 0.f;
434 moverGroundDot_ = -1.f;
435 moverGrounded_ = false;
436 moverHitWall_ = false;
437 auto recordNormal = [&](Vec3 normal) {
438 moverNormalX_ = normal.x;
439 moverNormalY_ = normal.y;
440 moverNormalZ_ = normal.z;
441 const float upDot =
442 normal.x * moverUpX_ + normal.y * moverUpY_ + normal.z * moverUpZ_;
443 moverGroundDot_ = std::max(moverGroundDot_, upDot);
444 if (upDot < moverSlopeCos_) moverHitWall_ = true;
445 };
446
447 for (int iteration = 0; iteration < maxIterations; ++iteration) {
448 moverIterations_ = iteration + 1;
449 const float currentAx = ax + position.x, currentAy = ay + position.y,
450 currentAz = az + position.z;
451 const float currentBx = bx + position.x, currentBy = by + position.y,
452 currentBz = bz + position.z;
453
454 if (checkCapsule(currentAx, currentAy, currentAz, currentBx, currentBy, currentBz,
455 radius)) {
456 const float recovery = -collisionDistance_ + skin;
457 Vec3 normal{normalX_, normalY_, normalZ_};
458 position.x += normal.x * recovery;
459 position.y += normal.y * recovery;
460 position.z += normal.z * recovery;
461 recordNormal(normal);
462 constrained = true;
463 if (planeCount < maxPlanes) planes[planeCount++] = normal;
464 continue;
465 }
466
467 const float travel = length(remaining);
468 if (travel <= 1e-6f) break;
469 if (!castCapsule(currentAx, currentAy, currentAz, currentBx, currentBy, currentBz, radius,
470 remaining.x, remaining.y, remaining.z)) {
471 position.x += remaining.x;
472 position.y += remaining.y;
473 position.z += remaining.z;
474 remaining = {};
475 break;
476 }
477
478 constrained = true;
479 const Vec3 normal{normalX_, normalY_, normalZ_};
480 recordNormal(normal);
481 if (planeCount < maxPlanes) planes[planeCount++] = normal;
482
483 const float safeFraction = std::max(0.f, castFraction_ - skin / travel);
484 position.x += remaining.x * safeFraction;
485 position.y += remaining.y * safeFraction;
486 position.z += remaining.z * safeFraction;
487 remaining.x *= 1.f - safeFraction;
488 remaining.y *= 1.f - safeFraction;
489 remaining.z *= 1.f - safeFraction;
490
491 // Project against every accumulated contact plane. Repeating handles corners where
492 // projecting against one plane can reintroduce motion into another.
493 for (int pass = 0; pass < planeCount; ++pass) {
494 for (int plane = 0; plane < planeCount; ++plane) {
495 const float into = dot(remaining, planes[plane]);
496 if (into < 0.f) {
497 remaining.x -= planes[plane].x * into;
498 remaining.y -= planes[plane].y * into;
499 remaining.z -= planes[plane].z * into;
500 }
501 }
502 }
503 }
504
505 moverGrounded_ = moverGroundDot_ >= moverSlopeCos_;
506 if (!moverGrounded_ && moverGroundSnap_ > 0.f) {
507 const Vec3 snap{-moverUpX_ * moverGroundSnap_, -moverUpY_ * moverGroundSnap_,
508 -moverUpZ_ * moverGroundSnap_};
509 if (castCapsule(ax + position.x, ay + position.y, az + position.z, bx + position.x,
510 by + position.y, bz + position.z, radius, snap.x, snap.y, snap.z) &&
511 !castStartedInside_) {
512 const Vec3 snapNormal{normalX_, normalY_, normalZ_};
513 const float groundDot = snapNormal.x * moverUpX_ + snapNormal.y * moverUpY_ +
514 snapNormal.z * moverUpZ_;
515 if (groundDot >= moverSlopeCos_) {
516 const float safeFraction =
517 std::max(0.f, castFraction_ - skin / moverGroundSnap_);
518 position.x += snap.x * safeFraction;
519 position.y += snap.y * safeFraction;
520 position.z += snap.z * safeFraction;
521 recordNormal(snapNormal);
522 moverGrounded_ = true;
523 constrained = true;
524 ++moverIterations_;
525 }
526 }
527 }
528 moverDeltaX_ = position.x;
529 moverDeltaY_ = position.y;
530 moverDeltaZ_ = position.z;
531
532 const Vec3 requested{deltaX, deltaY, deltaZ};
533 const float requestedUp =
534 requested.x * moverUpX_ + requested.y * moverUpY_ + requested.z * moverUpZ_;
535 const Vec3 lateral{requested.x - moverUpX_ * requestedUp,
536 requested.y - moverUpY_ * requestedUp,
537 requested.z - moverUpZ_ * requestedUp};
538 const float lateralLength = length(lateral);
539 if (!moverStepping_ && moverStepHeight_ > 0.f && moverHitWall_ && lateralLength > 1e-6f) {
540 struct MoverState {
541 float dx, dy, dz, nx, ny, nz, groundDot;
542 int iterations;
543 bool grounded, hitWall;
544 };
545 const MoverState direct{moverDeltaX_, moverDeltaY_, moverDeltaZ_,
546 moverNormalX_, moverNormalY_, moverNormalZ_,
547 moverGroundDot_, moverIterations_, moverGrounded_,
548 moverHitWall_};
549 auto restore = [&](const MoverState &state) {
550 moverDeltaX_ = state.dx;
551 moverDeltaY_ = state.dy;
552 moverDeltaZ_ = state.dz;
553 moverNormalX_ = state.nx;
554 moverNormalY_ = state.ny;
555 moverNormalZ_ = state.nz;
556 moverGroundDot_ = state.groundDot;
557 moverIterations_ = state.iterations;
558 moverGrounded_ = state.grounded;
559 moverHitWall_ = state.hitWall;
560 };
561 const Vec3 up{moverUpX_ * moverStepHeight_, moverUpY_ * moverStepHeight_,
562 moverUpZ_ * moverStepHeight_};
563
564 // The entire raised capsule must have ceiling clearance before trying the forward phase.
565 if (!castCapsule(ax, ay, az, bx, by, bz, radius, up.x, up.y, up.z)) {
566 moverStepping_ = true;
567 try {
568 moveCapsule(ax + up.x, ay + up.y, az + up.z, bx + up.x, by + up.y,
569 bz + up.z, radius, deltaX, deltaY, deltaZ);
570 } catch (...) {
571 moverStepping_ = false;
572 throw;
573 }
574 moverStepping_ = false;
575
576 Vec3 candidate{up.x + moverDeltaX_, up.y + moverDeltaY_, up.z + moverDeltaZ_};
577 const int candidateIterations = moverIterations_;
578 const bool candidateHitWall = moverHitWall_;
579 const float downDistance = moverStepHeight_ + moverGroundSnap_ + skin;
580 const Vec3 down{-moverUpX_ * downDistance, -moverUpY_ * downDistance,
581 -moverUpZ_ * downDistance};
582 if (castCapsule(ax + candidate.x, ay + candidate.y, az + candidate.z,
583 bx + candidate.x, by + candidate.y, bz + candidate.z, radius, down.x,
584 down.y, down.z) &&
585 !castStartedInside_) {
586 const Vec3 landingNormal{normalX_, normalY_, normalZ_};
587 const float landingDot = landingNormal.x * moverUpX_ +
588 landingNormal.y * moverUpY_ +
589 landingNormal.z * moverUpZ_;
590 if (landingDot >= moverSlopeCos_) {
591 const float safeFraction =
592 std::max(0.f, castFraction_ - skin / downDistance);
593 candidate.x += down.x * safeFraction;
594 candidate.y += down.y * safeFraction;
595 candidate.z += down.z * safeFraction;
596 const float directProgress =
597 (direct.dx * lateral.x + direct.dy * lateral.y + direct.dz * lateral.z) /
598 lateralLength;
599 const float candidateProgress =
600 (candidate.x * lateral.x + candidate.y * lateral.y +
601 candidate.z * lateral.z) /
602 lateralLength;
603 if (candidateProgress > directProgress + std::max(1e-4f, skin)) {
604 moverDeltaX_ = candidate.x;
605 moverDeltaY_ = candidate.y;
606 moverDeltaZ_ = candidate.z;
607 moverNormalX_ = landingNormal.x;
608 moverNormalY_ = landingNormal.y;
609 moverNormalZ_ = landingNormal.z;
610 moverGroundDot_ = landingDot;
611 moverGrounded_ = true;
612 moverHitWall_ = candidateHitWall;
613 moverIterations_ = candidateIterations + 2;
614 return true;
615 }
616 }
617 }
618 }
619 restore(direct);
620 }
621 return constrained;
622}
623
624} // namespace eve::physics
ActionParameterOperation operation
double value
float y
Definition AnimClip.cpp:738
float x
Definition AnimClip.cpp:738
float z
Definition AnimClip.cpp:738
float degrees
Definition CardTypes.cpp:35
int bz
Definition CaveMesh.cpp:114
int ax
Definition CaveMesh.cpp:113
int ay
Definition CaveMesh.cpp:113
int bx
Definition CaveMesh.cpp:114
float length
Definition CaveMesh.cpp:94
int az
Definition CaveMesh.cpp:113
int by
Definition CaveMesh.cpp:114
float py
float nx
float nz
float ny
float pz
float planes[6][4]
HexVec3 up
int h
std::vector< Colorf > px
std::uint32_t height
std::uint32_t width
std::array< float, 3 > position
MeleePoint3 b
Definition MeleeHit.cpp:41
MeleePoint3 a
Definition MeleeHit.cpp:40
float distance
Texture * normal
graphics::Canvas * previous
float radius
int steps
float t
float dz
float dy
float dx
std::uint32_t count
std::map< Cell, int > best
int iterations
Definition TreeMesh.cpp:311
uint32_t index
const UnitySourceAsset & source
std::uint32_t depth
EVENGINE_API_FOUNDATION public API.
Definition Exception.h:13
void setMoverGroundSnap(float distance)
Set the non-negative distance used to snap onto walkable ground after movement.
void fill(float distance)
Set every grid sample to the same finite distance.
bool moveCapsule(float ax, float ay, float az, float bx, float by, float bz, float radius, float deltaX, float deltaY, float deltaZ)
Resolve a capsule displacement against the field with sliding and depenetration. The capsule coordina...
void setMoverUp(float x, float y, float z)
Set the normalized reference-up direction used for ground classification.
void fillRegion(int x, int y, int z, int width, int height, int depth, float distance)
Fill an in-bounds grid subregion with one finite signed distance.
void sampleNormal(float x, float y, float z)
Estimate the normalized outward field gradient at a world position.
void setDistances(const std::vector< float > &distances)
Replace every sample in x-fastest, then y, then z order.
bool checkCapsule(float ax, float ay, float az, float bx, float by, float bz, float radius)
Test a capsule defined by two sphere centers against the solid.
void setMoverSlopeLimit(float degrees)
Set the maximum walkable slope angle in degrees, clamped to [0, 89.9].
bool castCapsule(float ax, float ay, float az, float bx, float by, float bz, float radius, float deltaX, float deltaY, float deltaZ)
Sweep a capsule through the field and cache the earliest contact.
bool castSphere(float x, float y, float z, float radius, float deltaX, float deltaY, float deltaZ)
Sweep a sphere through the field and cache the earliest contact.
DistanceField3D(int width, int height, int depth, float cellSize, float originX, float originY, float originZ, float outsideDistance)
Distance field 3 d.
void setDistanceRegion(int x, int y, int z, int width, int height, int depth, const std::vector< float > &distances)
Atomically replace a grid subregion in x-fastest, then y, then z order.
void setMoverSkinWidth(float width)
Set the non-negative separation retained from surfaces by moveCapsule.
void setDistance(int x, int y, int z, float distance)
Set a signed-distance sample at a grid vertex.
bool checkSphere(float x, float y, float z, float radius)
Test a sphere against the solid (distance <= radius).
void setMoverStepHeight(float height)
Set the maximum non-negative ledge height automatically climbed by moveCapsule.
float sample(float x, float y, float z) const
Trilinearly sample the field in world coordinates.
float getDistance(int x, int y, int z) const
Read a grid vertex; out-of-range vertices return the outside distance.
Optional physics backend for vehicle mobility and body attach.
Definition Climbing.h:36