Skip to content

feat: add Möller 1997 boolean tri-tri fast path with test coverage#846

Closed
facontidavide wants to merge 1 commit into
coal-library:develfrom
facontidavide:perf/moller-tritri-fastpath
Closed

feat: add Möller 1997 boolean tri-tri fast path with test coverage#846
facontidavide wants to merge 1 commit into
coal-library:develfrom
facontidavide:perf/moller-tritri-fastpath

Conversation

@facontidavide

Copy link
Copy Markdown

Summary

  • Adds a fast-path triangle-triangle intersection test for boolean-only mesh-mesh collision queries (when the user opts out of contact info, distance lower bound, and security margin).
  • Implementation is the classic Möller 1997 6-vertex separating-axis test, header-only in include/coal/internal/tri_tri_overlap.h. Returns a coplanar flag so the caller can fall back to GJK on coplanar / near-coplanar inputs (where Möller's SAT isn't reliable).
  • Cached fast-path eligibility flag (use_moller_fastpath_) on MeshCollisionTraversalNode keeps the per-leaf check to a single if.

⚠️ Open against #843 (gjk-solver-hoist)

This PR's leaf path is conceptually downstream of #843 (GJK hoist). The Möller fallback path (coplanar pairs) currently constructs a local GJKSolver per leaf — once #843 lands, a follow-up rebase will switch this PR's fallback to use the hoisted member solver solver_ for an additional small speedup on coplanar pairs. The change is mechanical and has been verified on the original branch where both commits live together.

This PR can be merged before #843 (with the local-solver fallback on the coplanar path) or after #843 (with a trivial rebase that switches fallback to solver_).

Implementation notes

  • include/coal/internal/tri_tri_overlap.h: header-only template triTriOverlap(P1, P2, P3, Q1, Q2, Q3, &coplanar) returning bool. Inputs are world-space Vec3s triples; transformation happens in leafCollides before the call.
  • MeshCollisionTraversalNode::leafCollides: when use_moller_fastpath_ is true, transforms both triangles into world frame, calls triTriOverlap, and either records a synthetic contact (positions are placeholders since the user opted out of contact info) or returns immediately on coplanar fallback.
  • use_moller_fastpath_ is precomputed at construction time as !request.enable_contact && !request.enable_distance_lower_bound && request.security_margin == 0. Computing it per-leaf would be wasteful; the request is fixed for a traversal.

Performance impact

The fast path is the largest single perf contributor for boolean motion-validation workloads (the workload the original commit targeted). On the synthetic benchmark — which uses default CollisionRequest with enable_contact=true — the fast path never fires (the eligibility gate is false), so the synthetic Δ is within noise.

Methodology

  • Hardware: x86-64 desktop, P-core pinned (taskset -c 4), turbo locked.
  • Build: cmake --build build -j12 -DCMAKE_BUILD_TYPE=Release in isolated worktrees; separate libcoal.so per variant. Both base and variant compiled with stock upstream flags.
  • Runs: N = 15 interleaved (base, variant, base, variant, …); median reported.
Workload Before (µs, median) After (µs, median) Δ N stdev (µs)
coal-test-benchmark total 62306.5 62248.1 -0.09% 15 base 788.8 / variant 212.5

For the actual fast-path numbers: original commit measurements on polso/gen4 mesh-mesh boolean motion-validation (a real-world workload outside this repo) showed -14 % to -34 % per joint — by a wide margin the largest single optimisation in the parent branch.

Correctness gate: full ctest suite passes including the two new tri-tri tests.

Tests

  • test/tri_tri_overlap_fuzz.cpp: 50,000 random triangle pairs cross-checked against a GJK oracle. 0 disagreements on non-coplanar inputs. This is the strongest single-test regression gate in the parent branch.
  • test/tri_tri_overlap_edge_cases.cpp: 10 named degenerate / corner-case pairs that the random fuzz never hits — coplanar-overlapping, coplanar-disjoint, identical triangles, shared-vertex / shared-edge boundary, zero-area triangle, collinear vertices, near-coplanar disjoint and overlapping, deep interpenetration. All 10 pass.
  • The fuzz + degeneracies pattern here is the gold standard the rest of the parent branch's tests aspire to.

Risk & regression analysis

  • Coplanar-pair correctness: Möller's SAT is unreliable on coplanar triangles. The implementation explicitly detects coplanarity and falls through to GJK; the fuzz oracle exercises this path in randomised inputs and the named edge cases pin both branches.
  • Distance-lower-bound information loss: when the fast path fires it sets sqrDistLowerBound = 0, which is the honest "no info" answer for a request that opted out of distance lower bound. Callers that need a tighter bound must enable request.enable_distance_lower_bound, which disables the fast path (eligibility gate becomes false) — by design.
  • Synthetic contact placeholder: when an overlap is detected and the user wants contact records but with placeholder data (enable_contact == false is the gating condition, but num_max_contacts may still be > 0), the synthetic Contact uses midpoint position and zero normal. This is consistent with "user opted out of contact info"; the fast-path check ensures the placeholder is only registered when the user has set enable_contact=false.

Adds a fast-path triangle-triangle intersection test for boolean-only
mesh collision queries (CollisionRequestFlag::NO_REQUEST and friends),
based on Möller 1997. The implementation lives in
include/coal/internal/tri_tri_overlap.h as a header-only template and
returns a coplanar flag so the caller can fall back to GJK on coplanar
or near-coplanar inputs (where Möller's 6-vertex separating-axis test
isn't reliable).

The fast path is enabled in MeshCollisionTraversalNode::leafCollides
when the request only needs a boolean answer. It transforms each
triangle's three vertices to world space, runs Möller, and on a
non-coplanar overlap reports the contact. Coplanar pairs fall through
to the existing GJK/EPA path.

Test coverage:

- test/tri_tri_overlap_fuzz.cpp: 50000 random triangle pairs cross-
  checked against a GJK oracle. 0 disagreements on non-coplanar inputs.

- test/tri_tri_overlap_edge_cases.cpp: 10 named degenerate cases that
  the random fuzz never hits — coplanar overlapping/disjoint, identical
  triangles, shared-vertex / shared-edge boundary, zero-area triangle,
  collinear vertices, near-coplanar disjoint/overlapping, deep
  interpenetration. All 10 pass.

On the polso/gen4 mesh-vs-mesh boolean motion-validation workload this
is the largest single perf win in the branch (-14% to -34% per joint).
@jorisv jorisv added the pr status wip To not review in weekly meeting label May 11, 2026
@lmontaut

Copy link
Copy Markdown
Contributor

See #858

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pr status wip To not review in weekly meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants