diff --git a/doc/XMLreference.rst b/doc/XMLreference.rst index 0b01759a..db20485c 100644 --- a/doc/XMLreference.rst +++ b/doc/XMLreference.rst @@ -1233,6 +1233,13 @@ The full list of processing steps applied by the compiler to each mesh is as fol faces at large angles relative to the average normal are excluded from the average. In this way, sharp edges (as in cube edges) are not smoothed. +.. _asset-mesh-maxhullvert: + +:at:`maxhullvert`: :at-val:`int, "-1"` + Maximum number of vertices in a mesh's convex hull. Currently this is implemented by asking qhull + `to teminate `__ after :at:`maxhullvert` vertices. The default + value of -1 means "unlimited". Positive values must be larger than 3. + .. _asset-mesh-vertex: :at:`vertex`: :at-val:`real(3*nvert), optional` @@ -7666,6 +7673,8 @@ if omitted. .. _default-mesh-scale: +.. _default-mesh-maxhullvert: + :el-prefix:`default/` |-| **mesh** (?) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ diff --git a/doc/XMLschema.rst b/doc/XMLschema.rst index 42a4131a..044d022b 100644 --- a/doc/XMLschema.rst +++ b/doc/XMLschema.rst @@ -103,6 +103,8 @@ | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | | | | | :ref:`refpos` | :ref:`refquat` | :ref:`scale` | :ref:`smoothnormal` | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +| | | | :ref:`maxhullvert` | | | | | +| | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_2| mesh |br| |_2| |L| | | .. table:: | | :ref:`plugin | \* | :class: mjcf-attributes | @@ -1325,7 +1327,7 @@ | :ref:`mesh | ? | :class: mjcf-attributes | | ` | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | -| | | | :ref:`scale` | | | | | +| | | | :ref:`scale` | :ref:`maxhullvert` | | | | | | | +-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+-----------------------------------------------------------------+ | +------------------------------------+----+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ | |_| default |br| |_| |L| | | .. table:: | diff --git a/doc/changelog.rst b/doc/changelog.rst index d1e6e023..5fe96611 100644 --- a/doc/changelog.rst +++ b/doc/changelog.rst @@ -2,6 +2,15 @@ Changelog ========= +Upcoming version (not yet released) +----------------------------------- + +General +^^^^^^^ + +1. Added :ref:`maxhullvert`, the maximum number of vertices in a mesh's convex hull. + + Version 3.1.6 (Jun 3, 2024) --------------------------- diff --git a/src/user/user_api.h b/src/user/user_api.h index 96650d7e..1908791f 100644 --- a/src/user/user_api.h +++ b/src/user/user_api.h @@ -446,6 +446,7 @@ typedef struct mjsMesh_ { // mesh specification double refquat[4]; // reference orientation double scale[3]; // rescale mesh mjtByte smoothnormal; // do not exclude large-angle faces from normals + int maxhullvert; // maximum vertex count for the convex hull mjFloatVec* uservert; // user vertex data mjFloatVec* usernormal; // user normal data mjFloatVec* usertexcoord; // user texcoord data diff --git a/src/user/user_init.c b/src/user/user_init.c index b45c2dde..6a6c8d15 100644 --- a/src/user/user_init.c +++ b/src/user/user_init.c @@ -243,6 +243,7 @@ void mjs_defaultMesh(mjsMesh* mesh) { memset(mesh, 0, sizeof(mjsMesh)); mesh->refquat[0] = 1; mesh->scale[0] = mesh->scale[1] = mesh->scale[2] = 1; + mesh->maxhullvert = -1; } diff --git a/src/user/user_mesh.cc b/src/user/user_mesh.cc index 06860079..79a1d1d0 100644 --- a/src/user/user_mesh.cc +++ b/src/user/user_mesh.cc @@ -50,8 +50,8 @@ #include #include -#include #include +#include #include "engine/engine_crossplatform.h" #include "engine/engine_io.h" #include "engine/engine_plugin.h" @@ -135,6 +135,7 @@ mjCMesh::mjCMesh(mjCModel* _model, mjCDef* _def) { center_ = NULL; graph_ = NULL; needhull_ = false; + maxhullvert_ = -1; invalidorientation_.first = -1; invalidorientation_.second = -1; validarea_ = true; @@ -223,6 +224,7 @@ void mjCMesh::CopyFromSpec() { facetexcoord_ = spec_facetexcoord_; file = &file_; content_type = &content_type_; + maxhullvert_ = spec.maxhullvert; uservert = &vert_; usernormal = &normal_; userface = &face_; @@ -1548,12 +1550,17 @@ double& mjCMesh::GetVolumeRef(mjtGeomInertia type) { // make graph describing convex hull -void mjCMesh::MakeGraph(void) { +void mjCMesh::MakeGraph() { int adr, ok, curlong, totlong, exitcode; double* data; facetT* facet, **facetp; vertexT* vertex, *vertex1, **vertex1p; - char qhopt[10] = "qhull Qt"; + + std::string qhopt = "qhull Qt"; + if (maxhullvert_ > -1) { + // qhull "TA" actually means "number of vertices added after the initial simplex" + qhopt += " TA" + std::to_string(maxhullvert_ - 4); + } // graph not needed for small meshes if (nvert() < 4) { @@ -1585,7 +1592,7 @@ void mjCMesh::MakeGraph(void) { qh->NOerrexit = false; if (!exitcode) { // actual init - qh_initflags(qh, qhopt); + qh_initflags(qh, const_cast(qhopt.c_str())); qh_init_B(qh, data, nvert(), 3, False); // construct convex hull @@ -1742,8 +1749,6 @@ void mjCMesh::MakeGraph(void) { } } - - // copy graph into face data void mjCMesh::CopyGraph(void) { // only if face data is missing diff --git a/src/user/user_objects.h b/src/user/user_objects.h index 8860a6c9..e3548686 100644 --- a/src/user/user_objects.h +++ b/src/user/user_objects.h @@ -778,6 +778,7 @@ class mjCMesh_ : public mjCBase { // size of mesh data to be copied into mjModel int szgraph_; // size of graph data in ints bool needhull_; // needs convex hull for collisions + int maxhullvert_; // max vertex count of convex hull mjCBoundingVolumeHierarchy tree_; // bounding volume hierarchy std::vector face_aabb_; // bounding boxes of all faces diff --git a/src/xml/xml_native_reader.cc b/src/xml/xml_native_reader.cc index 72a398b9..786e54ae 100644 --- a/src/xml/xml_native_reader.cc +++ b/src/xml/xml_native_reader.cc @@ -145,7 +145,7 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"default", "R", "1", "class"}, {"<"}, - {"mesh", "?", "1", "scale"}, + {"mesh", "?", "2", "scale", "maxhullvert"}, {"material", "?", "10", "texture", "emission", "specular", "shininess", "reflectance", "metallic", "roughness", "rgba", "texrepeat", "texuniform"}, {"joint", "?", "22", "type", "group", "pos", "axis", "springdamper", @@ -221,8 +221,9 @@ const char* MJCF[nMJCF][mjXATTRNUM] = { {"asset", "*", "0"}, {"<"}, - {"mesh", "*", "12", "name", "class", "content_type", "file", "vertex", "normal", - "texcoord", "face", "refpos", "refquat", "scale", "smoothnormal"}, + {"mesh", "*", "13", "name", "class", "content_type", "file", "vertex", "normal", + "texcoord", "face", "refpos", "refquat", "scale", "smoothnormal", + "maxhullvert"}, {"<"}, {"plugin", "*", "2", "plugin", "instance"}, {"<"}, @@ -1391,6 +1392,11 @@ void mjXReader::OneMesh(XMLElement* elem, mjsMesh* pmesh) { pmesh->smoothnormal = (n==1); } + if (ReadAttrInt(elem, "maxhullvert", &n)) { + if (n != 0 && n < 4) throw mjXError(elem, "maxhullvert must be larger than 3"); + pmesh->maxhullvert = n; + } + // read user vertex data if (ReadAttrTxt(elem, "vertex", text)) { auto uservert = ReadAttrVec(elem, "vertex"); diff --git a/test/user/testdata/torus_maxhullvert.xml b/test/user/testdata/torus_maxhullvert.xml new file mode 100644 index 00000000..e9fd03fa --- /dev/null +++ b/test/user/testdata/torus_maxhullvert.xml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/test/user/testdata/torus_maxhullvert_default.xml b/test/user/testdata/torus_maxhullvert_default.xml new file mode 100644 index 00000000..72444264 --- /dev/null +++ b/test/user/testdata/torus_maxhullvert_default.xml @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/test/user/user_mesh_test.cc b/test/user/user_mesh_test.cc index 56898655..cf62423d 100644 --- a/test/user/user_mesh_test.cc +++ b/test/user/user_mesh_test.cc @@ -41,6 +41,10 @@ static const char* const kCubePath = "user/testdata/cube.xml"; static const char* const kTorusPath = "user/testdata/torus.xml"; +static const char* const kTorusMaxhullVertPath = + "user/testdata/torus_maxhullvert.xml"; +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 kConvexInertiaPath = @@ -405,6 +409,25 @@ TEST_F(MjCMeshTest, TinyMeshLoads) { mj_deleteModel(model); } +// ------------- test max hull vert ------------------------------------------- +TEST_F(MjCMeshTest, MaxHullVert) { + const std::string xml_path = GetTestDataFilePath(kTorusMaxhullVertPath); + std::array error; + mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_GT(model->ngeom, 0); + ASSERT_EQ(model->mesh_graph[0], 4); + mj_deleteModel(model); +} + +TEST_F(MjCMeshTest, MaxHullVertDefault) { + const std::string xml_path = GetTestDataFilePath(kTorusDefaultMaxhullVertPath); + std::array error; + mjModel* model = mj_loadXML(xml_path.c_str(), 0, error.data(), error.size()); + ASSERT_GT(model->ngeom, 0); + ASSERT_EQ(model->mesh_graph[0], 64); + mj_deleteModel(model); +} + // ------------- test inline loading ------------------------------------------ TEST_F(MjCMeshTest, FaceNormalAutogenerated) { static constexpr char xml[] = R"(