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