diff --git a/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt b/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt index cc503bf09d6..edc3176316a 100644 --- a/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt +++ b/Sofa/Component/Collision/Detection/Algorithm/CMakeLists.txt @@ -6,6 +6,7 @@ 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}/AbstractSubCollisionPipeline.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BVHNarrowPhase.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceBroadPhase.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/BruteForceDetection.h @@ -17,8 +18,10 @@ set(HEADER_FILES ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/EndPoint.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/IncrSAP.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/MirrorIntersector.h + ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/MultiCollisionPipeline.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceDetection.h ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/RayTraceNarrowPhase.h + ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/SubCollisionPipeline.h ) set(SOURCE_FILES @@ -31,8 +34,10 @@ set(SOURCE_FILES ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DirectSAP.cpp ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/DirectSAPNarrowPhase.cpp ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/IncrSAP.cpp + ${SOFACOMPONENTCOLLISIONDETECTIONALGORITHM_SOURCE_DIR}/MultiCollisionPipeline.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/AbstractSubCollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/AbstractSubCollisionPipeline.h new file mode 100644 index 00000000000..ac9979213e9 --- /dev/null +++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/AbstractSubCollisionPipeline.h @@ -0,0 +1,123 @@ +/****************************************************************************** +* 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 + +namespace sofa::component::collision::detection::algorithm +{ + +class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API AbstractSubCollisionPipeline : public sofa::core::objectmodel::BaseObject +{ +public: + SOFA_ABSTRACT_CLASS(AbstractSubCollisionPipeline, sofa::core::objectmodel::BaseObject); + + virtual void computeCollisionReset() = 0; + virtual void computeCollisionDetection() = 0; + virtual void computeCollisionResponse() = 0; + virtual void doInit() = 0; + virtual void doBwdInit() {} + virtual void doHandleEvent(sofa::core::objectmodel::Event* e) = 0; + + virtual void doDraw(const core::visual::VisualParams*) {} + + AbstractSubCollisionPipeline() + : sofa::core::objectmodel::BaseObject() + , 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")) + { + + } + + void init() override final + { + bool validity = true; + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + + //Check given parameters + 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 (validity) + { + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); + } + + doInit(); + } + + static std::set< std::string > getResponseList() + { + 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; + } + + void draw(const core::visual::VisualParams* vparams) override final + { + const auto stateLifeCycle = vparams->drawTool()->makeStateLifeCycle(); + + doDraw(vparams); + } + + void handleEvent(sofa::core::objectmodel::Event* e) override final + { + doHandleEvent(e); + } + + sofa::MultiLink < AbstractSubCollisionPipeline, sofa::core::CollisionModel, sofa::BaseLink::FLAG_DUPLICATE > l_collisionModels; + sofa::SingleLink< AbstractSubCollisionPipeline, sofa::core::collision::Intersection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_intersectionMethod; + sofa::SingleLink< AbstractSubCollisionPipeline, sofa::core::collision::ContactManager, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_contactManager; +}; + +} // 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..00d6e6ce740 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,31 +66,44 @@ CollisionPipeline::CollisionPipeline() , d_depth(initData(&d_depth, defaultDepthValue, "depth", ("Max depth of bounding trees. (default=" + std::to_string(defaultDepthValue) + ", min=?, max=?)").c_str())) { + + m_subCollisionPipeline = sofa::core::objectmodel::New(); + m_subCollisionPipeline->d_depth.setParent(&this->d_depth); + m_subCollisionPipeline->l_broadPhaseDetection.set(this->broadPhaseDetection); + m_subCollisionPipeline->l_narrowPhaseDetection.set(this->narrowPhaseDetection); + m_multiCollisionPipeline = sofa::core::objectmodel::New(); + m_multiCollisionPipeline->l_subCollisionPipelines.add(m_subCollisionPipeline.get()); + + this->addSlave(m_subCollisionPipeline); + this->addSlave(m_multiCollisionPipeline); } -#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() << "CollisionPipeline is now a wrapper to MultiCollisionPipeline with a single SubCollisionPipeline."; + msg_info() << "If you want more flexibility, use directly the components MultiCollisionPipeline and SubCollisionPipeline, with their respective Data."; + + auto context = this->getContext(); + // set the whole collision models list to the sub collision pipeline + sofa::type::vector collisionModels; + context->get>(&collisionModels, BaseContext::SearchRoot); + + for(auto collisionModel : collisionModels) + { + m_subCollisionPipeline->l_collisionModels.add(collisionModel.get()); + } + + // set the other component to the sub collision pipeline (which is implcitely searched/set by PipelineImpl) + m_subCollisionPipeline->l_intersectionMethod.set(this->intersectionMethod); + m_subCollisionPipeline->l_broadPhaseDetection.set(this->broadPhaseDetection); + m_subCollisionPipeline->l_narrowPhaseDetection.set(this->narrowPhaseDetection); + m_subCollisionPipeline->l_contactManager.set(this->contactManager); + + m_subCollisionPipeline->init(); + m_multiCollisionPipeline->init(); + /// Insure that all the value provided by the user are valid and report message if it is not. checkDataValues() ; } @@ -107,220 +120,22 @@ 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); - } + m_multiCollisionPipeline->doCollisionReset(); } 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 - + m_multiCollisionPipeline->doCollisionDetection(collisionModels); } 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); - } + m_multiCollisionPipeline->doCollisionResponse(); } 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; + return m_multiCollisionPipeline->getResponseList(); } } // 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..f24f9a1828f 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 @@ -23,6 +23,8 @@ #include #include +#include +#include namespace sofa::component::collision::detection::algorithm { @@ -35,6 +37,7 @@ class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API CollisionPipeline : publi Data d_doPrintInfoMessage; Data d_doDebugDraw; Data d_depth; + protected: CollisionPipeline(); public: @@ -52,6 +55,9 @@ class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API CollisionPipeline : publi void doCollisionResponse() override; virtual void checkDataValues() ; + + MultiCollisionPipeline::SPtr m_multiCollisionPipeline; + SubCollisionPipeline::SPtr m_subCollisionPipeline; public: static const int defaultDepthValue; diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.cpp b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.cpp new file mode 100644 index 00000000000..ef1a0238cb4 --- /dev/null +++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.cpp @@ -0,0 +1,245 @@ +/****************************************************************************** +* 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 registerMultiCollisionPipeline(sofa::core::ObjectFactory* factory) +{ + factory->registerObjects(core::ObjectRegistrationData("Multiple collision pipelines in one.") + .add< MultiCollisionPipeline >()); +} + +MultiCollisionPipeline::MultiCollisionPipeline() + : d_parallelDetection(initData(&d_parallelDetection, false, "parallelDetection", "Parallelize collision detection.")) + , d_parallelResponse(initData(&d_parallelResponse, false, "parallelResponse", "(DISABLED) Parallelize collision response.")) + , l_subCollisionPipelines(initLink("subCollisionPipelines", "List of sub collision pipelines to handle.")) +{ +} + +void MultiCollisionPipeline::init() +{ + Inherit1::init(); + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid); + + if(l_subCollisionPipelines.size() == 0) + { + msg_warning() << "No SubCollisionPipeline defined in MultiCollisionPipeline. Nothing will be done." ; + + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + return; + } + + if(d_parallelDetection.getValue() || d_parallelResponse.getValue()) + { + m_taskScheduler = sofa::simulation::MainTaskSchedulerFactory::createInRegistry(); + assert(m_taskScheduler); + + m_taskScheduler->init(); + } + + // UX: warn if there is any CollisionModel not handled by any SubCollisionPipeline + simulation::Node* root = dynamic_cast(getContext()->getRootContext()); + std::vector sceneCollisionModels; + root->getTreeObjects(&sceneCollisionModels); + + 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->l_collisionModels) + { + pipelineCollisionModels.insert(cm); + } + } + + for (const auto& cm : sceneCollisionModels) + { + if (pipelineCollisionModels.find(cm) == pipelineCollisionModels.end()) + { + msg_warning() << "CollisionModel " << cm->getName() << " is not handled by any SubCollisionPipeline."; + } + } + +} + +void MultiCollisionPipeline::bwdInit() +{ + for(const auto& subPipeline : l_subCollisionPipelines) + { + subPipeline->doBwdInit(); + } +} + +void MultiCollisionPipeline::reset() +{ + +} + +void MultiCollisionPipeline::doCollisionReset() +{ + msg_info() << "MultiCollisionPipeline::doCollisionReset" ; + + for(const auto& subPipeline : l_subCollisionPipelines) + { + subPipeline->computeCollisionReset(); + } + + // re-order pipelines by order of distance + m_subCollisionPipelines.clear(); + for(auto* subPipeline : l_subCollisionPipelines) + { + const auto alarmDistance = subPipeline->l_intersectionMethod->getAlarmDistance(); + auto subPipelineIt = m_subCollisionPipelines.begin(); + + if(subPipelineIt == m_subCollisionPipelines.end()) + { + m_subCollisionPipelines.push_back(subPipeline); + } + else + { + while(subPipelineIt != m_subCollisionPipelines.end() && alarmDistance > (*subPipelineIt)->l_intersectionMethod->getAlarmDistance()) + { + subPipelineIt++; + } + m_subCollisionPipelines.insert(subPipelineIt, subPipeline); + } + } +} + +void MultiCollisionPipeline::doCollisionDetection(const type::vector& collisionModels) +{ + SOFA_UNUSED(collisionModels); + + SCOPED_TIMER_VARNAME(docollisiontimer, "doCollisionDetection"); + + msg_info() + << "doCollisionDetection, compute Bounding Trees" ; + + const sofa::simulation::ForEachExecutionPolicy execution = m_taskScheduler != nullptr && d_parallelDetection.getValue() ? + sofa::simulation::ForEachExecutionPolicy::PARALLEL : + sofa::simulation::ForEachExecutionPolicy::SEQUENTIAL; + + auto computeCollisionDetection = [&](const auto& range) + { + for (auto it = range.start; it != range.end; ++it) + { + (*it)->computeCollisionDetection(); + } + }; + + sofa::simulation::forEachRange(execution, *m_taskScheduler, m_subCollisionPipelines.begin(), m_subCollisionPipelines.end(), computeCollisionDetection); +} + +void MultiCollisionPipeline::doCollisionResponse() +{ + // disable parallel execution, as there is a potential race condition on Node + // It arises when while cleaning inactive contact, BaryCcontactMapper will detach the node, which clears _descendency set + // if two contact responses do the same in the same time, it will do a race condition on this particular node. +// const sofa::simulation::ForEachExecutionPolicy execution = m_taskScheduler != nullptr && d_parallelResponse.getValue() ? +// sofa::simulation::ForEachExecutionPolicy::PARALLEL : +// sofa::simulation::ForEachExecutionPolicy::SEQUENTIAL; + const sofa::simulation::ForEachExecutionPolicy execution = sofa::simulation::ForEachExecutionPolicy::SEQUENTIAL; + + auto computeCollisionResponse = [&](const auto& range) + { + for (auto it = range.start; it != range.end; ++it) + { + (*it)->computeCollisionResponse(); + } + }; + + sofa::simulation::forEachRange(execution, *m_taskScheduler, l_subCollisionPipelines.begin(), l_subCollisionPipelines.end(), computeCollisionResponse); +} + +std::set< std::string > MultiCollisionPipeline::getResponseList() const +{ + return AbstractSubCollisionPipeline::getResponseList(); +} + +void MultiCollisionPipeline::computeCollisionReset() +{ + if(!this->isComponentStateValid()) + return; + + doCollisionReset(); +} + +void MultiCollisionPipeline::computeCollisionDetection() +{ + if(!this->isComponentStateValid()) + return; + + //useless + std::vector collisionModels; + + doCollisionDetection(collisionModels); +} + +void MultiCollisionPipeline::computeCollisionResponse() +{ + if(!this->isComponentStateValid()) + return; + + doCollisionResponse(); +} + + +void MultiCollisionPipeline::draw(const core::visual::VisualParams* vparams) +{ + for (const auto& subPipeline : m_subCollisionPipelines) + { + subPipeline->draw(vparams); + } +} + +} // namespace sofa::component::collision::detection::algorithm diff --git a/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.h b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.h new file mode 100644 index 00000000000..dbe49a1a8f0 --- /dev/null +++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/MultiCollisionPipeline.h @@ -0,0 +1,84 @@ +/****************************************************************************** +* SOFA, Simulation Open-Framework Architecture * +* (c) 2006 INRIA, USTL, UJF, CNRS, MGH * +* * +* This program is free software; you can redistribute it and/or modify it * +* under the terms of the GNU Lesser General Public License as published by * +* the Free Software Foundation; either version 2.1 of the License, or (at * +* your option) any later version. * +* * +* This program is distributed in the hope that it will be useful, but WITHOUT * +* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * +* FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License * +* for more details. * +* * +* You should have received a copy of the GNU Lesser General Public License * +* along with this program. If not, see . * +******************************************************************************* +* Authors: The SOFA Team and external contributors (see Authors.txt) * +* * +* Contact information: contact@sofa-framework.org * +******************************************************************************/ +#pragma once +#include + +#include + +namespace sofa::simulation +{ +class TaskScheduler; +} + +namespace sofa::component::collision::detection::algorithm +{ + +class AbstractSubCollisionPipeline; + +class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API MultiCollisionPipeline : public sofa::core::collision::Pipeline +{ +public: + SOFA_CLASS(MultiCollisionPipeline, sofa::core::collision::Pipeline); + + sofa::Data d_depth; +protected: + MultiCollisionPipeline(); +public: + void init() override; + void bwdInit() 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; + + void reset() override; + + void draw(const core::visual::VisualParams* vparams) override; + + /// Remove collision response from last step + virtual void computeCollisionReset() override final; + /// Detect new collisions. Note that this step must not modify the simulation graph + virtual void computeCollisionDetection() override final; + /// Add collision response in the simulation graph + virtual void computeCollisionResponse() override final; + + sofa::simulation::TaskScheduler* m_taskScheduler{nullptr}; + + std::vector m_subCollisionPipelines; + +public: + sofa::Data d_parallelDetection; + sofa::Data d_parallelResponse; + sofa::MultiLink < MultiCollisionPipeline, AbstractSubCollisionPipeline, 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..1cdbb8cff5f --- /dev/null +++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.cpp @@ -0,0 +1,244 @@ +/****************************************************************************** +* 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 MultiCollisionPipeline.") + .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_broadPhaseDetection(initLink("broadPhaseDetection", "Broad phase detection to use in this pipeline")) + , l_narrowPhaseDetection(initLink("narrowPhaseDetection", "Narrow phase detection to use in this pipeline")) +{ +} + +void SubCollisionPipeline::doInit() +{ + bool validity = true; + + 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; + } + + if (!validity) + { + this->d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid); + } + +} + +void SubCollisionPipeline::computeCollisionReset() +{ + if (!this->isComponentStateValid()) + return; + + msg_info() << "SubCollisionPipeline::doCollisionReset"; + + l_broadPhaseDetection->setIntersectionMethod(l_intersectionMethod.get()); + l_narrowPhaseDetection->setIntersectionMethod(l_intersectionMethod.get()); + l_contactManager->setIntersectionMethod(l_intersectionMethod.get()); + + // clear all contacts + const type::vector& contacts = l_contactManager->getContacts(); + for (const auto& contact : contacts) + { + if (contact != nullptr) + { + contact->removeResponse(); + } + } +} + +void SubCollisionPipeline::computeCollisionDetection() +{ + SCOPED_TIMER_VARNAME(docollisiontimer, "doCollisionDetection"); + + if (!this->isComponentStateValid()) + return; + + msg_info() + << "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"); + + const bool continuous = l_intersectionMethod->useContinuous(); + const auto continuousIntersectionType = l_intersectionMethod->continuousIntersectionType(); + const SReal dt = getContext()->getDt(); + + int nActive = 0; + + const int used_depth = ( + (l_broadPhaseDetection->needsDeepBoundingTree()) || + (l_narrowPhaseDetection->needsDeepBoundingTree()) + ) ? d_depth.getValue() : 0; + + for (auto it = l_collisionModels.begin(); it != l_collisionModels.end(); ++it) + { + msg_info() + << "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; + } + + + msg_info() + << "doCollisionDetection, Computed "<getName(); + + { + SCOPED_TIMER_VARNAME(broadphase, "BroadPhase"); + l_intersectionMethod->beginBroadPhase(); + l_broadPhaseDetection->beginBroadPhase(); + l_broadPhaseDetection->addCollisionModels(vectBoundingVolume); // detection is done there + l_broadPhaseDetection->endBroadPhase(); + l_intersectionMethod->endBroadPhase(); + } + + msg_info() + << "doCollisionDetection, NarrowPhaseDetection "<< l_narrowPhaseDetection->getName(); + + { + SCOPED_TIMER_VARNAME(narrowphase, "NarrowPhase"); + l_intersectionMethod->beginNarrowPhase(); + l_narrowPhaseDetection->beginNarrowPhase(); + const type::vector >& vectCMPair = l_broadPhaseDetection->getCollisionModelPairs(); + + msg_info() + << "doCollisionDetection, "<< vectCMPair.size()<<" colliding model pairs" ; + + l_narrowPhaseDetection->addCollisionPairs(vectCMPair); + l_narrowPhaseDetection->endNarrowPhase(); + l_intersectionMethod->endNarrowPhase(); + } + +} + +void SubCollisionPipeline::computeCollisionResponse() +{ + if (!this->isComponentStateValid()) + return; + + core::objectmodel::BaseContext* scene = getContext(); + + msg_info() + << "Create Contacts " << l_contactManager->getName() ; + + { + SCOPED_TIMER_VARNAME(createContactsTimer, "CreateContacts"); + l_contactManager->createContacts(l_narrowPhaseDetection->getDetectionOutputs()); + } + + // finally we start the creation of collisionGroup + + const type::vector& contacts = l_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); + } + } + } + + SCOPED_TIMER_VARNAME(createResponseTimer, "CreateMovingObjectsResponse"); + + msg_info() + << "Linking all contacts to Scene" ; + + for (const auto& contact : notStaticContacts) + { + contact->createResponse(scene); + } +} + +} // 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..d4f27dd86a9 --- /dev/null +++ b/Sofa/Component/Collision/Detection/Algorithm/src/sofa/component/collision/detection/algorithm/SubCollisionPipeline.h @@ -0,0 +1,60 @@ +/****************************************************************************** +* 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 + +namespace sofa::component::collision::detection::algorithm +{ + +class SOFA_COMPONENT_COLLISION_DETECTION_ALGORITHM_API SubCollisionPipeline : public AbstractSubCollisionPipeline +{ +public: + using Inherited = AbstractSubCollisionPipeline; + SOFA_CLASS(SubCollisionPipeline, AbstractSubCollisionPipeline); +protected: + SubCollisionPipeline(); +public: + virtual ~SubCollisionPipeline() override = default; + void doInit() override; + void doHandleEvent(sofa::core::objectmodel::Event*) override {} + + void computeCollisionReset() override; + void computeCollisionDetection() override; + void computeCollisionResponse() override; + + sofa::Data d_depth; + sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::BroadPhaseDetection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_broadPhaseDetection; + sofa::SingleLink< SubCollisionPipeline, sofa::core::collision::NarrowPhaseDetection, sofa::BaseLink::FLAG_STOREPATH | sofa::BaseLink::FLAG_STRONGLINK > l_narrowPhaseDetection; + + 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..2541b470943 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 registerMultiCollisionPipeline(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); + registerMultiCollisionPipeline(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 3078fc26c56..f9dc70e5871 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/applications/projects/SceneChecking/CMakeLists.txt b/applications/projects/SceneChecking/CMakeLists.txt index e0832b0ad65..f4ce6b70cab 100644 --- a/applications/projects/SceneChecking/CMakeLists.txt +++ b/applications/projects/SceneChecking/CMakeLists.txt @@ -12,6 +12,7 @@ set(HEADER_FILES ${SCENECHECK_SRC_DIR}/config.h.in ${SCENECHECK_SRC_DIR}/init.h ${SCENECHECK_SRC_DIR}/SceneCheckAPIChange.h + ${SCENECHECK_SRC_DIR}/SceneCheckCollisionPipelineAndModels.h ${SCENECHECK_SRC_DIR}/SceneCheckCollisionResponse.h ${SCENECHECK_SRC_DIR}/SceneCheckDeprecatedComponents.h ${SCENECHECK_SRC_DIR}/SceneCheckDuplicatedName.h @@ -26,6 +27,7 @@ set(HEADER_FILES set(SOURCE_FILES ${SCENECHECK_SRC_DIR}/init.cpp ${SCENECHECK_SRC_DIR}/SceneCheckAPIChange.cpp + ${SCENECHECK_SRC_DIR}/SceneCheckCollisionPipelineAndModels.cpp ${SCENECHECK_SRC_DIR}/SceneCheckCollisionResponse.cpp ${SCENECHECK_SRC_DIR}/SceneCheckDeprecatedComponents.cpp ${SCENECHECK_SRC_DIR}/SceneCheckDuplicatedName.cpp diff --git a/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.cpp b/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.cpp new file mode 100644 index 00000000000..80019e7099b --- /dev/null +++ b/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.cpp @@ -0,0 +1,104 @@ +/****************************************************************************** +* 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 "SceneCheckCollisionPipelineAndModels.h" + +#include +#include +#include +#include + +namespace sofa::_scenechecking_ +{ + +const bool SceneCheckCollisionPipelineAndModelsRegistered = sofa::simulation::SceneCheckMainRegistry::addToRegistry(SceneCheckCollisionPipelineAndModels::newSPtr()); + +using sofa::simulation::Node; + +const std::string SceneCheckCollisionPipelineAndModels::getName() +{ + return "SceneCheckCollisionPipelineAndModels"; +} + +const std::string SceneCheckCollisionPipelineAndModels::getDesc() +{ + return "Ensure the consistency of the existence of a collision pipeline, and collision models in the scene."; +} + +void SceneCheckCollisionPipelineAndModels::doInit(Node* node) +{ + SOFA_UNUSED(node); +} + +void SceneCheckCollisionPipelineAndModels::doCheckOn(Node* node) +{ + const sofa::core::objectmodel::BaseContext* root = node->getContext()->getRootContext(); + + if(!root) + { + return; + } + + sofa::core::collision::Pipeline::SPtr anyPipeline{}; + root->get(anyPipeline, sofa::core::objectmodel::BaseContext::SearchDirection::SearchDown); + sofa::core::CollisionModel::SPtr anyCollisionModel{}; + root->get(anyCollisionModel, sofa::core::objectmodel::BaseContext::SearchDirection::SearchDown); + + if(anyPipeline) + { + if(anyCollisionModel) + { + // there is a collision pipeline and (at least one) collision model(s), carry on. + } + else + { + // there is a collision pipeline but no collision model. + // Either the collision pipeline is superfluous; + // or the collision model(s) has been forgotten. + m_message = "There is no collision model in this scene, but there is a collision pipeline. Either add one collision model or remove the collision pipeline."; + } + } + else + { + if(anyCollisionModel) + { + // At least one collision model has been detected but without any pipeline. + // Either the collision pipeline has been forgotten; + // or the collision model(s) is useless. + m_message = "At least one collision model has been found, but there is no collision pipeline. You may add a collision pipeline (or remove the collision model if it is not used)."; + } + else + { + // there is no collision pipeline and no collision model, the scene certainly does not involve any collision feature. + } + } +} + +void SceneCheckCollisionPipelineAndModels::doPrintSummary() +{ + if(!m_message.empty()) + { + msg_warning(this->getName()) << m_message; + } +} + + +} // namespace sofa::_scenechecking_ diff --git a/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.h b/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.h new file mode 100644 index 00000000000..0ce57d79d77 --- /dev/null +++ b/applications/projects/SceneChecking/src/SceneChecking/SceneCheckCollisionPipelineAndModels.h @@ -0,0 +1,54 @@ +/****************************************************************************** +* 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::_scenechecking_ +{ + +class SOFA_SCENECHECKING_API SceneCheckCollisionPipelineAndModels : public sofa::simulation::SceneCheck +{ +public: + virtual ~SceneCheckCollisionPipelineAndModels() {} + typedef std::shared_ptr SPtr; + static SPtr newSPtr() { return SPtr(new SceneCheckCollisionPipelineAndModels()); } + virtual const std::string getName() override; + virtual const std::string getDesc() override; + void doInit(sofa::simulation::Node* node) override; + void doCheckOn(sofa::simulation::Node* node) override; + void doPrintSummary() override; + +private: + std::string m_message; +}; + +} // namespace sofa::_scenechecking_ + +namespace sofa::scenechecking +{ + using _scenechecking_::SceneCheckCollisionPipelineAndModels; +} diff --git a/applications/projects/SceneChecking/tests/SceneChecker_test.cpp b/applications/projects/SceneChecking/tests/SceneChecker_test.cpp index 2ffe0b510c4..96c15f66513 100644 --- a/applications/projects/SceneChecking/tests/SceneChecker_test.cpp +++ b/applications/projects/SceneChecking/tests/SceneChecker_test.cpp @@ -38,6 +38,8 @@ using sofa::scenechecking::SceneCheckMissingRequiredPlugin; using sofa::scenechecking::SceneCheckDuplicatedName; #include using sofa::scenechecking::SceneCheckUsingAlias; +#include +using sofa::scenechecking::SceneCheckCollisionPipelineAndModels; #include using sofa::helper::system::PluginManager; @@ -238,6 +240,74 @@ struct SceneChecker_test : public BaseSimulationTest checker.validate(root.get(), &sceneLoader); } } + + void checkCollisionPipelineModels(bool withPipeline, bool withCollisionModel) + { + std::string scenePrefix = R"(" + + + + + + +)"; + std::string scenePipeline = R"(" + + + + + +)"; + std::string sceneModel = R"(" + + + + +)"; + std::string sceneSuffix = R"(" + +)"; + std::string scene = scenePrefix; + if(withPipeline) + { + scene += scenePipeline; + } + if(withCollisionModel) + { + scene += sceneModel; + } + scene += sceneSuffix; + + SceneCheckerVisitor checker(sofa::core::execparams::defaultInstance()); + checker.addCheck( SceneCheckCollisionPipelineAndModels::newSPtr() ); + + SceneLoaderXML sceneLoader; + const Node::SPtr root = sceneLoader.doLoadFromMemory("testscene", scene.c_str()); + ASSERT_NE(root.get(), nullptr); + root->init(sofa::core::execparams::defaultInstance()); + + if(!withPipeline && !withCollisionModel) + { + EXPECT_MSG_NOEMIT(Warning); + checker.validate(root.get(), &sceneLoader); + } + if(withPipeline && !withCollisionModel) + { + EXPECT_MSG_EMIT(Warning); + checker.validate(root.get(), &sceneLoader); + } + if(!withPipeline && withCollisionModel) + { + EXPECT_MSG_EMIT(Warning); + checker.validate(root.get(), &sceneLoader); + } + if(withPipeline && withCollisionModel) + { + EXPECT_MSG_NOEMIT(Warning); + checker.validate(root.get(), &sceneLoader); + } + + } }; TEST_F(SceneChecker_test, checkMissingRequiredPlugin ) @@ -279,3 +349,20 @@ TEST_F(SceneChecker_test, checkUsingAlias_withoutAlias ) { checkUsingAlias(false); } + +TEST_F(SceneChecker_test, checkCollisionPipelineModels_nothing ) +{ + checkCollisionPipelineModels(false, false); +} +TEST_F(SceneChecker_test, checkCollisionPipelineModels_onlyPipeline ) +{ + checkCollisionPipelineModels(true, false); +} +TEST_F(SceneChecker_test, checkCollisionPipelineModels_onlyModel ) +{ + checkCollisionPipelineModels(false, true); +} +TEST_F(SceneChecker_test, checkCollisionPipelineModels_both ) +{ + checkCollisionPipelineModels(true, true); +}