@@ -373,7 +373,7 @@ void C0Polyhedron::retriangulate()
373373 // sometimes with a similarly-sorted vector of surrounding elements
374374 // to go with it.
375375 auto surroundings_of =
376- [& nodes_to_elem_map , & surface ]
376+ [& nodes_to_elem_map , & surface ]
377377 (const Node & node ,
378378 std ::vector < Elem * > * surrounding_elems )
379379 {
@@ -439,7 +439,11 @@ void C0Polyhedron::retriangulate()
439439 v02 = static_cast < Point > (* surrounding_nodes [n + 1 ]) - node ,
440440 v03 = static_cast < Point > (* surrounding_nodes [n + 2 ]) - node ;
441441
442- total_solid_angle += solid_angle (v01 , v02 , v03 );
442+ // Solid angles are all negative because the surface triangulation triangles were pointing
443+ // outwards and the 4th node is inward because the polyhedron is convex
444+ // Some are zero when all the nodes selected are from the same (planar) polygon
445+ // Make it positive to store smallest angle first
446+ total_solid_angle += std ::abs (solid_angle (v01 , v02 , v03 ));
443447 }
444448
445449 return std ::make_pair (n_surrounding , total_solid_angle );
@@ -456,8 +460,11 @@ void C0Polyhedron::retriangulate()
456460 // in parallel.
457461 typedef std ::multimap < std ::pair < int , Real > , Node * > node_map_type ;
458462 node_map_type nodes_by_geometry ;
463+ // Keep track of index in map
459464 std ::map < Node * , node_map_type ::iterator > node_index ;
460465
466+ // The map is first sorted by the valence
467+ // then by the angle. Max angle, if they are negative (positive right now), first
461468 for (auto node : surface .node_ptr_range ())
462469 node_index [node ] =
463470 nodes_by_geometry .emplace (geometry_at (* node ), node );
@@ -466,6 +473,9 @@ void C0Polyhedron::retriangulate()
466473 // each vertex, and an inner loop to remove multiple tetrahedra in
467474 // cases where the vertex has more than 3 neighboring triangles.
468475
476+ // One of the checks does not work if we skip adding a 0 volume tet
477+ bool has_skipped_adding_tets_and_retriangulating = false;
478+
469479 // We'll be done when there are only three "unremoved" nodes left,
470480 // so they don't actually enclose any volume.
471481 for (auto i : make_range (nodes_by_geometry .size ()- 3 ))
@@ -525,8 +535,9 @@ void C0Polyhedron::retriangulate()
525535 std ::vector < Real > local_tet_quality (n_surrounding , 1 );
526536
527537 // From our center node with N surrounding nodes we can make N-2
528- // tetrahedra. The first N-3 each replace two surface tets with
529- // two new surface tets.
538+ // tetrahedra.
539+ // For the first N-3 tets we build, we may have to replace two surface
540+ // triangles with two new surface triangles.
530541 //
531542 // My first idea was to greedily pick nodes with the smallest
532543 // local (solid) angles to get the best quality. This works in
@@ -539,15 +550,28 @@ void C0Polyhedron::retriangulate()
539550 // My third idea is to try to fix the lowest quality tets first,
540551 // by picking cases where they have higher quality neighbors,
541552 // and creating those neighbors so as to change them.
553+ //
554+ // A fourth idea was to make sure to fix as many neighbor tets as
555+ // possible.
542556
543557 auto find_new_tet_nodes =
544- [& local_tet_quality , & find_valid_nodes_around ]
558+ [& local_tet_quality , & find_valid_nodes_around , & surrounding_nodes ]
545559 ()
546560 {
547561 unsigned int jbest = 0 ;
548562 auto [jminus , jplus ] = find_valid_nodes_around (jbest );
549563 Real qneighbest = std ::min (local_tet_quality [jminus ],
550564 local_tet_quality [jplus ]);
565+ // Count number of bad neighbors
566+ auto num_bad_neigh_best = (local_tet_quality [jminus ] <= 0 ) + (local_tet_quality [jplus ] <= 0 );
567+
568+ // Count the number of valid surrounding nodes
569+ unsigned int n_valid_surrounding = 0 ;
570+ for (const auto surr_node : surrounding_nodes )
571+ if (surr_node )
572+ n_valid_surrounding ++ ;
573+
574+ // Expected qualities post removing jbest from the surrounding nodes
551575 for (auto j : make_range (std ::size_t (1 ),
552576 local_tet_quality .size ()))
553577 {
@@ -565,77 +589,88 @@ void C0Polyhedron::retriangulate()
565589 qneighj > 0 )
566590 continue ;
567591
592+ const auto num_bad_neigh = (local_tet_quality [jminus ] <= 0 ) +
593+ (local_tet_quality [jplus ] <= 0 );
568594 // We want to try for the best possible fix.
569- if ((local_tet_quality [j ] - qneighj ) >
570- (local_tet_quality [jbest ] - qneighj ))
595+ // - The more bad (volume <= 0) neighbors we fix the better
596+ // - If same number of bad neighbors, pick best quality one
597+ if ((num_bad_neigh >= num_bad_neigh_best ) &&
598+ ((local_tet_quality [j ] - qneighj ) > (local_tet_quality [jbest ] - qneighj )))
571599 {
572600 jbest = j ;
573601 qneighbest = qneighj ;
602+ num_bad_neigh_best = num_bad_neigh ;
574603 }
575604 }
576605
606+ // If there are only 3 neighors and the tet is bad, we will skip it downstream
577607 libmesh_error_msg_if
578- (local_tet_quality [jbest ] <= 0 ,
608+ (n_valid_surrounding > 3 && local_tet_quality [jbest ] <= 0 ,
579609 "Cannot build non-singular non-inverted tet" );
580610
581611 std ::tie (jminus , jplus ) = find_valid_nodes_around (jbest );
582612
583613 return std ::make_tuple (jbest , jminus , jplus );
584614 };
585615
586- if (n_surrounding > 3 )
587- {
588- // We'll be searching local_tet_quality even after tet
589- // extraction disconnects us from some nodes; when we do we
590- // don't want to get one.
591- constexpr Real far_node = -1e6 ;
592-
593- // Vectors from the center node to each of its surrounding
594- // nodes are helpful for calculating prospective tet
595- // quality.
596- std ::vector < Point > v0s (n_surrounding );
597- for (auto j : make_range (n_surrounding ))
598- v0s [j ] = * (Point * )surrounding_nodes [j ] - * node ;
599-
600- // Find the tet quality we'd potentially get from each
601- // possible choice of tet
602- auto local_tet_quality_of =
603- [& surrounding_nodes , & v0s , & find_valid_nodes_around ]
604- (unsigned int j )
605- {
606- auto [jminus , jplus ] = find_valid_nodes_around (j );
607-
608- // Anything proportional to the ratio of volume to
609- // total-edge-length-cubed should peak for perfect tets
610- // but hit 0 for pancakes and slivers.
611-
612- const Real total_len =
613- v0s [j ].norm () + v0s [jminus ].norm () + v0s [jplus ].norm () +
614- (* (Point * )surrounding_nodes [jplus ] -
615- * (Point * )surrounding_nodes [j ]).norm () +
616- (* (Point * )surrounding_nodes [j ] -
617- * (Point * )surrounding_nodes [jminus ]).norm () +
618- (* (Point * )surrounding_nodes [jminus ] -
619- * (Point * )surrounding_nodes [jplus ]).norm ();
620-
621- // Orientation here is tricky. Think of the triple
622- // product as (v1 cross v2) dot v3, with right hand rule.
623- const Real six_vol =
624- triple_product (v0s [jminus ], v0s [jplus ], v0s [j ]);
616+ // Vectors from the center node to each of its surrounding
617+ // nodes are helpful for calculating prospective tet
618+ // quality.
619+ std ::vector < Point > v0s (n_surrounding );
620+ for (auto j : make_range (n_surrounding ))
621+ v0s [j ] = * (Point * )surrounding_nodes [j ] - * node ;
622+
623+ // Find the tet quality we'd potentially get from each
624+ // possible choice of tet
625+ auto local_tet_quality_of =
626+ [& surrounding_nodes , & v0s , & find_valid_nodes_around ]
627+ (unsigned int j )
628+ {
629+ auto [jminus , jplus ] = find_valid_nodes_around (j );
630+
631+ // Anything proportional to the ratio of volume to
632+ // total-edge-length-cubed should peak for perfect tets
633+ // but hit 0 for pancakes and slivers.
634+
635+ const Real total_len =
636+ v0s [j ].norm () + v0s [jminus ].norm () + v0s [jplus ].norm () +
637+ (* (Point * )surrounding_nodes [jplus ] -
638+ * (Point * )surrounding_nodes [j ]).norm () +
639+ (* (Point * )surrounding_nodes [j ] -
640+ * (Point * )surrounding_nodes [jminus ]).norm () +
641+ (* (Point * )surrounding_nodes [jminus ] -
642+ * (Point * )surrounding_nodes [jplus ]).norm ();
643+
644+ // Orientation here is tricky. Think of the triple
645+ // product as (v1 cross v2) dot v3, with right hand rule.
646+ const Real six_vol =
647+ triple_product (v0s [jminus ], v0s [jplus ], v0s [j ]);
648+
649+ return six_vol / (total_len * total_len * total_len );
650+ };
625651
626- return six_vol / (total_len * total_len * total_len );
627- };
652+ // Get the quality of the tets for every node around the node:
653+ // we have chosen 1 node. If we chose another node for the tet, there
654+ // are only two other nodes that are also neighbors, hence 1 tet per node
655+ // Note: we always need to know the quality, because as we delete vertices
656+ // we could end up with a a node with 3 surrounding nodes, but all coplanar!
657+ for (auto j : make_range (n_surrounding ))
658+ local_tet_quality [j ] = local_tet_quality_of (j );
628659
629- for (auto j : make_range (n_surrounding ))
630- local_tet_quality [j ] = local_tet_quality_of (j );
660+ // We'll be searching local_tet_quality even after tet
661+ // extraction disconnects us from some nodes; when we do we
662+ // don't want to re-use it so we mark the quality as -1e6
663+ constexpr Real far_node = -1e6 ;
631664
632- // If we have N surrounding nodes, we can make N tets and
665+ if (n_surrounding > 3 )
666+ {
667+ // If we have N surrounding nodes, we can make N-3 tets and
633668 // that'll bring us back to the 3-surrounding-node case to
634669 // finish.
635670 for (auto t : make_range (n_surrounding - 3 ))
636671 {
637672 libmesh_ignore (t );
638-
673+
639674 auto [jbest , jminus , jplus ] = find_new_tet_nodes ();
640675
641676 // Turn these four nodes into a tet
@@ -697,6 +732,7 @@ void C0Polyhedron::retriangulate()
697732 // unchanged until we're out of this inner loop; let's
698733 // recalculate it here, and then we'll be done with it.
699734 Node * & nbestref = surrounding_nodes [jbest ];
735+ libmesh_assert (nbestref );
700736 nodes_by_geometry .erase (node_index [nbestref ]);
701737 node_index [nbestref ] =
702738 nodes_by_geometry .emplace (geometry_at (* nbestref ), nbestref );
@@ -724,6 +760,18 @@ void C0Polyhedron::retriangulate()
724760 // counterclockwise" we get from the lambda.
725761 auto [j2 , j1 , j3 ] = find_new_tet_nodes ();
726762
763+ // If the final tet has zero volume, skip, we have covered all we need for this node
764+ // This occurs when considering a polygonal prism.
765+ // The opposite node on the other extruded face was deleted first
766+ // and depending on the triangulation, this leaves the node only attached
767+ // to 3 other coplanar nodes on one of the side
768+ if (local_tet_quality [j2 ] == 0 || local_tet_quality [j2 ] == far_node )
769+ {
770+ nodes_by_geometry .erase (geometry_it );
771+ has_skipped_adding_tets_and_retriangulating = true;
772+ continue ;
773+ }
774+
727775 // Turn these last four nodes into a tet
728776 Node * n1 = surrounding_nodes [j1 ],
729777 * n2 = surrounding_nodes [j2 ],
@@ -793,7 +841,8 @@ void C0Polyhedron::retriangulate()
793841 }
794842
795843 // At this point our surface should just have two triangles left.
796- libmesh_assert_equal_to (surface .n_elem (), 2 );
844+ if (!has_skipped_adding_tets_and_retriangulating )
845+ libmesh_assert_equal_to (surface .n_elem (), 2 );
797846}
798847
799848
0 commit comments