iMSTK
Interactive Medical Simulation Toolkit
imstkLooseOctree.cpp
1 /*
2 ** This file is part of the Interactive Medical Simulation Toolkit (iMSTK)
3 ** iMSTK is distributed under the Apache License, Version 2.0.
4 ** See accompanying NOTICE for details.
5 */
6 
7 #include "imstkLooseOctree.h"
8 #include "imstkLogger.h"
9 #include "imstkSurfaceMesh.h"
10 
11 namespace imstk
12 {
13 OctreeNode::OctreeNode(LooseOctree* const tree, OctreeNode* const pParent, const Vec3d& nodeCenter,
14  const double halfWidth, const uint32_t depth) :
15  m_pTree(tree),
16  m_pParent(pParent),
17  m_Center(nodeCenter),
18  m_LowerBound(nodeCenter - Vec3d(halfWidth, halfWidth, halfWidth)),
19  m_UpperBound(nodeCenter + Vec3d(halfWidth, halfWidth, halfWidth)),
20  m_LowerExtendedBound(nodeCenter - 2.0 * Vec3d(halfWidth, halfWidth, halfWidth)),
21  m_UpperExtendedBound(nodeCenter + 2.0 * Vec3d(halfWidth, halfWidth, halfWidth)),
22  m_HalfWidth(halfWidth),
23  m_Depth(depth),
24  m_MaxDepth(tree->m_MaxDepth),
25  m_bIsLeaf(true)
26 {
27  // Must initialize primitive linked lists and counters
28  for (int type = 0; type < OctreePrimitiveType::NumPrimitiveTypes; ++type)
29  {
30  clearPrimitiveData(static_cast<OctreePrimitiveType>(type));
31  }
32 }
33 
35 OctreeNode::getChildNode(const uint32_t childIdx) const
36 {
37 #if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG)
38  LOG_IF(FATAL, (!m_pChildren)) << "Children node block is nullptr";
39 #endif
40  return &m_pChildren->m_Nodes[childIdx];
41 }
42 
43 void
45 {
46  m_pPrimitiveListHeads[type] = nullptr;
47  m_PrimitiveCounts[type] = 0;
48 
49  if (!isLeaf())
50  {
51  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
52  {
53  m_pChildren->m_Nodes[childIdx].clearPrimitiveData(type);
54  }
55  }
56 }
57 
58 void
60 {
61  if (!isLeaf() || m_Depth == m_MaxDepth)
62  {
63  return;
64  }
65 
67  if (isLeaf())
68  {
69  //--------------------------------------------------------
70  //
71  // 6-------7
72  // /| /|
73  // 2-+-----3 |
74  // | | | | y
75  // | 4-----+-5 | z
76  // |/ |/ |/
77  // 0-------1 +--x
78  //
79  // 0 => 0, 0, 0
80  // 1 => 0, 0, 1
81  // 2 => 0, 1, 0
82  // 3 => 0, 1, 1
83  // 4 => 1, 0, 0
84  // 5 => 1, 0, 1
85  // 6 => 1, 1, 0
86  // 7 => 1, 1, 1
87  //
88  //--------------------------------------------------------
89 
91 
92  const auto childHalfWidth = m_HalfWidth * static_cast<double>(0.5);
93  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
94  {
95  auto newCenter = m_Center;
96  newCenter[0] += (childIdx & 1) ? childHalfWidth : -childHalfWidth;
97  newCenter[1] += (childIdx & 2) ? childHalfWidth : -childHalfWidth;
98  newCenter[2] += (childIdx & 4) ? childHalfWidth : -childHalfWidth;
99 
100  OctreeNode* const pChildNode = &m_pChildren->m_Nodes[childIdx];
101 
102  // Placement new: re-use existing memory block, just call constructor to re-initialize data
103  new(pChildNode) OctreeNode(m_pTree, this, newCenter, childHalfWidth, m_Depth + 1u);
104  }
105 
106  // Must explicitly mark as non-leaf node, and must do this after all children nodes are ready
107  m_bIsLeaf = false;
108  }
110 }
111 
112 void
114 {
115  if (isLeaf())
116  {
117  return;
118  }
119 
120  // Must explicitly mark as leaf node
121  m_bIsLeaf = true;
122 
123  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
124  {
125  auto& pChildNode = m_pChildren->m_Nodes[childIdx];
126  pChildNode.removeAllDescendants();
127  }
129 }
130 
131 void
133 {
134  if (isLeaf())
135  {
136  return;
137  }
138 
139  bool bAllEmpty = true;
140  bool bAllLeaves = true;
141  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
142  {
143  auto& pChildNode = m_pChildren->m_Nodes[childIdx];
144  pChildNode.removeEmptyDescendants();
145  bAllLeaves &= pChildNode.isLeaf();
146 
147  for (int i = 0; i < OctreePrimitiveType::NumPrimitiveTypes; ++i)
148  {
149  bAllEmpty &= (pChildNode.m_PrimitiveCounts[i] == 0);
150  }
151  }
152 
153  // Remove all 8 children nodes iff they are all leaf nodes and all empty nodes
154  if (bAllEmpty && bAllLeaves)
155  {
157  m_bIsLeaf = true;
158  }
159 }
160 
161 void
163 {
164  pPrimitive->m_pNode = this;
165  pPrimitive->m_bValid = true;
166 
167  m_PrimitiveLock[type].lock();
168  pPrimitive->m_pNext = m_pPrimitiveListHeads[type];
169  m_pPrimitiveListHeads[type] = pPrimitive;
170  m_PrimitiveCounts[type] += 1u;
171  m_PrimitiveLock[type].unlock();
172 }
173 
174 void
176 {
177  // Type alias, to reduce copy/past errors
178  static const auto type = OctreePrimitiveType::Point;
179 
180  if (m_Depth == m_MaxDepth)
181  {
182  keepPrimitive(pPrimitive, type);
183  return;
184  }
185 
186  // Split node if this is a leaf node
187  split();
188 
189  // Compute the index of the child node that contains this point
190  uint32_t childIdx = 0;
191  for (uint32_t dim = 0; dim < 3; ++dim)
192  {
193  if (m_Center[dim] < pPrimitive->m_Position[dim])
194  {
195  childIdx |= (1 << dim);
196  }
197  }
198 
199  m_pChildren->m_Nodes[childIdx].insertPoint(pPrimitive);
200 }
201 
202 void
204 {
205  const auto lowerCorner = pPrimitive->m_LowerCorner;
206  const auto upperCorner = pPrimitive->m_UpperCorner;
207  const Vec3d priCenter(
208  (lowerCorner[0] + upperCorner[0]) * 0.5,
209  (lowerCorner[1] + upperCorner[1]) * 0.5,
210  (lowerCorner[2] + upperCorner[2]) * 0.5);
211 
212 #if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG)
213  LOG_IF(FATAL, (this != m_pTree->m_pRootNode && !looselyContains(lowerCorner, upperCorner)))
214  << "Invalid primitive data (a non-root node must loosely contain primitives)";
215 #endif
216 
217  if (m_Depth == m_MaxDepth)
218  {
219  keepPrimitive(pPrimitive, type);
220  return;
221  }
222 
223  uint32_t childIdx = 0;
224  bool bStraddle = false;
225 
226  for (uint32_t dim = 0; dim < 3; ++dim)
227  {
228  if (m_Center[dim] < priCenter[dim])
229  {
230  if (m_Center[dim] - (m_HalfWidth * 0.5) > lowerCorner[dim]
231  || m_Center[dim] + (m_HalfWidth * 1.5) < upperCorner[dim])
232  {
233  bStraddle = true;
234  break;
235  }
236  else
237  {
238  childIdx |= (1 << dim);
239  }
240  }
241  else
242  {
243  if (m_Center[dim] + (m_HalfWidth * 0.5) < upperCorner[dim]
244  || m_Center[dim] - (m_HalfWidth * 1.5) > lowerCorner[dim])
245  {
246  bStraddle = true;
247  break;
248  }
249  }
250  }
251 
252  // If the primive straddles over multiple children nodes, we must keep it at the current node
253  if (bStraddle)
254  {
255  keepPrimitive(pPrimitive, type);
256  return;
257  }
258 
259  // Split node if this is a leaf node
260  split();
261 
262  // Insert the primitive to the child node that loosely contains it
263  m_pChildren->m_Nodes[childIdx].insertNonPointPrimitive(pPrimitive, type);
264 }
265 
266 LooseOctree::LooseOctree(const Vec3d& center, const double width, const double minWidth,
267  const double minWidthRatio /*= 1.0*/, const std::string name /*= "LooseOctree"*/) :
268  m_Name(name),
269  m_Center(center),
270  m_Width(width),
271  m_MinWidthRatio(minWidthRatio),
272  m_MinWidth(minWidth),
273  m_pRootNode(new OctreeNode(this, nullptr, center, width * static_cast<double>(0.5), 1u)),
274  m_NumAllocatedNodes(1u)
275 {
276 }
277 
279 {
280  // Firstly clear data recursively
281  clear();
282 
283  // Deallocate memory pool
285 
286  // Remove root node
287  delete m_pRootNode;
288 }
289 
290 void
292 {
293  // Return all tree nodes to memory pool except the root node
295 
296  for (int type = 0; type < OctreePrimitiveType::NumPrimitiveTypes; ++type)
297  {
298  clearPrimitive(static_cast<OctreePrimitiveType>(type));
299  }
300  // Remove all geometry pointers
301  m_sGeometryIndices.clear();
302 
303  // Set state to imcomplete
304  m_bCompleteBuild = false;
305 }
306 
307 void
309 {
310  // Recursively clear primitive data
312 
313  // Remove primitives from tree
314  if (m_vPrimitivePtrs[type].size() > 0)
315  {
316  for (const auto& pPrimitive: m_vPrimitivePtrs[type])
317  {
318  removeGeometry(pPrimitive->m_GeomIdx);
319  }
320  m_vPrimitivePtrs[type].resize(0);
321  }
322 
323  // Deallocate primitive memory blocks
324  for (const auto pPrimitiveBlock: m_pPrimitiveBlocks[type])
325  {
326  delete[] pPrimitiveBlock;
327  }
328  m_pPrimitiveBlocks[type].resize(0);
329 }
330 
331 uint32_t
333 {
334  return tbb::parallel_reduce(m_sActiveTreeNodeBlocks.range(),
335  0u,
336  [&](decltype(m_sActiveTreeNodeBlocks)::const_range_type& r, uint32_t prevResult) -> uint32_t
337  {
338  for (auto it = r.begin(), iEnd = r.end(); it != iEnd; ++it)
339  {
340  const auto pNodeBlock = *it;
341  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
342  {
343  const auto& pNode = pNodeBlock->m_Nodes[childIdx];
344  for (int type = 0; type < OctreePrimitiveType::NumPrimitiveTypes; ++type)
345  {
346  if (prevResult < pNode.m_PrimitiveCounts[type])
347  {
348  prevResult = pNode.m_PrimitiveCounts[type];
349  }
350  }
351  }
352  }
353  return prevResult;
354  },
355  [](const uint32_t x, const uint32_t y) -> uint32_t
356  {
357  return x > y ? x : y;
358  });
359 }
360 
361 uint32_t
362 LooseOctree::addPointSet(const std::shared_ptr<PointSet>& pointset)
363 {
364  // Type alias, to reduce copy/past errors
365  static const auto type = static_cast<int>(OctreePrimitiveType::Point);
366 
367  const auto pGeometry = static_cast<Geometry*>(pointset.get());
368  const auto geomIdx = static_cast<uint32_t>(pGeometry->getGlobalId());
369  addGeometry(geomIdx);
370 
371  const auto numNewPrimitives = static_cast<uint32_t>(pointset->getNumVertices());
372  const auto pPrimitiveBlock = new OctreePrimitive[numNewPrimitives];
373  m_pPrimitiveBlocks[type].push_back(pPrimitiveBlock);
374 
375  auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
376  vPrimitivePtrs.reserve(vPrimitivePtrs.size() + numNewPrimitives);
377  for (uint32_t idx = 0; idx < numNewPrimitives; ++idx)
378  {
379  const auto pPrimitive = &pPrimitiveBlock[idx];
380  new(pPrimitive) OctreePrimitive(pGeometry, geomIdx, idx); // Placement new
381  vPrimitivePtrs.push_back(pPrimitive);
382  }
383 
384  LOG(INFO) << "Added " << numNewPrimitives << " points to " << m_Name;
385  return numNewPrimitives;
386 }
387 
388 uint32_t
389 LooseOctree::addTriangleMesh(const std::shared_ptr<SurfaceMesh>& surfMesh)
390 {
391  // Type alias, to reduce copy/past errors
392  static const auto type = static_cast<int>(OctreePrimitiveType::Triangle);
393 
394  const auto pGeometry = static_cast<Geometry*>(surfMesh.get());
395  const auto geomIdx = static_cast<uint32_t>(pGeometry->getGlobalId());
396  addGeometry(geomIdx);
397 
398  const auto numNewPrimitives = static_cast<uint32_t>(surfMesh->getNumCells());
399  const auto pPrimitiveBlock = new OctreePrimitive[numNewPrimitives];
400  m_pPrimitiveBlocks[type].push_back(pPrimitiveBlock);
401 
402  auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
403  vPrimitivePtrs.reserve(vPrimitivePtrs.size() + numNewPrimitives);
404  for (uint32_t triIdx = 0; triIdx < numNewPrimitives; ++triIdx)
405  {
406  const auto pPrimitive = &pPrimitiveBlock[triIdx];
407  new(pPrimitive) OctreePrimitive(pGeometry, geomIdx, triIdx); // Placement new
408  vPrimitivePtrs.push_back(pPrimitive);
409  }
410 
411  LOG(INFO) << "Added " << numNewPrimitives << " triangles to " << m_Name;
412  return numNewPrimitives;
413 }
414 
415 uint32_t
416 LooseOctree::addAnalyticalGeometry(const std::shared_ptr<Geometry>& geometry)
417 {
418  // Type alias, to reduce copy/past errors
419  static const auto type = static_cast<int>(OctreePrimitiveType::Analytical);
420 
421  const auto pGeometry = geometry.get();
422  const auto geomIdx = static_cast<uint32_t>(pGeometry->getGlobalId());
423  addGeometry(geomIdx);
424 
425  const auto pPrimitiveBlock = new OctreePrimitive[1];
426  m_pPrimitiveBlocks[type].push_back(pPrimitiveBlock);
427 
428  const auto pPrimitive = &pPrimitiveBlock[0];
429  new(pPrimitive) OctreePrimitive(pGeometry, geomIdx, 0); // Placement new
430  m_vPrimitivePtrs[type].push_back(pPrimitive);
431 
432  LOG(INFO) << "Added a new analytical geometry to " << m_Name;
433  return 1u;
434 }
435 
436 void
437 LooseOctree::addGeometry(const uint32_t geomIdx)
438 {
439  LOG_IF(FATAL, (hasGeometry(geomIdx))) << "Geometry has previously been added";
440  m_sGeometryIndices.insert(geomIdx);
441 }
442 
443 void
444 LooseOctree::removeGeometry(const uint32_t geomIdx)
445 {
446  const auto it = m_sGeometryIndices.find(geomIdx);
447  if (it != m_sGeometryIndices.end())
448  {
449  m_sGeometryIndices.erase(it);
450  }
451 }
452 
453 void
455 {
456  if (m_sGeometryIndices.size() == 0)
457  {
458  LOG(WARNING) << "There was not any geometry added in the tree named '" << m_Name << "'";
459  return;
460  }
461 
462  // Compute the minimum bounding box of non-point primitives
463  if (m_vPrimitivePtrs[OctreePrimitiveType::Point].size() == 0
464  && (m_vPrimitivePtrs[OctreePrimitiveType::Triangle].size() > 0
465  || m_vPrimitivePtrs[OctreePrimitiveType::Analytical].size() > 0))
466  {
467  double minWidth = IMSTK_DOUBLE_MAX;
468  for (int type = OctreePrimitiveType::Triangle; type <= OctreePrimitiveType::Analytical; ++type)
469  {
470  const auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
471  if (vPrimitivePtrs.size() == 0)
472  {
473  continue;
474  }
475  const auto primitiveMinWidth = tbb::parallel_reduce(tbb::blocked_range<size_t>(0, vPrimitivePtrs.size()),
476  IMSTK_DOUBLE_MAX,
477  [&](const tbb::blocked_range<size_t>& r, double prevResult) -> double {
478  for (auto i = r.begin(), iEnd = r.end(); i != iEnd; ++i)
479  {
480  const auto pPrimitive = vPrimitivePtrs[i];
481  computePrimitiveBoundingBox(pPrimitive, static_cast<OctreePrimitiveType>(type));
482 
483  Vec3d widths;
484  for (uint32_t dim = 0; dim < 3; ++dim)
485  {
486  widths[dim] = pPrimitive->m_UpperCorner[dim] - pPrimitive->m_LowerCorner[dim];
487  }
488  auto maxBoxWidth = widths[0];
489  maxBoxWidth = maxBoxWidth < widths[1] ? widths[1] : maxBoxWidth;
490  maxBoxWidth = maxBoxWidth < widths[2] ? widths[2] : maxBoxWidth;
491  prevResult = prevResult > maxBoxWidth ? maxBoxWidth : prevResult;
492  }
493  return prevResult;
494  },
495  [](const double x, const double y) -> double {
496  return x < y ? x : y;
497  });
498 
499  minWidth = minWidth < primitiveMinWidth ? minWidth : primitiveMinWidth;
500  }
501 
502  if (minWidth < 1e-8)
503  {
504  LOG(WARNING) << "Object/triangles have too small size";
505  }
506  else
507  {
508  m_MinWidth = m_MinWidthRatio * minWidth;
509  }
510  }
511 
512  // Compute max depth that the tree can reach
513  m_MaxDepth = 1u;
514  uint32_t numLevelNodes = 1u;
515  uint32_t maxNumTreeNodes = 1u;
516  double nodeWidth = m_Width;
517 
518  while (nodeWidth * 0.5 > m_MinWidth) {
519  ++m_MaxDepth;
520  numLevelNodes *= 8u;
521  maxNumTreeNodes += numLevelNodes;
522  nodeWidth *= 0.5;
523  }
525  rebuild();
526  m_bCompleteBuild = true;
527 
528  LOG(INFO) << m_Name << " generated, center = [" << m_Center[0] << ", " << m_Center[1] << ", " << m_Center[2]
529  << "], width = " << m_Width << ", min width = " << m_MinWidth
530  << ", max depth = " << m_MaxDepth << ", max num. nodes = " << maxNumTreeNodes;
531 }
532 
533 void
535 {
536  if (!m_bCompleteBuild)
537  {
538  build();
539  }
541 }
542 
543 void
545 {
546  // Recursively remove all tree nodes other than root node
548 
549  // Clear root node data
550  for (int type = 0; type < OctreePrimitiveType::NumPrimitiveTypes; ++type)
551  {
552  m_pRootNode->clearPrimitiveData(static_cast<OctreePrimitiveType>(type));
553  }
554 
555  // Populate all primitives to tree nodes in a top-down manner
557  populateNonPointPrimitives(OctreePrimitiveType::Triangle);
558  populateNonPointPrimitives(OctreePrimitiveType::Analytical);
559 }
560 
561 void
563 {
564  const auto& vPrimitivePtrs = m_vPrimitivePtrs[OctreePrimitiveType::Point];
565  if (vPrimitivePtrs.size() == 0)
566  {
567  return;
568  }
569  ParallelUtils::parallelFor(vPrimitivePtrs.size(),
570  [&](const size_t idx) {
571  const auto pPrimitive = vPrimitivePtrs[idx];
572  const auto pointset = static_cast<PointSet*>(pPrimitive->m_pGeometry);
573  const auto point = pointset->getVertexPosition(pPrimitive->m_Idx);
574  pPrimitive->m_Position = { point[0], point[1], point[2] };
575  m_pRootNode->insertPoint(pPrimitive);
576  });
577 }
578 
579 void
581 {
582  const auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
583  if (vPrimitivePtrs.size() == 0)
584  {
585  return;
586  }
587  ParallelUtils::parallelFor(vPrimitivePtrs.size(),
588  [&](const size_t idx) {
589  const auto pPrimitive = vPrimitivePtrs[idx];
590  computePrimitiveBoundingBox(pPrimitive, type);
591  m_pRootNode->insertNonPointPrimitive(pPrimitive, type);
592  });
593 }
594 
595 void
597 {
598  // For all primitives, update their positions (if point) or bounding box (if non-point)
599  // Then, check their validity (valid primitive = it is still loosely contained in the node's bounding box)
601  updateBoundingBoxAndCheckValidity(OctreePrimitiveType::Triangle);
602  updateBoundingBoxAndCheckValidity(OctreePrimitiveType::Analytical);
603 
604  // Remove all invalid primitives from tree nodes
606 
607  // Insert the invalid primitives back to the tree
608  reinsertInvalidPrimitives(OctreePrimitiveType::Point);
609  reinsertInvalidPrimitives(OctreePrimitiveType::Triangle);
610  reinsertInvalidPrimitives(OctreePrimitiveType::Analytical);
611 
612  // Recursively remove all empty nodes, returning them to memory pool for recycling
614 }
615 
616 void
618 {
619  const auto& vPrimitivePtrs = m_vPrimitivePtrs[OctreePrimitiveType::Point];
620  if (vPrimitivePtrs.size() == 0)
621  {
622  return;
623  }
624  ParallelUtils::parallelFor(vPrimitivePtrs.size(),
625  [&](const size_t idx) {
626  const auto pPrimitive = vPrimitivePtrs[idx];
627  const auto pointset = static_cast<PointSet*>(pPrimitive->m_pGeometry);
628  const auto point = pointset->getVertexPosition(pPrimitive->m_Idx);
629 
630  // Cache the position
631  pPrimitive->m_Position = { point[0], point[1], point[2] };
632 
633  auto pNode = pPrimitive->m_pNode;
634  if (!pNode->looselyContains(point) && pNode != m_pRootNode)
635  {
636  // Go up, find the node tightly containing it (or stop if reached root node)
637  while (pNode != m_pRootNode) {
638  pNode = pNode->m_pParent; // Go up one level
639  if (pNode->contains(point) || pNode == m_pRootNode)
640  {
641  pPrimitive->m_bValid = false;
642  pPrimitive->m_pNode = pNode;
643  break;
644  }
645  }
646  }
647  else
648  {
649  pPrimitive->m_bValid = (pNode != m_pRootNode) ? true : false;
650  }
651  });
652 }
653 
654 void
656 {
657  const auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
658  if (vPrimitivePtrs.size() == 0)
659  {
660  return;
661  }
662  ParallelUtils::parallelFor(vPrimitivePtrs.size(),
663  [&](const size_t idx) {
664  const auto pPrimitive = vPrimitivePtrs[idx];
665  computePrimitiveBoundingBox(pPrimitive, type);
666  const auto lowerCorner = pPrimitive->m_LowerCorner;
667  const auto upperCorner = pPrimitive->m_UpperCorner;
668  const Vec3d center(
669  (lowerCorner[0] + upperCorner[0]) * 0.5,
670  (lowerCorner[1] + upperCorner[1]) * 0.5,
671  (lowerCorner[2] + upperCorner[2]) * 0.5);
672 
673  auto pNode = pPrimitive->m_pNode;
674  if (!pNode->looselyContains(lowerCorner, upperCorner) && pNode != m_pRootNode)
675  {
676  // Go up, find the node tightly containing it (or stop if reached root node)
677  while (pNode != m_pRootNode) {
678  pNode = pNode->m_pParent; // Go up one level
679 
680  if (pNode->contains(lowerCorner, upperCorner) || pNode == m_pRootNode)
681  {
682  pPrimitive->m_bValid = false;
683  pPrimitive->m_pNode = pNode;
684  break;
685  }
686  }
687  }
688  // If node still contains primitive + node depth reaches maxDepth
689  else if (pNode->m_Depth == m_MaxDepth)
690  {
691  pPrimitive->m_bValid = true;
692  }
693  // If node still contains primitive but node depth does not reach maxDepth,
694  // then check if the primitive straddles over children nodes
695  else
696  {
697  bool bStraddle = false;
698 
699  for (uint32_t dim = 0; dim < 3; ++dim)
700  {
701  if (m_Center[dim] < center[dim])
702  {
703  if (pNode->m_Center[dim] - (pNode->m_HalfWidth * 0.5) > lowerCorner[dim]
704  || pNode->m_Center[dim] + (pNode->m_HalfWidth * 1.5) < upperCorner[dim])
705  {
706  bStraddle = true;
707  break;
708  }
709  }
710  else
711  {
712  if (pNode->m_Center[dim] + (pNode->m_HalfWidth * 0.5) < upperCorner[dim]
713  || pNode->m_Center[dim] - (pNode->m_HalfWidth * 1.5) > lowerCorner[dim])
714  {
715  bStraddle = true;
716  break;
717  }
718  }
719  }
720 
721  // If the primitive straddles over children nodes, it cannot be moved down to any child node
722  if (bStraddle)
723  {
724  pPrimitive->m_bValid = true;
725  }
726  else
727  {
728  // Move the primitive down to a child node
729  pPrimitive->m_bValid = false;
730  pPrimitive->m_pNode = pNode;
731  }
732  }
733  });
734 }
735 
736 void
738 {
739  if (m_sActiveTreeNodeBlocks.size() == 0)
740  {
741  return;
742  }
743  tbb::parallel_for(m_sActiveTreeNodeBlocks.range(),
744  [&](decltype(m_sActiveTreeNodeBlocks)::const_range_type& r) {
745  for (auto it = r.begin(), iEnd = r.end(); it != iEnd; ++it)
746  {
747  const auto pNodeBlock = *it;
748  for (uint32_t childIdx = 0; childIdx < 8u; ++childIdx)
749  {
750  auto& pNode = pNodeBlock->m_Nodes[childIdx];
751  for (int type = 0; type < OctreePrimitiveType::NumPrimitiveTypes; ++type)
752  {
753  const auto pOldHead = pNode.m_pPrimitiveListHeads[type];
754  if (!pOldHead)
755  {
756  continue;
757  }
758 
759  OctreePrimitive* pIter = pOldHead;
760  OctreePrimitive* pNewHead = nullptr;
761  uint32_t count = 0;
762  while (pIter) {
763  const auto pNext = pIter->m_pNext;
764  if (pIter->m_bValid)
765  {
766  pIter->m_pNext = pNewHead;
767  pNewHead = pIter;
768  ++count;
769  }
770  pIter = pNext;
771  }
772  pNode.m_pPrimitiveListHeads[type] = pNewHead;
773  pNode.m_PrimitiveCounts[type] = count;
774  }
775  }
776  }
777  });
778 }
779 
780 void
782 {
783  const auto& vPrimitivePtrs = m_vPrimitivePtrs[type];
784  if (vPrimitivePtrs.size() == 0)
785  {
786  return;
787  }
788  ParallelUtils::parallelFor(vPrimitivePtrs.size(),
789  [&](const size_t idx) {
790  const auto pPrimitive = vPrimitivePtrs[idx];
791  if (pPrimitive->m_bValid)
792  {
793  return;
794  }
795 
796  const auto pNode = pPrimitive->m_pNode;
797  (type == OctreePrimitiveType::Point) ? pNode->insertPoint(pPrimitive) : pNode->insertNonPointPrimitive(pPrimitive, type);
798  });
799 }
800 
801 void
803 {
804 #if defined(DEBUG) || defined(_DEBUG) || !defined(NDEBUG)
805  LOG_IF(FATAL, (type == OctreePrimitiveType::Point))
806  << "Cannot compute bounding box for point primitive";
807 #endif
808 
809  Vec3d lowerCorner;
810  Vec3d upperCorner;
811 
812  if (type == OctreePrimitiveType::Triangle)
813  {
814  const auto surfMesh = static_cast<SurfaceMesh*>(pPrimitive->m_pGeometry);
815  const auto face = (*surfMesh->getCells())[pPrimitive->m_Idx];
816 
817  lowerCorner = surfMesh->getVertexPosition(face[0]);
818  upperCorner = lowerCorner;
819 
820  const Vec3d verts12[2] = {
821  surfMesh->getVertexPosition(face[1]),
822  surfMesh->getVertexPosition(face[2])
823  };
824 
825  for (uint32_t dim = 0; dim < 3; ++dim)
826  {
827  lowerCorner[dim] = lowerCorner[dim] < verts12[0][dim] ? lowerCorner[dim] : verts12[0][dim];
828  lowerCorner[dim] = lowerCorner[dim] < verts12[1][dim] ? lowerCorner[dim] : verts12[1][dim];
829 
830  upperCorner[dim] = upperCorner[dim] > verts12[0][dim] ? upperCorner[dim] : verts12[0][dim];
831  upperCorner[dim] = upperCorner[dim] > verts12[1][dim] ? upperCorner[dim] : verts12[1][dim];
832  }
833  }
834  else
835  {
836  pPrimitive->m_pGeometry->computeBoundingBox(lowerCorner, upperCorner);
837  }
838 
839  pPrimitive->m_LowerCorner = { lowerCorner[0], lowerCorner[1], lowerCorner[2] };
840  pPrimitive->m_UpperCorner = { upperCorner[0], upperCorner[1], upperCorner[2] };
841 }
842 
845 {
846  m_PoolLock.lock();
847  if (m_NumAvaiableBlocksInPool == 0)
848  {
849  // Allocate 64 more node blocks and put to the pool
851  }
852 
853  const auto pNodeBlock = m_pNodeBlockPoolHead;
854  m_pNodeBlockPoolHead = pNodeBlock->m_NextBlock;
856  m_PoolLock.unlock();
857  m_sActiveTreeNodeBlocks.insert(pNodeBlock);
858  return pNodeBlock;
859 }
860 
861 void
863 {
864  m_PoolLock.lock();
865  pNodeBlock->m_NextBlock = m_pNodeBlockPoolHead;
866  m_pNodeBlockPoolHead = pNodeBlock;
868  m_sActiveTreeNodeBlocks.unsafe_erase(pNodeBlock);
869  m_PoolLock.unlock();
870 }
871 
872 void
873 LooseOctree::allocateMoreNodeBlock(const uint32_t numBlocks)
874 {
875  // This is not a thead-safe function, thus it should be called only from a thread-safe function
876  // And it must be called only from the requestChildrenFromPool() function
877  const auto pBigBlock = new OctreeNodeBlock[numBlocks];
878  m_pNodeBigBlocks.push_back(pBigBlock);
879  for (uint32_t i = 0; i < numBlocks; ++i)
880  {
881  const auto pBlock = &pBigBlock[i];
882  pBlock->m_NextBlock = m_pNodeBlockPoolHead;
883  m_pNodeBlockPoolHead = pBlock;
884  }
885  m_NumAvaiableBlocksInPool += numBlocks;
886  m_NumAllocatedNodes += numBlocks * 8u;
887 }
888 
889 void
891 {
892  LOG_IF(FATAL, (m_NumAllocatedNodes != m_NumAvaiableBlocksInPool * 8u + 1u))
893  << "Internal data corrupted, may be all nodes were not returned from tree";
894 
895  for (const auto pBigBlock: m_pNodeBigBlocks)
896  {
897  delete[] pBigBlock;
898  }
899  m_pNodeBigBlocks.resize(0);
900  m_NumAllocatedNodes = 1u; // root node still remains
902 }
903 } // namespace imstk
void updatePositionAndCheckValidity()
For each point primitive, update its position from its parent geometry and check if it is still loose...
OctreeNodeBlock * m_NextBlock
Pointer to the next block in the memory pool.
void unlock()
End a thread-safe region.
Definition: imstkSpinLock.h:53
void clearPrimitive(const OctreePrimitiveType type)
Completely remove all data of the given primitive type in the tree.
std::array< double, 3 > m_Position
For a point primitive, store its position.
Base class for all geometries represented by discrete points and elements The pointsets follow a pipe...
Definition: imstkPointSet.h:25
void removeInvalidPrimitivesFromNodes()
Remove all invalid primitives from the tree nodes previously contained them.
const uint32_t m_Idx
Index of the primitive in the parent geometry (such as index of the triangle in a mesh) ...
OctreePrimitive * m_pPrimitiveListHeads[OctreePrimitiveType::NumPrimitiveTypes]
Heads of the link lists storing (Classified) primitives.
uint32_t m_NumAvaiableBlocksInPool
Count the number of nodes available in memory pool.
OctreeNode *const m_pRootNode
Root node, should not be reassigned throughout the existence of the tree.
LooseOctree(const Vec3d &center, const double width, const double minWidth, const double minWidthRatio=1.0, const std::string name="LooseOctree")
Octree constructor.
void populatePointPrimitives()
Populate point primitive to tree nodes, from top (root node) down to leaf nodes.
LooseOctree * m_pTree
Pointer to the octree, used to request children from memory pool during splitting node...
tbb::concurrent_unordered_set< OctreeNodeBlock * > m_sActiveTreeNodeBlocks
Set of node blocks that are in use (node blocks that have been taken from memory pool) ...
const double m_Width
Width of the tree bounding box.
OctreeNode * getChildNode(const uint32_t childIdx) const
Get a child node.
bool m_bAlwaysRebuild
If true, the octree is always be rebuit from scratch every time calling to update() ...
virtual void clear()
Clear all primitive and geometry data, but still keep allocated nodes in memory pool to recycle...
void computePrimitiveBoundingBox(OctreePrimitive *const pPrimitive, const OctreePrimitiveType type)
Compute the AABB bounding box of a non-point primitive.
OctreePrimitiveType
The OctreePrimitiveType enum Type of primitive stored in the octree.
void addGeometry(const uint32_t geomIdx)
Add geometry to the internal geometry list to check for duplication.
void update()
Update tree (the tree is rebuilt from scratch if m_bAlwaysRebuild is true, otherwise it is incrementa...
virtual void computeBoundingBox(Vec3d &lowerCorner, Vec3d &upperCorner, const double paddingPercent=0.0)
Compute the bounding box for the geometry.
const uint32_t m_Depth
Depth of this node (depth > 0, depth = 1 starting at the root node)
void updateBoundingBoxAndCheckValidity(const OctreePrimitiveType type)
For each non-point primitive, update its bounding box from its parent geometry and check if it is sti...
Compound Geometry.
std::array< double, 3 > m_LowerCorner
For a non-point primitive, store its AABB&#39;s lower corner.
OctreeNodeBlock * m_pChildren
Pointer to a memory block containing 8 children nodes.
std::array< double, 3 > m_UpperCorner
For a non-point primitive, store its AABB&#39;s upper corner.
ParallelUtils::SpinLock m_PoolLock
Atomic lock for multi-threading modification of the memory pool.
Geometry *const m_pGeometry
Pointer to the parent geometry that the primitive belong to.
void incrementalUpdate()
Incrementally update octree from current state.
ParallelUtils::SpinLock m_NodeSplitingLock
Mutex lock for thread-safe splitting node.
OctreeNode()
Dummy constructor, called only during memory allocation in memory pool.
void reinsertInvalidPrimitives(const OctreePrimitiveType type)
For each invalid primitive, insert it back to the tree in a top-down manner starting from the lowest ...
void rebuild()
Rebuild the tree from scratch.
uint32_t m_MaxDepth
Cache the max depth of the tree (maximum depth level possible)
void insertPoint(OctreePrimitive *const pPrimitive)
Insert a point primitive into the subtree in a top-down manner.
The OctreeNode class.
std::vector< OctreePrimitive * > m_pPrimitiveBlocks[OctreePrimitiveType::NumPrimitiveTypes]
const Vec3d m_Center
Center of the tree.
const Vec3d m_Center
Center of this node.
uint32_t addAnalyticalGeometry(const std::shared_ptr< Geometry > &geometry)
Add an analytical geometry (such as plane/sphere/cube etc) into the tree (it will not be populated to...
OctreeNodeBlock * requestChildrenFromPool()
Request a block of 8 tree nodes from memory pool (this is called only during splitting node) If the m...
void removeEmptyDescendants()
Recursively remove all descendant nodes that do not contain primitives (all 8 children of a node are ...
Base class for any geometrical representation.
Definition: imstkGeometry.h:22
uint32_t getMaxNumPrimitivesInNodes() const
Count the maximum number of primitives stored in a tree node.
ParallelUtils::SpinLock m_PrimitiveLock[OctreePrimitiveType::NumPrimitiveTypes]
Mutex lock for thread-safe primitive list modification.
double m_MinWidth
Minimum width allowed for the tree nodes.
const Vec3d & getVertexPosition(const size_t vertNum, DataType type=DataType::PostTransform) const
Returns the position of a vertex given its index.
uint32_t m_MaxDepth
Max depth of the tree, which is computed based on m_MinWidth.
void lock()
Start a thread-safe region, where only one thread can execute at a time until a call to the unlock fu...
Definition: imstkSpinLock.h:45
const double m_HalfWidth
Half width of the node AABB.
The OctreePrimitive struct For each octree primitive (point/triangle/analytical geometry), store its relevant data.
const double m_MinWidthRatio
If there is no point primitive, minWidth will be recomputed as minWidth = min(width of all non-point ...
std::vector< OctreePrimitive * > m_vPrimitivePtrs[OctreePrimitiveType::NumPrimitiveTypes]
Store pointers of primitives created from geometry elements, such as points, triangles, analytical geometries.
void removeGeometry(const uint32_t geomIdx)
Remove geometry from the internal geometry list (does nothing if the geometry does not exist...
Represents a set of triangles & vertices via an array of Vec3d double vertices & Vec3i integer indice...
uint32_t m_NumAllocatedNodes
Count the total number of allocated nodes so far.
const std::string m_Name
Name of the tree.
uint32_t addPointSet(const std::shared_ptr< PointSet > &pointset)
Add a PointSet geometry into the tree (the points will not be populated to tree nodes until calling t...
std::vector< OctreeNodeBlock * > m_pNodeBigBlocks
Class LooseOctree, where each tree node has a loose boundary which is exactly twice big as its exact...
OctreeNodeBlock * m_pNodeBlockPoolHead
The pool of tree nodes, storing pre-allocated nodes as a linked list.
bool looselyContains(const Vec3d &point)
Check if the given point is contained in the node loose boundary (which is 2X bigger than the boundin...
void clearPrimitiveData(const OctreePrimitiveType type)
Recursively clear primitive data (linked lists and counters) Note that the primitives are still exist...
bool m_bCompleteBuild
This is set to true after tree has been built, otherwise false.
void returnChildrenToPool(OctreeNodeBlock *const pNodeBlock)
Return 8 children nodes to memory pool (this is called only during destroying descendant nodes) ...
void deallocateMemoryPool()
Deallocate all node block in memory pool, called only during octree destructor.
bool isLeaf() const
Check if this node is a leaf node.
void allocateMoreNodeBlock(const uint32_t numBlocks)
Pre-allocate a given number of node blocks (each block contains 8 nodes) and add them to the memory p...
The OctreeNodeBlock struct This is a data structure to store a memory block of 8 tree node at a time ...
void removeAllDescendants()
Recursively remove all descendant nodes (return them back to memory pool) As a result, after calling to this function, the current node will become a leaf node.
void populateNonPointPrimitives(const OctreePrimitiveType type)
Populate non-point primitive (triangle/analytical geometry) to tree nodes, from top (root node) down ...
bool m_bIsLeaf
True if this node does not have any child node (a node should have either 0 or 8 children) ...
OctreeNode * m_pNode
Pointer to the octree node containing the primitive.
bool hasGeometry(uint32_t geomIdx) const
Check if a geometry with the given geometry index has been added to the octree before.
std::unordered_set< uint32_t > m_sGeometryIndices
List of all indices of the added geometries, to check for duplication such that one geometry cannot b...
void build()
Build octree from the provided geometries.
uint32_t m_PrimitiveCounts[OctreePrimitiveType::NumPrimitiveTypes]
Count the number of (classified) primitives stored in this node.
uint32_t addTriangleMesh(const std::shared_ptr< SurfaceMesh > &surfMesh)
Add a triangle mesh into the tree (the triangles of the mesh will not be populated to tree nodes unti...
void split()
Split node (requesting 8 children nodes from memory pool)
OctreePrimitive * m_pNext
Pointer to the next node in the primitive list of the octree node.
void insertNonPointPrimitive(OctreePrimitive *const pPrimitive, const OctreePrimitiveType type)
Insert a non-point primitive into the subtree in a top-down manner.
void keepPrimitive(OctreePrimitive *const pPrimitive, const OctreePrimitiveType type)
Keep the primitive at this node as cannot pass it down further to any child node. ...