Skip to content

Commit 2a3554c

Browse files
committed
Integrate passive flex contact implicitly
Contact of a flex with `passive` collisions enabled was applied as an explicit spring of fixed stiffness 1e4, which the timestep bounds: any stiffness worth having oscillates faster than the step can resolve, so the force was too soft to keep sheets apart and interpenetration was routine. Carry its curvature in the effective metric M + K instead, alongside the flex's own stretch and bending stiffness. The contact block k*J^T*J is appended to the per-vertex candidate list already assembled for the flex stencils, so it costs additional entries in an existing matrix rather than a new one, and the accompanying shift -h*K*v is what damps the stiff modes. At a 2 ms timestep this holds roughly 50x the stiffness an explicit force of the same step could. With the timestep no longer setting the bound, the stiffness is chosen as a natural frequency scaled by the participating vertex mass rather than left at a fixed 1e4, so one value suits models of any scale. Passive handling is scoped to contacts whose every dof is a flex vertex carried by the metric: flex against flex, flex against itself, and flex against static geometry, which contributes no dofs of its own. For those the Hessian is assembled in full. Contact with a body that can move would have that body's dofs dropped from it, and is left on the constraint solver. The feature now requires an integrator whose constraint solve runs in that metric, and is rejected with an error otherwise. Add model/flex/drape.xml as the example model, replacing sphere_passive, whose contacts no longer demonstrated the feature.
1 parent 0accc5b commit 2a3554c

12 files changed

Lines changed: 469 additions & 61 deletions

‎doc/XMLreference.rst‎

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4550,9 +4550,24 @@ extensions specific to flexes.
45504550
.. _flex-contact-passive:
45514551

45524552
:at:`passive`: :at-val:`[true, false], "false"`
4553-
When enabled, the contact is not added to the contact solver but it is instead used to compute passive
4554-
(spring-damper) contact forces. All contacts, regardless of the specified condim, are frictionless (condim 1). This
4555-
is an experimental feature.
4553+
When enabled, contact of this flex with another flex, with itself, or with static geometry is not added to the
4554+
contact solver and is instead applied as a passive normal force. Contact with a body that can move is left on the
4555+
constraint solver.
4556+
4557+
Friction is not modelled on this path: every passive contact is frictionless (condim 1) regardless of the
4558+
specified condim, and the force is purely normal. A flex therefore slides freely over static geometry, so a cloth
4559+
will not stay draped over a fixed shape and will not come to rest on a slope. Where friction matters more than
4560+
non-penetration, leave this option off.
4561+
4562+
The force is a penalty on penetration depth whose stiffness is chosen as a natural frequency scaled by the
4563+
participating vertex mass, so a single value is appropriate across model scales; it is not user-specified. That
4564+
stiffness is integrated implicitly, its curvature being carried by the effective metric, and is therefore far
4565+
stiffer than an explicit force at the same timestep could be. It follows that the feature requires an integrator
4566+
whose constraint solve runs in that metric: :at:`implicit` or :at:`implicitfast` with the CG solver, pyramidal
4567+
friction cones and sleep disabled. A model requesting passive flex collisions otherwise is rejected with an error.
4568+
4569+
Being a penalty force, it does not guarantee non-penetration: a thin flex moving fast enough to cross another
4570+
within one step will pass through it. This is an experimental feature.
45564571

45574572

45584573
.. _deformable-skin:

‎doc/changelog.rst‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,17 @@ Engine
4646
.. admonition:: Breaking API changes
4747
:class: attention
4848

49+
- Contacts of a flex with :ref:`passive<flexcomp-contact-passive>` collisions are now integrated implicitly:
50+
their stiffness is carried by the effective metric M + K rather than applied as an explicit spring, and so can
51+
be far stiffer than the timestep would otherwise permit -- at a 2 ms timestep, roughly 50x what an explicit
52+
force of the same step could hold. Being a penalty force it still does not guarantee non-penetration. A model
53+
using passive collisions changes in three ways and should be re-checked: the feature now requires an integrator
54+
whose constraint solve runs in that metric (:at:`implicit` or :at:`implicitfast` with the CG solver, pyramidal
55+
cones and sleep disabled) and is rejected with an error otherwise; passive handling now covers contact of such a
56+
flex with another flex, with itself, and with static geometry, while contact with a moving body returns to the
57+
constraint solver and gains friction; and the stiffness is no longer a fixed 1e4 but a natural frequency scaled
58+
by the participating vertex mass, which is considerably stiffer for typical models.
59+
4960
- Removed ``mjData.efm_L_rownnz``, ``mjData.efm_L_rowadr`` and ``mjData.efm_L_colind``. They described the sparsity
5061
of the effective-metric Cholesky factor, which no longer exists; ``mjData.efm_L`` now holds dense 3x3 blocks,
5162
9 numbers per covered vertex. ``mjData.efm_active`` no longer takes the value 2: nothing selects a solve path on
@@ -57,6 +68,10 @@ Engine
5768
Models
5869
^^^^^^
5970

71+
- Added `drape <https://github.com/google-deepmind/mujoco/blob/main/model/flex/drape.xml>`__ example model: three
72+
cloths draped over a sphere, demonstrating :ref:`passive<flex-contact-passive>` collisions. It replaces the
73+
``sphere_passive`` model, which has been removed.
74+
6075
- Added `bag <https://github.com/google-deepmind/mujoco/blob/main/model/flex/bag.xml>`__ example model: a cloth bag,
6176
held open by pinning the ring of vertices around its mouth, catching the standard humanoid dropped in from above.
6277
Unlike the poncho models, which are bending-only, this model exercises the 2D

‎model/flex/drape.xml‎

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
<!-- Copyright 2026 DeepMind Technologies Limited
2+
3+
Licensed under the Apache License, Version 2.0 (the "License");
4+
you may not use this file except in compliance with the License.
5+
You may obtain a copy of the License at
6+
7+
http://www.apache.org/licenses/LICENSE-2.0
8+
9+
Unless required by applicable law or agreed to in writing, software
10+
distributed under the License is distributed on an "AS IS" BASIS,
11+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
See the License for the specific language governing permissions and
13+
limitations under the License.
14+
-->
15+
16+
<mujoco model="Drape">
17+
<include file="scene.xml"/>
18+
19+
<!-- Three cloths dropped over a sphere. Every contact here is handled by the passive path: cloth
20+
against cloth, each cloth against itself, and cloth against the sphere, which is static and
21+
so contributes no dofs of its own. Their stiffness is carried implicitly by the effective
22+
metric, which is why the sheets rest on each other without interpenetrating. -->
23+
24+
<!-- The metric solve for qacc_smooth is iterative and its blocks do not see the vertex-to-vertex
25+
coupling that contact introduces, so a contact-rich scene like this one needs a larger
26+
iteration budget than the default to converge it. -->
27+
<option timestep="0.002" solver="CG" tolerance="1e-6" iterations="400" integrator="implicitfast"/>
28+
29+
<size memory="50M"/>
30+
31+
<worldbody>
32+
<geom name="ball" type="sphere" size=".3" pos="0 0 .3" rgba=".45 .45 .5 1"/>
33+
34+
<flexcomp type="grid" count="13 13 1" spacing=".055 .055 .055" pos="0 0 .68"
35+
radius=".004" mass=".25" name="cloth1" dim="2" rgba=".85 .35 .25 1">
36+
<contact selfcollide="auto" passive="true" solref="0.01 1" solimp=".95 .99 .0001"/>
37+
<elasticity young="2e4" poisson=".2" thickness="1e-3" elastic2d="both" damping="1e-4"/>
38+
</flexcomp>
39+
40+
<flexcomp type="grid" count="13 13 1" spacing=".055 .055 .055" pos=".06 -.04 .78"
41+
radius=".004" mass=".25" name="cloth2" dim="2" rgba=".25 .55 .8 1">
42+
<contact selfcollide="auto" passive="true" solref="0.01 1" solimp=".95 .99 .0001"/>
43+
<elasticity young="2e4" poisson=".2" thickness="1e-3" elastic2d="both" damping="1e-4"/>
44+
</flexcomp>
45+
46+
<flexcomp type="grid" count="13 13 1" spacing=".055 .055 .055" pos="-.05 .05 .88"
47+
radius=".004" mass=".25" name="cloth3" dim="2" rgba=".95 .8 .3 1">
48+
<contact selfcollide="auto" passive="true" solref="0.01 1" solimp=".95 .99 .0001"/>
49+
<elasticity young="2e4" poisson=".2" thickness="1e-3" elastic2d="both" damping="1e-4"/>
50+
</flexcomp>
51+
</worldbody>
52+
</mujoco>

‎model/flex/sphere_passive.xml‎

Lines changed: 0 additions & 36 deletions
This file was deleted.

‎src/engine/engine_core_constraint.c‎

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2549,11 +2549,26 @@ static int mj_nc(const mjModel* m, mjData* d, int* nnz) {
25492549
for (int i=0; i < ncon; i++) {
25502550
mjContact* con = d->contact + i;
25512551

2552-
// skip if passive
2553-
if ((con->flex[0] > -1 && m->flex_passive[con->flex[0]]) ||
2554-
(con->flex[1] > -1 && m->flex_passive[con->flex[1]])) {
2555-
con->efc_address = -1;
2556-
con->exclude = 4;
2552+
// Passive handling covers flex-flex contact, self-collision included, and contact between a
2553+
// flex and STATIC geometry. What those have in common is that every dof the contact touches is
2554+
// a flex vertex carried by the effective metric -- a static geom has none -- so the Hessian
2555+
// k*J^T*J is assembled in full rather than truncated. A flex against a MOVING body keeps the
2556+
// constraint path: its dofs would be dropped from the Hessian, and the passive force is a
2557+
// normal penalty with no friction cone, which is the wrong trade where a gripper closes on
2558+
// cloth. A contact is passive if either flex asks for it.
2559+
{
2560+
int f0 = con->flex[0], f1 = con->flex[1];
2561+
int wants = (f0 > -1 && m->flex_passive[f0]) || (f1 > -1 && m->flex_passive[f1]);
2562+
int ok = (f0 > -1 && f1 > -1); // flex-flex, or a flex with itself
2563+
for (int s = 0; s < 2 && !ok; s++) {
2564+
if (con->flex[s] < 0 && con->geom[s] > -1) {
2565+
ok = (m->body_weldid[m->geom_bodyid[con->geom[s]]] == 0); // welded to the world
2566+
}
2567+
}
2568+
if (wants && ok) {
2569+
con->efc_address = -1;
2570+
con->exclude = 4;
2571+
}
25572572
}
25582573

25592574
// skip if excluded

0 commit comments

Comments
 (0)
⌂ 换网址