iMSTK
Interactive Medical Simulation Toolkit
imstkLineMeshCut.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 "imstkLineMeshCut.h"
8 #include "imstkImplicitGeometry.h"
9 #include "imstkLineMesh.h"
10 #include "imstkPlane.h"
11 #include "imstkSurfaceMesh.h"
12 
13 namespace imstk
14 {
15 LineMeshCut::LineMeshCut()
16 {
18  setOutput(std::make_shared<LineMesh>());
19 }
20 
21 std::shared_ptr<LineMesh>
22 LineMeshCut::getOutputMesh()
23 {
24  return std::dynamic_pointer_cast<LineMesh>(getOutput(0));
25 }
26 
27 void
28 LineMeshCut::setInputMesh(std::shared_ptr<LineMesh> mesh)
29 {
30  setInput(mesh, 0);
31 }
32 
33 void
34 LineMeshCut::refinement(std::shared_ptr<AbstractCellMesh> outputGeom,
35  std::map<int, bool>& cutVerts)
36 {
37  auto outputLineMesh = std::dynamic_pointer_cast<LineMesh>(outputGeom);
38 
39  std::shared_ptr<VecDataArray<int, 2>> cells = outputLineMesh->getCells();
40  std::shared_ptr<VecDataArray<double, 3>> vertices = outputLineMesh->getVertexPositions();
41  std::shared_ptr<VecDataArray<double, 3>> initVerts = outputLineMesh->getInitialVertexPositions();
42  cells->reserve(cells->size() * 2);
43  vertices->reserve(vertices->size() * 2);
44  initVerts->reserve(initVerts->size() * 2);
45 
46  // Map between one exsiting edge to the new vert generated from the cutting
47  std::map<std::pair<int, int>, int> edgeVertMap;
48  for (const auto& curCutData : *m_CutData)
49  {
50  //const int curCutType = curCutData.cutType;
51  const int cellId = curCutData.cellId;
52  const int ptId0 = curCutData.ptIds[0];
53  const int ptId1 = curCutData.ptIds[1];
54  const Vec3d& coord0 = curCutData.cutCoords[0];
55  //const Vec3d& coord1 = curCutData.cutCoords[1];
56  const Vec3d& initCoord0 = curCutData.initCoords[0];
57  //const Vec3d& initCoord1 = curCutData.initCoords[1];
58 
59  // There is only one case just add a vertex from coord0
60  vertices->push_back(coord0);
61  initVerts->push_back(initCoord0);
62 
63  const int newPtId0 = vertices->size() - 1;
64 
65  // Rewire the edge from 1 to newPt
66  // And newPt to 0
67  const Vec2i prevCell = (*cells)[cellId];
68  (*cells)[cellId] = Vec2i(prevCell[0], newPtId0);
69  cells->push_back(Vec2i(newPtId0, prevCell[1]));
70 
71  // Add vertices to cutting path
72  cutVerts[newPtId0] = true; // Split it
73 
74  // Regenerate constraints on these vertices
75  m_RemoveConstraintVertices->insert(ptId0);
76  m_RemoveConstraintVertices->insert(ptId1);
77  m_AddConstraintVertices->insert(ptId0);
78  m_AddConstraintVertices->insert(ptId1);
79  m_AddConstraintVertices->insert(newPtId0);
80  }
81 }
82 
83 void
84 LineMeshCut::splitVerts(std::shared_ptr<AbstractCellMesh> outputGeom,
85  std::map<int, bool>& cutVerts,
86  std::shared_ptr<Geometry> cuttingGeom)
87 {
88  auto outputLineMesh = std::dynamic_pointer_cast<LineMesh>(outputGeom);
89 
90  std::shared_ptr<VecDataArray<int, 2>> cells = outputLineMesh->getCells();
91  std::shared_ptr<VecDataArray<double, 3>> vertices = outputLineMesh->getVertexPositions();
92  std::shared_ptr<VecDataArray<double, 3>> initVerts = outputLineMesh->getInitialVertexPositions();
93 
94  std::shared_ptr<ImplicitGeometry> cutGeometry = nullptr;
95  if (auto implicitCutGeom = std::dynamic_pointer_cast<ImplicitGeometry>(cuttingGeom))
96  {
97  cutGeometry = implicitCutGeom;
98  }
99  else if (auto surfMeshCutGeom = std::dynamic_pointer_cast<SurfaceMesh>(cuttingGeom))
100  {
101  // Assuming triangles in cutSurf are co-planar
102  std::shared_ptr<VecDataArray<int, 3>> cutTriangles = surfMeshCutGeom->getCells();
103  std::shared_ptr<VecDataArray<double, 3>> cutVertices = surfMeshCutGeom->getVertexPositions();
104 
105  // Compute cutting plane (assuming all triangles in cutSurf are co-planar)
106  const Vec3d p0 = (*cutVertices)[(*cutTriangles)[0][0]];
107  const Vec3d p1 = (*cutVertices)[(*cutTriangles)[0][1]];
108  const Vec3d p2 = (*cutVertices)[(*cutTriangles)[0][2]];
109  const Vec3d cutNormal = ((p1 - p0).cross(p2 - p0)).normalized();
110  cutGeometry = std::make_shared<Plane>(p0, cutNormal);
111  }
112  CHECK(cutGeometry != nullptr) <<
113  "Unsupported cut geometry. Only SurfaceMesh and ImplicitGeometry supported";
114 
115  // build vertexToTriangleListMap
116  outputLineMesh->computeVertexToCellMap();
117  const std::vector<std::unordered_set<int>>& vertexToCellMap = outputLineMesh->getVertexToCellMap();
118 
119  // Split cutting vertices (vertices on cut path)
120  for (const auto& cutVert : cutVerts)
121  {
122  bool useFirstVertex = false;
123 
124  // For all cells connected to the cut vertex, make a new vertex to connect too
125  for (const auto& cellId : vertexToCellMap[cutVert.first])
126  {
127  // The cut vertex should be re-used for at least/the first one of the cells
128  if (!useFirstVertex)
129  {
130  useFirstVertex = true;
131  continue;
132  }
133 
134  // For every cell connected to the vertex to be split, make a duplicate
135  // vertex with a newPtId, then rewire
136  const int newPtId = vertices->size();
137  vertices->push_back((*vertices)[cutVert.first]);
138  initVerts->push_back((*initVerts)[cutVert.first]);
139  (*m_CutVertMap)[cutVert.first] = newPtId;
140  m_AddConstraintVertices->insert(newPtId);
141 
142  // Whichever vertex was pointing to the cut vertex, rewire
143  Vec2i& cell = (*cells)[cellId];
144  if (cell[0] == cutVert.first)
145  {
146  cell[0] = newPtId;
147  }
148  else if (cell[1] == cutVert.first)
149  {
150  cell[1] = newPtId;
151  }
152  }
153  }
154 }
155 
156 std::shared_ptr<std::vector<CutData>>
158  std::shared_ptr<Geometry> cuttingGeom,
159  std::shared_ptr<AbstractCellMesh> geomToCut)
160 {
161  if (auto cuttingSurfMesh = std::dynamic_pointer_cast<SurfaceMesh>(cuttingGeom))
162  {
163  return generateSurfaceMeshCutData(cuttingSurfMesh,
164  std::dynamic_pointer_cast<LineMesh>(geomToCut));
165  }
166  else if (auto cuttingAnalyticGeom = std::dynamic_pointer_cast<AnalyticalGeometry>(cuttingGeom))
167  {
168  return generateImplicitCutData(cuttingAnalyticGeom,
169  std::dynamic_pointer_cast<LineMesh>(geomToCut));
170  }
171  LOG(FATAL) << "No case for cut geometry";
172  return nullptr;
173 }
174 
175 std::shared_ptr<std::vector<CutData>>
176 LineMeshCut::generateImplicitCutData(std::shared_ptr<ImplicitGeometry> cuttingGeom,
177  std::shared_ptr<LineMesh> geomToCut)
178 {
179  auto cutData = std::make_shared<std::vector<CutData>>();
180 
181  auto lineMeshToCut = std::dynamic_pointer_cast<LineMesh>(geomToCut);
182  std::shared_ptr<VecDataArray<int, 2>> cells = lineMeshToCut->getCells();
183  std::shared_ptr<VecDataArray<double, 3>> vertices = lineMeshToCut->getVertexPositions();
184  std::shared_ptr<VecDataArray<double, 3>> initVerts = lineMeshToCut->getInitialVertexPositions();
185 
186  // For every edge/segment
187  for (int i = 0; i < cells->size(); i++)
188  {
189  const Vec2i& cell = (*cells)[i];
190 
191  // Compute if cells vertices are inside or outside the geometry
192  const Vec2i ptSide =
193  Vec2i(ptBoundarySign((*vertices)[cell[0]], cuttingGeom),
194  ptBoundarySign((*vertices)[cell[1]], cuttingGeom));
195 
196  // There is 3 cases
197  // Both verts lie on pos side
198  // Both verts lie on neg side
199  // Verts lie on opposite sides
200 
201  // Both verts lie on the same side
202  const int caseType = ptSide.squaredNorm();
203  if (caseType != 2)
204  {
205  const int ptId0 = cell[0];
206  const int ptId1 = cell[1];
207 
208  const Vec3d pos0 = (*vertices)[ptId0];
209  const Vec3d pos1 = (*vertices)[ptId1];
210  const Vec3d initPos0 = (*initVerts)[ptId0];
211  const Vec3d initPos1 = (*initVerts)[ptId1];
212  const double func0 = cuttingGeom->getFunctionValue(pos0);
213  const double func1 = cuttingGeom->getFunctionValue(pos1);
214  const double frac = -func0 / (func1 - func0);
215  const Vec3d iPt = frac * (pos1 - pos0) + pos0;
216  const Vec3d init_iPt = frac * (initPos1 - initPos0) + initPos0;
217 
218  {
219  CutData cellCutData;
220  cellCutData.cutType = static_cast<int>(SegmentCutType::EDGE);
221  cellCutData.cellId = i;
222  cellCutData.ptIds[0] = ptId0;
223  cellCutData.ptIds[1] = ptId1;
224  cellCutData.cutCoords[0] = iPt;
225  cellCutData.cutCoords[1] = (*vertices)[ptId1];
226  cellCutData.initCoords[0] = init_iPt;
227  cellCutData.initCoords[1] = (*initVerts)[ptId1];
228  cutData->push_back(cellCutData);
229  }
230  }
231  }
232 
233  return cutData;
234 }
235 
236 std::shared_ptr<std::vector<CutData>>
237 LineMeshCut::generateSurfaceMeshCutData(std::shared_ptr<SurfaceMesh> cuttingGeom,
238  std::shared_ptr<LineMesh> geomToCut)
239 {
240  auto cutData = std::make_shared<std::vector<CutData>>();
241 
242  std::shared_ptr<VecDataArray<int, 3>> cutTriangles = cuttingGeom->getCells();
243  std::shared_ptr<VecDataArray<double, 3>> cutVertices = cuttingGeom->getVertexPositions();
244  std::shared_ptr<VecDataArray<int, 2>> lines = geomToCut->getCells();
245  std::shared_ptr<VecDataArray<double, 3>> vertices = geomToCut->getVertexPositions();
246 
247  // compute cutting plane (assuming all triangles in cutSurf are co-planar)
248  const Vec3d p0 = (*cutVertices)[(*cutTriangles)[0][0]];
249  const Vec3d p1 = (*cutVertices)[(*cutTriangles)[0][1]];
250  const Vec3d p2 = (*cutVertices)[(*cutTriangles)[0][2]];
251  const Vec3d cutNormal = (p1 - p0).cross(p2 - p0);
252  auto cutPlane = std::make_shared<Plane>(p0, cutNormal);
253 
254  // Compute cut data using infinite cutPlane
255  std::shared_ptr<std::vector<CutData>> planeCutData =
256  generateImplicitCutData(cutPlane, geomToCut);
257 
258  // Remove cutData that are out of the cutSurf
259  for (const CutData& cellCutData : *planeCutData)
260  {
261  const bool coord0In = pointProjectionInSurface(cellCutData.cutCoords[0], cuttingGeom);
262  const bool coord1In = pointProjectionInSurface(cellCutData.cutCoords[1], cuttingGeom);
263  if (coord0In && coord1In)
264  {
265  cutData->push_back(cellCutData);
266  }
267  }
268 
269  return cutData;
270 }
271 } // namespace imstk
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.
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.
Base class for all volume mesh types.
Definition: imstkLineMesh.h:18
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.
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.