Skip to content

Commit 45a4142

Browse files
committed
Add heuristics to tetrahedralize hexahedrons with high valence polygons on two sides
that arise from extrusion - dont create a 0 vol elements if we have 3 surrounding nodes and everybody is coplanar - try to avoid this situation by improving as many potential 0-volume neighbor tets as possible when selecting which tet to construct
1 parent e683adf commit 45a4142

File tree

2 files changed

+105
-55
lines changed

2 files changed

+105
-55
lines changed

src/geom/cell_c0polyhedron.C

Lines changed: 103 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -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

src/geom/cell_polyhedron.C

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ Polyhedron::Polyhedron (const std::vector<std::shared_ptr<Polygon>> & sides,
115115
{
116116
const Polygon & side = *sides[s];
117117
const Point x_i = side.point(0);
118+
// opposite of normal
118119
const Point n_i =
119120
(side.point(1) - side.point(0)).cross
120121
(side.point(0) - side.point(side.n_sides()-1)).unit();
@@ -123,7 +124,7 @@ Polyhedron::Polyhedron (const std::vector<std::shared_ptr<Polygon>> & sides,
123124
inward_normal = (n_i * (center - x_i) > TOLERANCE);
124125
}
125126

126-
// We're betting a lot on "our polyhedra are all convex", so let's
127+
// We're betting a lot on "our polygon sides are all convex", so let's
127128
// check that if we have time.
128129
#ifdef DEBUG
129130
for (unsigned int s : index_range(sides))

0 commit comments

Comments
 (0)