Line data Source code
1 : // The libMesh Finite Element Library.
2 : // Copyright (C) 2002-2026 Benjamin S. Kirk, John W. Peterson, Roy H. Stogner
3 :
4 : // This library is free software; you can redistribute it and/or
5 : // modify it under the terms of the GNU Lesser General Public
6 : // License as published by the Free Software Foundation; either
7 : // version 2.1 of the License, or (at your option) any later version.
8 :
9 : // This library is distributed in the hope that it will be useful,
10 : // but WITHOUT ANY WARRANTY; without even the implied warranty of
11 : // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
12 : // Lesser General Public License for more details.
13 :
14 : // You should have received a copy of the GNU Lesser General Public
15 : // License along with this library; if not, write to the Free Software
16 : // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
17 :
18 : // Local includes
19 : #include "libmesh/cell_c0polyhedron.h"
20 :
21 : #include "libmesh/enum_io_package.h"
22 : #include "libmesh/enum_order.h"
23 : #include "libmesh/face_polygon.h"
24 : #include "libmesh/mesh_tools.h"
25 : #include "libmesh/replicated_mesh.h"
26 : #include "libmesh/tensor_value.h"
27 : #include "libmesh/node.h"
28 :
29 : // C++ headers
30 : #include <numeric> // std::iota
31 :
32 : namespace libMesh
33 : {
34 :
35 : // ------------------------------------------------------------
36 : // C0Polyhedron class member functions
37 :
38 910 : C0Polyhedron::C0Polyhedron
39 910 : (const std::vector<std::shared_ptr<Polygon>> & sides, std::unique_ptr<Node> & mid_elem_node, Elem * p) :
40 : Polyhedron(sides, p),
41 910 : _has_mid_elem_node(false)
42 : {
43 910 : this->retriangulate();
44 :
45 : // Grab the last node
46 910 : if (_has_mid_elem_node)
47 224 : mid_elem_node.reset(this->_nodelinks_data.back());
48 910 : }
49 :
50 :
51 :
52 780 : C0Polyhedron::~C0Polyhedron() = default;
53 :
54 :
55 :
56 7 : std::unique_ptr<Elem> C0Polyhedron::disconnected_clone() const
57 : {
58 11 : auto sides = this->side_clones();
59 :
60 7 : std::unique_ptr<Node> mid_elem_node;
61 7 : std::unique_ptr<Elem> returnval = std::make_unique<C0Polyhedron>(sides, mid_elem_node);
62 : // Swap out the new mid elem node with the previous one
63 7 : if (mid_elem_node)
64 : {
65 0 : libmesh_assert(returnval->n_nodes() == this->n_nodes());
66 0 : returnval->set_node(returnval->n_nodes() - 1, this->_nodelinks_data.back());
67 : }
68 :
69 7 : returnval->set_id() = this->id();
70 : #ifdef LIBMESH_ENABLE_UNIQUE_ID
71 7 : if (this->valid_unique_id())
72 2 : returnval->set_unique_id(this->unique_id());
73 : #endif
74 :
75 7 : const auto n_elem_ints = this->n_extra_integers();
76 7 : returnval->add_extra_integers(n_elem_ints);
77 7 : for (unsigned int i = 0; i != n_elem_ints; ++i)
78 0 : returnval->set_extra_integer(i, this->get_extra_integer(i));
79 :
80 5 : returnval->inherit_data_from(*this);
81 :
82 9 : return returnval;
83 3 : }
84 :
85 :
86 :
87 : unsigned int
88 4248346 : C0Polyhedron::n_vertices() const
89 : {
90 5310376 : return this->_nodelinks_data.size() - _has_mid_elem_node;
91 : }
92 :
93 :
94 :
95 7886 : bool C0Polyhedron::is_vertex(const unsigned int i) const
96 : {
97 2252 : libmesh_assert_less (i, this->n_nodes());
98 :
99 7886 : if (i < this->n_vertices())
100 2144 : return true;
101 : else
102 378 : return false;
103 : }
104 :
105 :
106 :
107 420 : bool C0Polyhedron::is_edge(const unsigned int libmesh_dbg_var(i)) const
108 : {
109 120 : libmesh_assert_less (i, this->n_nodes());
110 :
111 420 : return false;
112 : }
113 :
114 :
115 :
116 420 : bool C0Polyhedron::is_face(const unsigned int libmesh_dbg_var(i)) const
117 : {
118 120 : libmesh_assert_less (i, this->n_nodes());
119 :
120 420 : return false;
121 : }
122 :
123 :
124 :
125 708 : bool C0Polyhedron::is_node_on_side(const unsigned int n,
126 : const unsigned int s) const
127 : {
128 192 : libmesh_assert_less (n, this->n_nodes());
129 192 : libmesh_assert_less (s, this->n_sides());
130 :
131 : const std::vector<unsigned int> & node_map =
132 708 : std::get<2>(_sidelinks_data[s]);
133 :
134 708 : const auto it = std::find(node_map.begin(), node_map.end(), n);
135 :
136 900 : return (it != node_map.end());
137 : }
138 :
139 : std::vector<unsigned int>
140 2470 : C0Polyhedron::nodes_on_side(const unsigned int s) const
141 : {
142 686 : libmesh_assert_less(s, this->n_sides());
143 3148 : return std::get<2>(_sidelinks_data[s]);
144 : }
145 :
146 : std::vector<unsigned int>
147 228 : C0Polyhedron::nodes_on_edge(const unsigned int e) const
148 : {
149 228 : auto [s, se] = _edge_lookup[e];
150 228 : const Polygon & face = *std::get<0>(_sidelinks_data[s]);
151 : const std::vector<unsigned int> & node_map =
152 57 : std::get<2>(_sidelinks_data[s]);
153 : std::vector<unsigned int> nodes_on_edge =
154 228 : face.nodes_on_side(se);
155 684 : for (auto i : index_range(nodes_on_edge))
156 570 : nodes_on_edge[i] = node_map[nodes_on_edge[i]];
157 285 : return nodes_on_edge;
158 : }
159 :
160 :
161 :
162 96 : bool C0Polyhedron::is_node_on_edge(const unsigned int n,
163 : const unsigned int e) const
164 : {
165 24 : libmesh_assert_less(e, _edge_lookup.size());
166 96 : auto [s, se] = _edge_lookup[e];
167 :
168 96 : const Polygon & face = *std::get<0>(_sidelinks_data[s]);
169 : const std::vector<unsigned int> & node_map =
170 24 : std::get<2>(_sidelinks_data[s]);
171 : std::vector<unsigned int> nodes_on_edge =
172 120 : face.nodes_on_side(se);
173 144 : for (auto noe : nodes_on_edge)
174 180 : if (node_map[noe] == n)
175 24 : return true;
176 :
177 0 : return false;
178 : }
179 :
180 :
181 :
182 : std::vector<unsigned int>
183 288 : C0Polyhedron::edges_adjacent_to_node(const unsigned int n) const
184 : {
185 288 : if (n == n_nodes() - 1)
186 27 : return {};
187 252 : return Polyhedron::edges_adjacent_to_node(n);
188 : }
189 :
190 :
191 :
192 0 : void C0Polyhedron::connectivity(const unsigned int sf,
193 : const IOPackage iop,
194 : std::vector<dof_id_type> & conn) const
195 : {
196 0 : libmesh_assert_less (sf, this->n_sub_elem());
197 0 : libmesh_assert_not_equal_to (iop, INVALID_IO_PACKAGE);
198 :
199 0 : const auto & subtet = this->_triangulation[sf];
200 :
201 0 : switch (iop)
202 : {
203 0 : case TECPLOT:
204 : {
205 0 : conn.resize(8);
206 0 : conn[0] = this->node_id(subtet[0])+1;
207 0 : conn[1] = this->node_id(subtet[1])+1;
208 0 : conn[2] = this->node_id(subtet[2])+1;
209 0 : conn[3] = this->node_id(subtet[2])+1;
210 0 : conn[4] = this->node_id(subtet[3])+1;
211 0 : conn[5] = this->node_id(subtet[3])+1;
212 0 : conn[6] = this->node_id(subtet[3])+1;
213 0 : conn[7] = this->node_id(subtet[3])+1;
214 0 : return;
215 : }
216 :
217 0 : case VTK:
218 : {
219 0 : conn.resize(4);
220 0 : conn[0] = this->node_id(subtet[0]);
221 0 : conn[1] = this->node_id(subtet[1]);
222 0 : conn[2] = this->node_id(subtet[2]);
223 0 : conn[3] = this->node_id(subtet[3]);
224 0 : return;
225 : }
226 :
227 0 : default:
228 0 : libmesh_error_msg("Unsupported IO package " << iop);
229 : }
230 : }
231 :
232 :
233 :
234 336 : Real C0Polyhedron::volume () const
235 : {
236 : // This specialization is ... probably good for general non-Lagrange
237 : // mappings, since we require our polyhedral faces to be planar, but
238 : // I'd rather be slow than wrong.
239 336 : if (this->mapping_type() != LAGRANGE_MAP)
240 0 : return this->Elem::volume();
241 :
242 : // We use a triangulation to calculate here.
243 :
244 89 : Real six_vol = 0;
245 2226 : for (const auto & subtet : this->_triangulation)
246 : {
247 1890 : const Point p0 = this->point(subtet[0]);
248 1890 : const Point p1 = this->point(subtet[1]);
249 1890 : const Point p2 = this->point(subtet[2]);
250 2395 : const Point p3 = this->point(subtet[3]);
251 :
252 505 : const Point v01 = p1 - p0;
253 505 : const Point v02 = p2 - p0;
254 505 : const Point v03 = p3 - p0;
255 :
256 1890 : six_vol += triple_product(v01, v02, v03);
257 : }
258 :
259 336 : return six_vol * (1./6.);
260 : }
261 :
262 :
263 :
264 8 : Point C0Polyhedron::true_centroid () const
265 : {
266 : // This specialization is good for Lagrange mappings only
267 8 : if (this->mapping_type() != LAGRANGE_MAP)
268 0 : return this->Elem::true_centroid();
269 :
270 2 : Real six_vol = 0;
271 2 : Point six_vol_weighted_centroid;
272 48 : for (const auto & subtet : this->_triangulation)
273 : {
274 40 : const Point p0 = this->point(subtet[0]);
275 40 : const Point p1 = this->point(subtet[1]);
276 40 : const Point p2 = this->point(subtet[2]);
277 50 : const Point p3 = this->point(subtet[3]);
278 :
279 10 : const Point v01 = p1 - p0;
280 10 : const Point v02 = p2 - p0;
281 10 : const Point v03 = p3 - p0;
282 :
283 30 : const Real six_tet_vol = triple_product(v01, v02, v03);
284 :
285 10 : const Point tet_centroid = (p0 + p1 + p2 + p3) * 0.25;
286 :
287 40 : six_vol += six_tet_vol;
288 :
289 10 : six_vol_weighted_centroid += six_tet_vol * tet_centroid;
290 : }
291 :
292 2 : return six_vol_weighted_centroid / six_vol;
293 : }
294 :
295 :
296 :
297 : std::pair<unsigned short int, unsigned short int>
298 0 : C0Polyhedron::second_order_child_vertex (const unsigned int /*n*/) const
299 : {
300 0 : libmesh_not_implemented();
301 : return std::pair<unsigned short int, unsigned short int> (0,0);
302 : }
303 :
304 :
305 :
306 72 : ElemType C0Polyhedron::side_type (const unsigned int libmesh_dbg_var(s)) const
307 : {
308 18 : libmesh_assert_less (s, this->n_sides());
309 72 : return C0POLYGON;
310 : }
311 :
312 :
313 :
314 : Point
315 84 : C0Polyhedron::side_vertex_average_normal(const unsigned int s) const
316 : {
317 24 : libmesh_assert_less (s, this->n_sides());
318 24 : libmesh_assert_equal_to(this->mapping_type(), LAGRANGE_MAP);
319 84 : const auto poly_side_ptr = this->side_ptr(s);
320 84 : const auto n_side_edges = poly_side_ptr->n_sides();
321 :
322 : // At the side vertex average, things simplify a bit
323 : // We get the side "plane" normal at all vertices, then average them
324 24 : Point normal;
325 48 : Point current_edge = poly_side_ptr->point(1) - poly_side_ptr->point(0);
326 84 : for (auto i : make_range(n_side_edges))
327 : {
328 84 : const Point next_edge = poly_side_ptr->point((i + 2) % n_side_edges) -
329 108 : poly_side_ptr->point((i + 1) % n_side_edges);
330 60 : const Point normal_at_vertex = current_edge.cross(next_edge);
331 24 : normal += normal_at_vertex;
332 : // Note: the sides are planar, we don't need to test them all
333 84 : if (normal.norm_sq() > TOLERANCE)
334 24 : break;
335 0 : current_edge = next_edge;
336 : }
337 84 : bool outward_normal = std::get<1>(_sidelinks_data[s]);
338 172 : return (outward_normal ? 1. : -1.) * normal.unit();
339 36 : }
340 :
341 :
342 : std::array<int, 4>
343 189 : C0Polyhedron::subelement_sides_to_poly_sides(unsigned int tri_i) const
344 : {
345 54 : std::array<int, 4> sides = {invalid_int, invalid_int, invalid_int, invalid_int};
346 54 : libmesh_assert(tri_i < this->n_subelements());
347 189 : const auto & tet_nodes = _triangulation[tri_i];
348 1617 : for (const auto poly_side : make_range(n_sides()))
349 : {
350 1836 : const auto sd_nodes = nodes_on_side(poly_side);
351 1836 : bool zero_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[0]) != sd_nodes.end();
352 1836 : bool one_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[1]) != sd_nodes.end();
353 1836 : bool two_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[2]) != sd_nodes.end();
354 1836 : bool three_in = std::find(sd_nodes.begin(), sd_nodes.end(), tet_nodes[3]) != sd_nodes.end();
355 :
356 : // Take advantage of the fact that the poly side can only contain one tet side
357 : // Note: we could optimize by replacing the booleans with the searches above
358 1428 : if (zero_in)
359 : {
360 567 : if (one_in)
361 : {
362 245 : if (two_in)
363 147 : sides[0] = poly_side;
364 98 : else if (three_in)
365 35 : sides[1] = poly_side;
366 : }
367 322 : else if (two_in && three_in)
368 35 : sides[3] = poly_side;
369 : }
370 861 : else if (three_in && two_in && one_in)
371 35 : sides[2] = poly_side;
372 : }
373 189 : return sides;
374 : }
375 :
376 :
377 910 : void C0Polyhedron::retriangulate()
378 : {
379 260 : this->_triangulation.clear();
380 :
381 : // We should already have a triangulation for each side. We'll turn
382 : // that into a triangulation of the entire polyhedral surface
383 : // (stored as a mesh, so we can use `find_neighbors()`, then turn
384 : // that into a tetrahedralization by shrinking the volume one tet at
385 : // a time, which should work fine for convex polyhedra.
386 :
387 520 : Parallel::Communicator comm_self; // the default communicator is 1 rank
388 1430 : ReplicatedMesh surface(comm_self);
389 :
390 : // poly_node_to_local_id[poly_node] is the local id of
391 : // poly_node in the Polyhedron, which is also the global id of the
392 : // corresponding node in the surface mesh.
393 520 : std::unordered_map<Node *, unsigned int> poly_node_to_id;
394 :
395 6440 : for (unsigned int s : make_range(this->n_sides()))
396 : {
397 5530 : const auto & [side, inward_normal, node_map] = this->_sidelinks_data[s];
398 :
399 16730 : for (auto t : make_range(side->n_subtriangles()))
400 : {
401 14400 : Elem * tri = surface.add_elem(Elem::build(TRI3));
402 :
403 11200 : const std::array<int, 3> subtri = side->subtriangle(t);
404 :
405 44800 : for (int i : make_range(3))
406 : {
407 33600 : const int side_id = subtri[i];
408 33600 : const Node * poly_node = side->node_ptr(side_id);
409 :
410 9600 : libmesh_assert_less(side_id, node_map.size());
411 33600 : const unsigned int local_id = node_map[side_id];
412 :
413 33600 : Node * surf_node = surface.query_node_ptr(local_id);
414 33600 : if (surf_node)
415 7480 : libmesh_assert_equal_to(*(const Point*)poly_node,
416 : *(const Point*)surf_node);
417 : else
418 7420 : surf_node = surface.add_point(*poly_node, local_id);
419 :
420 : // Get a consistent orientation for surface triangles,
421 : // facing their zeta directions outward
422 33600 : const int tri_node = inward_normal ? i : 2-i;
423 33600 : tri->set_node(tri_node, surf_node);
424 : }
425 : }
426 : }
427 :
428 260 : surface.allow_renumbering(false);
429 910 : surface.prepare_for_use();
430 :
431 : // We should have a watertight surface with consistent triangle
432 : // orientations. That's expensive to check.
433 : #ifdef DEBUG
434 34580 : auto verify_surface = [& surface] ()
435 : {
436 12420 : for (const Elem * elem : surface.element_ptr_range())
437 : {
438 44320 : for (auto s : make_range(3))
439 : {
440 33240 : const Elem * neigh = elem->neighbor_ptr(s);
441 33240 : libmesh_assert(neigh);
442 33240 : libmesh_assert_equal_to(neigh,
443 : surface.elem_ptr(neigh->id()));
444 33240 : const unsigned int ns = neigh->which_neighbor_am_i(elem);
445 33240 : libmesh_assert_less(ns, 3);
446 33240 : libmesh_assert_equal_to(elem->node_ptr(s),
447 : neigh->node_ptr((ns+1)%3));
448 33240 : libmesh_assert_equal_to(elem->node_ptr((s+1)%3),
449 : neigh->node_ptr(ns));
450 : }
451 : }
452 1340 : };
453 :
454 260 : verify_surface();
455 : #endif
456 :
457 : // First heuristic: try with no interior point
458 : // This might not succeed, not every surface triangulation gives a tetrahedralization
459 : // with no additional interior point
460 : // But if it succeeds, it uses less tetrahedra to cut the polyhedron
461 : #ifdef LIBMESH_ENABLE_EXCEPTIONS
462 : try
463 : {
464 : // We'll have to edit this as we change the surface elements, but we
465 : // have a method to initialize it easily.
466 780 : std::vector<std::vector<dof_id_type>> nodes_to_elem_vec_map;
467 910 : MeshTools::build_nodes_to_elem_map(surface, nodes_to_elem_vec_map);
468 :
469 : // We'll be inserting and deleting entries heavily, so we'll use
470 : // sets rather than vectors. We want to get the same results in
471 : // parallel, so we'll use element ids rather than Elem pointers
472 716 : std::vector<std::set<dof_id_type>> nodes_to_elem_map;
473 8330 : for (auto i : index_range(nodes_to_elem_vec_map))
474 : nodes_to_elem_map.emplace_back
475 12880 : (nodes_to_elem_vec_map[i].begin(),
476 13844 : nodes_to_elem_vec_map[i].end());
477 :
478 : // Now start meshing the volume enclosed by surface, one tet at a
479 : // time, with a greedy heuristic: find the vertex node with the most
480 : // acutely convex (solid) angle and strip it out as tetrahedra.
481 :
482 : // We'll want a vector of surrounding nodes for multiple uses,
483 : // sometimes with a similarly-sorted vector of surrounding elements
484 : // to go with it.
485 : auto surroundings_of =
486 5412 : [&nodes_to_elem_map, & surface]
487 : (const Node & node,
488 37664 : std::vector<Elem *> * surrounding_elems)
489 : {
490 : const std::set<dof_id_type> & elems_by_node =
491 9020 : nodes_to_elem_map[node.id()];
492 :
493 12628 : const unsigned int n_surrounding = elems_by_node.size();
494 3608 : libmesh_assert_greater_equal(n_surrounding, 3);
495 :
496 12628 : if (surrounding_elems)
497 : {
498 1340 : libmesh_assert(surrounding_elems->empty());
499 4690 : surrounding_elems->resize(n_surrounding);
500 : }
501 :
502 12628 : std::vector<Node *> surrounding_nodes(n_surrounding);
503 :
504 12628 : Elem * elem = surface.elem_ptr(*elems_by_node.begin());
505 62370 : for (auto i : make_range(n_surrounding))
506 : {
507 35530 : const unsigned int n = elem->get_node_index(&node);
508 14212 : libmesh_assert_not_equal_to(n, invalid_uint);
509 49742 : Node * next_node = elem->node_ptr((n+1)%3);
510 49742 : surrounding_nodes[i] = next_node;
511 49742 : if (surrounding_elems)
512 18756 : (*surrounding_elems)[i] = elem;
513 49742 : elem = elem->neighbor_ptr((n+2)%3);
514 14212 : libmesh_assert(elem);
515 14212 : libmesh_assert_equal_to(elem, surface.elem_ptr(elem->id()));
516 :
517 : // We should have a manifold here, but verifying that is
518 : // expensive
519 : #ifdef DEBUG
520 14212 : libmesh_assert_equal_to
521 : (std::count(surrounding_nodes.begin(),
522 : surrounding_nodes.end(), next_node),
523 : 1);
524 : #endif
525 : }
526 :
527 : // We should have finished a loop
528 3608 : libmesh_assert_equal_to
529 : (elem, surface.elem_ptr(*elems_by_node.begin()));
530 :
531 16236 : return surrounding_nodes;
532 910 : };
533 :
534 7938 : auto geometry_at = [&surroundings_of](const Node & node)
535 : {
536 : const std::vector<Node *> surrounding_nodes =
537 10206 : surroundings_of(node, nullptr);
538 :
539 : // Now sum up solid angles from tetrahedra created from the
540 : // trivial triangulation of the surrounding nodes loop
541 2268 : Real total_solid_angle = 0;
542 : const int n_surrounding =
543 4536 : cast_int<int>(surrounding_nodes.size());
544 :
545 27216 : for (auto n : make_range(n_surrounding-2))
546 : {
547 : const Point
548 24786 : v01 = static_cast<Point>(*surrounding_nodes[n]) - node,
549 24786 : v02 = static_cast<Point>(*surrounding_nodes[n+1]) - node,
550 24786 : v03 = static_cast<Point>(*surrounding_nodes[n+2]) - node;
551 :
552 19278 : total_solid_angle += solid_angle(v01, v02, v03);
553 : }
554 :
555 10206 : return std::make_pair(n_surrounding, total_solid_angle);
556 650 : };
557 :
558 : // We'll keep track of solid angles and node valences so we don't
559 : // waste time recomputing them when they haven't changed. We need
560 : // to be able to search efficiently for the smallest angles of each
561 : // valence, but also search efficiently for a particular node to
562 : // remove and reinsert it when its connectivity changes.
563 : //
564 : // Since C++11 multimap has guaranteed that pairs with matching keys
565 : // are kept in insertion order, so we can use Node * for values even
566 : // in parallel.
567 : typedef std::multimap<std::pair<int, Real>, Node*> node_map_type;
568 520 : node_map_type nodes_by_geometry;
569 324 : std::map<Node *, node_map_type::iterator> node_index;
570 :
571 8980 : for (auto node : surface.node_ptr_range())
572 7420 : node_index[node] =
573 15230 : nodes_by_geometry.emplace(geometry_at(*node), node);
574 :
575 : // In 3D, this will require nested loops: an outer loop to remove
576 : // each vertex, and an inner loop to remove multiple tetrahedra in
577 : // cases where the vertex has more than 3 neighboring triangles.
578 :
579 : // We'll be done when there are only three "unremoved" nodes left,
580 : // so they don't actually enclose any volume.
581 5376 : for (auto i : make_range(nodes_by_geometry.size()-3))
582 : {
583 1340 : auto geometry_it = nodes_by_geometry.begin();
584 4690 : auto geometry_key = geometry_it->first;
585 4690 : auto [valence, angle] = geometry_key;
586 4690 : Node * node = geometry_it->second;
587 1340 : libmesh_ignore(i);
588 :
589 : // If our lowest-valence nodes are all points of non-convexity,
590 : // skip to a higher valence.
591 4690 : while (angle > 2*pi-TOLERANCE)
592 : {
593 : geometry_it =
594 : nodes_by_geometry.upper_bound
595 0 : (std::make_pair(valence, Real(100)));
596 0 : libmesh_assert(geometry_it != nodes_by_geometry.end());
597 :
598 0 : std::tie(geometry_key, node) = *geometry_it;
599 0 : std::tie(valence, angle) = geometry_key;
600 : }
601 :
602 2680 : std::vector<Elem *> surrounding_elems;
603 : std::vector<Node *> surrounding_nodes =
604 6030 : surroundings_of(*node, &surrounding_elems);
605 :
606 2680 : const std::size_t n_surrounding = surrounding_nodes.size();
607 :
608 : // As we separate surrounding nodes from our center node, we'll
609 : // be marking them as nullptr; we still need to be able to find
610 : // predecessor and successor nodes in order afterward.
611 : auto find_valid_nodes_around =
612 10422 : [n_surrounding, & surrounding_nodes]
613 71793 : (unsigned int j)
614 : {
615 24318 : unsigned int jnext = (j+1)%n_surrounding;
616 32643 : while (!surrounding_nodes[jnext])
617 1071 : jnext = (jnext+1)%n_surrounding;
618 :
619 24318 : unsigned int jprev = (j+n_surrounding-1)%n_surrounding;
620 33129 : while (!surrounding_nodes[jprev])
621 1449 : jprev = (jprev+n_surrounding-1)%n_surrounding;
622 :
623 31266 : return std::make_pair(jprev, jnext);
624 4690 : };
625 :
626 : // We may have too many surrounding nodes to handle with
627 : // just one tet. In that case we'll keep a cache of the
628 : // element qualities that we'd get by making a tet with the
629 : // edge from the center node to each surrounding node, so we
630 : // can build the best tets first.
631 : //
632 : // In the case where we just have 3 nodes, we'll just pretend
633 : // they all have the same positive quality, so we can still
634 : // search this vector.
635 6190 : std::vector<Real> local_tet_quality(n_surrounding, 1);
636 :
637 : // From our center node with N surrounding nodes we can make N-2
638 : // tetrahedra. The first N-3 each replace two surface tets with
639 : // two new surface tets.
640 : //
641 : // My first idea was to greedily pick nodes with the smallest
642 : // local (solid) angles to get the best quality. This works in
643 : // 2D, but such a node can give a pancake tet in 3D.
644 : //
645 : // My second idea was to greedily pick nodes with the highest
646 : // prospective tet quality. This works for the first tets, but
647 : // can leave a pancake tet behind.
648 : //
649 : // My third idea is to try to fix the lowest quality tets first,
650 : // by picking cases where they have higher quality neighbors,
651 : // and creating those neighbors so as to change them.
652 :
653 : auto find_new_tet_nodes =
654 2232 : [& local_tet_quality, & find_valid_nodes_around]
655 46436 : ()
656 : {
657 1488 : unsigned int jbest = 0;
658 5208 : auto [jminus, jplus] = find_valid_nodes_around(jbest);
659 5208 : Real qneighbest = std::min(local_tet_quality[jminus],
660 6711 : local_tet_quality[jplus]);
661 5504 : for (auto j : make_range(std::size_t(1),
662 19932 : local_tet_quality.size()))
663 : {
664 : // We don't want to build a bad tet
665 14724 : if (local_tet_quality[j] <= 0)
666 470 : continue;
667 :
668 10794 : std::tie(jminus, jplus) = find_valid_nodes_around(j);
669 10794 : Real qneighj = std::min(local_tet_quality[jminus],
670 13878 : local_tet_quality[jplus]);
671 :
672 : // We don't want to build a tet that can't fix a neighbor
673 : // if we can build one that can.
674 10794 : if (qneighbest <= 0 &&
675 : qneighj > 0)
676 370 : continue;
677 :
678 : // We want to try for the best possible fix.
679 13212 : if ((local_tet_quality[j] - qneighj) >
680 13212 : (local_tet_quality[jbest] - qneighj))
681 : {
682 276 : jbest = j;
683 276 : qneighbest = qneighj;
684 : }
685 : }
686 :
687 6696 : libmesh_error_msg_if
688 : (local_tet_quality[jbest] <= 0,
689 : "Cannot build non-singular non-inverted tet");
690 :
691 5208 : std::tie(jminus, jplus) = find_valid_nodes_around(jbest);
692 :
693 6696 : return std::make_tuple(jbest, jminus, jplus);
694 4690 : };
695 :
696 4690 : if (n_surrounding > 3)
697 : {
698 : // We'll be searching local_tet_quality even after tet
699 : // extraction disconnects us from some nodes; when we do we
700 : // don't want to get one.
701 148 : constexpr Real far_node = -1e6;
702 :
703 : // Vectors from the center node to each of its surrounding
704 : // nodes are helpful for calculating prospective tet
705 : // quality.
706 666 : std::vector<Point> v0s(n_surrounding);
707 2590 : for (auto j : make_range(n_surrounding))
708 3848 : v0s[j] = *(Point *)surrounding_nodes[j] - *node;
709 :
710 : // Find the tet quality we'd potentially get from each
711 : // possible choice of tet
712 : auto local_tet_quality_of =
713 1332 : [& surrounding_nodes, & v0s, & find_valid_nodes_around]
714 21312 : (unsigned int j)
715 : {
716 3108 : auto [jminus, jplus] = find_valid_nodes_around(j);
717 :
718 : // Anything proportional to the ratio of volume to
719 : // total-edge-length-cubed should peak for perfect tets
720 : // but hit 0 for pancakes and slivers.
721 :
722 : const Real total_len =
723 7548 : v0s[j].norm() + v0s[jminus].norm() + v0s[jplus].norm() +
724 1332 : (*(Point *)surrounding_nodes[jplus] -
725 7104 : *(Point *)surrounding_nodes[j]).norm() +
726 1332 : (*(Point *)surrounding_nodes[j] -
727 4884 : *(Point *)surrounding_nodes[jminus]).norm() +
728 1332 : (*(Point *)surrounding_nodes[jminus] -
729 5772 : *(Point *)surrounding_nodes[jplus]).norm();
730 :
731 : // Orientation here is tricky. Think of the triple
732 : // product as (v1 cross v2) dot v3, with right hand rule.
733 : const Real six_vol =
734 3108 : triple_product(v0s[jminus], v0s[jplus], v0s[j]);
735 :
736 3108 : return six_vol / (total_len * total_len * total_len);
737 518 : };
738 :
739 2590 : for (auto j : make_range(n_surrounding))
740 2072 : local_tet_quality[j] = local_tet_quality_of(j);
741 :
742 : // If we have N surrounding nodes, we can make N tets and
743 : // that'll bring us back to the 3-surrounding-node case to
744 : // finish.
745 1036 : for (auto t : make_range(n_surrounding-3))
746 : {
747 148 : libmesh_ignore(t);
748 :
749 518 : auto [jbest, jminus, jplus] = find_new_tet_nodes();
750 :
751 : // Turn these four nodes into a tet
752 518 : Node * nbest = surrounding_nodes[jbest],
753 518 : * nminus = surrounding_nodes[jminus],
754 518 : * nplus = surrounding_nodes[jplus];
755 518 : this->add_tet(nminus->id(), nbest->id(), nplus->id(),
756 296 : node->id());
757 :
758 : // Replace the old two triangles around these nodes with the
759 : // proper two new triangles.
760 518 : Elem * oldtri1 = surrounding_elems[jminus],
761 518 : * oldtri2 = surrounding_elems[jbest],
762 518 : * newtri1 = surface.add_elem(Elem::build(TRI3)),
763 666 : * newtri2 = surface.add_elem(Elem::build(TRI3));
764 :
765 370 : const unsigned int c1 = oldtri1->get_node_index(node),
766 370 : c2 = oldtri2->get_node_index(node);
767 :
768 518 : newtri1->set_node(0, node);
769 518 : newtri1->set_node(1, nminus);
770 518 : newtri1->set_node(2, nplus);
771 :
772 518 : surrounding_elems[jminus] = newtri1;
773 :
774 296 : Elem * neigh10 = oldtri1->neighbor_ptr(c1);
775 518 : Elem * neigh12 = oldtri2->neighbor_ptr((c2+2)%3);
776 296 : newtri1->set_neighbor(0, neigh10);
777 518 : neigh10->set_neighbor(neigh10->which_neighbor_am_i(oldtri1), newtri1);
778 296 : newtri1->set_neighbor(1, newtri2);
779 148 : newtri1->set_neighbor(2, neigh12);
780 518 : neigh12->set_neighbor(neigh12->which_neighbor_am_i(oldtri2), newtri1);
781 :
782 518 : newtri2->set_node(0, nplus);
783 518 : newtri2->set_node(1, nminus);
784 518 : newtri2->set_node(2, nbest);
785 :
786 518 : Elem * neigh21 = oldtri1->neighbor_ptr((c1+1)%3);
787 518 : Elem * neigh22 = oldtri2->neighbor_ptr((c2+1)%3);
788 296 : newtri2->set_neighbor(0, newtri1);
789 148 : newtri2->set_neighbor(1, neigh21);
790 518 : neigh21->set_neighbor(neigh21->which_neighbor_am_i(oldtri1), newtri2);
791 296 : newtri2->set_neighbor(2, neigh22);
792 518 : neigh22->set_neighbor(neigh22->which_neighbor_am_i(oldtri2), newtri2);
793 :
794 2072 : for (unsigned int p : make_range(3))
795 : {
796 2442 : nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
797 2442 : nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
798 2442 : nodes_to_elem_map[newtri1->node_id(p)].insert(newtri1->id());
799 2442 : nodes_to_elem_map[newtri2->node_id(p)].insert(newtri2->id());
800 : }
801 :
802 : // We've finished replacing the old triangles.
803 518 : surface.delete_elem(oldtri1);
804 518 : surface.delete_elem(oldtri2);
805 :
806 : // The solid angle for the far node should now stay
807 : // unchanged until we're out of this inner loop; let's
808 : // recalculate it here, and then we'll be done with it.
809 296 : Node * & nbestref = surrounding_nodes[jbest];
810 518 : nodes_by_geometry.erase(node_index[nbestref]);
811 518 : node_index[nbestref] =
812 740 : nodes_by_geometry.emplace(geometry_at(*nbestref), nbestref);
813 :
814 : // The far node is no longer sharing an edge with our center
815 : // node. Make sure we don't use it again with the center
816 : // node.
817 518 : local_tet_quality[jbest] = far_node;
818 518 : nbestref = nullptr;
819 :
820 : // The potential tet qualities using the side nodes have
821 : // changed now that they're directly connected to each
822 : // other.
823 666 : local_tet_quality[jminus] =
824 518 : local_tet_quality_of(jminus);
825 :
826 666 : local_tet_quality[jplus] =
827 518 : local_tet_quality_of(jplus);
828 : }
829 : }
830 :
831 : // Now we should have just 3 surrounding nodes, with which to
832 : // make one tetrahedron. Put them in a counterclockwise
833 : // (looking from outside) orientation, not the "best, clockwise,
834 : // counterclockwise" we get from the lambda.
835 4690 : auto [j2, j1, j3] = find_new_tet_nodes();
836 :
837 : // Turn these last four nodes into a tet
838 4690 : Node * n1 = surrounding_nodes[j1],
839 4690 : * n2 = surrounding_nodes[j2],
840 4690 : * n3 = surrounding_nodes[j3];
841 4690 : this->add_tet(n1->id(), n2->id(), n3->id(), node->id());
842 :
843 : // Replace the three surface triangles of that tet with the new
844 : // fourth triangle.
845 4466 : Elem * oldtri1 = surrounding_elems[j1],
846 4466 : * oldtri2 = surrounding_elems[j2],
847 4466 : * oldtri3 = surrounding_elems[j3],
848 5742 : * newtri = surface.add_elem(Elem::build(TRI3));
849 :
850 3190 : const unsigned int c1 = oldtri1->get_node_index(node),
851 3190 : c2 = oldtri2->get_node_index(node),
852 3190 : c3 = oldtri3->get_node_index(node);
853 :
854 4466 : newtri->set_node(0, n1);
855 4466 : newtri->set_node(1, n2);
856 4466 : newtri->set_node(2, n3);
857 4466 : Elem * neigh0 = oldtri1->neighbor_ptr((c1+1)%3);
858 2552 : newtri->set_neighbor(0, neigh0);
859 4466 : neigh0->set_neighbor(neigh0->which_neighbor_am_i(oldtri1), newtri);
860 4466 : Elem * neigh1 = oldtri2->neighbor_ptr((c2+1)%3);
861 2552 : newtri->set_neighbor(1, neigh1);
862 4466 : neigh1->set_neighbor(neigh1->which_neighbor_am_i(oldtri2), newtri);
863 4466 : Elem * neigh2 = oldtri3->neighbor_ptr((c3+1)%3);
864 2552 : newtri->set_neighbor(2, neigh2);
865 4466 : neigh2->set_neighbor(neigh2->which_neighbor_am_i(oldtri3), newtri);
866 :
867 17864 : for (unsigned int p : make_range(3))
868 : {
869 21054 : nodes_to_elem_map[oldtri1->node_id(p)].erase(oldtri1->id());
870 21054 : nodes_to_elem_map[oldtri2->node_id(p)].erase(oldtri2->id());
871 21054 : nodes_to_elem_map[oldtri3->node_id(p)].erase(oldtri3->id());
872 21214 : nodes_to_elem_map[newtri->node_id(p)].insert(newtri->id());
873 : }
874 :
875 : // We've finished replacing the old triangles.
876 4466 : surface.delete_elem(oldtri1);
877 4466 : surface.delete_elem(oldtri2);
878 4466 : surface.delete_elem(oldtri3);
879 :
880 : // We should have used up all our surrounding nodes now, and we
881 : // shouldn't have messed up our surface in the process, and our
882 : // center node should no longer be part of the surface.
883 : #ifdef DEBUG
884 1276 : surrounding_nodes[j1] = nullptr;
885 1276 : surrounding_nodes[j2] = nullptr;
886 1276 : surrounding_nodes[j3] = nullptr;
887 :
888 5252 : for (auto ltq : surrounding_nodes)
889 3976 : libmesh_assert(!ltq);
890 :
891 1276 : if (surface.n_elem() > 3)
892 1080 : verify_surface();
893 :
894 9548 : for (const Elem * elem : surface.element_ptr_range())
895 33088 : for (auto p : make_range(3))
896 24816 : libmesh_assert_not_equal_to
897 : (elem->node_ptr(p), node);
898 : #endif
899 :
900 : // We've used up our center node, so it's not something we can
901 : // eliminate again.
902 3190 : nodes_by_geometry.erase(geometry_it);
903 : }
904 : // At this point our surface should just have two triangles left.
905 196 : libmesh_assert_equal_to(surface.n_elem(), 2);
906 686 : _has_mid_elem_node = false;
907 486 : }
908 : // Failed without an interior point.
909 : // Use a single vertex-average interior point and tetrahedralize around it
910 352 : catch ( libMesh::LogicError & )
911 : #endif
912 : {
913 : // Clear the triangulation we started building
914 64 : this->_triangulation.clear();
915 :
916 : // Get the vertex-average, no need to triangulate for this
917 224 : const auto v_avg = this->vertex_average();
918 224 : if (!_has_mid_elem_node)
919 : {
920 : // Create the mid element node. Add it to nodelinks
921 : // We'll attach a unique ptr to it later
922 224 : Node * mid_elem_node = new Node(v_avg);
923 224 : this->_nodelinks_data.push_back(mid_elem_node);
924 224 : _has_mid_elem_node = true;
925 : }
926 : else
927 : {
928 : // We are triangulating for a second time, we have already added this mid-element node
929 0 : libmesh_assert(n_vertices() == n_nodes() - 1);
930 : }
931 :
932 : // Build the tetrahedralization with each of the triangles on each side
933 1638 : for (unsigned int s : make_range(this->n_sides()))
934 : {
935 1414 : const auto & [side, inward_normal, node_map] = this->_sidelinks_data[s];
936 :
937 4382 : for (auto t : make_range(side->n_subtriangles()))
938 : {
939 : // Get all the nodes
940 2968 : const auto & n1 = node_map[side->subtriangle(t)[0]];
941 2968 : const auto & n2 = node_map[side->subtriangle(t)[1]];
942 2968 : const auto & n3 = node_map[side->subtriangle(t)[2]];
943 :
944 : // The mid node is the last node in the _nodes array
945 2968 : if (!inward_normal)
946 938 : this->add_tet((int)n1, (int)n2, (int)n3, this->n_nodes() - 1);
947 : else
948 2030 : this->add_tet((int)n1, (int)n3, (int)n2, this->n_nodes() - 1);
949 : }
950 : }
951 96 : }
952 1300 : }
953 :
954 :
955 8176 : void C0Polyhedron::add_tet(int n1,
956 : int n2,
957 : int n3,
958 : int n4)
959 : {
960 : #ifndef NDEBUG
961 2336 : const auto nn = this->n_nodes();
962 2336 : libmesh_assert_less(n1, nn);
963 2336 : libmesh_assert_less(n2, nn);
964 2336 : libmesh_assert_less(n3, nn);
965 2336 : libmesh_assert_less(n4, nn);
966 : #endif
967 :
968 10512 : const Point v12 = this->point(n2) - this->point(n1);
969 8176 : const Point v13 = this->point(n3) - this->point(n1);
970 8176 : const Point v14 = this->point(n4) - this->point(n1);
971 5840 : const Real six_vol = triple_product(v12, v13, v14);
972 : // We need to error on this in optimized modes to fall back onto the
973 : // tetrahedralization with a mid node
974 8400 : libmesh_error_msg_if(six_vol <= 0, "Creating flat tet");
975 :
976 7952 : this->_triangulation.push_back({n1, n2, n3, n4});
977 7952 : }
978 :
979 :
980 : } // namespace libMesh
|