Skip to content

Commit 2742de4

Browse files
smallquailcopybara-github
authored andcommitted
GitHub Pull Request: #3617
-- a97aea7 by Alessio <small.quail@gmail.com>: Fix flex bending attachments and cached frame couplings COPYBARA_INTEGRATE_REVIEW=#3617 from smallquail:fix-flex-bending-frames a97aea7 PiperOrigin-RevId: 988168360 Change-Id: Ica3135946e5279e3d3848e35da38088f47b6d2e1
1 parent 3991e54 commit 2742de4

8 files changed

Lines changed: 438 additions & 425 deletions

File tree

‎doc/XMLreference.rst‎

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4544,6 +4544,10 @@ stress-strain relationship. See also :ref:`deformable <CDeformable>` objects and
45444544
"both": bending and stretching. Bending is not yet supported by :ref:`dof<body-flexcomp-dof>` **trilinear** and
45454545
**quadratic**.
45464546

4547+
For non-rigid, non-interpolated flexes with bending stiffness, each vertex must be attached to a fixed body or a body with
4548+
three slide joints along its local positive X, Y and Z axes, in that order. Jointless welded children of such bodies
4549+
are supported. The slide body's ancestors must be fixed; articulated and mocap attachments are not supported.
4550+
45474551
.. _flex-contact:
45484552

45494553
:el-prefix:`flex/` |-| **contact** |?|

‎doc/changelog.rst‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,9 @@ General
1212
- Assets in a :ref:`meshdir<compiler-meshdir>` or :ref:`texturedir<compiler-texturedir>` now resolve correctly in
1313
:ref:`.mjz <MJZArchives>` archives when rewritten or when the directory path is absolute, uses ``..``, or uses a URI
1414
scheme.
15+
- The cached flex bending factor now retains cross-coordinate couplings between differently oriented vertex bodies.
16+
- Unsupported attachments of flex vertices with bending stiffness now produce a compiler error, including vertices
17+
welded to ball joints, non-XYZ slide joints, or bodies with moving ancestors.
1518

1619
Version 3.14.0 (September 22, 2026)
1720
-----------------------------------

‎src/engine/engine_setconst.c‎

Lines changed: 10 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1365,9 +1365,8 @@ static void setSpring(mjModel* m, mjData* d) {
13651365
// reverse-Cholesky of M + (h^2 + h*damping)*K_bend over
13661366
// the dofs of unpinned vertices of standard dim-2 flexes with bending. The matrix is constant
13671367
// (flat-rest bending stiffness, point masses), so the factor is computed here once and reused
1368-
// by the implicit-flex constraint solve every step. Bending couples only same-coordinate dofs,
1369-
// so the pattern is three interleaved copies of the vertex flap adjacency. Row order (flex
1370-
// order, vertex order, coordinate fastest) and the resulting fill count must match the
1368+
// by the implicit-flex constraint solve every step. Bending couples full 3x3 blocks when vertex
1369+
// frames differ. Row order (body order, coordinate fastest) and the fill count must match the
13711370
// compiler's symbolic sizing (checked below).
13721371
static void setEfm0Factor(mjModel* m, mjData* d) {
13731372
int nbd = m->nefm0dof;
@@ -1415,10 +1414,9 @@ static void setEfm0Factor(mjModel* m, mjData* d) {
14151414
}
14161415

14171416
// assemble the bending-only stiffness K = (h^2 + h*damping)*K_bend over all dofs with the
1418-
// shared stencil walker from engine_derivative: bending values are configuration-independent
1419-
// and stretch/interp are gated off, so the call is valid at set-constants time (d is used
1420-
// for stack scratch only). As in the per-step metric, the h^2 and h*damping parts enter only
1421-
// when the spring and damper forces are enabled
1417+
// shared stencil walker from engine_derivative. The compiler requires fixed vertex frames,
1418+
// whose orientations were computed by setSpring. As in the per-step metric, the h^2 and
1419+
// h*damping parts enter only when the spring and damper forces are enabled.
14221420
mjtNum s1 = mjDISABLED(mjDSBL_SPRING) ? 0 : h*h;
14231421
mjtNum s2 = mjDISABLED(mjDSBL_DAMPER) ? 0 : h;
14241422
int nv = m->nv;
@@ -1439,16 +1437,16 @@ static void setEfm0Factor(mjModel* m, mjData* d) {
14391437
dofrow[m->efm0_dofid[r]] = r;
14401438
}
14411439

1442-
// compact B to covered rows, keeping same-coordinate entries only: bending blocks are
1443-
// isotropic (q * I3), so the off-coordinate entries of assemble's 3x3 block pattern are
1444-
// structurally zero and dropping them preserves the pattern the compiler sized.
1440+
// Compact K_bend to covered rows, retaining full off-diagonal blocks Q_ij * R_i^T * R_j.
1441+
// Only same-body blocks are diagonal, even when several vertices share a weld parent.
1442+
// Keep structural zeros between bodies so the pattern is independent of their orientations.
14451443
// H = M + (h^2+h*d)*K_bend in compact dof indices: lower CSR (values) + upper CSR (pattern)
14461444
int nHl = 0, nHu = 0;
14471445
for (int r=0; r < nbd; r++) {
14481446
int dof = m->efm0_dofid[r];
14491447
for (int c=0; c < K_rownnz[dof]; c++) {
14501448
int rc = dofrow[K_colind[K_rowadr[dof] + c]];
1451-
if (rc < 0 || (rc - r) % 3 != 0) continue; // uncovered or off-coordinate
1449+
if (rc < 0 || (rc != r && rc/3 == r/3)) continue;
14521450
if (rc < r) nHl++;
14531451
else if (rc > r) nHu++;
14541452
}
@@ -1472,7 +1470,7 @@ static void setEfm0Factor(mjModel* m, mjData* d) {
14721470
for (int c=0; c < K_rownnz[dof]; c++) {
14731471
int adr = K_rowadr[dof] + c;
14741472
int rc = dofrow[K_colind[adr]];
1475-
if (rc < 0 || (rc - r) % 3 != 0) continue;
1473+
if (rc < 0 || (rc != r && rc/3 == r/3)) continue;
14761474
if (rc < r) {
14771475
Hl_colind[ladr] = rc;
14781476
Hl_val[ladr++] = K_val[adr];

‎src/user/user_mesh.cc‎

Lines changed: 30 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4162,19 +4162,6 @@ void mjCFlex::ResolveReferences(const mjCModel* m) {
41624162
mjCBody* pbody = static_cast<mjCBody*>(m->FindObject(mjOBJ_BODY, vertbody));
41634163
if (pbody) {
41644164
vertbodyid.push_back(pbody->id);
4165-
// pinned vertices with bending are only valid for static (jointless) pin
4166-
// bodies: the runtime treats pin velocity as zero, which is only correct
4167-
// for static bodies.
4168-
if (!pbody->joints.empty() &&
4169-
pbody->joints.size() != 3 &&
4170-
dim == 2 &&
4171-
(elastic2d == 1 || elastic2d == 3) &&
4172-
!interpolated) {
4173-
throw mjCError(this,
4174-
"pinned flex vertices with bending require a static (jointless) "
4175-
"pin body, body '%s' has joints",
4176-
vertbody.c_str());
4177-
}
41784165
} else {
41794166
throw mjCError(this, "unknown body '%s' in flex", vertbody.c_str());
41804167
}
@@ -4824,6 +4811,36 @@ void mjCFlex::Compile(const mjVFS* vfs) {
48244811

48254812
// bending stiffness (2D only)
48264813
if (dim == 2 && (elastic2d == 1 || elastic2d == 3) && !interpolated) {
4814+
// The bending kernels use R * qvel and R^T * force on the weld parent's XYZ slides.
4815+
// Validate compiled axes (including joint frames), and exclude articulated or mocap
4816+
// ancestors: their motion is absent from that mapping and makes the cached factor vary.
4817+
if (!rigid) {
4818+
for (int bid : vertbodyid) {
4819+
const mjCBody* body = model->Bodies()[bid];
4820+
const mjCBody* weld = model->Bodies()[body->weldid];
4821+
bool supported = weld->joints.empty();
4822+
if (weld->joints.size() == 3) {
4823+
supported = true;
4824+
for (int j = 0; j < 3; j++) {
4825+
const mjCJoint* joint = weld->joints[j];
4826+
supported &= joint->type == mjJNT_SLIDE;
4827+
for (int k = 0; k < 3; k++) {
4828+
supported &= std::abs(joint->axis[k] - (j == k)) <= mjEPS;
4829+
}
4830+
}
4831+
}
4832+
for (const mjCBody* ancestor = weld; ancestor; ancestor = ancestor->parent) {
4833+
supported &= !ancestor->mocap;
4834+
if (ancestor != weld) { supported &= ancestor->joints.empty(); }
4835+
}
4836+
if (!supported) {
4837+
throw mjCError(this,
4838+
"flex bending requires a fixed body or three XYZ slide joints "
4839+
"with fixed ancestors; unsupported vertex body '%s'",
4840+
body->name.c_str());
4841+
}
4842+
}
4843+
}
48274844
bending.assign(nedge * 17, 0);
48284845

48294846
for (unsigned int e = 0; e < nedge; e++) {

‎src/user/user_model.cc‎

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2122,8 +2122,7 @@ void mjCModel::SetSizes() {
21222122
}
21232123

21242124
// bending factor sizes: symbolic reverse-Cholesky count on the (M + K_bend) pattern.
2125-
// Bending couples only same-coordinate dofs of unpinned flap vertices, so the pattern is
2126-
// three interleaved copies of the vertex flap adjacency across all qualifying flexes.
2125+
// Each flap couples full 3x3 blocks: its vertex bodies can have different orientations.
21272126
// The count must match the symbolic factorization performed in mj_setConst (asserted there).
21282127
std::vector<int> body_slot(bodies_.size(), -1);
21292128
for (int i = 0; i < nflex; i++) {
@@ -2165,13 +2164,16 @@ void mjCModel::SetSizes() {
21652164
}
21662165
}
21672166

2168-
// dof-level upper-triangle pattern: row 3*s+k has columns {3*t+k : t > s, t in adj(s)}
2167+
// Expand each off-diagonal vertex block to all coordinate pairs. Diagonal blocks are
2168+
// diagonal (R_b^T * R_b = I); any off-coordinate factor fill is counted symbolically.
21692169
int n = 3 * nfree;
21702170
std::vector<std::vector<int>> upper(n);
21712171
for (int s = 0; s < nfree; s++) {
21722172
for (int t : adj[s]) {
21732173
if (t > s) {
2174-
for (int k = 0; k < 3; k++) { upper[3 * s + k].push_back(3 * t + k); }
2174+
for (int k = 0; k < 3; k++) {
2175+
for (int l = 0; l < 3; l++) { upper[3 * s + k].push_back(3 * t + l); }
2176+
}
21752177
}
21762178
}
21772179
}

‎src/user/user_objects.h‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -704,6 +704,7 @@ class mjCJoint : public mjCJoint_, private mjsJoint {
704704
friend class mjCDef;
705705
friend class mjCEquality;
706706
friend class mjCBody;
707+
friend class mjCFlex;
707708
friend class mjCModel;
708709
friend class mjCSensor;
709710
friend class mjXWriter;

0 commit comments

Comments
 (0)
⌂ 换网址