https://mooseframework.inl.gov
Classes | Public Member Functions | Private Member Functions | Private Attributes | Friends | List of all members
MortarSegmentHelper Class Reference

This class supports defining mortar segment mesh elements in 3D by projecting secondary and primary elements onto a linearized plane, computing the overlapping polygon formed by their projections, and triangulating the resulting nodes. More...

#include <MortarSegmentHelper.h>

Classes

struct  ReferenceMappingData
 Output containers and filtering data used while generating reference-coordinate mappings. More...
 

Public Member Functions

 MortarSegmentHelper (std::vector< Point > secondary_nodes, const Point &center, const Point &normal, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles)
 Construct a helper that generates mortar segment geometry only. More...
 
 MortarSegmentHelper (std::vector< Point > secondary_nodes, std::vector< Point > secondary_reference_points, const Point &center, const Point &normal, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles)
 Construct a helper that also tracks secondary parent-reference coordinates. More...
 
Point getIntersection (const Point &p1, const Point &p2, const Point &q1, const Point &q2, Real &s) const
 Computes the intersection between line segments defined by point pairs (p1,p2) and (q1,q2) Also computes s, the ratio of distance between (p1,p2) that the intersection falls, quantity s is useful in avoiding adding nearly degenerate nodes. More...
 
bool isInsideSecondary (const Point &pt) const
 Check that a point is inside the secondary polygon (for verification only) More...
 
bool isDisjoint (const std::vector< Point > &poly) const
 Checks whether polygons are disjoint for an easy out. More...
 
std::vector< PointprojectPrimaryPoly (const std::vector< Point > &primary_nodes) const
 Project a primary polygon into the helper plane while preserving the clipping orientation. More...
 
std::vector< PointclipPoly (const std::vector< Point > &primary_nodes) const
 Clip secondary element (defined in instantiation) against given primary polygon result is a set of 2D nodes defining clipped polygon. More...
 
void triangulatePoly (std::vector< Point > &poly_nodes, std::vector< std::vector< unsigned int >> &tri_map) const
 Triangulate a polygon according to the configured mortar-segment triangulation mode. More...
 
void getMortarSegments (const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes)
 Get mortar segments generated by a secondary and primary element pair. More...
 
void getMortarSegments (const std::vector< Point > &primary_nodes, const std::vector< Point > &primary_reference_points, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes, std::vector< std::array< Point, 3 >> &elem_to_secondary_reference_points, std::vector< std::array< Point, 3 >> &elem_to_primary_reference_points, Real minimum_segment_area=0.)
 Get mortar segments and aligned parent-reference coordinates by appending to the output containers. More...
 
Real area (const std::vector< Point > &nodes) const
 Compute area of polygon. More...
 
const Pointcenter () const
 Get center point of secondary element. More...
 
const Pointnormal () const
 Get the unit normal of the projection plane. More...
 
Real remainder () const
 Get area fraction remaining after clipping against primary elements. More...
 
Point point (unsigned int i) const
 Get 3D position of node of linearized secondary element. More...
 

Private Member Functions

void getMortarSegmentsImpl (const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes, ReferenceMappingData *reference_mapping)
 
std::vector< PointclipProjectedPoly (const std::vector< Point > &primary_poly) const
 Clip an already projected primary polygon against the secondary polygon. More...
 
std::optional< PointreferencePoint (const Point &point, const std::vector< Point > &poly, const std::vector< Point > &reference_points, std::string *failure_reason=nullptr) const
 Recover a parent-reference point from a projected sub-element map. More...
 

Private Attributes

Point _center
 Geometric center of secondary element. More...
 
Point _normal
 Unit normal of the plane used to project and clip the linearized secondary subpatch. More...
 
Point _u
 Vectors orthogonal to normal that span the plane projection will be performed on. More...
 
Point _v
 
Real _secondary_area
 Area of projected secondary element. More...
 
Real _remaining_area_fraction
 Fraction of area remaining after overlapping primary polygons clipped. More...
 
bool _debug
 
Real _tolerance = 1e-8
 Tolerance for intersection and clipping. More...
 
const MortarSegmentTriangulationMode _triangulation_mode
 Triangulation mode used for clipped polygons. More...
 
const bool _triangulate_triangles
 Whether already-triangular polygons should still be centroid-subdivided. More...
 
Real _area_tol
 Tolerance times secondary area for dimensional consistency. More...
 
Real _length_tol
 Tolerance times secondary area for dimensional consistency. More...
 
std::vector< Point_secondary_poly
 List of projected points on the linearized secondary element. More...
 
std::vector< Point_secondary_reference_points
 Parent reference points corresponding to _secondary_poly. More...
 

Friends

class MortarSegmentHelperTest
 

Detailed Description

This class supports defining mortar segment mesh elements in 3D by projecting secondary and primary elements onto a linearized plane, computing the overlapping polygon formed by their projections, and triangulating the resulting nodes.

Definition at line 35 of file MortarSegmentHelper.h.

Constructor & Destructor Documentation

◆ MortarSegmentHelper() [1/2]

MortarSegmentHelper::MortarSegmentHelper ( std::vector< Point secondary_nodes,
const Point center,
const Point normal,
const MortarSegmentTriangulationMode  triangulation_mode,
const bool  triangulate_triangles 
)

Construct a helper that generates mortar segment geometry only.

Definition at line 291 of file MortarSegmentHelper.C.

297  std::move(secondary_nodes), {}, center, normal, triangulation_mode, triangulate_triangles)
298 {
299 }
MortarSegmentHelper(std::vector< Point > secondary_nodes, const Point &center, const Point &normal, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles)
Construct a helper that generates mortar segment geometry only.
const Point & center() const
Get center point of secondary element.
const Point & normal() const
Get the unit normal of the projection plane.

◆ MortarSegmentHelper() [2/2]

MortarSegmentHelper::MortarSegmentHelper ( std::vector< Point secondary_nodes,
std::vector< Point secondary_reference_points,
const Point center,
const Point normal,
const MortarSegmentTriangulationMode  triangulation_mode,
const bool  triangulate_triangles 
)

Construct a helper that also tracks secondary parent-reference coordinates.

Definition at line 301 of file MortarSegmentHelper.C.

307  : _center(center),
308  _normal(normal),
309  _debug(false),
310  _triangulation_mode(triangulation_mode),
311  _triangulate_triangles(triangulate_triangles),
312  _secondary_reference_points(std::move(secondary_reference_points))
313 {
314  mooseAssert(_secondary_reference_points.empty() ||
315  secondary_nodes.size() == _secondary_reference_points.size(),
316  "Each projected secondary node needs one parent reference point.");
317 
318  _secondary_poly.clear();
319  _secondary_poly.reserve(secondary_nodes.size());
320 
321  // Get orientation of secondary poly
322  const Point e1 = secondary_nodes[0] - secondary_nodes[1];
323  const Point e2 = secondary_nodes[2] - secondary_nodes[1];
324  const Real orient = e2.cross(e1) * _normal;
325 
326  // u and v define the tangent plane of the element (at center)
327  // Note we embed orientation into our transformation to make 2D poly always
328  // positively oriented
329  _u = _normal.cross(secondary_nodes[0] - center).unit();
330  _v = (orient > 0) ? _normal.cross(_u).unit() : _u.cross(_normal).unit();
331 
332  // Transform problem to 2D plane spanned by u and v
333  for (const auto & node : secondary_nodes)
334  {
335  Point pt = node - _center;
336  _secondary_poly.emplace_back(pt * _u, pt * _v, 0);
337  }
338 
339  // Initialize area of secondary polygon
342 
343  // Tolerance for quantities with area dimensions
345 
346  // Tolerance for quantites with length dimensions
348 }
Point _center
Geometric center of secondary element.
Real _area_tol
Tolerance times secondary area for dimensional consistency.
Real _length_tol
Tolerance times secondary area for dimensional consistency.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
Real _tolerance
Tolerance for intersection and clipping.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on. ...
Real _secondary_area
Area of projected secondary element.
Point _normal
Unit normal of the plane used to project and clip the linearized secondary subpatch.
const Point & center() const
Get center point of secondary element.
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped polygons.
Real area(const std::vector< Point > &nodes) const
Compute area of polygon.
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _triangulate_triangles
Whether already-triangular polygons should still be centroid-subdivided.
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
std::vector< Point > _secondary_reference_points
Parent reference points corresponding to _secondary_poly.
const Point & normal() const
Get the unit normal of the projection plane.
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.

Member Function Documentation

◆ area()

Real MortarSegmentHelper::area ( const std::vector< Point > &  nodes) const

Compute area of polygon.

Definition at line 1283 of file MortarSegmentHelper.C.

Referenced by getMortarSegmentsImpl(), MortarSegmentHelper(), and triangulatePoly().

1284 {
1285  Real poly_area = 0;
1286  for (auto i : index_range(nodes))
1287  poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
1288  nodes[i](1) * nodes[(i + 1) % nodes.size()](0);
1289  poly_area *= 0.5;
1290  return poly_area;
1291 }
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto index_range(const T &sizable)

◆ center()

const Point& MortarSegmentHelper::center ( ) const
inline

Get center point of secondary element.

Definition at line 130 of file MortarSegmentHelper.h.

Referenced by MortarSegmentHelper().

130 { return _center; }
Point _center
Geometric center of secondary element.

◆ clipPoly()

std::vector< Point > MortarSegmentHelper::clipPoly ( const std::vector< Point > &  primary_nodes) const

Clip secondary element (defined in instantiation) against given primary polygon result is a set of 2D nodes defining clipped polygon.

Definition at line 443 of file MortarSegmentHelper.C.

Referenced by getMortarSegmentsImpl().

444 {
445  return clipProjectedPoly(projectPrimaryPoly(primary_nodes));
446 }
std::vector< Point > clipProjectedPoly(const std::vector< Point > &primary_poly) const
Clip an already projected primary polygon against the secondary polygon.
std::vector< Point > projectPrimaryPoly(const std::vector< Point > &primary_nodes) const
Project a primary polygon into the helper plane while preserving the clipping orientation.

◆ clipProjectedPoly()

std::vector< Point > MortarSegmentHelper::clipProjectedPoly ( const std::vector< Point > &  primary_poly) const
private

Clip an already projected primary polygon against the secondary polygon.

Keeping projection separate preserves the ordering shared with primary reference points.

Definition at line 449 of file MortarSegmentHelper.C.

Referenced by clipPoly(), and getMortarSegmentsImpl().

450 {
451  if (isDisjoint(primary_poly))
452  return {};
453 
454  // Initialize clipped poly with secondary poly (secondary is target poly)
455  std::vector<Point> clipped_poly = _secondary_poly;
456 
457  // Loop through clipping edges
458  for (auto i : index_range(primary_poly))
459  {
460  // If clipped poly trivial, return
461  if (clipped_poly.size() < 3)
462  {
463  clipped_poly.clear();
464  return clipped_poly;
465  }
466 
467  // Set input poly to current clipped poly
468  std::vector<Point> input_poly(clipped_poly);
469  clipped_poly.clear();
470 
471  // Get clipping edge
472  const Point & clip_pt1 = primary_poly[i];
473  const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
474  const Point edg = clip_pt2 - clip_pt1;
475  const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
476 
477  // Check if point is to the left of (or on) clip_edge
478  /*
479  * Note that use of tolerance here is to avoid degenerate case when lines are
480  * essentially on top of each other (common when meshes match across interface)
481  * since finding intersection is ill-conditioned in this case.
482  */
483  auto is_inside = [&edg, cp](const Point & pt, Real tol)
484  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
485 
486  // Loop through edges of target polygon (with previous clippings already included)
487  for (auto j : index_range(input_poly))
488  {
489  // Get target edge
490  const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
491  const Point prev_pt = input_poly[j];
492 
493  // TODO: Don't need to calculate both each loop
494  const bool is_current_inside = is_inside(curr_pt, _area_tol);
495  const bool is_previous_inside = is_inside(prev_pt, _area_tol);
496 
497  if (is_current_inside)
498  {
499  if (!is_previous_inside)
500  {
501  Real s;
502  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
503 
504  /*
505  * s is the fraction of distance along clip poly edge that intersection lies
506  * It is used here to avoid degenerate polygon cases. For example, consider a
507  * case like:
508  * o
509  * | (inside)
510  * ------|------
511  * | (outside)
512  * when the distance is small (< 1e-7) we don't want to to add both the point
513  * and intersection. Also note that when distance on the scale of 1e-7,
514  * area on scale of 1e-14 so is insignificant if this results in dropping
515  * a tri (for example if next edge crosses again)
516  */
517  if (s < (1 - _tolerance))
518  clipped_poly.push_back(intersect);
519  }
520  clipped_poly.push_back(curr_pt);
521  }
522  else if (is_previous_inside)
523  {
524  Real s;
525  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
526  if (s > _tolerance)
527  clipped_poly.push_back(intersect);
528  }
529  }
530  }
531 
532  // Make sure final clipped poly is not trivial
533  if (clipped_poly.size() < 3)
534  {
535  clipped_poly.clear();
536  return clipped_poly;
537  }
538 
539  // Clean up result by removing any duplicate nodes
540  std::vector<Point> cleaned_poly;
541  cleaned_poly.push_back(clipped_poly.back());
542  for (auto i : make_range(clipped_poly.size() - 1))
543  {
544  const Point prev_pt = cleaned_poly.back();
545  const Point curr_pt = clipped_poly[i];
546 
547  // If points are sufficiently distanced, add to output
548  if ((curr_pt - prev_pt).norm() > _length_tol)
549  cleaned_poly.push_back(curr_pt);
550  }
551 
552  mooseAssert(
553  cleaned_poly.size() <= 8,
554  "Our distributed mesh numbering scheme assumes that we have at most 8 nodes resulting from "
555  "clipping the projection of the primary sub-element onto the secondary sub-element");
556  return cleaned_poly;
557 }
Real _area_tol
Tolerance times secondary area for dimensional consistency.
Real _length_tol
Tolerance times secondary area for dimensional consistency.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
Real _tolerance
Tolerance for intersection and clipping.
bool isDisjoint(const std::vector< Point > &poly) const
Checks whether polygons are disjoint for an easy out.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto norm(const T &a)
IntRange< T > make_range(T beg, T end)
Point getIntersection(const Point &p1, const Point &p2, const Point &q1, const Point &q2, Real &s) const
Computes the intersection between line segments defined by point pairs (p1,p2) and (q1...
auto index_range(const T &sizable)

◆ getIntersection()

Point MortarSegmentHelper::getIntersection ( const Point p1,
const Point p2,
const Point q1,
const Point q2,
Real s 
) const

Computes the intersection between line segments defined by point pairs (p1,p2) and (q1,q2) Also computes s, the ratio of distance between (p1,p2) that the intersection falls, quantity s is useful in avoiding adding nearly degenerate nodes.

Definition at line 351 of file MortarSegmentHelper.C.

Referenced by clipProjectedPoly().

353 {
354  const Point dp = p2 - p1;
355  const Point dq = q2 - q1;
356  const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
357  const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
358  const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
359  const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
360  s = -alpha * (cp1q2 - cp1q1 - cq1q2);
361 
362  // Intersection should be between p1 and p2, if it's not (due to poor conditioning), simply
363  // move it to one of the end points
364  s = s > 1 ? 1. : s;
365  s = s < 0 ? 0. : s;
366  return p1 + s * dp;
367 }
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

◆ getMortarSegments() [1/2]

void MortarSegmentHelper::getMortarSegments ( const std::vector< Point > &  primary_nodes,
std::vector< Point > &  nodes,
std::vector< std::vector< unsigned int >> &  elem_to_nodes 
)

Get mortar segments generated by a secondary and primary element pair.

Parameters
primary_nodesList of primary element 3D nodes
Returns
nodes List of 3D mortar segment nodes
tri_map List of integer arrays defining which nodes belong to each mortar segment

Definition at line 930 of file MortarSegmentHelper.C.

933 {
934  getMortarSegmentsImpl(primary_nodes, nodes, elem_to_nodes, nullptr);
935 }
void getMortarSegmentsImpl(const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes, ReferenceMappingData *reference_mapping)

◆ getMortarSegments() [2/2]

void MortarSegmentHelper::getMortarSegments ( const std::vector< Point > &  primary_nodes,
const std::vector< Point > &  primary_reference_points,
std::vector< Point > &  nodes,
std::vector< std::vector< unsigned int >> &  elem_to_nodes,
std::vector< std::array< Point, 3 >> &  elem_to_secondary_reference_points,
std::vector< std::array< Point, 3 >> &  elem_to_primary_reference_points,
Real  minimum_segment_area = 0. 
)

Get mortar segments and aligned parent-reference coordinates by appending to the output containers.

Segments below minimum_segment_area are removed before mapping; retained vertices must map uniquely.

Definition at line 938 of file MortarSegmentHelper.C.

946 {
947  ReferenceMappingData reference_mapping{primary_reference_points,
948  elem_to_secondary_reference_points,
949  elem_to_primary_reference_points,
950  minimum_segment_area};
951  getMortarSegmentsImpl(primary_nodes, nodes, elem_to_nodes, &reference_mapping);
952 }
void getMortarSegmentsImpl(const std::vector< Point > &primary_nodes, std::vector< Point > &nodes, std::vector< std::vector< unsigned int >> &elem_to_nodes, ReferenceMappingData *reference_mapping)

◆ getMortarSegmentsImpl()

void MortarSegmentHelper::getMortarSegmentsImpl ( const std::vector< Point > &  primary_nodes,
std::vector< Point > &  nodes,
std::vector< std::vector< unsigned int >> &  elem_to_nodes,
ReferenceMappingData reference_mapping 
)
private

Definition at line 955 of file MortarSegmentHelper.C.

Referenced by getMortarSegments().

959 {
960  std::vector<Point> primary_poly;
961  std::vector<Point> primary_poly_reference_points;
962 
963  if (reference_mapping)
964  {
965  if (primary_nodes.size() != reference_mapping->primary_reference_points.size())
966  mooseError("Reference-interpolation mortar segment generation requires one primary "
967  "reference point per primary sub-element node.");
968  if (_secondary_poly.size() != _secondary_reference_points.size())
969  mooseError("Reference-interpolation mortar segment generation requires one secondary "
970  "reference point per secondary sub-element node.");
971  if (reference_mapping->elem_to_secondary_reference_points.size() != elem_to_nodes.size() ||
972  reference_mapping->elem_to_primary_reference_points.size() != elem_to_nodes.size())
973  mooseError("Reference-interpolation mortar segment outputs must be aligned before appending "
974  "new segments.");
975 
976  // Keep reference points in the projected polygon's orientation.
977  const Point e1 = primary_nodes[0] - primary_nodes[1];
978  const Point e2 = primary_nodes[2] - primary_nodes[1];
979  const Real orient = e2.cross(e1) * _u.cross(_v);
980  const auto n_verts = primary_nodes.size();
981 
982  primary_poly = projectPrimaryPoly(primary_nodes);
983  primary_poly_reference_points.reserve(reference_mapping->primary_reference_points.size());
984  for (const auto n : index_range(primary_nodes))
985  {
986  const auto primary_node_index = (orient > 0) ? n : n_verts - 1 - n;
987  primary_poly_reference_points.push_back(
988  reference_mapping->primary_reference_points[primary_node_index]);
989  }
990  }
991 
992  // Clip primary elem against secondary elem. Reference mode preserves the projected primary
993  // ordering so its reference points remain aligned.
994  std::vector<Point> clipped_poly =
995  reference_mapping ? clipProjectedPoly(primary_poly) : clipPoly(primary_nodes);
996  if (clipped_poly.size() < 3)
997  return;
998 
999  if (_debug)
1000  for (const auto & point : clipped_poly)
1001  if (!isInsideSecondary(point))
1002  mooseError("Clipped polygon not inside linearized secondary element");
1003 
1004  // Compute area of clipped polygon, update remaining area fraction
1005  _remaining_area_fraction -= area(clipped_poly) / _secondary_area;
1006 
1007  // Triangulate clip polygon. tri_map indices are local to clipped_poly (starting at 0); we
1008  // shift them into the global node numbering after appending the polygon nodes below.
1009  std::vector<std::vector<unsigned int>> tri_map;
1010  triangulatePoly(clipped_poly, tri_map);
1011  if (reference_mapping && reference_mapping->minimum_segment_area > 0.)
1012  tri_map.erase(
1013  std::remove_if(tri_map.begin(),
1014  tri_map.end(),
1015  [&clipped_poly, reference_mapping](const std::vector<unsigned int> & tri)
1016  {
1017  mooseAssert(tri.size() == 3,
1018  "Mortar segment triangulation should only produce TRI3 maps.");
1019  return triangleAreaHelper(clipped_poly[tri[0]],
1020  clipped_poly[tri[1]],
1021  clipped_poly[tri[2]]) <
1022  reference_mapping->minimum_segment_area;
1023  }),
1024  tri_map.end());
1025  if (tri_map.empty())
1026  return;
1027 
1028  std::vector<Point> secondary_node_reference_points;
1029  std::vector<Point> primary_node_reference_points;
1030  if (reference_mapping)
1031  {
1032  secondary_node_reference_points.reserve(clipped_poly.size());
1033  primary_node_reference_points.reserve(clipped_poly.size());
1034 
1035  const auto recover_reference_point = [this](const Point & projected_point,
1036  const std::vector<Point> & poly,
1037  const std::vector<Point> & reference_points,
1038  const char * const parent_name,
1039  const std::size_t node_index)
1040  {
1041  std::string failure_reason;
1042  const auto reference_point =
1043  referencePoint(projected_point, poly, reference_points, &failure_reason);
1044  if (!reference_point)
1045  mooseError("Unable to recover the ",
1046  parent_name,
1047  " parent reference point for retained 3D mortar overlap vertex ",
1048  node_index,
1049  " at projected point ",
1050  projected_point,
1051  ". Reason: ",
1052  failure_reason,
1053  ". Reference interpolation does not fall back to normal projection.");
1054 
1055  return *reference_point;
1056  };
1057 
1058  for (const auto node_index : index_range(clipped_poly))
1059  {
1060  const auto & point = clipped_poly[node_index];
1061  secondary_node_reference_points.push_back(recover_reference_point(
1062  point, _secondary_poly, _secondary_reference_points, "secondary", node_index));
1063  primary_node_reference_points.push_back(recover_reference_point(
1064  point, primary_poly, primary_poly_reference_points, "primary", node_index));
1065  }
1066  }
1067 
1068  // Transform clipped poly back to (linearized) 3d and append to list
1069  const auto offset = cast_int<unsigned int>(nodes.size());
1070  for (const auto & point : clipped_poly)
1071  nodes.emplace_back((point(0) * _u) + (point(1) * _v) + _center);
1072 
1073  for (const auto & tri : tri_map)
1074  {
1075  std::vector<unsigned int> shifted_tri;
1076  shifted_tri.reserve(tri.size());
1077  for (const auto local_index : tri)
1078  shifted_tri.push_back(offset + local_index);
1079  elem_to_nodes.push_back(std::move(shifted_tri));
1080 
1081  if (reference_mapping)
1082  {
1083  mooseAssert(tri.size() == 3, "Mortar segment triangulation should only produce TRI3 maps.");
1084  std::array<Point, 3> elem_secondary_reference_points;
1085  std::array<Point, 3> elem_primary_reference_points;
1086  for (const auto n : index_range(tri))
1087  {
1088  const auto local_node = tri[n];
1089  elem_secondary_reference_points[n] = secondary_node_reference_points[local_node];
1090  elem_primary_reference_points[n] = primary_node_reference_points[local_node];
1091  }
1092 
1093  reference_mapping->elem_to_secondary_reference_points.push_back(
1094  elem_secondary_reference_points);
1095  reference_mapping->elem_to_primary_reference_points.push_back(elem_primary_reference_points);
1096  }
1097  }
1098 }
std::vector< Point > clipProjectedPoly(const std::vector< Point > &primary_poly) const
Clip an already projected primary polygon against the secondary polygon.
std::optional< Point > referencePoint(const Point &point, const std::vector< Point > &poly, const std::vector< Point > &reference_points, std::string *failure_reason=nullptr) const
Recover a parent-reference point from a projected sub-element map.
Point _center
Geometric center of secondary element.
bool isInsideSecondary(const Point &pt) const
Check that a point is inside the secondary polygon (for verification only)
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:242
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
std::vector< Point > clipPoly(const std::vector< Point > &primary_nodes) const
Clip secondary element (defined in instantiation) against given primary polygon result is a set of 2D...
std::vector< Point > projectPrimaryPoly(const std::vector< Point > &primary_nodes) const
Project a primary polygon into the helper plane while preserving the clipping orientation.
void triangulatePoly(std::vector< Point > &poly_nodes, std::vector< std::vector< unsigned int >> &tri_map) const
Triangulate a polygon according to the configured mortar-segment triangulation mode.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on. ...
Real _secondary_area
Area of projected secondary element.
Real area(const std::vector< Point > &nodes) const
Compute area of polygon.
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
std::vector< Point > _secondary_reference_points
Parent reference points corresponding to _secondary_poly.
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.
auto index_range(const T &sizable)
Point point(unsigned int i) const
Get 3D position of node of linearized secondary element.

◆ isDisjoint()

bool MortarSegmentHelper::isDisjoint ( const std::vector< Point > &  poly) const

Checks whether polygons are disjoint for an easy out.

Definition at line 392 of file MortarSegmentHelper.C.

Referenced by clipProjectedPoly().

393 {
394  for (auto i : index_range(_secondary_poly))
395  {
396  // Get edge to check
397  const Point & q1 = _secondary_poly[i];
398  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
399  const Point edg = q2 - q1;
400  const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
401 
402  // If more optimization needed, could store these values for later
403  // Check if point is to the left of (or on) clip_edge
404  auto is_inside = [&edg, cp](Point & pt, Real tol)
405  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
406 
407  bool all_outside = true;
408  for (auto pt : poly)
409  if (is_inside(pt, _area_tol))
410  all_outside = false;
411 
412  if (all_outside)
413  return true;
414  }
415  return false;
416 }
Real _area_tol
Tolerance times secondary area for dimensional consistency.
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:242
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto index_range(const T &sizable)

◆ isInsideSecondary()

bool MortarSegmentHelper::isInsideSecondary ( const Point pt) const

Check that a point is inside the secondary polygon (for verification only)

Definition at line 370 of file MortarSegmentHelper.C.

Referenced by getMortarSegmentsImpl().

371 {
372  for (auto i : index_range(_secondary_poly))
373  {
374  const Point & q1 = _secondary_poly[i];
375  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
376 
377  const Point e1 = q2 - q1;
378  const Point e2 = pt - q1;
379 
380  // If point corresponds to one of the secondary vertices, skip
381  if (e2.norm() < _tolerance)
382  return true;
383 
384  const bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) < _area_tol;
385  if (!inside)
386  return false;
387  }
388  return true;
389 }
Real _area_tol
Tolerance times secondary area for dimensional consistency.
auto norm() const
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
Real _tolerance
Tolerance for intersection and clipping.
auto index_range(const T &sizable)

◆ normal()

const Point& MortarSegmentHelper::normal ( ) const
inline

Get the unit normal of the projection plane.

Definition at line 135 of file MortarSegmentHelper.h.

135 { return _normal; }
Point _normal
Unit normal of the plane used to project and clip the linearized secondary subpatch.

◆ point()

Point MortarSegmentHelper::point ( unsigned int  i) const
inline

Get 3D position of node of linearized secondary element.

Definition at line 145 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl(), and referencePoint().

146  {
147  return (_secondary_poly[i](0) * _u) + (_secondary_poly[i](1) * _v) + _center;
148  }
Point _center
Geometric center of secondary element.
std::vector< Point > _secondary_poly
List of projected points on the linearized secondary element.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on. ...

◆ projectPrimaryPoly()

std::vector< Point > MortarSegmentHelper::projectPrimaryPoly ( const std::vector< Point > &  primary_nodes) const

Project a primary polygon into the helper plane while preserving the clipping orientation.

Definition at line 419 of file MortarSegmentHelper.C.

Referenced by clipPoly(), and getMortarSegmentsImpl().

420 {
421  // Check orientation of primary_poly
422  const Point e1 = primary_nodes[0] - primary_nodes[1];
423  const Point e2 = primary_nodes[2] - primary_nodes[1];
424 
425  // Note we use u x v here instead of normal because it may be flipped if secondary elem was
426  // negatively oriented
427  const Real orient = e2.cross(e1) * _u.cross(_v);
428 
429  // Get primary_poly (primary is clipping poly). If negatively oriented, reverse
430  std::vector<Point> primary_poly;
431  const int n_verts = primary_nodes.size();
432  primary_poly.reserve(primary_nodes.size());
433  for (auto n : index_range(primary_nodes))
434  {
435  Point pt = (orient > 0) ? primary_nodes[n] - _center : primary_nodes[n_verts - 1 - n] - _center;
436  primary_poly.emplace_back(pt * _u, pt * _v, 0.);
437  }
438 
439  return primary_poly;
440 }
Point _center
Geometric center of secondary element.
Point _u
Vectors orthogonal to normal that span the plane projection will be performed on. ...
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto index_range(const T &sizable)

◆ referencePoint()

std::optional< Point > MortarSegmentHelper::referencePoint ( const Point point,
const std::vector< Point > &  poly,
const std::vector< Point > &  reference_points,
std::string *  failure_reason = nullptr 
) const
private

Recover a parent-reference point from a projected sub-element map.

Returns
No value for invalid or out-of-tolerance maps.

Definition at line 1101 of file MortarSegmentHelper.C.

Referenced by getMortarSegmentsImpl().

1105 {
1106  mooseAssert(poly.size() == reference_points.size(),
1107  "Projected point and reference point containers should be the same size.");
1108 
1109  if (failure_reason)
1110  failure_reason->clear();
1111 
1112  const auto fail = [failure_reason](const std::string & reason) -> std::optional<Point>
1113  {
1114  if (failure_reason)
1115  *failure_reason = reason;
1116  return std::nullopt;
1117  };
1118 
1119  if (!isFinitePoint(point))
1120  return fail("the projected target point contains a non-finite coordinate");
1121 
1122  for (const auto i : index_range(poly))
1123  {
1124  if (!isFinitePoint(poly[i]))
1125  return fail("projected polygon vertex " + std::to_string(i) +
1126  " contains a non-finite coordinate");
1127  if (!isFinitePoint(reference_points[i]))
1128  return fail("parent reference vertex " + std::to_string(i) +
1129  " contains a non-finite coordinate");
1130  }
1131 
1132  if (poly.size() != 3 && poly.size() != 4)
1133  return fail("reference point recovery only supports triangular and quadrilateral mortar "
1134  "sub-elements, but received " +
1135  std::to_string(poly.size()) + " vertices");
1136 
1137  Real minimum_edge_length = std::numeric_limits<Real>::max();
1138  Point local_origin;
1139  for (const auto & vertex : poly)
1140  local_origin += vertex;
1141  local_origin /= poly.size();
1142 
1143  Real local_scale = 0.;
1144  for (const auto i : index_range(poly))
1145  {
1146  minimum_edge_length =
1147  std::min(minimum_edge_length, (poly[(i + 1) % poly.size()] - poly[i]).norm());
1148  local_scale = std::max(local_scale, (poly[i] - local_origin).norm());
1149  }
1150 
1151  const Real singular_tolerance = 100. * std::numeric_limits<Real>::epsilon();
1152  if (!std::isfinite(local_scale) || local_scale <= singular_tolerance)
1153  return fail("the projected polygon has a zero local length scale");
1154  if (!std::isfinite(minimum_edge_length) ||
1155  minimum_edge_length / local_scale <= singular_tolerance)
1156  return fail("the projected polygon has a zero-length edge relative to its local scale");
1157 
1158  std::vector<Point> normalized_poly;
1159  normalized_poly.reserve(poly.size());
1160  for (const auto & vertex : poly)
1161  normalized_poly.push_back((vertex - local_origin) / local_scale);
1162  const Point normalized_point = (point - local_origin) / local_scale;
1163  const Real reference_tolerance =
1164  std::max(mortar_reference_mapping_tolerance, _area_tol / (minimum_edge_length * local_scale));
1165  std::array<Node, 4> element_nodes;
1166  const FEType fe_type(FIRST, LAGRANGE);
1167  const auto recover_with_libmesh = [&](auto & element) -> std::optional<Point>
1168  {
1169  for (const auto i : index_range(normalized_poly))
1170  {
1171  element_nodes[i] = normalized_poly[i];
1172  element_nodes[i].set_id(i);
1173  element.set_node(i, &element_nodes[i]);
1174  }
1175 
1176  if (!element.has_invertible_map(mortar_reference_mapping_tolerance))
1177  return fail("the projected sub-element map is degenerate or non-invertible");
1178 
1179  if (element.type() == QUAD4)
1180  for (const auto corner : make_range(element.n_vertices()))
1181  {
1182  Point tangent_xi;
1183  Point tangent_eta;
1184  for (const auto node : make_range(element.n_nodes()))
1185  {
1186  tangent_xi += FEInterface::shape_deriv(
1187  fe_type, 0, &element, node, 0, element.master_point(corner)) *
1188  element.point(node);
1189  tangent_eta += FEInterface::shape_deriv(
1190  fe_type, 0, &element, node, 1, element.master_point(corner)) *
1191  element.point(node);
1192  }
1193 
1194  const Real corner_jacobian = tangent_xi.cross(tangent_eta).norm();
1195  if (!std::isfinite(corner_jacobian) ||
1196  corner_jacobian <= mortar_reference_mapping_tolerance)
1197  return fail("the projected quadrilateral has a singular or ill-conditioned corner map");
1198  }
1199 
1200  Point local_reference = FEMap::inverse_map(
1201  2, &element, normalized_point, mortar_reference_mapping_tolerance, false, false);
1202  if (!isFinitePoint(local_reference))
1203  return fail("libMesh inverse_map produced a non-finite reference point");
1204 
1205  const Real inverse_map_error =
1206  (FEMap::map(2, &element, local_reference) - normalized_point).norm();
1207  if (!std::isfinite(inverse_map_error) || inverse_map_error > mortar_reference_mapping_tolerance)
1208  {
1209  std::ostringstream reason;
1210  reason << "the normalized inverse-map error " << inverse_map_error << " exceeds "
1211  << mortar_reference_mapping_tolerance;
1212  return fail(reason.str());
1213  }
1214 
1215  if (element.type() == TRI3)
1216  {
1217  std::array<Real, 3> weights;
1218  for (const auto i : index_range(weights))
1219  weights[i] = FEInterface::shape(fe_type, &element, i, local_reference, false);
1220 
1221  for (auto & weight : weights)
1222  weight = std::clamp(weight, 0., 1.);
1223  const Real weight_sum = std::accumulate(weights.begin(), weights.end(), 0.);
1224  if (!std::isfinite(weight_sum) || weight_sum <= singular_tolerance)
1225  return fail("clamped triangle barycentric coordinates have a zero or non-finite sum");
1226  for (auto & weight : weights)
1227  weight /= weight_sum;
1228 
1229  // Preserve partition of unity after clamping tolerance-sized violations.
1230  const auto corrected_weight =
1231  std::distance(weights.begin(), std::max_element(weights.begin(), weights.end()));
1232  weights[corrected_weight] = 1.;
1233  for (const auto i : index_range(weights))
1234  if (i != static_cast<unsigned int>(corrected_weight))
1235  weights[corrected_weight] -= weights[i];
1236 
1237  local_reference = Point();
1238  for (const auto i : index_range(weights))
1239  local_reference += weights[i] * element.master_point(i);
1240  }
1241  else
1242  {
1243  local_reference(0) = std::clamp(local_reference(0), -1., 1.);
1244  local_reference(1) = std::clamp(local_reference(1), -1., 1.);
1245  local_reference(2) = 0.;
1246  }
1247 
1248  if (!element.on_reference_element(local_reference, mortar_reference_mapping_tolerance))
1249  return fail("the clamped inverse-map result is outside the reference element");
1250 
1251  const Real round_trip_error =
1252  (FEMap::map(2, &element, local_reference) - normalized_point).norm();
1253  if (!std::isfinite(round_trip_error) || round_trip_error > reference_tolerance)
1254  {
1255  std::ostringstream reason;
1256  reason << "the normalized inverse-map round-trip error " << round_trip_error
1257  << " exceeds the clipping-consistent tolerance " << reference_tolerance;
1258  return fail(reason.str());
1259  }
1260 
1261  Point parent_reference;
1262  for (const auto i : index_range(normalized_poly))
1263  parent_reference +=
1264  FEInterface::shape(fe_type, &element, i, local_reference, false) * reference_points[i];
1265 
1266  if (!isFinitePoint(parent_reference))
1267  return fail("reference interpolation produced a non-finite parent reference point");
1268 
1269  return parent_reference;
1270  };
1271 
1272  if (poly.size() == 3)
1273  {
1274  Tri3 element;
1275  return recover_with_libmesh(element);
1276  }
1277 
1278  Quad4 element;
1279  return recover_with_libmesh(element);
1280 }
Real _area_tol
Tolerance times secondary area for dimensional consistency.
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:242
auto max(const L &left, const R &right)
TRI3
QUAD4
dof_id_type weight(const MeshBase &mesh, const processor_id_type pid)
TypeVector< typename CompareTypes< Real, T2 >::supertype > cross(const TypeVector< T2 > &v) const
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
auto norm(const T &a)
IntRange< T > make_range(T beg, T end)
T clamp(const T &x, T2 lowerlimit, T2 upperlimit)
Definition: MathUtils.h:314
auto min(const L &left, const R &right)
auto index_range(const T &sizable)
Point point(unsigned int i) const
Get 3D position of node of linearized secondary element.

◆ remainder()

Real MortarSegmentHelper::remainder ( ) const
inline

Get area fraction remaining after clipping against primary elements.

Definition at line 140 of file MortarSegmentHelper.h.

140 { return _remaining_area_fraction; }
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.

◆ triangulatePoly()

void MortarSegmentHelper::triangulatePoly ( std::vector< Point > &  poly_nodes,
std::vector< std::vector< unsigned int >> &  tri_map 
) const

Triangulate a polygon according to the configured mortar-segment triangulation mode.

Parameters
poly_nodesList of 2D nodes defining polygon. May be augmented with extra interior nodes (e.g. centroid) by triangulation modes that require them; callers should append the result to their nodes list before applying the offset to tri_map.
tri_mapOutput triangle list expressed in indices local to poly_nodes (i.e. starting at 0). Callers are responsible for shifting these indices into the global node numbering.

Definition at line 560 of file MortarSegmentHelper.C.

Referenced by getMortarSegmentsImpl().

562 {
563  // tri_map is populated with triangle indices that are local to poly_nodes (starting at 0).
564  // Callers are responsible for shifting these indices into a global node numbering.
565  const auto polygon_centroid = [](const std::vector<Point> & polygon_nodes)
566  {
567  Point centroid(0);
568  Real double_area = 0;
569  for (const auto i : index_range(polygon_nodes))
570  {
571  const auto & a = polygon_nodes[i];
572  const auto & b = polygon_nodes[(i + 1) % polygon_nodes.size()];
573  const Real cross = a(0) * b(1) - b(0) * a(1);
574  double_area += cross;
575  centroid(0) += (a(0) + b(0)) * cross;
576  centroid(1) += (a(1) + b(1)) * cross;
577  }
578 
579  if (std::abs(double_area) <= TOLERANCE)
580  {
581  for (const auto & node : polygon_nodes)
582  centroid += node;
583  centroid /= polygon_nodes.size();
584  return centroid;
585  }
586 
587  centroid /= (3. * double_area);
588  centroid(2) = 0;
589  return centroid;
590  };
591 
592  const auto append_triangle = [this, &poly_nodes, &tri_map](
593  const unsigned int a, const unsigned int b, const unsigned int c)
594  {
595  if (triangleAreaHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) <= _area_tol)
596  return false;
597 
598  if (orient2dHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) >= 0)
599  tri_map.push_back({a, b, c});
600  else
601  tri_map.push_back({a, c, b});
602 
603  return true;
604  };
605 
606  const auto point_in_triangle =
607  [this](const Point & p, const Point & a, const Point & b, const Point & c)
608  {
609  const Real ab = orient2dHelper(a, b, p);
610  const Real bc = orient2dHelper(b, c, p);
611  const Real ca = orient2dHelper(c, a, p);
612  return ab >= -_area_tol && bc >= -_area_tol && ca >= -_area_tol;
613  };
614 
615  const auto min_triangle_angle = [](const Point & a, const Point & b, const Point & c)
616  {
617  const auto clamp_cos = [](Real value) { return std::max(-1., std::min(1., value)); };
618  const auto angle_at =
619  [&clamp_cos](const Point & vertex, const Point & point_one, const Point & point_two)
620  {
621  const Point edge_one = point_one - vertex;
622  const Point edge_two = point_two - vertex;
623  const Real denom = edge_one.norm() * edge_two.norm();
624  if (denom <= TOLERANCE)
625  return 0.;
626  return std::acos(clamp_cos((edge_one * edge_two) / denom));
627  };
628 
629  return std::min({angle_at(a, b, c), angle_at(b, c, a), angle_at(c, a, b)});
630  };
631 
632  const auto canonicalize_polygon = [this, &poly_nodes]()
633  {
634  if (poly_nodes.size() < 3)
635  return;
636 
637  if (area(poly_nodes) < 0)
638  std::reverse(poly_nodes.begin(), poly_nodes.end());
639 
640  bool changed = true;
641  while (changed && poly_nodes.size() > 3)
642  {
643  changed = false;
644  for (const auto i : index_range(poly_nodes))
645  {
646  const auto prev = (i + poly_nodes.size() - 1) % poly_nodes.size();
647  const auto next = (i + 1) % poly_nodes.size();
648  if ((poly_nodes[i] - poly_nodes[prev]).norm() <= _length_tol ||
649  (poly_nodes[next] - poly_nodes[i]).norm() <= _length_tol ||
650  triangleAreaHelper(poly_nodes[prev], poly_nodes[i], poly_nodes[next]) <= _area_tol)
651  {
652  poly_nodes.erase(poly_nodes.begin() + i);
653  changed = true;
654  break;
655  }
656  }
657  }
658 
659  if (poly_nodes.size() >= 3 && area(poly_nodes) < 0)
660  std::reverse(poly_nodes.begin(), poly_nodes.end());
661  };
662 
663  const auto triangulate_with_ear_clipping =
664  [this, &poly_nodes, &point_in_triangle, &min_triangle_angle](
665  const bool perform_delaunay_flips)
666  {
667  std::vector<std::array<unsigned int, 3>> triangles;
668  if (poly_nodes.size() < 3)
669  return triangles;
670 
671  if (poly_nodes.size() == 3)
672  {
673  triangles.push_back(makeCCWTriangleHelper(poly_nodes, 0, 1, 2));
674  return triangles;
675  }
676 
677  std::vector<unsigned int> remaining_vertices(poly_nodes.size());
678  std::iota(remaining_vertices.begin(), remaining_vertices.end(), 0);
679 
680  while (remaining_vertices.size() > 3)
681  {
682  std::optional<std::size_t> best_position;
683  Real best_score = -std::numeric_limits<Real>::max();
684  Real best_area = -std::numeric_limits<Real>::max();
685 
686  for (const auto position : index_range(remaining_vertices))
687  {
688  const auto prev_position =
689  (position + remaining_vertices.size() - 1) % remaining_vertices.size();
690  const auto next_position = (position + 1) % remaining_vertices.size();
691  const auto prev = remaining_vertices[prev_position];
692  const auto curr = remaining_vertices[position];
693  const auto next = remaining_vertices[next_position];
694 
695  if (orient2dHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]) <= _area_tol)
696  continue;
697 
698  bool contains_other_vertex = false;
699  for (const auto other : remaining_vertices)
700  {
701  if (other == prev || other == curr || other == next)
702  continue;
703 
704  if (point_in_triangle(
705  poly_nodes[other], poly_nodes[prev], poly_nodes[curr], poly_nodes[next]))
706  {
707  contains_other_vertex = true;
708  break;
709  }
710  }
711 
712  if (contains_other_vertex)
713  continue;
714 
715  const Real candidate_score =
716  min_triangle_angle(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
717  const Real candidate_area =
718  triangleAreaHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
719  if (!best_position || candidate_score > best_score + TOLERANCE ||
720  (std::abs(candidate_score - best_score) <= TOLERANCE &&
721  candidate_area > best_area + _area_tol))
722  {
723  best_position = position;
724  best_score = candidate_score;
725  best_area = candidate_area;
726  }
727  }
728 
729  if (!best_position)
730  {
731  std::vector<std::array<unsigned int, 3>> best_fan;
732  Real best_fan_score = -std::numeric_limits<Real>::max();
733  Real best_fan_area = -std::numeric_limits<Real>::max();
734 
735  for (const auto root_position : index_range(remaining_vertices))
736  {
737  std::vector<std::array<unsigned int, 3>> candidate_fan;
738  Real candidate_score = std::numeric_limits<Real>::max();
739  Real candidate_area = std::numeric_limits<Real>::max();
740  bool valid_fan = true;
741  const auto root = remaining_vertices[root_position];
742 
743  for (unsigned int step = 1; step + 1 < remaining_vertices.size(); ++step)
744  {
745  const auto next_position = (root_position + step) % remaining_vertices.size();
746  const auto following_position = (root_position + step + 1) % remaining_vertices.size();
747  const auto vertex_one = remaining_vertices[next_position];
748  const auto vertex_two = remaining_vertices[following_position];
749 
750  if (orient2dHelper(poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]) <=
751  _area_tol)
752  {
753  valid_fan = false;
754  break;
755  }
756 
757  candidate_fan.push_back(
758  makeCCWTriangleHelper(poly_nodes, root, vertex_one, vertex_two));
759  candidate_score =
760  std::min(candidate_score,
761  min_triangle_angle(
762  poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
763  candidate_area =
764  std::min(candidate_area,
765  triangleAreaHelper(
766  poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
767  }
768 
769  if (!valid_fan || candidate_fan.empty())
770  continue;
771 
772  if (candidate_score > best_fan_score + TOLERANCE ||
773  (std::abs(candidate_score - best_fan_score) <= TOLERANCE &&
774  candidate_area > best_fan_area + _area_tol))
775  {
776  best_fan = std::move(candidate_fan);
777  best_fan_score = candidate_score;
778  best_fan_area = candidate_area;
779  }
780  }
781 
782  if (best_fan.empty())
783  for (unsigned int i = 1; i + 1 < remaining_vertices.size(); ++i)
784  best_fan.push_back(makeCCWTriangleHelper(poly_nodes,
785  remaining_vertices[0],
786  remaining_vertices[i],
787  remaining_vertices[i + 1]));
788 
789  triangles.insert(triangles.end(), best_fan.begin(), best_fan.end());
790  break;
791  }
792 
793  const auto prev_position =
794  (*best_position + remaining_vertices.size() - 1) % remaining_vertices.size();
795  const auto next_position = (*best_position + 1) % remaining_vertices.size();
796  triangles.push_back(makeCCWTriangleHelper(poly_nodes,
797  remaining_vertices[prev_position],
798  remaining_vertices[*best_position],
799  remaining_vertices[next_position]));
800  remaining_vertices.erase(remaining_vertices.begin() + *best_position);
801  }
802 
803  if (remaining_vertices.size() == 3)
804  triangles.push_back(makeCCWTriangleHelper(
805  poly_nodes, remaining_vertices[0], remaining_vertices[1], remaining_vertices[2]));
806 
807  if (!perform_delaunay_flips)
808  return triangles;
809 
810  std::set<std::array<unsigned int, 2>> boundary_edges;
811  for (const auto i : index_range(poly_nodes))
812  boundary_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
813 
814  performLocalDelaunayFlips(poly_nodes, boundary_edges, triangles);
815  return triangles;
816  };
817 
818  const auto is_convex_polygon = [this](const std::vector<Point> & polygon_nodes)
819  {
820  if (polygon_nodes.size() <= 3)
821  return true;
822 
823  for (const auto i : index_range(polygon_nodes))
824  {
825  const auto prev = (i + polygon_nodes.size() - 1) % polygon_nodes.size();
826  const auto next = (i + 1) % polygon_nodes.size();
827  if (orient2dHelper(polygon_nodes[prev], polygon_nodes[i], polygon_nodes[next]) <= _area_tol)
828  return false;
829  }
830 
831  return true;
832  };
833 
834  // Fewer than 3 nodes can't be triangulated
835  if (poly_nodes.size() < 3)
836  mooseError("Can't triangulate poly with fewer than 3 nodes");
837 
838  // Legacy centroid path: when the default triangulation (centroid) is selected
839  // and triangle re-tessellation is not requested, reproduce the legacy
840  // algorithm byte-for-byte so existing mortar baselines remain valid.
841  // Uses the arithmetic mean of the vertices (not the area-weighted centroid),
842  // emits one triangle per polygon edge without degeneracy filtering, and skips
843  // the canonicalization pass which would drop near-degenerate vertices and
844  // perturb integration weights in downstream test baselines.
846  {
847  if (poly_nodes.size() == 3)
848  {
849  tri_map.push_back({0, 1, 2});
850  return;
851  }
852 
853  const unsigned int n_verts = poly_nodes.size();
854  Point poly_center;
855  for (const auto & node : poly_nodes)
856  poly_center += node;
857  poly_center /= n_verts;
858 
859  for (const auto i : make_range(n_verts))
860  tri_map.push_back({i, (i + 1) % n_verts, n_verts});
861 
862  poly_nodes.push_back(poly_center);
863  return;
864  }
865 
866  canonicalize_polygon();
867  if (poly_nodes.size() < 3)
868  return;
869 
870  if (poly_nodes.size() == 3 && !_triangulate_triangles)
871  {
872  append_triangle(0, 1, 2);
873  return;
874  }
875 
876  const bool force_triangle_centroid_split = _triangulate_triangles && poly_nodes.size() == 3;
877 
879  !force_triangle_centroid_split)
880  {
881  const unsigned int n_verts = poly_nodes.size();
882  for (unsigned int i = 1; i + 1 < n_verts; ++i)
883  append_triangle(0, i, i + 1);
884  return;
885  }
886 
888  !force_triangle_centroid_split)
889  {
890 #if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
891  triangulateConstrainedDelaunayPolygon(poly_nodes, _area_tol, _length_tol, tri_map);
892  return;
893 #else
894  mooseError("The 'delaunay' mortar triangulation mode requires libMesh TriangleInterface or "
895  "Poly2Tri support.");
896 #endif
897  }
898 
900  !force_triangle_centroid_split)
901  {
902  for (const auto & triangle : triangulate_with_ear_clipping(true))
903  append_triangle(triangle[0], triangle[1], triangle[2]);
904  return;
905  }
906 
907  if (!force_triangle_centroid_split && !is_convex_polygon(poly_nodes))
908  {
909  for (const auto & triangle : triangulate_with_ear_clipping(true))
910  append_triangle(triangle[0], triangle[1], triangle[2]);
911  return;
912  }
913 
914  const unsigned int n_verts = poly_nodes.size();
915  const Point poly_center = polygon_centroid(poly_nodes);
916 
917  bool added_triangle = false;
918  for (const auto i : make_range(n_verts))
919  if (triangleAreaHelper(poly_nodes[i], poly_nodes[(i + 1) % n_verts], poly_center) > _area_tol)
920  {
921  tri_map.push_back({i, (i + 1) % n_verts, n_verts});
922  added_triangle = true;
923  }
924 
925  if (added_triangle)
926  poly_nodes.push_back(poly_center);
927 }
MetaPhysicL::DualNumber< V, D, asd > abs(const MetaPhysicL::DualNumber< V, D, asd > &a)
Definition: EigenADReal.h:50
Real _area_tol
Tolerance times secondary area for dimensional consistency.
auto norm() const
Real _length_tol
Tolerance times secondary area for dimensional consistency.
void mooseError(Args &&... args)
Emit an error message with the given stringified, concatenated args and terminate the application...
Definition: MooseError.h:311
auto max(const L &left, const R &right)
Real value(unsigned n, unsigned alpha, unsigned beta, Real x)
const MortarSegmentTriangulationMode _triangulation_mode
Triangulation mode used for clipped polygons.
Real area(const std::vector< Point > &nodes) const
Compute area of polygon.
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
const bool _triangulate_triangles
Whether already-triangular polygons should still be centroid-subdivided.
auto norm(const T &a)
IntRange< T > make_range(T beg, T end)
auto min(const L &left, const R &right)
auto index_range(const T &sizable)

Friends And Related Function Documentation

◆ MortarSegmentHelperTest

friend class MortarSegmentHelperTest
friend

Definition at line 183 of file MortarSegmentHelper.h.

Member Data Documentation

◆ _area_tol

Real MortarSegmentHelper::_area_tol
private

Tolerance times secondary area for dimensional consistency.

Definition at line 233 of file MortarSegmentHelper.h.

Referenced by clipProjectedPoly(), isDisjoint(), isInsideSecondary(), MortarSegmentHelper(), referencePoint(), and triangulatePoly().

◆ _center

Point MortarSegmentHelper::_center
private

Geometric center of secondary element.

Definition at line 189 of file MortarSegmentHelper.h.

Referenced by center(), getMortarSegmentsImpl(), MortarSegmentHelper(), point(), and projectPrimaryPoly().

◆ _debug

bool MortarSegmentHelper::_debug
private

Definition at line 213 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl().

◆ _length_tol

Real MortarSegmentHelper::_length_tol
private

Tolerance times secondary area for dimensional consistency.

Definition at line 238 of file MortarSegmentHelper.h.

Referenced by clipProjectedPoly(), MortarSegmentHelper(), and triangulatePoly().

◆ _normal

Point MortarSegmentHelper::_normal
private

Unit normal of the plane used to project and clip the linearized secondary subpatch.

Definition at line 194 of file MortarSegmentHelper.h.

Referenced by MortarSegmentHelper(), and normal().

◆ _remaining_area_fraction

Real MortarSegmentHelper::_remaining_area_fraction
private

Fraction of area remaining after overlapping primary polygons clipped.

Definition at line 211 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl(), MortarSegmentHelper(), and remainder().

◆ _secondary_area

Real MortarSegmentHelper::_secondary_area
private

Area of projected secondary element.

Definition at line 206 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl(), and MortarSegmentHelper().

◆ _secondary_poly

std::vector<Point> MortarSegmentHelper::_secondary_poly
private

List of projected points on the linearized secondary element.

Definition at line 243 of file MortarSegmentHelper.h.

Referenced by clipProjectedPoly(), getMortarSegmentsImpl(), isDisjoint(), isInsideSecondary(), MortarSegmentHelper(), and point().

◆ _secondary_reference_points

std::vector<Point> MortarSegmentHelper::_secondary_reference_points
private

Parent reference points corresponding to _secondary_poly.

Definition at line 246 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl(), and MortarSegmentHelper().

◆ _tolerance

Real MortarSegmentHelper::_tolerance = 1e-8
private

Tolerance for intersection and clipping.

Definition at line 218 of file MortarSegmentHelper.h.

Referenced by clipProjectedPoly(), isInsideSecondary(), and MortarSegmentHelper().

◆ _triangulate_triangles

const bool MortarSegmentHelper::_triangulate_triangles
private

Whether already-triangular polygons should still be centroid-subdivided.

Definition at line 228 of file MortarSegmentHelper.h.

Referenced by triangulatePoly().

◆ _triangulation_mode

const MortarSegmentTriangulationMode MortarSegmentHelper::_triangulation_mode
private

Triangulation mode used for clipped polygons.

Definition at line 223 of file MortarSegmentHelper.h.

Referenced by triangulatePoly().

◆ _u

Point MortarSegmentHelper::_u
private

Vectors orthogonal to normal that span the plane projection will be performed on.

These vectors are used to project the polygon clipping problem on a 2D plane, they are defined so the nodes of the projected polygon are listed with positive orientation

Definition at line 201 of file MortarSegmentHelper.h.

Referenced by getMortarSegmentsImpl(), MortarSegmentHelper(), point(), and projectPrimaryPoly().

◆ _v

Point MortarSegmentHelper::_v
private

The documentation for this class was generated from the following files: