Skip to content

Commit 3607fca

Browse files
committed
Try to fix hull problems, not just diagnose them
1 parent 3b36a9c commit 3607fca

2 files changed

Lines changed: 146 additions & 0 deletions

File tree

include/mesh/mesh_tet_interface.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -130,6 +130,16 @@ class MeshTetInterface
130130
*/
131131
[[nodiscard]] std::set<SurfaceIntegrity> check_hull_integrity() const;
132132

133+
/**
134+
* This function checks the integrity of the current set of
135+
* elements in the Mesh, and corrects what it can.
136+
*
137+
* \returns A set of SurfaceIntegrity codes from \p
138+
* check_hull_integrity() if there are problems it can't fix, or an
139+
* empty set otherwise.
140+
*/
141+
[[nodiscard]] std::set<SurfaceIntegrity> improve_hull_integrity();
142+
133143
/**
134144
* This function prints an informative message and throws an
135145
* exception based on the output of the check_hull_integrity()

src/mesh/mesh_tet_interface.C

Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -408,6 +408,142 @@ std::set<MeshTetInterface::SurfaceIntegrity> MeshTetInterface::check_hull_integr
408408

409409

410410

411+
std::set<MeshTetInterface::SurfaceIntegrity> MeshTetInterface::improve_hull_integrity()
412+
{
413+
// We don't really do anything parallel here, but we aspire to.
414+
libmesh_parallel_only(this->_mesh.comm());
415+
416+
std::set<MeshTetInterface::SurfaceIntegrity> integrityproblems =
417+
this->check_hull_integrity();
418+
419+
// If we have no problem, or a problem we can't fix, we're done.
420+
if (integrityproblems.empty() ||
421+
integrityproblems.count(NON_TRI3) ||
422+
integrityproblems.count(EMPTY_MESH))
423+
return integrityproblems;
424+
425+
// Possibly the user gave us an unprepared mesh with missing or bad
426+
// neighbor links?
427+
if (integrityproblems.count(MISSING_NEIGHBOR) ||
428+
integrityproblems.count(MISSING_BACKLINK) ||
429+
integrityproblems.count(BAD_NEIGHBOR_LINKS))
430+
{
431+
this->_mesh.find_neighbors();
432+
integrityproblems = this->check_hull_integrity();
433+
}
434+
435+
// If find_neighbors() doesn't fix these, I give up.
436+
if (integrityproblems.count(MISSING_NEIGHBOR) ||
437+
integrityproblems.count(MISSING_BACKLINK) ||
438+
integrityproblems.count(BAD_NEIGHBOR_LINKS))
439+
return integrityproblems;
440+
441+
// find_neighbors() might have fixed everything
442+
if (integrityproblems.empty())
443+
return integrityproblems;
444+
445+
// A non-oriented (but orientable!) surface is the only thing we
446+
// shouldn't have fixed or given up on by now.
447+
libmesh_assert_equal_to(integrityproblems.size(), 1);
448+
libmesh_assert_equal_to(integrityproblems.count(NON_ORIENTED), 1);
449+
450+
// We need one known-good triangle to start from. We'll pick the
451+
// most-negative-x normal among the triangles on the most-negative-x
452+
// point.
453+
454+
// We'll just implement this in serial for now.
455+
MeshSerializer mesh_serializer(this->_mesh);
456+
457+
const Node * lowest_point = (*this->_mesh.elements_begin())->node_ptr(0);
458+
459+
// Index by ids, not pointers, for consistency in parallel
460+
std::unordered_set<dof_id_type> attached_elements;
461+
462+
for (Elem * elem : this->_mesh.element_ptr_range())
463+
{
464+
for (const Node & node : elem->node_ref_range())
465+
{
466+
if (node(0) < (*lowest_point)(0))
467+
{
468+
lowest_point = &node;
469+
attached_elements.clear();
470+
}
471+
if (&node == lowest_point)
472+
attached_elements.insert(elem->id());
473+
}
474+
}
475+
476+
Elem * best_elem = nullptr;
477+
Real best_abs_normal_0;
478+
479+
for (dof_id_type id : attached_elements)
480+
{
481+
Elem * elem = this->_mesh.elem_ptr(id);
482+
const Point e01 = elem->point(1) - elem->point(0);
483+
const Point e02 = elem->point(2) - elem->point(0);
484+
const Point normal = e01.cross(e02).unit();
485+
const Real abs_normal_0 = std::abs(normal(0));
486+
487+
if (!best_elem || abs_normal_0 > best_abs_normal_0)
488+
{
489+
best_elem = elem;
490+
best_abs_normal_0 = abs_normal_0;
491+
}
492+
}
493+
494+
// Now flood-fill from that element to get a consistent orientation
495+
// for the others.
496+
std::unordered_set<dof_id_type> frontier_elements{best_elem->id()},
497+
finished_elements{};
498+
499+
BoundaryInfo & bi = this->_mesh.get_boundary_info();
500+
501+
while (!frontier_elements.empty())
502+
{
503+
const dof_id_type elem_id = *frontier_elements.begin();
504+
Elem & elem = this->_mesh.elem_ref(elem_id);
505+
for (auto s : elem.side_index_range())
506+
{
507+
Elem * neigh = elem.neighbor_ptr(s);
508+
libmesh_assert(neigh);
509+
libmesh_assert_less(neigh->which_neighbor_am_i(&elem), 3);
510+
511+
const Node * const n1 = elem.node_ptr(s);
512+
const Node * const n2 = elem.node_ptr((s+1)%3);
513+
const unsigned int i1 = neigh->local_node(n1->id());
514+
const unsigned int i2 = neigh->local_node(n2->id());
515+
libmesh_assert_less(i1, 3);
516+
libmesh_assert_less(i2, 3);
517+
518+
const dof_id_type neigh_id = neigh->id();
519+
520+
const bool frontier_neigh = frontier_elements.count(neigh_id);
521+
const bool finished_neigh = finished_elements.count(neigh_id);
522+
523+
// Are we flipped?
524+
if ((i2 + 1)%3 != i1)
525+
{
526+
// Are we a Moebius strip??? We give up.
527+
if (frontier_neigh || finished_neigh)
528+
return integrityproblems;
529+
530+
neigh->flip(&bi);
531+
}
532+
533+
if (!frontier_neigh && !finished_neigh)
534+
frontier_elements.insert(neigh_id);
535+
}
536+
537+
finished_elements.insert(elem_id);
538+
frontier_elements.erase(elem_id);
539+
}
540+
541+
this->_mesh.find_neighbors();
542+
543+
libmesh_assert(this->check_hull_integrity().empty());
544+
545+
return {};
546+
}
411547

412548

413549
void MeshTetInterface::process_hull_integrity_result

0 commit comments

Comments
 (0)