ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
TriangleMeshSimplification.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;
25 class Quadric {
26 public:
27  Quadric() {
28  A_.fill(0);
29  b_.fill(0);
30  c_ = 0;
31  }
32 
33  Quadric(const Eigen::Vector4d& plane, double weight = 1) {
34  Eigen::Vector3d n = plane.head<3>();
35  A_ = weight * n * n.transpose();
36  b_ = weight * plane(3) * n;
37  c_ = weight * plane(3) * plane(3);
38  }
39 
40  Quadric& operator+=(const Quadric& other) {
41  A_ += other.A_;
42  b_ += other.b_;
43  c_ += other.c_;
44  return *this;
45  }
46 
47  Quadric operator+(const Quadric& other) const {
48  Quadric res;
49  res.A_ = A_ + other.A_;
50  res.b_ = b_ + other.b_;
51  res.c_ = c_ + other.c_;
52  return res;
53  }
54 
55  double Eval(const Eigen::Vector3d& v) const {
56  Eigen::Vector3d Av = A_ * v;
57  double q = v.dot(Av) + 2 * b_.dot(v) + c_;
58  return q;
59  }
60 
61  bool IsInvertible() const { return std::fabs(A_.determinant()) > 1e-4; }
62 
63  Eigen::Vector3d Minimum() const { return -A_.ldlt().solve(b_); }
64 
65 public:
67  Eigen::Matrix3d A_;
70  Eigen::Vector3d b_;
72  double c_;
73 };
74 
75 std::shared_ptr<ccMesh> ccMesh::SimplifyVertexClustering(
76  double voxel_size,
78  contraction /* = SimplificationContraction::Average */) const {
79  if (hasTriangleUvs()) {
81  "[SimplifyVertexClustering] This mesh contains triangle uvs "
82  "that are not handled in this function");
83  }
84 
85  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(m_associatedCloud);
86  if (!cloud) {
88  "[ccMesh::SimplifyVertexClustering] mesh"
89  "should set associated cloud before using!");
90  }
91 
92  ccPointCloud* baseVertices = new ccPointCloud("vertices");
93  assert(baseVertices);
94  baseVertices->setEnabled(false);
95  // DGM: no need to lock it as it is only used by one mesh!
96  baseVertices->setLocked(false);
97  auto mesh = std::make_shared<ccMesh>(baseVertices);
98  mesh->addChild(baseVertices);
99 
100  if (voxel_size <= 0.0) {
101  utility::LogError("[VoxelGridFromPointCloud] voxel_size <= 0.0");
102  }
103 
104  Eigen::Vector3d voxel_size3 =
105  Eigen::Vector3d(voxel_size, voxel_size, voxel_size);
106  Eigen::Vector3d voxel_min_bound = GetMinBound() - voxel_size3 * 0.5;
107  Eigen::Vector3d voxel_max_bound = GetMaxBound() + voxel_size3 * 0.5;
108  if (voxel_size * std::numeric_limits<int>::max() <
109  (voxel_max_bound - voxel_min_bound).maxCoeff()) {
110  utility::LogError("[VoxelGridFromPointCloud] voxel_size is too small.");
111  }
112 
113  auto GetVoxelIdx = [&](const Eigen::Vector3d& vert) {
114  Eigen::Vector3d ref_coord = (vert - voxel_min_bound) / voxel_size;
115  Eigen::Vector3i idx(int(floor(ref_coord(0))), int(floor(ref_coord(1))),
116  int(floor(ref_coord(2))));
117  return idx;
118  };
119 
120  std::unordered_map<Eigen::Vector3i, std::unordered_set<int>,
122  voxel_vertices;
123  std::unordered_map<Eigen::Vector3i, int,
125  voxel_vert_ind;
126  int new_vidx = 0;
127  for (size_t vidx = 0; vidx < cloud->size(); ++vidx) {
128  const Eigen::Vector3i vox_idx = GetVoxelIdx(cloud->getEigenPoint(vidx));
129  voxel_vertices[vox_idx].insert(int(vidx));
130 
131  if (voxel_vert_ind.count(vox_idx) == 0) {
132  voxel_vert_ind[vox_idx] = new_vidx;
133  new_vidx++;
134  }
135  }
136 
137  // aggregate vertex info
138  bool has_vert_normal = cloud->hasNormals();
139  bool has_vert_color = cloud->hasColors();
140  baseVertices->resize(static_cast<unsigned int>(voxel_vertices.size()));
141  if (has_vert_normal) {
142  baseVertices->resizeTheNormsTable();
143  baseVertices->showNormals(true);
144  }
145  if (has_vert_color) {
146  baseVertices->resizeTheRGBTable();
147  baseVertices->showColors(true);
148  }
149 
150  auto AvgVertex = [&](const std::unordered_set<int> ind) {
151  Eigen::Vector3d aggr(0, 0, 0);
152  for (int vidx : ind) {
153  aggr += cloud->getEigenPoint(vidx);
154  }
155  aggr /= double(ind.size());
156  return aggr;
157  };
158  auto AvgNormal = [&](const std::unordered_set<int> ind) {
159  Eigen::Vector3d aggr(0, 0, 0);
160  for (int vidx : ind) {
161  aggr += cloud->getEigenNormal(static_cast<size_t>(vidx));
162  }
163  aggr /= double(ind.size());
164  return aggr;
165  };
166  auto AvgColor = [&](const std::unordered_set<int> ind) {
167  Eigen::Vector3d aggr(0, 0, 0);
168  for (int vidx : ind) {
169  aggr += cloud->getEigenColor(static_cast<size_t>(vidx));
170  }
171  aggr /= double(ind.size());
172  return aggr;
173  };
174 
175  if (contraction == SimplificationContraction::Average) {
176  for (const auto& voxel : voxel_vertices) {
177  int vox_vidx = voxel_vert_ind[voxel.first];
178  baseVertices->setPoint(static_cast<size_t>(vox_vidx),
179  AvgVertex(voxel.second));
180  if (has_vert_normal) {
181  baseVertices->setPointNormal(static_cast<size_t>(vox_vidx),
182  AvgNormal(voxel.second));
183  }
184  if (has_vert_color) {
185  baseVertices->setPointColor(static_cast<size_t>(vox_vidx),
186  AvgColor(voxel.second));
187  }
188  }
189  } else if (contraction == SimplificationContraction::Quadric) {
190  // Map triangles
191  std::unordered_map<int, std::unordered_set<int>> vert_to_triangles;
192  for (size_t tidx = 0; tidx < size(); ++tidx) {
193  const cloudViewer::VerticesIndexes* tri =
194  getTriangleVertIndexes(static_cast<unsigned>(tidx));
195  vert_to_triangles[tri->i1].emplace(int(tidx));
196  vert_to_triangles[tri->i2].emplace(int(tidx));
197  vert_to_triangles[tri->i3].emplace(int(tidx));
198  }
199 
200  for (const auto& voxel : voxel_vertices) {
201  size_t vox_vidx = static_cast<size_t>(voxel_vert_ind[voxel.first]);
202  Quadric q;
203  for (int vidx : voxel.second) {
204  for (int tidx : vert_to_triangles[vidx]) {
205  Eigen::Vector4d p = GetTrianglePlane(tidx);
206  double area = GetTriangleArea(tidx);
207  q += Quadric(p, area);
208  }
209  }
210  if (q.IsInvertible()) {
211  Eigen::Vector3d v = q.Minimum();
212  baseVertices->setPoint(vox_vidx, v);
213  } else {
214  baseVertices->setPoint(vox_vidx, AvgVertex(voxel.second));
215  }
216 
217  if (has_vert_normal) {
218  baseVertices->setPointNormal(vox_vidx, AvgNormal(voxel.second));
219  }
220  if (has_vert_color) {
221  baseVertices->setPointColor(vox_vidx, AvgColor(voxel.second));
222  }
223  }
224  }
225 
226  // connect vertices
227  std::unordered_set<Eigen::Vector3i, utility::hash_eigen<Eigen::Vector3i>>
228  triangles;
229  for (unsigned triIndx = 0; triIndx < size(); ++triIndx) {
230  Eigen::Vector3d v0, v1, v2;
231  getTriangleVertices(triIndx, v0.data(), v1.data(), v2.data());
232  int vidx0 = voxel_vert_ind[GetVoxelIdx(v0)];
233  int vidx1 = voxel_vert_ind[GetVoxelIdx(v1)];
234  int vidx2 = voxel_vert_ind[GetVoxelIdx(v2)];
235 
236  // only connect if in different voxels
237  if (vidx0 == vidx1 || vidx0 == vidx2 || vidx1 == vidx2) {
238  continue;
239  }
240 
241  // Note: there can be still double faces with different orientation
242  // The user has to clean up manually
243  if (vidx1 < vidx0 && vidx1 < vidx2) {
244  int tmp = vidx0;
245  vidx0 = vidx1;
246  vidx1 = vidx2;
247  vidx2 = tmp;
248  } else if (vidx2 < vidx0 && vidx2 < vidx1) {
249  int tmp = vidx1;
250  vidx1 = vidx0;
251  vidx0 = vidx2;
252  vidx2 = tmp;
253  }
254 
255  triangles.emplace(Eigen::Vector3i(vidx0, vidx1, vidx2));
256  }
257 
258  mesh->resize(triangles.size());
259  unsigned int tidx = 0;
260  for (const Eigen::Vector3i& triangle : triangles) {
261  mesh->setTriangle(tidx, triangle);
262  tidx++;
263  }
264 
265  if (hasTriNormals()) {
266  mesh->ComputeTriangleNormals();
267  }
268 
269  return mesh;
270 }
271 
272 std::shared_ptr<ccMesh> ccMesh::SimplifyQuadricDecimation(
273  int target_number_of_triangles,
274  double maximum_error /* = std::numeric_limits<double>::infinity()*/,
275  double boundary_weight /* = 1.0*/) const {
276  if (hasTriangleUvs()) {
278  "[SimplifyQuadricDecimation] This mesh contains triangle uvs "
279  "that are not handled in this function");
280  }
281  typedef std::tuple<double, int, int> CostEdge;
282 
283  ccPointCloud* cloud = ccHObjectCaster::ToPointCloud(m_associatedCloud);
284  if (!cloud) {
286  "[ccMesh::SimplifyVertexClustering] mesh"
287  "should set associated cloud before using!");
288  }
289 
290  ccPointCloud* baseVertices = new ccPointCloud("vertices");
291  assert(baseVertices);
292  baseVertices->setEnabled(false);
293  // DGM: no need to lock it as it is only used by one mesh!
294  baseVertices->setLocked(false);
295  auto mesh = std::make_shared<ccMesh>(baseVertices);
296  mesh->addChild(baseVertices);
297  mesh->merge(this, false);
298 
299  std::vector<bool> vertices_deleted(cloud->size(), false);
300  std::vector<bool> triangles_deleted(this->size(), false);
301 
302  // Map vertices to triangles and compute triangle planes and areas
303  std::vector<std::unordered_set<int>> vert_to_triangles(cloud->size());
304  std::vector<Eigen::Vector4d> triangle_planes(this->size());
305  std::vector<double> triangle_areas(this->size());
306  for (size_t tidx = 0; tidx < this->size(); ++tidx) {
307  const cloudViewer::VerticesIndexes* tri =
308  getTriangleVertIndexes(static_cast<unsigned>(tidx));
309  vert_to_triangles[tri->i1].emplace(static_cast<int>(tidx));
310  vert_to_triangles[tri->i2].emplace(static_cast<int>(tidx));
311  vert_to_triangles[tri->i3].emplace(static_cast<int>(tidx));
312 
313  triangle_planes[tidx] = GetTrianglePlane(tidx);
314  triangle_areas[tidx] = GetTriangleArea(tidx);
315  }
316 
317  // Compute the error metric per vertex
318  std::vector<Quadric> Qs(cloud->size());
319  for (size_t vidx = 0; vidx < cloud->size(); ++vidx) {
320  for (int tidx : vert_to_triangles[vidx]) {
321  Qs[vidx] += Quadric(triangle_planes[tidx], triangle_areas[tidx]);
322  }
323  }
324 
325  // For boundary edges add perpendicular plane quadric
326  auto edge_triangle_count = GetEdgeToTrianglesMap();
327  auto AddPerpPlaneQuadric = [&](int vidx0, int vidx1, int vidx2,
328  double area) {
329  int min = std::min(vidx0, vidx1);
330  int max = std::max(vidx0, vidx1);
331  Eigen::Vector2i edge(min, max);
332  if (edge_triangle_count[edge].size() != 1) {
333  return;
334  }
335  const auto& vert0 =
336  baseVertices->getEigenPoint(static_cast<size_t>(vidx0));
337  const auto& vert1 =
338  baseVertices->getEigenPoint(static_cast<size_t>(vidx1));
339  const auto& vert2 =
340  baseVertices->getEigenPoint(static_cast<size_t>(vidx2));
341  Eigen::Vector3d vert2p = (vert2 - vert0).cross(vert2 - vert1);
342  Eigen::Vector4d plane = ComputeTrianglePlane(vert0, vert1, vert2p);
343  Quadric quad(plane, area * boundary_weight);
344  Qs[vidx0] += quad;
345  Qs[vidx1] += quad;
346  };
347  for (size_t tidx = 0; tidx < this->size(); ++tidx) {
348  const auto& tria = getTriangle(tidx);
349  double area = triangle_areas[tidx];
350  AddPerpPlaneQuadric(tria(0), tria(1), tria(2), area);
351  AddPerpPlaneQuadric(tria(1), tria(2), tria(0), area);
352  AddPerpPlaneQuadric(tria(2), tria(0), tria(1), area);
353  }
354 
355  // Get valid edges and compute cost
356  // Note: We could also select all vertex pairs as edges with dist < eps
357  std::unordered_map<Eigen::Vector2i, Eigen::Vector3d,
359  vbars;
360  std::unordered_map<Eigen::Vector2i, double,
362  costs;
363  auto CostEdgeComp = [](const CostEdge& a, const CostEdge& b) {
364  return std::get<0>(a) > std::get<0>(b);
365  };
366  std::priority_queue<CostEdge, std::vector<CostEdge>, decltype(CostEdgeComp)>
367  queue(CostEdgeComp);
368 
369  auto AddEdge = [&](int vidx0, int vidx1, bool update) {
370  int min = std::min(vidx0, vidx1);
371  int max = std::max(vidx0, vidx1);
372  Eigen::Vector2i edge(min, max);
373  if (update || vbars.count(edge) == 0) {
374  const Quadric& Q0 = Qs[min];
375  const Quadric& Q1 = Qs[max];
376  Quadric Qbar = Q0 + Q1;
377  double cost;
378  Eigen::Vector3d vbar;
379  if (Qbar.IsInvertible()) {
380  vbar = Qbar.Minimum();
381  cost = Qbar.Eval(vbar);
382  } else {
383  const Eigen::Vector3d& v0 =
384  baseVertices->getEigenPoint(static_cast<size_t>(vidx0));
385  const Eigen::Vector3d& v1 =
386  baseVertices->getEigenPoint(static_cast<size_t>(vidx1));
387  Eigen::Vector3d vmid = (v0 + v1) / 2;
388  double cost0 = Qbar.Eval(v0);
389  double cost1 = Qbar.Eval(v1);
390  double costmid = Qbar.Eval(vbar);
391  cost = std::min(cost0, std::min(cost1, costmid));
392  if (cost == costmid) {
393  vbar = vmid;
394  } else if (cost == cost0) {
395  vbar = v0;
396  } else {
397  vbar = v1;
398  }
399  }
400  vbars[edge] = vbar;
401  costs[edge] = cost;
402  queue.push(CostEdge(cost, min, max));
403  }
404  };
405 
406  // add all edges to priority queue
407  for (size_t trindx = 0; trindx < this->size(); ++trindx) {
408  const auto& triangle = getTriangle(trindx);
409  AddEdge(triangle(0), triangle(1), false);
410  AddEdge(triangle(1), triangle(2), false);
411  AddEdge(triangle(2), triangle(0), false);
412  }
413 
414  // perform incremental edge collapse
415  bool has_vert_normal = cloud->hasNormals();
416  bool has_vert_color = cloud->hasColors();
417  int n_triangles = int(this->size());
418  while (n_triangles > target_number_of_triangles && !queue.empty()) {
419  // retrieve edge from queue
420  double cost;
421  int vidx0, vidx1;
422  std::tie(cost, vidx0, vidx1) = queue.top();
423  queue.pop();
424 
425  if (cost > maximum_error) {
426  break;
427  }
428 
429  // test if the edge has been updated (reinserted into queue)
430  Eigen::Vector2i edge(vidx0, vidx1);
431  bool valid = !vertices_deleted[vidx0] && !vertices_deleted[vidx1] &&
432  cost == costs[edge];
433  if (!valid) {
434  continue;
435  }
436 
437  // avoid flip of triangle normal
438  bool flipped = false;
439  for (int tidx : vert_to_triangles[vidx1]) {
440  if (triangles_deleted[tidx]) {
441  continue;
442  }
443 
444  const Eigen::Vector3i& tria =
445  mesh->getTriangle(static_cast<size_t>(tidx));
446  bool has_vidx0 =
447  vidx0 == tria(0) || vidx0 == tria(1) || vidx0 == tria(2);
448  bool has_vidx1 =
449  vidx1 == tria(0) || vidx1 == tria(1) || vidx1 == tria(2);
450  if (has_vidx0 && has_vidx1) {
451  continue;
452  }
453 
454  Eigen::Vector3d vert0 =
455  baseVertices->getEigenPoint(static_cast<size_t>(tria(0)));
456  Eigen::Vector3d vert1 =
457  baseVertices->getEigenPoint(static_cast<size_t>(tria(1)));
458  Eigen::Vector3d vert2 =
459  baseVertices->getEigenPoint(static_cast<size_t>(tria(2)));
460  Eigen::Vector3d norm_before = (vert1 - vert0).cross(vert2 - vert0);
461  norm_before /= norm_before.norm();
462 
463  if (vidx1 == tria(0)) {
464  vert0 = vbars[edge];
465  } else if (vidx1 == tria(1)) {
466  vert1 = vbars[edge];
467  } else if (vidx1 == tria(2)) {
468  vert2 = vbars[edge];
469  }
470 
471  Eigen::Vector3d norm_after = (vert1 - vert0).cross(vert2 - vert0);
472  norm_after /= norm_after.norm();
473  if (norm_before.dot(norm_after) < 0) {
474  flipped = true;
475  break;
476  }
477  }
478  if (flipped) {
479  continue;
480  }
481 
482  // Connect triangles from vidx1 to vidx0, or mark deleted
483  for (int tidx : vert_to_triangles[vidx1]) {
484  if (triangles_deleted[tidx]) {
485  continue;
486  }
487 
489  mesh->getTriangleVertIndexes(static_cast<unsigned>(tidx));
490  bool has_vidx0 = vidx0 == static_cast<int>(tria->i[0]) ||
491  vidx0 == static_cast<int>(tria->i[1]) ||
492  vidx0 == static_cast<int>(tria->i[2]);
493  bool has_vidx1 = vidx1 == static_cast<int>(tria->i[0]) ||
494  vidx1 == static_cast<int>(tria->i[1]) ||
495  vidx1 == static_cast<int>(tria->i[2]);
496  if (has_vidx0 && has_vidx1) {
497  triangles_deleted[tidx] = true;
498  n_triangles--;
499  continue;
500  }
501 
502  if (vidx1 == static_cast<int>(tria->i[0])) {
503  tria->i[0] = static_cast<unsigned>(vidx0);
504  } else if (vidx1 == static_cast<int>(tria->i[1])) {
505  tria->i[1] = static_cast<unsigned>(vidx0);
506  } else if (vidx1 == static_cast<int>(tria->i[2])) {
507  tria->i[2] = static_cast<unsigned>(vidx0);
508  }
509  vert_to_triangles[vidx0].insert(tidx);
510  }
511 
512  // update vertex vidx0 to vbar
513  baseVertices->setPoint(static_cast<size_t>(vidx0), vbars[edge]);
514  Qs[vidx0] += Qs[vidx1];
515  if (has_vert_normal) {
516  baseVertices->setEigenNormal(
517  static_cast<size_t>(vidx0),
518  0.5 * (baseVertices->getEigenNormal(
519  static_cast<size_t>(vidx0)) +
520  baseVertices->getEigenNormal(
521  static_cast<size_t>(vidx1))));
522  }
523  if (has_vert_color) {
524  baseVertices->setEigenColor(
525  static_cast<size_t>(vidx0),
526  0.5 * (baseVertices->getEigenColor(
527  static_cast<size_t>(vidx0)) +
528  baseVertices->getEigenColor(
529  static_cast<size_t>(vidx1))));
530  }
531  vertices_deleted[vidx1] = true;
532 
533  // Update edge costs for all triangles connecting to vidx0
534  for (const auto& tidx : vert_to_triangles[vidx0]) {
535  if (triangles_deleted[tidx]) {
536  continue;
537  }
538  const Eigen::Vector3i& tria =
539  mesh->getTriangle(static_cast<size_t>(tidx));
540  if (tria(0) == vidx0 || tria(1) == vidx0) {
541  AddEdge(tria(0), tria(1), true);
542  }
543  if (tria(1) == vidx0 || tria(2) == vidx0) {
544  AddEdge(tria(1), tria(2), true);
545  }
546  if (tria(2) == vidx0 || tria(0) == vidx0) {
547  AddEdge(tria(2), tria(0), true);
548  }
549  }
550  }
551 
552  // Apply changes to the triangle mesh
553  unsigned int next_free = 0;
554  std::unordered_map<int, int> vert_remapping;
555  for (unsigned int idx = 0; idx < baseVertices->size(); ++idx) {
556  if (!vertices_deleted[idx]) {
557  vert_remapping[int(idx)] = next_free;
558  baseVertices->setPoint(next_free, *baseVertices->getPoint(idx));
559  if (has_vert_normal) {
560  baseVertices->setPointNormal(next_free,
561  baseVertices->getPointNormal(idx));
562  }
563  if (has_vert_color) {
564  baseVertices->setPointColor(next_free,
565  baseVertices->getPointColor(idx));
566  }
567  next_free++;
568  }
569  }
570 
571  baseVertices->resize(next_free);
572  if (has_vert_normal) {
573  baseVertices->resizeTheNormsTable();
574  }
575  if (has_vert_color) {
576  baseVertices->resizeTheRGBTable();
577  }
578 
579  next_free = 0;
580  for (size_t idx = 0; idx < mesh->size(); ++idx) {
581  if (!triangles_deleted[idx]) {
582  Eigen::Vector3i tria = mesh->getTriangle(idx);
583  mesh->setTriangle(next_free,
584  Eigen::Vector3i(vert_remapping[tria(0)],
585  vert_remapping[tria(1)],
586  vert_remapping[tria(2)]));
587  next_free++;
588  }
589  }
590  mesh->resize(next_free);
591 
592  if (hasTriNormals()) {
593  mesh->ComputeTriangleNormals();
594  }
595 
596  return mesh;
597 }
int size
double voxel_size
Definition: VoxelGridIO.cpp:28
double c_
c_ = d . d, where d the non-normal component pf the plane parameters
Quadric operator+(const Quadric &other) const
bool IsInvertible() const
Eigen::Vector3d Minimum() const
Quadric(const Eigen::Vector4d &plane, double weight=1)
double Eval(const Eigen::Vector3d &v) const
Eigen::Vector3d b_
Eigen::Matrix3d A_
A_ = n . n^T, where n is the plane normal.
Quadric & operator+=(const Quadric &other)
virtual void showNormals(bool state)
Sets normals visibility.
virtual void showColors(bool state)
Sets colors visibility.
static ccPointCloud * ToPointCloud(ccHObject *obj, bool *isLockedVertices=nullptr)
Converts current object to 'equivalent' ccPointCloud.
std::shared_ptr< ccMesh > SimplifyQuadricDecimation(int target_number_of_triangles, double maximum_error=std::numeric_limits< double >::infinity(), double boundary_weight=1.0) const
std::shared_ptr< ccMesh > SimplifyVertexClustering(double voxel_size, SimplificationContraction contraction=SimplificationContraction::Average) 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 setEigenColor(size_t index, const Eigen::Vector3d &color)
bool resizeTheNormsTable()
Resizes the compressed normals array.
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 setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
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
const ecvColor::Rgb & getPointColor(unsigned pointIndex) const override
Returns color corresponding to a given point.
SimplificationContraction
Indicates the method that is used for mesh simplification if multiple vertices are combined to a sing...
Definition: GenericMesh.h:35
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
a[190]
const double * e
static std::unordered_map< Edge< T >, std::vector< size_t >, utility::hash_tuple< Edge< T > > > GetEdgeToTrianglesMap(const core::Tensor &tris_cpu)
MiniVec< float, N > floor(const MiniVec< float, N > &a)
Definition: MiniVec.h:75
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
Triangle described by the indexes of its 3 vertices.