Skip to content

Commit 94483e1

Browse files
quaglacopybara-github
authored andcommitted
Add discrete elastic rod bending elasticity to 1D flexes.
1D flexes had no elasticity besides edge springs, so cables needed the cable plugin, whose chain of ball joints gives a dense mass matrix along the cable. With this change, a positive `young` on a 1D flex adds bending forces on the existing translational vertex dofs. The model is a twist-free isotropic discrete elastic rod (Bergou et al. 2008). Every vertex with exactly two edges stores `k |κb|^2`, where κb is the discrete curvature binormal and `k = EI / (l0 + l1)` with I the second moment of area of the cross-section. The reference configuration (qpos0) must be straight: the compiler rejects any hinge bent by more than 1e-4 rad. Without material frames, an isotropic rod with a curved reference would only resist the curvature magnitude, so a planar arc could coil into a helix at no cost, and the force would not vanish through straight. Curved initial poses (coiled, draped) are set through qpos or keyframes and start with stored bending energy. Rods with a curved rest shape, such as coil springs, still need the cable plugin; curved rest shapes without material frames are left for a follow-up. Twist is assumed to relax instantly, which is exact unless both rod ends have constrained orientations. Damping acts on the rate of |κb| only, so rigid rotations are not damped. Forces go through the generic flex scatter, so pinned and articulated attachments work, and 1D bending is compatible with edge equality constraints, and with mocap attachments when its damping is zero (mocap vertices have no velocity). Interpolated and rigid 1D flexes get no rod bending. Under the discrete integrator the bending is integrated implicitly with a PSD Gauss-Newton Hessian that is exact at rest; it is state dependent, so it rides the per-step stiffness path, not the constant 2D bending factor. Other integrators integrate it explicitly. The matrix-free operator applies J'(W(Jv)) per hinge; only the CSR assembly forms the 9x9 hinge stiffness. The cross-section is selected by new `elasticity` attributes `section="circle|box"` and `sectionsize` (box half-sizes, stored as `mjsFlex.section`, of the new enum type `mjtFlexSection`, and `mjsFlex.sectionsize`). A circle uses the flex radius. A box uses the weak-axis `I = min(w h^3, h w^3) / 12`, because the bending model is isotropic. Both half-sizes are kept so that a later anisotropic model with material frames can use them without an API change. Decoupling the section from the radius also lets the collision/visual radius stay small, since flexcomp requires the spacing to be larger than the radius. The schema-generated files, references.h, the introspection enums and structs, and the wasm bindings are regenerated; APItypes.rst documents mjtFlexSection. The connecting rod of `model/flex/mechanisms/slidercrank.xml` and the arm of `dipper.xml` are now rods (21 vertices each) instead of 12-cell trilinear 3D flexes, with the same mass and (weak-axis) cross-section and a flex radius of half the section height, so they look as thick; two point equalities per attachment replace the four-node clamps. The trilinear cells shear-locked, so `young` is raised 1.8x and 2.25x to keep the original deflections, and the Rayleigh damping coefficients are divided by the same factors to keep the physical damping. `cantilever.xml` keeps the 3D comparison and gets a fourth beam: a 1D rod with the same box cross-section and material as the soft 3D beam. Its edge equalities set their own solref/solimp, since the scene's `equality` default does not apply to flex edges. It settles at a tip deflection of 0.0746 m, versus 0.0752 m from Euler-Bernoulli and 0.069 m for the 3D quadratic beams. Tests cover compiled data (circle and box), zero force at rest, force vs energy gradient, damping, the Hessian against finite differences, CSR assembly vs operator, a cantilever that matches Euler-Bernoulli tip deflection within 0.5% for both sections, XML save/reload of the section attributes, interpolated 1D flexes (no rod bending), the mocap/damping rule, the curved-reference compile error, a straight reference posed curved through qpos (the bending force straightens it), rigid 1D flexes (no rod bending, no straightness check), and loading and simulating the three mechanism models. MJWarp implements 1D rod bending with the same model: passive forces and damping (`passive.py`), the hinge stiffness assembled into the effective-metric CSR and its matrix-free operator for non-simple attachments and the velocity shift (`derivative.py`, `passive.flex_rod_mul`), a per-hinge map built in `put_model` (new Model fields `nflexrodhinge`, `flex_rodhinge_map`, `has_flex_stiff_dim23`, with the MJX warp shims regenerated), and the C rule that rods do not require the discrete integrator (`forward.check_discrete`). `flex_test.py` compares forces, damping, disable flags, `efm_K_val`, `efm_c`, the `qacc_smooth` residual and 10-step trajectories (including slider-crank- and dipper-like rods and an articulated attachment) with MuJoCo C, for one and two worlds. Under IPC, an elastic 1D flex is now a covered non-2D flex, so the sparse 2D metric factor falls back to blocks, like a 3D solid. `IpcTest.SparseFactorCoverage` keeps its inelastic cable case and adds an elastic rod case for this. PiperOrigin-RevId: 1003076441 Change-Id: I689ad4cdcd8ad952e693027aeb0af2263183dba9
1 parent aab9efd commit 94483e1

47 files changed

Lines changed: 2040 additions & 93 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎doc/APIreference/APItypes.rst‎

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -973,6 +973,15 @@ Type of built-in procedural mesh.
973973

974974
.. mujoco-include:: mjtMeshBuiltin
975975

976+
.. _mjtFlexSection:
977+
978+
mjtFlexSection
979+
~~~~~~~~~~~~~~
980+
981+
Cross-section of 1D flex :ref:`elasticity<flex-elasticity>`, see :ref:`section<flex-elasticity-section>`.
982+
983+
.. mujoco-include:: mjtFlexSection
984+
976985
.. _mjtConflict:
977986

978987
mjtConflict

‎doc/XMLreference.rst‎

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4202,9 +4202,11 @@ element is used to adjust the properties of all edges in the flex.
42024202
.. _flexcomp-elasticity-damping:
42034203
.. _flexcomp-elasticity-thickness:
42044204
.. _flexcomp-elasticity-elastic2d:
4205+
.. _flexcomp-elasticity-section:
4206+
.. _flexcomp-elasticity-sectionsize:
42054207

42064208
.. |body/flexcomp/elasticity attrib list| replace::
4207-
:at:`young`, :at:`poisson`, :at:`damping`, :at:`thickness`, :at:`elastic2d`
4209+
:at:`young`, :at:`poisson`, :at:`damping`, :at:`thickness`, :at:`elastic2d`, :at:`section`, :at:`sectionsize`
42084210

42094211
|body/flexcomp/elasticity attrib list|
42104212
Same as in :ref:`flex/elasticity<flex-elasticity>`.
@@ -4743,6 +4745,22 @@ to large displacements (finite rotations) and small strains, since it uses a non
47434745
stress-strain relationship. See also :ref:`deformable <CDeformable>` objects and `this model
47444746
<https://github.com/google-deepmind/mujoco/blob/main/model/flex/floppy.xml>`__.
47454747

4748+
For 1D flexes, elasticity is a twist-free, isotropic `discrete elastic rod
4749+
<https://www.cs.columbia.edu/cg/pdfs/143-rods.pdf>`__ bending model with the cross-section given by
4750+
:ref:`section<flex-elasticity-section>`. Every vertex shared by exactly two edges stores the energy
4751+
:math:`k |\kappa b|^2`, where :math:`\kappa b` is the discrete curvature binormal and
4752+
:math:`k = E I / (\ell_0 + \ell_1)`, with :math:`I` the second moment of area of the cross-section and
4753+
:math:`\ell_{0,1}` the reference lengths of the two edges. The reference configuration ``qpos0`` must be straight: a
4754+
compiler error is raised if any such vertex is bent by more than :math:`10^{-4}` rad, since without material frames an
4755+
isotropic rod with a curved reference would only resist the magnitude of its curvature, not its direction. A curved
4756+
initial pose, e.g. coiled or draped, can be set with ``qpos`` or a :ref:`keyframe<keyframe>`; the rod then starts with
4757+
stored bending energy and is pushed toward straightening. Rods with a curved rest shape, such as coil springs, can use
4758+
the :ref:`cable<CComposite>` composite instead. Twist is assumed to relax instantly, which is exact unless the
4759+
orientation of both ends of the rod is constrained. Stretching is not included; use :ref:`edge<flex-edge>` constraints
4760+
or stiffness instead. Rod bending is integrated implicitly by the :ref:`discrete<option-integrator>` integrator, using a
4761+
positive semi-definite approximation of its Hessian, and explicitly by all other integrators. It is not supported by
4762+
:ref:`dof<body-flexcomp-dof>` **trilinear** and **quadratic**.
4763+
47464764
.. _flex-elasticity-young:
47474765

47484766
:at:`young`: :at-val:`real(1), "0"`
@@ -4781,7 +4799,22 @@ stress-strain relationship. See also :ref:`deformable <CDeformable>` objects and
47814799
With the :ref:`discrete<option-integrator>` integrator, use :ref:`solver<option-solver>` **CG** for general attachments.
47824800
Newton supports fixed attachments and independent slide bodies with local positive X, Y and Z joints in that order
47834801
and fixed ancestors. Mocap attachments are not supported for non-rigid flexes with elasticity because mocap poses do
4784-
not provide velocities for elastic damping.
4802+
not provide velocities for elastic damping, except for 1D flexes with zero :ref:`damping<flex-elasticity-damping>`.
4803+
4804+
.. _flex-elasticity-section:
4805+
4806+
:at:`section`: :at-val:`[circle, box], "circle"`
4807+
Cross-section of 1D flexes, used to compute the second moment of area :math:`I` of the bending stiffness. "circle":
4808+
:math:`I = \pi r^4 / 4`, where :math:`r` is the flex :ref:`radius <deformable-flex-radius>`. "box": rectangle with
4809+
half-sizes given by :ref:`sectionsize<flex-elasticity-sectionsize>`. Since the bending model is isotropic, a box uses
4810+
the second moment of area of its weak axis, :math:`I = \min(w h^3, h w^3) / 12`, where :math:`w, h` are the full
4811+
sizes. Collisions and rendering always use the flex radius.
4812+
4813+
.. _flex-elasticity-sectionsize:
4814+
4815+
:at:`sectionsize`: :at-val:`real(2), "0 0"`
4816+
Half-sizes of the "box" cross-section of 1D flexes, units of length. Both values must be positive when
4817+
:ref:`section<flex-elasticity-section>` is "box"; ignored otherwise.
47854818

47864819
.. _flex-contact:
47874820

‎doc/XMLschema.rst‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1580,6 +1580,12 @@
15801580
.. grid-item::
15811581
:ref:`elastic2d<flexcomp-elasticity-elastic2d>`
15821582

1583+
.. grid-item::
1584+
:ref:`section<flexcomp-elasticity-section>`
1585+
1586+
.. grid-item::
1587+
:ref:`sectionsize<flexcomp-elasticity-sectionsize>`
1588+
15831589

15841590
.. dropdown:: :ref:`contact<flexcomp-contact>` :octicon:`dot`
15851591

@@ -1802,6 +1808,12 @@
18021808
.. grid-item::
18031809
:ref:`elastic2d<flex-elasticity-elastic2d>`
18041810

1811+
.. grid-item::
1812+
:ref:`section<flex-elasticity-section>`
1813+
1814+
.. grid-item::
1815+
:ref:`sectionsize<flex-elasticity-sectionsize>`
1816+
18051817

18061818
.. dropdown:: :ref:`skin<deformable-skin>` |*|
18071819

‎doc/XMLunits.rst‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -574,6 +574,8 @@
574574
- :math:`T`
575575
* - :ref:`flexcomp/elasticity/thickness<flexcomp-elasticity-thickness>`
576576
- :math:`L`
577+
* - :ref:`flexcomp/elasticity/sectionsize<flexcomp-elasticity-sectionsize>`
578+
- :math:`L`
577579
* - :ref:`flexcomp/contact/friction<flexcomp-contact-friction>`
578580
- :math:`1,\ L,\ L`
579581
* - :ref:`flexcomp/contact/solmix<flexcomp-contact-solmix>`

‎doc/changelog.rst‎

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ Upcoming Version (not yet released)
77

88
Engine
99
^^^^^^
10+
- Added bending :ref:`elasticity<flex-elasticity>` for 1D flexes, using a twist-free isotropic discrete elastic rod
11+
model. The cross-section is selected with the new :ref:`section<flex-elasticity-section>` attribute: a circle of the
12+
flex radius, or a box with half-sizes :ref:`sectionsize<flex-elasticity-sectionsize>` that uses the stiffness of its
13+
weak axis. It is enabled by a positive :ref:`young<flex-elasticity-young>` modulus, requires a straight reference
14+
configuration (curved initial poses can be set with ``qpos`` or keyframes), is compatible with edge equality
15+
constraints, and is integrated implicitly by the :ref:`discrete<option-integrator>` integrator. Previously ``young``
16+
was ignored for 1D flexes: existing models that set it now bend, and fail to compile if the cross-section is
17+
degenerate (zero radius with the default circle section), if a vertex joins a zero-length edge, or if the rod is not
18+
straight.
1019
- Trees marked :ref:`sleep="init"<body-sleep>` which cannot be put to sleep now cause a warning rather than an error,
1120
and start awake. See :ref:`Sleeping<siSleepSleeping>` for details.
1221
- Added single-shot :ref:`multicontact<coMultiCCD>` for geoms with positive contact :ref:`margins<body-geom-margin>`.
@@ -92,6 +101,12 @@ Bug fixes
92101
- Fixed a bug in the Filament renderer where textures did not allocate mipmap levels, preventing mipmap generation and
93102
causing minification aliasing, and where sRGB textures were unconditionally excluded from mipmapping.
94103

104+
Models
105+
^^^^^^
106+
- The `elastic mechanism <https://github.com/google-deepmind/mujoco/tree/main/model/flex/mechanisms/>`__ example models
107+
now use 1D flex rods: the cantilever comparison of quadratic flexes adds a rod, and the elastic slider-crank and the
108+
tendon-loaded dipper arm are built from rods.
109+
95110
Version 3.16.0 (October 14, 2026)
96111
---------------------------------
97112

‎doc/includes/references.h‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1712,6 +1712,10 @@ typedef enum mjtMeshBuiltin { // type of built-in procedural mesh
17121712
mjMESH_BUILTIN_SQUARE, // square
17131713
mjMESH_BUILTIN_DISC // disc
17141714
} mjtMeshBuiltin;
1715+
typedef enum mjtFlexSection { // cross-section of 1D flex elasticity
1716+
mjFLEXSECTION_CIRCLE = 0, // circle of the flex radius
1717+
mjFLEXSECTION_BOX // box with half-sizes sectionsize
1718+
} mjtFlexSection;
17151719
typedef enum mjtBuiltin { // type of built-in procedural texture
17161720
mjBUILTIN_NONE = 0, // no built-in texture
17171721
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -2113,6 +2117,8 @@ typedef struct mjsFlex_ { // flex specification
21132117
int elastic2d; // 2D passive forces; 0: none, 1: bending, 2: stretching, 3: both
21142118
int elastic3d; // experimental 3D material (mjSpec only); 0: Saint
21152119
// Venant-Kirchhoff, 1: Stable Neo-Hookean
2120+
mjtFlexSection section; // 1D cross-section (mjtFlexSection)
2121+
double sectionsize[2]; // 1D box cross-section half-sizes
21162122
int cellcount[3]; // grid cell count for finite cell method
21172123
int order; // interpolation order (1: trilinear, 2: quadratic)
21182124

‎include/mujoco/mjspec.h‎

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -89,6 +89,12 @@ typedef enum mjtMeshBuiltin { // type of built-in procedural mesh
8989
} mjtMeshBuiltin;
9090

9191

92+
typedef enum mjtFlexSection { // cross-section of 1D flex elasticity
93+
mjFLEXSECTION_CIRCLE = 0, // circle of the flex radius
94+
mjFLEXSECTION_BOX // box with half-sizes sectionsize
95+
} mjtFlexSection;
96+
97+
9298
typedef enum mjtBuiltin { // type of built-in procedural texture
9399
mjBUILTIN_NONE = 0, // no built-in texture
94100
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -534,6 +540,8 @@ typedef struct mjsFlex_ { // flex specification
534540
int elastic2d; // 2D passive forces; 0: none, 1: bending, 2: stretching, 3: both
535541
int elastic3d; // experimental 3D material (mjSpec only); 0: Saint
536542
// Venant-Kirchhoff, 1: Stable Neo-Hookean
543+
mjtFlexSection section; // 1D cross-section (mjtFlexSection)
544+
double sectionsize[2]; // 1D box cross-section half-sizes
537545
int cellcount[3]; // grid cell count for finite cell method
538546
int order; // interpolation order (1: trilinear, 2: quadratic)
539547

‎include/mujoco/mjspecmacro.h‎

Lines changed: 40 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -277,44 +277,46 @@
277277

278278
#define MJSFLEX_FIELDS \
279279
X ( mjsElement*, element, 1 ) \
280-
X ( int, contype, 1 ) \
281-
X ( int, conaffinity, 1 ) \
282-
X ( int, condim, 1 ) \
283-
X ( int, priority, 1 ) \
284-
XVEC( double, friction, 3 ) \
285-
X ( double, solmix, 1 ) \
286-
XVEC( mjtNum, solref, mjNREF ) \
287-
XVEC( mjtNum, solimp, mjNIMP ) \
288-
X ( double, margin, 1 ) \
289-
X ( double, gap, 1 ) \
290-
X ( int, dim, 1 ) \
291-
X ( double, radius, 1 ) \
292-
XVEC( double, size, 3 ) \
293-
X ( mjtBool, flatskin, 1 ) \
294-
X ( mjtFlexSelf, selfcollide, 1 ) \
295-
X ( int, passive, 1 ) \
296-
X ( int, activelayers, 1 ) \
297-
X ( int, group, 1 ) \
298-
X ( double, edgestiffness, 1 ) \
299-
X ( double, edgedamping, 1 ) \
300-
XVEC( float, rgba, 4 ) \
301-
X ( mjString*, material, 1 ) \
302-
X ( double, young, 1 ) \
303-
X ( double, poisson, 1 ) \
304-
X ( double, damping, 1 ) \
305-
X ( double, thickness, 1 ) \
306-
X ( int, elastic2d, 1 ) \
307-
X ( int, elastic3d, 1 ) \
308-
XVEC( int, cellcount, 3 ) \
309-
X ( int, order, 1 ) \
310-
X ( mjStringVec*, nodebody, 1 ) \
311-
X ( mjStringVec*, vertbody, 1 ) \
312-
X ( mjDoubleVec*, node, 1 ) \
313-
X ( mjDoubleVec*, vert, 1 ) \
314-
X ( mjIntVec*, elem, 1 ) \
315-
X ( mjFloatVec*, texcoord, 1 ) \
316-
X ( mjIntVec*, elemtexcoord, 1 ) \
317-
X ( mjString*, info, 1 )
280+
X ( int, contype, 1 ) \
281+
X ( int, conaffinity, 1 ) \
282+
X ( int, condim, 1 ) \
283+
X ( int, priority, 1 ) \
284+
XVEC( double, friction, 3 ) \
285+
X ( double, solmix, 1 ) \
286+
XVEC( mjtNum, solref, mjNREF ) \
287+
XVEC( mjtNum, solimp, mjNIMP ) \
288+
X ( double, margin, 1 ) \
289+
X ( double, gap, 1 ) \
290+
X ( int, dim, 1 ) \
291+
X ( double, radius, 1 ) \
292+
XVEC( double, size, 3 ) \
293+
X ( mjtBool, flatskin, 1 ) \
294+
X ( mjtFlexSelf, selfcollide, 1 ) \
295+
X ( int, passive, 1 ) \
296+
X ( int, activelayers, 1 ) \
297+
X ( int, group, 1 ) \
298+
X ( double, edgestiffness, 1 ) \
299+
X ( double, edgedamping, 1 ) \
300+
XVEC( float, rgba, 4 ) \
301+
X ( mjString*, material, 1 ) \
302+
X ( double, young, 1 ) \
303+
X ( double, poisson, 1 ) \
304+
X ( double, damping, 1 ) \
305+
X ( double, thickness, 1 ) \
306+
X ( int, elastic2d, 1 ) \
307+
X ( int, elastic3d, 1 ) \
308+
X ( mjtFlexSection, section, 1 ) \
309+
XVEC( double, sectionsize, 2 ) \
310+
XVEC( int, cellcount, 3 ) \
311+
X ( int, order, 1 ) \
312+
X ( mjStringVec*, nodebody, 1 ) \
313+
X ( mjStringVec*, vertbody, 1 ) \
314+
X ( mjDoubleVec*, node, 1 ) \
315+
X ( mjDoubleVec*, vert, 1 ) \
316+
X ( mjIntVec*, elem, 1 ) \
317+
X ( mjFloatVec*, texcoord, 1 ) \
318+
X ( mjIntVec*, elemtexcoord, 1 ) \
319+
X ( mjString*, info, 1 )
318320

319321

320322
//-------------------------------- mjsMesh ---------------------------------------------------------

0 commit comments

Comments
 (0)
⌂ 换网址