Move mesh inertia specification from global <compiler> to <asset/mesh>.

Three options:
- Convex: Use convex hull
- Exact: Use exact geometry (requires watertight mesh)
- Legacy: Legacy approximation (deprecated)

Fixes #2152

PiperOrigin-RevId: 688949900
Change-Id: I4537f343db917195c6fd58d517b189d5ee3be1cd
This commit is contained in:
Alessio Quaglino
2024-10-23 07:05:32 -07:00
committed by Copybara-Service
parent 0c97dcf1fd
commit b598d79b3f
19 changed files with 170 additions and 87 deletions
+20 -6
View File
@@ -815,12 +815,6 @@ has any effect. The settings here are global and apply to the entire model.
to the mass. This results in equivalent inertia boxes which extend far beyond the geometric boundaries of the model.
Note that the built-in OpenGL visualizer can render equivalent inertia boxes.
.. _compiler-exactmeshinertia:
:at:`exactmeshinertia`: :at-val:`[false, true], "false"`
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"`
@@ -1249,6 +1243,24 @@ The full list of processing steps applied by the compiler to each mesh is as fol
This attribute specifies the scaling that will be applied to the vertex data along each coordinate axis. Negative
values are allowed, resulting in flipping the mesh along the corresponding axis.
.. _asset-mesh-inertia:
:at:`inertia`: :at-val:`[convex, exact, legacy], "legacy"`
This attribute controls how the mesh is used when mass and inertia are
:ref:`inferred from geometry<_compiler-inertiafromgeom>`. The current default value :at-val:`legacy` will be changed
to :at-val:`convex` in a future release.
:at-val:`convex`
Use the mesh's convex hull to compute volume and inertia.
:at-val:`exact`
Use an exact algorithm to compute volume and inertia. This algorithm requires a well-oriented, watertight mesh and
will error otherwise.
:at-val:`legacy`
Use the legacy algorithm, which is similar to :at-val:`convex`, but leads to volume overcounting for non-convex
meshes.
.. _asset-mesh-smoothnormal:
:at:`smoothnormal`: :at-val:`[false, true], "false"`
@@ -8016,6 +8028,8 @@ if omitted.
.. _default-mesh-maxhullvert:
.. _default-mesh-inertia:
:el-prefix:`default/` |-| **mesh** (?)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+3 -5
View File
@@ -54,9 +54,7 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`discardvisual<compiler-discardvisual>` | :ref:`convexhull<compiler-convexhull>` | :ref:`usethread<compiler-usethread>` | :ref:`fusestatic<compiler-fusestatic>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`inertiafromgeom<compiler-inertiafromgeom>` | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`exactmeshinertia<compiler-exactmeshinertia>` | :ref:`assetdir<compiler-assetdir>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`alignfree<compiler-alignfree>` | | | | |
| | | | :ref:`inertiafromgeom<compiler-inertiafromgeom>` | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`assetdir<compiler-assetdir>` | :ref:`alignfree<compiler-alignfree>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| compiler |br| |_| |L| | | .. table:: |
@@ -105,7 +103,7 @@
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`refpos<asset-mesh-refpos>` | :ref:`refquat<asset-mesh-refquat>` | :ref:`scale<asset-mesh-scale>` | :ref:`smoothnormal<asset-mesh-smoothnormal>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`maxhullvert<asset-mesh-maxhullvert>` | | | | |
| | | | :ref:`maxhullvert<asset-mesh-maxhullvert>` | :ref:`inertia<asset-mesh-inertia>` | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_2| mesh |br| |_2| |L| | | .. table:: |
@@ -1422,7 +1420,7 @@
| :ref:`mesh | ? | :class: mjcf-attributes |
| <default-mesh>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`scale<default-mesh-scale>` | :ref:`maxhullvert<default-mesh-maxhullvert>` | | | |
| | | | :ref:`scale<default-mesh-scale>` | :ref:`maxhullvert<default-mesh-maxhullvert>` | :ref:`inertia<default-mesh-inertia>` | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| default |br| |_| |L| | | .. table:: |
+6 -1
View File
@@ -5,6 +5,11 @@ Changelog
Upcoming version (not yet released)
-----------------------------------
General
^^^^^^^
- The global compiler flag ``exactmeshinertia`` has been removed and replaced with the mesh-specific
:ref:`inertia<asset-mesh-inertia>` attribute.
MJX
^^^
- Added ``apply_ft``, ``jac``, and ``xfrc_accumulate`` as public functions.
@@ -1076,7 +1081,7 @@ Bug fixes
12. Fixed bug in the handling of ellipsoid-based fluid model forces in the new implicitfast integrator.
#. Removed spurious whole-arena copying in `mj_copyData`, which can considerably
`slow down <https://github.com/google-deepmind/mujoco/issues/568>`__ the copying operation.
#. Make :ref:`shellinertia<body-geom-shellinertia>` ignore :ref:`exactmeshinertia<compiler-exactmeshinertia>`, which is
#. Make :ref:`shellinertia<body-geom-shellinertia>` ignore ``exactmeshinertia``, which is
only used for legacy volume computations (`#759 <https://github.com/google-deepmind/mujoco/issues/759>`__).
+7 -2
View File
@@ -1638,9 +1638,14 @@ struct mjrContext_ { // custom OpenGL context
};
typedef struct mjrContext_ mjrContext;
typedef enum mjtGeomInertia_ { // type of inertia inference
mjINERTIA_VOLUME, // mass distributed in the volume
mjINERTIA_VOLUME = 0, // mass distributed in the volume
mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia;
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjINERTIA_CONVEX = 0, // convex mesh inertia
mjINERTIA_EXACT, // exact mesh inertia
mjINERTIA_LEGACY, // legacy mesh inertia
} mjtMeshInertia;
typedef enum mjtBuiltin_ { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -1700,7 +1705,6 @@ typedef struct mjSpec_ { // model specification
mjtByte fusestatic; // fuse static bodies with parent
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
@@ -1989,6 +1993,7 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position
double refquat[4]; // reference orientation
double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data
+9 -2
View File
@@ -57,11 +57,18 @@ extern "C" {
//-------------------------------- enum types (mjt) ------------------------------------------------
typedef enum mjtGeomInertia_ { // type of inertia inference
mjINERTIA_VOLUME, // mass distributed in the volume
mjINERTIA_VOLUME = 0, // mass distributed in the volume
mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia;
typedef enum mjtMeshInertia_ { // type of mesh inertia
mjINERTIA_CONVEX = 0, // convex mesh inertia
mjINERTIA_EXACT, // exact mesh inertia
mjINERTIA_LEGACY, // legacy mesh inertia
} mjtMeshInertia;
typedef enum mjtBuiltin_ { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -136,7 +143,6 @@ typedef struct mjSpec_ { // model specification
mjtByte fusestatic; // fuse static bodies with parent
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
@@ -447,6 +453,7 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position
double refquat[4]; // reference orientation
double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data
+10
View File
@@ -727,6 +727,16 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjINERTIA_SHELL', 1),
]),
)),
('mjtMeshInertia',
EnumDecl(
name='mjtMeshInertia',
declname='enum mjtMeshInertia_',
values=dict([
('mjINERTIA_CONVEX', 0),
('mjINERTIA_EXACT', 1),
('mjINERTIA_LEGACY', 2),
]),
)),
('mjtBuiltin',
EnumDecl(
name='mjtBuiltin',
+5 -5
View File
@@ -8914,11 +8914,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='range of geom groups used to compute inertia',
),
StructFieldDecl(
name='exactmeshinertia',
type=ValueType(name='mjtByte'),
doc='if false, use old formula',
),
StructFieldDecl(
name='alignfree',
type=ValueType(name='int'),
@@ -10291,6 +10286,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
),
doc='rescale mesh',
),
StructFieldDecl(
name='inertia',
type=ValueType(name='mjtMeshInertia'),
doc='inertia type (convex, legacy, exact)',
),
StructFieldDecl(
name='smoothnormal',
type=ValueType(name='mjtByte'),
+1
View File
@@ -246,6 +246,7 @@ void mjs_defaultMesh(mjsMesh* mesh) {
mesh->refquat[0] = 1;
mesh->scale[0] = mesh->scale[1] = mesh->scale[2] = 1;
mesh->maxhullvert = -1;
mesh->inertia = mjINERTIA_LEGACY;
}
+22 -22
View File
@@ -1250,22 +1250,24 @@ void mjCMesh::LoadMSH(mjResource* resource) {
void mjCMesh::ComputeVolume(double CoM[3], mjtGeomInertia type,
const double facecen[3], bool exactmeshinertia) {
const double facecen[3]) {
double nrm[3];
double cen[3];
GetVolumeRef(type) = 0;
mjuu_zerovec(CoM, 3);
for (int i=0; i < nface(); i++) {
int nf = (inertia == mjINERTIA_CONVEX) ? graph_[1] : nface();
int* f = (inertia == mjINERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data();
float* vv = vert_.data();
for (int i=0; i < nf; i++) {
// get area, normal and center
float* vv = vert_.data();
double a = _triangle(nrm, cen, vv+3*face_[3*i], vv+3*face_[3*i+1], vv+3*face_[3*i+2]);
double a = _triangle(nrm, cen, vv+3*f[3*i], vv+3*f[3*i+1], vv+3*f[3*i+2]);
// compute and add volume
const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]};
double vol = type==mjINERTIA_SHELL ? a : mjuu_dot3(vec, nrm) * a / 3;
// if legacy computation requested, then always positive
if (!exactmeshinertia && type==mjINERTIA_VOLUME) {
if (inertia == mjINERTIA_LEGACY) {
vol = abs(vol);
}
@@ -1410,21 +1412,17 @@ void mjCMesh::Process() {
for ( const auto type : { mjtGeomInertia::mjINERTIA_VOLUME, mjtGeomInertia::mjINERTIA_SHELL } ) {
double CoM[3] = {0, 0, 0};
double inert[6] = {0, 0, 0, 0, 0, 0};
bool exactmeshinertia = model->exactmeshinertia;
// compute CoM and volume from pyramid volumes
ComputeVolume(CoM, type, facecen, model->exactmeshinertia);
ComputeVolume(CoM, type, facecen);
// perform computation with convex mesh if volume is negative
if (GetVolumeRef(type) <= 0 && exactmeshinertia) {
mju_warning("Malformed mesh '%s', computing mesh inertia from convex hull", name.c_str());
exactmeshinertia = false;
ComputeVolume(CoM, type, facecen, exactmeshinertia);
}
// if volume is still invalid, skip the rest of the computations
// if volume is invalid, skip the rest of the computations
if (GetVolumeRef(type) < mjMINVAL) {
validvolume_ = GetVolumeRef(type) < 0 ? -1 : 0;
if (type == mjINERTIA_SHELL) {
validarea_ = 0;
} else {
validvolume_ = GetVolumeRef(type) < 0 ? -1 : 0;
}
continue;
}
@@ -1447,17 +1445,19 @@ void mjCMesh::Process() {
const int k[6][2] = {{0, 0}, {1, 1}, {2, 2}, {0, 1}, {0, 2}, {1, 2}};
double P[6] = {0, 0, 0, 0, 0, 0};
GetVolumeRef(type) = 0;
for (int i=0; i < nface(); i++) {
float* D = vert_.data()+3*face_[3*i];
float* E = vert_.data()+3*face_[3*i+1];
float* F = vert_.data()+3*face_[3*i+2];
int nf = (inertia == mjINERTIA_CONVEX) ? graph_[1] : nface();
int* f = (inertia == mjINERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data();
for (int i=0; i < nf; i++) {
float* D = vert_.data()+3*f[3*i];
float* E = vert_.data()+3*f[3*i+1];
float* F = vert_.data()+3*f[3*i+2];
// get area, normal and center; update volume
double a = _triangle(nrm, cen, D, E, F);
double vol = type==mjINERTIA_SHELL ? a : mjuu_dot3(cen, nrm) * a / 3;
// if legacy computation requested, then always positive
if (!exactmeshinertia && type==mjINERTIA_VOLUME) {
if (inertia == mjINERTIA_LEGACY) {
vol = abs(vol);
}
@@ -1554,7 +1554,7 @@ void mjCMesh::CheckMesh(mjtGeomInertia type) {
if (!processed_) {
return;
}
if ((invalidorientation_.first>=0 || invalidorientation_.second>=0) && model->exactmeshinertia)
if ((invalidorientation_.first>=0 || invalidorientation_.second>=0) && inertia == mjINERTIA_EXACT)
throw mjCError(this,
"faces of mesh '%s' have inconsistent orientation. Please check the "
"faces containing the vertices %d and %d.",
+2 -2
View File
@@ -868,6 +868,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
const std::vector<float>& UserTexcoord() const { return spec_texcoord_; }
const std::vector<int>& Face() const { return face_; }
const std::vector<int>& UserFace() const { return spec_face_; }
mjtMeshInertia Inertia() const { return spec.inertia; }
// setters
void SetNeedHull(bool needhull) { needhull_ = needhull; }
@@ -945,8 +946,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
std::vector<unsigned char> num_face_vertices_;
// compute the volume and center-of-mass of the mesh given the face center
void ComputeVolume(double CoM[3], mjtGeomInertia type, const double facecen[3],
bool exactmeshinertia);
void ComputeVolume(double CoM[3], mjtGeomInertia gtype, const double facecen[3]);
};
+1
View File
@@ -69,6 +69,7 @@ extern const mjMap bias_map[];
extern const mjMap stage_map[];
extern const mjMap datatype_map[];
extern const mjMap meshtype_map[];
extern const mjMap meshinertia_map[];
extern const mjMap flexself_map[];
+16 -9
View File
@@ -97,11 +97,10 @@ static void UpdateString(string& psuffix, int count, int i) {
const char* MJCF[nMJCF][mjXATTRNUM] = {
{"mujoco", "!", "1", "model"},
{"<"},
{"compiler", "*", "21", "autolimits", "boundmass", "boundinertia", "settotalmass",
{"compiler", "*", "20", "autolimits", "boundmass", "boundinertia", "settotalmass",
"balanceinertia", "strippath", "coordinate", "angle", "fitaabb", "eulerseq",
"meshdir", "texturedir", "discardvisual", "convexhull", "usethread",
"fusestatic", "inertiafromgeom", "inertiagrouprange", "exactmeshinertia",
"assetdir", "alignfree"},
"fusestatic", "inertiafromgeom", "inertiagrouprange", "assetdir", "alignfree"},
{"<"},
{"lengthrange", "?", "10", "mode", "useexisting", "uselimit",
"accel", "maxforce", "timeconst", "timestep",
@@ -150,7 +149,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"default", "R", "1", "class"},
{"<"},
{"mesh", "?", "2", "scale", "maxhullvert"},
{"mesh", "?", "3", "scale", "maxhullvert", "inertia"},
{"material", "?", "10", "texture", "emission", "specular", "shininess",
"reflectance", "metallic", "roughness", "rgba", "texrepeat", "texuniform"},
{"joint", "?", "22", "type", "group", "pos", "axis", "springdamper",
@@ -226,9 +225,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"asset", "*", "0"},
{"<"},
{"mesh", "*", "13", "name", "class", "content_type", "file", "vertex", "normal",
{"mesh", "*", "14", "name", "class", "content_type", "file", "vertex", "normal",
"texcoord", "face", "refpos", "refquat", "scale", "smoothnormal",
"maxhullvert"},
"maxhullvert", "inertia"},
{"<"},
{"plugin", "*", "2", "plugin", "instance"},
{"<"},
@@ -790,6 +789,14 @@ const mjMap meshtype_map[2] = {
};
// mesh inertia type
const mjMap meshinertia_map[3] = {
{"convex", mjINERTIA_CONVEX},
{"legacy", mjINERTIA_LEGACY},
{"exact", mjINERTIA_EXACT}
};
// flexcomp type
const mjMap fcomp_map[mjNFCOMPTYPES] = {
{"grid", mjFCOMPTYPE_GRID},
@@ -1020,9 +1027,6 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) {
}
MapValue(section, "inertiafromgeom", &spec->inertiafromgeom, TFAuto_map, 3);
ReadAttr(section, "inertiagrouprange", 2, spec->inertiagrouprange, text);
if (MapValue(section, "exactmeshinertia", &n, bool_map, 2)){
spec->exactmeshinertia = (n==1);
}
if (MapValue(section, "alignfree", &n, bool_map, 2)) {
spec->alignfree = (n==1);
}
@@ -1424,6 +1428,9 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* mesh, const mjVFS* vfs) {
ReadAttr(elem, "refpos", 3, mesh->refpos, text);
ReadAttr(elem, "refquat", 4, mesh->refquat, text);
ReadAttr(elem, "scale", 3, mesh->scale, text);
if (MapValue(elem, "inertia", &n, meshinertia_map, 3)) {
mesh->inertia = (mjtMeshInertia)n;
}
XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) {
+4 -3
View File
@@ -212,6 +212,9 @@ void mjXWriter::OneMesh(XMLElement* elem, const mjCMesh* mesh, mjCDef* def) {
}
WriteAttrTxt(elem, "content_type", mesh->ContentType());
WriteAttrTxt(elem, "file", mesh->File());
if (mesh->Inertia() != def->Mesh().Inertia()) {
WriteAttrTxt(elem, "inertia", FindValue(meshinertia_map, 3, mesh->Inertia()));
}
// write vertex data
if (!mesh->UserVert().empty()) {
@@ -930,9 +933,7 @@ void mjXWriter::Compiler(XMLElement* root) {
if (!model->usethread) {
WriteAttrTxt(section, "usethread", "false");
}
if (model->exactmeshinertia) {
WriteAttrTxt(section, "exactmeshinertia", "true");
}
if (model->boundmass) {
WriteAttr(section, "boundmass", 1, &model->boundmass);
}
+2 -2
View File
@@ -1,8 +1,8 @@
<mujoco>
<compiler fitaabb="false" exactmeshinertia="true"/>
<compiler fitaabb="false"/>
<asset>
<mesh file="stanford_bunny.obj"/>
<mesh file="stanford_bunny.obj" inertia="exact"/>
</asset>
<worldbody>
+24
View File
@@ -0,0 +1,24 @@
<mujoco>
<asset>
<mesh name="incomplete_convex" inertia="convex"
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
face="2 0 3 0 1 3 1 2 3" />
<mesh name="incomplete"
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
face="2 0 3 0 1 3 1 2 3"/>
<mesh name="complete"
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
face="2 0 3 0 1 3 1 2 3 1 0 2"/>
</asset>
<worldbody>
<body>
<geom type="mesh" mesh="incomplete_convex"/>
</body>
<body>
<geom type="mesh" mesh="incomplete"/>
</body>
<body>
<geom type="mesh" mesh="complete"/>
</body>
</worldbody>
</mujoco>
+6 -4
View File
@@ -1,9 +1,11 @@
<mujoco>
<compiler exactmeshinertia="true"/>
<default>
<mesh scale="10 10 10" inertia="exact"/>
</default>
<asset>
<mesh file="cube_cup.obj" scale="10 10 10"/>
<mesh file="cube_cup_quad.obj" scale="10 10 10"/>
<mesh file="cube_cup_hi.obj" scale="10 10 10"/>
<mesh file="cube_cup.obj"/>
<mesh file="cube_cup_quad.obj"/>
<mesh file="cube_cup_hi.obj"/>
</asset>
<worldbody>
<light directional="true" diffuse=".6 .6 .6" specular="0.2 0.2 0.2" pos="0 0 4" dir="0 0 -1"/>
+1 -1
View File
@@ -1,7 +1,7 @@
<mujoco>
<asset>
<mesh file="cube.obj"/>
<mesh name="inline_cube"
<mesh name="inline_cube" inertia="convex"
vertex="
-.5 -.5 -.5
.5 -.5 -.5
+24 -21
View File
@@ -50,6 +50,8 @@ static const char* const kTorusDefaultMaxhullVertPath =
"user/testdata/torus_maxhullvert_default.xml";
static const char* const kTorusShellPath =
"user/testdata/torus_shell.xml";
static const char* const kCompareInertiaPath =
"user/testdata/inertia_compare.xml";
static const char* const kConvexInertiaPath =
"user/testdata/inertia_convex.xml";
static const char* const kConcaveInertiaPath =
@@ -506,21 +508,35 @@ TEST_F(MjCMeshTest, TinyInertiaFails) {
"mass and inertia of moving bodies must be larger than mjMINVAL"));
}
TEST_F(MjCMeshTest, FlippedFaceAllowedInexactInertia) {
TEST_F(MjCMeshTest, FlippedFaceAllowedLegacyInertia) {
const std::string xml_path = GetTestDataFilePath(kMalformedFaceOBJPath);
std::array<char, 1024> error;
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(model, testing::NotNull());
EXPECT_THAT(model, testing::NotNull()) << error.data();
EXPECT_THAT(model->nmeshface, 4);
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, MissingFaceAllowedConvexInertia) {
const std::string xml_path = GetTestDataFilePath(kCompareInertiaPath);
std::array<char, 1024> error;
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size());
EXPECT_THAT(model, testing::NotNull()) << error.data();
EXPECT_THAT(model->nmeshface, 10);
EXPECT_THAT(model->body_inertia[3], model->body_inertia[9]);
EXPECT_THAT(model->body_inertia[4], model->body_inertia[10]);
EXPECT_THAT(model->body_inertia[5], model->body_inertia[11]);
EXPECT_NE(model->body_inertia[3], model->body_inertia[6]);
EXPECT_NE(model->body_inertia[4], model->body_inertia[7]);
EXPECT_NE(model->body_inertia[5], model->body_inertia[8]);
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, FlippedFaceFailsExactInertia) {
static constexpr char xml[] = R"(
<mujoco>
<compiler exactmeshinertia="true"/>
<asset>
<mesh name="example_mesh"
<mesh name="example_mesh" inertia="exact"
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
face="2 0 3 0 1 3 1 2 3 0 1 2" />
</asset>
@@ -742,10 +758,9 @@ TEST_F(MjCMeshTest, VolumeSmallAllowedShell) {
mj_deleteModel(model);
}
TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) {
TEST_F(MjCMeshTest, VolumeNegativeThrowsError) {
static constexpr char xml[] = R"(
<mujoco>
<compiler exactmeshinertia="true"/>
<asset>
MESH_DEFINITIONS
</asset>
@@ -758,7 +773,7 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) {
)";
static constexpr char bad_mesh[] = R"(
<mesh name="bad_mesh%d"
<mesh name="bad_mesh%d" inertia="exact"
vertex="0 0 0 1 0 0 0 1 0 0 0 1"
face="3 0 2 0 3 1 1 3 2 0 1 2"/>\n"
)";
@@ -782,20 +797,8 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) {
std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml_str.c_str(),
error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data();
EXPECT_LE(mju_abs(model->geom_size[0]), 1);
EXPECT_LE(mju_abs(model->geom_size[1]), 1);
EXPECT_LE(mju_abs(model->geom_size[2]), 1);
EXPECT_THAT(error.data(), StartsWith("Malformed mesh 'bad_mesh1'"));
// first 7 warnings fit in the length-500 warning buffer
for (int i = 2; i < mjMIN(nmesh+1, 8); ++i) {
std::string msg = "Malformed mesh 'bad_mesh" + std::to_string(i);
EXPECT_THAT(error.data(), HasSubstr(msg));
}
mj_deleteModel(model);
EXPECT_THAT(model, IsNull());
EXPECT_THAT(error.data(), HasSubstr("mesh volume is negative"));
}
}
+7 -2
View File
@@ -451,8 +451,13 @@ public enum mjtFont : int{
mjFONT_BIG = 2,
}
public enum mjtGeomInertia : int{
mjINERTIA_VOLUME = 1,
mjINERTIA_SHELL = 2,
mjINERTIA_VOLUME = 0,
mjINERTIA_SHELL = 1,
}
public enum mjtMeshInertia : int{
mjINERTIA_CONVEX = 0,
mjINERTIA_EXACT = 1,
mjINERTIA_LEGACY = 2,
}
public enum mjtBuiltin : int{
mjBUILTIN_NONE = 0,