libMesh
Loading...
Searching...
No Matches
poly2tri_triangulator.C
Go to the documentation of this file.
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
19#include "libmesh/libmesh_config.h"
20
21#ifdef LIBMESH_HAVE_POLY2TRI
22
23// We'd like reproduceability here even when different FP rounding can
24// lead to different triangle splitting decisions
25#include "libmesh/enforce_ieee754.h"
26
27// libmesh includes
28#include "libmesh/poly2tri_triangulator.h"
29
30#include "libmesh/boundary_info.h"
31#include "libmesh/elem.h"
32#include "libmesh/enum_elem_type.h"
33#include "libmesh/function_base.h"
34#include "libmesh/hashing.h"
35#include "libmesh/libmesh_logging.h"
36#include "libmesh/mesh_serializer.h"
37#include "libmesh/mesh_smoother_laplace.h"
38#include "libmesh/mesh_triangle_holes.h"
39#include "libmesh/unstructured_mesh.h"
40#include "libmesh/utility.h"
41
42// poly2tri includes
43#include "libmesh/ignore_warnings.h" // utf-8 comments should be fine...
44#include "poly2tri/poly2tri.h"
45#include "libmesh/restore_warnings.h"
46
47// Anonymous namespace - poly2tri doesn't define operator<(Point,Point)
48namespace
49{
50using namespace libMesh;
51
52struct P2TPointCompare
53{
54 bool operator()(const p2t::Point & a, const p2t::Point & b) const
55 {
56 return a.x < b.x || (a.x == b.x && a.y < b.y);
57 }
58};
59
60p2t::Point to_p2t(const libMesh::Point & p)
61{
62#if LIBMESH_DIM > 2
63 libmesh_error_msg_if
64 (p(2) != 0,
65 "Poly2TriTriangulator only supports point sets in the XY plane");
66#endif
67
68 return {double(p(0)), double(p(1))};
69}
70
71Real distance_from_circumcircle(const Elem & elem,
72 const Point & p)
73{
74 libmesh_assert_equal_to(elem.n_vertices(), 3);
75
77 const Real radius = (elem.point(0) - circumcenter).norm();
78 const Real p_dist = (p - circumcenter).norm();
79
80 return p_dist - radius;
81}
82
83
84bool in_circumcircle(const Elem & elem,
85 const Point & p,
86 const Real tol = 0)
87{
88 return (distance_from_circumcircle(elem, p) < tol);
89
90 /*
91 libmesh_assert_equal_to(elem.n_vertices(), 3);
92
93 const Point pv0 = elem.point(0) - p;
94 const Point pv1 = elem.point(1) - p;
95 const Point pv2 = elem.point(2) - p;
96
97 return ((pv0.norm_sq() * (pv1(0)*pv2(1)-pv2(0)*pv1(1))) -
98 (pv1.norm_sq() * (pv0(0)*pv2(1)-pv2(0)*pv0(1))) +
99 (pv2.norm_sq() * (pv0(0)*pv1(1)-pv1(0)*pv0(1)))) > 0;
100 */
101}
102
103
104std::pair<bool, unsigned short>
105can_delaunay_swap(const Elem & elem,
106 unsigned short side,
107 Real tol)
108{
109 const Elem * neigh = elem.neighbor_ptr(side);
110 if (!neigh)
111 return {false, 0};
112
113 unsigned short nn = 0;
114
115 // What neighbor node does elem not share?
116 for (; nn < 3; ++nn)
117 {
118 const Node * neigh_node = neigh->node_ptr(nn);
119 if (neigh_node == elem.node_ptr(0) ||
120 neigh_node == elem.node_ptr(1) ||
121 neigh_node == elem.node_ptr(2))
122 continue;
123
124 // Might we need to do a diagonal swap here? Avoid
125 // undoing a borderline swap.
126 if (in_circumcircle(elem, *neigh_node, tol))
127 break;
128 }
129
130 if (nn == 3)
131 return {false, 0};
132
133 const unsigned short n = (side+2)%3;
134 const RealVectorValue right =
135 (elem.point((n+1)%3)-elem.point(n)).unit();
136 const RealVectorValue mid =
137 (neigh->point(nn)-elem.point(n)).unit();
138 const RealVectorValue left =
139 (elem.point((n+2)%3)-elem.point(n)).unit();
140
141 // If the "middle" vector isn't really in the middle, we can't do a
142 // swap without involving other triangles (or we can't at all if
143 // there's a domain boundary in the way)
144 if (mid*right < left*right ||
145 left*mid < left*right)
146 return {false, 0};
147
148 return {true, nn};
149}
150
151
152[[maybe_unused]] void libmesh_assert_locally_delaunay(const Elem & elem)
153{
154 libmesh_ignore(elem);
155
156#ifndef NDEBUG
157 // -TOLERANCE, because we're fine with something a little inside the
158 // circumcircle
159 for (auto s : make_range(elem.n_sides()))
160 libmesh_assert(!can_delaunay_swap(elem, s, -TOLERANCE).first);
161#endif
162}
163
164template <typename Container>
165inline
166void libmesh_assert_delaunay(MeshBase & libmesh_dbg_var(mesh),
167 Container & new_elems)
168{
169 libmesh_ignore(new_elems);
170#ifndef NDEBUG
171 LOG_SCOPE("libmesh_assert_delaunay()", "Poly2TriTriangulator");
172
173 for (auto & elem : mesh.element_ptr_range())
174 libmesh_assert_locally_delaunay(*elem);
175
176 for (auto & [raw_elem, unique_elem] : new_elems)
177 {
178 libmesh_ignore(unique_elem); // avoid warnings on old gcc
179 libmesh_assert_locally_delaunay(*raw_elem);
180 }
181#endif
182}
183
184
185// Restore a triangulation's Delaunay property, starting with a set of
186// all triangles that might initially not be locally Delaunay with
187// their neighbors.
188template <typename Container>
189inline
190void restore_delaunay(Container & check_delaunay_on,
191 BoundaryInfo & boundary_info)
192{
193 LOG_SCOPE("restore_delaunay()", "Poly2TriTriangulator");
194
195 while (!check_delaunay_on.empty())
196 {
197 Elem & elem = **check_delaunay_on.begin();
198 check_delaunay_on.erase(&elem);
199 for (auto s : make_range(elem.n_sides()))
200 {
201 // Can we make a swap here? With what neighbor, with what
202 // far node? Use a negative tolerance to avoid swapping
203 // back and forth.
204 auto [can_swap, nn] =
205 can_delaunay_swap(elem, s, -TOLERANCE*TOLERANCE);
206 if (!can_swap)
207 continue;
208
209 Elem * neigh = elem.neighbor_ptr(s);
210
211 // If we made it here it's time to diagonal swap
212 const unsigned short n = (s+2)%3;
213
214 const std::array<Node *,4> nodes {elem.node_ptr(n),
215 elem.node_ptr((n+1)%3), neigh->node_ptr(nn),
216 elem.node_ptr((n+2)%3)};
217
218 // If we have to swap then either we or any of our neighbors
219 // might no longer be Delaunay
220 for (auto ds : make_range(3))
221 {
222 if (elem.neighbor_ptr(ds))
223 check_delaunay_on.insert(elem.neighbor_ptr(ds));
224 if (neigh->neighbor_ptr(ds))
225 check_delaunay_on.insert(neigh->neighbor_ptr(ds));
226 }
227
228 // An interior boundary between two newly triangulated
229 // triangles shouldn't have any bcids
230 libmesh_assert(!boundary_info.n_boundary_ids(neigh, (nn+1)%3));
231 libmesh_assert(!boundary_info.n_boundary_ids(&elem, (n+1)%3));
232
233 // The two changing boundary sides might have bcids
234 std::vector<boundary_id_type> bcids;
235 boundary_info.boundary_ids(&elem, (n+2)%3, bcids);
236 if (!bcids.empty())
237 {
238 boundary_info.remove_side(&elem, (n+2)%3);
239 boundary_info.add_side(neigh, (nn+1)%3, bcids);
240 }
241
242 boundary_info.boundary_ids(neigh, (nn+2)%3, bcids);
243 if (!bcids.empty())
244 {
245 boundary_info.remove_side(neigh, (nn+2)%3);
246 boundary_info.add_side(&elem, (n+1)%3, bcids);
247 }
248
249 elem.set_node((n+2)%3, nodes[2]);
250 neigh->set_node((nn+2)%3, nodes[0]);
251
252 // No need for a temporary array to swap these around, if we
253 // do it in the right order.
254 //
255 // Watch me neigh->neigh
256 Elem * neighneigh = neigh->neighbor_ptr((nn+2)%3);
257 if (neighneigh)
258 {
259 unsigned int snn = neighneigh->which_neighbor_am_i(neigh);
260 neighneigh->set_neighbor(snn, &elem);
261 }
262
263 Elem * elemoldneigh = elem.neighbor_ptr((n+2)%3);
264 if (elemoldneigh)
265 {
266 unsigned int seon = elemoldneigh->which_neighbor_am_i(&elem);
267 elemoldneigh->set_neighbor(seon, neigh);
268 }
269
270 elem.set_neighbor((n+1)%3, neigh->neighbor_ptr((nn+2)%3));
271 neigh->set_neighbor((nn+1)%3, elem.neighbor_ptr((n+2)%3));
272 elem.set_neighbor((n+2)%3, neigh);
273 neigh->set_neighbor((nn+2)%3, &elem);
274
275 // Start over after this much change, don't just loop to the
276 // next neighbor
277 break;
278 }
279 }
280}
281
282
283unsigned int segment_intersection(const Elem & elem,
284 Point & source,
285 const Point & target,
286 unsigned int source_side)
287{
288 libmesh_assert_equal_to(elem.dim(), 2);
289
290 const auto ns = elem.n_sides();
291
292 for (auto s : make_range(ns))
293 {
294 // Don't go backwards just because some FP roundoff said to
295 if (s == source_side)
296 continue;
297
298 const Point v0 = elem.point(s);
299 const Point v1 = elem.point((s+1)%ns);
300
301 // Calculate intersection parameters (fractions of the distance
302 // along each segment)
303 const Real raydx = target(0)-source(0),
304 raydy = target(1)-source(1),
305 edgedx = v1(0)-v0(0),
306 edgedy = v1(1)-v0(1);
307 const Real denom = edgedx * raydy - edgedy * raydx;
308
309 // divide-by-zero means the segments are parallel
310 if (denom == 0)
311 continue;
312
313 const Real one_over_denom = 1 / denom;
314
315 const Real targetsdx = v1(0)-target(0),
316 targetsdy = v1(1)-target(1);
317
318 const Real t_num = targetsdx * raydy -
319 targetsdy * raydx;
320 const Real t = t_num * one_over_denom;
321
322 if (t < -TOLERANCE*TOLERANCE || t > 1 + TOLERANCE*TOLERANCE)
323 continue;
324
325 const Real u_num = targetsdx * edgedy - targetsdy * edgedx;
326 const Real u = u_num * one_over_denom;
327
328 if (u < -TOLERANCE*TOLERANCE || u > 1 + TOLERANCE*TOLERANCE)
329 continue;
330
331/*
332 // Partial workaround for an old poly2tri bug (issue #39): if we
333 // end up with boundary points that are nearly-collinear but
334 // infinitesimally concave, p2t::CDT::Triangulate throws a "null
335 // triangle" exception. So let's try to be infinitesimally
336 // convex instead.
337 const Real ray_fraction = (1-u) * (1+TOLERANCE*TOLERANCE);
338*/
339 const Real ray_fraction = (1-u);
340
341 source(0) += raydx * ray_fraction;
342 source(1) += raydy * ray_fraction;
343 return s;
344 }
345
347}
348
349}
350
351namespace libMesh
352{
353//
354// Function definitions for the Poly2TriTriangulator class
355//
356
357// Constructor
359 dof_id_type n_boundary_nodes)
361 _n_boundary_nodes(n_boundary_nodes),
362 _refine_bdy_allowed(true)
363{
364}
365
366
368
369
370// Primary function responsible for performing the triangulation
372{
373 LOG_SCOPE("triangulate()", "Poly2TriTriangulator");
374
375 // We only operate on serialized meshes. And it's not safe to
376 // serialize earlier, because it would then be possible for the user
377 // to re-parallelize the mesh in between there and here.
378 MeshSerializer serializer(_mesh);
379
380 // We don't yet support every set of Triangulator options in the
381 // poly2tri implementation
382
383 // We don't support convex hull triangulation, only triangulation of
384 // (implicitly defined, by node ordering) polygons (with holes if
385 // requested)
387 libmesh_not_implemented();
388
389 // We currently don't handle region specifications
390 if (_regions)
391 libmesh_not_implemented();
392
393 // We won't support quads any time soon, or 1D/3D in this interface
394 // ever.
395 if (_elem_type != TRI3 &&
396 _elem_type != TRI6 &&
397 _elem_type != TRI7)
398 libmesh_not_implemented();
399
400 // If we have no explicit segments defined, we may get them from
401 // mesh elements
402 this->elems_to_segments();
403
404 // If we *still* have no explicit segments defined, we get them from
405 // the order of nodes.
407
408 // Insert additional new points in between existing boundary points,
409 // if that is requested and reasonable
411
412 // Triangulate the points we have, then see if we need to add more;
413 // repeat until we don't need to add more.
414 //
415 // This is currently done redundantly in parallel; make sure no
416 // processor quits before the others.
417 do
418 {
419 libmesh_parallel_only(_mesh.comm());
421 }
422 while (this->insert_refinement_points());
423
424 libmesh_parallel_only(_mesh.comm());
425
426 // Okay, we really do need to support boundary ids soon, but we
427 // don't yet
428 if (_markers)
429 libmesh_not_implemented();
430
432
433 // To the naked eye, a few smoothing iterations usually looks better,
434 // so we do this by default unless the user says not to.
435 if (this->_smooth_after_generating)
437
438 // The user might have requested TRI6 or higher instead of TRI3. We
439 // can do this before prepare_for_use() because all we need for it
440 // is find_neighbors(), which we did in insert_refinement_points()
442
443 // Prepare the mesh for use before returning. This ensures (among
444 // other things) that it is partitioned and therefore users can
445 // iterate over local elements, etc.
447}
448
449
451 (FunctionBase<Real> * desired)
452{
453 if (desired)
454 _desired_area_func = desired->clone();
455 else
456 _desired_area_func.reset();
457}
458
459
464
465
467 (const BoundaryInfo & boundary_info,
468 const Elem & elem,
469 unsigned int side)
470{
471 // We should only be calling this on a boundary side
472 libmesh_assert(!elem.neighbor_ptr(side));
473
474 std::vector<boundary_id_type> bcids;
475 boundary_info.boundary_ids(&elem, side, bcids);
476
477 // We should have one bcid on every boundary side.
478 libmesh_assert_equal_to(bcids.size(), 1);
479
480 if (bcids[0] == 0)
481 return this->refine_boundary_allowed();
482
483 // If we're not on an outer boundary side we'd better be on a hole
484 // side
485 libmesh_assert(this->_holes);
486
487 const boundary_id_type hole_num = bcids[0]-1;
488 libmesh_assert_less(hole_num, this->_holes->size());
489 const Hole * hole = (*this->_holes)[hole_num];
490 return hole->refine_boundary_allowed();
491}
492
493
495{
496 LOG_SCOPE("triangulate_current_points()", "Poly2TriTriangulator");
497
498 // Will the triangulation have holes?
499 const std::size_t n_holes = _holes != nullptr ? _holes->size() : 0;
500
501 // Mapping from Poly2Tri points to libMesh nodes, so we can get the
502 // connectivity translated back later.
503 std::map<const p2t::Point, Node *, P2TPointCompare> point_node_map;
504
505 // Poly2Tri data structures
506 // Poly2Tri takes vectors of pointers-to-Point for some reason, but
507 // we'll just make those shims to vectors of Point rather than
508 // individually/manually heap allocating everything.
509 std::vector<p2t::Point> outer_boundary_points;
510 std::vector<std::vector<p2t::Point>> inner_hole_points(n_holes);
511
513 libmesh_error_msg_if
514 (!nn, "Poly2TriTriangulator cannot triangulate an empty mesh!");
515
516 // Unless we're using an explicit segments list, we assume node ids
517 // are contiguous here.
518 if (this->segments.empty())
519 libmesh_error_msg_if
520 (_mesh.n_nodes() != nn,
521 "Poly2TriTriangulator needs contiguous node ids or explicit segments!");
522
523 // And if we have more nodes than outer boundary points, the rest
524 // may be interior "Steiner points". We use a set here so we can
525 // cheaply search and erase from it later, when we're identifying
526 // hole points.
527 std::set<p2t::Point, P2TPointCompare> steiner_points;
528
529 // If we were asked to use all mesh nodes as boundary nodes, now's
530 // the time to see how many that is.
532 {
534 libmesh_assert_equal_to(std::ptrdiff_t(_n_boundary_nodes),
535 std::distance(_mesh.nodes_begin(),
536 _mesh.nodes_end()));
537
538 }
539 else
540 libmesh_assert_less_equal(_n_boundary_nodes,
541 _mesh.n_nodes());
542
543 // Prepare poly2tri points for our nodes, sorted into outer boundary
544 // points and interior Steiner points.
545
546 if (this->segments.empty())
547 {
548 // If we have no segments even after taking elems into account,
549 // the nodal id ordering defines our outer polyline ordering
550 for (auto & node : _mesh.node_ptr_range())
551 {
552 const p2t::Point pt = to_p2t(*node);
553
554 // If we're out of boundary nodes, the rest are going to be
555 // Steiner points or hole points
556 if (node->id() < _n_boundary_nodes)
557 outer_boundary_points.push_back(pt);
558 else
559 steiner_points.insert(pt);
560
561 // We're not going to support overlapping nodes on the boundary
562 if (point_node_map.count(pt))
563 libmesh_not_implemented();
564
565 point_node_map.emplace(pt, node);
566 }
567 }
568 // If we have explicit segments defined, that's our outer polyline
569 // ordering:
570 else
571 {
572 // Let's make sure our segments are in order
574
575 // Add nodes from every segment, in order, to the outer polyline
576 for (auto [segment_start, segment_end] : this->segments)
577 {
578 if (last_id != DofObject::invalid_id)
579 libmesh_error_msg_if(segment_start != last_id,
580 "Disconnected triangulator segments");
581 last_id = segment_end;
582
583 Node * node = _mesh.query_node_ptr(segment_start);
584
585 libmesh_error_msg_if(!node,
586 "Triangulator segments reference nonexistent node id " <<
587 segment_start);
588
589 outer_boundary_points.emplace_back(double((*node)(0)), double((*node)(1)));
590 p2t::Point * pt = &outer_boundary_points.back();
591
592 // We're not going to support overlapping nodes on the boundary
593 if (point_node_map.count(*pt))
594 libmesh_not_implemented_msg
595 ("Triangulating overlapping boundary nodes is unsupported");
596
597 point_node_map.emplace(*pt, node);
598 }
599
600 libmesh_error_msg_if(last_id != this->segments[0].first,
601 "Non-closed-loop triangulator segments");
602
603 // If we have points that aren't in any segments, those will be
604 // Steiner points
605 for (auto & node : _mesh.node_ptr_range())
606 {
607 const p2t::Point pt = to_p2t(*node);
608 if (const auto it = point_node_map.find(pt);
609 it == point_node_map.end())
610 {
611 steiner_points.insert(pt);
612 point_node_map.emplace(pt, node);
613 }
614 else
615 libmesh_assert_equal_to(it->second, node);
616 }
617 }
618
619 // If we have any elements from a previous triangulation, we're
620 // going to replace them with our own. If we have any elements that
621 // were used to create our segments, we're done creating and we no
622 // longer need them.
624
625 // Keep track of what boundary ids we want to assign to each new
626 // triangle. We'll give the outer boundary BC 0, and give holes ids
627 // starting from 1. We've already got the point_node_map to find
628 // nodes, so we can just key on pairs of node ids to identify a side.
629 std::unordered_map<std::pair<dof_id_type,dof_id_type>,
630 boundary_id_type, libMesh::hash> side_boundary_id;
631
632 const boundary_id_type outer_bcid = 0;
633 const std::size_t n_outer = outer_boundary_points.size();
634
635 for (auto i : make_range(n_outer))
636 {
637 const Node * node1 =
638 libmesh_map_find(point_node_map, outer_boundary_points[i]),
639 * node2 =
640 libmesh_map_find(point_node_map, outer_boundary_points[(i+1)%n_outer]);
641
642 side_boundary_id.emplace(std::make_pair(node1->id(),
643 node2->id()),
644 outer_bcid);
645 }
646
647 // Create poly2tri triangulator with our mesh points
648 std::vector<p2t::Point *> outer_boundary_pointers(n_outer);
649 std::transform(outer_boundary_points.begin(),
650 outer_boundary_points.end(),
651 outer_boundary_pointers.begin(),
652 [](p2t::Point & p) { return &p; });
653
654
655 // Make sure shims for holes last as long as the CDT does; the
656 // poly2tri headers don't make clear whether or not they're hanging
657 // on to references to these vectors, and it would be reasonable for
658 // them to do so.
659 std::vector<std::vector<p2t::Point *>> inner_hole_pointers(n_holes);
660
661 p2t::CDT cdt{outer_boundary_pointers};
662
663 // Add any holes
664 for (auto h : make_range(n_holes))
665 {
666 const Hole * initial_hole = (*_holes)[h];
667 auto it = replaced_holes.find(initial_hole);
668 const Hole & our_hole =
669 (it == replaced_holes.end()) ?
670 *initial_hole : *it->second;
671 auto & poly2tri_hole = inner_hole_points[h];
672
673 for (auto i : make_range(our_hole.n_points()))
674 {
675 Point p = our_hole.point(i);
676 poly2tri_hole.emplace_back(to_p2t(p));
677
678 const auto & pt = poly2tri_hole.back();
679
680 // This won't be a steiner point.
681 steiner_points.erase(pt);
682
683 // If we see a hole point already in the mesh, we'll share
684 // that node. This might be a problem if it's a boundary
685 // node, but it might just be the same hole point already
686 // added during a previous triangulation refinement step.
687 if (point_node_map.count(pt))
688 {
689 libmesh_assert_equal_to
690 (point_node_map[pt],
691 _mesh.query_node_ptr(point_node_map[pt]->id()));
692 }
693 else
694 {
695 Node * node = _mesh.add_point(p, nn++);
696 point_node_map[pt] = node;
697 }
698 }
699
700 const boundary_id_type inner_bcid = h+1;
701 const std::size_t n_inner = poly2tri_hole.size();
702
703 for (auto i : make_range(n_inner))
704 {
705 const Node * node1 =
706 libmesh_map_find(point_node_map, poly2tri_hole[i]),
707 * node2 =
708 libmesh_map_find(point_node_map, poly2tri_hole[(i+1)%n_inner]);
709
710 side_boundary_id.emplace(std::make_pair(node1->id(),
711 node2->id()),
712 inner_bcid);
713 }
714
715 auto & poly2tri_ptrs = inner_hole_pointers[h];
716 poly2tri_ptrs.resize(n_inner);
717
718 std::transform(poly2tri_hole.begin(),
719 poly2tri_hole.end(),
720 poly2tri_ptrs.begin(),
721 [](p2t::Point & p) { return &p; });
722
723 cdt.AddHole(poly2tri_ptrs);
724 }
725
726 // Add any steiner points. We had them in a set, but post-C++11
727 // that won't give us non-const element access (even if we
728 // pinky-promise not to change the elements in any way that affects
729 // our Comparator), and Poly2Tri wants non-const elements (to store
730 // edge data?), so we need to move them here.
731 std::vector<p2t::Point> steiner_vector(steiner_points.begin(), steiner_points.end());
732 steiner_points.clear();
733 for (auto & p : steiner_vector)
734 cdt.AddPoint(&p);
735
736 // Triangulate!
737 cdt.Triangulate();
738
739 // Get poly2tri triangles, turn them into libMesh triangles
740 std::vector<p2t::Triangle *> triangles = cdt.GetTriangles();
741
742 // Do our own numbering, even on DistributedMesh
743 dof_id_type next_id = 0;
744
745 BoundaryInfo & boundary_info = _mesh.get_boundary_info();
746 boundary_info.clear();
747
748 // Add the triangles to our Mesh data structure.
749 for (auto ptri_ptr : triangles)
750 {
751 p2t::Triangle & ptri = *ptri_ptr;
752
753 // We always use TRI3 here, since that's what we have nodes for;
754 // if we need a higher order we can convert at the end.
755 auto elem = Elem::build_with_id(TRI3, next_id++);
756 for (auto v : make_range(3))
757 {
758 const p2t::Point & vertex = *ptri.GetPoint(v);
759
760 Node * node = libmesh_map_find(point_node_map, vertex);
761 libmesh_assert(node);
762 elem->set_node(v, node);
763 }
764
765 // We expect a consistent triangle orientation
766 libmesh_assert(!elem->is_flipped());
767
768 Elem * added_elem = _mesh.add_elem(std::move(elem));
769
770 for (auto v : make_range(3))
771 {
772 const Node & node1 = added_elem->node_ref(v),
773 & node2 = added_elem->node_ref((v+1)%3);
774
775 auto it = side_boundary_id.find(std::make_pair(node1.id(), node2.id()));
776 if (it == side_boundary_id.end())
777 it = side_boundary_id.find(std::make_pair(node2.id(), node1.id()));
778 if (it != side_boundary_id.end())
779 boundary_info.add_side(added_elem, v, it->second);
780 }
781 }
782}
783
784
785
787{
788 LOG_SCOPE("insert_refinement_points()", "Poly2TriTriangulator");
789
790 if (this->minimum_angle() != 0)
791 libmesh_not_implemented();
792
793 // We need neighbor pointers for ray casting and cavity finding
794 UnstructuredMesh & mesh = dynamic_cast<UnstructuredMesh &>(this->_mesh);
795 mesh.find_neighbors();
796
797 if (this->desired_area() == 0 &&
798 this->get_desired_area_function() == nullptr &&
799 !this->has_auto_area_function())
800 return false;
801
802 BoundaryInfo & boundary_info = _mesh.get_boundary_info();
803
804 // We won't immediately add these, lest we invalidate iterators on a
805 // ReplicatedMesh. They'll still be in the mesh neighbor topology
806 // for the purpose of doing Delaunay cavity stuff, so we need to
807 // manage memory here, but there's no point in adding them to the
808 // Mesh just to remove them again afterward when we hit up poly2tri.
809
810 // We'll need to be able to remove new elems from new_elems, in
811 // cases where a later refinement insertion has a not-yet-added
812 // element in its cavity, so we'll use a map here to make searching
813 // possible.
814 //
815 // For parallel consistency, we can't order a container we plan to
816 // iterate through based on Elem * or a hash of it. We'll be doing
817 // Delaunay swaps so we can't iterate based on geometry. These are
818 // not-yet-added elements so we can't iterate based on proper
819 // element ids ... but we can set a temporary element id to use for
820 // the purpose.
821 struct cmp {
822 bool operator()(Elem * a, Elem * b) const {
823 libmesh_assert(a == b || a->id() != b->id());
824 return (a->id() < b->id());
825 }
826 } comp;
827
828 std::map<Elem *, std::unique_ptr<Elem>, decltype(comp)> new_elems(comp);
829
830 // We should already be Delaunay when we get here, otherwise we
831 // won't be able to stay Delaunay later. But we're *not* always
832 // Delaunay when we get here? What the hell, poly2tri? Fixing this
833 // is expensive!
834 {
835 // restore_delaunay should get to the same Delaunay triangulation up to
836 // isomorphism regardless of ordering ... but we actually care
837 // about the isomorphisms! If a triangle's nodes are permuted on
838 // one processor vs another that's an issue. So sort our input
839 // carefully.
840 std::set<Elem *, decltype(comp)> all_elems
841 { mesh.elements_begin(), mesh.elements_end(), comp };
842
843 restore_delaunay(all_elems, boundary_info);
844
845 libmesh_assert_delaunay(mesh, new_elems);
846 }
847
848 // Map of which points follow which in the boundary polylines. If
849 // we have to add new boundary points, we'll use this to construct
850 // an updated this->segments to retriangulate with. If we have to
851 // add new hole points, we'll use this to insert points into an
852 // ArbitraryHole.
853 std::unordered_map<Point, Node *> next_boundary_node;
854
855 // In cases where we've been working with contiguous node id ranges;
856 // let's keep it that way.
859
860 // We can't handle duplicated nodes. We shouldn't ever create one,
861 // but let's make sure of that.
862#ifdef DEBUG
863 std::unordered_set<Point> mesh_points;
864 for (const Node * node : mesh.node_ptr_range())
865 {
866 libmesh_assert(!mesh_points.count(*node));
867 mesh_points.insert(*node);
868 }
869#endif
870
871 auto add_point = [&mesh,
872#ifdef DEBUG
873 &mesh_points,
874#endif
875 &nn](const Point & p)
876 {
877#ifdef DEBUG
878 libmesh_assert(!mesh_points.count(p));
879 mesh_points.insert(p);
880#endif
881 return mesh.add_point(p, nn++);
882 };
883
884 for (auto & elem : mesh.element_ptr_range())
885 {
886 // element_ptr_range skips deleted elements ... right?
887 libmesh_assert(elem);
888 libmesh_assert(elem->valid_id());
889
890 // We only handle triangles in our triangulation
891 libmesh_assert_equal_to(elem->level(), 0u);
892 libmesh_assert_equal_to(elem->type(), TRI3);
893
894 // If this triangle is as small as we desire, move along
895 if (!should_refine_elem(*elem))
896 continue;
897
898 // Otherwise add a Steiner point. We'd like to add the
899 // circumcenter ...
900 Point new_pt = elem->quasicircumcenter();
901
902 // And to give it a node;
903 Node * new_node = nullptr;
904
905 // But that might be outside our triangle, or even outside the
906 // boundary. We'll find a triangle that should contain our new
907 // point
908 Elem * cavity_elem = elem; // Start looking at elem anyway
909
910 // We'll refine a boundary later if necessary.
911 auto boundary_refine = [this, &next_boundary_node,
912 &cavity_elem, &new_node]
913 (unsigned int side)
914 {
915 libmesh_ignore(this); // Only used in dbg/devel
916 libmesh_assert(new_node);
917 libmesh_assert(new_node->valid_id());
918
919 Node * old_segment_start = cavity_elem->node_ptr(side),
920 * old_segment_end = cavity_elem->node_ptr((side+1)%3);
921 libmesh_assert(old_segment_start);
922 libmesh_assert(old_segment_start->valid_id());
923 libmesh_assert(old_segment_end);
924 libmesh_assert(old_segment_end->valid_id());
925
926 if (auto it = next_boundary_node.find(*old_segment_start);
927 it != next_boundary_node.end())
928 {
929 libmesh_assert(it->second == old_segment_end);
930 it->second = new_node;
931 }
932 else
933 {
934 // This would be an O(N) sanity check if we already
935 // have a segments vector or any holes. :-P
936 libmesh_assert(!this->segments.empty() ||
937 (_holes && !_holes->empty()) ||
938 (old_segment_end->id() ==
939 old_segment_start->id() + 1));
940 next_boundary_node[*old_segment_start] = new_node;
941 }
942
943 next_boundary_node[*new_node] = old_segment_end;
944 };
945
946 // Let's find a triangle containing our new point, or at least
947 // containing the end of a ray leading from our current triangle
948 // to the new point.
949 Point ray_start = elem->vertex_average();
950
951 // What side are we coming from, and what side are we going to?
952 unsigned int source_side = invalid_uint;
953 unsigned int side = invalid_uint;
954
955 while (!cavity_elem->contains_point(new_pt))
956 {
957 side = segment_intersection(*cavity_elem, ray_start, new_pt, source_side);
958
959 libmesh_assert_not_equal_to (side, invalid_uint);
960
961 Elem * neigh = cavity_elem->neighbor_ptr(side);
962 // If we're on a boundary, stop there. Refine the boundary
963 // if we're allowed, the boundary element otherwise.
964 if (!neigh)
965 {
966 if (this->is_refine_boundary_allowed(boundary_info,
967 *cavity_elem,
968 side))
969 {
970 new_pt = ray_start;
971 new_node = add_point(new_pt);
972 boundary_refine(side);
973 }
974 else
975 {
976 // Should we just add the vertex average of the
977 // boundary element, to minimize the number of
978 // slivers created?
979 //
980 // new_pt = cavity_elem->vertex_average();
981 //
982 // That works for a while, but it
983 // seems to be able to "run away" and leave us with
984 // crazy slivers on boundaries if we push interior
985 // refinement too far while disabling boundary
986 // refinement.
987 //
988 // Let's go back to refining our original problem
989 // element.
990 cavity_elem = elem;
991 new_pt = cavity_elem->vertex_average();
992 new_node = add_point(new_pt);
993
994 // This was going to be a side refinement but it's
995 // now an internal refinement
996 side = invalid_uint;
997 }
998
999 break;
1000 }
1001
1002 source_side = neigh->which_neighbor_am_i(cavity_elem);
1003 cavity_elem = neigh;
1004 side = invalid_uint;
1005 }
1006
1007 // If we're ready to create a new node and we're not on a
1008 // boundary ... should we be? We don't want to create any
1009 // sliver elements or confuse poly2tri or anything.
1010 if (side == invalid_uint && !new_node)
1011 {
1012 unsigned int worst_side = libMesh::invalid_uint;
1013 Real worst_cos = 1;
1014 for (auto s : make_range(3u))
1015 {
1016 // We never snap to a non-domain-boundary
1017 if (cavity_elem->neighbor_ptr(s))
1018 continue;
1019
1020 Real ax = cavity_elem->point(s)(0) - new_pt(0),
1021 ay = cavity_elem->point(s)(1) - new_pt(1),
1022 bx = cavity_elem->point((s+1)%3)(0) - new_pt(0),
1023 by = cavity_elem->point((s+1)%3)(1) - new_pt(1);
1024 const Real my_cos = (ax*bx+ay*by) /
1025 std::sqrt(ax*ax+ay*ay) /
1026 std::sqrt(bx*bx+by*by);
1027
1028 if (my_cos < worst_cos)
1029 {
1030 worst_side = s;
1031 worst_cos = my_cos;
1032 }
1033 }
1034
1035 // If we'd create a sliver element on the side, let's just
1036 // refine the side instead, if we're allowed.
1037 if (worst_cos < -0.6) // -0.5 is the best we could enforce?
1038 {
1039 side = worst_side;
1040
1041 if (this->is_refine_boundary_allowed(boundary_info,
1042 *cavity_elem,
1043 side))
1044 {
1045 // Let's just try bisecting for now
1046 new_pt = (cavity_elem->point(side) +
1047 cavity_elem->point((side+1)%3)) / 2;
1048 new_node = add_point(new_pt);
1049 boundary_refine(side);
1050 }
1051 else // Do the best we can under these restrictions
1052 {
1053 new_pt = cavity_elem->vertex_average();
1054 new_node = add_point(new_pt);
1055
1056 // This was going to be a side refinement but it's
1057 // now an internal refinement
1058 side = invalid_uint;
1059 }
1060 }
1061 else
1062 new_node = add_point(new_pt);
1063 }
1064 else
1065 libmesh_assert(new_node);
1066
1067 // Find the Delaunay cavity around the new point.
1068 std::set<Elem *, decltype(comp)> cavity(comp);
1069
1070 std::set<Elem *, decltype(comp)> unchecked_cavity ({cavity_elem}, comp);
1071 while (!unchecked_cavity.empty())
1072 {
1073 std::set<Elem *, decltype(comp)> checking_cavity(comp);
1074 checking_cavity.swap(unchecked_cavity);
1075 for (Elem * checking_elem : checking_cavity)
1076 {
1077 for (auto s : make_range(3u))
1078 {
1079 Elem * neigh = checking_elem->neighbor_ptr(s);
1080 if (!neigh || checking_cavity.count(neigh) || cavity.count(neigh))
1081 continue;
1082
1083 if (in_circumcircle(*neigh, new_pt, TOLERANCE*TOLERANCE))
1084 unchecked_cavity.insert(neigh);
1085 }
1086 }
1087
1088 libmesh_merge_move(cavity, checking_cavity);
1089 }
1090
1091 // Retriangulate the Delaunay cavity.
1092 // Each of our cavity triangle edges that are exterior to the
1093 // cavity will be a source of one new triangle.
1094
1095 // Set of elements that might need Delaunay swaps
1096 std::set<Elem *, decltype(comp)> check_delaunay_on(comp);
1097
1098 // Keep maps for doing neighbor pointer assignment. Not going
1099 // to iterate through these so hashing pointers is fine.
1100 std::unordered_map<Node *, std::pair<Elem *, boundary_id_type>>
1101 neighbors_CCW, neighbors_CW;
1102
1103 for (Elem * old_elem : cavity)
1104 {
1105 for (auto s : make_range(3u))
1106 {
1107 Elem * neigh = old_elem->neighbor_ptr(s);
1108 if (!neigh || !cavity.count(neigh))
1109 {
1110 Node * node_CW = old_elem->node_ptr(s),
1111 * node_CCW = old_elem->node_ptr((s+1)%3);
1112
1113 auto set_neighbors =
1114 [&neighbors_CW, &neighbors_CCW, &node_CW,
1115 &node_CCW, &boundary_info]
1116 (Elem * new_neigh, boundary_id_type bcid)
1117 {
1118 // Set clockwise neighbor and vice-versa if possible
1119 if (const auto CW_it = neighbors_CW.find(node_CW);
1120 CW_it == neighbors_CW.end())
1121 {
1122 libmesh_assert(!neighbors_CCW.count(node_CW));
1123 neighbors_CCW[node_CW] = std::make_pair(new_neigh, bcid);
1124 }
1125 else
1126 {
1127 Elem * neigh_CW = CW_it->second.first;
1128 if (new_neigh)
1129 {
1130 new_neigh->set_neighbor(0, neigh_CW);
1131 boundary_id_type bcid_CW = CW_it->second.second;
1132 if (bcid_CW != BoundaryInfo::invalid_id)
1133 boundary_info.add_side(new_neigh, 0, bcid_CW);
1134
1135 }
1136 if (neigh_CW)
1137 {
1138 neigh_CW->set_neighbor(2, new_neigh);
1139 if (bcid != BoundaryInfo::invalid_id)
1140 boundary_info.add_side(neigh_CW, 2, bcid);
1141 }
1142 neighbors_CW.erase(CW_it);
1143 }
1144
1145 // Set counter-CW neighbor and vice-versa if possible
1146 if (const auto CCW_it = neighbors_CCW.find(node_CCW);
1147 CCW_it == neighbors_CCW.end())
1148 {
1149 libmesh_assert(!neighbors_CW.count(node_CCW));
1150 neighbors_CW[node_CCW] = std::make_pair(new_neigh, bcid);
1151 }
1152 else
1153 {
1154 Elem * neigh_CCW = CCW_it->second.first;
1155 if (new_neigh)
1156 {
1157 boundary_id_type bcid_CCW = CCW_it->second.second;
1158 new_neigh->set_neighbor(2, neigh_CCW);
1159 if (bcid_CCW != BoundaryInfo::invalid_id)
1160 boundary_info.add_side(new_neigh, 2, bcid_CCW);
1161 }
1162 if (neigh_CCW)
1163 {
1164 neigh_CCW->set_neighbor(0, new_neigh);
1165 if (bcid != BoundaryInfo::invalid_id)
1166 boundary_info.add_side(neigh_CCW, 0, bcid);
1167 }
1168 neighbors_CCW.erase(CCW_it);
1169 }
1170 };
1171
1172 // We aren't going to try to add a sliver element if we
1173 // have a new boundary node here. We do need to
1174 // keep track of other elements' neighbors, though.
1175 if (old_elem == cavity_elem &&
1176 s == side)
1177 {
1178 std::vector<boundary_id_type> bcids;
1179 boundary_info.boundary_ids(old_elem, s, bcids);
1180 libmesh_assert_equal_to(bcids.size(), 1);
1181 set_neighbors(nullptr, bcids[0]);
1182 continue;
1183 }
1184
1185 auto new_elem = Elem::build_with_id(TRI3, ne++);
1186 new_elem->set_node(0, new_node);
1187 new_elem->set_node(1, node_CW);
1188 new_elem->set_node(2, node_CCW);
1189 libmesh_assert(!new_elem->is_flipped());
1190
1191 // Set in-and-out-of-cavity neighbor pointers
1192 new_elem->set_neighbor(1, neigh);
1193 if (neigh)
1194 {
1195 const unsigned int neigh_s =
1196 neigh->which_neighbor_am_i(old_elem);
1197 neigh->set_neighbor(neigh_s, new_elem.get());
1198 }
1199 else
1200 {
1201 std::vector<boundary_id_type> bcids;
1202 boundary_info.boundary_ids(old_elem, s, bcids);
1203 boundary_info.add_side(new_elem.get(), 1, bcids);
1204 }
1205
1206 // Set in-cavity neighbors' neighbor pointers
1207 set_neighbors(new_elem.get(), BoundaryInfo::invalid_id);
1208
1209 // C++ allows function argument evaluation in any
1210 // order, but we need get() to precede move
1211 Elem * new_elem_ptr = new_elem.get();
1212 new_elems.emplace(new_elem_ptr, std::move(new_elem));
1213
1214 check_delaunay_on.insert(new_elem_ptr);
1215 }
1216 }
1217
1218 boundary_info.remove(old_elem);
1219 }
1220
1221 // Now that we're done using our cavity elems (including with a
1222 // cavity.find() that used a comparator that dereferences the
1223 // elements!) it's safe to delete them.
1224 for (Elem * old_elem : cavity)
1225 {
1226 if (const auto it = new_elems.find(old_elem);
1227 it == new_elems.end())
1228 mesh.delete_elem(old_elem);
1229 else
1230 new_elems.erase(it);
1231 }
1232
1233 // Everybody found their match?
1234 libmesh_assert(neighbors_CW.empty());
1235 libmesh_assert(neighbors_CCW.empty());
1236
1237 // Because we're preserving boundaries here, our naive cavity
1238 // triangulation might not be a Delaunay triangulation. Let's
1239 // check and if necessary fix that; we depend on it when doing
1240 // future point insertions.
1241 restore_delaunay(check_delaunay_on, boundary_info);
1242
1243 // This is too expensive to do on every cavity in devel mode
1244#ifdef DEBUG
1245 libmesh_assert_delaunay(mesh, new_elems);
1246#endif
1247 }
1248
1249 // If we added any new boundary nodes, we're going to need to keep
1250 // track of the changes they made to the outer polyline and/or to
1251 // any holes.
1252 if (!next_boundary_node.empty())
1253 {
1254 auto checked_emplace = [this](dof_id_type new_first,
1255 dof_id_type new_second)
1256 {
1257#ifdef DEBUG
1258 for (auto [first, second] : this->segments)
1259 {
1260 libmesh_assert_not_equal_to(first, new_first);
1261 libmesh_assert_not_equal_to(second, new_second);
1262 }
1263 if (!this->segments.empty())
1264 libmesh_assert_equal_to(this->segments.back().second, new_first);
1265#endif
1266 libmesh_assert_not_equal_to(new_first, new_second);
1267
1268 this->segments.emplace_back (new_first, new_second);
1269 };
1270
1271 // Keep track of the outer polyline
1272 if (this->segments.empty())
1273 {
1275
1276 // Custom loop because we increment node_it 1+ times inside
1277 for (auto node_it = _mesh.nodes_begin(),
1278 node_end = _mesh.nodes_end();
1279 node_it != node_end;)
1280 {
1281 Node & node = **node_it;
1282 ++node_it;
1283
1284 const dof_id_type node_id = node.id();
1285
1286 // Don't add Steiner points
1287 if (node_id >= _n_boundary_nodes)
1288 break;
1289
1290 // Connect up the previous node, if we didn't already
1291 // connect it after some newly inserted nodes
1292 if (!this->segments.empty())
1293 last_id = this->segments.back().second;
1294
1295 if (last_id != DofObject::invalid_id &&
1296 last_id != node_id)
1297 checked_emplace(last_id, node_id);
1298
1299 last_id = node_id;
1300
1301 // Connect to any newly inserted nodes
1302 Node * this_node = &node;
1303 auto it = next_boundary_node.find(*this_node);
1304 while (it != next_boundary_node.end())
1305 {
1306 libmesh_assert(this_node->valid_id());
1307 Node * next_node = it->second;
1308 libmesh_assert(next_node->valid_id());
1309
1310 if (node_it != node_end &&
1311 next_node == *node_it)
1312 ++node_it;
1313
1314 checked_emplace(this_node->id(), next_node->id());
1315
1316 this_node = next_node;
1317 if (this_node->id() == this->segments.front().first)
1318 break;
1319
1320 it = next_boundary_node.find(*this_node);
1321 }
1322 }
1323
1324 // We expect a closed loop here
1325 if (this->segments.back().second != this->segments.front().first)
1326 checked_emplace(this->segments.back().second,
1327 this->segments.front().first);
1328 }
1329 else
1330 {
1331 std::vector<std::pair<unsigned int, unsigned int>> old_segments;
1332 old_segments.swap(this->segments);
1333
1334 auto old_it = old_segments.begin();
1335
1336 const Node * node = _mesh.node_ptr(old_it->first);
1337 const Node * const first_node = node;
1338
1339 do
1340 {
1341 const dof_id_type node_id = node->id();
1342 if (const auto it = next_boundary_node.find(*node);
1343 it == next_boundary_node.end())
1344 {
1345 while (node_id != old_it->first)
1346 {
1347 ++old_it;
1348 libmesh_assert(old_it != old_segments.end());
1349 }
1350 node = mesh.node_ptr(old_it->second);
1351 }
1352 else
1353 {
1354 node = it->second;
1355 }
1356
1357 checked_emplace(node_id, node->id());
1358 }
1359 while (node != first_node);
1360 }
1361
1362 // Keep track of any holes
1363 if (this->_holes)
1364 {
1365 // Do we have any holes that need to be newly replaced?
1366 for (const Hole * hole : *this->_holes)
1367 {
1368 if (this->replaced_holes.count(hole))
1369 continue;
1370
1371 bool hole_point_insertion = false;
1372 for (auto p : make_range(hole->n_points()))
1373 if (next_boundary_node.count(hole->point(p)))
1374 {
1375 hole_point_insertion = true;
1376 break;
1377 }
1378 if (hole_point_insertion)
1379 this->replaced_holes.emplace
1380 (hole, std::make_unique<ArbitraryHole>(*hole));
1381 }
1382
1383 // If we have any holes that are being replaced, make sure
1384 // their replacements are up to date.
1385 for (const Hole * hole : *this->_holes)
1386 {
1387 auto hole_it = replaced_holes.find(hole);
1388 if (hole_it == replaced_holes.end())
1389 continue;
1390
1391 ArbitraryHole & arb = *hole_it->second;
1392
1393 // We only need to update a replacement that's just had
1394 // new points inserted
1395 bool point_inserted = false;
1396 for (const Point & point : arb.get_points())
1397 if (next_boundary_node.count(point))
1398 {
1399 point_inserted = true;
1400 break;
1401 }
1402
1403 if (!point_inserted)
1404 continue;
1405
1406 // Find all points in the replacement hole
1407 std::vector<Point> new_points;
1408
1409 // Our outer polyline is expected to have points in
1410 // counter-clockwise order, so it proceeds "to the left"
1411 // from the point of view of rays inside the domain
1412 // pointing outward, and our next_boundary_node ordering
1413 // was filled accordingly.
1414 //
1415 // Our inner holes are expected to have points in
1416 // counter-clockwise order, but for holes "to the left
1417 // as viewed from the hole interior is the *opposite* of
1418 // "to the left as viewed from the domain interior". We
1419 // need to build the updated hole ordering "backwards".
1420
1421 // We should never see duplicate points when we add one
1422 // to a hole; if we do then we did something wrong.
1423 auto push_back_new_point = [&new_points](const Point & p) {
1424 // O(1) assert in devel
1425 libmesh_assert(new_points.empty() ||
1426 new_points.back() != p);
1427#ifdef DEBUG
1428 // O(N) asserts in dbg
1429 for (auto old_p : new_points)
1430 libmesh_assert_not_equal_to(old_p, p);
1431#endif
1432 new_points.push_back(p);
1433 };
1434
1435 for (auto point_it = arb.get_points().rbegin(),
1436 point_end = arb.get_points().rend();
1437 point_it != point_end;)
1438 {
1439 Point point = *point_it;
1440 ++point_it;
1441
1442 if (new_points.empty() ||
1443 (point != new_points.back() &&
1444 point != new_points.front()))
1445 push_back_new_point(point);
1446
1447 auto it = next_boundary_node.find(point);
1448 while (it != next_boundary_node.end())
1449 {
1450 point = *it->second;
1451 if (point == new_points.front())
1452 break;
1453 if (point_it != point_end &&
1454 point == *point_it)
1455 ++point_it;
1456 push_back_new_point(point);
1457 it = next_boundary_node.find(point);
1458 }
1459 }
1460
1461 std::reverse(new_points.begin(), new_points.end());
1462
1463 arb.set_points(std::move(new_points));
1464 }
1465 }
1466 }
1467
1468 // Okay, *now* we can add the new elements.
1469 for (auto & [raw_elem, unique_elem] : new_elems)
1470 {
1471 libmesh_assert_equal_to(raw_elem, unique_elem.get());
1472 libmesh_assert(!raw_elem->is_flipped());
1473 libmesh_ignore(raw_elem); // Old gcc warns "unused variable"
1474 mesh.add_elem(std::move(unique_elem));
1475 }
1476
1477 // Did we add anything?
1478 return !new_elems.empty();
1479}
1480
1481
1483{
1484 const Real min_area_target = this->desired_area();
1486
1487 // If this isn't a question, why are we here?
1488 libmesh_assert(min_area_target > 0 ||
1489 area_func != nullptr ||
1490 this->has_auto_area_function());
1491
1492 const Real area = elem.volume();
1493
1494 // If we don't have position-dependent area targets we can make a
1495 // decision quickly
1496 if (!area_func && !this->has_auto_area_function())
1497 return (area > min_area_target);
1498 else if(area_func && this->has_auto_area_function())
1499 libmesh_warning("WARNING: both desired are function and automatic area function are set. Using automatic area function.");
1500
1501 // If we do?
1502 //
1503 // See if we're meeting the local area target at all the elem
1504 // vertices first
1505 for (auto v : make_range(elem.n_vertices()))
1506 {
1507 // If we have an auto area function, we'll use it and override other area options
1508 const Real local_area_target = (*area_func)(elem.point(v));
1509 libmesh_error_msg_if
1510 (local_area_target <= 0,
1511 "Non-positive desired element areas are unachievable");
1512 if (area > local_area_target)
1513 return true;
1514 }
1515
1516 // If our vertices are happy, it's still possible that our interior
1517 // isn't. Are we allowed not to bother checking it?
1518 if (!min_area_target)
1519 return false;
1520
1521 libmesh_not_implemented_msg
1522 ("Combining a minimum desired_area with an area function isn't yet supported.");
1523}
1524
1525
1526} // namespace libMesh
1527
1528
1529// Unnecessary at end of file, *except* maybe we'll do a unity build
1530// someday, and in the meantime test coverage is nice.
1531#include "libmesh/restore_ieee754.h"
1532
1533
1534#endif // LIBMESH_HAVE_POLY2TRI
The BoundaryInfo class contains information relevant to boundary conditions including storing faces,...
std::size_t n_boundary_ids() const
void boundary_ids(const Node *node, std::vector< boundary_id_type > &vec_to_fill) const
Fills a user-provided std::vector with the boundary ids associated with Node node.
void clear()
Clears the underlying data structures and restores the object to a pristine state with no data stored...
static const boundary_id_type invalid_id
Number used for internal use.
void add_side(const dof_id_type elem, const unsigned short int side, const boundary_id_type id)
Add side side of element number elem with boundary id id to the boundary information data structure.
void remove_side(const Elem *elem, const unsigned short int side)
Removes all boundary conditions associated with side side of element elem, if any exist.
void remove(const Node *node)
Removes the boundary conditions associated with node node, if any exist.
bool valid_id() const
Definition dof_object.h:861
static constexpr dof_id_type invalid_id
An invalid id to distinguish an uninitialized DofObject.
Definition dof_object.h:473
dof_id_type id() const
Definition dof_object.h:819
This is the base class from which all geometric element types are derived.
Definition elem.h:96
virtual Node *& set_node(const unsigned int i)
Definition elem.h:2567
virtual Point quasicircumcenter() const
Definition elem.h:1046
virtual unsigned int n_vertices() const =0
const Point & point(const unsigned int i) const
Definition elem.h:2462
const Node & node_ref(const unsigned int i) const
Definition elem.h:2538
static std::unique_ptr< Elem > build_with_id(const ElemType type, dof_id_type id)
Calls the build() method above with a nullptr parent, and additionally sets the newly-created Elem's ...
Definition elem.C:556
void set_neighbor(const unsigned int i, Elem *n)
Assigns n as the neighbor.
Definition elem.h:2635
unsigned int which_neighbor_am_i(const Elem *e) const
This function tells you which neighbor e is.
Definition elem.h:2936
virtual bool is_flipped() const =0
virtual bool contains_point(const Point &p, Real tol=TOLERANCE) const
Definition elem.C:2784
virtual unsigned short dim() const =0
unsigned int level() const
Definition elem.h:3091
const Node * node_ptr(const unsigned int i) const
Definition elem.h:2516
virtual Real volume() const
Definition elem.C:3462
virtual ElemType type() const =0
virtual unsigned int n_sides() const =0
const Elem * neighbor_ptr(unsigned int i) const
Definition elem.h:2615
Point vertex_average() const
Definition elem.C:669
Base class for functors that can be evaluated at a point and (optionally) time.
virtual std::unique_ptr< FunctionBase< Output > > clone() const =0
This class defines the data structures necessary for Laplace smoothing.
virtual void smooth() override
Redefinition of the smooth function from the base class.
This is the MeshBase class.
Definition mesh_base.h:81
const BoundaryInfo & get_boundary_info() const
The information about boundary ids on the mesh.
Definition mesh_base.h:170
virtual const Node * node_ptr(const dof_id_type i) const =0
void prepare_for_use(const bool skip_renumber_nodes_and_elements, const bool skip_find_neighbors)
Prepare a newly created (or read) mesh for use.
Definition mesh_base.C:824
virtual dof_id_type n_nodes() const =0
virtual void delete_elem(Elem *e)=0
Removes element e from the mesh.
virtual dof_id_type max_node_id() const =0
virtual const Node * query_node_ptr(const dof_id_type i) const =0
void set_mesh_dimension(unsigned char d)
Resets the logical dimension of the mesh.
Definition mesh_base.h:423
virtual Node * add_point(const Point &p, const dof_id_type id=DofObject::invalid_id, const processor_id_type proc_id=DofObject::invalid_processor_id)=0
Add a new Node at Point p to the end of the vertex array, with processor_id procid.
virtual dof_id_type max_elem_id() const =0
virtual void clear_elems()=0
Deletes all the element data that is currently stored.
virtual Elem * add_elem(Elem *e)=0
Add elem e to the end of the element array.
Temporarily serialize a DistributedMesh for non-distributed-mesh capable code paths.
A Node is like a Point, but with more information.
Definition node.h:55
const Parallel::Communicator & comm() const
A Point defines a location in LIBMESH_DIM dimensional Real space.
Definition point.h:40
virtual FunctionBase< Real > * get_desired_area_function() override
Get the function giving desired triangle area as a function of position, or nullptr if no such functi...
virtual void set_desired_area_function(FunctionBase< Real > *desired) override
Set a function giving desired triangle area as a function of position.
bool is_refine_boundary_allowed(const BoundaryInfo &boundary_info, const Elem &elem, unsigned int side)
Is refining this element's boundary side allowed?
std::map< const Hole *, std::unique_ptr< ArbitraryHole > > replaced_holes
We might have to replace the user-provided holes with refined versions.
std::unique_ptr< FunctionBase< Real > > _desired_area_func
Location-dependent area requirements.
virtual void triangulate() override
Internally, this calls the poly2tri triangulation code in a loop, inserting our owner Steiner points ...
virtual ~Poly2TriTriangulator()
Empty destructor.
Poly2TriTriangulator(UnstructuredMesh &mesh, dof_id_type n_boundary_nodes=DofObject::invalid_id)
The constructor.
bool should_refine_elem(Elem &elem)
Returns true if the given element ought to be refined according to current criteria.
virtual bool refine_boundary_allowed() const override
Get whether or not the triangulation is allowed to refine the mesh boundary when refining the interio...
bool insert_refinement_points()
Add Steiner points as new mesh nodes, as necessary to refine an existing trangulation.
void triangulate_current_points()
Triangulate the current mesh and hole points.
dof_id_type _n_boundary_nodes
Keep track of how many mesh nodes are boundary nodes.
Another concrete instantiation of the hole, this one should be sufficiently general for most non-poly...
const std::vector< Point > & get_points() const
void set_points(std::vector< Point > points)
An abstract class for defining a 2-dimensional hole.
virtual bool refine_boundary_allowed() const
Get whether or not the triangulation is allowed to refine the mesh boundary when refining the interio...
virtual Point point(const unsigned int n) const =0
Return the nth point defining the hole.
virtual unsigned int n_points() const =0
The number of geometric points which define the hole.
void increase_triangle_order()
Helper function to upconvert Tri3 to any higher order triangle type if requested via _elem_type.
ElemType _elem_type
The type of elements to generate.
void nodes_to_segments(dof_id_type max_node_id)
Helper function to create PSLG segments from our node ordering, up to the maximum node id,...
void elems_to_segments()
Helper function to create PSLG segments from our other boundary-defining options (1D mesh edges,...
Real & minimum_angle()
Sets and/or gets the minimum desired angle.
bool _smooth_after_generating
Flag which tells whether we should smooth the mesh after it is generated.
void insert_any_extra_boundary_points()
Helper function to add extra points (midpoints of initial segments) to a PSLG triangulation.
@ PSLG
Triangulate the interior of a Planar Straight Line Graph, which is defined implicitly by the order of...
const std::vector< Hole * > * _holes
A pointer to a vector of Hole*s.
const std::vector< int > * _markers
Boundary markers.
std::vector< std::pair< unsigned int, unsigned int > > segments
When constructing a PSLG, if the node numbers do not define the desired boundary segments implicitly ...
bool has_auto_area_function()
Whether or not an auto area function has been set.
const std::vector< Region * > * _regions
A pointer to a vector of Regions*s.
UnstructuredMesh & _mesh
Reference to the mesh which is to be created by triangle.
Real & desired_area()
Sets and/or gets the desired triangle area.
TriangulationType _triangulation_type
The type of triangulation to perform: choices are: convex hull PSLG.
FunctionBase< Real > * get_auto_area_function()
Get the auto area function.
The UnstructuredMesh class is derived from the MeshBase class.
virtual void find_neighbors(const bool reset_remote_elements=false, const bool reset_current_list=true, const bool assert_valid=true) override
Other functions from MeshBase requiring re-definition.
static const Real b
MeshBase & mesh
auto norm(const T &a)
The libMesh namespace provides an interface to certain functionality in the library.
int8_t boundary_id_type
Definition id_types.h:51
void libmesh_ignore(const Args &...)
libmesh_assert(ctx)
const unsigned int invalid_uint
A number which is used quite often to represent an invalid or uninitialized value for an unsigned int...
Definition libmesh.h:303
void libmesh_merge_move(T &target, T &source)
static constexpr Real TOLERANCE
uint8_t dof_id_type
Definition id_types.h:67
TypeVector< T > circumcenter(const TypeVector< T > &p0, const TypeVector< T > &p1, const TypeVector< T > &p2)
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
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:176
const Real radius