iMSTK
Interactive Medical Simulation Toolkit
imstkPbdObjectStitching.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 "imstkPbdObjectStitching.h"
8 #include "imstkCellPicker.h"
9 #include "imstkLineMesh.h"
10 #include "imstkPbdBaryPointToPointConstraint.h"
11 #include "imstkPbdModel.h"
12 #include "imstkPbdObject.h"
13 #include "imstkPbdSolver.h"
14 #include "imstkPointPicker.h"
15 #include "imstkSurfaceMesh.h"
16 #include "imstkTaskGraph.h"
17 #include "imstkTetrahedralMesh.h"
18 #include "imstkTriangleToTetMap.h"
19 
20 namespace imstk
21 {
27 struct MeshSide
28 {
29  MeshSide(VecDataArray<double, 3>& verticest, AbstractDataArray* indicesPtrt,
30  PointwiseMap* mapt, int bodyIdt) : vertices(verticest),
31  indicesPtr(indicesPtrt), map(mapt), bodyId(bodyIdt)
32  {
33  }
34 
35  VecDataArray<double, 3>& vertices;
36  AbstractDataArray* indicesPtr = nullptr;
37  PointwiseMap* map = nullptr;
38  int bodyId = -1;
39 };
40 
41 template<int N>
42 static std::vector<PbdParticleId>
43 getElement(const PickData& pickData, const MeshSide& side)
44 {
45  std::vector<PbdParticleId> results(N);
46  if (pickData.idCount == 1 && pickData.cellType != IMSTK_VERTEX) // If given cell index
47  {
48  const Eigen::Matrix<int, N, 1>& cell = (*dynamic_cast<VecDataArray<int, N>*>(side.indicesPtr))[pickData.ids[0]];
49  for (int i = 0; i < N; i++)
50  {
51  int vertexId = cell[i];
52  if (side.map != nullptr)
53  {
54  vertexId = side.map->getParentVertexId(vertexId);
55  }
56  results[i] = { side.bodyId, vertexId };
57  }
58  }
59  else // If given vertex indices
60  {
61  for (int i = 0; i < N; i++)
62  {
63  int vertexId = pickData.ids[i];
64  if (side.map != nullptr)
65  {
66  vertexId = side.map->getParentVertexId(vertexId);
67  }
68  results[i] = { side.bodyId, vertexId };
69  }
70  }
71  return results;
72 }
73 
74 static std::vector<double>
75 getWeights(const PbdState& bodies, const std::vector<PbdParticleId>& particles, const Vec3d& pt)
76 {
77  std::vector<double> weights(particles.size());
78  if (particles.size() == 4)
79  {
80  const Vec4d baryCoord = baryCentric(pt,
81  bodies.getPosition(particles[0]),
82  bodies.getPosition(particles[1]),
83  bodies.getPosition(particles[2]),
84  bodies.getPosition(particles[3]));
85  weights[0] = baryCoord[0];
86  weights[1] = baryCoord[1];
87  weights[2] = baryCoord[2];
88  weights[3] = baryCoord[3];
89  }
90  else if (particles.size() == 3)
91  {
92  const Vec3d baryCoord = baryCentric(pt,
93  bodies.getPosition(particles[0]),
94  bodies.getPosition(particles[1]),
95  bodies.getPosition(particles[2]));
96  weights[0] = baryCoord[0];
97  weights[1] = baryCoord[1];
98  weights[2] = baryCoord[2];
99  }
100  else if (particles.size() == 2)
101  {
102  const Vec2d baryCoord = baryCentric(pt,
103  bodies.getPosition(particles[0]),
104  bodies.getPosition(particles[1]));
105  weights[0] = baryCoord[0];
106  weights[1] = baryCoord[1];
107  }
108  else if (particles.size() == 1)
109  {
110  weights[0] = 1.0;
111  }
112  return weights;
113 }
114 
115 PbdObjectStitching::PbdObjectStitching(std::shared_ptr<PbdObject> obj) :
116  m_objectToStitch(obj), m_pickMethod(std::make_shared<CellPicker>())
117 {
118  m_stitchingNode = std::make_shared<TaskNode>(std::bind(&PbdObjectStitching::updateStitching, this),
119  "PbdStitchingUpdate", true);
120  m_taskGraph->addNode(m_stitchingNode);
121 
122  m_taskGraph->addNode(m_objectToStitch->getPbdModel()->getIntegratePositionNode());
123  m_taskGraph->addNode(m_objectToStitch->getPbdModel()->getSolveNode());
124 
125  m_taskGraph->addNode(m_objectToStitch->getTaskGraph()->getSource());
126  m_taskGraph->addNode(m_objectToStitch->getTaskGraph()->getSink());
127 }
128 
129 void
130 PbdObjectStitching::beginStitch(const Vec3d& rayStart, const Vec3d& rayDir, const double maxDist)
131 {
132  auto pointPicker = std::make_shared<PointPicker>();
133  pointPicker->setPickingRay(rayStart, rayDir, maxDist);
134  m_pickMethod = pointPicker;
135 
136  m_performStitch = true;
137  LOG(INFO) << "Begin stitch";
138 }
139 
140 void
142 {
143  m_constraints.clear();
144  m_collisionConstraints.clear();
145 }
146 
147 void
149 {
150  std::shared_ptr<PbdModel> model = m_objectToStitch->getPbdModel();
151 
152  // PbdModel geometry can only be PointSet
153  std::shared_ptr<PointSet> pbdPhysicsGeom =
154  std::dynamic_pointer_cast<PointSet>(m_objectToStitch->getPhysicsGeometry());
155 
156  // If the geometry to pick hasn't been set yet, default it to the physics geometry
157  // Could be different in cases where user wants to pick a mapped geometry, mapping back
158  // to the physics geometry
159  std::shared_ptr<PointSet> pointSetToPick = std::dynamic_pointer_cast<PointSet>(m_geomToStitch);
160  if (m_geomToStitch == nullptr)
161  {
162  pointSetToPick = pbdPhysicsGeom;
163  }
164 
165  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = pbdPhysicsGeom->getVertexPositions();
166 
167  std::shared_ptr<AbstractDataArray> indicesPtr = nullptr;
168  if (auto cellMesh = std::dynamic_pointer_cast<AbstractCellMesh>(pointSetToPick))
169  {
170  indicesPtr = cellMesh->getAbstractCells();
171  }
172 
173  // If the user tries to pick
174  PointwiseMap* map = nullptr;
175  if (m_geometryToStitchMap != nullptr)
176  {
177  map = m_geometryToStitchMap.get();
178  }
179 
180  // Place all the data into a struct to pass around & for quick access without casting
181  // or dereferencing
182  MeshSide meshStruct(
183  *verticesPtr,
184  indicesPtr.get(),
185  map,
186  m_objectToStitch->getPbdBody()->bodyHandle);
187 
188  auto getCellVerts =
189  [&](const PickData& data)
190  {
191  const CellTypeId pickedCellType = data.cellType;
192 
193  std::vector<PbdParticleId> cellIdVerts;
194  if (pickedCellType == IMSTK_TETRAHEDRON)
195  {
196  cellIdVerts = getElement<4>(data, meshStruct);
197  }
198  else if (pickedCellType == IMSTK_TRIANGLE)
199  {
200  cellIdVerts = getElement<3>(data, meshStruct);
201  }
202  else if (pickedCellType == IMSTK_EDGE)
203  {
204  cellIdVerts = getElement<2>(data, meshStruct);
205  }
206  return cellIdVerts;
207  };
208 
209  auto pointPicker = std::dynamic_pointer_cast<PointPicker>(m_pickMethod);
210  pointPicker->setUseFirstHit(false);
211 
212  // Perform the picking only on surface data
213  auto surfMesh = std::dynamic_pointer_cast<SurfaceMesh>(pointSetToPick);
214  auto tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(pointSetToPick);
215  if (tetMesh != nullptr)
216  {
217  surfMesh = tetMesh->extractSurfaceMesh();
218  }
219  const std::vector<PickData>& pickData = m_pickMethod->pick(surfMesh);
220 
221  // Must have at least 2
222  if (pickData.size() < 1)
223  {
224  return;
225  }
226 
227  std::vector<std::pair<PickData, PickData>> constraintPair;
228  if (std::dynamic_pointer_cast<TetrahedralMesh>(pointSetToPick) != nullptr)
229  {
230  // ** Warning **, surface triangles are not 100% garunteed to tell inside/out
231  // Should use angle-weighted pseudonormals
232  surfMesh->computeTrianglesNormals();
233  std::shared_ptr<VecDataArray<double, 3>> faceNormalsPtr = surfMesh->getCellNormals();
234  const VecDataArray<double, 3>& faceNormals = *faceNormalsPtr;
235 
236  // Find all neighbor pairs with normals facing each other
237  for (size_t i = 0, j = 1; i < pickData.size() - 1; i++, j++)
238  {
239  const Vec3d& pt_i = pickData[i].pickPoint;
240  const Vec3d& pt_j = pickData[j].pickPoint;
241  const Vec3d& normal_i = faceNormals[pickData[i].ids[0]];
242  const Vec3d& normal_j = faceNormals[pickData[j].ids[0]];
243  const Vec3d diff = pt_j - pt_i;
244 
245  //bool faceOpposite = (normal_i.dot(normal_j) < 0.0);
246  bool faceInwards = (diff.dot(normal_i) > 0.0) && (diff.dot(normal_j) < 0.0);
247 
248  // If they face into each other
249  if (faceInwards)
250  {
251  constraintPair.push_back({ pickData[i], pickData[j] });
252  }
253  }
254 
255  // If no constraint pairs, no stitches can be placed
256  if (constraintPair.size() == 0)
257  {
258  return;
259  }
260 
261  // If we have a tet mesh and some results, map the picked surface triangles
262  // back to the tetrahedrons
263  TriangleToTetMap mapper;
264  mapper.setParentGeometry(tetMesh);
265  mapper.setChildGeometry(surfMesh);
266  mapper.compute();
267 
268  for (size_t i = 0; i < constraintPair.size(); i++)
269  {
270  PickData& pickData1 = constraintPair[i].first;
271  PickData& pickData2 = constraintPair[i].second;
272 
273  // Get the tet id from the triangle id
274  pickData1.ids[0] = mapper.getParentTetId(pickData1.ids[0]);
275  pickData1.idCount = 1;
276  pickData1.cellType = IMSTK_TETRAHEDRON;
277 
278  pickData2.ids[0] = mapper.getParentTetId(pickData2.ids[0]);
279  pickData2.idCount = 1;
280  pickData2.cellType = IMSTK_TETRAHEDRON;
281  // Leave pick point the same
282  }
283  }
284  else if (std::dynamic_pointer_cast<SurfaceMesh>(pointSetToPick) != nullptr)
285  {
286  // For a SurfaceMesh just constrain every pair
287  for (size_t i = 0, j = 1; i < pickData.size() - 1; i++, j++)
288  {
289  constraintPair.push_back({ pickData[i], pickData[j] });
290  }
291  }
292 
293  // Constrain only the pick points between the two elements
294  for (size_t i = 0; i < constraintPair.size(); i++)
295  {
296  const PickData& pickData1 = constraintPair[i].first;
297  const PickData& pickData2 = constraintPair[i].second;
298 
299  if (m_maxStitchDist == -1.0 || (pickData2.pickPoint - pickData1.pickPoint).norm() < m_maxStitchDist)
300  {
301  std::vector<PbdParticleId> cellVerts1 = getCellVerts(pickData1);
302  std::vector<double> weights1 = getWeights(model->getBodies(), cellVerts1, pickData1.pickPoint);
303  std::vector<PbdParticleId> cellVerts2 = getCellVerts(pickData2);
304  std::vector<double> weights2 = getWeights(model->getBodies(), cellVerts2, pickData2.pickPoint);
305 
306  // Cell to single point constraint
307  addConstraint(
308  cellVerts1, weights1,
309  cellVerts2, weights2,
310  m_stiffness, m_stiffness);
311  }
312  }
313 
314  m_collisionConstraints.reserve(m_constraints.size());
315  for (int i = 0; i < m_constraints.size(); i++)
316  {
317  m_collisionConstraints.push_back(m_constraints[i].get());
318  }
319 }
320 
321 void
323  const std::vector<PbdParticleId>& ptsA,
324  const std::vector<double>& weightsA,
325  const std::vector<PbdParticleId>& ptsB,
326  const std::vector<double>& weightsB,
327  const double stiffnessA, const double stiffnessB)
328 {
329  auto constraint = std::make_shared<PbdBaryPointToPointConstraint>();
330  constraint->initConstraint(
331  ptsA, weightsA,
332  ptsB, weightsB,
333  stiffnessA, stiffnessB);
334  m_constraints.push_back(constraint);
335 }
336 
337 void
339 {
340  m_objectToStitch->updateGeometries();
341 
342  // If started
343  if (m_performStitch)
344  {
345  addStitchConstraints();
346  m_performStitch = false;
347  }
348 
349  if (m_collisionConstraints.size() > 0)
350  {
351  m_objectToStitch->getPbdModel()->getSolver()->addConstraints(&m_collisionConstraints);
352  }
353 }
354 
355 void
356 PbdObjectStitching::initGraphEdges(std::shared_ptr<TaskNode> source, std::shared_ptr<TaskNode> sink)
357 {
358  std::shared_ptr<PbdModel> pbdModel = m_objectToStitch->getPbdModel();
359 
360  m_taskGraph->addEdge(source, m_objectToStitch->getTaskGraph()->getSource());
361  m_taskGraph->addEdge(m_objectToStitch->getTaskGraph()->getSink(), sink);
362 
363  // The ideal location is after the internal positional solve, before the collision solve
364  m_taskGraph->addEdge(pbdModel->getIntegratePositionNode(), m_stitchingNode);
365  m_taskGraph->addEdge(m_stitchingNode, pbdModel->getSolveNode());
366 }
367 } // namespace imstk
Base class for all geometries represented by discrete points and elements The pointsets follow a pipe...
Definition: imstkPointSet.h:25
void addStitchConstraints()
Compute/generate the constraints for stitching.
virtual void addConstraint(const std::vector< PbdParticleId > &ptsA, const std::vector< double > &weightsA, const std::vector< PbdParticleId > &ptsB, const std::vector< double > &weightsB, const double stiffnessA, const double stiffnessB)
Add constraint between a point on each element given via barycentric coordinates pt position = weight...
void compute() override
Compute the map.
void setParentGeometry(std::shared_ptr< Geometry > parent)
Get/Set parent geometry.
int getParentTetId(const int triId) const
Get the tet id that contains the triangle.
void beginStitch(const Vec3d &rayStart, const Vec3d &rayDir, const double maxDist=-1.0)
Begin a ray point stitch. Stitches two points for separate elements.
Compound Geometry.
virtual void updateStitching()
Update picking state, this should move grasp points.
void push_back(const ValueType &val)
Append the data array to hold the new value, resizes if neccesary.
std::shared_ptr< SurfaceMesh > extractSurfaceMesh() override
This method extracts the conforming triangular mesh from the tetrahedral mesh.
This class serves as the base class of DataArray, for typeless use.
int idCount
Indicates number of vertices (if 1 a cell or individual vertex)
SurfaceToTetrahedralMap serves as a PointwiseMap but also maps tets to triangle faces.
int getParentVertexId(const int childVertexId) const
Get the mapped/corresponding parent index, given a child index. returns -1 if no correspondence found...
Vec3d pickPoint
Some pickings may produce specific points on an element.
Represents a set of tetrahedrons & vertices via an array of Vec3d double vertices & Vec4i integer ind...
void setUseFirstHit(const bool useFirstHit)
Get/Set whether only the first hit is used otherwise all intersections are returned.
void setChildGeometry(std::shared_ptr< Geometry > child)
Get/Set child geometry.
PickData provides ids to indicate what was picked These may be optionally used to indicate the select...
std::shared_ptr< VecDataArray< double, 3 > > getVertexPositions(DataType type=DataType::PostTransform) const
Returns the vector of current positions of the mesh vertices.
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
PointwiseMap can compute & apply a mapping between parent and child PointSet geometries.
void removeStitchConstraints()
Clears all the stitches.
Provides interface for accessing particles from a 2d array of PbdBody,Particles.
Definition: imstkPbdBody.h:229
Picks points on elements of geomToPick via those that that are intersecting the provided ray...
CellTypeId cellType
Indicates picked cell type.
Packs the info needed to add a constraint to a side by reference (this way dynamic casting & derefere...
void initGraphEdges()
Initializes the edges of the SceneObject&#39;s computational graph.
int ids[4]
Ids of the cell or vertices.