9 #include "imstkNeedlePbdCH.h" 10 #include "imstkCollisionUtils.h" 11 #include "imstkCollisionData.h" 12 #include "imstkLineMesh.h" 13 #include "imstkNeedle.h" 14 #include "imstkPbdBaryPointToPointConstraint.h" 15 #include "imstkPbdModel.h" 16 #include "imstkPbdObject.h" 17 #include "imstkPbdSolver.h" 18 #include "imstkPointwiseMap.h" 19 #include "imstkPuncturable.h" 20 #include "imstkSurfaceMesh.h" 21 #include "imstkTetrahedralMesh.h" 24 #include "imstkPbdPointPointConstraint.h" 25 #include "imstkPbdDistanceConstraint.h" 26 #include "imstkPbdContactConstraint.h" 39 m_tissueSurfMesh = std::dynamic_pointer_cast<
SurfaceMesh>(m_pbdTissueObj->getCollidingGeometry());
42 m_needleObj = std::dynamic_pointer_cast<
PbdObject>(getInputObjectB());
43 m_needleMesh = std::dynamic_pointer_cast<
LineMesh>(m_needleObj->getCollidingGeometry());
46 m_threadObj = threadObj;
47 m_threadMesh = std::dynamic_pointer_cast<
LineMesh>(m_threadObj->getCollidingGeometry());
51 NeedlePbdCH::generateNewPunctureData()
54 auto needle = m_needleObj->getComponent<
Needle>();
55 auto puncturable = m_pbdTissueObj->getComponent<
Puncturable>();
58 auto one2one = std::dynamic_pointer_cast<
PointwiseMap>(m_pbdTissueObj->getPhysicsToCollidingMap());
59 CHECK(one2one !=
nullptr) <<
"No one to one map in NeedlePbdCH for tissue to surface";
61 auto physMesh = std::dynamic_pointer_cast<
TetrahedralMesh>(m_pbdTissueObj->getPhysicsGeometry());
64 int tipSegmentId = m_needleMesh->getNumCells() - 1;
66 Vec2i nodeIds = m_needleMesh->getCells()->at(tipSegmentId);
67 const Vec3d tip1 = m_needleMesh->getVertexPositions()->at(nodeIds[0]);
68 const Vec3d tip2 = m_needleMesh->getVertexPositions()->at(nodeIds[1]);
71 for (
int triangleId = 0; triangleId < m_tissueSurfMesh->getNumCells(); triangleId++)
73 const Vec3i& surfTriIds = m_tissueSurfMesh->getCells()->at(triangleId);
77 physTriIds[0] = one2one->getParentVertexId(surfTriIds[0]);
78 physTriIds[1] = one2one->getParentVertexId(surfTriIds[1]);
79 physTriIds[2] = one2one->getParentVertexId(surfTriIds[2]);
81 const Vec3d& a = physMesh->getVertexPositions()->at(physTriIds[0]);
82 const Vec3d& b = physMesh->getVertexPositions()->at(physTriIds[1]);
83 const Vec3d& c = physMesh->getVertexPositions()->at(physTriIds[2]);
86 Vec3d uvw = Vec3d::Zero();
90 if (needle->getState(punctureId) != Puncture::State::INSERTED)
93 if (CollisionUtils::testSegmentTriangle(tip1, tip2, a, b, c, uvw))
95 needle->setState(punctureId, Puncture::State::INSERTED);
98 Puncture& data = *needle->getPuncture(punctureId);
99 data.userData.id = triangleId;
100 data.userData.ids[0] = physTriIds[0];
101 data.userData.ids[1] = physTriIds[1];
102 data.userData.ids[2] = physTriIds[2];
103 data.userData.weights[0] = uvw[0];
104 data.userData.weights[1] = uvw[1];
105 data.userData.weights[2] = uvw[2];
110 newPuncture.triId = triangleId;
111 newPuncture.triVertIds = physTriIds;
112 newPuncture.baryCoords = uvw;
113 newPuncture.segId = tipSegmentId;
115 pData.needle.push_back(newPuncture);
117 m_needlePunctured =
true;
118 LOG(DEBUG) <<
"Needle punctured triangle: " << triangleId;
125 NeedlePbdCH::addPunctureConstraints()
128 auto needle = m_needleObj->getComponent<
Needle>();
129 auto physMesh = std::dynamic_pointer_cast<
TetrahedralMesh>(m_pbdTissueObj->getPhysicsGeometry());
130 auto puncturable = m_pbdTissueObj->getComponent<
Puncturable>();
135 for (
auto puncture = pData.needle.begin(); puncture != pData.needle.end();)
138 Vec3d closestPoint = { IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX };
139 double closestDist = IMSTK_DOUBLE_MAX;
142 int nearestSegmentId = -1;
144 const Vec3d& a = physMesh->getVertexPositions()->at(puncture->triVertIds[0]);
145 const Vec3d& b = physMesh->getVertexPositions()->at(puncture->triVertIds[1]);
146 const Vec3d& c = physMesh->getVertexPositions()->at(puncture->triVertIds[2]);
148 const Vec3d baryPoint = puncture->baryCoords.head<3>();
149 const Vec3d puncturePt = baryPoint[0] * a + baryPoint[1] * b + baryPoint[2] * c;
154 int previousSegment = puncture->segId;
156 int lowerBound = previousSegment - checkStride;
157 int upperBound = previousSegment + checkStride;
159 int strideStart = (lowerBound > 0) ? lowerBound : 0;
160 int strideEnd = (upperBound < m_needleMesh->getNumCells()) ? upperBound : m_needleMesh->getNumCells() - 1;
162 for (
int segmentId = strideStart; segmentId <= strideEnd; segmentId++)
164 const Vec2i& needleSegNodeIds = m_needleMesh->getCells()->at(segmentId);
165 const Vec3d& x1 = m_needleMesh->getVertexPositions()->at(needleSegNodeIds[0]);
166 const Vec3d& x2 = m_needleMesh->getVertexPositions()->at(needleSegNodeIds[1]);
171 const Vec3d segClosestPoint = CollisionUtils::closestPointOnSegment(puncturePt, x1, x2, caseType);
173 double newDist = (segClosestPoint - puncturePt).squaredNorm();
174 if (newDist < closestDist)
176 closestDist = newDist;
177 closestPoint = segClosestPoint;
178 nearestSegmentId = segmentId;
182 puncture->segId = nearestSegmentId;
186 Vec3d diffTail = closestPoint - m_needleMesh->getVertexPositions()->at(0);
187 Vec3d diffTip = closestPoint - m_needleMesh->getVertexPositions()->at(m_needleMesh->getNumVertices() - 1);
190 const double unpunctureEpsilon = 1e-8;
191 if (diffTail.norm() < unpunctureEpsilon || diffTip.norm() < unpunctureEpsilon)
194 needle->setState(punctureId, Puncture::State::REMOVED);
195 puncture = pData.needle.erase(puncture);
200 if (puncture->segId == 0)
204 pData.thread.push_back(*puncture);
207 needle->setState(punctureId, Puncture::State::REMOVED);
209 LOG(DEBUG) <<
"Thread punctured triangle: " << puncture->triId;
210 m_threadPunctured =
true;
211 puncture = pData.needle.erase(puncture);
218 const int bodyId = m_pbdTissueObj->getPbdBody()->bodyHandle;
219 const int needleBodyId = m_needleObj->getPbdBody()->bodyHandle;
220 auto pointTriangleConstraint = std::make_shared<SurfaceInsertionConstraint>();
221 pointTriangleConstraint->initConstraint(puncturePt,
223 { bodyId, puncture->triVertIds[0] },
224 { bodyId, puncture->triVertIds[1] },
225 { bodyId, puncture->triVertIds[2] },
228 m_needleToSurfaceStiffness, m_surfaceToNeedleStiffness
230 m_constraints.push_back(pointTriangleConstraint);
236 std::vector<std::pair<Vec2i, Vec2d>> punctureSegments;
241 for (
auto puncture = pData.thread.begin(); puncture != pData.thread.end();)
243 const Vec3d& a = physMesh->getVertexPositions()->at(puncture->triVertIds[0]);
244 const Vec3d& b = physMesh->getVertexPositions()->at(puncture->triVertIds[1]);
245 const Vec3d& c = physMesh->getVertexPositions()->at(puncture->triVertIds[2]);
247 const Vec3d& baryPoint = puncture->baryCoords;
248 const Vec3d& puncturePt = baryPoint[0] * a + baryPoint[1] * b + baryPoint[2] * c;
252 const int checkStride = 1;
253 const int previousSegment = puncture->segId;
255 const int lowerBound = previousSegment - checkStride;
256 const int upperBound = previousSegment + checkStride;
258 const int numSegsNonPenetrating = 4;
260 const int strideStart = (lowerBound > 0) ? lowerBound : 0;
261 const int strideEnd = (upperBound < m_threadMesh->getNumCells() - numSegsNonPenetrating) ? upperBound : m_threadMesh->getNumCells() - numSegsNonPenetrating;
263 int closestSegmentId = -1;
265 Vec3d closestPoint = { IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX, IMSTK_DOUBLE_MAX };
266 double closestDist = IMSTK_DOUBLE_MAX;
271 for (
int segmentId = strideStart; segmentId <= strideEnd; segmentId++)
273 const Vec2i& threadSegNodeIds = m_threadMesh->getCells()->at(segmentId);
274 const Vec3d& x1 = m_threadMesh->getVertexPositions()->at(threadSegNodeIds[0]);
275 const Vec3d& x2 = m_threadMesh->getVertexPositions()->at(threadSegNodeIds[1]);
277 const Vec3d segClosestPoint = CollisionUtils::closestPointOnSegment(puncturePt, x1, x2, caseType);
279 double newDist = (segClosestPoint - puncturePt).squaredNorm();
281 if (newDist < closestDist)
283 closestPoint = segClosestPoint;
284 closestDist = newDist;
285 closestSegmentId = segmentId;
289 puncture->segId = closestSegmentId;
303 const Vec2i& closestSegment = m_threadMesh->getCells()->at(puncture->segId);
305 if (closestSegment[0] < 0 || closestSegment[0] >= m_threadMesh->getNumCells()
306 || closestSegment[1] < 0 || closestSegment[1] >= m_threadMesh->getNumCells())
308 LOG(FATAL) <<
"Invalid thread segment id: " << closestSegment[0] <<
", " <<
312 const Vec3d& p = m_threadMesh->getVertexPositions()->at(closestSegment[0]);
313 const Vec3d& q = m_threadMesh->getVertexPositions()->at(closestSegment[1]);
316 const Vec2d segBary = baryCentric(closestPoint, p, q);
318 punctureSegments.push_back(std::make_pair(closestSegment, segBary));
325 std::sort(punctureSegments.begin(), punctureSegments.end(), [](
const auto& seg1,
const auto& seg2) {
326 return seg1.first[0] > seg2.first[0] || (seg1.first[0] == seg2.first[0] && seg1.second[1] > seg2.second[1]);
330 for (
int i = 0; i < pData.thread.size() && i < punctureSegments.size(); ++i)
332 const auto& puncture = pData.thread[i];
333 const auto& nearestSegNodeIds = punctureSegments[i].first;
334 const auto& segBary = punctureSegments[i].second;
336 const int tissueBodyId = m_pbdTissueObj->getPbdBody()->bodyHandle;
337 const int threadBodyId = m_threadObj->getPbdBody()->bodyHandle;
339 auto threadTriangleConstraint = std::make_shared<ThreadInsertionConstraint>();
341 threadTriangleConstraint->initConstraint(
342 m_pbdTissueObj->getPbdModel()->getBodies(),
343 { threadBodyId, nearestSegNodeIds[0] },
344 { threadBodyId, nearestSegNodeIds[1] },
346 { tissueBodyId, puncture.triVertIds[0] },
347 { tissueBodyId, puncture.triVertIds[1] },
348 { tissueBodyId, puncture.triVertIds[2] },
350 m_threadToSurfaceStiffness, m_surfaceToThreadStiffness);
351 m_constraints.push_back(threadTriangleConstraint);
357 const std::vector<CollisionElement>& elementsA,
358 const std::vector<CollisionElement>& elementsB)
360 auto needleObj = std::dynamic_pointer_cast<
PbdObject>(getInputObjectB());
361 auto needleMesh = std::dynamic_pointer_cast<
LineMesh>(needleObj->getCollidingGeometry());
363 m_constraints.clear();
365 auto threadBodyId = m_threadObj->getPbdBody()->bodyHandle;
366 auto needleBodyId = m_needleObj->getPbdBody()->bodyHandle;
369 int numTiedSegments = 1;
370 for (
int i = 0; i <= numTiedSegments; i++)
372 auto needleThreadConstraint = std::make_shared<PbdVertexToBodyConstraint>();
373 needleThreadConstraint->initConstraint(
374 m_threadObj->getPbdModel()->getBodies(),
376 needleMesh->getVertexPositions()->at(numTiedSegments - i),
379 m_constraints.push_back(needleThreadConstraint);
383 for (
size_t i = 0; i < m_stitchConstraints.size(); i++)
385 m_constraints.push_back(m_stitchConstraints[i]);
389 m_needlePunctured = didPuncture(elementsA, elementsB) || m_needlePunctured;
390 if (!m_needlePunctured)
394 if (m_needlePunctured ==
true || m_threadPunctured ==
true)
397 if (m_needlePunctured ==
true)
399 generateNewPunctureData();
404 addPunctureConstraints();
415 if (pData.needle.size() == 0)
417 m_needlePunctured =
false;
420 if (pData.thread.size() == 0)
422 m_threadPunctured =
false;
426 m_solverConstraints.resize(m_constraints.size());
427 for (
int i = 0; i < m_constraints.size(); i++)
429 m_solverConstraints[i] = m_constraints[i].get();
431 m_pbdTissueObj->getPbdModel()->getSolver()->addConstraints(&m_solverConstraints);
439 if (pData.thread.size() < 4)
441 LOG(INFO) <<
"Cant stitch less than 4 points punctured by thread, currently only " << pData.thread.size() <<
" points";
447 if (pData.thread.size() >= 4)
449 LOG(INFO) <<
"Stitching!";
451 auto physMesh = std::dynamic_pointer_cast<
TetrahedralMesh>(m_pbdTissueObj->getPhysicsGeometry());
452 std::shared_ptr<VecDataArray<double, 3>> tissueVerticesPtr = physMesh->
getVertexPositions();
455 std::vector<PuncturePoint> stitchedPoints;
456 for (
size_t pPointId = 0; pPointId < pData.thread.size(); pPointId++)
458 stitchedPoints.
push_back(pData.thread[pPointId]);
461 pData.stitch.push_back(stitchedPoints);
462 pData.thread.clear();
465 Vec3d center = Vec3d::Zero();
466 Vec3d pPoint = Vec3d::Zero();
467 for (
size_t pPointId = 0; pPointId < stitchedPoints.size(); pPointId++)
469 pPoint += tissueVertices[stitchedPoints[pPointId].triVertIds[0]] * stitchedPoints[pPointId].baryCoords[0]
470 + tissueVertices[stitchedPoints[pPointId].triVertIds[1]] * stitchedPoints[pPointId].baryCoords[1]
471 + tissueVertices[stitchedPoints[pPointId].triVertIds[2]] * stitchedPoints[pPointId].baryCoords[2];
474 center = pPoint / stitchedPoints.size();
475 m_stitchPoints.push_back(center);
480 auto points = pData.stitch.back();
481 for (
int pPointId = 0; pPointId < points.size(); pPointId++)
484 const PbdParticleId& stitchCenterPt = m_pbdTissueObj->getPbdModel()->addVirtualParticle(center, 0.0, Vec3d::Zero(),
true);
486 const int bodyId = m_pbdTissueObj->getPbdBody()->bodyHandle;
487 const PbdParticleId p0 = { bodyId, points[pPointId].triVertIds[0] };
488 const PbdParticleId p1 = { bodyId, points[pPointId].triVertIds[1] };
489 const PbdParticleId p2 = { bodyId, points[pPointId].triVertIds[2] };
491 auto constraint = std::make_shared<PbdBaryPointToPointConstraint>();
492 constraint->initConstraint(
494 { points[pPointId].baryCoords[0],
495 points[pPointId].baryCoords[1],
496 points[pPointId].baryCoords[2] },
497 { stitchCenterPt }, { 1.0 },
501 m_stitchConstraints.push_back(constraint);
507 NeedlePbdCH::didPuncture(
const std::vector<CollisionElement>& elementsA,
const std::vector<CollisionElement>& elementsB)
517 auto puncturable = tissueObj->getComponent<
Puncturable>();
518 std::shared_ptr<CollidingObject> needleObj = getInputObjectB();
519 auto needle = needleObj->getComponent<
Needle>();
521 CHECK(tissueObj !=
nullptr) <<
"NeedlePbdCH: tissueObj is null";
522 CHECK(needleObj !=
nullptr) <<
"NeedlePbdCH: needleObj is null";
523 CHECK(puncturable !=
nullptr) <<
"NeedlePbdCH: puncturable is null";
524 CHECK(needle !=
nullptr) <<
"NeedlePbdCH: needle is null";
526 CHECK(elementsA.size() == elementsB.size()) <<
"Number of elements in A and B must be the same";
527 if (elementsA.empty())
535 auto* needleElements = &elementsB;
536 auto* tissueElements = &elementsA;
538 for (
int i = 0; i < needleElements->size(); ++i)
540 const auto& needleElement = needleElements->at(i);
541 const auto& tissueElement = tissueElements->at(i);
543 CHECK(tissueElement.m_type == CollisionElementType::CellIndex)
544 <<
"Suturing only works with CDs that report CellIndex contact";
545 CHECK(tissueElement.m_element.m_CellIndexElement.parentId != -1)
546 <<
"Suturing only works with CDs that report parent ids";
550 int contactPtId = -1;
551 if (needleElement.m_type == CollisionElementType::CellIndex)
553 contactPtId = needleElement.m_element.m_CellIndexElement.ids[0];
556 const PunctureId punctureId =
getPunctureId(needle, puncturable, tissueElement.m_element.m_CellIndexElement.parentId);
559 if (needle->getState(punctureId) == Puncture::State::REMOVED)
561 needle->setState(punctureId, Puncture::State::TOUCHING);
562 puncturable->setPuncture(punctureId, needle->getPuncture(punctureId));
565 auto needleMesh = std::dynamic_pointer_cast<
LineMesh>(needleObj->getCollidingGeometry());
566 std::shared_ptr<VecDataArray<double, 3>> needleVerticesPtr = needleMesh->
getVertexPositions();
569 int endIndex = needleVertices.size() - 1;
570 const Vec3d needleDirection = (needleVertices[endIndex] - needleVertices[endIndex - 1]).normalized();
572 const Vec3d needleTip = needleVertices[endIndex];
582 if (tissueElement.m_element.m_CellIndexElement.cellType != IMSTK_TRIANGLE)
586 std::array<PbdParticleId, 3> ptsB = PbdCollisionHandling::getTriangle(tissueElement, tissueData);
587 const PbdState& bodies = m_pbdTissueObj->getPbdModel()->getBodies();
588 const Vec3d ab = bodies.getPosition(ptsB[1]) - bodies.getPosition(ptsB[0]);
589 const Vec3d ac = bodies.getPosition(ptsB[2]) - bodies.getPosition(ptsB[0]);
592 const Vec3d surfNormal = ac.cross(ab).normalized();
596 const double dotProduct = std::fabs(needleDirection.dot(surfNormal));
598 if (contactPtId == endIndex)
600 if (needle->getState(punctureId) == Puncture::State::TOUCHING)
604 if (dotProduct > m_threshold)
void handle(const std::vector< CollisionElement > &elementsA, const std::vector< CollisionElement > &elementsB) override
Handles puncture constraints for both the needle and the thread.
CollisionSideData getDataFromObject(std::shared_ptr< CollidingObject > obj)
Creates a CollisionSideData struct from the provided object, this gives all the info needed to respon...
std::pair< int, int > PbdParticleId
Index pair that refers to a particle in a PbdState. Index 0 is the body id, Index 1 is the particle i...
PunctureId getPunctureId(std::shared_ptr< Needle > needle, std::shared_ptr< Puncturable > puncturable, const int supportId)
Get puncture id between needle and puncturable.
Place this on an object to make it puncturable by a needle. This allows puncturables to know they've ...
void handle(const std::vector< CollisionElement > &elementsA, const std::vector< CollisionElement > &elementsB) override
Add collision constraints based off contact data.
void push_back(const ValueType &val)
Append the data array to hold the new value, resizes if neccesary.
void stitch()
Create stitching constraints on button press for four or more puncture points.
Represents a set of tetrahedrons & vertices via an array of Vec3d double vertices & Vec4i integer ind...
std::shared_ptr< VecDataArray< double, 3 > > getVertexPositions(DataType type=DataType::PostTransform) const
Returns the vector of current positions of the mesh vertices.
Base for all needles in imstk it supports global puncture state, per object puncture state...
Base class for all volume mesh types.
void init(std::shared_ptr< PbdObject > threadObj)
Initialize interaction data.
Base class for scene objects that move and/or deform under position based dynamics formulation...
The puncture itself is composed of a state and extra non-essential user data.
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
std::shared_ptr< CollidingObject > getInputObjectA() const
Get the input objects.
PointwiseMap can compute & apply a mapping between parent and child PointSet geometries.
Provides interface for accessing particles from a 2d array of PbdBody,Particles.
std::tuple< int, int, int > PunctureId
Punctures are identified via three ints. The needle id, the puncturable id, and a local id that allow...