LCOV - code coverage report
Current view: top level - src/mesh - mesh_tet_interface.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4476 (4beb67) with base a68cc6 Lines: 233 317 73.5 %
Date: 2026-06-03 20:22:46 Functions: 15 18 83.3 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2023 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
       3             : 
       4             : // This library is free software; you can redistribute it and/or
       5             : // modify it under the terms of the GNU Lesser General Public
       6             : // License as published by the Free Software Foundation; either
       7             : // version 2.1 of the License, or (at your option) any later version.
       8             : 
       9             : // This library is distributed in the hope that it will be useful,
      10             : // but WITHOUT ANY WARRANTY; without even the implied warranty of
      11             : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
      12             : // Lesser General Public License for more details.
      13             : 
      14             : // You should have received a copy of the GNU Lesser General Public
      15             : // License along with this library; if not, write to the Free Software
      16             : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
      17             : 
      18             : #include "libmesh/libmesh_config.h"
      19             : 
      20             : 
      21             : // C++ includes
      22             : #include <sstream>
      23             : 
      24             : // Local includes
      25             : #include "libmesh/mesh_tet_interface.h"
      26             : 
      27             : #include "libmesh/boundary_info.h"
      28             : #include "libmesh/elem.h"
      29             : #include "libmesh/elem_range.h"
      30             : #include "libmesh/mesh_modification.h"
      31             : #include "libmesh/mesh_serializer.h"
      32             : #include "libmesh/mesh_tools.h"
      33             : #include "libmesh/remote_elem.h"
      34             : #include "libmesh/unstructured_mesh.h"
      35             : 
      36             : #include "timpi/parallel_implementation.h"
      37             : 
      38             : namespace {
      39             :   using namespace libMesh;
      40             : 
      41             :   std::unordered_set<Elem *>
      42         639 :   flood_component (std::unordered_set<Elem *> & all_components,
      43             :                         Elem * elem)
      44             :   {
      45          18 :     libmesh_assert(!all_components.count(elem));
      46             : 
      47          18 :     std::unordered_set<Elem *> current_component;
      48             : 
      49         744 :     std::unordered_set<Elem *> elements_to_consider = {elem};
      50             : 
      51        4898 :     while (!elements_to_consider.empty())
      52             :       {
      53         236 :         std::unordered_set<Elem *> next_elements_to_consider;
      54             : 
      55       43094 :         for (Elem * considering : elements_to_consider)
      56             :           {
      57        1003 :             all_components.insert(considering);
      58        1003 :             current_component.insert(considering);
      59             : 
      60      155198 :             for (auto s : make_range(considering->n_sides()))
      61             :               {
      62      116434 :                 Elem * neigh = considering->neighbor_ptr(s);
      63             : 
      64      116505 :                 libmesh_error_msg_if
      65             :                   (!neigh,
      66             :                    "Tet generation encountered a 2D element with a null neighbor, but a\n"
      67             :                    "boundary must be a 2D closed manifold (surface).\n");
      68             : 
      69      116363 :                 if (all_components.find(neigh) == all_components.end())
      70        1358 :                   next_elements_to_consider.insert(neigh);
      71             :               }
      72             :           }
      73         116 :         elements_to_consider = next_elements_to_consider;
      74             :       }
      75             : 
      76         584 :     return current_component;
      77             :   }
      78             : 
      79             :   // Returns six times the signed volume of a tet formed by the given
      80             :   // 3 points and the origin
      81       30956 :   Real six_times_signed_tet_volume (const Point & p1,
      82             :                                     const Point & p2,
      83             :                                     const Point & p3)
      84             :   {
      85       30956 :     return p1(0)*p2(1)*p3(2)
      86       30956 :          - p1(0)*p3(1)*p2(2)
      87       30956 :          - p2(0)*p1(1)*p3(2)
      88       30956 :          + p2(0)*p3(1)*p1(2)
      89       30956 :          + p3(0)*p1(1)*p2(2)
      90       30956 :          - p3(0)*p2(1)*p1(2);
      91             :   }
      92             : 
      93             :   // Returns six times the signed volume of the space defined by the
      94             :   // manifold of surface elements in component
      95         568 :   Real six_times_signed_volume (const std::unordered_set<Elem *> component)
      96             :   {
      97          16 :     Real six_vol = 0;
      98             : 
      99       31524 :     for (const Elem * elem: component)
     100             :       {
     101         872 :         libmesh_assert_equal_to(elem->dim(), 2);
     102       61912 :         for (auto n : make_range(elem->n_vertices()-2))
     103       32700 :           six_vol += six_times_signed_tet_volume(elem->point(0),
     104             :                                                  elem->point(n+1),
     105             :                                                  elem->point(n+2));
     106             :       }
     107             : 
     108         568 :     return six_vol;
     109             :   }
     110             : }
     111             : 
     112             : namespace libMesh
     113             : {
     114             : 
     115             : //----------------------------------------------------------------------
     116             : // MeshTetInterface class members
     117         525 : MeshTetInterface::MeshTetInterface (UnstructuredMesh & mesh) :
     118         469 :   _verbosity(0), _desired_volume(0), _smooth_after_generating(false),
     119         525 :   _elem_type(TET4), _mesh(mesh)
     120             : {
     121         525 : }
     122             : 
     123             : 
     124         469 : MeshTetInterface::~MeshTetInterface() = default;
     125             : 
     126             : 
     127         142 : void MeshTetInterface::attach_hole_list
     128             :   (std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> holes)
     129             : {
     130           4 :   _holes = std::move(holes);
     131         142 : }
     132             : 
     133             : 
     134         639 : BoundingBox MeshTetInterface::volume_to_surface_mesh(UnstructuredMesh & mesh)
     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.
     138          18 :   libmesh_assert(MeshTools::valid_is_prepared(mesh));
     139             : 
     140         639 :   if (!mesh.is_prepared())
     141          71 :     mesh.prepare_for_use();
     142             : 
     143             :   // We'll return a bounding box for use by subclasses in basic sanity checks.
     144         621 :   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         639 :     const dof_id_type max_orig_id = mesh.max_elem_id();
     152             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     153         639 :     const unique_id_type max_unique_id = mesh.parallel_max_unique_id();
     154             : #endif
     155             : 
     156             :     // Change this if we add arbitrary polyhedra...
     157          18 :     const dof_id_type max_sides = 6;
     158             : 
     159          36 :     std::unordered_set<Elem *> elems_to_delete;
     160             : 
     161          54 :     std::vector<std::unique_ptr<Elem>> elems_to_add;
     162             : 
     163             :     // Convert all faces to surface elements
     164      510132 :     for (auto * elem : mesh.active_element_ptr_range())
     165             :       {
     166      265276 :         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      265276 :         if (elem->dim() == 2)
     172        2523 :           continue;
     173             : 
     174             :         // 3D elements will be removed after we've extracted their
     175             :         // surface faces.
     176       10762 :         elems_to_delete.insert(elem);
     177             : 
     178     1313517 :         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     1093894 :             if (elem->neighbor_ptr(s))
     183             :               {
     184             :                 // We're not supporting AMR meshes here yet
     185     1031260 :                 if (elem->level() != elem->neighbor_ptr(s)->level())
     186           0 :                   libmesh_not_implemented_msg
     187             :                     ("Tetrahedralizaton of adapted meshes is not currently supported");
     188      989004 :                 continue;
     189      946748 :               }
     190             : 
     191       38368 :             elems_to_add.push_back(elem->build_side_ptr(s));
     192         796 :             Elem * side_elem = elems_to_add.back().get();
     193             : 
     194             :             // Wipe the interior_parent before it can become a
     195             :             // dangling pointer later
     196       19582 :             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       19582 :             if (!mesh.is_replicated())
     202             :               {
     203       16796 :                 side_elem->set_id(max_orig_id + max_sides*elem->id() + s);
     204             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
     205       16796 :                 side_elem->set_unique_id(max_unique_id + max_sides*elem->id() + s);
     206             : #endif
     207             :               }
     208             :           }
     209         603 :       }
     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         639 :     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           0 :         map_type side_to_elem_map;
     226             : 
     227         576 :         std::unique_ptr<Elem> my_side, their_side;
     228             : 
     229       17372 :         for (auto & elem : elems_to_add)
     230             :           {
     231       67568 :             for (auto s : elem->side_index_range())
     232             :               {
     233       50772 :                 if (elem->neighbor_ptr(s))
     234           0 :                   continue;
     235       50772 :                 const dof_id_type key = elem->low_order_key(s);
     236           0 :                 auto bounds = side_to_elem_map.equal_range(key);
     237       50772 :                 if (bounds.first != bounds.second)
     238             :                   {
     239           0 :                     elem->side_ptr(my_side, s);
     240           0 :                     while (bounds.first != bounds.second)
     241             :                       {
     242           0 :                         Elem * potential_neighbor = bounds.first->second.first;
     243           0 :                         const unsigned int ns = bounds.first->second.second;
     244           0 :                         potential_neighbor->side_ptr(their_side, ns);
     245           0 :                         if (*my_side == *their_side)
     246             :                           {
     247           0 :                             elem->set_neighbor(s, potential_neighbor);
     248           0 :                             potential_neighbor->set_neighbor(ns, elem.get());
     249           0 :                             side_to_elem_map.erase (bounds.first);
     250           0 :                             break;
     251             :                           }
     252           0 :                         ++bounds.first;
     253             :                       }
     254             : 
     255           0 :                     if (!elem->neighbor_ptr(s))
     256             :                       side_to_elem_map.emplace
     257           0 :                         (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       17372 :         for (auto & elem : elems_to_add)
     265       67568 :           for (auto s : elem->side_index_range())
     266       50772 :             if (!elem->neighbor_ptr(s))
     267       50772 :               elem->set_neighbor(s, const_cast<RemoteElem*>(remote_elem));
     268         576 :       }
     269             : 
     270             :     // Remove volume and edge elements
     271      263314 :     for (Elem * elem : elems_to_delete)
     272      262675 :       mesh.delete_elem(elem);
     273             : 
     274             :     // Add the new elements outside the loop so we don't risk
     275             :     // invalidating iterators.
     276       20221 :     for (auto & elem : elems_to_add)
     277       21174 :       mesh.add_elem(std::move(elem));
     278         603 :   }
     279             : 
     280             :   // Fix up neighbor pointers, element counts, etc.
     281         639 :   mesh.prepare_for_use();
     282             : 
     283             :   // We're making tets; we need to start with tris
     284         639 :   MeshTools::Modification::all_tri(mesh);
     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         675 :   MeshSerializer mesh_serializer(mesh);
     293             : 
     294          54 :   std::vector<std::unordered_set<Elem *>> components;
     295          36 :   std::unordered_set<Elem *> in_component;
     296             : 
     297       91587 :   for (auto * elem : mesh.element_ptr_range())
     298        1748 :     if (!in_component.count(elem))
     299        1794 :       components.emplace_back(flood_component(in_component, elem));
     300             : 
     301          16 :   const std::unordered_set<Elem *> * biggest_component = nullptr;
     302          16 :   Real biggest_six_vol = 0;
     303        1136 :   for (const auto & component : components)
     304             :     {
     305        1189 :       Real six_vol = six_times_signed_volume(component);
     306         568 :       if (std::abs(six_vol) > std::abs(biggest_six_vol))
     307             :         {
     308          16 :           biggest_six_vol = six_vol;
     309          16 :           biggest_component = &component;
     310             :         }
     311             :     }
     312             : 
     313         568 :   if (!biggest_component)
     314           0 :     libmesh_error_msg("No non-zero-volume component found among " <<
     315             :                       components.size() << " boundary components");
     316             : 
     317        1136 :   for (const auto & component : components)
     318         568 :     if (&component != biggest_component)
     319             :       {
     320           0 :         for (Elem * elem: component)
     321           0 :           mesh.delete_elem(elem);
     322             :       }
     323             :     else
     324             :       {
     325       31524 :         for (Elem * elem: component)
     326             :           {
     327       30956 :             if (biggest_six_vol < 0)
     328        1168 :               elem->flip(&mesh.get_boundary_info());
     329             : 
     330      123824 :             for (auto & node : elem->node_ref_range())
     331       90252 :               surface_bb.union_with(node);
     332             :           }
     333             :       }
     334             : 
     335         568 :   mesh.prepare_for_use();
     336             : 
     337         584 :   return surface_bb;
     338         670 : }
     339             : 
     340             : 
     341         432 : std::set<MeshTetInterface::SurfaceIntegrity> MeshTetInterface::check_hull_integrity() const
     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         432 :   if (_mesh.n_elem() == 0)
     348           0 :     return {EMPTY_MESH};
     349             : 
     350          32 :   std::set<MeshTetInterface::SurfaceIntegrity> returnval;
     351             : 
     352         432 :   const BoundingBox bb = MeshTools::create_bounding_box(this->_mesh);
     353          16 :   const Point extents = bb.max() - bb.min();
     354         432 :   if (extents(0) == 0 ||
     355         864 :       extents(1) == 0 ||
     356          16 :       extents(2) == 0)
     357           0 :     returnval.insert(DEGENERATE_MESH);
     358             : 
     359             :   // Figure a area to use for relative tolerances when detecting
     360             :   // degenerate elements
     361         432 :   const Real ref_area = std::abs(extents(0) * extents(1)) +
     362         432 :                         std::abs(extents(0) * extents(2)) +
     363         432 :                         std::abs(extents(1) * extents(2));
     364             : 
     365         416 :   struct TriChecker {
     366             :     std::set<MeshTetInterface::SurfaceIntegrity> my_returnval;
     367             :     const Real my_ref_area;
     368             :     const unsigned int my_verbosity;
     369             : 
     370         432 :     TriChecker (Real ref_area, unsigned int verbosity) :
     371         402 :       my_returnval(), my_ref_area(ref_area),
     372         432 :       my_verbosity(verbosity) {}
     373           0 :     TriChecker (TriChecker & other, Threads::split) :
     374           0 :       my_returnval(), my_ref_area(other.my_ref_area),
     375           0 :       my_verbosity(other.my_verbosity) {}
     376             : 
     377         432 :     void operator()(const ConstElemRange & range) {
     378             : 
     379        3272 :       for (const Elem * elem : range)
     380             :         {
     381             :           // Check for proper element type
     382        2840 :           if (elem->type() != TRI3)
     383             :             {
     384           0 :               if (my_verbosity >= 50)
     385           0 :                 std::cerr << "Non-Tri3: " << elem->get_info() << std::endl;
     386           0 :               my_returnval.insert(NON_TRI3);
     387             :             }
     388             : 
     389             :           // Make sure it's a decent element.
     390        2840 :           if (elem->volume() < my_ref_area * TOLERANCE * TOLERANCE)
     391             :             {
     392           0 :               if (my_verbosity >= 50)
     393           0 :                 std::cerr << "Degenerate element: " << elem->get_info() << std::endl;
     394           0 :               my_returnval.insert(DEGENERATE_ELEMENT);
     395             :             }
     396             : 
     397       11360 :           for (auto s : elem->side_index_range())
     398             :             {
     399        8520 :               const Elem * const neigh = elem->neighbor_ptr(s);
     400             : 
     401        8520 :               if (neigh == nullptr)
     402             :                 {
     403           0 :                   if (my_verbosity >= 50)
     404           0 :                     std::cerr << "Element missing neighbor " << s << ": " << elem->get_info() << std::endl;
     405           0 :                   my_returnval.insert(MISSING_NEIGHBOR);
     406           0 :                   continue;
     407             :                 }
     408             : 
     409             :               // Make sure our neighbor points back to us
     410        8520 :               const unsigned int nn = neigh->which_neighbor_am_i(elem);
     411             : 
     412        8520 :               if (nn >= 3)
     413             :                 {
     414           0 :                   if (my_verbosity >= 50)
     415           0 :                     std::cerr << "Element missing backlink " << s << ": " << elem->get_info() << std::endl;
     416           0 :                   my_returnval.insert(MISSING_BACKLINK);
     417           0 :                   continue;
     418             :                 }
     419             : 
     420             :               // Our neighbor should have the same the edge nodes we do on
     421             :               // the neighboring edgei
     422        1440 :               const Node * const n1 = elem->node_ptr(s);
     423        8520 :               const Node * const n2 = elem->node_ptr((s+1)%3);
     424             : 
     425        8520 :               const unsigned int i1 = neigh->local_node(n1->id());
     426        8520 :               const unsigned int i2 = neigh->local_node(n2->id());
     427        8520 :               if (i1 >= 3 || i2 >= 3)
     428             :                 {
     429           0 :                   if (my_verbosity >= 50)
     430           0 :                     std::cerr << "Element with bad neighbor " << s << " nodes: " << elem->get_info() << std::endl;
     431           0 :                   my_returnval.insert(BAD_NEIGHBOR_NODES);
     432           0 :                   continue;
     433             :                 }
     434             : 
     435             :               // It should have those edge nodes in the opposite order
     436             :               // (because they have the same orientation we do)
     437        8520 :               if ((i2 + 1)%3 != i1)
     438             :                 {
     439         144 :                   if (my_verbosity >= 50)
     440           0 :                     std::cerr << "Element orientation mismatch with neighbor " << s << ": " << elem->get_info() << std::endl;
     441         144 :                   my_returnval.insert(NON_ORIENTED);
     442         144 :                   continue;
     443             :                 }
     444             : 
     445             :               // And it should have those edge nodes in the expected
     446             :               // places relative to its neighbor link
     447        8376 :               if (i2 != nn)
     448             :                 {
     449           0 :                   if (my_verbosity >= 50)
     450           0 :                     std::cerr << "Element with bad links on neighbor " << s << ": " << elem->get_info() << std::endl;
     451           0 :                   my_returnval.insert(BAD_NEIGHBOR_LINKS);
     452           0 :                   continue;
     453             :                 }
     454             :             }
     455             :         }
     456         432 :     }
     457             : 
     458           0 :     void join(TriChecker & other) {
     459           0 :       my_returnval.merge(other.my_returnval);
     460           0 :     }
     461             :   };
     462             : 
     463         448 :   TriChecker checker (ref_area, this->_verbosity);
     464             : 
     465             :   Threads::parallel_reduce
     466         432 :     (this->_mesh.active_local_element_stored_range(), checker);
     467             : 
     468             :   // Join problems found in threaded loop
     469          16 :   returnval.merge(checker.my_returnval);
     470             : 
     471             :   // Join problems found on other ranks
     472          32 :   std::set<char> int_set;
     473             :   std::transform
     474         402 :     (returnval.begin(), returnval.end(),
     475             :      std::inserter<std::set<char>>(int_set, int_set.end()),
     476          32 :      [](SurfaceIntegrity i){return int(i);});
     477         432 :   _mesh.comm().set_union(int_set);
     478             :   std::transform
     479         402 :     (int_set.begin(), int_set.end(),
     480             :      std::inserter<std::set<SurfaceIntegrity>>(returnval, returnval.end()),
     481          99 :      [](int i){return SurfaceIntegrity(i);});
     482             : 
     483          16 :   return returnval;
     484             : }
     485             : 
     486             : 
     487             : 
     488         430 : std::set<MeshTetInterface::SurfaceIntegrity> MeshTetInterface::improve_hull_integrity()
     489             : {
     490             :   // We don't really do anything parallel here, but we aspire to.
     491          14 :   libmesh_parallel_only(this->_mesh.comm());
     492             : 
     493             :   std::set<MeshTetInterface::SurfaceIntegrity> integrityproblems =
     494         444 :     this->check_hull_integrity();
     495             : 
     496             :   // If we have no problem, or a problem we can't fix, we're done.
     497          14 :   if (integrityproblems.empty() ||
     498         432 :       integrityproblems.count(NON_TRI3) ||
     499         418 :       integrityproblems.count(EMPTY_MESH))
     500          12 :     return integrityproblems;
     501             : 
     502             :   // Possibly the user gave us an unprepared mesh with missing or bad
     503             :   // neighbor links?
     504          69 :   if (integrityproblems.count(MISSING_NEIGHBOR) ||
     505          71 :       integrityproblems.count(MISSING_BACKLINK) ||
     506          71 :       integrityproblems.count(BAD_NEIGHBOR_LINKS))
     507             :   {
     508           0 :     this->_mesh.find_neighbors();
     509           0 :     integrityproblems = this->check_hull_integrity();
     510             :   }
     511             : 
     512             :   // If find_neighbors() doesn't fix these, I give up.
     513          69 :   if (integrityproblems.count(MISSING_NEIGHBOR) ||
     514          71 :       integrityproblems.count(MISSING_BACKLINK) ||
     515          71 :       integrityproblems.count(BAD_NEIGHBOR_LINKS))
     516           0 :     return integrityproblems;
     517             : 
     518             :   // find_neighbors() might have fixed everything
     519          71 :   if (integrityproblems.empty())
     520           0 :     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           2 :   libmesh_assert_equal_to(integrityproblems.size(), 1);
     525           2 :   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          75 :   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          71 :   BoundaryInfo & bi = this->_mesh.get_boundary_info();
     537             : 
     538         140 :   const Node * lowest_point = (*this->_mesh.elements_begin())->node_ptr(0);
     539             : 
     540             :   // Index by ids, not pointers, for consistency in parallel
     541           4 :   std::unordered_set<dof_id_type> attached_elements;
     542             : 
     543        1260 :   for (Elem * elem : this->_mesh.element_ptr_range())
     544             :     {
     545        2272 :       for (const Node & node : elem->node_ref_range())
     546             :         {
     547        1704 :           if (node(0) < (*lowest_point)(0))
     548             :             {
     549           2 :               lowest_point = &node;
     550           2 :               attached_elements.clear();
     551             :             }
     552        1704 :           if (&node == lowest_point)
     553         390 :             attached_elements.insert(elem->id());
     554             :         }
     555          67 :     }
     556             : 
     557           2 :   Elem * best_elem = nullptr;
     558           2 :   Real best_abs_normal_0 = 0;
     559             : 
     560         355 :   for (dof_id_type id : attached_elements)
     561             :     {
     562         284 :       Elem * elem = this->_mesh.elem_ptr(id);
     563          16 :       const Point e01 = elem->point(1) - elem->point(0);
     564           8 :       const Point e02 = elem->point(2) - elem->point(0);
     565         284 :       const Point normal = e01.cross(e02).unit();
     566         276 :       const Real abs_normal_0 = std::abs(normal(0));
     567             : 
     568         284 :       if (!best_elem || abs_normal_0 > best_abs_normal_0)
     569             :         {
     570           2 :           best_elem = elem;
     571           2 :           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          71 :           if (abs_normal_0 == normal(0))
     576           0 :             elem->flip(&bi);
     577             :         }
     578             :     }
     579             : 
     580             :   // Now flood-fill from that element to get a consistent orientation
     581             :   // for the others.
     582          73 :   std::unordered_set<dof_id_type> frontier_elements{best_elem->id()},
     583          73 :                                   finished_elements{};
     584             : 
     585         639 :   while (!frontier_elements.empty())
     586             :     {
     587         568 :       const dof_id_type elem_id = *frontier_elements.begin();
     588         568 :       Elem & elem = this->_mesh.elem_ref(elem_id);
     589        2272 :       for (auto s : elem.side_index_range())
     590             :         {
     591        1704 :           Elem * neigh = elem.neighbor_ptr(s);
     592          48 :           libmesh_assert(neigh);
     593          48 :           libmesh_assert_less(neigh->which_neighbor_am_i(&elem), 3);
     594             : 
     595          96 :           const Node * const n1 = elem.node_ptr(s);
     596        1704 :           const Node * const n2 = elem.node_ptr((s+1)%3);
     597        1704 :           const unsigned int i1 = neigh->local_node(n1->id());
     598        1704 :           const unsigned int i2 = neigh->local_node(n2->id());
     599          48 :           libmesh_assert_less(i1, 3);
     600          48 :           libmesh_assert_less(i2, 3);
     601             : 
     602        1704 :           const dof_id_type neigh_id = neigh->id();
     603             : 
     604          48 :           const bool frontier_neigh = frontier_elements.count(neigh_id);
     605          48 :           const bool finished_neigh = finished_elements.count(neigh_id);
     606             : 
     607             :           // Are we flipped?
     608        1704 :           if ((i2 + 1)%3 != i1)
     609             :             {
     610             :               // Are we a Moebius strip???  We give up.
     611         142 :               if (frontier_neigh || finished_neigh)
     612           0 :                 return integrityproblems;
     613             : 
     614         142 :               neigh->flip(&bi);
     615             :             }
     616             : 
     617        1704 :           if (!frontier_neigh && !finished_neigh)
     618          14 :             frontier_elements.insert(neigh_id);
     619             :         }
     620             : 
     621          16 :       finished_elements.insert(elem_id);
     622          16 :       frontier_elements.erase(elem_id);
     623             :     }
     624             : 
     625          71 :   this->_mesh.find_neighbors();
     626             : 
     627           2 :   libmesh_assert(this->check_hull_integrity().empty());
     628             : 
     629          71 :   return {};
     630          67 : }
     631             : 
     632             : 
     633         430 : void MeshTetInterface::process_hull_integrity_result
     634             :   (const std::set<MeshTetInterface::SurfaceIntegrity> & result) const
     635             : {
     636         444 :   std::ostringstream err_msg;
     637             : 
     638         430 :   if (result.empty()) // success
     639         444 :     return;
     640             : 
     641           0 :   err_msg << "Error! Conforming Delaunay mesh tetrahedralization requires a convex hull." << std::endl;
     642             : 
     643           0 :   if (result.count(NON_TRI3))
     644             :     {
     645           0 :       err_msg << "At least one non-Tri3 element was found in the input boundary mesh.  ";
     646           0 :       err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
     647             :     }
     648           0 :   if (result.count(MISSING_NEIGHBOR))
     649             :     {
     650           0 :       err_msg << "At least one triangle without three neighbors was found in the input boundary mesh.  ";
     651           0 :       err_msg << "A constrained Delaunay tetrahedralization boundary must be a triangular manifold without boundary." << std::endl;
     652             :     }
     653           0 :   if (result.count(EMPTY_MESH))
     654             :     {
     655           0 :       err_msg << "The input boundary mesh was empty!" << std::endl;
     656           0 :       err_msg << "Our constrained Delaunay tetrahedralization boundary must be a triangulation of Tri3 elements." << std::endl;
     657             :     }
     658           0 :   if (result.count(MISSING_BACKLINK))
     659             :     {
     660           0 :       err_msg << "At least one triangle neighbor without a return neighbor link was found in the input boundary mesh.  ";
     661           0 :       err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
     662             :     }
     663           0 :   if (result.count(BAD_NEIGHBOR_NODES))
     664             :     {
     665           0 :       err_msg << "At least one triangle neighbor without expected node links was found in the input boundary mesh.  ";
     666           0 :       err_msg << "A constrained Delaunay tetrahedralization boundary must be a conforming and non-adaptively-refined mesh." << std::endl;
     667             :     }
     668           0 :   if (result.count(NON_ORIENTED))
     669             :     {
     670           0 :       err_msg << "At least one triangle neighbor with an inconsistent orientation was found in the input boundary mesh.  ";
     671           0 :       err_msg << "A constrained Delaunay tetrahedralization boundary must be an oriented Tri3 mesh." << std::endl;
     672             :     }
     673           0 :   if (result.count(BAD_NEIGHBOR_LINKS))
     674           0 :     err_msg << "At least one triangle neighbor with inconsistent node and neighbor links was found in the input boundary mesh." << std::endl;
     675           0 :   if (result.count(DEGENERATE_ELEMENT))
     676           0 :     err_msg << "At least one input triangle is degenerate, with near-zero area relative to the manifold." << std::endl;
     677           0 :   if (result.count(DEGENERATE_MESH))
     678           0 :     err_msg << "The input mesh is degenerate, with zero thickness in at least one direction." << std::endl;
     679             : 
     680           0 :   libmesh_error_msg(err_msg.str());
     681         402 : }
     682             : 
     683             : 
     684             : 
     685           4 : void MeshTetInterface::delete_2D_hull_elements()
     686             : {
     687       10078 :   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       10072 :       if (elem->type() == TRI3)
     693          96 :         _mesh.delete_elem(elem);
     694           0 :     }
     695             : 
     696             :   // We just removed any boundary info associated with hull element
     697             :   // edges, so let's update the boundary id caches.
     698           6 :   this->_mesh.get_boundary_info().regenerate_id_sets();
     699           4 : }
     700             : 
     701             : 
     702             : 
     703             : } // namespace libMesh

Generated by: LCOV version 1.14