iMSTK
Interactive Medical Simulation Toolkit
All Classes Namespaces Functions Variables Typedefs Enumerations Enumerator Friends Pages
imstkSurfaceMesh.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 "imstkSurfaceMesh.h"
8 #include "imstkLogger.h"
9 #include "imstkVecDataArray.h"
10 #include "imstkGeometryUtilities.h"
11 
12 namespace imstk
13 {
14 void
16  std::shared_ptr<VecDataArray<int, 3>> triangleIndices,
17  const bool computeDerivedData)
18 {
19  CellMesh::initialize(vertices, triangleIndices);
20 
21  if (computeDerivedData)
22  {
23  this->computeVertexToCellMap();
24  //this->computeUVSeamVertexGroups();
25 
26  this->computeVertexNormals();
27  this->computeVertexTangents();
28  }
29 }
30 
31 void
33  std::shared_ptr<VecDataArray<int, 3>> triangleIndices,
34  std::shared_ptr<VecDataArray<double, 3>> normals,
35  const bool computeDerivedData)
36 {
37  this->initialize(vertices, triangleIndices, computeDerivedData);
38 
39  setVertexAttribute("normals", normals);
40 
41  if (computeDerivedData)
42  {
43  this->computeVertexToCellMap();
45  this->computeVertexNormals();
46  this->computeVertexTangents();
47  }
48 }
49 
50 double
52 {
53  // Hack to make shared_ptr of this, doesn't delete this
54  std::shared_ptr<SurfaceMesh> surfMesh(this, [](SurfaceMesh*) {});
55  if (GeometryUtils::isClosed(surfMesh))
56  {
57  return GeometryUtils::getVolume(surfMesh);
58  }
59  else
60  {
61  LOG(WARNING) << "SurfaceMesh not closed";
62  return 0.0;
63  }
64 }
65 
66 void
68 {
69  // Avoid reallocating if same size
70  std::shared_ptr<VecDataArray<double, 3>> triangleNormalsPtr = getCellNormals();
71  if (triangleNormalsPtr == nullptr)
72  {
73  triangleNormalsPtr = std::make_shared<VecDataArray<double, 3>>(m_indices->size());
74  }
75  else
76  {
77  if (m_indices->size() != triangleNormalsPtr->size())
78  {
79  triangleNormalsPtr->resize(m_indices->size());
80  }
81  }
82  VecDataArray<double, 3>& triangleNormals = *triangleNormalsPtr;
83 
84  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
85  const VecDataArray<int, 3>& indices = *m_indices;
86  for (int triangleId = 0; triangleId < triangleNormals.size(); ++triangleId)
87  {
88  const auto& t = indices[triangleId];
89  const auto& p0 = vertices[t[0]];
90  const auto& p1 = vertices[t[1]];
91  const auto& p2 = vertices[t[2]];
92 
93  triangleNormals[triangleId] = ((p1 - p0).cross(p2 - p0)).normalized();
94  }
95  setCellNormals("normals", triangleNormalsPtr);
96 }
97 
98 void
100 {
101  std::shared_ptr<VecDataArray<float, 2>> uvsPtr = getVertexTCoords();
102  if (uvsPtr != nullptr)
103  {
104  // Get the tangents, avoid reallocating if possible
105  std::shared_ptr<VecDataArray<double, 3>> triangleTangentsPtr = getCellTangents();
106  if (triangleTangentsPtr == nullptr)
107  {
108  triangleTangentsPtr = std::make_shared<VecDataArray<double, 3>>(m_indices->size());
109  }
110  else
111  {
112  if (m_indices->size() != triangleTangentsPtr->size())
113  {
114  triangleTangentsPtr->resize(m_indices->size());
115  }
116  }
117  VecDataArray<double, 3>& triangleTangents = *triangleTangentsPtr;
118 
119  // Get the normals, compute if we need too
120  std::shared_ptr<VecDataArray<double, 3>> triangleNormalsPtr = getCellNormals();
121  if (triangleNormalsPtr == nullptr)
122  {
124  }
125  triangleNormalsPtr = getCellNormals();
126  const VecDataArray<double, 3>& triangleNormals = *triangleNormalsPtr;
127  const VecDataArray<float, 2>& uvs = *uvsPtr;
128  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
129  const VecDataArray<int, 3>& indices = *m_indices;
130  for (int triangleId = 0; triangleId < triangleNormals.size(); ++triangleId)
131  {
132  const Vec3i& t = indices[triangleId];
133  const Vec3d& p0 = vertices[t[0]];
134  const Vec3d& p1 = vertices[t[1]];
135  const Vec3d& p2 = vertices[t[2]];
136  const Vec2f& uv0 = uvs[t[0]];
137  const Vec2f& uv1 = uvs[t[1]];
138  const Vec2f& uv2 = uvs[t[2]];
139 
140  const Vec3d diffPos1 = p1 - p0;
141  const Vec3d diffPos2 = p2 - p0;
142  const float diffUV1[2] = { uv1[0] - uv0[0], uv1[1] - uv0[1] };
143  const float diffUV2[2] = { uv2[0] - uv0[0], uv2[1] - uv0[1] };
144 
145  triangleTangents[triangleId] = (diffPos1 * diffUV2[1] - diffPos2 * diffUV1[0]) /
146  (diffUV1[0] * diffUV2[1] - diffUV1[1] * diffUV2[0]);
147  }
148  setCellTangents("tangents", triangleTangentsPtr);
149  }
150 }
151 
152 void
154 {
155  // Try to not to reallocate if we don't have too
156  std::shared_ptr<VecDataArray<double, 3>> vertexNormalsPtr = getVertexNormals();
157  if (vertexNormalsPtr == nullptr)
158  {
159  vertexNormalsPtr = std::make_shared<VecDataArray<double, 3>>(m_vertexPositions->size());
160  }
161  else
162  {
163  if (m_vertexPositions->size() != vertexNormalsPtr->size())
164  {
165  vertexNormalsPtr->resize(m_vertexPositions->size());
166  }
167  }
168  VecDataArray<double, 3>& vertexNormals = *vertexNormalsPtr;
169 
170  // First we must compute per triangle normals
171  this->computeTrianglesNormals();
172 
173  this->computeVertexToCellMap();
174 
175  // Sum them all into temp_normals
176  VecDataArray<double, 3> temp_normals(vertexNormals.size());
177  std::shared_ptr<VecDataArray<double, 3>> triangleNormalsPtr = getCellNormals();
178  const VecDataArray<double, 3>& triangleNormals = *triangleNormalsPtr;
179  for (int vertexId = 0; vertexId < vertexNormals.size(); ++vertexId)
180  {
181  temp_normals[vertexId] = Vec3d(0.0, 0.0, 0.0);
182  for (const int triangleId : m_vertexToCells.at(vertexId))
183  {
184  temp_normals[vertexId] += triangleNormals[triangleId];
185  }
186  }
187 
188  // Correct for UV seams
189  Vec3d normal;
190  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
191  for (int vertexId = 0; vertexId < vertexNormals.size(); ++vertexId)
192  {
193  NormalGroup group = { vertices[vertexId], vertexNormals[vertexId] };
194 
195  normal = temp_normals[vertexId];
196 
197  if (m_UVSeamVertexGroups.find(group) == m_UVSeamVertexGroups.end())
198  {
199  normal.normalize();
200  vertexNormals[vertexId] = normal;
201  continue;
202  }
203 
204  auto seamGroup = *m_UVSeamVertexGroups[group].get();
205 
206  for (auto index : seamGroup)
207  {
208  normal += temp_normals[index];
209  }
210 
211  normal.normalize();
212  vertexNormals[vertexId] = normal;
213  }
214 
215  setVertexNormals("normals", vertexNormalsPtr);
216 }
217 
218 void
220 {
221  const bool hasUVs = hasVertexAttribute(m_activeVertexTCoords);
222  if (hasUVs)
223  {
224  // Avoid reallocating if possible
225  std::shared_ptr<VecDataArray<float, 3>> vertexTangentsPtr = getVertexTangents();
226  if (vertexTangentsPtr == nullptr)
227  {
228  vertexTangentsPtr = std::make_shared<VecDataArray<float, 3>>(m_vertexPositions->size());
229  }
230  else
231  {
232  if (m_vertexPositions->size() != vertexTangentsPtr->size())
233  {
234  vertexTangentsPtr->resize(m_vertexPositions->size());
235  }
236  }
237  VecDataArray<float, 3>& vertexTangents = *vertexTangentsPtr;
238 
239  // First we need per triangle tangents
240  this->computeTriangleTangents();
241 
242  VecDataArray<double, 3> temp_vertex_tangents(vertexTangents.size());
243  std::shared_ptr<VecDataArray<double, 3>> triangleTangentsPtr = getCellTangents();
244  const VecDataArray<double, 3>& triangleTangents = *triangleTangentsPtr;
245  for (int vertexId = 0; vertexId < vertexTangents.size(); ++vertexId)
246  {
247  temp_vertex_tangents[vertexId] = Vec3d(0.0, 0.0, 0.0);
248  for (const int triangleId : m_vertexToCells.at(vertexId))
249  {
250  temp_vertex_tangents[vertexId] += triangleTangents[triangleId];
251  }
252  }
253 
254  // Correct for UV seams
255  Vec3d tangent;
256  for (int vertexId = 0; vertexId < vertexTangents.size(); ++vertexId)
257  {
258  tangent = temp_vertex_tangents[vertexId];
259  tangent.normalize();
260  vertexTangents[vertexId] = tangent.cast<float>();
261  }
262 
263  setVertexTangents("tangents", vertexTangentsPtr);
264  }
265  else
266  {
267  LOG(FATAL) << "Tried to compute per vertex tangents for mesh with no UVs";
268  }
269 }
270 
271 Vec3d
272 SurfaceMesh::computeBarycentricWeights(const int triId, const Vec3d& pos) const
273 {
274  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
275  const VecDataArray<int, 3>& triIndices = *m_indices;
276  return baryCentric(pos,
277  vertices[triIndices[triId][0]],
278  vertices[triIndices[triId][1]],
279  vertices[triIndices[triId][2]]);
280 }
281 
282 void
284 {
285  const size_t numVertices = this->getNumVertices();
286  const size_t numTriangles = this->getNumCells();
287 
288  // First find the list of triangles a given vertex is part of
289  std::vector<std::vector<int>> vertexNeighbors;
290  vertexNeighbors.resize(this->getNumVertices());
291  int triangleId = 0;
292  VecDataArray<int, 3>& triangleIndices = *m_indices;
293  for (const auto& tri : triangleIndices)
294  {
295  vertexNeighbors[tri[0]].push_back(triangleId);
296  vertexNeighbors[tri[1]].push_back(triangleId);
297  vertexNeighbors[tri[2]].push_back(triangleId);
298 
299  triangleId++;
300  }
301 
302  std::shared_ptr<VecDataArray<int, 3>> optimizedConnectivityPtr = std::make_shared<VecDataArray<int, 3>>();
303  VecDataArray<int, 3>& optimizedConnectivity = *optimizedConnectivityPtr;
304  std::vector<int> optimallyOrderedNodes;
305  std::list<int> triUnderConsideration;
306  std::vector<bool> isNodeAdded(numVertices, false);
307  std::vector<bool> isTriangleAdded(numTriangles, false);
308  std::list<int> newlyAddedNodes;
309 
310  // A. Initialize
311  optimallyOrderedNodes.push_back(0);
312  isNodeAdded.at(0) = true;
313  for (const auto& neighTriId : vertexNeighbors[0])
314  {
315  triUnderConsideration.push_back(neighTriId);
316  }
317 
318  // B. Iterate till all the nodes are added to optimized mesh
319  int vertId[3];
320 
321  while (!triUnderConsideration.empty())
322  {
323  // B.1 Add new nodes and triangles
324  for (const auto& triId : triUnderConsideration)
325  {
326  for (int i = 0; i < 3; ++i)
327  {
328  if (!isNodeAdded.at(triangleIndices[triId][i]))
329  {
330  optimallyOrderedNodes.push_back(triangleIndices[triId][i]);
331  isNodeAdded.at(triangleIndices[triId][i]) = true;
332  newlyAddedNodes.push_back(triangleIndices[triId][i]);
333  }
334  vertId[i] = *std::find(optimallyOrderedNodes.begin(),
335  optimallyOrderedNodes.end(),
336  triangleIndices[triId][i]);
337  }
338  optimizedConnectivity.push_back(Vec3i(vertId[0], vertId[1], vertId[2]));
339  isTriangleAdded.at(triId) = true;
340  }
341 
342  // B.2 Setup triangles to be considered for next iteration
343  triUnderConsideration.clear();
344  for (const auto& newNodes : newlyAddedNodes)
345  {
346  for (const auto& neighTriId : vertexNeighbors[newNodes])
347  {
348  if (!isTriangleAdded[neighTriId])
349  {
350  triUnderConsideration.push_back(neighTriId);
351  }
352  }
353  }
354  triUnderConsideration.sort();
355  triUnderConsideration.unique();
356 
357  newlyAddedNodes.clear();
358  }
359 
360  // C. Initialize this mesh with the newly computed ones
361  std::shared_ptr<VecDataArray<double, 3>> optimallyOrderedNodalPos = std::make_shared<VecDataArray<double, 3>>();
362  std::shared_ptr<VecDataArray<int, 3>> optConnectivityRenumbered = std::make_shared<VecDataArray<int, 3>>();
363 
364  // C.1 Get the positions
365  optimallyOrderedNodalPos->reserve(static_cast<int>(optimallyOrderedNodes.size()));
366  for (const auto& nodalId : optimallyOrderedNodes)
367  {
368  optimallyOrderedNodalPos->push_back(this->getInitialVertexPosition(nodalId));
369  }
370 
371  // C.2 Get the renumbered connectivity
372  for (size_t i = 0; i < numTriangles; ++i)
373  {
374  for (int j = 0; j < 3; ++j)
375  {
376  vertId[j] = (std::find(optimallyOrderedNodes.begin(),
377  optimallyOrderedNodes.end(),
378  optimizedConnectivity[i][j]) -
379  optimallyOrderedNodes.begin());
380  }
381 
382  Vec3i tmpTriArray = Vec3i(vertId[0], vertId[1], vertId[2]);
383  optConnectivityRenumbered->push_back(tmpTriArray);
384  }
385 
386  // D. Assign the rewired mesh data to the mesh
387  this->initialize(optimallyOrderedNodalPos, optConnectivityRenumbered);
388 }
389 
390 void
392 {
393  for (auto& tri : *m_indices)
394  {
395  std::swap(tri[0], tri[1]);
396  }
397 }
398 
399 void
401 {
402  // Enforce consistency in winding of a particular triangle with its neighbor (parent)
403  VecDataArray<int, 3>& indices = *m_indices;
404  auto enforceWindingConsistency =
405  [&](const size_t masterTriId, const size_t neighTriId)
406  {
407  const Vec3i& parentTri = indices[masterTriId];
408  Vec3i& neighTri = indices[neighTriId];
409 
410  for (unsigned int l = 0; l < 3; ++l)
411  {
412  for (unsigned int k = 0; k < 3; ++k)
413  {
414  if (parentTri[k] == neighTri[l] && parentTri[(k + 1) % 3] == neighTri[(l + 1) % 3])
415  {
416  // Flip the order of neighbor triangle
417  auto tempId = neighTri[0];
418  neighTri[0] = neighTri[1];
419  neighTri[1] = tempId;
420  break;
421  }
422  }
423  }
424  };
425 
426  // Search for triangle neighbors that share a common edge
427  auto getTriangleNeighbors =
428  [&](const size_t triID, int* neig)
429  {
430  const auto& currentTri = indices[triID];
431  size_t currentId = 0;
432  int numNeigh = 0;
433  for (int j = 0; j < indices.size(); j++)
434  {
435  Vec3i& tri = indices[j];
436  if (triID == currentId)
437  {
438  currentId++;
439  continue;
440  }
441 
442  int numCommon = 0;
443  for (int i = 0; i < 3; ++i)
444  {
445  if (currentTri[i] == tri[0] || currentTri[i] == tri[1] || currentTri[i] == tri[2])
446  {
447  numCommon++;
448  if (numCommon == 2)
449  {
450  neig[numNeigh] = (int)currentId;
451  numNeigh++;
452 
453  if (numNeigh == 3)
454  {
455  return;
456  }
457  else
458  {
459  break;
460  }
461  }
462  }
463  }
464  currentId++;
465  }
466  };
467 
468  // Start with a reference triangle and enforce the consistency of its neighbors
469  // Keep track of those neighbor triangles whose order is enforced but its neighbors not
470  // necessarily enforced (trianglesCorrected). Continue this until there is no
471  // triangle left in the list
472  std::vector<bool> trianglesCorrected(this->getNumCells(), false);
473  std::vector<size_t> correctedTriangles;
474 
475  size_t currentTriangle = 0; // Start with triangle 0
476  correctedTriangles.push_back(currentTriangle);
477  trianglesCorrected[currentTriangle] = true;
478  do
479  {
480  currentTriangle = correctedTriangles[0];
481  int neighborTri[3] = { -1, -1, -1 };
482  getTriangleNeighbors(currentTriangle, &neighborTri[0]);
483 
484  for (int i = 0; i < 3; ++i)
485  {
486  if (neighborTri[i] >= 0 && !trianglesCorrected[neighborTri[i]])
487  {
488  enforceWindingConsistency(currentTriangle, neighborTri[i]);
489 
490  correctedTriangles.push_back(neighborTri[i]);
491  trianglesCorrected[neighborTri[i]] = true;
492  }
493  }
494 
495  correctedTriangles.erase(
496  std::remove(correctedTriangles.begin(), correctedTriangles.end(), currentTriangle),
497  correctedTriangles.end());
498  }
499  while (correctedTriangles.size() > 0);
500 }
501 
502 void
504 {
505  // Reset vertex groups
506  m_UVSeamVertexGroups.clear();
507 
508  std::shared_ptr<VecDataArray<double, 3>> vertexNormalsPtr = getVertexNormals();
509  if (m_vertexPositions->size() != vertexNormalsPtr->size())
510  {
511  return;
512  }
513 
514  // Initial pass to bin vertices based on positions
515  const VecDataArray<double, 3>& vertexNormals = *vertexNormalsPtr;
516  const VecDataArray<double, 3>& vertices = *m_vertexPositions;
517  for (int i = 0; i < vertices.size(); i++)
518  {
519  NormalGroup group = { vertices[i], vertexNormals[i] };
520 
521  if (m_UVSeamVertexGroups.find(group) == m_UVSeamVertexGroups.end())
522  {
523  m_UVSeamVertexGroups.insert(
524  std::pair<NormalGroup, std::shared_ptr<std::vector<size_t>>>(
525  group, std::make_shared<std::vector<size_t>>()));
526  }
527  m_UVSeamVertexGroups[group]->push_back(i);
528  }
529 }
530 
532 SurfaceMesh::cloneImplementation() const
533 {
534  // Do shallow copy
535  SurfaceMesh* geom = new SurfaceMesh(*this);
536  // Deal with deep copy members
537  geom->m_indices = std::make_shared<VecDataArray<int, 3>>(*m_indices);
538  for (auto i : m_cellAttributes)
539  {
540  geom->m_cellAttributes[i.first] = i.second->clone();
541  }
542  geom->m_initialVertexPositions = std::make_shared<VecDataArray<double, 3>>(*m_initialVertexPositions);
543  geom->m_vertexPositions = std::make_shared<VecDataArray<double, 3>>(*m_vertexPositions);
544  for (auto i : m_vertexAttributes)
545  {
546  geom->m_vertexAttributes[i.first] = i.second->clone();
547  }
548  return geom;
549 }
550 } // namespace imstk
void setVertexNormals(const std::string &arrayName, std::shared_ptr< VecDataArray< double, 3 >> normals)
Get/Set the active normals.
void setCellNormals(const std::string &arrayName, std::shared_ptr< VecDataArray< double, 3 >> normals)
Get/Set the active normals.
void setVertexTangents(const std::string &arrayName, std::shared_ptr< VecDataArray< float, 3 >> tangents)
Get/Set the active tangents.
bool isClosed(std::shared_ptr< SurfaceMesh > surfMesh)
Returns if the surface is closed or not.
bool hasVertexAttribute(const std::string &arrayName) const
Check if a specific data array exists.
Compound Geometry.
void optimizeForDataLocality()
Rewire the node order and triangle connectivity to optimize for memory layout The intended use is for...
void initialize(std::shared_ptr< VecDataArray< double, 3 >> vertices, std::shared_ptr< VecDataArray< int, 3 >> triangleIndices, const bool computeDerivedData=false)
Initializes the rest of the data structures given vertex positions and triangle connectivity.
void initialize(std::shared_ptr< VecDataArray< double, 3 >> vertices, std::shared_ptr< VecDataArray< int, N >> indices)
Initializes the rest of the data structures given vertex positions and connectivity.
Definition: imstkCellMesh.h:38
void correctWindingOrder()
Enforces consistency in the winding order of the triangles.
void computeVertexNormals()
Computes the normals of all the vertices.
void setVertexAttribute(const std::string &arrayName, std::shared_ptr< AbstractDataArray > arr)
Set a data array holding some per vertex data.
int getNumCells() const override
Returns the number of cells.
void flipNormals()
Flip the normals for the whole mesh by reversing the winding order.
Vec3d computeBarycentricWeights(const int tetId, const Vec3d &pos) const override
compute the barycentric weights of a given point in 3D space for a given the triangle ...
void computeTriangleTangents()
Compute the tangents of all the triangles based off.
std::vector< std::unordered_set< int > > m_vertexToCells
Map of vertices to neighbor cells.
void setCellTangents(const std::string &arrayName, std::shared_ptr< VecDataArray< double, 3 >> tangents)
Get/Set the active tangents.
void computeUVSeamVertexGroups()
Finds vertices along vertex seams that share geometric properties.
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
VecDataArray< U, N > cast()
Templated copy the current array with a new internal data type, does not change the number of compone...
Vec3d & getInitialVertexPosition(const size_t vertNum)
Returns the initial position of a vertex given its index.
double getVolume() override
Get the volume enclosed by the surface mesh.
int getNumVertices() const
Returns the number of total vertices in the mesh.
void computeVertexTangents()
Comptues the tangents of all the vertices.
double getVolume(std::shared_ptr< SurfaceMesh > surfMesh)
Returns volume estimate of closed SurfaceMesh.
void computeTrianglesNormals()
Compute the normals of all the triangles.
Helper class for indentifying duplicate points.
void computeVertexToCellMap() override
Computes neighboring cells for all vertices.
Definition: imstkCellMesh.h:64