@@ -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
413549void MeshTetInterface ::process_hull_integrity_result
0 commit comments