diff --git a/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt b/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt
index cc503bf09d6..b59238f8a65 100644
--- a/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt
+++ b/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt
@@ -6,10 +6,12 @@ set(SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR "src/sofa/component/coll
set(HEADER_FILES
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/config.h.in
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/init.h
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BaseSubCollisionPipeline.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BVHNarrowPhase.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceBroadPhase.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceDetection.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/CollisionPM.h
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/CompositeCollisionPipeline.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DSAPBox.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/CollisionPipeline.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DirectSAP.h
@@ -19,20 +21,24 @@ set(HEADER_FILES
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/MirrorIntersector.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceDetection.h
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceNarrowPhase.h
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/SubCollisionPipeline.h
)
set(SOURCE_FILES
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/init.cpp
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BaseSubCollisionPipeline.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BVHNarrowPhase.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceBroadPhase.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceDetection.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DSAPBox.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/CollisionPipeline.cpp
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/CompositeCollisionPipeline.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DirectSAP.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DirectSAPNarrowPhase.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/IncrSAP.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceDetection.cpp
${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceNarrowPhase.cpp
+ ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/SubCollisionPipeline.cpp
)
sofa_find_package(Sofa.Simulation.Core REQUIRED)
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.cpp
new file mode 100644
index 00000000000..b15df4b776e
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.cpp
@@ -0,0 +1,86 @@
+/******************************************************************************
+* 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::collision::detection::algorithm
+{
+
+BaseSubCollisionPipeline::BaseSubCollisionPipeline()
+: sofa::core::objectmodel::BaseObject()
+{
+
+}
+
+void BaseSubCollisionPipeline::doBwdInit()
+{
+
+}
+
+void BaseSubCollisionPipeline::doDraw(const core::visual::VisualParams* vparams)
+{
+ SOFA_UNUSED(vparams);
+
+}
+
+void BaseSubCollisionPipeline::init()
+{
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Loading);
+
+ doInit();
+}
+
+/**
+ * @brief Queries all registered contact response types from the Contact factory.
+ *
+ * This static method iterates through all contact types registered in the
+ * Contact::Factory and returns their names. These represent the available
+ * collision response methods (e.g., "PenalityContactForceField", "FrictionContact").
+ *
+ * @return A set of strings containing all registered contact response type names.
+ */
+std::set< std::string > BaseSubCollisionPipeline::getResponseList()
+{
+ std::set< std::string > listResponse;
+ for (const auto& [key, creatorPtr] : *core::collision::Contact::Factory::getInstance())
+ {
+ listResponse.insert(key);
+ }
+ return listResponse;
+}
+
+void BaseSubCollisionPipeline::draw(const core::visual::VisualParams* vparams)
+{
+ const auto stateLifeCycle = vparams->drawTool()->makeStateLifeCycle();
+
+ doDraw(vparams);
+}
+
+void BaseSubCollisionPipeline::handleEvent(sofa::core::objectmodel::Event* e)
+{
+ doHandleEvent(e);
+}
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.h
new file mode 100644
index 00000000000..bdf1bab7919
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/BaseSubCollisionPipeline.h
@@ -0,0 +1,99 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#pragma once
+#include
+
+#include
+
+#include
+#include
+
+namespace sofa::core
+{
+class CollisionModel;
+}
+
+namespace sofa::component::collision::detection::algorithm
+{
+
+/**
+ * @brief Abstract base class defining the interface for sub-collision pipelines.
+ *
+ * This base class is designed to be used with CompositeCollisionPipeline, which
+ * aggregates multiple sub-pipelines and can execute them in parallel.
+ *
+ * @see SubCollisionPipeline for a concrete implementation
+ * @see CompositeCollisionPipeline for the aggregator that manages sub-pipelines
+ */
+class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API BaseSubCollisionPipeline : public sofa::core::objectmodel::BaseObject
+{
+public:
+ SOFA_ABSTRACT_CLASS(BaseSubCollisionPipeline, sofa::core::objectmodel::BaseObject);
+
+protected:
+ BaseSubCollisionPipeline();
+
+ /// @brief Called during initialization. Derived classes must implement validation and setup logic.
+ virtual void doInit() = 0;
+
+ /// @brief Called after all objects are initialized. Default implementation is empty.
+ virtual void doBwdInit();
+
+ /// @brief Called to handle simulation events. Derived classes must implement event processing.
+ virtual void doHandleEvent(sofa::core::objectmodel::Event* e) = 0;
+
+ /// @brief Called during rendering. Default implementation is empty.
+ virtual void doDraw(const core::visual::VisualParams* vparams);
+
+public:
+ ///@{
+ /// @name Collision Pipeline Interface
+ /// These methods define the three-phase collision workflow that derived classes must implement.
+
+ /// @brief Clears collision state from the previous time step (contacts, responses).
+ virtual void computeCollisionReset() = 0;
+
+ /// @brief Performs collision detection (bounding tree, broad phase, narrow phase).
+ virtual void computeCollisionDetection() = 0;
+
+ /// @brief Creates collision responses based on detected contacts.
+ virtual void computeCollisionResponse() = 0;
+
+ ///@}
+
+ /// @brief Returns the list of collision models handled by this sub-pipeline.
+ virtual std::vector getCollisionModels() = 0;
+
+ /// @brief Initializes the component. Marked final to enforce Template Method pattern.
+ void init() override final;
+
+ /// @brief Renders debug visualization. Marked final to enforce Template Method pattern.
+ void draw(const core::visual::VisualParams* vparams) override final;
+
+ /// @brief Processes simulation events. Marked final to enforce Template Method pattern.
+ void handleEvent(sofa::core::objectmodel::Event* e) override final;
+
+ /// @brief Returns all available contact response types registered in the Contact factory.
+ static std::set< std::string > getResponseList();
+};
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.cpp
index 4af2b8fb932..bbf6cf31888 100644
--- a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.cpp
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.cpp
@@ -66,33 +66,57 @@ CollisionPipeline::CollisionPipeline()
, d_depth(initData(&d_depth, defaultDepthValue, "depth",
("Max depth of bounding trees. (default=" + std::to_string(defaultDepthValue) + ", min=?, max=?)").c_str()))
{
+
}
-#ifdef SOFA_DUMP_VISITOR_INFO
-typedef simulation::Visitor::ctime_t ctime_t;
-#endif
-
void CollisionPipeline::init()
{
- Inherit1::init();
-
- if (broadPhaseDetection == nullptr)
- {
- msg_warning() << "A BroadPhase component is required to compute collision detection and was not found in the current scene";
- }
-
- if (narrowPhaseDetection == nullptr)
- {
- msg_warning() << "A NarrowPhase component is required to compute collision detection and was not found in the current scene";
- }
-
- if (contactManager == nullptr)
- {
- msg_warning() << "A ContactManager component is required to compute collision response and was not found in the current scene";
- }
-
+ msg_info() << "Since v26.06, CollisionPipeline is a wrapper to CompositeCollisionPipeline with a single SubCollisionPipeline.";
+ msg_info() << "If you want more flexibility, use directly the components CompositeCollisionPipeline and SubCollisionPipeline, with their respective Data.";
+
+ auto context = this->getContext();
+ assert(context);
+
+ m_subCollisionPipeline = sofa::core::objectmodel::New();
+ m_subCollisionPipeline->d_depth.setParent(&this->d_depth);
+
+ // set the whole collision models list to the sub collision pipeline
+ sofa::type::vector collisionModels;
+ context->get>(&collisionModels, BaseContext::SearchDown);
+ for(auto collisionModel : collisionModels)
+ {
+ m_subCollisionPipeline->l_collisionModels.add(collisionModel.get());
+ }
+
+ // set the other components to the sub collision pipeline
+ // intersection
+ sofa::core::collision::Intersection* intersectionMethod = nullptr;
+ context->get(intersectionMethod, BaseContext::SearchDown);
+ m_subCollisionPipeline->l_intersectionMethod.set(intersectionMethod);
+
+ // broad phase
+ sofa::core::collision::BroadPhaseDetection* broadPhaseDetection = nullptr;
+ context->get(broadPhaseDetection, BaseContext::SearchDown);
+ m_subCollisionPipeline->l_broadPhaseDetection.set(broadPhaseDetection);
+
+ // narrow phase
+ sofa::core::collision::NarrowPhaseDetection* narrowPhaseDetection = nullptr;
+ context->get(narrowPhaseDetection, BaseContext::SearchDown);
+ m_subCollisionPipeline->l_narrowPhaseDetection.set(narrowPhaseDetection);
+
+ // contact manager
+ sofa::core::collision::ContactManager* contactManager = nullptr;
+ context->get(contactManager, BaseContext::SearchDown);
+ m_subCollisionPipeline->l_contactManager.set(contactManager);
+
+ m_subCollisionPipeline->init();
+ this->l_subCollisionPipelines.add(m_subCollisionPipeline.get());
+ this->addSlave(m_subCollisionPipeline.get());
+
/// Insure that all the value provided by the user are valid and report message if it is not.
checkDataValues() ;
+
+ Inherit1::init();
}
void CollisionPipeline::checkDataValues()
@@ -105,222 +129,4 @@ void CollisionPipeline::checkDataValues()
}
}
-void CollisionPipeline::doCollisionReset()
-{
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "CollisionPipeline::doCollisionReset" ;
-
- // clear all contacts
- if (contactManager != nullptr)
- {
- const type::vector& contacts = contactManager->getContacts();
- for (const auto& contact : contacts)
- {
- if (contact != nullptr)
- {
- contact->removeResponse();
- }
- }
- }
-
- // clear all collision groups
- if (groupManager != nullptr)
- {
- core::objectmodel::BaseContext* scene = getContext();
- groupManager->clearGroups(scene);
- }
-}
-
-void CollisionPipeline::doCollisionDetection(const type::vector& collisionModels)
-{
- SCOPED_TIMER_VARNAME(docollisiontimer, "doCollisionDetection");
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "doCollisionDetection, compute Bounding Trees" ;
-
- // First, we compute a bounding volume for the collision model (for example bounding sphere)
- // or we have loaded a collision model that knows its other model
-
- type::vector vectBoundingVolume;
- {
- SCOPED_TIMER_VARNAME(bboxtimer, "ComputeBoundingTree");
-
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printNode("ComputeBoundingTree");
-#endif
- const bool continuous = intersectionMethod->useContinuous();
- const auto continuousIntersectionType = intersectionMethod->continuousIntersectionType();
- const SReal dt = getContext()->getDt();
-
- type::vector::const_iterator it;
- const type::vector::const_iterator itEnd = collisionModels.end();
- int nActive = 0;
-
- const int used_depth = (
- (broadPhaseDetection && broadPhaseDetection->needsDeepBoundingTree()) ||
- (narrowPhaseDetection && narrowPhaseDetection->needsDeepBoundingTree())
- ) ? d_depth.getValue() : 0;
-
- for (it = collisionModels.begin(); it != itEnd; ++it)
- {
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "doCollisionDetection, consider model" ;
-
- if (!(*it)->isActive()) continue;
-
- if (continuous)
- {
- const std::string msg = "Compute Continuous BoundingTree: " + (*it)->getName();
- ScopedAdvancedTimer continuousBoundingTreeTimer(msg.c_str());
- (*it)->computeContinuousBoundingTree(dt, continuousIntersectionType, used_depth);
- }
- else
- {
- std::string msg = "Compute BoundingTree: " + (*it)->getName();
- ScopedAdvancedTimer boundingTreeTimer(msg.c_str());
- (*it)->computeBoundingTree(used_depth);
- }
-
- vectBoundingVolume.push_back ((*it)->getFirst());
- ++nActive;
- }
-
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printCloseNode("ComputeBoundingTree");
-#endif
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "doCollisionDetection, Computed "<getName();
-
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printNode("BroadPhase");
-#endif
- {
- SCOPED_TIMER_VARNAME(broadphase, "BroadPhase");
- intersectionMethod->beginBroadPhase();
- broadPhaseDetection->beginBroadPhase();
- broadPhaseDetection->addCollisionModels(vectBoundingVolume); // detection is done there
- broadPhaseDetection->endBroadPhase();
- intersectionMethod->endBroadPhase();
- }
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printCloseNode("BroadPhase");
-#endif
-
- // then we start the narrow phase
- if (narrowPhaseDetection == nullptr)
- {
- return; // can't go further
- }
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "doCollisionDetection, NarrowPhaseDetection "<getName();
-
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printNode("NarrowPhase");
-#endif
- {
- SCOPED_TIMER_VARNAME(narrowphase, "NarrowPhase");
- intersectionMethod->beginNarrowPhase();
- narrowPhaseDetection->beginNarrowPhase();
- const type::vector >& vectCMPair = broadPhaseDetection->getCollisionModelPairs();
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "doCollisionDetection, "<< vectCMPair.size()<<" colliding model pairs" ;
-
- narrowPhaseDetection->addCollisionPairs(vectCMPair);
- narrowPhaseDetection->endNarrowPhase();
- intersectionMethod->endNarrowPhase();
- }
-#ifdef SOFA_DUMP_VISITOR_INFO
- simulation::Visitor::printCloseNode("NarrowPhase");
-#endif
-
-}
-
-void CollisionPipeline::doCollisionResponse()
-{
- core::objectmodel::BaseContext* scene = getContext();
- // then we start the creation of contacts
- if (narrowPhaseDetection == nullptr || contactManager == nullptr)
- {
- return; // can't go further
- }
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "Create Contacts " << contactManager->getName() ;
-
- {
- SCOPED_TIMER_VARNAME(createContactsTimer, "CreateContacts");
- contactManager->createContacts(narrowPhaseDetection->getDetectionOutputs());
- }
-
- // finally we start the creation of collisionGroup
-
- const type::vector& contacts = contactManager->getContacts();
-
- // First we remove all contacts with non-simulated objects and directly add them
- type::vector notStaticContacts;
-
- {
- SCOPED_TIMER_VARNAME(createStaticObjectsResponseTimer, "CreateStaticObjectsResponse");
- for (const auto& contact : contacts)
- {
- const auto collisionModels = contact->getCollisionModels();
- if (collisionModels.first != nullptr && !collisionModels.first->isSimulated())
- {
- contact->createResponse(collisionModels.second->getContext());
- }
- else if (collisionModels.second != nullptr && !collisionModels.second->isSimulated())
- {
- contact->createResponse(collisionModels.first->getContext());
- }
- else
- {
- notStaticContacts.push_back(contact);
- }
- }
- }
-
- if (groupManager == nullptr)
- {
- SCOPED_TIMER_VARNAME(createResponseTimer, "CreateMovingObjectsResponse");
-
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "Linking all contacts to Scene" ;
-
- for (const auto& contact : notStaticContacts)
- {
- contact->createResponse(scene);
- }
- }
- else
- {
- msg_info_when(d_doPrintInfoMessage.getValue())
- << "Create Groups "<getName();
-
- groupManager->createGroups(scene, notStaticContacts);
- }
-}
-
-std::set< std::string > CollisionPipeline::getResponseList() const
-{
- std::set< std::string > listResponse;
- core::collision::Contact::Factory::iterator it;
- for (it=core::collision::Contact::Factory::getInstance()->begin(); it!=core::collision::Contact::Factory::getInstance()->end(); ++it)
- {
- listResponse.insert(it->first);
- }
- return listResponse;
-}
-
} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.h
index 8d7a2233213..d497092d498 100644
--- a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.h
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CollisionPipeline.h
@@ -22,36 +22,30 @@
#pragma once
#include
-#include
+#include
+#include
namespace sofa::component::collision::detection::algorithm
{
-class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API CollisionPipeline : public sofa::simulation::PipelineImpl
+class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API CollisionPipeline : public CompositeCollisionPipeline
{
public:
- SOFA_CLASS(CollisionPipeline,sofa::simulation::PipelineImpl);
+ SOFA_CLASS(CollisionPipeline, CompositeCollisionPipeline);
Data d_doPrintInfoMessage;
Data d_doDebugDraw;
Data d_depth;
+
protected:
CollisionPipeline();
public:
void init() override;
- /// get the set of response available with the current collision pipeline
- std::set< std::string > getResponseList() const override;
protected:
- // -- Pipeline interface
- /// Remove collision response from last step
- void doCollisionReset() override;
- /// Detect new collisions. Note that this step must not modify the simulation graph
- void doCollisionDetection(const sofa::type::vector& collisionModels) override;
- /// Add collision response in the simulation graph
- void doCollisionResponse() override;
-
virtual void checkDataValues() ;
+
+ SubCollisionPipeline::SPtr m_subCollisionPipeline;
public:
static const int defaultDepthValue;
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.cpp
new file mode 100644
index 00000000000..e50d6e6b0e8
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.cpp
@@ -0,0 +1,226 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+#include
+
+#include
+using sofa::helper::ScopedAdvancedTimer ;
+
+#include
+
+
+namespace sofa::component::collision::detection::algorithm
+{
+
+using namespace sofa;
+using namespace sofa::core;
+using namespace sofa::core::collision;
+
+void registerCompositeCollisionPipeline(sofa::core::ObjectFactory* factory)
+{
+ factory->registerObjects(core::ObjectRegistrationData("Multiple collision pipelines in one.")
+ .add< CompositeCollisionPipeline >());
+}
+
+CompositeCollisionPipeline::CompositeCollisionPipeline()
+ : d_parallelDetection(initData(&d_parallelDetection, false, "parallelDetection", "Parallelize collision detection."))
+ , l_subCollisionPipelines(initLink("subCollisionPipelines", "List of sub collision pipelines to handle."))
+{
+}
+
+/**
+ * @brief Initializes the composite pipeline and validates its configuration.
+ *
+ * This method performs several validation and setup steps:
+ * 1. Validates that at least one sub-pipeline is linked
+ * 2. Initializes the task scheduler if parallel detection is enabled
+ * 3. Validates all linked sub-pipelines are valid (non-null)
+ * 4. Checks that all collision models in the scene are covered by at least one sub-pipeline
+ * (issues warnings for any uncovered models to help users identify configuration issues)
+ */
+void CompositeCollisionPipeline::init()
+{
+ Inherit1::init();
+
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid);
+
+ // Validate that at least one sub-pipeline is defined
+ if(l_subCollisionPipelines.size() == 0)
+ {
+ msg_warning() << "No SubCollisionPipeline defined in CompositeCollisionPipeline. Nothing will be done." ;
+
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+
+ // Initialize task scheduler for parallel execution if enabled
+ if(d_parallelDetection.getValue())
+ {
+ this->initTaskScheduler();
+ }
+
+ // Collect all collision models from the scene to verify coverage
+ simulation::Node* root = dynamic_cast(getContext());
+ std::vector sceneCollisionModels;
+ root->getTreeObjects(&sceneCollisionModels);
+
+ // Collect all collision models handled by sub-pipelines
+ std::set pipelineCollisionModels;
+ for(auto* subPipeline : l_subCollisionPipelines)
+ {
+ if(!subPipeline)
+ {
+ msg_error() << "One of the subCollisionPipeline is incorrect (nullptr or invalid) ";
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+
+ for (auto* cm : subPipeline->getCollisionModels())
+ {
+ pipelineCollisionModels.insert(cm);
+ }
+ }
+
+ // Warn about collision models not covered by any sub-pipeline
+ // This helps users identify configuration issues where some models won't participate in collision
+ for (const auto& cm : sceneCollisionModels)
+ {
+ if (pipelineCollisionModels.find(cm) == pipelineCollisionModels.end())
+ {
+ msg_warning() << "CollisionModel " << cm->getPathName() << " is not handled by any SubCollisionPipeline.";
+ }
+ }
+
+}
+
+void CompositeCollisionPipeline::reset()
+{
+
+}
+
+/// Delegates collision reset to all sub-pipelines sequentially.
+void CompositeCollisionPipeline::doCollisionReset()
+{
+ msg_info() << "CompositeCollisionPipeline::doCollisionReset" ;
+
+ for(const auto& subPipeline : l_subCollisionPipelines)
+ {
+ subPipeline->computeCollisionReset();
+ }
+}
+
+/**
+ * @brief Executes collision detection across all sub-pipelines.
+ *
+ * If parallel detection is enabled and a task scheduler is available, the detection
+ * phase of each sub-pipeline runs concurrently. This can significantly improve
+ * performance when there are multiple independent collision groups.
+ *
+ * @param collisionModels Ignored - each sub-pipeline uses its own linked collision models.
+ */
+void CompositeCollisionPipeline::doCollisionDetection(const type::vector& collisionModels)
+{
+ SOFA_UNUSED(collisionModels);
+
+ SCOPED_TIMER_VARNAME(docollisiontimer, "doCollisionDetection");
+
+ if(m_taskScheduler)
+ {
+ // Parallel execution: distribute sub-pipeline detection across available threads
+ auto computeCollisionDetection = [&](const auto& range)
+ {
+ for (auto it = range.start; it != range.end; ++it)
+ {
+ (*it)->computeCollisionDetection();
+ }
+ };
+
+ sofa::simulation::forEachRange(sofa::simulation::ForEachExecutionPolicy::PARALLEL, *m_taskScheduler, l_subCollisionPipelines.begin(), l_subCollisionPipelines.end(), computeCollisionDetection);
+ }
+ else
+ {
+ // Sequential execution: process each sub-pipeline one after another
+ for (const auto& subPipeline : l_subCollisionPipelines)
+ {
+ subPipeline->computeCollisionDetection();
+ }
+ }
+}
+
+/// Delegates collision response creation to all sub-pipelines sequentially.
+void CompositeCollisionPipeline::doCollisionResponse()
+{
+ for (const auto& subPipeline : l_subCollisionPipelines)
+ {
+ subPipeline->computeCollisionResponse();
+ }
+}
+
+/// Returns the list of available contact response types from the contact factory.
+std::set< std::string > CompositeCollisionPipeline::getResponseList() const
+{
+ return BaseSubCollisionPipeline::getResponseList();
+}
+
+/// Entry point for collision reset phase, called by the simulation loop.
+void CompositeCollisionPipeline::computeCollisionReset()
+{
+ if(!this->isComponentStateValid())
+ return;
+
+ doCollisionReset();
+}
+
+/// Entry point for collision detection phase, called by the simulation loop.
+void CompositeCollisionPipeline::computeCollisionDetection()
+{
+ if(!this->isComponentStateValid())
+ return;
+
+ // The collision models parameter is not used by this pipeline
+ // since each sub-pipeline manages its own set of models
+ static std::vector collisionModels{};
+
+ doCollisionDetection(collisionModels);
+}
+
+/// Entry point for collision response phase, called by the simulation loop.
+void CompositeCollisionPipeline::computeCollisionResponse()
+{
+ if(!this->isComponentStateValid())
+ return;
+
+ doCollisionResponse();
+}
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.h
new file mode 100644
index 00000000000..99088c894a5
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/CompositeCollisionPipeline.h
@@ -0,0 +1,102 @@
+/******************************************************************************
+* 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::collision::detection::algorithm
+{
+
+class BaseSubCollisionPipeline;
+
+/**
+ * @brief A collision pipeline that aggregates multiple sub-pipelines using the Composite pattern.
+ *
+ * CompositeCollisionPipeline enables partitioning collision detection into independent groups,
+ * where each group is handled by its own SubCollisionPipeline. This architecture provides
+ * several benefits:
+ *
+ * 1. Modularity: Different collision model groups can use different detection algorithms,
+ * intersection methods, or contact managers
+ *
+ * 2. Parallelization: When enabled via d_parallelDetection, the collision detection phase
+ * of each sub-pipeline runs concurrently, potentially improving performance on multi-core systems
+ *
+ * 3. Isolation: Collision models in different sub-pipelines won't generate contacts with each other,
+ * allowing intentional separation of non-interacting object groups
+ *
+ * @see SubCollisionPipeline
+ * @see BaseSubCollisionPipeline
+ */
+class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API CompositeCollisionPipeline : public sofa::core::collision::Pipeline, public sofa::simulation::TaskSchedulerUser
+{
+public:
+ SOFA_CLASS2(CompositeCollisionPipeline, sofa::core::collision::Pipeline, sofa::simulation::TaskSchedulerUser);
+
+ sofa::Data d_depth;
+protected:
+ CompositeCollisionPipeline();
+public:
+ /// @brief Initializes the pipeline and validates sub-pipeline configuration.
+ void init() override;
+
+ /// @brief Returns the set of available collision response types.
+ std::set< std::string > getResponseList() const override;
+protected:
+ // -- Pipeline interface
+
+ /// @brief Delegates reset to all sub-pipelines to clear previous contacts.
+ void doCollisionReset() override;
+
+ /// @brief Delegates collision detection to all sub-pipelines (optionally in parallel).
+ /// @note The collisionModels parameter is ignored; each sub-pipeline uses its own models.
+ void doCollisionDetection(const sofa::type::vector& collisionModels) override;
+
+ /// @brief Delegates response creation to all sub-pipelines.
+ void doCollisionResponse() override;
+
+ void reset() override;
+
+ /// @brief Entry point for collision reset, called by the simulation loop.
+ virtual void computeCollisionReset() override final;
+
+ /// @brief Entry point for collision detection, called by the simulation loop.
+ virtual void computeCollisionDetection() override final;
+
+ /// @brief Entry point for collision response, called by the simulation loop.
+ virtual void computeCollisionResponse() override final;
+
+public:
+ /// When true, collision detection across sub-pipelines runs in parallel using the task scheduler.
+ sofa::Data d_parallelDetection;
+
+ /// List of sub-pipelines to aggregate. Each handles an independent set of collision models.
+ sofa::MultiLink < CompositeCollisionPipeline, BaseSubCollisionPipeline, sofa::BaseLink::FLAG_DUPLICATE > l_subCollisionPipelines;
+
+
+ friend class CollisionPipeline; // to be able to call do*()
+};
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.cpp
new file mode 100644
index 00000000000..2e28949e6f8
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.cpp
@@ -0,0 +1,322 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#include
+
+#include
+
+#include
+
+#include
+
+#include
+using sofa::helper::ScopedAdvancedTimer ;
+
+#include
+
+
+namespace sofa::component::collision::detection::algorithm
+{
+
+using namespace sofa;
+using namespace sofa::core;
+using namespace sofa::core::collision;
+
+void registerSubCollisionPipeline(sofa::core::ObjectFactory* factory)
+{
+ factory->registerObjects(core::ObjectRegistrationData("Collision pipeline to be used with CompositeCollisionPipeline.")
+ .add< SubCollisionPipeline >());
+}
+
+SubCollisionPipeline::SubCollisionPipeline()
+ : Inherited()
+ , d_depth(initData(&d_depth, s_defaultDepthValue, "depth", ("Max depth of bounding trees. (default=" + std::to_string(s_defaultDepthValue) + ", min=?, max=?)").c_str()))
+ , l_collisionModels(initLink("collisionModels", "List of collision models to consider in this pipeline"))
+ , l_intersectionMethod(initLink("intersectionMethod", "Intersection method to use in this pipeline"))
+ , l_contactManager(initLink("contactManager", "Contact manager to use in this pipeline"))
+ , l_broadPhaseDetection(initLink("broadPhaseDetection", "Broad phase detection to use in this pipeline"))
+ , l_narrowPhaseDetection(initLink("narrowPhaseDetection", "Narrow phase detection to use in this pipeline"))
+{
+}
+
+/**
+ * @brief Validates that all required components are properly linked.
+ *
+ * Checks for the presence of all mandatory links:
+ * - At least one collision model
+ * - An intersection method
+ * - A contact manager
+ * - A broad phase detection component
+ * - A narrow phase detection component
+ *
+ * Sets the component state to Invalid if any required component is missing,
+ * which prevents the pipeline from executing collision detection.
+ */
+void SubCollisionPipeline::doInit()
+{
+ bool validity = true;
+
+ // Validate all required links are set
+ if (l_collisionModels.size() == 0)
+ {
+ msg_warning() << "At least one CollisionModel is required to compute collision detection.";
+ validity = false;
+ }
+
+ if (!l_intersectionMethod)
+ {
+ msg_warning() << "An Intersection detection component is required to compute collision detection.";
+ validity = false;
+ }
+
+ if (!l_contactManager)
+ {
+ msg_warning() << "A contact manager component is required to compute collision detection.";
+ validity = false;
+ }
+
+ if (!l_broadPhaseDetection)
+ {
+ msg_warning() << "A BroadPhase component is required to compute collision detection.";
+ validity = false;
+ }
+ if (!l_narrowPhaseDetection)
+ {
+ msg_warning() << "A NarrowPhase component is required to compute collision detection.";
+ validity = false;
+ }
+
+ // Set component state based on validation results
+ if (!validity)
+ {
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ }
+ else
+ {
+ this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid);
+ }
+
+}
+
+/**
+ * @brief Resets the collision state by clearing all existing contact responses.
+ *
+ * This method prepares for a new collision detection cycle by:
+ * 1. Propagating the intersection method to all detection components
+ * 2. Removing all contact responses created during the previous time step
+ *
+ * This ensures a clean slate before new collisions are detected.
+ */
+void SubCollisionPipeline::computeCollisionReset()
+{
+ if (!this->isComponentStateValid())
+ return;
+
+ msg_info() << "SubCollisionPipeline::doCollisionReset";
+
+ // Propagate the intersection method to all collision detection components
+ l_broadPhaseDetection->setIntersectionMethod(l_intersectionMethod.get());
+ l_narrowPhaseDetection->setIntersectionMethod(l_intersectionMethod.get());
+ l_contactManager->setIntersectionMethod(l_intersectionMethod.get());
+
+ // Remove all contact responses from the previous time step
+ const type::vector& contacts = l_contactManager->getContacts();
+ for (const auto& contact : contacts)
+ {
+ if (contact != nullptr)
+ {
+ contact->removeResponse();
+ }
+ }
+}
+
+/**
+ * @brief Performs collision detection in two phases: broad phase and narrow phase.
+ */
+void SubCollisionPipeline::computeCollisionDetection()
+{
+ SCOPED_TIMER_VARNAME(docollisiontimer, "doCollisionDetection");
+
+ if (!this->isComponentStateValid())
+ return;
+
+ msg_info() << "doCollisionDetection, compute Bounding Trees" ;
+
+ // Phase 1: Compute bounding volumes for all collision models
+ // These hierarchical structures enable efficient spatial queries
+ type::vector vectBoundingVolume;
+ {
+ SCOPED_TIMER_VARNAME(bboxtimer, "ComputeBoundingTree");
+
+ // Check if continuous collision detection (CCD) is enabled
+ const bool continuous = l_intersectionMethod->useContinuous();
+ const auto continuousIntersectionType = l_intersectionMethod->continuousIntersectionType();
+ const SReal dt = getContext()->getDt();
+
+ int nActive = 0;
+
+ // Use full tree depth only if detection algorithms require it, otherwise use depth 0
+ const int used_depth = (
+ (l_broadPhaseDetection->needsDeepBoundingTree()) ||
+ (l_narrowPhaseDetection->needsDeepBoundingTree())
+ ) ? d_depth.getValue() : 0;
+
+ // Iterate through all linked collision models
+ for (auto it = l_collisionModels.begin(); it != l_collisionModels.end(); ++it)
+ {
+ msg_info() << "doCollisionDetection, consider model" ;
+
+ // Skip inactive models
+ if (!(*it)->isActive()) continue;
+
+ if (continuous)
+ {
+ // CCD: Compute swept bounding volumes that cover the motion trajectory
+ const std::string msg = "Compute Continuous BoundingTree: " + (*it)->getName();
+ ScopedAdvancedTimer continuousBoundingTreeTimer(msg.c_str());
+ (*it)->computeContinuousBoundingTree(dt, continuousIntersectionType, used_depth);
+ }
+ else
+ {
+ // Discrete: Compute bounding volumes at current positions
+ std::string msg = "Compute BoundingTree: " + (*it)->getName();
+ ScopedAdvancedTimer boundingTreeTimer(msg.c_str());
+ (*it)->computeBoundingTree(used_depth);
+ }
+
+ // getFirst() returns the root of the bounding tree hierarchy
+ vectBoundingVolume.push_back ((*it)->getFirst());
+ ++nActive;
+ }
+
+
+ msg_info() << "doCollisionDetection, Computed "<getName();
+
+ {
+ SCOPED_TIMER_VARNAME(broadphase, "BroadPhase");
+ l_intersectionMethod->beginBroadPhase();
+ l_broadPhaseDetection->beginBroadPhase();
+ l_broadPhaseDetection->addCollisionModels(vectBoundingVolume); // Actual detection happens here
+ l_broadPhaseDetection->endBroadPhase();
+ l_intersectionMethod->endBroadPhase();
+ }
+
+ // Phase 3: Narrow Phase Detection
+ // Performs precise intersection tests on potentially colliding pairs
+ msg_info() << "doCollisionDetection, NarrowPhaseDetection "<< l_narrowPhaseDetection->getName();
+
+ {
+ SCOPED_TIMER_VARNAME(narrowphase, "NarrowPhase");
+ l_intersectionMethod->beginNarrowPhase();
+ l_narrowPhaseDetection->beginNarrowPhase();
+
+ // Get the pairs identified by broad phase
+ const type::vector >& vectCMPair = l_broadPhaseDetection->getCollisionModelPairs();
+
+ msg_info() << "doCollisionDetection, "<< vectCMPair.size()<<" colliding model pairs" ;
+
+ // Perform precise intersection tests on each pair
+ l_narrowPhaseDetection->addCollisionPairs(vectCMPair);
+ l_narrowPhaseDetection->endNarrowPhase();
+ l_intersectionMethod->endNarrowPhase();
+ }
+
+}
+
+/**
+ * @brief Creates collision responses based on detected contacts.
+ */
+void SubCollisionPipeline::computeCollisionResponse()
+{
+ if (!this->isComponentStateValid())
+ return;
+
+ core::objectmodel::BaseContext* scene = getContext();
+
+ msg_info() << "Create Contacts " << l_contactManager->getName() ;
+
+ // Create contact objects from narrow phase detection results
+ {
+ SCOPED_TIMER_VARNAME(createContactsTimer, "CreateContacts");
+ l_contactManager->createContacts(l_narrowPhaseDetection->getDetectionOutputs());
+ }
+
+ const type::vector& contacts = l_contactManager->getContacts();
+
+ // Separate contacts into two categories based on whether they involve static objects
+ type::vector notStaticContacts;
+
+ // Process contacts involving static (non-simulated) objects first
+ // These get their response attached to the simulated object's context
+ {
+ SCOPED_TIMER_VARNAME(createStaticObjectsResponseTimer, "CreateStaticObjectsResponse");
+ for (const auto& contact : contacts)
+ {
+ const auto collisionModels = contact->getCollisionModels();
+ if (collisionModels.first != nullptr && !collisionModels.first->isSimulated())
+ {
+ // First model is static, attach response to second model's context
+ contact->createResponse(collisionModels.second->getContext());
+ }
+ else if (collisionModels.second != nullptr && !collisionModels.second->isSimulated())
+ {
+ // Second model is static, attach response to first model's context
+ contact->createResponse(collisionModels.first->getContext());
+ }
+ else
+ {
+ // Both models are simulated, handle separately
+ notStaticContacts.push_back(contact);
+ }
+ }
+ }
+
+ // Process contacts between two simulated (moving) objects
+ // These get their response attached to the scene context
+ SCOPED_TIMER_VARNAME(createResponseTimer, "CreateMovingObjectsResponse");
+
+ msg_info() << "Linking all contacts to Scene" ;
+
+ for (const auto& contact : notStaticContacts)
+ {
+ contact->createResponse(scene);
+ }
+}
+
+
+/// Returns the list of collision models explicitly linked to this pipeline.
+std::vector SubCollisionPipeline::getCollisionModels()
+{
+ std::vector collisionModels;
+ collisionModels.reserve(l_collisionModels.getSize());
+ for(auto* collisionModel : l_collisionModels)
+ {
+ collisionModels.push_back(collisionModel);
+ }
+ return collisionModels;
+}
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.h
new file mode 100644
index 00000000000..406d036b493
--- /dev/null
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.h
@@ -0,0 +1,111 @@
+/******************************************************************************
+* SOFA, Simulation Open-Framework Architecture *
+* (c) 2006 INRIA, USTL, UJF, CNRS, MGH *
+* *
+* This program is free software; you can redistribute it and/or modify it *
+* under the terms of the GNU Lesser General Public License as published by *
+* the Free Software Foundation; either version 2.1 of the License, or (at *
+* your option) any later version. *
+* *
+* This program is distributed in the hope that it will be useful, but WITHOUT *
+* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or *
+* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License *
+* for more details. *
+* *
+* You should have received a copy of the GNU Lesser General Public License *
+* along with this program. If not, see . *
+*******************************************************************************
+* Authors: The SOFA Team and external contributors (see Authors.txt) *
+* *
+* Contact information: contact@sofa-framework.org *
+******************************************************************************/
+#pragma once
+#include
+
+#include
+
+#include
+#include
+#include
+#include
+#include
+
+#include
+#include
+
+namespace sofa::component::collision::detection::algorithm
+{
+
+/**
+ * @brief A self-contained collision pipeline for a specific set of collision models.
+ *
+ * SubCollisionPipeline implements a complete collision detection and response workflow
+ * for an explicitly defined subset of collision models. Unlike the standard CollisionPipeline
+ * which processes all collision models in the scene graph, this component only handles
+ * the collision models explicitly linked to it.
+ *
+ * This class is designed to be used as part of a CompositeCollisionPipeline, which can
+ * aggregate multiple SubCollisionPipelines to handle different groups of collision models
+ * independently (and potentially in parallel).
+ *
+ * Required components (via links):
+ * - At least one CollisionModel
+ * - An Intersection method (e.g., MinProximityIntersection, NewProximityIntersection)
+ * - A BroadPhaseDetection (e.g., BruteForceBroadPhase, BVHNarrowPhase)
+ * - A NarrowPhaseDetection (e.g., BVHNarrowPhase, DirectSAP)
+ * - A ContactManager (e.g., DefaultContactManager)
+ *
+ * @see CompositeCollisionPipeline
+ * @see BaseSubCollisionPipeline
+ */
+class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API SubCollisionPipeline : public BaseSubCollisionPipeline
+{
+public:
+ using Inherited = BaseSubCollisionPipeline;
+ SOFA_CLASS(SubCollisionPipeline, Inherited);
+protected:
+ SubCollisionPipeline();
+public:
+ virtual ~SubCollisionPipeline() override = default;
+
+ /// @brief Validates that all required components are linked and sets the component state accordingly.
+ void doInit() override;
+
+ /// @brief Event handling (currently no-op for this pipeline).
+ void doHandleEvent(sofa::core::objectmodel::Event*) override {}
+
+ /// @brief Clears contact responses from the previous time step.
+ void computeCollisionReset() override;
+
+ /// @brief Performs collision detection: computes bounding trees, runs broad and narrow phase detection.
+ void computeCollisionDetection() override;
+
+ /// @brief Creates contact responses based on detected collisions.
+ void computeCollisionResponse() override;
+
+ /// @brief Returns the list of collision models handled by this pipeline.
+ std::vector getCollisionModels() override;
+
+ /// Maximum depth of bounding trees used in collision detection.
+ sofa::Data d_depth;
+
+ /// List of collision models to process in this pipeline.
+ sofa::MultiLink < SubCollisionPipeline, sofa::core::CollisionModel, sofa::BaseLink::FLAG_DUPLICATE > l_collisionModels;
+
+ /// Intersection method defining how to detect intersections between geometric primitives.
+ sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::Intersection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_intersectionMethod;
+
+ /// Contact manager responsible for creating and managing contact objects.
+ sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::ContactManager, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_contactManager;
+
+ /// Broad phase detection algorithm for quickly identifying potentially colliding pairs.
+ sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::BroadPhaseDetection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_broadPhaseDetection;
+
+ /// Narrow phase detection algorithm for precise intersection testing.
+ sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::NarrowPhaseDetection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_narrowPhaseDetection;
+
+ /// Default value for the bounding tree depth parameter.
+ static inline constexpr unsigned int s_defaultDepthValue = 6;
+};
+
+} // namespace sofa::component::collision::detection::algorithm
diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/init.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/init.cpp
index d6e879397e0..b8ae17c38bf 100644
--- a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/init.cpp
+++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/init.cpp
@@ -29,6 +29,8 @@ namespace sofa::component::collision::detection::algorithm
extern void registerBruteForceBroadPhase(sofa::core::ObjectFactory* factory);
extern void registerBruteForceDetection(sofa::core::ObjectFactory* factory);
extern void registerBVHNarrowPhase(sofa::core::ObjectFactory* factory);
+extern void registerCompositeCollisionPipeline(sofa::core::ObjectFactory* factory);
+extern void registerSubCollisionPipeline(sofa::core::ObjectFactory* factory);
extern void registerCollisionPipeline(sofa::core::ObjectFactory* factory);
extern void registerDirectSAP(sofa::core::ObjectFactory* factory);
extern void registerDirectSAPNarrowPhase(sofa::core::ObjectFactory* factory);
@@ -64,6 +66,8 @@ void registerObjects(sofa::core::ObjectFactory* factory)
registerBruteForceBroadPhase(factory);
registerBruteForceDetection(factory);
registerBVHNarrowPhase(factory);
+ registerCompositeCollisionPipeline(factory);
+ registerSubCollisionPipeline(factory);
registerCollisionPipeline(factory);
registerDirectSAP(factory);
registerDirectSAPNarrowPhase(factory);
diff --git a/Sofa/Component/Collision/Detection/Algorithm/tests/CollisionPipeline_test.cpp b/Sofa/Component/Collision/Detection/Algorithm/tests/CollisionPipeline_test.cpp
index ae151f8efd5..9bb60fc3a8d 100644
--- a/Sofa/Component/Collision/Detection/Algorithm/tests/CollisionPipeline_test.cpp
+++ b/Sofa/Component/Collision/Detection/Algorithm/tests/CollisionPipeline_test.cpp
@@ -72,12 +72,14 @@ class TestCollisionPipeline : public BaseSimulationTest {
void checkCollisionPipelineWithMissingBroadPhase();
void checkCollisionPipelineWithMissingNarrowPhase();
void checkCollisionPipelineWithMissingContactManager();
+ void checkCollisionPipelineWithMissingCollisionModel();
int checkCollisionPipelineWithMonkeyValueForDepth(int value);
void doSetUp() override
{
this->loadPlugins({
Sofa.Component.StateContainer,
+ Sofa.Component.Collision.Geometry,
Sofa.Component.Collision.Detection.Algorithm,
Sofa.Component.Collision.Detection.Intersection,
Sofa.Component.Collision.Response.Contact
@@ -104,6 +106,10 @@ void TestCollisionPipeline::checkCollisionPipelineWithNoAttributes()
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
@@ -127,6 +133,10 @@ void TestCollisionPipeline::checkCollisionPipelineWithMissingIntersection()
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
@@ -149,6 +159,10 @@ void TestCollisionPipeline::checkCollisionPipelineWithMissingBroadPhase()
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
@@ -170,8 +184,12 @@ void TestCollisionPipeline::checkCollisionPipelineWithMissingNarrowPhase()
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
-
+
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
ASSERT_NE(root.get(), nullptr) ;
root->init(sofa::core::execparams::defaultInstance()) ;
@@ -191,6 +209,34 @@ void TestCollisionPipeline::checkCollisionPipelineWithMissingContactManager()
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n" ;
+
+ root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
+ ASSERT_NE(root.get(), nullptr) ;
+ root->init(sofa::core::execparams::defaultInstance()) ;
+
+ BaseObject* clp = root->getObject("pipeline") ;
+ ASSERT_NE(clp, nullptr) ;
+
+}
+
+void TestCollisionPipeline::checkCollisionPipelineWithMissingCollisionModel()
+{
+ EXPECT_MSG_EMIT(Warning) ;
+ EXPECT_MSG_NOEMIT(Error) ;
+
+ std::stringstream scene ;
+ scene << " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
@@ -212,6 +258,10 @@ int TestCollisionPipeline::checkCollisionPipelineWithMonkeyValueForDepth(int dva
" \n"
" \n"
" \n"
+ " \n"
+ " \n"
+ " \n"
+ " \n"
" \n" ;
root = SceneLoaderXML::loadFromMemory ("testscene", scene.str().c_str());
@@ -252,6 +302,12 @@ TEST_F(TestCollisionPipeline, checkCollisionPipelineWithMissingContactManager)
this->checkCollisionPipelineWithMissingContactManager();
}
+TEST_F(TestCollisionPipeline, checkCollisionPipelineWithMissingCollisionModel)
+{
+ this->checkCollisionPipelineWithMissingCollisionModel();
+}
+
+
TEST_F(TestCollisionPipeline, checkCollisionPipelineWithMonkeyValueForDepth_OpenIssue)
{
const std::vector> testvalues = {
diff --git a/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn b/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn
new file mode 100644
index 00000000000..50fdf09fd01
--- /dev/null
+++ b/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn
@@ -0,0 +1,96 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn.view b/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn.view
new file mode 100644
index 00000000000..0f039dffcad
--- /dev/null
+++ b/examples/Component/Collision/Detection/CompositeCollisionPipeline.scn.view
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn b/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn
new file mode 100644
index 00000000000..9fd746092c6
--- /dev/null
+++ b/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn
@@ -0,0 +1,75 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn.view b/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn.view
new file mode 100644
index 00000000000..0f039dffcad
--- /dev/null
+++ b/examples/Component/Collision/Detection/CompositeCollisionPipeline_none.scn.view
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/examples/RegressionStateScenes.regression-tests b/examples/RegressionStateScenes.regression-tests
index 6270d2211ad..fc9ec569ad2 100644
--- a/examples/RegressionStateScenes.regression-tests
+++ b/examples/RegressionStateScenes.regression-tests
@@ -37,6 +37,8 @@ Demos/SofaScene.scn 120 1e-5 1 1
### Component scenes ###
Component/Collision/Response/RuleBasedContactManager.scn 100 1e-4 0 1
Component/Collision/Response/FrictionContact.scn 100 1e-4 0 1
+Component/Collision/Detection/CompositeCollisionPipeline.scn 200 1e-4 0 1
+Component/Collision/Detection/CompositeCollisionPipeline_none.scn 200 1e-4 0 1
Component/Constraint/Lagrangian/BilateralLagrangianConstraint_NNCG.scn 100 1e-4 0 1
Component/Constraint/Lagrangian/BilateralLagrangianConstraint_PGS.scn 100 1e-4 0 1
Component/Constraint/Lagrangian/BilateralLagrangianConstraint_UGS.scn 100 1e-4 0 1