ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvPointCloudLOD.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 "ecvPointCloudLOD.h"
9 
10 // Local
11 #include "ecvBBox.h"
12 #include "ecvPointCloud.h"
13 
14 // Qt
15 #include <QElapsedTimer>
16 #include <QThread>
17 
19 class ccPointCloudLODThread : public QThread {
20  Q_OBJECT
21 
22 public:
25  ccPointCloudLOD& lod,
26  uint32_t maxCountPerCell)
27  : QThread(),
28  m_cloud(cloud),
29  m_lod(lod),
30  m_octree(0),
31  m_maxCountPerCell(maxCountPerCell),
32  m_maxLevel(0) {}
33 
35  virtual ~ccPointCloudLODThread() { terminate(); }
36 
37 protected:
39  uint8_t fillNode(ccPointCloudLOD::Node& node) const {
40  const ccOctree::cellsContainer& cellCodes =
41  m_octree->pointsAndTheirCellCodes();
42  const unsigned char bitDec =
44  const cloudViewer::DgmOctree::CellCode currentTruncatedCellCode =
45  (cellCodes[node.firstCodeIndex].theCode >> bitDec);
46 
47  // first count the number of points and compute their center
48  {
49  node.pointCount = 0;
50 #ifdef COMPUTE_REAL_RADIUS
51  CCVector3d sumP(0, 0, 0);
52 #else // otherwise we use the bounding box
53  ccBBox bbox;
54 #endif
55  for (uint32_t codeIndex = node.firstCodeIndex;
56  codeIndex < cellCodes.size() &&
57  (cellCodes[codeIndex].theCode >> bitDec) ==
58  currentTruncatedCellCode;
59  ++codeIndex) {
60  ++node.pointCount;
61  const CCVector3* P =
62  m_cloud.getPoint(cellCodes[codeIndex].theIndex);
63 #ifdef COMPUTE_REAL_RADIUS
64  sumP += CCVector3d::fromArray(P->u);
65 #else
66  bbox.add(*P);
67 #endif
68  }
69 
70  // compute the radius
71 #ifdef COMPUTE_REAL_RADIUS
72  if (node.pointCount > 1) {
73  sumP /= node.pointCount;
74  double maxSquareRadius = 0;
75  for (uint32_t i = 0; i < node.pointCount; ++i) {
76  const CCVector3* P = m_cloud.getPoint(
77  cellCodes[node.firstCodeIndex + i].theIndex);
78  double squareRadius =
79  (CCVector3d::fromArray(P->u) - sumP).norm2();
80  if (squareRadius > maxSquareRadius) {
81  maxSquareRadius = squareRadius;
82  }
83  }
84  node.radius = static_cast<float>(sqrt(maxSquareRadius));
85  }
86  // update the center
87  node.center = CCVector3f::fromArray(sumP.u);
88 #else
89  if (node.pointCount > 1) {
90  node.radius = static_cast<float>(bbox.getDiagNormd());
91  }
92  node.center = CCVector3f::fromArray(bbox.getCenter().u);
93 #endif
94  }
95 
96  // do we need to subdivide this cell?
97  if (node.pointCount > m_maxCountPerCell &&
98  node.level + 1 <= m_maxLevel) {
99  for (uint32_t i = 0; i < node.pointCount;) {
100  int32_t childNodeIndex = m_lod.newCell(node.level + 1);
101  ccPointCloudLOD::Node& childNode =
102  m_lod.node(childNodeIndex, node.level + 1);
103  childNode.firstCodeIndex = node.firstCodeIndex + i;
104 
105  uint8_t childIndex = fillNode(childNode);
106  node.childIndexes[childIndex] = childNodeIndex;
107  node.childCount++;
108  i += childNode.pointCount;
109  }
110  }
111 
112  // return the node relative position
113  return static_cast<uint8_t>(currentTruncatedCellCode & 7);
114  }
115 
117  uint8_t fillNode_flat(ccPointCloudLOD::Node& node) const {
118  const ccOctree::cellsContainer& cellCodes =
119  m_octree->pointsAndTheirCellCodes();
120  const unsigned char bitDec =
122  const cloudViewer::DgmOctree::CellCode currentTruncatedCellCode =
123  (cellCodes[node.firstCodeIndex].theCode >> bitDec);
124 
125  // first count the number of points and compute their center
126  {
127  node.pointCount = 0;
128  CCVector3d sumP(0, 0, 0);
129  for (uint32_t codeIndex = node.firstCodeIndex;
130  codeIndex < cellCodes.size() &&
131  (cellCodes[codeIndex].theCode >> bitDec) ==
132  currentTruncatedCellCode;
133  ++codeIndex) {
134  ++node.pointCount;
135  const CCVector3* P =
136  m_cloud.getPoint(cellCodes[codeIndex].theIndex);
137  sumP += CCVector3d::fromArray(P->u);
138  }
139 
140  // compute the radius
141  if (node.pointCount > 1) {
142  sumP /= node.pointCount;
143  double maxSquareRadius = 0;
144  for (uint32_t i = 0; i < node.pointCount; ++i) {
145  const CCVector3* P = m_cloud.getPoint(
146  cellCodes[node.firstCodeIndex + i].theIndex);
147  double squareRadius =
148  (CCVector3d::fromArray(P->u) - sumP).norm2();
149  if (squareRadius > maxSquareRadius) {
150  maxSquareRadius = squareRadius;
151  }
152  }
153  node.radius = static_cast<float>(sqrt(maxSquareRadius));
154  }
155 
156  // update the center
157  node.center = CCVector3f::fromArray(sumP.u);
158  }
159 
160  // return the node relative position
161  return static_cast<uint8_t>(currentTruncatedCellCode & 7);
162  }
163 
164  // reimplemented from QThread
165  virtual void run() {
166  // reset structure
167  m_lod.clearData();
169 
170  unsigned pointCount = m_cloud.size();
171  if (pointCount == 0) {
173  return;
174  }
175 
176  CVLog::Print(QString("[LoD] Preparing LoD acceleration structure for "
177  "cloud '%1' [%2 points]...")
178  .arg(m_cloud.getName())
179  .arg(pointCount));
180  QElapsedTimer timer;
181  timer.start();
182 
183  // first we need an octree
185  if (!m_octree) {
187  if (m_octree->build(0 /*progressCallback*/) <= 0) {
188  // not enough memory
189  CVLog::Warning(QString("[LoD] Failed to compute octree on "
190  "cloud '%1' (not enough memory)")
191  .arg(m_cloud.getName()));
193  return;
194  }
195 
196  if (!m_cloud.getOctree()) // be sure that it hasn't been built in
197  // the meantime!
198  {
200  }
201  }
202 
203  // init LoD structure
204  if (!m_lod.initInternal(m_octree)) {
205  // not enough memory
206  CVLog::Warning(QString("[LoD] Failed to compute LOD structure on "
207  "cloud '%1' (not enough memory)")
208  .arg(m_cloud.getName()));
210  return;
211  }
212 
213  // make sure we deprecate the LOD structure when this octree is
214  // modified!
215  QObject::connect(m_octree.data(), &ccOctree::updated, this,
216  [&]() { m_cloud.clearLOD(); });
217 
218  m_maxLevel = static_cast<uint8_t>(
219  std::max<size_t>(1, m_lod.m_levels.size())) -
220  1;
222 
223 #if 0 // recursive path
224  // recursive
225  fillNode(m_lod.root());
226 
228  //m_lod.updateMaxRadii();
229  //m_lod.setMaxLevel(m_maxLevel);
230 
231  for (size_t i = 1; i < m_lod.m_levels.size(); ++i)
232  {
233  CVLog::Print(QString("[LoD] Level %1: %2 cells").arg(i).arg(m_lod.m_levels[i].data.size()));
234  }
235 
236 #else // layer by layer
237 
238  // init with root node
240 
241  // first we allow the division of nodes as deep as possible but with a
242  // minimum number of points per cell
243  for (uint8_t currentLevel = 0; currentLevel < m_maxLevel;
244  ++currentLevel) {
245  ccPointCloudLOD::Level& level = m_lod.m_levels[currentLevel];
246  if (level.data.empty()) {
247  break;
248  }
249 
250  // update maxRadius for the previous level
251  //{
252  // float maxRadius = 0;
253  // for (ccPointCloudLOD::Node& n : level.data)
254  // {
255  // if (n.radius > maxRadius)
256  // {
257  // maxRadius = n.radius;
258  // }
259  // }
260  // level.maxRadius = maxRadius;
261  // }
262 
263  // the previous level is now ready!
264  CVLog::Print(QString("[LoD] Level %1: %2 cells")
265  .arg(currentLevel)
266  .arg(level.data.size()));
267 
268  // now we can create the next level
269  if (currentLevel + 1 < m_maxLevel) {
270  for (ccPointCloudLOD::Node& node : level.data) {
271  // do we need to subdivide this cell?
272  if (node.pointCount > m_maxCountPerCell) {
273  for (uint32_t i = 0; i < node.pointCount;) {
274  int32_t childNodeIndex =
275  m_lod.newCell(node.level + 1);
276  ccPointCloudLOD::Node& childNode =
277  m_lod.node(childNodeIndex, node.level + 1);
278  childNode.firstCodeIndex = node.firstCodeIndex + i;
279 
280  uint8_t childIndex = fillNode_flat(childNode);
281  node.childIndexes[childIndex] = childNodeIndex;
282  node.childCount++;
283  i += childNode.pointCount;
284  }
285  }
286  }
287  }
288  }
289 
291  m_maxLevel = static_cast<uint8_t>(
292  std::max<size_t>(1, m_lod.m_levels.size())) -
293  1;
294 
295  // refinement step
296  if (true) {
297  // we look at the 'main' depth level (with the most point)
298  uint8_t biggestLevel = 0;
299  for (uint8_t i = 1; i <= m_maxLevel; ++i) {
300  if (m_lod.m_levels[i].data.size() >
301  m_lod.m_levels[biggestLevel].data.size()) {
302  biggestLevel = i;
303  }
304  }
305 
306  // now compute the mean radius for this level
307  // double meanRadius = 0;
308  //{
309  // const ccPointCloudLOD::Level& level =
310  // m_lod.m_levels[biggestLevel]; size_t cellCount =
311  // level.data.size(); for (size_t i = 0; i < cellCount; ++i)
312  // {
313  // meanRadius += level.data[i].radius;
314  // }
315 
316  // meanRadius /= cellCount;
317  //}
318 
319  // and divide again the cells (with a lower limit on the number of
320  // points)
321  biggestLevel = std::min<uint8_t>(biggestLevel, 10);
322  for (uint8_t currentLevel = 0; currentLevel < biggestLevel;
323  ++currentLevel) {
324  ccPointCloudLOD::Level& level = m_lod.m_levels[currentLevel];
325  assert(!level.data.empty());
326 
327  size_t cellCountBefore =
328  m_lod.m_levels[currentLevel + 1].data.size();
329  for (ccPointCloudLOD::Node& node : level.data) {
330  // do we need to subdivide this cell?
331  if (node.childCount == 0 && node.pointCount > 16) {
332  for (uint32_t i = 0; i < node.pointCount;) {
333  int32_t childNodeIndex =
334  m_lod.newCell(node.level + 1);
335  ccPointCloudLOD::Node& childNode =
336  m_lod.node(childNodeIndex, node.level + 1);
337  childNode.firstCodeIndex = node.firstCodeIndex + i;
338 
339  uint8_t childIndex = fillNode_flat(childNode);
340  node.childIndexes[childIndex] = childNodeIndex;
341  node.childCount++;
342  i += childNode.pointCount;
343  }
344  }
345  }
346 
347  size_t cellCountAfter =
348  m_lod.m_levels[currentLevel + 1].data.size();
349  CVLog::Print(QString("[LoD][pass 2] Level %1: %2 cells (+%3)")
350  .arg(currentLevel + 1)
351  .arg(cellCountAfter)
352  .arg(cellCountAfter - cellCountBefore));
353  }
354 
356  m_maxLevel = static_cast<uint8_t>(
357  std::max<size_t>(1, m_lod.m_levels.size())) -
358  1;
359  }
360 #endif
361 
363 
364  CVLog::Print(
365  QString("[LoD] Acceleration structure ready for cloud '%1' "
366  "(max level: %2 / mem. = %3 Mb / duration: %4 s.)")
367  .arg(m_cloud.getName())
368  .arg(m_maxLevel)
369  .arg(m_lod.memory() / static_cast<double>(1 << 20), 0,
370  'f', 2)
371  .arg(timer.elapsed() / 1000.0, 0, 'f', 1));
372  }
373 
378  uint8_t m_maxLevel;
379 };
380 
382  : m_indexMap(0),
383  m_lastIndexMap(0),
384  m_octree(0),
385  m_thread(0),
386  m_state(NOT_INITIALIZED) {
387  clearData(); // initializes the root node
388 }
389 
391 
392 size_t ccPointCloudLOD::memory() const {
393  size_t thisSize = sizeof(ccPointCloudLOD);
394 
395  size_t totalNodeCount = 0;
396  for (size_t i = 0; i < m_levels.size(); ++i) {
397  totalNodeCount += m_levels[i].data.size();
398  }
399  size_t nodeSize = sizeof(Node);
400  size_t nodesSize = totalNodeCount * nodeSize;
401 
402  return nodesSize + thisSize;
403 }
404 
406  if (!cloud) {
407  assert(false);
408  return false;
409  }
410 
411  if (isBroken()) {
412  return false;
413  }
414 
415  if (!m_thread) {
416  m_thread = new ccPointCloudLODThread(*cloud, *this, 256);
417  } else if (m_thread->isRunning()) {
418  // already running?
419  assert(false);
420  return true;
421  }
422 
423  m_thread->start();
424  return true;
425 }
426 
428  // 1 empty (root) node
429  m_levels.resize(1);
430  m_levels.front().data.resize(1);
431  m_levels.front().data.front() = Node();
432 }
433 
434 bool ccPointCloudLOD::initInternal(ccOctree::Shared octree) {
435  if (!octree) {
436  return false;
437  }
438 
439  // clear the structure (just in case)
440  clearData();
441 
442  QMutexLocker locker(&m_mutex);
443 
444  try {
447  } catch (const std::bad_alloc&) {
448  // not enough memory
449  return false;
450  }
451 
452  m_octree = octree;
453 
454  return true;
455 }
456 
457 int32_t ccPointCloudLOD::newCell(unsigned char level) {
458  assert(level != 0);
459  assert(level < m_levels.size());
460  Level& l = m_levels[level];
461 
462  // assert(l.data.size() < l.data.capacity());
463  l.data.emplace_back(level);
464 
465  return static_cast<int32_t>(l.data.size()) - 1;
466 }
467 
468 // void ccPointCloudLOD::updateMaxRadii()
469 //{
470 // QMutexLocker locker(&m_mutex);
471 //
472 // for (size_t i = 0; i < m_levels.size(); ++i)
473 // {
474 // if (!m_levels[i].data.empty())
475 // {
476 // float maxRadius = 0;
477 // for (Node& n : m_levels[i].data)
478 // {
479 // if (n.radius > maxRadius)
480 // {
481 // maxRadius = n.radius;
482 // }
483 // }
484 // m_levels[i].maxRadius = m_levels[i].data.front().radius;
485 // }
486 // }
487 // }
488 
490  QMutexLocker locker(&m_mutex);
491 
492  for (size_t i = 1; i < m_levels.size();
493  ++i) // DGM: always keep the root node!
494  {
495  if (!m_levels[i].data.empty()) {
496  m_levels[i].data.shrink_to_fit();
497  } else {
498  // first empty level: we can reduce the number of levels and stop
499  // here!
500  m_levels.resize(i);
501  break;
502  }
503  }
504  m_levels.shrink_to_fit();
505 }
506 
508  if (m_thread && m_thread->isRunning()) {
509  m_thread->terminate();
510  m_thread->wait();
511  }
512 
513  m_mutex.lock();
514 
515  if (m_thread) {
516  delete m_thread;
517  m_thread = 0;
518  }
519 
520  m_levels.clear();
522 
523  m_mutex.unlock();
524 }
525 
527  if (m_state != INITIALIZED) {
528  return;
529  }
530 
532 
533  for (size_t l = 0; l < m_levels.size(); ++l) {
534  for (Node& n : m_levels[l].data) {
535  n.displayedPointCount = 0;
536  n.intersection = Frustum::INSIDE;
537  }
538  }
539 }
540 
542 public:
544  const Frustum& frustum,
545  unsigned char maxLevel)
546  : m_lod(lod),
547  m_frustum(frustum),
548  m_maxLevel(maxLevel),
549  m_hasClipPlanes(false) {}
550 
551  void setClipPlanes(const ccClipPlaneSet& clipPlanes) {
552  try {
553  m_clipPlanes = clipPlanes;
554  } catch (const std::bad_alloc&) {
555  // not enough memory
556  m_hasClipPlanes = false;
557  }
558  m_hasClipPlanes = !m_clipPlanes.empty();
559  }
560 
561  void propagateFlag(ccPointCloudLOD::Node& node, uint8_t flag) {
562  node.intersection = flag;
563 
564  if (node.childCount) {
565  for (int i = 0; i < 8; ++i) {
566  if (node.childIndexes[i] >= 0) {
568  m_lod.node(node.childIndexes[i], node.level + 1),
569  flag);
570  }
571  }
572  }
573  }
574 
575  uint32_t flag(ccPointCloudLOD::Node& node) {
578  for (size_t i = 0; i < m_clipPlanes.size(); ++i) {
579  // distance from center to clip plane
580  // we assume the plane normal (= 3 first coefficients) is
581  // normalized!
582  const Tuple4Tpl<double>& eq = m_clipPlanes[i].equation;
583  double dist = eq.x * node.center.x + eq.y * node.center.y +
584  eq.z * node.center.z +
585  eq.w /* / CCVector3d::vnorm(eq.u) */;
586 
587  if (dist < node.radius) {
588  if (dist <= -node.radius) {
590  break;
591  } else {
593  }
594  }
595  }
596  }
597 
598  uint32_t visibleCount = 0;
599  switch (node.intersection) {
600  case Frustum::INSIDE:
601  visibleCount = node.pointCount;
602  // no need to propagate the visibility to the children as the
603  // default value should already be 'INSIDE'
604  break;
605 
606  case Frustum::INTERSECT:
607  // we have to test the children
608  {
609  if (node.level < m_maxLevel && node.childCount) {
610  for (int i = 0; i < 8; ++i) {
611  if (node.childIndexes[i] >= 0) {
612  ccPointCloudLOD::Node& childNode = m_lod.node(
613  node.childIndexes[i], node.level + 1);
614  visibleCount += flag(childNode);
615  }
616  }
617 
618  if (visibleCount == 0) {
619  // as no point is visible we can flag this node as
620  // being outside/invisible
622  }
623  } else {
624  // we have to consider that all points are visible
625  visibleCount = node.pointCount;
626  }
627  }
628  break;
629 
630  case Frustum::OUTSIDE:
631  // be sure that all children nodes are flagged as outside!
633  break;
634  }
635 
636  return visibleCount;
637  }
638 
641  unsigned char m_maxLevel;
644 };
645 
646 uint32_t ccPointCloudLOD::flagVisibility(const Frustum& frustum,
647  ccClipPlaneSet* clipPlanes /*=0*/) {
648  if (m_state != INITIALIZED) {
649  assert(false);
651  return 0;
652  }
653 
654  resetVisibility();
655 
656  PointCloudLODVisibilityFlagger lodVisibility(
657  *this, frustum, static_cast<unsigned char>(m_levels.size()));
658  if (clipPlanes) {
659  lodVisibility.setClipPlanes(*clipPlanes);
660  }
661 
662  m_currentState.visiblePoints = lodVisibility.flag(root());
663 
665 }
666 
668  if (m_indexMap.capacity() == 0) {
669  assert(false);
670  return 0;
671  }
672 
673  uint32_t displayedCount = 0;
674 
675  if (node.childCount) {
676  uint32_t thisNodeRemainingCount =
678  assert(count <= thisNodeRemainingCount);
679  bool displayAll = (count >= thisNodeRemainingCount);
680 
681  for (int i = 0; i < 8; ++i) {
682  if (node.childIndexes[i] >= 0) {
683  ccPointCloudLOD::Node& childNode =
684  this->node(node.childIndexes[i], node.level + 1);
685  if (childNode.intersection == Frustum::OUTSIDE) continue;
686  if (childNode.pointCount == childNode.displayedPointCount)
687  continue;
688  uint32_t childNodeRemainingCount =
689  (childNode.pointCount - childNode.displayedPointCount);
690 
691  uint32_t childMaxCount = 0;
692  if (displayAll) {
693  childMaxCount = childNodeRemainingCount;
694  } else {
695  double ratio =
696  static_cast<double>(childNodeRemainingCount) /
697  thisNodeRemainingCount;
698  childMaxCount = static_cast<uint32_t>(ceil(ratio * count));
699  if (displayedCount + childMaxCount > count) {
700  assert(count >= displayedCount);
701  childMaxCount = count - displayedCount;
702  i = 8; // we can stop right now
703  }
704  }
705 
706  uint32_t childDisplayedCount =
707  addNPointsToIndexMap(childNode, childMaxCount);
708  // assert(childDisplayedCount == childMaxCount || !displayAll ||
709  // childNode.intersection != Frustum::INSIDE);
710  assert(childDisplayedCount <= childMaxCount);
711 
712  displayedCount += childDisplayedCount;
713  assert(displayedCount <= count);
714  }
715  }
716  } else {
717  // we can display all the points
718  // uint32_t iStart = node.displayedPointCount;
719  uint32_t iStop =
721 
722  displayedCount = iStop - node.displayedPointCount;
723  assert(m_indexMap.size() + displayedCount <= m_indexMap.capacity());
724 
725  const ccOctree::cellsContainer& cellCodes =
726  m_octree->pointsAndTheirCellCodes();
727  for (uint32_t i = node.displayedPointCount; i < iStop; ++i) {
728  unsigned pointIndex = cellCodes[node.firstCodeIndex + i].theIndex;
729  m_indexMap.push_back(pointIndex);
730  }
731  }
732 
733  node.displayedPointCount += displayedCount;
734 
735  return displayedCount;
736 }
737 
739  unsigned char level,
740  unsigned& maxCount,
741  unsigned& remainingPointsAtThisLevel) {
742  remainingPointsAtThisLevel = 0;
743  m_lastIndexMap.clear();
744 
745  if (!m_octree || level >= m_levels.size()) {
746  assert(false);
747  maxCount = 0;
748  return m_lastIndexMap; // empty
749  }
750 
751  if (m_state != INITIALIZED) {
752  maxCount = 0;
753  return m_lastIndexMap; // empty
754  }
755 
757  // assert(false);
758  maxCount = 0;
759  return m_lastIndexMap; // empty
760  }
761 
762  m_indexMap.clear();
763  try {
764  m_indexMap.reserve(maxCount);
765  } catch (const std::bad_alloc&) {
766  // not enough memory
767  return m_lastIndexMap; // empty
768  }
769 
770  Level& l = m_levels[level];
771  uint32_t thisPassDisplayCount = 0;
772 
773  bool earlyStop = false;
774  size_t earlyStopIndex = 0;
775 
776  // special case: we have to finish/continue at the same level than the
777  // previous run
778  if (m_currentState.unfinishedLevel == level) {
779  bool displayAll = (m_currentState.unfinishedPoints <= maxCount);
780 
781  // display all leaf cells of the current level
782  for (size_t i = 0; i < l.data.size(); ++i) {
783  Node& node = l.data[i];
784 
785  if (node.childCount) // skip non leaf cells
786  continue;
787  assert(node.intersection != UNDEFINED);
788  if (node.intersection == Frustum::OUTSIDE) continue;
789  if (node.pointCount == node.displayedPointCount) continue;
790 
791  uint32_t nodeMaxCount = 0;
792  uint32_t nodeRemainingCount =
794  if (displayAll) {
795  nodeMaxCount = nodeRemainingCount;
796  } else {
797  double ratio = static_cast<double>(nodeRemainingCount) /
799  nodeMaxCount = static_cast<uint32_t>(ceil(ratio * maxCount));
800  // safety check
801  if (m_indexMap.size() + nodeMaxCount >= maxCount) {
802  assert(maxCount >= m_indexMap.size());
803  nodeMaxCount =
804  maxCount - static_cast<uint32_t>(m_indexMap.size());
805 
806  earlyStop = true;
807  earlyStopIndex = i;
808 
809  i = l.data.size(); // we can stop after this node!
810  }
811  }
812 
813  uint32_t nodeDisplayCount =
814  addNPointsToIndexMap(node, nodeMaxCount);
815  assert(nodeDisplayCount <= nodeMaxCount);
816 
817  thisPassDisplayCount += nodeDisplayCount;
818  assert(thisPassDisplayCount == m_indexMap.size());
819  remainingPointsAtThisLevel +=
821  }
822  }
823 
824  uint32_t totalRemainingCount =
826  // remove the already displayed points (= unfinished from previous run)
827  assert(totalRemainingCount >= thisPassDisplayCount);
828  totalRemainingCount -= thisPassDisplayCount;
829 
830  // do we still have data to display AND can we display it?
831  if (totalRemainingCount != 0 && thisPassDisplayCount < maxCount) {
832  // we shouldn't have any unfinished work at this point!
833  assert(!earlyStop && remainingPointsAtThisLevel == 0);
834 
835  uint32_t mapFreeSize = maxCount - thisPassDisplayCount;
836 
837  bool displayAll = (mapFreeSize > totalRemainingCount);
838 
839  // for all cells of the input level
840  for (size_t i = 0; i < l.data.size(); ++i) {
841  Node& node = l.data[i];
842 
843  assert(node.intersection != UNDEFINED);
844  if (node.intersection == Frustum::OUTSIDE) continue;
845  if (node.pointCount == node.displayedPointCount) continue;
846 
847  uint32_t nodeMaxCount = 0;
848  uint32_t nodeRemainingCount =
850  if (displayAll) {
851  nodeMaxCount = nodeRemainingCount;
852  } else if (node.childCount) {
853  double ratio = static_cast<double>(nodeRemainingCount) /
854  totalRemainingCount;
855  nodeMaxCount = static_cast<uint32_t>(ceil(ratio * mapFreeSize));
856  // safety check
857  if (m_indexMap.size() + nodeMaxCount >= maxCount) {
858  assert(maxCount >= m_indexMap.size());
859  nodeMaxCount =
860  maxCount - static_cast<uint32_t>(m_indexMap.size());
861 
862  earlyStop = true;
863  earlyStopIndex = i;
864 
865  i = l.data.size(); // we can stop after this node!
866  }
867  }
868 
869  uint32_t nodeDisplayCount =
870  addNPointsToIndexMap(node, nodeMaxCount);
871  assert(nodeDisplayCount <= nodeMaxCount);
872 
873  thisPassDisplayCount += nodeDisplayCount;
874  assert(thisPassDisplayCount == m_indexMap.size());
875 
876  if (node.childCount == 0) {
877  remainingPointsAtThisLevel +=
879  }
880  }
881  }
882 
883  maxCount = static_cast<unsigned>(m_indexMap.size());
884  m_currentState.displayedPoints += static_cast<uint32_t>(m_indexMap.size());
885 
886  if (earlyStop) {
887  // be sure to properly finish to count the number of 'unfinished'
888  // points!
889  for (size_t i = earlyStopIndex + 1; i < l.data.size(); ++i) {
890  Node& node = l.data[i];
891 
892  if (node.childCount) // skip non leaf nodes
893  continue;
894  assert(node.intersection != UNDEFINED);
895  if (node.intersection == Frustum::OUTSIDE) continue;
896  if (node.pointCount == node.displayedPointCount) continue;
897  uint32_t nodeRemainingCount =
899  remainingPointsAtThisLevel += nodeRemainingCount;
900  }
901  }
902 
903  if (remainingPointsAtThisLevel) {
904  m_currentState.unfinishedLevel = static_cast<int>(level);
905  m_currentState.unfinishedPoints = remainingPointsAtThisLevel;
906  } else {
909  }
910 
912  return m_indexMap;
913 }
914 
915 #include "ecvPointCloudLOD.moc"
int count
static bool Warning(const char *format,...)
Prints out a formatted warning message in console.
Definition: CVLog.cpp:133
static bool Print(const char *format,...)
Prints out a formatted message in console.
Definition: CVLog.cpp:113
@ OUTSIDE
Definition: ecvFrustum.h:97
@ INSIDE
Definition: ecvFrustum.h:99
@ INTERSECT
Definition: ecvFrustum.h:98
Intersection sphereInFrustum(const CCVector3f &c, float r) const
Definition: ecvFrustum.h:113
void setClipPlanes(const ccClipPlaneSet &clipPlanes)
uint32_t flag(ccPointCloudLOD::Node &node)
void propagateFlag(ccPointCloudLOD::Node &node, uint8_t flag)
PointCloudLODVisibilityFlagger(ccPointCloudLOD &lod, const Frustum &frustum, unsigned char maxLevel)
Type y
Definition: CVGeom.h:137
Type u[3]
Definition: CVGeom.h:139
Type x
Definition: CVGeom.h:137
Type z
Definition: CVGeom.h:137
Type x
Definition: CVGeom.h:653
Type y
Definition: CVGeom.h:653
Type w
Definition: CVGeom.h:653
Type z
Definition: CVGeom.h:653
static Vector3Tpl fromArray(const int a[3])
Constructor from an int array.
Definition: CVGeom.h:268
Bounding box structure.
Definition: ecvBBox.h:25
virtual void setOctree(ccOctree::Shared octree, bool autoAddChild=true)
Sets the associated octree.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
Octree structure.
Definition: ecvOctree.h:27
void updated()
Signal sent when the octree organization is modified (cleared, etc.)
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
Thread for background computation.
ccPointCloudLOD & m_lod
ccPointCloudLODThread(ccPointCloud &cloud, ccPointCloudLOD &lod, uint32_t maxCountPerCell)
Default constructor.
uint8_t fillNode(ccPointCloudLOD::Node &node) const
Fills a node (and returns its relative position) + recursive.
uint8_t fillNode_flat(ccPointCloudLOD::Node &node) const
Fills a node (and returns its relative position)
virtual ~ccPointCloudLODThread()
Destructor.
ccOctree::Shared m_octree
L.O.D. (Level of Detail) structure.
QMutex m_mutex
For concurrent access.
void clear()
Clears the structure.
int32_t newCell(unsigned char level)
Reserves a new cell at a given level.
LODIndexSet m_indexMap
Index map.
ccPointCloudLODThread * m_thread
Computing thread.
void shrink_to_fit()
Shrinks the internal data to its minimum size.
virtual ~ccPointCloudLOD()
Destructor.
LODIndexSet m_lastIndexMap
Last index map (pointer on)
LODIndexSet & getIndexMap(unsigned char level, unsigned &maxCount, unsigned &remainingPointsAtThisLevel)
Builds an index map with the remaining visible points.
State m_state
State.
uint32_t flagVisibility(const Frustum &frustum, ccClipPlaneSet *clipPlanes=0)
Test all cells visibility with a given frustum.
ccPointCloudLOD()
Default constructor.
uint32_t addNPointsToIndexMap(Node &node, uint32_t count)
size_t memory() const
Returns the memory used by the structure (in bytes)
RenderParams m_currentState
Current rendering state.
Node & node(int32_t index, unsigned char level)
bool isBroken()
Returns whether the structure is broken or not.
void setState(State state)
Sets the current state.
void resetVisibility()
Updates the max radius per level FOR ALL CELLS.
bool init(ccPointCloud *cloud)
Initializes the construction process (asynchronous)
std::vector< Level > m_levels
Per-level cells data.
void clearData()
Clears the internal (nodes) data.
static const unsigned char UNDEFINED
Undefined visibility flag.
const ccOctree::Shared & octree() const
Returns the associated octree.
friend ccPointCloudLODThread
ccOctree::Shared m_octree
Associated octree.
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
double getDiagNormd() const
Returns diagonal length (double precision)
Definition: BoundingBox.h:175
Vector3Tpl< T > getCenter() const
Returns center.
Definition: BoundingBox.h:164
void add(const Vector3Tpl< T > &P)
'Enlarges' the bounding box with a point
Definition: BoundingBox.h:131
unsigned CellCode
Type of the code of an octree cell.
Definition: DgmOctree.h:78
static const int MAX_OCTREE_LEVEL
Max octree subdivision level.
Definition: DgmOctree.h:67
std::vector< IndexAndCode > cellsContainer
Container of 'IndexAndCode' structures.
Definition: DgmOctree.h:351
static unsigned char GET_BIT_SHIFT(unsigned char level)
Returns the binary shift for a given level of subdivision.
Definition: DgmOctree.cpp:113
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
std::vector< ccClipPlane > ccClipPlaneSet
std::vector< unsigned > LODIndexSet
L.O.D. indexes set.
GraphType data
Definition: graph_cut.cc:138
MiniVec< float, N > ceil(const MiniVec< float, N > &a)
Definition: MiniVec.h:89
cloudViewer::DgmOctree * octree
std::vector< Node > data
Octree 'tree' node.
std::array< int32_t, 8 > childIndexes
Parameters of the current render state.
int unfinishedLevel
Previously unfinished level.
uint32_t visiblePoints
Number of visible points (for the last visibility test)
unsigned unfinishedPoints
Previously unfinished level.
uint32_t displayedPoints
Number of already displayed points.