iMSTK
Interactive Medical Simulation Toolkit
NeedleEmbedder.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "NeedleEmbedder.h"
8 #include "EmbeddingConstraint.h"
9 #include "imstkCollisionData.h"
10 #include "imstkLineMesh.h"
11 #include "imstkPbdModel.h"
12 #include "imstkPbdObject.h"
13 #include "imstkPbdSolver.h"
14 #include "imstkPuncturable.h"
15 #include "imstkStraightNeedle.h"
16 #include "imstkTaskNode.h"
17 #include "imstkTetrahedralMesh.h"
18 
19 using namespace imstk;
20 
21 static bool
22 testPlaneLine2(const Vec3d& p, const Vec3d& q,
23  const Vec3d& planePt, const Vec3d& planeNormal, Vec3d& iPt,
24  double& t)
25 {
26  const Vec3d n = q - p;
27  const double denom = n.dot(planeNormal);
28  // Plane and line are parallel
29  if (std::abs(denom) < IMSTK_DOUBLE_EPS)
30  {
31  return false;
32  }
33  // const Vec3d dir = n.normalized();
34  t = (planePt - p).dot(planeNormal) / denom;
35  iPt = p + t * n;
36 }
37 
42 static bool
43 testSegmentTriangle2(const Vec3d& p, const Vec3d& q,
44  const Vec3d& a, const Vec3d& b, const Vec3d& c, Vec3d& uvw,
45  bool& segmentInPlane)
46 {
47  segmentInPlane = false;
48  Vec3d iPt;
49  double t;
50  if (testPlaneLine2(p, q, a, (b - a).cross(c - a).normalized(), iPt, t))
51  {
52  uvw = baryCentric(iPt, a, b, c);
53  if (t > 0.0 && t < 1.0)
54  {
55  segmentInPlane = true;
56  if (uvw[0] >= 0.0 && uvw[1] >= 0.0 && uvw[2] >= 0.0)
57  {
58  return true;
59  }
60  }
61  }
62  return false;
63 }
64 
65 TissueData::TissueData(std::shared_ptr<PbdObject> obj) :
66  obj(obj),
67  geom(std::dynamic_pointer_cast<TetrahedralMesh>(obj->getPhysicsGeometry())),
68  verticesPtr(geom->getVertexPositions()),
69  vertices(*verticesPtr),
70  indicesPtr(geom->getCells()),
71  indices(*indicesPtr)
72 {
73 }
74 
75 NeedleData::NeedleData(std::shared_ptr<PbdObject> obj) :
76  obj(obj),
77  needle(obj->getComponent<StraightNeedle>()),
78  verticesPtr(needle->getNeedleGeometry()->getVertexPositions()),
79  vertices(*verticesPtr),
80  cellsPtr(needle->getNeedleGeometry()->getCells()),
81  cells(*cellsPtr)
82 {
83  needle->getNeedleGeometry()->updatePostTransformData();
84 }
85 
86 void
88  TissueData& tissueData, NeedleData& needleData,
89  int v1, int v2, int v3, const Vec3d& iPt)
90 {
91  // Hashable triangle (to resolve shared triangles, any order of v1,v2,v3 maps to same constraint)
92  TriCell triCell(v1, v2, v3);
93 
94  // If constraint doesn't already exist for this triangle
95  if (m_faceConstraints.count(triCell) == 0)
96  {
97  const int bodyId = tissueData.obj->getPbdBody()->bodyHandle;
98 
99  auto constraint = std::make_shared<EmbeddingConstraint>();
100 
101  constraint->initConstraint(
102  tissueData.obj->getPbdModel()->getBodies(),
103  { needleData.obj->getPbdBody()->bodyHandle, 0 },
104  { bodyId, v1 }, { bodyId, v2 }, { bodyId, v3 },
105  &needleData.vertices[0], &needleData.vertices[1], m_compliance);
106  // Constraint acts along needle perpendicular
107  constraint->setFriction(m_friction);
108  constraint->setRestitution(1.0);
109 
110  // Add the constraint to a map of face->constraint
111  m_faceConstraints[triCell] = constraint;
112  }
113 }
114 
115 void
117 {
118  auto puncturable = m_tissueObject->getComponent<Puncturable>();
119  auto needle = m_needleObject->getComponent<StraightNeedle>();
120 
121  TissueData tissueData(m_tissueObject);
122  NeedleData needleData(m_needleObject);
123 
124  // If collision elements are present transition to touching
125  const PunctureId punctureId = getPunctureId(needle, puncturable);
126  if ((m_cdData->elementsA.size() > 0 || m_cdData->elementsB.size() > 0)
127  && needle->getState(punctureId) == Puncture::State::REMOVED)
128  {
129  needle->setState(punctureId, Puncture::State::TOUCHING);
130  puncturable->setPuncture(punctureId, needle->getPuncture(punctureId));
131  }
132 
133  // If needle needle is touching the surface then test for puncture/insertion
134  if (needle->getState(punctureId) == Puncture::State::TOUCHING)
135  {
136  // Get force along the needle axes
137  const Vec3d needleAxes = needle->getNeedleDirection();
138  const double fN = std::max(needleAxes.dot(needleData.obj->getPbdBody()->externalForce), 0.0);
139 
140  // If the normal force exceeds the threshold, mark needle as inserted
141  if (fN > m_forceThreshold)
142  {
143  // Disable collision handling if needle is inside
144  needle->setState(punctureId, Puncture::State::INSERTED);
145  m_pbdCHNode->setEnabled(false);
146  //LOG(INFO) << "Punctured at " << fN << "N";
147  }
148  }
149 
150  // Debug points and triangles for visualization
151  m_debugEmbeddingPoints.clear();
152  m_debugEmbeddedTriangles.clear();
153 
154  if (needle->getState(punctureId) == Puncture::State::INSERTED)
155  {
156  // To "enter" a triangle we must be previously above it and then below it
157  // To "exit" a triangle the same is true but we need to be "inside" the triangle
158 
159  // For culling we compute the sphere bounding the tet, then compute whether this
160  // sphere intersects the line.
161  // Note: This is fastest for a single straight large line vs many tets. It should
162  // be noted that a full sphere sweep (ie: bound line with sphere) would cause a giant
163  // sphere for the line.
164 
165  const double dt = m_needleObject->getPbdModel()->getTimeStep();
166  for (int i = 0; i < needleData.cells.size(); i++)
167  {
168  const Vec2i& seg = needleData.cells[i];
169 
170  const Vec3d& line_x0 = needleData.vertices[seg[0]];
171  const Vec3d& line_x1 = needleData.vertices[seg[1]];
172 
173  const Vec3d& prev_line_x0 = needlePrevVertices[seg[0]];
174  const Vec3d& prev_line_x1 = needlePrevVertices[seg[1]];
175 
176  const Vec3d diff = line_x1 - line_x0;
177  const Vec3d axes = diff.normalized();
178 
179  for (int j = 0; j < tissueData.indices.size(); j++)
180  {
181  const Vec4i& tet = tissueData.indices[j];
182 
183  // Compute bounding sphere for tet
184  const Vec3d& tet_x0 = tissueData.vertices[tet[0]];
185  const Vec3d& tet_x1 = tissueData.vertices[tet[1]];
186  const Vec3d& tet_x2 = tissueData.vertices[tet[2]];
187  const Vec3d& tet_x3 = tissueData.vertices[tet[3]];
188  const Vec3d center = (tet_x0 + tet_x1 + tet_x2 + tet_x3) * 0.25;
189 
190  // Find the max distance from the center
191  const double tetSphereSqrDist =
192  std::max((tissueData.vertices[tet[0]] - center).squaredNorm(),
193  std::max((tissueData.vertices[tet[1]] - center).squaredNorm(),
194  std::max((tissueData.vertices[tet[2]] - center).squaredNorm(),
195  (tissueData.vertices[tet[3]] - center).squaredNorm())));
196 
197  // Compute distance to line axes
198  const Vec3d diffCenter = center - line_x0;
199  const double sqrDistCenterToAxes = (diffCenter - diffCenter.dot(axes) * axes).squaredNorm();
200  // Slight increase in size to account for movement (this imposes a speed limit since we
201  // are checking intersection with both previous and current)
202  if (sqrDistCenterToAxes <= tetSphereSqrDist * 2.0)
203  {
204  // For every face of the tet
205  int faces[4][3] = {
206  { tet[0], tet[1], tet[2] },
207  { tet[1], tet[2], tet[3] },
208  { tet[0], tet[2], tet[3] },
209  { tet[0], tet[1], tet[3] } };
210  for (int k = 0; k < 4; k++)
211  {
212  const Vec3d& tri_x0 = tissueData.vertices[faces[k][0]];
213  const Vec3d& tri_x1 = tissueData.vertices[faces[k][1]];
214  const Vec3d& tri_x2 = tissueData.vertices[faces[k][2]];
215 
216  bool currInPlane = false;
217  Vec3d curr_uvw = Vec3d::Zero();
218  const bool isCurrIntersected = testSegmentTriangle2(
219  line_x0, line_x1,
220  tri_x0, tri_x1, tri_x2,
221  curr_uvw, currInPlane);
222 
223  // If currently intersecting
224  if (isCurrIntersected)
225  {
226  // Check if the previous planar intersection of the axes of the line
227  // was above the triangle
228  const Vec3d& prev_tri_x0 = tissuePrevVertices[faces[k][0]];
229  const Vec3d& prev_tri_x1 = tissuePrevVertices[faces[k][1]];
230  const Vec3d& prev_tri_x2 = tissuePrevVertices[faces[k][2]];
231 
232  Vec3d prev_uvw = Vec3d::Zero();
233  bool prevInPlane = false;
234  const bool isPrevIntersected = testSegmentTriangle2(
235  prev_line_x0, prev_line_x1,
236  prev_tri_x0, prev_tri_x1, prev_tri_x2,
237  prev_uvw, prevInPlane);
238 
239  // If previous in triangle it must be entering correctly
240  if (prev_uvw[0] >= 0.0 && prev_uvw[1] >= 0.0 && prev_uvw[2] >= 0.0)
241  {
242  addFaceEmbeddingConstraint(
243  tissueData, needleData,
244  faces[k][0],
245  faces[k][1],
246  faces[k][2],
247  curr_uvw[0] * tri_x0 + curr_uvw[1] * tri_x1 + curr_uvw[2] * tri_x2);
248  }
249  }
250  }
251  }
252  }
253  }
254 
255  // Add constraint to the PBD solver and RBD system
256  m_constraints.resize(0);
257  m_constraints.reserve(m_faceConstraints.size());
258 
259  // Check all existing constraints too see if they exited
260  std::vector<TriCell> constraintsToDelete;
261  for (auto i = m_faceConstraints.begin(); i != m_faceConstraints.end(); i++)
262  {
263  const std::vector<PbdParticleId>& particles = i->second->getParticles();
264  const Vec3d& tri_x0 = tissueData.vertices[particles[0].second];
265  const Vec3d& tri_x1 = tissueData.vertices[particles[1].second];
266  const Vec3d& tri_x2 = tissueData.vertices[particles[2].second];
267 
268  bool currInPlane = false;
269  Vec3d curr_uvw = Vec3d::Zero();
270  const bool isCurrIntersected = testSegmentTriangle2(
271  *i->second->getP(), *i->second->getQ(),
272  tri_x0, tri_x1, tri_x2,
273  curr_uvw, currInPlane);
274 
275  // If exited/no longer touching triangle
276  if (!currInPlane)
277  {
278  constraintsToDelete.push_back(TriCell(particles[0].second, particles[1].second, particles[2].second));
279  }
280  }
281  for (auto i : constraintsToDelete)
282  {
283  m_faceConstraints.erase(i);
284  }
285 
286  for (auto i = m_faceConstraints.begin(); i != m_faceConstraints.end(); i++)
287  {
288  const std::vector<PbdParticleId>& particles = i->second->getParticles();
289  m_debugEmbeddingPoints.push_back(i->second->getIntersectionPoint());
290  m_debugEmbeddedTriangles.push_back(Vec3i(particles[0].second, particles[1].second, particles[2].second));
291 
292  // Add pbd constraint
293  m_constraints.push_back(i->second.get());
294  }
295  if (m_constraints.size() == 0)
296  {
297  needle->setState(punctureId, Puncture::State::REMOVED);
298  m_pbdCHNode->setEnabled(true);
299  //LOG(INFO) << "Unpunctured!";
300  }
301  tissueData.obj->getPbdModel()->getSolver()->addConstraints(&m_constraints);
302  }
303 
304  // Stash the vertices (at this timestep) for use on the next iteration
305  tissuePrevVertices.resize(tissueData.vertices.size());
306  std::copy(tissueData.vertices.begin(), tissueData.vertices.end(), tissuePrevVertices.begin());
307  needlePrevVertices.resize(needleData.vertices.size());
308  std::copy(needleData.vertices.begin(), needleData.vertices.end(), needlePrevVertices.begin());
309 }
virtual void addFaceEmbeddingConstraint(TissueData &tissueData, NeedleData &needleData, int v1, int v2, int v3, const Vec3d &iPt)
Adds embedding constraint (ie: The constraint maintained after puncture)
Utility for triangle comparison.
void update()
Add embedding constraints based off contact data We need to add the constraint once and then update 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&#39;ve ...
Compound Geometry.
Definition of straight, single segment needle.
iterator begin()
begin(), end() to mirror std::vector
double m_compliance
used in xPBD, inverse of Stiffness
Represents a set of tetrahedrons & vertices via an array of Vec3d double vertices & Vec4i integer ind...
void setPuncture(const PunctureId &id, std::shared_ptr< Puncture > data)
Get/Set puncture data.
std::tuple< int, int, int > PunctureId
Punctures are identified via three ints. The needle id, the puncturable id, and a local id that allow...
Definition: imstkPuncture.h:21
Flattened out with reference members.