@@ -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).
13721371static 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 ];
0 commit comments