ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
Delaunay2dMesh.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 #ifdef _MSC_VER
9 #pragma warning(disable : 4996) // Use of [[deprecated]] feature
10 #endif
11 
12 #include <Delaunay2dMesh.h>
13 
14 // local
15 #include <CVPointCloud.h>
17 #include <Polyline.h>
18 
19 #if defined(USE_CGAL_LIB)
20 // CGAL Lib
21 #include <CGAL/Constrained_Delaunay_triangulation_2.h>
22 #include <CGAL/Delaunay_triangulation_2.h>
23 #include <CGAL/Exact_predicates_inexact_constructions_kernel.h>
24 #include <CGAL/Triangulation_vertex_base_with_info_2.h>
25 #include <CGAL/version_macros.h>
26 #endif
27 
28 using namespace cloudViewer;
29 
31  : m_associatedCloud(nullptr),
32  m_triIndexes(nullptr),
33  m_globalIterator(nullptr),
34  m_globalIteratorEnd(nullptr),
35  m_numberOfTriangles(0),
36  m_cloudIsOwnedByMesh(false) {}
37 
39  linkMeshWith(nullptr);
40 
41  if (m_triIndexes) delete[] m_triIndexes;
42 }
43 
45 #if defined(USE_CGAL_LIB)
46  return true;
47 #else
48  return false;
49 #endif
50 }
51 
53  bool passOwnership) {
54  if (m_associatedCloud == aCloud) return;
55 
56  // previous cloud?
58 
59  m_associatedCloud = aCloud;
60  m_cloudIsOwnedByMesh = passOwnership;
61 }
62 
63 bool Delaunay2dMesh::buildMesh(const std::vector<CCVector2>& points2D,
64  const std::vector<int>& segments2D,
65  std::string& outputErrorStr) {
66 #if defined(USE_CGAL_LIB)
67 
68  // CGAL boilerplate
69  typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
70  // We define a vertex_base with info. The "info" (std::size_t) allow us to
71  // keep track of the original point index.
72  typedef CGAL::Triangulation_vertex_base_with_info_2<std::size_t, K> Vb;
73  typedef CGAL::Constrained_triangulation_face_base_2<K> Fb;
74  typedef CGAL::Triangulation_data_structure_2<Vb, Fb> Tds;
75  // CGAL 6.0+ deprecated No_intersection_tag, default behavior is no
76  // intersections CGAL_VERSION_NR format: MMmmbb (Major, minor, bugfix),
77  // e.g., 60000 for 6.0.0
78 #if CGAL_VERSION_NR >= 60000
79  typedef CGAL::Constrained_Delaunay_triangulation_2<K, Tds> CDT;
80 #else
81  typedef CGAL::No_intersection_tag Itag;
82  typedef CGAL::Constrained_Delaunay_triangulation_2<K, Tds, Itag> CDT;
83 #endif
84  typedef CDT::Point cgalPoint;
85 
86  std::vector<std::pair<cgalPoint, std::size_t>> constraints;
87  std::size_t constrCount = segments2D.size();
88 
89  try {
90  constraints.reserve(constrCount);
91  } catch (const std::bad_alloc&) {
92  outputErrorStr = "Not enough memory";
93  return false;
94  };
95 
96  // We create the Constrained Delaunay Triangulation (CDT)
97  CDT cdt;
98 
99  // We build the constraints
100  for (std::size_t i = 0; i < constrCount; ++i) {
101  const CCVector2* pt = &points2D[segments2D[i]];
102  constraints.emplace_back(cgalPoint(pt->x, pt->y), segments2D[i]);
103  }
104  // The CDT is built according to the constraints
105  cdt.insert(constraints.begin(), constraints.end());
106 
107  m_numberOfTriangles = static_cast<unsigned>(cdt.number_of_faces());
108  m_triIndexes = new int[cdt.number_of_faces() * 3];
109 
110  // The cgal data structure is converted into CC one
111  if (m_numberOfTriangles > 0) {
112  int faceCount = 0;
113  for (CDT::Face_iterator face = cdt.faces_begin();
114  face != cdt.faces_end(); ++face, faceCount += 3) {
115  m_triIndexes[0 + faceCount] =
116  static_cast<int>(face->vertex(0)->info());
117  m_triIndexes[1 + faceCount] =
118  static_cast<int>(face->vertex(1)->info());
119  m_triIndexes[2 + faceCount] =
120  static_cast<int>(face->vertex(2)->info());
121  };
122  }
123 
126  return true;
127 #else
128  outputErrorStr = "CGAL library not supported";
129  return false;
130 #endif
131 }
132 
133 bool Delaunay2dMesh::buildMesh(const std::vector<CCVector2>& points2D,
134  std::size_t pointCountToUse,
135  std::string& outputErrorStr) {
136 #if defined(USE_CGAL_LIB)
137 
138  // CGAL boilerplate
139  typedef CGAL::Exact_predicates_inexact_constructions_kernel K;
140  // We define a vertex_base with info. The "info" (std::size_t) allow us to
141  // keep track of the original point index.
142  typedef CGAL::Triangulation_vertex_base_with_info_2<std::size_t, K> Vb;
143  typedef CGAL::Triangulation_data_structure_2<Vb> Tds;
144  typedef CGAL::Delaunay_triangulation_2<K, Tds> DT;
145  typedef DT::Point cgalPoint;
146 
147  std::vector<std::pair<cgalPoint, std::size_t>> pts;
148  std::size_t pointCount = points2D.size();
149 
150  // we will use at most 'pointCountToUse' points (if not 0)
151  if (pointCountToUse > 0 && pointCountToUse < pointCount) {
152  pointCount = pointCountToUse;
153  }
154 
155  if (pointCount < 3) {
156  outputErrorStr = "Not enough points";
157  return false;
158  }
159 
160  try {
161  pts.reserve(pointCount);
162  } catch (const std::bad_alloc&) {
163  outputErrorStr = "Not enough memory";
164  return false;
165  };
166 
168  if (m_triIndexes) {
169  delete[] m_triIndexes;
170  m_triIndexes = nullptr;
171  }
172 
173  for (std::size_t i = 0; i < pointCount; ++i) {
174  const CCVector2* pt = &points2D[i];
175  pts.emplace_back(cgalPoint(pt->x, pt->y), i);
176  }
177 
178  // The delaunay triangulation is built according to the 2D point cloud
179  DT dt(pts.begin(), pts.end());
180 
181  m_numberOfTriangles = static_cast<unsigned>(dt.number_of_faces());
182  m_triIndexes = new int[dt.number_of_faces() * 3];
183 
184  // The cgal data structure is converted into CC one
185  if (m_numberOfTriangles > 0) {
186  int faceCount = 0;
187  for (DT::Face_iterator face = dt.faces_begin(); face != dt.faces_end();
188  ++face, faceCount += 3) {
189  m_triIndexes[0 + faceCount] =
190  static_cast<int>(face->vertex(0)->info());
191  m_triIndexes[1 + faceCount] =
192  static_cast<int>(face->vertex(1)->info());
193  m_triIndexes[2 + faceCount] =
194  static_cast<int>(face->vertex(2)->info());
195  };
196  }
197 
200  return true;
201 #else
202  outputErrorStr = "CGAL library not supported";
203  return false;
204 #endif
205 }
206 
208  const std::vector<CCVector2>& vertices2D,
209  const std::vector<CCVector2>& polygon2D,
210  bool removeOutside /*=true*/) {
211  if (!m_triIndexes || m_numberOfTriangles == 0) return false;
212 
213  // we expect the same number of 2D points as the actual number of points in
214  // the associated mesh (if any)
215  if (m_associatedCloud &&
216  static_cast<std::size_t>(m_associatedCloud->size()) !=
217  vertices2D.size())
218  return false;
219 
220  unsigned lastValidIndex = 0;
221 
222  // test each triangle center
223  {
224  const int* _triIndexes = m_triIndexes;
225  for (unsigned i = 0; i < m_numberOfTriangles; ++i, _triIndexes += 3) {
226  // compute the triangle's barycenter
227  const CCVector2& A = vertices2D[_triIndexes[0]];
228  const CCVector2& B = vertices2D[_triIndexes[1]];
229  const CCVector2& C = vertices2D[_triIndexes[2]];
230  CCVector2 G = (A + B + C) / 3.0;
231 
232  // if G is inside the 'polygon'
233  bool isInside =
235  G, polygon2D);
236  if ((removeOutside && isInside) || (!removeOutside && !isInside)) {
237  // we keep the corresponding triangle
238  if (lastValidIndex != i)
239  memcpy(m_triIndexes + 3 * lastValidIndex, _triIndexes,
240  3 * sizeof(int));
241  ++lastValidIndex;
242  }
243  }
244  }
245 
246  // new number of triangles
247  m_numberOfTriangles = lastValidIndex;
248  if (m_numberOfTriangles) {
249  // shouldn't fail as m_numberOfTriangles is smaller!
250  m_triIndexes = static_cast<int*>(
251  realloc(m_triIndexes, sizeof(int) * 3 * m_numberOfTriangles));
252  } else {
253  // no triangle left!
254  delete[] m_triIndexes;
255  m_triIndexes = nullptr;
256  }
257 
258  // update iterators
261 
262  return true;
263 }
264 
266  PointCoordinateType maxEdgeLength) {
267  if (!m_associatedCloud || maxEdgeLength <= 0) return false;
268 
269  PointCoordinateType squareMaxEdgeLength = maxEdgeLength * maxEdgeLength;
270 
271  unsigned lastValidIndex = 0;
272  const int* _triIndexes = m_triIndexes;
273  for (unsigned i = 0; i < m_numberOfTriangles; ++i, _triIndexes += 3) {
274  const CCVector3* A = m_associatedCloud->getPoint(_triIndexes[0]);
275  const CCVector3* B = m_associatedCloud->getPoint(_triIndexes[1]);
276  const CCVector3* C = m_associatedCloud->getPoint(_triIndexes[2]);
277 
278  if ((*B - *A).norm2() <= squareMaxEdgeLength &&
279  (*C - *A).norm2() <= squareMaxEdgeLength &&
280  (*C - *B).norm2() <= squareMaxEdgeLength) {
281  if (lastValidIndex != i)
282  memcpy(m_triIndexes + 3 * lastValidIndex, _triIndexes,
283  sizeof(int) * 3);
284  ++lastValidIndex;
285  }
286  }
287 
288  if (lastValidIndex < m_numberOfTriangles) {
289  m_numberOfTriangles = lastValidIndex;
290  if (m_numberOfTriangles != 0) {
291  // shouldn't fail as m_numberOfTriangles is smaller than before!
292  m_triIndexes = static_cast<int*>(realloc(
293  m_triIndexes, sizeof(int) * 3 * m_numberOfTriangles));
294  } else // no more triangles?!
295  {
296  delete m_triIndexes;
297  m_triIndexes = nullptr;
298  }
301  }
302 
303  return true;
304 }
305 
307  if (!m_associatedCloud) return;
308 
310 
311  const int* _triIndexes = m_triIndexes;
312  for (unsigned i = 0; i < m_numberOfTriangles; ++i, _triIndexes += 3) {
313  tri.A = *m_associatedCloud->getPoint(_triIndexes[0]);
314  tri.B = *m_associatedCloud->getPoint(_triIndexes[1]);
315  tri.C = *m_associatedCloud->getPoint(_triIndexes[2]);
316  action(tri);
317  }
318 }
319 
322 }
323 
325  assert(m_associatedCloud);
326  if (m_globalIterator >= m_globalIteratorEnd) return nullptr;
327 
331 
332  return &m_dumpTriangle; // temporary!
333 }
334 
336  if (m_globalIterator >= m_globalIteratorEnd) return nullptr;
337 
341 
342  m_globalIterator += 3;
343 
344  return &m_dumpTriangleIndexes;
345 }
346 
348  assert(m_associatedCloud && triangleIndex < m_numberOfTriangles);
349 
350  const int* tri = m_triIndexes + 3 * triangleIndex;
354 
355  return static_cast<GenericTriangle*>(&m_dumpTriangle);
356 }
357 
358 void Delaunay2dMesh::getTriangleVertices(unsigned triangleIndex,
359  CCVector3& A,
360  CCVector3& B,
361  CCVector3& C) const {
362  assert(m_associatedCloud && triangleIndex < m_numberOfTriangles);
363 
364  const int* tri = m_triIndexes + 3 * triangleIndex;
365  m_associatedCloud->getPoint(*tri++, A);
366  m_associatedCloud->getPoint(*tri++, B);
367  m_associatedCloud->getPoint(*tri++, C);
368 }
369 
370 void Delaunay2dMesh::getTriangleVertices(unsigned triangleIndex,
371  double A[3],
372  double B[3],
373  double C[3]) const {
374  assert(m_associatedCloud && triangleIndex < m_numberOfTriangles);
375 
376  const int* tri = m_triIndexes + 3 * triangleIndex;
377  m_associatedCloud->getPoint(*tri++, A);
378  m_associatedCloud->getPoint(*tri++, B);
379  m_associatedCloud->getPoint(*tri++, C);
380 }
381 
383  unsigned triangleIndex) {
384  assert(triangleIndex < m_numberOfTriangles);
385 
386  return reinterpret_cast<VerticesIndexes*>(m_triIndexes + 3 * triangleIndex);
387 }
388 
390  if (m_associatedCloud) {
391  m_associatedCloud->getBoundingBox(bbMin, bbMax);
392  } else {
393  bbMin = bbMax = CCVector3(0, 0, 0);
394  }
395 }
396 
398  const std::vector<CCVector2>& contourPoints) {
399  size_t count = contourPoints.size();
400  if (count < 3) {
401  // not enough points
402  return nullptr;
403  }
404 
405  // DGM: we check that last vertex is different from the first one!
406  //(yes it happens ;)
407  if (contourPoints.back().x == contourPoints.front().x &&
408  contourPoints.back().y == contourPoints.front().y)
409  --count;
410 
411  std::string errorStr;
412  Delaunay2dMesh* mesh = new Delaunay2dMesh();
413  if (!mesh->buildMesh(contourPoints, count, errorStr) || mesh->size() == 0) {
414  // triangulation failed
415  delete mesh;
416  return nullptr;
417  }
418 
419  if (!mesh->removeOuterTriangles(contourPoints, contourPoints, true) ||
420  mesh->size() == 0) {
421  // an error occurred
422  delete mesh;
423  return nullptr;
424  }
425 
426  return mesh;
427 }
428 
430  GenericIndexedCloudPersist* contourPoints, int flatDimension /*=-1*/) {
431  if (!contourPoints) {
432  assert(false);
433  return nullptr;
434  }
435 
436  unsigned count = contourPoints->size();
437  if (count < 3) {
438  // Not enough input points
439  return nullptr;
440  }
441 
442  std::vector<CCVector2> contourPoints2D;
443  try {
444  contourPoints2D.reserve(count);
445  } catch (const std::bad_alloc&) {
446  // Not enough memory
447  return nullptr;
448  }
449 
450  if (flatDimension >= 0 && flatDimension <= 2) // X, Y or Z
451  {
452  const unsigned char Z = static_cast<unsigned char>(flatDimension);
453  const unsigned char X = (Z == 2 ? 0 : Z + 1);
454  const unsigned char Y = (X == 2 ? 0 : X + 1);
455  for (unsigned i = 0; i < contourPoints->size(); ++i) {
456  const CCVector3* P = contourPoints->getPoint(i);
457  contourPoints2D.push_back(CCVector2(P->u[X], P->u[Y]));
458  }
459  } else {
460  assert(flatDimension < 0);
461  Neighbourhood Yk(contourPoints);
462  if (!Yk.projectPointsOn2DPlane<CCVector2>(contourPoints2D)) {
463  // something bad happened
464  return nullptr;
465  }
466  }
467 
470  return dMesh;
471 }
Vector2Tpl< PointCoordinateType > CCVector2
Default 2D Vector.
Definition: CVGeom.h:780
Vector3Tpl< PointCoordinateType > CCVector3
Default 3D Vector.
Definition: CVGeom.h:798
float PointCoordinateType
Type of the coordinates of a (N-D) point.
Definition: CVTypes.h:16
int count
void * X
Definition: SmallVector.cpp:45
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:36
Type y
Definition: CVGeom.h:36
A class to compute and handle a Delaunay 2D mesh on a subset of points.
VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex) override
Returns the indexes of the vertices of a given triangle.
int * m_globalIterator
Iterator on the list of triangle vertex indexes.
int * m_triIndexes
Triangle vertex indexes.
unsigned m_numberOfTriangles
The number of triangles.
void forEach(genericTriangleAction action) override
Fast iteration mechanism.
static Delaunay2dMesh * TesselateContour(const std::vector< CCVector2 > &contourPoints)
~Delaunay2dMesh() override
Delaunay2dMesh destructor.
virtual unsigned size() const override
Returns the number of triangles.
static bool Available()
Returns whether 2D Delaunay triangulation is supported or not.
GenericIndexedCloud * m_associatedCloud
Associated point cloud.
void placeIteratorAtBeginning() override
Places the mesh iterator at the beginning.
void getBoundingBox(CCVector3 &bbMin, CCVector3 &bbMax) override
Returns the mesh bounding-box.
virtual bool removeOuterTriangles(const std::vector< CCVector2 > &vertices2D, const std::vector< CCVector2 > &polygon2D, bool removeOutside=true)
Removes the triangles falling outside of a given (2D) polygon.
bool removeTrianglesWithEdgesLongerThan(PointCoordinateType maxEdgeLength)
Filters out the triangles based on their edge length.
int * m_globalIteratorEnd
End position of global iterator.
virtual void getTriangleVertices(unsigned triangleIndex, CCVector3 &A, CCVector3 &B, CCVector3 &C) const override
Returns the vertices of a given triangle.
Delaunay2dMesh()
Delaunay2dMesh constructor.
virtual bool buildMesh(const std::vector< CCVector2 > &points2D, std::size_t pointCountToUse, std::string &outputErrorStr)
Build the Delaunay mesh on top a set of 2D points.
virtual void linkMeshWith(GenericIndexedCloud *aCloud, bool passOwnership=false)
Associate this mesh to a point cloud.
GenericTriangle * _getNextTriangle() override
Returns the next triangle (relatively to the global iterator position)
VerticesIndexes * getNextTriangleVertIndexes() override
GenericTriangle * _getTriangle(unsigned triangleIndex) override
Returns the ith triangle.
VerticesIndexes m_dumpTriangleIndexes
Dump triangle index structure to transmit temporary data.
SimpleTriangle m_dumpTriangle
Dump triangle structure to transmit temporary data.
virtual void getBoundingBox(CCVector3 &bbMin, CCVector3 &bbMax)=0
Returns the cloud bounding box.
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
A generic 3D point cloud with index-based point access.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
std::function< void(GenericTriangle &)> genericTriangleAction
Generic function to apply to a triangle (used by foreach)
Definition: GenericMesh.h:53
A generic triangle interface.
static bool isPointInsidePoly(const CCVector2 &P, const GenericIndexedCloud *polyVertices)
Tests if a point is inside a polygon (2D)
bool projectPointsOn2DPlane(std::vector< Vec2D > &points2D, const PointCoordinateType *planeEquation=nullptr, CCVector3 *O=nullptr, CCVector3 *X=nullptr, CCVector3 *Y=nullptr, InputVectorsUsage vectorsUsage=None)
Projects points on the best fitting LS plane.
Definition: Neighbourhood.h:89
A simple triangle class.
std::vector< unsigned int > face
Generic file read and write utility for python interface.
Triangle described by the indexes of its 3 vertices.