ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TriangleMeshSubdivide.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 <Logging.h>
9 
10 #include <Eigen/Dense>
11 #include <queue>
12 #include <tuple>
13 #include <unordered_map>
14 #include <unordered_set>
15 
16 #include "ecvHObjectCaster.h"
17 #include "ecvMesh.h"
18 #include "ecvPointCloud.h"
19 
20 using namespace cloudViewer;
21 std::shared_ptr<ccMesh> ccMesh::SubdivideMidpoint(
22  int number_of_iterations) const {
23  if (hasTriangleUvs()) {
25  "[SubdivideMidpoint] This mesh contains triangle uvs that are "
26  "not handled in this function");
27  }
28 
29  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(m_associatedCloud);
30  if (!cloud) {
32  "[ccMesh::SimplifyVertexClustering] mesh"
33  "should set associated cloud before using!");
34  }
35 
36  ccPointCloud* baseVertices = new ccPointCloud("vertices");
37  assert(baseVertices);
38  baseVertices->setEnabled(false);
39  // DGM: no need to lock it as it is only used by one mesh!
40  baseVertices->setLocked(false);
41  auto mesh = std::make_shared<ccMesh>(baseVertices);
42  mesh->addChild(baseVertices);
43  mesh->merge(this, false);
44 
45  bool has_vert_normal = cloud->hasNormals();
46  bool has_vert_color = cloud->hasColors();
47 
48  // Compute and return midpoint.
49  // Also adds edge - new vertex refrence to new_verts map.
50  auto SubdivideEdge =
51  [&](std::unordered_map<Eigen::Vector2i, int,
53  new_verts,
54  int vidx0, int vidx1) {
55  size_t min = static_cast<size_t>(std::min(vidx0, vidx1));
56  size_t max = static_cast<size_t>(std::max(vidx0, vidx1));
57  Eigen::Vector2i edge(min, max);
58  if (new_verts.count(edge) == 0) {
59  baseVertices->addEigenPoint(
60  0.5 * (baseVertices->getEigenPoint(min) +
61  baseVertices->getEigenPoint(max)));
62 
63  if (has_vert_normal) {
64  baseVertices->addEigenNorm(
65  0.5 * (baseVertices->getEigenNormal(min) +
66  baseVertices->getEigenNormal(max)));
67  }
68  if (has_vert_color) {
69  baseVertices->addEigenColor(
70  0.5 * (baseVertices->getEigenColor(min) +
71  baseVertices->getEigenColor(max)));
72  }
73  int vidx01 = int(baseVertices->size()) - 1;
74  new_verts[edge] = vidx01;
75  return vidx01;
76  } else {
77  return new_verts[edge];
78  }
79  };
80 
81  for (int iter = 0; iter < number_of_iterations; ++iter) {
82  std::unordered_map<Eigen::Vector2i, int,
84  new_verts;
85  std::vector<Eigen::Vector3i> new_triangles(4 * mesh->size());
86  for (size_t tidx = 0; tidx < mesh->size(); ++tidx) {
87  const auto& triangle = mesh->getTriangle(tidx);
88  int vidx0 = triangle(0);
89  int vidx1 = triangle(1);
90  int vidx2 = triangle(2);
91  int vidx01 = SubdivideEdge(new_verts, vidx0, vidx1);
92  int vidx12 = SubdivideEdge(new_verts, vidx1, vidx2);
93  int vidx20 = SubdivideEdge(new_verts, vidx2, vidx0);
94  new_triangles[tidx * 4 + 0] =
95  Eigen::Vector3i(vidx0, vidx01, vidx20);
96  new_triangles[tidx * 4 + 1] =
97  Eigen::Vector3i(vidx01, vidx1, vidx12);
98  new_triangles[tidx * 4 + 2] =
99  Eigen::Vector3i(vidx12, vidx2, vidx20);
100  new_triangles[tidx * 4 + 3] =
101  Eigen::Vector3i(vidx01, vidx12, vidx20);
102  }
103  mesh->setTriangles(new_triangles);
104  }
105 
106  if (hasTriNormals()) {
107  mesh->ComputeTriangleNormals();
108  }
109 
110  return mesh;
111 }
112 
113 std::shared_ptr<ccMesh> ccMesh::SubdivideLoop(int number_of_iterations) const {
114  if (hasTriangleUvs()) {
116  "[SubdivideLoop] This mesh contains triangle uvs that are not "
117  "handled in this function");
118  }
119  typedef std::unordered_map<Eigen::Vector2i, int,
121  EdgeNewVertMap;
122  typedef std::unordered_map<Eigen::Vector2i, std::unordered_set<int>,
124  EdgeTrianglesMap;
125  typedef std::vector<std::unordered_set<int>> VertexNeighbours;
126 
127  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(m_associatedCloud);
128  if (!cloud) {
130  "[ccMesh::SimplifyVertexClustering] mesh"
131  "should set associated cloud before using!");
132  }
133 
134  bool has_vert_normal = cloud->hasNormals();
135  bool has_vert_color = cloud->hasColors();
136 
137  auto UpdateVertex = [&](int vidx, const std::shared_ptr<ccMesh>& old_mesh,
138  std::shared_ptr<ccMesh>& new_mesh,
139  const std::unordered_set<int>& nbs,
140  const EdgeTrianglesMap& edge_to_triangles) {
141  // check if boundary edge and get nb vertices in that case
142  std::unordered_set<int> boundary_nbs;
143  for (int nb : nbs) {
144  const Eigen::Vector2i edge = GetOrderedEdge(vidx, nb);
145  if (edge_to_triangles.at(edge).size() == 1) {
146  boundary_nbs.insert(nb);
147  }
148  }
149 
150  // in manifold meshes this should not happen
151  if (boundary_nbs.size() > 2) {
153  "[SubdivideLoop] boundary edge with > 2 neighbours, maybe "
154  "mesh is not manifold.");
155  }
156 
157  double beta, alpha;
158  if (boundary_nbs.size() >= 2) {
159  beta = 1. / 8.;
160  alpha = 1. - boundary_nbs.size() * beta;
161  } else if (nbs.size() == 3) {
162  beta = 3. / 16.;
163  alpha = 1. - nbs.size() * beta;
164  } else {
165  beta = 3. / (8. * nbs.size());
166  alpha = 1. - nbs.size() * beta;
167  }
168 
169  ccPointCloud* newVertice =
170  ccHObjectCaster::ToPointCloud(new_mesh->getAssociatedCloud());
171  if (!newVertice) {
173  "[ccMesh::SimplifyVertexClustering] mesh"
174  "should set associated cloud before using!");
175  }
176  ccPointCloud* oldVertice =
177  ccHObjectCaster::ToPointCloud(old_mesh->getAssociatedCloud());
178  if (!oldVertice) {
180  "[ccMesh::SimplifyVertexClustering] mesh"
181  "should set associated cloud before using!");
182  }
183 
184  newVertice->setPoint(
185  static_cast<unsigned>(vidx),
186  *oldVertice->getPoint(static_cast<unsigned>(vidx)) * alpha);
187  if (has_vert_normal) {
188  newVertice->setPointNormal(
189  static_cast<unsigned>(vidx),
190  static_cast<PointCoordinateType>(alpha) *
191  oldVertice->getPointNormal(
192  static_cast<unsigned>(vidx)));
193  }
194  if (has_vert_color) {
195  newVertice->setEigenColor(
196  static_cast<unsigned>(vidx),
197  alpha * oldVertice->getEigenColor(
198  static_cast<unsigned>(vidx)));
199  }
200 
201  auto Update = [&](int nb) {
202  CCVector3* p = newVertice->getPointPtr(static_cast<unsigned>(vidx));
203  *p += *oldVertice->getPoint(static_cast<unsigned>(nb)) * beta;
204  if (has_vert_normal) {
205  newVertice->setPointNormal(
206  static_cast<unsigned>(vidx),
207  newVertice->getPointNormal(
208  static_cast<unsigned>(vidx)) +
209  static_cast<PointCoordinateType>(beta) *
210  oldVertice->getPointNormal(
211  static_cast<unsigned>(nb)));
212  }
213  if (has_vert_color) {
214  newVertice->setEigenColor(
215  static_cast<unsigned>(vidx),
216  newVertice->getEigenColor(static_cast<unsigned>(vidx)) +
217  beta * oldVertice->getEigenColor(
218  static_cast<unsigned>(nb)));
219  }
220  };
221 
222  if (boundary_nbs.size() >= 2) {
223  for (int nb : boundary_nbs) {
224  Update(nb);
225  }
226  } else {
227  for (int nb : nbs) {
228  Update(nb);
229  }
230  }
231  };
232 
233  auto SubdivideEdge = [&](int vidx0, int vidx1,
234  const std::shared_ptr<ccMesh>& old_mesh,
235  std::shared_ptr<ccMesh>& new_mesh,
236  EdgeNewVertMap& new_verts,
237  const EdgeTrianglesMap& edge_to_triangles) {
238  Eigen::Vector2i edge = GetOrderedEdge(vidx0, vidx1);
239 
240  ccPointCloud* newVertice =
241  ccHObjectCaster::ToPointCloud(new_mesh->getAssociatedCloud());
242  if (!newVertice) {
244  "[ccMesh::SimplifyVertexClustering] mesh"
245  "should set associated cloud before using!");
246  }
247  const ccPointCloud* oldVertice =
248  ccHObjectCaster::ToPointCloud(old_mesh->getAssociatedCloud());
249  if (!oldVertice) {
251  "[ccMesh::SimplifyVertexClustering] mesh"
252  "should set associated cloud before using!");
253  }
254 
255  if (new_verts.count(edge) == 0) {
256  Eigen::Vector3d new_vert =
257  oldVertice->getEigenPoint(static_cast<size_t>(vidx0)) +
258  oldVertice->getEigenPoint(static_cast<size_t>(vidx1));
259  Eigen::Vector3d new_normal;
260  if (has_vert_normal) {
261  new_normal =
262  oldVertice->getEigenNormal(static_cast<size_t>(vidx0)) +
263  oldVertice->getEigenNormal(static_cast<size_t>(vidx1));
264  }
265  Eigen::Vector3d new_color;
266  if (has_vert_color) {
267  new_color =
268  oldVertice->getEigenColor(static_cast<size_t>(vidx0)) +
269  oldVertice->getEigenColor(static_cast<size_t>(vidx1));
270  }
271 
272  const auto& edge_triangles = edge_to_triangles.at(edge);
273  if (edge_triangles.size() < 2) {
274  new_vert *= 0.5;
275  if (has_vert_normal) {
276  new_normal *= 0.5;
277  }
278  if (has_vert_color) {
279  new_color *= 0.5;
280  }
281  } else {
282  new_vert *= 3. / 8.;
283  if (has_vert_normal) {
284  new_normal *= 3. / 8.;
285  }
286  if (has_vert_color) {
287  new_color *= 3. / 8.;
288  }
289  size_t n_adjacent_trias = edge_triangles.size();
290  double scale = 1. / (4. * n_adjacent_trias);
291  for (int tidx : edge_triangles) {
292  const auto& tria =
293  old_mesh->getTriangle(static_cast<size_t>(tidx));
294  int vidx2 =
295  (tria(0) != vidx0 && tria(0) != vidx1)
296  ? tria(0)
297  : ((tria(1) != vidx0 && tria(1) != vidx1)
298  ? tria(1)
299  : tria(2));
300  new_vert += scale * oldVertice->getEigenPoint(
301  static_cast<size_t>(vidx2));
302  if (has_vert_normal) {
303  new_normal +=
304  scale * oldVertice->getEigenNormal(
305  static_cast<size_t>(vidx2));
306  }
307  if (has_vert_color) {
308  new_color +=
309  scale * oldVertice->getEigenColor(
310  static_cast<size_t>(vidx2));
311  }
312  }
313  }
314 
315  int vidx01 = int(oldVertice->size() + new_verts.size());
316 
317  newVertice->setPoint(static_cast<size_t>(vidx01), new_vert);
318  if (has_vert_normal) {
319  newVertice->setEigenNormal(static_cast<unsigned>(vidx01),
320  new_normal);
321  }
322  if (has_vert_color) {
323  newVertice->setEigenColor(static_cast<unsigned>(vidx01),
324  new_color);
325  }
326 
327  new_verts[edge] = vidx01;
328  return vidx01;
329  } else {
330  int vidx01 = new_verts[edge];
331  return vidx01;
332  }
333  };
334 
335  auto InsertTriangle = [&](int tidx, int vidx0, int vidx1, int vidx2,
336  std::shared_ptr<ccMesh>& mesh,
337  EdgeTrianglesMap& edge_to_triangles,
338  VertexNeighbours& vertex_neighbours) {
339  mesh->setTriangle(static_cast<size_t>(tidx),
340  Eigen::Vector3i(vidx0, vidx1, vidx2));
341  edge_to_triangles[GetOrderedEdge(vidx0, vidx1)].insert(tidx);
342  edge_to_triangles[GetOrderedEdge(vidx1, vidx2)].insert(tidx);
343  edge_to_triangles[GetOrderedEdge(vidx2, vidx0)].insert(tidx);
344  vertex_neighbours[vidx0].insert(vidx1);
345  vertex_neighbours[vidx0].insert(vidx2);
346  vertex_neighbours[vidx1].insert(vidx0);
347  vertex_neighbours[vidx1].insert(vidx2);
348  vertex_neighbours[vidx2].insert(vidx0);
349  vertex_neighbours[vidx2].insert(vidx1);
350  };
351 
352  EdgeTrianglesMap edge_to_triangles;
353  VertexNeighbours vertex_neighbours(cloud->size());
354  for (size_t tidx = 0; tidx < size(); ++tidx) {
355  const auto& tria = getTriangle(tidx);
356  Eigen::Vector2i e0 = GetOrderedEdge(tria(0), tria(1));
357  edge_to_triangles[e0].insert(int(tidx));
358  Eigen::Vector2i e1 = GetOrderedEdge(tria(1), tria(2));
359  edge_to_triangles[e1].insert(int(tidx));
360  Eigen::Vector2i e2 = GetOrderedEdge(tria(2), tria(0));
361  edge_to_triangles[e2].insert(int(tidx));
362 
363  if (edge_to_triangles[e0].size() > 2 ||
364  edge_to_triangles[e1].size() > 2 ||
365  edge_to_triangles[e2].size() > 2) {
366  utility::LogWarning("[SubdivideLoop] non-manifold edge.");
367  }
368 
369  vertex_neighbours[tria(0)].insert(tria(1));
370  vertex_neighbours[tria(0)].insert(tria(2));
371  vertex_neighbours[tria(1)].insert(tria(0));
372  vertex_neighbours[tria(1)].insert(tria(2));
373  vertex_neighbours[tria(2)].insert(tria(0));
374  vertex_neighbours[tria(2)].insert(tria(1));
375  }
376 
377  ccPointCloud* oldVertices = new ccPointCloud("vertices");
378  assert(oldVertices);
379  oldVertices->setEnabled(false);
380  // DGM: no need to lock it as it is only used by one mesh!
381  oldVertices->setLocked(false);
382  auto old_mesh = std::make_shared<ccMesh>(oldVertices);
383  old_mesh->addChild(oldVertices);
384  old_mesh->merge(this, false);
385 
386  for (int iter = 0; iter < number_of_iterations; ++iter) {
387  size_t n_new_vertices =
388  old_mesh->getVerticeSize() + edge_to_triangles.size();
389  size_t n_new_triangles = 4 * old_mesh->size();
390 
391  ccPointCloud* newVertices = new ccPointCloud("vertices");
392  assert(newVertices);
393  newVertices->setEnabled(false);
394  // DGM: no need to lock it as it is only used by one mesh!
395  newVertices->setLocked(false);
396  auto new_mesh = std::make_shared<ccMesh>(newVertices);
397  new_mesh->addChild(newVertices);
398 
399  newVertices->resize(static_cast<unsigned>(n_new_vertices));
400  if (has_vert_normal) {
401  newVertices->resizeTheNormsTable();
402  }
403  if (has_vert_color) {
404  newVertices->resizeTheRGBTable();
405  }
406  new_mesh->resize(n_new_triangles);
407 
408  EdgeNewVertMap new_verts;
409  EdgeTrianglesMap new_edge_to_triangles;
410  VertexNeighbours new_vertex_neighbours(n_new_vertices);
411 
412  for (size_t vidx = 0; vidx < old_mesh->getVerticeSize(); ++vidx) {
413  UpdateVertex(int(vidx), old_mesh, new_mesh, vertex_neighbours[vidx],
414  edge_to_triangles);
415  }
416 
417  for (size_t tidx = 0; tidx < old_mesh->size(); ++tidx) {
418  const auto& triangle = old_mesh->getTriangle(tidx);
419  int vidx0 = triangle(0);
420  int vidx1 = triangle(1);
421  int vidx2 = triangle(2);
422 
423  int vidx01 = SubdivideEdge(vidx0, vidx1, old_mesh, new_mesh,
424  new_verts, edge_to_triangles);
425  int vidx12 = SubdivideEdge(vidx1, vidx2, old_mesh, new_mesh,
426  new_verts, edge_to_triangles);
427  int vidx20 = SubdivideEdge(vidx2, vidx0, old_mesh, new_mesh,
428  new_verts, edge_to_triangles);
429 
430  InsertTriangle(int(tidx) * 4 + 0, vidx0, vidx01, vidx20, new_mesh,
431  new_edge_to_triangles, new_vertex_neighbours);
432  InsertTriangle(int(tidx) * 4 + 1, vidx01, vidx1, vidx12, new_mesh,
433  new_edge_to_triangles, new_vertex_neighbours);
434  InsertTriangle(int(tidx) * 4 + 2, vidx12, vidx2, vidx20, new_mesh,
435  new_edge_to_triangles, new_vertex_neighbours);
436  InsertTriangle(int(tidx) * 4 + 3, vidx01, vidx12, vidx20, new_mesh,
437  new_edge_to_triangles, new_vertex_neighbours);
438  }
439 
440  old_mesh = std::move(new_mesh);
441  edge_to_triangles = std::move(new_edge_to_triangles);
442  vertex_neighbours = std::move(new_vertex_neighbours);
443  }
444 
445  if (hasTriNormals()) {
446  old_mesh->ComputeTriangleNormals();
447  }
448 
449  return old_mesh;
450 }
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int size
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
std::shared_ptr< ccMesh > SubdivideLoop(int number_of_iterations) const
std::shared_ptr< ccMesh > SubdivideMidpoint(int number_of_iterations) const
virtual void setLocked(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:117
virtual void setEnabled(bool state)
Sets the "enabled" property.
Definition: ecvObject.h:102
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void addEigenNorm(const Eigen::Vector3d &N)
void setEigenColor(size_t index, const Eigen::Vector3d &color)
bool resizeTheNormsTable()
Resizes the compressed normals array.
void addEigenColor(const Eigen::Vector3d &color)
void setEigenNormal(size_t index, const Eigen::Vector3d &normal)
Eigen::Vector3d getEigenNormal(size_t index) const
bool hasNormals() const override
Returns whether normals are enabled or not.
bool resizeTheRGBTable(bool fillWithWhite=false)
Resizes the RGB colors array.
bool hasColors() const override
Returns whether colors are enabled or not.
bool resize(unsigned numberOfPoints) override
Resizes all the active features arrays.
void setPointNormal(size_t pointIndex, const CCVector3 &N)
Sets a particular point normal (shortcut)
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
Eigen::Vector3d getEigenColor(size_t index) const
void addEigenPoint(const Eigen::Vector3d &point)
CCVector3 * getPointPtr(size_t index)
unsigned size() const override
Definition: PointCloudTpl.h:38
Eigen::Vector3d getEigenPoint(size_t index) const
void setPoint(size_t index, const CCVector3 &P)
const CCVector3 * getPoint(unsigned index) const override
#define LogWarning(...)
Definition: Logging.h:72
#define LogError(...)
Definition: Logging.h:60
double e2[36]
static Edge< T > GetOrderedEdge(T vidx0, T vidx1)
brief Helper function to get an edge with ordered vertex indices.
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