Add geom adhesion: contacts that pull, via translated friction cones.

https://youtu.be/GioWwB36XHI

The new geom attribute adhesion (units of force, signed; pair-level
override) translates the contact friction cone along its normal so
that the force origin lies strictly inside it. Consequences: each
contact can pull with up to the given force before breaking, and the
tangential friction budget becomes mu*(f_N + adhesion) -- the
Mohr-Coulomb yield condition with cohesion c = mu*adhesion -- so
lightly-squeezed grasps retain a guaranteed friction floor.

A translated cone factors exactly into {constant attractive force}
+ {original cone}, so no solver kernels change. The implementation is
this factorization: a constant attraction along contact normals
accumulated into the new mjData.qfrc_adhesion (summed into
qfrc_passive), plus a bias of adhesive contact rows' reference
acceleration (aref += R*adhesion), which makes resting penetration
exactly independent of adhesion. Contacts of adhesive pairs remain
active throughout the gap zone, producing rows with positive violation
whose reference acceleration pulls: a tether that resists pull-off
smoothly, captures objects released within the band into steady
contact, and detaches at the specified force. Adhesion values of the
two geoms combine by sum; explicit pairs override.

mj_contactForce reports the net interface force (cone force minus the
adhesive pull), whose normal component can now be negative. Negative
adhesion is allowed and produces a repulsive offset (air hockey).

PiperOrigin-RevId: 950858148
Change-Id: I879c08eba7ae501e5c0f8c2f807167344da4c2bc
This commit is contained in:
Yuval Tassa
2026-07-20 08:35:17 -07:00
committed by Copybara-Service
parent b942c9f922
commit a264d0bc8b
28 changed files with 784 additions and 50 deletions
+39 -2
View File
@@ -2797,6 +2797,29 @@ helps clarify the role of bodies and geoms in MuJoCo.
additionally displays an arrow along the tangential surface velocity at the contact point. This attribute can be
modified at runtime.
.. _body-geom-adhesion:
.. youtube:: GioWwB36XHI
:align: right
:width: 40%
:at:`adhesion`: :at-val:`real, "0"`
Adhesive force of contacts with this geom, in units of force. Geometrically, the friction cone is translated down
along the normal so that the force origin lies strictly inside it: each contact can pull with up to ``adhesion``
before breaking, and the friction budget becomes :math:`\mu(f_N + \text{adhesion})`. Contacts resist sliding even
under zero normal force, the defining property of cohesive materials. This is useful for sticky materials (tape,
gecko feet, tacky rubber) and as a physical stabilizer for grasping. The adhesion of a contact is the sum of the
values of the two contacting geoms, or the value of the higher-:ref:`priority<body-geom-priority>` geom if priorities
differ; an explicit contact :ref:`pair<contact-pair>` overrides both. Note that adhesion is *per contact*: a box face
resting on a plane generates four contact points and therefore four times the pull-off force of a single-point
contact. To let adhesion act across a small separation (attraction at a distance), set :ref:`gap<body-geom-gap>` to
the desired interaction range. This can be used to model magnets. Resting penetration is unaffected by adhesion (the
compression behavior of the contact is unchanged; only a tensile branch is added), and :ref:`mj_contactForce` reports
the net interface force, whose normal component can be negative under tension. The underlying model is described in
the :ref:`Computation chapter<soAdhesion>`. For adhesion as a *controlled* force — switched on and off like a vacuum
gripper, dividing a total force between a body's contacts and pressing the bodies together — see the :ref:`adhesion
actuator<actuator-adhesion>`.
.. _body-geom-fromto:
:at:`fromto`: :at-val:`real(6), optional`
@@ -3488,10 +3511,12 @@ joints and tendons have different sets of attributes, while all geoms in the com
.. _composite-geom-surfacevel:
.. _composite-geom-adhesion:
.. |body/composite/geom attrib list| replace::
:at:`type`, :at:`contype`, :at:`conaffinity`, :at:`condim`, :at:`group`, :at:`priority`, :at:`size`, :at:`material`,
:at:`rgba`, :at:`friction`, :at:`mass`, :at:`density`, :at:`solmix`, :at:`solref`, :at:`solimp`, :at:`margin`,
:at:`gap`, :at:`surfacevel`
:at:`gap`, :at:`surfacevel`, :at:`adhesion`
|body/composite/geom attrib list|
Same meaning as regular :ref:`geom <body-geom>` attributes.
@@ -4224,6 +4249,12 @@ friction can only be created with this element.
``margin`` and ``margin + gap`` are included in ``mjData.contact`` as inactive contacts but no contact forces are
generated.
.. _contact-pair-adhesion:
:at:`adhesion`: :at-val:`real, "0"`
Adhesive force of contacts generated by this pair, overriding the sum of the geoms'
:ref:`adhesion<body-geom-adhesion>` values. See there for detailed semantics.
.. _contact-exclude:
@@ -6415,7 +6446,9 @@ geckos and insects rather than an industrial vacuum gripper. In order to enable
:ref:`gap<body-geom-gap>` attribute of the body's geoms to a positive value. This creates a layer around each geom where
contacts are detected but no contact forces are generated, and the adhesive force can act across this gap. In the video
above, such inactive contacts are blue, while active contacts are orange. An adhesion actuator's length is always 0.
:at:`ctrlrange` is required and must also be nonnegative (no repulsive forces are allowed). The underlying :el:`general`
:at:`ctrlrange` is required and must also be nonnegative (no repulsive forces are allowed). For adhesion as a
*passive* property of the contacting surfaces — always on, per contact, and leaving resting penetration unaffected —
see the :ref:`geom/adhesion<body-geom-adhesion>` attribute. The underlying :el:`general`
attributes are set as follows:
=========== ======= =========== ========
@@ -9586,6 +9619,8 @@ if omitted.
.. _default-geom-surfacevel:
.. _default-geom-adhesion:
.. _default-geom-fromto:
.. _default-geom-axisangle:
@@ -9754,6 +9789,8 @@ if omitted.
.. _default-pair-margin:
.. _default-pair-adhesion:
:el-prefix:`default/` |-| **pair** |?|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+15
View File
@@ -911,6 +911,9 @@
.. grid-item::
:ref:`surfacevel<body-geom-surfacevel>`
.. grid-item::
:ref:`adhesion<body-geom-adhesion>`
.. grid-item::
:ref:`fromto<body-geom-fromto>`
@@ -1379,6 +1382,9 @@
.. grid-item::
:ref:`surfacevel<composite-geom-surfacevel>`
.. grid-item::
:ref:`adhesion<composite-geom-adhesion>`
.. dropdown:: :ref:`site<composite-site>` :octicon:`dot`
@@ -1871,6 +1877,9 @@
.. grid-item::
:ref:`margin<contact-pair-margin>`
.. grid-item::
:ref:`adhesion<contact-pair-adhesion>`
.. dropdown:: :ref:`exclude<contact-exclude>` |*|
@@ -5504,6 +5513,9 @@
.. grid-item::
:ref:`surfacevel<default-geom-surfacevel>`
.. grid-item::
:ref:`adhesion<default-geom-adhesion>`
.. grid-item::
:ref:`fromto<default-geom-fromto>`
@@ -5726,6 +5738,9 @@
.. grid-item::
:ref:`margin<default-pair-margin>`
.. grid-item::
:ref:`adhesion<default-pair-adhesion>`
.. dropdown:: :ref:`equality<default-equality>` :octicon:`dot`
+12 -1
View File
@@ -21,6 +21,16 @@ Engine
velocity relative to the moving surface rather than to the geom, since that is the quantity the constraint acts
on; for geoms without :at:`surfacevel` the two are identical. Contact-point visualization draws an arrow along the
surface velocity at contacts with moving surfaces.
.. youtube:: GioWwB36XHI
:align: right
:width: 35%
- Added :ref:`geom/adhesion<body-geom-adhesion>` and :ref:`pair/adhesion<contact-pair-adhesion>`: an adhesive force
associated with a contact, useful for modeling sticky materials. Contacts can pull with up to the given force before
breaking, and the friction budget becomes :math:`\mu(f_N + \text{adhesion})`. Combined with :ref:`gap<body-geom-gap>`,
adhesive contacts apply "adhesion at a distance", useful for modeling magnets. Resting penetration is unaffected by
adhesion. :ref:`mj_contactForce` reports the net interface force, whose normal component can now be negative.
- Replaced midpoint integration of free bodies with :ref:`gyroscopic derivatives<geFreeBody>` in the ``implicitfast``
:ref:`integrator<geIntegrators>`: the bias-force derivative of every standalone free body is applied via a local
unsymmetric solve of its decoupled block, making ``implicitfast`` identical to ``implicit`` for such bodies.
@@ -56,7 +66,8 @@ Engine
.. admonition:: Breaking ABI changes
:class: caution
- Added ``texid``, ``texuniform`` and ``texrepeat`` fields to ``mjvGeom``.
- Added ``texid``, ``texuniform`` and ``texrepeat`` fields to :ref:`mjvGeom`.
- The :ref:`mjContact`` struct gained an ``adhesion`` member, changing its size and layout.
.. admonition:: Bug fixes
:class: admonition
+34 -11
View File
@@ -1507,17 +1507,18 @@ here is to construct a sensible and intuitive parameterization of the constraint
.. _soExactDiag:
**Diagonal approximation:** The approximation has three sources of error: (i) it is frozen at ``qpos0`` rather than
evaluated at the current configuration; (ii) it averages the directional inverse inertia into a scalar, assuming
isotropy; and (iii) it treats the contributions of different bodies as independent, ignoring kinematic coupling through
shared DOFs. These errors are usually modest, but can become significant for models with highly anisotropic inertias or
long kinematic chains that operate far from ``qpos0``. In severe cases — particularly when the averaged inertia becomes
near-zero despite finite directional inertia — the regularizer :math:`R` becomes near-zero, making constraints
infinitely hard and causing divergence. The :ref:`diagexact<option-flag-diagexact>` flag replaces the approximation with
the exact diagonal :math:`A_{ii} = \|Y_i\|^2`, where :math:`Y = J M^{-1/2}` is the whitened Jacobian, computed at the
current configuration. This eliminates all three sources of error at a modest runtime cost: computing :math:`Y` requires
a back-substitution with the Cholesky factor of the mass matrix for each active constraint row; if
:ref:`dual solvers<soAlgorithms>` are used (PGS or NoSlip), the cost is negligible since :math:`Y` is computed anyway.
Diagonal approximation
The approximation has three sources of error: (i) it is frozen at ``qpos0`` rather than evaluated at the current
configuration; (ii) it averages the directional inverse inertia into a scalar, assuming isotropy; and (iii) it treats
the contributions of different bodies as independent, ignoring kinematic coupling through shared DOFs. These errors
are usually modest, but can become significant for models with highly anisotropic inertias or long kinematic chains
that operate far from ``qpos0``. In severe cases — particularly when the averaged inertia becomes near-zero despite
finite directional inertia — the regularizer :math:`R` becomes near-zero, making constraints infinitely hard and
causing divergence. The :ref:`diagexact<option-flag-diagexact>` flag replaces the approximation with the exact
diagonal :math:`A_{ii} = \|Y_i\|^2`, where :math:`Y = J M^{-1/2}` is the whitened Jacobian, computed at the current
configuration. This eliminates all three sources of error at a modest runtime cost: computing :math:`Y` requires a
back-substitution with the Cholesky factor of the mass matrix for each active constraint row; if :ref:`dual
solvers<soAlgorithms>` are used (PGS or NoSlip), the cost is negligible since :math:`Y` is computed anyway.
Next we explain how the reference acceleration is computed. As already mentioned, we use a spring-damper model
parameterized by *damping* and *stiffness* coefficients element-wise:
@@ -1537,6 +1538,28 @@ velocity of the surface material, so that the reference acceleration drives the
surface; this is how conveyor belts and turntables are implemented, and it is also the quantity reported in the
contact rows of ``mjData.efc_vel``.
.. _soAdhesion:
Adhesion
Contacts of geoms with nonzero :ref:`adhesion<body-geom-adhesion>` force :math:`\delta` can pull: the feasible force
set is the friction cone *translated down the contact normal* by :math:`\delta`. This is implemented with an exact
factorization which leaves the cone machinery untouched. A constant attractive force :math:`\delta` along the contact
normal is accumulated into the passive force ``mjData.qfrc_adhesion``, and the reference acceleration of the contact's
normal row is biased:
.. math::
\ar \rightarrow \ar + R \, \delta
(for pyramidal cones the bias is distributed equally over the :math:`2(\mathrm{dim}-1)` edges). To see that this
factorization is exactly cone translation, combine :math:`f = (A+R)^{-1}(\ar - \au)` with :eq:`eq:identity` to obtain
the force relation :math:`R f = \ar - \ac`, and consider the net interface force :math:`f - \delta`: the passive
attraction cancels :math:`A \delta` in :eq:`eq:identity` while the bias cancels :math:`R \delta` in the force
relation, so the pair :math:`(f - \delta, \ac)` satisfies exactly the unbiased equations, with the cone membership of
:math:`f` becoming membership of the translated cone for :math:`f - \delta`. Consequently the compression branch of
the net contact force is independent of adhesion — resting penetration is unaffected — while a tensile branch of depth
:math:`\delta` is added. Adhesive contacts remain active when separated within the :ref:`gap<body-geom-gap>` band and
the biased reference acceleration continues to pull the geoms together across this distance.
To summarize, the constraint behavior is determined by three per-constraint quantities: impedance :math:`0<d<1`, damping
:math:`b > 0`, and stiffness :math:`k \geq 0`. These are computed from the :at:`solimp` and :at:`solref` attributes as
described in the :ref:`solver parameters <soRefScaling>` section of the Modeling chapter, which also offers additional
+7
View File
@@ -34,6 +34,7 @@ typedef struct mjContact_ { // result of collision detection functions
mjtNum solref[mjNREF]; // constraint solver reference, normal direction
mjtNum solreffriction[mjNREF]; // constraint solver reference, friction directions
mjtNum solimp[mjNIMP]; // constraint solver impedance
mjtNum adhesion; // adhesive force along the contact normal
// internal storage used by solver
mjtNum mu; // friction of regularized cone, set by mj_makeConstraint
@@ -263,6 +264,7 @@ typedef struct mjData_ {
mjtNum* qfrc_damper; // passive damper force (nv x 1)
mjtNum* qfrc_gravcomp; // passive gravity compensation force (nv x 1)
mjtNum* qfrc_fluid; // passive fluid force (nv x 1)
mjtNum* qfrc_adhesion; // passive contact adhesion force (nv x 1)
mjtNum* qfrc_passive; // total passive force (nv x 1)
// computed by mj_sensorVel/mj_subtreeVel if needed
@@ -679,6 +681,7 @@ typedef struct mjModel_ {
mjtBool flg_gravcomp; // whether any body has nonzero gravcomp
mjtBool flg_surfacevel; // whether any geom has nonzero surfacevel
mjtBool flg_adhesion; // whether any geom or pair has nonzero adhesion
// ------------------------------- options and statistics
@@ -807,6 +810,7 @@ typedef struct mjModel_ {
mjtNum* geom_margin; // geometric inflation for contact (ngeom x 1)
mjtNum* geom_gap; // additional contact detection buffer (ngeom x 1)
mjtNum* geom_surfacevel; // surface velocity in local frame: lin,ang (ngeom x 6)
mjtNum* geom_adhesion; // adhesive force of contacts (ngeom x 1)
mjtNum* geom_fluid; // fluid interaction parameters (ngeom x mjNFLUID)
mjtNum* geom_user; // user data (ngeom x nuser_geom)
float* geom_rgba; // rgba when material is omitted (ngeom x 4)
@@ -1050,6 +1054,7 @@ typedef struct mjModel_ {
mjtNum* pair_solimp; // solver impedance: contact (npair x mjNIMP)
mjtNum* pair_margin; // geometric inflation for contact (npair x 1)
mjtNum* pair_gap; // additional contact detection buffer (npair x 1)
mjtNum* pair_adhesion; // adhesive force of contacts (npair x 1)
mjtNum* pair_friction; // tangent1, 2, spin, roll1, 2 (npair x 5)
// excluded body pairs for collision detection
@@ -1902,6 +1907,7 @@ typedef struct mjsGeom_ { // geom specification
double margin; // margin for contact detection
double gap; // additional contact detection buffer
double surfacevel[6]; // surface velocity in local frame: linear, angular
double adhesion; // adhesive force of contacts
// inertia inference
double mass; // used to compute density
@@ -2164,6 +2170,7 @@ typedef struct mjsPair_ { // pair specification
mjtNum solimp[mjNIMP]; // solver impedance
double margin; // margin for contact detection
double gap; // additional contact detection buffer
double adhesion; // adhesive force of contacts
double friction[5]; // full contact friction
mjString* info; // message appended to errors
} mjsPair;