diff --git a/include/sdf/Mesh.hh b/include/sdf/Mesh.hh index f19d98818..7cc48c955 100644 --- a/include/sdf/Mesh.hh +++ b/include/sdf/Mesh.hh @@ -37,6 +37,47 @@ namespace sdf // Forward declarations. class ParserConfig; + /// \brief Mesh optimization method + enum class MeshOptimization + { + /// \brief No mesh optimization + NONE, + /// \brief Convex hull + CONVEX_HULL, + /// \brief Convex decomposition + CONVEX_DECOMPOSITION + }; + + /// \brief Convex decomposition + class SDFORMAT_VISIBLE ConvexDecomposition + { + /// \brief Default constructor + public: ConvexDecomposition(); + + /// \brief Load the contact based on a element pointer. This is *not* the + /// usual entry point. Typical usage of the SDF DOM is through the Root + /// object. + /// \param[in] _sdf The SDF Element pointer + /// \return Errors, which is a vector of Error objects. Each Error includes + /// an error code and message. An empty vector indicates no error. + public: Errors Load(ElementPtr _sdf); + + /// \brief Get a pointer to the SDF element that was used during + /// load. + /// \return SDF element pointer. The value will be nullptr if Load has + /// not been called. + public: sdf::ElementPtr Element() const; + + /// \brief Get the maximum number of convex hulls that can be generated. + public: unsigned int MaxConvexHulls() const; + + /// \brief Set the maximum number of convex hulls that can be generated. + public: void SetMaxConvexHulls(unsigned int _maxConvexHulls); + + /// \brief Private data pointer. + GZ_UTILS_IMPL_PTR(dataPtr) + }; + /// \brief Mesh represents a mesh shape, and is usually accessed through a /// Geometry. class SDFORMAT_VISIBLE Mesh @@ -61,6 +102,37 @@ namespace sdf /// an error code and message. An empty vector indicates no error. public: Errors Load(sdf::ElementPtr _sdf, const ParserConfig &_config); + /// \brief Get the mesh's optimization method + /// \return The mesh optimization method. + /// MeshOptimization::NONE if no mesh simplificaton is done. + public: MeshOptimization Optimization() const; + + /// \brief Get the mesh's optimization method + /// \return The mesh optimization method. + /// Empty string if no mesh simplificaton is done. + public: std::string OptimizationStr() const; + + /// \brief Set the mesh optimization method. + /// \param[in] _optimization The mesh optimization method. + public: void SetOptimization(MeshOptimization _optimization); + + /// \brief Set the mesh optimization method. + /// \param[in] _optimization The mesh optimization method. + /// \return True if the _optimizationStr parameter matched a known + /// mesh optimization method. False if the mesh optimization method + /// could not be set. + public: bool SetOptimization(const std::string &_optimizationStr); + + /// \brief Get the associated ConvexDecomposition object + /// \returns Pointer to the associated ConvexDecomposition object, + /// nullptr if the Mesh doesn't contain a ConvexDecomposition element. + public: const sdf::ConvexDecomposition *ConvexDecomposition() const; + + /// \brief Set the associated ConvexDecomposition object. + /// \param[in] _convexDecomposition The ConvexDecomposition object. + public: void SetConvexDecomposition( + const sdf::ConvexDecomposition &_convexDecomposition); + /// \brief Get the mesh's URI. /// \return The URI of the mesh data. public: std::string Uri() const; diff --git a/sdf/1.11/mesh_shape.sdf b/sdf/1.11/mesh_shape.sdf index 61bce76dd..a59300d46 100644 --- a/sdf/1.11/mesh_shape.sdf +++ b/sdf/1.11/mesh_shape.sdf @@ -1,5 +1,19 @@ Mesh shape + + + + Set whether to optimize the mesh using one of the specified methods. Values include: "convex_hull" - a single convex hull that encapsulates the mesh, "convex_decomposition" - decompose the mesh into multiple convex hull meshes. Default value is an empty string which means no mesh optimization. + + + + + Convex decomposition parameters. Applicable if the mesh optimization attribute is set to convex_decomposition + + Maximum number of convex hulls to decompose into. If the input mesh has multiple submeshes, this limit is applied when decomposing each submesh + + + Mesh uri diff --git a/sdf/1.12/mesh_shape.sdf b/sdf/1.12/mesh_shape.sdf index 61bce76dd..a59300d46 100644 --- a/sdf/1.12/mesh_shape.sdf +++ b/sdf/1.12/mesh_shape.sdf @@ -1,5 +1,19 @@ Mesh shape + + + + Set whether to optimize the mesh using one of the specified methods. Values include: "convex_hull" - a single convex hull that encapsulates the mesh, "convex_decomposition" - decompose the mesh into multiple convex hull meshes. Default value is an empty string which means no mesh optimization. + + + + + Convex decomposition parameters. Applicable if the mesh optimization attribute is set to convex_decomposition + + Maximum number of convex hulls to decompose into. If the input mesh has multiple submeshes, this limit is applied when decomposing each submesh + + + Mesh uri diff --git a/src/Mesh.cc b/src/Mesh.cc index 9dfddcf2b..f026e2d63 100644 --- a/src/Mesh.cc +++ b/src/Mesh.cc @@ -14,8 +14,10 @@ * limitations under the License. * */ +#include #include #include +#include #include #include "sdf/CustomInertiaCalcProperties.hh" @@ -27,9 +29,35 @@ using namespace sdf; -// Private data class +/// Mesh Optimization method strings. These should match the data in +/// `enum class MeshOptimization` located in Mesh.hh, and the size +/// template parameter should match the number of elements as well. +constexpr std::array kMeshOptimizationStrs = +{ + "", + "convex_hull", + "convex_decomposition" +}; + +// Private data class for ConvexDecomposition +class sdf::ConvexDecomposition::Implementation +{ + /// \brief Maximum number of convex hulls to generate. + public: unsigned int maxConvexHulls{16u}; + + /// \brief The SDF element pointer used during load. + public: sdf::ElementPtr sdf = nullptr; +}; + +// Private data class for Mesh class sdf::Mesh::Implementation { + /// \brief Mesh optimization method + public: MeshOptimization optimization = MeshOptimization::NONE; + + /// \brief Optional convex decomposition. + public: std::optional convexDecomposition; + /// \brief The mesh's URI. public: std::string uri = ""; @@ -49,6 +77,62 @@ class sdf::Mesh::Implementation public: sdf::ElementPtr sdf = nullptr; }; +///////////////////////////////////////////////// +ConvexDecomposition::ConvexDecomposition() + : dataPtr(gz::utils::MakeImpl()) +{ +} + +///////////////////////////////////////////////// +Errors ConvexDecomposition::Load(ElementPtr _sdf) +{ + Errors errors; + + this->dataPtr->sdf = _sdf; + + // Check that sdf is a valid pointer + if (!_sdf) + { + errors.push_back({ErrorCode::ELEMENT_MISSING, + "Attempting to load convex decomposition, " + "but the provided SDF element is null."}); + return errors; + } + + // We need a convex_decomposition element + if (_sdf->GetName() != "convex_decomposition") + { + errors.push_back({ErrorCode::ELEMENT_INCORRECT_TYPE, + "Attempting to load convex decomposition, but the provided SDF " + "element is not ."}); + return errors; + } + + this->dataPtr->maxConvexHulls = _sdf->Get( + errors, "max_convex_hulls", + this->dataPtr->maxConvexHulls).first; + + return errors; +} + +///////////////////////////////////////////////// +sdf::ElementPtr ConvexDecomposition::Element() const +{ + return this->dataPtr->sdf; +} + +///////////////////////////////////////////////// +unsigned int ConvexDecomposition::MaxConvexHulls() const +{ + return this->dataPtr->maxConvexHulls; +} + +///////////////////////////////////////////////// +void ConvexDecomposition::SetMaxConvexHulls(unsigned int _maxConvexHulls) +{ + this->dataPtr->maxConvexHulls = _maxConvexHulls; +} + ///////////////////////////////////////////////// Mesh::Mesh() : dataPtr(gz::utils::MakeImpl()) @@ -61,6 +145,7 @@ Errors Mesh::Load(ElementPtr _sdf) return this->Load(_sdf, ParserConfig::GlobalConfig()); } + ///////////////////////////////////////////////// Errors Mesh::Load(ElementPtr _sdf, const ParserConfig &_config) { @@ -87,6 +172,20 @@ Errors Mesh::Load(ElementPtr _sdf, const ParserConfig &_config) return errors; } + // Optimization + if (_sdf->HasAttribute("optimization")) + { + this->SetOptimization(_sdf->Get("optimization", "").first); + } + + if (_sdf->HasElement("convex_decomposition")) + { + this->dataPtr->convexDecomposition.emplace(); + Errors err = this->dataPtr->convexDecomposition->Load( + _sdf->GetElement("convex_decomposition", errors)); + errors.insert(errors.end(), err.begin(), err.end()); + } + if (_sdf->HasElement("uri")) { std::unordered_set paths; @@ -140,6 +239,56 @@ sdf::ElementPtr Mesh::Element() const return this->dataPtr->sdf; } +////////////////////////////////////////////////// +MeshOptimization Mesh::Optimization() const +{ + return this->dataPtr->optimization; +} + +////////////////////////////////////////////////// +std::string Mesh::OptimizationStr() const +{ + size_t index = static_cast(this->dataPtr->optimization); + if (index < kMeshOptimizationStrs.size()) + return std::string(kMeshOptimizationStrs[index]); + return ""; +} + +////////////////////////////////////////////////// +bool Mesh::SetOptimization(const std::string &_optimizationStr) +{ + for (size_t i = 0; i < kMeshOptimizationStrs.size(); ++i) + { + if (_optimizationStr == kMeshOptimizationStrs[i]) + { + this->dataPtr->optimization = static_cast(i); + return true; + } + } + return false; +} + +////////////////////////////////////////////////// +void Mesh::SetOptimization(MeshOptimization _optimization) +{ + this->dataPtr->optimization = _optimization; +} + +////////////////////////////////////////////////// +const sdf::ConvexDecomposition *Mesh::ConvexDecomposition() const +{ + if (this->dataPtr->convexDecomposition.has_value()) + return &this->dataPtr->convexDecomposition.value(); + return nullptr; +} + +////////////////////////////////////////////////// + void Mesh::SetConvexDecomposition( + const sdf::ConvexDecomposition &_convexDecomposition) +{ + this->dataPtr->convexDecomposition = _convexDecomposition; +} + ////////////////////////////////////////////////// std::string Mesh::Uri() const { @@ -244,6 +393,18 @@ sdf::ElementPtr Mesh::ToElement(sdf::Errors &_errors) const sdf::ElementPtr elem(new sdf::Element); sdf::initFile("mesh_shape.sdf", elem); + // Optimization + elem->GetAttribute("optimization")->Set( + this->OptimizationStr()); + + if (this->dataPtr->convexDecomposition.has_value()) + { + sdf::ElementPtr convexDecomp = elem->GetElement("convex_decomposition", + _errors); + convexDecomp->GetElement("max_convex_hulls")->Set( + this->dataPtr->convexDecomposition->MaxConvexHulls()); + } + // Uri sdf::ElementPtr uriElem = elem->GetElement("uri", _errors); uriElem->Set(_errors, this->Uri()); diff --git a/src/Mesh_TEST.cc b/src/Mesh_TEST.cc index 51c1751f4..29ef70d85 100644 --- a/src/Mesh_TEST.cc +++ b/src/Mesh_TEST.cc @@ -34,6 +34,9 @@ TEST(DOMMesh, Construction) sdf::Mesh mesh; EXPECT_EQ(nullptr, mesh.Element()); + EXPECT_EQ(std::string(), mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::NONE, mesh.Optimization()); + EXPECT_EQ(nullptr, mesh.ConvexDecomposition()); EXPECT_EQ(std::string(), mesh.FilePath()); EXPECT_EQ(std::string(), mesh.Uri()); EXPECT_EQ(std::string(), mesh.Submesh()); @@ -45,24 +48,37 @@ TEST(DOMMesh, Construction) TEST(DOMMesh, MoveConstructor) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_decomposition")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); mesh.SetScale({0.5, 0.6, 0.7}); mesh.SetFilePath("/pear"); + sdf::ConvexDecomposition convexDecomp; + EXPECT_EQ(nullptr, convexDecomp.Element()); + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + sdf::Mesh mesh2(std::move(mesh)); + EXPECT_EQ("convex_decomposition", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, mesh2.Optimization()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); EXPECT_TRUE(mesh2.CenterSubmesh()); EXPECT_EQ("/pear", mesh2.FilePath()); + + auto convexDecomp2 = mesh2.ConvexDecomposition(); + ASSERT_NE(nullptr, convexDecomp2); + EXPECT_EQ(10u, convexDecomp2->MaxConvexHulls()); } ///////////////////////////////////////////////// TEST(DOMMesh, CopyConstructor) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -70,6 +86,9 @@ TEST(DOMMesh, CopyConstructor) mesh.SetFilePath("/pear"); sdf::Mesh mesh2(mesh); + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -81,6 +100,7 @@ TEST(DOMMesh, CopyConstructor) TEST(DOMMesh, CopyAssignmentOperator) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -89,6 +109,9 @@ TEST(DOMMesh, CopyAssignmentOperator) sdf::Mesh mesh2; mesh2 = mesh; + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -100,6 +123,7 @@ TEST(DOMMesh, CopyAssignmentOperator) TEST(DOMMesh, MoveAssignmentOperator) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("banana"); mesh.SetSubmesh("watermelon"); mesh.SetCenterSubmesh(true); @@ -108,6 +132,9 @@ TEST(DOMMesh, MoveAssignmentOperator) sdf::Mesh mesh2; mesh2 = std::move(mesh); + EXPECT_EQ("convex_hull", mesh2.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ("banana", mesh2.Uri()); EXPECT_EQ("watermelon", mesh2.Submesh()); EXPECT_EQ(gz::math::Vector3d(0.5, 0.6, 0.7), mesh2.Scale()); @@ -140,6 +167,29 @@ TEST(DOMMesh, Set) sdf::Mesh mesh; EXPECT_EQ(nullptr, mesh.Element()); + EXPECT_EQ(std::string(), mesh.OptimizationStr()); + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); + EXPECT_EQ("convex_hull", mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_HULL, mesh.Optimization()); + mesh.SetOptimization(sdf::MeshOptimization::CONVEX_DECOMPOSITION); + EXPECT_EQ("convex_decomposition", mesh.OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, + mesh.Optimization()); + // check invalid inputs + EXPECT_FALSE(mesh.SetOptimization("invalid")); + { + auto invalidMeshOpt = static_cast(99); + mesh.SetOptimization(invalidMeshOpt); + EXPECT_EQ(invalidMeshOpt, mesh.Optimization()); + EXPECT_EQ("", mesh.OptimizationStr()); + } + + sdf::ConvexDecomposition convexDecomp; + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + ASSERT_NE(nullptr, mesh.ConvexDecomposition()); + EXPECT_EQ(10u, mesh.ConvexDecomposition()->MaxConvexHulls()); + EXPECT_EQ(std::string(), mesh.Uri()); mesh.SetUri("http://myuri.com"); EXPECT_EQ("http://myuri.com", mesh.Uri()); @@ -165,6 +215,7 @@ TEST(DOMMesh, Set) TEST(DOMMesh, Load) { sdf::Mesh mesh; + sdf::ConvexDecomposition convexDecomp; sdf::Errors errors; // Null element name @@ -173,6 +224,11 @@ TEST(DOMMesh, Load) EXPECT_EQ(sdf::ErrorCode::ELEMENT_MISSING, errors[0].Code()); EXPECT_EQ(nullptr, mesh.Element()); + errors = convexDecomp.Load(nullptr); + ASSERT_EQ(1u, errors.size()); + EXPECT_EQ(sdf::ErrorCode::ELEMENT_MISSING, errors[0].Code()); + EXPECT_EQ(nullptr, convexDecomp.Element()); + // Bad element name sdf::ElementPtr sdf(new sdf::Element()); sdf->SetName("bad"); @@ -181,6 +237,11 @@ TEST(DOMMesh, Load) EXPECT_EQ(sdf::ErrorCode::ELEMENT_INCORRECT_TYPE, errors[0].Code()); EXPECT_NE(nullptr, mesh.Element()); + errors = convexDecomp.Load(sdf); + ASSERT_EQ(1u, errors.size()); + EXPECT_EQ(sdf::ErrorCode::ELEMENT_INCORRECT_TYPE, errors[0].Code()); + EXPECT_NE(nullptr, convexDecomp.Element()); + // Missing element sdf->SetName("mesh"); errors = mesh.Load(sdf); @@ -296,21 +357,30 @@ TEST(DOMMesh, ToElement) { sdf::Mesh mesh; + EXPECT_TRUE(mesh.SetOptimization("convex_decomposition")); mesh.SetUri("mesh-uri"); mesh.SetScale(gz::math::Vector3d(1, 2, 3)); mesh.SetSubmesh("submesh"); mesh.SetCenterSubmesh(false); + sdf::ConvexDecomposition convexDecomp; + convexDecomp.SetMaxConvexHulls(10u); + mesh.SetConvexDecomposition(convexDecomp); + sdf::ElementPtr elem = mesh.ToElement(); ASSERT_NE(nullptr, elem); sdf::Mesh mesh2; mesh2.Load(elem); + EXPECT_EQ(mesh.OptimizationStr(), mesh2.OptimizationStr()); + EXPECT_EQ(mesh.Optimization(), mesh2.Optimization()); EXPECT_EQ(mesh.Uri(), mesh2.Uri()); EXPECT_EQ(mesh.Scale(), mesh2.Scale()); EXPECT_EQ(mesh.Submesh(), mesh2.Submesh()); EXPECT_EQ(mesh.CenterSubmesh(), mesh2.CenterSubmesh()); + ASSERT_NE(nullptr, mesh2.ConvexDecomposition()); + EXPECT_EQ(10u, mesh2.ConvexDecomposition()->MaxConvexHulls()); } ///////////////////////////////////////////////// @@ -332,6 +402,7 @@ TEST(DOMMesh, ToElementErrorOutput) sdf::Mesh mesh; sdf::Errors errors; + EXPECT_TRUE(mesh.SetOptimization("convex_hull")); mesh.SetUri("mesh-uri"); mesh.SetScale(gz::math::Vector3d(1, 2, 3)); mesh.SetSubmesh("submesh"); @@ -345,6 +416,9 @@ TEST(DOMMesh, ToElementErrorOutput) errors = mesh2.Load(elem); EXPECT_TRUE(errors.empty()); + EXPECT_EQ(mesh.OptimizationStr(), mesh2.OptimizationStr()); + EXPECT_EQ(mesh.Optimization(), mesh2.Optimization()); + EXPECT_EQ(nullptr, mesh2.ConvexDecomposition()); EXPECT_EQ(mesh.Uri(), mesh2.Uri()); EXPECT_EQ(mesh.Scale(), mesh2.Scale()); EXPECT_EQ(mesh.Submesh(), mesh2.Submesh()); diff --git a/test/integration/geometry_dom.cc b/test/integration/geometry_dom.cc index 07d50d570..297d1f0af 100644 --- a/test/integration/geometry_dom.cc +++ b/test/integration/geometry_dom.cc @@ -179,6 +179,12 @@ TEST(DOMGeometry, Shapes) EXPECT_EQ(sdf::GeometryType::MESH, meshCol->Geom()->Type()); const sdf::Mesh *meshColGeom = meshCol->Geom()->MeshShape(); ASSERT_NE(nullptr, meshColGeom); + EXPECT_EQ("convex_decomposition", meshColGeom->OptimizationStr()); + EXPECT_EQ(sdf::MeshOptimization::CONVEX_DECOMPOSITION, + meshColGeom->Optimization()); + ASSERT_NE(nullptr, meshColGeom->ConvexDecomposition()); + EXPECT_EQ(4u, meshColGeom->ConvexDecomposition()->MaxConvexHulls()); + EXPECT_EQ("https://fuel.gazebosim.org/1.0/an_org/models/a_model/mesh/" "mesh.dae", meshColGeom->Uri()); EXPECT_TRUE(gz::math::Vector3d(0.1, 0.2, 0.3) == diff --git a/test/sdf/shapes.sdf b/test/sdf/shapes.sdf index 1f5f4fa27..f411afa37 100644 --- a/test/sdf/shapes.sdf +++ b/test/sdf/shapes.sdf @@ -120,7 +120,10 @@ - + + + 4 + https://fuel.gazebosim.org/1.0/an_org/models/a_model/mesh/mesh.dae my_submesh