ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvNormalVectors.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 "ecvNormalVectors.h"
9 
10 // Local
11 #include "ecvHObjectCaster.h"
12 #include "ecvNormalCompressor.h"
13 #include "ecvSensor.h"
14 #include "ecvSingleton.h"
15 
16 // cloudViewer
17 #include <CVGeom.h>
19 #include <GenericIndexedMesh.h>
21 #include <Neighbourhood.h>
22 
23 // System
24 #include <assert.h>
25 
26 #include <random>
27 
28 // unique instance
30 
31 // Number of points for local modeling to compute normals with 2D1/2 Delaunay
32 // triangulation
33 static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_TRI = 6;
34 // Number of points for local modeling to compute normals with least square
35 // plane
36 static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_LS = 3;
37 // Number of points for local modeling to compute normals with quadratic
38 // 'height' function
39 static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_QUADRIC = 6;
40 
42  if (!s_uniqueInstance.instance)
43  s_uniqueInstance.instance = new ccNormalVectors();
44  return s_uniqueInstance.instance;
45 }
46 
48 
50 
52 
54  const PointCoordinateType N[]) {
55  unsigned index = ccNormalCompressor::Compress(N);
56 
57  return static_cast<CompressedNormType>(index);
58 }
59 
61  if (!m_theNormalHSVColors.empty()) {
62  return true;
63  }
64 
65  if (m_theNormalVectors.empty()) {
66  //'init' should be called first!
67  return false;
68  }
69 
70  try {
72  } catch (const std::bad_alloc&) {
73  // not enough memory
74  return false;
75  }
76 
77  for (size_t i = 0; i < m_theNormalVectors.size(); ++i) {
80  }
81 
82  return true;
83 }
84 
85 const ecvColor::Rgb& ccNormalVectors::getNormalHSVColor(unsigned index) const {
86  assert(index < m_theNormalVectors.size());
87  return m_theNormalHSVColors[index];
88 }
89 
91  unsigned numberOfVectors = ccNormalCompressor::NULL_NORM_CODE + 1;
92  try {
93  m_theNormalVectors.resize(numberOfVectors);
94  } catch (const std::bad_alloc&) {
95  CVLog::Warning("[ccNormalVectors::init] Not enough memory!");
96  return false;
97  }
98 
99  for (unsigned i = 0; i < numberOfVectors; ++i) {
101  m_theNormalVectors[i].normalize();
102  }
103 
104  return true;
105 }
106 
108  ccGenericPointCloud* theCloud,
109  NormsIndexesTableType& theNormsCodes,
110  Orientation preferredOrientation) {
111  assert(theCloud);
112 
113  // preferred orientation
114  CCVector3 prefOrientation(0, 0, 0);
115  CCVector3 originPoint(0, 0, 0);
116  bool useOriginPoint = false;
117  bool fromOriginPoint = true;
118 
119  switch (preferredOrientation) {
120  case PLUS_X:
121  case MINUS_X:
122  case PLUS_Y:
123  case MINUS_Y:
124  case PLUS_Z:
125  case MINUS_Z: {
126  // 0-5 = +/-X,Y,Z
127  assert(preferredOrientation >= 0 && preferredOrientation <= 5);
128 
129  prefOrientation.u[preferredOrientation >> 1] =
130  ((preferredOrientation & 1) == 0
131  ? PC_ONE
132  : -PC_ONE); // odd number --> inverse
133  // direction
134  } break;
135 
136  case PLUS_BARYCENTER:
137  case MINUS_BARYCENTER: {
138  originPoint =
140  theCloud);
141  CVLog::Print(
142  QString("[UpdateNormalOrientations] Barycenter: (%1;%2;%3)")
143  .arg(originPoint.x)
144  .arg(originPoint.y)
145  .arg(originPoint.z));
146  useOriginPoint = true;
147  fromOriginPoint = (preferredOrientation == PLUS_BARYCENTER);
148  } break;
149 
150  case PLUS_ORIGIN:
151  case MINUS_ORIGIN: {
152  originPoint = CCVector3(0, 0, 0);
153  useOriginPoint = true;
154  fromOriginPoint = (preferredOrientation == PLUS_ORIGIN);
155  } break;
156 
157  case PREVIOUS: {
158  if (!theCloud->hasNormals()) {
160  "[UpdateNormalOrientations] Can't orient the new "
161  "normals with the previous ones... as the cloud has no "
162  "normals!");
163  return false;
164  }
165  } break;
166 
167  case MINUS_SENSOR_ORIGIN:
168  case PLUS_SENSOR_ORIGIN: {
169  // look for the first sensor (child) with a valid origin
170  bool sensorFound = false;
171  for (unsigned i = 0; i < theCloud->getChildrenNumber(); ++i) {
172  ccHObject* child = theCloud->getChild(i);
173  if (child && child->isKindOf(CV_TYPES::SENSOR)) {
174  ccSensor* sensor = ccHObjectCaster::ToSensor(child);
175  if (sensor->getActiveAbsoluteCenter(originPoint)) {
176  useOriginPoint = true;
177  fromOriginPoint =
178  (preferredOrientation == PLUS_SENSOR_ORIGIN);
179  sensorFound = true;
180  break;
181  }
182  }
183  }
184  if (!sensorFound) {
186  "[UpdateNormalOrientations] Could not find a valid "
187  "sensor child");
188  return false;
189  }
190  } break;
191 
192  default:
193  assert(false);
194  return false;
195  }
196 
197  // we check each normal orientation
198  for (unsigned i = 0; i < theNormsCodes.currentSize(); i++) {
199  const CompressedNormType& code = theNormsCodes.getValue(i);
200  CCVector3 N = GetNormal(code);
201 
202  if (preferredOrientation == PREVIOUS) {
203  prefOrientation = theCloud->getPointNormal(i);
204  } else if (useOriginPoint) {
205  if (fromOriginPoint) {
206  prefOrientation = *(theCloud->getPoint(i)) - originPoint;
207  } else {
208  prefOrientation = originPoint - *(theCloud->getPoint(i));
209  }
210  }
211 
212  // we eventually check the sign
213  if (N.dot(prefOrientation) < 0) {
214  // inverse normal and re-compress it
215  N *= -1;
216  theNormsCodes.setValue(i, ccNormalVectors::GetNormIndex(N.u));
217  }
218  }
219 
220  return true;
221 }
222 
224  ccGenericPointCloud* cloud) {
225  if (!cloud) {
226  assert(false);
227  return 0;
228  }
229 
230  PointCoordinateType largetDim = cloud->getOwnBB().getMaxBoxDim();
231 
232  return largetDim /
233  std::min<unsigned>(100, std::max<unsigned>(1, cloud->size() / 100));
234 }
235 
237  ccGenericPointCloud* cloud,
238  cloudViewer::DgmOctree* inputOctree /*=nullptr*/,
239  cloudViewer::GenericProgressCallback* progressCb /*=nullptr*/) {
240  if (!cloud) {
241  assert(false);
242  return 0;
243  }
244 
245  cloudViewer::DgmOctree* octree = inputOctree;
246  if (!octree) {
247  octree = new cloudViewer::DgmOctree(cloud);
248  if (octree->build() <= 0) {
249  delete octree;
251  "[GuessBestRadius] Failed to compute the cloud octree");
252  return 0;
253  }
254  }
255 
256  PointCoordinateType bestRadius = GuessNaiveRadius(cloud);
257  if (bestRadius == 0) {
258  CVLog::Warning("[GuessBestRadius] The cloud has invalid dimensions");
259  return 0;
260  }
261 
262  if (cloud->size() < 100) {
263  // no need to do anything else for very small clouds!
264  return bestRadius;
265  }
266 
267  // we are now going to sample the cloud so as to compute statistics on the
268  // density
269  {
270  static const int s_aimedPop = 16;
271  static const int s_aimedPopRange = 4;
272  static const int s_minPop = 6;
273  static const double s_minAboveMinRatio = 0.97;
274 
275  const unsigned sampleCount =
276  std::min<unsigned>(200, cloud->size() / 10);
277 
278  double aimedPop = s_aimedPop;
279  PointCoordinateType radius = bestRadius;
280  PointCoordinateType lastRadius = radius;
281  double lastMeanPop = 0;
282 
283  std::random_device rd; // non-deterministic generator
284  std::mt19937 gen(rd()); // to seed mersenne twister.
285  std::uniform_int_distribution<unsigned> dist(0, cloud->size() - 1);
286 
287  // we may have to do this several times
288  for (size_t attempt = 0; attempt < 10; ++attempt) {
289  int totalCount = 0;
290  int totalSquareCount = 0;
291  int minPop = 0;
292  int maxPop = 0;
293  int aboveMinPopCount = 0;
294 
295  unsigned char octreeLevel =
297  radius);
298 
299  for (size_t i = 0; i < sampleCount; ++i) {
300  unsigned randomIndex = dist(gen);
301  assert(randomIndex < cloud->size());
302 
303  const CCVector3* P = cloud->getPoint(randomIndex);
306  *P, radius, Yk, octreeLevel);
307  assert(n >= 1);
308 
309  totalCount += n;
310  totalSquareCount += n * n;
311  if (i == 0) {
312  minPop = maxPop = n;
313  } else {
314  if (n < minPop)
315  minPop = n;
316  else if (n > maxPop)
317  maxPop = n;
318  }
319 
320  if (n >= s_minPop) {
321  ++aboveMinPopCount;
322  }
323  }
324 
325  double meanPop = static_cast<double>(totalCount) / sampleCount;
326  double stdDevPop = sqrt(
327  fabs(static_cast<double>(totalSquareCount) / sampleCount -
328  meanPop * meanPop));
329  double aboveMinPopRatio =
330  static_cast<double>(aboveMinPopCount) / sampleCount;
331 
332  CVLog::Print(QString("[GuessBestRadius] Radius = %1 -> samples "
333  "population in [%2 ; %3] (mean %4 / std. dev. "
334  "%5 / %6% above mininmum)")
335  .arg(radius)
336  .arg(minPop)
337  .arg(maxPop)
338  .arg(meanPop)
339  .arg(stdDevPop)
340  .arg(aboveMinPopRatio * 100));
341 
342  if (fabs(meanPop - aimedPop) < s_aimedPopRange) {
343  // we have found a correct radius
344  bestRadius = radius;
345 
346  if (aboveMinPopRatio < s_minAboveMinRatio) {
347  // CVLog::Warning("[GuessBestRadius] The cloud density is
348  // very inhomogeneous! You may have to increase the radius
349  // to get valid normals everywhere... but the result will be
350  // smoother");
351  aimedPop = s_aimedPop +
352  (2.0 * stdDevPop) /* * (1.0-aboveMinPopRatio)*/;
353  assert(aimedPop >= s_aimedPop);
354  } else {
355  break;
356  }
357  }
358 
359  // otherwise we have to find a better estimate for the radius
360  PointCoordinateType newRadius = radius;
361  //(warning: we consider below that the number of points is
362  // proportional to the SURFACE of the neighborhood)
363  assert(meanPop >= 1.0);
364  if (attempt == 0) {
365  // this is our best (only) guess for the moment
366  bestRadius = radius;
367 
368  newRadius = radius * sqrt(aimedPop / meanPop);
369  } else {
370  // keep track of our best guess nevertheless
371  if (fabs(meanPop - aimedPop) < fabs(bestRadius - aimedPop)) {
372  bestRadius = radius;
373  }
374 
375  double slope = (radius * radius - lastRadius * lastRadius) /
376  (meanPop - lastMeanPop);
377  PointCoordinateType newSquareRadius =
378  lastRadius * lastRadius +
379  (aimedPop - lastMeanPop) * slope;
380  if (newSquareRadius > 0) {
381  newRadius = sqrt(newSquareRadius);
382  } else {
383  // can't do any better!
384  break;
385  }
386  }
387 
388  lastRadius = radius;
389  lastMeanPop = meanPop;
390 
391  radius = newRadius;
392  }
393  }
394 
395  if (octree && !inputOctree) {
396  delete octree;
397  octree = 0;
398  }
399 
400  return bestRadius;
401 }
402 
404  ccGenericPointCloud* theCloud,
405  NormsIndexesTableType& theNormsCodes,
406  CV_LOCAL_MODEL_TYPES localModel,
407  PointCoordinateType localRadius,
408  Orientation preferredOrientation /*=UNDEFINED*/,
409  cloudViewer::GenericProgressCallback* progressCb /*=nullptr*/,
410  cloudViewer::DgmOctree* inputOctree /*=nullptr*/) {
411  assert(theCloud);
412 
413  unsigned pointCount = theCloud->size();
414  if (pointCount < 3) {
415  return false;
416  }
417 
418  cloudViewer::DgmOctree* theOctree = inputOctree;
419  if (!theOctree) {
420  theOctree = new cloudViewer::DgmOctree(theCloud);
421  if (theOctree->build() <= 0) {
422  delete theOctree;
423  return false;
424  }
425  }
426 
427  // reserve some memory to store the (compressed) normals
428  if (!theNormsCodes.isAllocated() ||
429  theNormsCodes.currentSize() < pointCount) {
430  if (!theNormsCodes.resizeSafe(pointCount)) {
431  if (theOctree && !inputOctree) delete theOctree;
432  return false;
433  }
434  }
435 
436  // we instantiate 3D normal vectors
437  NormsTableType* theNorms = new NormsTableType;
438  static const CCVector3 blankN(0, 0, 0);
439  if (!theNorms->resizeSafe(pointCount, true, &blankN)) {
440  theNormsCodes.resize(0);
441  if (theOctree && !inputOctree) delete theOctree;
442  return false;
443  }
444  // theNorms->fill(0);
445 
446  void* additionalParameters[2] = {reinterpret_cast<void*>(theNorms),
447  reinterpret_cast<void*>(&localRadius)};
448 
449  unsigned processedCells = 0;
450  switch (localModel) {
451  case LS: {
452  unsigned char level =
453  theOctree
455  localRadius);
456  processedCells = theOctree->executeFunctionForAllCellsAtLevel(
457  level, &(ComputeNormsAtLevelWithLS), additionalParameters,
458  true, progressCb, "Normals Computation[LS]");
459  } break;
460  case TRI: {
461  unsigned char level =
464  processedCells =
466  level, &(ComputeNormsAtLevelWithTri),
467  additionalParameters,
470  progressCb, "Normals Computation[TRI]");
471  } break;
472  case QUADRIC: {
473  unsigned char level =
474  theOctree
476  localRadius);
477  processedCells = theOctree->executeFunctionForAllCellsAtLevel(
479  additionalParameters, true, progressCb,
480  "Normals Computation[QUADRIC]");
481  } break;
482 
483  default:
484  break;
485  }
486 
487  // error or canceled by user?
488  if (processedCells == 0 ||
489  (progressCb && progressCb->isCancelRequested())) {
490  theNormsCodes.resize(0);
491  return false;
492  }
493 
494  // we 'compress' each normal
495  std::fill(theNormsCodes.begin(), theNormsCodes.end(), 0);
496  for (unsigned i = 0; i < pointCount; i++) {
497  const CCVector3& N = theNorms->at(i);
498  CompressedNormType nCode = GetNormIndex(N);
499  theNormsCodes.setValue(i, nCode);
500  }
501 
502  theNorms->release();
503  theNorms = 0;
504 
505  // preferred orientation
506  if (preferredOrientation != UNDEFINED) {
507  UpdateNormalOrientations(theCloud, theNormsCodes, preferredOrientation);
508  }
509 
510  if (theOctree && !inputOctree) {
511  delete theOctree;
512  theOctree = 0;
513  }
514 
515  return true;
516 }
517 
520  const CCVector3& P,
521  CCVector3& N) {
523 
524  Tuple3ub dims;
525  const PointCoordinateType* h = Z.getQuadric(&dims);
526  if (h) {
527  const CCVector3* gv = Z.getGravityCenter();
528  assert(gv);
529 
530  const unsigned char& iX = dims.x;
531  const unsigned char& iY = dims.y;
532  const unsigned char& iZ = dims.z;
533 
534  PointCoordinateType lX = P.u[iX] - gv->u[iX];
535  PointCoordinateType lY = P.u[iY] - gv->u[iY];
536 
537  N.u[iX] = h[1] + (2 * h[3] * lX) + (h[4] * lY);
538  N.u[iY] = h[2] + (2 * h[5] * lY) + (h[4] * lX);
539  N.u[iZ] = -1;
540 
541  // normalize the result
542  N.normalize();
543 
544  return true;
545  } else {
546  return false;
547  }
548 }
549 
551  cloudViewer::GenericIndexedCloudPersist* pointAndNeighbors,
552  CCVector3& N) {
553  N = CCVector3(0, 0, 0);
554 
555  if (!pointAndNeighbors) {
556  assert(false);
557  return false;
558  }
559 
560  if (pointAndNeighbors->size() < 3) {
561  return false;
562  }
563 
564  cloudViewer::Neighbourhood Z(pointAndNeighbors);
565  const CCVector3* _N = Z.getLSPlaneNormal();
566  if (_N) {
567  N = *_N;
568  return true;
569  } else {
570  return false;
571  }
572 }
573 
575  cloudViewer::GenericIndexedCloudPersist* pointAndNeighbors,
576  CCVector3& N) {
577  N = CCVector3(0, 0, 0);
578 
579  if (!pointAndNeighbors) {
580  assert(false);
581  return false;
582  }
583 
584  if (pointAndNeighbors->size() < 3) {
585  return false;
586  }
587 
588  cloudViewer::Neighbourhood Z(pointAndNeighbors);
589 
590  // we mesh the neighbour points (2D1/2)
591  std::string errorStr;
595  if (!theMesh) {
596  return false;
597  }
598 
599  unsigned triCount = theMesh->size();
600 
601  // for all triangles
602  theMesh->placeIteratorAtBeginning();
603  for (unsigned j = 0; j < triCount; ++j) {
604  // we can't use getNextTriangleVertIndexes (which is faster on mesh
605  // groups but not multi-thread compatible) but anyway we'll never get
606  // mesh groups here!
607  const cloudViewer::VerticesIndexes* tsi =
608  theMesh->getTriangleVertIndexes(j);
609 
610  // we look if the central point is one of the triangle's vertices
611  if (tsi->i1 == 0 || tsi->i2 == 0 || tsi->i3 == 0) {
612  const CCVector3* A = pointAndNeighbors->getPoint(tsi->i1);
613  const CCVector3* B = pointAndNeighbors->getPoint(tsi->i2);
614  const CCVector3* C = pointAndNeighbors->getPoint(tsi->i3);
615 
616  CCVector3 no = (*B - *A).cross(*C - *A);
617  // no.normalize();
618  N += no;
619  }
620  }
621 
622  delete theMesh;
623  theMesh = 0;
624 
625  // normalize the 'mean' vector
626  N.normalize();
627 
628  return true;
629 }
630 
633  void** additionalParameters,
635  // additional parameters
636  NormsTableType* theNorms =
637  static_cast<NormsTableType*>(additionalParameters[0]);
638  PointCoordinateType radius =
639  *static_cast<PointCoordinateType*>(additionalParameters[1]);
640 
642  nNSS.level = cell.level;
643  nNSS.prepare(radius, cell.parentOctree->getCellSize(nNSS.level));
644  cell.parentOctree->getCellPos(cell.truncatedCode, cell.level, nNSS.cellPos,
645  true);
646  cell.parentOctree->computeCellCenter(nNSS.cellPos, cell.level,
647  nNSS.cellCenter);
648 
649  // we already know which points are lying in the current cell
650  unsigned pointCount = cell.points->size();
651  nNSS.pointsInNeighbourhood.resize(pointCount);
652  cloudViewer::DgmOctree::NeighboursSet::iterator it =
653  nNSS.pointsInNeighbourhood.begin();
654  for (unsigned j = 0; j < pointCount; ++j, ++it) {
655  it->point = cell.points->getPointPersistentPtr(j);
656  it->pointIndex = cell.points->getPointGlobalIndex(j);
657  }
659 
660  for (unsigned i = 0; i < pointCount; ++i) {
661  cell.points->getPoint(i, nNSS.queryPoint);
662 
663  // warning: there may be more points at the end of
664  // nNSS.pointsInNeighbourhood than the actual nearest neighbors (k)!
666  nNSS, radius, false);
667  float cur_radius = radius;
669  cur_radius < 16 * radius) {
670  cur_radius *= 1.189207115f;
672  nNSS, cur_radius, false);
673  }
676  &nNSS.pointsInNeighbourhood, k);
677 
678  CCVector3 N;
679  if (ComputeNormalWithQuadric(&neighbours, nNSS.queryPoint, N)) {
680  theNorms->setValue(cell.points->getPointGlobalIndex(i), N);
681  }
682  }
683 
684  if (nProgress && !nProgress->oneStep()) return false;
685  }
686 
687  return true;
688 }
689 
692  void** additionalParameters,
694  // additional parameters
695  NormsTableType* theNorms =
696  static_cast<NormsTableType*>(additionalParameters[0]);
697  PointCoordinateType radius =
698  *static_cast<PointCoordinateType*>(additionalParameters[1]);
699 
701  nNSS.level = cell.level;
702  nNSS.prepare(radius, cell.parentOctree->getCellSize(nNSS.level));
703  cell.parentOctree->getCellPos(cell.truncatedCode, cell.level, nNSS.cellPos,
704  true);
705  cell.parentOctree->computeCellCenter(nNSS.cellPos, cell.level,
706  nNSS.cellCenter);
707 
708  // we already know which points are lying in the current cell
709  unsigned pointCount = cell.points->size();
710  nNSS.pointsInNeighbourhood.resize(pointCount);
711  {
712  cloudViewer::DgmOctree::NeighboursSet::iterator it =
713  nNSS.pointsInNeighbourhood.begin();
714  for (unsigned j = 0; j < pointCount; ++j, ++it) {
715  it->point = cell.points->getPointPersistentPtr(j);
716  it->pointIndex = cell.points->getPointGlobalIndex(j);
717  }
718  }
720 
721  for (unsigned i = 0; i < pointCount; ++i) {
722  cell.points->getPoint(i, nNSS.queryPoint);
723 
724  // warning: there may be more points at the end of
725  // nNSS.pointsInNeighbourhood than the actual nearest neighbors (k)!
727  nNSS, radius, false);
728  float cur_radius = radius;
730  cur_radius < 16 * radius) {
731  cur_radius *= 1.189207115f;
733  nNSS, cur_radius, false);
734  }
737  &nNSS.pointsInNeighbourhood, k);
738 
739  CCVector3 N;
740  if (ComputeNormalWithLS(&neighbours, N)) {
741  theNorms->setValue(cell.points->getPointGlobalIndex(i), N);
742  }
743  }
744 
745  if (nProgress && !nProgress->oneStep()) {
746  return false;
747  }
748  }
749 
750  return true;
751 }
752 
755  void** additionalParameters,
757  // additional parameters
758  NormsTableType* theNorms =
759  static_cast<NormsTableType*>(additionalParameters[0]);
760 
762  nNSS.level = cell.level;
764  cell.parentOctree->getCellPos(cell.truncatedCode, cell.level, nNSS.cellPos,
765  true);
766  cell.parentOctree->computeCellCenter(nNSS.cellPos, cell.level,
767  nNSS.cellCenter);
768 
769  // we already know which points are lying in the current cell
770  unsigned pointCount = cell.points->size();
771  nNSS.pointsInNeighbourhood.resize(pointCount);
772  cloudViewer::DgmOctree::NeighboursSet::iterator it =
773  nNSS.pointsInNeighbourhood.begin();
774  {
775  for (unsigned j = 0; j < pointCount; ++j, ++it) {
776  it->point = cell.points->getPointPersistentPtr(j);
777  it->pointIndex = cell.points->getPointGlobalIndex(j);
778  }
779  }
781 
782  for (unsigned i = 0; i < pointCount; ++i) {
783  cell.points->getPoint(i, nNSS.queryPoint);
784 
785  unsigned k =
791  &nNSS.pointsInNeighbourhood, k);
792 
793  CCVector3 N;
794  if (ComputeNormalWithTri(&neighbours, N)) {
795  theNorms->setValue(cell.points->getPointGlobalIndex(i), N);
796  }
797  }
798 
799  if (nProgress && !nProgress->oneStep()) return false;
800  }
801 
802  return true;
803 }
804 
806  double& dip_deg) {
807  int iStrike = static_cast<int>(strike_deg);
808  int iDip = static_cast<int>(dip_deg);
809 
810  return QString("N%1°E - %2°")
811  .arg(iStrike, 3, 10, QChar('0'))
812  .arg(iDip, 3, 10, QChar('0'));
813 }
814 
816  PointCoordinateType dip_deg, PointCoordinateType dipDir_deg) {
817  int iDipDir = static_cast<int>(dipDir_deg);
818  int iDip = static_cast<int>(dip_deg);
819 
820  return QString("Dip: %1 deg. - Dip direction: %2 deg.")
821  .arg(iDip, 3, 10, QChar('0'))
822  .arg(iDipDir, 3, 10, QChar('0'));
823 }
824 
826  const CCVector3& N,
827  PointCoordinateType& strike_deg,
828  PointCoordinateType& dip_deg) {
829  // Adapted from Andy Michael's 'stridip.c':
830  // Finds strike and dip of plane given normal vector having components n, e,
831  // and u output is in degrees north of east and then uses a right hand rule
832  // for the dip of the plane
833  if (N.norm2() > std::numeric_limits<PointCoordinateType>::epsilon()) {
834  strike_deg =
835  180.0 -
837  N.y, N.x)); // atan2 output is between -180 and 180! So
838  // strike is always positive here
840  sqrt(N.x * N.x + N.y * N.y); // x is the horizontal magnitude
841  dip_deg = cloudViewer::RadiansToDegrees(atan2(x, N.z));
842  } else {
843  strike_deg = dip_deg =
844  std::numeric_limits<PointCoordinateType>::quiet_NaN();
845  }
846 }
847 
849  const CCVector3& N,
850  PointCoordinateType& dip_deg,
851  PointCoordinateType& dipDir_deg) {
852  // http://en.wikipedia.org/wiki/Structural_geology#Geometries
853 
854  if (N.norm2d() > std::numeric_limits<PointCoordinateType>::epsilon()) {
855  // The dip direction must be the same for parallel facets, regardless
856  // of whether their normals point upwards or downwards.
857  //
858  // The formula using atan2() with the swapped N.x and N.y already
859  // gives the correct results for facets with the normal pointing
860  // upwards, so just use the sign of N.z to invert the normals if they
861  // point downwards.
862  double Nsign =
863  N.z < 0 ? -1.0
864  : 1.0; // DGM: copysign is not available on VS2012
865 
866  //"Dip direction is measured in 360 degrees, generally clockwise from
867  // North"
868  double dipDir_rad =
869  atan2(Nsign * N.x, Nsign * N.y); // result in [-pi,+pi]
870  if (dipDir_rad < 0) {
871  dipDir_rad += 2 * M_PI;
872  }
873 
874  // Dip angle
875  //
876  // acos() returns values in [0, pi] but using fabs() all the normals
877  // are considered pointing upwards, so the actual result will be in
878  // [0, pi/2] as required by the definition of dip.
879  // We skip the division by r because the normal is a unit vector.
880  double dip_rad = acos(fabs(N.z));
881 
882  dipDir_deg = static_cast<PointCoordinateType>(
883  cloudViewer::RadiansToDegrees(dipDir_rad));
884  dip_deg = static_cast<PointCoordinateType>(
886  } else {
887  dipDir_deg = dip_deg =
888  std::numeric_limits<PointCoordinateType>::quiet_NaN();
889  }
890 }
891 
893  PointCoordinateType dip_deg,
894  PointCoordinateType dipDir_deg,
895  bool upward /*=true*/) {
896  // specific case
897  if (std::isnan(dip_deg) || std::isnan(dipDir_deg)) {
898  return CCVector3(0, 0, 0);
899  }
900 
901  double Nz = cos(cloudViewer::DegreesToRadians(dip_deg));
902  double Nxy = sqrt(1.0 - Nz * Nz);
903  double dipDir_rad = cloudViewer::DegreesToRadians(dipDir_deg);
904  CCVector3 N(static_cast<PointCoordinateType>(Nxy * sin(dipDir_rad)),
905  static_cast<PointCoordinateType>(Nxy * cos(dipDir_rad)),
906  static_cast<PointCoordinateType>(Nz));
907 
908 #ifdef _DEBUG
909  // internal consistency test
910  PointCoordinateType dip2, dipDir2;
911  ConvertNormalToDipAndDipDir(N, dip2, dipDir2);
912  assert(fabs(dip2 - dip_deg) < 1.0e-3 &&
913  (dip2 == 0 || fabs(dipDir2 - dipDir_deg) < 1.0e-3));
914 #endif
915 
916  if (!upward) {
917  N = -N;
918  }
919  return N;
920 }
921 
923  float& H,
924  float& S,
925  float& V) {
926  PointCoordinateType dip = 0, dipDir = 0;
927  ConvertNormalToDipAndDipDir(N, dip, dipDir);
928 
929  H = static_cast<float>(dipDir);
930  if (H == 360.0f) // H is in [0;360[
931  H = 0;
932  S = static_cast<float>(dip / 90); // S is in [0;1]
933  V = 1.0f;
934 }
935 
937  ecvColor::Rgbf col((N.x + 1) / 2, (N.y + 1) / 2, (N.z + 1) / 2);
938  return ecvColor::Rgb(static_cast<ColorCompType>(col.r * ecvColor::MAX),
939  static_cast<ColorCompType>(col.g * ecvColor::MAX),
940  static_cast<ColorCompType>(col.b * ecvColor::MAX));
941 }
constexpr PointCoordinateType PC_ONE
'1' as a PointCoordinateType value
Definition: CVConst.h:67
CV_LOCAL_MODEL_TYPES
Definition: CVConst.h:121
@ LS
Definition: CVConst.h:123
@ TRI
Definition: CVConst.h:124
@ QUADRIC
Definition: CVConst.h:125
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 size
int points
virtual void release()
Decrease counter and deletes object when 0.
Definition: CVShareable.cpp:35
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
Array of compressed 3D normals (single index)
Array of (uncompressed) 3D normals (Nx,Ny,Nz)
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
void normalize()
Sets vector norm to unity.
Definition: CVGeom.h:428
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
Type norm2() const
Returns vector square norm.
Definition: CVGeom.h:417
double norm2d() const
Returns vector square norm (forces double precision output)
Definition: CVGeom.h:419
Type & getValue(size_t index)
Definition: ecvArray.h:100
bool isAllocated() const
Returns whether some memory has been allocated or not.
Definition: ecvArray.h:67
bool resizeSafe(size_t count, bool initNewElements=false, const Type *valueForNewElements=nullptr)
Resizes memory (no exception thrown)
Definition: ecvArray.h:70
void setValue(size_t index, const Type &value)
Definition: ecvArray.h:102
unsigned currentSize() const
Definition: ecvArray.h:112
virtual bool hasNormals() const
Returns whether normals are enabled or not.
A 3D cloud interface with associated features (color, normals, octree, etc.)
virtual const CCVector3 & getPointNormal(unsigned pointIndex) const =0
Returns normal corresponding to a given point.
ccBBox getOwnBB(bool withGLFeatures=false) override
Returns the entity's own bounding-box.
static ccSensor * ToSensor(ccHObject *obj)
Converts current object to ccSensor (if possible)
Hierarchical CLOUDVIEWER Object.
Definition: ecvHObject.h:25
unsigned getChildrenNumber() const
Returns the number of children.
Definition: ecvHObject.h:312
ccHObject * getChild(unsigned childPos) const
Returns the ith child.
Definition: ecvHObject.h:325
static const unsigned NULL_NORM_CODE
Null normal code.
static unsigned Compress(const PointCoordinateType N[3])
Compression algorithm.
static void Decompress(unsigned index, PointCoordinateType N[3], unsigned char level=QUANTIZE_LEVEL)
Decompression algorithm.
Compressed normal vectors handler.
static QString ConvertStrikeAndDipToString(double &strike_deg, double &dip_deg)
virtual ~ccNormalVectors()
Default destructor.
bool init()
Inits internal structures.
static QString ConvertDipAndDipDirToString(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg)
Converts geological 'dip direction & dip' parameters to a string.
static void ReleaseUniqueInstance()
Releases unique instance.
static void ConvertNormalToDipAndDipDir(const CCVector3 &N, PointCoordinateType &dip_deg, PointCoordinateType &dipDir_deg)
Converts a normal vector to geological 'dip direction & dip' parameters.
std::vector< CCVector3 > m_theNormalVectors
Compressed normal vectors.
static bool ComputeNormsAtLevelWithTri(const cloudViewer::DgmOctree::octreeCell &cell, void **additionalParameters, cloudViewer::NormalizedProgress *nProgress=nullptr)
Cellular method for octree-based normal computation.
static bool ComputeNormalWithLS(cloudViewer::GenericIndexedCloudPersist *pointAndNeighbors, CCVector3 &N)
Helper: computes the normal (with best LS fit)
ccNormalVectors()
Default constructor.
static void ConvertNormalToHSV(const CCVector3 &N, float &H, float &S, float &V)
Converts a normal vector to HSV color space.
static bool ComputeNormalWithTri(cloudViewer::GenericIndexedCloudPersist *pointAndNeighbors, CCVector3 &N)
Helper: computes the normal (with Delaunay 2.5D)
static bool ComputeNormsAtLevelWithQuadric(const cloudViewer::DgmOctree::octreeCell &cell, void **additionalParameters, cloudViewer::NormalizedProgress *nProgress=nullptr)
Cellular method for octree-based normal computation.
static PointCoordinateType GuessNaiveRadius(ccGenericPointCloud *cloud)
static void ConvertNormalToStrikeAndDip(const CCVector3 &N, PointCoordinateType &strike_deg, PointCoordinateType &dip_deg)
static ecvColor::Rgb ConvertNormalToRGB(const CCVector3 &N)
Converts a normal vector to RGB color space.
static PointCoordinateType GuessBestRadius(ccGenericPointCloud *cloud, cloudViewer::DgmOctree *cloudOctree=nullptr, cloudViewer::GenericProgressCallback *progressCb=nullptr)
std::vector< ecvColor::Rgb > m_theNormalHSVColors
'HSV' colors corresponding to each compressed normal index
static bool ComputeNormalWithQuadric(cloudViewer::GenericIndexedCloudPersist *points, const CCVector3 &P, CCVector3 &N)
Helper: computes the normal (with Delaunay 2.5D)
static bool UpdateNormalOrientations(ccGenericPointCloud *theCloud, NormsIndexesTableType &theNormsCodes, Orientation preferredOrientation)
Updates normals orientation based on a preferred orientation.
static bool ComputeCloudNormals(ccGenericPointCloud *cloud, NormsIndexesTableType &theNormsCodes, CV_LOCAL_MODEL_TYPES localModel, PointCoordinateType localRadius, Orientation preferredOrientation=UNDEFINED, cloudViewer::GenericProgressCallback *progressCb=nullptr, cloudViewer::DgmOctree *inputOctree=nullptr)
Computes normal at each point of a given cloud.
static CCVector3 ConvertDipAndDipDirToNormal(PointCoordinateType dip_deg, PointCoordinateType dipDir_deg, bool upward=true)
static const CCVector3 & GetNormal(unsigned normIndex)
Static access to ccNormalVectors::getNormal.
Orientation
'Default' orientations
@ PREVIOUS
Re-use previous normal (if any)
@ PLUS_X
N.x always positive.
@ MINUS_BARYCENTER
Normals always towards the cloud barycenter.
@ MINUS_Z
N.z always negative.
@ PLUS_Y
N.y always positive.
@ PLUS_BARYCENTER
Normals always opposite to the cloud barycenter.
@ PLUS_ORIGIN
Normals always opposite to the origin.
@ PLUS_Z
N.z always positive.
@ MINUS_Y
N.y always negative.
@ MINUS_ORIGIN
Normals always towards the origin.
@ UNDEFINED
Undefined (no orientation is required)
@ MINUS_X
N.x always negative.
static ccNormalVectors * GetUniqueInstance()
Returns unique instance.
static bool ComputeNormsAtLevelWithLS(const cloudViewer::DgmOctree::octreeCell &cell, void **additionalParameters, cloudViewer::NormalizedProgress *nProgress=nullptr)
Cellular method for octree-based normal computation.
static CompressedNormType GetNormIndex(const PointCoordinateType N[])
Returns the compressed index corresponding to a normal vector.
const ecvColor::Rgb & getNormalHSVColor(unsigned index) const
Returns the HSV color equivalent to a given compressed normal index.
bool enableNormalHSVColorsArray()
Allocates normal HSV colors array.
bool isKindOf(CV_CLASS_ENUM type) const
Definition: ecvObject.h:128
Generic sensor interface.
Definition: ecvSensor.h:27
bool getActiveAbsoluteCenter(CCVector3 &vec) const
Gets currently active absolute position.
Definition: ecvSensor.cpp:102
T getMaxBoxDim() const
Returns maximal box dimension.
Definition: BoundingBox.h:185
A kind of ReferenceCloud based on the DgmOctree::NeighboursSet structure.
The octree structure used throughout the library.
Definition: DgmOctree.h:39
void getCellPos(CellCode code, unsigned char level, Tuple3i &cellPos, bool isCodeTruncated) const
Definition: DgmOctree.cpp:498
unsigned findNearestNeighborsStartingFromCell(NearestNeighboursSearchStruct &nNSS, bool getOnlyPointsWithValidScalar=false) const
Definition: DgmOctree.cpp:1655
unsigned char findBestLevelForAGivenNeighbourhoodSizeExtraction(PointCoordinateType radius) const
Definition: DgmOctree.cpp:2664
int findNeighborsInASphereStartingFromCell(NearestNeighboursSearchStruct &nNSS, double radius, bool sortValues=true) const
Advanced form of the nearest neighbours search algorithm (in a sphere)
Definition: DgmOctree.cpp:2479
const PointCoordinateType & getCellSize(unsigned char level) const
Returns the octree cells length for a given level of subdivision.
Definition: DgmOctree.h:494
int getPointsInSphericalNeighbourhood(const CCVector3 &sphereCenter, PointCoordinateType radius, NeighboursSet &neighbours, unsigned char level) const
Returns the points falling inside a sphere.
Definition: DgmOctree.cpp:1846
void computeCellCenter(CellCode code, unsigned char level, CCVector3 &center, bool isCodeTruncated=false) const
Definition: DgmOctree.h:862
unsigned executeFunctionForAllCellsAtLevel(unsigned char level, octreeCellFunc func, void **additionalParameters, bool multiThread=false, GenericProgressCallback *progressCb=nullptr, const char *functionTitle=nullptr, int maxThreadCount=0)
Definition: DgmOctree.cpp:3573
int build(GenericProgressCallback *progressCb=nullptr)
Builds the structure.
Definition: DgmOctree.cpp:196
unsigned executeFunctionForAllCellsStartingAtLevel(unsigned char startingLevel, octreeCellFunc func, void **additionalParameters, unsigned minNumberOfPointsPerCell, unsigned maxNumberOfPointsPerCell, bool multiThread=true, GenericProgressCallback *progressCb=nullptr, const char *functionTitle=nullptr, int maxThreadCount=0)
Definition: DgmOctree.cpp:3820
std::vector< PointDescriptor > NeighboursSet
A set of neighbours.
Definition: DgmOctree.h:133
unsigned char findBestLevelForAGivenPopulationPerCell(unsigned indicativeNumberOfPointsPerCell) const
Definition: DgmOctree.cpp:2737
virtual unsigned size() const =0
Returns the number of points.
A generic 3D point cloud with index-based and presistent access to points.
virtual const CCVector3 * getPoint(unsigned index) const =0
Returns the ith point.
A generic mesh with index-based vertex access.
virtual VerticesIndexes * getTriangleVertIndexes(unsigned triangleIndex)=0
Returns the indexes of the vertices of a given triangle.
virtual unsigned size() const =0
Returns the number of triangles.
virtual void placeIteratorAtBeginning()=0
Places the mesh iterator at the beginning.
virtual bool isCancelRequested()=0
Checks if the process should be canceled.
static CCVector3 ComputeGravityCenter(const GenericCloud *theCloud)
Computes the gravity center of a point cloud.
static constexpr bool DO_NOT_DUPLICATE_VERTICES
Definition: Neighbourhood.h:30
const PointCoordinateType * getQuadric(Tuple3ub *dims=nullptr)
Returns the best interpolating 2.5D quadric.
const CCVector3 * getGravityCenter()
Returns gravity center.
static constexpr int IGNORE_MAX_EDGE_LENGTH
Definition: Neighbourhood.h:27
GenericIndexedMesh * triangulateOnPlane(bool duplicateVertices, PointCoordinateType maxEdgeLength, std::string &outputErrorStr)
Applies 2D Delaunay triangulation.
const CCVector3 * getLSPlaneNormal()
Returns best interpolating plane (Least-square) normal vector.
bool oneStep()
Increments total progress value of a single unit.
unsigned size() const override
Returns the number of points.
virtual unsigned getPointGlobalIndex(unsigned localIndex) const
const CCVector3 * getPointPersistentPtr(unsigned index) override
Returns the ith point as a persistent pointer.
const CCVector3 * getPoint(unsigned index) const override
Returns the ith point.
RGB color structure.
Definition: ecvColorTypes.h:49
unsigned int CompressedNormType
Compressed normals type.
Definition: ecvBasicTypes.h:16
unsigned char ColorCompType
Default color components type (R,G and B)
Definition: ecvColorTypes.h:29
static ecvSingleton< ccNormalVectors > s_uniqueInstance
static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_LS
static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_QUADRIC
static const unsigned NUMBER_OF_POINTS_FOR_NORM_WITH_TRI
normal_z x
@ SENSOR
Definition: CVTypes.h:116
float RadiansToDegrees(int radians)
Convert radians to degrees.
Definition: CVMath.h:71
float DegreesToRadians(int degrees)
Convert degrees to radians.
Definition: CVMath.h:98
RgbTpl< ColorCompType > Rgb
3 components, default type
constexpr ColorCompType MAX
Max value of a single color component (default type)
Definition: ecvColorTypes.h:34
cloudViewer::NormalizedProgress * nProgress
cloudViewer::DgmOctree * octree
unsigned char octreeLevel
Container of in/out parameters for nearest neighbour(s) search.
Definition: DgmOctree.h:161
unsigned char level
Level of subdivision of the octree at which to start the search.
Definition: DgmOctree.h:171
Tuple3i cellPos
Position in the octree of the cell including the query point.
Definition: DgmOctree.h:184
CCVector3 cellCenter
Coordinates of the center of the cell including the query point.
Definition: DgmOctree.h:189
unsigned minNumberOfNeighbors
Minimal number of neighbours to find.
Definition: DgmOctree.h:177
void prepare(PointCoordinateType radius, PointCoordinateType cellSize)
Updates maxD2 and minD2 with search radius and cellSize.
Definition: DgmOctree.h:270
Octree cell descriptor.
Definition: DgmOctree.h:354
ReferenceCloud * points
Set of points lying inside this cell.
Definition: DgmOctree.h:365
const DgmOctree * parentOctree
Octree to which the cell belongs.
Definition: DgmOctree.h:359
unsigned char level
Cell level of subdivision.
Definition: DgmOctree.h:367
CellCode truncatedCode
Truncated cell code.
Definition: DgmOctree.h:361
Triangle described by the indexes of its 3 vertices.
Generic singleton encapsulation structure.
Definition: ecvSingleton.h:12