ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
HalfEdgeTriangleMesh.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 "HalfEdgeTriangleMesh.h"
9 
10 // LOCAL
11 #include <Logging.h>
12 
13 #include "ecvHObjectCaster.h"
14 #include "ecvMesh.h"
15 #include "ecvPointCloud.h"
16 
17 // EIGEN
18 #include <Eigen/Dense>
19 
20 // SYSTEM
21 #include <array>
22 #include <numeric>
23 #include <tuple>
24 #include <unordered_map>
25 
26 using namespace cloudViewer;
27 namespace cloudViewer {
28 namespace geometry {
29 
31  int triangle_index,
32  int next,
33  int twin)
34  : next_(next),
35  twin_(twin),
36  vertex_indices_(vertex_indices),
37  triangle_index_(triangle_index) {}
38 
41  half_edges_.clear();
43  return *this;
44 }
45 
47  return half_edges_.size() > 0 &&
49 }
50 
51 int HalfEdgeTriangleMesh::nextHalfEdgeFromVertex(int half_edge_index) const {
52  const HalfEdge &curr_he = half_edges_[half_edge_index];
53  int next_he_index = curr_he.next_;
54  const HalfEdge &next_he = half_edges_[next_he_index];
55  int next_next_he_index = next_he.next_;
56  const HalfEdge &next_next_he = half_edges_[next_next_he_index];
57  int next_next_twin_he_index = next_next_he.twin_;
58  return next_next_twin_he_index;
59 }
60 
62  int vertex_index) const {
63  int init_he_index = ordered_half_edge_from_vertex_[vertex_index][0];
64  const HalfEdge &init_he = half_edges_[init_he_index];
65 
66  if (!init_he.IsBoundary()) {
67  utility::LogError("The vertex {:d} is not on boundary.", vertex_index);
68  }
69 
70  std::vector<int> boundary_half_edge_indices;
71  int curr_he_index = init_he_index;
72  boundary_half_edge_indices.push_back(curr_he_index);
73  curr_he_index = nextHalfEdgeOnBoundary(curr_he_index);
74  while (curr_he_index != init_he_index && curr_he_index != -1) {
75  boundary_half_edge_indices.push_back(curr_he_index);
76  curr_he_index = nextHalfEdgeOnBoundary(curr_he_index);
77  }
78  return boundary_half_edge_indices;
79 }
80 
82  int vertex_index) const {
83  std::vector<int> boundary_half_edges =
85  std::vector<int> boundary_vertices;
86  for (const int &half_edge_idx : boundary_half_edges) {
87  boundary_vertices.push_back(
88  half_edges_[half_edge_idx].vertex_indices_(0));
89  }
90  return boundary_vertices;
91 }
92 
93 std::vector<std::vector<int>> HalfEdgeTriangleMesh::getBoundaries() const {
94  std::vector<std::vector<int>> boundaries;
95  std::unordered_set<int> visited;
96 
97  for (int vertex_ind = 0; vertex_ind < int(vertices_.size()); ++vertex_ind) {
98  if (visited.find(vertex_ind) != visited.end()) {
99  continue;
100  }
101  // It is guaranteed that if a vertex in on boundary, the starting
102  // edge must be on boundary. After purging, it's also guaranteed that
103  // a vertex always have out-going half-edges after purging.
104  int first_half_edge_ind = ordered_half_edge_from_vertex_[vertex_ind][0];
105  if (half_edges_[first_half_edge_ind].IsBoundary()) {
106  std::vector<int> boundary = boundaryVerticesFromVertex(vertex_ind);
107  boundaries.push_back(boundary);
108  for (int boundary_vertex : boundary) {
109  visited.insert(boundary_vertex);
110  }
111  }
112  visited.insert(vertex_ind);
113  }
114  return boundaries;
115 }
116 
118  int curr_half_edge_index) const {
119  if (!hasHalfEdges() || curr_half_edge_index >= int(half_edges_.size()) ||
120  curr_half_edge_index == -1) {
122  "edge index {:d} out of range or half-edges not available.",
123  curr_half_edge_index);
124  return -1;
125  }
126  if (!half_edges_[curr_half_edge_index].IsBoundary()) {
128  "The currented half-edge index {:d} is on boundary.",
129  curr_half_edge_index);
130  return -1;
131  }
132 
133  // curr_half_edge's end point and next_half_edge's start point is the same
134  // vertex. It is guaranteed that next_half_edge is the first edge
135  // in ordered_half_edge_from_vertex_ and next_half_edge is a boundary edge.
136  int vertex_index = half_edges_[curr_half_edge_index].vertex_indices_(1);
137  int next_half_edge_index = ordered_half_edge_from_vertex_[vertex_index][0];
138  if (!half_edges_[next_half_edge_index].IsBoundary()) {
140  "[nextHalfEdgeOnBoundary] The next half-edge along the "
141  "boundary is not a boundary edge.");
142  return -1;
143  }
144  return next_half_edge_index;
145 }
146 
147 std::shared_ptr<HalfEdgeTriangleMesh>
149  ccPointCloud *baseVertices = new ccPointCloud("vertices");
150  assert(baseVertices);
151  auto mesh_cpy = std::make_shared<ccMesh>(baseVertices);
152  auto het_mesh = std::make_shared<HalfEdgeTriangleMesh>();
153 
154  // Copy
155  *mesh_cpy = mesh;
156 
157  // Purge to remove duplications
158  mesh_cpy->RemoveDuplicatedVertices();
159  mesh_cpy->RemoveDuplicatedTriangles();
160  mesh_cpy->RemoveUnreferencedVertices();
161  mesh_cpy->RemoveDegenerateTriangles();
162 
163  // Collect half edges
164  // Check: for valid manifolds, there mustn't be duplicated half-edges
165  std::unordered_map<Eigen::Vector2i, size_t,
167  vertex_indices_to_half_edge_index;
168 
169  for (size_t triangle_index = 0; triangle_index < mesh_cpy->size();
170  triangle_index++) {
171  const Eigen::Vector3i &triangle = mesh_cpy->getTriangle(triangle_index);
172  size_t num_half_edges = het_mesh->half_edges_.size();
173 
174  size_t he_0_index = num_half_edges;
175  size_t he_1_index = num_half_edges + 1;
176  size_t he_2_index = num_half_edges + 2;
177  HalfEdge he_0(Eigen::Vector2i(triangle(0), triangle(1)),
178  int(triangle_index), int(he_1_index), -1);
179  HalfEdge he_1(Eigen::Vector2i(triangle(1), triangle(2)),
180  int(triangle_index), int(he_2_index), -1);
181  HalfEdge he_2(Eigen::Vector2i(triangle(2), triangle(0)),
182  int(triangle_index), int(he_0_index), -1);
183 
184  if (vertex_indices_to_half_edge_index.find(he_0.vertex_indices_) !=
185  vertex_indices_to_half_edge_index.end() ||
186  vertex_indices_to_half_edge_index.find(he_1.vertex_indices_) !=
187  vertex_indices_to_half_edge_index.end() ||
188  vertex_indices_to_half_edge_index.find(he_2.vertex_indices_) !=
189  vertex_indices_to_half_edge_index.end()) {
191  "ComputeHalfEdges failed. Duplicated half-edges.");
192  }
193 
194  het_mesh->half_edges_.push_back(he_0);
195  het_mesh->half_edges_.push_back(he_1);
196  het_mesh->half_edges_.push_back(he_2);
197  vertex_indices_to_half_edge_index[he_0.vertex_indices_] = he_0_index;
198  vertex_indices_to_half_edge_index[he_1.vertex_indices_] = he_1_index;
199  vertex_indices_to_half_edge_index[he_2.vertex_indices_] = he_2_index;
200  }
201 
202  // Fill twin half-edge. In the previous step, it is already guaranteed that
203  // each half-edge can have at most one twin half-edge.
204  for (size_t this_he_index = 0; this_he_index < het_mesh->half_edges_.size();
205  this_he_index++) {
206  HalfEdge &this_he = het_mesh->half_edges_[this_he_index];
207  Eigen::Vector2i twin_end_points(this_he.vertex_indices_(1),
208  this_he.vertex_indices_(0));
209  if (this_he.twin_ == -1 &&
210  vertex_indices_to_half_edge_index.find(twin_end_points) !=
211  vertex_indices_to_half_edge_index.end()) {
212  size_t twin_he_index =
213  vertex_indices_to_half_edge_index[twin_end_points];
214  HalfEdge &twin_he = het_mesh->half_edges_[twin_he_index];
215  this_he.twin_ = int(twin_he_index);
216  twin_he.twin_ = int(this_he_index);
217  }
218  }
219 
220  // Get out-going half-edges from each vertex. This can be done during
221  // half-edge construction. Done here for readability.
222  std::vector<std::vector<int>> half_edges_from_vertex(
223  mesh_cpy->getVerticeSize());
224  for (size_t half_edge_index = 0;
225  half_edge_index < het_mesh->half_edges_.size(); half_edge_index++) {
226  int src_vertex_index =
227  het_mesh->half_edges_[half_edge_index].vertex_indices_(0);
228  half_edges_from_vertex[src_vertex_index].push_back(
229  int(half_edge_index));
230  }
231 
232  // Find ordered half-edges from each vertex by traversal. To be a valid
233  // manifold, there can be at most 1 boundary half-edge from each vertex.
234  het_mesh->ordered_half_edge_from_vertex_.resize(mesh_cpy->getVerticeSize());
235  for (size_t vertex_index = 0; vertex_index < mesh_cpy->getVerticeSize();
236  vertex_index++) {
237  size_t num_boundaries = 0;
238  int init_half_edge_index = 0;
239  for (const int &half_edge_index :
240  half_edges_from_vertex[vertex_index]) {
241  if (het_mesh->half_edges_[half_edge_index].IsBoundary()) {
242  num_boundaries++;
243  init_half_edge_index = half_edge_index;
244  }
245  }
246  if (num_boundaries > 1) {
247  utility::LogError("ComputeHalfEdges failed. Invalid vertex.");
248  }
249  // If there is a boundary edge, start from that; otherwise start
250  // with any half-edge (default 0) started from this vertex.
251  if (num_boundaries == 0) {
252  init_half_edge_index = half_edges_from_vertex[vertex_index][0];
253  }
254 
255  // Push edges to ordered_half_edge_from_vertex_.
256  int curr_he_index = init_half_edge_index;
257  het_mesh->ordered_half_edge_from_vertex_[vertex_index].push_back(
258  curr_he_index);
259  int next_next_twin_he_index =
260  het_mesh->nextHalfEdgeFromVertex(curr_he_index);
261  curr_he_index = next_next_twin_he_index;
262  while (curr_he_index != -1 && curr_he_index != init_half_edge_index) {
263  het_mesh->ordered_half_edge_from_vertex_[vertex_index].push_back(
264  curr_he_index);
265  next_next_twin_he_index =
266  het_mesh->nextHalfEdgeFromVertex(curr_he_index);
267  curr_he_index = next_next_twin_he_index;
268  }
269  }
270 
271  mesh_cpy->ComputeVertexNormals();
272  het_mesh->vertices_ = mesh_cpy->getEigenVertices();
273  het_mesh->vertex_normals_ = mesh_cpy->getVertexNormals();
274  het_mesh->vertex_colors_ = mesh_cpy->getVertexColors();
275  het_mesh->triangles_ = mesh_cpy->getTriangles();
276  het_mesh->triangle_normals_ = mesh_cpy->getTriangleNormals();
277 
278  return het_mesh;
279 }
280 
282  const HalfEdgeTriangleMesh &mesh) {
284  // TODO
285  return *this;
286 }
287 
289  const HalfEdgeTriangleMesh &mesh) const {
290  return (HalfEdgeTriangleMesh(*this) += mesh);
291 }
292 
293 } // namespace geometry
294 } // namespace cloudViewer
long vertex_index
math::float4 next
Triangular mesh.
Definition: ecvMesh.h:35
ccMesh & RemoveDegenerateTriangles()
Function that removes degenerate triangles, i.e., triangles that reference a single vertex multiple t...
ccMesh & RemoveDuplicatedTriangles()
Function that removes duplicated triangles, i.e., removes triangles that reference the same three ver...
ccMesh & RemoveUnreferencedVertices()
This function removes vertices from the triangle mesh that are not referenced in any triangle of the ...
ccMesh & RemoveDuplicatedVertices()
Function that removes duplicated verties, i.e., vertices that have identical coordinates.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
HalfEdge class contains vertex, triangle info about a half edge, as well as relations of next and twi...
int next_
Index of the next HalfEdge in the same triangle.
Eigen::Vector2i vertex_indices_
Index of the ordered vertices forming this half edge.
HalfEdgeTriangleMesh inherits TriangleMesh class with the addition of HalfEdge data structure for eac...
HalfEdgeTriangleMesh(const char *name="HalfEdgeTriangleMesh")
Default Constructor.
std::vector< std::vector< int > > ordered_half_edge_from_vertex_
std::vector< int > boundaryHalfEdgesFromVertex(int vertex_index) const
bool hasHalfEdges() const
Returns true if half-edges have already been computed.
static std::shared_ptr< HalfEdgeTriangleMesh > CreateFromTriangleMesh(const ccMesh &mesh)
HalfEdgeTriangleMesh & operator+=(const HalfEdgeTriangleMesh &mesh)
std::vector< HalfEdge > half_edges_
List of HalfEdge in the mesh.
std::vector< int > boundaryVerticesFromVertex(int vertex_index) const
HalfEdgeTriangleMesh operator+(const HalfEdgeTriangleMesh &mesh) const
std::vector< std::vector< int > > getBoundaries() const
Returns a vector of boundaries. A boundary is a vector of vertices.
int nextHalfEdgeOnBoundary(int curr_half_edge_index) const
int nextHalfEdgeFromVertex(int init_half_edge_index) const
virtual HalfEdgeTriangleMesh & clear() override
virtual ecvMeshBase & clear()
Definition: ecvMeshBase.cpp:41
ecvMeshBase & operator+=(const ecvMeshBase &mesh)
Definition: ecvMeshBase.cpp:94
std::vector< Eigen::Vector3d > vertices_
Vertex coordinates.
Definition: ecvMeshBase.h:132
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
Generic file read and write utility for python interface.
Eigen::Matrix< Index, 3, 1 > Vector3i
Definition: knncpp.h:30
Eigen::Matrix< Index, 2, 1 > Vector2i
Definition: knncpp.h:29