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
+47 -29
View File
@@ -1724,10 +1724,10 @@ int mj_broadphase(const mjModel* m, mjData* d, int* bfpair, int maxpair) {
//----------------------------- narrow-phase collision detection -----------------------------------
// compute contact condim, solref, solimp, friction
// compute contact condim, solref, solimp, friction, adhesion
static void mj_contactParam(const mjModel* m, int* condim,
mjtNum* solref, mjtNum* solimp, mjtNum* friction,
int g1, int g2, int f1, int f2) {
mjtNum* adhesion, int g1, int g2, int f1, int f2) {
mjtNum fri[3];
// get parameters from geom1 or flex1
@@ -1737,6 +1737,7 @@ static void mj_contactParam(const mjModel* m, int* condim,
const mjtNum* solref1 = (f1 < 0) ? m->geom_solref+g1*mjNREF : m->flex_solref+f1*mjNREF;
const mjtNum* solimp1 = (f1 < 0) ? m->geom_solimp+g1*mjNIMP : m->flex_solimp+f1*mjNIMP;
const mjtNum* friction1 = (f1 < 0) ? m->geom_friction+g1*3 : m->flex_friction+f1*3;
mjtNum adhesion1 = (f1 < 0) ? m->geom_adhesion[g1] : 0;
// get parameters from geom2 or flex2
int priority2 = (f2 < 0) ? m->geom_priority[g2] : m->flex_priority[f2];
@@ -1745,6 +1746,10 @@ static void mj_contactParam(const mjModel* m, int* condim,
const mjtNum* solref2 = (f2 < 0) ? m->geom_solref+g2*mjNREF : m->flex_solref+f2*mjNREF;
const mjtNum* solimp2 = (f2 < 0) ? m->geom_solimp+g2*mjNIMP : m->flex_solimp+f2*mjNIMP;
const mjtNum* friction2 = (f2 < 0) ? m->geom_friction+g2*3 : m->flex_friction+f2*3;
mjtNum adhesion2 = (f2 < 0) ? m->geom_adhesion[g2] : 0;
// adhesion: each surface contributes its own attraction
*adhesion = adhesion1 + adhesion2;
// different priority: copy from item with higher priority
if (priority1 > priority2) {
@@ -1752,12 +1757,14 @@ static void mj_contactParam(const mjModel* m, int* condim,
mju_copy(solref, solref1, mjNREF);
mju_copy(solimp, solimp1, mjNIMP);
mju_copy(fri, friction1, 3);
*adhesion = adhesion1;
}
else if (priority1 < priority2) {
*condim = condim2;
mju_copy(solref, solref2, mjNREF);
mju_copy(solimp, solimp2, mjNIMP);
mju_copy(fri, friction2, 3);
*adhesion = adhesion2;
}
// same priority
@@ -1820,17 +1827,27 @@ static void mj_contactParam(const mjModel* m, int* condim,
static void mj_setContact(const mjModel* m, mjContact* con,
int condim, mjtNum includemargin,
const mjtNum* solref, const mjtNum* solreffriction,
const mjtNum* solimp, const mjtNum* friction) {
const mjtNum* solimp, const mjtNum* friction,
mjtNum adhesion) {
// set parameters
con->dim = condim;
con->includemargin = includemargin;
con->adhesion = adhesion;
mj_assignRef(m, con->solref, solref);
mj_assignRef(m, con->solreffriction, solreffriction);
mj_assignImp(m, con->solimp, solimp);
mj_assignFriction(m, con->friction, friction);
// exclude in gap
con->exclude = (con->dist >= includemargin);
// exclude in gap; adhesive contacts remain active in the gap, producing
// rows with positive violation whose reference acceleration pulls (tether)
con->exclude = (con->dist >= includemargin) && !adhesion;
// in-gap adhesive contacts have no surface contact and therefore no
// friction: reduce to normal-only rows (with tangential rows present, fast
// slip would recruit normal force and brake tangential motion at a distance)
if (adhesion && con->dist >= includemargin) {
con->dim = 1;
}
// complete frame
mju_makeFrame(con->frame);
@@ -2010,7 +2027,7 @@ static void mj_narrowphase(const mjModel* m, mjData* d, const mjcPair* buffer, i
continue;
int condim;
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP], adhesion;
mjtNum solreffriction[mjNREF] = {0};
int g1 = pairbuffer[i].geom_geom.g1;
int g2 = pairbuffer[i].geom_geom.g2;
@@ -2021,11 +2038,12 @@ static void mj_narrowphase(const mjModel* m, mjData* d, const mjcPair* buffer, i
mju_copy(solref, m->pair_solref+mjNREF*ipair, mjNREF);
mju_copy(solimp, m->pair_solimp+mjNIMP*ipair, mjNIMP);
mju_copy(friction, m->pair_friction+5*ipair, 5);
adhesion = m->pair_adhesion[ipair];
if (m->pair_solreffriction[mjNREF*ipair] || m->pair_solreffriction[mjNREF*ipair + 1]) {
mju_copy(solreffriction, m->pair_solreffriction+mjNREF*ipair, mjNREF);
}
} else {
mj_contactParam(m, &condim, solref, solimp, friction, g1, g2, -1, -1);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, g1, g2, -1, -1);
}
mjPreContact* bc = arg.conbuffer + pairbuffer[i].conpos;
@@ -2044,7 +2062,7 @@ static void mj_narrowphase(const mjModel* m, mjData* d, const mjcPair* buffer, i
c->elem[1] = -1;
c->vert[0] = -1;
c->vert[1] = -1;
mj_setContact(m, c, condim, margin, solref, solreffriction, solimp, friction);
mj_setContact(m, c, condim, margin, solref, solreffriction, solimp, friction, adhesion);
}
conpos += ncon;
}
@@ -2065,9 +2083,9 @@ static void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
int condim;
int flex_vertnum = m->flex_vertnum[f];
mjtNum gap = m->geom_gap[g] + m->flex_gap[f];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, g, -1, -1, f);
// collide all flex vertices with plane
for (int i=0; i < flex_vertnum; i++) {
@@ -2099,7 +2117,7 @@ static void mj_collidePlaneFlex(const mjModel* m, mjData* d, int g, int f) {
con.vert[1] = i;
// set remaining contact parameters
mj_setContact(m, &con, condim, margin, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, margin, solref, solreffriction, solimp, friction, adhesion);
// add to mjData, abort if too many contacts
if (mj_addContact(m, d, &con)) {
@@ -2120,9 +2138,9 @@ static void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
mjtNum margin = mj_assignMargin(m, m->geom_margin[g] + m->flex_margin[f]);
mjtNum gap = m->geom_gap[g] + m->flex_gap[f];
int condim;
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, g, -1, -1, f);
// allocate temporary contact array on stack (zero-initialized)
mj_markStack(d);
@@ -2153,7 +2171,7 @@ static void mj_collideSdfFlex(const mjModel* m, mjData* d, int g, int f) {
con.vert[1] = -1;
// set remaining contact parameters
mj_setContact(m, &con, condim, margin, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, margin, solref, solreffriction, solimp, friction, adhesion);
// add to mjData, abort if too many contacts
if (mj_addContact(m, d, &con)) {
@@ -2221,9 +2239,9 @@ static void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
int condim;
int flex_elemnum = m->flex_elemnum[f];
mjtNum radius = m->flex_radius[f];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5];
mjtNum solref[mjNREF], solimp[mjNIMP], friction[5], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f, f);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, -1, -1, f, f);
condim = 1;
// process all elements
@@ -2240,7 +2258,7 @@ static void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
mju_copy3(con.pos, precon.pos);
mju_copy3(con.frame, precon.normal);
mju_copy3(con.frame + 3, precon.tangent);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction, adhesion);
if (mj_addContact(m, d, &con)) return;
}
@@ -2251,7 +2269,7 @@ static void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
mju_copy3(con.pos, precon.pos);
mju_copy3(con.frame, precon.normal);
mju_copy3(con.frame + 3, precon.tangent);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction, adhesion);
if (mj_addContact(m, d, &con)) return;
}
@@ -2262,7 +2280,7 @@ static void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
mju_copy3(con.pos, precon.pos);
mju_copy3(con.frame, precon.normal);
mju_copy3(con.frame + 3, precon.tangent);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction, adhesion);
if (mj_addContact(m, d, &con)) return;
}
@@ -2273,7 +2291,7 @@ static void mj_collideFlexInternal(const mjModel* m, mjData* d, int f) {
mju_copy3(con.pos, precon.pos);
mju_copy3(con.frame, precon.normal);
mju_copy3(con.frame + 3, precon.tangent);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction);
mj_setContact(m, &con, condim, 0, solref, solreffriction, solimp, friction, adhesion);
if (mj_addContact(m, d, &con)) return;
}
}
@@ -2439,9 +2457,9 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
// get contact parameters
int condim;
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, g, -1, -1, f);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, g, -1, -1, f);
// allocate mjContact[num] on the arena
mjContact* con =
@@ -2475,7 +2493,7 @@ void mj_collideGeomElem(const mjModel* m, mjData* d, int g, int f, int e) {
}
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction, adhesion);
}
// add to ncon
@@ -2548,9 +2566,9 @@ void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2
// get contact parameters
int condim;
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f1, f2);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, -1, -1, f1, f2);
mjContact* con = (mjContact*) mj_arenaAllocByte(d, sizeof(mjContact) * num, _Alignof(mjContact));
if (!con) {
@@ -2577,7 +2595,7 @@ void mj_collideElems(const mjModel* m, mjData* d, int f1, int e1, int f2, int e2
mju_copy3(con[i].frame + 3, precon[i].tangent);
// set remaining contact parameters
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, margin, solref, solreffriction, solimp, friction, adhesion);
}
// add to ncon
@@ -2638,9 +2656,9 @@ void mj_collideElemVert(const mjModel* m, mjData* d, int f, int e, int v) {
// get contact parameters
int condim;
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP];
mjtNum friction[5], solref[mjNREF], solimp[mjNIMP], adhesion;
mjtNum solreffriction[mjNREF] = {0};
mj_contactParam(m, &condim, solref, solimp, friction, -1, -1, f, f);
mj_contactParam(m, &condim, solref, solimp, friction, &adhesion, -1, -1, f, f);
// allocate mjContact[num] on the arena
@@ -2671,7 +2689,7 @@ void mj_collideElemVert(const mjModel* m, mjData* d, int f, int e, int v) {
mju_copy3(con[i].frame + 3, precon[i].tangent);
// set remaining contact parameters
mj_setContact(m, con + i, condim, 0, solref, solreffriction, solimp, friction);
mj_setContact(m, con + i, condim, 0, solref, solreffriction, solimp, friction, adhesion);
}
// add to ncon