libMesh
mesh_tet_interface.C
Go to the documentation of this file.
1 // The libMesh Finite Element Library.
2 // Copyright (C) 2002-2023 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 
20 
21 // C++ includes
22 #include <sstream>
23 
24 // Local includes
25 #include "libmesh/mesh_tet_interface.h"
26 
27 #include "libmesh/boundary_info.h"
28 #include "libmesh/elem.h"
29 #include "libmesh/mesh_modification.h"
30 #include "libmesh/mesh_serializer.h"
31 #include "libmesh/mesh_tools.h"
32 #include "libmesh/remote_elem.h"
33 #include "libmesh/unstructured_mesh.h"
34 
35 namespace {
36  using namespace libMesh;
37 
38  std::unordered_set<Elem *>
39  flood_component (std::unordered_set<Elem *> & all_components,
40  Elem * elem)
41  {
42  libmesh_assert(!all_components.count(elem));
43 
44  std::unordered_set<Elem *> current_component;
45 
46  std::unordered_set<Elem *> elements_to_consider = {elem};
47 
48  while (!elements_to_consider.empty())
49  {
50  std::unordered_set<Elem *> next_elements_to_consider;
51 
52  for (Elem * considering : elements_to_consider)
53  {
54  all_components.insert(considering);
55  current_component.insert(considering);
56 
57  for (auto s : make_range(considering->n_sides()))
58  {
59  Elem * neigh = considering->neighbor_ptr(s);
60 
61  libmesh_error_msg_if
62  (!neigh,
63  "Tet generation encountered a 2D element with a null neighbor, but a\n"
64  "boundary must be a 2D closed manifold (surface).\n");
65 
66  if (all_components.find(neigh) == all_components.end())
67  next_elements_to_consider.insert(neigh);
68  }
69  }
70  elements_to_consider = next_elements_to_consider;
71  }
72 
73  return current_component;
74  }
75 
76  // Returns six times the signed volume of a tet formed by the given
77  // 3 points and the origin
78  Real six_times_signed_tet_volume (const Point & p1,
79  const Point & p2,
80  const Point & p3)
81  {
82  return p1(0)*p2(1)*p3(2)
83  - p1(0)*p3(1)*p2(2)
84  - p2(0)*p1(1)*p3(2)
85  + p2(0)*p3(1)*p1(2)
86  + p3(0)*p1(1)*p2(2)
87  - p3(0)*p2(1)*p1(2);
88  }
89 
90  // Returns six times the signed volume of the space defined by the
91  // manifold of surface elements in component
92  Real six_times_signed_volume (const std::unordered_set<Elem *> component)
93  {
94  Real six_vol = 0;
95 
96  for (const Elem * elem: component)
97  {
98  libmesh_assert_equal_to(elem->dim(), 2);
99  for (auto n : make_range(elem->n_vertices()-2))
100  six_vol += six_times_signed_tet_volume(elem->point(0),
101  elem->point(n+1),
102  elem->point(n+2));
103  }
104 
105  return six_vol;
106  }
107 }
108 
109 namespace libMesh
110 {
111 
112 //----------------------------------------------------------------------
113 // MeshTetInterface class members
115  _desired_volume(0), _smooth_after_generating(false),
116  _elem_type(TET4), _mesh(mesh)
117 {
118 }
119 
120 
122 
123 
125  (std::unique_ptr<std::vector<std::unique_ptr<UnstructuredMesh>>> holes)
126 {
127  _holes = std::move(holes);
128 }
129 
130 
132 {
133  // If we've been handed an unprepared mesh then we need to be made
134  // aware of that and fix that; we're relying on neighbor pointers.
136 
137  if (!mesh.is_prepared())
139 
140  // We'll return a bounding box for use by subclasses in basic sanity checks.
141  BoundingBox surface_bb;
142 
143  // First convert all volume boundaries to surface elements; this
144  // gives us a manifold bounding the mesh, though it may not be a
145  // connected manifold even if the volume mesh was connected.
146  {
147  // Make sure ids are in sync and valid on a DistributedMesh
148  const dof_id_type max_orig_id = mesh.max_elem_id();
149 #ifdef LIBMESH_ENABLE_UNIQUE_ID
150  const unique_id_type max_unique_id = mesh.parallel_max_unique_id();
151 #endif
152 
153  // Change this if we add arbitrary polyhedra...
154  const dof_id_type max_sides = 6;
155 
156  std::unordered_set<Elem *> elems_to_delete;
157 
158  std::vector<std::unique_ptr<Elem>> elems_to_add;
159 
160  // Convert all faces to surface elements
161  for (auto * elem : mesh.active_element_ptr_range())
162  {
163  libmesh_error_msg_if (elem->dim() < 2,
164  "Cannot use meshes with 0D or 1D elements to define a volume");
165 
166  // If we've already got 2D elements then those are (part of)
167  // our surface.
168  if (elem->dim() == 2)
169  continue;
170 
171  // 3D elements will be removed after we've extracted their
172  // surface faces.
173  elems_to_delete.insert(elem);
174 
175  for (auto s : make_range(elem->n_sides()))
176  {
177  // If there's a neighbor on this side then there's not a
178  // boundary
179  if (elem->neighbor_ptr(s))
180  {
181  // We're not supporting AMR meshes here yet
182  if (elem->level() != elem->neighbor_ptr(s)->level())
183  libmesh_not_implemented_msg
184  ("Tetrahedralizaton of adapted meshes is not currently supported");
185  continue;
186  }
187 
188  elems_to_add.push_back(elem->build_side_ptr(s));
189  Elem * side_elem = elems_to_add.back().get();
190 
191  // Wipe the interior_parent before it can become a
192  // dangling pointer later
193  side_elem->set_interior_parent(nullptr);
194 
195  // If the mesh is replicated then its automatic id
196  // setting is fine. If not, then we need unambiguous ids
197  // independent of element traversal.
198  if (!mesh.is_replicated())
199  {
200  side_elem->set_id(max_orig_id + max_sides*elem->id() + s);
201 #ifdef LIBMESH_ENABLE_UNIQUE_ID
202  side_elem->set_unique_id(max_unique_id + max_sides*elem->id() + s);
203 #endif
204  }
205  }
206  }
207 
208  // If the mesh is replicated then its automatic neighbor finding
209  // is fine. If not, then we need to insert them ourselves, but
210  // it's easy because we can use the fact (from our implementation
211  // above) that our new elements have no parents or children, plus
212  // the fact (from the tiny fraction of homology I understand) that
213  // a manifold boundary is a manifold with no boundary.
214  //
215  // See UnstructuredMesh::find_neighbors() for more explanation of
216  // (a more complicated version of) the algorithm here.
217  if (!mesh.is_replicated())
218  {
219  typedef dof_id_type key_type;
220  typedef std::pair<Elem *, unsigned char> val_type;
221  typedef std::unordered_multimap<key_type, val_type> map_type;
222  map_type side_to_elem_map;
223 
224  std::unique_ptr<Elem> my_side, their_side;
225 
226  for (auto & elem : elems_to_add)
227  {
228  for (auto s : elem->side_index_range())
229  {
230  if (elem->neighbor_ptr(s))
231  continue;
232  const dof_id_type key = elem->low_order_key(s);
233  auto bounds = side_to_elem_map.equal_range(key);
234  if (bounds.first != bounds.second)
235  {
236  elem->side_ptr(my_side, s);
237  while (bounds.first != bounds.second)
238  {
239  Elem * potential_neighbor = bounds.first->second.first;
240  const unsigned int ns = bounds.first->second.second;
241  potential_neighbor->side_ptr(their_side, ns);
242  if (*my_side == *their_side)
243  {
244  elem->set_neighbor(s, potential_neighbor);
245  potential_neighbor->set_neighbor(ns, elem.get());
246  side_to_elem_map.erase (bounds.first);
247  break;
248  }
249  ++bounds.first;
250  }
251 
252  if (!elem->neighbor_ptr(s))
253  side_to_elem_map.emplace
254  (key, std::make_pair(elem.get(), cast_int<unsigned char>(s)));
255  }
256  }
257  }
258 
259  // At this point we *should* have a match for everything, so
260  // anything we don't have a match for is remote.
261  for (auto & elem : elems_to_add)
262  for (auto s : elem->side_index_range())
263  if (!elem->neighbor_ptr(s))
264  elem->set_neighbor(s, const_cast<RemoteElem*>(remote_elem));
265  }
266 
267  // Remove volume and edge elements
268  for (Elem * elem : elems_to_delete)
269  mesh.delete_elem(elem);
270 
271  // Add the new elements outside the loop so we don't risk
272  // invalidating iterators.
273  for (auto & elem : elems_to_add)
274  mesh.add_elem(std::move(elem));
275  }
276 
277  // Fix up neighbor pointers, element counts, etc.
279 
280  // We're making tets; we need to start with tris
282 
283  // Partition surface into connected components. At this point I'm
284  // finally going to give up and serialize, because at least we got
285  // from 3D down to 2D first, and because I don't want to have to
286  // turn flood_component into a while loop with a parallel sync in
287  // the middle, and because we do have to serialize *eventually*
288  // anyways unless we get a parallel tetrahedralizer backend someday.
289  MeshSerializer mesh_serializer(mesh);
290 
291  std::vector<std::unordered_set<Elem *>> components;
292  std::unordered_set<Elem *> in_component;
293 
294  for (auto * elem : mesh.element_ptr_range())
295  if (!in_component.count(elem))
296  components.emplace_back(flood_component(in_component, elem));
297 
298  const std::unordered_set<Elem *> * biggest_component = nullptr;
299  Real biggest_six_vol = 0;
300  for (const auto & component : components)
301  {
302  Real six_vol = six_times_signed_volume(component);
303  if (std::abs(six_vol) > std::abs(biggest_six_vol))
304  {
305  biggest_six_vol = six_vol;
306  biggest_component = &component;
307  }
308  }
309 
310  if (!biggest_component)
311  libmesh_error_msg("No non-zero-volume component found among " <<
312  components.size() << " boundary components");
313 
314  for (const auto & component : components)
315  if (&component != biggest_component)
316  {
317  for (Elem * elem: component)
318  mesh.delete_elem(elem);
319  }
320  else
321  {
322  for (Elem * elem: component)
323  {
324  if (biggest_six_vol < 0)
325  elem->flip(&mesh.get_boundary_info());
326 
327  for (auto & node : elem->node_ref_range())
328  surface_bb.union_with(node);
329  }
330  }
331 
333 
334  return surface_bb;
335 }
336 
337 
339 {
340  // Check for easy return: if the Mesh is empty (i.e. if
341  // somebody called triangulate_conformingDelaunayMesh on
342  // a Mesh with no elements, then hull integrity check must
343  // fail...
344  if (_mesh.n_elem() == 0)
345  return 3;
346 
347  for (auto & elem : this->_mesh.element_ptr_range())
348  {
349  // Check for proper element type
350  if (elem->type() != TRI3)
351  {
352  //libmesh_error_msg("ERROR: Some of the elements in the original mesh were not TRI3!");
353  return 1;
354  }
355 
356  for (auto neigh : elem->neighbor_ptr_range())
357  {
358  if (neigh == nullptr)
359  {
360  // libmesh_error_msg("ERROR: Non-convex hull, cannot be tetrahedralized.");
361  return 2;
362  }
363  }
364  }
365 
366  // If we made it here, return success!
367  return 0;
368 }
369 
370 
371 
373 {
374  if (result != 0)
375  {
376  libMesh::err << "Error! Conforming Delaunay mesh tetrahedralization requires a convex hull." << std::endl;
377 
378  if (result==1)
379  {
380  libMesh::err << "Non-TRI3 elements were found in the input Mesh. ";
381  libMesh::err << "A constrained Delaunay triangulation requires a convex hull of TRI3 elements." << std::endl;
382  }
383 
384  libmesh_error_msg("Consider calling TetGenMeshInterface::pointset_convexhull() followed by Mesh::find_neighbors() first.");
385  }
386 }
387 
388 
389 
391 {
392  for (auto & elem : this->_mesh.element_ptr_range())
393  {
394  // Check for proper element type. Yes, we legally delete elements while
395  // iterating over them because no entries from the underlying container
396  // are actually erased.
397  if (elem->type() == TRI3)
398  _mesh.delete_elem(elem);
399  }
400 
401  // We just removed any boundary info associated with hull element
402  // edges, so let's update the boundary id caches.
404 }
405 
406 
407 
408 } // namespace libMesh
OStreamProxy err
unique_id_type & set_unique_id()
Definition: dof_object.h:858
bool is_prepared() const
Definition: mesh_base.h:198
virtual unique_id_type parallel_max_unique_id() const =0
unsigned check_hull_integrity()
This function checks the integrity of the current set of elements in the Mesh to see if they comprise...
IntRange< unsigned short > side_index_range() const
Definition: elem.h:2710
virtual std::unique_ptr< Elem > build_side_ptr(const unsigned int i)=0
bool valid_is_prepared(const MeshBase &mesh)
A function for testing whether a mesh&#39;s cached is_prepared() setting is not a false positive...
Definition: mesh_tools.C:1182
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly ecreated (or read) mesh for use.
Definition: mesh_base.C:759
virtual dof_id_type low_order_key(const unsigned int s) const =0
This is the base class from which all geometric element types are derived.
Definition: elem.h:94
MeshBase & mesh
The libMesh namespace provides an interface to certain functionality in the library.
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition: mesh_base.h:165
MeshTetInterface(UnstructuredMesh &mesh)
Constructor.
void set_interior_parent(Elem *p)
Sets the pointer to the element&#39;s interior_parent.
Definition: elem.C:1248
dof_id_type & set_id()
Definition: dof_object.h:836
void all_tri(MeshBase &mesh)
Subdivides any non-simplex elements in a Mesh to produce simplex (triangular in 2D, tetrahedral in 3D) elements.
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
dof_id_type id() const
Definition: dof_object.h:828
The UnstructuredMesh class is derived from the MeshBase class.
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
virtual ~MeshTetInterface()
Default destructor in base class.
virtual dof_id_type max_elem_id() const =0
libmesh_assert(ctx)
static BoundingBox volume_to_surface_mesh(UnstructuredMesh &mesh)
Remove volume elements from the given mesh, after converting their outer boundary faces to surface el...
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition: elem.h:2618
void regenerate_id_sets()
Clears and regenerates the cached sets of ids.
void attach_hole_list(std::unique_ptr< std::vector< std::unique_ptr< UnstructuredMesh >>> holes)
Attaches a vector of Mesh pointers defining holes which will be meshed around.
virtual void flip(BoundaryInfo *boundary_info)=0
Flips the element (by swapping node and neighbor pointers) to have a mapping Jacobian of opposite sig...
SimpleRange< NodeRefIter > node_ref_range()
Returns a range with all nodes of an element, usable in range-based for loops.
Definition: elem.h:2665
Defines a Cartesian bounding box by the two corner extremum.
Definition: bounding_box.h:40
virtual unsigned int n_sides() const =0
const Elem * neighbor_ptr(unsigned int i) const
Definition: elem.h:2598
unsigned int level() const
Definition: elem.h:3074
virtual unsigned int n_vertices() const =0
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
virtual std::unique_ptr< Elem > side_ptr(unsigned int i)=0
virtual unsigned short dim() const =0
Temporarily serialize a DistributedMesh for non-distributed-mesh capable code paths.
void process_hull_integrity_result(unsigned result)
This function prints an informative message and crashes based on the output of the check_hull_integri...
virtual bool is_replicated() const
Definition: mesh_base.h:233
IntRange< T > make_range(T beg, T end)
The 2-parameter make_range() helper function returns an IntRange<T> when both input parameters are of...
Definition: int_range.h:140
SimpleRange< NeighborPtrIter > neighbor_ptr_range()
Returns a range with all neighbors of an element, usable in range-based for loops.
Definition: elem.h:3503
void union_with(const Point &p)
Enlarges this bounding box to include the given point.
Definition: bounding_box.h:286
virtual dof_id_type n_elem() const =0
UnstructuredMesh & _mesh
Local reference to the mesh we are working with.
virtual ElemType type() const =0
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition: point.h:39
const Point & point(const unsigned int i) const
Definition: elem.h:2453
uint8_t unique_id_type
Definition: id_types.h:86
void delete_2D_hull_elements()
Delete original convex hull elements from the Mesh after performing a Delaunay tetrahedralization.
uint8_t dof_id_type
Definition: id_types.h:67
const RemoteElem * remote_elem
Definition: remote_elem.C:57