diff --git a/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/HexahedronSetTopologyContainer.h b/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/HexahedronSetTopologyContainer.h index 257ffffed58..731fc121bcf 100644 --- a/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/HexahedronSetTopologyContainer.h +++ b/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/HexahedronSetTopologyContainer.h @@ -433,10 +433,10 @@ class SOFA_SOFABASETOPOLOGY_API HexahedronSetTopologyContainer : public QuadSetT /// force the creation of quads Data d_createQuadArray; -protected: - /// provides the set of hexahedra. + /// provides the set of hexahedra. Data< sofa::helper::vector > d_hexahedron; +protected: /// provides the set of edges for each hexahedron. sofa::helper::vector m_edgesInHexahedron; diff --git a/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/QuadSetTopologyContainer.h b/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/QuadSetTopologyContainer.h index a41e24e6962..d6a554b4d30 100644 --- a/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/QuadSetTopologyContainer.h +++ b/SofaKernel/modules/SofaBaseTopology/src/SofaBaseTopology/QuadSetTopologyContainer.h @@ -300,11 +300,11 @@ class SOFA_SOFABASETOPOLOGY_API QuadSetTopologyContainer : public EdgeSetTopolog void cleanQuadTopologyFromDirty(); const bool& isQuadTopologyDirty() {return m_quadTopologyDirty;} -protected: - +public: /// provides the set of quads. Data< sofa::helper::vector > d_quad; +protected: /// provides the 4 edges in each quad. sofa::helper::vector m_edgesInQuad; diff --git a/modules/SofaMiscTopology/CMakeLists.txt b/modules/SofaMiscTopology/CMakeLists.txt index b352443fbfd..2789f96e410 100644 --- a/modules/SofaMiscTopology/CMakeLists.txt +++ b/modules/SofaMiscTopology/CMakeLists.txt @@ -14,9 +14,11 @@ set(SOURCE_FILES list(APPEND HEADER_FILES ${SOFAMISCTOPOLOGY_SRC}/TopologicalChangeProcessor.h + ${SOFAMISCTOPOLOGY_SRC}/TopologyChecker.h ) list(APPEND SOURCE_FILES ${SOFAMISCTOPOLOGY_SRC}/TopologicalChangeProcessor.cpp + ${SOFAMISCTOPOLOGY_SRC}/TopologyChecker.cpp ) sofa_find_package(SofaBase REQUIRED) # SofaBaseTopology diff --git a/modules/SofaMiscTopology/SofaMiscTopology_test/CMakeLists.txt b/modules/SofaMiscTopology/SofaMiscTopology_test/CMakeLists.txt index effc55452b1..ff502888089 100644 --- a/modules/SofaMiscTopology/SofaMiscTopology_test/CMakeLists.txt +++ b/modules/SofaMiscTopology/SofaMiscTopology_test/CMakeLists.txt @@ -6,9 +6,11 @@ project(SofaMiscTopology_test) sofa_find_package(SofaMiscTopology REQUIRED) set(HEADER_FILES) -set(SOURCE_FILES TopologicalChangeProcessor_test.cpp) +set(SOURCE_FILES + TopologicalChangeProcessor_test.cpp + TopologyChecker_test.cpp) -add_definitions("-DSOFAMISCTOPOLOGY_TEST_SCENES_DIR=\"${CMAKE_SOURCE_DIR}/examples/Components/topology/TopologicalModifiers\"") +add_definitions("-DSOFAMISCTOPOLOGY_TEST_SCENES_DIR=\"${CMAKE_SOURCE_DIR}/examples/Components/topology\"") add_executable(${PROJECT_NAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} Sofa.Testing SofaMiscTopology) diff --git a/modules/SofaMiscTopology/SofaMiscTopology_test/TopologicalChangeProcessor_test.cpp b/modules/SofaMiscTopology/SofaMiscTopology_test/TopologicalChangeProcessor_test.cpp index 66515a76871..4014f570700 100644 --- a/modules/SofaMiscTopology/SofaMiscTopology_test/TopologicalChangeProcessor_test.cpp +++ b/modules/SofaMiscTopology/SofaMiscTopology_test/TopologicalChangeProcessor_test.cpp @@ -51,7 +51,6 @@ struct TopologicalChangeProcessor_test: public BaseSimulationTest sofa::simpleapi::importPlugin("SofaComponentAll"); // Load the scene from the xml file std::string filePath = std::string(SOFAMISCTOPOLOGY_TEST_SCENES_DIR) + "/" + m_fileName; - std::cout << "filePath: " << filePath << std::endl; m_instance = BaseSimulationTest::SceneInstance(); // Load scene m_instance.loadSceneFile(filePath); @@ -83,7 +82,7 @@ struct InciseProcessor_test : TopologicalChangeProcessor_test { InciseProcessor_test() : TopologicalChangeProcessor_test() { - m_fileName = "IncisionTrianglesProcess.scn"; + m_fileName = "/TopologicalModifiers/IncisionTrianglesProcess.scn"; } bool testTopologyChanges() override @@ -136,7 +135,7 @@ struct RemoveTriangleProcessor_test : TopologicalChangeProcessor_test { RemoveTriangleProcessor_test() : TopologicalChangeProcessor_test() { - m_fileName = "RemovingTrianglesProcess.scn"; + m_fileName = "/TopologicalModifiers/RemovingTrianglesProcess.scn"; } bool testTopologyChanges() override @@ -189,7 +188,7 @@ struct AddTriangleProcessor_test : TopologicalChangeProcessor_test { AddTriangleProcessor_test() : TopologicalChangeProcessor_test() { - m_fileName = "AddingTrianglesProcess.scn"; + m_fileName = "/TopologicalModifiers/AddingTrianglesProcess.scn"; } bool testTopologyChanges() override @@ -243,7 +242,7 @@ struct RemoveTetrahedronProcessor_test : TopologicalChangeProcessor_test { RemoveTetrahedronProcessor_test() : TopologicalChangeProcessor_test() { - m_fileName = "RemovingTetraProcess.scn"; + m_fileName = "/TopologicalModifiers/RemovingTetraProcess.scn"; } bool testTopologyChanges() override @@ -299,7 +298,7 @@ struct AddTetrahedronProcessor_test : TopologicalChangeProcessor_test { AddTetrahedronProcessor_test() : TopologicalChangeProcessor_test() { - m_fileName = "AddingTetraProcess.scn"; + m_fileName = "/TopologicalModifiers/AddingTetraProcess.scn"; } bool testTopologyChanges() override diff --git a/modules/SofaMiscTopology/SofaMiscTopology_test/TopologyChecker_test.cpp b/modules/SofaMiscTopology/SofaMiscTopology_test/TopologyChecker_test.cpp new file mode 100644 index 00000000000..a655676ba1e --- /dev/null +++ b/modules/SofaMiscTopology/SofaMiscTopology_test/TopologyChecker_test.cpp @@ -0,0 +1,1370 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +using sofa::helper::testing::BaseSimulationTest; + +namespace +{ + +using namespace sofa::component::topology; +using namespace sofa::core::topology; +using namespace sofa::simulation; +using sofa::component::misc::TopologyChecker; + +/** Test TopologyChecker class on different valid and unvalid topology containers + */ + +struct TopologyChecker_test: public BaseSimulationTest +{ + /// Store SceneInstance + BaseSimulationTest::SceneInstance m_instance; + + /// Name of the file to load + std::string m_fileName = ""; + + /// Node containing topology + Node::SPtr m_topoNode = nullptr; + + /// Method use at start to load the scene file + void SetUp() + { + sofa::simpleapi::importPlugin("SofaComponentAll"); + // Load the scene from the xml file + std::string filePath = std::string(SOFAMISCTOPOLOGY_TEST_SCENES_DIR) + "/" + m_fileName; + m_instance = BaseSimulationTest::SceneInstance(); + // Load scene + m_instance.loadSceneFile(filePath); + // Init scene + m_instance.initScene(); + + // Test if root is not null + if(!m_instance.root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return; + } + } + + /// Method to really do the test per type of topology container, to be implemented by child classes + virtual bool testValidTopology() = 0; + + /// Method to test invalid topology container, to be implemented by child classes + virtual bool testInvalidContainer() = 0; + + /// Unload the scene + void TearDown() + { + if (m_instance.root !=nullptr) + sofa::simulation::getSimulation()->unload(m_instance.root); + } + +}; + + +struct EdgeTopologyChecker_test : TopologyChecker_test +{ + EdgeTopologyChecker_test() : TopologyChecker_test() + { + m_fileName = "TopologicalModifiers/RemovingTriangle2EdgeProcess.scn"; + } + + + EdgeSetTopologyContainer* loadTopo() + { + Node::SPtr root = m_instance.root; + if (!root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return nullptr; + } + + Node::SPtr nodeTopoTri = root.get()->getChild("SquareGravity"); + if (!nodeTopoTri) + { + ADD_FAILURE() << "Error 'SquareGravity' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + m_topoNode = nodeTopoTri->getChild("Edge Mesh"); + if (!m_topoNode) + { + ADD_FAILURE() << "Error 'Edge Mesh' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + EdgeSetTopologyContainer* topoCon = dynamic_cast(m_topoNode->getMeshTopology()); + if (topoCon == nullptr) + { + ADD_FAILURE() << "Error: EdgeSetTopologyContainer not found in 'Edge Mesh' Node, in scene: " << m_fileName << std::endl; + return nullptr; + } + + return topoCon; + } + + + bool testUnsetTopologyLink() + { + EdgeSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}}); + + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + return checker->l_topology.get() == topoCon; + } + + + bool testValidTopology() override + { + EdgeSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + // check topology at start + EXPECT_EQ(topoCon->getNbTriangles(), 0); + EXPECT_EQ(topoCon->getNbEdges(), 96); + EXPECT_EQ(topoCon->getNbPoints(), 774); + + EXPECT_EQ(checker->checkTopology(), true); + + //// to test incise animates the scene at least 1.2s + for (int i = 0; i < 20; i++) + { + m_instance.simulate(0.01); + } + + EXPECT_EQ(topoCon->getNbTriangles(), 0); + EXPECT_EQ(topoCon->getNbEdges(), 333); + EXPECT_EQ(topoCon->getNbPoints(), 261); + + return checker->checkTopology() == true; + } + + + bool testInvalidContainer() override + { + EdgeSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + + // mix edge + edges[0][0] = edges[0][1]; + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + + return checker->checkEdgeTopology() == false; + } + + + bool testInvalidEdge2VertexContainer() + { + EdgeSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + + // Add edges without updating cross container + edges.push_back(Topology::Edge(0, 10)); + EXPECT_EQ(topoCon->getNbEdges(), 97); + + // container should be ok + EXPECT_EQ(checker->checkEdgeContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + return checker->checkEdgeToVertexCrossContainer() == false; + } +}; + + +struct TriangleTopologyChecker_test : TopologyChecker_test +{ + TriangleTopologyChecker_test() : TopologyChecker_test() + { + m_fileName = "TopologicalModifiers/RemovingTrianglesProcess.scn"; + } + + + TriangleSetTopologyContainer* loadTopo() + { + Node::SPtr root = m_instance.root; + if (!root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return nullptr; + } + + m_topoNode = root.get()->getChild("SquareGravity"); + if (!m_topoNode) + { + ADD_FAILURE() << "Error 'SquareGravity' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + TriangleSetTopologyContainer* topoCon = dynamic_cast(m_topoNode->getMeshTopology()); + if (topoCon == nullptr) + { + ADD_FAILURE() << "Error: TriangleSetTopologyContainer not found in 'SquareGravity' Node, in scene: " << m_fileName << std::endl; + return nullptr; + } + + return topoCon; + } + + + bool testValidTopology() override + { + TriangleSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + // check topology at start + EXPECT_EQ(topoCon->getNbTriangles(), 1450); + EXPECT_EQ(topoCon->getNbEdges(), 2223); + EXPECT_EQ(topoCon->getNbPoints(), 774); + + EXPECT_EQ(checker->checkTopology(), true); + + //// to test incise animates the scene at least 1.2s + for (int i = 0; i < 20; i++) + { + m_instance.simulate(0.01); + } + + EXPECT_EQ(topoCon->getNbTriangles(), 145); + EXPECT_EQ(topoCon->getNbEdges(), 384); + EXPECT_EQ(topoCon->getNbPoints(), 261); + + return checker->checkTopology() == true; + } + + bool testInvalidContainer() override + { + TriangleSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto triangles = sofa::helper::getWriteAccessor(topoCon->d_triangle); + + // mix triangle + triangles[0][0] = triangles[0][1]; + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + + return checker->checkTriangleTopology() == false; + } + + + bool testInvalidTriangle2EdgeContainer() + { + TriangleSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto triangles = sofa::helper::getWriteAccessor(topoCon->d_triangle); + + // Add triangle without updating cross container + triangles.push_back(Topology::Triangle(0, 10, 100)); + + // container should be ok + EXPECT_EQ(checker->checkTriangleContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), false); + // restore good container + triangles.resize(triangles.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), true); + + + // Mix some triangle vertices + int tmp = triangles[0][0]; + triangles[0][0] = triangles[10][0]; + triangles[10][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), false); + // restore good container + triangles[10][0] = triangles[0][0]; + triangles[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), true); + + // Mix some triangles + Topology::Triangle tmpT = triangles[0]; + triangles[0] = triangles[10]; + triangles[10] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), false); + // restore good container + triangles[10] = triangles[0]; + triangles[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTriangleToEdgeCrossContainer(), true); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + Topology::Edge tmpE = edges[0]; + edges[0] = edges[10]; + edges[10] = tmpE; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTopology(), false); + + return true; + } + + + bool testInvalidTriangle2VertexContainer() + { + TriangleSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto triangles = sofa::helper::getWriteAccessor(topoCon->d_triangle); + + // Add triangle without updating cross container + triangles.push_back(Topology::Triangle(0, 10, 100)); + + // container should be ok + EXPECT_EQ(checker->checkTriangleContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), false); + // restore good container + triangles.resize(triangles.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), true); + + + // Mix some triangle vertices + int tmp = triangles[0][0]; + triangles[0][0] = triangles[10][0]; + triangles[10][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), false); + // restore good container + triangles[10][0] = triangles[0][0]; + triangles[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), true); + + // Mix some triangles + Topology::Triangle tmpT = triangles[0]; + triangles[0] = triangles[10]; + triangles[10] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), false); + // restore good container + triangles[10] = triangles[0]; + triangles[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTriangleToVertexCrossContainer(), true); + + return true; + } +}; + + +struct QuadTopologyChecker_test : TopologyChecker_test +{ + QuadTopologyChecker_test() : TopologyChecker_test() + { + m_fileName = "TopologicalModifiers/RemovingQuad2TriangleProcess.scn"; + } + + + QuadSetTopologyContainer* loadTopo() + { + Node::SPtr root = m_instance.root; + if (!root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return nullptr; + } + + m_topoNode = root.get()->getChild("Q"); + if (!m_topoNode) + { + ADD_FAILURE() << "Error 'Q' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + QuadSetTopologyContainer* topoCon = dynamic_cast(m_topoNode->getMeshTopology()); + if (topoCon == nullptr) + { + ADD_FAILURE() << "Error: QuadSetTopologyContainer not found in 'Q' Node, in scene: " << m_fileName << std::endl; + return nullptr; + } + + return topoCon; + } + + + bool testValidTopology() override + { + QuadSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + // check topology at start + EXPECT_EQ(topoCon->getNbQuads(), 9); + EXPECT_EQ(topoCon->getNbEdges(), 24); + EXPECT_EQ(topoCon->getNbPoints(), 16); + + EXPECT_EQ(checker->checkTopology(), true); + + //// to test incise animates the scene at least 1.2s + for (int i = 0; i < 40; i++) + { + m_instance.simulate(0.01); + } + + EXPECT_EQ(topoCon->getNbQuads(), 7); + EXPECT_EQ(topoCon->getNbEdges(), 20); + EXPECT_EQ(topoCon->getNbPoints(), 14); + + return checker->checkTopology() == true; + } + + + bool testInvalidContainer() override + { + QuadSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto quads = sofa::helper::getWriteAccessor(topoCon->d_quad); + + // mix triangle + quads[0][0] = quads[0][1]; + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + + return checker->checkQuadTopology() == false; + } + + + bool testInvalidQuad2EdgeContainer() + { + QuadSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto quads = sofa::helper::getWriteAccessor(topoCon->d_quad); + + // Add triangle without updating cross container + quads.push_back(Topology::Quad(0, 2, 4, 8)); + + // container should be ok + EXPECT_EQ(checker->checkQuadContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), false); + // restore good container + quads.resize(quads.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), true); + + + // Mix some quad vertices + int tmp = quads[0][0]; + quads[0][0] = quads[4][0]; + quads[4][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), false); + // restore good container + quads[4][0] = quads[0][0]; + quads[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), true); + + // Mix some quads + Topology::Quad tmpT = quads[0]; + quads[0] = quads[4]; + quads[4] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), false); + // restore good container + quads[4] = quads[0]; + quads[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkQuadToEdgeCrossContainer(), true); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + Topology::Edge tmpE = edges[0]; + edges[0] = edges[10]; + edges[10] = tmpE; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTopology(), false); + + return true; + } + + + bool testInvalidQuad2VertexContainer() + { + QuadSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto quads = sofa::helper::getWriteAccessor(topoCon->d_quad); + + // Add triangle without updating cross container + quads.push_back(Topology::Quad(0, 2, 4, 8)); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), false); + // restore good container + quads.resize(quads.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), true); + + + // Mix some quad vertices + int tmp = quads[0][0]; + quads[0][0] = quads[4][0]; + quads[4][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), false); + // restore good container + quads[4][0] = quads[0][0]; + quads[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), true); + + // Mix some quads + Topology::Quad tmpT = quads[0]; + quads[0] = quads[4]; + quads[4] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), false); + // restore good container + quads[4] = quads[0]; + quads[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkQuadToVertexCrossContainer(), true); + + return true; + } +}; + + + +struct TetrahedronTopologyChecker_test : TopologyChecker_test +{ + TetrahedronTopologyChecker_test() : TopologyChecker_test() + { + m_fileName = "TopologicalModifiers/RemovingTetraProcess.scn"; + } + + + TetrahedronSetTopologyContainer* loadTopo() + { + Node::SPtr root = m_instance.root; + if (!root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return nullptr; + } + + m_topoNode = root.get()->getChild("TT"); + if (!m_topoNode) + { + ADD_FAILURE() << "Error 'TT' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + TetrahedronSetTopologyContainer* topoCon = dynamic_cast(m_topoNode->getMeshTopology()); + if (topoCon == nullptr) + { + ADD_FAILURE() << "Error: TetrahedronSetTopologyContainer not found in 'TT' Node, in scene: " << m_fileName << std::endl; + return nullptr; + } + + return topoCon; + } + + + bool testValidTopology() override + { + TetrahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + // check topology at start + EXPECT_EQ(topoCon->getNbTetrahedra(), 44); + EXPECT_EQ(topoCon->getNbTriangles(), 112); + EXPECT_EQ(topoCon->getNbEdges(), 93); + EXPECT_EQ(topoCon->getNbPoints(), 26); + + EXPECT_EQ(checker->checkTopology(), true); + + //// to test incise animates the scene at least 1.2s + for (int i = 0; i < 20; i++) + { + m_instance.simulate(0.01); + } + + EXPECT_EQ(topoCon->getNbTetrahedra(), 34); + EXPECT_EQ(topoCon->getNbTriangles(), 97); + EXPECT_EQ(topoCon->getNbEdges(), 86); + EXPECT_EQ(topoCon->getNbPoints(), 25); + + return checker->checkTopology() == true; + } + + bool testInvalidContainer() override + { + TetrahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto tetra = sofa::helper::getWriteAccessor(topoCon->d_tetrahedron); + + // mix triangle + tetra[0][0] = tetra[0][1]; + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + + return checker->checkTetrahedronTopology() == false; + } + + bool testInvalidTetrahedron2TriangleContainer() + { + TetrahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto tetra = sofa::helper::getWriteAccessor(topoCon->d_tetrahedron); + + // Add triangle without updating cross container + tetra.push_back(Topology::Tetrahedron(0, 2, 8, 12)); + + // container should be ok + EXPECT_EQ(checker->checkTetrahedronContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), false); + // restore good container + tetra.resize(tetra.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), true); + + // Mix some tetrahedron vertices + int tmp = tetra[0][0]; + tetra[0][0] = tetra[10][0]; + tetra[10][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), false); + // restore good container + tetra[10][0] = tetra[0][0]; + tetra[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), true); + + // Mix some triangles + Topology::Tetrahedron tmpT = tetra[0]; + tetra[0] = tetra[10]; + tetra[10] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), false); + // restore good container + tetra[10] = tetra[0]; + tetra[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToTriangleCrossContainer(), true); + + auto triangles = sofa::helper::getWriteAccessor(topoCon->d_triangle); + // Mix some triangles + Topology::Triangle tmpTri = triangles[0]; + triangles[0] = triangles[10]; + triangles[10] = tmpTri; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTopology(), false); + // restore good container + triangles[10] = triangles[0]; + triangles[0] = tmpTri; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTopology(), true); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + Topology::Edge tmpE = edges[0]; + edges[0] = edges[10]; + edges[10] = tmpE; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTopology(), false); + + return true; + } + + bool testInvalidTetrahedron2EdgeContainer() + { + TetrahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto tetra = sofa::helper::getWriteAccessor(topoCon->d_tetrahedron); + + // Add triangle without updating cross container + tetra.push_back(Topology::Tetrahedron(0, 2, 8, 12)); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), false); + // restore good container + tetra.resize(tetra.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), true); + + // Mix some tetrahedron vertices + int tmp = tetra[0][0]; + tetra[0][0] = tetra[10][0]; + tetra[10][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), false); + // restore good container + tetra[10][0] = tetra[0][0]; + tetra[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), true); + + // Mix some triangles + Topology::Tetrahedron tmpT = tetra[0]; + tetra[0] = tetra[10]; + tetra[10] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), false); + // restore good container + tetra[10] = tetra[0]; + tetra[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToEdgeCrossContainer(), true); + + return true; + } + + bool testInvalidTetrahedron2VertexContainer() + { + TetrahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto tetra = sofa::helper::getWriteAccessor(topoCon->d_tetrahedron); + + // Add triangle without updating cross container + tetra.push_back(Topology::Tetrahedron(0, 2, 8, 12)); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), false); + // restore good container + tetra.resize(tetra.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), true); + + // Mix some tetrahedron vertices + int tmp = tetra[0][0]; + tetra[0][0] = tetra[10][0]; + tetra[10][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), false); + // restore good container + tetra[10][0] = tetra[0][0]; + tetra[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), true); + + // Mix some triangles + Topology::Tetrahedron tmpT = tetra[0]; + tetra[0] = tetra[10]; + tetra[10] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), false); + // restore good container + tetra[10] = tetra[0]; + tetra[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkTetrahedronToVertexCrossContainer(), true); + + return true; + } +}; + + + +struct HexahedronTopologyChecker_test : TopologyChecker_test +{ + HexahedronTopologyChecker_test() : TopologyChecker_test() + { + m_fileName = "TopologicalModifiers/RemovingHexa2QuadProcess.scn"; + } + + + HexahedronSetTopologyContainer* loadTopo() + { + Node::SPtr root = m_instance.root; + if (!root) + { + ADD_FAILURE() << "Error while loading the scene: " << m_fileName << std::endl; + return nullptr; + } + + m_topoNode = root.get()->getChild("H"); + if (!m_topoNode) + { + ADD_FAILURE() << "Error 'H' Node not found in scene: " << m_fileName << std::endl; + return nullptr; + } + + HexahedronSetTopologyContainer* topoCon = dynamic_cast(m_topoNode->getMeshTopology()); + if (topoCon == nullptr) + { + ADD_FAILURE() << "Error: HexahedronSetTopologyContainer not found in 'H' Node, in scene: " << m_fileName << std::endl; + return nullptr; + } + + return topoCon; + } + + + bool testValidTopology() override + { + HexahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + // check topology at start + EXPECT_EQ(topoCon->getNbHexahedra(), 9); + EXPECT_EQ(topoCon->getNbQuads(), 42); + EXPECT_EQ(topoCon->getNbEdges(), 64); + EXPECT_EQ(topoCon->getNbPoints(), 32); + + EXPECT_EQ(checker->checkTopology(), true); + + // to test incise animates the scene at least 1.2s + for (int i = 0; i < 41; i++) + { + m_instance.simulate(0.01); + } + + EXPECT_EQ(topoCon->getNbHexahedra(), 5); + EXPECT_EQ(topoCon->getNbQuads(), 25); + EXPECT_EQ(topoCon->getNbEdges(), 41); + EXPECT_EQ(topoCon->getNbPoints(), 22); + + return checker->checkTopology() == true; + } + + bool testInvalidContainer() override + { + HexahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + auto hexahedra = sofa::helper::getWriteAccessor(topoCon->d_hexahedron); + + // mix triangle + hexahedra[0][0] = hexahedra[0][1]; + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + + return checker->checkHexahedronTopology() == false; + } + + bool testInvalidHexahedron2QuadContainer() + { + HexahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + sofa::helper::WriteAccessor< sofa::core::objectmodel::Data > > hexa = topoCon->d_hexahedron; + + // Add triangle without updating cross container + hexa.push_back(Topology::Hexahedron(0, 2, 4, 12, 14, 18, 20, 22)); + + // container should be ok + EXPECT_EQ(checker->checkHexahedronContainer(), true); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), false); + // restore good container + hexa.resize(hexa.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), true); + + // Mix some hexahedron vertices + int tmp = hexa[0][0]; + hexa[0][0] = hexa[6][0]; + hexa[6][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), false); + // restore good container + hexa[6][0] = hexa[0][0]; + hexa[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), true); + + // Mix some triangles + Topology::Hexahedron tmpT = hexa[0]; + hexa[0] = hexa[6]; + hexa[6] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), false); + // restore good container + hexa[6] = hexa[0]; + hexa[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), true); + + auto quads = sofa::helper::getWriteAccessor(topoCon->d_quad); + // Mix some quads + Topology::Quad tmpTri = quads[0]; + quads[0] = quads[10]; + quads[10] = tmpTri; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), false); + // restore good container + quads[10] = quads[0]; + quads[0] = tmpTri; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToQuadCrossContainer(), true); + + return true; + } + + bool testInvalidHexahedron2EdgeContainer() + { + HexahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + sofa::helper::WriteAccessor< sofa::core::objectmodel::Data > > hexa = topoCon->d_hexahedron; + + // Add triangle without updating cross container + hexa.push_back(Topology::Hexahedron(0, 2, 4, 12, 14, 18, 20, 22)); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), false); + // restore good container + hexa.resize(hexa.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), true); + + // Mix some hexahedron vertices + int tmp = hexa[0][0]; + hexa[0][0] = hexa[6][0]; + hexa[6][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), false); + // restore good container + hexa[6][0] = hexa[0][0]; + hexa[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), true); + + // Mix some triangles + Topology::Hexahedron tmpT = hexa[0]; + hexa[0] = hexa[6]; + hexa[6] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), false); + // restore good container + hexa[6] = hexa[0]; + hexa[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), true); + + auto edges = sofa::helper::getWriteAccessor(topoCon->d_edge); + Topology::Edge tmpE = edges[0]; + edges[0] = edges[10]; + edges[10] = tmpE; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToEdgeCrossContainer(), false); + + return true; + } + + bool testInvalidHexahedron2VertexContainer() + { + HexahedronSetTopologyContainer* topoCon = loadTopo(); + if (topoCon == nullptr) + return false; + + std::string topoLink = "@" + topoCon->getName(); + auto obj = sofa::simpleapi::createObject(m_topoNode, "TopologyChecker", { + {"name", "checker"}, + {"topology", topoLink} + }); + TopologyChecker* checker = dynamic_cast(obj.get()); + checker->init(); + + sofa::helper::WriteAccessor< sofa::core::objectmodel::Data > > hexa = topoCon->d_hexahedron; + + // Add triangle without updating cross container + hexa.push_back(Topology::Hexahedron(0, 2, 4, 12, 14, 18, 20, 22)); + + // Topology checher should detect an error + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), false); + // restore good container + hexa.resize(hexa.size() - 1); + EXPECT_MSG_NOEMIT(Error); // reset no emit error for next true negative test + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), true); + + // Mix some hexahedron vertices + int tmp = hexa[0][0]; + hexa[0][0] = hexa[6][0]; + hexa[6][0] = tmp; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), false); + // restore good container + hexa[6][0] = hexa[0][0]; + hexa[0][0] = tmp; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), true); + + // Mix some triangles + Topology::Hexahedron tmpT = hexa[0]; + hexa[0] = hexa[6]; + hexa[6] = tmpT; + EXPECT_MSG_EMIT(Error); + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), false); + // restore good container + hexa[6] = hexa[0]; + hexa[0] = tmpT; + EXPECT_MSG_NOEMIT(Error); + EXPECT_EQ(checker->checkHexahedronToVertexCrossContainer(), true); + + return true; + } +}; + + + +TEST_F(EdgeTopologyChecker_test, UnsetTopologyLink) +{ + ASSERT_TRUE(this->testUnsetTopologyLink()); +} + +TEST_F(EdgeTopologyChecker_test, Check_Valid_Topology) +{ + ASSERT_TRUE(this->testValidTopology()); +} + +TEST_F(EdgeTopologyChecker_test, Check_Invalid_Container) +{ + ASSERT_TRUE(this->testInvalidContainer()); +} + +TEST_F(EdgeTopologyChecker_test, Check_Invalid_Edge2VertexCrossContainer) +{ + ASSERT_TRUE(this->testInvalidEdge2VertexContainer()); +} + + + +TEST_F(TriangleTopologyChecker_test, Check_Valid_Topology) +{ + ASSERT_TRUE(this->testValidTopology()); +} + +TEST_F(TriangleTopologyChecker_test, Check_Invalid_Containers) +{ + ASSERT_TRUE(this->testInvalidContainer()); +} + +TEST_F(TriangleTopologyChecker_test, Check_Invalid_Triangle2EdgeCrossContainer) +{ + ASSERT_TRUE(this->testInvalidTriangle2EdgeContainer()); +} + +TEST_F(TriangleTopologyChecker_test, Check_Invalid_Triangle2VertexCrossContainer) +{ + ASSERT_TRUE(this->testInvalidTriangle2VertexContainer()); +} + + + +TEST_F(QuadTopologyChecker_test, Check_Valid_Topology) +{ + ASSERT_TRUE(this->testValidTopology()); +} + +TEST_F(QuadTopologyChecker_test, Check_Invalid_Containers) +{ + ASSERT_TRUE(this->testInvalidContainer()); +} + +TEST_F(QuadTopologyChecker_test, Check_Invalid_Quad2EdgeCrossContainer) +{ + ASSERT_TRUE(this->testInvalidQuad2EdgeContainer()); +} + +TEST_F(QuadTopologyChecker_test, Check_Invalid_Quad2VertexCrossContainer) +{ + ASSERT_TRUE(this->testInvalidQuad2VertexContainer()); +} + + + +TEST_F(TetrahedronTopologyChecker_test, Check_Valid_Topology) +{ + ASSERT_TRUE(this->testValidTopology()); +} + +TEST_F(TetrahedronTopologyChecker_test, Check_Invalid_Containers) +{ + ASSERT_TRUE(this->testInvalidContainer()); +} + +TEST_F(TetrahedronTopologyChecker_test, Check_Invalid_Tetrahedron2TriangleCrossContainer) +{ + ASSERT_TRUE(this->testInvalidTetrahedron2TriangleContainer()); +} + +TEST_F(TetrahedronTopologyChecker_test, Check_Invalid_Tetrahedron2EdgeCrossContainer) +{ + ASSERT_TRUE(this->testInvalidTetrahedron2EdgeContainer()); +} + +TEST_F(TetrahedronTopologyChecker_test, Check_Invalid_Tetrahedron2VertexCrossContainer) +{ + ASSERT_TRUE(this->testInvalidTetrahedron2VertexContainer()); +} + + + +TEST_F(HexahedronTopologyChecker_test, Check_Valid_Topology) +{ + ASSERT_TRUE(this->testValidTopology()); +} + +TEST_F(HexahedronTopologyChecker_test, Check_Invalid_Containers) +{ + ASSERT_TRUE(this->testInvalidContainer()); +} + +TEST_F(HexahedronTopologyChecker_test, Check_Invalid_Hexahedron2QuadCrossContainer) +{ + ASSERT_TRUE(this->testInvalidHexahedron2QuadContainer()); +} + +TEST_F(HexahedronTopologyChecker_test, Check_Invalid_Hexahedron2EdgeCrossContainer) +{ + ASSERT_TRUE(this->testInvalidHexahedron2EdgeContainer()); +} + +TEST_F(HexahedronTopologyChecker_test, Check_Invalid_Hexahedron2VertexCrossContainer) +{ + ASSERT_TRUE(this->testInvalidHexahedron2VertexContainer()); +} + +}// namespace diff --git a/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.cpp b/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.cpp new file mode 100644 index 00000000000..3695e9feaa5 --- /dev/null +++ b/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.cpp @@ -0,0 +1,993 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#include +#include +#include + +#include +#include +#include + + +#include + +namespace sofa::component::misc +{ + +using namespace defaulttype; +using namespace sofa::core::topology; + + +int TopologyCheckerClass = core::RegisterObject("Read topological Changes and process them.") + .add< TopologyChecker >(); + + +TopologyChecker::TopologyChecker() + : d_eachStep(initData(&d_eachStep, false, "eachStep", "Check topology at each step")) + , l_topology(initLink("topology", "link to the topology container")) + , m_topology(nullptr) +{ + this->f_listening.setValue(true); +} + + +TopologyChecker::~TopologyChecker() +{ + +} + + +void TopologyChecker::init() +{ + if (l_topology.empty()) + { + msg_info() << "link to Topology container should be set to ensure right behavior. First Topology found in current context will be used."; + l_topology.set(this->getContext()->getMeshTopologyLink()); + } + + m_topology = l_topology.get(); + msg_info() << "Topology path used: '" << l_topology.getLinkedPath() << "'"; + + if (m_topology == nullptr) + { + msg_error() << "No topology component found at path: " << l_topology.getLinkedPath() << ", nor in current context: " << this->getContext()->name; + sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } +} + +void TopologyChecker::reinit() +{ + checkTopology(); +} + + +bool TopologyChecker::checkTopology() +{ + bool result = false; + if (m_topology->getTopologyType() == TopologyElementType::HEXAHEDRON) + result = checkHexahedronTopology(); + if (m_topology->getTopologyType() == TopologyElementType::TETRAHEDRON) + result = checkTetrahedronTopology(); + else if (m_topology->getTopologyType() == TopologyElementType::QUAD) + result = checkQuadTopology(); + else if (m_topology->getTopologyType() == TopologyElementType::TRIANGLE) + result = checkTriangleTopology(); + else if (m_topology->getTopologyType() == TopologyElementType::EDGE) + result = checkEdgeTopology(); + + return result; +} + + + +bool TopologyChecker::checkEdgeTopology() +{ + // check edge container consistency + if (checkEdgeContainer() == false) + return false; + + // check cross elements consistency + return checkEdgeToVertexCrossContainer(); +} + + +bool TopologyChecker::checkEdgeContainer() +{ + sofa::Size nbE = m_topology->getNbEdges(); + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + + if (nbE != my_edges.size()) + { + msg_error() << "CheckEdgeTopology failed: not the good number of edges, getNbEdges returns " << nbE << " whereas edge array size is: " << my_edges.size(); + return false; + } + + // check edge buffer + bool res = true; + for (sofa::Index i = 0; i < nbE; ++i) + { + const auto& edge = my_edges[i]; + if (edge[0] == edge[1]) { + msg_error() << "CheckEdgeTopology failed: edge " << i << " has 2 identical vertices: " << edge; + res = false; + } + } + + return res; +} + +bool TopologyChecker::checkEdgeToVertexCrossContainer() +{ + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + std::set edgeSet; + + bool res = true; + sofa::Size nbP = m_topology->getNbPoints(); + for (sofa::Index i = 0; i < nbP; ++i) + { + const auto& EdgesAV = m_topology->getEdgesAroundVertex(i); + for (size_t j = 0; j < EdgesAV.size(); ++j) + { + const Topology::Edge& edge = my_edges[EdgesAV[j]]; + if (!(edge[0] == i || edge[1] == i)) + { + msg_error() << "CheckEdgeTopology failed: edge " << EdgesAV[j] << ": [" << edge << "] not around vertex: " << i; + res = false; + } + + // count number of edge + edgeSet.insert(EdgesAV[j]); + } + } + + if (edgeSet.size() != my_edges.size()) + { + msg_error() << "CheckEdgeTopology failed: found " << edgeSet.size() << " edges in m_edgesAroundVertex out of " << my_edges.size(); + res = false; + } + + return res; +} + + + + +bool TopologyChecker::checkTriangleTopology() +{ + // check edge container consistency + if (checkTriangleContainer() == false) + return false; + + // check cross elements consistency + bool res = checkTriangleToEdgeCrossContainer(); + + res = res && checkTriangleToVertexCrossContainer(); + + return res && checkEdgeTopology(); +} + + +bool TopologyChecker::checkTriangleContainer() +{ + sofa::Size nbT = m_topology->getNbTriangles(); + const sofa::core::topology::BaseMeshTopology::SeqTriangles& my_triangles = m_topology->getTriangles(); + + if (nbT != my_triangles.size()) + { + msg_error() << "CheckTriangleTopology failed: not the good number of triangles, getNbTriangles returns " << nbT << " whereas triangle array size is: " << my_triangles.size(); + return false; + } + + // check triangle buffer + bool res = true; + for (sofa::Index i = 0; i < nbT; ++i) + { + const auto& triangle = my_triangles[i]; + if (triangle[0] == triangle[1] || triangle[0] == triangle[2] || triangle[1] == triangle[2]) { + msg_error() << "CheckTriangleTopology failed: triangle " << i << " has 2 identical vertices: " << triangle; + res = false; + } + } + + return res; +} + + +bool TopologyChecker::checkTriangleToEdgeCrossContainer() +{ + bool ret = true; + sofa::Size nbT = m_topology->getNbTriangles(); + const sofa::core::topology::BaseMeshTopology::SeqTriangles& my_triangles = m_topology->getTriangles(); + + // check triangles buffer + std::set triangleSet; + + sofa::Size nbE = m_topology->getNbEdges(); + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + + // check edges in triangles + for (sofa::Index i = 0; i < nbT; ++i) + { + const Topology::Triangle& triangle = my_triangles[i]; + const auto& eInTri = m_topology->getEdgesInTriangle(i); + + for (unsigned int j = 0; j < 3; j++) + { + if (eInTri[j] == Topology::InvalidID) + { + msg_error() << "CheckTriangleTopology failed: EdgesInTriangle of triangle: " << i << ": " << triangle << " has invalid ID: " << eInTri; + ret = false; + continue; + } + + const Topology::Edge& edge = my_edges[eInTri[j]]; + int cptFound = 0; + for (unsigned int k = 0; k < 3; k++) + if (edge[0] == triangle[k] || edge[1] == triangle[k]) + cptFound++; + + if (cptFound != 2) + { + msg_error() << "CheckTriangleTopology failed: edge: " << eInTri[j] << ": [" << edge << "] not found in triangle: " << i << ": " << triangle; + ret = false; + } + } + } + + + // check triangles around edges + // check m_trianglesAroundEdge using checked m_edgesInTriangle + triangleSet.clear(); + for (sofa::Index edgeId = 0; edgeId < nbE; ++edgeId) + { + const BaseMeshTopology::TrianglesAroundEdge& tes = m_topology->getTrianglesAroundEdge(edgeId); + for (auto triId : tes) + { + const BaseMeshTopology::EdgesInTriangle& eInTri = m_topology->getEdgesInTriangle(triId); + bool check_triangle_edge_shell = (eInTri[0] == edgeId) + || (eInTri[1] == edgeId) + || (eInTri[2] == edgeId); + if (!check_triangle_edge_shell) + { + msg_error() << "CheckTriangleTopology failed: triangle: " << triId << " with edges: [" << eInTri << "] not found around edge: " << edgeId; + ret = false; + } + + triangleSet.insert(triId); + } + } + + if (triangleSet.size() != my_triangles.size()) + { + msg_error() << "CheckTriangleTopology failed: found " << triangleSet.size() << " triangles in m_trianglesAroundEdge out of " << my_triangles.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkTriangleToVertexCrossContainer() +{ + bool ret = true; + // check cross element + sofa::Size nbP = m_topology->getNbPoints(); + const sofa::core::topology::BaseMeshTopology::SeqTriangles& my_triangles = m_topology->getTriangles(); + + // check triangles around vertex + std::set triangleSet; + for (sofa::Index i = 0; i < nbP; ++i) + { + const auto& triAV = m_topology->getTrianglesAroundVertex(i); + for (size_t j = 0; j < triAV.size(); ++j) + { + const Topology::Triangle& triangle = my_triangles[triAV[j]]; + bool check_triangle_vertex_shell = (triangle[0] == i) || (triangle[1] == i) || (triangle[2] == i); + if (!check_triangle_vertex_shell) + { + msg_error() << "CheckTriangleTopology failed: triangle " << triAV[j] << ": [" << triangle << "] not around vertex: " << i; + ret = false; + } + + triangleSet.insert(triAV[j]); + } + } + + if (triangleSet.size() != my_triangles.size()) + { + msg_error() << "CheckTriangleTopology failed: found " << triangleSet.size() << " triangles in trianglesAroundVertex out of " << my_triangles.size(); + ret = false; + } + + return ret; +} + + + + +bool TopologyChecker::checkQuadTopology() +{ + // check edge container consistency + if (checkQuadContainer() == false) + return false; + + // check cross elements consistency + bool res = checkQuadToEdgeCrossContainer(); + + res = res && checkQuadToVertexCrossContainer(); + + return res && checkEdgeTopology(); +} + + +bool TopologyChecker::checkQuadContainer() +{ + sofa::Size nbQ = m_topology->getNbQuads(); + const sofa::core::topology::BaseMeshTopology::SeqQuads& my_quads = m_topology->getQuads(); + + if (nbQ != my_quads.size()) + { + msg_error() << "checkQuadTopology failed: not the good number of quads, getNbQuads returns " << nbQ << " whereas quad array size is: " << my_quads.size(); + return false; + } + + // check quad buffer + bool res = true; + for (sofa::Index i = 0; i < nbQ; ++i) + { + const auto& quad = my_quads[i]; + for (int j = 0; j < 3; ++j) + { + for (int k = j + 1; k < 4; ++k) + { + if (quad[j] == quad[k]) + { + msg_error() << "checkQuadTopology failed: quad " << i << " has 2 identical vertices: " << quad; + res = false; + } + } + } + } + + return res; +} + + +bool TopologyChecker::checkQuadToEdgeCrossContainer() +{ + bool ret = true; + sofa::Size nbQ = m_topology->getNbQuads(); + const sofa::core::topology::BaseMeshTopology::SeqQuads& my_quads = m_topology->getQuads(); + + std::set quadSet; + + sofa::Size nbE = m_topology->getNbEdges(); + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + + // check edges in quads + for (sofa::Index i = 0; i < nbQ; ++i) + { + const Topology::Quad& quad = my_quads[i]; + const auto& eInQ = m_topology->getEdgesInQuad(i); + + for (auto eId : eInQ) + { + if (eId == Topology::InvalidID) + { + msg_error() << "CheckQuadTopology failed: EdgesInQuad of quad: " << i << ": " << quad << " has invalid ID: " << eInQ; + ret = false; + continue; + } + + const Topology::Edge& edge = my_edges[eId]; + int cptFound = 0; + for (unsigned int k = 0; k < 4; k++) + if (edge[0] == quad[k] || edge[1] == quad[k]) + cptFound++; + + if (cptFound != 2) + { + msg_error() << "CheckQuadTopology failed: edge: " << eId << ": [" << edge << "] not found in quad: " << i << ": " << quad; + ret = false; + } + } + } + + // check quads around edges + // check m_quadsAroundEdge using checked m_edgesInQuad + quadSet.clear(); + for (sofa::Index edgeId = 0; edgeId < nbE; ++edgeId) + { + const BaseMeshTopology::QuadsAroundEdge& qAE = m_topology->getQuadsAroundEdge(edgeId); + for (auto qId : qAE) + { + const BaseMeshTopology::EdgesInQuad& eInQ = m_topology->getEdgesInQuad(qId); + bool check_quad_edge_shell = (eInQ[0] == edgeId) + || (eInQ[1] == edgeId) + || (eInQ[2] == edgeId) + || (eInQ[3] == edgeId); + if (!check_quad_edge_shell) + { + msg_error() << "CheckQuadTopology failed: quad: " << qId << " with edges: [" << eInQ << "] not found around edge: " << edgeId; + ret = false; + } + + quadSet.insert(qId); + } + } + + if (quadSet.size() != my_quads.size()) + { + msg_error() << "CheckQuadTopology failed: found " << quadSet.size() << " quads in m_quadsAroundEdge out of " << my_quads.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkQuadToVertexCrossContainer() +{ + bool ret = true; + // check cross element + sofa::Size nbP = m_topology->getNbPoints(); + const sofa::core::topology::BaseMeshTopology::SeqQuads& my_quads = m_topology->getQuads(); + + // check quads around vertex + std::set quadSet; + for (sofa::Index i = 0; i < nbP; ++i) + { + const auto& quadAV = m_topology->getQuadsAroundVertex(i); + for (size_t j = 0; j < quadAV.size(); ++j) + { + const Topology::Quad& quad = my_quads[quadAV[j]]; + bool check_quad_vertex_shell = (quad[0] == i) || (quad[1] == i) || (quad[2] == i) || (quad[3] == i); + if (!check_quad_vertex_shell) + { + msg_error() << "CheckQuadTopology failed: quad " << quadAV[j] << ": [" << quad << "] not around vertex: " << i; + ret = false; + } + + quadSet.insert(quadAV[j]); + } + } + + if (quadSet.size() != my_quads.size()) + { + msg_error() << "CheckQuadTopology failed: found " << quadSet.size() << " quads in quadsAroundVertex out of " << my_quads.size(); + ret = false; + } + + return ret; +} + + + + +bool TopologyChecker::checkTetrahedronTopology() +{ + // check edge container consistency + if (checkTetrahedronContainer() == false) + return false; + + // check cross elements consistency + bool res = checkTetrahedronToTriangleCrossContainer(); + + res = res && checkTetrahedronToEdgeCrossContainer(); + + res = res && checkTetrahedronToVertexCrossContainer(); + + return res && checkTriangleTopology(); +} + + +bool TopologyChecker::checkTetrahedronContainer() +{ + sofa::Size nbT = m_topology->getNbTetrahedra(); + const sofa::core::topology::BaseMeshTopology::SeqTetrahedra& my_tetrahedra = m_topology->getTetrahedra(); + + if (nbT != my_tetrahedra.size()) + { + msg_error() << "checkTetrahedronTopology failed: not the good number of tetrahedra, getNbTetrahedra returns " << nbT << " whereas triangle array size is: " << my_tetrahedra.size(); + return false; + } + + // check tetrahedron buffer + bool res = true; + for (sofa::Index i = 0; i < nbT; ++i) + { + const auto& tetra = my_tetrahedra[i]; + for (int j = 0; j < 3; ++j) + { + for (int k = j + 1; k < 4; ++k) + { + if (tetra[j] == tetra[k]) + { + msg_error() << "checkTetrahedronTopology failed: tetrahedron " << i << " has 2 identical vertices: " << tetra; + res = false; + } + } + } + } + + return res; +} + + +bool TopologyChecker::checkTetrahedronToTriangleCrossContainer() +{ + bool ret = true; + sofa::Size nbT = m_topology->getNbTetrahedra(); + const sofa::core::topology::BaseMeshTopology::SeqTetrahedra& my_tetrahedra = m_topology->getTetrahedra(); + + std::set tetrahedronSet; + + sofa::Size nbTri = m_topology->getNbTriangles(); + const sofa::core::topology::BaseMeshTopology::SeqTriangles& my_triangles = m_topology->getTriangles(); + + // check first m_trianglesInTetrahedron + for (sofa::Index tetraId = 0; tetraId < nbT; ++tetraId) + { + const Topology::Tetrahedron& tetrahedron = my_tetrahedra[tetraId]; + const auto& triInTetra = m_topology->getTrianglesInTetrahedron(tetraId); + + for (unsigned int j = 0; j < 4; j++) + { + if (triInTetra[j] == Topology::InvalidID) + { + msg_error() << "checkTetrahedronTopology failed: TrianglesInTetrahedron of tetrahedron: " << tetraId << ": " << tetrahedron << " has invalid ID: " << triInTetra; + ret = false; + continue; + } + + const Topology::Triangle& triangle = my_triangles[triInTetra[j]]; + int cptFound = 0; + for (unsigned int k = 0; k < 4; k++) + if (triangle[0] == tetrahedron[k] || triangle[1] == tetrahedron[k] || triangle[2] == tetrahedron[k]) + cptFound++; + + if (cptFound != 3) + { + msg_error() << "checkTetrahedronTopology failed: triangle: " << triInTetra[j] << ": [" << triangle << "] not found in tetrahedron: " << tetraId << ": " << tetrahedron; + ret = false; + } + } + } + + // check tetrahedra around triangles + // check m_tetrahedraAroundTriangle using checked m_trianglesInTetrahedron + tetrahedronSet.clear(); + for (sofa::Index triId = 0; triId < nbTri; ++triId) + { + const BaseMeshTopology::TetrahedraAroundTriangle& tes = m_topology->getTetrahedraAroundTriangle(triId); + for (auto tetraId : tes) + { + const BaseMeshTopology::TrianglesInTetrahedron& triInTetra = m_topology->getTrianglesInTetrahedron(tetraId); + bool check_tetra_triangle_shell = (triInTetra[0] == triId) + || (triInTetra[1] == triId) + || (triInTetra[2] == triId) + || (triInTetra[3] == triId); + if (!check_tetra_triangle_shell) + { + msg_error() << "checkTetrahedronTopology failed: tetrahedron: " << tetraId << " with triangle: [" << triInTetra << "] not found around triangle: " << tetraId; + ret = false; + } + + tetrahedronSet.insert(tetraId); + } + } + + if (tetrahedronSet.size() != my_tetrahedra.size()) + { + msg_error() << "checkTetrahedronTopology failed: found " << tetrahedronSet.size() << " tetrahedra in m_tetrahedraAroundTriangle out of " << my_tetrahedra.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkTetrahedronToEdgeCrossContainer() +{ + bool ret = true; + sofa::Size nbT = m_topology->getNbTetrahedra(); + const sofa::core::topology::BaseMeshTopology::SeqTetrahedra& my_tetrahedra = m_topology->getTetrahedra(); + + std::set tetrahedronSet; + + sofa::Size nbE = m_topology->getNbEdges(); + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + // check edges in tetrahedra + for (sofa::Index i = 0; i < nbT; ++i) + { + const Topology::Tetrahedron& tetrahedron = my_tetrahedra[i]; + const auto& eInTetra = m_topology->getEdgesInTetrahedron(i); + + for (unsigned int j = 0; j < 6; j++) + { + const Topology::Edge& edge = my_edges[eInTetra[j]]; + int cptFound = 0; + for (unsigned int k = 0; k < 4; k++) + if (edge[0] == tetrahedron[k] || edge[1] == tetrahedron[k]) + cptFound++; + + if (cptFound != 2) + { + msg_error() << "checkTetrahedronTopology failed: edge: " << eInTetra[j] << ": [" << edge << "] not found in tetrahedron: " << i << ": " << tetrahedron; + ret = false; + } + } + } + + // check tetrahedra around edges + // check m_tetrahedraAroundEdge using checked m_edgesInTetrahedron + tetrahedronSet.clear(); + for (sofa::Index edgeId = 0; edgeId < nbE; ++edgeId) + { + const BaseMeshTopology::TetrahedraAroundEdge& tes = m_topology->getTetrahedraAroundEdge(edgeId); + for (auto tetraId : tes) + { + const BaseMeshTopology::EdgesInTetrahedron& eInTetra = m_topology->getEdgesInTetrahedron(tetraId); + bool check_tetra_edge_shell = (eInTetra[0] == edgeId) + || (eInTetra[1] == edgeId) + || (eInTetra[2] == edgeId) + || (eInTetra[3] == edgeId) + || (eInTetra[4] == edgeId) + || (eInTetra[5] == edgeId); + if (!check_tetra_edge_shell) + { + msg_error() << "checkTetrahedronTopology failed: tetrahedron: " << tetraId << " with edges: [" << eInTetra << "] not found around edge: " << edgeId; + ret = false; + } + + tetrahedronSet.insert(tetraId); + } + } + + if (tetrahedronSet.size() != my_tetrahedra.size()) + { + msg_error() << "CheckTriangleTopology failed: found " << tetrahedronSet.size() << " tetrahedra in m_tetrahedraAroundTriangle out of " << my_tetrahedra.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkTetrahedronToVertexCrossContainer() +{ + bool ret = true; + const sofa::core::topology::BaseMeshTopology::SeqTetrahedra& my_tetrahedra = m_topology->getTetrahedra(); + + // check cross element + sofa::Size nbP = m_topology->getNbPoints(); + + // check tetrahedra around vertex + std::set tetrahedronSet; + for (sofa::Index pId = 0; pId < nbP; ++pId) + { + const auto& tetraAV = m_topology->getTetrahedraAroundVertex(pId); + for (auto tetraId : tetraAV) + { + const Topology::Tetrahedron& tetra = my_tetrahedra[tetraId]; + bool check_tetra_vertex_shell = (tetra[0] == pId) + || (tetra[1] == pId) + || (tetra[2] == pId) + || (tetra[3] == pId); + if (!check_tetra_vertex_shell) + { + msg_error() << "checkTetrahedronTopology failed: Tetrahedron " << tetraId << ": [" << tetra << "] not around vertex: " << pId; + ret = false; + } + + tetrahedronSet.insert(tetraId); + } + } + + if (tetrahedronSet.size() != my_tetrahedra.size()) + { + msg_error() << "checkTetrahedronTopology failed: found " << tetrahedronSet.size() << " tetrahedra in tetrahedraAroundVertex out of " << my_tetrahedra.size(); + ret = false; + } + + return ret; +} + + + + +bool TopologyChecker::checkHexahedronTopology() +{ + // check edge container consistency + if (checkHexahedronContainer() == false) + return false; + + // check cross elements consistency + bool res = checkHexahedronToQuadCrossContainer(); + + res = res && checkHexahedronToEdgeCrossContainer(); + + res = res && checkHexahedronToVertexCrossContainer(); + + return res && checkQuadTopology(); +} + + +bool TopologyChecker::checkHexahedronContainer() +{ + sofa::Size nbH = m_topology->getNbHexahedra(); + const sofa::core::topology::BaseMeshTopology::SeqHexahedra& my_hexahedra = m_topology->getHexahedra(); + + if (nbH != my_hexahedra.size()) + { + msg_error() << "checkHexahedronTopology failed: not the good number of hexahedra, getNbHexahedra returns " << nbH << " whereas hexahedra array size is: " << my_hexahedra.size(); + return false; + } + + // check hexahedron buffer + bool res = true; + for (sofa::Index i = 0; i < nbH; ++i) + { + const auto& hexahedron = my_hexahedra[i]; + for (int j = 0; j < 7; ++j) + { + for (int k = j + 1; k < 8; ++k) + { + if (hexahedron[j] == hexahedron[k]) + { + msg_error() << "checkHexahedronTopology failed: hexahedron " << i << " has 2 identical vertices: " << hexahedron; + res = false; + } + } + } + } + + return res; +} + + +bool TopologyChecker::checkHexahedronToQuadCrossContainer() +{ + bool ret = true; + sofa::Size nbH = m_topology->getNbHexahedra(); + const sofa::core::topology::BaseMeshTopology::SeqHexahedra& my_hexahedra = m_topology->getHexahedra(); + + std::set hexahedronSet; + + sofa::Size nbQ = m_topology->getNbQuads(); + const sofa::core::topology::BaseMeshTopology::SeqQuads& my_quads = m_topology->getQuads(); + + // check first m_quadsInHexahedron + for (sofa::Index hexaId = 0; hexaId < nbH; ++hexaId) + { + const Topology::Hexahedron& hexahedron = my_hexahedra[hexaId]; + const auto& qInHexa = m_topology->getQuadsInHexahedron(hexaId); + + for (auto qId : qInHexa) + { + if (qId == Topology::InvalidID) + { + msg_error() << "checkHexahedronTopology failed: QuadsInHexahedron of hexahedron: " << hexaId << ": " << hexahedron << " has invalid ID: " << qInHexa; + ret = false; + continue; + } + + const Topology::Quad& quad = my_quads[qId]; + int cptFound = 0; + for (unsigned int k = 0; k < 8; k++) + if (quad[0] == hexahedron[k] || quad[1] == hexahedron[k] || quad[2] == hexahedron[k] || quad[3] == hexahedron[k]) + cptFound++; + + if (cptFound != 4) + { + msg_error() << "checkHexahedronTopology failed: quad: " << qId << ": [" << quad << "] not found in hexahedron: " << hexaId << ": " << hexahedron; + ret = false; + } + } + } + + // check hexahedra around triangles + // check m_hexahedraAroundTriangle using checked m_trianglesInHexahedron + hexahedronSet.clear(); + for (sofa::Index qId = 0; qId < nbQ; ++qId) + { + const BaseMeshTopology::HexahedraAroundQuad& hAq = m_topology->getHexahedraAroundQuad(qId); + for (auto hexaId : hAq) + { + const BaseMeshTopology::QuadsInHexahedron& qInHexa = m_topology->getQuadsInHexahedron(hexaId); + bool check_hexa_quad_shell = false; + for (auto quadID : qInHexa) + { + if (quadID == qId) { + check_hexa_quad_shell = true; + break; + } + } + + if (!check_hexa_quad_shell) + { + msg_error() << "checkHexahedronTopology failed: hexahedron: " << hexaId << " with quad: [" << qInHexa << "] not found around quad: " << qId; + ret = false; + } + + hexahedronSet.insert(hexaId); + } + } + + if (hexahedronSet.size() != my_hexahedra.size()) + { + msg_error() << "checkHexahedronTopology failed: found " << hexahedronSet.size() << " hexahedra in m_hexahedraAroundQuad out of " << my_hexahedra.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkHexahedronToEdgeCrossContainer() +{ + bool ret = true; + sofa::Size nbH = m_topology->getNbHexahedra(); + const sofa::core::topology::BaseMeshTopology::SeqHexahedra& my_hexahedra = m_topology->getHexahedra(); + + std::set hexahedronSet; + + sofa::Size nbE = m_topology->getNbEdges(); + const sofa::core::topology::BaseMeshTopology::SeqEdges& my_edges = m_topology->getEdges(); + // check edges in hexahedra + for (sofa::Index i = 0; i < nbH; ++i) + { + const Topology::Hexahedron& hexahedron = my_hexahedra[i]; + const auto& eInHexa = m_topology->getEdgesInHexahedron(i); + + for (unsigned int j = 0; j < 6; j++) + { + const Topology::Edge& edge = my_edges[eInHexa[j]]; + int cptFound = 0; + for (unsigned int k = 0; k < 8; k++) + if (edge[0] == hexahedron[k] || edge[1] == hexahedron[k]) + cptFound++; + + if (cptFound != 2) + { + msg_error() << "checkHexahedronTopology failed: edge: " << eInHexa[j] << ": [" << edge << "] not found in hexahedron: " << i << ": " << hexahedron; + ret = false; + } + } + } + + // check hexahedra around edges + // check m_hexahedraAroundEdge using checked m_edgesInHexahedron + hexahedronSet.clear(); + for (sofa::Index edgeId = 0; edgeId < nbE; ++edgeId) + { + const BaseMeshTopology::HexahedraAroundEdge& hAe = m_topology->getHexahedraAroundEdge(edgeId); + for (auto hexaId : hAe) + { + const BaseMeshTopology::EdgesInHexahedron& eInHexa = m_topology->getEdgesInHexahedron(hexaId); + + bool check_hexa_edge_shell = false; + for (auto eInID : eInHexa) + { + if (eInID == edgeId) { + check_hexa_edge_shell = true; + break; + } + } + + if (!check_hexa_edge_shell) + { + msg_error() << "checkHexahedronTopology failed: hexahedron: " << hexaId << " with edges: [" << eInHexa << "] not found around edge: " << edgeId; + ret = false; + } + + hexahedronSet.insert(hexaId); + } + } + + if (hexahedronSet.size() != my_hexahedra.size()) + { + msg_error() << "checkHexahedronTopology failed: found " << hexahedronSet.size() << " hexahedra in m_hexahedraAroundEdge out of " << my_hexahedra.size(); + ret = false; + } + + return ret; +} + + +bool TopologyChecker::checkHexahedronToVertexCrossContainer() +{ + bool ret = true; + const sofa::core::topology::BaseMeshTopology::SeqHexahedra& my_hexahedra = m_topology->getHexahedra(); + + // check cross element + sofa::Size nbP = m_topology->getNbPoints(); + + // check hexahedra around vertex + std::set hexahedronSet; + for (sofa::Index pId = 0; pId < nbP; ++pId) + { + const auto& hexaAV = m_topology->getHexahedraAroundVertex(pId); + for (auto hexaId : hexaAV) + { + const Topology::Hexahedron& hexa = my_hexahedra[hexaId]; + bool check_hexa_vertex_shell = false; + for (int j = 0; j < 8; ++j) + { + if (hexa[j] == pId) { + check_hexa_vertex_shell = true; + break; + } + } + + if (!check_hexa_vertex_shell) + { + msg_error() << "checkHexahedronTopology failed: Hexahedron " << hexaId << ": [" << hexa << "] not around vertex: " << pId; + ret = false; + } + + hexahedronSet.insert(hexaId); + } + } + + if (hexahedronSet.size() != my_hexahedra.size()) + { + msg_error() << "checkHexahedronTopology failed: found " << hexahedronSet.size() << " hexahedra in hexahedraAroundVertex out of " << my_hexahedra.size(); + ret = false; + } + + return ret; +} + + +void TopologyChecker::handleEvent(sofa::core::objectmodel::Event* event) +{ + if (sofa::core::objectmodel::KeypressedEvent* ev = dynamic_cast(event)) + { + if (ev->getKey() == 'T') + { + bool res = checkTopology(); + if (!res) + msg_error() << "checkTopology Error!!"; + } + } + + if (simulation::AnimateEndEvent::checkEventType(event) && d_eachStep.getValue()) + { + checkTopology(); + } +} + + +void TopologyChecker::draw(const core::visual::VisualParams* vparams) +{ + if (!m_topology) + return; + + if (!vparams->displayFlags().getShowBehaviorModels()) + return; + +} + +} // namespace sofa::component::misc + diff --git a/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.h b/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.h new file mode 100644 index 00000000000..30c07b45fbb --- /dev/null +++ b/modules/SofaMiscTopology/src/SofaMiscTopology/TopologyChecker.h @@ -0,0 +1,167 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once + +#include + +#include +#include + +#include + +namespace sofa::component::misc +{ + +/** +* The class TopologyChecker represents a SOFA component which can be added in a scene graph to test a given Topology. +* The topology component to be tested need to be linked using @sa l_topology. +* If the Data @sa d_eachStep is set to true, the topology will be tested at each step using the generic method @sa checkTopology +* +* Otherwise each method can be called manually: +* @CheckTopology will call the appropriate Check{TopologyType}Topology then call the lower level of CheckTopology. +* - i.e for a Tetrahedron Topology, CheckTopology with call @sa checkTetrahedronTopology then @sa checkTriangleTopology and finally @sa checkEdgeTopology +* - At each level the topology is checked through the main element container and also the cross topology containers +* - Each method return a bool and will display msg_error if problems are detected. +*/ +class SOFA_SOFAMISCTOPOLOGY_API TopologyChecker: public core::objectmodel::BaseObject +{ +public: + SOFA_CLASS(TopologyChecker, core::objectmodel::BaseObject); + + void init() override; + + void reinit() override; + + void handleEvent(sofa::core::objectmodel::Event* event) override; + + void draw(const core::visual::VisualParams* vparams) override; + + bool checkTopology(); + + + /// Edge methods + ///{ + /// Full method to check Edge Topology. Will call @sa checkEdgeContainer and @sa checkEdgeToVertexCrossContainer + bool checkEdgeTopology(); + + /// Method to test Edge container concistency + bool checkEdgeContainer(); + + /// Method to test Edge to vertex cross container concistency + bool checkEdgeToVertexCrossContainer(); + ///} + + + /// Triangle methods + ///{ + /// Full method to check Triangle Topology. Will call @sa checkTriangleContainer, @sa checkTriangleToEdgeCrossContainer and @sa checkTriangleToVertexCrossContainer + bool checkTriangleTopology(); + + /// Method to test Triangle container concistency + bool checkTriangleContainer(); + + /// Method to test triangles to edges cross container concistency + bool checkTriangleToEdgeCrossContainer(); + + /// Method to test triangles to vertices cross container concistency + bool checkTriangleToVertexCrossContainer(); + ///} + + + /// Quad methods + ///{ + /// Full method to check Quad Topology. Will call @sa checkQuadContainer, @sa checkQuadToEdgeCrossContainer and @sa checkQuadToVertexCrossContainer + bool checkQuadTopology(); + + /// Method to test quad container concistency + bool checkQuadContainer(); + + /// Method to test quads to edges cross container concistency + bool checkQuadToEdgeCrossContainer(); + + /// Method to test quads to vertices cross container concistency + bool checkQuadToVertexCrossContainer(); + /// } + + + /// Tetrahedron methods + ///{ + /// Full method to check Tetrahedron Topology. Will call @sa checkTetrahedronContainer, @sa checkTetrahedronToTriangleCrossContainer + /// @sa checkTetrahedronToEdgeCrossContainer and @sa checkTetrahedronToVertexCrossContainer + bool checkTetrahedronTopology(); + + /// Method to test Tetrahedron container concistency + bool checkTetrahedronContainer(); + + /// Method to test Tetrahedron to triangles cross container concistency + bool checkTetrahedronToTriangleCrossContainer(); + + /// Method to test Tetrahedron to edges cross container concistency + bool checkTetrahedronToEdgeCrossContainer(); + + /// Method to test Tetrahedron to vertices cross container concistency + bool checkTetrahedronToVertexCrossContainer(); + /// } + + + /// Hexahedron methods + ///{ + /// Full method to check Hexahedron Topology. Will call @sa checkHexahedronContainer, @sa checkHexahedronToQuadCrossContainer + /// @sa checkHexahedronToEdgeCrossContainer and @sa checkHexahedronToVertexCrossContainer + bool checkHexahedronTopology(); + + /// Method to test Hexahedron container concistency + bool checkHexahedronContainer(); + + /// Method to test Hexahedron to quads cross container concistency + bool checkHexahedronToQuadCrossContainer(); + + /// Method to test Hexahedron to edges cross container concistency + bool checkHexahedronToEdgeCrossContainer(); + + /// Method to test Hexahedron to vertices cross container concistency + bool checkHexahedronToVertexCrossContainer(); + /// } + + + +protected: + TopologyChecker(); + + ~TopologyChecker() override; + + +public: + /// bool to check topology at each step. + Data d_eachStep; + + /// Link to be set to the topology container in the component graph. + SingleLink l_topology; + + +protected: + core::topology::BaseMeshTopology::SPtr m_topology; + +}; + + +} // namespace sofa::component::misc