iMSTK
Interactive Medical Simulation Toolkit
imstkTetrahedralMesh.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 "imstkTetrahedralMesh.h"
8 #include "imstkLogger.h"
9 #include "imstkParallelUtils.h"
10 #include "imstkGeometryUtilities.h"
11 #include "imstkSurfaceMesh.h"
12 
13 namespace imstk
14 {
15 double
17 {
18  double volume = 0.0;
19  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
20  for (const Vec4i& tet : *m_indices)
21  {
22  const double tetVol = tetVolume(vertices[tet[0]], vertices[tet[1]], vertices[tet[2]], vertices[tet[3]]);
23  if (tetVol < 0.0)
24  {
25  LOG(WARNING) << "Tetrahedron is inverted, has negative volume!";
26  }
27  volume += tetVol;
28  }
29 
30  return volume;
31 }
32 
33 void
34 TetrahedralMesh::setStrainParameters(std::shared_ptr<VecDataArray<double, 3>> strainParameters)
35 {
36  CHECK(strainParameters->size() == m_indices->size()) << "Strain parameters must be the same size as the number of tetrahedra";
37  setCellAttribute(StrainParameterName, strainParameters);
38 }
39 
40 std::shared_ptr<imstk::VecDataArray<double, 3>>
42 {
43  std::shared_ptr<VecDataArray<double, 3>> params;
44  if (hasCellAttribute(StrainParameterName))
45  {
46  params = std::dynamic_pointer_cast<VecDataArray<double, 3>>(getCellAttribute(StrainParameterName));
47  }
48 
49  if (params == nullptr)
50  {
51  params = std::make_shared<VecDataArray<double, 3>>(m_indices->size());
52  for (int i = 0; i < params->size(); ++i)
53  {
54  (*params)[i] = Vec3d(-1.0, 0.0, 0.0);
55  }
56  setCellAttribute(StrainParameterName, params);
57  }
58 
59  if (params->size() != m_indices->size())
60  {
61  LOG(WARNING) << "Strain parameters are not the same size as the number of tetrahedra";
62  return nullptr;
63  }
64 
65  return params;
66 }
67 
68 std::string TetrahedralMesh::StrainParameterName = "StrainParameters";
69 
70 std::shared_ptr<SurfaceMesh>
72 {
73  const std::array<Vec3i, 4> facePattern = {
74  Vec3i(0, 1, 2), Vec3i(0, 1, 3), Vec3i(0, 2, 3), Vec3i(1, 2, 3)
75  };
76  const std::array<int, 4> unusedVert = { 3, 2, 1, 0 };
77 
78  // Find and store the tetrahedral faces that are unique
79  const VecDataArray<int, 4>& tetraIndices = *m_indices;
80  std::shared_ptr<VecDataArray<double, 3>> tetVerticesPtr = getVertexPositions();
81  const VecDataArray<double, 3>& tetVertices = *tetVerticesPtr;
82  std::shared_ptr<VecDataArray<int, 3>> triIndicesPtr = std::make_shared<VecDataArray<int, 3>>();
83  VecDataArray<int, 3>& triIndices = *triIndicesPtr;
84  //std::vector<size_t> surfaceTriTet;
85 
86  // Gives surfaceTri id/faceid -> index of unused vert for face (4 verts per tet, one will be unused)
87  std::vector<size_t> tetRemainingVert;
88 
89  bool unique;
90  int foundAt;
91  int a, b, c;
92 
93  // For every tetrahedron
94  for (int i = 0; i < tetraIndices.size(); i++)
95  {
96  const Vec4i& tet = tetraIndices[i];
97 
98  // For every triangle face of the tetrahedron
99  for (int t = 0; t < 4; ++t)
100  {
101  unique = true;
102  foundAt = 0;
103  a = tet[facePattern[t][0]];
104  b = tet[facePattern[t][1]];
105  c = tet[facePattern[t][2]];
106 
107  // Search in reverse for matching face (consider using hash/unordered or ordered binary tree instead)
108  for (int j = triIndices.size() - 1; j != -1; j--)
109  {
110  const Vec3i& tri = triIndices[j];
111  // Checks all equivalence permutations
112  if (((tri[0] == a)
113  && ((tri[1] == b && tri[2] == c) || (tri[1] == c && tri[2] == b)))
114  || ((tri[1] == a)
115  && ((tri[0] == b && tri[2] == c) || (tri[0] == c && tri[2] == b)))
116  || ((tri[2] == a)
117  && ((tri[1] == b && tri[0] == c) || (tri[1] == c && tri[0] == b))))
118  {
119  unique = false;
120  foundAt = j;
121  break;
122  }
123  }
124 
125  // If not found yet, insert as potentially unique face
126  if (unique)
127  {
128  triIndices.push_back(Vec3i(a, b, c));
129  //surfaceTriTet.push_back(tetId);
130  tetRemainingVert.push_back(tet[unusedVert[t]]);
131  }
132  // If found, erase face, it is not unique anymore
133  else
134  {
135  triIndices.erase(foundAt);
136  tetRemainingVert.erase(tetRemainingVert.begin() + foundAt);
137  }
138  }
139  }
140  // Finally we end up with a set of unique faces, surfaceTri
141 
142  // Ensure all faces are have correct windings (such that interior vertex of the tet is inside)
143  for (int i = 0; i < triIndices.size(); i++)
144  {
145  const Vec3d& v0 = tetVertices[triIndices[i][0]];
146  const Vec3d& v1 = tetVertices[triIndices[i][1]];
147  const Vec3d& v2 = tetVertices[triIndices[i][2]];
148  const Vec3d normal = ((v1 - v0).cross(v2 - v0));
149  const Vec3d centroid = (v0 + v1 + v2) / 3.0;
150 
151  // Vertex that does not contribute to the face
152  const Vec3d unusedVertex = tetVertices[tetRemainingVert.at(i)];
153 
154  // If the normal is correct, it should be pointing in the same direction as the (face centroid-unusedVertex)
155  if (normal.dot(centroid - unusedVertex) < 0)
156  {
157  std::swap(triIndices[i][2], triIndices[i][1]);
158  }
159  }
160 
161  // All the existing triangles are still pointing to the old vertex buffer
162  // we need to reindex and make a new vertex buffer
163 
164  // Create a map of old to new indices
165  std::unordered_map<int, int> oldToNewVertId;
166  for (int i = 0; i < triIndices.size(); i++)
167  {
168  Vec3i& face = triIndices[i];
169 
170  // If the vertex hasn't been reassigned
171  if (oldToNewVertId.count(face[0]) == 0)
172  {
173  // Use size as new index
174  const int newVertexId = static_cast<int>(oldToNewVertId.size());
175  oldToNewVertId[face[0]] = newVertexId;
176  face[0] = newVertexId; // Relabel the old one
177  }
178  // If the vertex has already been reassigned
179  else
180  {
181  face[0] = oldToNewVertId[face[0]];
182  }
183 
184  if (oldToNewVertId.count(face[1]) == 0)
185  {
186  const int newVertexId = static_cast<int>(oldToNewVertId.size());
187  oldToNewVertId[face[1]] = newVertexId;
188  face[1] = newVertexId;
189  }
190  else
191  {
192  face[1] = oldToNewVertId[face[1]];
193  }
194 
195  if (oldToNewVertId.count(face[2]) == 0)
196  {
197  const int newVertexId = static_cast<int>(oldToNewVertId.size());
198  oldToNewVertId[face[2]] = newVertexId;
199  face[2] = newVertexId;
200  }
201  else
202  {
203  face[2] = oldToNewVertId[face[2]];
204  }
205  }
206 
207  auto triVerticesPtr = std::make_shared<VecDataArray<double, 3>>(static_cast<int>(oldToNewVertId.size()));
208  VecDataArray<double, 3>& triVertices = *triVerticesPtr;
209 
210  for (auto vertIndexPair : oldToNewVertId)
211  {
212  const int tetVertId = vertIndexPair.first;
213  const int triVertId = vertIndexPair.second;
214  // Copy the vertex over
215  triVertices[triVertId] = tetVertices[tetVertId];
216  }
217 
218  // \todo: Copy over attributes (can't be done yet as type copying of data arrays is not possible)
219 
220  // Create and attach surface mesh
221  auto surfMesh = std::make_shared<SurfaceMesh>();
222  surfMesh->initialize(triVerticesPtr, triIndicesPtr);
223  return surfMesh;
224 }
225 
226 Vec4d
227 TetrahedralMesh::computeBarycentricWeights(const int tetId, const Vec3d& pos) const
228 {
229  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
230  const VecDataArray<int, 4>& tetraIndices = *m_indices;
231  return baryCentric(pos,
232  vertices[tetraIndices[tetId][0]],
233  vertices[tetraIndices[tetId][1]],
234  vertices[tetraIndices[tetId][2]],
235  vertices[tetraIndices[tetId][3]]);
236 }
237 
238 void
239 TetrahedralMesh::computeTetrahedronBoundingBox(const size_t& tetId, Vec3d& min, Vec3d& max) const
240 {
241  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
242  const VecDataArray<int, 4>& tetraIndices = *m_indices;
243  auto v1 = vertices[tetraIndices[tetId][0]];
244  auto v2 = vertices[tetraIndices[tetId][1]];
245  auto v3 = vertices[tetraIndices[tetId][2]];
246  auto v4 = vertices[tetraIndices[tetId][3]];
247 
248  std::array<double, 4> arrayx = { v1[0], v2[0], v3[0], v4[0] };
249  std::array<double, 4> arrayy = { v1[1], v2[1], v3[1], v4[1] };
250  std::array<double, 4> arrayz = { v1[2], v2[2], v3[2], v4[2] };
251 
252  min[0] = *std::min_element(arrayx.begin(), arrayx.end());
253  min[1] = *std::min_element(arrayy.begin(), arrayy.end());
254  min[2] = *std::min_element(arrayz.begin(), arrayz.end());
255 
256  max[0] = *std::max_element(arrayx.begin(), arrayx.end());
257  max[1] = *std::max_element(arrayy.begin(), arrayy.end());
258  max[2] = *std::max_element(arrayz.begin(), arrayz.end());
259 }
260 
262 TetrahedralMesh::cloneImplementation() const
263 {
264  // Do shallow copy
265  TetrahedralMesh* geom = new TetrahedralMesh(*this);
266  // Deal with deep copy members
267  geom->m_indices = std::make_shared<VecDataArray<int, 4>>(*m_indices);
268  for (auto i : m_cellAttributes)
269  {
270  geom->m_cellAttributes[i.first] = i.second->clone();
271  }
272  geom->m_initialVertexPositions = std::make_shared<VecDataArray<double, 3>>(*m_initialVertexPositions);
273  geom->m_vertexPositions = std::make_shared<VecDataArray<double, 3>>(*m_vertexPositions);
274  for (auto i : m_vertexAttributes)
275  {
276  geom->m_vertexAttributes[i.first] = i.second->clone();
277  }
278  return geom;
279 }
280 } // namespace imstk
Compound Geometry.
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.
Represents a set of tetrahedrons & vertices via an array of Vec3d double vertices & Vec4i integer ind...
std::shared_ptr< VecDataArray< double, 3 > > getStrainParameters()
Get/Set the strain parameters for the tetrahedral mesh the strain parameters are expected to be a Vec...
void computeTetrahedronBoundingBox(const size_t &tetId, Vec3d &min, Vec3d &max) const
Compute the bounding box of a given tetrahedron.
std::shared_ptr< VecDataArray< double, 3 > > getVertexPositions(DataType type=DataType::PostTransform) const
Returns the vector of current positions of the mesh vertices.
Vec4d computeBarycentricWeights(const int tetId, const Vec3d &pos) const override
compute the barycentric weights of a given point in 3D space for a given the tetrahedra ...
bool hasCellAttribute(const std::string &arrayName) const
Check if a specific data array exists.
double getVolume() override
Compute and return the volume of the tetrahedral mesh.