1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5 
6 /**
7  * \file InOutOctree.hpp
8  *
9  * \brief Defines an InOutOctree for containment queries on a surface.
10  */
11 
12 #ifndef AXOM_QUEST_INOUT_OCTREE__HPP_
13 #define AXOM_QUEST_INOUT_OCTREE__HPP_
14 
15 #include "axom/core.hpp"
16 #include "axom/slic.hpp"
17 #include "axom/slam.hpp"
18 #include "axom/primal.hpp"
19 #include "axom/mint.hpp"
20 #include "axom/spin.hpp"
21 
22 #include "detail/inout/BlockData.hpp"
23 #include "detail/inout/MeshWrapper.hpp"
24 #include "detail/inout/InOutOctreeValidator.hpp"
25 #include "detail/inout/InOutOctreeStats.hpp"
26 
27 #include "axom/fmt.hpp"
28 
29 #include <vector>
30 #include <iterator>
31 #include <limits>
32 #include <sstream>
33 #include <unordered_map>
34 
35 #ifndef DUMP_VTK_MESH
36 //  #define DUMP_VTK_MESH
37 #endif
38 
39 #ifndef DUMP_OCTREE_INFO
40 //  #define DUMP_OCTREE_INFO 1
41 #endif
42 
43 #ifndef DEBUG_OCTREE_ACTIVE
44 //  #define DEBUG_OCTREE_ACTIVE
45 #endif
46 
47 #if defined(DEBUG_OCTREE_ACTIVE) && defined(AXOM_DEBUG)
48   #define QUEST_OCTREE_DEBUG_LOG_IF(_cond, _msg) \
49     if(_cond) SLIC_DEBUG(_msg)
50 #else
51   #define QUEST_OCTREE_DEBUG_LOG_IF(_cond, _msg) ((void)0)
52 #endif
53 
54 // The following four variables are used in the QUEST_OCTREE_DEBUG_LOG_IF macro
55 // and are only active when DEBUG_OCTREE_ACTIVE is defined
56 #define DEBUG_VERT_IDX -2  // 1160
57 #define DEBUG_TRI_IDX -2   // 187820
58 
59 #define DEBUG_BLOCK_1 BlockIndex::invalid_index()
60 //                     BlockIndex( {1346,1972,1691}, 12)
61 #define DEBUG_BLOCK_2 BlockIndex::invalid_index()
62 //                     BlockIndex( {336,493,423}, 10)
63 
64 namespace axom
65 {
66 namespace quest
67 {
68 namespace detail
69 {
70 template <int DIM>
71 class InOutOctreeMeshDumper;
72 
73 template <int DIM, typename Derived>
74 class InOutOctreeMeshDumperBase;
75 
76 }  // namespace detail
77 
78 /**
79  * \class
80  * \brief Handles generation of a point containment spatial index over a surface mesh
81  *
82  * The point containment queries determine whether a given arbitrary point in
83  * space lies inside or outside of the surface.  This class depends on a
84  * watertight surface mesh.  In order to repair common mesh defects, this
85  * class modifies the Mesh passed to it.  Please discard all other copies
86  * of the Mesh pointer.
87  */
88 template <int DIM>
89 class InOutOctree : public spin::SpatialOctree<DIM, InOutBlockData>
90 {
91 private:
92   friend class detail::InOutOctreeStats<DIM>;
93   friend class detail::InOutOctreeValidator<DIM>;
94   friend class detail::InOutOctreeMeshDumper<DIM>;
95   friend class detail::InOutOctreeMeshDumperBase<DIM, detail::InOutOctreeMeshDumper<DIM>>;
96 
97 public:
98   using OctreeBaseType = spin::OctreeBase<DIM, InOutBlockData>;
99   using SpatialOctreeType = spin::SpatialOctree<DIM, InOutBlockData>;
100 
101   using GeometricBoundingBox = typename SpatialOctreeType::GeometricBoundingBox;
102   using SpacePt = typename SpatialOctreeType::SpacePt;
103   using SpaceVector = typename SpatialOctreeType::SpaceVector;
104   using BlockIndex = typename SpatialOctreeType::BlockIndex;
105   using GridPt = typename OctreeBaseType::GridPt;
106   using SpaceRay = primal::Ray<double, DIM>;
107 
108 private:
109   enum GenerationState
110   {
111     INOUTOCTREE_UNINITIALIZED,
112     INOUTOCTREE_VERTICES_INSERTED,
113     INOUTOCTREE_MESH_REORDERED,
114     INOUTOCTREE_ELEMENTS_INSERTED,
115     INOUTOCTREE_LEAVES_COLORED
116   };
117 
118   static double DEFAULT_VERTEX_WELD_THRESHOLD;
119   static double DEFAULT_BOUNDING_BOX_SCALE_FACTOR;
120 
121 public:
122   using SurfaceMesh = typename MeshWrapper<DIM>::SurfaceMesh;
123 
124   using VertexIndex = axom::IndexType;
125   using CellIndex = axom::IndexType;
126   using IndexRegistry = slam::FieldRegistry<slam::Set<VertexIndex>, VertexIndex>;
127 
128   using SpaceCell = typename MeshWrapper<DIM>::SpaceCell;
129 
130   using MeshVertexSet = typename MeshWrapper<DIM>::MeshVertexSet;
131   using MeshElementSet = typename MeshWrapper<DIM>::MeshElementSet;
132   using CellVertIndices = typename MeshWrapper<DIM>::CellVertIndices;
133   using VertexIndexMap = typename MeshWrapper<DIM>::VertexIndexMap;
134 
135   // Type aliases for the Relations from Gray leaf blocks to mesh entities
136   static const int MAX_VERTS_PER_BLOCK = 1;
137   using VertexBlockMap = slam::Map<slam::Set<>, BlockIndex>;
138   using STLIndirection =
139     slam::policies::STLVectorIndirection<VertexIndex, VertexIndex>;
140 
141   using GrayLeafSet = slam::PositionSet<>;
142   using BVStride =
143     slam::policies::CompileTimeStride<VertexIndex, MAX_VERTS_PER_BLOCK>;
144   using ConstantCardinality =
145     slam::policies::ConstantCardinality<VertexIndex, BVStride>;
146   using GrayLeafVertexRelation = slam::StaticRelation<VertexIndex,
147                                                       VertexIndex,
148                                                       ConstantCardinality,
149                                                       STLIndirection,
150                                                       GrayLeafSet,
151                                                       MeshVertexSet>;
152 
153   using VariableCardinality =
154     slam::policies::VariableCardinality<VertexIndex, STLIndirection>;
155   using GrayLeafElementRelation = slam::StaticRelation<VertexIndex,
156                                                        VertexIndex,
157                                                        VariableCardinality,
158                                                        STLIndirection,
159                                                        GrayLeafSet,
160                                                        MeshElementSet>;
161   using CellIndexSet = typename GrayLeafElementRelation::RelationSubset;
162 
163   using GrayLeafsLevelMap = slam::Map<slam::Set<>, GrayLeafSet>;
164   using GrayLeafVertexRelationLevelMap =
165     slam::Map<slam::Set<>, GrayLeafVertexRelation>;
166   using GrayLeafElementRelationLevelMap =
167     slam::Map<slam::Set<>, GrayLeafElementRelation>;
168 
169 public:
170   /**
171    * \brief Construct an InOutOctree to handle containment queries on a surface mesh
172    *
173    * \param [in] bb The spatial extent covered by the octree
174    * \note We slightly scale the bounding box so all mesh elements are
175    * guaranteed to be enclosed by the octree and to remedy problems we've
176    * encountered related to meshes that are aligned with the octree grid
177    * \note The InOutOctree modifies its mesh in an effort to repair common
178    * problems. Please make sure to discard all old copies of the meshPtr.
179    */
InOutOctree(const GeometricBoundingBox & bb,SurfaceMesh * & meshPtr)180   InOutOctree(const GeometricBoundingBox& bb, SurfaceMesh*& meshPtr)
181     : SpatialOctreeType(
182         GeometricBoundingBox(bb).scale(DEFAULT_BOUNDING_BOX_SCALE_FACTOR))
183     , m_meshWrapper(meshPtr)
184     , m_vertexToBlockMap(&m_meshWrapper.vertexSet())
185     //
186     , m_grayLeafsMap(&this->m_levels)
187     , m_grayLeafToVertexRelationLevelMap(&this->m_levels)
188     , m_grayLeafToElementRelationLevelMap(&this->m_levels)
189     //
190     , m_generationState(INOUTOCTREE_UNINITIALIZED)
191   {
192     setVertexWeldThreshold(DEFAULT_VERTEX_WELD_THRESHOLD);
193   }
194 
195   /**
196    * \brief Generate the spatial index over the surface mesh
197    */
198   void generateIndex();
199 
200   /**
201    * \brief The point containment query.
202    *
203    * \param pt The point at which we are checking for containment
204    * \return True if the point is within (or on) the surface, false otherwise
205    * \note Points outside the octree bounding box are considered outside
206    */
207   bool within(const SpacePt& pt) const;
208 
209   /**
210    * \brief Sets the threshold for welding vertices during octree construction
211    *
212    * \param [in] thresh The cutoff distance at which we consider two vertices
213    * to be identical during the octree construction
214    *
215    * \pre thresh >= 0
216    * \pre This function cannot be called after the octree has been constructed
217    *
218    * \note The InOutOctree requires the input surface to be watertight so this
219    * parameter should be set with care. A welding threshold that is too
220    * high could unnecessarily merge vertices and create topological defects,
221    * while a value that is too low risks leaving gaps in meshes with tolerances
222    * between vertices. The default value tends to work well in practice.
223    *
224    * \note The code actually uses the square of the threshold for comparisons
225    */
setVertexWeldThreshold(double thresh)226   void setVertexWeldThreshold(double thresh)
227   {
228     SLIC_WARNING_IF(thresh < 0.,
229                     "Distance threshold for vertices cannot be negative.");
230 
231     SLIC_WARNING_IF(m_generationState > INOUTOCTREE_UNINITIALIZED,
232                     "Can only set the vertex welding threshold "
233                       << "before initializing the InOutOctree");
234 
235     m_vertexWeldThresholdSquared = thresh * thresh;
236   }
237 
238 private:
239   /**
240    * \brief Helper function to insert a vertex into the octree
241    *
242    * \param idx The index of the vertex that we are inserting
243    * \param startingLevel (optional, default = 0) The octree level at which
244    * to begin the search for the leaf node covering this vertex
245    */
246   void insertVertex(VertexIndex idx, int startingLevel = 0);
247 
248   /**
249    * \brief Insert all mesh cells into the octree, generating a PM octree
250    */
251   void insertMeshCells();
252 
253   /**
254    * \brief Set a color for each leaf block of the octree.
255    *
256    * Black blocks are entirely within the surface, white blocks are entirely
257    * outside the surface and Gray blocks intersect the surface.
258    */
259   void colorOctreeLeaves();
260 
261   /**
262    * Use octree index over mesh vertices to convert the input 'soup of cells'
263    * into an indexed mesh representation.
264    * In particular, all vertices in the mesh that are nearly coincident will be
265    * merged and degenerate cells (where the three vertices do not have unique
266    * indices) will be removed.
267    */
268   void updateSurfaceMeshVertices();
269 
270 private:
271   /**
272    * \brief Checks if all indexed cells in the block share a common vertex
273    *
274    * \param leafBlock [in] The current octree block
275    * \param leafData [inout] The data associated with this block
276    * \note A side effect of this function is that we set the leafData's vertex
277    * to the common vertex if one is found
278    * \return True, if all cells indexed by this leaf share a common vertex, false otherwise.
279    */
280   bool allCellsIncidentInCommonVertex(const BlockIndex& leafBlock,
281                                       DynamicGrayBlockData& leafData) const;
282 
283   /**
284    * \brief Finds a color for the given block \a blk and propagates to neighbors
285    *
286    * \note Propagates color to same-level and coarser level neighbors
287    * \param blk The block to color
288    * \param blkData The data associated with this block
289    * \return True if we were able to find a color for \a blk, false otherwise
290    */
291   bool colorLeafAndNeighbors(const BlockIndex& blk, InOutBlockData& blkData);
292 
293   /**
294    * \brief Predicate to determine if the vertex is indexed by the blk
295    *
296    * \pre This function assumes the vertices have been inserted and the mesh has been reordered
297    * \param vIdx The index of the vertex to check
298    * \param blk The block that we are checking against
299    * \return true if \a vIdx is indexed by \a blk, false otherwise
300    */
blockIndexesVertex(VertexIndex vIdx,const BlockIndex & blk) const301   bool blockIndexesVertex(VertexIndex vIdx, const BlockIndex& blk) const
302   {
303     SLIC_ASSERT(m_generationState >= INOUTOCTREE_MESH_REORDERED);
304 
305     // Needs to account for non-leaf ancestors of the block
306     return vIdx >= 0 && m_vertexToBlockMap[vIdx].isDescendantOf(blk);
307   }
308 
309   /**
310    * \brief Predicate to determine if any of the elements vertices are indexed by the given BlockIndex
311    *
312    * \pre This function assumes the vertices have been inserted and the mesh has been reordered
313    * \param tIdx The index of the cell to check
314    * \param blk The block that we are checking against
315    * \return true if one of the cell's vertices are indexed by \a blk, false otherwise
316    */
blockIndexesElementVertex(CellIndex tIdx,const BlockIndex & blk) const317   bool blockIndexesElementVertex(CellIndex tIdx, const BlockIndex& blk) const
318   {
319     SLIC_ASSERT(m_generationState >= INOUTOCTREE_MESH_REORDERED);
320 
321     CellVertIndices tVerts = m_meshWrapper.cellVertexIndices(tIdx);
322     for(int i = 0; i < tVerts.size(); ++i)
323     {
324       // Using the vertex-to-block cache to avoid numerical degeneracies
325       if(blockIndexesVertex(tVerts[i], blk)) return true;
326     }
327     return false;
328   }
329 
330   /**
331    * \brief Determines whether the specified 3D point is within the gray leaf
332    *
333    * \param queryPt The point we are querying
334    * \param leafBlk The block of the gray leaf
335    * \param data The data associated with the leaf block
336    * \return True, if the point is inside the local surface associated with this
337    * block, false otherwise
338    */
339   template <int TDIM>
340   typename std::enable_if<TDIM == 3, bool>::type withinGrayBlock(
341     const SpacePt& queryPt,
342     const BlockIndex& leafBlk,
343     const InOutBlockData& data) const;
344 
345   /**
346    * \brief Determines whether the specified 2D point is within the gray leaf
347    *
348    * \param queryPt The point we are querying
349    * \param leafBlk The block of the gray leaf
350    * \param data The data associated with the leaf block
351    * \return True, if the point is inside the local surface associated with this
352    * block, false otherwise
353    */
354   template <int TDIM = DIM>
355   typename std::enable_if<TDIM == 2, bool>::type withinGrayBlock(
356     const SpacePt& queryPt,
357     const BlockIndex& leafBlk,
358     const InOutBlockData& data) const;
359 
360   /**
361    * \brief Returns the index of the mesh vertex associated with the given leaf block
362    *
363    * \pre leafBlk is a leaf block of the octree
364    * \param leafBlk The BlockIndex of a leaf block in the octree
365    * \param leafData The data associated with this leaf block
366    * \return The index of the mesh vertex associated with this leaf block
367    */
368   VertexIndex leafVertex(const BlockIndex& leafBlk,
369                          const InOutBlockData& leafData) const;
370 
371   /**
372    * \brief Returns the set of mesh cell indices associated with the given leaf block
373    *
374    * \pre leafBlk is a leaf block of the octree
375    * \param leafBlk The BlockIndex of a leaf block in the octree
376    * \param leafData The data associated with this leaf block
377    * \return The set of mesh cell indices associated with this leaf block
378    */
379   CellIndexSet leafCells(const BlockIndex& leafBlk,
380                          const InOutBlockData& leafData) const;
381 
382 private:
383   DISABLE_COPY_AND_ASSIGNMENT(InOutOctree);
384   DISABLE_MOVE_AND_ASSIGNMENT(InOutOctree);
385 
386   /// \brief Checks internal consistency of the octree representation
387   void checkValid() const;
388 
389   /// \brief Helper function to verify that all leaves at the given level have a color
390   void checkAllLeavesColoredAtLevel(int AXOM_DEBUG_PARAM(level)) const;
391 
392   void dumpOctreeMeshVTK(const std::string& name) const;
393   void dumpSurfaceMeshVTK(const std::string& name) const;
394 
395   /**
396    * \brief Utility function to dump any Inside blocks whose neighbors are
397    * outside (and vice-versa)
398    *
399    * \note There should not be any such blocks in a valid InOutOctree
400    */
401   void dumpDifferentColoredNeighborsMeshVTK(const std::string& name) const;
402 
403   /// \brief Utility function to print some statistics about the InOutOctree instance
404   void printOctreeStats() const;
405 
406 protected:
407   MeshWrapper<DIM> m_meshWrapper;
408 
409   VertexBlockMap m_vertexToBlockMap;
410 
411   GrayLeafsLevelMap m_grayLeafsMap;
412   GrayLeafVertexRelationLevelMap m_grayLeafToVertexRelationLevelMap;
413   GrayLeafElementRelationLevelMap m_grayLeafToElementRelationLevelMap;
414 
415   GenerationState m_generationState;
416 
417   IndexRegistry m_indexRegistry;
418 
419   double m_vertexWeldThresholdSquared;
420 
421   /// Bounding box scaling factor for dealing with grazing triangles
422   double m_boundingBoxScaleFactor {DEFAULT_BOUNDING_BOX_SCALE_FACTOR};
423 };
424 
425 template <int DIM>
426 double InOutOctree<DIM>::DEFAULT_VERTEX_WELD_THRESHOLD = 1E-9;
427 
428 template <int DIM>
429 double InOutOctree<DIM>::DEFAULT_BOUNDING_BOX_SCALE_FACTOR = 1.000123;
430 
431 namespace
432 {
433 #ifdef AXOM_DEBUG
434 /// \brief Utility function to print the vertex indices of a cell
operator <<(std::ostream & os,const InOutOctree<3>::CellVertIndices & tvInd)435 inline std::ostream& operator<<(std::ostream& os,
436                                 const InOutOctree<3>::CellVertIndices& tvInd)
437 {
438   os << fmt::format("[{}]", fmt::join(tvInd, ","));
439   return os;
440 }
441 
operator <<(std::ostream & os,const InOutOctree<3>::CellIndexSet & tSet)442 inline std::ostream& operator<<(std::ostream& os,
443                                 const InOutOctree<3>::CellIndexSet& tSet)
444 {
445   os << fmt::format("[{}]", fmt::join(tSet, ","));
446   return os;
447 }
448 
449 #endif
450 }  // namespace
451 
452 template <int DIM>
generateIndex()453 void InOutOctree<DIM>::generateIndex()
454 {
455   using Timer = axom::utilities::Timer;
456 
457   // Loop through mesh vertices
458   SLIC_INFO(
459     fmt::format("  Generating InOutOctree over surface mesh with {} vertices "
460                 "and {} elements.",
461                 m_meshWrapper.numMeshVertices(),
462                 m_meshWrapper.numMeshCells()));
463 
464   Timer timer;
465 
466   // STEP 1 -- Add mesh vertices to octree
467   timer.start();
468   int numMeshVerts = m_meshWrapper.numMeshVertices();
469   for(int idx = 0; idx < numMeshVerts; ++idx)
470   {
471     insertVertex(idx);
472   }
473   timer.stop();
474   m_generationState = INOUTOCTREE_VERTICES_INSERTED;
475   SLIC_INFO(
476     fmt::format("\t--Inserting vertices took {} seconds.", timer.elapsed()));
477 
478   // STEP 1(b) -- Update the mesh vertices and cells with after vertex welding from octree
479   timer.start();
480   updateSurfaceMeshVertices();
481   timer.stop();
482   m_generationState = INOUTOCTREE_MESH_REORDERED;
483 
484   SLIC_INFO("\t--Updating mesh took " << timer.elapsed() << " seconds.");
485   SLIC_INFO(
486     fmt::format("  After inserting vertices, reindexed mesh has {} vertices "
487                 "and {} cells.",
488                 m_meshWrapper.numMeshVertices(),
489                 m_meshWrapper.numMeshCells()));
490 
491 #ifdef DUMP_OCTREE_INFO
492   // -- Print some stats about the octree
493   SLIC_INFO("** Octree stats after inserting vertices");
494   dumpSurfaceMeshVTK("surfaceMesh");
495   dumpOctreeMeshVTK("prOctree");
496   printOctreeStats();
497 #endif
498   checkValid();
499 
500   // STEP 2 -- Add mesh cells (segments in 2D; triangles in 3D) to octree
501   timer.start();
502   insertMeshCells();
503   timer.stop();
504   m_generationState = INOUTOCTREE_ELEMENTS_INSERTED;
505   SLIC_INFO("\t--Inserting cells took " << timer.elapsed() << " seconds.");
506 
507   // STEP 3 -- Color the blocks of the octree
508   // -- Black (in), White(out), Gray(Intersects surface)
509   timer.start();
510   colorOctreeLeaves();
511 
512   timer.stop();
513   m_generationState = INOUTOCTREE_LEAVES_COLORED;
514   SLIC_INFO("\t--Coloring octree leaves took " << timer.elapsed() << " seconds.");
515 
516 // -- Print some stats about the octree
517 #ifdef DUMP_OCTREE_INFO
518   SLIC_INFO("** Octree stats after inserting cells");
519   dumpOctreeMeshVTK("pmOctree");
520   dumpDifferentColoredNeighborsMeshVTK("differentNeighbors");
521   printOctreeStats();
522 #endif
523   checkValid();
524 
525   // CLEANUP -- Finally, fix up the surface mesh after octree operations
526   timer.start();
527   m_meshWrapper.regenerateSurfaceMesh();
528   timer.stop();
529   SLIC_INFO("\t--Regenerating the mesh took " << timer.elapsed() << " seconds.");
530 
531   SLIC_INFO("  Finished generating the InOutOctree.");
532 }
533 
534 template <int DIM>
insertVertex(VertexIndex idx,int startingLevel)535 void InOutOctree<DIM>::insertVertex(VertexIndex idx, int startingLevel)
536 {
537   const SpacePt pt = m_meshWrapper.getMeshVertexPosition(idx);
538 
539   BlockIndex block = this->findLeafBlock(pt, startingLevel);
540   InOutBlockData& blkData = (*this)[block];
541 
542   QUEST_OCTREE_DEBUG_LOG_IF(
543     idx == DEBUG_VERT_IDX,
544     fmt::format("\t -- inserting pt {} with index {}. "
545                 "Looking at block {} w/ blockBB {} indexing leaf vertex {}",
546                 pt,
547                 idx,
548                 block,
549                 this->blockBoundingBox(block),
550                 blkData.dataIndex()));
551 
552   if(!blkData.hasData())
553   {
554     blkData.setData(idx);
555 
556     // Update the vertex-to-block map for this vertex
557     if(m_generationState >= INOUTOCTREE_MESH_REORDERED)
558       m_vertexToBlockMap[idx] = block;
559   }
560   else
561   {
562     // check if we should merge the vertices
563     VertexIndex origVertInd = blkData.dataIndex();
564     if(squared_distance(pt, m_meshWrapper.getMeshVertexPosition(origVertInd)) >=
565        m_vertexWeldThresholdSquared)
566     {
567       blkData.clear();
568       this->refineLeaf(block);
569 
570       insertVertex(origVertInd, block.childLevel());
571       insertVertex(idx, block.childLevel());
572     }
573   }
574 
575   QUEST_OCTREE_DEBUG_LOG_IF(
576     blkData.dataIndex() == DEBUG_VERT_IDX,
577     fmt::format("-- vertex {} is indexed in block {}. Leaf vertex is {}",
578                 idx,
579                 block,
580                 blkData.dataIndex()));
581 }
582 
583 template <int DIM>
insertMeshCells()584 void InOutOctree<DIM>::insertMeshCells()
585 {
586   using Timer = axom::utilities::Timer;
587   using LeavesLevelMap = typename OctreeBaseType::OctreeLevelType;
588 
589   SLIC_ASSERT(m_meshWrapper.meshWasReindexed());
590 
591   // Temporary arrays of DyamicGrayBlockData for current and next level
592   using DynamicLevelData = std::vector<DynamicGrayBlockData>;
593   const int NUM_INIT_DATA_ENTRIES = 1 << 10;
594   DynamicLevelData currentLevelData;
595   DynamicLevelData nextLevelData;
596   currentLevelData.reserve(NUM_INIT_DATA_ENTRIES);
597   nextLevelData.reserve(NUM_INIT_DATA_ENTRIES);
598 
599   /// --- Initialize root level data
600   BlockIndex rootBlock = this->root();
601   InOutBlockData& rootData = (*this)[rootBlock];
602 
603   currentLevelData.push_back(DynamicGrayBlockData());
604   DynamicGrayBlockData& dynamicRootData = currentLevelData[0];
605   if(rootData.hasData()) dynamicRootData.setVertex(rootData.dataIndex());
606   dynamicRootData.setLeafFlag(rootData.isLeaf());
607   rootData.setData(0);
608 
609   // Add all cell references to the root
610   {
611     int const numCells = m_meshWrapper.numMeshCells();
612     dynamicRootData.cells().reserve(numCells);
613     for(int idx = 0; idx < numCells; ++idx)
614     {
615       dynamicRootData.addCell(idx);
616     }
617   }
618 
619   // Iterate through octree levels
620   // and insert cells into the blocks that they intersect
621   for(int lev = 0; lev < this->m_levels.size(); ++lev)
622   {
623     Timer levelTimer(true);
624 
625     auto& gvRelData = m_indexRegistry.addNamelessBuffer();
626     auto& geIndRelData = m_indexRegistry.addNamelessBuffer();
627     auto& geSizeRelData = m_indexRegistry.addNamelessBuffer();
628     geSizeRelData.push_back(0);
629 
630     int nextLevelDataBlockCounter = 0;
631 
632     auto& levelLeafMap = this->getOctreeLevel(lev);
633     auto itEnd = levelLeafMap.end();
634     for(auto it = levelLeafMap.begin(); it != itEnd; ++it)
635     {
636       InOutBlockData& blkData = *it;
637 
638       if(!blkData.hasData()) continue;
639 
640       BlockIndex blk(it.pt(), lev);
641       DynamicGrayBlockData& dynamicLeafData =
642         currentLevelData[blkData.dataIndex()];
643 
644       bool isInternal = !dynamicLeafData.isLeaf();
645       bool isLeafThatMustRefine =
646         !isInternal && !allCellsIncidentInCommonVertex(blk, dynamicLeafData);
647 
648       QUEST_OCTREE_DEBUG_LOG_IF(
649         DEBUG_BLOCK_1 == blk || DEBUG_BLOCK_2 == blk,
650         fmt::format("Attempting to insert cells from block {}."
651                     "\n\tDynamic data: {}"
652                     "\n\tBlock data: {}"
653                     "\n\tAbout to finalize? {}",
654                     blk,
655                     dynamicLeafData,
656                     blkData,
657                     (!isInternal && !isLeafThatMustRefine ? " yes" : "no")));
658 
659       // Leaf blocks that don't refine are 'finalized'
660       // -- add  them to the current level's relations
661       if(!isInternal && !isLeafThatMustRefine)
662       {
663         if(dynamicLeafData.hasCells())
664         {
665           // Set the leaf data in the octree
666           blkData.setData(static_cast<int>(gvRelData.size()));
667 
668           // Add the vertex index to the gray blocks vertex relation
669           gvRelData.push_back(dynamicLeafData.vertexIndex());
670 
671           // Add the cells to the gray block's element relations
672           std::copy(dynamicLeafData.cells().begin(),
673                     dynamicLeafData.cells().end(),
674                     std::back_inserter(geIndRelData));
675           geSizeRelData.push_back(static_cast<int>(geIndRelData.size()));
676 
677           QUEST_OCTREE_DEBUG_LOG_IF(
678             DEBUG_BLOCK_1 == blk || DEBUG_BLOCK_2 == blk,
679             fmt::format("[Added block {} into tree as a gray leaf]."
680                         "\n\tDynamic data: {}"
681                         "\n\tBlock data: {}",
682                         blk,
683                         dynamicLeafData,
684                         blkData));
685         }
686       }
687       else
688       {
689         /// Otherwise, we must distribute the block data among the children
690 
691         // Refine the leaf if necessary
692         if(isLeafThatMustRefine)
693         {
694           const VertexIndex vIdx = dynamicLeafData.vertexIndex();
695 
696           this->refineLeaf(blk);
697           dynamicLeafData.setLeafFlag(false);
698 
699           // Reinsert the vertex into the tree, if vIdx was indexed by blk
700           if(blockIndexesVertex(vIdx, blk))
701             insertVertex(vIdx, blk.childLevel());
702         }
703         else if(isInternal)
704         {
705           // Need to mark the leaf as internal since we were using its data
706           // as an index into the DynamicGrayBlockData array
707           blkData.setInternal();
708         }
709 
710         SLIC_ASSERT_MSG(
711           this->isInternal(blk),
712           fmt::format(
713             "Block {} was refined, so it should be marked as internal.",
714             blk));
715 
716         /// Setup caches for data associated with children
717         BlockIndex childBlk[BlockIndex::NUM_CHILDREN];
718         GeometricBoundingBox childBB[BlockIndex::NUM_CHILDREN];
719         DynamicGrayBlockData childData[BlockIndex::NUM_CHILDREN];
720         DynamicGrayBlockData* childDataPtr[BlockIndex::NUM_CHILDREN];
721 
722         const typename LeavesLevelMap::BroodData& broodData =
723           this->getOctreeLevel(lev + 1).getBroodData(blk.pt());
724 
725         for(int j = 0; j < BlockIndex::NUM_CHILDREN; ++j)
726         {
727           childBlk[j] = blk.child(j);
728           childBB[j] = this->blockBoundingBox(childBlk[j]);
729 
730           // expand bounding box slightly to deal with grazing cells
731           childBB[j].scale(m_boundingBoxScaleFactor);
732 
733           const InOutBlockData& childBlockData = broodData[j];
734           if(!childBlockData.hasData())
735           {
736             childData[j] = DynamicGrayBlockData();
737             childData[j].setLeafFlag(childBlockData.isLeaf());
738           }
739           else
740           {
741             childData[j] = DynamicGrayBlockData(childBlockData.dataIndex(),
742                                                 childBlockData.isLeaf());
743           }
744 
745           childDataPtr[j] = &childData[j];
746         }
747 
748         // Check that the vector has enough capacity for all children
749         // This ensures that our child data pointers will not be invalidated
750         if(nextLevelData.capacity() <
751            (nextLevelData.size() + BlockIndex::NUM_CHILDREN))
752           nextLevelData.reserve(nextLevelData.size() * 4);
753 
754         // Add all cells to intersecting children blocks
755         DynamicGrayBlockData::CellList& parentCells = dynamicLeafData.cells();
756         int numCells = static_cast<int>(parentCells.size());
757         for(int i = 0; i < numCells; ++i)
758         {
759           CellIndex tIdx = parentCells[i];
760           SpaceCell spaceTri = m_meshWrapper.cellPositions(tIdx);
761           GeometricBoundingBox tBB = m_meshWrapper.cellBoundingBox(tIdx);
762 
763           for(int j = 0; j < BlockIndex::numChildren(); ++j)
764           {
765             bool shouldAddCell = blockIndexesElementVertex(tIdx, childBlk[j]) ||
766               (childDataPtr[j]->isLeaf() ? intersect(spaceTri, childBB[j])
767                                          : intersect(tBB, childBB[j]));
768 
769             QUEST_OCTREE_DEBUG_LOG_IF(
770               DEBUG_BLOCK_1 == childBlk[j] || DEBUG_BLOCK_2 == childBlk[j],
771               //&& tIdx == DEBUG_TRI_IDX
772               fmt::format("Attempting to insert cell {} @ {} w/ BB {}"
773                           "\n\t into block {} w/ BB {} and data {} "
774                           "\n\tShould add? {}",
775                           tIdx,
776                           spaceTri,
777                           tBB,
778                           childBlk[j],
779                           childBB[j],
780                           *childDataPtr[j],
781                           (shouldAddCell ? " yes" : "no")));
782 
783             if(shouldAddCell)
784             {
785               // Place the DynamicGrayBlockData in the array before adding its data
786               if(!childDataPtr[j]->hasCells())
787               {
788                 // Copy the DynamicGrayBlockData into the array
789                 nextLevelData.push_back(childData[j]);
790 
791                 // Update the child data pointer
792                 childDataPtr[j] = &nextLevelData[nextLevelDataBlockCounter];
793 
794                 // Set the data in the octree to this index and update the index
795                 (*this)[childBlk[j]].setData(nextLevelDataBlockCounter++);
796               }
797 
798               childDataPtr[j]->addCell(tIdx);
799 
800               QUEST_OCTREE_DEBUG_LOG_IF(
801                 DEBUG_BLOCK_1 == childBlk[j] || DEBUG_BLOCK_2 == childBlk[j],
802                 //&& tIdx == DEBUG_TRI_IDX
803                 fmt::format(
804                   "Added cell {} @ {} with verts [{}]"
805                   "\n\tinto block {} with data {}.",
806                   tIdx,
807                   spaceTri,
808                   fmt::join(m_meshWrapper.cellVertexIndices(tIdx), ", "),
809                   childBlk[j],
810                   *(childDataPtr[j])));
811             }
812           }
813         }
814       }
815     }
816 
817     if(!levelLeafMap.empty())
818     {
819       // Create the relations from gray leaves to mesh vertices and elements
820       m_grayLeafsMap[lev] = GrayLeafSet(static_cast<int>(gvRelData.size()));
821 
822       m_grayLeafToVertexRelationLevelMap[lev] =
823         GrayLeafVertexRelation(&m_grayLeafsMap[lev], &m_meshWrapper.vertexSet());
824       m_grayLeafToVertexRelationLevelMap[lev].bindIndices(
825         static_cast<int>(gvRelData.size()),
826         &gvRelData);
827 
828       m_grayLeafToElementRelationLevelMap[lev] =
829         GrayLeafElementRelation(&m_grayLeafsMap[lev],
830                                 &m_meshWrapper.elementSet());
831       m_grayLeafToElementRelationLevelMap[lev].bindBeginOffsets(
832         m_grayLeafsMap[lev].size(),
833         &geSizeRelData);
834       m_grayLeafToElementRelationLevelMap[lev].bindIndices(
835         static_cast<int>(geIndRelData.size()),
836         &geIndRelData);
837     }
838 
839     currentLevelData.clear();
840     nextLevelData.swap(currentLevelData);
841 
842     if(!levelLeafMap.empty())
843       SLIC_DEBUG("\tInserting cells into level "
844                  << lev << " took " << levelTimer.elapsed() << " seconds.");
845   }
846 }
847 
848 template <int DIM>
colorOctreeLeaves()849 void InOutOctree<DIM>::colorOctreeLeaves()
850 {
851   // Note (KW): Existence of leaf implies that either
852   // * it is gray
853   // * one of its siblings is gray
854   // * one of its siblings has a gray descendant
855 
856   using Timer = axom::utilities::Timer;
857   using GridPtVec = std::vector<GridPt>;
858   GridPtVec uncoloredBlocks;
859 
860   // Bottom-up traversal of octree
861   for(int lev = this->maxLeafLevel() - 1; lev >= 0; --lev)
862   {
863     uncoloredBlocks.clear();
864     Timer levelTimer(true);
865 
866     auto& levelLeafMap = this->getOctreeLevel(lev);
867     auto itEnd = levelLeafMap.end();
868     for(auto it = levelLeafMap.begin(); it != itEnd; ++it)
869     {
870       if(!it->isLeaf()) continue;
871 
872       BlockIndex leafBlk(it.pt(), lev);
873       InOutBlockData& blockData = *it;
874       if(!colorLeafAndNeighbors(leafBlk, blockData))
875         uncoloredBlocks.push_back(leafBlk.pt());
876     }
877 
878     // Iterate through the uncolored blocks until all have a color
879     // This terminates since we know that one of its siblings
880     // (or their descendants) is gray
881     while(!uncoloredBlocks.empty())
882     {
883       int prevCount = static_cast<int>(uncoloredBlocks.size());
884       AXOM_UNUSED_VAR(prevCount);
885 
886       GridPtVec prevVec;
887       prevVec.swap(uncoloredBlocks);
888       auto end = prevVec.end();
889       for(auto it = prevVec.begin(); it < end; ++it)
890       {
891         BlockIndex leafBlk(*it, lev);
892         if(!colorLeafAndNeighbors(leafBlk, (*this)[leafBlk]))
893           uncoloredBlocks.push_back(*it);
894       }
895 
896       SLIC_ASSERT_MSG(
897         static_cast<int>(uncoloredBlocks.size()) < prevCount,
898         fmt::format("Problem coloring leaf blocks at level {}. "
899                     "There are {} blocks that are still not colored. "
900                     "First problem block is: {}",
901                     lev,
902                     uncoloredBlocks.size(),
903                     BlockIndex(uncoloredBlocks[0], lev)));
904     }
905 
906     if(!levelLeafMap.empty())
907     {
908       checkAllLeavesColoredAtLevel(lev);
909       SLIC_DEBUG(fmt::format("\tColoring level {} took {} seconds.",
910                              lev,
911                              levelTimer.elapsed()));
912     }
913   }
914 }
915 
916 template <int DIM>
colorLeafAndNeighbors(const BlockIndex & leafBlk,InOutBlockData & leafData)917 bool InOutOctree<DIM>::colorLeafAndNeighbors(const BlockIndex& leafBlk,
918                                              InOutBlockData& leafData)
919 {
920   bool isColored = leafData.isColored();
921 
922   QUEST_OCTREE_DEBUG_LOG_IF(
923     leafBlk == DEBUG_BLOCK_1 || leafBlk == DEBUG_BLOCK_2,
924     fmt::format("Trying to color {} with data: {}", leafBlk, leafData));
925 
926   if(!isColored)
927   {
928     // Leaf does not yet have a color... try to find its color from same-level face neighbors
929     for(int i = 0; !isColored && i < leafBlk.numFaceNeighbors(); ++i)
930     {
931       BlockIndex neighborBlk = leafBlk.faceNeighbor(i);
932       if(this->isLeaf(neighborBlk))
933       {
934         const InOutBlockData& neighborData = (*this)[neighborBlk];
935 
936         QUEST_OCTREE_DEBUG_LOG_IF(
937           DEBUG_BLOCK_1 == neighborBlk || DEBUG_BLOCK_2 == neighborBlk ||
938             DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
939           fmt::format("Spreading color to block {} with data {}, "
940                       "bounding box {} w/ midpoint {}"
941                       "\n\t\t from block {} with data {}, "
942                       "bounding box {} w/ midpoint {}.",
943                       leafBlk,
944                       leafData,
945                       this->blockBoundingBox(leafBlk),
946                       this->blockBoundingBox(leafBlk).getCentroid(),
947                       neighborBlk,
948                       neighborData,
949                       this->blockBoundingBox(neighborBlk),
950                       this->blockBoundingBox(neighborBlk).getCentroid()));
951 
952         switch(neighborData.color())
953         {
954         case InOutBlockData::Black:
955           leafData.setBlack();
956           break;
957         case InOutBlockData::White:
958           leafData.setWhite();
959           break;
960         case InOutBlockData::Gray:
961         {
962           SpacePt faceCenter =
963             SpacePt::midpoint(this->blockBoundingBox(leafBlk).getCentroid(),
964                               this->blockBoundingBox(neighborBlk).getCentroid());
965           if(withinGrayBlock<DIM>(faceCenter, neighborBlk, neighborData))
966             leafData.setBlack();
967           else
968             leafData.setWhite();
969         }
970         break;
971         case InOutBlockData::Undetermined:
972           break;
973         }
974 
975         isColored = leafData.isColored();
976 
977         QUEST_OCTREE_DEBUG_LOG_IF(
978           isColored &&
979             (DEBUG_BLOCK_1 == neighborBlk || DEBUG_BLOCK_2 == neighborBlk),
980           fmt::format("Leaf block was colored -- {} now has data {}",
981                       leafBlk,
982                       leafData));
983       }
984     }
985   }
986 
987   // If the block has a color, try to color its face neighbors at the same or coarser resolution
988   if(isColored)
989   {
990     for(int i = 0; i < leafBlk.numFaceNeighbors(); ++i)
991     {
992       BlockIndex neighborBlk = this->coveringLeafBlock(leafBlk.faceNeighbor(i));
993       if(neighborBlk != BlockIndex::invalid_index())
994       {
995         InOutBlockData& neighborData = (*this)[neighborBlk];
996         if(!neighborData.isColored())
997         {
998           QUEST_OCTREE_DEBUG_LOG_IF(
999             DEBUG_BLOCK_1 == neighborBlk || DEBUG_BLOCK_2 == neighborBlk ||
1000               DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1001             fmt::format("Spreading color from block {} with data {}, "
1002                         "bounding box {} w/ midpoint {}"
1003                         "\n\t\t to block {} with data {}, "
1004                         "bounding box {} w/ midpoint {}.",
1005                         leafBlk,
1006                         leafData,
1007                         this->blockBoundingBox(leafBlk),
1008                         this->blockBoundingBox(leafBlk).getCentroid(),
1009                         neighborBlk,
1010                         neighborData,
1011                         this->blockBoundingBox(neighborBlk),
1012                         this->blockBoundingBox(neighborBlk).getCentroid()));
1013 
1014           switch(leafData.color())
1015           {
1016           case InOutBlockData::Black:
1017             neighborData.setBlack();
1018             break;
1019           case InOutBlockData::White:
1020             neighborData.setWhite();
1021             break;
1022           case InOutBlockData::Gray:
1023           {
1024             // Check the center of the shared face between the same-level neighbor of the gray block
1025             SpacePt faceCenter = SpacePt::midpoint(
1026               this->blockBoundingBox(leafBlk).getCentroid(),
1027               this->blockBoundingBox(leafBlk.faceNeighbor(i)).getCentroid());
1028 
1029             if(withinGrayBlock<DIM>(faceCenter, leafBlk, leafData))
1030               neighborData.setBlack();
1031             else
1032               neighborData.setWhite();
1033           }
1034           break;
1035           case InOutBlockData::Undetermined:
1036             break;
1037           }
1038 
1039           QUEST_OCTREE_DEBUG_LOG_IF(
1040             neighborData.isColored() &&
1041               (DEBUG_BLOCK_1 == neighborBlk || DEBUG_BLOCK_2 == neighborBlk),
1042             fmt::format("Neighbor block was colored -- {} now has data {}",
1043                         neighborBlk,
1044                         neighborData));
1045         }
1046       }
1047     }
1048   }
1049 
1050   return isColored;
1051 }
1052 
1053 template <int DIM>
leafVertex(const BlockIndex & leafBlk,const InOutBlockData & leafData) const1054 typename InOutOctree<DIM>::VertexIndex InOutOctree<DIM>::leafVertex(
1055   const BlockIndex& leafBlk,
1056   const InOutBlockData& leafData) const
1057 {
1058   if(m_generationState >= INOUTOCTREE_ELEMENTS_INSERTED)
1059   {
1060     SLIC_ASSERT(leafData.hasData());
1061     return m_grayLeafToVertexRelationLevelMap[leafBlk.level()]
1062                                              [leafData.dataIndex()][0];
1063   }
1064   else
1065   {
1066     return leafData.dataIndex();
1067   }
1068 }
1069 
1070 template <int DIM>
leafCells(const BlockIndex & leafBlk,const InOutBlockData & leafData) const1071 typename InOutOctree<DIM>::CellIndexSet InOutOctree<DIM>::leafCells(
1072   const BlockIndex& leafBlk,
1073   const InOutBlockData& leafData) const
1074 {
1075   SLIC_ASSERT(m_generationState >= INOUTOCTREE_ELEMENTS_INSERTED &&
1076               leafData.hasData());
1077 
1078   return m_grayLeafToElementRelationLevelMap[leafBlk.level()][leafData.dataIndex()];
1079 }
1080 
1081 template <int DIM>
1082 template <int TDIM>
withinGrayBlock(const SpacePt & queryPt,const BlockIndex & leafBlk,const InOutBlockData & leafData) const1083 typename std::enable_if<TDIM == 3, bool>::type InOutOctree<DIM>::withinGrayBlock(
1084   const SpacePt& queryPt,
1085   const BlockIndex& leafBlk,
1086   const InOutBlockData& leafData) const
1087 {
1088   /// Finds a ray from queryPt to a point of a triangle within leafBlk.
1089   /// Then find the first triangle along this ray. The orientation of the ray
1090   /// against this triangle's normal indicates queryPt's containment.
1091   /// It is inside when the dot product is positive.
1092 
1093   SLIC_ASSERT(leafData.color() == InOutBlockData::Gray);
1094   SLIC_ASSERT(leafData.hasData());
1095 
1096   GeometricBoundingBox blockBB = this->blockBoundingBox(leafBlk);
1097 
1098   SpacePt triPt;
1099 
1100   CellIndexSet triSet = leafCells(leafBlk, leafData);
1101   const int numTris = triSet.size();
1102   for(int i = 0; i < numTris; ++i)
1103   {
1104     /// Get the triangle
1105     CellIndex idx = triSet[i];
1106     SpaceCell tri = m_meshWrapper.cellPositions(idx);
1107 
1108     /// Find a point from this triangle within the bounding box of the mesh
1109     primal::Polygon<double, DIM> poly = primal::clip(tri, blockBB);
1110     if(poly.numVertices() == 0)
1111     {
1112       // Account for cases where the triangle only grazes the bounding box.
1113       // Here, intersect(tri,blockBB) is true, but the clipping algorithm
1114       // produces an empty polygon.  To resolve this, clip against a
1115       // slightly expanded bounding box
1116       GeometricBoundingBox expandedBB = blockBB;
1117       expandedBB.scale(10 * m_boundingBoxScaleFactor);
1118 
1119       poly = primal::clip(tri, expandedBB);
1120 
1121       // If that still doesn't work, move on to the next triangle
1122       if(poly.numVertices() == 0)
1123       {
1124         continue;
1125       }
1126     }
1127 
1128     triPt = poly.centroid();
1129 
1130     /// Use a ray from the query point to the triangle point to find an
1131     /// intersection. Note: We have to check all triangles to ensure that
1132     /// there is not a closer triangle than tri along this direction.
1133     CellIndex tIdx = MeshWrapper<DIM>::NO_CELL;
1134     double minRayParam = std::numeric_limits<double>::infinity();
1135     SpaceRay ray(queryPt, SpaceVector(queryPt, triPt));
1136 
1137     QUEST_OCTREE_DEBUG_LOG_IF(
1138       DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1139       fmt::format("Checking if pt {} is within block {} with data {}, "
1140                   "ray is {}, triangle point is {} on triangle with index {}.",
1141                   queryPt,
1142                   leafBlk,
1143                   leafData,
1144                   ray,
1145                   triPt,
1146                   idx));
1147 
1148     double rayParam = 0;
1149     if(primal::intersect(tri, ray, rayParam))
1150     {
1151       minRayParam = rayParam;
1152       tIdx = idx;
1153 
1154       QUEST_OCTREE_DEBUG_LOG_IF(
1155         DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1156         fmt::format("... intersection for triangle w/ index {} at ray "
1157                     "parameter {} at point {}",
1158                     tIdx,
1159                     minRayParam,
1160                     ray.at(minRayParam)));
1161     }
1162 
1163     for(int j = 0; j < numTris; ++j)
1164     {
1165       CellIndex localIdx = triSet[j];
1166       if(localIdx == idx) continue;
1167 
1168       if(primal::intersect(m_meshWrapper.cellPositions(localIdx), ray, rayParam))
1169       {
1170         if(rayParam < minRayParam)
1171         {
1172           minRayParam = rayParam;
1173           tIdx = localIdx;
1174 
1175           QUEST_OCTREE_DEBUG_LOG_IF(
1176             DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1177             fmt::format("... intersection for triangle w/ index {} at ray "
1178                         "parameter {} at point {}",
1179                         tIdx,
1180                         minRayParam,
1181                         ray.at(minRayParam)));
1182         }
1183       }
1184     }
1185 
1186     if(tIdx == MeshWrapper<DIM>::NO_CELL)
1187     {
1188       continue;
1189     }
1190 
1191     // Inside when the dot product of the normal with this triangle is positive
1192     SpaceVector normal =
1193       (tIdx == idx) ? tri.normal() : m_meshWrapper.cellPositions(tIdx).normal();
1194 
1195     return normal.dot(ray.direction()) > 0.;
1196   }
1197 
1198   SLIC_DEBUG("Could not determine inside/outside for point "
1199              << queryPt << " on block " << leafBlk);
1200 
1201   return false;  // query points on boundary might get here -- revisit this.
1202 }
1203 
1204 template <int DIM>
1205 template <int TDIM>
withinGrayBlock(const SpacePt & queryPt,const BlockIndex & leafBlk,const InOutBlockData & leafData) const1206 typename std::enable_if<TDIM == 2, bool>::type InOutOctree<DIM>::withinGrayBlock(
1207   const SpacePt& queryPt,
1208   const BlockIndex& leafBlk,
1209   const InOutBlockData& leafData) const
1210 {
1211   /// Finds a ray from queryPt to a point of a segment within leafBlk.
1212   /// Then finds the first segment along this ray. The orientation of the ray
1213   /// against this segment's normal indicates queryPt's containment.
1214   /// It is inside when the dot product is positive.
1215 
1216   SLIC_ASSERT(leafData.color() == InOutBlockData::Gray);
1217   SLIC_ASSERT(leafData.hasData());
1218 
1219   GeometricBoundingBox blockBB = this->blockBoundingBox(leafBlk);
1220   GeometricBoundingBox expandedBB = blockBB;
1221   expandedBB.scale(m_boundingBoxScaleFactor);
1222 
1223   SpacePt segmentPt;
1224 
1225   CellIndexSet segmentSet = leafCells(leafBlk, leafData);
1226   const int numSegments = segmentSet.size();
1227   for(int i = 0; i < numSegments; ++i)
1228   {
1229     /// Get the segment
1230     CellIndex idx = segmentSet[i];
1231     SpaceCell seg = m_meshWrapper.cellPositions(idx);
1232 
1233     /// Find a point from this segment within the expanded bounding box of the mesh
1234     // We'll use the midpoint of the segment after clipping it against the bounding box
1235     double pMin, pMax;
1236     const bool intersects = primal::intersect(seg, expandedBB, pMin, pMax);
1237     if(!intersects)
1238     {
1239       continue;
1240     }
1241     segmentPt = seg.at(0.5 * (pMin + pMax));
1242 
1243     // Using a ray from query pt to point on this segment
1244     // Find closest intersection to surface within cell inside this bounding box
1245     CellIndex tIdx = MeshWrapper<DIM>::NO_CELL;
1246     double minRayParam = std::numeric_limits<double>::infinity();
1247     double minSegParam = std::numeric_limits<double>::infinity();
1248     SpaceRay ray(queryPt, SpaceVector(queryPt, segmentPt));
1249 
1250     QUEST_OCTREE_DEBUG_LOG_IF(
1251       DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1252       fmt::format("Checking if pt {} is within block {} with data {}, "
1253                   "ray is {}, segment point is {} on segment with index {}.",
1254                   queryPt,
1255                   leafBlk,
1256                   leafData,
1257                   ray,
1258                   segmentPt,
1259                   idx));
1260 
1261     double rayParam = 0;
1262     double segParam = 0;
1263     if(primal::intersect(ray, seg, rayParam, segParam))
1264     {
1265       minRayParam = rayParam;
1266       minSegParam = segParam;
1267       tIdx = idx;
1268 
1269       QUEST_OCTREE_DEBUG_LOG_IF(
1270         DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1271         fmt::format("... intersection for segment w/ index {} "
1272                     "at ray parameter {} and segment parameter {} "
1273                     "is at point {}",
1274                     tIdx,
1275                     minRayParam,
1276                     minSegParam,
1277                     ray.at(minRayParam)));
1278     }
1279 
1280     for(int j = 0; j < numSegments; ++j)
1281     {
1282       CellIndex localIdx = segmentSet[j];
1283       if(localIdx == idx) continue;
1284 
1285       if(primal::intersect(ray,
1286                            m_meshWrapper.cellPositions(localIdx),
1287                            rayParam,
1288                            segParam))
1289       {
1290         if(rayParam < minRayParam)
1291         {
1292           minRayParam = rayParam;
1293           minSegParam = segParam;
1294           tIdx = localIdx;
1295 
1296           QUEST_OCTREE_DEBUG_LOG_IF(
1297             DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1298             fmt::format("... intersection for segment w/ index {} "
1299                         "at ray parameter {} and segment parameter {} "
1300                         "is at point {}",
1301                         tIdx,
1302                         minRayParam,
1303                         minSegParam,
1304                         ray.at(minRayParam)));
1305         }
1306       }
1307     }
1308     if(tIdx == MeshWrapper<DIM>::NO_CELL)
1309     {
1310       continue;
1311     }
1312 
1313     // Get the surface normal at the intersection point
1314     // If the latter is a vertex, the normal is the average of its two incident segments
1315     SpaceVector normal =
1316       m_meshWrapper.surfaceNormal(tIdx, minSegParam, segmentSet);
1317 
1318     QUEST_OCTREE_DEBUG_LOG_IF(DEBUG_BLOCK_1 == leafBlk || DEBUG_BLOCK_2 == leafBlk,
1319                               fmt::format("... normal {}, ray {}, dot {}",
1320                                           normal,
1321                                           ray,
1322                                           normal.dot(ray.direction())));
1323 
1324     // Query point is inside when the dot product of the normal with ray is positive
1325     return normal.dot(ray.direction()) > 0.;
1326   }
1327 
1328   SLIC_DEBUG("Could not determine inside/outside for point "
1329              << queryPt << " on block " << leafBlk);
1330 
1331   return false;  // query points on boundary might get here -- revisit this.
1332 }
1333 
1334 template <int DIM>
updateSurfaceMeshVertices()1335 void InOutOctree<DIM>::updateSurfaceMeshVertices()
1336 {
1337   // Create a map from old vertex indices to new vertex indices
1338   MeshVertexSet origVerts(m_meshWrapper.numMeshVertices());
1339   VertexIndexMap vertexIndexMap(&origVerts, MeshWrapper<DIM>::NO_VERTEX);
1340 
1341   // Generate unique indices for new mesh vertices
1342   int uniqueVertexCounter = 0;
1343   for(int i = 0; i < origVerts.size(); ++i)
1344   {
1345     // Find the block and its indexed vertex in the octree
1346     BlockIndex leafBlock =
1347       this->findLeafBlock(m_meshWrapper.getMeshVertexPosition(i));
1348     SLIC_ASSERT((*this)[leafBlock].hasData());
1349     VertexIndex vInd = (*this)[leafBlock].dataIndex();
1350 
1351     // If the indexed vertex doesn't have a new id, give it one
1352     if(vertexIndexMap[vInd] == MeshWrapper<DIM>::NO_VERTEX)
1353       vertexIndexMap[vInd] = uniqueVertexCounter++;
1354 
1355     // If this is not the indexed vertex of the block, set the new index
1356     if(vInd != i) vertexIndexMap[i] = vertexIndexMap[vInd];
1357   }
1358 
1359   // Use the index map to reindex the mesh verts and elements
1360   m_meshWrapper.reindexMesh(uniqueVertexCounter, vertexIndexMap);
1361 
1362   // Update the octree leaf vertex IDs to the new mesh IDs
1363   // and create the map from the new vertices to their octree blocks
1364   m_vertexToBlockMap = VertexBlockMap(&m_meshWrapper.vertexSet());
1365   for(int i = 0; i < m_meshWrapper.numMeshVertices(); ++i)
1366   {
1367     const SpacePt& pos = m_meshWrapper.vertexPosition(i);
1368     BlockIndex leafBlock = this->findLeafBlock(pos);
1369     SLIC_ASSERT(this->isLeaf(leafBlock) && (*this)[leafBlock].hasData());
1370 
1371     (*this)[leafBlock].setData(i);
1372     m_vertexToBlockMap[i] = leafBlock;
1373   }
1374 }
1375 
1376 template <int DIM>
allCellsIncidentInCommonVertex(const BlockIndex & leafBlock,DynamicGrayBlockData & leafData) const1377 bool InOutOctree<DIM>::allCellsIncidentInCommonVertex(
1378   const BlockIndex& leafBlock,
1379   DynamicGrayBlockData& leafData) const
1380 {
1381   bool shareCommonVert = false;
1382 
1383   VertexIndex commonVert = leafData.vertexIndex();
1384 
1385   const int numCells = leafData.numCells();
1386   const auto& cells = leafData.cells();
1387 
1388   if(blockIndexesVertex(commonVert, leafBlock))
1389   {
1390     // This is a leaf node containing the indexed vertex
1391     // Loop through the triangles and check that all are incident with this
1392     // vertex
1393     for(int i = 0; i < numCells; ++i)
1394     {
1395       if(!m_meshWrapper.incidentInVertex(m_meshWrapper.cellVertexIndices(cells[i]),
1396                                          commonVert))
1397       {
1398         return false;
1399       }
1400     }
1401     shareCommonVert = true;
1402   }
1403   else
1404   {
1405     SLIC_ASSERT(numCells > 0);
1406     switch(numCells)
1407     {
1408     case 1:
1409       /// Choose an arbitrary vertex from this cell
1410       commonVert = m_meshWrapper.cellVertexIndices(cells[0])[0];
1411       shareCommonVert = true;
1412       break;
1413     case 2:
1414       /// Find a vertex that both triangles share
1415       shareCommonVert =
1416         m_meshWrapper.haveSharedVertex(cells[0], cells[1], commonVert);
1417       break;
1418     default:  // numCells > 3
1419       if(DIM == 3)
1420       {
1421         /// Find a vertex that the first three triangles share
1422         shareCommonVert =
1423           m_meshWrapper.haveSharedVertex(cells[0], cells[1], cells[2], commonVert);
1424 
1425         /// Check that all other triangles have this vertex
1426         for(int i = 3; shareCommonVert && i < numCells; ++i)
1427         {
1428           if(!m_meshWrapper.incidentInVertex(
1429                m_meshWrapper.cellVertexIndices(cells[i]),
1430                commonVert))
1431             shareCommonVert = false;
1432         }
1433       }
1434       break;
1435     }
1436 
1437     if(shareCommonVert) leafData.setVertex(commonVert);
1438   }
1439 
1440   return shareCommonVert;
1441 }
1442 
1443 template <int DIM>
within(const SpacePt & pt) const1444 bool InOutOctree<DIM>::within(const SpacePt& pt) const
1445 {
1446   if(this->boundingBox().contains(pt))
1447   {
1448     const BlockIndex block = this->findLeafBlock(pt);
1449     const InOutBlockData& data = (*this)[block];
1450 
1451     switch(data.color())
1452     {
1453     case InOutBlockData::Black:
1454       return true;
1455     case InOutBlockData::White:
1456       return false;
1457     case InOutBlockData::Gray:
1458       return withinGrayBlock<DIM>(pt, block, data);
1459     case InOutBlockData::Undetermined:
1460       SLIC_ASSERT_MSG(
1461         false,
1462         fmt::format("Error -- All leaf blocks must have a color. The color of "
1463                     "leafBlock {} was 'Undetermined' when querying point {}",
1464                     block,
1465                     pt));
1466       break;
1467     }
1468   }
1469 
1470   return false;
1471 }
1472 
1473 template <int DIM>
printOctreeStats() const1474 void InOutOctree<DIM>::printOctreeStats() const
1475 {
1476   detail::InOutOctreeStats<DIM> octreeStats(*this);
1477   SLIC_INFO(octreeStats.summaryStats());
1478 
1479 #ifdef DUMP_VTK_MESH
1480   // Print out some debug meshes for vertex, triangle and/or blocks defined in
1481   // DEBUG_XXX macros
1482   if(m_generationState >= INOUTOCTREE_ELEMENTS_INSERTED)
1483   {
1484     detail::InOutOctreeMeshDumper<DIM> meshDumper(*this);
1485 
1486     if(DEBUG_VERT_IDX >= 0 && DEBUG_VERT_IDX < m_meshWrapper.numMeshVertices())
1487     {
1488       meshDumper.dumpLocalOctreeMeshesForCell("debug_", DEBUG_VERT_IDX);
1489     }
1490     if(DEBUG_TRI_IDX >= 0 && DEBUG_TRI_IDX < m_meshWrapper.numMeshCells())
1491     {
1492       meshDumper.dumpLocalOctreeMeshesForCell("debug_", DEBUG_TRI_IDX);
1493     }
1494 
1495     if(DEBUG_BLOCK_1 != BlockIndex::invalid_index() &&
1496        this->hasBlock(DEBUG_BLOCK_1))
1497     {
1498       meshDumper.dumpLocalOctreeMeshesForBlock("debug_", DEBUG_BLOCK_1);
1499     }
1500 
1501     if(DEBUG_BLOCK_2 != BlockIndex::invalid_index() &&
1502        this->hasBlock(DEBUG_BLOCK_2))
1503     {
1504       meshDumper.dumpLocalOctreeMeshesForBlock("debug_", DEBUG_BLOCK_2);
1505     }
1506   }
1507 #endif
1508 }
1509 
1510 template <int DIM>
checkAllLeavesColoredAtLevel(int AXOM_DEBUG_PARAM (level)) const1511 void InOutOctree<DIM>::checkAllLeavesColoredAtLevel(int AXOM_DEBUG_PARAM(level)) const
1512 {
1513 #ifdef AXOM_DEBUG
1514   detail::InOutOctreeValidator<DIM> validator(*this);
1515   validator.checkAllLeavesColoredAtLevel(level);
1516 #endif
1517 }
1518 
1519 template <int DIM>
checkValid() const1520 void InOutOctree<DIM>::checkValid() const
1521 {
1522 #ifdef AXOM_DEBUG
1523   SLIC_DEBUG(fmt::format(
1524     "Inside InOutOctree::checkValid() to verify state of {} octree",
1525     (m_generationState >= INOUTOCTREE_ELEMENTS_INSERTED ? "PM" : "PR")));
1526 
1527   detail::InOutOctreeValidator<DIM> validator(*this);
1528   validator.checkValid();
1529 
1530   SLIC_DEBUG("done.");
1531 #endif
1532 }
1533 
1534 template <int DIM>
dumpSurfaceMeshVTK(const std::string & name) const1535 void InOutOctree<DIM>::dumpSurfaceMeshVTK(const std::string& name) const
1536 {
1537 #ifdef DUMP_VTK_MESH
1538 
1539   detail::InOutOctreeMeshDumper<DIM> meshDumper(*this);
1540   meshDumper.dumpSurfaceMeshVTK(name);
1541 
1542 #else
1543   AXOM_UNUSED_VAR(name);
1544 #endif
1545 }
1546 
1547 template <int DIM>
dumpOctreeMeshVTK(const std::string & name) const1548 void InOutOctree<DIM>::dumpOctreeMeshVTK(const std::string& name) const
1549 {
1550 #ifdef DUMP_VTK_MESH
1551 
1552   detail::InOutOctreeMeshDumper<DIM> meshDumper(*this);
1553   meshDumper.dumpOctreeMeshVTK(name);
1554 
1555 #else
1556   AXOM_UNUSED_VAR(name);
1557 #endif
1558 }
1559 
1560 template <int DIM>
dumpDifferentColoredNeighborsMeshVTK(const std::string & name) const1561 void InOutOctree<DIM>::dumpDifferentColoredNeighborsMeshVTK(
1562   const std::string& name) const
1563 {
1564 #ifdef DUMP_VTK_MESH
1565 
1566   detail::InOutOctreeMeshDumper<DIM> meshDumper(*this);
1567   meshDumper.dumpDifferentColoredNeighborsMeshVTK(name);
1568 
1569 #else
1570   AXOM_UNUSED_VAR(name);
1571 #endif
1572 }
1573 
1574 }  // end namespace quest
1575 }  // end namespace axom
1576 
1577 // Note: The following needs to be included after InOutOctree is defined
1578 #include "detail/inout/InOutOctreeMeshDumper.hpp"
1579 
1580 #endif  // AXOM_QUEST_INOUT_OCTREE__HPP_
1581