Skip to content

Commit 85f3e01

Browse files
cdtwiggmeta-codesync[bot]
authored andcommitted
Swept-AABB broadphase for the SDF collision sequence error function (#1527)
Summary: Pull Request resolved: #1527 Add a conservative broadphase to `SDFCollisionSequenceErrorFunctionT` that skips any (vertex, collider) pair whose swept segment cannot reach the collider. The segment is already mapped into the collider's local SDF frame for the sweep; if the segment's local axis-aligned bounding box does not overlap the collider's SDF `bounds()`, the pair cannot penetrate and is skipped before the more expensive sphere-trace and deepest-point search. Because both the cull and the detailed test operate in the same local frame, the cull is exact — it only ever skips pairs whose detailed test would have found no penetration — so the error, gradient, and Jacobian are unchanged. This replaces running the sphere-trace for every (vertex, collider) pair regardless of proximity, which matters once the participating-vertex set or collider count is large. It adds `bounds()` to the (already duck-typed) collider interface this function relies on. Reviewed By: yutingye Differential Revision: D108807344 fbshipit-source-id: 1de7688f8e86807d1e5074102c466569848d9661
1 parent d95c669 commit 85f3e01

2 files changed

Lines changed: 39 additions & 1 deletion

File tree

‎momentum/character_sequence_solver/sdf_collision_sequence_error_function.h‎

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,11 @@ namespace momentum {
6666
/// gradient is mapped to world space including the world-to-collider scale factor, which the
6767
/// derivatives require whenever the collider's joint carries a non-unit scale.
6868
///
69+
/// A conservative broadphase skips any (vertex, collider) pair whose swept segment cannot reach the
70+
/// collider's SDF bounds, so distant pairs cost only an axis-aligned-bounding-box test.
71+
///
6972
/// The `SdfColliderType` is duck-typed; see `SDFCollisionErrorFunctionT` for the required
70-
/// interface.
73+
/// interface (this function additionally uses `bounds()`).
7174
template <typename T, typename SdfColliderType = SDFColliderT<float>>
7275
class SDFCollisionSequenceErrorFunctionT : public SequenceErrorFunctionT<T> {
7376
public:
@@ -170,6 +173,15 @@ class SDFCollisionSequenceErrorFunctionT : public SequenceErrorFunctionT<T> {
170173
const SweptIntervalT& interval,
171174
const SdfColliderType& collider) const;
172175

176+
/// Conservative broadphase: returns false when the swept segment's local-frame AABB cannot reach
177+
/// the collider's SDF bounds, so the pair cannot penetrate and can be skipped. Because the sweep
178+
/// is evaluated in the same local frame, this is exact — it only skips pairs whose detailed test
179+
/// would have found no penetration.
180+
[[nodiscard]] bool sweptSegmentMayCollide(
181+
const Eigen::Vector3<T>& localPos0,
182+
const Eigen::Vector3<T>& localPos1,
183+
const SdfColliderType& collider) const;
184+
173185
// Sphere-trace step budget for crossing detection. Generous: the |sdf|-clamped step cannot skip a
174186
// surface, so this only bounds work, it does not affect accuracy.
175187
static constexpr int kMaxMarchSteps = 64;
@@ -325,6 +337,20 @@ T SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::findDeepestPoint(
325337
return T(0.5) * (lo + hi);
326338
}
327339

340+
template <typename T, typename SdfColliderType>
341+
bool SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::sweptSegmentMayCollide(
342+
const Eigen::Vector3<T>& localPos0,
343+
const Eigen::Vector3<T>& localPos1,
344+
const SdfColliderType& collider) const {
345+
const auto bounds = collider.bounds();
346+
const Eigen::Vector3<T> boundsMin = bounds.min().template cast<T>();
347+
const Eigen::Vector3<T> boundsMax = bounds.max().template cast<T>();
348+
const Eigen::Vector3<T> segMin = localPos0.cwiseMin(localPos1);
349+
const Eigen::Vector3<T> segMax = localPos0.cwiseMax(localPos1);
350+
return !(
351+
(segMin.array() > boundsMax.array()).any() || (segMax.array() < boundsMin.array()).any());
352+
}
353+
328354
template <typename T, typename SdfColliderType>
329355
double SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::getError(
330356
std::span<const ModelParametersT<T>> /* modelParameters */,
@@ -359,6 +385,9 @@ double SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::getError(
359385
if (segLen < std::numeric_limits<T>::epsilon()) {
360386
continue; // no motion: static penetration is the per-frame function's job
361387
}
388+
if (!sweptSegmentMayCollide(localPos0, localPos1, collider)) {
389+
continue;
390+
}
362391

363392
const auto intervals = findPenetratingIntervals(localPos0, delta, segLen, collider);
364393
for (const auto& interval : intervals) {
@@ -418,6 +447,9 @@ double SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::getGradient(
418447
if (segLen < std::numeric_limits<T>::epsilon()) {
419448
continue;
420449
}
450+
if (!sweptSegmentMayCollide(localPos0, localPos1, collider)) {
451+
continue;
452+
}
421453

422454
const size_t commonAncestor = detail_sdf_collision::findCommonAncestorForVertex(
423455
*character_.skinWeights, this->skeleton_, vertexIndex, collider.parentJoint());
@@ -532,6 +564,9 @@ double SDFCollisionSequenceErrorFunctionT<T, SdfColliderType>::getJacobian(
532564
if (segLen < std::numeric_limits<T>::epsilon()) {
533565
continue;
534566
}
567+
if (!sweptSegmentMayCollide(localPos0, localPos1, collider)) {
568+
continue;
569+
}
535570

536571
const size_t commonAncestor = detail_sdf_collision::findCommonAncestorForVertex(
537572
*character_.skinWeights, this->skeleton_, vertexIndex, collider.parentJoint());

‎momentum/test/character_sequence_solver/sequence_test.cpp‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,9 @@ struct AnalyticSphereCollider {
6969
dist > T(0) ? Eigen::Vector3<T>(localPos / dist) : Eigen::Vector3<T>::UnitX();
7070
return {dist - T(radius), grad};
7171
}
72+
[[nodiscard]] axel::BoundingBox<float> bounds() const {
73+
return axel::BoundingBox<float>(Eigen::Vector3f::Zero(), radius);
74+
}
7275
[[nodiscard]] size_t parentJoint() const {
7376
return parent;
7477
}

0 commit comments

Comments
 (0)