From bea24f512bebbda6f585b5a7121695a17f9d16f8 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:05:08 +0100 Subject: [PATCH 1/4] [Topology] Add support for adding prism and pyramid elements in MeshTopology --- .../container/constant/MeshTopology.cpp | 36 +++++++++++++++++++ .../container/constant/MeshTopology.h | 2 ++ .../sofa/core/topology/BaseMeshTopology.cpp | 10 ++++++ .../src/sofa/core/topology/BaseMeshTopology.h | 2 ++ 4 files changed, 50 insertions(+) diff --git a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp index c7ce634725c..e8f4fd5387e 100644 --- a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp +++ b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp @@ -518,6 +518,8 @@ void MeshTopology::init() const auto hexahedra = sofa::helper::getReadAccessor(d_seqHexahedra); const auto tetrahedra = sofa::helper::getReadAccessor(d_seqTetrahedra); + const auto prisms = sofa::helper::getReadAccessor(d_seqPrisms); + const auto pyramids = sofa::helper::getReadAccessor(d_seqPyramids); const auto quads = sofa::helper::getReadAccessor(d_seqQuads); const auto triangles = sofa::helper::getReadAccessor(d_seqTriangles); const auto edges = sofa::helper::getReadAccessor(d_seqEdges); @@ -527,6 +529,10 @@ void MeshTopology::init() m_upperElementType = geometry::ElementType::HEXAHEDRON; else if (!tetrahedra.empty()) m_upperElementType = sofa::geometry::ElementType::TETRAHEDRON; + else if (!prisms.empty()) + m_upperElementType = sofa::geometry::ElementType::PRISM; + else if (!pyramids.empty()) + m_upperElementType = sofa::geometry::ElementType::PYRAMID; else if (!quads.empty()) m_upperElementType = sofa::geometry::ElementType::QUAD; else if (!triangles.empty()) @@ -565,6 +571,8 @@ void MeshTopology::init() countPoints(quads); countPoints(tetrahedra); countPoints(hexahedra); + countPoints(prisms); + countPoints(pyramids); nbPoints = n; } @@ -745,6 +753,34 @@ void MeshTopology::addHexa(Index p1, Index p2, Index p3, Index p4, Index p5, Ind if (p8 >= nbPoints) nbPoints = p8+1; } +void MeshTopology::addPrism(Index a, Index b, Index c, Index d, Index e, Index f) +{ + auto seqPrisms = helper::getWriteOnlyAccessor(d_seqPrisms); + seqPrisms.emplace_back(a, b, c, d, e, f); + + for (const auto v : {a,b,c,d,e,f}) + { + if (v >= nbPoints) + { + nbPoints = v+1; + } + } +} + +void MeshTopology::addPyramid(Index a, Index b, Index c, Index d, Index e) +{ + auto seqPyramids = helper::getWriteOnlyAccessor(d_seqPyramids); + seqPyramids.emplace_back(a, b, c, d, e); + + for (const auto v : {a,b,c,d,e}) + { + if (v >= nbPoints) + { + nbPoints = v+1; + } + } +} + void MeshTopology::addUV(SReal u, SReal v) { d_seqUVs.beginEdit()->push_back(type::Vec<2,SReal>((SReal)u, (SReal)v)); diff --git a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h index 4b4962809c3..7fb2c5d6fa1 100644 --- a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h +++ b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h @@ -250,6 +250,8 @@ class SOFA_COMPONENT_TOPOLOGY_CONTAINER_CONSTANT_API MeshTopology : public core: void addQuad( Index a, Index b, Index c, Index d ) override; void addTetra( Index a, Index b, Index c, Index d ) override; void addHexa( Index a, Index b, Index c, Index d, Index e, Index f, Index g, Index h ) override; + void addPrism( Index a, Index b, Index c, Index d, Index e, Index f ) override; + void addPyramid( Index a, Index b, Index c, Index d, Index e ) override; /// get the current revision of this mesh (use to detect changes) int getRevision() const override { return revision; } diff --git a/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.cpp b/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.cpp index bbca664a8ee..cca27efb3a2 100644 --- a/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.cpp +++ b/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.cpp @@ -355,6 +355,16 @@ void BaseMeshTopology::addHexa(Index, Index, Index, Index, Index, Index, Index, msg_error() << "addHexa() not supported."; } +void BaseMeshTopology::addPrism(Index a, Index b, Index c, Index d, Index e, Index f) +{ + msg_error() << "addPrism() not supported."; +} + +void BaseMeshTopology::addPyramid(Index a, Index b, Index c, Index d, Index e) +{ + msg_error() << "addPyramid() not supported."; +} + void BaseMeshTopology::reOrientateTriangle(TriangleID /*id*/) { msg_error() << "reOrientateTriangle() not supported."; diff --git a/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.h b/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.h index 78bedc85b30..7910c16640f 100644 --- a/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.h +++ b/Sofa/framework/Core/src/sofa/core/topology/BaseMeshTopology.h @@ -278,6 +278,8 @@ class SOFA_CORE_API BaseMeshTopology : public core::topology::Topology virtual void addQuad( Index a, Index b, Index c, Index d ); virtual void addTetra( Index a, Index b, Index c, Index d ); virtual void addHexa( Index a, Index b, Index c, Index d, Index e, Index f, Index g, Index h ); + virtual void addPrism( Index a, Index b, Index c, Index d, Index e, Index f ); + virtual void addPyramid( Index a, Index b, Index c, Index d, Index e ); /// @} /// get information about connexity of the mesh From 7bc7b9f22f3fb4e23a3a79b2fce150703d7a796f Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:17:32 +0100 Subject: [PATCH 2/4] [Topology] Refactor point counting logic in MeshTopology Centralize point counting into a reusable `countPoints` method, simplifying element addition functions and improving maintainability. --- .../container/constant/MeshTopology.cpp | 112 ++++++------------ .../container/constant/MeshTopology.h | 17 +++ 2 files changed, 52 insertions(+), 77 deletions(-) diff --git a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp index e8f4fd5387e..b0e14ac3a76 100644 --- a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp +++ b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.cpp @@ -551,30 +551,13 @@ void MeshTopology::init() // compute the number of points, if the topology is charged from the scene or if it was loaded from a MeshLoader without any points data. if (nbPoints==0) { - unsigned int n = 0; - const auto countPoints = [&n](const auto& seqElements) - { - for (const auto& element : seqElements) - { - for (const auto pointId : element) - { - if (n <= pointId) - { - n = 1 + pointId; - } - } - } - }; - - countPoints(edges); - countPoints(triangles); - countPoints(quads); - countPoints(tetrahedra); - countPoints(hexahedra); - countPoints(prisms); - countPoints(pyramids); - - nbPoints = n; + nbPoints = std::max(nbPoints, countPoints(edges)); + nbPoints = std::max(nbPoints, countPoints(triangles)); + nbPoints = std::max(nbPoints, countPoints(quads)); + nbPoints = std::max(nbPoints, countPoints(tetrahedra)); + nbPoints = std::max(nbPoints, countPoints(hexahedra)); + nbPoints = std::max(nbPoints, countPoints(prisms)); + nbPoints = std::max(nbPoints, countPoints(pyramids)); } @@ -702,83 +685,58 @@ void MeshTopology::addPoint(SReal px, SReal py, SReal pz) void MeshTopology::addEdge( Index a, Index b ) { - d_seqEdges.beginEdit()->push_back(Edge(a, b)); - d_seqEdges.endEdit(); - if (a >= nbPoints) nbPoints = a+1; - if (b >= nbPoints) nbPoints = b+1; + const Edge addedElement { a, b }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqEdges); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addTriangle( Index a, Index b, Index c ) { - d_seqTriangles.beginEdit()->push_back(Triangle(a, b, c) ); - d_seqTriangles.endEdit(); - if (a >= nbPoints) nbPoints = a+1; - if (b >= nbPoints) nbPoints = b+1; - if (c >= nbPoints) nbPoints = c+1; + const Triangle addedElement { a, b, c }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqTriangles); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addQuad(Index a, Index b, Index c, Index d) { - d_seqQuads.beginEdit()->push_back(Quad(a, b, c, d)); - d_seqQuads.endEdit(); - if (a >= nbPoints) nbPoints = a+1; - if (b >= nbPoints) nbPoints = b+1; - if (c >= nbPoints) nbPoints = c+1; - if (d >= nbPoints) nbPoints = d+1; + const Quad addedElement { a, b, c, d }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqQuads); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addTetra( Index a, Index b, Index c, Index d ) { - d_seqTetrahedra.beginEdit()->push_back(Tetra(a, b, c, d) ); - d_seqTetrahedra.endEdit(); - if (a >= nbPoints) nbPoints = a+1; - if (b >= nbPoints) nbPoints = b+1; - if (c >= nbPoints) nbPoints = c+1; - if (d >= nbPoints) nbPoints = d+1; + const Tetrahedron addedElement { a, b, c, d }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqTetrahedra); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addHexa(Index p1, Index p2, Index p3, Index p4, Index p5, Index p6, Index p7, Index p8) { - - d_seqHexahedra.beginEdit()->push_back(Hexa(p1, p2, p3, p4, p5, p6, p7, p8)); - - d_seqHexahedra.endEdit(); - if (p1 >= nbPoints) nbPoints = p1+1; - if (p2 >= nbPoints) nbPoints = p2+1; - if (p3 >= nbPoints) nbPoints = p3+1; - if (p4 >= nbPoints) nbPoints = p4+1; - if (p5 >= nbPoints) nbPoints = p5+1; - if (p6 >= nbPoints) nbPoints = p6+1; - if (p7 >= nbPoints) nbPoints = p7+1; - if (p8 >= nbPoints) nbPoints = p8+1; + const Hexahedron addedElement { p1, p2, p3, p4, p5, p6, p7, p8 }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqHexahedra); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addPrism(Index a, Index b, Index c, Index d, Index e, Index f) { - auto seqPrisms = helper::getWriteOnlyAccessor(d_seqPrisms); - seqPrisms.emplace_back(a, b, c, d, e, f); - - for (const auto v : {a,b,c,d,e,f}) - { - if (v >= nbPoints) - { - nbPoints = v+1; - } - } + const Prism addedElement { a, b, c, d, e, f }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqPrisms); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addPyramid(Index a, Index b, Index c, Index d, Index e) { - auto seqPyramids = helper::getWriteOnlyAccessor(d_seqPyramids); - seqPyramids.emplace_back(a, b, c, d, e); - - for (const auto v : {a,b,c,d,e}) - { - if (v >= nbPoints) - { - nbPoints = v+1; - } - } + const Pyramid addedElement { a, b, c, d, e }; + auto seqElements = helper::getWriteOnlyAccessor(d_seqPyramids); + seqElements.push_back(addedElement); + nbPoints = std::max(nbPoints, countPoints(std::array{addedElement})); } void MeshTopology::addUV(SReal u, SReal v) diff --git a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h index 7fb2c5d6fa1..281f5ceeca1 100644 --- a/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h +++ b/Sofa/Component/Topology/Container/Constant/src/sofa/component/topology/container/constant/MeshTopology.h @@ -310,6 +310,23 @@ class SOFA_COMPONENT_TOPOLOGY_CONTAINER_CONSTANT_API MeshTopology : public core: protected: Size nbPoints; + template + Size countPoints(const ElementContainer& seqElements) + { + Size n = 0; + for (const auto& element : seqElements) + { + for (const auto pointId : element) + { + if (n <= pointId) + { + n = 1 + pointId; + } + } + } + return n; + } + bool validTetrahedra; bool validHexahedra; From 5360377d3c608ef718ea2de978dbabec510de1a8 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:38:25 +0100 Subject: [PATCH 3/4] [Topology] Refactor MeshTopology test to use shared setup method Introduce `doSetUp` for common initialization, simplifying and improving test code readability. --- .../Constant/tests/MeshTopology_test.cpp | 39 ++++++++++++------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp b/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp index 47d3f72da70..6ca839e440d 100644 --- a/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp +++ b/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp @@ -46,7 +46,14 @@ using namespace sofa::testing; class MeshTopology_test : public BaseTest { public: - bool testEmptyContainer(); + void doSetUp() override + { + m_topo = sofa::core::objectmodel::New< MeshTopology >(); + ASSERT_TRUE(m_topo != nullptr); + ASSERT_EQ(m_topo->getNbPoints(), 0u); + } + + bool testEmptyContainer() const; bool testHexahedronTopology(); bool testTetrahedronTopology(); @@ -56,30 +63,32 @@ class MeshTopology_test : public BaseTest bool testEdgeTopology(); bool testVertexTopology(); + +protected: + + MeshTopology::SPtr m_topo { nullptr }; }; -bool MeshTopology_test::testEmptyContainer() +bool MeshTopology_test::testEmptyContainer() const { - const MeshTopology::SPtr topoCon = sofa::core::objectmodel::New< MeshTopology >(); - - EXPECT_EQ(topoCon->getNbHexahedra(), 0); - EXPECT_EQ(topoCon->getHexahedra().size(), 0); + EXPECT_EQ(m_topo->getNbHexahedra(), 0); + EXPECT_EQ(m_topo->getHexahedra().size(), 0); - EXPECT_EQ(topoCon->getNbHexahedra(), 0); - EXPECT_EQ(topoCon->getTetrahedra().size(), 0); + EXPECT_EQ(m_topo->getNbHexahedra(), 0); + EXPECT_EQ(m_topo->getTetrahedra().size(), 0); - EXPECT_EQ(topoCon->getNbQuads(), 0); - EXPECT_EQ(topoCon->getQuads().size(), 0); + EXPECT_EQ(m_topo->getNbQuads(), 0); + EXPECT_EQ(m_topo->getQuads().size(), 0); - EXPECT_EQ(topoCon->getNbTriangles(), 0); - EXPECT_EQ(topoCon->getTriangles().size(), 0); + EXPECT_EQ(m_topo->getNbTriangles(), 0); + EXPECT_EQ(m_topo->getTriangles().size(), 0); - EXPECT_EQ(topoCon->getNbEdges(), 0); - EXPECT_EQ(topoCon->getEdges().size(), 0); + EXPECT_EQ(m_topo->getNbEdges(), 0); + EXPECT_EQ(m_topo->getEdges().size(), 0); - EXPECT_EQ(topoCon->getNbPoints(), 0); + EXPECT_EQ(m_topo->getNbPoints(), 0); return true; } From 1a32bc3b137be453ea25fdd742223fc03b7e41a7 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:46:12 +0100 Subject: [PATCH 4/4] [Topology] Add unit tests for adding elements in MeshTopology Add tests for adding point, edge, triangle, quad, tetrahedron, hexahedron, prism, and pyramid elements to verify correct functionality and point counting. --- .../Constant/tests/MeshTopology_test.cpp | 116 +++++++++++++++++- 1 file changed, 115 insertions(+), 1 deletion(-) diff --git a/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp b/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp index 6ca839e440d..e580a134430 100644 --- a/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp +++ b/Sofa/Component/Topology/Container/Constant/tests/MeshTopology_test.cpp @@ -64,6 +64,15 @@ class MeshTopology_test : public BaseTest bool testEdgeTopology(); bool testVertexTopology(); + void testAddPoint() const; + void testAddEdge() const; + void testAddTriangle() const; + void testAddQuad() const; + void testAddTetrahedron() const; + void testAddHexahedron() const; + void testAddPrism() const; + void testAddPyramid() const; + protected: MeshTopology::SPtr m_topo { nullptr }; @@ -585,11 +594,77 @@ bool MeshTopology_test::testVertexTopology() return true; } +void MeshTopology_test::testAddPoint() const +{ + const auto before = m_topo->getNbPoints(); + m_topo->addPoint(0.0, 0.0, 0.0); + EXPECT_EQ(m_topo->getNbPoints(), before + 1); +} + +void MeshTopology_test::testAddEdge() const +{ + m_topo->addEdge(0, 1); + EXPECT_EQ(m_topo->getNbPoints(), 2u); + + m_topo->addEdge(2, 10); + EXPECT_EQ(m_topo->getNbPoints(), 11u); +} + +void MeshTopology_test::testAddTriangle() const +{ + m_topo->addTriangle(0, 1, 2); + EXPECT_EQ(m_topo->getNbPoints(), 3u); + + m_topo->addTriangle(5, 3, 4); + EXPECT_EQ(m_topo->getNbPoints(), 6u); +} + +void MeshTopology_test::testAddQuad() const +{ + m_topo->addQuad(0, 1, 2, 3); + EXPECT_EQ(m_topo->getNbPoints(), 4u); + + m_topo->addQuad(7, 6, 5, 4); + EXPECT_EQ(m_topo->getNbPoints(), 8u); +} + +void MeshTopology_test::testAddTetrahedron() const +{ + m_topo->addTetra(0, 1, 2, 3); + EXPECT_EQ(m_topo->getNbPoints(), 4u); + m_topo->addTetra(2, 5, 9, 3); + EXPECT_EQ(m_topo->getNbPoints(), 10u); +} +void MeshTopology_test::testAddHexahedron() const +{ + m_topo->addHexa(0, 1, 2, 3, 4, 5, 6, 7); + EXPECT_EQ(m_topo->getNbPoints(), 8u); -TEST_F(MeshTopology_test, testEmptyContainer) + m_topo->addHexa(10, 11, 12, 13, 14, 15, 16, 31); + EXPECT_EQ(m_topo->getNbPoints(), 32u); +} + +void MeshTopology_test::testAddPrism() const +{ + m_topo->addPrism(0, 1, 2, 3, 4, 5); + EXPECT_EQ(m_topo->getNbPoints(), 6u); + + m_topo->addPrism(4, 8, 6, 7, 2, 12); + EXPECT_EQ(m_topo->getNbPoints(), 13u); +} + +void MeshTopology_test::testAddPyramid() const { + m_topo->addPyramid(0, 1, 2, 3, 4); + EXPECT_EQ(m_topo->getNbPoints(), 5u); + + m_topo->addPyramid(10, 11, 12, 13, 20); + EXPECT_EQ(m_topo->getNbPoints(), 21u); +} + +TEST_F(MeshTopology_test, testEmptyContainer) { ASSERT_TRUE(testEmptyContainer()); } @@ -624,6 +699,45 @@ TEST_F(MeshTopology_test, testEdgeTopology) // ASSERT_TRUE(testVertexTopology()); //} +TEST_F(MeshTopology_test, testAddPoint) +{ + testAddPoint(); +} + +TEST_F(MeshTopology_test, testAddEdge) +{ + testAddEdge(); +} + +TEST_F(MeshTopology_test, testAddTriangle) +{ + testAddTriangle(); +} + +TEST_F(MeshTopology_test, testAddQuad) +{ + testAddQuad(); +} + +TEST_F(MeshTopology_test, testAddTetrahedron) +{ + testAddTetrahedron(); +} + +TEST_F(MeshTopology_test, testAddHexahedron) +{ + testAddHexahedron(); +} + +TEST_F(MeshTopology_test, testAddPrism) +{ + testAddPrism(); +} + +TEST_F(MeshTopology_test, testAddPyramid) +{ + testAddPyramid(); +} // TODO epernod 2018-07-05: test element on Border