Add automatic free-joint alignment.

PiperOrigin-RevId: 671010083
Change-Id: Ib7d60f75c7f545b1886703715663db8de77feeef
This commit is contained in:
Yuval Tassa
2024-09-04 10:06:29 -07:00
committed by Copybara-Service
parent ae4987026c
commit 8954a088ce
18 changed files with 308 additions and 13 deletions
+9
View File
@@ -658,6 +658,15 @@ Type of limit specification.
.. mujoco-include:: mjtLimited
.. _mjtAlignFree:
mjtAlignFree
~~~~~~~~~~~~
Whether to align free joints with the inertial frame.
.. mujoco-include:: mjtAlignFree
.. _mjtInertiaFromGeom:
mjtInertiaFromGeom
+22
View File
@@ -822,6 +822,14 @@ has any effect. The settings here are global and apply to the entire model.
If this attribute is set to false, computes mesh inertia with the legacy algorithm, which is exact only for convex
meshes. If set to true, it is exact for any closed mesh geometry.
.. _compiler-alignfree:
:at:`alignfree`: :at-val:`[false, true], "false"`
This attribute toggles the default behaviour of an optimization that applies to bodies with a
:ref:`free joint<body-freejoint>` and no child bodies.
When true, the body frame and free joint will automatically be aligned with inertial frame, which leads to both
faster and more stable simulation. See :ref:`freejoint/align<body-freejoint-align>` for details.
.. _compiler-inertiagrouprange:
:at:`inertiagrouprange`: :at-val:`int(2), "0 5"`
@@ -2251,6 +2259,20 @@ inherited*. If the XML model is saved, it will appear as a regular joint of type
Integer group to which the joint belongs. This attribute can be used for custom tags. It is also used by the
visualizer to enable and disable the rendering of entire groups of joints.
.. _body-freejoint-align:
:at:`align`: :at-val:`[false, true, auto], "auto"`
When set to :at-val:`true`, the body frame and free joint will automatically be aligned with inertial frame. When set
to :at-val:`false`, no alignment will occur. When set to :at-val:`auto`, the compiler's
:ref:`alignfree<compiler-alignfree>` global attribute will be respected.
Inertial frame alignment is an optimization only applies to bodies with a free joint and no child bodies ("simple
free bodies"). The alignment diagonalizes the 6x6 inertia matrix and minimizes bias forces, leading to faster and
more stable simulation. While this behaviour is a strict improvement, it modifies the semantics of the free joint,
making ``qpos`` and ``qvel`` values saved in older versions (for example, in :ref:`keyframes<keyframe>`) invalid.
Note that the :at:`align` attribute is never saved to XML. Instead, the pose of simple free bodies and their children
will be modified such that the body frame and inertial frame are aligned.
.. _body-geom:
+3 -1
View File
@@ -56,6 +56,8 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`inertiafromgeom<compiler-inertiafromgeom>` | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`exactmeshinertia<compiler-exactmeshinertia>` | :ref:`assetdir<compiler-assetdir>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`alignfree<compiler-alignfree>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| compiler |br| |_| |L| | | .. table:: |
| :ref:`lengthrange | ? | :class: mjcf-attributes |
@@ -290,7 +292,7 @@
| :ref:`freejoint | \* | :class: mjcf-attributes |
| <body-freejoint>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`name<body-freejoint-name>` | :ref:`group<body-freejoint-group>` | | | |
| | | | :ref:`name<body-freejoint-name>` | :ref:`group<body-freejoint-group>` | :ref:`align<body-freejoint-align>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| body |br| |_| |L| | | .. table:: |
+16 -3
View File
@@ -18,13 +18,26 @@ General
- The functions ``mjs_findMesh`` and ``mjs_findKeyframe`` were replaced by ``mjs_findElement``, which allows to look
for any object type.
- Added the :ref:`nativeccd<option-flag-nativeccd>` flag. When this flag is enabled, general convex collision
detection is handled natively, as opposed to using `libccd <https://github.com/danfis/libccd>`__. This feature is in
early stages of testing.
- Added a new way of defining :ref:`connect<equality-connect>` equality constraints, using two sites rather than bodies.
The new semantic is useful when the assumption that the constraint is satisfied in the base configuration does not
hold. In this case the sites will "snap together" at the beginning of the simulation. Additionally, changing the site
positions in ``mjModel.site_pos`` at runtime can be used to modify the constraint.
- Added the :ref:`nativeccd<option-flag-nativeccd>` flag. When this flag is enabled, general convex collision
detection is handled natively, as opposed to using `libccd <https://github.com/danfis/libccd>`__. This feature is in
early stages of testing.
- Introduced an optimization that applies to bodies with a :ref:`free joint<body-freejoint>` and no child bodies (i.e.
simple free-floating bodies): aligning the free joint (body frame) with the inertial frame. The alignment diagonalizes
the related 6x6 inertia sub-matrix, leading to faster simulation, and minimizes bias forces, leading to more
stable simulation of free bodies.
While this optimization is a strict improvement over unaligned free joints, it also changes the semantics of the
joint's degrees-of-freedom w.r.t to previous versions. Therefore, ``qpos`` and ``qvel`` values saved in older versions
(for example, in :ref:`keyframes<keyframe>`) will become invalid.
This feature can be toggled individually using the :ref:`freejoint/align<body-freejoint-align>` attribute or globally
using the compiler :ref:`alignfree<compiler-alignfree>` attribute. The latter attribute currently defaults to "false"
due to the potential breakage described above, but could be changed to "true" in a future release. Aligned free joints
are recommended for all new models.
- Added :ref:`mjSpec` option for creating a texture from a buffer.
- :ref:`shellinertia <body-geom-shellinertia>` is now supported by all geom types.
- When :ref:`attaching<meAttachment>` sub-models, :ref:`keyframes<keyframe>` will now be correctly merged into the
+7
View File
@@ -1650,6 +1650,11 @@ typedef enum mjtLimited_ { // type of limit specification
mjLIMITED_TRUE, // limited
mjLIMITED_AUTO, // limited inferred from presence of range
} mjtLimited;
typedef enum mjtAlignFree_ { // whether to align free joints with the inertial frame
mjALIGNFREE_FALSE = 0, // don't align
mjALIGNFREE_TRUE, // align
mjALIGNFREE_AUTO, // respect the global compiler flag
} mjtAlignFree;
typedef enum mjtInertiaFromGeom_ { // whether to infer body inertias from child geoms
mjINERTIAFROMGEOM_FALSE = 0, // do not use; inertial element required
mjINERTIAFROMGEOM_TRUE, // always use; overwrite inertial element
@@ -1688,6 +1693,7 @@ typedef struct mjSpec_ { // model specification
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia
mjtByte exactmeshinertia; // if false, use old formula
int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation
// engine data
@@ -1778,6 +1784,7 @@ typedef struct mjsJoint_ { // joint specification
double pos[3]; // anchor position
double axis[3]; // joint axis
double ref; // value at reference configuration: qpos0
int align; // align free joint with body com (mjtAlignFree)
// stiffness
double stiffness; // stiffness coefficient
+8
View File
@@ -84,6 +84,12 @@ typedef enum mjtLimited_ { // type of limit specification
mjLIMITED_AUTO, // limited inferred from presence of range
} mjtLimited;
typedef enum mjtAlignFree_ { // whether to align free joints with the inertial frame
mjALIGNFREE_FALSE = 0, // don't align
mjALIGNFREE_TRUE, // align
mjALIGNFREE_AUTO, // respect the global compiler flag
} mjtAlignFree;
typedef enum mjtInertiaFromGeom_ { // whether to infer body inertias from child geoms
mjINERTIAFROMGEOM_FALSE = 0, // do not use; inertial element required
@@ -131,6 +137,7 @@ typedef struct mjSpec_ { // model specification
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia
mjtByte exactmeshinertia; // if false, use old formula
int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation
// engine data
@@ -231,6 +238,7 @@ typedef struct mjsJoint_ { // joint specification
double pos[3]; // anchor position
double axis[3]; // joint axis
double ref; // value at reference configuration: qpos0
int align; // align free joint with body com (mjtAlignFree)
// stiffness
double stiffness; // stiffness coefficient
+10
View File
@@ -759,6 +759,16 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjLIMITED_AUTO', 2),
]),
)),
('mjtAlignFree',
EnumDecl(
name='mjtAlignFree',
declname='enum mjtAlignFree_',
values=dict([
('mjALIGNFREE_FALSE', 0),
('mjALIGNFREE_TRUE', 1),
('mjALIGNFREE_AUTO', 2),
]),
)),
('mjtInertiaFromGeom',
EnumDecl(
name='mjtInertiaFromGeom',
+10
View File
@@ -8361,6 +8361,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='mjtByte'),
doc='if false, use old formula',
),
StructFieldDecl(
name='alignfree',
type=ValueType(name='int'),
doc='align free joints with inertial frame',
),
StructFieldDecl(
name='LRopt',
type=ValueType(name='mjLROpt'),
@@ -8792,6 +8797,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
type=ValueType(name='double'),
doc='value at reference configuration: qpos0',
),
StructFieldDecl(
name='align',
type=ValueType(name='int'),
doc='align free joint with body com (mjtAlignFree)',
),
StructFieldDecl(
name='stiffness',
type=ValueType(name='double'),
+5 -5
View File
@@ -24,7 +24,7 @@
<body name="anchor2" pos="-1 0 0"/>
<body name="beam2" pos="-1 0 0">
<freejoint/>
<freejoint align="false"/>
<geom class="box"/>
</body>
@@ -44,22 +44,22 @@
</body>
<body name="box_condim1" pos="4 0 0">
<freejoint/>
<freejoint align="false"/>
<geom class="box" condim="1"/>
</body>
<body name="box_condim3" pos="5 0 0">
<freejoint/>
<freejoint align="false"/>
<geom class="box" condim="3"/>
</body>
<body name="box_condim4" pos="6 0 0">
<freejoint/>
<freejoint align="false"/>
<geom class="box" condim="4"/>
</body>
<body name="box_condim6" pos="7 0 0">
<freejoint/>
<freejoint align="false"/>
<geom class="box" condim="6"/>
</body>
</worldbody>
+1
View File
@@ -104,6 +104,7 @@ void mjs_defaultJoint(mjsJoint* joint) {
joint->axis[2] = 1;
joint->limited = mjLIMITED_AUTO;
joint->actfrclimited = mjLIMITED_AUTO;
joint->align = mjALIGNFREE_AUTO;
mj_defaultSolRefImp(joint->solref_limit, joint->solimp_limit);
mj_defaultSolRefImp(joint->solref_friction, joint->solimp_friction);
}
+49
View File
@@ -1579,6 +1579,33 @@ void mjCBody::Compile(void) {
margin = std::max(margin, geoms[i]->margin);
}
// check conditions for free-joint alignment
bool align_free = (joints.size() == 1 && // only one joint AND
joints[0]->spec.type == mjJNT_FREE && // it is a free joint AND
bodies.empty() && // no child bodies AND
(joints[0]->spec.align == 1 || // either joint.align="true"
(joints[0]->spec.align == 2 && // or joint.align="auto"
model->alignfree))); // and compiler.align="true"
// free-joint alignment, phase 1 (this body + child geoms)
double ipos_inverse[3], iquat_inverse[4];
if (align_free) {
// accumulate iframe transformation to body frame
mjuu_frameaccum(pos, quat, ipos, iquat);
// compute inverse iframe transformation
mjuu_frameinvert(ipos_inverse, iquat_inverse, ipos, iquat);
// save iframe, set it to null
mjuu_setvec(ipos, 0, 0, 0);
mjuu_setvec(iquat, 1, 0, 0, 0);
// apply inverse iframe transformation to all child geoms
for (int i=0; i<geoms.size(); i++) {
mjuu_frameaccumChild(ipos_inverse, iquat_inverse, geoms[i]->pos, geoms[i]->quat);
}
}
// compute bounding volume hierarchy
ComputeBVH();
@@ -1652,6 +1679,28 @@ void mjCBody::Compile(void) {
}
}
}
// free joint alignment, phase 2 (transform sites, cameras and lights)
if (align_free) {
// frames have already been compiled and applied to children
// sites
for (int i=0; i<sites.size(); i++) {
mjuu_frameaccumChild(ipos_inverse, iquat_inverse, sites[i]->pos, sites[i]->quat);
}
// cameras
for (int i=0; i<cameras.size(); i++) {
mjuu_frameaccumChild(ipos_inverse, iquat_inverse, cameras[i]->pos, cameras[i]->quat);
}
// lights
for (int i=0; i<lights.size(); i++) {
double qunit[4]= {1, 0, 0, 0};
mjuu_frameaccumChild(ipos_inverse, iquat_inverse, lights[i]->pos, qunit);
mjuu_rotVecQuat(lights[i]->dir, lights[i]->dir, iquat_inverse);
}
}
}
+7 -3
View File
@@ -97,11 +97,11 @@ static void UpdateString(string& psuffix, int count, int i) {
const char* MJCF[nMJCF][mjXATTRNUM] = {
{"mujoco", "!", "1", "model"},
{"<"},
{"compiler", "*", "20", "autolimits", "boundmass", "boundinertia", "settotalmass",
{"compiler", "*", "21", "autolimits", "boundmass", "boundinertia", "settotalmass",
"balanceinertia", "strippath", "coordinate", "angle", "fitaabb", "eulerseq",
"meshdir", "texturedir", "discardvisual", "convexhull", "usethread",
"fusestatic", "inertiafromgeom", "inertiagrouprange", "exactmeshinertia",
"assetdir"},
"assetdir", "alignfree"},
{"<"},
{"lengthrange", "?", "10", "mode", "useexisting", "uselimit",
"accel", "maxforce", "timeconst", "timestep",
@@ -271,7 +271,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
"solreflimit", "solimplimit", "solreffriction", "solimpfriction",
"stiffness", "range", "actuatorfrcrange", "actuatorgravcomp", "margin", "ref",
"springref", "armature", "damping", "frictionloss", "user"},
{"freejoint", "*", "2", "name", "group"},
{"freejoint", "*", "3", "name", "group", "align"},
{"geom", "*", "33", "name", "class", "type", "contype", "conaffinity", "condim",
"group", "priority", "size", "material", "friction", "mass", "density",
"shellinertia", "solmix", "solref", "solimp",
@@ -1021,6 +1021,9 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) {
if (MapValue(section, "exactmeshinertia", &n, bool_map, 2)){
spec->exactmeshinertia = (n==1);
}
if (MapValue(section, "alignfree", &n, bool_map, 2)) {
spec->alignfree = (n==1);
}
// lengthrange subelement
XMLElement* elem = FindSubElem(section, "lengthrange");
@@ -3423,6 +3426,7 @@ void mjXReader::Body(XMLElement* section, mjsBody* body, mjsFrame* frame,
mjs_setString(joint->name, name.c_str());
}
ReadAttrInt(elem, "group", &joint->group);
MapValue(elem, "align", &joint->align, TFAuto_map, 3);
}
// geom sub-element
+3
View File
@@ -924,6 +924,9 @@ void mjXWriter::Compiler(XMLElement* root) {
if (model->boundinertia) {
WriteAttr(section, "boundinertia", 1, &model->boundinertia);
}
if (model->alignfree) {
WriteAttrTxt(section, "alignfree", "true");
}
if (!model->autolimits) {
WriteAttrTxt(section, "autolimits", "false");
}
+1 -1
View File
@@ -72,7 +72,7 @@ TEST_F(EllipsoidFluidTest, GeomsEquivalentToBodies) {
<option wind="5 5 0" density="10"/>
<worldbody>
<body pos="1 2 3">
<freejoint/>
<freejoint align="false"/>
<geom type="box" size=".1 .01 0.01" pos="0.1 0 0" euler="40 0 0" fluidshape="ellipsoid"/>
<geom type="box" size=".1 .01 0.01" pos="-.1 0 0" euler="0 20 0" fluidshape="ellipsoid"/>
</body>
+25
View File
@@ -0,0 +1,25 @@
<mujoco>
<!--
This model replicates the behaviour seen in this video:
https://www.youtube.com/watch?v=1n-HMSCDYtM
see also
https://en.wikipedia.org/wiki/Tennis_racket_theorem
-->
<option integrator="RK4">
<flag energy="enable" gravity="disable"/>
</option>
<worldbody>
<light pos="0 0 .3"/>
<geom type="plane" size=".2 .2 .01" pos="0 0 -.1"/>
<body>
<freejoint align="true"/>
<geom type="cylinder" rgba="0 .6 1 1" size=".01 .04" pos="-.00085 0 0"/>
<geom type="cylinder" rgba="0 .6 1 1" size=".004 .015" zaxis="1 0 0" pos=".01415 0 0"/>
</body>
</worldbody>
<keyframe>
<key qpos="0 0 0 1 0 0 1" qvel="0 0 0 0 20 .1"/>
</keyframe>
</mujoco>
+44
View File
@@ -0,0 +1,44 @@
<mujoco>
<compiler alignfree="true"/>
<worldbody>
<light pos="0 0 10"/>
<geom type="plane" pos="0 0 -1.5" size="5 5 .01"/>
<body name="body" pos="0 0 1">
<freejoint/>
<geom type="capsule" size=".1" fromto="0 0 0 1 0 0"/>
<geom name="geom1" type="capsule" size=".1" fromto="1 0 0 1 1 0"/>
<frame pos="1 1 0" euler="0 30 0">
<geom name="geom2" type="capsule" size=".1" fromto="0 0 0 0 0 1"/>
<site name="site" pos="-.2 0 1" type="box" size=".03 .06 .1" quat="1 2 3 4" rgba="0 1 0 1"/>
<light name="light" pos="0 0 1.1" dir="0 -1 -1"/>
<camera name="camera" pos="0 0 1.2" euler="10 20 30"/>
</frame>
</body>
<site name="anchor" pos="0 0 2" size=".1"/>
</worldbody>
<tendon>
<spatial springlength="0 1" stiffness="10000" width=".05">
<site site="site"/>
<site site="anchor"/>
</spatial>
</tendon>
<sensor>
<framepos objtype="body" objname="body"/>
<framexaxis objtype="body" objname="body"/>
<frameyaxis objtype="body" objname="body"/>
<framepos objtype="geom" objname="geom1"/>
<framexaxis objtype="geom" objname="geom1"/>
<frameyaxis objtype="geom" objname="geom1"/>
<framepos objtype="geom" objname="geom2"/>
<framexaxis objtype="geom" objname="geom2"/>
<frameyaxis objtype="geom" objname="geom2"/>
<framepos objtype="site" objname="site"/>
<framexaxis objtype="site" objname="site"/>
<frameyaxis objtype="site" objname="site"/>
<framepos objtype="camera" objname="camera"/>
<framexaxis objtype="camera" objname="camera"/>
<frameyaxis objtype="camera" objname="camera"/>
</sensor>
</mujoco>
+83
View File
@@ -39,10 +39,12 @@ std::vector<mjtNum> AsVector(const mjtNum* array, int n) {
}
using std::string;
using ::testing::DoubleNear;
using ::testing::ElementsAre;
using ::testing::HasSubstr;
using ::testing::IsNull;
using ::testing::NotNull;
using ::testing::Pointwise;
// -------------------- test OS filesystem fallback ----------------------------
@@ -1164,6 +1166,87 @@ TEST_F(MjCGeomTest, BadMeshZeroMassDensityDoesntError) {
mj_deleteModel(model);
}
// ------------- test joints --------------------------------------------------
using MjCJointTest = MujocoTest;
TEST_F(MjCJointTest, AlignFree) {
const std::string xml_path =
GetTestDataFilePath("user/testdata/freejoint.xml");
std::array<char, 1024> err;
mjSpec* s = mj_parseXML(xml_path.c_str(), nullptr, err.data(), err.size());
ASSERT_THAT(s, NotNull()) << err.data();
s->alignfree = 1; // auto-aligned free joint
mjModel* m = mj_compile(s, nullptr);
ASSERT_THAT(m, NotNull());
// with alignfree the body has sameframe and simple and all dof are simple
EXPECT_EQ(m->body_sameframe[1], 1);
EXPECT_EQ(m->body_simple[1], 1);
EXPECT_EQ(m->dof_simplenum[0], 6);
// make unaligned model
s->alignfree = 0; // unaligned free joint
mjModel* m_u = mj_compile(s, nullptr);
ASSERT_THAT(m_u, NotNull());
// no sameframe or simple
EXPECT_EQ(m_u->body_sameframe[1], 0);
EXPECT_EQ(m_u->body_simple[1], 0);
// make datas for both models
mjData* d = mj_makeData(m);
mjData* d_u = mj_makeData(m_u);
// call mj_forward
mj_forward(m, d);
mj_forward(m_u, d_u);
// expect x-frames (sensors) to match to very high precision
double eps = 1e-10;
EXPECT_THAT(AsVector(d->sensordata, m->nsensordata),
Pointwise(DoubleNear(eps), AsVector(d_u->sensordata, m->nsensordata)));
// no frame sensors for lights, test separately
EXPECT_THAT(AsVector(d->light_xpos, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xpos, 3)));
EXPECT_THAT(AsVector(d->light_xdir, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xdir, 3)));
// reduce timestep to 0.1ms and use RK4, simulate for 1 second
m->opt.timestep = m_u->opt.timestep = 1e-4;
m->opt.integrator = m_u->opt.integrator = mjINT_RK4;
while (d->time < 1) {
mj_step(m, d);
mj_step(m_u, d_u);
}
// expect qpos to be significantly different, since the semantics are changed
mj_markStack(d);
int nq = m->nq;
mjtNum* dqpos = mj_stackAllocNum(d, nq);
mju_sub(dqpos, d->qpos, d_u->qpos, nq);
EXPECT_GT(mju_norm(dqpos, nq), 1.0);
mj_freeStack(d);
// expect x-frames to match to reasonable precision
eps = 1e-5;
EXPECT_THAT(AsVector(d->sensordata, m->nsensordata),
Pointwise(DoubleNear(eps), AsVector(d_u->sensordata, m->nsensordata)));
EXPECT_THAT(AsVector(d->light_xpos, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xpos, 3)));
EXPECT_THAT(AsVector(d->light_xdir, 3),
Pointwise(DoubleNear(eps), AsVector(d_u->light_xdir, 3)));
mj_deleteData(d_u);
mj_deleteData(d);
mj_deleteModel(m_u);
mj_deleteModel(m);
mj_deleteSpec(s);
}
// ------------- test height fields --------------------------------------------
using MjCHFieldTest = MujocoTest;
+5
View File
@@ -470,6 +470,11 @@ public enum mjtLimited : int{
mjLIMITED_TRUE = 1,
mjLIMITED_AUTO = 2,
}
public enum mjtAlignFree : int{
mjALIGNFREE_FALSE = 0,
mjALIGNFREE_TRUE = 1,
mjALIGNFREE_AUTO = 2,
}
public enum mjtInertiaFromGeom : int{
mjINERTIAFROMGEOM_FALSE = 0,
mjINERTIAFROMGEOM_TRUE = 1,