Fix: Include ancestor DOFs in flex interpolation factorization.

The reduced dense factorization for flex interpolation now considers all DOFs in the kinematic chain of the body containing the flex, using mj_bodyChain, instead of only the DOFs directly associated with that body. This is necessary for correctly handling pinned flexes when their parent body is part of a larger kinematic structure.

PiperOrigin-RevId: 872854468
Change-Id: Idbe9fb459084dde9e8eb1076c70dbb685c1b0bdb
This commit is contained in:
Alessio Quaglino
2026-02-20 05:30:12 -08:00
committed by Copybara-Service
parent 8c9ba9bcf5
commit f9a39413cf
2 changed files with 111 additions and 7 deletions
+47 -7
View File
@@ -27,6 +27,7 @@
#include "engine/engine_core_constraint.h"
#include "engine/engine_core_smooth.h"
#include "engine/engine_derivative.h"
#include "engine/engine_core_util.h"
#include "engine/engine_inverse.h"
#include "engine/engine_island.h"
#include "engine/engine_macro.h"
@@ -1189,14 +1190,38 @@ void mj_implicitSkip(const mjModel* m, mjData* d, int skipfactor) {
// flex: reduced dense factorization
if (has_flex_interp && !sleep_filter) {
// temporary allocations for body chain
int* chain_dofs = mjSTACKALLOC(d, nv, int);
int* seen_dof = mjSTACKALLOC(d, nv, int);
mju_fillInt(seen_dof, 0, nv);
// identify flex DOFs
// For pinned nodes (body_dofnum==0): use bodyChain to include parent DOFs
// For regular flex nodes: use body_dofadr for one-way coupling
for (int f=0; f < m->nflex; f++) {
if (m->flex_interp[f]) {
int nodenum = m->flex_nodenum[f];
int nodeadr = m->flex_nodeadr[f];
for (int n=0; n < nodenum; n++) {
int b = m->flex_nodebodyid[nodeadr + n];
nflexdofs += m->body_dofnum[b];
int chain_nnz;
if (m->body_dofnum[b] == 0) {
// Pinned node: use bodyChain to get parent DOFs
chain_nnz = mj_bodyChain(m, b, chain_dofs);
} else {
// Regular flex node: use body's own DOFs only
chain_nnz = m->body_dofnum[b];
for (int j = 0; j < chain_nnz; j++) {
chain_dofs[j] = m->body_dofadr[b] + j;
}
}
for (int i=0; i < chain_nnz; i++) {
int dof = chain_dofs[i];
if (!seen_dof[dof]) {
seen_dof[dof] = 1;
nflexdofs++;
}
}
}
}
}
@@ -1207,19 +1232,34 @@ void mj_implicitSkip(const mjModel* m, mjData* d, int skipfactor) {
int* global2local = mjSTACKALLOC(d, nv, int);
mju_fillInt(global2local, -1, nv);
// collect unique DOFs in order
int cnt = 0;
mju_fillInt(seen_dof, 0, nv);
for (int f=0; f < m->nflex; f++) {
if (m->flex_interp[f]) {
int nodenum = m->flex_nodenum[f];
int nodeadr = m->flex_nodeadr[f];
for (int n=0; n < nodenum; n++) {
int b = m->flex_nodebodyid[nodeadr + n];
int dofnum = m->body_dofnum[b];
int dofadr = m->body_dofadr[b];
for (int j=0; j < dofnum; j++) {
flex_dof_indices[cnt] = dofadr + j;
global2local[dofadr + j] = cnt;
cnt++;
int chain_nnz;
if (m->body_dofnum[b] == 0) {
// Pinned node: use bodyChain to get parent DOFs
chain_nnz = mj_bodyChain(m, b, chain_dofs);
} else {
// Regular flex node: use body's own DOFs only
chain_nnz = m->body_dofnum[b];
for (int j = 0; j < chain_nnz; j++) {
chain_dofs[j] = m->body_dofadr[b] + j;
}
}
for (int i=0; i < chain_nnz; i++) {
int dof = chain_dofs[i];
if (!seen_dof[dof]) {
seen_dof[dof] = 1;
flex_dof_indices[cnt] = dof;
global2local[dof] = cnt;
cnt++;
}
}
}
}