Skip to content

Commit 2e35f68

Browse files
authored
Merge pull request #3339 from roystgnr/poly2tri_ui
Poly2tri error reporting, feature add, refinement fixes
2 parents 2fbc438 + 886eb49 commit 2e35f68

File tree

6 files changed

+334
-62
lines changed

6 files changed

+334
-62
lines changed

include/mesh/mesh_triangle_holes.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -288,9 +288,9 @@ class TriangulatorInterface::ArbitraryHole : public TriangulatorInterface::Hole
288288
* edges in a subdomain with a listed id will define the hole.
289289
*
290290
* If no ids are given, the hole will be defined by all 1D Edge
291-
* elements and all 2D boundary edges.
291+
* elements and all outward-facing 2D boundary edges.
292292
*
293-
* In either case the, hole definition should give a single connected
293+
* In either case, the hole definition should give a single connected
294294
* boundary, topologically a circle. The hole is defined when the
295295
* MeshedHole is constructed, and ignores any subsequent changes to
296296
* the input mesh.

include/mesh/triangulator_interface.h

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
#include "libmesh/libmesh.h"
2727

2828
// C++ includes
29+
#include <set>
2930
#include <vector>
3031

3132
namespace libMesh
@@ -231,6 +232,15 @@ class TriangulatorInterface
231232
*/
232233
void attach_region_list(const std::vector<Region*> * regions) { _regions = regions; }
233234

235+
/**
236+
* A set of ids to allow on the outer boundary loop: interpreted as
237+
* boundary ids of 2D elements and/or subdomain ids of 1D edges. If
238+
* this is empty, then the outer boundary may be constructed from
239+
* boundary edges of any id!
240+
*/
241+
void set_outer_boundary_ids(std::set<std::size_t> bdy_ids) { _bdy_ids = std::move(bdy_ids); }
242+
const std::set<std::size_t> & get_outer_boundary_ids() const { return _bdy_ids; }
243+
234244
protected:
235245
/**
236246
* Helper function to create PSLG segments from our other
@@ -278,6 +288,11 @@ class TriangulatorInterface
278288
*/
279289
const std::vector<Region*> * _regions;
280290

291+
/**
292+
* A set of ids to allow on the outer boundary loop.
293+
*/
294+
std::set<std::size_t> _bdy_ids;
295+
281296
/**
282297
* The type of elements to generate. (Defaults to
283298
* TRI3).

src/mesh/mesh_triangle_holes.C

Lines changed: 118 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -266,16 +266,34 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
266266
MeshSerializer serial(const_cast<MeshBase &>(mesh),
267267
/* serial */ true, /* only proc 0 */ true);
268268

269+
// Try to keep in sync even if we throw an error on proc 0, so we
270+
// can examine errors in our unit tests in parallel too.
271+
std::string error_reported;
272+
273+
auto report_error = [&mesh, &error_reported](std::string er) {
274+
error_reported = std::move(er);
275+
mesh.comm().broadcast(error_reported);
276+
libmesh_error_msg(error_reported);
277+
};
278+
269279
if (mesh.processor_id() != 0)
270280
{
271-
// Receive what proc 0 will send later
281+
// Make sure proc 0 didn't just fail
282+
mesh.comm().broadcast(error_reported);
283+
libmesh_error_msg_if(!error_reported.empty(), error_reported);
284+
285+
// Receive the points proc 0 will send later
272286
mesh.comm().broadcast(_points);
273287
return;
274288
}
275289

276290
// We'll find all the line segments first, then stitch them together
277-
// afterward
278-
std::multimap<const Node *, const Node *> hole_edge_map;
291+
// afterward. If the line segments come from 2D element sides then
292+
// we'll label their edge_type as "1" for clockwise orientation
293+
// around the element or "2" for CCW, to make it easier to detect
294+
// and scream about cases where we have a disconnected outer
295+
// boundary.
296+
std::multimap<const Node *, std::pair<const Node *, int>> hole_edge_map;
279297

280298
std::vector<boundary_id_type> bcids;
281299

@@ -288,9 +306,11 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
288306
if (ids.empty() || ids.count(elem->subdomain_id()))
289307
{
290308
hole_edge_map.emplace(elem->node_ptr(0),
291-
elem->node_ptr(1));
309+
std::make_pair(elem->node_ptr(1),
310+
/*edge*/ 0));
292311
hole_edge_map.emplace(elem->node_ptr(1),
293-
elem->node_ptr(0));
312+
std::make_pair(elem->node_ptr(0),
313+
/*edge*/ 0));
294314
}
295315
continue;
296316
}
@@ -314,30 +334,35 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
314334
if (add_edge)
315335
{
316336
hole_edge_map.emplace(elem->node_ptr(s),
317-
elem->node_ptr((s+1)%ns));
337+
std::make_pair(elem->node_ptr((s+1)%ns),
338+
/*counter-CW*/ 2));
318339
// Do we really need to support flipped 2D elements?
319340
hole_edge_map.emplace(elem->node_ptr((s+1)%ns),
320-
elem->node_ptr(s));
341+
std::make_pair(elem->node_ptr(s),
342+
/*clockwise*/ 1));
321343
continue;
322344
}
323345
}
324346
}
325347
}
326348

327-
libmesh_error_msg_if
328-
(hole_edge_map.empty(),
329-
"No valid hole edges found in mesh!");
349+
if (hole_edge_map.empty())
350+
report_error("No valid hole edges found in mesh!");
330351

331352
// Function to pull a vector of points out of the map; a loop of
332353
// edges connecting these points defines a hole boundary. If the
333354
// mesh has multiple boundaries (e.g. because it had holes itself),
334355
// then a random vector will be extracted; this function will be
335356
// called multiple times so that the various options can be
336-
// compared.
337-
auto extract_edge_vector = [&hole_edge_map]() {
357+
// compared. We choose the largest option.
358+
auto extract_edge_vector = [&report_error, &hole_edge_map]() {
338359
// Start with any edge
339-
std::vector<const Node *> hole_points
340-
{hole_edge_map.begin()->first, hole_edge_map.begin()->second};
360+
std::pair<std::vector<const Node *>, int> hole_points_and_edge_type
361+
{{hole_edge_map.begin()->first, hole_edge_map.begin()->second.first},
362+
hole_edge_map.begin()->second.second};
363+
364+
int & edge_type = hole_points_and_edge_type.second;
365+
auto & hole_points = hole_points_and_edge_type.first;
341366

342367
// We won't be needing to search for this edge
343368
hole_edge_map.erase(hole_points.front());
@@ -351,19 +376,31 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
351376
{
352377
auto [next_it_begin, next_it_end] = hole_edge_map.equal_range(n);
353378

354-
libmesh_error_msg_if
355-
(std::distance(next_it_begin, next_it_end) != 2,
356-
"Bad edge topology in MeshedHole");
379+
if (std::distance(next_it_begin, next_it_end) != 2)
380+
report_error("Bad edge topology found by MeshedHole");
357381

358382
const Node * next = nullptr;
359-
for (const auto [key, val] : as_range(next_it_begin, next_it_end))
383+
for (const auto & [key, val] : as_range(next_it_begin, next_it_end))
360384
{
361385
libmesh_assert_equal_to(key, n);
362386
libmesh_ignore(key);
363-
libmesh_assert_not_equal_to(val, n);
364-
if (val == last)
387+
libmesh_assert_not_equal_to(val.first, n);
388+
389+
// Don't go backwards on the edge we just traversed
390+
if (val.first == last)
365391
continue;
366-
next = val;
392+
393+
// We can support mixes of Edge and Tri-side edges, but we
394+
// can't do proper error detection on flipped triangles.
395+
if (val.second != edge_type &&
396+
val.second != 0)
397+
{
398+
if (!edge_type)
399+
edge_type = val.second;
400+
else
401+
report_error("MeshedHole sees inconsistent triangle orientations on boundary");
402+
}
403+
next = val.first;
367404
}
368405

369406
// We should never hit the same n twice!
@@ -374,17 +411,38 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
374411

375412
hole_points.pop_back();
376413

377-
return hole_points;
414+
return hole_points_and_edge_type;
378415
};
379416

417+
/*
418+
* If it's not obvious which loop we find is really the loop we
419+
* want, then we should die with a nice error message.
420+
*/
421+
int n_negative_areas = 0,
422+
n_positive_areas = 0,
423+
n_edgeelem_loops = 0;
424+
380425
std::vector<const Node *> outer_hole_points;
381-
Real twice_outer_area = 0;
426+
int outer_edge_type = -1;
427+
Real twice_outer_area = 0,
428+
abs_twice_outer_area = 0;
429+
382430
while (!hole_edge_map.empty()) {
383-
std::vector<const Node *> hole_points = extract_edge_vector();
431+
auto [hole_points, edge_type] = extract_edge_vector();
432+
433+
if (edge_type == 0)
434+
{
435+
++n_edgeelem_loops;
436+
if (n_edgeelem_loops > 1)
437+
report_error("MeshedHole is confused by multiple loops of Edge elements");
438+
if (n_positive_areas || n_negative_areas)
439+
report_error("MeshedHole is confused by meshes with both Edge and 2D-side boundaries");
440+
}
441+
384442
const std::size_t n_hole_points = hole_points.size();
385-
libmesh_error_msg_if
386-
(n_hole_points < 3, "Loop with only " << n_hole_points <<
387-
" hole edges found in mesh!");
443+
if (n_hole_points < 3)
444+
report_error("Loop with only " + std::to_string(n_hole_points) +
445+
" hole edges found in mesh!");
388446

389447
Real twice_this_area = 0;
390448
const Point p0 = *hole_points[0];
@@ -396,10 +454,20 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
396454
twice_this_area += e_0im.cross(e_0i)(2);
397455
}
398456

399-
if (std::abs(twice_this_area) > std::abs(twice_outer_area))
457+
auto abs_twice_this_area = std::abs(twice_this_area);
458+
459+
if (((abs_twice_this_area == twice_this_area) && edge_type == 2) ||
460+
(edge_type == 1))
461+
++n_positive_areas;
462+
else
463+
++n_negative_areas;
464+
465+
if (abs_twice_this_area > abs_twice_outer_area)
400466
{
401467
twice_outer_area = twice_this_area;
468+
abs_twice_outer_area = abs_twice_this_area;
402469
outer_hole_points = std::move(hole_points);
470+
outer_edge_type = edge_type;
403471
}
404472
}
405473

@@ -409,15 +477,34 @@ TriangulatorInterface::MeshedHole::MeshedHole(const MeshBase & mesh,
409477
_points.begin(),
410478
[](const Node * n){ return Point(*n); });
411479

412-
libmesh_error_msg_if
413-
(!twice_outer_area,
414-
"Zero-area MeshedHoles are not currently supported");
480+
if (!twice_outer_area)
481+
report_error("Zero-area MeshedHoles are not currently supported");
415482

416483
// We ordered ourselves counter-clockwise? But a hole is expected
417484
// to be clockwise, so use the reverse order.
418485
if (twice_outer_area > 0)
419486
std::reverse(_points.begin(), _points.end());
420487

488+
if (((twice_outer_area > 0) && outer_edge_type == 2) ||
489+
outer_edge_type == 1)
490+
{
491+
if (n_positive_areas > 1)
492+
report_error("MeshedHole found " +
493+
std::to_string(n_positive_areas) +
494+
" counter-clockwise boundaries and cannot choose one!");
495+
496+
}
497+
else if (outer_edge_type != 0)
498+
{
499+
if (n_negative_areas > 1)
500+
report_error("MeshedHole found " +
501+
std::to_string(n_positive_areas) +
502+
" clockwise boundaries and cannot choose one!");
503+
504+
}
505+
506+
// Hey, no errors! Broadcast that empty string.
507+
mesh.comm().broadcast(error_reported);
421508
mesh.comm().broadcast(_points);
422509
}
423510

0 commit comments

Comments
 (0)