LCOV - code coverage report
Current view: top level - src/geom - cell_c0polyhedron.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 264 353 74.8 %
Date: 2025-08-19 19:27:09 Functions: 29 34 85.3 %
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             : // Local includes
      19             : #include "libmesh/cell_c0polyhedron.h"
      20             : 
      21             : #include "libmesh/enum_order.h"
      22             : #include "libmesh/face_polygon.h"
      23             : #include "libmesh/mesh_tools.h"
      24             : #include "libmesh/replicated_mesh.h"
      25             : #include "libmesh/tensor_value.h"
      26             : 
      27             : // C++ headers
      28             : #include <numeric> // std::iota
      29             : 
      30             : namespace libMesh
      31             : {
      32             : 
      33             : // ------------------------------------------------------------
      34             : // C0Polyhedron class member functions
      35             : 
      36        3849 : C0Polyhedron::C0Polyhedron
      37        3849 :   (const std::vector<std::shared_ptr<Polygon>> & sides, Elem * p) :
      38        3849 :   Polyhedron(sides, p)
      39             : {
      40        3849 :   this->retriangulate();
      41        3849 : }
      42             : 
      43             : 
      44             : 
      45        7258 : C0Polyhedron::~C0Polyhedron() = default;
      46             : 
      47             : 
      48             : 
      49          15 : std::unique_ptr<Elem> C0Polyhedron::disconnected_clone() const
      50             : {
      51          19 :   auto sides = this->side_clones();
      52             : 
      53             :   std::unique_ptr<Elem> returnval =
      54          15 :     std::make_unique<C0Polyhedron>(sides);
      55             : 
      56          15 :   returnval->set_id() = this->id();
      57             : #ifdef LIBMESH_ENABLE_UNIQUE_ID
      58          15 :   if (this->valid_unique_id())
      59           2 :     returnval->set_unique_id(this->unique_id());
      60             : #endif
      61             : 
      62          15 :   const auto n_elem_ints = this->n_extra_integers();
      63          15 :   returnval->add_extra_integers(n_elem_ints);
      64          15 :   for (unsigned int i = 0; i != n_elem_ints; ++i)
      65           0 :     returnval->set_extra_integer(i, this->get_extra_integer(i));
      66             : 
      67          13 :   returnval->inherit_data_from(*this);
      68             : 
      69          17 :   return returnval;
      70          11 : }
      71             : 
      72             : 
      73             : 
      74       10440 : bool C0Polyhedron::is_vertex(const unsigned int libmesh_dbg_var(i)) const
      75             : {
      76        1208 :   libmesh_assert (i < this->n_nodes());
      77             : 
      78       10440 :   return true;
      79             : }
      80             : 
      81             : 
      82             : 
      83        1704 : bool C0Polyhedron::is_edge(const unsigned int libmesh_dbg_var(i)) const
      84             : {
      85          48 :   libmesh_assert_less (i, this->n_nodes());
      86             : 
      87        1704 :   return false;
      88             : }
      89             : 
      90             : 
      91             : 
      92        1704 : bool C0Polyhedron::is_face(const unsigned int libmesh_dbg_var(i)) const
      93             : {
      94          48 :   libmesh_assert_less (i, this->n_nodes());
      95             : 
      96        1704 :   return false;
      97             : }
      98             : 
      99             : 
     100             : 
     101        2568 : bool C0Polyhedron::is_node_on_side(const unsigned int n,
     102             :                                    const unsigned int s) const
     103             : {
     104         120 :   libmesh_assert_less (n, this->n_nodes());
     105         120 :   libmesh_assert_less (s, this->n_sides());
     106             : 
     107             :   const std::vector<unsigned int> & node_map =
     108        2568 :     std::get<2>(_sidelinks_data[s]);
     109             : 
     110        2568 :   const auto it = std::find(node_map.begin(), node_map.end(), n);
     111             : 
     112        2688 :   return (it != node_map.end());
     113             : }
     114             : 
     115             : std::vector<unsigned int>
     116        1218 : C0Polyhedron::nodes_on_side(const unsigned int s) const
     117             : {
     118          78 :   libmesh_assert_less(s, this->n_sides());
     119        1296 :   return std::get<2>(_sidelinks_data[s]);
     120             : }
     121             : 
     122             : std::vector<unsigned int>
     123         720 : C0Polyhedron::nodes_on_edge(const unsigned int e) const
     124             : {
     125         720 :   auto [s, se] = _edge_lookup[e];
     126         720 :   const Polygon & face = *std::get<0>(_sidelinks_data[s]);
     127             :   const std::vector<unsigned int> & node_map =
     128          60 :     std::get<2>(_sidelinks_data[s]);
     129             :   std::vector<unsigned int> nodes_on_edge =
     130         720 :     face.nodes_on_side(se);
     131        2160 :   for (auto i : index_range(nodes_on_edge))
     132        1560 :     nodes_on_edge[i] = node_map[nodes_on_edge[i]];
     133         780 :   return nodes_on_edge;
     134             : }
     135             : 
     136             : 
     137             : 
     138         288 : bool C0Polyhedron::is_node_on_edge(const unsigned int n,
     139             :                                    const unsigned int e) const
     140             : {
     141          24 :   libmesh_assert_less(e, _edge_lookup.size());
     142         288 :   auto [s, se] = _edge_lookup[e];
     143             : 
     144         288 :   const Polygon & face = *std::get<0>(_sidelinks_data[s]);
     145             :   const std::vector<unsigned int> & node_map =
     146          24 :     std::get<2>(_sidelinks_data[s]);
     147             :   std::vector<unsigned int> nodes_on_edge =
     148         312 :     face.nodes_on_side(se);
     149         432 :   for (auto noe : nodes_on_edge)
     150         468 :     if (node_map[noe] == n)
     151          24 :       return true;
     152             : 
     153           0 :   return false;
     154             : }
     155             : 
     156             : 
     157             : 
     158           0 : void C0Polyhedron::connectivity(const unsigned int /*sf*/,
     159             :                                 const IOPackage /*iop*/,
     160             :                                 std::vector<dof_id_type> & /*conn*/) const
     161             : {
     162           0 :   libmesh_not_implemented();
     163             : }
     164             : 
     165             : 
     166             : 
     167          83 : Real C0Polyhedron::volume () const
     168             : {
     169             :   // This specialization is good for Lagrange mappings only
     170          83 :   if (this->mapping_type() != LAGRANGE_MAP)
     171           0 :     return this->Elem::volume();
     172             : 
     173             :   // We use a triangulation to calculate here.
     174             : 
     175           3 :   Real six_vol = 0;
     176         498 :   for (const auto & subtet : this->_triangulation)
     177             :     {
     178         415 :       const Point p0 = this->point(subtet[0]);
     179         415 :       const Point p1 = this->point(subtet[1]);
     180         415 :       const Point p2 = this->point(subtet[2]);
     181         430 :       const Point p3 = this->point(subtet[3]);
     182             : 
     183          15 :       const Point v01 = p1 - p0;
     184          15 :       const Point v02 = p2 - p0;
     185          15 :       const Point v03 = p3 - p0;
     186             : 
     187         415 :       six_vol += triple_product(v01, v02, v03);
     188             :     }
     189             : 
     190          83 :   return six_vol * (1./6.);
     191             : }
     192             : 
     193             : 
     194             : 
     195          24 : Point C0Polyhedron::true_centroid () const
     196             : {
     197             :   // This specialization is good for Lagrange mappings only
     198          24 :   if (this->mapping_type() != LAGRANGE_MAP)
     199           0 :     return this->Elem::true_centroid();
     200             : 
     201           2 :   Real six_vol = 0;
     202           2 :   Point six_vol_weighted_centroid;
     203         144 :   for (const auto & subtet : this->_triangulation)
     204             :     {
     205         120 :       const Point p0 = this->point(subtet[0]);
     206         120 :       const Point p1 = this->point(subtet[1]);
     207         120 :       const Point p2 = this->point(subtet[2]);
     208         130 :       const Point p3 = this->point(subtet[3]);
     209             : 
     210          10 :       const Point v01 = p1 - p0;
     211          10 :       const Point v02 = p2 - p0;
     212          10 :       const Point v03 = p3 - p0;
     213             : 
     214         110 :       const Real six_tet_vol = triple_product(v01, v02, v03);
     215             : 
     216          10 :       const Point tet_centroid = (p0 + p1 + p2 + p3) * 0.25;
     217             : 
     218         120 :       six_vol += six_tet_vol;
     219             : 
     220          10 :       six_vol_weighted_centroid += six_tet_vol * tet_centroid;
     221             :     }
     222             : 
     223           2 :   return six_vol_weighted_centroid / six_vol;
     224             : }
     225             : 
     226             : 
     227             : 
     228             : std::pair<unsigned short int, unsigned short int>
     229           0 : C0Polyhedron::second_order_child_vertex (const unsigned int /*n*/) const
     230             : {
     231           0 :   libmesh_not_implemented();
     232             :   return std::pair<unsigned short int, unsigned short int> (0,0);
     233             : }
     234             : 
     235             : 
     236             : 
     237         216 : ElemType C0Polyhedron::side_type (const unsigned int libmesh_dbg_var(s)) const
     238             : {
     239          18 :   libmesh_assert_less (s, this->n_sides());
     240         216 :   return C0POLYGON;
     241             : }
     242             : 
     243             : 
     244             : 
     245        3849 : void C0Polyhedron::retriangulate()
     246             : {
     247         110 :   this->_triangulation.clear();
     248             : 
     249             :   // We should already have a triangulation for each side.  We'll turn
     250             :   // that into a triangulation of the entire polyhedral surface
     251             :   // (stored as a mesh, so we can use `find_neighbors()`, then turn
     252             :   // that into a tetrahedralization by shrinking the volume one tet at
     253             :   // a time, which should work fine for convex polyhedra.
     254             : 
     255         220 :   Parallel::Communicator comm_self; // the default communicator is 1 rank
     256        4069 :   ReplicatedMesh surface(comm_self);
     257             : 
     258             :   // poly_node_to_local_id[poly_node] is the local id of
     259             :   // poly_node in the Polyhedron, which is also the global id of the
     260             :   // corresponding node in the surface mesh.
     261         220 :   std::unordered_map<Node *, unsigned int> poly_node_to_id;
     262             : 
     263       26943 :   for (unsigned int s : make_range(this->n_sides()))
     264             :     {
     265       23094 :       const auto & [side, inward_normal, node_map] = this->_sidelinks_data[s];
     266             : 
     267       69282 :       for (auto t : make_range(side->n_subtriangles()))
     268             :         {
     269       47508 :           Elem * tri = surface.add_elem(Elem::build(TRI3));
     270             : 
     271       46188 :           const std::array<int, 3> subtri = side->subtriangle(t);
     272             : 
     273      184752 :           for (int i : make_range(3))
     274             :             {
     275      138564 :               const int side_id = subtri[i];
     276      138564 :               const Node * poly_node = side->node_ptr(side_id);
     277             : 
     278        3960 :               libmesh_assert_less(side_id, node_map.size());
     279      138564 :               const unsigned int local_id = node_map[side_id];
     280             : 
     281      138564 :               Node * surf_node = surface.query_node_ptr(local_id);
     282      138564 :               if (surf_node)
     283        3080 :                 libmesh_assert_equal_to(*(const Point*)poly_node,
     284             :                                         *(const Point*)surf_node);
     285             :               else
     286       30792 :                 surf_node = surface.add_point(*poly_node, local_id);
     287             : 
     288             :               // Get a consistent orientation for surface triangles,
     289             :               // facing their zeta directions outward
     290      138564 :               const int tri_node = inward_normal ? i : 2-i;
     291      138564 :               tri->set_node(tri_node, surf_node);
     292             :             }
     293             :         }
     294             :     }
     295             : 
     296         110 :   surface.allow_renumbering(false);
     297        3849 :   surface.prepare_for_use();
     298             : 
     299             :   // We should have a watertight surface with consistent triangle
     300             :   // orientations.  That's expensive to check.
     301             : #ifdef DEBUG
     302       13750 :   auto verify_surface = [& surface] ()
     303             :     {
     304        4950 :       for (const Elem * elem : surface.element_ptr_range())
     305             :         {
     306       17600 :           for (auto s : make_range(3))
     307             :             {
     308       13200 :               const Elem * neigh = elem->neighbor_ptr(s);
     309       13200 :               libmesh_assert(neigh);
     310       13200 :               libmesh_assert_equal_to(neigh,
     311             :                                       surface.elem_ptr(neigh->id()));
     312       13200 :               const unsigned int ns = neigh->which_neighbor_am_i(elem);
     313       13200 :               libmesh_assert_less(ns, 3);
     314       13200 :               libmesh_assert_equal_to(elem->node_ptr(s),
     315             :                                       neigh->node_ptr((ns+1)%3));
     316       13200 :               libmesh_assert_equal_to(elem->node_ptr((s+1)%3),
     317             :                                       neigh->node_ptr(ns));
     318             :             }
     319             :         }
     320         550 :     };
     321             : 
     322         110 :   verify_surface();
     323             : #endif
     324             : 
     325             :   // We'll have to edit this as we change the surface elements, but we
     326             :   // have a method to initialize it easily.
     327         330 :   std::vector<std::vector<dof_id_type>> nodes_to_elem_vec_map;
     328        3849 :   MeshTools::build_nodes_to_elem_map(surface, nodes_to_elem_vec_map);
     329             : 
     330             :   // We'll be inserting and deleting entries heavily, so we'll use
     331             :   // sets rather than vectors.  We want to get the same results in
     332             :   // parallel, so we'll use element ids rather than Elem pointers
     333         330 :   std::vector<std::set<dof_id_type>> nodes_to_elem_map;
     334       34641 :   for (auto i : index_range(nodes_to_elem_vec_map))
     335             :     nodes_to_elem_map.emplace_back
     336       60704 :       (nodes_to_elem_vec_map[i].begin(),
     337       33432 :        nodes_to_elem_vec_map[i].end());
     338             : 
     339             :   // Now start meshing the volume enclosed by surface, one tet at a
     340             :   // time, with a greedy heuristic: find the vertex node with the most
     341             :   // acutely convex (solid) angle and strip it out as tetrahedra.
     342             : 
     343             :   // We'll want a vector of surrounding nodes for multiple uses,
     344             :   // sometimes with a similarly-sorted vector of surrounding elements
     345             :   // to go with it.
     346             :   auto surroundings_of =
     347       47177 :     [&nodes_to_elem_map, & surface]
     348             :     (const Node & node,
     349       59937 :      std::vector<Elem *> * surrounding_elems)
     350             :     {
     351             :       const std::set<dof_id_type> & elems_by_node =
     352        3575 :         nodes_to_elem_map[node.id()];
     353             : 
     354       50037 :       const unsigned int n_surrounding = elems_by_node.size();
     355        1430 :       libmesh_assert_greater_equal(n_surrounding, 3);
     356             : 
     357       50037 :       if (surrounding_elems)
     358             :         {
     359         550 :           libmesh_assert(surrounding_elems->empty());
     360       19245 :           surrounding_elems->resize(n_surrounding);
     361             :         }
     362             : 
     363       50037 :       std::vector<Node *> surrounding_nodes(n_surrounding);
     364             : 
     365       50037 :       Elem * elem = surface.elem_ptr(*elems_by_node.begin());
     366      246336 :       for (auto i : make_range(n_surrounding))
     367             :         {
     368      190689 :           const unsigned int n = elem->get_node_index(&node);
     369        5610 :           libmesh_assert_not_equal_to(n, invalid_uint);
     370      196299 :           Node * next_node = elem->node_ptr((n+1)%3);
     371      196299 :           surrounding_nodes[i] = next_node;
     372      196299 :           if (surrounding_elems)
     373       59385 :             (*surrounding_elems)[i] = elem;
     374      196299 :           elem = elem->neighbor_ptr((n+2)%3);
     375        5610 :           libmesh_assert(elem);
     376        5610 :           libmesh_assert_equal_to(elem, surface.elem_ptr(elem->id()));
     377             : 
     378             :           // We should have a manifold here, but verifying that is
     379             :           // expensive
     380             : #ifdef DEBUG
     381        5610 :           libmesh_assert_equal_to
     382             :             (std::count(surrounding_nodes.begin(),
     383             :                         surrounding_nodes.end(), next_node),
     384             :              1);
     385             : #endif
     386             :         }
     387             : 
     388             :       // We should have finished a loop
     389        1430 :       libmesh_assert_equal_to
     390             :         (elem, surface.elem_ptr(*elems_by_node.begin()));
     391             : 
     392       51467 :       return surrounding_nodes;
     393        3849 :     };
     394             : 
     395       30792 :   auto geometry_at = [&surroundings_of](const Node & node)
     396             :     {
     397             :       const std::vector<Node *> surrounding_nodes =
     398       31672 :         surroundings_of(node, nullptr);
     399             : 
     400             :       // Now sum up solid angles from tetrahedra created from the
     401             :       // trivial triangulation of the surrounding nodes loop
     402         880 :       Real total_solid_angle = 0;
     403             :       const int n_surrounding =
     404        1760 :         cast_int<int>(surrounding_nodes.size());
     405             : 
     406      107772 :       for (auto n : make_range(n_surrounding-2))
     407             :         {
     408             :           const Point
     409       79180 :             v01 = static_cast<Point>(*surrounding_nodes[n]) - node,
     410       79180 :             v02 = static_cast<Point>(*surrounding_nodes[n+1]) - node,
     411       79180 :             v03 = static_cast<Point>(*surrounding_nodes[n+2]) - node;
     412             : 
     413       76980 :           total_solid_angle += solid_angle(v01, v02, v03);
     414             :         }
     415             : 
     416       31672 :       return std::make_pair(n_surrounding, total_solid_angle);
     417        3739 :     };
     418             : 
     419             :   // We'll keep track of solid angles and node valences so we don't
     420             :   // waste time recomputing them when they haven't changed.  We need
     421             :   // to be able to search efficiently for the smallest angles of each
     422             :   // valence, but also search efficiently for a particular node to
     423             :   // remove and reinsert it when its connectivity changes.
     424             :   //
     425             :   // Since C++11 multimap has guaranteed that pairs with matching keys
     426             :   // are kept in insertion order, so we can use Node * for values even
     427             :   // in parallel.
     428             :   typedef std::multimap<std::pair<int, Real>, Node*> node_map_type;
     429         220 :   node_map_type nodes_by_geometry;
     430         220 :   std::map<Node *, node_map_type::iterator> node_index;
     431             : 
     432       38380 :   for (auto node : surface.node_ptr_range())
     433       30792 :     node_index[node] =
     434       65213 :       nodes_by_geometry.emplace(geometry_at(*node), node);
     435             : 
     436             :   // In 3D, this will require nested loops: an outer loop to remove
     437             :   // each vertex, and an inner loop to remove multiple tetrahedra in
     438             :   // cases where the vertex has more than 3 neighboring triangles.
     439             : 
     440             :   // We'll be done when there are only three "unremoved" nodes left,
     441             :   // so they don't actually enclose any volume.
     442       23094 :   for (auto i : make_range(nodes_by_geometry.size()-3))
     443             :     {
     444         550 :       auto geometry_it = nodes_by_geometry.begin();
     445       19245 :       auto geometry_key = geometry_it->first;
     446       19245 :       auto [valence, angle] = geometry_key;
     447       19245 :       Node * node = geometry_it->second;
     448         550 :       libmesh_ignore(i);
     449             : 
     450             :       // If our lowest-valence nodes are all points of non-convexity,
     451             :       // skip to a higher valence.
     452       19245 :       while (angle > 2*pi-TOLERANCE)
     453             :         {
     454             :           geometry_it =
     455             :             nodes_by_geometry.upper_bound
     456           0 :               (std::make_pair(valence, Real(100)));
     457           0 :           libmesh_assert(geometry_it != nodes_by_geometry.end());
     458             : 
     459           0 :           std::tie(geometry_key, node) = *geometry_it;
     460           0 :           std::tie(valence, angle) = geometry_key;
     461             :         }
     462             : 
     463        1100 :       std::vector<Elem *> surrounding_elems;
     464             :       std::vector<Node *> surrounding_nodes =
     465       19795 :         surroundings_of(*node, &surrounding_elems);
     466             : 
     467        1100 :       const std::size_t n_surrounding = surrounding_nodes.size();
     468             : 
     469             :       // As we separate surrounding nodes from our center node, we'll
     470             :       // be marking them as nullptr; we still need to be able to find
     471             :       // predecessor and successor nodes in order afterward.
     472             :       auto find_valid_nodes_around =
     473       72580 :         [n_surrounding, & surrounding_nodes]
     474      160560 :         (unsigned int j)
     475             :       {
     476       76980 :         unsigned int jnext = (j+1)%n_surrounding;
     477       79180 :         while (!surrounding_nodes[jnext])
     478           0 :           jnext = (jnext+1)%n_surrounding;
     479             : 
     480       76980 :         unsigned int jprev = (j+n_surrounding-1)%n_surrounding;
     481       79180 :         while (!surrounding_nodes[jprev])
     482           0 :           jprev = (jprev+n_surrounding-1)%n_surrounding;
     483             : 
     484       79180 :         return std::make_pair(jprev, jnext);
     485       19245 :       };
     486             : 
     487             :       // We may have too many surrounding nodes to handle with
     488             :       // just one tet.  In that case we'll keep a cache of the
     489             :       // element qualities that we'd get by making a tet with the
     490             :       // edge from the center node to each surrounding node, so we
     491             :       // can build the best tets first.
     492             :       //
     493             :       // In the case where we just have 3 nodes, we'll just pretend
     494             :       // they all have the same positive quality, so we can still
     495             :       // search this vector.
     496       19795 :       std::vector<Real> local_tet_quality(n_surrounding, 1);
     497             : 
     498             :       // From our center node with N surrounding nodes we can make N-2
     499             :       // tetrahedra.  The first N-3 each replace two surface tets with
     500             :       // two new surface tets.
     501             :       //
     502             :       // My first idea was to greedily pick nodes with the smallest
     503             :       // local (solid) angles to get the best quality.  This works in
     504             :       // 2D, but such a node can give a pancake tet in 3D.
     505             :       //
     506             :       // My second idea was to greedily pick nodes with the highest
     507             :       // prospective tet quality.  This works for the first tets, but
     508             :       // can leave a pancake tet behind.
     509             :       //
     510             :       // My third idea is to try to fix the lowest quality tets first,
     511             :       // by picking cases where they have higher quality neighbors,
     512             :       // and creating those neighbors so as to change them.
     513             : 
     514             :       auto find_new_tet_nodes =
     515       18145 :         [& local_tet_quality, & find_valid_nodes_around]
     516       34095 :         ()
     517             :       {
     518         550 :         unsigned int jbest = 0;
     519       19245 :         auto [jminus, jplus] = find_valid_nodes_around(jbest);
     520       19245 :         Real qneighbest = std::min(local_tet_quality[jminus],
     521       19795 :                                    local_tet_quality[jplus]);
     522       19245 :         for (auto j : make_range(std::size_t(1),
     523       58835 :                                  local_tet_quality.size()))
     524             :           {
     525             :             // We don't want to build a bad tet
     526       39590 :             if (local_tet_quality[j] <= 0)
     527           0 :               continue;
     528             : 
     529       38490 :             std::tie(jminus, jplus) = find_valid_nodes_around(j);
     530       38490 :             Real qneighj = std::min(local_tet_quality[jminus],
     531       39590 :                                     local_tet_quality[jplus]);
     532             : 
     533             :             // We don't want to build a tet that can't fix a neighbor
     534             :             // if we can build one that can.
     535       38490 :             if (qneighbest <= 0 &&
     536             :                 qneighj > 0)
     537           0 :               continue;
     538             : 
     539             :             // We want to try for the best possible fix.
     540       39590 :             if ((local_tet_quality[j] - qneighj) >
     541       39590 :                 (local_tet_quality[jbest] - qneighj))
     542             :               {
     543           0 :                 jbest = j;
     544           0 :                 qneighbest = qneighj;
     545             :               }
     546             :           }
     547             : 
     548       19795 :         libmesh_error_msg_if
     549             :           (local_tet_quality[jbest] <= 0,
     550             :            "Cannot build non-singular non-inverted tet");
     551             : 
     552       19245 :         std::tie(jminus, jplus) = find_valid_nodes_around(jbest);
     553             : 
     554       19795 :         return std::make_tuple(jbest, jminus, jplus);
     555       19245 :       };
     556             : 
     557       19245 :       if (n_surrounding > 3)
     558             :         {
     559             :           // We'll be searching local_tet_quality even after tet
     560             :           // extraction disconnects us from some nodes; when we do we
     561             :           // don't want to get one.
     562           0 :           constexpr Real far_node = -1e6;
     563             : 
     564             :           // Vectors from the center node to each of its surrounding
     565             :           // nodes are helpful for calculating prospective tet
     566             :           // quality.
     567           0 :           std::vector<Point> v0s(n_surrounding);
     568           0 :           for (auto j : make_range(n_surrounding))
     569           0 :             v0s[j] = *(Point *)surrounding_nodes[j] - *node;
     570             : 
     571             :           // Find the tet quality we'd potentially get from each
     572             :           // possible choice of tet
     573             :           auto local_tet_quality_of =
     574           0 :             [& surrounding_nodes, & v0s, & find_valid_nodes_around]
     575           0 :             (unsigned int j)
     576             :           {
     577           0 :             auto [jminus, jplus] = find_valid_nodes_around(j);
     578             : 
     579             :             // Anything proportional to the ratio of volume to
     580             :             // total-edge-length-cubed should peak for perfect tets
     581             :             // but hit 0 for pancakes and slivers.
     582             : 
     583             :             const Real total_len =
     584           0 :               v0s[j].norm() + v0s[jminus].norm() + v0s[jplus].norm() +
     585           0 :               (*(Point *)surrounding_nodes[jplus] -
     586           0 :                *(Point *)surrounding_nodes[j]).norm() +
     587           0 :               (*(Point *)surrounding_nodes[j] -
     588           0 :                *(Point *)surrounding_nodes[jminus]).norm() +
     589           0 :               (*(Point *)surrounding_nodes[jminus] -
     590           0 :                *(Point *)surrounding_nodes[jplus]).norm();
     591             : 
     592             :             // Orientation here is tricky.  Think of the triple
     593             :             // product as (v1 cross v2) dot v3, with right hand rule.
     594             :             const Real six_vol =
     595           0 :               triple_product(v0s[jminus], v0s[jplus], v0s[j]);
     596             : 
     597           0 :             return six_vol / (total_len * total_len * total_len);
     598           0 :           };
     599             : 
     600           0 :           for (auto j : make_range(n_surrounding))
     601           0 :             local_tet_quality[j] = local_tet_quality_of(j);
     602             : 
     603             :           // If we have N surrounding nodes, we can make N tets and
     604             :           // that'll bring us back to the 3-surrounding-node case to
     605             :           // finish.
     606           0 :           for (auto t : make_range(n_surrounding-3))
     607             :             {
     608           0 :               libmesh_ignore(t);
     609             : 
     610           0 :               auto [jbest, jminus, jplus] = find_new_tet_nodes();
     611             : 
     612             :               // Turn these four nodes into a tet
     613           0 :               Node * nbest  = surrounding_nodes[jbest],
     614           0 :                    * nminus = surrounding_nodes[jminus],
     615           0 :                    * nplus  = surrounding_nodes[jplus];
     616           0 :               this->add_tet(nminus->id(), nbest->id(), nplus->id(),
     617           0 :                             node->id());
     618             : 
     619             :               // Replace the old two triangles around these nodes with the
     620             :               // proper two new triangles.
     621           0 :               Elem * oldtri1 = surrounding_elems[jminus],
     622           0 :                    * oldtri2 = surrounding_elems[jbest],
     623           0 :                    * newtri1 = surface.add_elem(Elem::build(TRI3)),
     624           0 :                    * newtri2 = surface.add_elem(Elem::build(TRI3));
     625             : 
     626           0 :               const unsigned int c1 = oldtri1->get_node_index(node),
     627           0 :                                  c2 = oldtri2->get_node_index(node);
     628             : 
     629           0 :               newtri1->set_node(0, node);
     630           0 :               newtri1->set_node(1, nminus);
     631           0 :               newtri1->set_node(2, nplus);
     632             : 
     633           0 :               surrounding_elems[jminus] = newtri1;
     634             : 
     635           0 :               Elem * neigh10 = oldtri1->neighbor_ptr(c1);
     636           0 :               Elem * neigh12 = oldtri2->neighbor_ptr((c2+2)%3);
     637           0 :               newtri1->set_neighbor(0, neigh10);
     638           0 :               neigh10->set_neighbor(neigh10->which_neighbor_am_i(oldtri1), newtri1);
     639           0 :               newtri1->set_neighbor(1, newtri2);
     640           0 :               newtri1->set_neighbor(2, neigh12);
     641           0 :               neigh12->set_neighbor(neigh12->which_neighbor_am_i(oldtri2), newtri1);
     642             : 
     643           0 :               newtri2->set_node(0, nplus);
     644           0 :               newtri2->set_node(1, nminus);
     645           0 :               newtri2->set_node(2, nbest);
     646             : 
     647           0 :               Elem * neigh21 = oldtri1->neighbor_ptr((c1+1)%3);
     648           0 :               Elem * neigh22 = oldtri2->neighbor_ptr((c2+1)%3);
     649           0 :               newtri2->set_neighbor(0, newtri1);
     650           0 :               newtri2->set_neighbor(1, neigh21);
     651           0 :               neigh21->set_neighbor(neigh21->which_neighbor_am_i(oldtri1), newtri2);
     652           0 :               newtri2->set_neighbor(2, neigh22);
     653           0 :               neigh22->set_neighbor(neigh22->which_neighbor_am_i(oldtri2), newtri2);
     654             : 
     655           0 :               for (unsigned int p : make_range(3))
     656             :                 {
     657           0 :                   nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
     658           0 :                   nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
     659           0 :                   nodes_to_elem_map[newtri1->node_id(p)].insert(newtri1->id());
     660           0 :                   nodes_to_elem_map[newtri2->node_id(p)].insert(newtri2->id());
     661             :                 }
     662             : 
     663             :               // We've finished replacing the old triangles.
     664           0 :               surface.delete_elem(oldtri1);
     665           0 :               surface.delete_elem(oldtri2);
     666             : 
     667             :               // The solid angle for the far node should now stay
     668             :               // unchanged until we're out of this inner loop; let's
     669             :               // recalculate it here, and then we'll be done with it.
     670           0 :               Node * & nbestref = surrounding_nodes[jbest];
     671           0 :               nodes_by_geometry.erase(node_index[nbestref]);
     672           0 :               node_index[nbestref] =
     673           0 :                 nodes_by_geometry.emplace(geometry_at(*nbestref), nbestref);
     674             : 
     675             :               // The far node is no longer sharing an edge with our center
     676             :               // node.  Make sure we don't use it again with the center
     677             :               // node.
     678           0 :               local_tet_quality[jbest] = far_node;
     679           0 :               nbestref = nullptr;
     680             : 
     681             :               // The potential tet qualities using the side nodes have
     682             :               // changed now that they're directly connected to each
     683             :               // other.
     684           0 :               local_tet_quality[jminus] =
     685           0 :                 local_tet_quality_of(jminus);
     686             : 
     687           0 :               local_tet_quality[jplus] =
     688           0 :                 local_tet_quality_of(jplus);
     689             :             }
     690             :         }
     691             : 
     692             :       // Now we should have just 3 surrounding nodes, with which to
     693             :       // make one tetrahedron.  Put them in a counterclockwise
     694             :       // (looking from outside) orientation, not the "best, clockwise,
     695             :       // counterclockwise" we get from the lambda.
     696       19245 :       auto [j2, j1, j3] = find_new_tet_nodes();
     697             : 
     698             :       // Turn these last four nodes into a tet
     699       19245 :       Node * n1 = surrounding_nodes[j1],
     700       19245 :            * n2 = surrounding_nodes[j2],
     701       19245 :            * n3 = surrounding_nodes[j3];
     702       19245 :       this->add_tet(n1->id(), n2->id(), n3->id(), node->id());
     703             : 
     704             :       // Replace the three surface triangles of that tet with the new
     705             :       // fourth triangle.
     706       19245 :       Elem * oldtri1 = surrounding_elems[j1],
     707       19245 :            * oldtri2 = surrounding_elems[j2],
     708       19245 :            * oldtri3 = surrounding_elems[j3],
     709       19795 :            * newtri = surface.add_elem(Elem::build(TRI3));
     710             : 
     711       18695 :       const unsigned int c1 = oldtri1->get_node_index(node),
     712       18695 :                          c2 = oldtri2->get_node_index(node),
     713       18695 :                          c3 = oldtri3->get_node_index(node);
     714             : 
     715       19245 :       newtri->set_node(0, n1);
     716       19245 :       newtri->set_node(1, n2);
     717       19245 :       newtri->set_node(2, n3);
     718       19245 :       Elem * neigh0 = oldtri1->neighbor_ptr((c1+1)%3);
     719        1100 :       newtri->set_neighbor(0, neigh0);
     720       19245 :       neigh0->set_neighbor(neigh0->which_neighbor_am_i(oldtri1), newtri);
     721       19245 :       Elem * neigh1 = oldtri2->neighbor_ptr((c2+1)%3);
     722        1100 :       newtri->set_neighbor(1, neigh1);
     723       19245 :       neigh1->set_neighbor(neigh1->which_neighbor_am_i(oldtri2), newtri);
     724       19245 :       Elem * neigh2 = oldtri3->neighbor_ptr((c3+1)%3);
     725        1100 :       newtri->set_neighbor(2, neigh2);
     726       19245 :       neigh2->set_neighbor(neigh2->which_neighbor_am_i(oldtri3), newtri);
     727             : 
     728       76980 :       for (unsigned int p : make_range(3))
     729             :         {
     730       61035 :           nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
     731       61035 :           nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
     732       61035 :           nodes_to_elem_map[oldtri3->node_id(p)].erase(oldtri3->id());
     733       61035 :           nodes_to_elem_map[newtri->node_id(p)].insert(newtri->id());
     734             :         }
     735             : 
     736             :       // We've finished replacing the old triangles.
     737       19245 :       surface.delete_elem(oldtri1);
     738       19245 :       surface.delete_elem(oldtri2);
     739       19245 :       surface.delete_elem(oldtri3);
     740             : 
     741             :       // We should have used up all our surrounding nodes now, and we
     742             :       // shouldn't have messed up our surface in the process, and our
     743             :       // center node should no longer be part of the surface.
     744             : #ifdef DEBUG
     745         550 :       surrounding_nodes[j1] = nullptr;
     746         550 :       surrounding_nodes[j2] = nullptr;
     747         550 :       surrounding_nodes[j3] = nullptr;
     748             : 
     749        2200 :       for (auto ltq : surrounding_nodes)
     750        1650 :         libmesh_assert(!ltq);
     751             : 
     752         550 :       if (surface.n_elem() > 3)
     753         440 :         verify_surface();
     754             : 
     755        3850 :       for (const Elem * elem : surface.element_ptr_range())
     756       13200 :         for (auto p : make_range(3))
     757        9900 :           libmesh_assert_not_equal_to
     758             :             (elem->node_ptr(p), node);
     759             : #endif
     760             : 
     761             :       // We've used up our center node, so it's not something we can
     762             :       // eliminate again.
     763       18695 :       nodes_by_geometry.erase(geometry_it);
     764             :     }
     765             : 
     766             :   // At this point our surface should just have two triangles left.
     767         110 :   libmesh_assert_equal_to(surface.n_elem(), 2);
     768       11107 : }
     769             : 
     770             : 
     771       19245 : void C0Polyhedron::add_tet(int n1,
     772             :                            int n2,
     773             :                            int n3,
     774             :                            int n4)
     775             : {
     776             : #ifndef NDEBUG
     777         550 :   const auto nn = this->n_nodes();
     778         550 :   libmesh_assert_less(n1, nn);
     779         550 :   libmesh_assert_less(n2, nn);
     780         550 :   libmesh_assert_less(n3, nn);
     781         550 :   libmesh_assert_less(n4, nn);
     782             : 
     783         550 :   const Point v12 = this->point(n2) - this->point(n1);
     784         550 :   const Point v13 = this->point(n3) - this->point(n1);
     785         550 :   const Point v14 = this->point(n4) - this->point(n1);
     786         550 :   const Real six_vol = triple_product(v12, v13, v14);
     787         550 :   libmesh_assert_greater(six_vol, Real(0));
     788             : #endif
     789             : 
     790       19245 :   this->_triangulation.push_back({n1, n2, n3, n4});
     791       19245 : }
     792             : 
     793             : 
     794             : } // namespace libMesh

Generated by: LCOV version 1.14