libMesh
Public Member Functions | Protected Types | Protected Member Functions | Static Protected Member Functions | Protected Attributes | List of all members
libMesh::NetGenMeshInterface Class Reference

Class NetGenMeshInterface provides an interface for tetrahedralization of meshes using the NetGen library. More...

#include <mesh_netgen_interface.h>

Inheritance diagram for libMesh::NetGenMeshInterface:
[legend]

Public Member Functions

 NetGenMeshInterface (UnstructuredMesh &mesh)
 Constructor. More...
 
virtual ~NetGenMeshInterface () override=default
 Empty destructor. More...
 
virtual void triangulate () override
 Method invokes NetGen library to compute a tetrahedralization. More...
 
Realdesired_volume ()
 Sets and/or gets the desired tetrahedron volume. More...
 
bool & smooth_after_generating ()
 Sets/gets flag which tells whether to do two steps of Laplace mesh smoothing after generating the grid. More...
 
ElemTypeelem_type ()
 Sets and/or gets the desired element type. More...
 
void attach_hole_list (std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>> holes)
 Attaches a vector of Mesh pointers defining holes which will be meshed around. More...
 
void set_verbosity (unsigned int v)
 Sets a verbosity level, defaulting to 0 (print nothing), to be set as high as 100 (print everything). More...
 

Protected Types

enum  SurfaceIntegrity {
  NON_TRI3 = 1, MISSING_NEIGHBOR = 2, EMPTY_MESH = 3, MISSING_BACKLINK = 4,
  BAD_NEIGHBOR_NODES = 5, NON_ORIENTED = 6, BAD_NEIGHBOR_LINKS = 7, DEGENERATE_ELEMENT = 8,
  DEGENERATE_MESH = 9
}
 Enumeration of possible surface mesh integrity issues. More...
 

Protected Member Functions

std::set< SurfaceIntegritycheck_hull_integrity () const
 This function checks the integrity of the current set of elements in the Mesh to see if they comprise a topological manifold that (if it's also geometrically valid) would define valid boundary for a tetrahedralized volume. More...
 
std::set< SurfaceIntegrityimprove_hull_integrity ()
 This function checks the integrity of the current set of elements in the Mesh, and corrects what it can. More...
 
void process_hull_integrity_result (const std::set< SurfaceIntegrity > &result) const
 This function prints an informative message and throws an exception based on the output of the check_hull_integrity() function. More...
 
void delete_2D_hull_elements ()
 Delete original convex hull elements from the Mesh after performing a Delaunay tetrahedralization. More...
 

Static Protected Member Functions

static BoundingBox volume_to_surface_mesh (UnstructuredMesh &mesh)
 Remove volume elements from the given mesh, after converting their outer boundary faces to surface elements. More...
 

Protected Attributes

MeshSerializer _serializer
 Tetgen only operates on serial meshes. More...
 
unsigned int _verbosity
 verbosity setting More...
 
Real _desired_volume
 The desired volume for the elements in the resulting mesh. More...
 
bool _smooth_after_generating
 Flag which tells whether we should smooth the mesh after it is generated. More...
 
ElemType _elem_type
 The exact type of tetrahedra we intend to construct. More...
 
UnstructuredMesh_mesh
 Local reference to the mesh we are working with. More...
 
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
 A pointer to a vector of meshes each defining a hole. More...
 

Detailed Description

Class NetGenMeshInterface provides an interface for tetrahedralization of meshes using the NetGen library.

For information about TetGen cf. NetGen github repository.

Author
Roy H. Stogner
Date
2024

Definition at line 52 of file mesh_netgen_interface.h.

Member Enumeration Documentation

◆ SurfaceIntegrity

Enumeration of possible surface mesh integrity issues.

Enumerator
NON_TRI3 
MISSING_NEIGHBOR 
EMPTY_MESH 
MISSING_BACKLINK 
BAD_NEIGHBOR_NODES 
NON_ORIENTED 
BAD_NEIGHBOR_LINKS 
DEGENERATE_ELEMENT 
DEGENERATE_MESH 

Definition at line 124 of file mesh_tet_interface.h.

124  {
125  NON_TRI3 = 1, // a non-TRI3 element is found
126  MISSING_NEIGHBOR = 2, // an element with a nullptr-neighbor is found
127  EMPTY_MESH = 3, // the mesh is empty
128  MISSING_BACKLINK = 4, // an element neighbor isn't linked back to it
129  BAD_NEIGHBOR_NODES = 5, // an element neighbor isn't linked to expected nodes
130  NON_ORIENTED = 6, // an element neighbor has inconsistent orientation
131  BAD_NEIGHBOR_LINKS = 7, // an element neighbor has other inconsistent links
132  DEGENERATE_ELEMENT = 8, // an element has zero area
133  DEGENERATE_MESH = 9 // the mesh clearly bounds zero volume
134  };

Constructor & Destructor Documentation

◆ NetGenMeshInterface()

libMesh::NetGenMeshInterface::NetGenMeshInterface ( UnstructuredMesh mesh)
explicit

Constructor.

Takes a reference to the mesh containing the triangulated surface which is to be tetrahedralized.

Definition at line 75 of file mesh_netgen_interface.C.

75  :
78 {
79 }
MeshBase & mesh
MeshTetInterface(UnstructuredMesh &mesh)
Constructor.
MeshSerializer _serializer
Tetgen only operates on serial meshes.

◆ ~NetGenMeshInterface()

virtual libMesh::NetGenMeshInterface::~NetGenMeshInterface ( )
overridevirtualdefault

Empty destructor.

Member Function Documentation

◆ attach_hole_list()

void libMesh::MeshTetInterface::attach_hole_list ( std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>>  holes)
inherited

Attaches a vector of Mesh pointers defining holes which will be meshed around.

We use unique_ptr here because we expect that we may need to modify these meshes internally.

Definition at line 128 of file mesh_tet_interface.C.

Referenced by MeshTetTest::testHole(), and MeshTetTest::testSphereShell().

129 {
130  _holes = std::move(holes);
131 }
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
A pointer to a vector of meshes each defining a hole.

◆ check_hull_integrity()

std::set< MeshTetInterface::SurfaceIntegrity > libMesh::MeshTetInterface::check_hull_integrity ( ) const
protectedinherited

This function checks the integrity of the current set of elements in the Mesh to see if they comprise a topological manifold that (if it's also geometrically valid) would define valid boundary for a tetrahedralized volume.

Named check_hull_integrity() for backward compatibility, but now accepts non-convex manifolds.

Returns
a set of enums describing problems found, or an empty set if no problems are found.

Definition at line 341 of file mesh_tet_interface.C.

References libMesh::MeshTetInterface::_mesh, libMesh::MeshTetInterface::_verbosity, libMesh::MeshBase::active_local_element_stored_range(), libMesh::MeshTetInterface::BAD_NEIGHBOR_LINKS, libMesh::MeshTetInterface::BAD_NEIGHBOR_NODES, libMesh::ParallelObject::comm(), libMesh::MeshTools::create_bounding_box(), libMesh::MeshTetInterface::DEGENERATE_ELEMENT, libMesh::MeshTetInterface::DEGENERATE_MESH, libMesh::MeshTetInterface::EMPTY_MESH, libMesh::Elem::get_info(), libMesh::DofObject::id(), int, libMesh::Elem::local_node(), libMesh::BoundingBox::max(), libMesh::BoundingBox::min(), libMesh::MeshTetInterface::MISSING_BACKLINK, libMesh::MeshTetInterface::MISSING_NEIGHBOR, libMesh::MeshBase::n_elem(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ptr(), libMesh::MeshTetInterface::NON_ORIENTED, libMesh::MeshTetInterface::NON_TRI3, libMesh::Threads::parallel_reduce(), libMesh::Real, TIMPI::Communicator::set_union(), libMesh::Elem::side_index_range(), libMesh::TOLERANCE, libMesh::TRI3, libMesh::Elem::type(), libMesh::Elem::volume(), and libMesh::Elem::which_neighbor_am_i().

Referenced by libMesh::MeshTetInterface::improve_hull_integrity().

342 {
343  // Check for easy return: if the Mesh is empty (i.e. if
344  // somebody called triangulate_conformingDelaunayMesh on
345  // a Mesh with no elements, then hull integrity check must
346  // fail...
347  if (_mesh.n_elem() == 0)
348  return {EMPTY_MESH};
349 
350  std::set<MeshTetInterface::SurfaceIntegrity> returnval;
351 
353  const Point extents = bb.max() - bb.min();
354  if (extents(0) == 0 ||
355  extents(1) == 0 ||
356  extents(2) == 0)
357  returnval.insert(DEGENERATE_MESH);
358 
359  // Figure a area to use for relative tolerances when detecting
360  // degenerate elements
361  const Real ref_area = std::abs(extents(0) * extents(1)) +
362  std::abs(extents(0) * extents(2)) +
363  std::abs(extents(1) * extents(2));
364 
365  struct TriChecker {
366  std::set<MeshTetInterface::SurfaceIntegrity> my_returnval;
367  const Real my_ref_area;
368  const unsigned int my_verbosity;
369 
370  TriChecker (Real ref_area, unsigned int verbosity) :
371  my_returnval(), my_ref_area(ref_area),
372  my_verbosity(verbosity) {}
373  TriChecker (TriChecker & other, Threads::split) :
374  my_returnval(), my_ref_area(other.my_ref_area),
375  my_verbosity(other.my_verbosity) {}
376 
377  void operator()(const ConstElemRange & range) {
378 
379  for (const Elem * elem : range)
380  {
381  // Check for proper element type
382  if (elem->type() != TRI3)
383  {
384  if (my_verbosity >= 50)
385  std::cerr << "Non-Tri3: " << elem->get_info() << std::endl;
386  my_returnval.insert(NON_TRI3);
387  }
388 
389  // Make sure it's a decent element.
390  if (elem->volume() < my_ref_area * TOLERANCE * TOLERANCE)
391  {
392  if (my_verbosity >= 50)
393  std::cerr << "Degenerate element: " << elem->get_info() << std::endl;
394  my_returnval.insert(DEGENERATE_ELEMENT);
395  }
396 
397  for (auto s : elem->side_index_range())
398  {
399  const Elem * const neigh = elem->neighbor_ptr(s);
400 
401  if (neigh == nullptr)
402  {
403  if (my_verbosity >= 50)
404  std::cerr << "Element missing neighbor " << s << ": " << elem->get_info() << std::endl;
405  my_returnval.insert(MISSING_NEIGHBOR);
406  continue;
407  }
408 
409  // Make sure our neighbor points back to us
410  const unsigned int nn = neigh->which_neighbor_am_i(elem);
411 
412  if (nn >= 3)
413  {
414  if (my_verbosity >= 50)
415  std::cerr << "Element missing backlink " << s << ": " << elem->get_info() << std::endl;
416  my_returnval.insert(MISSING_BACKLINK);
417  continue;
418  }
419 
420  // Our neighbor should have the same the edge nodes we do on
421  // the neighboring edgei
422  const Node * const n1 = elem->node_ptr(s);
423  const Node * const n2 = elem->node_ptr((s+1)%3);
424 
425  const unsigned int i1 = neigh->local_node(n1->id());
426  const unsigned int i2 = neigh->local_node(n2->id());
427  if (i1 >= 3 || i2 >= 3)
428  {
429  if (my_verbosity >= 50)
430  std::cerr << "Element with bad neighbor " << s << " nodes: " << elem->get_info() << std::endl;
431  my_returnval.insert(BAD_NEIGHBOR_NODES);
432  continue;
433  }
434 
435  // It should have those edge nodes in the opposite order
436  // (because they have the same orientation we do)
437  if ((i2 + 1)%3 != i1)
438  {
439  if (my_verbosity >= 50)
440  std::cerr << "Element orientation mismatch with neighbor " << s << ": " << elem->get_info() << std::endl;
441  my_returnval.insert(NON_ORIENTED);
442  continue;
443  }
444 
445  // And it should have those edge nodes in the expected
446  // places relative to its neighbor link
447  if (i2 != nn)
448  {
449  if (my_verbosity >= 50)
450  std::cerr << "Element with bad links on neighbor " << s << ": " << elem->get_info() << std::endl;
451  my_returnval.insert(BAD_NEIGHBOR_LINKS);
452  continue;
453  }
454  }
455  }
456  }
457 
458  void join(TriChecker & other) {
459  my_returnval.merge(other.my_returnval);
460  }
461  };
462 
463  TriChecker checker (ref_area, this->_verbosity);
464 
466  (this->_mesh.active_local_element_stored_range(), checker);
467 
468  // Join problems found in threaded loop
469  returnval.merge(checker.my_returnval);
470 
471  // Join problems found on other ranks
472  std::set<char> int_set;
473  std::transform
474  (returnval.begin(), returnval.end(),
475  std::inserter<std::set<char>>(int_set, int_set.end()),
476  [](SurfaceIntegrity i){return int(i);});
477  _mesh.comm().set_union(int_set);
478  std::transform
479  (int_set.begin(), int_set.end(),
480  std::inserter<std::set<SurfaceIntegrity>>(returnval, returnval.end()),
481  [](int i){return SurfaceIntegrity(i);});
482 
483  return returnval;
484 }
A Node is like a Point, but with more information.
Definition: node.h:52
std::string get_info() const
Prints relevant information about the element to a string.
Definition: elem.C:2956
libMesh::BoundingBox create_bounding_box(const MeshBase &mesh)
Definition: mesh_tools.C:566
static constexpr Real TOLERANCE
Dummy "splitting object" used to distinguish splitting constructors from copy constructors.
Definition: threads_none.h:63
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
const Parallel::Communicator & comm() const
The StoredRange class defines a contiguous, divisible set of objects.
Definition: stored_range.h:54
SurfaceIntegrity
Enumeration of possible surface mesh integrity issues.
dof_id_type id() const
Definition: dof_object.h:819
unsigned int which_neighbor_am_i(const Elem *e) const
This function tells you which neighbor e is.
Definition: elem.h:2933
const Point & min() const
Definition: bounding_box.h:77
const ConstElemRange & active_local_element_stored_range() const
Definition: mesh_base.C:1928
void parallel_reduce(const Range &range, Body &body, unsigned int n_threads=libMesh::n_threads())
Execute the provided reduction operation in parallel on the specified range.
Definition: threads_none.h:109
Defines a Cartesian bounding box by the two corner extremum.
Definition: bounding_box.h:40
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2612
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
unsigned int local_node(const dof_id_type i) const
Definition: elem.h:2493
const Point & max() const
Definition: bounding_box.h:86
virtual dof_id_type n_elem() const =0
unsigned int _verbosity
verbosity setting
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
void ErrorVector unsigned int
Definition: adjoints_ex3.C:360
void set_union(T &data, const unsigned int root_id) const

◆ delete_2D_hull_elements()

void libMesh::MeshTetInterface::delete_2D_hull_elements ( )
protectedinherited

Delete original convex hull elements from the Mesh after performing a Delaunay tetrahedralization.

Definition at line 685 of file mesh_tet_interface.C.

References libMesh::MeshTetInterface::_mesh, libMesh::MeshBase::delete_elem(), libMesh::MeshBase::get_boundary_info(), libMesh::BoundaryInfo::regenerate_id_sets(), libMesh::TRI3, and libMesh::Elem::type().

Referenced by libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

686 {
687  for (auto & elem : this->_mesh.element_ptr_range())
688  {
689  // Check for proper element type. Yes, we legally delete elements while
690  // iterating over them because no entries from the underlying container
691  // are actually erased.
692  if (elem->type() == TRI3)
693  _mesh.delete_elem(elem);
694  }
695 
696  // We just removed any boundary info associated with hull element
697  // edges, so let's update the boundary id caches.
699 }
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
void regenerate_id_sets()
Clears and regenerates the cached sets of ids.
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.

◆ desired_volume()

Real& libMesh::MeshTetInterface::desired_volume ( )
inlineinherited

Sets and/or gets the desired tetrahedron volume.

Set to zero to disable volume constraint.

Definition at line 68 of file mesh_tet_interface.h.

References libMesh::MeshTetInterface::_desired_volume.

Referenced by main(), and MeshTetTest::testSphereShell().

68 {return _desired_volume;}
Real _desired_volume
The desired volume for the elements in the resulting mesh.

◆ elem_type()

ElemType& libMesh::MeshTetInterface::elem_type ( )
inlineinherited

Sets and/or gets the desired element type.

This should be a Tet type.

Definition at line 81 of file mesh_tet_interface.h.

References libMesh::MeshTetInterface::_elem_type.

81 {return _elem_type;}
ElemType _elem_type
The exact type of tetrahedra we intend to construct.

◆ improve_hull_integrity()

std::set< MeshTetInterface::SurfaceIntegrity > libMesh::MeshTetInterface::improve_hull_integrity ( )
protectedinherited

This function checks the integrity of the current set of elements in the Mesh, and corrects what it can.

Returns
A set of SurfaceIntegrity codes from check_hull_integrity() if there are problems it can't fix, or an empty set otherwise.

Definition at line 488 of file mesh_tet_interface.C.

References libMesh::MeshTetInterface::_mesh, libMesh::MeshTetInterface::BAD_NEIGHBOR_LINKS, libMesh::MeshTetInterface::check_hull_integrity(), libMesh::ParallelObject::comm(), libMesh::TypeVector< T >::cross(), libMesh::MeshBase::elem_ptr(), libMesh::MeshBase::elem_ref(), libMesh::MeshTetInterface::EMPTY_MESH, libMesh::UnstructuredMesh::find_neighbors(), libMesh::Elem::flip(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::libmesh_assert(), libMesh::Elem::local_node(), libMesh::MeshTetInterface::MISSING_BACKLINK, libMesh::MeshTetInterface::MISSING_NEIGHBOR, libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ptr(), libMesh::Elem::node_ref_range(), libMesh::MeshTetInterface::NON_ORIENTED, libMesh::MeshTetInterface::NON_TRI3, libMesh::Elem::point(), libMesh::Real, libMesh::Elem::side_index_range(), and libMesh::Elem::which_neighbor_am_i().

Referenced by triangulate(), and libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

489 {
490  // We don't really do anything parallel here, but we aspire to.
491  libmesh_parallel_only(this->_mesh.comm());
492 
493  std::set<MeshTetInterface::SurfaceIntegrity> integrityproblems =
494  this->check_hull_integrity();
495 
496  // If we have no problem, or a problem we can't fix, we're done.
497  if (integrityproblems.empty() ||
498  integrityproblems.count(NON_TRI3) ||
499  integrityproblems.count(EMPTY_MESH))
500  return integrityproblems;
501 
502  // Possibly the user gave us an unprepared mesh with missing or bad
503  // neighbor links?
504  if (integrityproblems.count(MISSING_NEIGHBOR) ||
505  integrityproblems.count(MISSING_BACKLINK) ||
506  integrityproblems.count(BAD_NEIGHBOR_LINKS))
507  {
508  this->_mesh.find_neighbors();
509  integrityproblems = this->check_hull_integrity();
510  }
511 
512  // If find_neighbors() doesn't fix these, I give up.
513  if (integrityproblems.count(MISSING_NEIGHBOR) ||
514  integrityproblems.count(MISSING_BACKLINK) ||
515  integrityproblems.count(BAD_NEIGHBOR_LINKS))
516  return integrityproblems;
517 
518  // find_neighbors() might have fixed everything
519  if (integrityproblems.empty())
520  return integrityproblems;
521 
522  // A non-oriented (but orientable!) surface is the only thing we
523  // shouldn't have fixed or given up on by now.
524  libmesh_assert_equal_to(integrityproblems.size(), 1);
525  libmesh_assert_equal_to(integrityproblems.count(NON_ORIENTED), 1);
526 
527  // We need one known-good triangle to start from. We'll pick the
528  // most-negative-x normal among the triangles on the most-negative-x
529  // point.
530 
531  // We'll just implement this in serial for now.
532  MeshSerializer mesh_serializer(this->_mesh);
533 
534  // I don't see why we'd need boundary info here, but maybe we'll
535  // want to preserve edge/node conditions eventually?
536  BoundaryInfo & bi = this->_mesh.get_boundary_info();
537 
538  const Node * lowest_point = (*this->_mesh.elements_begin())->node_ptr(0);
539 
540  // Index by ids, not pointers, for consistency in parallel
541  std::unordered_set<dof_id_type> attached_elements;
542 
543  for (Elem * elem : this->_mesh.element_ptr_range())
544  {
545  for (const Node & node : elem->node_ref_range())
546  {
547  if (node(0) < (*lowest_point)(0))
548  {
549  lowest_point = &node;
550  attached_elements.clear();
551  }
552  if (&node == lowest_point)
553  attached_elements.insert(elem->id());
554  }
555  }
556 
557  Elem * best_elem = nullptr;
558  Real best_abs_normal_0 = 0;
559 
560  for (dof_id_type id : attached_elements)
561  {
562  Elem * elem = this->_mesh.elem_ptr(id);
563  const Point e01 = elem->point(1) - elem->point(0);
564  const Point e02 = elem->point(2) - elem->point(0);
565  const Point normal = e01.cross(e02).unit();
566  const Real abs_normal_0 = std::abs(normal(0));
567 
568  if (!best_elem || abs_normal_0 > best_abs_normal_0)
569  {
570  best_elem = elem;
571  best_abs_normal_0 = abs_normal_0;
572 
573  // Make sure that element is actually a good one, by
574  // flipping it if it's not.
575  if (abs_normal_0 == normal(0))
576  elem->flip(&bi);
577  }
578  }
579 
580  // Now flood-fill from that element to get a consistent orientation
581  // for the others.
582  std::unordered_set<dof_id_type> frontier_elements{best_elem->id()},
583  finished_elements{};
584 
585  while (!frontier_elements.empty())
586  {
587  const dof_id_type elem_id = *frontier_elements.begin();
588  Elem & elem = this->_mesh.elem_ref(elem_id);
589  for (auto s : elem.side_index_range())
590  {
591  Elem * neigh = elem.neighbor_ptr(s);
592  libmesh_assert(neigh);
593  libmesh_assert_less(neigh->which_neighbor_am_i(&elem), 3);
594 
595  const Node * const n1 = elem.node_ptr(s);
596  const Node * const n2 = elem.node_ptr((s+1)%3);
597  const unsigned int i1 = neigh->local_node(n1->id());
598  const unsigned int i2 = neigh->local_node(n2->id());
599  libmesh_assert_less(i1, 3);
600  libmesh_assert_less(i2, 3);
601 
602  const dof_id_type neigh_id = neigh->id();
603 
604  const bool frontier_neigh = frontier_elements.count(neigh_id);
605  const bool finished_neigh = finished_elements.count(neigh_id);
606 
607  // Are we flipped?
608  if ((i2 + 1)%3 != i1)
609  {
610  // Are we a Moebius strip??? We give up.
611  if (frontier_neigh || finished_neigh)
612  return integrityproblems;
613 
614  neigh->flip(&bi);
615  }
616 
617  if (!frontier_neigh && !finished_neigh)
618  frontier_elements.insert(neigh_id);
619  }
620 
621  finished_elements.insert(elem_id);
622  frontier_elements.erase(elem_id);
623  }
624 
625  this->_mesh.find_neighbors();
626 
627  libmesh_assert(this->check_hull_integrity().empty());
628 
629  return {};
630 }
A Node is like a Point, but with more information.
Definition: node.h:52
IntRange< unsigned short > side_index_range() const
Definition: elem.h:2724
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true, const bool assert_valid=true) override
Other functions from MeshBase requiring re-definition.
const Parallel::Communicator & comm() const
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
dof_id_type id() const
Definition: dof_object.h:819
unsigned int which_neighbor_am_i(const Elem *e) const
This function tells you which neighbor e is.
Definition: elem.h:2933
The BoundaryInfo class contains information relevant to boundary conditions including storing faces...
Definition: boundary_info.h:57
libmesh_assert(ctx)
TypeVector< typename CompareTypes< T, T2 >::supertype > cross(const TypeVector< T2 > &v) const
Definition: type_vector.h:885
virtual void flip(BoundaryInfo *boundary_info)=0
Flips the element (by swapping node and neighbor pointers) to have a mapping Jacobian of opposite sig...
virtual const Elem * elem_ptr(const dof_id_type i) const =0
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2612
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::set< SurfaceIntegrity > check_hull_integrity() const
This function checks the integrity of the current set of elements in the Mesh to see if they comprise...
Temporarily serialize a DistributedMesh for non-distributed-mesh capable code paths.
const Node * node_ptr(const unsigned int i) const
Definition: elem.h:2513
virtual const Elem & elem_ref(const dof_id_type i) const
Definition: mesh_base.h:778
unsigned int local_node(const dof_id_type i) const
Definition: elem.h:2493
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Point & point(const unsigned int i) const
Definition: elem.h:2459
uint8_t dof_id_type
Definition: id_types.h:67

◆ process_hull_integrity_result()

void libMesh::MeshTetInterface::process_hull_integrity_result ( const std::set< SurfaceIntegrity > &  result) const
protectedinherited

This function prints an informative message and throws an exception based on the output of the check_hull_integrity() function.

It is a separate function so that you can check hull integrity without exiting or catching an exception if desired.

Definition at line 634 of file mesh_tet_interface.C.

Referenced by triangulate(), and libMesh::TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole().

635 {
636  std::ostringstream err_msg;
637 
638  if (result.empty()) // success
639  return;
640 
641  err_msg << "Error! Conforming Delaunay mesh tetrahedralization requires a convex hull." << std::endl;
642 
643  if (result.count(NON_TRI3))
644  {
645  err_msg << "At least one non-Tri3 element was found in the input boundary mesh. ";
646  err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
647  }
648  if (result.count(MISSING_NEIGHBOR))
649  {
650  err_msg << "At least one triangle without three neighbors was found in the input boundary mesh. ";
651  err_msg << "A constrained Delaunay tetrahedralization boundary must be a triangular manifold without boundary." << std::endl;
652  }
653  if (result.count(EMPTY_MESH))
654  {
655  err_msg << "The input boundary mesh was empty!" << std::endl;
656  err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
657  }
658  if (result.count(MISSING_BACKLINK))
659  {
660  err_msg << "At least one triangle neighbor without a return neighbor link was found in the input boundary mesh. ";
661  err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
662  }
663  if (result.count(BAD_NEIGHBOR_NODES))
664  {
665  err_msg << "At least one triangle neighbor without expected node links was found in the input boundary mesh. ";
666  err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
667  }
668  if (result.count(NON_ORIENTED))
669  {
670  err_msg << "At least one triangle neighbor with an inconsistent orientation was found in the input boundary mesh. ";
671  err_msg << "A constrained Delaunay tetrahedralization boundary must be an oriented Tri3 mesh." << std::endl;
672  }
673  if (result.count(BAD_NEIGHBOR_LINKS))
674  err_msg << "At least one triangle neighbor with inconsistent node and neighbor links was found in the input boundary mesh." << std::endl;
675  if (result.count(DEGENERATE_ELEMENT))
676  err_msg << "At least one input triangle is degenerate, with near-zero area relative to the manifold." << std::endl;
677  if (result.count(DEGENERATE_MESH))
678  err_msg << "The input mesh is degenerate, with zero thickness in at least one direction." << std::endl;
679 
680  libmesh_error_msg(err_msg.str());
681 }

◆ set_verbosity()

void libMesh::MeshTetInterface::set_verbosity ( unsigned int  v)
inlineinherited

Sets a verbosity level, defaulting to 0 (print nothing), to be set as high as 100 (print everything).

For verbosity >= 50, print all detected surface mesh integrity issues as they're found. Subclasses may add other output at other verbosity levels.

Definition at line 208 of file mesh_tet_interface.h.

References libMesh::MeshTetInterface::_verbosity.

Referenced by MeshTetTest::testNetGen().

209 {
210  this->_verbosity = v;
211 }
unsigned int _verbosity
verbosity setting

◆ smooth_after_generating()

bool& libMesh::MeshTetInterface::smooth_after_generating ( )
inlineinherited

Sets/gets flag which tells whether to do two steps of Laplace mesh smoothing after generating the grid.

False by default (for compatibility with old TetGenMeshInterface behavior).

Definition at line 75 of file mesh_tet_interface.h.

References libMesh::MeshTetInterface::_smooth_after_generating.

bool _smooth_after_generating
Flag which tells whether we should smooth the mesh after it is generated.

◆ triangulate()

void libMesh::NetGenMeshInterface::triangulate ( )
overridevirtual

Method invokes NetGen library to compute a tetrahedralization.

Implements libMesh::MeshTetInterface.

Definition at line 83 of file mesh_netgen_interface.C.

References libMesh::MeshTetInterface::_desired_volume, libMesh::MeshTetInterface::_holes, libMesh::MeshTetInterface::_mesh, libMesh::MeshBase::add_elem(), libMesh::MeshBase::add_point(), libMesh::BoundaryInfo::add_side(), libMesh::MeshCommunication::broadcast(), libMesh::Elem::build_with_id(), libMesh::MeshBase::clear(), libMesh::BoundingBox::contains(), libMesh::MeshBase::delete_elem(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::MeshTetInterface::improve_hull_integrity(), libMesh::DofObject::invalid_id, libMesh::libmesh_assert(), libMesh::libmesh_ignore(), libMesh::make_range(), libMesh::MeshTools::n_elem(), libMesh::MeshBase::n_elem(), libMesh::MeshBase::n_nodes(), libMesh::n_threads(), libMesh::MeshBase::node_ptr(), libMesh::Utility::pow(), libMesh::MeshBase::prepare_for_use(), libMesh::MeshTetInterface::process_hull_integrity_result(), libMesh::ParallelObject::processor_id(), libMesh::TET4, libMesh::TRI3, libMesh::TRI6, libMesh::TRI7, and libMesh::MeshTetInterface::volume_to_surface_mesh().

Referenced by main().

84 {
85  using namespace nglib;
86 
87  LOG_SCOPE("triangulate()", "NetGenMeshInterface");
88 
89  // We're hoping to do volume_to_surface_mesh in parallel at least,
90  // but then we'll need to serialize any hole meshes to rank 0 so it
91  // can use them in serial.
92 
93  const BoundingBox mesh_bb =
95 
96  std::vector<MeshSerializer> hole_serializers;
97  if (_holes)
98  for (std::unique_ptr<UnstructuredMesh> & hole : *_holes)
99  {
100  const BoundingBox hole_bb =
102 
103  libmesh_error_msg_if
104  (!mesh_bb.contains(hole_bb),
105  "Found hole with bounding box " << hole_bb <<
106  "\nextending outside of mesh bounding box " << mesh_bb);
107 
108  hole_serializers.emplace_back
109  (*hole, /* need_serial */ true,
110  /* serial_only_needed_on_proc_0 */ true);
111  }
112 
113  // This should probably only be done on rank 0, but the API is
114  // designed with the hope that we'll parallelize it eventually
115  auto integrity = this->improve_hull_integrity();
116  this->process_hull_integrity_result(integrity);
117 
118  // If we're not rank 0, we're just going to wait for rank 0 to call
119  // Netgen, then receive its data afterward, we're not going to hope
120  // that Netgen does the exact same thing on every processor.
121  if (this->_mesh.processor_id() != 0)
122  {
123  // We don't need our holes anymore. Delete their serializers
124  // first to avoid dereferencing dangling pointers.
125  hole_serializers.clear();
126  if (_holes)
127  _holes->clear();
128 
129  // Receive the mesh data rank 0 will send later, then fix it up
130  // together
131  MeshCommunication().broadcast(this->_mesh);
132 
133  // If we got an empty mesh here then our tetrahedralization
134  // failed.
135  libmesh_error_msg_if (!this->_mesh.n_elem(),
136  "NetGen failed to generate any tetrahedra");
137 
138  this->_mesh.prepare_for_use();
139  return;
140  }
141 
142  Ng_Meshing_Parameters params;
143 
144  Ng_SetNumThreads(cast_int<int>(libMesh::n_threads()));
145 
146  // Override any default parameters we might need to, to avoid
147  // inserting nodes we don't want.
148  params.uselocalh = false;
149  params.minh = 0;
150  params.elementsperedge = 1;
151  params.elementspercurve = 1;
152  params.closeedgeenable = false;
153  params.closeedgefact = 0;
154  params.minedgelenenable = false;
155  params.minedgelen = 0;
156 
157  // Try to get a no-extra-nodes mesh if we're asked to, or try to
158  // translate our desired volume into NetGen terms otherwise.
159  //
160  // Spoiler alert: all we can do is try; NetGen uses a marching front
161  // algorithm that can insert extra nodes despite all my best
162  // efforts.
163  if (_desired_volume == 0) // shorthand for "no refinement"
164  {
165  params.maxh = std::numeric_limits<double>::max();
166  params.fineness = 0; // "coarse" in the docs
167  params.grading = 1; // "aggressive local grading" to avoid smoothing??
168 
169  // Turning off optimization steps avoids another opportunity for
170  // Netgen to try to add more nodes.
171  params.optsteps_3d = 0;
172  }
173  else
174  params.maxh = double(std::pow(_desired_volume, 1./3.));
175 
176  // Keep track of how NetGen copies of nodes map back to our original
177  // nodes, so we can connect new elements to nodes correctly.
178  std::unordered_map<int, dof_id_type> ng_to_libmesh_id;
179 
180  auto handle_ng_result = [](Ng_Result result) {
181  static const std::vector<std::string> result_types =
182  {"Netgen error", "Netgen success", "Netgen surface input error",
183  "Netgen volume failure", "Netgen STL input error",
184  "Netgen surface failure", "Netgen file not found"};
185 
186  if (result+1 >= 0 &&
187  std::size_t(result+1) < result_types.size())
188  libmesh_error_msg_if
189  (result, "Ng_GenerateVolumeMesh failed: " <<
190  result_types[result+1]);
191  else
192  libmesh_error_msg
193  ("Ng_GenerateVolumeMesh failed with an unknown error code");
194  };
195 
196  // Keep track of what boundary ids we want to assign to each new
197  // triangle. We'll give the outer boundary BC 0, and give holes ids
198  // starting from 1.
199  // We key on sorted tuples of node ids to identify a side.
200  std::unordered_map<std::array<dof_id_type,3>,
201  boundary_id_type, libMesh::hash> side_boundary_id;
202 
203  auto insert_id = []
204  (std::array<dof_id_type,3> & array,
205  dof_id_type n_id)
206  {
207  libmesh_assert_less(n_id, DofObject::invalid_id);
208  unsigned int i=0;
209  while (array[i] < n_id)
210  ++i;
211  while (i < 3)
212  std::swap(array[i++], n_id);
213  };
214 
215  WrappedNgMesh ngmesh;
216 
217  // Create surface mesh in the WrappedNgMesh
218  {
219  // NetGen appears to use ONE-BASED numbering for its nodes, and
220  // since it doesn't return an id when adding nodes we'll have to
221  // track the numbering ourselves.
222  int ng_id = 1;
223 
224  auto create_surface_component =
225  [this, &ng_id, &ng_to_libmesh_id, &ngmesh, &side_boundary_id, &insert_id]
226  (UnstructuredMesh & srcmesh, bool hole_mesh,
227  boundary_id_type bcid)
228  {
229  LOG_SCOPE("create_surface_component()", "NetGenMeshInterface");
230 
231  // Keep track of what nodes we've already added to the Netgen
232  // mesh vs what nodes we need to add. We'll keep track by id,
233  // not by point location. I don't know if Netgen can handle
234  // multiple nodes with the same point location, but if they can
235  // it's not going to be *us* who breaks that feature.
236  std::unordered_map<dof_id_type, int> libmesh_to_ng_id;
237 
238  // Keep track of what nodes we've already added to the main
239  // mesh from a hole mesh.
240  std::unordered_map<dof_id_type, dof_id_type> hole_to_main_mesh_id;
241 
242  // Use a separate array for passing points to NetGen, just in case
243  // we're not using double-precision ourselves.
244  std::array<double, 3> point_val;
245 
246  // And an array for element vertices
247  std::array<int, 3> elem_nodes;
248 
249  for (const auto * elem : srcmesh.element_ptr_range())
250  {
251  // If someone has triangles we can't triangulate, we have a
252  // problem
253  if (elem->type() == TRI6 ||
254  elem->type() == TRI7)
255  libmesh_not_implemented_msg
256  ("Netgen tetrahedralization currently only supports TRI3 boundaries");
257 
258  // If someone has non-triangles, let's just ignore them.
259  if (elem->type() != TRI3)
260  continue;
261 
262  std::array<dof_id_type,3> sorted_ids =
265 
266  for (int ni : make_range(3))
267  {
268  // Just using the "invert_trigs" option in NetGen params
269  // doesn't work for me, so we'll have to have properly
270  // oriented the tris earlier.
271  auto & elem_node = hole_mesh ? elem_nodes[2-ni] : elem_nodes[ni];
272 
273  const Node & n = elem->node_ref(ni);
274  auto n_id = n.id();
275  if (hole_mesh)
276  {
277  if (auto it = hole_to_main_mesh_id.find(n_id);
278  it != hole_to_main_mesh_id.end())
279  {
280  n_id = it->second;
281  }
282  else
283  {
284  Node * n_new = this->_mesh.add_point(n);
285  const dof_id_type n_new_id = n_new->id();
286  hole_to_main_mesh_id.emplace(n_id, n_new_id);
287  n_id = n_new_id;
288  }
289  }
290 
291  if (auto it = libmesh_to_ng_id.find(n_id);
292  it != libmesh_to_ng_id.end())
293  {
294  const int existing_ng_id = it->second;
295  elem_node = existing_ng_id;
296  }
297  else
298  {
299  for (auto i : make_range(3))
300  point_val[i] = double(n(i));
301 
302  Ng_AddPoint(ngmesh, point_val.data());
303 
304  ng_to_libmesh_id[ng_id] = n_id;
305  libmesh_to_ng_id[n_id] = ng_id;
306  elem_node = ng_id;
307  ++ng_id;
308  }
309 
310  insert_id(sorted_ids, n_id);
311  }
312 
313  side_boundary_id[sorted_ids] = bcid;
314 
315  Ng_AddSurfaceElement(ngmesh, NG_TRIG, elem_nodes.data());
316  }
317  };
318 
319  // Number the outer boundary 0, and the holes starting from 1
320  boundary_id_type bcid = 0;
321 
322  create_surface_component(this->_mesh, false, bcid);
323 
324  if (_holes)
325  for (const std::unique_ptr<UnstructuredMesh> & h : *_holes)
326  create_surface_component(*h, true, ++bcid);
327  }
328 
329  {
330  LOG_SCOPE("Ng_GenerateVolumeMesh()", "NetGenMeshInterface");
331 
332  auto result = Ng_GenerateVolumeMesh(ngmesh, &params);
333  handle_ng_result(result);
334  }
335 
336  const int n_elem = Ng_GetNE(ngmesh);
337 
338  // If Netgen fails us, we're likely to get n_elem <= 0. This is a
339  // common enough failure from bad setups that I want to make sure
340  // it's thrown in parallel so as to not desynchronize any unit tests
341  // that trigger it. So we'll broadcast the empty mesh to indicate
342  // the problem and enable throwing exceptions in parallel.
343  if (n_elem <= 0)
344  {
345  this->_mesh.clear();
346  MeshCommunication().broadcast(this->_mesh);
347  libmesh_error_msg ("NetGen failed to generate any tetrahedra");
348  }
349 
350  const dof_id_type n_points = Ng_GetNP(ngmesh);
351  const dof_id_type old_nodes = this->_mesh.n_nodes();
352 
353  // Netgen may have generated new interior nodes
354  if (n_points != old_nodes)
355  {
356  std::array<double, 3> point_val;
357 
358  // We should only be getting new nodes if we asked for them
359  if (!_desired_volume)
360  {
361  std::cout <<
362  "NetGen output " << n_points <<
363  " points when we gave it " <<
364  old_nodes << " and disabled refinement\n" <<
365  "If new interior points are acceptable in your mesh, please set\n" <<
366  "a non-zero desired_volume to indicate that. If new interior\n" <<
367  "points are not acceptable in your mesh, you may need a different\n" <<
368  "(non-advancing-front?) mesh generator." << std::endl;
369  libmesh_error();
370  }
371  else
372  for (auto i : make_range(old_nodes, n_points))
373  {
374  // i+1 since ng uses ONE-BASED numbering
375  Ng_GetPoint (ngmesh, i+1, point_val.data());
376  const Point p(point_val[0], point_val[1], point_val[2]);
377  Node * n_new = this->_mesh.add_point(p);
378  const dof_id_type n_new_id = n_new->id();
379  ng_to_libmesh_id[i+1] = n_new_id;
380  }
381  }
382 
383  for (auto * elem : this->_mesh.element_ptr_range())
384  this->_mesh.delete_elem(elem);
385 
386  BoundaryInfo * bi = & this->_mesh.get_boundary_info();
387 
388  for (auto i : make_range(n_elem))
389  {
390  // Enough data to return even a Tet10 without a segfault if nglib
391  // went nuts
392  int ngnodes[11];
393 
394  // i+1 since we must be 1-based with these ids too...
395  Ng_Volume_Element_Type ngtype =
396  Ng_GetVolumeElement(ngmesh, i+1, ngnodes);
397 
398  // But really nglib shouldn't go nuts
399  libmesh_assert(ngtype == NG_TET);
400  libmesh_ignore(ngtype);
401 
402  auto elem = this->_mesh.add_elem(Elem::build_with_id(TET4, i));
403  for (auto n : make_range(4))
404  {
405  const dof_id_type node_id =
406  libmesh_map_find(ng_to_libmesh_id, ngnodes[n]);
407  elem->set_node(n, this->_mesh.node_ptr(node_id));
408  }
409 
410  // NetGen and we disagree about node numbering orientation
411  elem->orient(bi);
412 
413  for (auto s : make_range(4))
414  {
415  std::array<dof_id_type,3> sorted_ids =
418 
419  std::vector<unsigned int> nos = elem->nodes_on_side(s);
420  for (auto n : nos)
421  insert_id(sorted_ids, elem->node_id(n));
422 
423  if (auto it = side_boundary_id.find(sorted_ids);
424  it != side_boundary_id.end())
425  bi->add_side(elem, s, it->second);
426  }
427  }
428 
429  // We don't need our holes anymore. Delete their serializers
430  // first to avoid dereferencing dangling pointers.
431  hole_serializers.clear();
432  if (_holes)
433  _holes->clear();
434 
435  // Send our data to other ranks, then fix it up together
436  MeshCommunication().broadcast(this->_mesh);
437  this->_mesh.prepare_for_use();
438 }
std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh > > > _holes
A pointer to a vector of meshes each defining a hole.
unsigned int n_threads()
Definition: libmesh_base.h:109
dof_id_type n_elem(const MeshBase::const_element_iterator &begin, const MeshBase::const_element_iterator &end)
Count up the number of elements of a specific type (as defined by an iterator range).
Definition: mesh_tools.C:1004
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition: mesh_base.C:824
void process_hull_integrity_result(const std::set< SurfaceIntegrity > &result) const
This function prints an informative message and throws an exception based on the output of the check_...
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
void libmesh_ignore(const Args &...)
T pow(const T &x)
Definition: utility.h:296
int8_t boundary_id_type
Definition: id_types.h:51
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition: dof_object.h:473
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
libmesh_assert(ctx)
static BoundingBox volume_to_surface_mesh(UnstructuredMesh &mesh)
Remove volume elements from the given mesh, after converting their outer boundary faces to surface el...
Definition: libmesh.C:81
virtual void clear()
Deletes all the element and node data that is currently stored.
Definition: mesh_base.C:1029
Real _desired_volume
The desired volume for the elements in the resulting mesh.
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem&#39;s ...
Definition: elem.C:556
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:176
std::set< SurfaceIntegrity > improve_hull_integrity()
This function checks the integrity of the current set of elements in the Mesh, and corrects what it c...
virtual dof_id_type n_elem() const =0
virtual const Node * node_ptr(const dof_id_type i) const =0
processor_id_type processor_id() const
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
virtual dof_id_type n_nodes() const =0
uint8_t dof_id_type
Definition: id_types.h:67

◆ volume_to_surface_mesh()

BoundingBox libMesh::MeshTetInterface::volume_to_surface_mesh ( UnstructuredMesh mesh)
staticprotectedinherited

Remove volume elements from the given mesh, after converting their outer boundary faces to surface elements.

Returns the bounding box of the mesh; this is useful for detecting misplaced holes later.

Definition at line 134 of file mesh_tet_interface.C.

References libMesh::MeshBase::add_elem(), libMesh::MeshTools::Modification::all_tri(), libMesh::Elem::build_side_ptr(), libMesh::MeshBase::delete_elem(), libMesh::Elem::dim(), libMesh::Elem::flip(), libMesh::MeshBase::get_boundary_info(), libMesh::DofObject::id(), libMesh::MeshBase::is_prepared(), libMesh::MeshBase::is_replicated(), libMesh::Elem::level(), libMesh::libmesh_assert(), libMesh::Elem::low_order_key(), libMesh::make_range(), libMesh::MeshBase::max_elem_id(), mesh, libMesh::Elem::n_sides(), libMesh::Elem::neighbor_ptr(), libMesh::Elem::node_ref_range(), libMesh::MeshBase::parallel_max_unique_id(), libMesh::MeshBase::prepare_for_use(), libMesh::Real, libMesh::remote_elem, libMesh::DofObject::set_id(), libMesh::Elem::set_interior_parent(), libMesh::Elem::set_neighbor(), libMesh::DofObject::set_unique_id(), libMesh::Elem::side_index_range(), libMesh::Elem::side_ptr(), libMesh::BoundingBox::union_with(), and libMesh::MeshTools::valid_is_prepared().

Referenced by triangulate().

135 {
136  // If we've been handed an unprepared mesh then we need to be made
137  // aware of that and fix that; we're relying on neighbor pointers.
139 
140  if (!mesh.is_prepared())
142 
143  // We'll return a bounding box for use by subclasses in basic sanity checks.
144  BoundingBox surface_bb;
145 
146  // First convert all volume boundaries to surface elements; this
147  // gives us a manifold bounding the mesh, though it may not be a
148  // connected manifold even if the volume mesh was connected.
149  {
150  // Make sure ids are in sync and valid on a DistributedMesh
151  const dof_id_type max_orig_id = mesh.max_elem_id();
152 #ifdef LIBMESH_ENABLE_UNIQUE_ID
153  const unique_id_type max_unique_id = mesh.parallel_max_unique_id();
154 #endif
155 
156  // Change this if we add arbitrary polyhedra...
157  const dof_id_type max_sides = 6;
158 
159  std::unordered_set<Elem *> elems_to_delete;
160 
161  std::vector<std::unique_ptr<Elem>> elems_to_add;
162 
163  // Convert all faces to surface elements
164  for (auto * elem : mesh.active_element_ptr_range())
165  {
166  libmesh_error_msg_if (elem->dim() < 2,
167  "Cannot use meshes with 0D or 1D elements to define a volume");
168 
169  // If we've already got 2D elements then those are (part of)
170  // our surface.
171  if (elem->dim() == 2)
172  continue;
173 
174  // 3D elements will be removed after we've extracted their
175  // surface faces.
176  elems_to_delete.insert(elem);
177 
178  for (auto s : make_range(elem->n_sides()))
179  {
180  // If there's a neighbor on this side then there's not a
181  // boundary
182  if (elem->neighbor_ptr(s))
183  {
184  // We're not supporting AMR meshes here yet
185  if (elem->level() != elem->neighbor_ptr(s)->level())
186  libmesh_not_implemented_msg
187  ("Tetrahedralizaton of adapted meshes is not currently supported");
188  continue;
189  }
190 
191  elems_to_add.push_back(elem->build_side_ptr(s));
192  Elem * side_elem = elems_to_add.back().get();
193 
194  // Wipe the interior_parent before it can become a
195  // dangling pointer later
196  side_elem->set_interior_parent(nullptr);
197 
198  // If the mesh is replicated then its automatic id
199  // setting is fine. If not, then we need unambiguous ids
200  // independent of element traversal.
201  if (!mesh.is_replicated())
202  {
203  side_elem->set_id(max_orig_id + max_sides*elem->id() + s);
204 #ifdef LIBMESH_ENABLE_UNIQUE_ID
205  side_elem->set_unique_id(max_unique_id + max_sides*elem->id() + s);
206 #endif
207  }
208  }
209  }
210 
211  // If the mesh is replicated then its automatic neighbor finding
212  // is fine. If not, then we need to insert them ourselves, but
213  // it's easy because we can use the fact (from our implementation
214  // above) that our new elements have no parents or children, plus
215  // the fact (from the tiny fraction of homology I understand) that
216  // a manifold boundary is a manifold with no boundary.
217  //
218  // See UnstructuredMesh::find_neighbors() for more explanation of
219  // (a more complicated version of) the algorithm here.
220  if (!mesh.is_replicated())
221  {
222  typedef dof_id_type key_type;
223  typedef std::pair<Elem *, unsigned char> val_type;
224  typedef std::unordered_multimap<key_type, val_type> map_type;
225  map_type side_to_elem_map;
226 
227  std::unique_ptr<Elem> my_side, their_side;
228 
229  for (auto & elem : elems_to_add)
230  {
231  for (auto s : elem->side_index_range())
232  {
233  if (elem->neighbor_ptr(s))
234  continue;
235  const dof_id_type key = elem->low_order_key(s);
236  auto bounds = side_to_elem_map.equal_range(key);
237  if (bounds.first != bounds.second)
238  {
239  elem->side_ptr(my_side, s);
240  while (bounds.first != bounds.second)
241  {
242  Elem * potential_neighbor = bounds.first->second.first;
243  const unsigned int ns = bounds.first->second.second;
244  potential_neighbor->side_ptr(their_side, ns);
245  if (*my_side == *their_side)
246  {
247  elem->set_neighbor(s, potential_neighbor);
248  potential_neighbor->set_neighbor(ns, elem.get());
249  side_to_elem_map.erase (bounds.first);
250  break;
251  }
252  ++bounds.first;
253  }
254 
255  if (!elem->neighbor_ptr(s))
256  side_to_elem_map.emplace
257  (key, std::make_pair(elem.get(), cast_int<unsigned char>(s)));
258  }
259  }
260  }
261 
262  // At this point we *should* have a match for everything, so
263  // anything we don't have a match for is remote.
264  for (auto & elem : elems_to_add)
265  for (auto s : elem->side_index_range())
266  if (!elem->neighbor_ptr(s))
267  elem->set_neighbor(s, const_cast<RemoteElem*>(remote_elem));
268  }
269 
270  // Remove volume and edge elements
271  for (Elem * elem : elems_to_delete)
272  mesh.delete_elem(elem);
273 
274  // Add the new elements outside the loop so we don't risk
275  // invalidating iterators.
276  for (auto & elem : elems_to_add)
277  mesh.add_elem(std::move(elem));
278  }
279 
280  // Fix up neighbor pointers, element counts, etc.
282 
283  // We're making tets; we need to start with tris
285 
286  // Partition surface into connected components. At this point I'm
287  // finally going to give up and serialize, because at least we got
288  // from 3D down to 2D first, and because I don't want to have to
289  // turn flood_component into a while loop with a parallel sync in
290  // the middle, and because we do have to serialize *eventually*
291  // anyways unless we get a parallel tetrahedralizer backend someday.
292  MeshSerializer mesh_serializer(mesh);
293 
294  std::vector<std::unordered_set<Elem *>> components;
295  std::unordered_set<Elem *> in_component;
296 
297  for (auto * elem : mesh.element_ptr_range())
298  if (!in_component.count(elem))
299  components.emplace_back(flood_component(in_component, elem));
300 
301  const std::unordered_set<Elem *> * biggest_component = nullptr;
302  Real biggest_six_vol = 0;
303  for (const auto & component : components)
304  {
305  Real six_vol = six_times_signed_volume(component);
306  if (std::abs(six_vol) > std::abs(biggest_six_vol))
307  {
308  biggest_six_vol = six_vol;
309  biggest_component = &component;
310  }
311  }
312 
313  if (!biggest_component)
314  libmesh_error_msg("No non-zero-volume component found among " <<
315  components.size() << " boundary components");
316 
317  for (const auto & component : components)
318  if (&component != biggest_component)
319  {
320  for (Elem * elem: component)
321  mesh.delete_elem(elem);
322  }
323  else
324  {
325  for (Elem * elem: component)
326  {
327  if (biggest_six_vol < 0)
328  elem->flip(&mesh.get_boundary_info());
329 
330  for (auto & node : elem->node_ref_range())
331  surface_bb.union_with(node);
332  }
333  }
334 
336 
337  return surface_bb;
338 }
bool is_prepared() const
Definition: mesh_base.C:1057
virtual unique_id_type parallel_max_unique_id() const =0
bool valid_is_prepared(const MeshBase &mesh)
A function for testing whether a mesh&#39;s cached is_prepared() setting is not a false positive...
Definition: mesh_tools.C:1256
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition: mesh_base.C:824
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:170
void set_interior_parent(Elem *p)
Sets the pointer to the element&#39;s interior_parent.
Definition: elem.C:1222
dof_id_type & set_id()
Definition: dof_object.h:827
void all_tri(MeshBase &mesh)
Subdivides any non-simplex elements in a Mesh to produce simplex (triangular in 2D, tetrahedral in 3D) elements.
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
virtual dof_id_type max_elem_id() const =0
libmesh_assert(ctx)
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition: elem.h:2632
Defines a Cartesian bounding box by the two corner extremum.
Definition: bounding_box.h:40
void set_unique_id(unique_id_type new_id)
Sets the unique_id for this DofObject.
Definition: dof_object.h:848
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
Temporarily serialize a DistributedMesh for non-distributed-mesh capable code paths.
virtual bool is_replicated() const
Definition: mesh_base.h:369
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:176
void union_with(const Point &p)
Enlarges this bounding box to include the given point.
Definition: bounding_box.h:286
uint8_t unique_id_type
Definition: id_types.h:86
uint8_t dof_id_type
Definition: id_types.h:67
const RemoteElem * remote_elem
Definition: remote_elem.C:57

Member Data Documentation

◆ _desired_volume

Real libMesh::MeshTetInterface::_desired_volume
protectedinherited

The desired volume for the elements in the resulting mesh.

Unlimited (indicated by 0) by default

Definition at line 178 of file mesh_tet_interface.h.

Referenced by libMesh::MeshTetInterface::desired_volume(), triangulate(), and libMesh::TetGenMeshInterface::triangulate_pointset().

◆ _elem_type

ElemType libMesh::MeshTetInterface::_elem_type
protectedinherited

The exact type of tetrahedra we intend to construct.

Definition at line 189 of file mesh_tet_interface.h.

Referenced by libMesh::MeshTetInterface::elem_type().

◆ _holes

std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh> > > libMesh::MeshTetInterface::_holes
protectedinherited

A pointer to a vector of meshes each defining a hole.

If this is nullptr, there are no holes!

Definition at line 200 of file mesh_tet_interface.h.

Referenced by triangulate().

◆ _mesh

UnstructuredMesh& libMesh::MeshTetInterface::_mesh
protectedinherited

◆ _serializer

MeshSerializer libMesh::NetGenMeshInterface::_serializer
protected

Tetgen only operates on serial meshes.

Definition at line 78 of file mesh_netgen_interface.h.

◆ _smooth_after_generating

bool libMesh::MeshTetInterface::_smooth_after_generating
protectedinherited

◆ _verbosity

unsigned int libMesh::MeshTetInterface::_verbosity
protectedinherited

The documentation for this class was generated from the following files: