ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvContourExtractor.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 "ecvContourExtractor.h"
9 
10 #include "ecvContourExtractorDlg.h"
11 
12 // CV_CORE_LIB
14 #include <Neighbourhood.h>
15 #include <PointProjectionTools.h>
16 
17 // CV_DB_LIB
18 #include <CVLog.h>
19 #include <ecv2DLabel.h>
20 #include <ecvPointCloud.h>
21 
22 #ifdef USE_TBB
23 #include <tbb/parallel_for.h>
24 #endif
25 
26 // System
27 #include <assert.h>
28 
29 #include <cmath>
30 #include <set>
31 
32 // list of already used point to avoid hull's inner loops
38 };
39 
41 typedef std::list<Vertex2D*>::iterator VertexIterator;
42 typedef std::list<Vertex2D*>::const_iterator ConstVertexIterator;
43 
44 struct Edge {
46 
47  Edge(const VertexIterator& A,
48  unsigned _nearestPointIndex,
49  float _nearestPointSquareDist)
50  : itA(A),
51  nearestPointIndex(_nearestPointIndex),
52  nearestPointSquareDist(_nearestPointSquareDist) {}
53 
54  // operator
55  inline bool operator<(const Edge& e) const {
57  }
58 
60  unsigned nearestPointIndex;
62 };
63 
65 
68  unsigned& minIndex,
69  const VertexIterator& itA,
70  const VertexIterator& itB,
71  const std::vector<Vertex2D>& points,
72  const std::vector<HullPointFlags>& pointFlags,
73  PointCoordinateType minSquareEdgeLength,
74  bool allowLongerChunks = false,
75  double minCosAngle = -1.0) {
76  // look for the nearest point in the input set
77  PointCoordinateType minDist2 = -1;
78  const CCVector2 AB = **itB - **itA;
79  const PointCoordinateType squareLengthAB = AB.norm2();
80  const unsigned pointCount = static_cast<unsigned>(points.size());
81 
82 #ifdef USE_TBB
83  tbb::parallel_for(
84  static_cast<unsigned int>(0), pointCount, [&](unsigned int i) {
85  const Vertex2D& P = points[i];
86  if (pointFlags[P.index] != POINT_NOT_USED) return;
87 
88  // skip the edge vertices!
89  if (P.index == (*itA)->index || P.index == (*itB)->index) {
90  return;
91  }
92 
93  // we only consider 'inner' points
94  const CCVector2 AP = P - **itA;
95  if (AB.x * AP.y - AB.y * AP.x < 0) {
96  return;
97  }
98 
99  // check the angle
100  if (minCosAngle > -1.0) {
101  const CCVector2 PB = **itB - P;
102  const PointCoordinateType dotProd =
103  AP.x * PB.x + AP.y * PB.y;
104  const PointCoordinateType minDotProd =
105  static_cast<PointCoordinateType>(
106  minCosAngle *
107  std::sqrt(AP.norm2() * PB.norm2()));
108  if (dotProd < minDotProd) {
109  return;
110  }
111  }
112 
113  const PointCoordinateType dot =
114  AB.dot(AP); // = cos(PAB) * ||AP|| * ||AB||
115  if (dot >= 0 && dot <= squareLengthAB) {
116  const CCVector2 HP = AP - AB * (dot / squareLengthAB);
117  const PointCoordinateType dist2 = HP.norm2();
118  if (minDist2 < 0 || dist2 < minDist2) {
119  // the 'nearest' point must also be a valid candidate
120  //(i.e. at least one of the created edges is smaller
121  // than the original one and we don't create too small
122  // edges!)
123  const PointCoordinateType squareLengthAP = AP.norm2();
124  const PointCoordinateType squareLengthBP =
125  (P - **itB).norm2();
126  if (squareLengthAP >= minSquareEdgeLength &&
127  squareLengthBP >= minSquareEdgeLength &&
128  (allowLongerChunks ||
129  (squareLengthAP < squareLengthAB ||
130  squareLengthBP < squareLengthAB))) {
131  minDist2 = dist2;
132  minIndex = i;
133  }
134  }
135  }
136  });
137 #else
138  for (unsigned i = 0; i < pointCount; ++i) {
139  const Vertex2D& P = points[i];
140  if (pointFlags[P.index] != POINT_NOT_USED) continue;
141 
142  // skip the edge vertices!
143  if (P.index == (*itA)->index || P.index == (*itB)->index) {
144  continue;
145  }
146 
147  // we only consider 'inner' points
148  CCVector2 AP = P - **itA;
149  if (AB.x * AP.y - AB.y * AP.x < 0) {
150  continue;
151  }
152 
153  // check the angle
154  if (minCosAngle > -1.0) {
155  CCVector2 PB = **itB - P;
156  PointCoordinateType dotProd = AP.x * PB.x + AP.y * PB.y;
157  PointCoordinateType minDotProd = static_cast<PointCoordinateType>(
158  minCosAngle * std::sqrt(AP.norm2() * PB.norm2()));
159  if (dotProd < minDotProd) {
160  continue;
161  }
162  }
163 
164  PointCoordinateType dot = AB.dot(AP); // = cos(PAB) * ||AP|| * ||AB||
165  if (dot >= 0 && dot <= squareLengthAB) {
166  CCVector2 HP = AP - AB * (dot / squareLengthAB);
167  PointCoordinateType dist2 = HP.norm2();
168  if (minDist2 < 0 || dist2 < minDist2) {
169  // the 'nearest' point must also be a valid candidate
170  //(i.e. at least one of the created edges is smaller than the
171  // original one and we don't create too small edges!)
172  PointCoordinateType squareLengthAP = AP.norm2();
173  PointCoordinateType squareLengthBP = (P - **itB).norm2();
174  if (squareLengthAP >= minSquareEdgeLength &&
175  squareLengthBP >= minSquareEdgeLength &&
176  (allowLongerChunks || (squareLengthAP < squareLengthAB ||
177  squareLengthBP < squareLengthAB))) {
178  minDist2 = dist2;
179  minIndex = i;
180  }
181  }
182  }
183  }
184 #endif
185 
186  return (minDist2 < 0 ? minDist2 : minDist2 / squareLengthAB);
187 }
188 
190  std::vector<Vertex2D>& points,
191  std::list<Vertex2D*>& hullPoints,
192  ContourType contourType,
193  bool allowMultiPass,
194  PointCoordinateType maxSquareEdgeLength /*=0*/,
195  bool enableVisualDebugMode /*=false*/,
196  double maxAngleDeg /*=0.0*/) {
197  // first compute the Convex hull
199  hullPoints))
200  return false;
201 
202  // do we really need to compute the concave hull?
203  if (hullPoints.size() < 2 || maxSquareEdgeLength < 0) return true;
204 
205  unsigned pointCount = static_cast<unsigned>(points.size());
206 
207  std::vector<HullPointFlags> pointFlags;
208  try {
209  pointFlags.resize(pointCount, POINT_NOT_USED);
210  } catch (...) {
211  // not enough memory
212  return false;
213  }
214 
215  double minCosAngle =
216  maxAngleDeg <= 0 ? -1.0 : std::cos(maxAngleDeg * M_PI / 180.0);
217 
218  // hack: compute the theoretical 'minimal' edge length
219  PointCoordinateType minSquareEdgeLength = 0;
220  {
221  CCVector2 minP, maxP;
222  for (size_t i = 0; i < pointCount; ++i) {
223  const Vertex2D& P = points[i];
224  if (i) {
225  minP.x = std::min(P.x, minP.x);
226  minP.y = std::min(P.y, minP.y);
227  maxP.x = std::max(P.x, maxP.x);
228  maxP.y = std::max(P.y, maxP.y);
229  } else {
230  minP = maxP = P;
231  }
232  }
233  minSquareEdgeLength =
234  (maxP - minP).norm2() /
235  static_cast<PointCoordinateType>(
236  1.0e7); // 10^-7 of the max bounding rectangle side
237  minSquareEdgeLength =
238  std::min(minSquareEdgeLength, maxSquareEdgeLength / 10);
239 
240  // we remove very small edges
241  for (VertexIterator itA = hullPoints.begin(); itA != hullPoints.end();
242  ++itA) {
243  VertexIterator itB = itA;
244  ++itB;
245  if (itB == hullPoints.end()) itB = hullPoints.begin();
246  if ((**itB - **itA).norm2() < minSquareEdgeLength) {
247  pointFlags[(*itB)->index] = POINT_FROZEN;
248  hullPoints.erase(itB);
249  }
250  }
251 
252  if (contourType != FULL) {
253  // we will now try to determine which part of the contour is the
254  // 'upper' one and which one is the 'lower' one
255 
256  // search for the min and max vertices
257  VertexIterator itLeft = hullPoints.begin();
258  VertexIterator itRight = hullPoints.begin();
259  {
260  for (VertexIterator it = hullPoints.begin();
261  it != hullPoints.end(); ++it) {
262  if ((*it)->x < (*itLeft)->x ||
263  ((*it)->x == (*itLeft)->x && (*it)->y < (*itLeft)->y)) {
264  itLeft = it;
265  }
266  if ((*it)->x > (*itRight)->x ||
267  ((*it)->x == (*itRight)->x &&
268  (*it)->y < (*itRight)->y)) {
269  itRight = it;
270  }
271  }
272  }
273  assert(itLeft != itRight);
274  // find the right way to go
275  {
276  VertexIterator itBefore = itLeft;
277  if (itBefore == hullPoints.begin()) itBefore = hullPoints.end();
278  --itBefore;
279  VertexIterator itAfter = itLeft;
280  ++itAfter;
281  if (itAfter == hullPoints.end()) itAfter = hullPoints.begin();
282 
283  bool forward =
284  ((**itBefore - **itLeft).cross(**itAfter - **itLeft) <
285  0 &&
286  contourType == LOWER);
287  if (!forward) std::swap(itLeft, itRight);
288  }
289 
290  // copy the right part
291  std::list<Vertex2D*> halfHullPoints;
292  try {
293  for (VertexIterator it = itLeft;; ++it) {
294  if (it == hullPoints.end()) it = hullPoints.begin();
295  halfHullPoints.push_back(*it);
296  if (it == itRight) break;
297  }
298  } catch (const std::bad_alloc&) {
299  // not enough memory
300  return false;
301  }
302  // replace the input hull by the selected part
303  hullPoints = halfHullPoints;
304  }
305 
306  if (hullPoints.size() < 2) {
307  // no more edges?!
308  return false;
309  }
310  }
311 
312  // DEBUG MECHANISM
313  ccContourExtractorDlg debugDialog;
314  ccPointCloud* debugCloud = 0;
315  ccPolyline* debugContour = 0;
316  ccPointCloud* debugContourVertices = 0;
317  if (enableVisualDebugMode) {
318  debugDialog.init();
319  debugDialog.setGeometry(50, 50, 800, 600);
320  debugDialog.show();
321 
322  // create point cloud with all (2D) input points
323  {
324  debugCloud = new ccPointCloud;
325  debugCloud->reserve(pointCount);
326  for (size_t i = 0; i < pointCount; ++i) {
327  const Vertex2D& P = points[i];
328  debugCloud->addPoint(CCVector3(P.x, P.y, 0));
329  }
330  debugCloud->setPointSize(3);
331  debugDialog.addToDisplay(debugCloud,
332  false); // the window will take care of
333  // deleting this entity!
334  }
335 
336  // create polyline
337  {
338  debugContourVertices = new ccPointCloud;
339  debugContour = new ccPolyline(debugContourVertices);
340  debugContour->addChild(debugContourVertices);
341  unsigned hullSize = static_cast<unsigned>(hullPoints.size());
342  debugContour->reserve(hullSize);
343  debugContourVertices->reserve(hullSize);
344  unsigned index = 0;
345  for (VertexIterator itA = hullPoints.begin();
346  itA != hullPoints.end(); ++itA, ++index) {
347  const Vertex2D* P = *itA;
348  debugContourVertices->addPoint(CCVector3(P->x, P->y, 0));
349  debugContour->addPointIndex(index /*(*itA)->index*/);
350  }
351  debugContour->setColor(ecvColor::red);
352  debugContourVertices->setEnabled(false);
353  debugContour->setClosed(contourType == FULL);
354  debugDialog.addToDisplay(debugContour,
355  false); // the window will take care of
356  // deleting this entity!
357  }
358 
359  // set zoom
360  {
361  ccBBox box = debugCloud->getOwnBB();
362  debugDialog.zoomOn(box);
363  }
364  debugDialog.refresh();
365  }
366 
367  // Warning: high STL containers usage ahead ;)
368  unsigned step = 0;
369  bool somethingHasChanged = true;
370  while (somethingHasChanged) {
371  try {
372  somethingHasChanged = false;
373  ++step;
374 
375  // reset point flags
376  // for (size_t i=0; i<pointCount; ++i)
377  //{
378  // if (pointFlags[i] != POINT_FROZEN)
379  // pointFlags[i] = POINT_NOT_USED;
380  // }
381 
382  // build the initial edge list & flag the convex hull points
383  std::multiset<Edge> edges;
384  // initial number of edges
385  assert(hullPoints.size() >= 2);
386  size_t initEdgeCount = hullPoints.size();
387  if (contourType != FULL) --initEdgeCount;
388 
389  VertexIterator itB = hullPoints.begin();
390  for (size_t i = 0; i < initEdgeCount; ++i) {
391  VertexIterator itA = itB;
392  ++itB;
393  if (itB == hullPoints.end()) itB = hullPoints.begin();
394 
395  // we will only process the edges that are longer than the
396  // maximum specified length
397  if ((**itB - **itA).norm2() > maxSquareEdgeLength) {
398  unsigned nearestPointIndex = 0;
400  nearestPointIndex, itA, itB, points, pointFlags,
401  minSquareEdgeLength, step > 1, minCosAngle);
402 
403  if (minSquareDist >= 0) {
404  Edge e(itA, nearestPointIndex, minSquareDist);
405  edges.insert(e);
406  }
407  }
408 
409  pointFlags[(*itA)->index] = POINT_USED;
410  }
411 
412  // flag the last vertex as well for non closed contours!
413  if (contourType != FULL)
414  pointFlags[(*hullPoints.rbegin())->index] = POINT_USED;
415 
416  while (!edges.empty()) {
417  // current edge (AB)
418  // this should be the edge with the nearest 'candidate'
419  Edge e = *edges.begin();
420  edges.erase(edges.begin());
421 
422  VertexIterator itA = e.itA;
423  VertexIterator itB = itA;
424  ++itB;
425  if (itB == hullPoints.end()) {
426  assert(contourType == FULL);
427  itB = hullPoints.begin();
428  }
429 
430  // nearest point
431  const Vertex2D& P = points[e.nearestPointIndex];
432  assert(pointFlags[P.index] ==
433  POINT_NOT_USED); // we don't consider already used
434  // points!
435 
436  // create labels
437  cc2DLabel* edgeLabel = 0;
438  cc2DLabel* label = 0;
439  if (enableVisualDebugMode && !debugDialog.isSkipped()) {
440  edgeLabel = new cc2DLabel("edge");
441  unsigned indexA = 0;
442  unsigned indexB = 0;
443  for (size_t i = 0; i < pointCount; ++i) {
444  const Vertex2D& P = points[i];
445  if (&P == *itA) indexA = static_cast<unsigned>(i);
446  if (&P == *itB) indexB = static_cast<unsigned>(i);
447  }
448  edgeLabel->addPickedPoint(debugCloud, indexA);
449  edgeLabel->addPickedPoint(debugCloud, indexB);
450  edgeLabel->setVisible(true);
451  edgeLabel->setDisplayedIn2D(false);
452  debugDialog.addToDisplay(edgeLabel);
453  debugDialog.refresh();
454 
455  label = new cc2DLabel("nearest point");
456  label->addPickedPoint(debugCloud, e.nearestPointIndex);
457  label->setVisible(true);
458  label->setSelected(true);
459  debugDialog.addToDisplay(label);
460  debugDialog.displayMessage(
461  QString("nearest point found index #%1 (dist = %2)")
462  .arg(e.nearestPointIndex)
463  .arg(sqrt(e.nearestPointSquareDist)),
464  true);
465  }
466 
467  // check that we don't create too small edges!
468  // CCVector2 AP = (P-**itA);
469  // CCVector2 PB = (**itB-P);
470  // PointCoordinateType squareLengthAP = (P-**itA).norm2();
471  // PointCoordinateType squareLengthPB = (**itB-P).norm2();
474  // assert( squareLengthAP < e.squareLength || squareLengthPB <
475  // e.squareLength ); if (squareLengthAP < minSquareEdgeLength ||
476  // squareLengthPB < minSquareEdgeLength)
477  //{
478  // pointFlags[P.index] = POINT_IGNORED;
479  // edges.push(e); //retest the edge!
480  // if (enableVisualDebugMode)
481  // debugDialog.displayMessage("nearest point is too
482  // close!",true);
483  // }
484 
485  // last check: the new segments must not intersect with the
486  // actual hull!
487  bool intersect = false;
488  // if (false)
489  {
490  for (VertexIterator itJ = hullPoints.begin(), itI = itJ++;
491  itI != hullPoints.end(); ++itI, ++itJ) {
492  if (itJ == hullPoints.end()) {
493  if (contourType == FULL)
494  itJ = hullPoints.begin();
495  else
496  break;
497  }
498 
499  if (((*itI)->index != (*itA)->index &&
500  (*itJ)->index != (*itA)->index &&
502  segmentIntersect(**itI, **itJ, **itA,
503  P)) ||
504  ((*itI)->index != (*itB)->index &&
505  (*itJ)->index != (*itB)->index &&
507  segmentIntersect(**itI, **itJ, P,
508  **itB))) {
509  intersect = true;
510  break;
511  }
512  }
513  }
514  if (!intersect) {
515  // add point to concave hull
516  VertexIterator itP = hullPoints.insert(
517  itB == hullPoints.begin() ? hullPoints.end() : itB,
519 
520  // we won't use P anymore!
521  pointFlags[P.index] = POINT_USED;
522 
523  somethingHasChanged = true;
524 
525  if (enableVisualDebugMode && !debugDialog.isSkipped()) {
526  if (debugContour) {
527  debugContourVertices->clear();
528  unsigned hullSize =
529  static_cast<unsigned>(hullPoints.size());
530  debugContourVertices->reserve(hullSize);
531  unsigned index = 0;
532  for (VertexIterator it = hullPoints.begin();
533  it != hullPoints.end(); ++it, ++index) {
534  const Vertex2D* P = *it;
535  debugContourVertices->addPoint(
536  CCVector3(P->x, P->y, 0));
537  }
538  debugContour->reserve(hullSize);
539  debugContour->addPointIndex(hullSize - 1);
540  debugDialog.refresh();
541  }
542  debugDialog.displayMessage(
543  "point has been added to contour", true);
544  }
545 
546  // update all edges that were having 'P' as their nearest
547  // candidate as well
548  if (!edges.empty()) {
549  std::vector<VertexIterator> removed;
550  std::multiset<Edge>::const_iterator lastValidIt =
551  edges.end();
552  for (std::multiset<Edge>::const_iterator it =
553  edges.begin();
554  it != edges.end(); ++it) {
555  if ((*it).nearestPointIndex ==
556  e.nearestPointIndex) {
557  // we'll have to put them back afterwards!
558  removed.push_back((*it).itA);
559 
560  edges.erase(it);
561  if (edges.empty()) break;
562  if (lastValidIt != edges.end())
563  it = lastValidIt;
564  else
565  it = edges.begin();
566  } else {
567  lastValidIt = it;
568  }
569  }
570 
571  // update the removed edges info and put them back in
572  // the main list
573  for (size_t i = 0; i < removed.size(); ++i) {
574  VertexIterator itC = removed[i];
575  VertexIterator itD = itC;
576  ++itD;
577  if (itD == hullPoints.end())
578  itD = hullPoints.begin();
579 
580  unsigned nearestPointIndex = 0;
581  PointCoordinateType minSquareDist =
583  nearestPointIndex, itC, itD, points,
584  pointFlags, minSquareEdgeLength,
585  false, minCosAngle);
586 
587  if (minSquareDist >= 0) {
588  Edge e(itC, nearestPointIndex, minSquareDist);
589  edges.insert(e);
590  }
591  }
592  }
593 
594  // we'll inspect the two new segments later (if necessary)
595  if ((P - **itA).norm2() > maxSquareEdgeLength) {
596  unsigned nearestPointIndex = 0;
597  PointCoordinateType minSquareDist =
598  FindNearestCandidate(nearestPointIndex, itA,
599  itP, points, pointFlags,
600  minSquareEdgeLength, false,
601  minCosAngle);
602 
603  if (minSquareDist >= 0) {
604  Edge e(itA, nearestPointIndex, minSquareDist);
605  edges.insert(e);
606  }
607  }
608  if ((**itB - P).norm2() > maxSquareEdgeLength) {
609  unsigned nearestPointIndex = 0;
610  PointCoordinateType minSquareDist =
611  FindNearestCandidate(nearestPointIndex, itP,
612  itB, points, pointFlags,
613  minSquareEdgeLength, false,
614  minCosAngle);
615 
616  if (minSquareDist >= 0) {
617  Edge e(itP, nearestPointIndex, minSquareDist);
618  edges.insert(e);
619  }
620  }
621  } else {
622  if (enableVisualDebugMode)
623  debugDialog.displayMessage(
624  "[rejected] new edge would intersect the "
625  "current contour!",
626  true);
627  }
628 
629  // remove labels
630  if (label) {
631  assert(enableVisualDebugMode);
632  debugDialog.removFromDisplay(label);
633  delete label;
634  label = 0;
635  // debugDialog.refresh();
636  }
637 
638  if (edgeLabel) {
639  assert(enableVisualDebugMode);
640  debugDialog.removFromDisplay(edgeLabel);
641  delete edgeLabel;
642  edgeLabel = 0;
643  // debugDialog.refresh();
644  }
645  }
646  } catch (...) {
647  // not enough memory
648  return false;
649  }
650 
651  if (!allowMultiPass) break;
652  }
653 
654  return true;
655 }
656 
657 typedef std::list<Vertex2D*> Hull2D;
658 
661  bool allowMultiPass,
662  PointCoordinateType maxEdgeLength /*=0*/,
663  const PointCoordinateType* preferredNormDim /*=0*/,
664  const PointCoordinateType* preferredUpDir /*=0*/,
665  ContourType contourType /*=FULL*/,
666  std::vector<unsigned>* originalPointIndexes /*=0*/,
667  bool enableVisualDebugMode /*=false*/,
668  double maxAngleDeg /*=0.0*/) {
669  assert(points);
670 
671  if (!points) return nullptr;
672 
673  unsigned ptsCount = points->size();
674  if (ptsCount < 3) return nullptr;
675 
677  // local base
678  CCVector3 O;
679  CCVector3 X;
680  CCVector3 Y;
681 
684 
685  // we project the input points on a plane
686  std::vector<Vertex2D> points2D;
687  PointCoordinateType* planeEq = nullptr;
688 
689  if (preferredUpDir != nullptr) {
690  Y = CCVector3(preferredUpDir);
692  }
693 
694  // if the user has specified a default direction, we'll use it as
695  // 'projecting plane'
696  PointCoordinateType preferredPlaneEq[4] = {0, 0, 1, 0};
697 
698  if (preferredNormDim != nullptr) {
699  const CCVector3* G = points->getPoint(
700  0); // any point through which the plane passes is ok
701  preferredPlaneEq[0] = preferredNormDim[0];
702  preferredPlaneEq[1] = preferredNormDim[1];
703  preferredPlaneEq[2] = preferredNormDim[2];
704  CCVector3::vnormalize(preferredPlaneEq);
705  preferredPlaneEq[3] = CCVector3::vdot(G->u, preferredPlaneEq);
706  planeEq = preferredPlaneEq;
707 
708  if (preferredUpDir != nullptr) {
709  O = *G;
710  // Y = CCVector3(preferredUpDir); //already done above
711  X = Y.cross(CCVector3(preferredNormDim));
713  }
714  }
715 
716  if (!Yk.projectPointsOn2DPlane<Vertex2D>(points2D, planeEq, &O, &X, &Y,
717  vectorsUsage)) {
719  "[ExtractFlatContour] Failed to project the points on the LS "
720  "plane (not enough memory?)!");
721  return 0;
722  }
723 
724  // update the points indexes (not done by
725  // Neighbourhood::projectPointsOn2DPlane)
726  {
727  for (unsigned i = 0; i < ptsCount; ++i) points2D[i].index = i;
728  }
729 
730  // try to get the points on the convex/concave hull to build the contour and
731  // the polygon
732  Hull2D hullPoints;
733  if (!ExtractConcaveHull2D(points2D, hullPoints, contourType, allowMultiPass,
734  maxEdgeLength * maxEdgeLength,
735  enableVisualDebugMode, maxAngleDeg)) {
737  "[ExtractFlatContour] Failed to compute the convex hull of the "
738  "input points!");
739  return 0;
740  }
741 
742  if (originalPointIndexes) {
743  try {
744  originalPointIndexes->resize(hullPoints.size(), 0);
745  } catch (const std::bad_alloc&) {
746  // not enough memory
747  CVLog::Error("[ExtractFlatContour] Not enough memory!");
748  return 0;
749  }
750 
751  unsigned i = 0;
752  for (Hull2D::const_iterator it = hullPoints.begin();
753  it != hullPoints.end(); ++it, ++i) {
754  (*originalPointIndexes)[i] = (*it)->index;
755  }
756  }
757 
758  unsigned hullPtsCount = static_cast<unsigned>(hullPoints.size());
759 
760  // create vertices
761  ccPointCloud* contourVertices = new ccPointCloud();
762  {
763  if (!contourVertices->reserve(hullPtsCount)) {
764  delete contourVertices;
765  contourVertices = nullptr;
766  CVLog::Error("[ExtractFlatContour] Not enough memory!");
767  return nullptr;
768  }
769 
770  // projection on the LS plane (in 3D)
771  for (Hull2D::const_iterator it = hullPoints.begin();
772  it != hullPoints.end(); ++it) {
773  contourVertices->addPoint(O + X * (*it)->x + Y * (*it)->y);
774  }
775  contourVertices->setName("vertices");
776  contourVertices->setEnabled(false);
777  }
778 
779  // we create the corresponding (3D) polyline
780  ccPolyline* contourPolyline = new ccPolyline(contourVertices);
781  if (contourPolyline->reserve(hullPtsCount)) {
782  contourPolyline->addPointIndex(0, hullPtsCount);
783  contourPolyline->setClosed(contourType == FULL);
784  contourPolyline->setVisible(true);
785  contourPolyline->setName("contour");
786  contourPolyline->addChild(contourVertices);
787  } else {
788  delete contourPolyline;
789  contourPolyline = nullptr;
791  "[ExtractFlatContour] Not enough memory to create the contour "
792  "polyline!");
793  }
794 
795  return contourPolyline;
796 }
797 
800  bool allowMultiPass,
801  PointCoordinateType maxEdgeLength,
802  std::vector<ccPolyline*>& parts,
803  bool allowSplitting /*=true*/,
804  const PointCoordinateType* preferredDim /*=0*/,
805  bool enableVisualDebugMode /*=false*/) {
806  parts.clear();
807 
808  // extract whole contour
809  ccPolyline* basePoly =
810  ExtractFlatContour(points, allowMultiPass, maxEdgeLength,
811  preferredDim, 0, FULL, 0, enableVisualDebugMode);
812  if (!basePoly) {
813  return false;
814  } else if (!allowSplitting) {
815  parts.push_back(basePoly);
816  return true;
817  }
818 
819  // and split it if necessary
820  bool success = basePoly->split(maxEdgeLength, parts);
821 
822  delete basePoly;
823  basePoly = 0;
824 
825  return success;
826 }
constexpr double M_PI
Pi.
Definition: CVConst.h:19
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 points
void * X
Definition: SmallVector.cpp:45
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type dot(const Vector2Tpl &v) const
Dot product.
Definition: CVGeom.h:71
Type x
Definition: CVGeom.h:36
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:61
Type y
Definition: CVGeom.h:36
Vector3Tpl cross(const Vector3Tpl &v) const
Cross product.
Definition: CVGeom.h:412
static void vnormalize(PointCoordinateType p[])
Definition: CVGeom.h:569
static PointCoordinateType vdot(const PointCoordinateType p[], const PointCoordinateType q[])
Definition: CVGeom.h:520
2D label (typically attached to points)
Definition: ecv2DLabel.h:22
bool addPickedPoint(ccGenericPointCloud *cloud, unsigned pointIndex, bool entityCenter=false)
Adds a point to this label.
void setDisplayedIn2D(bool state)
Whether to display the label in 2D.
Definition: ecv2DLabel.h:114
Bounding box structure.
Definition: ecvBBox.h:25
Dialog for debugging contour extraction.
void removFromDisplay(ccHObject *obj)
Removes an entity from the (2D/3D) display.
bool isSkipped() const
Returns whether the dialog has been 'skipped' or not.
void init()
Initializes the display.
void addToDisplay(ccHObject *obj, bool noDependency=true)
Adds an entity to the (2D/3D) display.
void zoomOn(const ccBBox &bbox)
Zooms on a given 2D region (3D bouding-box considered in 2D only)
void refresh()
Forces refresh.
void displayMessage(QString message, bool waitForUserConfirmation=false)
Display a new message.
static ccPolyline * ExtractFlatContour(cloudViewer::GenericIndexedCloudPersist *points, bool allowMultiPass, PointCoordinateType maxEdgeLength=0, const PointCoordinateType *preferredNormDim=0, const PointCoordinateType *preferredUpDir=0, ContourType contourType=FULL, std::vector< unsigned > *originalPointIndexes=0, bool enableVisualDebugMode=false, double maxAngleDeg=0.0)
Extracts a unique closed (2D) contour polyline of a point cloud.
static bool ExtractConcaveHull2D(std::vector< cloudViewer::PointProjectionTools::IndexedCCVector2 > &points, std::list< cloudViewer::PointProjectionTools::IndexedCCVector2 * > &hullPoints, ContourType contourType, bool allowMultiPass, PointCoordinateType maxSquareLength=0, bool enableVisualDebugMode=false, double maxAngleDeg=90.0)
Determines the 'concave' hull of a set of points.
virtual void setVisible(bool state)
Sets entity visibility.
virtual void setSelected(bool state)
Selects/Unselects entity.
void setPointSize(unsigned size=0)
Sets point size.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
virtual bool addChild(ccHObject *child, int dependencyFlags=DP_PARENT_OF_OTHER, int insertIndex=-1)
Adds a child.
virtual void setName(const QString &name)
Sets object name.
Definition: ecvObject.h:75
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.)
bool reserve(unsigned numberOfPoints) override
Reserves memory for all the active features.
void clear() override
Clears the entity from all its points and features.
Colored polyline.
Definition: ecvPolyline.h:24
bool split(PointCoordinateType maxEdgeLength, std::vector< ccPolyline * > &parts)
Splits the polyline into several parts based on a maximum edge length.
void setColor(const ecvColor::Rgb &col)
Sets the polyline color.
Definition: ecvPolyline.h:81
A generic 3D point cloud with index-based and presistent access to points.
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
void addPoint(const CCVector3 &P)
Adds a 3D point to the database.
static bool extractConvexHull2D(std::vector< IndexedCCVector2 > &points, std::list< IndexedCCVector2 * > &hullPoints)
Determines the convex hull of a set of points.
void setClosed(bool state)
Sets whether the polyline is closed or not.
Definition: Polyline.h:29
void clear(bool unusedParam=true) override
Clears the cloud.
Definition: Polyline.cpp:15
virtual bool addPointIndex(unsigned globalIndex)
Point global index insertion mechanism.
virtual bool reserve(unsigned n)
Reserves some memory for hosting the point references.
std::list< Vertex2D * >::iterator VertexIterator
__host__ __device__ float3 cross(float3 a, float3 b)
Definition: cutil_math.h:1295
__host__ __device__ float dot(float2 a, float2 b)
Definition: cutil_math.h:1119
int min(int a, int b)
Definition: cutil_math.h:53
int max(int a, int b)
Definition: cutil_math.h:48
cloudViewer::PointProjectionTools::IndexedCCVector2 Vertex2D
@ POINT_USED
@ POINT_IGNORED
@ POINT_NOT_USED
@ POINT_FROZEN
std::list< Vertex2D * >::const_iterator ConstVertexIterator
std::list< Vertex2D * > Hull2D
std::list< Vertex2D * >::iterator VertexIterator
static PointCoordinateType FindNearestCandidate(unsigned &minIndex, const VertexIterator &itA, const VertexIterator &itB, const std::vector< Vertex2D > &points, const std::vector< HullPointFlags > &pointFlags, PointCoordinateType minSquareEdgeLength, bool allowLongerChunks=false, double minCosAngle=-1.0)
Finds the nearest (available) point to an edge.
constexpr Rgb red(MAX, 0, 0)
void swap(cloudViewer::core::SmallVectorImpl< T > &LHS, cloudViewer::core::SmallVectorImpl< T > &RHS)
Implement std::swap in terms of SmallVector swap.
Definition: SmallVector.h:1370
unsigned nearestPointIndex
VertexIterator itA
float nearestPointSquareDist
bool operator<(const Edge &e) const
Edge(const VertexIterator &A, unsigned _nearestPointIndex, float _nearestPointSquareDist)