Size the box-box edge-axis tie-break to float32 rounding - #1597
Closed
jsw7460 wants to merge 1 commit into
Closed
Conversation
box_box picks the separating axis of least penetration, and the winner selects between two different manifold routines: a face axis is clipped into several points, an edge axis becomes the closest point between two segments. Edge axes are held to a margin so that faces win near-ties, since the face manifold is the better one for the solver. That margin is relative, `separation * (1.0 - 1e-12)`, inherited from MuJoCo's C where mjtNum is double and 1e-12 is a few thousand ULPs. Here everything is float32, `1.0 - 1e-12` rounds to exactly 1.0, and the test reduces to `c3 < separation`. It matters more than a missing tie-break usually would, because the tie is not incidental. When both boxes are level -- a bar resting on a table -- four of the nine edge-cross axes come out exactly parallel to the face normal. They are the same direction expressed two ways, so their separations agree to the last bit and rounding decides the winner. Over 2000 resting poses of a 120x6x30 mm bar on a 1.2x0.8 m table, overlapping by 0.1 mm, 92 of them hand the contact to the edge-edge branch, which then reports penetrations around 0.9 m at points outside both boxes. MuJoCo reports four contacts at -1e-4 m for every one of those poses. In a simulation the solver undoing that penetration throws the bar off the table at over 10 m/s and the scene goes non-finite shortly after. This is not an artifact of one machine's arithmetic: the pose in the test reports -0.9563501 m on both x86_64 with CUDA and arm64 on the CPU backend, agreeing to every digit printed. Restoring a relative margin does not fix it. The separations are sums of box extents times rotation entries, so their float32 error is absolute and proportional to the extents -- around 5e-8 m for supports of order 0.4 m. Against a resting separation of 1e-4 m that is a relative error near 5e-4, so 1e-6 and 1e-4 both leave all 92 poses failing. This scales the slack by the box extents instead, which takes the same 2000 poses to zero. The test calls box_box directly with the geom poses kinematics produces for one of those configurations. Going through a model would need nativeccd disabled to reach this collider, and that leaves box-box on the analytic path for later tests in the same process.
Collaborator
Collaborator
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fix box_box ghost contacts on level resting surfaces under FP32
When two boxes rest flat against each other, four of the nine edge-cross separating axes are parallel to the face normal, creating an exact tie in separation depth. In FP32, the legacy relative margin
1.0 - 1e-12rounds to 1.0, allowing FP32 cancellation noise in cross-product evaluation to erroneously pick an edge axis over the face axis. The subsequent edge-edge routine degenerates on face contacts and generates phantom contact points hundreds of millimeters deep.This patch introduces an absolute
edge_slackproportional to the sum of the box half-extents (1e-6 * sum(sizes)), ensuring face axes are stably preferred on near-ties regardless of near-zero resting separation. Adds a regression test covering the degenerate resting pose.Simply increasing the relative margin does not work: the separations are sums of box extents times rotation entries, so their FP32 error is absolute (~5e-8 m for supports of order 0.4 m) against a resting separation of ~1e-4 m — a relative error near 5e-4. Both 1e-6 and 1e-4 leave every failing pose failing.
(Note: Mujoco's C has since rewritten this collider on main using Sutherland-Hodgman face clipping and precision-aware constants split on mjUSESINGLE. Porting that is likely the better long-term direction)