Add an IPC-style integrator for penetration-free flex contact - #3420
Open
smallquail wants to merge 5 commits into
Open
Add an IPC-style integrator for penetration-free flex contact#3420smallquail wants to merge 5 commits into
smallquail wants to merge 5 commits into
Conversation
smallquail
force-pushed
the
ipc-edge
branch
3 times, most recently
from
August 3, 2026 16:37
80a6e5b to
d797146
Compare
smallquail
force-pushed
the
ipc-edge
branch
5 times, most recently
from
August 7, 2026 14:57
dbcb5fe to
1546fca
Compare
Thin deformables in sustained contact are the case MuJoCo's constraint solver handles least well: a cloth pressed against another cloth needs the contact to be resolved to a tolerance the soft-constraint model does not target, and the failure mode is interpenetration rather than a visible instability. Add `integrator="ipc"`, a barrier-free augmented-Lagrangian integrator for flex contact (Li et al., arXiv 2512.12151). An outer AL loop owns the contact multipliers and a conservative-advancement CCD that keeps the committed configuration intersection-free; the inner problem is the model's own primal CG over the monolithic system, so contact is solved in the same metric as the smooth dynamics rather than as separate rows. Contact enters as a force plus a stiffness rather than as constraint rows: each pair contributes D*ref*w to the smooth force and D*w*w^T to the effective metric, which reaches the gradient and both line-search coefficients with no per-iteration row walk. The pair list is published in mjData.efm_contact for the duration of a solve, so the metric's consumers -- the CSR assembly, the shift, the matrix-free operator and the preconditioner fold -- all read one representation, and passive flex contact and this integrator produce it the same way. The active set is assembled and scored from a single persistent set: the injected rows and the line-search merit had been drawn from two different sets, which on a fast approach disagreed badly enough that the line search rejected steps that were optimal for the problem it had been handed, and the truncated iterate was committed through the CCD blend -- total momentum changed by 6.1x its correct value at contact onset. IpcTest.SelfContactConservesMomentum guards it. Geometry lives in engine_collision_continuous: point-triangle and edge-edge distance with barycentric coordinates and the swept-segment time of impact, which the discrete collision generators do not expose.
Contact was reaching the metric two ways: assembled into the effective stiffness CSR, which mjd_effMulAdd then applied, and separately by mj_extraStiffMulAdd at the solver's two operator sites. Both computed the same D*w*w^T from the same published pair list. Apply it once, inside mjd_effMulAdd. Contact is a sum of rank-1 terms, so applying it matrix-free costs O(npt) per pair against the O(npt^2) that assembling it costs to store and to apply -- measured on a bag model, 5457 pairs would have added ~786k nonzeros to a 74k-entry CSR. The CSR's three contact passes and the solver's two direct calls go away with it. mjd_effMulAdd takes flg_contact because its consumers differ: the linear solves want the contact term, while an energy evaluation that accounts for contact separately would double-count it. It is also now safe to call with the metric inactive, which is what lets the solver reach it on a model that has contact but no assembled flex stiffness. The publication no longer has to be cleared between the metric build and the solve, since one consumer cannot double-apply it.
The merged continuous-collision module carries geometry only: the pair struct holds the type, participants and geom id, and the AL state that used to ride in it belongs to the solver. Define that solver-side pair here, with the geometry members leading so a pair is passed to the mjc_* API by cast rather than by copy, and move the AL warm-start into the active-set seed, since the candidate array is geometry-only. Follow the renames: pairGap, pairVerts returning the participant count, pairBand, standoff with an explicit cap, advance with an optional per-pair TOI output. The standoff cap and detection band constants move here from the module, where review left no solver-specific constants.
Flex-flex pairs measure their gap at the midsurface: mjc_pairGap does not subtract the flex radii, so a pair whose mesh geometry is tighter than the combined radii still has a valid gap and stays under CCD coverage. Flex-vs-geom pairs (types 2/3/4) kept subtracting the flex radius, so the same configuration that motivated the flex-flex convention -- geometry closer than the radius, such as a liner resting on the surface it wraps -- produced a permanently negative gap there, leaving those pairs unusable. Return the raw distance for the three geom pair types in mjc_pairGap and in conGapAdv, which must agree with it for conservative advancement. A geom has no radius to fold in, so the gap is now flex-midsurface to geom surface. bandOffset gains the three geom cases so each flex participant contributes its radius to the broad-phase reach, leaving detection range unchanged; the standoff target, min(band, cap), is untouched.
The injection loop skipped every non-flex-flex barrier pair whenever the model contained an articulated or rigid body, leaving those contacts to the native constraint rows. The pairs stayed in the active set regardless: their multipliers kept ascending and conservative advancement kept enforcing them, while no corresponding force ever entered the solve. Flex resting on a static geom in a model that also contains a free body therefore had no restoring force at all. It settled onto the surface, and the advance then froze, since every step from there intersects. Inject the full active set in that case as well. Candidate generation is flex-only and its geom pairs are restricted to static geoms, so this adds no contact class: it restores rows that are already barriered whenever the same flex touches the same geom in a model without rigid bodies.
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.
What
A new opt-in integrator,
integrator="ipc", providing penetration-free contact for deformable flexes: flex–flex contact including self-collision (vertex–triangle and edge–edge) and flex-vs-static-geom. All other constraints — rigid contacts, limits, friction, equality — remain on the native pipeline, solved simultaneously. The integrator is fully opt-in: models that don't select it are unaffected.How it works
IPC methods prevent penetration by combining an implicit position-level step with continuous collision detection (CCD). This implementation makes that work inside MuJoCo's existing solver architecture rather than beside it:
Per-step implicit minimization. Each step minimizes an incremental potential over the flex vertex positions (and coupled joint dofs). Flex elasticity is carried by the implicit effective metric M + K.
Augmented-Lagrangian contact (why not just a barrier). Classic IPC enforces non-penetration with a stiff log-barrier, which brings severe ill-conditioning and barrier-parameter tuning. Here each contact pair instead carries a persistent Lagrange multiplier — a running contact-force estimate — and a few outer iterations per step alternate between solving the contact-augmented problem and updating the multipliers. Forces converge to the right values at moderate, fixed stiffness: no barrier, no conditioning cliff.
MuJoCo's own solver as the inner solver. Each outer iteration's subproblem is exactly one MuJoCo constraint solve (the CG solver on the standard primal objective): the IPC contact terms enter through a small hook as additional quadratic terms in the objective, so native constraints and IPC contact are resolved together by the existing machinery. There is no separate optimizer.
CCD-bounded commits. Candidate pairs are gathered with a conservative motion margin; every accepted position update advances only along a verified intersection-free path, and a step commits only once its full motion has been absorbed — so the committed trajectory never tunnels, and a step that cannot complete warns rather than silently losing motion.
Scope and limitations
Testing
engine_ipc_testcovers the distance kernels (point–triangle, segment–segment, vertex–geom), pinned-vertex handling, and contact scenarios; exercised on cloth/bag models with heavy self-contact.