LCOV - code coverage report
Current view: top level - src/mesh - mesh_triangle_holes.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 330 371 88.9 %
Date: 2025-08-19 19:27:09 Functions: 35 37 94.6 %
Legend: Lines: hit not hit

          Line data    Source code
       1             : // The libMesh Finite Element Library.
       2             : // Copyright (C) 2002-2025 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             : // Local includes
      22             : #include "libmesh/mesh_triangle_holes.h"
      23             : 
      24             : #include "libmesh/boundary_info.h"
      25             : #include "libmesh/elem.h"
      26             : #include "libmesh/int_range.h"
      27             : #include "libmesh/mesh_base.h"
      28             : #include "libmesh/mesh_serializer.h"
      29             : #include "libmesh/node.h"
      30             : #include "libmesh/parallel_algebra.h" // Packing<Point>
      31             : #include "libmesh/simple_range.h"
      32             : 
      33             : // TIMPI includes
      34             : #include "timpi/parallel_implementation.h" // broadcast
      35             : 
      36             : // C++ includes
      37             : #include <algorithm>
      38             : 
      39             : namespace
      40             : {
      41             :   using namespace libMesh;
      42             : 
      43       23488 :   int signof(Real val) {
      44      776559 :     return (0 < val) - (val < 0);
      45             :   }
      46             : 
      47             :   // Return 1 iff counter-clockwise turn
      48             :   // Return -1 iff clockwise turn
      49             :   // Return 0 iff collinear
      50      552105 :   int orientation(const Point & p0,
      51             :                   const Point & p1,
      52             :                   const Point & p2)
      53             :   {
      54      568947 :     const Real detleft  = (p0(0)-p2(0))*(p1(1)-p2(1));
      55      568947 :     const Real detright = (p0(1)-p2(1))*(p1(0)-p2(0));
      56             : 
      57      568947 :     return signof(detleft - detright);
      58             :   }
      59             : 
      60             :   // Same, but for the ray target as it goes to infinity
      61      184035 :   int ray_orientation(const Point & p0,
      62             :                       const Point & p1,
      63             :                       const Point & source,
      64             :                       const Point & ray_target)
      65             :   {
      66        5614 :     const Point rayvec = ray_target - source;
      67        5614 :     const Point edgevec = p1 - p0;
      68      189649 :     const Real det = edgevec(0)*rayvec(1)-edgevec(1)*rayvec(0);
      69             : 
      70      189649 :     return signof(det);
      71             :   }
      72             : 
      73      189649 :   bool is_intersection(const Point & source,
      74             :                        const Point & ray_target,
      75             :                        const Point & edge_pt0,
      76             :                        const Point & edge_pt1)
      77             :   {
      78      184035 :     int orient_st0 = orientation(source, ray_target, edge_pt0);
      79      184035 :     int orient_st1 = orientation(source, ray_target, edge_pt1);
      80      184035 :     int orient_edge_s = orientation(edge_pt0, edge_pt1, source);
      81      184035 :     int orient_edge_t = ray_orientation(edge_pt0, edge_pt1, source, ray_target);
      82             : 
      83             :     // Intersection on interior
      84      189649 :     if ((orient_st0 == -orient_st1) &&
      85             :         (orient_edge_s != orient_edge_t))
      86         538 :       return true;
      87             : 
      88             :     // Ray intersects edge_pt1
      89      172158 :     if (orient_st1 == 0)
      90       21723 :       return true;
      91             : 
      92             :     // Source is on line; we don't count that
      93             :     // if (orient_edge_s == 0)
      94             :     // Ray is parallel to edge; no intersection;
      95             :     // if (orient_edge_t == 0)
      96             :     // Ray intersects edge_pt0; we don't count that
      97             :     // if (orient_st0 == 0)
      98             : 
      99        4432 :     return false;
     100             :   }
     101             : 
     102             :   // Returns a positive distance iff the ray from source in the
     103             :   // direction of ray_target intersects the edge from pt0
     104             :   // (non-inclusive) to pt1 (inclusive), -1 otherwise.
     105             :   //
     106             :   // If the intersection is a "glancing" one at a corner, return -1.
     107      189649 :   Real find_intersection(const Point & source,
     108             :                          const Point & ray_target,
     109             :                          const Point & edge_pt0,
     110             :                          const Point & edge_pt1,
     111             :                          const Point & edge_pt2)
     112             :   {
     113             :     // Quick and more numerically stable check
     114      189649 :     if (!is_intersection(source, ray_target, edge_pt0, edge_pt1))
     115        4432 :       return -1;
     116             : 
     117             :     // Calculate intersection parameters (fractions of the distance
     118             :     // along each segment)
     119       39214 :     const Real raydx = ray_target(0)-source(0),
     120       39214 :                raydy = ray_target(1)-source(1),
     121       39214 :                edgedx = edge_pt1(0)-edge_pt0(0),
     122       39214 :                edgedy = edge_pt1(1)-edge_pt0(1);
     123       39214 :     const Real denom = edgedx * raydy - edgedy * raydx;
     124             : 
     125             :     // divide-by-zero means the segments are parallel
     126       39214 :     if (denom == 0)
     127         128 :       return -1;
     128             : 
     129       34938 :     const Real one_over_denom = 1 / denom;
     130             : 
     131       34938 :     const Real targetsdx = edge_pt1(0)-ray_target(0),
     132       34938 :                targetsdy = edge_pt1(1)-ray_target(1);
     133             : 
     134       37046 :     const Real t_num = targetsdx * raydy -
     135       34938 :                        targetsdy * raydx;
     136       34938 :     const Real t = t_num * one_over_denom;
     137             : 
     138             :     // There's an intersection between the ray line and the edge?
     139       34938 :     if (t >= 0 && t < 1)
     140             :       {
     141             :         // There's an intersection right on a vertex?  We'll count it
     142             :         // if and only if it isn't a "double-intersection", if the
     143             :         // *next* edge in line is on the other side of our ray.
     144       34938 :         if (!t)
     145             :           {
     146       17447 :             const Real prevdx = edge_pt0(0)-ray_target(0),
     147       17447 :                        prevdy = edge_pt0(1)-ray_target(1);
     148       18479 :             const Real p_num = prevdx * raydy -
     149       17447 :                                prevdy * raydx;
     150             : 
     151       17447 :             const Real nextdx = edge_pt2(0)-ray_target(0),
     152       17447 :                        nextdy = edge_pt2(1)-ray_target(1);
     153       18479 :             const Real n_num = nextdx * raydy -
     154       17447 :                                nextdy * raydx;
     155             : 
     156       17447 :             if (signof(p_num) != -signof(n_num))
     157         310 :               return -1;
     158             :           }
     159             : 
     160       24402 :         const Real u_num = targetsdx * edgedy - targetsdy * edgedx;
     161       24402 :         const Real u = u_num * one_over_denom;
     162       24402 :         const Real ray_fraction = (1-u);
     163             : 
     164             :         // Intersection is in the other direction!?
     165       24402 :         if (ray_fraction < 0)
     166         106 :           return -1;
     167             : 
     168             :         const Real distance =
     169       20840 :           ray_fraction * std::sqrt(raydx*raydx + raydy*raydy);
     170       20840 :         return distance;
     171             :       }
     172             : 
     173           0 :     return -1;
     174             :   }
     175             : }
     176             : 
     177             : 
     178             : namespace libMesh
     179             : {
     180             : 
     181             : //
     182             : // Hole member functions
     183             : //
     184        1144 : Real TriangulatorInterface::Hole::area() const
     185             : {
     186        1144 :   return this->areavec().norm() / 2;
     187             : }
     188             : 
     189             : 
     190        1144 : RealGradient TriangulatorInterface::Hole::areavec() const
     191             : {
     192        1144 :   const unsigned int np = this->n_points();
     193             : 
     194        1144 :   if (np < 3)
     195           2 :     return 0;
     196             : 
     197        1073 :   const Point p0 = this->point(0);
     198             : 
     199             :   // Every segment (p_{i-1},p_i) from i=2 on defines a triangle w.r.t.
     200             :   // p_0.  Add up the cross products of those triangles.  We'll save
     201             :   // the division by 2 and the norm for the end.
     202             :   //
     203             :   // Your hole points had best be coplanar, but this should work
     204             :   // regardless of which plane they're in.  If you're in the XY plane,
     205             :   // then the standard counter-clockwise hole point ordering gives you
     206             :   // a positive areavec(2);
     207             : 
     208          34 :   RealGradient areavec = 0;
     209             : 
     210        3661 :   for (unsigned int i=2; i != np; ++i)
     211             :     {
     212        2588 :       const Point e_0im = this->point(i-1) - p0,
     213        2588 :                   e_0i  = this->point(i) - p0;
     214             : 
     215        2500 :       areavec += e_0i.cross(e_0im);
     216             :     }
     217             : 
     218        1073 :   return areavec;
     219             : }
     220             : 
     221             : 
     222             : 
     223             : std::vector<Real>
     224       34784 : TriangulatorInterface::Hole::find_ray_intersections(Point ray_start,
     225             :                                                     Point ray_target) const
     226             : {
     227       34784 :   const auto np = this->n_points();
     228             : 
     229        1044 :   std::vector<Real> intersection_distances;
     230             : 
     231      224433 :   for (auto i : make_range(np))
     232             :     {
     233      189649 :       const Point & p0 = this->point(i),
     234      189649 :                   & p1 = this->point((i+1)%np),
     235      189649 :                   & p2 = this->point((i+2)%np);
     236             :       const Real intersection_distance =
     237      189649 :         find_intersection(ray_start, ray_target, p0, p1, p2);
     238      189649 :       if (intersection_distance >= 0)
     239             :         intersection_distances.push_back
     240       20840 :           (intersection_distance);
     241             :     }
     242             : 
     243       34784 :   return intersection_distances;
     244             : }
     245             : 
     246             : 
     247             : 
     248        1440 : Point TriangulatorInterface::Hole::calculate_inside_point() const
     249             : {
     250             :   // Start with the vertex average
     251             : 
     252             :   // Turns out "I'm a fully compliant C++17 compiler!" doesn't
     253             :   // mean "I have a full C++17 standard library!"
     254             :   // inside = std::reduce(points.begin(), points.end());
     255          50 :   Point inside = 0;
     256       15113 :   for (auto i : make_range(this->n_points()))
     257       13673 :     inside += this->point(i);
     258             : 
     259        1440 :   inside /= this->n_points();
     260             : 
     261             :   // Count the number of intersections with a ray to the right,
     262             :   // keep track of how far they are
     263          50 :   Point ray_target = inside + Point(1);
     264             :   std::vector<Real> intersection_distances =
     265        1490 :     this->find_ray_intersections(inside, ray_target);
     266             : 
     267             :   // The vertex average isn't on the interior, and we found no
     268             :   // intersections to the right?  Try looking to the left.
     269        1440 :   if (!intersection_distances.size())
     270             :     {
     271           0 :       ray_target = inside - Point(1);
     272             :       intersection_distances =
     273           0 :         this->find_ray_intersections(inside, ray_target);
     274             :     }
     275             : 
     276             :   // I'd make this an assert, but I'm not 100% confident we can't
     277             :   // get here via some kind of FP error on a weird hole shape.
     278        1490 :   libmesh_error_msg_if
     279             :     (!intersection_distances.size(),
     280             :      "Can't find a center for a MeshedHole!");
     281             : 
     282        1440 :   if (intersection_distances.size() % 2)
     283          50 :     return inside;
     284             : 
     285             :   // The vertex average is outside.  So go from the vertex average to
     286             :   // the closest edge intersection, then halfway to the next-closest.
     287             : 
     288             :   // Find the nearest first.
     289           0 :   Real min_distance    = std::numeric_limits<Real>::max(),
     290           0 :        second_distance = std::numeric_limits<Real>::max();
     291           0 :   for (Real d : intersection_distances)
     292           0 :     if (d < min_distance)
     293             :       {
     294           0 :         second_distance = min_distance;
     295           0 :         min_distance = d;
     296             :       }
     297             : 
     298           0 :   const Point ray = ray_target - inside;
     299           0 :   inside += ray * (min_distance + second_distance)/2;
     300             : 
     301           0 :   return inside;
     302             : }
     303             : 
     304             : 
     305       33344 : bool TriangulatorInterface::Hole::contains(Point p) const
     306             : {
     307             :   // Count the number of intersections with a ray to the right,
     308             :   // keep track of how far they are
     309         994 :   Point ray_target = p + Point(1);
     310             :   std::vector<Real> intersection_distances =
     311       33344 :     this->find_ray_intersections(p, ray_target);
     312             : 
     313             :   // Odd number of intersections == we're inside
     314             :   // Even number == we're outside
     315       35332 :   return intersection_distances.size() % 2;
     316             : }
     317             : 
     318             : 
     319             : 
     320             : //
     321             : // PolygonHole member functions
     322             : //
     323        1081 : TriangulatorInterface::PolygonHole::PolygonHole(const Point & center,
     324             :                                                 Real radius,
     325        1081 :                                                 unsigned int n_points_in) :
     326        1005 :   _center(center),
     327        1005 :   _radius(radius),
     328        1081 :   _n_points(n_points_in)
     329        1081 : {}
     330             : 
     331             : 
     332       35941 : unsigned int TriangulatorInterface::PolygonHole::n_points() const
     333             : {
     334       35941 :   return _n_points;
     335             : }
     336             : 
     337             : 
     338      325515 : Point TriangulatorInterface::PolygonHole::point(const unsigned int n) const
     339             : {
     340             :   // The nth point lies at the angle theta = 2 * pi * n / _n_points
     341      325515 :   const Real theta = static_cast<Real>(n) * 2.0 * libMesh::pi / static_cast<Real>(_n_points);
     342             : 
     343      334809 :   return Point(_center(0) + _radius*std::cos(theta), // x=r*cos(theta)
     344      325515 :                _center(1) + _radius*std::sin(theta), // y=r*sin(theta)
     345      344103 :                0.);
     346             : }
     347             : 
     348             : 
     349         154 : Point TriangulatorInterface::PolygonHole::inside() const
     350             : {
     351             :   // The center of the hole is definitely inside.
     352         154 :   return _center;
     353             : }
     354             : 
     355             : 
     356             : //
     357             : // AffineHole member functions
     358             : //
     359      284355 : Point TriangulatorInterface::AffineHole::point(const unsigned int n) const
     360             : {
     361      284355 :   return this->transform(_underlying.point(n));
     362             : }
     363             : 
     364             : 
     365           0 : Point TriangulatorInterface::AffineHole::inside() const
     366             : {
     367           0 :   return this->transform(_underlying.inside());
     368             : }
     369             : 
     370             : 
     371      284355 : Point TriangulatorInterface::AffineHole::transform(const Point & p) const
     372             : {
     373      284355 :   const Real cos_a = std::cos(_angle);
     374      284355 :   const Real sin_a = std::sin(_angle);
     375      284355 :   return Point(p(0)*cos_a-p(1)*sin_a + _shift(0),
     376      308385 :                p(1)*cos_a+p(1)*sin_a + _shift(1));
     377             : }
     378             : 
     379             : 
     380             : //
     381             : // ArbitraryHole member functions
     382             : //
     383         217 : TriangulatorInterface::ArbitraryHole::ArbitraryHole(const Point & center,
     384         217 :                                                     std::vector<Point> points)
     385         201 :   : _center(center),
     386         225 :     _points(std::move(points))
     387             : {
     388         217 :   _segment_indices.push_back(0);
     389         225 :   _segment_indices.push_back(_points.size());
     390         217 : }
     391             : 
     392             : 
     393           0 : TriangulatorInterface::ArbitraryHole::ArbitraryHole(const Point & center,
     394             :                                                     std::vector<Point> points,
     395           0 :                                                     std::vector<unsigned int> segment_indices)
     396           0 :   : _center(center),
     397           0 :     _points(std::move(points)),
     398           0 :     _segment_indices(std::move(segment_indices))
     399           0 : {}
     400             : 
     401             : 
     402         651 : TriangulatorInterface::ArbitraryHole::ArbitraryHole(std::vector<Point> points)
     403         699 :   : _points(std::move(points))
     404             : {
     405         651 :   _segment_indices.push_back(0);
     406         675 :   _segment_indices.push_back(_points.size());
     407         651 :   _center = this->calculate_inside_point();
     408         651 : }
     409             : 
     410             : 
     411         142 : TriangulatorInterface::ArbitraryHole::ArbitraryHole(const Hole & orig)
     412         142 :   : _center(orig.inside())
     413             : {
     414         142 :   const unsigned int np = orig.n_points();
     415         142 :   _points.reserve(np);
     416         710 :   for (auto i : make_range(np))
     417        1120 :     _points.push_back(orig.point(i));
     418         142 : }
     419             : 
     420             : 
     421       14539 : unsigned int TriangulatorInterface::ArbitraryHole::n_points() const
     422             : {
     423       15007 :   return _points.size();
     424             : }
     425             : 
     426             : 
     427      310402 : Point TriangulatorInterface::ArbitraryHole::point(const unsigned int n) const
     428             : {
     429        9476 :   libmesh_assert_less (n, _points.size());
     430      319878 :   return _points[n];
     431             : }
     432             : 
     433             : 
     434           4 : Point TriangulatorInterface::ArbitraryHole::inside() const
     435             : {
     436           4 :   return _center;
     437             : }
     438             : 
     439             : 
     440          16 : std::vector<unsigned int> TriangulatorInterface::ArbitraryHole::segment_indices() const
     441             : {
     442          16 :   return _segment_indices;
     443             : }
     444             : 
     445             : 
     446             : //
     447             : // MeshedHole member functions
     448             : //
     449             : 
     450             : 
     451         726 : TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
     452         726 :                                               std::set<std::size_t> ids)
     453         800 :   : _center(std::numeric_limits<Real>::max())
     454             : {
     455             :   // We'll want to do this on one processor and broadcast to the rest;
     456             :   // otherwise we can get out of sync by doing things like using
     457             :   // pointers as keys.
     458          28 :   libmesh_parallel_only(mesh.comm());
     459             : 
     460             :   MeshSerializer serial(const_cast<MeshBase &>(mesh),
     461         749 :                         /* serial */ true, /* only proc 0 */ true);
     462             : 
     463             :   // Try to keep in sync even if we throw an error on proc 0, so we
     464             :   // can examine errors in our unit tests in parallel too.
     465          34 :   std::string error_reported;
     466             : 
     467          78 :   auto report_error = [&mesh, &error_reported](std::string er) {
     468          36 :     error_reported = std::move(er);
     469          36 :     mesh.comm().broadcast(error_reported);
     470         102 :     libmesh_error_msg(error_reported);
     471         726 :   };
     472             : 
     473         754 :   if (mesh.processor_id() != 0)
     474             :     {
     475             :       // Make sure proc 0 didn't just fail
     476         598 :       mesh.comm().broadcast(error_reported);
     477         946 :       libmesh_error_msg_if(!error_reported.empty(), error_reported);
     478             : 
     479             :       // Receive the points proc 0 will send later
     480         421 :       mesh.comm().broadcast(_points);
     481         421 :       mesh.comm().broadcast(_midpoints);
     482          11 :       return;
     483             :     }
     484             : 
     485             :   // We'll find all the line segments first, then stitch them together
     486             :   // afterward.  If the line segments come from 2D element sides then
     487             :   // we'll label their edge_type as "1" for clockwise orientation
     488             :   // around the element or "2" for CCW, to make it easier to detect
     489             :   // and scream about cases where we have a disconnected outer
     490             :   // boundary.
     491             :   std::multimap<const Node *,
     492          28 :                 std::pair<const Node *, int>> hole_edge_map;
     493             : 
     494             :   // If we're looking at higher-order elements, we have mid-edge edge
     495             :   // nodes to worry about.  hole_midpoint_map[{m,n}][i] should give us
     496             :   // the ith mid-edge node traveling from vertex m to vertex n
     497             :   std::map<std::pair<const Node *, const Node *>,
     498          28 :            std::vector<const Node *>> hole_midpoint_map;
     499             : 
     500          28 :   std::vector<boundary_id_type> bcids;
     501             : 
     502          14 :   const BoundaryInfo & boundary_info = mesh.get_boundary_info();
     503             : 
     504        1746 :   for (const auto & elem : mesh.active_element_ptr_range())
     505             :     {
     506         854 :       if (elem->dim() == 1)
     507             :         {
     508         494 :           if (ids.empty() || ids.count(elem->subdomain_id()))
     509             :             {
     510         364 :               hole_edge_map.emplace(elem->node_ptr(0),
     511         327 :                                     std::make_pair(elem->node_ptr(1),
     512         111 :                                                    /*edge*/ 0));
     513         364 :               hole_edge_map.emplace(elem->node_ptr(1),
     514         327 :                                     std::make_pair(elem->node_ptr(0),
     515         111 :                                                    /*edge*/ 0));
     516         364 :               if (elem->type() == EDGE3)
     517             :                 {
     518          88 :                   hole_midpoint_map.emplace(std::make_pair(elem->node_ptr(0),
     519         104 :                                                            elem->node_ptr(1)),
     520         112 :                                             std::vector<const Node *>{elem->node_ptr(2)});
     521          88 :                   hole_midpoint_map.emplace(std::make_pair(elem->node_ptr(1),
     522         104 :                                                            elem->node_ptr(0)),
     523         200 :                                             std::vector<const Node *>{elem->node_ptr(2)});
     524             :                 }
     525         268 :               else if (elem->type() == EDGE4)
     526             :                 {
     527           0 :                   hole_midpoint_map.emplace(std::make_pair(elem->node_ptr(0),
     528           0 :                                                            elem->node_ptr(1)),
     529           0 :                                             std::vector<const Node *>{elem->node_ptr(2),
     530           0 :                                                                       elem->node_ptr(3)});
     531           0 :                   hole_midpoint_map.emplace(std::make_pair(elem->node_ptr(1),
     532           0 :                                                            elem->node_ptr(0)),
     533           0 :                                             std::vector<const Node *>{elem->node_ptr(3),
     534           0 :                                                                       elem->node_ptr(2)});
     535             :                 }
     536             :               else
     537          29 :                 libmesh_assert_equal_to(elem->default_side_order(), 1);
     538             :             }
     539         494 :           continue;
     540             :         }
     541             : 
     542         360 :       if (elem->dim() == 2)
     543             :         {
     544         360 :           const auto ns = elem->n_sides();
     545        1776 :           for (auto s : make_range(ns))
     546             :             {
     547        1416 :               boundary_info.boundary_ids(elem, s, bcids);
     548             : 
     549         198 :               bool add_edge = false;
     550        1614 :               if (!elem->neighbor_ptr(s) && ids.empty())
     551          38 :                 add_edge = true;
     552             : 
     553         198 :               if (!add_edge)
     554        1120 :                 for (auto b : bcids)
     555           0 :                   if (ids.count(b))
     556           0 :                     add_edge = true;
     557             : 
     558        1196 :               if (add_edge)
     559             :                 {
     560         296 :                   hole_edge_map.emplace(elem->node_ptr(s),
     561         258 :                                         std::make_pair(elem->node_ptr((s+1)%ns),
     562         114 :                                                        /*counter-CW*/ 2));
     563             :                   // Do we really need to support flipped 2D elements?
     564         296 :                   hole_edge_map.emplace(elem->node_ptr((s+1)%ns),
     565         258 :                                         std::make_pair(elem->node_ptr(s),
     566         114 :                                                        /*clockwise*/ 1));
     567             : 
     568         296 :                   if (elem->default_side_order() == 2)
     569             :                     {
     570          96 :                       hole_midpoint_map.emplace(std::make_pair(elem->node_ptr(s),
     571         128 :                                                                elem->node_ptr((s+1)%ns)),
     572         144 :                                                 std::vector<const Node *>{elem->node_ptr(s+ns)});
     573          96 :                       hole_midpoint_map.emplace(std::make_pair(elem->node_ptr((s+1)%ns),
     574         128 :                                                                elem->node_ptr(s)),
     575         240 :                                                 std::vector<const Node *>{elem->node_ptr(s+ns)});
     576             :                     }
     577             :                   else
     578          22 :                     libmesh_assert_equal_to(elem->default_side_order(), 1);
     579             : 
     580         296 :                   continue;
     581             :                 }
     582             :             }
     583             :         }
     584         100 :     }
     585             : 
     586         128 :   if (hole_edge_map.empty())
     587          30 :     report_error("No valid hole edges found in mesh!");
     588             : 
     589             :   // Function to pull a vector of points out of the map; a loop of
     590             :   // edges connecting these points defines a hole boundary.  If the
     591             :   // mesh has multiple boundaries (e.g. because it had holes itself),
     592             :   // then a random vector will be extracted; this function will be
     593             :   // called multiple times so that the various options can be
     594             :   // compared.  We choose the largest option.
     595             :   auto extract_edge_vector =
     596        1521 :     [&report_error, &hole_edge_map, &hole_midpoint_map]() {
     597             :     std::tuple<std::vector<const Node *>, std::vector<const Node *>, int>
     598             :     hole_points_and_edge_type
     599         184 :       {{hole_edge_map.begin()->first, hole_edge_map.begin()->second.first},
     600         320 :        {}, hole_edge_map.begin()->second.second};
     601             : 
     602          16 :     auto & hole_points = std::get<0>(hole_points_and_edge_type);
     603          16 :     auto & midpoint_points = std::get<1>(hole_points_and_edge_type);
     604          16 :     int & edge_type = std::get<2>(hole_points_and_edge_type);
     605             : 
     606             :     // We won't be needing to search for this edge
     607          16 :     hole_edge_map.erase(hole_points.front());
     608             : 
     609             :     // Sort the remaining edges into a connected order
     610         152 :     for (const Node * last = hole_points.front(),
     611         152 :                     *    n = hole_points.back();
     612         654 :                          n != hole_points.front();
     613         118 :                       last = n,
     614         502 :                          n = hole_points.back())
     615             :       {
     616          60 :         auto [next_it_begin, next_it_end] = hole_edge_map.equal_range(n);
     617             : 
     618         514 :         if (std::distance(next_it_begin, next_it_end) != 2)
     619          15 :           report_error("Bad edge topology found by MeshedHole");
     620             : 
     621         502 :         const Node * next = nullptr;
     622        1506 :         for (const auto & [key, val] : as_range(next_it_begin, next_it_end))
     623             :           {
     624         118 :             libmesh_assert_equal_to(key, n);
     625         118 :             libmesh_ignore(key);
     626         118 :             libmesh_assert_not_equal_to(val.first, n);
     627             : 
     628             :             // Don't go backwards on the edge we just traversed
     629        1004 :             if (val.first == last)
     630         443 :               continue;
     631             : 
     632             :             // We can support mixes of Edge and Tri-side edges, but we
     633             :             // can't do proper error detection on flipped triangles.
     634         502 :             if (val.second != edge_type &&
     635           0 :                 val.second != 0)
     636             :               {
     637           0 :                 if (!edge_type)
     638           0 :                   edge_type = val.second;
     639             :                 else
     640           0 :                   report_error("MeshedHole sees inconsistent triangle orientations on boundary");
     641             :               }
     642         502 :             next = val.first;
     643             :           }
     644             : 
     645             :         // We should never hit the same n twice!
     646          59 :         hole_edge_map.erase(next_it_begin, next_it_end);
     647             : 
     648         502 :         hole_points.push_back(next);
     649             :       }
     650             : 
     651         779 :     for (auto i : make_range(hole_points.size()-1))
     652             :       {
     653         768 :         const auto & midpoints = hole_midpoint_map[{hole_points[i],hole_points[i+1]}];
     654         562 :         midpoint_points.insert(midpoint_points.end(),
     655         289 :                                midpoints.begin(), midpoints.end());
     656             :       }
     657             : 
     658          15 :     hole_points.pop_back();
     659             : 
     660         140 :     return hole_points_and_edge_type;
     661         128 :   };
     662             : 
     663             :   /*
     664             :    * If it's not obvious which loop we find is really the loop we
     665             :    * want, then we should die with a nice error message.
     666             :    */
     667          14 :   int n_negative_areas = 0,
     668          14 :       n_positive_areas = 0,
     669          14 :       n_edgeelem_loops = 0;
     670             : 
     671          31 :   std::vector<const Node *> outer_hole_points, outer_mid_points;
     672          14 :   int outer_edge_type = -1;
     673          14 :   Real twice_outer_area = 0,
     674          14 :        abs_twice_outer_area = 0;
     675             : 
     676             : #ifdef DEBUG
     677             :   // Area and edge type, for error reporting
     678          28 :   std::vector<std::pair<Real, int>> areas;
     679             : #endif
     680             : 
     681         256 :   while (!hole_edge_map.empty()) {
     682         167 :     auto [hole_points, mid_points, edge_type] = extract_edge_vector();
     683             : 
     684         140 :     if (edge_type == 0)
     685             :     {
     686          88 :       ++n_edgeelem_loops;
     687          88 :       if (n_edgeelem_loops > 1)
     688          15 :         report_error("MeshedHole is confused by multiple loops of Edge elements");
     689          76 :       if (n_positive_areas || n_negative_areas)
     690           0 :         report_error("MeshedHole is confused by meshes with both Edge and 2D-side boundaries");
     691             :     }
     692             : 
     693          28 :     const std::size_t n_hole_points = hole_points.size();
     694         128 :     if (n_hole_points < 3)
     695          11 :       report_error("Loop with only " + std::to_string(n_hole_points) +
     696             :                    " hole edges found in mesh!");
     697             : 
     698          14 :     Real twice_this_area = 0;
     699         128 :     const Point p0 = *hole_points[0];
     700         460 :     for (unsigned int i=2; i != n_hole_points; ++i)
     701             :       {
     702         373 :         const Point e_0im = *hole_points[i-1] - p0,
     703         332 :                     e_0i  = *hole_points[i] - p0;
     704             : 
     705         332 :         twice_this_area += e_0i.cross(e_0im)(2);
     706             :       }
     707             : 
     708          14 :     auto abs_twice_this_area = std::abs(twice_this_area);
     709             : 
     710         128 :     if (((twice_this_area > 0) && edge_type == 2) ||
     711          59 :         ((twice_this_area < 0) && edge_type == 1))
     712           0 :       ++n_positive_areas;
     713         128 :     else if (edge_type != 0)
     714          52 :       ++n_negative_areas;
     715             : 
     716             : #ifdef DEBUG
     717          14 :     areas.push_back({twice_this_area/2,edge_type});
     718             : #endif
     719             : 
     720         128 :     if (abs_twice_this_area > abs_twice_outer_area)
     721             :       {
     722          13 :         twice_outer_area = twice_this_area;
     723          13 :         abs_twice_outer_area = abs_twice_this_area;
     724          13 :         outer_hole_points = std::move(hole_points);
     725          13 :         outer_mid_points = std::move(mid_points);
     726         116 :         outer_edge_type = edge_type;
     727             :       }
     728             :   }
     729             : 
     730         116 :   _points.resize(outer_hole_points.size());
     731             :   std::transform(outer_hole_points.begin(),
     732             :                  outer_hole_points.end(),
     733             :                  _points.begin(),
     734         528 :                  [](const Node * n){ return Point(*n); });
     735         116 :   _midpoints.resize(outer_mid_points.size());
     736             :   std::transform(outer_mid_points.begin(),
     737             :                  outer_mid_points.end(),
     738             :                  _midpoints.begin(),
     739         220 :                  [](const Node * n){ return Point(*n); });
     740             : 
     741         104 :   if (!twice_outer_area)
     742           0 :     report_error("Zero-area MeshedHoles are not currently supported");
     743             : 
     744             :   // We ordered ourselves counter-clockwise?  But a hole is expected
     745             :   // to be clockwise, so use the reverse order.
     746         104 :   if (twice_outer_area > 0)
     747             :     {
     748          49 :       std::reverse(_points.begin(), _points.end());
     749             : 
     750             :       // Our midpoints are numbered e.g.
     751             :       // (01a)(01b)(12a)(12b)(23a)(23b)(30a)(30b) for points 0123, but
     752             :       // if we reverse to get 3210 then we want our midpoints to be
     753             :       // (23b)(23a)(12b)(12a)(01b)(01a)(30b)(30a)
     754          55 :       const unsigned int n_midpoints = _midpoints.size() / _points.size();
     755           7 :       auto split_it = _midpoints.end() - n_midpoints;
     756          49 :       std::reverse(_midpoints.begin(), split_it);
     757          49 :       std::reverse(split_it, _midpoints.end());
     758             :     }
     759             : 
     760             : #ifdef DEBUG
     761           1 :   auto print_areas = [areas](){
     762           1 :     libMesh::out << "Found boundary areas:\n";
     763           4 :     static const std::vector<std::string> edgenames {"E","CW","CCW"};
     764           3 :     for (auto area : areas)
     765           2 :       libMesh::out << '(' << edgenames[area.second] << ' ' <<
     766           2 :         area.first << ')';
     767           1 :     libMesh::out << std::endl;
     768          25 :   };
     769             : #else
     770             :   auto print_areas = [](){};
     771             : #endif
     772             : 
     773         104 :   if (((twice_outer_area > 0) && outer_edge_type == 2) ||
     774          52 :       ((twice_outer_area < 0) && outer_edge_type == 1))
     775             :     {
     776           0 :       if (n_positive_areas > 1)
     777             :         {
     778           0 :           print_areas();
     779           0 :           report_error("MeshedHole found " +
     780           0 :                        std::to_string(n_positive_areas) +
     781             :                        " counter-clockwise boundaries and cannot choose one!");
     782             :         }
     783             : 
     784             :     }
     785         104 :   else if (outer_edge_type != 0)
     786             :     {
     787          40 :       if (n_negative_areas > 1)
     788             :         {
     789           1 :           print_areas();
     790          27 :           report_error("MeshedHole found " +
     791          51 :                        std::to_string(n_negative_areas) +
     792             :                        " clockwise boundaries and cannot choose one!");
     793             :         }
     794             : 
     795             :     }
     796             : 
     797             :   // Hey, no errors!  Broadcast that empty string.
     798          92 :   mesh.comm().broadcast(error_reported);
     799          92 :   mesh.comm().broadcast(_points);
     800          92 :   mesh.comm().broadcast(_midpoints);
     801         670 : }
     802             : 
     803             : 
     804        3410 : unsigned int TriangulatorInterface::MeshedHole::n_points() const
     805             : {
     806        3410 :   return _points.size();
     807             : }
     808             : 
     809             : 
     810        2770 : unsigned int TriangulatorInterface::MeshedHole::n_midpoints() const
     811             : {
     812         112 :   libmesh_assert (!(_midpoints.size() % _points.size()));
     813        2994 :   return _midpoints.size() / _points.size();
     814             : }
     815             : 
     816             : 
     817       35115 : Point TriangulatorInterface::MeshedHole::point(const unsigned int n) const
     818             : {
     819        1444 :   libmesh_assert_less (n, _points.size());
     820       36559 :   return _points[n];
     821             : }
     822             : 
     823             : 
     824        1168 : Point TriangulatorInterface::MeshedHole::midpoint(const unsigned int m,
     825             :                                                   const unsigned int n) const
     826             : {
     827        1168 :   const unsigned int n_mid = this->n_midpoints();
     828          48 :   libmesh_assert_less (m, n_mid);
     829          48 :   libmesh_assert_less (n, _points.size());
     830        1216 :   return _midpoints[n*n_mid+m];
     831             : }
     832             : 
     833             : 
     834         158 : Point TriangulatorInterface::MeshedHole::inside() const
     835             : {
     836             :   // This is expensive to compute, so only do it when we first need it
     837         158 :   if (_center(0) == std::numeric_limits<Real>::max())
     838         150 :     _center = this->calculate_inside_point();
     839             : 
     840         158 :   return _center;
     841             : }
     842             : 
     843             : 
     844             : 
     845             : } // namespace libMesh

Generated by: LCOV version 1.14