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 MeshWrapper.hpp 8 * 9 * \brief Defines a templated mesh wrapper class for the InOutOctree. 10 */ 11 12 #ifndef AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__HPP_ 13 #define AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__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 "axom/fmt.hpp" 23 24 namespace axom 25 { 26 namespace quest 27 { 28 /** 29 * \brief A utility class that wraps access to the mesh data of and InOutOctree 30 * 31 * This class helps separate the specifics of accessing the underlying mesh 32 * for an InOutOctree. It is customized for unstructured Segment meshes in 2D 33 * and Triangle meshes in 3D. 34 * 35 * If we want to support other surface mesh types (e.g. quad meshes in 3D), 36 * we'll have to customize it a bit more. 37 * 38 * \note Uses the CRTP pattern to allow the dimension-specific derived classes 39 * to share the dimension-independent implementation 40 */ 41 template <int DIM> 42 class MeshWrapper; 43 44 template <int DIM, typename Derived> 45 class SimplexMeshWrapper 46 { 47 public: 48 using VertexIndex = axom::IndexType; 49 using CellIndex = axom::IndexType; 50 51 using MeshVertexSet = slam::PositionSet<>; 52 using MeshElementSet = slam::PositionSet<>; 53 using SurfaceMesh = mint::Mesh; 54 55 using SpacePt = primal::Point<double, DIM>; 56 using SpaceVector = primal::Vector<double, DIM>; 57 using GeometricBoundingBox = primal::BoundingBox<double, DIM>; 58 59 using VertexIndexMap = slam::Map<slam::Set<VertexIndex>, VertexIndex>; 60 using VertexPositionMap = slam::Map<slam::Set<VertexIndex>, SpacePt>; 61 62 /// Always DIM verts since we're representing a d-dimensional simplicial mesh in dimension d 63 static constexpr int NUM_CELL_VERTS = DIM; 64 using STLIndirection = 65 slam::policies::STLVectorIndirection<VertexIndex, VertexIndex>; 66 using TVStride = slam::policies::CompileTimeStride<VertexIndex, NUM_CELL_VERTS>; 67 using ConstantCardinality = 68 slam::policies::ConstantCardinality<VertexIndex, TVStride>; 69 using CellVertexRelation = slam::StaticRelation<VertexIndex, 70 VertexIndex, 71 ConstantCardinality, 72 STLIndirection, 73 MeshElementSet, 74 MeshVertexSet>; 75 using CellVertIndices = typename CellVertexRelation::RelationSubset; 76 77 // \brief A vertex index to indicate that there is no associated vertex 78 static constexpr VertexIndex NO_VERTEX = -1; 79 80 /// \brief A vertex index to indicate that there is no associated vertex 81 static constexpr CellIndex NO_CELL = -1; 82 83 protected: SimplexMeshWrapper(SurfaceMesh * & meshPtr)84 SimplexMeshWrapper(SurfaceMesh*& meshPtr) 85 : m_surfaceMesh(meshPtr) 86 , m_vertexPositions(&m_vertexSet) 87 , m_cellToVertexRelation() 88 { } 89 90 private: 91 /// Utility functions to get a pointer to the derived type (part of CRTP pattern) getDerived()92 Derived* getDerived() { return static_cast<Derived*>(this); } 93 /// Utility functions to get a const pointer to the derived type (part of CRTP pattern) getDerived() const94 const Derived* getDerived() const 95 { 96 return static_cast<const Derived*>(this); 97 } 98 99 public: 100 /** Predicate to determine if the wrapped surface mesh has been reindexed */ meshWasReindexed() const101 bool meshWasReindexed() const { return m_meshWasReindexed; } 102 103 /** Const accessor to the vertex set of the wrapped surface mesh */ vertexSet() const104 const MeshVertexSet& vertexSet() const { return m_vertexSet; } 105 106 /** Accessor to the vertex set of the wrapped surface mesh */ vertexSet()107 MeshVertexSet& vertexSet() { return m_vertexSet; } 108 109 /** Const accessor to the element set of the wrapped surface mesh */ elementSet() const110 const MeshElementSet& elementSet() const { return m_elementSet; } 111 112 /** Accessor to the element set of the wrapped surface mesh */ elementSet()113 MeshElementSet& elementSet() { return m_elementSet; } 114 115 /** Accessor for the number of vertices in the wrapped surface mesh */ numMeshVertices() const116 int numMeshVertices() const 117 { 118 if(m_meshWasReindexed) 119 return m_vertexSet.size(); 120 else 121 return m_surfaceMesh->getNumberOfNodes(); 122 } 123 124 /** Accessor for the number of elements in the wrapped surface mesh */ numMeshCells() const125 int numMeshCells() const 126 { 127 if(m_meshWasReindexed) 128 return m_elementSet.size(); 129 else 130 return m_surfaceMesh->getNumberOfCells(); 131 } 132 133 /** 134 * \brief Returns position of vertex with index \a idx within the surface mesh 135 * 136 * \note Use this function instead of vertexPosition() if calling in a context 137 * where the mesh might not yet have been reindexed 138 * \sa vertexPosition() 139 */ getMeshVertexPosition(VertexIndex idx) const140 SpacePt getMeshVertexPosition(VertexIndex idx) const 141 { 142 if(m_meshWasReindexed) 143 { 144 return m_vertexPositions[idx]; 145 } 146 else 147 { 148 SLIC_ASSERT(m_surfaceMesh->getDimension() == SpacePt::dimension()); 149 150 SpacePt pt; 151 m_surfaceMesh->getNode(idx, pt.data()); 152 153 return pt; 154 } 155 } 156 157 /** 158 * \brief Returns spatial position of vertex with index \a idx from wrapped surface mesh 159 * 160 * \note Use after mesh has been reindexed 161 */ vertexPosition(VertexIndex idx) const162 const SpacePt& vertexPosition(VertexIndex idx) const 163 { 164 return m_vertexPositions[idx]; 165 } 166 167 /** 168 * \brief Returns the indices of the boundary vertices of the element 169 * of the wrapped surface mesh with the given index 170 * 171 * \param idx The index of an element within the surface mesh 172 */ cellVertexIndices(CellIndex idx) const173 CellVertIndices cellVertexIndices(CellIndex idx) const 174 { 175 return m_cellToVertexRelation[idx]; 176 } 177 178 /** 179 * \brief Finds the index of a vertex in cell \a c1 that is not in cell \a c0 180 * 181 * \param c0 The index of the first cell 182 * \param c1 The index of the second cell 183 * \pre \a c0 and \a c1 must be distinct cells 184 * \return The index of a vertex in \a c1 that is not in \a c0; NO_VERTEX if one does not exist 185 */ distinctVertex(CellIndex c0,CellIndex c1) const186 VertexIndex distinctVertex(CellIndex c0, CellIndex c1) const 187 { 188 SLIC_ASSERT_MSG(c0 != c1, 189 "Expected two different cell indices in " 190 "quest::MeshWrapper::findDistinctVertex," 191 << " got cell index " << c0 << " twice."); 192 193 // Find a vertex from the local surface that is not incident in the first cell 194 CellVertIndices cvRel0 = cellVertexIndices(c0); 195 CellVertIndices cvRel1 = cellVertexIndices(c1); 196 197 for(int i = 0; i < NUM_CELL_VERTS; ++i) 198 if(!incidentInVertex(cvRel0, cvRel1[i])) return cvRel1[i]; 199 200 SLIC_ASSERT_MSG( 201 false, 202 fmt::format("There should be a vertex in cell {} that was not in cell {}", 203 c1, 204 c0)); 205 return NO_VERTEX; 206 } 207 208 /** 209 * \brief Determine if the two given cells have a vertex in common 210 * 211 * \param c0 The index of the first cell 212 * \param c1 The index of the second cell 213 * \param [out] sharedVert The index of the shared vertex, if it exists 214 * \return true if the two cells have a vertex 215 * in common (returned in sharedVert), false otherwise 216 */ haveSharedVertex(CellIndex c0,CellIndex c1,VertexIndex & sharedVert) const217 bool haveSharedVertex(CellIndex c0, CellIndex c1, VertexIndex& sharedVert) const 218 { 219 // There are two cells -- check that they have at least one common vertex 220 CellVertIndices cvRel0 = cellVertexIndices(c0); 221 CellVertIndices cvRel1 = cellVertexIndices(c1); 222 223 for(int i = 0; i < NUM_CELL_VERTS; ++i) 224 { 225 if(getDerived()->incidentInVertex(cvRel0, cvRel1[i])) 226 { 227 sharedVert = cvRel1[i]; 228 return true; 229 } 230 } 231 return false; 232 } 233 234 /** 235 * \brief Determine if the three given cells have a vertex in common 236 * 237 * \param c0 The index of the first cell 238 * \param c1 The index of the second cell 239 * \param c2 The index of the second cell 240 * \param [out] sharedVert The index of the shared vertex, if it exists 241 * \return true if the three cells have a vertex in common 242 * (returned in sharedVert), false otherwise 243 */ haveSharedVertex(CellIndex c0,CellIndex c1,CellIndex c2,VertexIndex & sharedVert) const244 bool haveSharedVertex(CellIndex c0, 245 CellIndex c1, 246 CellIndex c2, 247 VertexIndex& sharedVert) const 248 { 249 CellVertIndices c0Verts = cellVertexIndices(c0); 250 CellVertIndices c1Verts = cellVertexIndices(c1); 251 CellVertIndices c2Verts = cellVertexIndices(c2); 252 253 for(int i = 0; i < NUM_CELL_VERTS; ++i) 254 { 255 // check if a vertex from the third cell is in the first and second 256 if(getDerived()->incidentInVertex(c0Verts, c2Verts[i]) && 257 getDerived()->incidentInVertex(c1Verts, c2Verts[i])) 258 { 259 sharedVert = c2Verts[i]; 260 return true; 261 } 262 } 263 264 return false; 265 } 266 267 protected: 268 SurfaceMesh*& m_surfaceMesh; // ref to pointer to allow changing the mesh 269 270 MeshVertexSet m_vertexSet {0}; 271 MeshElementSet m_elementSet {0}; 272 273 VertexPositionMap m_vertexPositions; 274 275 std::vector<VertexIndex> m_cv_data; 276 CellVertexRelation m_cellToVertexRelation; 277 278 bool m_meshWasReindexed {false}; 279 }; 280 281 template <> 282 class MeshWrapper<2> : public SimplexMeshWrapper<2, MeshWrapper<2>> 283 { 284 public: 285 static constexpr int DIM = 2; 286 using Base = SimplexMeshWrapper<DIM, MeshWrapper<DIM>>; 287 using Base::CellIndex; 288 using Base::GeometricBoundingBox; 289 using Base::SpacePt; 290 using Base::SpaceVector; 291 using Base::SurfaceMesh; 292 using Base::VertexIndex; 293 using Base::VertexIndexMap; 294 using Base::VertexPositionMap; 295 296 /// \brief A constant for the number of boundary vertices in an edge */ 297 static constexpr int NUM_EDGE_VERTS = DIM; 298 using SpaceCell = primal::Segment<double, NUM_EDGE_VERTS>; 299 300 public: 301 /// \brief Constructor for a mesh wrapper */ MeshWrapper(SurfaceMesh * & meshPtr)302 MeshWrapper(SurfaceMesh*& meshPtr) : Base(meshPtr) { } 303 304 /** 305 * \brief Helper function to compute the bounding box of an edge 306 * 307 * \param idx The edges's index within the surface mesh 308 */ cellBoundingBox(CellIndex idx) const309 GeometricBoundingBox cellBoundingBox(CellIndex idx) const 310 { 311 // Get the ids of the verts bounding this edge 312 CellVertIndices vertIds = cellVertexIndices(idx); 313 return GeometricBoundingBox(vertexPosition(vertIds[0]), 314 vertexPosition(vertIds[1])); 315 } 316 317 /** 318 * \brief Utility function to retrieve the positions of the edge's vertices 319 * 320 * \return A SpaceCell (Segment) whose vertices are positioned in space 321 */ cellPositions(CellIndex idx) const322 SpaceCell cellPositions(CellIndex idx) const 323 { 324 CellVertIndices verts = cellVertexIndices(idx); 325 return SpaceCell(vertexPosition(verts[0]), vertexPosition(verts[1])); 326 } 327 328 /// \brief Checks whether the indexed segment contains a reference to the given vertex incidentInVertex(const CellVertIndices & ids,VertexIndex vIdx) const329 bool incidentInVertex(const CellVertIndices& ids, VertexIndex vIdx) const 330 { 331 return (ids[0] == vIdx) || (ids[1] == vIdx); 332 } 333 334 /** 335 * \brief Returns the normal vector of the surface for the cell with index \a cidx 336 * 337 * If we are at an endpoint of the segment (i.e. if the \a segmentParameter is close 338 * to 0 or 1), we compute the average normal of its incident segments 339 */ 340 template <typename CellIndexSet> surfaceNormal(CellIndex cidx,double segmentParameter,const CellIndexSet & otherCells) const341 SpaceVector surfaceNormal(CellIndex cidx, 342 double segmentParameter, 343 const CellIndexSet& otherCells) const 344 { 345 SpaceVector vec = this->cellPositions(cidx).template normal<2>(); 346 347 // Check if the point is at the first vertex of the segment 348 if(axom::utilities::isNearlyEqual(segmentParameter, 0.)) 349 { 350 vec = vec.unitVector(); 351 for(auto idx : otherCells) 352 { 353 auto vidx = cellVertexIndices(cidx)[0]; 354 if(idx != cidx && incidentInVertex(cellVertexIndices(idx), vidx)) 355 { 356 vec += this->cellPositions(idx).template normal<2>().unitVector(); 357 } 358 } 359 } 360 // Check if the point is at the second vertex of the segment 361 else if(axom::utilities::isNearlyEqual(segmentParameter, 1.)) 362 { 363 vec = vec.unitVector(); 364 for(auto idx : otherCells) 365 { 366 auto vidx = cellVertexIndices(cidx)[1]; 367 if(idx != cidx && incidentInVertex(cellVertexIndices(idx), vidx)) 368 { 369 vec += this->cellPositions(idx).template normal<2>().unitVector(); 370 } 371 } 372 } 373 374 return vec.unitVector(); 375 } 376 377 /** 378 * \brief Reindexes the mesh vertices and edge indices using the given map 379 * 380 * \param numVertices The number of vertices in the new mesh 381 * \param vertexIndexMap A mapping from the old vertex indices to the new ones 382 * \note This step clears out the original mesh, 383 * which can be reconstructed using the regenerateSurfaceMesh() function 384 */ reindexMesh(int numVertices,const VertexIndexMap & vertexIndexMap)385 void reindexMesh(int numVertices, const VertexIndexMap& vertexIndexMap) 386 { 387 // Create a vertex set on the new vertices and grab coordinates from the old ones 388 m_vertexSet = MeshVertexSet(numVertices); 389 m_vertexPositions = VertexPositionMap(&m_vertexSet); 390 391 int numOrigVertices = numMeshVertices(); 392 for(int i = 0; i < numOrigVertices; ++i) 393 { 394 const VertexIndex& vInd = vertexIndexMap[i]; 395 m_vertexPositions[vInd] = getMeshVertexPosition(i); 396 } 397 398 // Update the vertex IDs of the triangles to the new vertices 399 // and create a SLAM relation on these 400 int numOrigEdges = numMeshCells(); 401 402 m_cv_data.clear(); 403 m_cv_data.reserve(NUM_EDGE_VERTS * numOrigEdges); 404 for(axom::IndexType i = 0; i < numOrigEdges; ++i) 405 { 406 // Grab relation from mesh 407 using UMesh = mint::UnstructuredMesh<mint::SINGLE_SHAPE>; 408 axom::IndexType* vertIds = 409 static_cast<UMesh*>(m_surfaceMesh)->getCellNodeIDs(i); 410 411 // Remap the vertex IDs 412 for(int j = 0; j < NUM_EDGE_VERTS; ++j) 413 vertIds[j] = vertexIndexMap[vertIds[j]]; 414 415 // Add to relation if not degenerate edge 416 // (namely, we need 2 unique vertex IDs) 417 if((vertIds[0] != vertIds[1])) 418 { 419 m_cv_data.push_back(vertIds[0]); 420 m_cv_data.push_back(vertIds[1]); 421 } 422 } 423 424 m_elementSet = 425 MeshElementSet(static_cast<int>(m_cv_data.size()) / NUM_EDGE_VERTS); 426 m_cellToVertexRelation = CellVertexRelation(&m_elementSet, &m_vertexSet); 427 m_cellToVertexRelation.bindIndices(static_cast<int>(m_cv_data.size()), 428 &m_cv_data); 429 430 // Delete old mesh, and NULL its pointer 431 delete m_surfaceMesh; 432 m_surfaceMesh = nullptr; 433 434 m_meshWasReindexed = true; 435 } 436 437 /// \brief Add the vertex positions and edge boundary relations to the surface mesh regenerateSurfaceMesh()438 void regenerateSurfaceMesh() 439 { 440 if(m_surfaceMesh != nullptr) 441 { 442 delete m_surfaceMesh; 443 m_surfaceMesh = nullptr; 444 } 445 446 using UMesh = mint::UnstructuredMesh<mint::SINGLE_SHAPE>; 447 UMesh* edgeMesh = 448 new UMesh(DIM, mint::SEGMENT, m_vertexSet.size(), m_elementSet.size()); 449 450 // Add vertices to the mesh (i.e. vertex positions) 451 for(int i = 0; i < m_vertexSet.size(); ++i) 452 { 453 const SpacePt& pt = vertexPosition(i); 454 edgeMesh->appendNode(pt[0], pt[1]); 455 } 456 457 // Add edges to the mesh (i.e. boundary vertices) 458 for(int i = 0; i < m_elementSet.size(); ++i) 459 { 460 const CellIndex* tv = &cellVertexIndices(i)[0]; 461 edgeMesh->appendCell(tv); 462 } 463 464 m_surfaceMesh = edgeMesh; 465 } 466 }; 467 468 template <> 469 class MeshWrapper<3> : public SimplexMeshWrapper<3, MeshWrapper<3>> 470 { 471 public: 472 static constexpr int DIM = 3; 473 using Base = SimplexMeshWrapper<DIM, MeshWrapper<DIM>>; 474 using Base::CellIndex; 475 using Base::GeometricBoundingBox; 476 using Base::SpacePt; 477 using Base::SurfaceMesh; 478 using Base::VertexIndex; 479 using Base::VertexIndexMap; 480 using Base::VertexPositionMap; 481 482 /// \brief A constant for the number of boundary vertices in a triangle */ 483 static constexpr int NUM_TRI_VERTS = 3; 484 using SpaceCell = primal::Triangle<double, DIM>; 485 486 public: 487 /// \brief Constructor for a mesh wrapper */ MeshWrapper(SurfaceMesh * & meshPtr)488 MeshWrapper(SurfaceMesh*& meshPtr) : Base(meshPtr) { } 489 490 /** 491 * \brief Helper function to compute the bounding box of a triangle 492 * 493 * \param idx The triangle's index within the surface mesh 494 */ cellBoundingBox(CellIndex idx) const495 GeometricBoundingBox cellBoundingBox(CellIndex idx) const 496 { 497 // Get the ids of the verts bounding this triangle 498 CellVertIndices vertIds = cellVertexIndices(idx); 499 500 GeometricBoundingBox bb(vertexPosition(vertIds[0])); 501 bb.addPoint(vertexPosition(vertIds[1])); 502 bb.addPoint(vertexPosition(vertIds[2])); 503 504 return bb; 505 } 506 507 /** 508 * \brief Utility function to retrieve the positions of the triangle's vertices 509 * 510 * \return A SpaceCell (Triangle) whose vertices are positioned in space 511 */ cellPositions(CellIndex idx) const512 SpaceCell cellPositions(CellIndex idx) const 513 { 514 CellVertIndices verts = cellVertexIndices(idx); 515 return SpaceCell(vertexPosition(verts[0]), 516 vertexPosition(verts[1]), 517 vertexPosition(verts[2])); 518 } 519 520 /// \brief Checks whether the indexed triangle contains a reference to the given vertex incidentInVertex(const CellVertIndices & ids,VertexIndex vIdx) const521 bool incidentInVertex(const CellVertIndices& ids, VertexIndex vIdx) const 522 { 523 return (ids[0] == vIdx) || (ids[1] == vIdx) || (ids[2] == vIdx); 524 } 525 526 /** 527 * \brief Reindexes the mesh vertices and triangle indices using the given map 528 * 529 * \param numVertices The number of vertices in the new mesh 530 * \param vertexIndexMap A mapping from the old vertex indices to the new ones 531 * \note This step clears out the original mesh, 532 * which can be reconstructed using the regenerateSurfaceMesh() function 533 */ reindexMesh(int numVertices,const VertexIndexMap & vertexIndexMap)534 void reindexMesh(int numVertices, const VertexIndexMap& vertexIndexMap) 535 { 536 // Create a vertex set on the new vertices and grab coordinates from the old ones 537 m_vertexSet = MeshVertexSet(numVertices); 538 m_vertexPositions = VertexPositionMap(&m_vertexSet); 539 540 int numOrigVertices = numMeshVertices(); 541 for(int i = 0; i < numOrigVertices; ++i) 542 { 543 const VertexIndex& vInd = vertexIndexMap[i]; 544 m_vertexPositions[vInd] = getMeshVertexPosition(i); 545 } 546 547 // Update the vertex IDs of the triangles to the new vertices 548 // and create a SLAM relation on these 549 int numOrigTris = numMeshCells(); 550 551 m_cv_data.clear(); 552 m_cv_data.reserve(NUM_TRI_VERTS * numOrigTris); 553 for(axom::IndexType i = 0; i < numOrigTris; ++i) 554 { 555 // Grab relation from mesh 556 using UMesh = mint::UnstructuredMesh<mint::SINGLE_SHAPE>; 557 axom::IndexType* vertIds = 558 static_cast<UMesh*>(m_surfaceMesh)->getCellNodeIDs(i); 559 560 // Remap the vertex IDs 561 for(int j = 0; j < NUM_TRI_VERTS; ++j) 562 vertIds[j] = vertexIndexMap[vertIds[j]]; 563 564 // Add to relation if not degenerate triangles 565 // (namely, we need 3 unique vertex IDs) 566 if((vertIds[0] != vertIds[1]) && (vertIds[1] != vertIds[2]) && 567 (vertIds[2] != vertIds[0])) 568 { 569 m_cv_data.push_back(vertIds[0]); 570 m_cv_data.push_back(vertIds[1]); 571 m_cv_data.push_back(vertIds[2]); 572 } 573 } 574 575 m_elementSet = 576 MeshElementSet(static_cast<int>(m_cv_data.size()) / NUM_TRI_VERTS); 577 m_cellToVertexRelation = CellVertexRelation(&m_elementSet, &m_vertexSet); 578 m_cellToVertexRelation.bindIndices(static_cast<int>(m_cv_data.size()), 579 &m_cv_data); 580 581 // Delete old mesh, and NULL its pointer 582 delete m_surfaceMesh; 583 m_surfaceMesh = nullptr; 584 585 m_meshWasReindexed = true; 586 } 587 588 /// \brief Add the vertex positions and triangle boundary relations to the surface mesh regenerateSurfaceMesh()589 void regenerateSurfaceMesh() 590 { 591 if(m_surfaceMesh != nullptr) 592 { 593 delete m_surfaceMesh; 594 m_surfaceMesh = nullptr; 595 } 596 597 using UMesh = mint::UnstructuredMesh<mint::SINGLE_SHAPE>; 598 UMesh* triMesh = 599 new UMesh(DIM, mint::TRIANGLE, m_vertexSet.size(), m_elementSet.size()); 600 601 // Add vertices to the mesh (i.e. vertex positions) 602 for(int i = 0; i < m_vertexSet.size(); ++i) 603 { 604 const SpacePt& pt = vertexPosition(i); 605 triMesh->appendNode(pt[0], pt[1], pt[2]); 606 } 607 608 // Add triangles to the mesh (i.e. boundary vertices) 609 for(int i = 0; i < m_elementSet.size(); ++i) 610 { 611 const CellIndex* tv = &cellVertexIndices(i)[0]; 612 triMesh->appendCell(tv); 613 } 614 615 m_surfaceMesh = triMesh; 616 } 617 }; 618 619 } // namespace quest 620 } // namespace axom 621 622 #endif // AXOM_QUEST_INOUT_OCTREE_MESH_WRAPPER__HPP_ 623