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 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +