LCOV - code coverage report
Current view: top level - src/mesh - poly2tri_triangulator.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4510 (71b108) with base 165cb7 Lines: 572 626 91.4 %
Date: 2026-07-31 22:57:43 Functions: 27 28 96.4 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2026 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             : 
      19             : #include "libmesh/libmesh_config.h"
      20             : 
      21             : #ifdef LIBMESH_HAVE_POLY2TRI
      22             : 
      23             : // We'd like reproduceability here even when different FP rounding can
      24             : // lead to different triangle splitting decisions
      25             : #include "libmesh/enforce_ieee754.h"
      26             : 
      27             : // libmesh includes
      28             : #include "libmesh/poly2tri_triangulator.h"
      29             : 
      30             : #include "libmesh/boundary_info.h"
      31             : #include "libmesh/elem.h"
      32             : #include "libmesh/enum_elem_type.h"
      33             : #include "libmesh/function_base.h"
      34             : #include "libmesh/hashing.h"
      35             : #include "libmesh/libmesh_logging.h"
      36             : #include "libmesh/mesh_serializer.h"
      37             : #include "libmesh/mesh_smoother_laplace.h"
      38             : #include "libmesh/mesh_triangle_holes.h"
      39             : #include "libmesh/unstructured_mesh.h"
      40             : #include "libmesh/utility.h"
      41             : 
      42             : // poly2tri includes
      43             : #include "libmesh/ignore_warnings.h" // utf-8 comments should be fine...
      44             : #include "poly2tri/poly2tri.h"
      45             : #include "libmesh/restore_warnings.h"
      46             : 
      47             : // Anonymous namespace - poly2tri doesn't define operator<(Point,Point)
      48             : namespace
      49             : {
      50             : using namespace libMesh;
      51             : 
      52             : struct P2TPointCompare
      53             : {
      54     1122820 :   bool operator()(const p2t::Point & a, const p2t::Point & b) const
      55             :   {
      56    39680804 :     return a.x < b.x || (a.x == b.x && a.y < b.y);
      57             :   }
      58             : };
      59             : 
      60      548333 : p2t::Point to_p2t(const libMesh::Point & p)
      61             : {
      62             : #if LIBMESH_DIM > 2
      63      548333 :   libmesh_error_msg_if
      64             :     (p(2) != 0,
      65             :      "Poly2TriTriangulator only supports point sets in the XY plane");
      66             : #endif
      67             : 
      68      548333 :   return {double(p(0)), double(p(1))};
      69             : }
      70             : 
      71     5608600 : Real distance_from_circumcircle(const Elem & elem,
      72             :                                 const Point & p)
      73             : {
      74     1970920 :   libmesh_assert_equal_to(elem.n_vertices(), 3);
      75             : 
      76     5608600 :   const Point circumcenter = elem.quasicircumcenter();
      77     5608600 :   const Real radius = (elem.point(0) - circumcenter).norm();
      78     5503160 :   const Real p_dist = (p - circumcenter).norm();
      79             : 
      80     5608600 :   return p_dist - radius;
      81             : }
      82             : 
      83             : 
      84     1970920 : bool in_circumcircle(const Elem & elem,
      85             :                      const Point & p,
      86             :                      const Real tol = 0)
      87             : {
      88     2506701 :   return (distance_from_circumcircle(elem, p) < tol);
      89             : 
      90             :   /*
      91             :   libmesh_assert_equal_to(elem.n_vertices(), 3);
      92             : 
      93             :   const Point pv0 = elem.point(0) - p;
      94             :   const Point pv1 = elem.point(1) - p;
      95             :   const Point pv2 = elem.point(2) - p;
      96             : 
      97             :   return ((pv0.norm_sq() * (pv1(0)*pv2(1)-pv2(0)*pv1(1))) -
      98             :           (pv1.norm_sq() * (pv0(0)*pv2(1)-pv2(0)*pv0(1))) +
      99             :           (pv2.norm_sq() * (pv0(0)*pv1(1)-pv1(0)*pv0(1)))) > 0;
     100             :   */
     101             : }
     102             : 
     103             : 
     104             : std::pair<bool, unsigned short>
     105     5535979 : can_delaunay_swap(const Elem & elem,
     106             :                   unsigned short side,
     107             :                   Real tol)
     108             : {
     109     5535979 :   const Elem * neigh = elem.neighbor_ptr(side);
     110     5535979 :   if (!neigh)
     111      383412 :     return {false, 0};
     112             : 
     113     1958074 :   unsigned short nn = 0;
     114             : 
     115             :   // What neighbor node does elem not share?
     116    20609984 :   for (; nn < 3; ++nn)
     117             :     {
     118     6151996 :       const Node * neigh_node = neigh->node_ptr(nn);
     119    13364278 :       if (neigh_node == elem.node_ptr(0) ||
     120    24031671 :           neigh_node == elem.node_ptr(1) ||
     121     3255868 :           neigh_node == elem.node_ptr(2))
     122    10119808 :         continue;
     123             : 
     124             :       // Might we need to do a diagonal swap here?  Avoid
     125             :       // undoing a borderline swap.
     126     5152567 :       if (in_circumcircle(elem, *neigh_node, tol))
     127           4 :         break;
     128             :     }
     129             : 
     130     5152567 :   if (nn == 3)
     131     5152425 :     return {false, 0};
     132             : 
     133         142 :   const unsigned short n = (side+2)%3;
     134             :   const RealVectorValue right =
     135         150 :     (elem.point((n+1)%3)-elem.point(n)).unit();
     136             :   const RealVectorValue mid =
     137         150 :     (neigh->point(nn)-elem.point(n)).unit();
     138             :   const RealVectorValue left =
     139         150 :     (elem.point((n+2)%3)-elem.point(n)).unit();
     140             : 
     141             :   // If the "middle" vector isn't really in the middle, we can't do a
     142             :   // swap without involving other triangles (or we can't at all if
     143             :   // there's a domain boundary in the way)
     144         146 :   if (mid*right < left*right ||
     145           4 :       left*mid < left*right)
     146           0 :     return {false, 0};
     147             : 
     148         142 :   return {true, nn};
     149             : }
     150             : 
     151             : 
     152      660928 : [[maybe_unused]] void libmesh_assert_locally_delaunay(const Elem & elem)
     153             : {
     154      660928 :   libmesh_ignore(elem);
     155             : 
     156             : #ifndef NDEBUG
     157             :   // -TOLERANCE, because we're fine with something a little inside the
     158             :   // circumcircle
     159     2643712 :   for (auto s : make_range(elem.n_sides()))
     160     1982784 :     libmesh_assert(!can_delaunay_swap(elem, s, -TOLERANCE).first);
     161             : #endif
     162      660928 : }
     163             : 
     164             : template <typename Container>
     165             : inline
     166        2356 : void libmesh_assert_delaunay(MeshBase & libmesh_dbg_var(mesh),
     167             :                              Container & new_elems)
     168             : {
     169        2356 :   libmesh_ignore(new_elems);
     170             : #ifndef NDEBUG
     171        4712 :   LOG_SCOPE("libmesh_assert_delaunay()", "Poly2TriTriangulator");
     172             : 
     173      436916 :   for (auto & elem : mesh.element_ptr_range())
     174      434560 :     libmesh_assert_locally_delaunay(*elem);
     175             : 
     176      228724 :   for (auto & [raw_elem, unique_elem] : new_elems)
     177             :     {
     178      226368 :       libmesh_ignore(unique_elem); // avoid warnings on old gcc
     179      226368 :       libmesh_assert_locally_delaunay(*raw_elem);
     180             :     }
     181             : #endif
     182        2356 : }
     183             : 
     184             : 
     185             : // Restore a triangulation's Delaunay property, starting with a set of
     186             : // all triangles that might initially not be locally Delaunay with
     187             : // their neighbors.
     188             : template <typename Container>
     189             : inline
     190       83638 : void restore_delaunay(Container & check_delaunay_on,
     191             :                       BoundaryInfo & boundary_info)
     192             : {
     193        4712 :   LOG_SCOPE("restore_delaunay()", "Poly2TriTriangulator");
     194             : 
     195     1346986 :   while (!check_delaunay_on.empty())
     196             :     {
     197     1184422 :       Elem & elem = **check_delaunay_on.begin();
     198     1184422 :       check_delaunay_on.erase(&elem);
     199     4737475 :       for (auto s : make_range(elem.n_sides()))
     200             :         {
     201             :           // Can we make a swap here?  With what neighbor, with what
     202             :           // far node?  Use a negative tolerance to avoid swapping
     203             :           // back and forth.
     204      100090 :           auto [can_swap, nn] =
     205     3553195 :             can_delaunay_swap(elem, s, -TOLERANCE*TOLERANCE);
     206     3553195 :           if (!can_swap)
     207     3553053 :             continue;
     208             : 
     209           8 :           Elem * neigh = elem.neighbor_ptr(s);
     210             : 
     211             :           // If we made it here it's time to diagonal swap
     212         142 :           const unsigned short n = (s+2)%3;
     213             : 
     214           8 :           const std::array<Node *,4> nodes {elem.node_ptr(n),
     215         142 :             elem.node_ptr((n+1)%3), neigh->node_ptr(nn),
     216         142 :             elem.node_ptr((n+2)%3)};
     217             : 
     218             :           // If we have to swap then either we or any of our neighbors
     219             :           // might no longer be Delaunay
     220         568 :           for (auto ds : make_range(3))
     221             :             {
     222         438 :               if (elem.neighbor_ptr(ds))
     223         355 :                 check_delaunay_on.insert(elem.neighbor_ptr(ds));
     224         438 :               if (neigh->neighbor_ptr(ds))
     225         355 :                 check_delaunay_on.insert(neigh->neighbor_ptr(ds));
     226             :             }
     227             : 
     228             :           // An interior boundary between two newly triangulated
     229             :           // triangles shouldn't have any bcids
     230           4 :           libmesh_assert(!boundary_info.n_boundary_ids(neigh, (nn+1)%3));
     231           4 :           libmesh_assert(!boundary_info.n_boundary_ids(&elem, (n+1)%3));
     232             : 
     233             :           // The two changing boundary sides might have bcids
     234           8 :           std::vector<boundary_id_type> bcids;
     235         142 :           boundary_info.boundary_ids(&elem, (n+2)%3, bcids);
     236         142 :           if (!bcids.empty())
     237             :             {
     238          71 :               boundary_info.remove_side(&elem, (n+2)%3);
     239          71 :               boundary_info.add_side(neigh, (nn+1)%3, bcids);
     240             :             }
     241             : 
     242         142 :           boundary_info.boundary_ids(neigh, (nn+2)%3, bcids);
     243         142 :           if (!bcids.empty())
     244             :             {
     245          71 :               boundary_info.remove_side(neigh, (nn+2)%3);
     246          71 :               boundary_info.add_side(&elem, (n+1)%3, bcids);
     247             :             }
     248             : 
     249         142 :           elem.set_node((n+2)%3, nodes[2]);
     250         142 :           neigh->set_node((nn+2)%3, nodes[0]);
     251             : 
     252             :           // No need for a temporary array to swap these around, if we
     253             :           // do it in the right order.
     254             :           //
     255             :           // Watch me neigh->neigh
     256           8 :           Elem * neighneigh = neigh->neighbor_ptr((nn+2)%3);
     257         142 :           if (neighneigh)
     258             :             {
     259          71 :               unsigned int snn = neighneigh->which_neighbor_am_i(neigh);
     260           4 :               neighneigh->set_neighbor(snn, &elem);
     261             :             }
     262             : 
     263           8 :           Elem * elemoldneigh = elem.neighbor_ptr((n+2)%3);
     264         142 :           if (elemoldneigh)
     265             :             {
     266          71 :               unsigned int seon = elemoldneigh->which_neighbor_am_i(&elem);
     267           4 :               elemoldneigh->set_neighbor(seon, neigh);
     268             :             }
     269             : 
     270          12 :           elem.set_neighbor((n+1)%3, neigh->neighbor_ptr((nn+2)%3));
     271         142 :           neigh->set_neighbor((nn+1)%3, elem.neighbor_ptr((n+2)%3));
     272           4 :           elem.set_neighbor((n+2)%3, neigh);
     273           4 :           neigh->set_neighbor((nn+2)%3, &elem);
     274             : 
     275             :           // Start over after this much change, don't just loop to the
     276             :           // next neighbor
     277           4 :           break;
     278             :         }
     279             :     }
     280       83638 : }
     281             : 
     282             : 
     283       16188 : unsigned int segment_intersection(const Elem & elem,
     284             :                                   Point & source,
     285             :                                   const Point & target,
     286             :                                   unsigned int source_side)
     287             : {
     288         456 :   libmesh_assert_equal_to(elem.dim(), 2);
     289             : 
     290       16188 :   const auto ns = elem.n_sides();
     291             : 
     292       34861 :   for (auto s : make_range(ns))
     293             :     {
     294             :       // Don't go backwards just because some FP roundoff said to
     295       34861 :       if (s == source_side)
     296       18147 :         continue;
     297             : 
     298       34435 :       const Point v0 = elem.point(s);
     299       34435 :       const Point v1 = elem.point((s+1)%ns);
     300             : 
     301             :       // Calculate intersection parameters (fractions of the distance
     302             :       // along each segment)
     303       34435 :       const Real raydx = target(0)-source(0),
     304       34435 :                  raydy = target(1)-source(1),
     305       34435 :                  edgedx = v1(0)-v0(0),
     306       34435 :                  edgedy = v1(1)-v0(1);
     307       34435 :       const Real denom = edgedx * raydy - edgedy * raydx;
     308             : 
     309             :       // divide-by-zero means the segments are parallel
     310       34435 :       if (denom == 0)
     311           0 :         continue;
     312             : 
     313       34435 :       const Real one_over_denom = 1 / denom;
     314             : 
     315       34435 :       const Real targetsdx = v1(0)-target(0),
     316       34435 :                  targetsdy = v1(1)-target(1);
     317             : 
     318       36375 :       const Real t_num = targetsdx * raydy -
     319       34435 :                          targetsdy * raydx;
     320       34435 :       const Real t = t_num * one_over_denom;
     321             : 
     322       34435 :       if (t < -TOLERANCE*TOLERANCE || t > 1 + TOLERANCE*TOLERANCE)
     323        6624 :         continue;
     324             : 
     325       27619 :       const Real u_num = targetsdx * edgedy - targetsdy * edgedx;
     326       27619 :       const Real u = u_num * one_over_denom;
     327             : 
     328       27619 :       if (u < -TOLERANCE*TOLERANCE || u > 1 + TOLERANCE*TOLERANCE)
     329       11109 :         continue;
     330             : 
     331             : /*
     332             :       // Partial workaround for an old poly2tri bug (issue #39): if we
     333             :       // end up with boundary points that are nearly-collinear but
     334             :       // infinitesimally concave, p2t::CDT::Triangulate throws a "null
     335             :       // triangle" exception.  So let's try to be infinitesimally
     336             :       // convex instead.
     337             :       const Real ray_fraction = (1-u) * (1+TOLERANCE*TOLERANCE);
     338             : */
     339       16188 :       const Real ray_fraction = (1-u);
     340             : 
     341       16188 :       source(0) += raydx * ray_fraction;
     342       16188 :       source(1) += raydy * ray_fraction;
     343       15732 :       return s;
     344             :     }
     345             : 
     346           0 :   return libMesh::invalid_uint;
     347             : }
     348             : 
     349             : }
     350             : 
     351             : namespace libMesh
     352             : {
     353             : //
     354             : // Function definitions for the Poly2TriTriangulator class
     355             : //
     356             : 
     357             : // Constructor
     358        2059 : Poly2TriTriangulator::Poly2TriTriangulator(UnstructuredMesh & mesh,
     359        2059 :                                            dof_id_type n_boundary_nodes)
     360             :   : TriangulatorInterface(mesh),
     361        1943 :     _n_boundary_nodes(n_boundary_nodes),
     362        2059 :     _refine_bdy_allowed(true)
     363             : {
     364        2059 : }
     365             : 
     366             : 
     367        3886 : Poly2TriTriangulator::~Poly2TriTriangulator() = default;
     368             : 
     369             : 
     370             : // Primary function responsible for performing the triangulation
     371        1988 : void Poly2TriTriangulator::triangulate()
     372             : {
     373         112 :   LOG_SCOPE("triangulate()", "Poly2TriTriangulator");
     374             : 
     375             :   // We only operate on serialized meshes.  And it's not safe to
     376             :   // serialize earlier, because it would then be possible for the user
     377             :   // to re-parallelize the mesh in between there and here.
     378        2092 :   MeshSerializer serializer(_mesh);
     379             : 
     380             :   // We don't yet support every set of Triangulator options in the
     381             :   // poly2tri implementation
     382             : 
     383             :   // We don't support convex hull triangulation, only triangulation of
     384             :   // (implicitly defined, by node ordering) polygons (with holes if
     385             :   // requested)
     386        1988 :   if (_triangulation_type != PSLG)
     387           0 :     libmesh_not_implemented();
     388             : 
     389             :   // We currently don't handle region specifications
     390        1988 :   if (_regions)
     391           0 :     libmesh_not_implemented();
     392             : 
     393             :   // We won't support quads any time soon, or 1D/3D in this interface
     394             :   // ever.
     395        2044 :   if (_elem_type != TRI3 &&
     396        1940 :       _elem_type != TRI6 &&
     397           2 :       _elem_type != TRI7)
     398           0 :     libmesh_not_implemented();
     399             : 
     400             :   // If we have no explicit segments defined, we may get them from
     401             :   // mesh elements
     402        1988 :   this->elems_to_segments();
     403             : 
     404             :   // If we *still* have no explicit segments defined, we get them from
     405             :   // the order of nodes.
     406        1775 :   this->nodes_to_segments(_n_boundary_nodes);
     407             : 
     408             :   // Insert additional new points in between existing boundary points,
     409             :   // if that is requested and reasonable
     410        1775 :   this->insert_any_extra_boundary_points();
     411             : 
     412             :   // Triangulate the points we have, then see if we need to add more;
     413             :   // repeat until we don't need to add more.
     414             :   //
     415             :   // This is currently done redundantly in parallel; make sure no
     416             :   // processor quits before the others.
     417         130 :   do
     418             :     {
     419         180 :       libmesh_parallel_only(_mesh.comm());
     420        6390 :       this->triangulate_current_points();
     421             :     }
     422        6390 :   while (this->insert_refinement_points());
     423             : 
     424          50 :   libmesh_parallel_only(_mesh.comm());
     425             : 
     426             :   // Okay, we really do need to support boundary ids soon, but we
     427             :   // don't yet
     428        1775 :   if (_markers)
     429           0 :     libmesh_not_implemented();
     430             : 
     431        1775 :   _mesh.set_mesh_dimension(2);
     432             : 
     433             :   // To the naked eye, a few smoothing iterations usually looks better,
     434             :   // so we do this by default unless the user says not to.
     435        1775 :   if (this->_smooth_after_generating)
     436           8 :     LaplaceMeshSmoother(_mesh, 2).smooth();
     437             : 
     438             :   // The user might have requested TRI6 or higher instead of TRI3.  We
     439             :   // can do this before prepare_for_use() because all we need for it
     440             :   // is find_neighbors(), which we did in insert_refinement_points()
     441        1775 :   this->increase_triangle_order();
     442             : 
     443             :   // Prepare the mesh for use before returning.  This ensures (among
     444             :   // other things) that it is partitioned and therefore users can
     445             :   // iterate over local elements, etc.
     446        1704 :   _mesh.prepare_for_use();
     447        1972 : }
     448             : 
     449             : 
     450         710 : void Poly2TriTriangulator::set_desired_area_function
     451             :   (FunctionBase<Real> * desired)
     452             : {
     453         710 :   if (desired)
     454         280 :     _desired_area_func = desired->clone();
     455             :   else
     456          16 :     _desired_area_func.reset();
     457         710 : }
     458             : 
     459             : 
     460      763960 : FunctionBase<Real> * Poly2TriTriangulator::get_desired_area_function ()
     461             : {
     462      763960 :   return _desired_area_func.get();
     463             : }
     464             : 
     465             : 
     466       22365 : bool Poly2TriTriangulator::is_refine_boundary_allowed
     467             :   (const BoundaryInfo & boundary_info,
     468             :    const Elem & elem,
     469             :    unsigned int side)
     470             : {
     471             :   // We should only be calling this on a boundary side
     472         630 :   libmesh_assert(!elem.neighbor_ptr(side));
     473             : 
     474        1260 :   std::vector<boundary_id_type> bcids;
     475       22365 :   boundary_info.boundary_ids(&elem, side, bcids);
     476             : 
     477             :   // We should have one bcid on every boundary side.
     478         630 :   libmesh_assert_equal_to(bcids.size(), 1);
     479             : 
     480       22365 :   if (bcids[0] == 0)
     481       19525 :     return this->refine_boundary_allowed();
     482             : 
     483             :   // If we're not on an outer boundary side we'd better be on a hole
     484             :   // side
     485          80 :   libmesh_assert(this->_holes);
     486             : 
     487        2840 :   const boundary_id_type hole_num = bcids[0]-1;
     488          80 :   libmesh_assert_less(hole_num, this->_holes->size());
     489        2840 :   const Hole * hole = (*this->_holes)[hole_num];
     490        2840 :   return hole->refine_boundary_allowed();
     491             : }
     492             : 
     493             : 
     494        6390 : void Poly2TriTriangulator::triangulate_current_points()
     495             : {
     496         360 :   LOG_SCOPE("triangulate_current_points()", "Poly2TriTriangulator");
     497             : 
     498             :   // Will the triangulation have holes?
     499        6390 :   const std::size_t n_holes = _holes != nullptr ? _holes->size() : 0;
     500             : 
     501             :   // Mapping from Poly2Tri points to libMesh nodes, so we can get the
     502             :   // connectivity translated back later.
     503         360 :   std::map<const p2t::Point, Node *, P2TPointCompare> point_node_map;
     504             : 
     505             :   // Poly2Tri data structures
     506             :   // Poly2Tri takes vectors of pointers-to-Point for some reason, but
     507             :   // we'll just make those shims to vectors of Point rather than
     508             :   // individually/manually heap allocating everything.
     509         540 :   std::vector<p2t::Point> outer_boundary_points;
     510        6750 :   std::vector<std::vector<p2t::Point>> inner_hole_points(n_holes);
     511             : 
     512        6390 :   dof_id_type nn = _mesh.max_node_id();
     513        6390 :   libmesh_error_msg_if
     514             :     (!nn, "Poly2TriTriangulator cannot triangulate an empty mesh!");
     515             : 
     516             :   // Unless we're using an explicit segments list, we assume node ids
     517             :   // are contiguous here.
     518        6390 :   if (this->segments.empty())
     519           0 :     libmesh_error_msg_if
     520             :       (_mesh.n_nodes() != nn,
     521             :        "Poly2TriTriangulator needs contiguous node ids or explicit segments!");
     522             : 
     523             :   // And if we have more nodes than outer boundary points, the rest
     524             :   // may be interior "Steiner points".  We use a set here so we can
     525             :   // cheaply search and erase from it later, when we're identifying
     526             :   // hole points.
     527         360 :   std::set<p2t::Point, P2TPointCompare> steiner_points;
     528             : 
     529             :   // If we were asked to use all mesh nodes as boundary nodes, now's
     530             :   // the time to see how many that is.
     531        6390 :   if (_n_boundary_nodes == DofObject::invalid_id)
     532             :     {
     533        1775 :       _n_boundary_nodes = _mesh.n_nodes();
     534          50 :       libmesh_assert_equal_to(std::ptrdiff_t(_n_boundary_nodes),
     535             :                               std::distance(_mesh.nodes_begin(),
     536             :                                             _mesh.nodes_end()));
     537             : 
     538             :     }
     539             :   else
     540         130 :     libmesh_assert_less_equal(_n_boundary_nodes,
     541             :                               _mesh.n_nodes());
     542             : 
     543             :   // Prepare poly2tri points for our nodes, sorted into outer boundary
     544             :   // points and interior Steiner points.
     545             : 
     546        6390 :   if (this->segments.empty())
     547             :     {
     548             :       // If we have no segments even after taking elems into account,
     549             :       // the nodal id ordering defines our outer polyline ordering
     550           0 :       for (auto & node : _mesh.node_ptr_range())
     551             :         {
     552           0 :           const p2t::Point pt = to_p2t(*node);
     553             : 
     554             :           // If we're out of boundary nodes, the rest are going to be
     555             :           // Steiner points or hole points
     556           0 :           if (node->id() < _n_boundary_nodes)
     557           0 :             outer_boundary_points.push_back(pt);
     558             :           else
     559           0 :             steiner_points.insert(pt);
     560             : 
     561             :           // We're not going to support overlapping nodes on the boundary
     562           0 :           if (point_node_map.count(pt))
     563           0 :             libmesh_not_implemented();
     564             : 
     565           0 :           point_node_map.emplace(pt, node);
     566           0 :         }
     567             :     }
     568             :   // If we have explicit segments defined, that's our outer polyline
     569             :   // ordering:
     570             :   else
     571             :     {
     572             :       // Let's make sure our segments are in order
     573         180 :       dof_id_type last_id = DofObject::invalid_id;
     574             : 
     575             :       // Add nodes from every segment, in order, to the outer polyline
     576      142852 :       for (auto [segment_start, segment_end] : this->segments)
     577             :         {
     578      136462 :           if (last_id != DofObject::invalid_id)
     579      130072 :             libmesh_error_msg_if(segment_start != last_id,
     580             :                                  "Disconnected triangulator segments");
     581      130696 :           last_id = segment_end;
     582             : 
     583      136462 :           Node * node = _mesh.query_node_ptr(segment_start);
     584             : 
     585      136462 :           libmesh_error_msg_if(!node,
     586             :                                "Triangulator segments reference nonexistent node id " <<
     587             :                                segment_start);
     588             : 
     589      136462 :           outer_boundary_points.emplace_back(double((*node)(0)), double((*node)(1)));
     590        3844 :           p2t::Point * pt = &outer_boundary_points.back();
     591             : 
     592             :           // We're not going to support overlapping nodes on the boundary
     593        3844 :           if (point_node_map.count(*pt))
     594           0 :             libmesh_not_implemented_msg
     595             :               ("Triangulating overlapping boundary nodes is unsupported");
     596             : 
     597      132618 :           point_node_map.emplace(*pt, node);
     598             :         }
     599             : 
     600        6390 :         libmesh_error_msg_if(last_id != this->segments[0].first,
     601             :                              "Non-closed-loop triangulator segments");
     602             : 
     603             :       // If we have points that aren't in any segments, those will be
     604             :       // Steiner points
     605      987432 :       for (auto & node : _mesh.node_ptr_range())
     606             :         {
     607      515672 :           const p2t::Point pt = to_p2t(*node);
     608      501544 :           if (const auto it = point_node_map.find(pt);
     609       14128 :               it == point_node_map.end())
     610             :             {
     611      354798 :               steiner_points.insert(pt);
     612      354798 :               point_node_map.emplace(pt, node);
     613             :             }
     614             :           else
     615        3844 :             libmesh_assert_equal_to(it->second, node);
     616        6030 :         }
     617             :     }
     618             : 
     619             :   // If we have any elements from a previous triangulation, we're
     620             :   // going to replace them with our own.  If we have any elements that
     621             :   // were used to create our segments, we're done creating and we no
     622             :   // longer need them.
     623        6390 :   _mesh.clear_elems();
     624             : 
     625             :   // Keep track of what boundary ids we want to assign to each new
     626             :   // triangle.  We'll give the outer boundary BC 0, and give holes ids
     627             :   // starting from 1.  We've already got the point_node_map to find
     628             :   // nodes, so we can just key on pairs of node ids to identify a side.
     629             :   std::unordered_map<std::pair<dof_id_type,dof_id_type>,
     630         360 :                      boundary_id_type, libMesh::hash> side_boundary_id;
     631             : 
     632        6390 :   const boundary_id_type outer_bcid = 0;
     633         360 :   const std::size_t n_outer = outer_boundary_points.size();
     634             : 
     635      142852 :   for (auto i : make_range(n_outer))
     636             :     {
     637             :       const Node * node1 =
     638      140306 :         libmesh_map_find(point_node_map, outer_boundary_points[i]),
     639             :                  * node2 =
     640      140306 :         libmesh_map_find(point_node_map, outer_boundary_points[(i+1)%n_outer]);
     641             : 
     642      132618 :       side_boundary_id.emplace(std::make_pair(node1->id(),
     643       11532 :                                               node2->id()),
     644        3844 :                                outer_bcid);
     645             :     }
     646             : 
     647             :   // Create poly2tri triangulator with our mesh points
     648        6570 :   std::vector<p2t::Point *> outer_boundary_pointers(n_outer);
     649             :   std::transform(outer_boundary_points.begin(),
     650             :                  outer_boundary_points.end(),
     651             :                  outer_boundary_pointers.begin(),
     652        4024 :                  [](p2t::Point & p) { return &p; });
     653             : 
     654             : 
     655             :   // Make sure shims for holes last as long as the CDT does; the
     656             :   // poly2tri headers don't make clear whether or not they're hanging
     657             :   // on to references to these vectors, and it would be reasonable for
     658             :   // them to do so.
     659        6750 :   std::vector<std::vector<p2t::Point *>> inner_hole_pointers(n_holes);
     660             : 
     661        6750 :   p2t::CDT cdt{outer_boundary_pointers};
     662             : 
     663             :   // Add any holes
     664       12780 :   for (auto h : make_range(n_holes))
     665             :     {
     666        6570 :       const Hole * initial_hole = (*_holes)[h];
     667         180 :       auto it = replaced_holes.find(initial_hole);
     668             :       const Hole & our_hole =
     669        6432 :         (it == replaced_holes.end()) ?
     670          42 :         *initial_hole : *it->second;
     671         360 :       auto & poly2tri_hole = inner_hole_points[h];
     672             : 
     673       53179 :       for (auto i : make_range(our_hole.n_points()))
     674             :         {
     675       46789 :           Point p = our_hole.point(i);
     676       92260 :           poly2tri_hole.emplace_back(to_p2t(p));
     677             : 
     678        1318 :           const auto & pt = poly2tri_hole.back();
     679             : 
     680             :           // This won't be a steiner point.
     681        1318 :           steiner_points.erase(pt);
     682             : 
     683             :           // If we see a hole point already in the mesh, we'll share
     684             :           // that node.  This might be a problem if it's a boundary
     685             :           // node, but it might just be the same hole point already
     686             :           // added during a previous triangulation refinement step.
     687        1318 :           if (point_node_map.count(pt))
     688             :             {
     689        1166 :               libmesh_assert_equal_to
     690             :                 (point_node_map[pt],
     691             :                  _mesh.query_node_ptr(point_node_map[pt]->id()));
     692             :             }
     693             :           else
     694             :             {
     695        5396 :               Node * node = _mesh.add_point(p, nn++);
     696        5396 :               point_node_map[pt] = node;
     697             :             }
     698             :         }
     699             : 
     700        6390 :       const boundary_id_type inner_bcid = h+1;
     701         360 :       const std::size_t n_inner = poly2tri_hole.size();
     702             : 
     703       53179 :       for (auto i : make_range(n_inner))
     704             :         {
     705             :           const Node * node1 =
     706       48107 :             libmesh_map_find(point_node_map, poly2tri_hole[i]),
     707             :                      * node2 =
     708       48107 :             libmesh_map_find(point_node_map, poly2tri_hole[(i+1)%n_inner]);
     709             : 
     710       45471 :           side_boundary_id.emplace(std::make_pair(node1->id(),
     711        3954 :                                                   node2->id()),
     712        1318 :                                    inner_bcid);
     713             :         }
     714             : 
     715         360 :       auto & poly2tri_ptrs = inner_hole_pointers[h];
     716        6390 :       poly2tri_ptrs.resize(n_inner);
     717             : 
     718             :       std::transform(poly2tri_hole.begin(),
     719             :                      poly2tri_hole.end(),
     720             :                      poly2tri_ptrs.begin(),
     721        1498 :                      [](p2t::Point & p) { return &p; });
     722             : 
     723        6390 :       cdt.AddHole(poly2tri_ptrs);
     724             :     }
     725             : 
     726             :   // Add any steiner points.  We had them in a set, but post-C++11
     727             :   // that won't give us non-const element access (even if we
     728             :   // pinky-promise not to change the elements in any way that affects
     729             :   // our Comparator), and Poly2Tri wants non-const elements (to store
     730             :   // edge data?), so we need to move them here.
     731        6750 :   std::vector<p2t::Point> steiner_vector(steiner_points.begin(), steiner_points.end());
     732         180 :   steiner_points.clear();
     733      330079 :   for (auto & p : steiner_vector)
     734      323689 :     cdt.AddPoint(&p);
     735             : 
     736             :   // Triangulate!
     737        6390 :   cdt.Triangulate();
     738             : 
     739             :   // Get poly2tri triangles, turn them into libMesh triangles
     740        6570 :   std::vector<p2t::Triangle *> triangles = cdt.GetTriangles();
     741             : 
     742             :   // Do our own numbering, even on DistributedMesh
     743         180 :   dof_id_type next_id = 0;
     744             : 
     745        6390 :   BoundaryInfo & boundary_info = _mesh.get_boundary_info();
     746        6390 :   boundary_info.clear();
     747             : 
     748             :   // Add the triangles to our Mesh data structure.
     749      837019 :   for (auto ptri_ptr : triangles)
     750             :     {
     751       23398 :       p2t::Triangle & ptri = *ptri_ptr;
     752             : 
     753             :       // We always use TRI3 here, since that's what we have nodes for;
     754             :       // if we need a higher order we can convert at the end.
     755      854027 :       auto elem = Elem::build_with_id(TRI3, next_id++);
     756     3322516 :       for (auto v : make_range(3))
     757             :         {
     758       70194 :           const p2t::Point & vertex = *ptri.GetPoint(v);
     759             : 
     760     2491887 :           Node * node = libmesh_map_find(point_node_map, vertex);
     761       70194 :           libmesh_assert(node);
     762     2491887 :           elem->set_node(v, node);
     763             :         }
     764             : 
     765             :       // We expect a consistent triangle orientation
     766       23398 :       libmesh_assert(!elem->is_flipped());
     767             : 
     768      877425 :       Elem * added_elem = _mesh.add_elem(std::move(elem));
     769             : 
     770     3322516 :       for (auto v : make_range(3))
     771             :         {
     772      140388 :           const Node & node1 = added_elem->node_ref(v),
     773     2491887 :                      & node2 = added_elem->node_ref((v+1)%3);
     774             : 
     775     2491887 :           auto it = side_boundary_id.find(std::make_pair(node1.id(), node2.id()));
     776     2491887 :           if (it == side_boundary_id.end())
     777     2355425 :             it = side_boundary_id.find(std::make_pair(node2.id(), node1.id()));
     778     2491887 :           if (it != side_boundary_id.end())
     779      183251 :             boundary_info.add_side(added_elem, v, it->second);
     780             :         }
     781      783833 :     }
     782       18450 : }
     783             : 
     784             : 
     785             : 
     786        6390 : bool Poly2TriTriangulator::insert_refinement_points()
     787             : {
     788         360 :   LOG_SCOPE("insert_refinement_points()", "Poly2TriTriangulator");
     789             : 
     790        6390 :   if (this->minimum_angle() != 0)
     791           0 :     libmesh_not_implemented();
     792             : 
     793             :   // We need neighbor pointers for ray casting and cavity finding
     794        6390 :   UnstructuredMesh & mesh = dynamic_cast<UnstructuredMesh &>(this->_mesh);
     795        6390 :   mesh.find_neighbors();
     796             : 
     797        1974 :   if (this->desired_area() == 0 &&
     798        6461 :       this->get_desired_area_function() == nullptr &&
     799           2 :       !this->has_auto_area_function())
     800           2 :     return false;
     801             : 
     802        6319 :   BoundaryInfo & boundary_info = _mesh.get_boundary_info();
     803             : 
     804             :   // We won't immediately add these, lest we invalidate iterators on a
     805             :   // ReplicatedMesh.  They'll still be in the mesh neighbor topology
     806             :   // for the purpose of doing Delaunay cavity stuff, so we need to
     807             :   // manage memory here, but there's no point in adding them to the
     808             :   // Mesh just to remove them again afterward when we hit up poly2tri.
     809             : 
     810             :   // We'll need to be able to remove new elems from new_elems, in
     811             :   // cases where a later refinement insertion has a not-yet-added
     812             :   // element in its cavity, so we'll use a map here to make searching
     813             :   // possible.
     814             :   //
     815             :   // For parallel consistency, we can't order a container we plan to
     816             :   // iterate through based on Elem * or a hash of it.  We'll be doing
     817             :   // Delaunay swaps so we can't iterate based on geometry.  These are
     818             :   // not-yet-added elements so we can't iterate based on proper
     819             :   // element ids ... but we can set a temporary element id to use for
     820             :   // the purpose.
     821             :   struct cmp {
     822      687618 :     bool operator()(Elem * a, Elem * b) const {
     823      687618 :       libmesh_assert(a == b || a->id() != b->id());
     824     1949161 :       return (a->id() < b->id());
     825             :     }
     826             :   } comp;
     827             : 
     828         356 :   std::map<Elem *, std::unique_ptr<Elem>, decltype(comp)> new_elems(comp);
     829             : 
     830             :   // We should already be Delaunay when we get here, otherwise we
     831             :   // won't be able to stay Delaunay later.  But we're *not* always
     832             :   // Delaunay when we get here?  What the hell, poly2tri?  Fixing this
     833             :   // is expensive!
     834             :   {
     835             :     // restore_delaunay should get to the same Delaunay triangulation up to
     836             :     // isomorphism regardless of ordering ... but we actually care
     837             :     // about the isomorphisms!  If a triangle's nodes are permuted on
     838             :     // one processor vs another that's an issue.  So sort our input
     839             :     // carefully.
     840             :     std::set<Elem *, decltype(comp)> all_elems
     841        6853 :       { mesh.elements_begin(), mesh.elements_end(), comp };
     842             : 
     843        6319 :     restore_delaunay(all_elems, boundary_info);
     844             : 
     845         178 :     libmesh_assert_delaunay(mesh, new_elems);
     846             :   }
     847             : 
     848             :   // Map of which points follow which in the boundary polylines.  If
     849             :   // we have to add new boundary points, we'll use this to construct
     850             :   // an updated this->segments to retriangulate with.  If we have to
     851             :   // add new hole points, we'll use this to insert points into an
     852             :   // ArbitraryHole.
     853         356 :   std::unordered_map<Point, Node *> next_boundary_node;
     854             : 
     855             :   // In cases where we've been working with contiguous node id ranges;
     856             :   // let's keep it that way.
     857        6319 :   dof_id_type nn = _mesh.max_node_id();
     858        6319 :   dof_id_type ne = _mesh.max_elem_id();
     859             : 
     860             :   // We can't handle duplicated nodes.  We shouldn't ever create one,
     861             :   // but let's make sure of that.
     862             : #ifdef DEBUG
     863         178 :   std::unordered_set<Point> mesh_points;
     864       14450 :   for (const Node * node : mesh.node_ptr_range())
     865             :     {
     866       14272 :       libmesh_assert(!mesh_points.count(*node));
     867       14272 :       mesh_points.insert(*node);
     868             :     }
     869             : #endif
     870             : 
     871       72963 :   auto add_point = [&mesh,
     872             : #ifdef DEBUG
     873             :                     &mesh_points,
     874             : #endif
     875       86031 :                     &nn](const Point & p)
     876             :     {
     877             : #ifdef DEBUG
     878        2178 :       libmesh_assert(!mesh_points.count(p));
     879        2178 :       mesh_points.insert(p);
     880             : #endif
     881       76721 :       return mesh.add_point(p, nn++);
     882        6141 :     };
     883             : 
     884     1493752 :   for (auto & elem : mesh.element_ptr_range())
     885             :     {
     886             :       // element_ptr_range skips deleted elements ... right?
     887       21468 :       libmesh_assert(elem);
     888       21468 :       libmesh_assert(elem->valid_id());
     889             : 
     890             :       // We only handle triangles in our triangulation
     891       21468 :       libmesh_assert_equal_to(elem->level(), 0u);
     892       21468 :       libmesh_assert_equal_to(elem->type(), TRI3);
     893             : 
     894             :       // If this triangle is as small as we desire, move along
     895      762114 :       if (!should_refine_elem(*elem))
     896      684795 :         continue;
     897             : 
     898             :       // Otherwise add a Steiner point.  We'd like to add the
     899             :       // circumcenter ...
     900       77319 :       Point new_pt = elem->quasicircumcenter();
     901             : 
     902             :       // And to give it a node;
     903       77319 :       Node * new_node = nullptr;
     904             : 
     905             :       // But that might be outside our triangle, or even outside the
     906             :       // boundary.  We'll find a triangle that should contain our new
     907             :       // point
     908       77319 :       Elem * cavity_elem = elem; // Start looking at elem anyway
     909             : 
     910             :       // We'll refine a boundary later if necessary.
     911       20033 :       auto boundary_refine = [this, &next_boundary_node,
     912             :                               &cavity_elem, &new_node]
     913       89700 :         (unsigned int side)
     914             :       {
     915         598 :         libmesh_ignore(this); // Only used in dbg/devel
     916         598 :         libmesh_assert(new_node);
     917         598 :         libmesh_assert(new_node->valid_id());
     918             : 
     919       21229 :         Node * old_segment_start = cavity_elem->node_ptr(side),
     920       21229 :              * old_segment_end   = cavity_elem->node_ptr((side+1)%3);
     921         598 :         libmesh_assert(old_segment_start);
     922         598 :         libmesh_assert(old_segment_start->valid_id());
     923         598 :         libmesh_assert(old_segment_end);
     924         598 :         libmesh_assert(old_segment_end->valid_id());
     925             : 
     926       21229 :         if (auto it = next_boundary_node.find(*old_segment_start);
     927         598 :             it != next_boundary_node.end())
     928             :           {
     929           0 :             libmesh_assert(it->second == old_segment_end);
     930           0 :             it->second = new_node;
     931             :           }
     932             :         else
     933             :           {
     934             :             // This would be an O(N) sanity check if we already
     935             :             // have a segments vector or any holes.  :-P
     936         598 :             libmesh_assert(!this->segments.empty() ||
     937             :                            (_holes && !_holes->empty()) ||
     938             :                            (old_segment_end->id() ==
     939             :                             old_segment_start->id() + 1));
     940       21229 :             next_boundary_node[*old_segment_start] = new_node;
     941             :           }
     942             : 
     943       21229 :         next_boundary_node[*new_node] = old_segment_end;
     944       96370 :       };
     945             : 
     946             :       // Let's find a triangle containing our new point, or at least
     947             :       // containing the end of a ray leading from our current triangle
     948             :       // to the new point.
     949       77319 :       Point ray_start = elem->vertex_average();
     950             : 
     951             :       // What side are we coming from, and what side are we going to?
     952        2178 :       unsigned int source_side = invalid_uint;
     953        2178 :       unsigned int side = invalid_uint;
     954             : 
     955       85768 :       while (!cavity_elem->contains_point(new_pt))
     956             :         {
     957       16188 :           side = segment_intersection(*cavity_elem, ray_start, new_pt, source_side);
     958             : 
     959         456 :           libmesh_assert_not_equal_to (side, invalid_uint);
     960             : 
     961       16188 :           Elem * neigh = cavity_elem->neighbor_ptr(side);
     962             :           // If we're on a boundary, stop there.  Refine the boundary
     963             :           // if we're allowed, the boundary element otherwise.
     964       16188 :           if (!neigh)
     965             :             {
     966        7739 :               if (this->is_refine_boundary_allowed(boundary_info,
     967             :                                                    *cavity_elem,
     968             :                                                    side))
     969             :                 {
     970        7100 :                   new_pt = ray_start;
     971        7100 :                   new_node = add_point(new_pt);
     972        7100 :                   boundary_refine(side);
     973             :                 }
     974             :               else
     975             :                 {
     976             :                   // Should we just add the vertex average of the
     977             :                   // boundary element, to minimize the number of
     978             :                   // slivers created?
     979             :                   //
     980             :                   // new_pt = cavity_elem->vertex_average();
     981             :                   //
     982             :                   // That works for a while, but it
     983             :                   // seems to be able to "run away" and leave us with
     984             :                   // crazy slivers on boundaries if we push interior
     985             :                   // refinement too far while disabling boundary
     986             :                   // refinement.
     987             :                   //
     988             :                   // Let's go back to refining our original problem
     989             :                   // element.
     990         639 :                   cavity_elem = elem;
     991         639 :                   new_pt = cavity_elem->vertex_average();
     992         639 :                   new_node = add_point(new_pt);
     993             : 
     994             :                   // This was going to be a side refinement but it's
     995             :                   // now an internal refinement
     996          18 :                   side = invalid_uint;
     997             :                 }
     998             : 
     999         218 :               break;
    1000             :             }
    1001             : 
    1002        8449 :           source_side = neigh->which_neighbor_am_i(cavity_elem);
    1003        8449 :           cavity_elem = neigh;
    1004         238 :           side = invalid_uint;
    1005             :         }
    1006             : 
    1007             :       // If we're ready to create a new node and we're not on a
    1008             :       // boundary ... should we be?  We don't want to create any
    1009             :       // sliver elements or confuse poly2tri or anything.
    1010       77319 :       if (side == invalid_uint && !new_node)
    1011             :         {
    1012        1960 :           unsigned int worst_side = libMesh::invalid_uint;
    1013        1960 :           Real worst_cos = 1;
    1014      278320 :           for (auto s : make_range(3u))
    1015             :             {
    1016             :               // We never snap to a non-domain-boundary
    1017      214620 :               if (cavity_elem->neighbor_ptr(s))
    1018      177951 :                 continue;
    1019             : 
    1020       25631 :               Real ax = cavity_elem->point(s)(0) - new_pt(0),
    1021       25631 :                    ay = cavity_elem->point(s)(1) - new_pt(1),
    1022       25631 :                    bx = cavity_elem->point((s+1)%3)(0) - new_pt(0),
    1023       25631 :                    by = cavity_elem->point((s+1)%3)(1) - new_pt(1);
    1024       27075 :               const Real my_cos = (ax*bx+ay*by) /
    1025       25631 :                                   std::sqrt(ax*ax+ay*ay) /
    1026       25631 :                                   std::sqrt(bx*bx+by*by);
    1027             : 
    1028       25631 :               if (my_cos < worst_cos)
    1029             :                 {
    1030         700 :                   worst_side = s;
    1031         700 :                   worst_cos = my_cos;
    1032             :                 }
    1033             :             }
    1034             : 
    1035             :           // If we'd create a sliver element on the side, let's just
    1036             :           // refine the side instead, if we're allowed.
    1037       69580 :           if (worst_cos < -0.6) // -0.5 is the best we could enforce?
    1038             :             {
    1039         412 :               side = worst_side;
    1040             : 
    1041       14626 :               if (this->is_refine_boundary_allowed(boundary_info,
    1042             :                                                    *cavity_elem,
    1043             :                                                    side))
    1044             :                 {
    1045             :                   // Let's just try bisecting for now
    1046       14129 :                   new_pt = (cavity_elem->point(side) +
    1047       14129 :                             cavity_elem->point((side+1)%3)) / 2;
    1048       14129 :                   new_node = add_point(new_pt);
    1049       14129 :                   boundary_refine(side);
    1050             :                 }
    1051             :               else // Do the best we can under these restrictions
    1052             :                 {
    1053         497 :                   new_pt = cavity_elem->vertex_average();
    1054         497 :                   new_node = add_point(new_pt);
    1055             : 
    1056             :                   // This was going to be a side refinement but it's
    1057             :                   // now an internal refinement
    1058          14 :                   side = invalid_uint;
    1059             :                 }
    1060             :             }
    1061             :           else
    1062       55366 :             new_node = add_point(new_pt);
    1063             :         }
    1064             :       else
    1065         218 :         libmesh_assert(new_node);
    1066             : 
    1067             :       // Find the Delaunay cavity around the new point.
    1068        4356 :       std::set<Elem *, decltype(comp)> cavity(comp);
    1069             : 
    1070       79497 :       std::set<Elem *, decltype(comp)> unchecked_cavity ({cavity_elem}, comp);
    1071      242607 :       while (!unchecked_cavity.empty())
    1072             :         {
    1073        9312 :           std::set<Elem *, decltype(comp)> checking_cavity(comp);
    1074        4656 :           checking_cavity.swap(unchecked_cavity);
    1075      385530 :           for (Elem * checking_elem : checking_cavity)
    1076             :             {
    1077      880968 :               for (auto s : make_range(3u))
    1078             :                 {
    1079      660726 :                   Elem * neigh = checking_elem->neighbor_ptr(s);
    1080      660726 :                   if (!neigh || checking_cavity.count(neigh) || cavity.count(neigh))
    1081      204693 :                     continue;
    1082             : 
    1083      456033 :                   if (in_circumcircle(*neigh, new_pt, TOLERANCE*TOLERANCE))
    1084      138897 :                     unchecked_cavity.insert(neigh);
    1085             :                 }
    1086             :             }
    1087             : 
    1088        4656 :           libmesh_merge_move(cavity, checking_cavity);
    1089             :         }
    1090             : 
    1091             :       // Retriangulate the Delaunay cavity.
    1092             :       // Each of our cavity triangle edges that are exterior to the
    1093             :       // cavity will be a source of one new triangle.
    1094             : 
    1095             :       // Set of elements that might need Delaunay swaps
    1096        4356 :       std::set<Elem *, decltype(comp)> check_delaunay_on(comp);
    1097             : 
    1098             :       // Keep maps for doing neighbor pointer assignment.  Not going
    1099             :       // to iterate through these so hashing pointers is fine.
    1100             :       std::unordered_map<Node *, std::pair<Elem *, boundary_id_type>>
    1101        4356 :         neighbors_CCW, neighbors_CW;
    1102             : 
    1103      297561 :       for (Elem * old_elem : cavity)
    1104             :         {
    1105      880968 :           for (auto s : make_range(3u))
    1106             :             {
    1107      660726 :               Elem * neigh = old_elem->neighbor_ptr(s);
    1108      660726 :               if (!neigh || !cavity.count(neigh))
    1109             :                 {
    1110      374880 :                   Node * node_CW = old_elem->node_ptr(s),
    1111      374880 :                        * node_CCW = old_elem->node_ptr((s+1)%3);
    1112             : 
    1113             :                   auto set_neighbors =
    1114      353760 :                     [&neighbors_CW, &neighbors_CCW, &node_CW,
    1115             :                       &node_CCW, &boundary_info]
    1116     1178636 :                     (Elem * new_neigh, boundary_id_type bcid)
    1117             :                   {
    1118             :                     // Set clockwise neighbor and vice-versa if possible
    1119      374880 :                     if (const auto CW_it = neighbors_CW.find(node_CW);
    1120       10560 :                         CW_it == neighbors_CW.end())
    1121             :                       {
    1122        5414 :                         libmesh_assert(!neighbors_CCW.count(node_CW));
    1123       15974 :                         neighbors_CCW[node_CW] = std::make_pair(new_neigh, bcid);
    1124             :                       }
    1125             :                     else
    1126             :                       {
    1127      182683 :                         Elem * neigh_CW = CW_it->second.first;
    1128      182683 :                         if (new_neigh)
    1129             :                           {
    1130        9688 :                             new_neigh->set_neighbor(0, neigh_CW);
    1131      171962 :                             boundary_id_type bcid_CW = CW_it->second.second;
    1132      171962 :                             if (bcid_CW != BoundaryInfo::invalid_id)
    1133       15123 :                               boundary_info.add_side(new_neigh, 0, bcid_CW);
    1134             : 
    1135             :                           }
    1136      182683 :                         if (neigh_CW)
    1137             :                           {
    1138        9440 :                             neigh_CW->set_neighbor(2, new_neigh);
    1139      167560 :                             if (bcid != BoundaryInfo::invalid_id)
    1140       10721 :                               boundary_info.add_side(neigh_CW, 2, bcid);
    1141             :                           }
    1142        5146 :                         neighbors_CW.erase(CW_it);
    1143             :                       }
    1144             : 
    1145             :                     // Set counter-CW neighbor and vice-versa if possible
    1146      374880 :                     if (const auto CCW_it = neighbors_CCW.find(node_CCW);
    1147       10560 :                         CCW_it == neighbors_CCW.end())
    1148             :                       {
    1149        5146 :                         libmesh_assert(!neighbors_CW.count(node_CCW));
    1150        5146 :                         neighbors_CW[node_CCW] = std::make_pair(new_neigh, bcid);
    1151             :                       }
    1152             :                     else
    1153             :                       {
    1154      192197 :                         Elem * neigh_CCW = CCW_it->second.first;
    1155      192197 :                         if (new_neigh)
    1156             :                           {
    1157      186091 :                             boundary_id_type bcid_CCW = CCW_it->second.second;
    1158       10484 :                             new_neigh->set_neighbor(2, neigh_CCW);
    1159      186091 :                             if (bcid_CCW != BoundaryInfo::invalid_id)
    1160       10508 :                               boundary_info.add_side(new_neigh, 2, bcid_CCW);
    1161             :                           }
    1162      192197 :                         if (neigh_CCW)
    1163             :                           {
    1164       10236 :                             neigh_CCW->set_neighbor(0, new_neigh);
    1165      181689 :                             if (bcid != BoundaryInfo::invalid_id)
    1166        6106 :                               boundary_info.add_side(neigh_CCW, 0, bcid);
    1167             :                           }
    1168        5414 :                         neighbors_CCW.erase(CCW_it);
    1169             :                       }
    1170      739200 :                   };
    1171             : 
    1172             :                   // We aren't going to try to add a sliver element if we
    1173             :                   // have a new boundary node here.  We do need to
    1174             :                   // keep track of other elements' neighbors, though.
    1175      374880 :                   if (old_elem == cavity_elem &&
    1176        3040 :                       s == side)
    1177             :                     {
    1178        1196 :                       std::vector<boundary_id_type> bcids;
    1179       21229 :                       boundary_info.boundary_ids(old_elem, s, bcids);
    1180         598 :                       libmesh_assert_equal_to(bcids.size(), 1);
    1181       21229 :                       set_neighbors(nullptr, bcids[0]);
    1182         598 :                       continue;
    1183             :                     }
    1184             : 
    1185      363613 :                   auto new_elem = Elem::build_with_id(TRI3, ne++);
    1186      353651 :                   new_elem->set_node(0, new_node);
    1187      353651 :                   new_elem->set_node(1, node_CW);
    1188      353651 :                   new_elem->set_node(2, node_CCW);
    1189        9962 :                   libmesh_assert(!new_elem->is_flipped());
    1190             : 
    1191             :                   // Set in-and-out-of-cavity neighbor pointers
    1192       19924 :                   new_elem->set_neighbor(1, neigh);
    1193      353651 :                   if (neigh)
    1194             :                     {
    1195             :                       const unsigned int neigh_s =
    1196      313110 :                         neigh->which_neighbor_am_i(old_elem);
    1197       26460 :                       neigh->set_neighbor(neigh_s, new_elem.get());
    1198             :                     }
    1199             :                   else
    1200             :                     {
    1201        2284 :                       std::vector<boundary_id_type> bcids;
    1202       40541 :                       boundary_info.boundary_ids(old_elem, s, bcids);
    1203       40541 :                       boundary_info.add_side(new_elem.get(), 1, bcids);
    1204             :                     }
    1205             : 
    1206             :                   // Set in-cavity neighbors' neighbor pointers
    1207      363613 :                   set_neighbors(new_elem.get(), BoundaryInfo::invalid_id);
    1208             : 
    1209             :                   // C++ allows function argument evaluation in any
    1210             :                   // order, but we need get() to precede move
    1211      353651 :                   Elem * new_elem_ptr = new_elem.get();
    1212      343689 :                   new_elems.emplace(new_elem_ptr, std::move(new_elem));
    1213             : 
    1214      343689 :                   check_delaunay_on.insert(new_elem_ptr);
    1215      333727 :                 }
    1216             :             }
    1217             : 
    1218      220242 :           boundary_info.remove(old_elem);
    1219             :         }
    1220             : 
    1221             :       // Now that we're done using our cavity elems (including with a
    1222             :       // cavity.find() that used a comparator that dereferences the
    1223             :       // elements!) it's safe to delete them.
    1224      297561 :       for (Elem * old_elem : cavity)
    1225             :         {
    1226      220242 :           if (const auto it = new_elems.find(old_elem);
    1227        6204 :               it == new_elems.end())
    1228      154709 :             mesh.delete_elem(old_elem);
    1229             :           else
    1230       63687 :             new_elems.erase(it);
    1231             :         }
    1232             : 
    1233             :       // Everybody found their match?
    1234        2178 :       libmesh_assert(neighbors_CW.empty());
    1235        2178 :       libmesh_assert(neighbors_CCW.empty());
    1236             : 
    1237             :       // Because we're preserving boundaries here, our naive cavity
    1238             :       // triangulation might not be a Delaunay triangulation.  Let's
    1239             :       // check and if necessary fix that; we depend on it when doing
    1240             :       // future point insertions.
    1241       77319 :       restore_delaunay(check_delaunay_on, boundary_info);
    1242             : 
    1243             :       // This is too expensive to do on every cavity in devel mode
    1244             : #ifdef DEBUG
    1245        2178 :       libmesh_assert_delaunay(mesh, new_elems);
    1246             : #endif
    1247        5963 :     }
    1248             : 
    1249             :   // If we added any new boundary nodes, we're going to need to keep
    1250             :   // track of the changes they made to the outer polyline and/or to
    1251             :   // any holes.
    1252        6319 :   if (!next_boundary_node.empty())
    1253             :     {
    1254             :       auto checked_emplace = [this](dof_id_type new_first,
    1255       11398 :                                     dof_id_type new_second)
    1256             :       {
    1257             : #ifdef DEBUG
    1258       70128 :         for (auto [first, second] : this->segments)
    1259             :           {
    1260       67250 :             libmesh_assert_not_equal_to(first, new_first);
    1261       67250 :             libmesh_assert_not_equal_to(second, new_second);
    1262             :           }
    1263        2878 :         if (!this->segments.empty())
    1264        2764 :           libmesh_assert_equal_to(this->segments.back().second, new_first);
    1265             : #endif
    1266        2878 :         libmesh_assert_not_equal_to(new_first, new_second);
    1267             : 
    1268       99291 :         this->segments.emplace_back (new_first, new_second);
    1269       99291 :       };
    1270             : 
    1271             :       // Keep track of the outer polyline
    1272        4047 :       if (this->segments.empty())
    1273             :         {
    1274           0 :           dof_id_type last_id = DofObject::invalid_id;
    1275             : 
    1276             :           // Custom loop because we increment node_it 1+ times inside
    1277           0 :           for (auto node_it = _mesh.nodes_begin(),
    1278           0 :                node_end = _mesh.nodes_end();
    1279           0 :                node_it != node_end;)
    1280             :             {
    1281           0 :               Node & node = **node_it;
    1282           0 :               ++node_it;
    1283             : 
    1284           0 :               const dof_id_type node_id = node.id();
    1285             : 
    1286             :               // Don't add Steiner points
    1287           0 :               if (node_id >= _n_boundary_nodes)
    1288           0 :                 break;
    1289             : 
    1290             :               // Connect up the previous node, if we didn't already
    1291             :               // connect it after some newly inserted nodes
    1292           0 :               if (!this->segments.empty())
    1293           0 :                 last_id = this->segments.back().second;
    1294             : 
    1295           0 :               if (last_id != DofObject::invalid_id &&
    1296           0 :                   last_id != node_id)
    1297           0 :                 checked_emplace(last_id, node_id);
    1298             : 
    1299           0 :               last_id = node_id;
    1300             : 
    1301             :               // Connect to any newly inserted nodes
    1302           0 :               Node * this_node = &node;
    1303           0 :               auto it = next_boundary_node.find(*this_node);
    1304           0 :               while (it != next_boundary_node.end())
    1305             :                 {
    1306           0 :                   libmesh_assert(this_node->valid_id());
    1307           0 :                   Node * next_node = it->second;
    1308           0 :                   libmesh_assert(next_node->valid_id());
    1309             : 
    1310           0 :                   if (node_it != node_end &&
    1311           0 :                       next_node == *node_it)
    1312           0 :                     ++node_it;
    1313             : 
    1314           0 :                   checked_emplace(this_node->id(), next_node->id());
    1315             : 
    1316           0 :                   this_node = next_node;
    1317           0 :                   if (this_node->id() == this->segments.front().first)
    1318           0 :                     break;
    1319             : 
    1320           0 :                   it = next_boundary_node.find(*this_node);
    1321             :                 }
    1322             :             }
    1323             : 
    1324             :           // We expect a closed loop here
    1325           0 :           if (this->segments.back().second != this->segments.front().first)
    1326           0 :             checked_emplace(this->segments.back().second,
    1327           0 :                             this->segments.front().first);
    1328             :         }
    1329             :       else
    1330             :         {
    1331         228 :           std::vector<std::pair<unsigned int, unsigned int>> old_segments;
    1332        4047 :           old_segments.swap(this->segments);
    1333             : 
    1334         114 :           auto old_it  = old_segments.begin();
    1335             : 
    1336        4047 :           const Node * node = _mesh.node_ptr(old_it->first);
    1337         114 :           const Node * const first_node = node;
    1338             : 
    1339        2764 :           do
    1340             :             {
    1341        5756 :               const dof_id_type node_id = node->id();
    1342      102169 :               if (const auto it = next_boundary_node.find(*node);
    1343        2878 :                   it == next_boundary_node.end())
    1344             :                 {
    1345      139897 :                   while (node_id != old_it->first)
    1346             :                     {
    1347        2124 :                       ++old_it;
    1348        2124 :                       libmesh_assert(old_it != old_segments.end());
    1349             :                     }
    1350       64539 :                   node = mesh.node_ptr(old_it->second);
    1351             :                 }
    1352             :               else
    1353             :                 {
    1354       37630 :                   node = it->second;
    1355             :                 }
    1356             : 
    1357      102169 :               checked_emplace(node_id, node->id());
    1358             :             }
    1359      102169 :           while (node != first_node);
    1360             :         }
    1361             : 
    1362             :       // Keep track of any holes
    1363        4047 :       if (this->_holes)
    1364             :         {
    1365             :           // Do we have any holes that need to be newly replaced?
    1366        4686 :           for (const Hole * hole : *this->_holes)
    1367             :             {
    1368        1071 :               if (this->replaced_holes.count(hole))
    1369        1065 :                 continue;
    1370             : 
    1371          36 :               bool hole_point_insertion = false;
    1372        5822 :                 for (auto p : make_range(hole->n_points()))
    1373        9240 :                   if (next_boundary_node.count(hole->point(p)))
    1374             :                     {
    1375           4 :                       hole_point_insertion = true;
    1376           4 :                       break;
    1377             :                     }
    1378        1278 :               if (hole_point_insertion)
    1379             :                 this->replaced_holes.emplace
    1380         146 :                   (hole, std::make_unique<ArbitraryHole>(*hole));
    1381             :             }
    1382             : 
    1383             :           // If we have any holes that are being replaced, make sure
    1384             :           // their replacements are up to date.
    1385        4686 :           for (const Hole * hole : *this->_holes)
    1386             :             {
    1387          66 :               auto hole_it = replaced_holes.find(hole);
    1388        2343 :               if (hole_it == replaced_holes.end())
    1389        1704 :                 continue;
    1390             : 
    1391          34 :               ArbitraryHole & arb = *hole_it->second;
    1392             : 
    1393             :               // We only need to update a replacement that's just had
    1394             :               // new points inserted
    1395          34 :               bool point_inserted = false;
    1396       12496 :               for (const Point & point : arb.get_points())
    1397         672 :                 if (next_boundary_node.count(point))
    1398             :                   {
    1399          18 :                     point_inserted = true;
    1400          18 :                     break;
    1401             :                   }
    1402             : 
    1403        1207 :               if (!point_inserted)
    1404         552 :                 continue;
    1405             : 
    1406             :               // Find all points in the replacement hole
    1407          18 :               std::vector<Point> new_points;
    1408             : 
    1409             :               // Our outer polyline is expected to have points in
    1410             :               // counter-clockwise order, so it proceeds "to the left"
    1411             :               // from the point of view of rays inside the domain
    1412             :               // pointing outward, and our next_boundary_node ordering
    1413             :               // was filled accordingly.
    1414             :               //
    1415             :               // Our inner holes are expected to have points in
    1416             :               // counter-clockwise order, but for holes "to the left
    1417             :               // as viewed from the hole interior is the *opposite* of
    1418             :               // "to the left as viewed from the domain interior".  We
    1419             :               // need to build the updated hole ordering "backwards".
    1420             : 
    1421             :               // We should never see duplicate points when we add one
    1422             :               // to a hole; if we do then we did something wrong.
    1423         816 :               auto push_back_new_point = [&new_points](const Point & p) {
    1424             :                 // O(1) assert in devel
    1425         278 :                 libmesh_assert(new_points.empty() ||
    1426             :                                new_points.back() != p);
    1427             : #ifdef DEBUG
    1428             :                 // O(N) asserts in dbg
    1429        2620 :                 for (auto old_p : new_points)
    1430        2342 :                   libmesh_assert_not_equal_to(old_p, p);
    1431             : #endif
    1432        9869 :                 new_points.push_back(p);
    1433        9591 :               };
    1434             : 
    1435         326 :               for (auto point_it = arb.get_points().rbegin(),
    1436          18 :                    point_end = arb.get_points().rend();
    1437        6106 :                    point_it != point_end;)
    1438             :                 {
    1439        5467 :                   Point point = *point_it;
    1440         154 :                   ++point_it;
    1441             : 
    1442        5603 :                   if (new_points.empty() ||
    1443         136 :                       (point != new_points.back() &&
    1444         136 :                        point != new_points.front()))
    1445         154 :                     push_back_new_point(point);
    1446             : 
    1447         154 :                   auto it = next_boundary_node.find(point);
    1448        9869 :                   while (it != next_boundary_node.end())
    1449             :                     {
    1450        4828 :                       point = *it->second;
    1451         136 :                       if (point == new_points.front())
    1452          12 :                         break;
    1453        4514 :                       if (point_it != point_end &&
    1454         112 :                           point == *point_it)
    1455          56 :                         ++point_it;
    1456         124 :                       push_back_new_point(point);
    1457         124 :                       it = next_boundary_node.find(point);
    1458             :                     }
    1459             :                 }
    1460             : 
    1461         621 :               std::reverse(new_points.begin(), new_points.end());
    1462             : 
    1463        1242 :               arb.set_points(std::move(new_points));
    1464             :             }
    1465             :         }
    1466             :     }
    1467             : 
    1468             :   // Okay, *now* we can add the new elements.
    1469      294437 :   for (auto & [raw_elem, unique_elem] : new_elems)
    1470             :     {
    1471        8116 :       libmesh_assert_equal_to(raw_elem, unique_elem.get());
    1472        8116 :       libmesh_assert(!raw_elem->is_flipped());
    1473        8116 :       libmesh_ignore(raw_elem); // Old gcc warns "unused variable"
    1474      304350 :       mesh.add_elem(std::move(unique_elem));
    1475             :     }
    1476             : 
    1477             :   // Did we add anything?
    1478        6319 :   return !new_elems.empty();
    1479             : }
    1480             : 
    1481             : 
    1482      762114 : bool Poly2TriTriangulator::should_refine_elem(Elem & elem)
    1483             : {
    1484      762114 :   const Real min_area_target = this->desired_area();
    1485      762114 :   FunctionBase<Real> *area_func = this->has_auto_area_function() ? this->get_auto_area_function() : this->get_desired_area_function();
    1486             : 
    1487             :   // If this isn't a question, why are we here?
    1488       21468 :   libmesh_assert(min_area_target > 0 ||
    1489             :                  area_func != nullptr ||
    1490             :                  this->has_auto_area_function());
    1491             : 
    1492      762114 :   const Real area = elem.volume();
    1493             : 
    1494             :   // If we don't have position-dependent area targets we can make a
    1495             :   // decision quickly
    1496      762114 :   if (!area_func && !this->has_auto_area_function())
    1497      181192 :     return (area > min_area_target);
    1498       16364 :   else if(area_func && this->has_auto_area_function())
    1499             :     libmesh_warning("WARNING:  both desired are function and automatic area function are set.  Using automatic area function.");
    1500             : 
    1501             :   // If we do?
    1502             :   //
    1503             :   // See if we're meeting the local area target at all the elem
    1504             :   // vertices first
    1505     2180552 :   for (auto v : make_range(elem.n_vertices()))
    1506             :     {
    1507             :       // If we have an auto area function, we'll use it and override other area options
    1508     1696520 :       const Real local_area_target = (*area_func)(elem.point(v));
    1509     1650040 :       libmesh_error_msg_if
    1510             :         (local_area_target <= 0,
    1511             :          "Non-positive desired element areas are unachievable");
    1512     1650040 :       if (area > local_area_target)
    1513        1420 :         return true;
    1514             :     }
    1515             : 
    1516             :   // If our vertices are happy, it's still possible that our interior
    1517             :   // isn't.  Are we allowed not to bother checking it?
    1518      530512 :   if (!min_area_target)
    1519       14944 :     return false;
    1520             : 
    1521           0 :   libmesh_not_implemented_msg
    1522             :     ("Combining a minimum desired_area with an area function isn't yet supported.");
    1523             : }
    1524             : 
    1525             : 
    1526             : } // namespace libMesh
    1527             : 
    1528             : 
    1529             : // Unnecessary at end of file, *except* maybe we'll do a unity build
    1530             : // someday, and in the meantime test coverage is nice.
    1531             : #include "libmesh/restore_ieee754.h"
    1532             : 
    1533             : 
    1534             : #endif // LIBMESH_HAVE_POLY2TRI

Generated by: LCOV version 1.14