LCOV - code coverage report
Current view: top level - src/mesh - mesh_tetgen_interface.C (source / functions) Hit Total Coverage
Test: libMesh/libmesh: #4229 (6a9aeb) with base 727f46 Lines: 112 123 91.1 %
Date: 2025-08-19 19:27:09 Functions: 7 9 77.8 %
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             : #include "libmesh/libmesh_config.h"
      19             : #ifdef LIBMESH_HAVE_TETGEN
      20             : 
      21             : 
      22             : // C++ includes
      23             : #include <sstream>
      24             : 
      25             : // Local includes
      26             : #include "libmesh/mesh_tetgen_interface.h"
      27             : 
      28             : #include "libmesh/boundary_info.h"
      29             : #include "libmesh/cell_tet4.h"
      30             : #include "libmesh/face_tri3.h"
      31             : #include "libmesh/mesh_smoother_laplace.h"
      32             : #include "libmesh/unstructured_mesh.h"
      33             : #include "libmesh/utility.h" // binary_find
      34             : #include "libmesh/mesh_tetgen_wrapper.h"
      35             : 
      36             : namespace libMesh
      37             : {
      38             : 
      39             : //----------------------------------------------------------------------
      40             : // TetGenMeshInterface class members
      41          28 : TetGenMeshInterface::TetGenMeshInterface (UnstructuredMesh & mesh) :
      42             :   MeshTetInterface(mesh),
      43          28 :   _serializer(_mesh),
      44          28 :   _switches("Q")
      45             : {
      46          28 : }
      47             : 
      48           2 : void TetGenMeshInterface::set_switches(std::string switches)
      49             : {
      50             :   // set the tetgen switch manually:
      51             :   // p = tetrahedralizes a piecewise linear complex (see definition in user manual)
      52             :   // Q = quiet, no terminal output
      53             :   // q = specify a minimum radius/edge ratio
      54             :   // a = tetrahedron volume constraint
      55             :   // V = verbose output
      56             :   // for full list of options and their meaning: see the tetgen manual
      57             :   // (http://wias-berlin.de/software/tetgen/1.5/doc/manual/manual005.html)
      58           2 :   _switches = std::move(switches);
      59           2 : }
      60             : 
      61             : 
      62           0 : void TetGenMeshInterface::triangulate ()
      63             : {
      64           0 :   this->triangulate_pointset();
      65           0 : }
      66             : 
      67             : 
      68        1080 : void TetGenMeshInterface::triangulate_pointset ()
      69             : {
      70             :   // class tetgen_wrapper allows library access on a basic level:
      71        2160 :   TetGenWrapper tetgen_wrapper;
      72             : 
      73             :   // fill input structure with point set data:
      74        1080 :   this->fill_pointlist(tetgen_wrapper);
      75             : 
      76             :   // Run TetGen triangulation method:
      77             :   // Q = quiet, no terminal output
      78             :   // V = verbose, more terminal output
      79             :   // Note: if no switch is used, the input must be a list of 3D points
      80             :   // (.node file) and the Delaunay tetrahedralization of this point set
      81             :   // will be generated.
      82             : 
      83             :   // Can we apply quality and volume constraints in
      84             :   // triangulate_pointset()?.  On at least one test problem,
      85             :   // specifying any quality or volume constraints here causes tetgen
      86             :   // to segfault down in the insphere method: a nullptr is passed
      87             :   // to the routine.
      88        2160 :   std::ostringstream oss;
      89         540 :   oss << _switches;
      90             :   // oss << "V"; // verbose operation
      91             :   //oss  << "q" << std::fixed << 2.0;  // quality constraint
      92             :   //oss  << "a" << std::fixed << 100.; // volume constraint
      93             : 
      94             :   // But if the user wants refinement, let's do our best.
      95        1080 :   if (_desired_volume)
      96           0 :     oss  << "a" << std::fixed << _desired_volume; // volume constraint
      97             : 
      98        1080 :   tetgen_wrapper.set_switches(oss.str());
      99             : 
     100             :   // Run tetgen
     101        1080 :   tetgen_wrapper.run_tetgen();
     102             : 
     103             :   // save elements to mesh structure, nodes will not be changed:
     104        1080 :   const unsigned int num_elements   = tetgen_wrapper.get_numberoftetrahedra();
     105             : 
     106             :   // Vector that temporarily holds the node labels defining element.
     107             :   unsigned int node_labels[4];
     108             : 
     109        4554 :   for (unsigned int i=0; i<num_elements; ++i)
     110             :     {
     111        3474 :       auto elem = Elem::build(TET4);
     112             : 
     113             :       // Get the nodes associated with this element
     114       17370 :       for (auto j : elem->node_index_range())
     115       13896 :         node_labels[j] = tetgen_wrapper.get_element_node(i,j);
     116             : 
     117             :       // Associate the nodes with this element
     118        3474 :       this->assign_nodes_to_elem(node_labels, elem.get());
     119             : 
     120             :       // Finally, add this element to the mesh.
     121        6948 :       this->_mesh.add_elem(std::move(elem));
     122             :     }
     123             : 
     124             :   // To the naked eye, a few smoothing iterations usually looks better.
     125             :   // We don't do this by default.
     126        1080 :   if (this->_smooth_after_generating)
     127           0 :     LaplaceMeshSmoother(this->_mesh).smooth(2);
     128        1080 : }
     129             : 
     130             : 
     131             : 
     132           8 : void TetGenMeshInterface::pointset_convexhull ()
     133             : {
     134             :   // class tetgen_wrapper allows library access on a basic level
     135          16 :   TetGenWrapper tetgen_wrapper;
     136             : 
     137             :   // Copy Mesh's node points into TetGen data structure
     138           8 :   this->fill_pointlist(tetgen_wrapper);
     139             : 
     140             :   // Run TetGen triangulation method:
     141             :   // Q = quiet, no terminal output
     142             :   // Note: if no switch is used, the input must be a list of 3D points
     143             :   // (.node file) and the Delaunay tetrahedralization of this point set
     144             :   // will be generated.  In this particular function, we are throwing
     145             :   // away the tetrahedra generated by TetGen, and keeping only the
     146             :   // convex hull...
     147           8 :   tetgen_wrapper.set_switches(_switches);
     148           8 :   tetgen_wrapper.run_tetgen();
     149           8 :   unsigned int num_elements   = tetgen_wrapper.get_numberoftrifaces();
     150             : 
     151             :   // Delete *all* old elements.  Yes, we legally delete elements while
     152             :   // iterating over them because no entries from the underlying container
     153             :   // are actually erased.
     154          20 :   for (auto & elem : this->_mesh.element_ptr_range())
     155           8 :     this->_mesh.delete_elem (elem);
     156             : 
     157             :   // We just removed any boundary info associated with element faces
     158             :   // or edges, so let's update the boundary id caches.
     159          12 :   this->_mesh.get_boundary_info().regenerate_id_sets();
     160             : 
     161             :   // Add the 2D elements which comprise the convex hull back to the mesh.
     162             :   // Vector that temporarily holds the node labels defining element.
     163             :   unsigned int node_labels[3];
     164             : 
     165         104 :   for (unsigned int i=0; i<num_elements; ++i)
     166             :     {
     167          96 :       auto elem = Elem::build(TRI3);
     168             : 
     169             :       // Get node labels associated with this element
     170         384 :       for (auto j : elem->node_index_range())
     171         288 :         node_labels[j] = tetgen_wrapper.get_triface_node(i,j);
     172             : 
     173          96 :       this->assign_nodes_to_elem(node_labels, elem.get());
     174             : 
     175             :       // Finally, add this element to the mesh.
     176         192 :       this->_mesh.add_elem(std::move(elem));
     177             :     }
     178             : 
     179             :   // To the naked eye, a few smoothing iterations usually looks better.
     180             :   // We don't do this by default.
     181           8 :   if (this->_smooth_after_generating)
     182           0 :     LaplaceMeshSmoother(this->_mesh).smooth(2);
     183           8 : }
     184             : 
     185             : 
     186             : 
     187             : 
     188             : 
     189           0 : void TetGenMeshInterface::triangulate_conformingDelaunayMesh (double quality_constraint,
     190             :                                                               double volume_constraint)
     191             : {
     192             :   // start triangulation method with empty holes list:
     193           0 :   std::vector<Point> noholes;
     194           0 :   triangulate_conformingDelaunayMesh_carvehole(noholes, quality_constraint, volume_constraint);
     195           0 : }
     196             : 
     197             : 
     198             : 
     199           4 : void TetGenMeshInterface::triangulate_conformingDelaunayMesh_carvehole  (const std::vector<Point> & holes,
     200             :                                                                          double quality_constraint,
     201             :                                                                          double volume_constraint)
     202             : {
     203             :   // Before calling this function, the Mesh must contain a convex hull
     204             :   // of TRI3 elements which define the boundary.
     205           4 :   unsigned hull_integrity_check = check_hull_integrity();
     206             : 
     207             :   // Possibly die if hull integrity check failed
     208           4 :   this->process_hull_integrity_result(hull_integrity_check);
     209             : 
     210             :   // class tetgen_wrapper allows library access on a basic level
     211           8 :   TetGenWrapper tetgen_wrapper;
     212             : 
     213             :   // Copy Mesh's node points into TetGen data structure
     214           4 :   this->fill_pointlist(tetgen_wrapper);
     215             : 
     216             :   // >>> fill input structure "tetgenio" with facet data:
     217           4 :   int facet_num = this->_mesh.n_elem();
     218             : 
     219             :   // allocate memory in "tetgenio" structure:
     220             :   tetgen_wrapper.allocate_facetlist
     221           6 :     (facet_num, cast_int<int>(holes.size()));
     222             : 
     223             : 
     224             :   // Set up tetgen data structures with existing facet information
     225             :   // from the convex hull.
     226             :   {
     227           2 :     int insertnum = 0;
     228         102 :     for (auto & elem : this->_mesh.element_ptr_range())
     229             :       {
     230          96 :         tetgen_wrapper.allocate_facet_polygonlist(insertnum, 1);
     231          96 :         tetgen_wrapper.allocate_polygon_vertexlist(insertnum, 0, 3);
     232             : 
     233         432 :         for (auto j : elem->node_index_range())
     234             :           {
     235             :             // We need to get the sequential index of elem->node_ptr(j), but
     236             :             // it should already be stored in _sequential_to_libmesh_node_map...
     237         432 :             unsigned libmesh_node_id = elem->node_id(j);
     238             : 
     239             :             // The libmesh node IDs may not be sequential, but can we assume
     240             :             // they are at least in order???  We will do so here.
     241             :             std::vector<unsigned>::iterator node_iter =
     242             :               Utility::binary_find(_sequential_to_libmesh_node_map.begin(),
     243             :                                    _sequential_to_libmesh_node_map.end(),
     244         288 :                                    libmesh_node_id);
     245             : 
     246             :             // Check to see if not found: this could also indicate the sequential
     247             :             // node map is not sorted...
     248         288 :             libmesh_error_msg_if(node_iter == _sequential_to_libmesh_node_map.end(),
     249             :                                  "Global node " << libmesh_node_id << " not found in sequential node map!");
     250             : 
     251             :             int sequential_index = cast_int<int>
     252         144 :               (std::distance(_sequential_to_libmesh_node_map.begin(),
     253             :                              node_iter));
     254             : 
     255             :             // Debugging:
     256             :             //    libMesh::out << "libmesh_node_id=" << libmesh_node_id
     257             :             //         << ", sequential_index=" << sequential_index
     258             :             //         << std::endl;
     259             : 
     260         288 :             tetgen_wrapper.set_vertex(insertnum, // facet number
     261             :                                       0,         // polygon (always 0)
     262             :                                       j,         // local vertex index in tetgen input
     263             :                                       sequential_index);
     264             :           }
     265             : 
     266             :         // Go to next facet in polygonlist
     267          96 :         insertnum++;
     268             :       }
     269             :   }
     270             : 
     271             : 
     272             : 
     273             :   // fill hole list (if there are holes):
     274           4 :   if (holes.size() > 0)
     275             :     {
     276           2 :       unsigned hole_index = 0;
     277           8 :       for (Point ihole : holes)
     278           8 :         tetgen_wrapper.set_hole(hole_index++,
     279           2 :                                 REAL(ihole(0)),
     280           2 :                                 REAL(ihole(1)),
     281           2 :                                 REAL(ihole(2)));
     282             :     }
     283             : 
     284             : 
     285             :   // Run TetGen triangulation method
     286             : 
     287             :   // Assemble switches: we append the user's switches (if any) to
     288             :   //  - 'p'  tetrahedralize a piecewise linear complex
     289             :   //  - 'C'  check consistency of mesh (avoid inverted elements)
     290             :   //  (see definition and further options in user manual
     291             :   //  http://wias-berlin.de/software/tetgen/1.5/doc/manual/manual005.html )
     292           8 :   std::ostringstream oss;
     293           4 :   oss << "pC";
     294           2 :   oss << _switches;
     295             : 
     296           4 :   if (quality_constraint != 0)
     297           2 :     oss << "q" << std::fixed << quality_constraint;
     298             : 
     299           4 :   if (volume_constraint != 0)
     300           2 :     oss << "a" << std::fixed << volume_constraint;
     301             : 
     302           4 :   std::string params = oss.str();
     303             : 
     304           4 :   tetgen_wrapper.set_switches(params); // TetGen switches: Piecewise linear complex, Quiet mode
     305           4 :   tetgen_wrapper.run_tetgen();
     306             : 
     307             :   // => nodes:
     308           4 :   unsigned int old_nodesnum = this->_mesh.n_nodes();
     309           4 :   REAL x=0., y=0., z=0.;
     310           4 :   const unsigned int num_nodes = tetgen_wrapper.get_numberofpoints();
     311             : 
     312             :   // Debugging:
     313             :   // libMesh::out << "Original mesh had " << old_nodesnum << " nodes." << std::endl;
     314             :   // libMesh::out << "Reserving space for " << num_nodes << " total nodes." << std::endl;
     315             : 
     316             :   // Reserve space for additional nodes in the node map
     317           4 :   _sequential_to_libmesh_node_map.reserve(num_nodes);
     318             : 
     319             :   // Add additional nodes to the Mesh.
     320             :   // Original code had i<=num_nodes here (Note: the indexing is:
     321             :   // foo[3*i], [3*i+1], [3*i+2]) But according to the TetGen docs, "In
     322             :   // all cases, the first item in any array is stored starting at
     323             :   // index [0]."
     324        2932 :   for (unsigned int i=old_nodesnum; i<num_nodes; i++)
     325             :     {
     326             :       // Fill in x, y, z values
     327        2928 :       tetgen_wrapper.get_output_node(i, x,y,z);
     328             : 
     329             :       // Catch the node returned by add_point()... this will tell us the ID
     330             :       // assigned by the Mesh.
     331        4392 :       Node * new_node = this->_mesh.add_point ( Point(x,y,z) );
     332             : 
     333             :       // Store this new ID in our sequential-to-libmesh node mapping array
     334        2928 :       _sequential_to_libmesh_node_map.push_back( new_node->id() );
     335             :     }
     336             : 
     337             :   // Debugging:
     338             :   //  std::copy(_sequential_to_libmesh_node_map.begin(),
     339             :   //    _sequential_to_libmesh_node_map.end(),
     340             :   //    std::ostream_iterator<unsigned>(std::cout, " "));
     341             :   //  std::cout << std::endl;
     342             : 
     343             : 
     344             :   // => tetrahedra:
     345           4 :   const unsigned int num_elements = tetgen_wrapper.get_numberoftetrahedra();
     346             : 
     347             :   // Vector that temporarily holds the node labels defining element connectivity.
     348             :   unsigned int node_labels[4];
     349             : 
     350        9980 :   for (unsigned int i=0; i<num_elements; i++)
     351             :     {
     352             :       // TetGen only supports Tet4 elements.
     353        9976 :       auto elem = Elem::build(TET4);
     354             : 
     355             :       // Fill up the the node_labels vector
     356       49880 :       for (auto j : elem->node_index_range())
     357       39904 :         node_labels[j] = tetgen_wrapper.get_element_node(i,j);
     358             : 
     359             :       // Associate nodes with this element
     360        9976 :       this->assign_nodes_to_elem(node_labels, elem.get());
     361             : 
     362             :       // Finally, add this element to the mesh
     363       19952 :       this->_mesh.add_elem(std::move(elem));
     364             :     }
     365             : 
     366             :   // Delete original convex hull elements.  Is there ever a case where
     367             :   // we should not do this?
     368           4 :   this->delete_2D_hull_elements();
     369             : 
     370             :   // To the naked eye, a few smoothing iterations usually looks better.
     371             :   // We don't do this by default.
     372           4 :   if (this->_smooth_after_generating)
     373           0 :     LaplaceMeshSmoother(_mesh).smooth(2);
     374           4 : }
     375             : 
     376             : 
     377             : 
     378             : 
     379             : 
     380        1092 : void TetGenMeshInterface::fill_pointlist(TetGenWrapper & wrapper)
     381             : {
     382             :   // fill input structure with point set data:
     383        1092 :   wrapper.allocate_pointlist( this->_mesh.n_nodes() );
     384             : 
     385             :   // Make enough space to store a mapping between the implied sequential
     386             :   // node numbering used in tetgen and libmesh's (possibly) non-sequential
     387             :   // numbering scheme.
     388        1092 :   _sequential_to_libmesh_node_map.clear();
     389        1092 :   _sequential_to_libmesh_node_map.resize( this->_mesh.n_nodes() );
     390             : 
     391             :   {
     392         546 :     unsigned index = 0;
     393        8014 :     for (auto & node : this->_mesh.node_ptr_range())
     394             :       {
     395        6376 :         _sequential_to_libmesh_node_map[index] = node->id();
     396       12752 :         wrapper.set_node(index++,
     397        3188 :                          REAL((*node)(0)),
     398        3188 :                          REAL((*node)(1)),
     399        3188 :                          REAL((*node)(2)));
     400             :       }
     401             :   }
     402        1092 : }
     403             : 
     404             : 
     405             : 
     406             : 
     407             : 
     408       13546 : void TetGenMeshInterface::assign_nodes_to_elem(unsigned * node_labels, Elem * elem)
     409             : {
     410       67634 :   for (auto j : elem->node_index_range())
     411             :     {
     412             :       // Get the mapped node index to ask the Mesh for
     413       54088 :       unsigned mapped_node_id = _sequential_to_libmesh_node_map[ node_labels[j] ];
     414             : 
     415       54088 :       Node * current_node = this->_mesh.node_ptr( mapped_node_id );
     416             : 
     417       54088 :       elem->set_node(j, current_node);
     418             :     }
     419       13546 : }
     420             : 
     421             : } // namespace libMesh
     422             : 
     423             : 
     424             : #endif // #ifdef LIBMESH_HAVE_TETGEN

Generated by: LCOV version 1.14