From b598d79b3f65f7beb7373ceed6057d444106633f Mon Sep 17 00:00:00 2001 From: Alessio Quaglino Date: Wed, 23 Oct 2024 07:05:32 -0700 Subject: [PATCH] Move mesh inertia specification from global to . 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 --- doc/XMLreference.rst | 26 ++++++++++--- doc/XMLschema.rst | 8 ++-- doc/changelog.rst | 7 +++- doc/includes/references.h | 9 ++++- include/mujoco/mjspec.h | 11 +++++- introspect/enums.py | 10 +++++ introspect/structs.py | 10 ++--- src/user/user_init.c | 1 + src/user/user_mesh.cc | 44 +++++++++++----------- src/user/user_objects.h | 4 +- src/xml/xml_base.h | 1 + src/xml/xml_native_reader.cc | 25 ++++++++----- src/xml/xml_native_writer.cc | 7 ++-- test/user/testdata/fitmesh_inertiabox.xml | 4 +- test/user/testdata/inertia_compare.xml | 24 ++++++++++++ test/user/testdata/inertia_concave.xml | 10 +++-- test/user/testdata/inertia_convex.xml | 2 +- test/user/user_mesh_test.cc | 45 ++++++++++++----------- unity/Runtime/Bindings/MjBindings.cs | 9 ++++- 19 files changed, 170 insertions(+), 87 deletions(-) create mode 100644 test/user/testdata/inertia_compare.xml diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index a2bccc95..8c3e72de 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -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** (?) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index d12ab310..4b45b68b 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -54,9 +54,7 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`discardvisual` | :ref:`convexhull` | :ref:`usethread` | :ref:`fusestatic` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`inertiafromgeom` | :ref:`inertiagrouprange` | :ref:`exactmeshinertia` | :ref:`assetdir` | | -| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`alignfree` | | | | | +| | | | :ref:`inertiafromgeom` | :ref:`inertiagrouprange` | :ref:`assetdir` | :ref:`alignfree` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| compiler |br| |_| |L| | | .. table:: | @@ -105,7 +103,7 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`refpos` | :ref:`refquat` | :ref:`scale` | :ref:`smoothnormal` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`maxhullvert` | | | | | +| | | | :ref:`maxhullvert` | :ref:`inertia` | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_2| mesh |br| |_2| |L| | | .. table:: | @@ -1422,7 +1420,7 @@ | :ref:`mesh | ? | :class: mjcf-attributes | | ` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`scale` | :ref:`maxhullvert` | | | | +| | | | :ref:`scale` | :ref:`maxhullvert` | :ref:`inertia` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| default |br| |_| |L| | | .. table:: | diff --git a/doc/changelog.rst b/doc/changelog.rst index 2f05ba5a..d3ec0db6 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -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` 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 `__ the copying operation. -#. Make :ref:`shellinertia` ignore :ref:`exactmeshinertia`, which is +#. Make :ref:`shellinertia` ignore ``exactmeshinertia``, which is only used for legacy volume computations (`#759 `__). diff --git a/doc/includes/references.h b/doc/includes/references.h index 820e571e..75e1419b 100644 --- a/doc/includes/references.h +++ b/doc/includes/references.h @@ -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 diff --git a/include/mujoco/mjspec.h b/include/mujoco/mjspec.h index c4e9cafd..c6d38c79 100644 --- a/include/mujoco/mjspec.h +++ b/include/mujoco/mjspec.h @@ -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 diff --git a/introspect/enums.py b/introspect/enums.py index f22f485e..b4b07400 100644 --- a/introspect/enums.py +++ b/introspect/enums.py @@ -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', diff --git a/introspect/structs.py b/introspect/structs.py index d844f0c6..449ac230 100644 --- a/introspect/structs.py +++ b/introspect/structs.py @@ -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'), diff --git a/src/user/user_init.c b/src/user/user_init.c index 59ae0cc0..d2ef5798 100644 --- a/src/user/user_init.c +++ b/src/user/user_init.c @@ -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; } diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 5b7174d4..fbe2383c 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -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.", diff --git a/src/user/user_objects.h b/src/user/user_objects.h index c2ee11b6..85db8da8 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -868,6 +868,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh { const std::vector& UserTexcoord() const { return spec_texcoord_; } const std::vector& Face() const { return face_; } const std::vector& 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 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]); }; diff --git a/src/xml/xml_base.h b/src/xml/xml_base.h index 23268ea0..a022831c 100644 --- a/src/xml/xml_base.h +++ b/src/xml/xml_base.h @@ -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[]; diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index d2f21048..17fe2609 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -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) { diff --git a/src/xml/xml_native_writer.cc b/src/xml/xml_native_writer.cc index e4805fef..02c265a3 100644 --- a/src/xml/xml_native_writer.cc +++ b/src/xml/xml_native_writer.cc @@ -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); } diff --git a/test/user/testdata/fitmesh_inertiabox.xml b/test/user/testdata/fitmesh_inertiabox.xml index e6d7ed35..e58552ad 100644 --- a/test/user/testdata/fitmesh_inertiabox.xml +++ b/test/user/testdata/fitmesh_inertiabox.xml @@ -1,8 +1,8 @@ - + - + diff --git a/test/user/testdata/inertia_compare.xml b/test/user/testdata/inertia_compare.xml new file mode 100644 index 00000000..f59d1199 --- /dev/null +++ b/test/user/testdata/inertia_compare.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + diff --git a/test/user/testdata/inertia_concave.xml b/test/user/testdata/inertia_concave.xml index 821057fc..10ec4efb 100644 --- a/test/user/testdata/inertia_concave.xml +++ b/test/user/testdata/inertia_concave.xml @@ -1,9 +1,11 @@ - + + + - - - + + + diff --git a/test/user/testdata/inertia_convex.xml b/test/user/testdata/inertia_convex.xml index ce9df614..4b9e2207 100644 --- a/test/user/testdata/inertia_convex.xml +++ b/test/user/testdata/inertia_convex.xml @@ -1,7 +1,7 @@ - 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 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"( - - @@ -742,10 +758,9 @@ TEST_F(MjCMeshTest, VolumeSmallAllowedShell) { mj_deleteModel(model); } -TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) { +TEST_F(MjCMeshTest, VolumeNegativeThrowsError) { static constexpr char xml[] = R"( - MESH_DEFINITIONS @@ -758,7 +773,7 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) { )"; static constexpr char bad_mesh[] = R"( - \n" )"; @@ -782,20 +797,8 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) { std::array 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")); } } diff --git a/unity/Runtime/Bindings/MjBindings.cs b/unity/Runtime/Bindings/MjBindings.cs index adcbbd9f..6562d6e0 100644 --- a/unity/Runtime/Bindings/MjBindings.cs +++ b/unity/Runtime/Bindings/MjBindings.cs @@ -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,