From 4046d43f014e57dd0159e7e6fe4a6f2f604a4288 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 10:47:56 +0100 Subject: [PATCH 01/10] deactivate checks so that the component can be used with a MeshTopology component --- .../mapping/Hexa2TetraTopologicalMapping.cpp | 70 +++++++++---------- 1 file changed, 35 insertions(+), 35 deletions(-) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp index 28e13d64e9b..ee181ef2d54 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp @@ -68,34 +68,34 @@ void Hexa2TetraTopologicalMapping::init() Inherit1::init(); - if (!this->checkTopologyInputTypes()) // method will display error message if false - { - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } - - TetrahedronSetTopologyModifier* to_tstm { nullptr }; - toModel->getContext()->get(to_tstm); - if (!to_tstm) - { - msg_error() << "No TetrahedronSetTopologyModifier found in the Tetrahedron topology Node."; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } + // if (!this->checkTopologyInputTypes()) // method will display error message if false + // { + // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + // return; + // } + + // TetrahedronSetTopologyModifier* to_tstm { nullptr }; + // toModel->getContext()->get(to_tstm); + // if (!to_tstm) + // { + // msg_error() << "No TetrahedronSetTopologyModifier found in the Tetrahedron topology Node."; + // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + // return; + // } // INITIALISATION of TETRAHEDRAL mesh from HEXAHEDRAL mesh : - TetrahedronSetTopologyContainer *to_tstc { nullptr }; - toModel->getContext()->get(to_tstc); - if (!to_tstc) - { - msg_error() << "No TetrahedronSetTopologyContainer found in the Tetrahedron topology Node."; - this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - return; - } + // TetrahedronSetTopologyContainer *toModel { nullptr }; + // toModel->getContext()->get(toModel); + // if (!toModel) + // { + // msg_error() << "No TetrahedronSetTopologyContainer found in the Tetrahedron topology Node."; + // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + // return; + // } // Clear output topology - to_tstc->clear(); + toModel->clear(); // Set the same number of points toModel->setNbPoints(fromModel->getNbPoints()); @@ -165,21 +165,21 @@ void Hexa2TetraTopologicalMapping::init() if(!swapped) { - to_tstc->addTetra(c[0],c[5],c[1],c[6]); - to_tstc->addTetra(c[0],c[1],c[3],c[6]); - to_tstc->addTetra(c[1],c[3],c[6],c[2]); - to_tstc->addTetra(c[6],c[3],c[0],c[7]); - to_tstc->addTetra(c[6],c[7],c[0],c[5]); - to_tstc->addTetra(c[7],c[5],c[4],c[0]); + toModel->addTetra(c[0],c[5],c[1],c[6]); + toModel->addTetra(c[0],c[1],c[3],c[6]); + toModel->addTetra(c[1],c[3],c[6],c[2]); + toModel->addTetra(c[6],c[3],c[0],c[7]); + toModel->addTetra(c[6],c[7],c[0],c[5]); + toModel->addTetra(c[7],c[5],c[4],c[0]); } else { - to_tstc->addTetra(c[0],c[5],c[6],c[1]); - to_tstc->addTetra(c[0],c[1],c[6],c[3]); - to_tstc->addTetra(c[1],c[3],c[2],c[6]); - to_tstc->addTetra(c[6],c[3],c[7],c[0]); - to_tstc->addTetra(c[6],c[7],c[5],c[0]); - to_tstc->addTetra(c[7],c[5],c[0],c[4]); + toModel->addTetra(c[0],c[5],c[6],c[1]); + toModel->addTetra(c[0],c[1],c[6],c[3]); + toModel->addTetra(c[1],c[3],c[2],c[6]); + toModel->addTetra(c[6],c[3],c[7],c[0]); + toModel->addTetra(c[6],c[7],c[5],c[0]); + toModel->addTetra(c[7],c[5],c[0],c[4]); } for (int j = 0; j < numberTetraInHexa; j++) { From 40fa2bc77336046d103cd83fff9faf36b468317c Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:51:33 +0100 Subject: [PATCH 02/10] the componente never use the topology modifer. It can be removed --- .../topology/mapping/Hexa2TetraTopologicalMapping.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp index ee181ef2d54..851c99c3072 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp @@ -74,15 +74,6 @@ void Hexa2TetraTopologicalMapping::init() // return; // } - // TetrahedronSetTopologyModifier* to_tstm { nullptr }; - // toModel->getContext()->get(to_tstm); - // if (!to_tstm) - // { - // msg_error() << "No TetrahedronSetTopologyModifier found in the Tetrahedron topology Node."; - // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - // return; - // } - // INITIALISATION of TETRAHEDRAL mesh from HEXAHEDRAL mesh : // TetrahedronSetTopologyContainer *toModel { nullptr }; From 23e74013389c2a4a9af659173c6f0f3f28185c52 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:52:25 +0100 Subject: [PATCH 03/10] the output model does not need to be a TetrahedronSetTopologyContainer as we already have a BaseMeshTopology --- .../topology/mapping/Hexa2TetraTopologicalMapping.cpp | 9 --------- 1 file changed, 9 deletions(-) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp index 851c99c3072..4ee617f24ee 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp @@ -76,15 +76,6 @@ void Hexa2TetraTopologicalMapping::init() // INITIALISATION of TETRAHEDRAL mesh from HEXAHEDRAL mesh : - // TetrahedronSetTopologyContainer *toModel { nullptr }; - // toModel->getContext()->get(toModel); - // if (!toModel) - // { - // msg_error() << "No TetrahedronSetTopologyContainer found in the Tetrahedron topology Node."; - // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - // return; - // } - // Clear output topology toModel->clear(); From 57beb321f7935514990bd1c9e343b50e340f6a76 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:54:15 +0100 Subject: [PATCH 04/10] check pointer --- .../topology/mapping/Hexa2TetraTopologicalMapping.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp index 4ee617f24ee..71383919cfd 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp @@ -68,6 +68,13 @@ void Hexa2TetraTopologicalMapping::init() Inherit1::init(); + if (toModel == nullptr) + { + msg_error() << "No target topology container found."; + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } + // if (!this->checkTopologyInputTypes()) // method will display error message if false // { // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); From 02b4eca9439cd6771aae04d412e1d6563c8f190d Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 11:55:52 +0100 Subject: [PATCH 05/10] don't check topology input types because it can be an empty MeshTopology --- .../topology/mapping/Hexa2TetraTopologicalMapping.cpp | 6 ------ 1 file changed, 6 deletions(-) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp index 71383919cfd..e42d04d76a0 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2TetraTopologicalMapping.cpp @@ -75,12 +75,6 @@ void Hexa2TetraTopologicalMapping::init() return; } - // if (!this->checkTopologyInputTypes()) // method will display error message if false - // { - // this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); - // return; - // } - // INITIALISATION of TETRAHEDRAL mesh from HEXAHEDRAL mesh : // Clear output topology From da57f8235524d3e46fe5b62f7dae722e9ce02efc Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 9 Feb 2026 12:01:47 +0100 Subject: [PATCH 06/10] Replace TetrahedronSetTopology components with MeshTopology in scene files --- ...etrahedronHyperelasticityFEMForceField.scn | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/examples/Component/SolidMechanics/FEM/TetrahedronHyperelasticityFEMForceField.scn b/examples/Component/SolidMechanics/FEM/TetrahedronHyperelasticityFEMForceField.scn index 278dcc80140..fa3fdef7756 100644 --- a/examples/Component/SolidMechanics/FEM/TetrahedronHyperelasticityFEMForceField.scn +++ b/examples/Component/SolidMechanics/FEM/TetrahedronHyperelasticityFEMForceField.scn @@ -51,9 +51,7 @@ - - - + @@ -75,9 +73,7 @@ - - - + @@ -100,9 +96,7 @@ - - - + @@ -125,9 +119,7 @@ - - - + @@ -149,9 +141,7 @@ - - - + From bb796552c5a0bc9e1cf4b11ca66138168c2fce55 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Sun, 1 Mar 2026 17:31:41 +0100 Subject: [PATCH 07/10] Add Hexa2PrismTopologicalMapping component and registration --- .../Component/Topology/Mapping/CMakeLists.txt | 2 + .../mapping/Hexa2PrismTopologicalMapping.cpp | 165 ++++++++++++++++++ .../mapping/Hexa2PrismTopologicalMapping.h | 30 ++++ .../sofa/component/topology/mapping/init.cpp | 2 + 4 files changed, 199 insertions(+) create mode 100644 Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp create mode 100644 Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h diff --git a/Sofa/Component/Topology/Mapping/CMakeLists.txt b/Sofa/Component/Topology/Mapping/CMakeLists.txt index f39826a9308..ce20badac38 100644 --- a/Sofa/Component/Topology/Mapping/CMakeLists.txt +++ b/Sofa/Component/Topology/Mapping/CMakeLists.txt @@ -8,6 +8,7 @@ set(HEADER_FILES ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/init.h ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/CenterPointTopologicalMapping.h ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Edge2QuadTopologicalMapping.h + ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2PrismTopologicalMapping.h ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2QuadTopologicalMapping.h ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2TetraTopologicalMapping.h ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/IdentityTopologicalMapping.h @@ -21,6 +22,7 @@ set(SOURCE_FILES ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/init.cpp ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/CenterPointTopologicalMapping.cpp ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Edge2QuadTopologicalMapping.cpp + ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2PrismTopologicalMapping.cpp ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2QuadTopologicalMapping.cpp ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Hexa2TetraTopologicalMapping.cpp ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/IdentityTopologicalMapping.cpp diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp new file mode 100644 index 00000000000..7e8aea52dfd --- /dev/null +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp @@ -0,0 +1,165 @@ +#include +#include +#include + +#include + +#include +#include +#include +#include + +namespace sofa::component::topology::mapping +{ + +void registerHexa2PrismTopologicalMapping(sofa::core::ObjectFactory* factory) +{ + factory->registerObjects(core::ObjectRegistrationData("Topological mapping where HexahedronSetTopology is converted to PrismSetTopology") + .add< Hexa2PrismTopologicalMapping >()); +} + +Hexa2PrismTopologicalMapping::Hexa2PrismTopologicalMapping() + : sofa::core::topology::TopologicalMapping() + , d_swapping(initData(&d_swapping, false, "swapping", "Boolean enabling to swap hexa-edges\n in order to avoid bias effect")) +{ + m_inputType = geometry::ElementType::HEXAHEDRON; + m_outputType = geometry::ElementType::PRISM; +} + +Hexa2PrismTopologicalMapping::~Hexa2PrismTopologicalMapping() +{ +} + +void Hexa2PrismTopologicalMapping::init() +{ + using namespace container::dynamic; + + Inherit1::init(); + + if (toModel == nullptr) + { + msg_error() << "No target topology container found."; + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } + + // INITIALISATION of PRISM mesh from HEXAHEDRAL mesh: + + // Clear output topology + toModel->clear(); + + // Set the same number of points + toModel->setNbPoints(fromModel->getNbPoints()); + + auto Loc2GlobVec = sofa::helper::getWriteOnlyAccessor(Loc2GlobDataVec); + Loc2GlobVec.clear(); + Glob2LocMap.clear(); + + const size_t nbcubes = fromModel->getNbHexahedra(); + + // These values are only correct if the mesh is a grid topology + int nx = 2; + int ny = 1; + { + const auto* grid = dynamic_cast(fromModel.get()); + if (grid != nullptr) + { + nx = grid->getNx() - 1; + ny = grid->getNy() - 1; + } + } + + static constexpr int numberPrismsInHexa = 2; + Loc2GlobVec.reserve(nbcubes * numberPrismsInHexa); + + const bool swapping = d_swapping.getValue(); + + // Tessellation of each cube into 2 triangular prisms + // Hexahedron vertices: + // 4-------7 + // /| /| + // 0-------3 | + // | 5----|--6 + // |/ | / + // 1-------2 + // + // Decomposition into 2 prisms: + // - Prism 1: vertices [0, 1, 5] (bottom triangle) and [3, 2, 6] (top triangle) + // - Prism 2: vertices [0, 5, 4] (bottom triangle) and [3, 6, 7] (top triangle) + // This ensures face consistency between neighboring hexahedra + + for (size_t i = 0; i < nbcubes; ++i) + { + core::topology::BaseMeshTopology::Hexa c = fromModel->getHexahedron(i); + + bool swapped = false; + + if (swapping) + { + if (!((i % nx) & 1)) + { + // swap all points on the X edges + std::swap(c[0], c[1]); + std::swap(c[3], c[2]); + std::swap(c[4], c[5]); + std::swap(c[7], c[6]); + swapped = !swapped; + } + if (((i / nx) % ny) & 1) + { + // swap all points on the Y edges + std::swap(c[0], c[3]); + std::swap(c[1], c[2]); + std::swap(c[4], c[7]); + std::swap(c[5], c[6]); + swapped = !swapped; + } + if ((i / (nx * ny)) & 1) + { + // swap all points on the Z edges + std::swap(c[0], c[4]); + std::swap(c[1], c[5]); + std::swap(c[2], c[6]); + std::swap(c[3], c[7]); + swapped = !swapped; + } + } + + if (!swapped) + { + // Standard decomposition ensuring face consistency between neighbors + toModel->addPrism(c[0], c[1], c[5], c[3], c[2], c[6]); // Prism 1 + toModel->addPrism(c[0], c[5], c[4], c[3], c[6], c[7]); // Prism 2 + } + else + { + // Swapped decomposition + toModel->addPrism(c[0], c[1], c[5], c[3], c[2], c[6]); // Prism 1 + toModel->addPrism(c[0], c[5], c[4], c[3], c[6], c[7]); // Prism 2 + } + + for (int j = 0; j < numberPrismsInHexa; ++j) + { + Loc2GlobVec.push_back(i); + } + Glob2LocMap[i] = static_cast(Loc2GlobVec.size()) - 1; + } + + // Need to fully init the target topology + toModel->init(); + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); +} + +Index Hexa2PrismTopologicalMapping::getFromIndex(Index /*ind*/) +{ + return sofa::InvalidID; +} + +void Hexa2PrismTopologicalMapping::updateTopologicalMappingTopDown() +{ + msg_warning() << "Method Hexa2PrismTopologicalMapping::updateTopologicalMappingTopDown() not yet implemented!"; + // TODO... +} + +} // namespace sofa::component::topology::mapping diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h new file mode 100644 index 00000000000..b4bffbfc15e --- /dev/null +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h @@ -0,0 +1,30 @@ + +#pragma once + +#include +#include +#include + +namespace sofa::component::topology::mapping +{ + +class Hexa2PrismTopologicalMapping : public sofa::core::topology::TopologicalMapping +{ +public: + SOFA_CLASS(Hexa2PrismTopologicalMapping, sofa::core::topology::TopologicalMapping); + + using Index = sofa::core::topology::BaseMeshTopology::Index; + using Prism = sofa::core::topology::BaseMeshTopology::Prism; + + Hexa2PrismTopologicalMapping(); + virtual ~Hexa2PrismTopologicalMapping(); + + virtual void init() override; + virtual Index getFromIndex(Index ind) override; + virtual void updateTopologicalMappingTopDown() override; + +private: + sofa::Data d_swapping; +}; + +} // namespace sofa::component::topology::mapping diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/init.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/init.cpp index 7a520cdffd2..c8be6966b7d 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/init.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/init.cpp @@ -28,6 +28,7 @@ namespace sofa::component::topology::mapping extern void registerCenterPointTopologicalMapping(sofa::core::ObjectFactory* factory); extern void registerEdge2QuadTopologicalMapping(sofa::core::ObjectFactory* factory); +extern void registerHexa2PrismTopologicalMapping(sofa::core::ObjectFactory* factory); extern void registerHexa2QuadTopologicalMapping(sofa::core::ObjectFactory* factory); extern void registerHexa2TetraTopologicalMapping(sofa::core::ObjectFactory* factory); extern void registerIdentityTopologicalMapping(sofa::core::ObjectFactory* factory); @@ -62,6 +63,7 @@ void registerObjects(sofa::core::ObjectFactory* factory) { registerCenterPointTopologicalMapping(factory); registerEdge2QuadTopologicalMapping(factory); + registerHexa2PrismTopologicalMapping(factory); registerHexa2QuadTopologicalMapping(factory); registerHexa2TetraTopologicalMapping(factory); registerIdentityTopologicalMapping(factory); From 6316f99e36290ddc85eed9313af4fd41f34e7a35 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Sun, 1 Mar 2026 17:42:44 +0100 Subject: [PATCH 08/10] Add support for rendering prisms in DrawMesh --- .../Core/src/sofa/core/visual/DrawMesh.h | 117 +++++++++++++++++- 1 file changed, 115 insertions(+), 2 deletions(-) diff --git a/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h b/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h index cc1df95ff66..1239e4035d3 100644 --- a/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h +++ b/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h @@ -361,6 +361,116 @@ struct SOFA_CORE_API DrawElementMesh } }; +template<> +struct SOFA_CORE_API DrawElementMesh + : public BaseDrawMesh, 5> +{ + using ElementType = sofa::geometry::Prism; + friend BaseDrawMesh; + static constexpr std::size_t NumberTrianglesInPrism = 2; + static constexpr std::size_t NumberQuadsInPrism = 3; + + static constexpr ColorContainer defaultColors { + sofa::type::RGBAColor::green(), + sofa::type::RGBAColor::teal(), + sofa::type::RGBAColor(0.7f,0.7f,0.1f,1.f), + sofa::type::RGBAColor(0.7f,0.0f,0.0f,1.f), + sofa::type::RGBAColor(0.0f,0.7f,0.0f,1.f) + }; + +private: + template + void doDraw( + sofa::helper::visual::DrawTool* drawTool, + const PositionContainer& position, + sofa::core::topology::BaseMeshTopology* topology, + const IndicesContainer& elementIndices, + const ColorContainer& colors) + { + if (!topology) + return; + + const auto& elements = topology->getPrisms(); + + // Allocate space for rendering points + for ( auto& p : renderedPoints) + { + p.resize((elementIndices.size() * NumberTrianglesInPrism * sofa::geometry::Triangle::NumberOfNodes) + + (elementIndices.size() * NumberQuadsInPrism * sofa::geometry::Quad::NumberOfNodes)); + } + + std::array renderedPointId {}; + + for (auto i : elementIndices) + { + const auto& prism = elements[i]; + const auto center = this->elementCenter(position, prism); + + // Draw bottom triangle (vertices 0, 1, 2) + { + const sofa::Index vertexIndices[sofa::geometry::Triangle::NumberOfNodes] = { prism[0], prism[1], prism[2] }; + for (std::size_t k = 0; k < sofa::geometry::Triangle::NumberOfNodes; ++k) + { + const auto p = this->applyElementSpace(position[vertexIndices[k]], center); + renderedPoints[0][renderedPointId[0]++] = sofa::type::toVec3(p); + } + } + + // Draw top triangle (vertices 3, 4, 5) + { + const sofa::Index vertexIndices[sofa::geometry::Triangle::NumberOfNodes] = { prism[3], prism[4], prism[5] }; + for (std::size_t k = 0; k < sofa::geometry::Triangle::NumberOfNodes; ++k) + { + const auto p = this->applyElementSpace(position[vertexIndices[k]], center); + renderedPoints[1][renderedPointId[1]++] = sofa::type::toVec3(p); + } + } + + // Draw quad face 1 (vertices 0, 1, 4, 3) + { + const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[0], prism[1], prism[4], prism[3] }; + for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) + { + const auto p = this->applyElementSpace(position[vertexIndices[k]], center); + renderedPoints[2][renderedPointId[2]++] = sofa::type::toVec3(p); + } + } + + // Draw quad face 2 (vertices 1, 2, 5, 4) + { + const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[1], prism[2], prism[5], prism[4] }; + for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) + { + const auto p = this->applyElementSpace(position[vertexIndices[k]], center); + renderedPoints[3][renderedPointId[3]++] = sofa::type::toVec3(p); + } + } + + // Draw quad face 3 (vertices 2, 0, 3, 5) + { + const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[2], prism[0], prism[3], prism[5] }; + for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) + { + const auto p = this->applyElementSpace(position[vertexIndices[k]], center); + renderedPoints[4][renderedPointId[4]++] = sofa::type::toVec3(p); + } + } + } + + // Draw triangles + for (std::size_t j = 0; j < NumberTrianglesInPrism; ++j) + { + drawTool->drawTriangles(renderedPoints[j], colors[j]); + } + + // Draw quads + for (std::size_t j = 0; j < NumberQuadsInPrism; ++j) + { + drawTool->drawQuads(renderedPoints[NumberTrianglesInPrism + j], colors[NumberTrianglesInPrism + j]); + } + } +}; + template<> struct SOFA_CORE_API DrawElementMesh : public BaseDrawMesh, 6> @@ -460,6 +570,7 @@ class SOFA_CORE_API DrawMesh { drawElements(drawTool, position, topology); drawElements(drawTool, position, topology); + drawElements(drawTool, position, topology); } template @@ -477,8 +588,9 @@ class SOFA_CORE_API DrawMesh const auto hasTetra = !topology->getTetrahedra().empty(); const auto hasHexa = !topology->getHexahedra().empty(); + const auto hasPrism = !topology->getPrisms().empty(); - const bool hasVolumeElements = hasTetra || hasHexa; + const bool hasVolumeElements = hasTetra || hasHexa || hasPrism; if (!hasSurfaceElements && !hasVolumeElements) { @@ -503,7 +615,8 @@ class SOFA_CORE_API DrawMesh DrawElementMesh, DrawElementMesh, DrawElementMesh, - DrawElementMesh + DrawElementMesh, + DrawElementMesh > m_meshes; }; From 5ca0e632309260af87df8f3b2528eb6b66c4b3d7 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 2 Mar 2026 15:04:24 +0100 Subject: [PATCH 09/10] Fix DrawMesh and Hexa2PrismTopologicalMapping --- .../mapping/Hexa2PrismTopologicalMapping.cpp | 137 ++++++------------ .../mapping/Hexa2PrismTopologicalMapping.h | 37 +++-- .../Core/src/sofa/core/visual/DrawMesh.h | 81 ++++------- .../Geometry/src/sofa/geometry/Prism.h | 13 ++ 4 files changed, 112 insertions(+), 156 deletions(-) diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp index 7e8aea52dfd..0211e3ab7bc 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.cpp @@ -1,14 +1,27 @@ +/****************************************************************************** +* 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 - namespace sofa::component::topology::mapping { @@ -19,21 +32,13 @@ void registerHexa2PrismTopologicalMapping(sofa::core::ObjectFactory* factory) } Hexa2PrismTopologicalMapping::Hexa2PrismTopologicalMapping() - : sofa::core::topology::TopologicalMapping() - , d_swapping(initData(&d_swapping, false, "swapping", "Boolean enabling to swap hexa-edges\n in order to avoid bias effect")) { m_inputType = geometry::ElementType::HEXAHEDRON; m_outputType = geometry::ElementType::PRISM; } -Hexa2PrismTopologicalMapping::~Hexa2PrismTopologicalMapping() -{ -} - void Hexa2PrismTopologicalMapping::init() { - using namespace container::dynamic; - Inherit1::init(); if (toModel == nullptr) @@ -43,8 +48,11 @@ void Hexa2PrismTopologicalMapping::init() return; } - // INITIALISATION of PRISM mesh from HEXAHEDRAL mesh: + convertHexaToPrisms(); +} +void Hexa2PrismTopologicalMapping::convertHexaToPrisms() +{ // Clear output topology toModel->clear(); @@ -55,90 +63,39 @@ void Hexa2PrismTopologicalMapping::init() Loc2GlobVec.clear(); Glob2LocMap.clear(); - const size_t nbcubes = fromModel->getNbHexahedra(); - - // These values are only correct if the mesh is a grid topology - int nx = 2; - int ny = 1; - { - const auto* grid = dynamic_cast(fromModel.get()); - if (grid != nullptr) - { - nx = grid->getNx() - 1; - ny = grid->getNy() - 1; - } - } - - static constexpr int numberPrismsInHexa = 2; - Loc2GlobVec.reserve(nbcubes * numberPrismsInHexa); + const sofa::Size nbCubes = fromModel->getNbHexahedra(); - const bool swapping = d_swapping.getValue(); + static constexpr std::size_t numberPrismsInHexa = 2; + Loc2GlobVec.reserve(nbCubes * numberPrismsInHexa); // Tessellation of each cube into 2 triangular prisms // Hexahedron vertices: - // 4-------7 - // /| /| - // 0-------3 | - // | 5----|--6 - // |/ | / - // 1-------2 + // Y n3---------n2 + // ^ / /| + // | / / | + // n7---------n6 | + // | | | + // | n0------|--n1 + // | / | / + // |/ |/ + // n4---------n5-->X + // / + // / + // Z // // Decomposition into 2 prisms: - // - Prism 1: vertices [0, 1, 5] (bottom triangle) and [3, 2, 6] (top triangle) - // - Prism 2: vertices [0, 5, 4] (bottom triangle) and [3, 6, 7] (top triangle) - // This ensures face consistency between neighboring hexahedra + // - Prism 1: vertices [0, 5, 1] (bottom triangle) and [3, 6, 2] (top triangle) + // - Prism 2: vertices [0, 4, 5] (bottom triangle) and [3, 7, 6] (top triangle) - for (size_t i = 0; i < nbcubes; ++i) + for (size_t i = 0; i < nbCubes; ++i) { core::topology::BaseMeshTopology::Hexa c = fromModel->getHexahedron(i); - bool swapped = false; - - if (swapping) - { - if (!((i % nx) & 1)) - { - // swap all points on the X edges - std::swap(c[0], c[1]); - std::swap(c[3], c[2]); - std::swap(c[4], c[5]); - std::swap(c[7], c[6]); - swapped = !swapped; - } - if (((i / nx) % ny) & 1) - { - // swap all points on the Y edges - std::swap(c[0], c[3]); - std::swap(c[1], c[2]); - std::swap(c[4], c[7]); - std::swap(c[5], c[6]); - swapped = !swapped; - } - if ((i / (nx * ny)) & 1) - { - // swap all points on the Z edges - std::swap(c[0], c[4]); - std::swap(c[1], c[5]); - std::swap(c[2], c[6]); - std::swap(c[3], c[7]); - swapped = !swapped; - } - } - - if (!swapped) - { - // Standard decomposition ensuring face consistency between neighbors - toModel->addPrism(c[0], c[1], c[5], c[3], c[2], c[6]); // Prism 1 - toModel->addPrism(c[0], c[5], c[4], c[3], c[6], c[7]); // Prism 2 - } - else - { - // Swapped decomposition - toModel->addPrism(c[0], c[1], c[5], c[3], c[2], c[6]); // Prism 1 - toModel->addPrism(c[0], c[5], c[4], c[3], c[6], c[7]); // Prism 2 - } + // Standard decomposition ensuring face consistency between neighbors + toModel->addPrism(c[0], c[5], c[1], c[3], c[6], c[2]); // Prism 1 + toModel->addPrism(c[0], c[4], c[5], c[3], c[7], c[6]); // Prism 2 - for (int j = 0; j < numberPrismsInHexa; ++j) + for (unsigned j = 0; j < numberPrismsInHexa; ++j) { Loc2GlobVec.push_back(i); } diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h index b4bffbfc15e..8956879df6b 100644 --- a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h +++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/Hexa2PrismTopologicalMapping.h @@ -1,30 +1,45 @@ - +/****************************************************************************** +* 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 namespace sofa::component::topology::mapping { -class Hexa2PrismTopologicalMapping : public sofa::core::topology::TopologicalMapping +class SOFA_COMPONENT_TOPOLOGY_MAPPING_API Hexa2PrismTopologicalMapping : public sofa::core::topology::TopologicalMapping { public: SOFA_CLASS(Hexa2PrismTopologicalMapping, sofa::core::topology::TopologicalMapping); - using Index = sofa::core::topology::BaseMeshTopology::Index; - using Prism = sofa::core::topology::BaseMeshTopology::Prism; - - Hexa2PrismTopologicalMapping(); - virtual ~Hexa2PrismTopologicalMapping(); - virtual void init() override; virtual Index getFromIndex(Index ind) override; virtual void updateTopologicalMappingTopDown() override; -private: - sofa::Data d_swapping; +protected: + Hexa2PrismTopologicalMapping(); + + void convertHexaToPrisms(); }; } // namespace sofa::component::topology::mapping diff --git a/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h b/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h index 1239e4035d3..d08445b1fb4 100644 --- a/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h +++ b/Sofa/framework/Core/src/sofa/core/visual/DrawMesh.h @@ -373,9 +373,9 @@ struct SOFA_CORE_API DrawElementMesh static constexpr ColorContainer defaultColors { sofa::type::RGBAColor::green(), sofa::type::RGBAColor::teal(), - sofa::type::RGBAColor(0.7f,0.7f,0.1f,1.f), - sofa::type::RGBAColor(0.7f,0.0f,0.0f,1.f), - sofa::type::RGBAColor(0.0f,0.7f,0.0f,1.f) + sofa::type::RGBAColor::navy(), + sofa::type::RGBAColor::gold(), + sofa::type::RGBAColor::purple() }; private: @@ -393,11 +393,11 @@ struct SOFA_CORE_API DrawElementMesh const auto& elements = topology->getPrisms(); // Allocate space for rendering points - for ( auto& p : renderedPoints) - { - p.resize((elementIndices.size() * NumberTrianglesInPrism * sofa::geometry::Triangle::NumberOfNodes) + - (elementIndices.size() * NumberQuadsInPrism * sofa::geometry::Quad::NumberOfNodes)); - } + renderedPoints[0].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes); + renderedPoints[1].resize(elementIndices.size() * sofa::geometry::Triangle::NumberOfNodes); + renderedPoints[2].resize(elementIndices.size() * sofa::geometry::Quad::NumberOfNodes); + renderedPoints[3].resize(elementIndices.size() * sofa::geometry::Quad::NumberOfNodes); + renderedPoints[4].resize(elementIndices.size() * sofa::geometry::Quad::NumberOfNodes); std::array renderedPointId {}; @@ -406,68 +406,39 @@ struct SOFA_CORE_API DrawElementMesh const auto& prism = elements[i]; const auto center = this->elementCenter(position, prism); - // Draw bottom triangle (vertices 0, 1, 2) - { - const sofa::Index vertexIndices[sofa::geometry::Triangle::NumberOfNodes] = { prism[0], prism[1], prism[2] }; - for (std::size_t k = 0; k < sofa::geometry::Triangle::NumberOfNodes; ++k) - { - const auto p = this->applyElementSpace(position[vertexIndices[k]], center); - renderedPoints[0][renderedPointId[0]++] = sofa::type::toVec3(p); - } - } - - // Draw top triangle (vertices 3, 4, 5) + const auto drawTriangle = [&](sofa::Index bufferId, sofa::Index v0, sofa::Index v1, sofa::Index v2) { - const sofa::Index vertexIndices[sofa::geometry::Triangle::NumberOfNodes] = { prism[3], prism[4], prism[5] }; + const std::array vertexIndices { prism[v0], prism[v1], prism[v2] }; for (std::size_t k = 0; k < sofa::geometry::Triangle::NumberOfNodes; ++k) { const auto p = this->applyElementSpace(position[vertexIndices[k]], center); - renderedPoints[1][renderedPointId[1]++] = sofa::type::toVec3(p); - } - } - - // Draw quad face 1 (vertices 0, 1, 4, 3) - { - const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[0], prism[1], prism[4], prism[3] }; - for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) - { - const auto p = this->applyElementSpace(position[vertexIndices[k]], center); - renderedPoints[2][renderedPointId[2]++] = sofa::type::toVec3(p); + renderedPoints[bufferId][renderedPointId[bufferId]++] = sofa::type::toVec3(p); } - } + }; - // Draw quad face 2 (vertices 1, 2, 5, 4) - { - const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[1], prism[2], prism[5], prism[4] }; - for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) - { - const auto p = this->applyElementSpace(position[vertexIndices[k]], center); - renderedPoints[3][renderedPointId[3]++] = sofa::type::toVec3(p); - } - } + drawTriangle(0, 0, 1, 2); + drawTriangle(1, 5, 4, 3); - // Draw quad face 3 (vertices 2, 0, 3, 5) + const auto drawQuad = [&](sofa::Index bufferId, sofa::Index v0, sofa::Index v1, sofa::Index v2, sofa::Index v3) { - const sofa::Index vertexIndices[sofa::geometry::Quad::NumberOfNodes] = { prism[2], prism[0], prism[3], prism[5] }; + const std::array vertexIndices { prism[v0], prism[v1], prism[v2], prism[v3] }; for (std::size_t k = 0; k < sofa::geometry::Quad::NumberOfNodes; ++k) { const auto p = this->applyElementSpace(position[vertexIndices[k]], center); - renderedPoints[4][renderedPointId[4]++] = sofa::type::toVec3(p); + renderedPoints[bufferId][renderedPointId[bufferId]++] = sofa::type::toVec3(p); } - } - } + }; - // Draw triangles - for (std::size_t j = 0; j < NumberTrianglesInPrism; ++j) - { - drawTool->drawTriangles(renderedPoints[j], colors[j]); + drawQuad(2, 0, 2, 5, 3); + drawQuad(3, 0, 3, 4, 1); + drawQuad(4, 1, 4, 5, 2); } - // Draw quads - for (std::size_t j = 0; j < NumberQuadsInPrism; ++j) - { - drawTool->drawQuads(renderedPoints[NumberTrianglesInPrism + j], colors[NumberTrianglesInPrism + j]); - } + drawTool->drawTriangles(renderedPoints[0], colors[0]); + drawTool->drawTriangles(renderedPoints[1], colors[1]); + drawTool->drawQuads(renderedPoints[2], colors[2]); + drawTool->drawQuads(renderedPoints[3], colors[3]); + drawTool->drawQuads(renderedPoints[4], colors[4]); } }; diff --git a/Sofa/framework/Geometry/src/sofa/geometry/Prism.h b/Sofa/framework/Geometry/src/sofa/geometry/Prism.h index 0bdcdc48431..3dc55a7e5fb 100644 --- a/Sofa/framework/Geometry/src/sofa/geometry/Prism.h +++ b/Sofa/framework/Geometry/src/sofa/geometry/Prism.h @@ -33,6 +33,19 @@ struct Prism static constexpr ElementType Element_type = ElementType::PRISM; Prism() = delete; + + // CONVENTION : indices ordering for the nodes of a prism : + // + // 5 + // / | \ + // / | \ + // 3---+----4 + // | | | + // | 2 | + // | / \ | + // |/ \| + // 0--------1 + // }; using Pentahedron SOFA_ATTRIBUTE_DEPRECATED("v25.12", "v26.06", "Pentahedron is renamed to Prism") = Prism; From 0526cc24b682f9c1fc67dcdcfdeb8fdbbdf20e57 Mon Sep 17 00:00:00 2001 From: Alex Bilger Date: Mon, 2 Mar 2026 15:04:37 +0100 Subject: [PATCH 10/10] add example scene --- .../Mapping/Hexa2PrismTopologicalMapping.scn | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 examples/Component/Topology/Mapping/Hexa2PrismTopologicalMapping.scn diff --git a/examples/Component/Topology/Mapping/Hexa2PrismTopologicalMapping.scn b/examples/Component/Topology/Mapping/Hexa2PrismTopologicalMapping.scn new file mode 100644 index 00000000000..00196cbadc0 --- /dev/null +++ b/examples/Component/Topology/Mapping/Hexa2PrismTopologicalMapping.scn @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + +