iMSTK
Interactive Medical Simulation Toolkit
imstkSurfaceMeshCut.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 "imstkSurfaceMeshCut.h"
8 #include "imstkAnalyticalGeometry.h"
9 #include "imstkGeometryUtilities.h"
10 #include "imstkLogger.h"
11 #include "imstkPlane.h"
12 #include "imstkSurfaceMesh.h"
13 
14 #include <set>
15 
16 namespace imstk
17 {
18 SurfaceMeshCut::SurfaceMeshCut()
19 {
21  setOutput(std::make_shared<SurfaceMesh>());
22 }
23 
24 std::shared_ptr<SurfaceMesh>
25 SurfaceMeshCut::getOutputMesh()
26 {
27  return std::dynamic_pointer_cast<SurfaceMesh>(getOutput(0));
28 }
29 
30 void
31 SurfaceMeshCut::setInputMesh(std::shared_ptr<SurfaceMesh> inputMesh)
32 {
33  setInput(inputMesh, 0);
34 }
35 
36 void
37 SurfaceMeshCut::refinement(std::shared_ptr<AbstractCellMesh> outputGeom,
38  std::map<int, bool>& cutVerts)
39 {
40  auto outputSurfaceMesh = std::dynamic_pointer_cast<SurfaceMesh>(outputGeom);
41 
42  std::shared_ptr<VecDataArray<int, 3>> cells = outputSurfaceMesh->getCells();
43  std::shared_ptr<VecDataArray<double, 3>> vertices = outputSurfaceMesh->getVertexPositions();
44  std::shared_ptr<VecDataArray<double, 3>> initVerts = outputSurfaceMesh->getInitialVertexPositions();
45  cells->reserve(cells->size() * 2);
46  vertices->reserve(vertices->size() * 2);
47  initVerts->reserve(initVerts->size() * 2);
48 
49  // map between one exsiting edge to the new vert generated from the cutting
50  std::map<std::pair<int, int>, int> edgeVertMap;
51  for (const auto& curCutData : *m_CutData)
52  {
53  const int curCutType = curCutData.cutType;
54  const int triId = curCutData.cellId;
55  const int ptId0 = curCutData.ptIds[0];
56  const int ptId1 = curCutData.ptIds[1];
57  const Vec3d& cood0 = curCutData.cutCoords[0];
58  const Vec3d& cood1 = curCutData.cutCoords[1];
59  const Vec3d& initCood0 = curCutData.initCoords[0];
60  const Vec3d& initCood1 = curCutData.initCoords[1];
61 
62  if (curCutType == static_cast<int>(TriCutType::EDGE)
63  || curCutType == static_cast<int>(TriCutType::EDGE_VERT))
64  {
65  int newPtId = -1;
66  auto edge0 = std::pair<int, int>(ptId0, ptId1);
67  auto edge1 = std::pair<int, int>(ptId1, ptId0);
68 
69  // add new point
70  if (edgeVertMap.find(edge1) != edgeVertMap.end())
71  {
72  newPtId = edgeVertMap[edge1];
73  }
74  else
75  {
76  newPtId = vertices->size();
77  vertices->push_back(cood0);
78  initVerts->push_back(initCood0);
79  edgeVertMap[edge0] = newPtId;
80  }
81 
82  // update triangle indices
83  Vec3i ptIds = (*cells)[triId];
84  int ptId2 = -1;
85  if (ptIds[0] != ptId0 && ptIds[0] != ptId1)
86  {
87  ptId2 = ptIds[0];
88  }
89  else if (ptIds[1] != ptId0 && ptIds[1] != ptId1)
90  {
91  ptId2 = ptIds[1];
92  }
93  else
94  {
95  ptId2 = ptIds[2];
96  }
97  (*cells)[triId] = Vec3i(ptId2, ptId0, newPtId);
98  cells->push_back(Vec3i(ptId2, newPtId, ptId1));
99 
100  // add vertices to cutting path
101  if (curCutType == static_cast<int>(TriCutType::EDGE_VERT))
102  {
103  cutVerts[ptId2] = (cutVerts.find(ptId2) != cutVerts.end());
104  cutVerts[newPtId] = (cutVerts.find(newPtId) != cutVerts.end());
105  }
106 
107  m_RemoveConstraintVertices->insert(ptId0);
108  m_RemoveConstraintVertices->insert(ptId1);
109  m_RemoveConstraintVertices->insert(ptId2);
110  m_AddConstraintVertices->insert(ptId0);
111  m_AddConstraintVertices->insert(ptId1);
112  m_AddConstraintVertices->insert(ptId2);
113  m_AddConstraintVertices->insert(newPtId);
114  }
115  else if (curCutType == static_cast<int>(TriCutType::EDGE_EDGE))
116  {
117  int newPtId0 = -1;
118  int newPtId1 = -1;
119 
120  Vec3i ptIds = (*cells)[triId];
121  int ptId2 = -1;
122  if (ptIds[0] != ptId0 && ptIds[0] != ptId1)
123  {
124  ptId2 = ptIds[0];
125  }
126  else if (ptIds[1] != ptId0 && ptIds[1] != ptId1)
127  {
128  ptId2 = ptIds[1];
129  }
130  else
131  {
132  ptId2 = ptIds[2];
133  }
134 
135  auto edge00 = std::pair<int, int>(ptId2, ptId0);
136  auto edge01 = std::pair<int, int>(ptId0, ptId2);
137  auto edge10 = std::pair<int, int>(ptId1, ptId2);
138  auto edge11 = std::pair<int, int>(ptId2, ptId1);
139 
140  // add new points
141  if (edgeVertMap.find(edge01) != edgeVertMap.end())
142  {
143  newPtId0 = edgeVertMap[edge01];
144  }
145  else
146  {
147  newPtId0 = vertices->size();
148  vertices->push_back(cood0);
149  initVerts->push_back(initCood0);
150  edgeVertMap[edge00] = newPtId0;
151  }
152  if (edgeVertMap.find(edge11) != edgeVertMap.end())
153  {
154  newPtId1 = edgeVertMap[edge11];
155  }
156  else
157  {
158  newPtId1 = vertices->size();
159  vertices->push_back(cood1);
160  initVerts->push_back(initCood1);
161  edgeVertMap[edge10] = newPtId1;
162  }
163 
164  // update triangle indices
165  (*cells)[triId] = Vec3i(ptId2, newPtId0, newPtId1);
166  cells->push_back(Vec3i(newPtId0, ptId0, ptId1));
167  cells->push_back(Vec3i(newPtId0, ptId1, newPtId1));
168 
169  // add vertices to cutting path
170  cutVerts[newPtId0] = (cutVerts.find(newPtId0) != cutVerts.end());
171  cutVerts[newPtId1] = (cutVerts.find(newPtId1) != cutVerts.end());
172 
173  m_RemoveConstraintVertices->insert(ptId0);
174  m_RemoveConstraintVertices->insert(ptId1);
175  m_RemoveConstraintVertices->insert(ptId2);
176  m_AddConstraintVertices->insert(ptId0);
177  m_AddConstraintVertices->insert(ptId1);
178  m_AddConstraintVertices->insert(ptId2);
179  m_AddConstraintVertices->insert(newPtId0);
180  m_AddConstraintVertices->insert(newPtId1);
181  }
182  else if (curCutType == static_cast<int>(TriCutType::VERT_VERT))
183  {
184  // add vertices to cutting path
185  cutVerts[ptId0] = (cutVerts.find(ptId0) != cutVerts.end()) ? true : false;
186  cutVerts[ptId1] = (cutVerts.find(ptId1) != cutVerts.end()) ? true : false;
187 
188  m_RemoveConstraintVertices->insert(ptId0);
189  m_RemoveConstraintVertices->insert(ptId1);
190  m_AddConstraintVertices->insert(ptId0);
191  m_AddConstraintVertices->insert(ptId1);
192  }
193  else
194  {
195  //do nothing
196  }
197  }
198 }
199 
200 void
201 SurfaceMeshCut::splitVerts(std::shared_ptr<AbstractCellMesh> outputGeom,
202  std::map<int, bool>& cutVerts, std::shared_ptr<Geometry> cuttingGeom)
203 {
204  auto outputSurfaceMesh = std::dynamic_pointer_cast<SurfaceMesh>(outputGeom);
205 
206  std::shared_ptr<VecDataArray<int, 3>> triangles = outputSurfaceMesh->getCells();
207  std::shared_ptr<VecDataArray<double, 3>> vertices = outputSurfaceMesh->getVertexPositions();
208  std::shared_ptr<VecDataArray<double, 3>> initVerts = outputSurfaceMesh->getInitialVertexPositions();
209 
210  std::shared_ptr<ImplicitGeometry> cutGeometry = nullptr;
211  if (auto implicitCutGeom = std::dynamic_pointer_cast<ImplicitGeometry>(cuttingGeom))
212  {
213  cutGeometry = implicitCutGeom;
214  }
215  else if (auto surfMeshCutGeom = std::dynamic_pointer_cast<SurfaceMesh>(cuttingGeom))
216  {
217  // Assuming triangles in cutSurf are co-planar
218  auto cutTriangles = surfMeshCutGeom->getCells();
219  auto cutVertices = surfMeshCutGeom->getVertexPositions();
220 
221  // Compute cutting plane (assuming all triangles in cutSurf are co-planar)
222  const Vec3d p0 = (*cutVertices)[(*cutTriangles)[0][0]];
223  const Vec3d p1 = (*cutVertices)[(*cutTriangles)[0][1]];
224  const Vec3d p2 = (*cutVertices)[(*cutTriangles)[0][2]];
225  const Vec3d cutNormal = ((p1 - p0).cross(p2 - p0)).normalized();
226  cutGeometry = std::make_shared<Plane>(p0, cutNormal);
227  }
228  CHECK(cutGeometry != nullptr) <<
229  "Unsupported cut geometry. Only SurfaceMesh and ImplicitGeometry supported";
230 
231  // build vertexToTriangleListMap
232  std::vector<std::set<int>> vertexNeighborTriangles;
233  vertexNeighborTriangles.clear();
234  vertexNeighborTriangles.resize(vertices->size());
235 
236  int triangleId = 0;
237  for (const auto& tri : *triangles)
238  {
239  vertexNeighborTriangles.at(tri[0]).insert(triangleId);
240  vertexNeighborTriangles.at(tri[1]).insert(triangleId);
241  vertexNeighborTriangles.at(tri[2]).insert(triangleId);
242  triangleId++;
243  }
244 
245  // split cutting vertices
246  for (const auto& cutVert : cutVerts)
247  {
248  if (cutVert.second == false && !vertexOnBoundary(triangles, vertexNeighborTriangles[cutVert.first]))
249  {
250  // do not split vertex since it's the cutting end in surface
251  (*m_CutVertMap)[cutVert.first] = cutVert.first;
252  }
253  else
254  {
255  // split vertex
256  int newPtId = vertices->size();
257  vertices->push_back((*vertices)[cutVert.first]);
258  initVerts->push_back((*initVerts)[cutVert.first]);
259  (*m_CutVertMap)[cutVert.first] = newPtId;
260  m_AddConstraintVertices->insert(newPtId);
261 
262  for (const auto& t : vertexNeighborTriangles[cutVert.first])
263  {
264  //if triangle on the negative side
265  Vec3d pt0 = (*vertices)[(*triangles)[t][0]];
266  Vec3d pt1 = (*vertices)[(*triangles)[t][1]];
267  Vec3d pt2 = (*vertices)[(*triangles)[t][2]];
268 
269  if (ptBoundarySign(pt0, cutGeometry) < 0
270  || ptBoundarySign(pt1, cutGeometry) < 0
271  || ptBoundarySign(pt2, cutGeometry) < 0)
272  {
273  for (int i = 0; i < 3; i++)
274  {
275  if ((*triangles)[t][i] == cutVert.first)
276  {
277  (*triangles)[t][i] = newPtId;
278  }
279  }
280  }
281  }
282  }
283  }
284 }
285 
286 std::shared_ptr<std::vector<CutData>>
288  std::shared_ptr<Geometry> cuttingGeom,
289  std::shared_ptr<AbstractCellMesh> geomToCut)
290 {
291  if (auto cuttingSurfMesh = std::dynamic_pointer_cast<SurfaceMesh>(cuttingGeom))
292  {
293  return generateSurfaceMeshCutData(cuttingSurfMesh,
294  std::dynamic_pointer_cast<SurfaceMesh>(geomToCut));
295  }
296  else if (auto cuttingAnalyticGeom = std::dynamic_pointer_cast<AnalyticalGeometry>(cuttingGeom))
297  {
298  return generateImplicitCutData(cuttingAnalyticGeom,
299  std::dynamic_pointer_cast<SurfaceMesh>(geomToCut));
300  }
301  LOG(FATAL) << "No case for cut geometry";
302  return nullptr;
303 }
304 
305 std::shared_ptr<std::vector<CutData>>
306 SurfaceMeshCut::generateImplicitCutData(std::shared_ptr<AnalyticalGeometry> cuttingGeom,
307  std::shared_ptr<SurfaceMesh> geomToCut)
308 {
309  auto cutData = std::make_shared<std::vector<CutData>>();
310 
311  std::shared_ptr<VecDataArray<int, 3>> triangles = geomToCut->getCells();
312  std::shared_ptr<VecDataArray<double, 3>> vertices = geomToCut->getVertexPositions();
313  std::shared_ptr<VecDataArray<double, 3>> initVerts = geomToCut->getInitialVertexPositions();
314 
315  std::set<std::pair<int, int>> repeatEdges; // make sure not boundary edge in vert-vert case
316 
317  for (int i = 0; i < triangles->size(); i++)
318  {
319  const Vec3i& tri = (*triangles)[i];
320 
321  // Compute if triangles vertex is inside or outside the geometry
322  const Vec3i ptSide =
323  Vec3i(ptBoundarySign((*vertices)[tri[0]], cuttingGeom),
324  ptBoundarySign((*vertices)[tri[1]], cuttingGeom),
325  ptBoundarySign((*vertices)[tri[2]], cuttingGeom));
326 
327  // Square norm removes negatives
328  // Produces differing outputs when some vertices
329  // are on the border
330  const int caseType = ptSide.squaredNorm();
331 
332  CutData newCutData;
333  switch (caseType)
334  {
335  case 1:
336  for (int j = 0; j < 3; j++)
337  {
338  if (ptSide[j] != 0)
339  {
340  int idx0 = (j + 1) % 3;
341  int idx1 = (j + 2) % 3;
342  int ptId0 = tri[idx0];
343  int ptId1 = tri[idx1];
344 
345  std::pair<int, int> cutEdge = std::make_pair(ptId1, ptId0);
346  // if find cut triangle from the other side of the edge, add newCutData
347  if (repeatEdges.find(cutEdge) != repeatEdges.end())
348  {
349  newCutData.cutType = static_cast<int>(TriCutType::VERT_VERT);
350  newCutData.cellId = i;
351  newCutData.ptIds[0] = ptId0;
352  newCutData.ptIds[1] = ptId1;
353  newCutData.cutCoords[0] = (*vertices)[ptId0];
354  newCutData.cutCoords[1] = (*vertices)[ptId1];
355  newCutData.initCoords[0] = (*initVerts)[ptId0];
356  newCutData.initCoords[1] = (*initVerts)[ptId1];
357  cutData->push_back(newCutData);
358  }
359  else
360  {
361  repeatEdges.insert(std::make_pair(ptId0, ptId1));
362  }
363  }
364  }
365  break;
366  case 2:
367  if (ptSide.sum() == 0)
368  {
369  for (int j = 0; j < 3; j++)
370  {
371  if (ptSide[j] == 0)
372  {
373  int idx0 = (j + 1) % 3;
374  int idx1 = (j + 2) % 3;
375  int ptId0 = tri[idx0];
376  int ptId1 = tri[idx1];
377  Vec3d pos0 = (*vertices)[ptId0];
378  Vec3d pos1 = (*vertices)[ptId1];
379  Vec3d initPos0 = (*initVerts)[ptId0];
380  Vec3d initPos1 = (*initVerts)[ptId1];
381  double func0 = cuttingGeom->getFunctionValue(pos0);
382  double func1 = cuttingGeom->getFunctionValue(pos1);
383  double frac = -func0 / (func1 - func0);
384 
385  newCutData.cutType = static_cast<int>(TriCutType::EDGE_VERT);
386  newCutData.cellId = i;
387  newCutData.ptIds[0] = ptId0;
388  newCutData.ptIds[1] = ptId1;
389  newCutData.cutCoords[0] = frac * (pos1 - pos0) + pos0;
390  newCutData.cutCoords[1] = (*vertices)[tri[j]];
391  newCutData.initCoords[0] = frac * (initPos1 - initPos0) + initPos0;
392  newCutData.initCoords[1] = (*initVerts)[tri[j]];
393  cutData->push_back(newCutData);
394  }
395  }
396  }
397  else
398  {
399  // newCutData.cutType = CutType::VERT;
400  }
401  break;
402  case 3:
403  if (ptSide.sum() == -1 || ptSide.sum() == 1)
404  {
405  for (int j = 0; j < 3; j++)
406  {
407  if (ptSide[j] == -ptSide.sum())
408  {
409  int idx0 = (j + 1) % 3;
410  int idx1 = (j + 2) % 3;
411  int ptId0 = tri[idx0];
412  int ptId1 = tri[idx1];
413  int ptId2 = tri[j];
414  Vec3d pos0 = (*vertices)[ptId0];
415  Vec3d pos1 = (*vertices)[ptId1];
416  Vec3d pos2 = (*vertices)[ptId2];
417  Vec3d initPos0 = (*initVerts)[ptId0];
418  Vec3d initPos1 = (*initVerts)[ptId1];
419  Vec3d initPos2 = (*initVerts)[ptId2];
420  double func0 = cuttingGeom->getFunctionValue(pos0);
421  double func1 = cuttingGeom->getFunctionValue(pos1);
422  double func2 = cuttingGeom->getFunctionValue(pos2);
423  double frac0 = -func0 / (func2 - func0);
424  double frac1 = -func1 / (func2 - func1);
425 
426  newCutData.cutType = static_cast<int>(TriCutType::EDGE_EDGE);
427  newCutData.cellId = i;
428  newCutData.ptIds[0] = ptId0;
429  newCutData.ptIds[1] = ptId1;
430  newCutData.cutCoords[0] = frac0 * (pos2 - pos0) + pos0;
431  newCutData.cutCoords[1] = frac1 * (pos2 - pos1) + pos1;
432  newCutData.initCoords[0] = frac0 * (initPos2 - initPos0) + initPos0;
433  newCutData.initCoords[1] = frac1 * (initPos2 - initPos1) + initPos1;
434  cutData->push_back(newCutData);
435  }
436  }
437  }
438  else
439  {
440  // no intersection
441  }
442  break;
443  default:
444  break;
445  }
446  }
447  return cutData;
448 }
449 
450 std::shared_ptr<std::vector<CutData>>
451 SurfaceMeshCut::generateSurfaceMeshCutData(std::shared_ptr<SurfaceMesh> cuttingGeom,
452  std::shared_ptr<SurfaceMesh> geomToCut)
453 {
454  auto cutData = std::make_shared<std::vector<CutData>>();
455 
456  std::shared_ptr<VecDataArray<int, 3>> cutTriangles = cuttingGeom->getCells();
457  std::shared_ptr<VecDataArray<double, 3>> cutVertices = cuttingGeom->getVertexPositions();
458  std::shared_ptr<VecDataArray<int, 3>> triangles = geomToCut->getCells();
459  std::shared_ptr<VecDataArray<double, 3>> vertices = geomToCut->getVertexPositions();
460 
461  // compute cutting plane (assuming all triangles in cutSurf are co-planar)
462  Vec3d p0 = (*cutVertices)[(*cutTriangles)[0][0]];
463  Vec3d p1 = (*cutVertices)[(*cutTriangles)[0][1]];
464  Vec3d p2 = (*cutVertices)[(*cutTriangles)[0][2]];
465  Vec3d cutNormal = (p1 - p0).cross(p2 - p0);
466  auto cutPlane = std::make_shared<Plane>(p0, cutNormal);
467 
468  // Compute cut data using infinite cutPlane
469  std::shared_ptr<std::vector<CutData>> planeCutData =
470  generateImplicitCutData(std::dynamic_pointer_cast<AnalyticalGeometry>(cutPlane), geomToCut);
471 
472  // Remove cutData that are out of the cutSurf
473  for (const CutData& curCutData : *planeCutData)
474  {
475  bool coord0In = pointProjectionInSurface(curCutData.cutCoords[0], cuttingGeom);
476  bool coord1In = pointProjectionInSurface(curCutData.cutCoords[1], cuttingGeom);
477  CutData newCurCutData = curCutData;
478 
479  switch (curCutData.cutType)
480  {
481  case static_cast<int>(TriCutType::VERT_VERT):
482  if (coord0In && coord1In)
483  {
484  cutData->push_back(newCurCutData);
485  }
486  break;
487  case static_cast<int>(TriCutType::EDGE_VERT):
488  if (coord0In) // edge inside
489  {
490  if (coord1In) // vert inside
491  {
492  cutData->push_back(newCurCutData);
493  }
494  else
495  {
496  newCurCutData.cutType = static_cast<int>(TriCutType::EDGE);
497  cutData->push_back(newCurCutData);
498  }
499  }
500  break;
501  case static_cast<int>(TriCutType::EDGE_EDGE):
502  if (coord0In)
503  {
504  if (coord1In)
505  {
506  cutData->push_back(newCurCutData);
507  }
508  else
509  {
510  auto& tri = (*triangles)[newCurCutData.cellId];
511  for (int i = 0; i < 3; i++)
512  {
513  if (tri[i] == newCurCutData.ptIds[0])
514  {
515  int idx0 = (i + 2) % 3;
516  newCurCutData.ptIds[0] = tri[idx0];
517  newCurCutData.ptIds[1] = tri[i];
518  break;
519  }
520  }
521  newCurCutData.cutType = static_cast<int>(TriCutType::EDGE);
522  cutData->push_back(newCurCutData);
523  }
524  }
525  else if (coord1In) // && coord0Out
526  {
527  auto& tri = (*triangles)[newCurCutData.cellId];
528  for (int i = 0; i < 3; i++)
529  {
530  if (tri[i] == curCutData.ptIds[0])
531  {
532  int idx0 = (i + 1) % 3;
533  int idx1 = (i + 2) % 3;
534  newCurCutData.ptIds[0] = tri[idx0];
535  newCurCutData.ptIds[1] = tri[idx1];
536  break;
537  }
538  }
539  newCurCutData.cutCoords[0] = newCurCutData.cutCoords[1];
540  newCurCutData.initCoords[0] = newCurCutData.initCoords[1];
541  newCurCutData.cutType = static_cast<int>(TriCutType::EDGE);
542  cutData->push_back(newCurCutData);
543  }
544  break;
545  default:
546  break;
547  }
548  }
549 
550  return cutData;
551 }
552 } // namespace imstk
void splitVerts(std::shared_ptr< AbstractCellMesh > outputGeom, std::map< int, bool > &cutVerts, std::shared_ptr< Geometry > cuttingGeom) override
Split the cutting vertices, separating them into two.
Compound Geometry.
int ptBoundarySign(const Vec3d &pt, std::shared_ptr< Geometry > geometry)
Determine the sign of the point -1 if inside, 1 if outside, 0 if on boundary defined by epsilon...
void setNumOutputPorts(const size_t numPorts)
Get/Set the amount of output ports.
std::shared_ptr< Geometry > getOutput(size_t port=0) const
Returns output geometry given port, returns nullptr if doesn&#39;t exist.
void setInput(std::shared_ptr< Geometry > inputGeometry, size_t port=0)
Set the input at the port.
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
std::shared_ptr< std::vector< CutData > > generateCutData(std::shared_ptr< Geometry > cuttingGeom, std::shared_ptr< AbstractCellMesh > geomToCut) override
Generate CutData which defines how the cut should be performed.
void setOutput(std::shared_ptr< Geometry > inputGeometry, const size_t port=0)
Set the output at the port.
void refinement(std::shared_ptr< AbstractCellMesh > outputGeom, std::map< int, bool > &cutVerts) override
Refine the mesh adding vertices and changing connectivity along the cut.