diff --git a/Sofa/Component/Topology/Mapping/CMakeLists.txt b/Sofa/Component/Topology/Mapping/CMakeLists.txt
index f39826a9308..abbbc099e7f 100644
--- a/Sofa/Component/Topology/Mapping/CMakeLists.txt
+++ b/Sofa/Component/Topology/Mapping/CMakeLists.txt
@@ -13,6 +13,7 @@ set(HEADER_FILES
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/IdentityTopologicalMapping.h
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Quad2TriangleTopologicalMapping.h
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/SubsetTopologicalMapping.h
+ ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/SubsetTopologicalMultiMapping.h
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Tetra2TriangleTopologicalMapping.h
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Triangle2EdgeTopologicalMapping.h
)
@@ -26,6 +27,7 @@ set(SOURCE_FILES
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/IdentityTopologicalMapping.cpp
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Quad2TriangleTopologicalMapping.cpp
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/SubsetTopologicalMapping.cpp
+ ${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/SubsetTopologicalMultiMapping.cpp
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Tetra2TriangleTopologicalMapping.cpp
${SOFACOMPONENTTOPOLOGYMAPPING_SOURCE_DIR}/Triangle2EdgeTopologicalMapping.cpp
)
diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.cpp b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.cpp
new file mode 100644
index 00000000000..05dc18bab92
--- /dev/null
+++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.cpp
@@ -0,0 +1,212 @@
+/******************************************************************************
+ * 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
+
+namespace sofa::component::topology::mapping
+{
+
+void registerSubsetTopologicalMultiMapping(sofa::core::ObjectFactory* factory)
+{
+ factory->registerObjects(
+ core::ObjectRegistrationData("Merges multiple input topologies (points, edges, triangles, "
+ "quads, tetrahedra, hexahedra) "
+ "into a single output topology with index remapping. "
+ "Exposes indexPairs Data for linking to SubsetMultiMapping.")
+ .add());
+}
+
+SubsetTopologicalMultiMapping::SubsetTopologicalMultiMapping()
+ : l_inputs(initLink("input", "Input topology sources to merge")),
+ l_output(initLink("output", "Output merged topology")),
+ d_flipNormals(
+ initData(&d_flipNormals, sofa::type::vector(), "flipNormals",
+ "Per-source boolean flags to reverse triangle and quad winding order")),
+ d_indexPairs(initData(&d_indexPairs, sofa::type::vector(), "indexPairs",
+ "Output: flat array of (source_index, coord_in_source) pairs"))
+{
+ d_indexPairs.setReadOnly(true);
+}
+
+SubsetTopologicalMultiMapping::~SubsetTopologicalMultiMapping() = default;
+
+void SubsetTopologicalMultiMapping::init()
+{
+ BaseObject::init();
+
+ if (l_inputs.size())
+ {
+ for (const auto inputTopology : l_inputs)
+ {
+ if (!inputTopology.get())
+ {
+ msg_error() << "Input topology '" << inputTopology.path << "' could not be found.";
+ d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+ }
+ }
+ else
+ {
+ msg_error() << "No input topologies found to be linked. Set the 'input' Data with "
+ "paths to the topologies considered as inputs for the mapping.";
+ d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+
+ if (!l_output.get())
+ {
+ msg_error()
+ << "Null output topology. Set the 'output' Data with the path to the output topology.";
+ d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+
+ mapTopologies();
+
+ d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid);
+}
+
+void SubsetTopologicalMultiMapping::reinit()
+{
+ if (d_componentState.getValue() == sofa::core::objectmodel::ComponentState::Invalid) return;
+
+ mapTopologies();
+}
+
+void SubsetTopologicalMultiMapping::mapTopologies()
+{
+ const auto numInputs = l_inputs.size();
+
+ l_output->clear();
+ m_pointOffsets.clear();
+
+ // Compute per-source point offsets
+ sofa::Size totalPoints{0};
+ for (const auto input : l_inputs)
+ {
+ m_pointOffsets.push_back(totalPoints);
+ totalPoints += input->getNbPoints();
+ }
+ l_output->setNbPoints(totalPoints);
+
+ // Build indexPairs necessary to attach mechanical mapping through SubsetMultiMapping link
+ {
+ auto indexPairs = sofa::helper::getWriteOnlyAccessor(d_indexPairs);
+ indexPairs.clear();
+ indexPairs.reserve(totalPoints * 2);
+
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const auto nbPts = l_inputs.get(srcIdx)->getNbPoints();
+ for (sofa::Size p = 0; p < nbPts; ++p)
+ {
+ indexPairs.push_back(srcIdx);
+ indexPairs.push_back(p);
+ }
+ }
+ }
+
+ // Pre-normalize flipNormals to exactly numInputs entries
+ auto flipVec = sofa::helper::getWriteAccessor(d_flipNormals);
+ if (flipVec.size() > numInputs)
+ {
+ msg_warning() << "flipNormals has " << flipVec.size() << " entries but there are only "
+ << numInputs << " input topologies. Extra entries will be discarded.";
+ flipVec.resize(numInputs);
+ }
+ else if (flipVec.size() < numInputs)
+ {
+ flipVec.resize(numInputs, false);
+ }
+
+ // Concatenate edges from sources with offset remapping
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const sofa::Size offset = m_pointOffsets[srcIdx];
+
+ for (const auto& edge : l_inputs.get(srcIdx)->getEdges())
+ l_output->addEdge(edge[0] + offset, edge[1] + offset);
+ }
+
+ // Concatenate triangles with offset remapping; optionally flip normals
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const sofa::Size offset = m_pointOffsets[srcIdx];
+ const bool flip = flipVec[srcIdx];
+
+ for (const auto& tri : l_inputs.get(srcIdx)->getTriangles())
+ {
+ if (flip)
+ l_output->addTriangle(tri[0] + offset, tri[2] + offset, tri[1] + offset);
+ else
+ l_output->addTriangle(tri[0] + offset, tri[1] + offset, tri[2] + offset);
+ }
+ }
+
+ // Concatenate quads with offset remapping; optionally flip normals
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const sofa::Size offset = m_pointOffsets[srcIdx];
+ const bool flip = flipVec[srcIdx];
+
+ for (auto& quad : l_inputs.get(srcIdx)->getQuads())
+ {
+ if (flip)
+ l_output->addQuad(quad[0] + offset, quad[3] + offset, quad[2] + offset,
+ quad[1] + offset);
+ else
+ l_output->addQuad(quad[0] + offset, quad[1] + offset, quad[2] + offset,
+ quad[3] + offset);
+ }
+ }
+
+ // Concatenate tetrahedra with offset remapping
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const sofa::Size offset = m_pointOffsets[srcIdx];
+
+ for (const auto& tet : l_inputs.get(srcIdx)->getTetrahedra())
+ l_output->addTetra(tet[0] + offset, tet[1] + offset, tet[2] + offset, tet[3] + offset);
+ }
+
+ // Concatenate hexahedra with offset remapping
+ for (sofa::Size srcIdx = 0; srcIdx < numInputs; ++srcIdx)
+ {
+ const sofa::Size offset = m_pointOffsets[srcIdx];
+
+ for (const auto& hex : l_inputs.get(srcIdx)->getHexahedra())
+ {
+ l_output->addHexa(hex[0] + offset, hex[1] + offset, hex[2] + offset, hex[3] + offset,
+ hex[4] + offset, hex[5] + offset, hex[6] + offset, hex[7] + offset);
+ }
+ }
+
+ msg_info() << "Merged " << numInputs << " topologies: " << totalPoints << " points, "
+ << l_output->getNbEdges() << " edges, " << l_output->getNbTriangles()
+ << " triangles, " << l_output->getNbQuads() << " quads, "
+ << l_output->getNbTetrahedra() << " tetrahedra, " << l_output->getNbHexahedra()
+ << " hexahedra.";
+}
+
+} // namespace sofa::component::topology::mapping
diff --git a/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.h b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.h
new file mode 100644
index 00000000000..fafaf746f45
--- /dev/null
+++ b/Sofa/Component/Topology/Mapping/src/sofa/component/topology/mapping/SubsetTopologicalMultiMapping.h
@@ -0,0 +1,84 @@
+/******************************************************************************
+ * 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 SubsetTopologicalMultiMapping
+ * @brief Merges multiple input topologies into a single output topology.
+ *
+ * For each input topology, points are concatenated with index offsets, and geometric primitives
+ * (edges/triangles/quads/tetrahedra/hexahedra are remapped accordingly. Triangle/quad winding
+ * order can be reversed per source via flipNormals.
+ *
+ * This is the topological counterpart to SubsetMultiMapping.
+ * The indexPairs output can be linked to a SubsetMultiMapping in scenes.
+ *
+ * Merging is performed once during init(). Dynamic topology changes are not supported.
+ */
+class SOFA_COMPONENT_TOPOLOGY_MAPPING_API SubsetTopologicalMultiMapping
+ : public sofa::core::objectmodel::BaseObject
+{
+ public:
+ SOFA_CLASS(SubsetTopologicalMultiMapping, sofa::core::objectmodel::BaseObject);
+
+ using Index = sofa::Index;
+ using BaseMeshTopology = sofa::core::topology::BaseMeshTopology;
+
+ void init() override;
+ void reinit() override;
+
+ /// N input topologies
+ MultiLink
+ l_inputs;
+
+ /// Single output topology
+ SingleLink
+ l_output;
+
+ Data>
+ d_flipNormals; ///< Per-source flags to reverse triangle and/or quad winding order
+ Data>
+ d_indexPairs; ///< Output: flat array of (source_index, coord_in_source) pairs,
+ ///< linkable to SubsetMultiMapping
+
+ protected:
+ SubsetTopologicalMultiMapping();
+ ~SubsetTopologicalMultiMapping() override;
+
+ private:
+ void mapTopologies();
+
+ /// Per-source cumulative point offsets. m_pointOffsets[i] = total points from sources 0..i-1.
+ sofa::type::vector m_pointOffsets;
+};
+
+} // 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..44dcfdc8f66 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
@@ -33,6 +33,7 @@ extern void registerHexa2TetraTopologicalMapping(sofa::core::ObjectFactory* fact
extern void registerIdentityTopologicalMapping(sofa::core::ObjectFactory* factory);
extern void registerQuad2TriangleTopologicalMapping(sofa::core::ObjectFactory* factory);
extern void registerSubsetTopologicalMapping(sofa::core::ObjectFactory* factory);
+extern void registerSubsetTopologicalMultiMapping(sofa::core::ObjectFactory* factory);
extern void registerTetra2TriangleTopologicalMapping(sofa::core::ObjectFactory* factory);
extern void registerTriangle2EdgeTopologicalMapping(sofa::core::ObjectFactory* factory);
@@ -67,6 +68,7 @@ void registerObjects(sofa::core::ObjectFactory* factory)
registerIdentityTopologicalMapping(factory);
registerQuad2TriangleTopologicalMapping(factory);
registerSubsetTopologicalMapping(factory);
+ registerSubsetTopologicalMultiMapping(factory);
registerTetra2TriangleTopologicalMapping(factory);
registerTriangle2EdgeTopologicalMapping(factory);
}
diff --git a/examples/Component/Topology/Mapping/SubsetTopologicalMultiMapping.scn b/examples/Component/Topology/Mapping/SubsetTopologicalMultiMapping.scn
new file mode 100644
index 00000000000..feac0cef0e4
--- /dev/null
+++ b/examples/Component/Topology/Mapping/SubsetTopologicalMultiMapping.scn
@@ -0,0 +1,172 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+