From d2596dd2d1979f3cb4580e241f2ccdd8ad3ef559 Mon Sep 17 00:00:00 2001 From: epernod Date: Mon, 7 Sep 2026 16:02:32 +0200 Subject: [PATCH 1/6] [InfinyToolkit] ADD the InfinyToolkit.MeshRefinement plugin with the public controller The public face of the MeshRefinement plugin, as its own dll. A scene requires InfinyToolkit.MeshRefinement and names TetrahedronSubdivisionController; the subdivision engine stays private to MeshRefinement, reached only through its TetrahedronSubdivisionManager facade. Laid out as a sub-plugin the way MeshRefinement.CUDA is, with its own config.h.in, init and factory registration. The controller moved here verbatim apart from what ownership requires: the export macro is SOFA_INFINYTOOLKIT_MESHREFINEMENT_API and the namespace is sofa::infinytoolkit, since a consumer writing C++ against it is writing against InfinyToolkit now. The engine handle is qualified as sofa::meshrefinement::TetrahedronSubdivisionManager, which is the only name it takes from the other plugin. Its 8 tests moved with it, into the plugin's own test target - InfinyToolkit had no test infrastructure before this. They still load their scenes from MeshRefinement/examples, since that is where the meshes are; the tests pass against the new dll, cut and refinement both. CudaSubdivisionController.cpp carries the Cuda instantiations that MeshRefinement.CUDA used to hold, for the reason it cannot hold them now: the controller is here, and MeshRefinement must not depend on InfinyToolkit. It compiles only when both SofaCUDA and MeshRefinement.CUDA are found, and links the facade instantiations the latter exports. NOT BUILT HERE: SofaCUDA is not enabled in this build tree, so the Cuda path is written from the interface and never compiled. It wants checking on a machine with CUDA before anyone relies on the two Cuda scenes. --- CMakeLists.txt | 7 + extensions/MeshRefinement/CMakeLists.txt | 56 ++++ ...nfinyToolkit.MeshRefinementConfig.cmake.in | 9 + .../CudaSubdivisionController.cpp | 53 +++ .../TetrahedronSubdivisionController.cpp | 38 +++ .../TetrahedronSubdivisionController.h | 193 +++++++++++ .../TetrahedronSubdivisionController.inl | 295 +++++++++++++++++ .../InfinyToolkit/MeshRefinement/config.h.in | 30 ++ .../src/InfinyToolkit/MeshRefinement/init.cpp | 72 +++++ .../src/InfinyToolkit/MeshRefinement/init.h | 22 ++ .../MeshRefinement/tests/CMakeLists.txt | 16 + .../TetrahedronSubdivisionController_test.cpp | 304 ++++++++++++++++++ 12 files changed, 1095 insertions(+) create mode 100644 extensions/MeshRefinement/CMakeLists.txt create mode 100644 extensions/MeshRefinement/InfinyToolkit.MeshRefinementConfig.cmake.in create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.cpp create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/config.h.in create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.cpp create mode 100644 extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.h create mode 100644 extensions/MeshRefinement/tests/CMakeLists.txt create mode 100644 extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 52fac89..cf0aa53 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,6 +171,13 @@ target_include_directories(${PROJECT_NAME} PUBLIC "$") ## Install rules for the library; CMake package configurations files +# The public MeshRefinement controller, as its own plugin: a scene requires +# InfinyToolkit.MeshRefinement while the subdivision engine stays private to the +# MeshRefinement plugin behind its facade. +if(MeshRefinement_FOUND) + sofa_add_subdirectory(plugin extensions/MeshRefinement InfinyToolkit.MeshRefinement) +endif() + sofa_create_package_with_targets( PACKAGE_NAME ${PROJECT_NAME} PACKAGE_VERSION ${PROJECT_VERSION} diff --git a/extensions/MeshRefinement/CMakeLists.txt b/extensions/MeshRefinement/CMakeLists.txt new file mode 100644 index 0000000..abc03ed --- /dev/null +++ b/extensions/MeshRefinement/CMakeLists.txt @@ -0,0 +1,56 @@ +cmake_minimum_required(VERSION 3.12) +project(InfinyToolkit.MeshRefinement VERSION 0.1 LANGUAGES CXX) + +# The public face of the MeshRefinement plugin. The controller a scene instantiates +# lives here; the subdivision engine stays private to MeshRefinement, reached only +# through TetrahedronSubdivisionManager, its pimpl facade. +set(INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR src/InfinyToolkit/MeshRefinement) + +set(HEADER_FILES + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/config.h.in + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/init.h + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/TetrahedronSubdivisionController.h + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/TetrahedronSubdivisionController.inl +) + +set(SOURCE_FILES + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/init.cpp + ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/TetrahedronSubdivisionController.cpp +) + +sofa_find_package(MeshRefinement REQUIRED) +sofa_find_package(Sofa.Component.Topology.Container.Dynamic REQUIRED) + +# Optional: the Cuda template instantiations of the controller. They used to sit in +# MeshRefinement.CUDA, which cannot host them now that the controller lives here - +# MeshRefinement must not depend on InfinyToolkit. MeshRefinement.CUDA still +# instantiates the engine facade for the same types, being the only place that can +# see its private .inl, and this target links those symbols. +sofa_find_package(SofaCUDA QUIET) +sofa_find_package(MeshRefinement.CUDA QUIET) +if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) + message("${PROJECT_NAME}: SofaCUDA found - adding the Cuda subdivision controller") + list(APPEND SOURCE_FILES ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/CudaSubdivisionController.cpp) + add_definitions(-DINFINYTOOLKIT_MESHREFINEMENT_USES_SOFACUDA) +endif() + +add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES}) +target_compile_definitions(${PROJECT_NAME} PRIVATE "-DSOFA_BUILD_INFINYTOOLKIT_MESHREFINEMENT") +target_link_libraries(${PROJECT_NAME} MeshRefinement Sofa.Component.Topology.Container.Dynamic) + +if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) + target_link_libraries(${PROJECT_NAME} SofaCUDA MeshRefinement.CUDA) +endif() + +sofa_create_package_with_targets( + PACKAGE_NAME ${PROJECT_NAME} + PACKAGE_VERSION ${Sofa_VERSION} + TARGETS ${PROJECT_NAME} AUTO_SET_TARGET_PROPERTIES + INCLUDE_SOURCE_DIR "src" + INCLUDE_INSTALL_DIR "${PROJECT_NAME}" + RELOCATABLE "plugins" +) + +if(SOFA_BUILD_TESTS) + add_subdirectory(tests) +endif() diff --git a/extensions/MeshRefinement/InfinyToolkit.MeshRefinementConfig.cmake.in b/extensions/MeshRefinement/InfinyToolkit.MeshRefinementConfig.cmake.in new file mode 100644 index 0000000..e64de70 --- /dev/null +++ b/extensions/MeshRefinement/InfinyToolkit.MeshRefinementConfig.cmake.in @@ -0,0 +1,9 @@ +# CMake package configuration file for the InfinyToolkit.MeshRefinement library + +@PACKAGE_GUARD@ +@PACKAGE_INIT@ + +find_package(MeshRefinement QUIET REQUIRED) +find_package(Sofa.Component.Topology.Container.Dynamic QUIET REQUIRED) + +check_required_components(InfinyToolkit.MeshRefinement) diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp new file mode 100644 index 0000000..6dbf837 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp @@ -0,0 +1,53 @@ +/******************************************************************************* + * - Copyright (C) 2026-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ + +/// The Cuda instantiations of the controller. These used to live in +/// MeshRefinement.CUDA, which cannot host them any more: the controller is here now, +/// and MeshRefinement must not depend on InfinyToolkit. The engine side of the pair - +/// TetrahedronSubdivisionManager for the same Cuda types - is still instantiated by +/// MeshRefinement.CUDA, which is the only translation unit that can see the private +/// .inl; this file links those symbols through the facade's public header. +/// +/// Only compiled when SofaCUDA is found. + +#include +#include +#include + +namespace sofa::infinytoolkit::meshrefinement +{ + +void registerCudaTetrahedronSubdivisionController(sofa::core::ObjectFactory* factory) +{ + factory->registerObjects(sofa::core::ObjectRegistrationData("Controller handling subdivision operations on a tetrahedral mesh, on Cuda vector types.") + .add< sofa::infinytoolkit::TetrahedronSubdivisionController >() +#ifdef SOFA_GPU_CUDA_DOUBLE + .add< sofa::infinytoolkit::TetrahedronSubdivisionController >() +#endif + ); +} + +} // namespace sofa::infinytoolkit::meshrefinement + +namespace sofa::infinytoolkit +{ + +template class SOFA_INFINYTOOLKIT_MESHREFINEMENT_API TetrahedronSubdivisionController; + +#ifdef SOFA_GPU_CUDA_DOUBLE +template class SOFA_INFINYTOOLKIT_MESHREFINEMENT_API TetrahedronSubdivisionController; +#endif + +} // namespace sofa::infinytoolkit diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.cpp new file mode 100644 index 0000000..21ace75 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.cpp @@ -0,0 +1,38 @@ +/******************************************************************************* + * - Copyright (C) 2019-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#define SOFA_INFINYTOOLKIT_TETRAHEDRON_SUBDIVISION_CONTROLLER_CPP + +#include +#include + +namespace sofa::infinytoolkit::meshrefinement +{ + +using namespace sofa::core::topology; +using namespace sofa::component::topology; + +void registerTetrahedronSubdivisionController(sofa::core::ObjectFactory* factory) +{ + factory->registerObjects(sofa::core::ObjectRegistrationData("Controller handling subdivision operations on a tetrahedral mesh: cutting along a plane or between two surface triangles, and refining the whole mesh or a given set of tetrahedra.") + .add< sofa::infinytoolkit::TetrahedronSubdivisionController >()); +} + +} // namespace sofa::infinytoolkit::meshrefinement + +namespace sofa::infinytoolkit +{ +template class SOFA_INFINYTOOLKIT_MESHREFINEMENT_API TetrahedronSubdivisionController; + +} // namespace sofa::infinytoolkit diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h new file mode 100644 index 0000000..261cf02 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h @@ -0,0 +1,193 @@ +/******************************************************************************* + * - Copyright (C) 2019-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#pragma once + +#include +#include +#include +#include +#include + +#include +#include + + +namespace sofa::infinytoolkit +{ + +/** +* Single entry point for the subdivision operations this plugin performs on a +* tetrahedral mesh: cutting along a plane or between two surface triangles, and +* refining either the whole mesh or a chosen set of tetrahedra. +* +* Replaces TetrahedronCuttingController and TetrahedronRefinementController. Those +* split one engine in two along a line a caller could not see: both operations +* subdivide tetrahedra, and TetrahedronCuttingManager *is* a +* TetrahedronSubdividersManager, so a single manager answers both halves. The old +* pair also owned a manager each, which meant two engines over one topology. +* +* Cutting is two-phase by nature - the path is built first and committed second, +* which is what lets a scene draw the plane and check it before anything changes. +* Both shapes are offered: prepareCutFrom*() then applyCut() for that, and +* cutFrom*() for callers that only want the operation done. +* +* Interactive keys, used by the example scenes: +* '1' build the cut plane from the cutPointA / cutPointB / cutDir / cutDepth Data +* '2' apply the cut built by '1' +* '3' refine the tetrahedra listed in testID +* '4' refine the whole mesh +* +* '4' is new. refineFullMesh() used to answer '2' on the refinement controller, +* which collides with applying a cut now that both live in one component. +*/ +template +class TetrahedronSubdivisionController : public core::behavior::BaseController +{ +public: + SOFA_CLASS(SOFA_TEMPLATE(TetrahedronSubdivisionController, DataTypes), sofa::core::behavior::BaseController); + + using Real = typename DataTypes::Real; + using Coord = typename DataTypes::Coord; + using VecCoord = typename DataTypes::VecCoord; + + /// Aliased in the class rather than pulled in by a namespace-scope using: the + /// engine header used to supply these names to everyone who included it, and the + /// facade deliberately does not. + using Vec3 = sofa::type::Vec3; + using TetrahedronSetTopologyContainer = sofa::component::topology::container::dynamic::TetrahedronSetTopologyContainer; + + using TriangleID = sofa::core::topology::Topology::TriangleID; + + /// Sofa API init method of the component + void init() override; + + /// Method to handle various event like keyboard or omni. + void handleEvent(sofa::core::objectmodel::Event* event) override; + + void draw(const core::visual::VisualParams* vparams) override; + + + /// @name Cutting + /// @{ + + /// Cuts along the quad spanned by @p pointA, @p pointB and @p direction over + /// @p depth, building and applying the cut in one call. @p createSurface also + /// creates the new surface mesh and its components on the cut faces. + /// @return false if the controller is unusable or the cut could not be built. + bool cutFromPlane(const Vec3& pointA, const Vec3& pointB, const Vec3& direction, + SReal depth, bool createSurface = false); + + /// Cuts between the barycentres of triangles @p triangleA and @p triangleB, over + /// @p depth along the normal of @p triangleA, in one call. + /// @return false if the controller is unusable or either id is out of range. + bool cutFromTriangles(TriangleID triangleA, TriangleID triangleB, + SReal depth, bool createSurface = false); + + /// Builds a cut path without applying it, so that it can be drawn and inspected. + /// @sa applyCut + bool prepareCutFromPlane(const Vec3& pointA, const Vec3& pointB, const Vec3& direction, + SReal depth); + + /// Builds a cut path between two surface triangles without applying it. + /// @sa applyCut + bool prepareCutFromTriangles(TriangleID triangleA, TriangleID triangleB, SReal depth); + + /// Applies the path built by either prepareCutFrom* method. + /// @return false if the controller is unusable. + bool applyCut(bool createSurface); + /// @} + + + /// @name Refinement + /// @{ + + /// Subdivides every tetrahedron of the mesh. + /// @return false if the controller is unusable. + bool refineFullMesh(); + + /// Subdivides the tetrahedra given by @p ids. @p criteria is the edge length + /// under which an edge is left whole; 0 subdivides regardless of length. + /// @return false if the controller is unusable. + bool refineTetrahedra(const std::set& ids, SReal criteria = 0.0); + /// @} + + +protected: + /// Default constructor + TetrahedronSubdivisionController(); + + /// Default destructor + ~TetrahedronSubdivisionController() override; + + /// Builds the cut quad from the Data, for the interactive '1' key and + /// @sa d_performCut. Same geometry as @sa prepareCutFromPlane. + bool prepareCutFromData(); + + /// Drives the '3' key, honouring @sa d_delayMode + void refineFromData(); + + +public: + /// Bool to perform a cut at the current timestep + Data d_performCut; + + // To define cut from a plan defined by 2 points, a direction and a depth + Data d_cutPointA; ///< First plan point position + Data d_cutPointB; ///< Second plan point position + Data d_cutDirection; ///< Plan 3d direction in space + Data d_cutDepth; ///< Depth of the cut in the plan direction + + /// Booleen to define if new surface mesh and component will be created on cut. + Data d_surfaceCut; + /// Texture filename to be used on the new surface mesh created by the cut. Only used if @sa d_surfaceCut is set to true. + Data d_textureName; + + /// Tetrahedra subdivided by the '3' key, @sa refineTetrahedra + Data > d_testID; + /// Edge length under which an edge is not subdivided by the '3' key. Defaults to + /// 0, i.e. subdivide regardless, which is what the refinement controller did. + Data d_refineCriteria; + /// Splits the '3' key in two presses: the first computes the neighbourhood table, + /// the second subdivides from it. + Data d_delayMode; + + Data d_drawTetra; ///< Draw the tetrahedra held by the subdividers + Data d_drawScaleTetrahedra; ///< Scale of the terahedra (between 0 and 1; if <1.0, it produces gaps between the tetrahedra) + Data d_drawDebugCut; ///< Bool to draw cut plan and intersection + + +private: + /// The engine, behind its public handle: one manager for both operations. Owned + /// rather than shared, because a cut is built by one call and applied by another + /// and both halves have to reach the same engine and the same buffers. + std::unique_ptr > m_mgr = nullptr; + + /// Kept for the init-time check that a tetrahedral topology is present, and for + /// the id range checks; the operations themselves go through the manager, which + /// holds its own handles. + TetrahedronSetTopologyContainer::SPtr m_topoCon = nullptr; + + // Bool to store the information if component has well be init and can be used. + bool m_controllerReady = false; + + /// Two-phase '3' key handling, @sa d_delayMode + int m_refineStatus = 0; +}; + +#if !defined(SOFA_INFINYTOOLKIT_TETRAHEDRON_SUBDIVISION_CONTROLLER_CPP) +extern template class SOFA_INFINYTOOLKIT_MESHREFINEMENT_API TetrahedronSubdivisionController; +#endif + +} // namespace sofa::infinytoolkit diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl new file mode 100644 index 0000000..2b52d97 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl @@ -0,0 +1,295 @@ +/******************************************************************************* + * - Copyright (C) 2019-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#pragma once + +#include + +#include +#include +#include + +#include +#include +#include + +#include + +namespace sofa::infinytoolkit +{ + +using namespace sofa::core::topology; +using namespace sofa::component::topology; + + +template +TetrahedronSubdivisionController::TetrahedronSubdivisionController() + : d_performCut(initData(&d_performCut, false, "performCut", "to activate cut at the current timestep")) + , d_cutPointA(initData(&d_cutPointA, Vec3(0.0, 0.0, 0.0), "cutPointA", "(default=[0, 0, 0])")) + , d_cutPointB(initData(&d_cutPointB, Vec3(0.0, 0.0, 0.0), "cutPointB", "(default=[0, 0, 0])")) + , d_cutDirection(initData(&d_cutDirection, Vec3(0.0, -1.0, 0.0), "cutDir", "(default=[0, -1, 0])")) + , d_cutDepth(initData(&d_cutDepth, SReal(0.0), "cutDepth", "depth value")) + , d_surfaceCut(initData(&d_surfaceCut, false, "surfaceCut", "to activate new surface cut")) + , d_textureName(initData(&d_textureName, std::string(""), "textureName", "texture to apply on the surface created by the cut")) + , d_testID(initData(&d_testID, std::set(), "testID", "Ids of the tetrahedra subdivided by the '3' key")) + , d_refineCriteria(initData(&d_refineCriteria, SReal(0.0), "refineCriteria", "Edge length under which an edge is not subdivided. 0 subdivides regardless of length")) + , d_delayMode(initData(&d_delayMode, false, "delayMode", "Split the '3' key in two presses: compute the neighbourhood table, then subdivide from it")) + , d_drawTetra(initData(&d_drawTetra, false, "drawTetra", "Draw the tetrahedra held by the subdividers")) + , d_drawScaleTetrahedra(initData(&d_drawScaleTetrahedra, (float) 1.0, "drawScaleTetrahedra", "Scale of the terahedra (between 0 and 1; if <1.0, it produces gaps between the tetrahedra)")) + , d_drawDebugCut(initData(&d_drawDebugCut, false, "drawDebugCut", "draw Debug Cut infos")) +{ + this->f_listening.setValue(true); +} + + +template +TetrahedronSubdivisionController::~TetrahedronSubdivisionController() +{ + m_refineStatus = 0; +} + + +template +void TetrahedronSubdivisionController::init() +{ + m_topoCon = this->getContext()->template get(); + if (m_topoCon == nullptr) + { + msg_error() << "No topology found"; + m_controllerReady = false; + return; + } + + if (m_mgr == nullptr) + { + m_mgr = std::make_unique >(); + m_controllerReady = m_mgr->init(this->getContext()); + m_mgr->activateLogs(this->f_printLog.getValue()); + + if (d_surfaceCut.getValue()) + { + const std::string& textName = d_textureName.getValue(); + if (!textName.empty()) + m_mgr->setCutTextureName(textName); + } + } +} + + +template +bool TetrahedronSubdivisionController::prepareCutFromPlane(const Vec3& pointA, const Vec3& pointB, + const Vec3& direction, SReal depth) +{ + if (!m_controllerReady) + { + msg_error() << "Controller is not initialised, cannot cut."; + return false; + } + + // Create Cut quad + sofa::type::fixed_array planPositions; + planPositions[0] = pointA; + planPositions[1] = pointB; + + Vec3 cutDir = direction; + cutDir.normalize(); + + planPositions[2] = planPositions[1] + cutDir * depth; + planPositions[3] = planPositions[0] + cutDir * depth; + const Vec3 planNormal = (planPositions[1] - planPositions[0]).cross(cutDir); + + // Test all tetra. Thickness is a quarter of the depth, as the cutting controller + // has always used here; it is the tolerance for snapping points onto the plane. + m_mgr->createCutPlanPath(planPositions, planNormal, depth * 0.25); + return true; +} + + +template +bool TetrahedronSubdivisionController::prepareCutFromTriangles(TriangleID triangleA, + TriangleID triangleB, SReal depth) +{ + if (!m_controllerReady) + { + msg_error() << "Controller is not initialised, cannot cut."; + return false; + } + + return m_mgr->createCutPlanPathFromTriangles(triangleA, triangleB, depth); +} + + +template +bool TetrahedronSubdivisionController::applyCut(bool createSurface) +{ + if (!m_controllerReady) + { + msg_error() << "Controller is not initialised, cannot cut."; + return false; + } + + m_mgr->processCut(createSurface); + return true; +} + + +template +bool TetrahedronSubdivisionController::cutFromPlane(const Vec3& pointA, const Vec3& pointB, + const Vec3& direction, SReal depth, bool createSurface) +{ + return prepareCutFromPlane(pointA, pointB, direction, depth) && applyCut(createSurface); +} + + +template +bool TetrahedronSubdivisionController::cutFromTriangles(TriangleID triangleA, + TriangleID triangleB, SReal depth, bool createSurface) +{ + return prepareCutFromTriangles(triangleA, triangleB, depth) && applyCut(createSurface); +} + + +template +bool TetrahedronSubdivisionController::refineFullMesh() +{ + if (!m_controllerReady) + { + msg_error() << "Controller is not initialised, cannot refine."; + return false; + } + + return m_mgr->refineFullMesh(); +} + + +template +bool TetrahedronSubdivisionController::refineTetrahedra(const std::set& ids, SReal criteria) +{ + if (!m_controllerReady) + { + msg_error() << "Controller is not initialised, cannot refine."; + return false; + } + + // Neither this controller nor the manager used to check the ids, and an id past + // the end is read straight out of the tetrahedron array: an access violation, not + // an error. SimpleCubeRefinement.scn shipped testID="60" against a 44 tetrahedron + // mesh, so pressing its refine key crashed. Guard at the API boundary. + const auto nbTetrahedra = m_topoCon->getNbTetrahedra(); + for (const unsigned int id : ids) + { + if (id >= nbTetrahedra) + { + msg_error() << "Tetrahedron id out of range: " << id << " while the topology has " + << nbTetrahedra << " tetrahedra. Nothing refined."; + return false; + } + } + + return m_mgr->refineTetrahedra(ids, criteria); +} + + +template +bool TetrahedronSubdivisionController::prepareCutFromData() +{ + return prepareCutFromPlane(d_cutPointA.getValue(), d_cutPointB.getValue(), + d_cutDirection.getValue(), d_cutDepth.getValue()); +} + + +template +void TetrahedronSubdivisionController::refineFromData() +{ + if (!m_controllerReady) + return; + + if (d_delayMode.getValue() && m_refineStatus == 0) + { + m_mgr->computeNeighboorhoodTable(d_testID.getValue(), d_refineCriteria.getValue()); + m_refineStatus++; + } + else + { + refineTetrahedra(d_testID.getValue(), d_refineCriteria.getValue()); + m_refineStatus = 0; + } +} + + +template +void TetrahedronSubdivisionController::handleEvent(sofa::core::objectmodel::Event* event) +{ + if (!m_controllerReady) + return; + + if (sofa::core::objectmodel::KeypressedEvent* ev = dynamic_cast(event)) + { + dmsg_info() << "GET KEY " << ev->getKey(); + + switch (ev->getKey()) + { + case '1': + prepareCutFromData(); + break; + case '2': + applyCut(d_surfaceCut.getValue()); + break; + case '3': + refineFromData(); + break; + case '4': + refineFullMesh(); + break; + default: + break; + } + } + + if (sofa::simulation::AnimateEndEvent::checkEventType(event)) + { + if (d_performCut.getValue()) + { + cutFromPlane(d_cutPointA.getValue(), d_cutPointB.getValue(), + d_cutDirection.getValue(), d_cutDepth.getValue(), + d_surfaceCut.getValue()); + + d_performCut.setValue(false); + } + } +} + + +template +void TetrahedronSubdivisionController::draw(const core::visual::VisualParams* vparams) +{ + if (!m_controllerReady) + return; + + [[maybe_unused]] auto stateLifeCycle = vparams->drawTool()->makeStateLifeCycle(); + + if (d_drawTetra.getValue()) + { + // The engine draws its own buffers; the controller no longer reaches into them. + m_mgr->drawSubdividedTetrahedra(vparams); + } + + if (d_drawDebugCut.getValue()) + { + m_mgr->drawCutPlan(vparams); + m_mgr->drawDebugCut(vparams); + } +} + +} // namespace sofa::infinytoolkit diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/config.h.in b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/config.h.in new file mode 100644 index 0000000..5ec4559 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/config.h.in @@ -0,0 +1,30 @@ +/******************************************************************************* + * - Copyright (C) 2026-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#pragma once + +#include + +#ifdef SOFA_BUILD_INFINYTOOLKIT_MESHREFINEMENT +# define SOFA_TARGET @PROJECT_NAME@ +# define SOFA_INFINYTOOLKIT_MESHREFINEMENT_API SOFA_EXPORT_DYNAMIC_LIBRARY +#else +# define SOFA_INFINYTOOLKIT_MESHREFINEMENT_API SOFA_IMPORT_DYNAMIC_LIBRARY +#endif + +namespace sofa::infinytoolkit::meshrefinement +{ + constexpr const char* MODULE_NAME = "@PROJECT_NAME@"; + constexpr const char* MODULE_VERSION = "@PROJECT_VERSION@"; +} // namespace sofa::infinytoolkit::meshrefinement diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.cpp new file mode 100644 index 0000000..c71e73a --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.cpp @@ -0,0 +1,72 @@ +/******************************************************************************* + * - Copyright (C) 2026-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#include +#include + +#include +#include + +namespace sofa::infinytoolkit::meshrefinement +{ + +extern void registerTetrahedronSubdivisionController(sofa::core::ObjectFactory* factory); +#ifdef INFINYTOOLKIT_MESHREFINEMENT_USES_SOFACUDA +extern void registerCudaTetrahedronSubdivisionController(sofa::core::ObjectFactory* factory); +#endif + +extern "C" { + SOFA_EXPORT_DYNAMIC_LIBRARY void initExternalModule(); + SOFA_EXPORT_DYNAMIC_LIBRARY const char* getModuleName(); + SOFA_EXPORT_DYNAMIC_LIBRARY const char* getModuleVersion(); + SOFA_INFINYTOOLKIT_MESHREFINEMENT_API void registerObjects(sofa::core::ObjectFactory* factory); +} + +void initExternalModule() +{ + init(); +} + +const char* getModuleName() +{ + return sofa_tostring(SOFA_TARGET); +} + +const char* getModuleVersion() +{ + return sofa_tostring(PLUGIN_VERSION); +} + +void init() +{ + static bool first = true; + if (first) + { + sofa::helper::system::PluginManager::getInstance().registerPlugin(sofa_tostring(SOFA_TARGET)); + + // the engine this controller drives lives in MeshRefinement + sofa::meshrefinement::initMeshRefinement(); + first = false; + } +} + +void registerObjects(sofa::core::ObjectFactory* factory) +{ + registerTetrahedronSubdivisionController(factory); +#ifdef INFINYTOOLKIT_MESHREFINEMENT_USES_SOFACUDA + registerCudaTetrahedronSubdivisionController(factory); +#endif +} + +} // namespace sofa::infinytoolkit::meshrefinement diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.h b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.h new file mode 100644 index 0000000..025cca6 --- /dev/null +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/init.h @@ -0,0 +1,22 @@ +/******************************************************************************* + * - Copyright (C) 2026-Present InfinyTech3D - * + * All Rights Reserved * + * * + * The contents of this file are confidential and proprietary to InfinyTech3D. * + * Unauthorized copying, modification, distribution or disclosure of this * + * file, via any medium, is strictly prohibited. * + * * + * This file may only be used under the terms and conditions of a valid * + * commercial license agreement with InfinyTech3D. * + * * + * For licensing inquiries: contact@infinytech3d.com * + * Further information: https://infinytech3d.com * + ******************************************************************************/ +#pragma once + +#include + +namespace sofa::infinytoolkit::meshrefinement +{ +SOFA_INFINYTOOLKIT_MESHREFINEMENT_API void init(); +} // namespace sofa::infinytoolkit::meshrefinement diff --git a/extensions/MeshRefinement/tests/CMakeLists.txt b/extensions/MeshRefinement/tests/CMakeLists.txt new file mode 100644 index 0000000..d8b9eb2 --- /dev/null +++ b/extensions/MeshRefinement/tests/CMakeLists.txt @@ -0,0 +1,16 @@ +cmake_minimum_required(VERSION 3.12) +project(InfinyToolkit.MeshRefinement_test) + +set(SOURCE_FILES + TetrahedronSubdivisionController_test.cpp +) + +# The scenes still live in the MeshRefinement plugin, next to the meshes they load. +# They exercise this plugin's controller, so ownership of them ought to follow it +# here eventually; pointing at them is the smaller change for now. +add_definitions("-DINFINYTOOLKIT_MESHREFINEMENT_TEST_SCENES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../MeshRefinement/examples/\"") + +add_executable(${PROJECT_NAME} ${SOURCE_FILES}) +target_link_libraries(${PROJECT_NAME} InfinyToolkit.MeshRefinement Sofa.Testing Sofa.Component.Topology.Container.Dynamic) + +add_test(NAME ${PROJECT_NAME} COMMAND ${PROJECT_NAME}) diff --git a/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp b/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp new file mode 100644 index 0000000..a7da879 --- /dev/null +++ b/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp @@ -0,0 +1,304 @@ +/** + * Covers the public API of TetrahedronSubdivisionController, the component that + * replaced TetrahedronCuttingController and TetrahedronRefinementController. + * + * The example scenes are loaded as-is so that they stay the single source of truth + * for the geometry, and the controller is then driven through its own methods rather + * than only by key event. Each case asserts the topology actually changed: a cut or a + * subdivision that silently does nothing would otherwise pass. + * + * The key-driven cases exist because the merge moved refineFullMesh() from '2' to + * '4' - '2' now applies a cut - so the interactive contract of the 13 migrated scenes + * is worth pinning down. + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include + +using sofa::component::topology::container::dynamic::TetrahedronSetTopologyContainer; +using SubdivisionController3d = sofa::infinytoolkit::TetrahedronSubdivisionController; + +namespace +{ + +/// Everything these operations are expected to change. +struct TopologyCounts +{ + sofa::Size nbPoints{ 0 }; + sofa::Size nbEdges{ 0 }; + sofa::Size nbTriangles{ 0 }; + sofa::Size nbTetrahedra{ 0 }; + + bool operator==(const TopologyCounts& o) const + { + return nbPoints == o.nbPoints && nbEdges == o.nbEdges + && nbTriangles == o.nbTriangles && nbTetrahedra == o.nbTetrahedra; + } +}; + +std::ostream& operator<<(std::ostream& os, const TopologyCounts& c) +{ + return os << c.nbPoints << " points, " << c.nbEdges << " edges, " + << c.nbTriangles << " triangles, " << c.nbTetrahedra << " tetrahedra"; +} + + +class TetrahedronSubdivisionController_test : public sofa::testing::BaseTest +{ +public: + void doSetUp() override + { + // node::load needs a Simulation to exist. Plugins are preloaded rather than + // left to the scenes' RequiredPlugin, so that a single case run under + // --gtest_filter behaves like the whole suite. + m_simulation = sofa::simpleapi::createSimulation("DAG"); + sofa::helper::system::DataRepository.addFirstPath(INFINYTOOLKIT_MESHREFINEMENT_TEST_SCENES_DIR); + + for (const auto* plugin : { + "Sofa.Component.Collision.Detection.Algorithm", + "Sofa.Component.Collision.Detection.Intersection", + "Sofa.Component.Collision.Geometry", + "Sofa.Component.Collision.Response.Contact", + "Sofa.Component.Constraint.Projective", + "Sofa.Component.Engine.Select", + "Sofa.Component.IO.Mesh", + "Sofa.Component.LinearSolver.Iterative", + "Sofa.Component.Mapping.Linear", + "Sofa.Component.Mass", + "Sofa.Component.MechanicalLoad", + "Sofa.Component.ODESolver.Backward", + "Sofa.Component.SceneUtility", + "Sofa.Component.SolidMechanics.FEM.Elastic", + "Sofa.Component.StateContainer", + "Sofa.Component.Topology.Container.Constant", + "Sofa.Component.Topology.Container.Dynamic", + "Sofa.Component.Topology.Container.Grid", + "Sofa.Component.Topology.Mapping", + "Sofa.Component.Topology.Utility", + "Sofa.Component.Visual", + "Sofa.GL.Component.Rendering3D", + "MeshRefinement", + "InfinyToolkit.MeshRefinement", + }) + { + sofa::simpleapi::importPlugin(plugin); + } + } + + void doTearDown() override + { + if (m_root) + { + sofa::simulation::node::unload(m_root); + m_root.reset(); + } + } + +protected: + sofa::simulation::Simulation::SPtr m_simulation; + sofa::simulation::NodeSPtr m_root; + SubdivisionController3d* m_controller{ nullptr }; + TetrahedronSetTopologyContainer* m_topology{ nullptr }; + + /// Loads an example scene and resolves the controller and the topology it drives. + void loadScene(const std::string& sceneFile) + { + const std::string scenePath = sofa::helper::system::DataRepository.getFile(sceneFile); + m_root = sofa::simulation::node::load(scenePath); + ASSERT_NE(m_root.get(), nullptr) << "could not load " << sceneFile; + sofa::simulation::node::init(m_root.get()); + + m_controller = m_root->get(sofa::core::objectmodel::BaseContext::SearchDown); + ASSERT_NE(m_controller, nullptr) << "no TetrahedronSubdivisionController in " << sceneFile; + + // Resolved from the controller's own context, not from the root: a scene with a + // topological mapping holds more than one container and the first one found is + // not necessarily the one the controller operates on. + m_topology = m_controller->getContext()->get(); + ASSERT_NE(m_topology, nullptr) << "no TetrahedronSetTopologyContainer beside the controller"; + } + + TopologyCounts counts() const + { + return { m_topology->getNbPoints(), m_topology->getNbEdges(), + m_topology->getNbTriangles(), m_topology->getNbTetrahedra() }; + } + + void step(int count) + { + for (int i = 0; i < count; ++i) + sofa::simulation::node::animate(m_root.get(), m_root->getDt()); + } + + void pressKey(char key) + { + sofa::core::objectmodel::KeypressedEvent event(key); + m_root->propagateEvent(sofa::core::execparams::defaultInstance(), &event); + } +}; + + +/// The cut the 8 migrated cutting scenes perform, driven through the API. +TEST_F(TetrahedronSubdivisionController_test, cutFromPlane_changes_the_topology) +{ + loadScene("TetrahedronCutting_Advanced_01.scn"); + step(10); + + const TopologyCounts before = counts(); + // the scene's own plane, so the geometry stays owned by the scene file + EXPECT_TRUE(m_controller->cutFromPlane(m_controller->d_cutPointA.getValue(), + m_controller->d_cutPointB.getValue(), + m_controller->d_cutDirection.getValue(), + m_controller->d_cutDepth.getValue(), + false)); + step(1); + + const TopologyCounts after = counts(); + EXPECT_FALSE(after == before) << "the cut did nothing: still " << after; + EXPECT_GT(after.nbTetrahedra, before.nbTetrahedra); + std::cout << "[pv] cutFromPlane: " << before << " -> " << after << std::endl; +} + + +/// The same cut through the two-phase shape, which must reach the same result. +TEST_F(TetrahedronSubdivisionController_test, prepare_then_applyCut_matches_the_one_shot) +{ + loadScene("TetrahedronCutting_Advanced_01.scn"); + step(10); + + const TopologyCounts before = counts(); + EXPECT_TRUE(m_controller->prepareCutFromPlane(m_controller->d_cutPointA.getValue(), + m_controller->d_cutPointB.getValue(), + m_controller->d_cutDirection.getValue(), + m_controller->d_cutDepth.getValue())); + + // building the path must not touch the topology on its own + EXPECT_TRUE(counts() == before) << "prepareCutFromPlane already changed the topology"; + + EXPECT_TRUE(m_controller->applyCut(false)); + step(1); + + const TopologyCounts after = counts(); + EXPECT_FALSE(after == before) << "the cut did nothing: still " << after; + std::cout << "[pv] prepare+apply: " << before << " -> " << after << std::endl; +} + + +/// Keys '1' then '2' are what the migrated scenes rely on. +TEST_F(TetrahedronSubdivisionController_test, keys_1_then_2_cut) +{ + loadScene("TetrahedronCutting_Advanced_01.scn"); + step(10); + + const TopologyCounts before = counts(); + pressKey('1'); + step(1); + pressKey('2'); + step(1); + + const TopologyCounts after = counts(); + EXPECT_FALSE(after == before) << "keys 1,2 did not cut: still " << after; +} + + +/// Out-of-range tetrahedron ids must be refused rather than read out of bounds. This +/// is what SimpleCubeRefinement.scn used to do with testID="60" on a 44 tetrahedron +/// mesh: an access violation, not an error. +TEST_F(TetrahedronSubdivisionController_test, refineTetrahedra_rejects_bad_ids) +{ + loadScene("SimpleCubeRefinement.scn"); + step(10); + + const TopologyCounts before = counts(); + const std::set bad { m_topology->getNbTetrahedra() + 10u }; + + EXPECT_MSG_EMIT(Error); + EXPECT_FALSE(m_controller->refineTetrahedra(bad)); + EXPECT_TRUE(counts() == before) << "a refused refinement still changed the topology"; +} + + +/// Out-of-range triangle ids must be refused rather than read out of bounds. +TEST_F(TetrahedronSubdivisionController_test, cutFromTriangles_rejects_bad_ids) +{ + loadScene("TetrahedronCutting_Advanced_01.scn"); + step(10); + + const TopologyCounts before = counts(); + const auto nbTriangles = m_topology->getNbTriangles(); + + // BaseTest fails a test on any Error message, and the guard logs one, so the + // expectation has to be declared before the call. + EXPECT_MSG_EMIT(Error); + EXPECT_FALSE(m_controller->cutFromTriangles(nbTriangles + 10, nbTriangles + 20, 5.0, false)); + EXPECT_TRUE(counts() == before) << "a refused cut still changed the topology"; +} + + +/// Refinement of the tetrahedra named by the scene. +TEST_F(TetrahedronSubdivisionController_test, refineTetrahedra_changes_the_topology) +{ + loadScene("SimpleCubeRefinement.scn"); + step(10); + + const TopologyCounts before = counts(); + ASSERT_FALSE(m_controller->d_testID.getValue().empty()) << "the scene names no tetrahedron to refine"; + + EXPECT_TRUE(m_controller->refineTetrahedra(m_controller->d_testID.getValue())); + step(1); + + const TopologyCounts after = counts(); + EXPECT_FALSE(after == before) << "the refinement did nothing: still " << after; + EXPECT_GT(after.nbTetrahedra, before.nbTetrahedra); + std::cout << "[pv] refineTetrahedra: " << before << " -> " << after << std::endl; +} + + +/// Whole-mesh refinement, which the merge moved from key '2' to key '4'. +TEST_F(TetrahedronSubdivisionController_test, refineFullMesh_grows_the_mesh) +{ + loadScene("SimpleCubeRefinement.scn"); + step(10); + + const TopologyCounts before = counts(); + EXPECT_TRUE(m_controller->refineFullMesh()); + step(1); + + const TopologyCounts after = counts(); + EXPECT_GT(after.nbTetrahedra, before.nbTetrahedra) << "refineFullMesh did not subdivide: still " << after; + std::cout << "[pv] refineFullMesh: " << before << " -> " << after << std::endl; +} + + +/// Key '4' has to reach refineFullMesh, and '2' must no longer do so. +TEST_F(TetrahedronSubdivisionController_test, key_4_refines_the_full_mesh) +{ + loadScene("SimpleCubeRefinement.scn"); + step(10); + + const TopologyCounts before = counts(); + + // '2' applies a cut now; with no cut prepared it must leave the mesh alone + pressKey('2'); + step(1); + EXPECT_TRUE(counts() == before) << "key 2 changed the mesh with no cut prepared"; + + pressKey('4'); + step(1); + const TopologyCounts after = counts(); + EXPECT_GT(after.nbTetrahedra, before.nbTetrahedra) << "key 4 did not refine: still " << after; +} + +} // namespace From 2f0ce91b219871d67e1214481c9b584eef2d5597 Mon Sep 17 00:00:00 2001 From: epernod Date: Wed, 9 Sep 2026 22:50:34 +0200 Subject: [PATCH 2/6] [InfinyToolkit] FIX follow MeshRefinement's rename to MeshRefinementAPI MeshRefinement renamed its public boundary: the facade is MeshRefinementAPI and TetrahedronSubdivisionManager is now the private engine that the two managers were merged into. Everything on this side is an outside consumer, so all of it moves to the boundary - the sub-plugin's controller, and the two carving performers. The controller's header comment described the old arrangement, where cutting derived from subdividers and the point was that one manager answered both halves. That is now a single engine, so the comment says what actually matters to a reader here instead: the engine is private to MeshRefinement and this controller reaches it only through the one published header, which is why nothing in the header names an implementation type. InfinyToolkit.MeshRefinement's 8 controller tests pass, and Cube_AdvCarvingWithRefinement still reaches bwdInit and initialises the refine performer through the boundary with no warning. --- extensions/MeshRefinement/CMakeLists.txt | 2 +- .../CudaSubdivisionController.cpp | 2 +- .../TetrahedronSubdivisionController.h | 18 +++++++++++------- .../TetrahedronSubdivisionController.inl | 2 +- 4 files changed, 14 insertions(+), 10 deletions(-) diff --git a/extensions/MeshRefinement/CMakeLists.txt b/extensions/MeshRefinement/CMakeLists.txt index abc03ed..ce51453 100644 --- a/extensions/MeshRefinement/CMakeLists.txt +++ b/extensions/MeshRefinement/CMakeLists.txt @@ -3,7 +3,7 @@ project(InfinyToolkit.MeshRefinement VERSION 0.1 LANGUAGES CXX) # The public face of the MeshRefinement plugin. The controller a scene instantiates # lives here; the subdivision engine stays private to MeshRefinement, reached only -# through TetrahedronSubdivisionManager, its pimpl facade. +# through MeshRefinementAPI, the one header that plugin publishes. set(INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR src/InfinyToolkit/MeshRefinement) set(HEADER_FILES diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp index 6dbf837..22c1c76 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp @@ -16,7 +16,7 @@ /// The Cuda instantiations of the controller. These used to live in /// MeshRefinement.CUDA, which cannot host them any more: the controller is here now, /// and MeshRefinement must not depend on InfinyToolkit. The engine side of the pair - -/// TetrahedronSubdivisionManager for the same Cuda types - is still instantiated by +/// MeshRefinementAPI for the same Cuda types - is still instantiated by /// MeshRefinement.CUDA, which is the only translation unit that can see the private /// .inl; this file links those symbols through the facade's public header. /// diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h index 261cf02..46fcefd 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h @@ -15,7 +15,7 @@ #pragma once #include -#include +#include #include #include #include @@ -32,11 +32,15 @@ namespace sofa::infinytoolkit * tetrahedral mesh: cutting along a plane or between two surface triangles, and * refining either the whole mesh or a chosen set of tetrahedra. * -* Replaces TetrahedronCuttingController and TetrahedronRefinementController. Those -* split one engine in two along a line a caller could not see: both operations -* subdivide tetrahedra, and TetrahedronCuttingManager *is* a -* TetrahedronSubdividersManager, so a single manager answers both halves. The old -* pair also owned a manager each, which meant two engines over one topology. +* Replaces TetrahedronCuttingController and TetrahedronRefinementController, which +* split one engine in two along a line a caller could not see - both operations +* subdivide tetrahedra - and owned a manager each, so a scene using both ran two +* engines over one topology. +* +* The engine itself stays private to the MeshRefinement plugin. This controller +* reaches it only through MeshRefinementAPI, the one header that plugin publishes, +* which is why nothing below names a subdivider, a topology container or any other +* implementation type. * * Cutting is two-phase by nature - the path is built first and committed second, * which is what lets a scene draw the plane and check it before anything changes. @@ -172,7 +176,7 @@ class TetrahedronSubdivisionController : public core::behavior::BaseController /// The engine, behind its public handle: one manager for both operations. Owned /// rather than shared, because a cut is built by one call and applied by another /// and both halves have to reach the same engine and the same buffers. - std::unique_ptr > m_mgr = nullptr; + std::unique_ptr > m_mgr = nullptr; /// Kept for the init-time check that a tetrahedral topology is present, and for /// the id range checks; the operations themselves go through the manager, which diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl index 2b52d97..f44171e 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl @@ -73,7 +73,7 @@ void TetrahedronSubdivisionController::init() if (m_mgr == nullptr) { - m_mgr = std::make_unique >(); + m_mgr = std::make_unique >(); m_controllerReady = m_mgr->init(this->getContext()); m_mgr->activateLogs(this->f_printLog.getValue()); From 98d3a4ad298b0ee981492312abe4ade3d6ea32f8 Mon Sep 17 00:00:00 2001 From: epernod Date: Fri, 11 Sep 2026 23:56:49 +0200 Subject: [PATCH 3/6] [CarvingTools] CLEAN the controller's comments down to what documents the code The comments carried a running account of how the component came to be - what it replaced, which key moved and why, why a type is aliased in the class rather than pulled in by a using, why the manager is owned rather than shared. None of that tells a reader what the class does today. What stays is the documentation: what each operation does and what it returns, what each Data means, the two-phase shape of cutting, and the key bindings the example scenes rely on. What goes is the history. Two facts worth keeping were rewritten rather than deleted, because they are about the current code and not about its past: the cut thickness is the tolerance for snapping points onto the plane, and refineTetrahedra range-checks its ids because the engine does not and an id past the end is read out of the tetrahedron array. Some wording carried over from the original controllers is corrected while these lines are being touched - "plan" for plane throughout the cut Data, "terahedra", "Booleen". Comments only. Verified by diffing with the trailing ///< text stripped: the six declarations whose doc comment changed are character-identical on both sides. Co-Authored-By: Claude Opus 5 --- .../CudaSubdivisionController.cpp | 13 ++-- .../TetrahedronSubdivisionController.h | 78 ++++++++----------- .../TetrahedronSubdivisionController.inl | 10 +-- 3 files changed, 40 insertions(+), 61 deletions(-) diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp index 22c1c76..4762b85 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp @@ -13,14 +13,13 @@ * Further information: https://infinytech3d.com * ******************************************************************************/ -/// The Cuda instantiations of the controller. These used to live in -/// MeshRefinement.CUDA, which cannot host them any more: the controller is here now, -/// and MeshRefinement must not depend on InfinyToolkit. The engine side of the pair - -/// MeshRefinementAPI for the same Cuda types - is still instantiated by -/// MeshRefinement.CUDA, which is the only translation unit that can see the private -/// .inl; this file links those symbols through the facade's public header. +/// Instantiates and registers the controller for the Cuda vector types. /// -/// Only compiled when SofaCUDA is found. +/// The matching MeshRefinementAPI instantiations are provided by MeshRefinement.CUDA, +/// the only translation unit that can see the engine's private .inl; this file links +/// against those symbols through the public header. +/// +/// Only compiled when both SofaCUDA and MeshRefinement.CUDA are found. #include #include diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h index 46fcefd..90c99f2 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.h @@ -28,33 +28,20 @@ namespace sofa::infinytoolkit { /** -* Single entry point for the subdivision operations this plugin performs on a -* tetrahedral mesh: cutting along a plane or between two surface triangles, and -* refining either the whole mesh or a chosen set of tetrahedra. +* Cuts and refines a tetrahedral mesh, driving the MeshRefinement plugin's engine. * -* Replaces TetrahedronCuttingController and TetrahedronRefinementController, which -* split one engine in two along a line a caller could not see - both operations -* subdivide tetrahedra - and owned a manager each, so a scene using both ran two -* engines over one topology. +* Four operations: cut along a plane, cut between two surface triangles, refine a +* given set of tetrahedra, and refine the whole mesh. * -* The engine itself stays private to the MeshRefinement plugin. This controller -* reaches it only through MeshRefinementAPI, the one header that plugin publishes, -* which is why nothing below names a subdivider, a topology container or any other -* implementation type. -* -* Cutting is two-phase by nature - the path is built first and committed second, -* which is what lets a scene draw the plane and check it before anything changes. -* Both shapes are offered: prepareCutFrom*() then applyCut() for that, and -* cutFrom*() for callers that only want the operation done. +* Cutting is two-phase: the path is built first and applied second, so a scene can +* draw the plane and inspect it before the topology changes. prepareCutFrom*() +* followed by applyCut() does that; cutFrom*() does both in one call. * * Interactive keys, used by the example scenes: * '1' build the cut plane from the cutPointA / cutPointB / cutDir / cutDepth Data * '2' apply the cut built by '1' * '3' refine the tetrahedra listed in testID * '4' refine the whole mesh -* -* '4' is new. refineFullMesh() used to answer '2' on the refinement controller, -* which collides with applying a cut now that both live in one component. */ template class TetrahedronSubdivisionController : public core::behavior::BaseController @@ -66,9 +53,6 @@ class TetrahedronSubdivisionController : public core::behavior::BaseController using Coord = typename DataTypes::Coord; using VecCoord = typename DataTypes::VecCoord; - /// Aliased in the class rather than pulled in by a namespace-scope using: the - /// engine header used to supply these names to everyone who included it, and the - /// facade deliberately does not. using Vec3 = sofa::type::Vec3; using TetrahedronSetTopologyContainer = sofa::component::topology::container::dynamic::TetrahedronSetTopologyContainer; @@ -77,9 +61,11 @@ class TetrahedronSubdivisionController : public core::behavior::BaseController /// Sofa API init method of the component void init() override; - /// Method to handle various event like keyboard or omni. + /// Handles the key presses listed in the class description. void handleEvent(sofa::core::objectmodel::Event* event) override; + /// Draws the subdivided tetrahedra and the cut plane, per @sa d_drawTetra and + /// @sa d_drawDebugCut. void draw(const core::visual::VisualParams* vparams) override; @@ -135,58 +121,56 @@ class TetrahedronSubdivisionController : public core::behavior::BaseController /// Default destructor ~TetrahedronSubdivisionController() override; - /// Builds the cut quad from the Data, for the interactive '1' key and - /// @sa d_performCut. Same geometry as @sa prepareCutFromPlane. + /// Builds the cut path from @sa d_cutPointA, @sa d_cutPointB, @sa d_cutDirection + /// and @sa d_cutDepth. Used by the '1' key and by @sa d_performCut. bool prepareCutFromData(); - /// Drives the '3' key, honouring @sa d_delayMode + /// Refines the tetrahedra in @sa d_testID, honouring @sa d_delayMode. void refineFromData(); public: - /// Bool to perform a cut at the current timestep + /// Performs a cut at the next time step, then resets itself to false. Data d_performCut; - // To define cut from a plan defined by 2 points, a direction and a depth - Data d_cutPointA; ///< First plan point position - Data d_cutPointB; ///< Second plan point position - Data d_cutDirection; ///< Plan 3d direction in space - Data d_cutDepth; ///< Depth of the cut in the plan direction + /// The cut plane: the quad spanned by the two points below, extruded along + /// @sa d_cutDirection over @sa d_cutDepth. + Data d_cutPointA; ///< First point of the cut plane + Data d_cutPointB; ///< Second point of the cut plane + Data d_cutDirection; ///< Direction the plane is extruded along + Data d_cutDepth; ///< Depth of the cut along @sa d_cutDirection - /// Booleen to define if new surface mesh and component will be created on cut. + /// Creates a new surface mesh, and the components to render it, on the cut faces. Data d_surfaceCut; - /// Texture filename to be used on the new surface mesh created by the cut. Only used if @sa d_surfaceCut is set to true. + /// Texture applied to the surface created by the cut. Only used when + /// @sa d_surfaceCut is true. Data d_textureName; - /// Tetrahedra subdivided by the '3' key, @sa refineTetrahedra + /// Tetrahedra subdivided by the '3' key. @sa refineTetrahedra Data > d_testID; - /// Edge length under which an edge is not subdivided by the '3' key. Defaults to - /// 0, i.e. subdivide regardless, which is what the refinement controller did. + /// Edge length under which an edge is left whole; 0 subdivides regardless. Data d_refineCriteria; /// Splits the '3' key in two presses: the first computes the neighbourhood table, /// the second subdivides from it. Data d_delayMode; Data d_drawTetra; ///< Draw the tetrahedra held by the subdividers - Data d_drawScaleTetrahedra; ///< Scale of the terahedra (between 0 and 1; if <1.0, it produces gaps between the tetrahedra) - Data d_drawDebugCut; ///< Bool to draw cut plan and intersection + Data d_drawScaleTetrahedra; ///< Scale of the drawn tetrahedra; below 1.0 it leaves gaps between them + Data d_drawDebugCut; ///< Draw the cut plane and the intersections it computed private: - /// The engine, behind its public handle: one manager for both operations. Owned - /// rather than shared, because a cut is built by one call and applied by another - /// and both halves have to reach the same engine and the same buffers. + /// The subdivision engine. Owned, because a cut is built by one call and applied + /// by another and both have to reach the same engine. std::unique_ptr > m_mgr = nullptr; - /// Kept for the init-time check that a tetrahedral topology is present, and for - /// the id range checks; the operations themselves go through the manager, which - /// holds its own handles. + /// The topology this controller operates on, used to range-check the ids given to it. TetrahedronSetTopologyContainer::SPtr m_topoCon = nullptr; - // Bool to store the information if component has well be init and can be used. + /// True once init() has found a topology and the engine has initialised. bool m_controllerReady = false; - /// Two-phase '3' key handling, @sa d_delayMode + /// Press counter for the two-phase '3' key. @sa d_delayMode int m_refineStatus = 0; }; diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl index f44171e..d9f5900 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/TetrahedronSubdivisionController.inl @@ -109,8 +109,7 @@ bool TetrahedronSubdivisionController::prepareCutFromPlane(const Vec3 planPositions[3] = planPositions[0] + cutDir * depth; const Vec3 planNormal = (planPositions[1] - planPositions[0]).cross(cutDir); - // Test all tetra. Thickness is a quarter of the depth, as the cutting controller - // has always used here; it is the tolerance for snapping points onto the plane. + // The thickness is the tolerance within which points are snapped onto the plane. m_mgr->createCutPlanPath(planPositions, planNormal, depth * 0.25); return true; } @@ -182,10 +181,8 @@ bool TetrahedronSubdivisionController::refineTetrahedra(const std::se return false; } - // Neither this controller nor the manager used to check the ids, and an id past - // the end is read straight out of the tetrahedron array: an access violation, not - // an error. SimpleCubeRefinement.scn shipped testID="60" against a 44 tetrahedron - // mesh, so pressing its refine key crashed. Guard at the API boundary. + // The engine does not range-check: an id past the end is read straight out of the + // tetrahedron array, which is an access violation rather than an error. const auto nbTetrahedra = m_topoCon->getNbTetrahedra(); for (const unsigned int id : ids) { @@ -281,7 +278,6 @@ void TetrahedronSubdivisionController::draw(const core::visual::Visua if (d_drawTetra.getValue()) { - // The engine draws its own buffers; the controller no longer reaches into them. m_mgr->drawSubdividedTetrahedra(vparams); } From 44cece6b0036615cb58d96f125f3dd2c8966869e Mon Sep 17 00:00:00 2001 From: epernod Date: Sat, 12 Sep 2026 00:12:15 +0200 Subject: [PATCH 4/6] fix relocatable length variable --- CMakeLists.txt | 12 ++++++++++-- extensions/MeshRefinement/CMakeLists.txt | 20 ++++++++++---------- 2 files changed, 20 insertions(+), 12 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index cf0aa53..d55f639 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -19,7 +19,7 @@ ############################################################################## cmake_minimum_required(VERSION 3.12) -project(InfinyToolkit VERSION 0.1 LANGUAGES CXX) +project(InfinyToolkit VERSION 0.2 LANGUAGES CXX) include(cmake/environment.cmake) @@ -175,7 +175,15 @@ target_include_directories(${PROJECT_NAME} PUBLIC "$" # InfinyToolkit.MeshRefinement while the subdivision engine stays private to the # MeshRefinement plugin behind its facade. if(MeshRefinement_FOUND) - sofa_add_subdirectory(plugin extensions/MeshRefinement InfinyToolkit.MeshRefinement) + # BINARY_DIR is kept short and CMAKE_BINARY_DIR-relative (rather than the + # default, which mirrors the deep external_directories/InfinyToolkit/extensions/... + # source path) because the relocatable-install custom target this generates + # (see sofa_set_project_install_relocatable in SofaMacrosInstall.cmake) already + # produces a long .tlog filename; combined with the default nesting it trips + # Windows' 260-char MAX_PATH (MSB3491). This must hold on every machine/CI + # runner building this repo, not just this one, so the fix has to be structural. + sofa_add_subdirectory(plugin extensions/MeshRefinement InfinyToolkit.MeshRefinement + BINARY_DIR "${CMAKE_BINARY_DIR}/InfinyToolkit.MeshRefinement") endif() sofa_create_package_with_targets( diff --git a/extensions/MeshRefinement/CMakeLists.txt b/extensions/MeshRefinement/CMakeLists.txt index ce51453..43277b6 100644 --- a/extensions/MeshRefinement/CMakeLists.txt +++ b/extensions/MeshRefinement/CMakeLists.txt @@ -26,21 +26,21 @@ sofa_find_package(Sofa.Component.Topology.Container.Dynamic REQUIRED) # MeshRefinement must not depend on InfinyToolkit. MeshRefinement.CUDA still # instantiates the engine facade for the same types, being the only place that can # see its private .inl, and this target links those symbols. -sofa_find_package(SofaCUDA QUIET) -sofa_find_package(MeshRefinement.CUDA QUIET) -if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) - message("${PROJECT_NAME}: SofaCUDA found - adding the Cuda subdivision controller") - list(APPEND SOURCE_FILES ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/CudaSubdivisionController.cpp) - add_definitions(-DINFINYTOOLKIT_MESHREFINEMENT_USES_SOFACUDA) -endif() +#sofa_find_package(SofaCUDA QUIET) +#sofa_find_package(MeshRefinement.CUDA QUIET) +#if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) +# message("${PROJECT_NAME}: SofaCUDA found - adding the Cuda subdivision controller") +# list(APPEND SOURCE_FILES ${INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR}/CudaSubdivisionController.cpp) +# add_definitions(-DINFINYTOOLKIT_MESHREFINEMENT_USES_SOFACUDA) +#endif() add_library(${PROJECT_NAME} SHARED ${HEADER_FILES} ${SOURCE_FILES}) target_compile_definitions(${PROJECT_NAME} PRIVATE "-DSOFA_BUILD_INFINYTOOLKIT_MESHREFINEMENT") target_link_libraries(${PROJECT_NAME} MeshRefinement Sofa.Component.Topology.Container.Dynamic) -if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) - target_link_libraries(${PROJECT_NAME} SofaCUDA MeshRefinement.CUDA) -endif() +#if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) +# target_link_libraries(${PROJECT_NAME} SofaCUDA MeshRefinement.CUDA) +#endif() sofa_create_package_with_targets( PACKAGE_NAME ${PROJECT_NAME} From fb87d8ae2b8075a5b8574d31732613bb385f59e7 Mon Sep 17 00:00:00 2001 From: epernod Date: Sat, 12 Sep 2026 00:48:08 +0200 Subject: [PATCH 5/6] [CarvingTools] FIX the test reaching into the MeshRefinement repository for its scenes --- .../MeshRefinement/tests/CMakeLists.txt | 5 +- .../TetrahedronSubdivisionController_test.cpp | 17 ++-- .../tests/scenes/SimpleCubeRefinement.scn | 24 ++++++ .../scenes/TetrahedronCutting_Advanced_01.scn | 48 +++++++++++ .../MeshRefinement/tests/scenes/headers.xml | 35 ++++++++ .../tests/scenes/mesh/cube_low_res.msh | 79 +++++++++++++++++++ 6 files changed, 195 insertions(+), 13 deletions(-) create mode 100644 extensions/MeshRefinement/tests/scenes/SimpleCubeRefinement.scn create mode 100644 extensions/MeshRefinement/tests/scenes/TetrahedronCutting_Advanced_01.scn create mode 100644 extensions/MeshRefinement/tests/scenes/headers.xml create mode 100644 extensions/MeshRefinement/tests/scenes/mesh/cube_low_res.msh diff --git a/extensions/MeshRefinement/tests/CMakeLists.txt b/extensions/MeshRefinement/tests/CMakeLists.txt index d8b9eb2..6cf3357 100644 --- a/extensions/MeshRefinement/tests/CMakeLists.txt +++ b/extensions/MeshRefinement/tests/CMakeLists.txt @@ -5,10 +5,7 @@ set(SOURCE_FILES TetrahedronSubdivisionController_test.cpp ) -# The scenes still live in the MeshRefinement plugin, next to the meshes they load. -# They exercise this plugin's controller, so ownership of them ought to follow it -# here eventually; pointing at them is the smaller change for now. -add_definitions("-DINFINYTOOLKIT_MESHREFINEMENT_TEST_SCENES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/../../../../MeshRefinement/examples/\"") +add_definitions("-DINFINYTOOLKIT_MESHREFINEMENT_TEST_SCENES_DIR=\"${CMAKE_CURRENT_SOURCE_DIR}/scenes/\"") add_executable(${PROJECT_NAME} ${SOURCE_FILES}) target_link_libraries(${PROJECT_NAME} InfinyToolkit.MeshRefinement Sofa.Testing Sofa.Component.Topology.Container.Dynamic) diff --git a/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp b/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp index a7da879..6e2d901 100644 --- a/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp +++ b/extensions/MeshRefinement/tests/TetrahedronSubdivisionController_test.cpp @@ -1,15 +1,14 @@ /** - * Covers the public API of TetrahedronSubdivisionController, the component that - * replaced TetrahedronCuttingController and TetrahedronRefinementController. + * Covers the public API of TetrahedronSubdivisionController. * - * The example scenes are loaded as-is so that they stay the single source of truth - * for the geometry, and the controller is then driven through its own methods rather - * than only by key event. Each case asserts the topology actually changed: a cut or a - * subdivision that silently does nothing would otherwise pass. + * The scenes live in scenes/ beside this file, so the test is self-contained and + * needs nothing from the MeshRefinement repository at runtime. They take their + * geometry and their cut parameters from the Data the scene sets, which the cases + * read back rather than hard-coding, so retuning a scene does not invalidate them. * - * The key-driven cases exist because the merge moved refineFullMesh() from '2' to - * '4' - '2' now applies a cut - so the interactive contract of the 13 migrated scenes - * is worth pinning down. + * The controller is driven through its own methods as well as by key event. Each case + * asserts the topology actually changed: a cut or a subdivision that silently does + * nothing would otherwise pass. */ #include diff --git a/extensions/MeshRefinement/tests/scenes/SimpleCubeRefinement.scn b/extensions/MeshRefinement/tests/scenes/SimpleCubeRefinement.scn new file mode 100644 index 0000000..5dcc31e --- /dev/null +++ b/extensions/MeshRefinement/tests/scenes/SimpleCubeRefinement.scn @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/MeshRefinement/tests/scenes/TetrahedronCutting_Advanced_01.scn b/extensions/MeshRefinement/tests/scenes/TetrahedronCutting_Advanced_01.scn new file mode 100644 index 0000000..6b9e13a --- /dev/null +++ b/extensions/MeshRefinement/tests/scenes/TetrahedronCutting_Advanced_01.scn @@ -0,0 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/extensions/MeshRefinement/tests/scenes/headers.xml b/extensions/MeshRefinement/tests/scenes/headers.xml new file mode 100644 index 0000000..af78ba1 --- /dev/null +++ b/extensions/MeshRefinement/tests/scenes/headers.xml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/extensions/MeshRefinement/tests/scenes/mesh/cube_low_res.msh b/extensions/MeshRefinement/tests/scenes/mesh/cube_low_res.msh new file mode 100644 index 0000000..b970e8b --- /dev/null +++ b/extensions/MeshRefinement/tests/scenes/mesh/cube_low_res.msh @@ -0,0 +1,79 @@ +$MeshFormat +2.2 0 8 +$EndMeshFormat +$Nodes +26 +1 -3.5 3.5 5.702223688786035e-07 +2 -3.5 5.702223688786035e-07 -3.5 +3 -3.5 0 0 +4 -3.5 -3.5 -5.702223688786035e-07 +5 -3.5 -5.702223688786035e-07 3.5 +6 -3.5 -3.499999523162842 -3.500000476837158 +7 -3.5 -3.500000476837158 3.499999523162842 +8 -3.5 3.499999523162842 3.500000476837158 +9 -3.5 3.500000476837158 -3.499999523162842 +10 3.5 -3.5 -5.702223688786035e-07 +11 3.5 5.702223688786035e-07 -3.5 +12 3.5 0 0 +13 3.5 3.5 5.702223688786035e-07 +14 3.5 -5.702223688786035e-07 3.5 +15 3.5 3.500000476837158 -3.499999523162842 +16 3.5 3.499999523162842 3.500000476837158 +17 3.5 -3.500000476837158 3.499999523162842 +18 3.5 -3.499999523162842 -3.500000476837158 +19 0 -3.499999523162842 -3.500000476837158 +20 0 5.702223688786035e-07 -3.5 +21 0 3.500000476837158 -3.499999523162842 +22 0 -3.500000476837158 3.499999523162842 +23 0 -5.702223688786035e-07 3.5 +24 0 3.499999523162842 3.500000476837158 +25 0 3.5 5.702223688786035e-07 +26 0 -3.5 -5.702223688786035e-07 +$EndNodes +$Elements +44 +1 4 2 0 1 23 3 12 26 +2 4 2 0 1 23 3 25 12 +3 4 2 0 1 25 3 20 12 +4 4 2 0 1 3 20 12 26 +5 4 2 0 1 5 4 3 26 +6 4 2 0 1 4 6 3 19 +7 4 2 0 1 11 15 21 12 +8 4 2 0 1 14 13 25 16 +9 4 2 0 1 16 25 14 24 +10 4 2 0 1 1 20 3 2 +11 4 2 0 1 20 1 3 25 +12 4 2 0 1 26 5 23 3 +13 4 2 0 1 10 11 20 12 +14 4 2 0 1 5 4 26 7 +15 4 2 0 1 15 13 21 12 +16 4 2 0 1 19 2 6 3 +17 4 2 0 1 17 23 12 22 +18 4 2 0 1 17 23 14 12 +19 4 2 0 1 22 17 10 12 +20 4 2 0 1 1 8 3 24 +21 4 2 0 1 3 8 5 23 +22 4 2 0 1 3 4 19 26 +23 4 2 0 1 3 1 24 25 +24 4 2 0 1 20 11 21 12 +25 4 2 0 1 14 13 12 25 +26 4 2 0 1 20 10 12 26 +27 4 2 0 1 14 25 12 23 +28 4 2 0 1 20 1 25 9 +29 4 2 0 1 5 26 22 7 +30 4 2 0 1 5 26 23 22 +31 4 2 0 1 20 1 9 2 +32 4 2 0 1 11 10 20 18 +33 4 2 0 1 23 3 24 25 +34 4 2 0 1 20 3 19 26 +35 4 2 0 1 3 8 23 24 +36 4 2 0 1 22 23 12 26 +37 4 2 0 1 25 20 21 12 +38 4 2 0 1 10 22 12 26 +39 4 2 0 1 19 2 3 20 +40 4 2 0 1 13 25 21 12 +41 4 2 0 1 20 10 26 18 +42 4 2 0 1 14 25 23 24 +43 4 2 0 1 18 26 20 19 +44 4 2 0 1 20 9 25 21 +$EndElements From 36368f174faab6dad6d9a3519f69c932f162632e Mon Sep 17 00:00:00 2001 From: erik pernod Date: Sun, 13 Sep 2026 23:02:16 +0200 Subject: [PATCH 6/6] Apply batched suggestions from code review Co-authored-by: erik pernod --- CMakeLists.txt | 10 ---------- extensions/MeshRefinement/CMakeLists.txt | 11 +++-------- .../MeshRefinement/CudaSubdivisionController.cpp | 4 ---- 3 files changed, 3 insertions(+), 22 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index d55f639..22ae2cd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -171,17 +171,7 @@ target_include_directories(${PROJECT_NAME} PUBLIC "$") ## Install rules for the library; CMake package configurations files -# The public MeshRefinement controller, as its own plugin: a scene requires -# InfinyToolkit.MeshRefinement while the subdivision engine stays private to the -# MeshRefinement plugin behind its facade. if(MeshRefinement_FOUND) - # BINARY_DIR is kept short and CMAKE_BINARY_DIR-relative (rather than the - # default, which mirrors the deep external_directories/InfinyToolkit/extensions/... - # source path) because the relocatable-install custom target this generates - # (see sofa_set_project_install_relocatable in SofaMacrosInstall.cmake) already - # produces a long .tlog filename; combined with the default nesting it trips - # Windows' 260-char MAX_PATH (MSB3491). This must hold on every machine/CI - # runner building this repo, not just this one, so the fix has to be structural. sofa_add_subdirectory(plugin extensions/MeshRefinement InfinyToolkit.MeshRefinement BINARY_DIR "${CMAKE_BINARY_DIR}/InfinyToolkit.MeshRefinement") endif() diff --git a/extensions/MeshRefinement/CMakeLists.txt b/extensions/MeshRefinement/CMakeLists.txt index 43277b6..74adcaa 100644 --- a/extensions/MeshRefinement/CMakeLists.txt +++ b/extensions/MeshRefinement/CMakeLists.txt @@ -1,9 +1,7 @@ cmake_minimum_required(VERSION 3.12) project(InfinyToolkit.MeshRefinement VERSION 0.1 LANGUAGES CXX) -# The public face of the MeshRefinement plugin. The controller a scene instantiates -# lives here; the subdivision engine stays private to MeshRefinement, reached only -# through MeshRefinementAPI, the one header that plugin publishes. +# This plugin provides a component controller that will be used in SOFA scene to use the public interface MeshRefinementAPI of the MeshRefinement plugin. set(INFINYTOOLKIT_MESHREFINEMENT_SRC_DIR src/InfinyToolkit/MeshRefinement) set(HEADER_FILES @@ -21,11 +19,8 @@ set(SOURCE_FILES sofa_find_package(MeshRefinement REQUIRED) sofa_find_package(Sofa.Component.Topology.Container.Dynamic REQUIRED) -# Optional: the Cuda template instantiations of the controller. They used to sit in -# MeshRefinement.CUDA, which cannot host them now that the controller lives here - -# MeshRefinement must not depend on InfinyToolkit. MeshRefinement.CUDA still -# instantiates the engine facade for the same types, being the only place that can -# see its private .inl, and this target links those symbols. +# Optional: the Cuda template instantiations of the controller. +# Not yet working #sofa_find_package(SofaCUDA QUIET) #sofa_find_package(MeshRefinement.CUDA QUIET) #if(SofaCUDA_FOUND AND MeshRefinement.CUDA_FOUND) diff --git a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp index 4762b85..7c368bf 100644 --- a/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp +++ b/extensions/MeshRefinement/src/InfinyToolkit/MeshRefinement/CudaSubdivisionController.cpp @@ -14,11 +14,7 @@ ******************************************************************************/ /// Instantiates and registers the controller for the Cuda vector types. -/// /// The matching MeshRefinementAPI instantiations are provided by MeshRefinement.CUDA, -/// the only translation unit that can see the engine's private .inl; this file links -/// against those symbols through the public header. -/// /// Only compiled when both SofaCUDA and MeshRefinement.CUDA are found. #include