diff --git a/scenes/MeshRepulsion.scn b/scenes/MeshRepulsion.scn
index 8e1bd4a..32ca6bf 100644
--- a/scenes/MeshRepulsion.scn
+++ b/scenes/MeshRepulsion.scn
@@ -56,8 +56,11 @@
-
+
diff --git a/src/CGALPlugin/MeshRepulsionEngine.cpp b/src/CGALPlugin/MeshRepulsionEngine.cpp
index 9847a5f..00d8f08 100644
--- a/src/CGALPlugin/MeshRepulsionEngine.cpp
+++ b/src/CGALPlugin/MeshRepulsionEngine.cpp
@@ -1,168 +1,424 @@
/*****************************************************************************
- * - Copyright (C) 2020-Present InfinyTech3D - *
- * *
- * This file is part of the InfinyToolkit plugin for the SOFA framework. *
- * *
- * This file is dual-licensed: *
- * *
- * 1) Commercial License: *
- * This file may be used under the terms of a valid commercial license *
- * agreement provided wih the software by InfinyTech3D. *
- * *
- * 2) GNU General Public License (GPLv3) Usage *
- * Alternatively, this file may be used under the terms of the *
- * GNU General Public License version 3 as published by the *
- * Free Software Foundation: https://www.gnu.org/licenses/gpl-3.0.html *
- * *
- * Contact: contact@infinytech3d.com *
- * Further information: https://infinytech3d.com *
- ****************************************************************************/
+* - Copyright (C) 2020-Present InfinyTech3D - *
+* *
+* This file is part of the InfinyToolkit plugin for the SOFA framework. *
+* *
+* This file is dual-licensed: *
+* *
+* 1) Commercial License: *
+* This file may be used under the terms of a valid commercial license *
+* agreement provided wih the software by InfinyTech3D. *
+* *
+* 2) GNU General Public License (GPLv3) Usage *
+* Alternatively, this file may be used under the terms of the *
+* GNU General Public License version 3 as published by the *
+* Free Software Foundation: https://www.gnu.org/licenses/gpl-3.0.html *
+* *
+* Contact: contact@infinytech3d.com *
+* Further information: https://infinytech3d.com *
+****************************************************************************/
#define SOFA_COMPONENT_NEARESTTEXCOORDS_CPP
#include
+
#include
#include
#include
#include
-#include
-#include
+#include
+
+#include
+#include
+#include
+#include
+#include
namespace cgal
{
-using namespace sofa;
-using namespace sofa::defaulttype;
-using namespace sofa::helper;
+ using namespace sofa;
+ using namespace sofa::defaulttype;
+ using namespace sofa::helper;
-void registerMeshRepulsionEngine(sofa::core::ObjectFactory* factory)
-{
- factory->registerObjects(sofa::core::ObjectRegistrationData("MeshRepulsionEngine engine description to complete")
- .add< MeshRepulsionEngine >());
-}
-
-
-MeshRepulsionEngine::MeshRepulsionEngine()
- : l_masterState(initLink("masterState", "link to the mechanical state of the tool, if not set, manager will search for a MechanicalState with tag: AngioToolState."))
- , l_slaveState(initLink("slaveState", "link to the mechanical state of the balloon, if not set, manager will search for a MechanicalState with tag: BalloonState."))
- , l_masterTopology(initLink("masterTopology", "link to the mechanical state of the balloon, if not set, manager will search for a MechanicalState with tag: BalloonState."))
- , l_slaveTopology(initLink("slaveTopology", "link to the mechanical state of the balloon, if not set, manager will search for a MechanicalState with tag: BalloonState."))
-
- //: d_inputPositions(initData(&d_inputPositions, "inputPositions", "Indices of the points on the first model"))
- //, d_mapPositions(initData(&d_mapPositions, "mapPositions", "Indices of the points on the second model"))
- //, d_mapTexCoords(initData(&d_mapTexCoords, "mapTexCoords", "Indices of the points on the second model"))
- //, d_radius(initData(&d_radius, (SReal)1, "radius", "Radius to search corresponding fixed point"))
- //, d_useInterpolation(initData(&d_useInterpolation, false, "useInterpolation", "Radius to search corresponding fixed point"))
- //, d_drawInterpolation(initData(&d_drawInterpolation, false, "drawInterpolation", "Radius to search corresponding fixed point"))
- //, d_outputTexCoords(initData(&d_outputTexCoords, "outputTexCoords", "Radius to search corresponding fixed point"))
-{
- f_listening.setValue(true);
-}
+ void registerMeshRepulsionEngine(sofa::core::ObjectFactory* factory)
+ {
+ factory->registerObjects(sofa::core::ObjectRegistrationData(
+ "MeshRepulsionEngine: SOFA/CGAL bridge for progressive slave/master repulsion")
+ .add());
+ }
-MeshRepulsionEngine::~MeshRepulsionEngine()
-{
-}
+ MeshRepulsionEngine::MeshRepulsionEngine()
+ : l_masterState(initLink("masterState", "link to the master MechanicalState"))
+ , l_masterTopology(initLink("masterTopology", "link to the master BaseMeshTopology"))
+ , l_slaveState(initLink("slaveState", "link to the slave MechanicalState"))
+ , l_slaveTopology(initLink("slaveTopology", "link to the slave BaseMeshTopology"))
+ , d_enableCgalDetection(initData(&d_enableCgalDetection, true, "enableCgalDetection", "Enable CGAL inside/outside detection"))
+ , d_rebuildMasterEveryStep(initData(&d_rebuildMasterEveryStep, false, "rebuildMasterEveryStep", "Rebuild the master CGAL mesh at every animation step"))
+ , d_logEveryNSteps(initData(&d_logEveryNSteps, static_cast(1), "logEveryNSteps", "Print diagnostics every N animation steps"))
+ , d_runDetectionAtInit(initData(&d_runDetectionAtInit, true, "runDetectionAtInit", "Run one CGAL detection during init()"))
+ , d_logAsWarning(initData(&d_logAsWarning, true, "logAsWarning", "Print diagnostics with msg_warning instead of msg_info"))
+ , d_writeCsv(initData(&d_writeCsv, true, "writeCsv", "Write diagnostics to a CSV file"))
+ , d_csvFilePath(initData(&d_csvFilePath, std::string("MeshRepulsion_diagnostics.csv"), "csvFilePath", "CSV file path for diagnostics"))
+ , d_lastStep(initData(&d_lastStep, static_cast(0), "lastStep", "Last diagnostic step"))
+ , d_lastMasterVertexCount(initData(&d_lastMasterVertexCount, static_cast(0), "lastMasterVertexCount", "Last master vertex count"))
+ , d_lastMasterTriangleCount(initData(&d_lastMasterTriangleCount, static_cast(0), "lastMasterTriangleCount", "Last master triangle count"))
+ , d_lastSlaveVertexCount(initData(&d_lastSlaveVertexCount, static_cast(0), "lastSlaveVertexCount", "Last slave vertex count"))
+ , d_lastSlaveTriangleCount(initData(&d_lastSlaveTriangleCount, static_cast(0), "lastSlaveTriangleCount", "Last slave triangle count"))
+ , d_lastInsideCount(initData(&d_lastInsideCount, static_cast(0), "lastInsideCount", "Last slave vertices inside master"))
+ , d_lastBoundaryCount(initData(&d_lastBoundaryCount, static_cast(0), "lastBoundaryCount", "Last slave vertices on master boundary"))
+ , d_lastOutsideCount(initData(&d_lastOutsideCount, static_cast(0), "lastOutsideCount", "Last slave vertices outside master"))
+ , d_lastSlaveCgalFaceCount(initData(&d_lastSlaveCgalFaceCount, static_cast(0), "lastSlaveCgalFaceCount", "Last CGAL slave face count"))
+ {
+ f_listening.setValue(true);
+ std::cout << "LOADED MeshRepulsionEngine loader-position-fix" << std::endl;
+ }
-void MeshRepulsionEngine::init()
-{
+ MeshRepulsionEngine::~MeshRepulsionEngine() = default;
- // need to add check here if link to the mechanicalObject are correct
- // if not put : sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
- sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid);
-}
+ void MeshRepulsionEngine::init()
+ {
+ // need to add check here if link to the mechanicalObject are correct
+ // if not put : sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ if (!l_masterState.get() || !l_slaveState.get() || !l_masterTopology.get() || !l_slaveTopology.get())
+ {
+ msg_error() << "MeshRepulsionEngine invalid links. Expected masterState, slaveState, masterTopology and slaveTopology.";
+ sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Invalid);
+ return;
+ }
+ sofa::core::objectmodel::BaseObject::d_componentState.setValue(sofa::core::objectmodel::ComponentState::Valid);
-void MeshRepulsionEngine::doUpdate()
-{
- std::cout << "call MeshRepulsionEngine::doUpdate()" << std::endl;
+ std::cout << "[CGAL detection] init(): links are valid. Detection will run in bwdInit(), after loaders/mechanical objects are initialized. runDetectionAtInit="
+ << (d_runDetectionAtInit.getValue() ? "true" : "false") << std::endl;
+ }
+ void MeshRepulsionEngine::bwdInit()
+ {
+ if (d_componentState.getValue() != sofa::core::objectmodel::ComponentState::Valid)
+ {
+ return;
+ }
-}
+ std::cout << "[CGAL detection] bwdInit(): post-init diagnostic point reached." << std::endl;
+ if (d_runDetectionAtInit.getValue())
+ {
+ updateIntersectedPositions();
+ }
+ }
-void MeshRepulsionEngine::updateIntersectedPositions()
-{
- std::cout << "call MeshRepulsionEngine::updateIntersectedPositions()" << std::endl;
+ void MeshRepulsionEngine::doUpdate()
+ {
+ // The engine is event-driven for now. The real work is performed in handleEvent().
+ std::cout << "call MeshRepulsionEngine::doUpdate()" << std::endl;
- // get access to the meshes:
- sofa::helper::ReadAccessor masterVertices = l_masterState.get()->read(sofa::core::vec_id::read_access::restPosition)->getValue();
- sofa::helper::WriteAccessor > slaveVertices = *l_slaveState.get()->write(sofa::core::vec_id::write_access::position);
+ }
+
+ // HELPER METHOD TO GET A CGAL POINT FROM A SOFA COORD
+ MeshRepulsionEngine::CgalPoint MeshRepulsionEngine::toCgalPoint(const Coord& p)
+ {
+ return CgalPoint(static_cast(p[0]), static_cast(p[1]), static_cast(p[2]));
+ }
+
+ bool MeshRepulsionEngine::buildSurfaceMeshFromSofa(
+ const VecCoord& positions,
+ sofa::core::topology::BaseMeshTopology* topology,
+ CgalSurfaceMesh& outputMesh,
+ std::string& errorMessage) const
+ {
+ outputMesh.clear();
+ errorMessage.clear();
- std::cout << "number of vertices for the master mesh: " << masterVertices.size() << std::endl;
- std::cout << "number of vertices for the slave mesh: " << slaveVertices.size() << std::endl;
+ if (!topology)
+ {
+ errorMessage = "null topology";
+ return false;
+ }
+
+ if (positions.empty())
+ {
+ errorMessage = "empty position vector";
+ return false;
+ }
- sofa::core::topology::BaseMeshTopology* _masterTopology = l_masterTopology.get();
- sofa::core::topology::BaseMeshTopology* _slaveTopology = l_slaveTopology.get();
+ const auto nbTriangles = topology->getNbTriangles();
+ if (nbTriangles == 0)
+ {
+ errorMessage = "topology has no triangles; needs a surface topology for inside/outside tests";
+ return false;
+ }
- std::cout << "number of triangles for the master mesh: " << _masterTopology->getNbTriangles() << std::endl;
- std::cout << "number of triangles for the slave mesh: " << _slaveTopology->getNbTriangles() << std::endl;
+ std::vector vertexMap;
+ vertexMap.reserve(positions.size());
+ for (const Coord& p : positions)
+ {
+ vertexMap.emplace_back(outputMesh.add_vertex(toCgalPoint(p)));
+ }
+ std::size_t invalidTriangles = 0;
+ std::size_t rejectedTriangles = 0;
- // CGAL tambouille here and push vertices in slaveVertices which is in write mode
-}
+ for (std::size_t i = 0; i < static_cast(nbTriangles); ++i)
+ {
+ const auto tri = topology->getTriangle(i);
+ const auto a = static_cast(tri[0]);
+ const auto b = static_cast(tri[1]);
+ const auto c = static_cast(tri[2]);
+
+ if (a >= positions.size() || b >= positions.size() || c >= positions.size() || a == b || b == c || a == c)
+ {
+ ++invalidTriangles;
+ continue;
+ }
+
+ auto f = outputMesh.add_face(vertexMap[a], vertexMap[b], vertexMap[c]);
+ if (f == CgalSurfaceMesh::null_face())
+ {
+ f = outputMesh.add_face(vertexMap[a], vertexMap[c], vertexMap[b]);
+ }
+
+ if (f == CgalSurfaceMesh::null_face())
+ {
+ ++rejectedTriangles;
+ }
+ }
+ if (outputMesh.number_of_faces() == 0)
+ {
+ std::ostringstream oss;
+ oss << "no valid CGAL face created from " << nbTriangles
+ << " SOFA triangles; invalid=" << invalidTriangles
+ << ", rejected=" << rejectedTriangles;
+ errorMessage = oss.str();
+ return false;
+ }
-void MeshRepulsionEngine::handleEvent(sofa::core::objectmodel::Event* event)
-{
- if (d_componentState.getValue() != sofa::core::objectmodel::ComponentState::Valid)
- return;
+ if (invalidTriangles > 0 || rejectedTriangles > 0)
+ {
+ std::ostringstream oss;
+ oss << "surface mesh built with warnings: invalid=" << invalidTriangles
+ << ", rejected=" << rejectedTriangles;
+ errorMessage = oss.str();
+ }
- if (sofa::core::objectmodel::KeypressedEvent::checkEventType(event))
+ return true;
+ }
+
+ bool MeshRepulsionEngine::rebuildMasterCgalMesh(const VecCoord& masterVertices)
{
- sofa::core::objectmodel::KeypressedEvent* ev = dynamic_cast(event);
- if (ev->getKey() == 'D')
+ m_masterCgalMesh = std::make_unique();
+
+ std::string errorMessage;
+ if (!buildSurfaceMeshFromSofa(masterVertices, l_masterTopology.get(), *m_masterCgalMesh, errorMessage))
+ {
+ msg_error() << "Could not build master CGAL mesh: " << errorMessage;
+ m_masterCgalMesh.reset();
+ return false;
+ }
+
+ if (!errorMessage.empty())
{
- // do something if ctrl + D is pressed
+ msg_warning() << "Master CGAL mesh warning: " << errorMessage;
}
+
+ msg_info() << "Master CGAL mesh built: vertices=" << m_masterCgalMesh->number_of_vertices()
+ << " faces=" << m_masterCgalMesh->number_of_faces();
+ return true;
}
- else if (sofa::simulation::AnimateEndEvent::checkEventType(event))
+
+ void MeshRepulsionEngine::updateIntersectedPositions()
{
- // will be called at the end of each animation step. The idea is to move point here and at next step SOFA will move the rest of the mesh using the solver
- updateIntersectedPositions();
+ ++m_stepCounter;
+ std::cout << "[CGAL detection] updateIntersectedPositions() called, step=" << m_stepCounter << std::endl;
+
+ if (!d_enableCgalDetection.getValue())
+ {
+ return;
+ }
+
+ if (!l_masterState.get() || !l_slaveState.get() || !l_masterTopology.get() || !l_slaveTopology.get())
+ {
+ msg_error() << "MeshRepulsionEngine links became invalid during simulation.";
+ return;
+ }
+
+ sofa::helper::ReadAccessor masterVertices = l_masterState.get()->read(sofa::core::vec_id::read_access::position)->getValue();
+ sofa::helper::ReadAccessor slaveVertices = l_slaveState.get()->read(sofa::core::vec_id::read_access::position)->getValue();
+
+ auto* masterTopology = l_masterTopology.get();
+ auto* slaveTopology = l_slaveTopology.get();
+
+ if (!m_masterCgalMesh || d_rebuildMasterEveryStep.getValue())
+ {
+ if (!rebuildMasterCgalMesh(masterVertices))
+ {
+ return;
+ }
+ }
+
+ CgalSurfaceMesh slaveCgalMesh;
+ std::string slaveBuildWarning;
+ const bool slaveMeshOk = buildSurfaceMeshFromSofa(slaveVertices, slaveTopology, slaveCgalMesh, slaveBuildWarning);
+ if (!slaveMeshOk)
+ {
+ msg_warning() << "Could not build slave CGAL surface mesh: " << slaveBuildWarning;
+ }
+
+ CGAL::Side_of_triangle_mesh insideTester(*m_masterCgalMesh);
+
+ std::size_t insideCount = 0;
+ std::size_t boundaryCount = 0;
+ std::size_t outsideCount = 0;
+
+ for (const Coord& p : slaveVertices)
+ {
+ const auto side = insideTester(toCgalPoint(p));
+ if (side == CGAL::ON_BOUNDED_SIDE)
+ {
+ ++insideCount;
+ }
+ else if (side == CGAL::ON_BOUNDARY)
+ {
+ ++boundaryCount;
+ }
+ else
+ {
+ ++outsideCount;
+ }
+ }
+
+ const auto masterTriangleCount = static_cast(masterTopology->getNbTriangles());
+ const auto slaveTriangleCount = static_cast(slaveTopology->getNbTriangles());
+ const auto slaveCgalFaceCount = static_cast(slaveMeshOk ? slaveCgalMesh.number_of_faces() : 0);
+
+ d_lastStep.setValue(static_cast(m_stepCounter));
+ d_lastMasterVertexCount.setValue(static_cast(masterVertices.size()));
+ d_lastMasterTriangleCount.setValue(masterTriangleCount);
+ d_lastSlaveVertexCount.setValue(static_cast(slaveVertices.size()));
+ d_lastSlaveTriangleCount.setValue(slaveTriangleCount);
+ d_lastInsideCount.setValue(static_cast(insideCount));
+ d_lastBoundaryCount.setValue(static_cast(boundaryCount));
+ d_lastOutsideCount.setValue(static_cast(outsideCount));
+ d_lastSlaveCgalFaceCount.setValue(slaveCgalFaceCount);
+
+ const unsigned int logEvery = std::max(1u, d_logEveryNSteps.getValue());
+ if ((m_stepCounter % logEvery) == 0)
+ {
+ std::ostringstream oss;
+ oss << "[CGAL detection] step=" << m_stepCounter
+ << " masterVertices=" << masterVertices.size()
+ << " masterTriangles=" << masterTriangleCount
+ << " slaveVertices=" << slaveVertices.size()
+ << " slaveTriangles=" << slaveTriangleCount
+ << " inside=" << insideCount
+ << " boundary=" << boundaryCount
+ << " outside=" << outsideCount
+ << " slaveCgalFaces=" << slaveCgalFaceCount;
+
+ std::cout << oss.str() << std::endl;
+ if (d_logAsWarning.getValue())
+ {
+ msg_warning() << oss.str();
+ }
+ else
+ {
+ msg_info() << oss.str();
+ }
+
+ if (!slaveBuildWarning.empty())
+ {
+ msg_warning() << "Slave CGAL mesh warning: " << slaveBuildWarning;
+ }
+ }
+
+ if (d_writeCsv.getValue())
+ {
+ const std::string path = d_csvFilePath.getValue();
+ const bool writeHeader = (m_stepCounter == 1);
+ std::ofstream csv(path, std::ios::app);
+ if (csv)
+ {
+ if (writeHeader)
+ {
+ csv << "step,masterVertices,masterTriangles,slaveVertices,slaveTriangles,inside,boundary,outside,slaveCgalFaces\n";
+ }
+ csv << m_stepCounter << ','
+ << masterVertices.size() << ','
+ << masterTriangleCount << ','
+ << slaveVertices.size() << ','
+ << slaveTriangleCount << ','
+ << insideCount << ','
+ << boundaryCount << ','
+ << outsideCount << ','
+ << slaveCgalFaceCount << "\n";
+ }
+ else
+ {
+ msg_warning() << "Could not open diagnostic CSV: " << path;
+ }
+ }
}
-}
+ void MeshRepulsionEngine::handleEvent(sofa::core::objectmodel::Event* event)
+ {
+ if (d_componentState.getValue() != sofa::core::objectmodel::ComponentState::Valid)
+ {
+ return;
+ }
+ if (sofa::core::objectmodel::KeypressedEvent::checkEventType(event))
+ {
+ auto* ev = dynamic_cast(event);
+ if (ev)
+ {
+ std::cout << " KeypressedEvent received: " << ev->getKey() << std::endl;
+ }
+ }
+ else if (sofa::simulation::AnimateEndEvent::checkEventType(event))
+ {
+ std::cout << " AnimateEndEvent received" << std::endl;
+ updateIntersectedPositions();
+ }
+ }
+ void MeshRepulsionEngine::draw(const core::visual::VisualParams*)
+ {
+ //if (m_mapPositionIds.empty() || d_drawInterpolation.getValue() == false)
+ // return;
+
+ //const auto stateLifeCycle = vparams->drawTool()->makeStateLifeCycle();
+ //vparams->drawTool()->setLightingEnabled(false);
+ //sofa::helper::ReadAccessor< Data< type::vector< Vec3 > > > fullPositions = d_inputPositions;
+ //sofa::helper::ReadAccessor< Data< type::vector< Vec3 > > > mapPositions = d_mapPositions;
+
+ //std::vector vertices;
+ //std::vector colors;
+
+ //if (m_mapPositionIds.size() == fullPositions.size())
+ //{
+ // for (unsigned int i = 0; i < fullPositions.size(); ++i)
+ // {
+ // vertices.emplace_back(fullPositions[i]);
+ // vertices.emplace_back(mapPositions[m_mapPositionIds[i]]);
+ // colors.emplace_back(m_mapColors[i]);
+ // }
+ //}
+ //else
+ //{
+ // for (unsigned int i = 0; i < fullPositions.size(); ++i)
+ // {
+ // for (unsigned int j = 0; j < 3; ++j)
+ // {
+ // vertices.emplace_back(fullPositions[i]);
+ // vertices.emplace_back(mapPositions[m_mapPositionIds[i * 3 + j]]);
+ // colors.emplace_back(m_mapColors[i]);
+ // }
+ // }
+ //}
+
+ //vparams->drawTool()->drawLines(vertices, 1, colors);
+ }
-void MeshRepulsionEngine::draw(const core::visual::VisualParams* vparams)
-{
- //if (m_mapPositionIds.empty() || d_drawInterpolation.getValue() == false)
- // return;
-
- //const auto stateLifeCycle = vparams->drawTool()->makeStateLifeCycle();
- //vparams->drawTool()->setLightingEnabled(false);
- //sofa::helper::ReadAccessor< Data< type::vector< Vec3 > > > fullPositions = d_inputPositions;
- //sofa::helper::ReadAccessor< Data< type::vector< Vec3 > > > mapPositions = d_mapPositions;
-
- //std::vector vertices;
- //std::vector colors;
-
- //if (m_mapPositionIds.size() == fullPositions.size())
- //{
- // for (unsigned int i = 0; i < fullPositions.size(); ++i)
- // {
- // vertices.emplace_back(fullPositions[i]);
- // vertices.emplace_back(mapPositions[m_mapPositionIds[i]]);
- // colors.emplace_back(m_mapColors[i]);
- // }
- //}
- //else
- //{
- // for (unsigned int i = 0; i < fullPositions.size(); ++i)
- // {
- // for (unsigned int j = 0; j < 3; ++j)
- // {
- // vertices.emplace_back(fullPositions[i]);
- // vertices.emplace_back(mapPositions[m_mapPositionIds[i * 3 + j]]);
- // colors.emplace_back(m_mapColors[i]);
- // }
- // }
- //}
-
- //vparams->drawTool()->drawLines(vertices, 1, colors);
-}
-
-
-} //namespace cgal
+} // namespace cgal
diff --git a/src/CGALPlugin/MeshRepulsionEngine.h b/src/CGALPlugin/MeshRepulsionEngine.h
index 2fe30f7..a90ea42 100644
--- a/src/CGALPlugin/MeshRepulsionEngine.h
+++ b/src/CGALPlugin/MeshRepulsionEngine.h
@@ -1,22 +1,22 @@
/*****************************************************************************
- * - Copyright (C) 2020-Present InfinyTech3D - *
- * *
- * This file is part of the InfinyToolkit plugin for the SOFA framework. *
- * *
- * This file is dual-licensed: *
- * *
- * 1) Commercial License: *
- * This file may be used under the terms of a valid commercial license *
- * agreement provided wih the software by InfinyTech3D. *
- * *
- * 2) GNU General Public License (GPLv3) Usage *
- * Alternatively, this file may be used under the terms of the *
- * GNU General Public License version 3 as published by the *
- * Free Software Foundation: https://www.gnu.org/licenses/gpl-3.0.html *
- * *
- * Contact: contact@infinytech3d.com *
- * Further information: https://infinytech3d.com *
- ****************************************************************************/
+* - Copyright (C) 2020-Present InfinyTech3D - *
+* *
+* This file is part of the InfinyToolkit plugin for the SOFA framework. *
+* *
+* This file is dual-licensed: *
+* *
+* 1) Commercial License: *
+* This file may be used under the terms of a valid commercial license *
+* agreement provided wih the software by InfinyTech3D. *
+* *
+* 2) GNU General Public License (GPLv3) Usage *
+* Alternatively, this file may be used under the terms of the *
+* GNU General Public License version 3 as published by the *
+* Free Software Foundation: https://www.gnu.org/licenses/gpl-3.0.html *
+* *
+* Contact: contact@infinytech3d.com *
+* Further information: https://infinytech3d.com *
+****************************************************************************/
#pragma once
#include
@@ -27,53 +27,113 @@
#include
#include
+#include
+#include
+
+#include
+#include
+#include
+
namespace cgal
{
-using sofa::type::Vec3;
+ using sofa::type::Vec3;
-/** Attach given pair of particles, projecting the positions of the second particles to the first ones.
-*/
-class SOFA_CGALPLUGIN_API MeshRepulsionEngine : public sofa::core::DataEngine
-{
-public:
- SOFA_CLASS(MeshRepulsionEngine, sofa::core::DataEngine);
+ /*
+ TODO: Attach given pair of particles, projecting the positions of the second particles to the first ones.
+ */
+
+ /**
+ * @brief SOFA/CGAL bridge used to detect.
+ *
+ * reads master/slave MechanicalState and BaseMeshTopology from the scene;
+ * converts the master surface topology to a CGAL::Surface_mesh;
+ * converts the slave surface topology to a CGAL::Surface_mesh for validation/debug;
+ * counts slave vertices inside/on/outside the master using CGAL::Side_of_triangle_mesh;
+ * does not move any point yet.
+ *
+ */
+ class SOFA_CGALPLUGIN_API MeshRepulsionEngine : public sofa::core::DataEngine
+ {
+ public:
+ SOFA_CLASS(MeshRepulsionEngine, sofa::core::DataEngine);
+
+ using DataTypes = sofa::defaulttype::Vec3Types;
+ using Coord = DataTypes::Coord;
+ using VecCoord = DataTypes::VecCoord;
+ using BaseLink = sofa::core::objectmodel::BaseLink;
+
+ using Kernel = CGAL::Exact_predicates_inexact_constructions_kernel;
+ using CgalPoint = Kernel::Point_3;
+ using CgalSurfaceMesh = CGAL::Surface_mesh;
+
+ MeshRepulsionEngine();
+ ~MeshRepulsionEngine() override;
+
+ void init() override;
+ void bwdInit() override;
+ void doUpdate() override;
+
+ void handleEvent(sofa::core::objectmodel::Event* event) override;
+
+ void draw(const sofa::core::visual::VisualParams* vparams) override;
+
+ protected:
+ void updateIntersectedPositions();
+
+ private:
+ static CgalPoint toCgalPoint(const Coord& p);
+
+ bool buildSurfaceMeshFromSofa(
+ const VecCoord& positions,
+ sofa::core::topology::BaseMeshTopology* topology,
+ CgalSurfaceMesh& outputMesh,
+ std::string& errorMessage) const;
+
+ bool rebuildMasterCgalMesh(const VecCoord& masterVertices);
- using DataTypes = sofa::defaulttype::Vec3Types;
- using Coord = DataTypes::Coord;
- using VecCoord = DataTypes::VecCoord;
- using BaseLink = sofa::core::objectmodel::BaseLink;
+ public:
+ // Inputs Data
- MeshRepulsionEngine();
- ~MeshRepulsionEngine() override;
+ sofa::core::objectmodel::SingleLink, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_masterState;
+ sofa::core::objectmodel::SingleLink l_masterTopology;
- void init() override;
- void doUpdate() override;
+ sofa::core::objectmodel::SingleLink, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_slaveState;
+ sofa::core::objectmodel::SingleLink l_slaveTopology;
- void handleEvent(sofa::core::objectmodel::Event* event) override;
+ // Enable/disable the CGAL detection bridge without removing the component from the scene.
+ sofa::core::objectmodel::Data d_enableCgalDetection;
- void draw(const sofa::core::visual::VisualParams* vparams) override;
+ // Rebuild the master CGAL mesh at every step. Keep false for a fixed master.
+ sofa::core::objectmodel::Data d_rebuildMasterEveryStep;
-protected:
- void updateIntersectedPositions();
+ // Print one CGAL diagnostic line every N animation steps (1 means every step).
+ sofa::core::objectmodel::Data d_logEveryNSteps;
-public:
- /// Inputs Data
- //Data< type::vector< Vec3 > > d_inputPositions; ///< Full mesh position
- //Data< type::vector< Vec3 > > d_mapPositions; ///< Surface mesh position
- //Data< type::vector > d_mapTexCoords; ///< Surface mesh texcoords
+ // Run one detection automatically after initialization, during bwdInit().
+ sofa::core::objectmodel::Data d_runDetectionAtInit;
+ // Print diagnostics as warnings. Warnings are visible even when info logs are filtered.
+ sofa::core::objectmodel::Data d_logAsWarning;
- sofa::core::objectmodel::SingleLink< MeshRepulsionEngine, sofa::core::behavior::MechanicalState, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_masterState;
- sofa::core::objectmodel::SingleLink l_masterTopology;
+ // Write diagnostics to a CSV file in the current working directory.
+ sofa::core::objectmodel::Data d_writeCsv;
+ sofa::core::objectmodel::Data d_csvFilePath;
+ // Last computed values. These are visible on theEngine in the SOFA GUI data panel.
+ sofa::core::objectmodel::Data d_lastStep;
+ sofa::core::objectmodel::Data d_lastMasterVertexCount;
+ sofa::core::objectmodel::Data d_lastMasterTriangleCount;
+ sofa::core::objectmodel::Data d_lastSlaveVertexCount;
+ sofa::core::objectmodel::Data d_lastSlaveTriangleCount;
+ sofa::core::objectmodel::Data d_lastInsideCount;
+ sofa::core::objectmodel::Data d_lastBoundaryCount;
+ sofa::core::objectmodel::Data d_lastOutsideCount;
+ sofa::core::objectmodel::Data d_lastSlaveCgalFaceCount;
- sofa::core::objectmodel::SingleLink< MeshRepulsionEngine, sofa::core::behavior::MechanicalState, BaseLink::FLAG_STOREPATH | BaseLink::FLAG_STRONGLINK> l_slaveState;
- sofa::core::objectmodel::SingleLink l_slaveTopology;
-
-private:
- // std::vector m_mapPositionIds; ///< vector to store map position Id per position from full mesh. (same size as @d_inputPositions)
- // std::vector m_mapColors; ///< vector to store color to draw the mapping per position from full mesh. (same size as @d_inputPositions)
-};
+ private:
+ std::unique_ptr m_masterCgalMesh;
+ std::size_t m_stepCounter = 0;
+ };
-} //namespace cgal
+} // namespace cgal