iMSTK
Interactive Medical Simulation Toolkit
imstkPbdObjectCellRemoval.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 "imstkPbdObjectCellRemoval.h"
8 #include "imstkPbdConstraintContainer.h"
9 #include "imstkPbdModel.h"
10 #include "imstkPbdObject.h"
11 #include "imstkCellMesh.h"
12 #include "imstkSurfaceMesh.h"
13 #include "imstkPointwiseMap.h"
14 #include "imstkTetrahedralMesh.h"
15 
16 #include <iostream>
17 
18 namespace
19 {
20 // This is from `ExtractSurfaceMesh` in TetrahedralMesh, did not find whether
21 // the vertices in a tetrahedron are suppose to be in a specific order
22 static const std::array<imstk::Vec3i, 4> facePattern = {
23  imstk::Vec3i(0, 1, 2), imstk::Vec3i(0, 1, 3), imstk::Vec3i(0, 2, 3), imstk::Vec3i(1, 2, 3)
24 };
25 
26 bool
27 isOn(imstk::Vec3i tri, imstk::Vec4i tet)
28 {
29  const auto& triArray = tet.array();
30  for (int i = 0; i < 3; ++i)
31  {
32  if (!(triArray == tri[i]).any())
33  {
34  return false;
35  }
36  }
37  return true;
38 }
39 
40 imstk::Vec3i
41 getFace(const std::array<imstk::Vec3i, 4>& pattern, const imstk::Vec4i& tet, int index)
42 {
43  return imstk::Vec3i(
44  tet[pattern[index][0]],
45  tet[pattern[index][1]],
46  tet[pattern[index][2]]);
47 }
48 
49 bool
50 tryGetSharedFace(imstk::Vec4i left, imstk::Vec4i right, std::pair<imstk::Vec3i, imstk::Vec3i>& faces)
51 {
52  for (int leftIndex = 0; leftIndex < 4; ++leftIndex)
53  {
54  auto leftFace = getFace(facePattern, left, leftIndex);
55  for (int rightIndex = 0; rightIndex < 4; ++rightIndex)
56  {
57  auto rightFace = getFace(facePattern, right, rightIndex);
58  bool isSame = true;
59  for (int i = 0; i < 3; ++i)
60  {
61  if (!(leftFace.array() == rightFace[i]).any())
62  {
63  isSame = false;
64  break;
65  }
66  }
67  if (isSame)
68  {
69  faces.first = leftFace;
70  faces.second = rightFace;
71  return true;
72  }
73  }
74  }
75  return false;
76 }
77 } // namespace
78 
79 namespace imstk
80 {
81 PbdObjectCellRemoval::PbdObjectCellRemoval(std::shared_ptr<PbdObject> pbdObj, OtherMeshUpdateType alsoUpdate) :
82  m_obj(pbdObj),
83  m_updateMode(alsoUpdate)
84 {
85  // Add checks here as needed
86 
87  // Get mesh and add dummy vertex for storing removed cell
88  m_mesh = std::dynamic_pointer_cast<AbstractCellMesh>(pbdObj->getPhysicsGeometry());
89  addDummyVertex(m_mesh);
90 
91  // Update fixed node ids to account for dummy vertex at index zero
92  for (int fixedId = 0; fixedId < m_obj->getPbdBody()->fixedNodeIds.size(); fixedId++)
93  {
94  m_obj->getPbdBody()->fixedNodeIds[fixedId]++;
95  }
96 
97  // Reinitialize to account for new dummy vertex
98  pbdObj->initialize();
99 
100  // Note: maps on the pbdObject are no longer valid after this point
101  int alsoUpdateInt = static_cast<int>(alsoUpdate);
102 
103  if (alsoUpdateInt != 0)
104  {
105  auto tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_mesh);
106 
107  if (tetMesh != nullptr)
108  {
109  if (pbdObj->getCollidingGeometry() == pbdObj->getVisualGeometry() && alsoUpdate != OtherMeshUpdateType::None)
110  {
111  alsoUpdate = OtherMeshUpdateType::Collision;
112  }
113 
114  tetMesh->computeVertexToCellMap();
115 
116  if ((alsoUpdateInt & static_cast<int>(OtherMeshUpdateType::Collision)) != 0)
117  {
118  auto mesh = std::dynamic_pointer_cast<SurfaceMesh>(pbdObj->getCollidingGeometry());
119  auto map = std::dynamic_pointer_cast<PointwiseMap>(pbdObj->getPhysicsToCollidingMap());
120  if (mesh == nullptr)
121  {
122  LOG(WARNING) << "Collision mesh not a surface mesh, can't maintain for cell removal";
123  }
124  else if (map == nullptr)
125  {
126  LOG(WARNING) << "PhysicsToCollidingMap not a Pointwise map, can't maintain for cell removal";
127  }
128  else
129  {
130  setupForExtraMeshUpdates(mesh, map);
131  }
132  }
133 
134  if ((alsoUpdateInt & (static_cast<int>(OtherMeshUpdateType::VisualSeparateVertices) |
135  static_cast<int>(OtherMeshUpdateType::VisualReuseVertices))) != 0)
136  {
137  auto mesh = std::dynamic_pointer_cast<SurfaceMesh>(pbdObj->getVisualGeometry());
138  auto map = std::dynamic_pointer_cast<PointwiseMap>(pbdObj->getPhysicsToVisualMap());
139  if (mesh == nullptr)
140  {
141  LOG(WARNING) << "Visual mesh not a surface mesh, can't maintain for cell removal";
142  }
143  else if (map == nullptr)
144  {
145  LOG(WARNING) << "PhysicsToVisualMap not a Pointwise map, can't maintain for cell removal";
146  }
147  else
148  {
149  setupForExtraMeshUpdates(mesh, map);
150  m_linkedMeshData.back().newVertexOnSplit = alsoUpdate == OtherMeshUpdateType::VisualSeparateVertices;
151  }
152  }
153  }
154  else
155  {
156  LOG(WARNING) << "Underlying mesh not a tet mesh, cannot maintain other meshes";
157  m_updateMode = OtherMeshUpdateType::None;
158  }
159  }
160 }
161 
162 void
164 {
165  m_cellsToRemove.push_back(cellId);
166 }
167 
168 void
170 {
171  if (m_cellsToRemove.empty())
172  {
173  return;
174  }
175 
176  // Only for tetmeshes...
177  for (auto& data : m_linkedMeshData)
178  {
179  updateMesh(data);
180  }
181 
182  removeConstraints();
183 
184  m_removedCells.insert(m_removedCells.end(), m_cellsToRemove.begin(), m_cellsToRemove.end());
185  std::sort(m_removedCells.begin(), m_removedCells.end());
186  m_cellsToRemove.clear();
187 
188  fixup();
189 }
190 
191 void
192 PbdObjectCellRemoval::updateMesh(LinkedMeshData& data)
193 { // m_mesh->getAbstractCells()->getNumberOfComponents();
194  auto cellVerts = std::dynamic_pointer_cast<DataArray<int>>(m_mesh->getAbstractCells()); // underlying 1D array
195 
196  constexpr int vertsPerTri = 3;
197 
198  auto& triangles = *(data.surfaceMesh->getCells());
199  auto& vertices = *(data.surfaceMesh->getVertexPositions());
200 
201  auto tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_mesh);
202 
203  // "Remove" all triangles that are adjacent to tets that are being removed
204  for (int cellId : m_cellsToRemove)
205  {
206  auto range = data.tetToTriMap.equal_range(cellId);
207  for (auto item = range.first; item != range.second; ++item)
208  {
209  triangles[item->second] = { 0, 0, 0 };
210  }
211  data.tetToTriMap.erase(cellId);
212  }
213 
214  const auto& tetrahedra = *(tetMesh->getCells());
215  const auto& tetVertices = *(tetMesh->getVertexPositions());
216 
217  // Add all triangles that are on neighboring faces but NOT on other removed tets
218  for (int cellId : m_cellsToRemove)
219  {
220  // All neighbors of the tetrahedron
221  auto range = data.tetAdjancencyMap.equal_range(cellId);
222  for (auto item = range.first; item != range.second; ++item)
223  {
224  auto otherTetIndex = item->second.first;
225 
226  // Don't add if the other tet is in the progress of being removed or has already been removed
227  if (std::find(m_cellsToRemove.cbegin(), m_cellsToRemove.cend(), otherTetIndex) != m_cellsToRemove.cend()
228  || std::find(m_removedCells.cbegin(), m_removedCells.cend(), otherTetIndex) != m_removedCells.cend())
229  {
230  continue;
231  }
232 
233  auto faceOnTetMesh = item->second.second;
234  // Add Vertices
235  Vec3i triangle = { 0, 0, 0 };
236  for (int i = 0; i < vertsPerTri; ++i)
237  {
238  int tetVertexIndex = faceOnTetMesh[i];
239 
240  // Check if the tet vertex is already on the surface mesh
241  auto found = data.tetVertToTriVertMap.find(tetVertexIndex);
242  // Reuse vertex _if_ its found
243  // For visual meshes we want a new vertex so a different uv coordinate
244  // can be calculated
245  if (data.newVertexOnSplit || found == data.tetVertToTriVertMap.cend())
246  {
247  triangle[i] = vertices.size();
248  vertices.push_back(tetVertices[tetVertexIndex]);
249  data.tetVertToTriVertMap[tetVertexIndex] = triangle[i];
250  data.map->addNewUniquePoint(triangle[i], tetVertexIndex);
251  }
252  else
253  {
254  triangle[i] = found->second;
255  }
256  }
257 
258  const Vec3d& v0 = tetVertices[faceOnTetMesh[0]];
259  const Vec3d& v1 = tetVertices[faceOnTetMesh[1]];
260  const Vec3d& v2 = tetVertices[faceOnTetMesh[2]];
261  const Vec3d normal = ((v1 - v0).cross(v2 - v0));
262  const Vec3d centroid = (v0 + v1 + v2) / 3.0;
263 
264  const Vec4i& tet = tetrahedra[otherTetIndex];
265  const Vec3d tetCentroid =
266  (tetVertices[tet[0]] +
267  tetVertices[tet[1]] +
268  tetVertices[tet[2]] +
269  tetVertices[tet[3]]) / 4.0;
270 
271  int triangleIndex = triangles.size();
272 
273  // If the normal is correct, it should be pointing in the same direction as the (face centroid - tetCentroid)
274  if (normal.dot(centroid - tetCentroid) < 0)
275  {
276  triangles.push_back({ triangle[0], triangle[2], triangle[1] });
277  }
278  else
279  {
280  triangles.push_back(triangle);
281  }
282  data.tetToTriMap.insert({ otherTetIndex, triangleIndex });
283  }
284  data.tetAdjancencyMap.erase(cellId);
285  }
286 
287  for (int cellId : m_cellsToRemove)
288  {
289  tetMesh->setTetrahedraAsRemoved(cellId);
290  }
291 
292  if (!m_cellsToRemove.empty())
293  {
294  data.surfaceMesh->postModified();
295  data.surfaceMesh->getVertexPositions()->postModified();
296  data.surfaceMesh->getCells()->postModified();
297  }
298 }
299 
300 void
301 PbdObjectCellRemoval::removeConstraints()
302 {
303  // Mesh Data
304  int bodyId = m_obj->getPbdBody()->bodyHandle;
305  const int vertsPerCell = m_mesh->getAbstractCells()->getNumberOfComponents();
306  auto cellVerts = std::dynamic_pointer_cast<DataArray<int>>(m_mesh->getAbstractCells()); // underlying 1D array
307 
308  // Constraint Data
309  std::shared_ptr<PbdConstraintContainer> constraintsPtr = m_obj->getPbdModel()->getConstraints();
310  const std::vector<std::shared_ptr<PbdConstraint>>& constraints = constraintsPtr->getConstraints();
311 
312  for (int i = 0; i < m_cellsToRemove.size(); i++)
313  {
314  int cellId = m_cellsToRemove[i];
315 
316  // Find and remove the associated constraints
317  for (auto j = constraints.begin(); j != constraints.end();)
318  {
319  const std::vector<PbdParticleId>& vertexIds = (*j)->getParticles();
320 
321  // Check that constraint involves this body and get associated vertices
322  bool isBody = false;
323  for (int cVertId = 0; cVertId < vertexIds.size(); cVertId++)
324  {
325  if (vertexIds[cVertId].first == bodyId)
326  {
327  isBody = true;
328  break;
329  }
330  }
331 
332  // Skip if body is not involved
333  if (isBody == false)
334  {
335  j++;
336  continue;
337  }
338 
339  // Check if the constraint involves ONLY the body of interest
340  // This is used for removing constraints that connect two or more bodies
341  bool isOnlyBody = true;
342  for (int cVertId = 0; cVertId < vertexIds.size(); cVertId++)
343  {
344  if (vertexIds[cVertId].first != bodyId)
345  {
346  isOnlyBody = false;
347  break;
348  }
349  }
350 
351  // Sets for comparing constraint vertices to cell vertices
352  std::unordered_set<int> constraintVertIds;
353  std::unordered_set<int> cellVertIds;
354  // Fill in sets
355  for (int vertId = 0; vertId < vertsPerCell; vertId++)
356  {
357  cellVertIds.insert((*cellVerts)[cellId * vertsPerCell + vertId]);
358  }
359 
360  for (int cVertId = 0; cVertId < vertexIds.size(); cVertId++)
361  {
362  constraintVertIds.insert(vertexIds[cVertId].second);
363  }
364 
365  // Check if cell nodes are a subset of the nodes used for the constraint
366  bool isSubset = true;
367  for (const auto& elem : constraintVertIds)
368  {
369  if (cellVertIds.find(elem) == cellVertIds.end())
370  {
371  isSubset = false;
372  break;
373  }
374  }
375 
376  // Handle constraints connecting two bodies when an associated cell is deleted.
377  bool isMultiBodyConstraint = false;
378  if (isOnlyBody == false)
379  {
380  for (int cVertId = 0; cVertId < vertexIds.size(); cVertId++)
381  {
382  if (vertexIds[cVertId].first == bodyId && cellVertIds.find(vertexIds[cVertId].second) != cellVertIds.end())
383  {
384  isMultiBodyConstraint = true;
385  }
386  }
387  }
388 
389  if (isSubset == true || (isMultiBodyConstraint && isSubset == false))
390  {
391  j = constraintsPtr->eraseConstraint(j);
392  }
393  else
394  {
395  j++;
396  }
397  }
398 
399  // Set removed cell to dummy vertex
400  for (int k = 0; k < vertsPerCell; k++)
401  {
402  (*cellVerts)[cellId * vertsPerCell + k] = 0;
403  }
404  }
405 
406  if (m_cellsToRemove.size() > 0)
407  {
408  // Note: if the collision geometry is different from the physics geometry the collision geometry
409  // will need to be updated. This is not yet implemented.
410  m_mesh->getAbstractCells()->postModified();
411  std::cout << "Removing " << m_cellsToRemove.size() << " Cells";
412  }
413 }
414 
415 void
416 PbdObjectCellRemoval::fixup()
417 {
418  auto volumeMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_mesh);
419  if (volumeMesh == nullptr)
420  {
421  return;
422  }
423  // Gather all the actual points in the tetrahedron
424  std::unordered_set<int> validTetVertices;
425  for (const auto& tet : *volumeMesh->getTetrahedraIndices())
426  {
427  validTetVertices.insert(tet[0]);
428  validTetVertices.insert(tet[1]);
429  validTetVertices.insert(tet[2]);
430  validTetVertices.insert(tet[3]);
431  }
432 
433  for (auto& meshData : m_linkedMeshData)
434  {
435  auto map = meshData.map->getMap();
436  auto& triangles = *(meshData.surfaceMesh->getTriangleIndices());
437  for (auto& tri : triangles)
438  {
439  for (int j = 0; j < 3; j++)
440  {
441  if (tri[j] != 0 && validTetVertices.find(map[tri[j]]) == validTetVertices.end())
442  {
443  tri = { 0, 0, 0 };
444  break;
445  }
446  }
447  }
448  }
449 }
450 
451 void
452 PbdObjectCellRemoval::setupForExtraMeshUpdates(std::shared_ptr<SurfaceMesh> surfaceMesh, std::shared_ptr<PointwiseMap> map)
453 {
454  auto tetMesh = std::dynamic_pointer_cast<TetrahedralMesh>(m_mesh);
455 
456  CHECK(tetMesh != nullptr);
457  CHECK(surfaceMesh != nullptr);
458  CHECK(map != nullptr);
459 
460  LinkedMeshData data;
461  data.surfaceMesh = surfaceMesh;
462  data.map = map;
463 
464  std::multimap<int, int> tetToTriMap;
465  std::multimap<int, std::pair<int, Vec3i>> tetAdjancencyMap;
466 
467  const auto& tetrahedra = *(tetMesh->getCells());
468  const auto& triangles = *(surfaceMesh->getCells());
469 
470  // TODO need to check if that vertex isn't already there
471  addDummyVertex(surfaceMesh);
472  map->compute();
473 
474  // Create reverse lookup map for finding existing vertices
475  for (auto entry : map->getMap())
476  {
477  data.tetVertToTriVertMap[entry.second] = entry.first;
478  }
479 
480  // Create a map that collects connects tetrahdra to
481  // triangles on the mesh. This way the triangles can be
482  // removed when the tetrahedron is removed
483  const auto& triVertToTetVertMap = map->getMap();
484 
485  for (int tetIndex = 0; tetIndex < tetrahedra.size(); ++tetIndex)
486  {
487  const auto& tet = tetrahedra[tetIndex];
488 
489  // Find faces on surfacemesh that are on the current tet
490  for (int triIndex = 0; triIndex < triangles.size(); ++triIndex)
491  {
492  Vec3i triangle = triangles[triIndex];
493  bool allInTet = true;
494  for (int i = 0; i < 3; ++i)
495  {
496  auto found = triVertToTetVertMap.find(triangle[i]);
497  if (found == triVertToTetVertMap.cend())
498  {
499  allInTet = false;
500  break;
501  }
502  triangle[i] = found->second;
503  }
504 
505  if (allInTet && isOn(triangle, tet))
506  {
507  tetToTriMap.insert({ tetIndex, triIndex });
508  }
509  }
510 
511  // Build a structure where adjacent tetrahedra and their faces can be looked up
512  // This way a new face can be created on the adjacent tetrahedron when it's neighbor
513  // is removed
514  for (int otherTetIndex = 0; otherTetIndex < tetrahedra.size(); ++otherTetIndex)
515  {
516  if (tetIndex == otherTetIndex)
517  {
518  continue;
519  }
520 
521  // If we find a face in the adjacency map, we've already been there
522  auto range = tetAdjancencyMap.equal_range(otherTetIndex);
523  auto found = std::find_if(range.first, range.second, [tetIndex](const auto& item) { return item.second.first == tetIndex; });
524  if (found != range.second)
525  {
526  continue;
527  }
528 
529  auto other = tetrahedra[otherTetIndex];
530  std::pair<Vec3i, Vec3i> faces;
531  if (tryGetSharedFace(tet, other, faces))
532  {
533  tetAdjancencyMap.insert({ tetIndex, { otherTetIndex, faces.second } });
534  tetAdjancencyMap.insert({ otherTetIndex, { tetIndex, faces.first } });
535  }
536  }
537  }
538 
539  data.tetToTriMap = tetToTriMap;
540  data.tetAdjancencyMap = tetAdjancencyMap;
541  m_linkedMeshData.push_back(data);
542 }
543 
544 void
545 PbdObjectCellRemoval::addDummyVertexPointSet(std::shared_ptr<PointSet> pointSet)
546 {
547  // Add a dummy vertex to the vertices
548  std::shared_ptr<VecDataArray<double, 3>> verticesPtr = pointSet->getVertexPositions();
549  VecDataArray<double, 3>& vertices = *verticesPtr;
550  vertices.resize(vertices.size() + 1);
551 
552  for (int i = vertices.size() - 1; i >= 1; i--)
553  {
554  vertices[i] = vertices[i - 1];
555  }
556 
557  // Note: may cause collision issues
558  vertices[0] = Vec3d(0.0, 0.0, 0.0);
559  pointSet->setInitialVertexPositions(std::make_shared<VecDataArray<double, 3>>(*verticesPtr));
560 }
561 
562 void
563 PbdObjectCellRemoval::addDummyVertex(std::shared_ptr<AbstractCellMesh> mesh)
564 {
565  addDummyVertexPointSet(mesh);
566 
567  // Mesh data
568  const int vertsPerCell = mesh->getAbstractCells()->getNumberOfComponents();
569  auto cellVerts = std::dynamic_pointer_cast<DataArray<int>>(mesh->getAbstractCells());
570 
571  // Then shift all indices by 1
572  for (int cellId = 0; cellId < mesh->getNumCells(); cellId++)
573  {
574  for (int vertId = 0; vertId < vertsPerCell; vertId++)
575  {
576  (*cellVerts)[cellId * vertsPerCell + vertId]++;
577  }
578  }
579 }
580 } // namespace imstk
void removeCellOnApply(int cellId)
Adds cell to list of cells to be removed.
Compound Geometry.
void resize(const int size) override
Resize data array to hold exactly size number of values.
Represents a set of tetrahedrons & vertices via an array of Vec3d double vertices & Vec4i integer ind...
void postModified()
emits signal to all observers, informing them on the current address in memory and size of array ...
void setTetrahedraAsRemoved(const unsigned int tetId)
Get/set method for removed elements from the mesh.
void apply()
removed cells and associated constraints
Simple dynamic array implementation that also supports event posting and viewing/facade.