Enable interior nodes for interpolated flex shell mode.

Previously shell mode required cellcount=1 along at least one axis. This CL
adds support for cellcount > 1 in all three axes by pinning interior grid nodes
to the parent body and reconstructing their positions from boundary nodes via
Transfinite Interpolation (TFI).

PiperOrigin-RevId: 924314800
Change-Id: I8c2438f4866dd4133feed65f535a1ab69f0c9188
This commit is contained in:
Alessio Quaglino
2026-05-31 10:35:16 -07:00
committed by Copybara-Service
parent 4548e81e4d
commit 91c92279d2
14 changed files with 681 additions and 39 deletions
+20 -2
View File
@@ -644,8 +644,26 @@ bool mjCFlexcomp::Make(mjsBody* body, char* error, int error_sz, const mjVFS* vf
int nz = flex->spec.cellcount[2] * flex->spec.order + 1;
int nnode = nx * ny * nz;
// mark empty cells and pin nodes exclusively in empty cells
MarkEmptyCells(flex, point.data(), npnt, minmax, nx, ny, nz);
// mark empty cells and pin nodes exclusively in empty cells (volume mode only)
if (!dflex->elastic2d) {
MarkEmptyCells(flex, point.data(), npnt, minmax, nx, ny, nz);
}
// shell mode: pin all interior (non-boundary) nodes
if (dflex->elastic2d) {
for (int gi = 0; gi < nx; gi++) {
for (int gj = 0; gj < ny; gj++) {
for (int gk = 0; gk < nz; gk++) {
bool is_boundary = (gi == 0 || gi == nx-1 ||
gj == 0 || gj == ny-1 ||
gk == 0 || gk == nz-1);
if (!is_boundary) {
pinned[gi*ny*nz + gj*nz + gk] = true;
}
}
}
}
}
// if MarkEmptyCells pinned any nodes, force centered=false
// so that pf->node (local positions) is saved to the model
+3 -7
View File
@@ -4698,11 +4698,7 @@ void mjCFlex::Compile(const mjVFS* vfs) {
if (spec.cellcount[0] == 0 || spec.cellcount[1] == 0 || spec.cellcount[2] == 0) {
throw mjCError(this, "cellcount cannot be 0 in any dimension when interpolation order > 0");
}
if (elastic2d && !(spec.cellcount[0] == 1 || spec.cellcount[1] == 1 || spec.cellcount[2] == 1)) {
throw mjCError(this,
"shell trilinear flex requires at least one dimension "
"with cell count equal to one (no interior nodes)");
}
int expected_nodes = (spec.cellcount[0] * spec.order + 1) *
(spec.cellcount[1] * spec.order + 1) *
(spec.cellcount[2] * spec.order + 1);
@@ -4944,9 +4940,9 @@ void mjCFlex::Compile(const mjVFS* vfs) {
// create shell fragments and element-vertex collision pairs
CreateShellPair();
// recompute cell_empty from vertex/element geometry
// recompute cell_empty from vertex/element geometry (volume mode only)
// (survives XML round-trips where flexcomp data is lost)
if (interpolated && cell_empty.empty()) {
if (interpolated && !elastic2d && cell_empty.empty()) {
int cx = spec.cellcount[0], cy = spec.cellcount[1], cz = spec.cellcount[2];
if (cx * cy * cz > 1) {
ComputeCellEmpty(vertxpos.data(), elem_.data(), nvert, nelem, dim);