Skip to content

[FEATURE] Add analytical sphere-sphere narrow-phase contact#2963

Open
Kashu7100 wants to merge 2 commits into
Genesis-Embodied-AI:mainfrom
Kashu7100:kashu/analytic-sphere-sphere-narrowphase
Open

[FEATURE] Add analytical sphere-sphere narrow-phase contact#2963
Kashu7100 wants to merge 2 commits into
Genesis-Embodied-AI:mainfrom
Kashu7100:kashu/analytic-sphere-sphere-narrowphase

Conversation

@Kashu7100

@Kashu7100 Kashu7100 commented Jun 17, 2026

Copy link
Copy Markdown
Collaborator

Summary

Sphere-sphere collision pairs currently fall through to the iterative MPR narrow-phase path (and, on a cold contact cache, GJK+EPA as well). A sphere-sphere contact is a closed form, so this PR routes it through a dedicated analytic function — mirroring the existing sphere-capsule, sphere-box, and capsule-capsule specializations.

func_sphere_sphere_contact (in collider/capsule_contact.py) computes the contact directly from the two centers and radii:

normal      = (c_a - c_b) / ||c_a - c_b||      (B -> A, into geom A)
penetration = r_a + r_b - ||c_a - c_b||
contact     = c_a - (r_a - penetration/2) * normal

with an arbitrary-direction fallback for coincident centers. It is wired into all three narrow-phase dispatch sites (the multi-contact perturbation loop, func_narrow_phase_convex_vs_convex, and the split contact0 path).

Motivation

The closed form is also exactly differentiable, unlike the EPA path, which fails to converge on the smoothly-curved sphere-sphere Minkowski boundary (see the requires_grad guard in collider.py).

Benchmark

Two overlapping spheres (r=0.10 / r=0.15, centers 0.18 apart → exact penetration 0.07), 8192 parallel envs, float32 GPU, timing pure collider.detection() with pinned positions (500 calls after warmup). MPR baseline measured by reverting only the dispatch branch in the same build.

per detection() call penetration err normal contact pos
Analytic (this PR) 96.7 µs 7.2e-9 (float32 ε) exact (-1,0,0) exact
MPR (current) 12 407.8 µs 6.0e-6 ~0.5° off-axis drifts

~128× faster on the full detection workload and more accurate.

Test

Adds test_sphere_sphere_analytical_accuracy to tests/test_rigid_physics_analytical_vs_gjk.py (passes).

🤖 Generated with Claude Code

Sphere-sphere pairs previously fell through to the iterative MPR
(and, on a cold cache, GJK+EPA) narrow-phase path. A sphere-sphere
contact is a closed form, so route it through a dedicated analytic
function alongside the existing sphere-capsule / sphere-box /
capsule-capsule specializations.

func_sphere_sphere_contact computes the contact directly from the two
centers and radii:
  normal      = (c_a - c_b) / ||c_a - c_b||   (B -> A)
  penetration = r_a + r_b - ||c_a - c_b||
  contact     = c_a - (r_a - penetration/2) * normal
with an arbitrary-direction fallback for coincident centers.

Wired into all three narrow-phase dispatch sites (multi-contact loop,
func_narrow_phase_convex_vs_convex, and the split contact0 path).

Benchmark (8192 parallel pairs, float32 GPU, pure detection()):
  - analytic: ~96 us/call, penetration error ~7e-9 (float32 eps),
    exact normal and contact point
  - MPR baseline: ~12408 us/call, penetration error ~6e-6, normal
    ~0.5 deg off-axis
=> ~128x faster and more accurate for sphere-sphere.

The closed form is also exactly differentiable, unlike the EPA path
which fails to converge on the smoothly-curved sphere-sphere Minkowski
boundary.

Adds test_sphere_sphere_analytical_accuracy.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 94a77bab8c

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

geoms_info,
rigid_global_info,
)
elif geoms_info.type[i_ga] == gs.GEOM_TYPE.SPHERE and geoms_info.type[i_gb] == gs.GEOM_TYPE.SPHERE:

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Allow sphere-sphere through the requires_grad guard

This new analytical dispatch still cannot run in differentiable scenes: collider setup rejects any smooth/smooth pair when requires_grad=True, and the existing guard in genesis/engine/solvers/rigid/collider/collider.py lines 548-555 explicitly raises for sphere-sphere before narrowphase is reached. As a result, the added closed-form sphere-sphere path remains unusable for the differentiable case it is meant to unblock; the guard/specialized mask needs to exempt sphere-sphere pairs handled analytically.

Useful? React with 👍 / 👎.

@Kashu7100 Kashu7100 changed the title Add analytical sphere-sphere narrow-phase contact [FEATURE] Add analytical sphere-sphere narrow-phase contact Jun 17, 2026
@hughperkins

Copy link
Copy Markdown
Collaborator

Nice that you updated tests/test_rigid_physics_analytical_vs_gjk.py 🙌

Skimming the PR, looks plausible to me. (skimmed very quickly, like 30 seconds, to be clear).

@github-actions

Copy link
Copy Markdown

🔴 Benchmark Regression Detected ➡️ Report

Addresses Codex review on Genesis-Embodied-AI#2963: the analytic sphere-sphere path was
unreachable under requires_grad=True because the collider guard rejected
all smooth/smooth pairs before narrowphase, so the closed-form path could
not unblock the differentiable case it was meant to.

Make sphere-sphere fully differentiable:
  - collider guard: exempt sphere-sphere (still rejects sphere-ellipsoid
    and ellipsoid-ellipsoid, which have no analytic path and tunnel through
    diff_gjk's EPA).
  - backward: func_differentiable_sphere_contact reconstructs the contact in
    closed form from the geom centres and radii, bypassing the Minkowski-
    triangle reconstruction (degenerate for two smoothly-curved surfaces).
  - backward kernel: both contact loops dispatch sphere-sphere pairs to the
    analytic reconstruction.
  - forward: func_add_diff_contact_input_sphere registers a self-referential
    diff-contact-input entry so the backward pass processes the analytic
    contact.

Gradients w.r.t. geom positions match finite differences to ~1e-11 (64-bit).

Tests: test_diff_smooth_pair_raises now covers a sphere-ellipsoid pair (still
unsupported); new test_diff_sphere_sphere_contact checks the exact forward
contact and FD-validates the analytic gradients.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@Kashu7100

Copy link
Copy Markdown
Collaborator Author

Addressed the Codex P2 in 38c9a78 — sphere-sphere is now fully supported under requires_grad=True, not just exempt from the guard.

Codex correctly noted the guard made the analytic path unreachable. Just relaxing the guard wasn't enough, though: the differentiable backward (func_narrow_phase_diff_convex_vs_convex) reconstructs every contact from a Minkowski triangle, which is degenerate for two spheres — it produced NaN gradients. So the fix is end-to-end:

  • Guard (collider.py): exempt sphere-sphere only; sphere-ellipsoid / ellipsoid-ellipsoid still raise (no analytic path, still tunnel through EPA).
  • Backward (diff_gjk.py): new func_differentiable_sphere_contact reconstructs the contact in closed form from the geom centres + radii.
  • Backward kernel (narrowphase.py): both contact loops dispatch sphere-sphere pairs to the analytic reconstruction.
  • Forward (narrowphase.py + contact.py): func_add_diff_contact_input_sphere registers a self-referential diff-input entry so the backward processes the analytic contact.

Gradients w.r.t. geom positions match finite differences to ~1e-11 (64-bit). test_diff_smooth_pair_raises now covers a sphere-ellipsoid pair (still unsupported), and a new test_diff_sphere_sphere_contact checks the exact forward contact + FD-validates the gradients. Full test_grad.py + test_rigid_physics_analytical_vs_gjk.py pass (25 passed).

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 38c9a78b11

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

radius_b = geoms_info.data[i_gb][0]
delta = geoms_state.pos[i_ga, i_b] - geoms_state.pos[i_gb, i_b]
dist = delta.norm()
contact_normal = delta / dist

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Guard the sphere-sphere backward at coincident centers

When two differentiable spheres have the same center (or are close enough that the forward path uses its dist <= EPS fallback), this backward reconstruction still computes delta / dist with dist == 0, so collider.backward() produces NaN contact normals/gradients even though func_sphere_sphere_contact generated a finite contact using an arbitrary normal. This leaves an initialization/state that the new guard now allows under requires_grad=True but cannot actually differentiate safely.

Useful? React with 👍 / 👎.

@duburcqa

duburcqa commented Jun 18, 2026

Copy link
Copy Markdown
Collaborator
image

This table is false advertising. sphere-sphere collision detection is NOT drifting but exact (and there is neither penetration depth nor normal error). MPR is biased, but the post-processing step func_apply_smooth_refinement that could right after fixes this before it gets stored in the contact state. We have a unit test validating exactly this: test_no_drift.

@Kashu7100

Copy link
Copy Markdown
Collaborator Author

that's fair. but the speed ups are real.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants