ACloudViewer  3.9.4
A Modern Library for 3D Data Processing
ecvMinimumSpanningTreeForNormsDirection.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 
9 
10 // cloudViewer
11 #include <ReferenceCloud.h>
12 
13 // local
14 #include "CVLog.h"
15 #include "ecvOctree.h"
16 #include "ecvPointCloud.h"
17 #include "ecvProgressDialog.h"
18 #include "ecvScalarField.h"
19 
20 // system
21 #include <map>
22 #include <queue>
23 #include <set>
24 #include <vector>
25 
27 class Edge {
28 public:
30  typedef std::pair<unsigned, unsigned> Key;
31 
33  inline static Key ConstructKey(unsigned v1, unsigned v2) {
34  return v1 > v2 ? std::make_pair(v2, v1) : std::make_pair(v1, v2);
35  }
36 
38  Edge(unsigned v1, unsigned v2, float weight)
39  : m_key(ConstructKey(v1, v2)), m_weight(weight) {
40  assert(m_weight >= 0);
41  }
42 
44  inline bool operator<(const Edge& other) const {
45  return m_weight > other.m_weight;
46  }
47 
49  const unsigned& v1() const { return m_key.first; }
51  const unsigned& v2() const { return m_key.second; }
52 
53 protected:
56 
58  float m_weight;
59 };
60 
62 class Graph {
63 public:
65  Graph() {}
66 
68  typedef std::set<unsigned> IndexSet;
69 
71 
74  bool reserve(unsigned vertexCount) {
75  m_edges.clear();
76 
77  try {
79  } catch (const std::bad_alloc&) {
80  // not enough memory
81  return false;
82  }
83 
84  return true;
85  }
86 
88  unsigned vertexCount() const {
89  return static_cast<unsigned>(m_vertexNeighbors.size());
90  } // the m_vertexNeighbors vector size shouldn't be bigger than a 32 bits
91  // integer (see 'reserve')
92 
94  size_t edgeCount() const { return m_edges.size(); }
95 
98  float weight(unsigned v1, unsigned v2) const {
99  assert(v1 < m_vertexNeighbors.size() && v2 < m_vertexNeighbors.size());
100 
101  // try to find the corresponding edge (otherwise return -1)
102  std::map<Edge::Key, float>::const_iterator it =
103  m_edges.find(Edge::ConstructKey(v1, v2));
104 
105  return (it == m_edges.end() ? -1 : it->second);
106  }
107 
109  void addEdge(unsigned v1, unsigned v2, float weight = 0.0f) {
110  assert(v1 < m_vertexNeighbors.size() && v2 < m_vertexNeighbors.size());
111 
113  m_vertexNeighbors[v1].insert(v2);
114  m_vertexNeighbors[v2].insert(v1);
115  }
116 
118  inline const IndexSet& getVertexNeighbors(unsigned index) const {
119  return m_vertexNeighbors[index];
120  }
121 
122 protected:
124  std::map<Edge::Key, float> m_edges;
125 
127  std::vector<IndexSet> m_vertexNeighbors;
128 };
129 
131 #ifdef WITH_GRAPH
132  const Graph& graph,
133 #else
135  unsigned char level,
136  unsigned kNN,
137 #endif
138  ecvProgressDialog* progressCb = 0) {
139  assert(cloud && cloud->hasNormals());
140 
141 // #define COLOR_PATCHES
142 #ifdef COLOR_PATCHES
143  // Test: color patches
145  cloud->showColors(true);
146 
147  // Test: arrival time
148  int sfIdx = cloud->getScalarFieldIndexByName("MST arrival time");
149  if (sfIdx < 0) sfIdx = cloud->addScalarField("MST arrival time");
150  ccScalarField* sf =
151  static_cast<ccScalarField*>(cloud->getScalarField(sfIdx));
152  sf->fill(NAN_VALUE);
153  cloud->setCurrentDisplayedScalarField(sfIdx);
154 #endif
155 
156  // reset
157  std::priority_queue<Edge> priorityQueue;
158  std::vector<bool> visited;
159  unsigned visitedCount = 0;
160 #ifdef WITH_GRAPH
161  unsigned vertexCount = graph.vertexCount();
162 #else
163  unsigned vertexCount = cloud->size();
164 #endif
165 
166  // instantiate the 'visited' table
167  try {
168  visited.resize(vertexCount, false);
169 
170  // progress notification
171  cloudViewer::NormalizedProgress nProgress(progressCb, vertexCount);
172  if (progressCb) {
173  progressCb->update(0);
174  progressCb->setMethodTitle(QObject::tr("Orient normals (MST)"));
175 #ifdef WITH_GRAPH
176  progressCb->setInfo(QObject::tr("Compute Minimum spanning "
177  "tree\nPoints: %1\nEdges: %2")
178  .arg(vertexCount)
179  .arg(graph.edgeCount()));
180 #else
181  progressCb->setInfo(
182  QObject::tr("Compute Minimum spanning tree\nPoints: %1")
183  .arg(vertexCount));
184 #endif
185  progressCb->start();
186  }
187 
188 #ifndef WITH_GRAPH
190  nNSS.level = level;
191  nNSS.minNumberOfNeighbors =
192  kNN + 1; //+1 because we'll get the query point itself!
193 #endif
194 
195  // while unvisited vertices remain...
196  unsigned firstUnvisitedIndex = 0;
197  size_t patchCount = 0;
198  size_t inversionCount = 0;
199  while (visitedCount < vertexCount) {
200  // find the first not-yet-visited vertex
201  while (visited[firstUnvisitedIndex]) {
202  ++firstUnvisitedIndex;
203  }
204 
205  // set it as "visited"
206  {
207  visited[firstUnvisitedIndex] = true;
208  ++visitedCount;
209  // add its neighbors to the priority queue
210 #ifdef WITH_GRAPH
211  const Graph::IndexSet& neighbors =
212  graph.getVertexNeighbors(firstUnvisitedIndex);
213  for (Graph::IndexSet::const_iterator it = neighbors.begin();
214  it != neighbors.end(); ++it) {
215  priorityQueue.push(
216  Edge(firstUnvisitedIndex, *it,
217  graph.weight(firstUnvisitedIndex, *it)));
218  }
219 #else
220  const CCVector3* P = cloud->getPoint(firstUnvisitedIndex);
221  nNSS.queryPoint = *P;
223  level);
224  octree->computeCellCenter(nNSS.cellPos, level, nNSS.cellCenter);
225  nNSS.pointsInNeighbourhood.clear();
227 
228  // look for neighbors in a sphere
229  unsigned neighborCount =
231  false);
232  neighborCount = std::min(neighborCount, kNN + 1);
233 
234  // current point index
235  const CCVector3& N1 =
236  cloud->getPointNormal(firstUnvisitedIndex);
237  for (unsigned j = 0; j < neighborCount; ++j) {
238  // current neighbor index
239  unsigned neighborIndex =
240  nNSS.pointsInNeighbourhood[j].pointIndex;
241  if (firstUnvisitedIndex != neighborIndex &&
242  !visited[neighborIndex]) {
243  const CCVector3& N2 =
244  cloud->getPointNormal(neighborIndex);
245  // dot product
246  float weight = std::max(
247  0.0f,
248  1.0f - static_cast<float>(fabs(N1.dot(N2))));
249 
250  // distance
251  // float weight =
252  // sqrt(nNSS.pointsInNeighbourhood[j].squareDistd);
253 
254  // mutual dot product
255  // const CCVector3* P2 =
256  // cloud->getPoint(static_cast<unsigned>(neighborIndex));
257  // CCVector3 uAB = *P2 - *P1;
258  // uAB.normalize();
259  // float weight = (fabs(CCVector3::vdot(uAB.u, N1) +
260  // fabs(CCVector3::vdot(uAB.u, N2)))) / 2;
261 
262  priorityQueue.push(Edge(firstUnvisitedIndex,
263  neighborIndex, weight));
264  }
265  }
266 #endif
267 
268  if (progressCb && !nProgress.oneStep()) {
269  break;
270  }
271  }
272 
273 #ifdef COLOR_PATCHES
275  cloud->setPointColor(static_cast<unsigned>(firstUnvisitedIndex),
276  patchCol.rgb);
277  sf->setValue(static_cast<unsigned>(firstUnvisitedIndex),
278  static_cast<ScalarType>(visitedCount));
279 #endif
280 
281  while (!priorityQueue.empty() && visitedCount < vertexCount) {
282  // process next edge (with the lowest 'weight')
283  Edge element = priorityQueue.top();
284  priorityQueue.pop();
285 
286  // shall the normal be inverted?
287  const CCVector3& N1 = cloud->getPointNormal(
288  static_cast<unsigned>(element.v1()));
289  const CCVector3& N2 = cloud->getPointNormal(
290  static_cast<unsigned>(element.v2()));
291  bool inverNormal = (N1.dot(N2) < 0);
292  unsigned v = 0;
293  // we should change the vertex that has not been visited yet
294  if (!visited[element.v1()]) {
295  v = element.v1();
296  if (inverNormal) {
297  cloud->setPointNormal(static_cast<unsigned>(v), -N1);
298  ++inversionCount;
299  }
300  } else if (!visited[element.v2()]) {
301  v = element.v2();
302  if (inverNormal) {
303  cloud->setPointNormal(static_cast<unsigned>(v), -N2);
304  ++inversionCount;
305  }
306  } else {
307  continue;
308  }
309 
310  // set it as "visited"
311  {
312  visited[v] = true;
313  ++visitedCount;
314  // add its neighbors to the priority queue
315 #ifdef WITH_GRAPH
316  const Graph::IndexSet& neighbors =
317  graph.getVertexNeighbors(v);
318  for (Graph::IndexSet::const_iterator it = neighbors.begin();
319  it != neighbors.end(); ++it)
320  priorityQueue.push(Edge(v, *it, graph.weight(v, *it)));
321 #else
322  const CCVector3* P = cloud->getPoint(v);
323  nNSS.queryPoint = *P;
325  level);
326  octree->computeCellCenter(nNSS.cellPos, level,
327  nNSS.cellCenter);
328  nNSS.pointsInNeighbourhood.clear();
330 
331  // look for neighbors in a sphere
332  unsigned neighborCount =
334  false);
335  neighborCount = std::min(neighborCount, kNN + 1);
336  // current point index
337  const CCVector3& N1 = cloud->getPointNormal(v);
338  for (unsigned j = 0; j < neighborCount; ++j) {
339  // current neighbor index
340  unsigned neighborIndex =
341  nNSS.pointsInNeighbourhood[j].pointIndex;
342  if (v != neighborIndex && !visited[neighborIndex]) {
343  const CCVector3& N2 =
344  cloud->getPointNormal(neighborIndex);
345  // dot product
346  float weight = std::max(
347  0.0f, 1.0f - static_cast<float>(
348  fabs(N1.dot(N2))));
349 
350  // distance
351  // float weight =
352  // sqrt(nNSS.pointsInNeighbourhood[j].squareDistd);
353 
354  // mutual dot product
355  // const CCVector3* P2 =
356  // cloud->getPoint(static_cast<unsigned>(neighborIndex));
357  // CCVector3 uAB = *P2 - *P1;
358  // uAB.normalize();
359  // float weight = (fabs(CCVector3::vdot(uAB.u, N1) +
360  // fabs(CCVector3::vdot(uAB.u, N2)))) / 2;
361 
362  priorityQueue.push(Edge(v, neighborIndex, weight));
363  }
364  }
365 #endif
366  }
367 
368 #ifdef COLOR_PATCHES
369  cloud->setPointColor(static_cast<unsigned>(v), patchCol);
370  sf->setValue(static_cast<unsigned>(v),
371  static_cast<ScalarType>(visitedCount));
372 #endif
373  if (progressCb && !nProgress.oneStep()) {
374  visitedCount =
375  static_cast<unsigned>(vertexCount); // early stop
376  break;
377  }
378  }
379 
380  // new patch
381  ++patchCount;
382  }
383 
384 #ifdef COLOR_PATCHES
385  sf->computeMinAndMax();
386  cloud->showSF(true);
387 #endif
388 
389  if (progressCb) {
390  progressCb->stop();
391  }
392 
393  CVLog::Print(
394  QString("[ResolveNormalsWithMST] Patches = %1 / Inversions: %2")
395  .arg(patchCount)
396  .arg(inversionCount));
397  } catch (const std::bad_alloc&) {
398  // not enough memory
399  return false;
400  }
401 
402  return true;
403 }
404 
407  void** additionalParameters,
409  // parameters
410  Graph* graph = static_cast<Graph*>(additionalParameters[0]);
411  ccPointCloud* cloud = static_cast<ccPointCloud*>(additionalParameters[1]);
412 
413  // structure for the nearest neighbor search
414  unsigned kNN = *static_cast<unsigned*>(additionalParameters[2]);
415 
417  nNSS.level = cell.level;
418  nNSS.minNumberOfNeighbors =
419  kNN + 1; //+1 because we'll get the query point itself!
420  cell.parentOctree->getCellPos(cell.truncatedCode, cell.level, nNSS.cellPos,
421  true);
422  cell.parentOctree->computeCellCenter(nNSS.cellPos, cell.level,
423  nNSS.cellCenter);
424 
425  unsigned n = cell.points->size(); // number of points in the current cell
426 
427  // we already know some of the neighbours: the points in the current cell!
428  {
429  try {
430  nNSS.pointsInNeighbourhood.resize(n);
431  } catch (... /*const std::bad_alloc&*/) // out of memory
432  {
433  return false;
434  }
435 
436  cloudViewer::DgmOctree::NeighboursSet::iterator it =
437  nNSS.pointsInNeighbourhood.begin();
438  for (unsigned i = 0; i < n; ++i, ++it) {
439  it->point = cell.points->getPointPersistentPtr(i);
440  it->pointIndex = cell.points->getPointGlobalIndex(i);
441  }
442  }
444 
445  // for each point in the cell
446  for (unsigned i = 0; i < n; ++i) {
447  cell.points->getPoint(i, nNSS.queryPoint);
448 
449  // look for neighbors in a sphere
450  unsigned neighborCount =
452  false);
453  neighborCount = std::min(neighborCount, kNN + 1);
454 
455  // current point index
456  unsigned index = cell.points->getPointGlobalIndex(i);
457  const CCVector3& N1 = cloud->getPointNormal(index);
458  // const CCVector3* P1 = cloud->getPoint(static_cast<unsigned>(index));
459  for (unsigned j = 0; j < neighborCount; ++j) {
460  // current neighbor index
461  unsigned neighborIndex = nNSS.pointsInNeighbourhood[j].pointIndex;
462  if (index != neighborIndex) {
463  const CCVector3& N2 = cloud->getPointNormal(neighborIndex);
464  // dot product
465  float weight = std::max(
466  0.0f, 1.0f - static_cast<float>(fabs(N1.dot(N2))));
467 
468  // distance
469  // float weight =
470  // sqrt(nNSS.pointsInNeighbourhood[j].squareDistd);
471 
472  // mutual dot product
473  // const CCVector3* P2 =
474  // cloud->getPoint(static_cast<unsigned>(neighborIndex));
475  // CCVector3 uAB = *P2 - *P1;
476  // uAB.normalize();
477  // float weight = (fabs(CCVector3::vdot(uAB.u, N1) +
478  // fabs(CCVector3::vdot(uAB.u, N2)))) / 2;
479 
480  graph->addEdge(index, neighborIndex, weight);
481  }
482  }
483 
484  if (nProgress && !nProgress->oneStep()) return false;
485  }
486 
487  return true;
488 }
489 
491  ccPointCloud* cloud,
492  unsigned kNN /*=6*/,
493  ecvProgressDialog* progressDlg /*=0*/) {
494  assert(cloud);
495  if (!cloud->hasNormals()) {
497  QString("Cloud '%1' has no normals!").arg(cloud->getName()));
498  return false;
499  }
500 
501  // we need the octree
502  if (!cloud->getOctree()) {
503  if (!cloud->computeOctree(progressDlg)) {
504  CVLog::Warning(QString("[orientNormalsWithMST] Could not compute "
505  "octree on cloud '%1'")
506  .arg(cloud->getName()));
507  return false;
508  }
509  }
510  ccOctree::Shared octree = cloud->getOctree();
511  assert(octree);
512 
513  unsigned char level = octree->findBestLevelForAGivenPopulationPerCell(kNN);
514 
515  bool result = true;
516  try {
517 #ifdef WITH_GRAPH
518  Graph graph;
519  if (!graph.reserve(cloud->size())) {
520  // not enough memory!
521  result = false;
522  }
523 
524  // parameters
525  void* additionalParameters[3] = {reinterpret_cast<void*>(&graph),
526  reinterpret_cast<void*>(cloud),
527  reinterpret_cast<void*>(&kNN)};
528 
530  level, &ComputeMSTGraphAtLevel, additionalParameters,
531  false, // not compatible with parallel strategies!
532  progressDlg, "Build Spanning Tree") == 0) {
533  // something went wrong
535  QString("Failed to compute Spanning Tree on cloud '%1'")
536  .arg(cloud->getName()));
537  result = false;
538  } else {
539  if (!ResolveNormalsWithMST(cloud, graph, progressDlg)) {
540  // something went wrong
541  CVLog::Warning(QString("Failed to compute Minimum Spanning "
542  "Tree on cloud '%1'")
543  .arg(cloud->getName()));
544  result = false;
545  }
546  }
547 #else
548  if (!ResolveNormalsWithMST(cloud, octree, level, kNN, progressDlg)) {
549  // something went wrong
550  CVLog::Warning(QString("Failed to resolve normals orientation with "
551  "Minimum Spanning Tree on cloud '%1'")
552  .arg(cloud->getName()));
553  result = false;
554  }
555 #endif
556  } catch (...) {
557  CVLog::Error(
558  QString("Process failed on cloud '%1'").arg(cloud->getName()));
559  result = false;
560  }
561 
562  return result;
563 }
constexpr ScalarType NAN_VALUE
NaN as a ScalarType value.
Definition: CVConst.h:76
core::Tensor result
Definition: VtkUtils.cpp:76
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
static bool Error(const char *format,...)
Display an error dialog with formatted message.
Definition: CVLog.cpp:143
Generic graph structure.
void addEdge(unsigned v1, unsigned v2, float weight=0.0f)
Adds or updates the edge (v1,v2)
std::set< unsigned > IndexSet
Set of indexes.
std::map< Edge::Key, float > m_edges
Graph edges.
bool reserve(unsigned vertexCount)
Reserves memory for graph.
unsigned vertexCount() const
Returns the number of vertices.
size_t edgeCount() const
Returns the number of edges.
std::vector< IndexSet > m_vertexNeighbors
Set of neighbors for each vertex.
Graph()
Default constructor.
const IndexSet & getVertexNeighbors(unsigned index) const
Returns the set of edges connected to a given vertex.
float weight(unsigned v1, unsigned v2) const
Type dot(const Vector3Tpl &v) const
Dot product.
Definition: CVGeom.h:408
virtual void showColors(bool state)
Sets colors visibility.
virtual void showSF(bool state)
Sets active scalarfield visibility.
virtual ccOctree::Shared computeOctree(cloudViewer::GenericProgressCallback *progressCb=nullptr, bool autoAddChild=true)
Computes the cloud octree.
virtual ccOctree::Shared getOctree() const
Returns the associated octree (if any)
static bool OrientNormals(ccPointCloud *cloud, unsigned kNN=6, ecvProgressDialog *progressDlg=0)
Main entry point.
virtual QString getName() const
Returns object name.
Definition: ecvObject.h:72
QSharedPointer< ccOctree > Shared
Shared pointer.
Definition: ecvOctree.h:32
A 3D cloud and its associated features (color, normals, scalar fields, etc.)
void setCurrentDisplayedScalarField(int index)
Sets the currently displayed scalar field.
int addScalarField(const char *uniqueName) override
Creates a new scalar field and registers it.
bool hasNormals() const override
Returns whether normals are enabled or not.
void setPointColor(size_t pointIndex, const ecvColor::Rgb &col)
Sets a particular point color.
void setPointNormal(size_t pointIndex, const CCVector3 &N)
Sets a particular point normal (shortcut)
const CCVector3 & getPointNormal(unsigned pointIndex) const override
Returns normal corresponding to a given point.
bool setRGBColor(ColorCompType r, ColorCompType g, ColorCompType b)
Set a unique color for the whole cloud (shortcut)
A scalar field associated to display-related parameters.
void computeMinAndMax() override
Determines the min and max values.
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
void getTheCellPosWhichIncludesThePoint(const CCVector3 *thePoint, Tuple3i &cellPos) const
Definition: DgmOctree.h:779
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
unsigned char findBestLevelForAGivenPopulationPerCell(unsigned indicativeNumberOfPointsPerCell) const
Definition: DgmOctree.cpp:2737
bool oneStep()
Increments total progress value of a single unit.
int getScalarFieldIndexByName(const char *name) const
Returns the index of a scalar field represented by its name.
ScalarField * getScalarField(int index) const
Returns a pointer to a specific scalar field.
unsigned size() const override
Definition: PointCloudTpl.h:38
const CCVector3 * getPoint(unsigned index) const override
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.
void fill(ScalarType fillValue=0)
Fills the array with a particular value.
Definition: ScalarField.h:77
void setValue(std::size_t index, ScalarType value)
Definition: ScalarField.h:96
static Rgb Random(bool lightOnly=true)
Generates a random color.
RGB color structure.
Definition: ecvColorTypes.h:49
Graphical progress indicator (thread-safe)
static bool ComputeMSTGraphAtLevel(const cloudViewer::DgmOctree::octreeCell &cell, void **additionalParameters, cloudViewer::NormalizedProgress *nProgress)
static bool ResolveNormalsWithMST(ccPointCloud *cloud, ccOctree::Shared &octree, unsigned char level, unsigned kNN, ecvProgressDialog *progressCb=0)
CLOUDVIEWER_HOST_DEVICE Pair< First, Second > make_pair(const First &_first, const Second &_second)
Definition: SlabTraits.h:49
constexpr Rgb white(MAX, MAX, MAX)
cloudViewer::NormalizedProgress * nProgress
cloudViewer::DgmOctree * octree
Weighted graph edge.
const unsigned & v2() const
Returns second vertex (index)
Edge(unsigned v1, unsigned v2, float weight)
Default constructor.
const unsigned & v1() const
Returns first vertex (index)
std::pair< unsigned, unsigned > Key
Unique edge key type.
bool operator<(const Edge &other) const
Strict weak ordering operator (required by std::priority_queue)
static Key ConstructKey(unsigned v1, unsigned v2)
Returns the unique key of an edge (no vertex order)
float m_weight
Associated weight.
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
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