ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvTetraMesh.cpp
Go to the documentation of this file.
1 // ----------------------------------------------------------------------------
2 // - CloudViewer: www.cloudViewer.org -
3 // ----------------------------------------------------------------------------
4 // Copyright (c) 2018-2024 www.cloudViewer.org
5 // SPDX-License-Identifier: MIT
6 // ----------------------------------------------------------------------------
7 
8 #include "ecvTetraMesh.h"
9 
10 // EIGEN
11 #include <Eigen/Dense>
12 
13 // SYSTEM
14 #include <array>
15 #include <numeric>
16 #include <tuple>
17 #include <unordered_map>
18 
19 // LOCAL
20 #include <Logging.h>
21 
22 #include "ecvHObjectCaster.h"
23 #include "ecvMesh.h"
24 #include "ecvPointCloud.h"
25 
26 namespace cloudViewer {
27 namespace geometry {
28 
31  tetras_.clear();
32  return *this;
33 }
34 
36  typedef decltype(tetras_)::value_type Vector4i;
37  if (mesh.IsEmpty()) return (*this);
38  size_t old_vert_num = vertices_.size();
39  size_t old_tetra_num = tetras_.size();
40  size_t add_tetra_num = mesh.tetras_.size();
42  tetras_.resize(tetras_.size() + mesh.tetras_.size());
43  Vector4i index_shift = Vector4i::Constant(static_cast<int>(old_vert_num));
44  for (size_t i = 0; i < add_tetra_num; i++) {
45  tetras_[old_tetra_num + i] = mesh.tetras_[i] + index_shift;
46  }
47  return (*this);
48 }
49 
51  return (TetraMesh(*this) += mesh);
52 }
53 
55  typedef decltype(tetras_)::value_type::Scalar Index;
56  typedef std::tuple<double, double, double> Coordinate3;
57  std::unordered_map<Coordinate3, size_t,
59  point_to_old_index;
60  std::vector<Index> index_old_to_new(vertices_.size());
61  size_t old_vertex_num = vertices_.size();
62  size_t k = 0; // new index
63  for (size_t i = 0; i < old_vertex_num; i++) { // old index
64  Coordinate3 coord = std::make_tuple(vertices_[i](0), vertices_[i](1),
65  vertices_[i](2));
66  if (point_to_old_index.find(coord) == point_to_old_index.end()) {
67  point_to_old_index[coord] = i;
68  vertices_[k] = vertices_[i];
69  index_old_to_new[i] = (Index)k;
70  k++;
71  } else {
72  index_old_to_new[i] = index_old_to_new[point_to_old_index[coord]];
73  }
74  }
75  vertices_.resize(k);
76  if (k < old_vertex_num) {
77  for (auto &tetra : tetras_) {
78  tetra(0) = index_old_to_new[tetra(0)];
79  tetra(1) = index_old_to_new[tetra(1)];
80  tetra(2) = index_old_to_new[tetra(2)];
81  tetra(3) = index_old_to_new[tetra(3)];
82  }
83  }
85  "[RemoveDuplicatedVertices] {:d} vertices have been removed.",
86  (int)(old_vertex_num - k));
87 
88  return *this;
89 }
90 
92  typedef decltype(tetras_)::value_type::Scalar Index;
93  typedef std::tuple<Index, Index, Index, Index> Index4;
94  std::unordered_map<Index4, size_t, utility::hash_tuple<Index4>>
95  tetra_to_old_index;
96  size_t old_tetra_num = tetras_.size();
97  size_t k = 0;
98  for (size_t i = 0; i < old_tetra_num; i++) {
99  Index4 index;
100  std::array<Index, 4> t{tetras_[i](0), tetras_[i](1), tetras_[i](2),
101  tetras_[i](3)};
102 
103  // We sort the indices to find duplicates, because tetra (0-1-2-3)
104  // and tetra (2-0-3-1) are the same.
105  std::sort(t.begin(), t.end());
106  index = std::make_tuple(t[0], t[1], t[2], t[3]);
107 
108  if (tetra_to_old_index.find(index) == tetra_to_old_index.end()) {
109  tetra_to_old_index[index] = i;
110  tetras_[k] = tetras_[i];
111  k++;
112  }
113  }
114  tetras_.resize(k);
116  "[RemoveDuplicatedTetras] {:d} tetras have been removed.",
117  (int)(old_tetra_num - k));
118 
119  return *this;
120 }
121 
123  typedef decltype(tetras_)::value_type::Scalar Index;
124  std::vector<bool> vertex_has_reference(vertices_.size(), false);
125  for (const auto &tetra : tetras_) {
126  vertex_has_reference[tetra(0)] = true;
127  vertex_has_reference[tetra(1)] = true;
128  vertex_has_reference[tetra(2)] = true;
129  vertex_has_reference[tetra(3)] = true;
130  }
131  std::vector<Index> index_old_to_new(vertices_.size());
132  size_t old_vertex_num = vertices_.size();
133  size_t k = 0; // new index
134  for (size_t i = 0; i < old_vertex_num; i++) { // old index
135  if (vertex_has_reference[i]) {
136  vertices_[k] = vertices_[i];
137  index_old_to_new[i] = (Index)k;
138  k++;
139  } else {
140  index_old_to_new[i] = -1;
141  }
142  }
143  vertices_.resize(k);
144  if (k < old_vertex_num) {
145  for (auto &tetra : tetras_) {
146  tetra(0) = index_old_to_new[tetra(0)];
147  tetra(1) = index_old_to_new[tetra(1)];
148  tetra(2) = index_old_to_new[tetra(2)];
149  tetra(3) = index_old_to_new[tetra(3)];
150  }
151  }
153  "[RemoveUnreferencedVertices] {:d} vertices have been removed.",
154  (int)(old_vertex_num - k));
155 
156  return *this;
157 }
158 
160  size_t old_tetra_num = tetras_.size();
161  size_t k = 0;
162  for (size_t i = 0; i < old_tetra_num; i++) {
163  const auto &tetra = tetras_[i];
164  if (tetra(0) != tetra(1) && tetra(0) != tetra(2) &&
165  tetra(0) != tetra(3) && tetra(1) != tetra(2) &&
166  tetra(1) != tetra(3) && tetra(2) != tetra(3)) {
167  tetras_[k] = tetras_[i];
168  k++;
169  }
170  }
171  tetras_.resize(k);
173  "[RemoveDegenerateTetras] {:d} tetras have been "
174  "removed.",
175  (int)(old_tetra_num - k));
176  return *this;
177 }
178 
179 std::shared_ptr<ccMesh> TetraMesh::ExtractTriangleMesh(
180  const std::vector<double> &values, double level) {
181  typedef decltype(tetras_)::value_type::Scalar Index;
182  static_assert(std::is_signed<Index>(), "Index type must be signed");
183  typedef std::tuple<Index, Index> Index2;
184 
185  ccPointCloud *baseVertices = new ccPointCloud("vertices");
186  assert(baseVertices);
187  auto triangle_mesh = std::make_shared<ccMesh>(baseVertices);
188 
189  if (values.size() != vertices_.size()) {
191  "[ExtractTriangleMesh] number of values does not match the "
192  "number of vertices.");
193  }
194 
195  auto SurfaceIntersectionTest = [](double v0, double v1, double level) {
196  return (v0 < level && v1 >= level) || (v0 >= level && v1 < level);
197  };
198 
199  auto ComputeEdgeVertex = [&values, level, this](Index idx1, Index idx2) {
200  double v1 = values[idx1];
201  double v2 = values[idx2];
202 
203  double t = (level - v2) / (v1 - v2);
204  if (std::isnan(t) || t < 0 || t > 1) {
205  t = 0.5;
206  }
207  Eigen::Vector3d intersection =
208  t * vertices_[idx1] + (1 - t) * vertices_[idx2];
209 
210  return intersection;
211  };
212 
213  auto ComputeTriangleNormal = [](const Eigen::Vector3d &a,
214  const Eigen::Vector3d &b,
215  const Eigen::Vector3d &c) {
216  Eigen::Vector3d ab = b - a;
217  Eigen::Vector3d ac = c - a;
218  return ab.cross(ac);
219  };
220 
221  auto MakeSortedTuple2 = [](Index a, Index b) {
222  if (a < b)
223  return std::make_tuple(a, b);
224  else
225  return std::make_tuple(b, a);
226  };
227 
228  auto HasCommonVertexIndex = [](Index2 a, Index2 b) {
229  return std::get<0>(b) == std::get<0>(a) ||
230  std::get<1>(b) == std::get<0>(a) ||
231  std::get<0>(b) == std::get<1>(a) ||
232  std::get<1>(b) == std::get<1>(a);
233  };
234 
235  std::unordered_map<Index2, size_t, utility::hash_tuple<Index2>>
236  intersecting_edges;
237 
238  const int tetra_edges[][2] = {{0, 1}, {0, 2}, {0, 3},
239  {1, 2}, {1, 3}, {2, 3}};
240 
241  for (size_t tetra_i = 0; tetra_i < tetras_.size(); ++tetra_i) {
242  const auto &tetra = tetras_[tetra_i];
243 
244  std::array<Eigen::Vector3d, 4> verts;
245  std::array<Index2, 4> keys; // keys for the edges
246  std::array<Index, 4> verts_indices;
247  std::array<Eigen::Vector3d, 4> edge_dirs;
248  int num_verts = 0;
249 
250  for (int tet_edge_i = 0; tet_edge_i < 6; ++tet_edge_i) {
251  Index edge_vert1 = tetra[tetra_edges[tet_edge_i][0]];
252  Index edge_vert2 = tetra[tetra_edges[tet_edge_i][1]];
253  double vert_value1 = values[edge_vert1];
254  double vert_value2 = values[edge_vert2];
255  if (SurfaceIntersectionTest(vert_value1, vert_value2, level)) {
256  Index2 index = MakeSortedTuple2(edge_vert1, edge_vert2);
257  verts[num_verts] = ComputeEdgeVertex(edge_vert1, edge_vert2);
258  keys[num_verts] = index;
259 
260  // make edge_vert1 be the vertex that is smaller than level
261  // (inside)
262  if (values[edge_vert1] > values[edge_vert2])
263  std::swap(edge_vert1, edge_vert2);
264  edge_dirs[num_verts] =
265  vertices_[edge_vert2] - vertices_[edge_vert1];
266  ++num_verts;
267  }
268  }
269 
270  // add vertices and get the vertex indices
271  for (int i = 0; i < num_verts; ++i) {
272  if (intersecting_edges.count(keys[i]) == 0) {
273  Index idx = static_cast<Index>(intersecting_edges.size());
274  verts_indices[i] = idx;
275  intersecting_edges[keys[i]] = idx;
276  baseVertices->addEigenPoint(verts[i]);
277  } else {
278  verts_indices[i] = Index(intersecting_edges[keys[i]]);
279  }
280  }
281 
282  // create triangles for this tetrahedron
283  if (3 == num_verts) {
284  Eigen::Vector3i tri(verts_indices[0], verts_indices[1],
285  verts_indices[2]);
286 
287  Eigen::Vector3d tri_normal =
288  ComputeTriangleNormal(verts[0], verts[1], verts[2]);
289 
290  // accumulate to improve robustness of the triangle orientation test
291  double dot = 0;
292  for (int i = 0; i < 3; ++i) dot += tri_normal.dot(edge_dirs[i]);
293  if (dot < 0) std::swap(tri.x(), tri.y());
294 
295  triangle_mesh->addTriangle(tri);
296  } else if (4 == num_verts) {
297  std::array<int, 4> order = {-1, 0, 0, 0};
298  if (HasCommonVertexIndex(keys[0], keys[1]) &&
299  HasCommonVertexIndex(keys[0], keys[2])) {
300  order = {1, 0, 2, 3};
301  } else if (HasCommonVertexIndex(keys[0], keys[1]) &&
302  HasCommonVertexIndex(keys[0], keys[3])) {
303  order = {1, 0, 3, 2};
304  } else if (HasCommonVertexIndex(keys[0], keys[2]) &&
305  HasCommonVertexIndex(keys[0], keys[3])) {
306  order = {2, 0, 3, 1};
307  }
308 
309  if (order[0] != -1) {
310  // accumulate to improve robustness of the triangle orientation
311  // test
312  double dot = 0;
313  for (int i = 0; i < 4; ++i) {
314  Eigen::Vector3d tri_normal = ComputeTriangleNormal(
315  verts[order[(4 + i - 1) % 4]], verts[order[i]],
316  verts[order[(i + 1) % 4]]);
317  dot += tri_normal.dot(edge_dirs[order[i]]);
318  }
319  if (dot < 0) std::reverse(order.begin(), order.end());
320 
321  std::array<Eigen::Vector3i, 2> tris;
322  if ((verts[order[0]] - verts[order[2]]).squaredNorm() <
323  (verts[order[1]] - verts[order[3]]).squaredNorm()) {
324  tris[0] << verts_indices[order[0]], verts_indices[order[1]],
325  verts_indices[order[2]];
326  tris[1] << verts_indices[order[2]], verts_indices[order[3]],
327  verts_indices[order[0]];
328  } else {
329  tris[0] << verts_indices[order[0]], verts_indices[order[1]],
330  verts_indices[order[3]];
331  tris[1] << verts_indices[order[1]], verts_indices[order[2]],
332  verts_indices[order[3]];
333  }
334 
335  triangle_mesh->addTriangle(tris[0]);
336  triangle_mesh->addTriangle(tris[1]);
337  } else {
339  "[ExtractTriangleMesh] failed to create triangles for "
340  "tetrahedron {:d}: invalid edge configuration for "
341  "tetrahedron",
342  int(tetra_i));
343  }
344  } else if (0 != num_verts) {
346  "[ExtractTriangleMesh] failed to create triangles for "
347  "tetrahedron {:d}: unexpected number of vertices {:d}",
348  int(tetra_i), num_verts);
349  }
350  }
351 
352  return triangle_mesh;
353 }
354 
355 } // namespace geometry
356 } // namespace cloudViewer
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void addEigenPoint(const Eigen::Vector3d &point)
Tetra mesh contains vertices and tetrahedra represented by the indices to the vertices.
Definition: ecvTetraMesh.h:29
TetraMesh & operator+=(const TetraMesh &mesh)
TetraMesh & RemoveDuplicatedVertices()
Function that removes duplicated verties, i.e., vertices that have identical coordinates.
TetraMesh(const char *name="TetraMesh")
Default ccMesh constructor.
Definition: ecvTetraMesh.h:34
std::vector< Eigen::Vector4i, cloudViewer::utility::Vector4i_allocator > tetras_
List of tetras denoted by the index of points forming the tetra.
Definition: ecvTetraMesh.h:108
TetraMesh & RemoveUnreferencedVertices()
This function removes vertices from the tetra mesh that are not referenced in any tetrahedron of the ...
std::shared_ptr< ccMesh > ExtractTriangleMesh(const std::vector< double > &values, double level)
Function to extract a triangle mesh of the specified iso-surface at a level This method applies prima...
TetraMesh & RemoveDuplicatedTetras()
Function that removes duplicated tetrahedra, i.e., removes tetrahedra that reference the same four ve...
virtual TetraMesh & clear() override
TetraMesh operator+(const TetraMesh &mesh) const
TetraMesh & RemoveDegenerateTetras()
Function that removes degenerate tetrahedra, i.e., tetrahedra that reference a single vertex multiple...
virtual ecvMeshBase & clear()
Definition: ecvMeshBase.cpp:41
ecvMeshBase & operator+=(const ecvMeshBase &mesh)
Definition: ecvMeshBase.cpp:94
virtual bool IsEmpty() const override
Definition: ecvMeshBase.cpp:48
std::vector< Eigen::Vector3d > vertices_
Vertex coordinates.
Definition: ecvMeshBase.h:132
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
#define LogDebug(...)
Definition: Logging.h:90
a[190]
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
Eigen::Matrix< Index, 4, 1 > Vector4i
Definition: knncpp.h:31
Eigen::MatrixXd::Index Index
Definition: knncpp.h:26
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370