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

Generated by: LCOV version 1.14