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. 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. 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: .. _compiler-alignfree:
:at:`alignfree`: :at-val:`[false, true], "false"` :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 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. 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: .. _asset-mesh-smoothnormal:
:at:`smoothnormal`: :at-val:`[false, true], "false"` :at:`smoothnormal`: :at-val:`[false, true], "false"`
@@ -8016,6 +8028,8 @@ if omitted.
.. _default-mesh-maxhullvert: .. _default-mesh-maxhullvert:
.. _default-mesh-inertia:
:el-prefix:`default/` |-| **mesh** (?) :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:`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:`inertiafromgeom<compiler-inertiafromgeom>` | :ref:`inertiagrouprange<compiler-inertiagrouprange>` | :ref:`assetdir<compiler-assetdir>` | :ref:`alignfree<compiler-alignfree>` | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
| | | | :ref:`alignfree<compiler-alignfree>` | | | | |
| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ |
+------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
| |_| compiler |br| |_| |L| | | .. table:: | | |_| 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:`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:: | | |_2| mesh |br| |_2| |L| | | .. table:: |
@@ -1422,7 +1420,7 @@
| :ref:`mesh | ? | :class: mjcf-attributes | | :ref:`mesh | ? | :class: mjcf-attributes |
| <default-mesh>` | | | | <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:: | | |_| default |br| |_| |L| | | .. table:: |
+6 -1
View File
@@ -5,6 +5,11 @@ Changelog
Upcoming version (not yet released) 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 MJX
^^^ ^^^
- Added ``apply_ft``, ``jac``, and ``xfrc_accumulate`` as public functions. - 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. 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 #. Removed spurious whole-arena copying in `mj_copyData`, which can considerably
`slow down <https://github.com/google-deepmind/mujoco/issues/568>`__ the copying operation. `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>`__). 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 struct mjrContext_ mjrContext;
typedef enum mjtGeomInertia_ { // type of inertia inference 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 mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia; } 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 typedef enum mjtBuiltin_ { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2 mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -1700,7 +1705,6 @@ typedef struct mjSpec_ { // model specification
mjtByte fusestatic; // fuse static bodies with parent mjtByte fusestatic; // fuse static bodies with parent
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom) int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia 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 int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation mjLROpt LRopt; // options for lengthrange computation
@@ -1989,6 +1993,7 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position double refpos[3]; // reference position
double refquat[4]; // reference orientation double refquat[4]; // reference orientation
double scale[3]; // rescale mesh double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtByte smoothnormal; // do not exclude large-angle faces from normals mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data mjFloatVec* uservert; // user vertex data
+9 -2
View File
@@ -57,11 +57,18 @@ extern "C" {
//-------------------------------- enum types (mjt) ------------------------------------------------ //-------------------------------- enum types (mjt) ------------------------------------------------
typedef enum mjtGeomInertia_ { // type of inertia inference 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 mjINERTIA_SHELL, // mass distributed on the surface
} mjtGeomInertia; } 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 typedef enum mjtBuiltin_ { // type of built-in procedural texture
mjBUILTIN_NONE = 0, // no built-in texture mjBUILTIN_NONE = 0, // no built-in texture
mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2 mjBUILTIN_GRADIENT, // gradient: rgb1->rgb2
@@ -136,7 +143,6 @@ typedef struct mjSpec_ { // model specification
mjtByte fusestatic; // fuse static bodies with parent mjtByte fusestatic; // fuse static bodies with parent
int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom) int inertiafromgeom; // use geom inertias (mjtInertiaFromGeom)
int inertiagrouprange[2]; // range of geom groups used to compute inertia 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 int alignfree; // align free joints with inertial frame
mjLROpt LRopt; // options for lengthrange computation mjLROpt LRopt; // options for lengthrange computation
@@ -447,6 +453,7 @@ typedef struct mjsMesh_ { // mesh specification
double refpos[3]; // reference position double refpos[3]; // reference position
double refquat[4]; // reference orientation double refquat[4]; // reference orientation
double scale[3]; // rescale mesh double scale[3]; // rescale mesh
mjtMeshInertia inertia; // inertia type (convex, legacy, exact)
mjtByte smoothnormal; // do not exclude large-angle faces from normals mjtByte smoothnormal; // do not exclude large-angle faces from normals
int maxhullvert; // maximum vertex count for the convex hull int maxhullvert; // maximum vertex count for the convex hull
mjFloatVec* uservert; // user vertex data mjFloatVec* uservert; // user vertex data
+10
View File
@@ -727,6 +727,16 @@ ENUMS: Mapping[str, EnumDecl] = dict([
('mjINERTIA_SHELL', 1), ('mjINERTIA_SHELL', 1),
]), ]),
)), )),
('mjtMeshInertia',
EnumDecl(
name='mjtMeshInertia',
declname='enum mjtMeshInertia_',
values=dict([
('mjINERTIA_CONVEX', 0),
('mjINERTIA_EXACT', 1),
('mjINERTIA_LEGACY', 2),
]),
)),
('mjtBuiltin', ('mjtBuiltin',
EnumDecl( EnumDecl(
name='mjtBuiltin', name='mjtBuiltin',
+5 -5
View File
@@ -8914,11 +8914,6 @@ STRUCTS: Mapping[str, StructDecl] = dict([
), ),
doc='range of geom groups used to compute inertia', doc='range of geom groups used to compute inertia',
), ),
StructFieldDecl(
name='exactmeshinertia',
type=ValueType(name='mjtByte'),
doc='if false, use old formula',
),
StructFieldDecl( StructFieldDecl(
name='alignfree', name='alignfree',
type=ValueType(name='int'), type=ValueType(name='int'),
@@ -10291,6 +10286,11 @@ STRUCTS: Mapping[str, StructDecl] = dict([
), ),
doc='rescale mesh', doc='rescale mesh',
), ),
StructFieldDecl(
name='inertia',
type=ValueType(name='mjtMeshInertia'),
doc='inertia type (convex, legacy, exact)',
),
StructFieldDecl( StructFieldDecl(
name='smoothnormal', name='smoothnormal',
type=ValueType(name='mjtByte'), type=ValueType(name='mjtByte'),
+1
View File
@@ -246,6 +246,7 @@ void mjs_defaultMesh(mjsMesh* mesh) {
mesh->refquat[0] = 1; mesh->refquat[0] = 1;
mesh->scale[0] = mesh->scale[1] = mesh->scale[2] = 1; mesh->scale[0] = mesh->scale[1] = mesh->scale[2] = 1;
mesh->maxhullvert = -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, void mjCMesh::ComputeVolume(double CoM[3], mjtGeomInertia type,
const double facecen[3], bool exactmeshinertia) { const double facecen[3]) {
double nrm[3]; double nrm[3];
double cen[3]; double cen[3];
GetVolumeRef(type) = 0; GetVolumeRef(type) = 0;
mjuu_zerovec(CoM, 3); 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 // get area, normal and center
float* vv = vert_.data(); double a = _triangle(nrm, cen, vv+3*f[3*i], vv+3*f[3*i+1], vv+3*f[3*i+2]);
double a = _triangle(nrm, cen, vv+3*face_[3*i], vv+3*face_[3*i+1], vv+3*face_[3*i+2]);
// compute and add volume // compute and add volume
const double vec[3] = {cen[0]-facecen[0], cen[1]-facecen[1], cen[2]-facecen[2]}; 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; double vol = type==mjINERTIA_SHELL ? a : mjuu_dot3(vec, nrm) * a / 3;
// if legacy computation requested, then always positive // if legacy computation requested, then always positive
if (!exactmeshinertia && type==mjINERTIA_VOLUME) { if (inertia == mjINERTIA_LEGACY) {
vol = abs(vol); vol = abs(vol);
} }
@@ -1410,21 +1412,17 @@ void mjCMesh::Process() {
for ( const auto type : { mjtGeomInertia::mjINERTIA_VOLUME, mjtGeomInertia::mjINERTIA_SHELL } ) { for ( const auto type : { mjtGeomInertia::mjINERTIA_VOLUME, mjtGeomInertia::mjINERTIA_SHELL } ) {
double CoM[3] = {0, 0, 0}; double CoM[3] = {0, 0, 0};
double inert[6] = {0, 0, 0, 0, 0, 0}; double inert[6] = {0, 0, 0, 0, 0, 0};
bool exactmeshinertia = model->exactmeshinertia;
// compute CoM and volume from pyramid volumes // 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 volume is invalid, skip the rest of the computations
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 (GetVolumeRef(type) < mjMINVAL) { if (GetVolumeRef(type) < mjMINVAL) {
validvolume_ = GetVolumeRef(type) < 0 ? -1 : 0; if (type == mjINERTIA_SHELL) {
validarea_ = 0;
} else {
validvolume_ = GetVolumeRef(type) < 0 ? -1 : 0;
}
continue; 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}}; 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}; double P[6] = {0, 0, 0, 0, 0, 0};
GetVolumeRef(type) = 0; GetVolumeRef(type) = 0;
for (int i=0; i < nface(); i++) { int nf = (inertia == mjINERTIA_CONVEX) ? graph_[1] : nface();
float* D = vert_.data()+3*face_[3*i]; int* f = (inertia == mjINERTIA_CONVEX) ? graph_ + 2 + 3*(graph_[0]+graph_[1]) : face_.data();
float* E = vert_.data()+3*face_[3*i+1]; for (int i=0; i < nf; i++) {
float* F = vert_.data()+3*face_[3*i+2]; 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 // get area, normal and center; update volume
double a = _triangle(nrm, cen, D, E, F); double a = _triangle(nrm, cen, D, E, F);
double vol = type==mjINERTIA_SHELL ? a : mjuu_dot3(cen, nrm) * a / 3; double vol = type==mjINERTIA_SHELL ? a : mjuu_dot3(cen, nrm) * a / 3;
// if legacy computation requested, then always positive // if legacy computation requested, then always positive
if (!exactmeshinertia && type==mjINERTIA_VOLUME) { if (inertia == mjINERTIA_LEGACY) {
vol = abs(vol); vol = abs(vol);
} }
@@ -1554,7 +1554,7 @@ void mjCMesh::CheckMesh(mjtGeomInertia type) {
if (!processed_) { if (!processed_) {
return; return;
} }
if ((invalidorientation_.first>=0 || invalidorientation_.second>=0) && model->exactmeshinertia) if ((invalidorientation_.first>=0 || invalidorientation_.second>=0) && inertia == mjINERTIA_EXACT)
throw mjCError(this, throw mjCError(this,
"faces of mesh '%s' have inconsistent orientation. Please check the " "faces of mesh '%s' have inconsistent orientation. Please check the "
"faces containing the vertices %d and %d.", "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<float>& UserTexcoord() const { return spec_texcoord_; }
const std::vector<int>& Face() const { return face_; } const std::vector<int>& Face() const { return face_; }
const std::vector<int>& UserFace() const { return spec_face_; } const std::vector<int>& UserFace() const { return spec_face_; }
mjtMeshInertia Inertia() const { return spec.inertia; }
// setters // setters
void SetNeedHull(bool needhull) { needhull_ = needhull; } void SetNeedHull(bool needhull) { needhull_ = needhull; }
@@ -945,8 +946,7 @@ class mjCMesh: public mjCMesh_, private mjsMesh {
std::vector<unsigned char> num_face_vertices_; std::vector<unsigned char> num_face_vertices_;
// compute the volume and center-of-mass of the mesh given the face center // 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], void ComputeVolume(double CoM[3], mjtGeomInertia gtype, const double facecen[3]);
bool exactmeshinertia);
}; };
+1
View File
@@ -69,6 +69,7 @@ extern const mjMap bias_map[];
extern const mjMap stage_map[]; extern const mjMap stage_map[];
extern const mjMap datatype_map[]; extern const mjMap datatype_map[];
extern const mjMap meshtype_map[]; extern const mjMap meshtype_map[];
extern const mjMap meshinertia_map[];
extern const mjMap flexself_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] = { const char* MJCF[nMJCF][mjXATTRNUM] = {
{"mujoco", "!", "1", "model"}, {"mujoco", "!", "1", "model"},
{"<"}, {"<"},
{"compiler", "*", "21", "autolimits", "boundmass", "boundinertia", "settotalmass", {"compiler", "*", "20", "autolimits", "boundmass", "boundinertia", "settotalmass",
"balanceinertia", "strippath", "coordinate", "angle", "fitaabb", "eulerseq", "balanceinertia", "strippath", "coordinate", "angle", "fitaabb", "eulerseq",
"meshdir", "texturedir", "discardvisual", "convexhull", "usethread", "meshdir", "texturedir", "discardvisual", "convexhull", "usethread",
"fusestatic", "inertiafromgeom", "inertiagrouprange", "exactmeshinertia", "fusestatic", "inertiafromgeom", "inertiagrouprange", "assetdir", "alignfree"},
"assetdir", "alignfree"},
{"<"}, {"<"},
{"lengthrange", "?", "10", "mode", "useexisting", "uselimit", {"lengthrange", "?", "10", "mode", "useexisting", "uselimit",
"accel", "maxforce", "timeconst", "timestep", "accel", "maxforce", "timeconst", "timestep",
@@ -150,7 +149,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"default", "R", "1", "class"}, {"default", "R", "1", "class"},
{"<"}, {"<"},
{"mesh", "?", "2", "scale", "maxhullvert"}, {"mesh", "?", "3", "scale", "maxhullvert", "inertia"},
{"material", "?", "10", "texture", "emission", "specular", "shininess", {"material", "?", "10", "texture", "emission", "specular", "shininess",
"reflectance", "metallic", "roughness", "rgba", "texrepeat", "texuniform"}, "reflectance", "metallic", "roughness", "rgba", "texrepeat", "texuniform"},
{"joint", "?", "22", "type", "group", "pos", "axis", "springdamper", {"joint", "?", "22", "type", "group", "pos", "axis", "springdamper",
@@ -226,9 +225,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = {
{"asset", "*", "0"}, {"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", "texcoord", "face", "refpos", "refquat", "scale", "smoothnormal",
"maxhullvert"}, "maxhullvert", "inertia"},
{"<"}, {"<"},
{"plugin", "*", "2", "plugin", "instance"}, {"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 // flexcomp type
const mjMap fcomp_map[mjNFCOMPTYPES] = { const mjMap fcomp_map[mjNFCOMPTYPES] = {
{"grid", mjFCOMPTYPE_GRID}, {"grid", mjFCOMPTYPE_GRID},
@@ -1020,9 +1027,6 @@ void mjXReader::Compiler(XMLElement* section, mjSpec* spec) {
} }
MapValue(section, "inertiafromgeom", &spec->inertiafromgeom, TFAuto_map, 3); MapValue(section, "inertiafromgeom", &spec->inertiafromgeom, TFAuto_map, 3);
ReadAttr(section, "inertiagrouprange", 2, spec->inertiagrouprange, text); 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)) { if (MapValue(section, "alignfree", &n, bool_map, 2)) {
spec->alignfree = (n==1); 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, "refpos", 3, mesh->refpos, text);
ReadAttr(elem, "refquat", 4, mesh->refquat, text); ReadAttr(elem, "refquat", 4, mesh->refquat, text);
ReadAttr(elem, "scale", 3, mesh->scale, 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"); XMLElement* eplugin = FirstChildElement(elem, "plugin");
if (eplugin) { 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, "content_type", mesh->ContentType());
WriteAttrTxt(elem, "file", mesh->File()); WriteAttrTxt(elem, "file", mesh->File());
if (mesh->Inertia() != def->Mesh().Inertia()) {
WriteAttrTxt(elem, "inertia", FindValue(meshinertia_map, 3, mesh->Inertia()));
}
// write vertex data // write vertex data
if (!mesh->UserVert().empty()) { if (!mesh->UserVert().empty()) {
@@ -930,9 +933,7 @@ void mjXWriter::Compiler(XMLElement* root) {
if (!model->usethread) { if (!model->usethread) {
WriteAttrTxt(section, "usethread", "false"); WriteAttrTxt(section, "usethread", "false");
} }
if (model->exactmeshinertia) {
WriteAttrTxt(section, "exactmeshinertia", "true");
}
if (model->boundmass) { if (model->boundmass) {
WriteAttr(section, "boundmass", 1, &model->boundmass); WriteAttr(section, "boundmass", 1, &model->boundmass);
} }
+2 -2
View File
@@ -1,8 +1,8 @@
<mujoco> <mujoco>
<compiler fitaabb="false" exactmeshinertia="true"/> <compiler fitaabb="false"/>
<asset> <asset>
<mesh file="stanford_bunny.obj"/> <mesh file="stanford_bunny.obj" inertia="exact"/>
</asset> </asset>
<worldbody> <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> <mujoco>
<compiler exactmeshinertia="true"/> <default>
<mesh scale="10 10 10" inertia="exact"/>
</default>
<asset> <asset>
<mesh file="cube_cup.obj" scale="10 10 10"/> <mesh file="cube_cup.obj"/>
<mesh file="cube_cup_quad.obj" scale="10 10 10"/> <mesh file="cube_cup_quad.obj"/>
<mesh file="cube_cup_hi.obj" scale="10 10 10"/> <mesh file="cube_cup_hi.obj"/>
</asset> </asset>
<worldbody> <worldbody>
<light directional="true" diffuse=".6 .6 .6" specular="0.2 0.2 0.2" pos="0 0 4" dir="0 0 -1"/> <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> <mujoco>
<asset> <asset>
<mesh file="cube.obj"/> <mesh file="cube.obj"/>
<mesh name="inline_cube" <mesh name="inline_cube" inertia="convex"
vertex=" vertex="
-.5 -.5 -.5 -.5 -.5 -.5
.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"; "user/testdata/torus_maxhullvert_default.xml";
static const char* const kTorusShellPath = static const char* const kTorusShellPath =
"user/testdata/torus_shell.xml"; "user/testdata/torus_shell.xml";
static const char* const kCompareInertiaPath =
"user/testdata/inertia_compare.xml";
static const char* const kConvexInertiaPath = static const char* const kConvexInertiaPath =
"user/testdata/inertia_convex.xml"; "user/testdata/inertia_convex.xml";
static const char* const kConcaveInertiaPath = static const char* const kConcaveInertiaPath =
@@ -506,21 +508,35 @@ TEST_F(MjCMeshTest, TinyInertiaFails) {
"mass and inertia of moving bodies must be larger than mjMINVAL")); "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); const std::string xml_path = GetTestDataFilePath(kMalformedFaceOBJPath);
std::array<char, 1024> error; std::array<char, 1024> error;
mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); 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); EXPECT_THAT(model->nmeshface, 4);
mj_deleteModel(model); 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) { TEST_F(MjCMeshTest, FlippedFaceFailsExactInertia) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>
<compiler exactmeshinertia="true"/>
<asset> <asset>
<mesh name="example_mesh" <mesh name="example_mesh" inertia="exact"
vertex="0 0 0 1 0 0 0 1 0 0 0 1" 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" /> face="2 0 3 0 1 3 1 2 3 0 1 2" />
</asset> </asset>
@@ -742,10 +758,9 @@ TEST_F(MjCMeshTest, VolumeSmallAllowedShell) {
mj_deleteModel(model); mj_deleteModel(model);
} }
TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) { TEST_F(MjCMeshTest, VolumeNegativeThrowsError) {
static constexpr char xml[] = R"( static constexpr char xml[] = R"(
<mujoco> <mujoco>
<compiler exactmeshinertia="true"/>
<asset> <asset>
MESH_DEFINITIONS MESH_DEFINITIONS
</asset> </asset>
@@ -758,7 +773,7 @@ TEST_F(MjCMeshTest, VolumeNegativeDefaultsLegacy) {
)"; )";
static constexpr char bad_mesh[] = R"( 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" 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" 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; std::array<char, 1024> error;
mjModel* model = LoadModelFromString(xml_str.c_str(), mjModel* model = LoadModelFromString(xml_str.c_str(),
error.data(), error.size()); error.data(), error.size());
EXPECT_THAT(model, NotNull()) << error.data(); EXPECT_THAT(model, IsNull());
EXPECT_LE(mju_abs(model->geom_size[0]), 1); EXPECT_THAT(error.data(), HasSubstr("mesh volume is negative"));
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);
} }
} }
+7 -2
View File
@@ -451,8 +451,13 @@ public enum mjtFont : int{
mjFONT_BIG = 2, mjFONT_BIG = 2,
} }
public enum mjtGeomInertia : int{ public enum mjtGeomInertia : int{
mjINERTIA_VOLUME = 1, mjINERTIA_VOLUME = 0,
mjINERTIA_SHELL = 2, mjINERTIA_SHELL = 1,
}
public enum mjtMeshInertia : int{
mjINERTIA_CONVEX = 0,
mjINERTIA_EXACT = 1,
mjINERTIA_LEGACY = 2,
} }
public enum mjtBuiltin : int{ public enum mjtBuiltin : int{
mjBUILTIN_NONE = 0, mjBUILTIN_NONE = 0,