Skip to content

Commit 36d0df9

Browse files
quaglacopybara-github
authored andcommitted
Allow self-collision for interpolated flexes.
Flexes with `dof="trilinear"` or `dof="quadratic"` could not set `selfcollide`. The vertices of interpolated flexes have no body, so the shared-body filter that skips adjacent elements of other flexes does not apply, and every pair of adjacent elements would collide at rest. - Remove the compiler error for interpolated flexes with self-collision. - Skip element pairs with vertices in the same or neighboring cells of the interpolation grid (`mjc_ElemSharedNode`) in all self-collision midphase paths (direct, SAP, BVH). Such elements share grid nodes and can only intersect if the grid inverts locally, so their contacts would only fight the elasticity. A vertex on an internal cell plane counts for the cells on both sides, so the rule is symmetric and robust to rounding. Self-collision thus requires at least 3 cells along an axis, and flexes with at most 2 cells along every axis skip it altogether. - BVH self- and flex-flex leaves are box-filtered before pairs are pushed, and marked active only when a pair is pushed. - `cellcount` now also sizes the pinned-node array of flexcomp types grid, box, cylinder and ellipsoid, which could crash. - MJWarp raises `NotImplementedError` for interpolated flexes with self-collision; the `ipc` mode does not handle interpolated flexes. Self-collision is opt-in for existing models (interpolated flexes could only compile with `selfcollide="none"`), but `auto` is the default for new ones. On a 560-triangle box surface with `cellcount="2 2 3"`, it adds about 0.5 ms per step at rest (SAP; 0.8 ms with BVH), and pressed flat by a plate collision takes 5.9 ms per step instead of 0.8 ms, mostly in the narrowphase. PiperOrigin-RevId: 1003063514 Change-Id: I25d9f408639ae8c22576f2cee01020963038523a
1 parent 39b7193 commit 36d0df9

9 files changed

Lines changed: 505 additions & 36 deletions

File tree

‎doc/XMLreference.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4801,6 +4801,10 @@ extensions specific to flexes.
48014801
**sap** in 1D and 2D, and **bvh** in 3D. Which strategy performs better depends on the specifics of the model. The
48024802
automatic setting is just a simple rule which we have found to perform well in general.
48034803

4804+
For interpolated flexes (**trilinear** or **quadratic** :ref:`dofs<body-flexcomp-dof>`), elements in the same or
4805+
neighboring :ref:`cells<body-flexcomp-cellcount>` do not self-collide, so self-collision requires at least 3 cells
4806+
along an axis.
4807+
48044808
.. _flex-contact-activelayers:
48054809

48064810
:at:`activelayers`: :at-val:`int(1), "1"`

‎doc/changelog.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@ Engine
2424
Models solved with PGS, noslip or islands disabled can stop at slightly different iterations. The
2525
:ref:`meaninertia<statistic-meaninertia>` attribute is deprecated and will be removed in a future release (compiling
2626
a model that sets it now gives a warning; remove the attribute to migrate).
27+
- Interpolated flexes (**trilinear** or **quadratic** :ref:`dofs<body-flexcomp-dof>`) now support
28+
:ref:`self-collision<flex-contact-selfcollide>`, excluding elements in the same or neighboring cells.
2729

2830
Compiler
2931
^^^^^^^^

‎mjx/mujoco/mjx/third_party/mujoco_warp/_src/io.py‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -505,6 +505,9 @@ def _check_friction(name: str, id_: int, condim: int, friction, checks):
505505
raise NotImplementedError("Flex internal collisions are not implemented.")
506506
if (mjm.flex_rigid != 0).any():
507507
raise NotImplementedError("Rigid flexes are not implemented.")
508+
# TODO(team): skip element pairs driven by a common grid node, as in mjc_ElemSharedNode
509+
if np.any((mjm.flex_interp != 0) & (mjm.flex_selfcollide != 0) & ((mjm.flex_contype & mjm.flex_conaffinity) != 0)):
510+
raise NotImplementedError("Self-collision of interpolated flexes (dof=trilinear or quadratic) is not implemented.")
508511
m.nmaxcondim = np.concatenate(condim_arrays).max()
509512
m.nmaxpyramid = np.maximum(1, 2 * (m.nmaxcondim - 1))
510513
m.has_sdf_geom = (mjm.geom_type == mujoco.mjtGeom.mjGEOM_SDF).any()

‎src/engine/engine_collision_driver.c‎

Lines changed: 37 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -544,6 +544,17 @@ static int pushElemElem(mjData* d, int f1, int e1, int f2, int e2, int group, in
544544
}
545545

546546

547+
// push an elem-elem self-collision pair of flex f onto the arena, unless it is excluded
548+
static int pushSelfElemElem(const mjModel* m, mjData* d, int f, int e1, int e2, int group,
549+
int npair) {
550+
// interpolated flex: skip elements driven by a common grid node
551+
if (mjc_ElemSharedNode(m, f, e1, e2)) {
552+
return npair;
553+
}
554+
return pushElemElem(d, f, e1, f, e2, group, npair);
555+
}
556+
557+
547558
// filter candidate collision pair; return 0 if pair should be discarded
548559
static int filterCollisionPair(const mjModel* m, mjData* d, int g1, int g2, int ipair,
549560
int merged, int startadr, int pairadr) {
@@ -811,8 +822,12 @@ void mj_collision(const mjModel* m, mjData* d) {
811822
// under the ipc flag the IPC step resolves a dim-2 flex's self-contact itself
812823
if (mjc_ipcOwnsFlexFlex(m, f, f)) continue;
813824

814-
// active element collisions
815-
if (m->flex_selfcollide[f] != mjFLEXSELF_NONE) {
825+
// active element collisions; for an interpolated flex with at most 2 cells along every
826+
// axis, all cells are neighbors, so every pair is excluded by mjc_ElemSharedNode
827+
const int* cellnum = m->flex_cellnum + 3*f;
828+
int all_excluded = m->flex_interp[f] && cellnum[0] <= 2 && cellnum[1] <= 2 &&
829+
cellnum[2] <= 2;
830+
if (m->flex_selfcollide[f] != mjFLEXSELF_NONE && !all_excluded) {
816831
int group = flexReducible(m, f) ? ngroup++ : -1;
817832

818833
// element-element: midphase
@@ -836,7 +851,7 @@ void mj_collision(const mjModel* m, mjData* d) {
836851
if (mj_isElemActive(m, f, e1)) {
837852
for (int e2=e1+1; e2 < flex_elemnum; e2++) {
838853
if (mj_isElemActive(m, f, e2)) {
839-
ncandidate = pushElemElem(d, f, e1, f, e2, group, ncandidate);
854+
ncandidate = pushSelfElemElem(m, d, f, e1, e2, group, ncandidate);
840855
}
841856
}
842857
}
@@ -1130,15 +1145,26 @@ static int mj_collideTree(const mjModel* m, mjData* d, int bf1, int bf2,
11301145

11311146
// flex : flex
11321147
else {
1133-
// both are leaves
1134-
// box filter applied in mjc_ElemElem, bitmask filter applied earlier
1148+
// both are leaves: same box filter as mjc_ElemElem (margin and gap ignored in
1149+
// self-collisions), bitmask filter applied earlier
11351150
if (isleaf1 && isleaf2) {
1136-
if (bf1 != bf2 || nodeid1 != nodeid2) {
1137-
npair = pushElemElem(d, f1, nodeid1, f2, nodeid2, group, npair);
1151+
mjtNum margin = 0;
1152+
if (bf1 != bf2) {
1153+
margin = mj_assignMargin(m, m->flex_margin[f1] + m->flex_margin[f2]) +
1154+
m->flex_gap[f1] + m->flex_gap[f2];
11381155
}
1139-
if (mark_active) {
1140-
d->bvh_active[node1 + bvhadr1] = true;
1141-
d->bvh_active[node2 + bvhadr2] = true;
1156+
if (!filterBox(d->flexelem_aabb + 6*(m->flex_elemadr[f1] + nodeid1),
1157+
d->flexelem_aabb + 6*(m->flex_elemadr[f2] + nodeid2), margin)) {
1158+
int npair_old = npair;
1159+
if (bf1 != bf2) {
1160+
npair = pushElemElem(d, f1, nodeid1, f2, nodeid2, group, npair);
1161+
} else if (nodeid1 != nodeid2) {
1162+
npair = pushSelfElemElem(m, d, f1, nodeid1, nodeid2, group, npair);
1163+
}
1164+
if (mark_active && npair > npair_old) {
1165+
d->bvh_active[node1 + bvhadr1] = true;
1166+
d->bvh_active[node2 + bvhadr2] = true;
1167+
}
11421168
}
11431169
continue;
11441170
}
@@ -2324,7 +2350,7 @@ static int mj_collideFlexSAPPairs(const mjModel* m, mjData* d, int f, int group,
23242350
for (int i=0; i < nsappair; i++) {
23252351
int e1 = elid[sappair[i].hi];
23262352
int e2 = elid[sappair[i].lo];
2327-
npair = pushElemElem(d, f, e1, f, e2, group, npair);
2353+
npair = pushSelfElemElem(m, d, f, e1, e2, group, npair);
23282354
}
23292355

23302356
mj_freeStack(d);

‎src/engine/engine_collision_flex.c‎

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -386,6 +386,61 @@ int mjc_GeomElem(const mjModel* m, mjData* d, mjPreContact* con, int g, int f, i
386386
}
387387

388388

389+
// range [lo, hi] of the interpolation grid cells containing vertex v of flex f, per axis: a vertex
390+
// on an internal cell plane belongs to the cells on both sides, so that the result does not
391+
// depend on the side the vertex is rounded to; vertices within eps of a plane are treated as on
392+
// the plane, where eps is relative to the cell size and covers the rounding of flex_vert0 in
393+
// [0, 1] in single precision
394+
static void vertCellRange(const mjModel* m, int f, int v, int lo[3], int hi[3]) {
395+
const mjtNum* coord = m->flex_vert0 + 3*(m->flex_vertadr[f] + v);
396+
const int* cellnum = m->flex_cellnum + 3*f;
397+
for (int i = 0; i < 3; i++) {
398+
mjtNum u = coord[i] * cellnum[i];
399+
mjtNum eps = 1e-6 * cellnum[i];
400+
lo[i] = mjMAX(0, mjMIN(cellnum[i] - 1, (int)mju_floor(u - eps)));
401+
hi[i] = mjMAX(0, mjMIN(cellnum[i] - 1, (int)mju_floor(u + eps)));
402+
}
403+
}
404+
405+
406+
// return 1 if elements e1 and e2 of flex f have vertices in the same or in neighboring cells of
407+
// its interpolation grid, 0 for non-interpolated flexes. Such elements are driven by a common
408+
// grid node (neighboring cells share a face, edge or corner of nodes, for both trilinear and
409+
// quadratic interpolation) and can only intersect if the grid inverts locally, which is resisted
410+
// by elasticity; their vertices have no body for the shared-body filter
411+
int mjc_ElemSharedNode(const mjModel* m, int f, int e1, int e2) {
412+
if (!m->flex_interp[f]) {
413+
return 0;
414+
}
415+
416+
int dim = m->flex_dim[f];
417+
const int* edata1 = m->flex_elem + m->flex_elemdataadr[f] + e1*(dim + 1);
418+
const int* edata2 = m->flex_elem + m->flex_elemdataadr[f] + e2*(dim + 1);
419+
int lo1[4][3], hi1[4][3], lo2[4][3], hi2[4][3];
420+
for (int i = 0; i <= dim; i++) {
421+
vertCellRange(m, f, edata1[i], lo1[i], hi1[i]);
422+
vertCellRange(m, f, edata2[i], lo2[i], hi2[i]);
423+
}
424+
425+
// cell ranges within distance 1 along every axis
426+
for (int i1 = 0; i1 <= dim; i1++) {
427+
for (int i2 = 0; i2 <= dim; i2++) {
428+
int shared = 1;
429+
for (int k = 0; k < 3; k++) {
430+
if (lo2[i2][k] - hi1[i1][k] > 1 || lo1[i1][k] - hi2[i2][k] > 1) {
431+
shared = 0;
432+
break;
433+
}
434+
}
435+
if (shared) {
436+
return 1;
437+
}
438+
}
439+
}
440+
return 0;
441+
}
442+
443+
389444
// test two elems for collision, return number of contacts
390445
int mjc_ElemElem(const mjModel* m, mjData* d, mjPreContact* con, int f1, int e1,
391446
int f2, int e2, mjtNum margin) {

‎src/engine/engine_collision_flex.h‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,10 @@ int mjc_PlaneFlex(const mjModel* m, mjData* d, mjPreContact* con, int* vert, int
3131
int mjc_GeomElem(const mjModel* m, mjData* d, mjPreContact* con, int g, int f, int e,
3232
mjtNum margin);
3333

34+
// return 1 if elements e1 and e2 of flex f are driven by a common node of its
35+
// interpolation grid
36+
int mjc_ElemSharedNode(const mjModel* m, int f, int e1, int e2);
37+
3438
// test two elems for collision, return number of contacts
3539
int mjc_ElemElem(const mjModel* m, mjData* d, mjPreContact* con, int f1, int e1,
3640
int f2, int e2, mjtNum margin);

‎src/user/user_flexcomp.cc‎

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -309,14 +309,12 @@ bool mjCFlexcomp::Make(mjsBody* body, char* error, int error_sz, const mjVFS* vf
309309
int nnode = 0;
310310
if (doftype == mjFCOMPDOF_TRILINEAR || doftype == mjFCOMPDOF_QUADRATIC) {
311311
int order = doftype == mjFCOMPDOF_TRILINEAR ? 1 : 2;
312-
// multi-cell count for mesh/direct/gmsh, else single cell
312+
// cellcount applies to all types (see the nodal mesh below), else single cell
313313
int cx = 1, cy = 1, cz = 1;
314-
if (type == mjFCOMPTYPE_MESH || type == mjFCOMPTYPE_DIRECT || type == mjFCOMPTYPE_GMSH) {
315-
if (cellcount[0] >= 0) {
316-
cx = cellcount[0];
317-
cy = cellcount[1];
318-
cz = cellcount[2];
319-
}
314+
if (cellcount[0] >= 0) {
315+
cx = cellcount[0];
316+
cy = cellcount[1];
317+
cz = cellcount[2];
320318
}
321319
nnode = (cx * order + 1) * (cy * order + 1) * (cz * order + 1);
322320
}
@@ -2510,9 +2508,6 @@ void mjCFlex::Compile(const mjVFS* vfs) {
25102508
if (edgestiffness > 0 && dim > 1) {
25112509
throw mjCError(this, "edge stiffness only available for dim=1, please use elasticity plugins");
25122510
}
2513-
if (interpolated && selfcollide != mjFLEXSELF_NONE) {
2514-
throw mjCError(this, "trilinear interpolation cannot do self-collision");
2515-
}
25162511
nelem = (int)elem_.size() / (dim + 1);
25172512

25182513
// elastic2d checks

0 commit comments

Comments
 (0)