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