https://mooseframework.inl.gov
Public Member Functions | Private Attributes | 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>

Public Member Functions

 MortarSegmentHelper (const std::vector< Point > secondary_nodes, const Point &center, const Point &normal, const MortarSegmentTriangulationMode triangulation_mode, const bool triangulate_triangles)
 
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...
 
Real area (const std::vector< Point > &nodes) const
 Compute area of polygon. More...
 
const Pointcenter () const
 Get center point of secondary element. 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 Attributes

Point _center
 Geometric center of secondary element. More...
 
Point _normal
 Normal at geometric center of secondary element. 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...
 

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 28 of file MortarSegmentHelper.h.

Constructor & Destructor Documentation

◆ MortarSegmentHelper()

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

Definition at line 270 of file MortarSegmentHelper.C.

275  : _center(center),
276  _normal(normal),
277  _debug(false),
278  _triangulation_mode(triangulation_mode),
279  _triangulate_triangles(triangulate_triangles)
280 {
281  _secondary_poly.clear();
282  _secondary_poly.reserve(secondary_nodes.size());
283 
284  // Get orientation of secondary poly
285  const Point e1 = secondary_nodes[0] - secondary_nodes[1];
286  const Point e2 = secondary_nodes[2] - secondary_nodes[1];
287  const Real orient = e2.cross(e1) * _normal;
288 
289  // u and v define the tangent plane of the element (at center)
290  // Note we embed orientation into our transformation to make 2D poly always
291  // positively oriented
292  _u = _normal.cross(secondary_nodes[0] - center).unit();
293  _v = (orient > 0) ? _normal.cross(_u).unit() : _u.cross(_normal).unit();
294 
295  // Transform problem to 2D plane spanned by u and v
296  for (const auto & node : secondary_nodes)
297  {
298  Point pt = node - _center;
299  _secondary_poly.emplace_back(pt * _u, pt * _v, 0);
300  }
301 
302  // Initialize area of secondary polygon
305 
306  // Tolerance for quantities with area dimensions
308 
309  // Tolerance for quantites with length dimensions
311 }
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
Normal at geometric center of secondary element.
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
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 932 of file MortarSegmentHelper.C.

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

933 {
934  Real poly_area = 0;
935  for (auto i : index_range(nodes))
936  poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
937  nodes[i](1) * nodes[(i + 1) % nodes.size()](0);
938  poly_area *= 0.5;
939  return poly_area;
940 }
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 97 of file MortarSegmentHelper.h.

Referenced by MortarSegmentHelper().

97 { 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 406 of file MortarSegmentHelper.C.

Referenced by getMortarSegments().

407 {
408  std::vector<Point> primary_poly = projectPrimaryPoly(primary_nodes);
409 
410  if (isDisjoint(primary_poly))
411  {
412  primary_poly.clear();
413  return primary_poly;
414  }
415 
416  // Initialize clipped poly with secondary poly (secondary is target poly)
417  std::vector<Point> clipped_poly = _secondary_poly;
418 
419  // Loop through clipping edges
420  for (auto i : index_range(primary_poly))
421  {
422  // If clipped poly trivial, return
423  if (clipped_poly.size() < 3)
424  {
425  clipped_poly.clear();
426  return clipped_poly;
427  }
428 
429  // Set input poly to current clipped poly
430  std::vector<Point> input_poly(clipped_poly);
431  clipped_poly.clear();
432 
433  // Get clipping edge
434  const Point & clip_pt1 = primary_poly[i];
435  const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
436  const Point edg = clip_pt2 - clip_pt1;
437  const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
438 
439  // Check if point is to the left of (or on) clip_edge
440  /*
441  * Note that use of tolerance here is to avoid degenerate case when lines are
442  * essentially on top of each other (common when meshes match across interface)
443  * since finding intersection is ill-conditioned in this case.
444  */
445  auto is_inside = [&edg, cp](const Point & pt, Real tol)
446  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
447 
448  // Loop through edges of target polygon (with previous clippings already included)
449  for (auto j : index_range(input_poly))
450  {
451  // Get target edge
452  const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
453  const Point prev_pt = input_poly[j];
454 
455  // TODO: Don't need to calculate both each loop
456  const bool is_current_inside = is_inside(curr_pt, _area_tol);
457  const bool is_previous_inside = is_inside(prev_pt, _area_tol);
458 
459  if (is_current_inside)
460  {
461  if (!is_previous_inside)
462  {
463  Real s;
464  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
465 
466  /*
467  * s is the fraction of distance along clip poly edge that intersection lies
468  * It is used here to avoid degenerate polygon cases. For example, consider a
469  * case like:
470  * o
471  * | (inside)
472  * ------|------
473  * | (outside)
474  * when the distance is small (< 1e-7) we don't want to to add both the point
475  * and intersection. Also note that when distance on the scale of 1e-7,
476  * area on scale of 1e-14 so is insignificant if this results in dropping
477  * a tri (for example if next edge crosses again)
478  */
479  if (s < (1 - _tolerance))
480  clipped_poly.push_back(intersect);
481  }
482  clipped_poly.push_back(curr_pt);
483  }
484  else if (is_previous_inside)
485  {
486  Real s;
487  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
488  if (s > _tolerance)
489  clipped_poly.push_back(intersect);
490  }
491  }
492  }
493 
494  // Make sure final clipped poly is not trivial
495  if (clipped_poly.size() < 3)
496  {
497  clipped_poly.clear();
498  return clipped_poly;
499  }
500 
501  // Clean up result by removing any duplicate nodes
502  std::vector<Point> cleaned_poly;
503  cleaned_poly.push_back(clipped_poly.back());
504  for (auto i : make_range(clipped_poly.size() - 1))
505  {
506  const Point prev_pt = cleaned_poly.back();
507  const Point curr_pt = clipped_poly[i];
508 
509  // If points are sufficiently distanced, add to output
510  if ((curr_pt - prev_pt).norm() > _length_tol)
511  cleaned_poly.push_back(curr_pt);
512  }
513 
514  mooseAssert(
515  cleaned_poly.size() <= 8,
516  "Our distributed mesh numbering scheme assumes that we have at most 8 nodes resulting from "
517  "clipping the projection of the primary sub-element onto the secondary sub-element");
518  return cleaned_poly;
519 }
Real _area_tol
Tolerance times secondary area for dimensional consistency.
Real _length_tol
Tolerance times secondary area for dimensional consistency.
std::vector< Point > projectPrimaryPoly(const std::vector< Point > &primary_nodes) const
Project a primary polygon into the helper plane while preserving the clipping orientation.
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 314 of file MortarSegmentHelper.C.

Referenced by clipPoly().

316 {
317  const Point dp = p2 - p1;
318  const Point dq = q2 - q1;
319  const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
320  const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
321  const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
322  const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
323  s = -alpha * (cp1q2 - cp1q1 - cq1q2);
324 
325  // Intersection should be between p1 and p2, if it's not (due to poor conditioning), simply
326  // move it to one of the end points
327  s = s > 1 ? 1. : s;
328  s = s < 0 ? 0. : s;
329  return p1 + s * dp;
330 }
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real

◆ getMortarSegments()

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 892 of file MortarSegmentHelper.C.

895 {
896  // Clip primary elem against secondary elem
897  std::vector<Point> clipped_poly = clipPoly(primary_nodes);
898  if (clipped_poly.size() < 3)
899  return;
900 
901  if (_debug)
902  for (auto pt : clipped_poly)
903  if (!isInsideSecondary(pt))
904  mooseError("Clipped polygon not inside linearized secondary element");
905 
906  // Compute area of clipped polygon, update remaining area fraction
907  _remaining_area_fraction -= area(clipped_poly) / _secondary_area;
908 
909  // Triangulate clip polygon. tri_map indices are local to clipped_poly (starting at 0); we
910  // shift them into the global node numbering after appending the polygon nodes below.
911  std::vector<std::vector<unsigned int>> tri_map;
912  triangulatePoly(clipped_poly, tri_map);
913  if (tri_map.empty())
914  return;
915 
916  // Transform clipped poly back to (linearized) 3d and append to list
917  const auto offset = cast_int<unsigned int>(nodes.size());
918  for (auto pt : clipped_poly)
919  nodes.emplace_back((pt(0) * _u) + (pt(1) * _v) + _center);
920 
921  for (const auto & tri : tri_map)
922  {
923  std::vector<unsigned int> shifted_tri;
924  shifted_tri.reserve(tri.size());
925  for (const auto local_index : tri)
926  shifted_tri.push_back(offset + local_index);
927  elem_to_nodes.push_back(std::move(shifted_tri));
928  }
929 }
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)
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...
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.
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.
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.

◆ isDisjoint()

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

Checks whether polygons are disjoint for an easy out.

Definition at line 355 of file MortarSegmentHelper.C.

Referenced by clipPoly().

356 {
357  for (auto i : index_range(_secondary_poly))
358  {
359  // Get edge to check
360  const Point & q1 = _secondary_poly[i];
361  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
362  const Point edg = q2 - q1;
363  const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
364 
365  // If more optimization needed, could store these values for later
366  // Check if point is to the left of (or on) clip_edge
367  auto is_inside = [&edg, cp](Point & pt, Real tol)
368  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
369 
370  bool all_outside = true;
371  for (auto pt : poly)
372  if (is_inside(pt, _area_tol))
373  all_outside = false;
374 
375  if (all_outside)
376  return true;
377  }
378  return false;
379 }
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 333 of file MortarSegmentHelper.C.

Referenced by getMortarSegments().

334 {
335  for (auto i : index_range(_secondary_poly))
336  {
337  const Point & q1 = _secondary_poly[i];
338  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
339 
340  const Point e1 = q2 - q1;
341  const Point e2 = pt - q1;
342 
343  // If point corresponds to one of the secondary vertices, skip
344  if (e2.norm() < _tolerance)
345  return true;
346 
347  const bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) < _area_tol;
348  if (!inside)
349  return false;
350  }
351  return true;
352 }
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)

◆ point()

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

Get 3D position of node of linearized secondary element.

Definition at line 107 of file MortarSegmentHelper.h.

108  {
109  return (_secondary_poly[i](0) * _u) + (_secondary_poly[i](1) * _v) + _center;
110  }
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 382 of file MortarSegmentHelper.C.

Referenced by clipPoly().

383 {
384  // Check orientation of primary_poly
385  const Point e1 = primary_nodes[0] - primary_nodes[1];
386  const Point e2 = primary_nodes[2] - primary_nodes[1];
387 
388  // Note we use u x v here instead of normal because it may be flipped if secondary elem was
389  // negatively oriented
390  const Real orient = e2.cross(e1) * _u.cross(_v);
391 
392  // Get primary_poly (primary is clipping poly). If negatively oriented, reverse
393  std::vector<Point> primary_poly;
394  const int n_verts = primary_nodes.size();
395  primary_poly.reserve(primary_nodes.size());
396  for (auto n : index_range(primary_nodes))
397  {
398  Point pt = (orient > 0) ? primary_nodes[n] - _center : primary_nodes[n_verts - 1 - n] - _center;
399  primary_poly.emplace_back(pt * _u, pt * _v, 0.);
400  }
401 
402  return primary_poly;
403 }
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)

◆ remainder()

Real MortarSegmentHelper::remainder ( ) const
inline

Get area fraction remaining after clipping against primary elements.

Definition at line 102 of file MortarSegmentHelper.h.

102 { 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 522 of file MortarSegmentHelper.C.

Referenced by getMortarSegments().

524 {
525  // tri_map is populated with triangle indices that are local to poly_nodes (starting at 0).
526  // Callers are responsible for shifting these indices into a global node numbering.
527  const auto polygon_centroid = [](const std::vector<Point> & polygon_nodes)
528  {
529  Point centroid(0);
530  Real double_area = 0;
531  for (const auto i : index_range(polygon_nodes))
532  {
533  const auto & a = polygon_nodes[i];
534  const auto & b = polygon_nodes[(i + 1) % polygon_nodes.size()];
535  const Real cross = a(0) * b(1) - b(0) * a(1);
536  double_area += cross;
537  centroid(0) += (a(0) + b(0)) * cross;
538  centroid(1) += (a(1) + b(1)) * cross;
539  }
540 
541  if (std::abs(double_area) <= TOLERANCE)
542  {
543  for (const auto & node : polygon_nodes)
544  centroid += node;
545  centroid /= polygon_nodes.size();
546  return centroid;
547  }
548 
549  centroid /= (3. * double_area);
550  centroid(2) = 0;
551  return centroid;
552  };
553 
554  const auto append_triangle = [this, &poly_nodes, &tri_map](
555  const unsigned int a, const unsigned int b, const unsigned int c)
556  {
557  if (triangleAreaHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) <= _area_tol)
558  return false;
559 
560  if (orient2dHelper(poly_nodes[a], poly_nodes[b], poly_nodes[c]) >= 0)
561  tri_map.push_back({a, b, c});
562  else
563  tri_map.push_back({a, c, b});
564 
565  return true;
566  };
567 
568  const auto point_in_triangle =
569  [this](const Point & p, const Point & a, const Point & b, const Point & c)
570  {
571  const Real ab = orient2dHelper(a, b, p);
572  const Real bc = orient2dHelper(b, c, p);
573  const Real ca = orient2dHelper(c, a, p);
574  return ab >= -_area_tol && bc >= -_area_tol && ca >= -_area_tol;
575  };
576 
577  const auto min_triangle_angle = [](const Point & a, const Point & b, const Point & c)
578  {
579  const auto clamp_cos = [](Real value) { return std::max(-1., std::min(1., value)); };
580  const auto angle_at =
581  [&clamp_cos](const Point & vertex, const Point & point_one, const Point & point_two)
582  {
583  const Point edge_one = point_one - vertex;
584  const Point edge_two = point_two - vertex;
585  const Real denom = edge_one.norm() * edge_two.norm();
586  if (denom <= TOLERANCE)
587  return 0.;
588  return std::acos(clamp_cos((edge_one * edge_two) / denom));
589  };
590 
591  return std::min({angle_at(a, b, c), angle_at(b, c, a), angle_at(c, a, b)});
592  };
593 
594  const auto canonicalize_polygon = [this, &poly_nodes]()
595  {
596  if (poly_nodes.size() < 3)
597  return;
598 
599  if (area(poly_nodes) < 0)
600  std::reverse(poly_nodes.begin(), poly_nodes.end());
601 
602  bool changed = true;
603  while (changed && poly_nodes.size() > 3)
604  {
605  changed = false;
606  for (const auto i : index_range(poly_nodes))
607  {
608  const auto prev = (i + poly_nodes.size() - 1) % poly_nodes.size();
609  const auto next = (i + 1) % poly_nodes.size();
610  if ((poly_nodes[i] - poly_nodes[prev]).norm() <= _length_tol ||
611  (poly_nodes[next] - poly_nodes[i]).norm() <= _length_tol ||
612  triangleAreaHelper(poly_nodes[prev], poly_nodes[i], poly_nodes[next]) <= _area_tol)
613  {
614  poly_nodes.erase(poly_nodes.begin() + i);
615  changed = true;
616  break;
617  }
618  }
619  }
620 
621  if (poly_nodes.size() >= 3 && area(poly_nodes) < 0)
622  std::reverse(poly_nodes.begin(), poly_nodes.end());
623  };
624 
625  const auto triangulate_with_ear_clipping =
626  [this, &poly_nodes, &point_in_triangle, &min_triangle_angle](
627  const bool perform_delaunay_flips)
628  {
629  std::vector<std::array<unsigned int, 3>> triangles;
630  if (poly_nodes.size() < 3)
631  return triangles;
632 
633  if (poly_nodes.size() == 3)
634  {
635  triangles.push_back(makeCCWTriangleHelper(poly_nodes, 0, 1, 2));
636  return triangles;
637  }
638 
639  std::vector<unsigned int> remaining_vertices(poly_nodes.size());
640  std::iota(remaining_vertices.begin(), remaining_vertices.end(), 0);
641 
642  while (remaining_vertices.size() > 3)
643  {
644  std::optional<std::size_t> best_position;
645  Real best_score = -std::numeric_limits<Real>::max();
646  Real best_area = -std::numeric_limits<Real>::max();
647 
648  for (const auto position : index_range(remaining_vertices))
649  {
650  const auto prev_position =
651  (position + remaining_vertices.size() - 1) % remaining_vertices.size();
652  const auto next_position = (position + 1) % remaining_vertices.size();
653  const auto prev = remaining_vertices[prev_position];
654  const auto curr = remaining_vertices[position];
655  const auto next = remaining_vertices[next_position];
656 
657  if (orient2dHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]) <= _area_tol)
658  continue;
659 
660  bool contains_other_vertex = false;
661  for (const auto other : remaining_vertices)
662  {
663  if (other == prev || other == curr || other == next)
664  continue;
665 
666  if (point_in_triangle(
667  poly_nodes[other], poly_nodes[prev], poly_nodes[curr], poly_nodes[next]))
668  {
669  contains_other_vertex = true;
670  break;
671  }
672  }
673 
674  if (contains_other_vertex)
675  continue;
676 
677  const Real candidate_score =
678  min_triangle_angle(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
679  const Real candidate_area =
680  triangleAreaHelper(poly_nodes[prev], poly_nodes[curr], poly_nodes[next]);
681  if (!best_position || candidate_score > best_score + TOLERANCE ||
682  (std::abs(candidate_score - best_score) <= TOLERANCE &&
683  candidate_area > best_area + _area_tol))
684  {
685  best_position = position;
686  best_score = candidate_score;
687  best_area = candidate_area;
688  }
689  }
690 
691  if (!best_position)
692  {
693  std::vector<std::array<unsigned int, 3>> best_fan;
694  Real best_fan_score = -std::numeric_limits<Real>::max();
695  Real best_fan_area = -std::numeric_limits<Real>::max();
696 
697  for (const auto root_position : index_range(remaining_vertices))
698  {
699  std::vector<std::array<unsigned int, 3>> candidate_fan;
700  Real candidate_score = std::numeric_limits<Real>::max();
701  Real candidate_area = std::numeric_limits<Real>::max();
702  bool valid_fan = true;
703  const auto root = remaining_vertices[root_position];
704 
705  for (unsigned int step = 1; step + 1 < remaining_vertices.size(); ++step)
706  {
707  const auto next_position = (root_position + step) % remaining_vertices.size();
708  const auto following_position = (root_position + step + 1) % remaining_vertices.size();
709  const auto vertex_one = remaining_vertices[next_position];
710  const auto vertex_two = remaining_vertices[following_position];
711 
712  if (orient2dHelper(poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]) <=
713  _area_tol)
714  {
715  valid_fan = false;
716  break;
717  }
718 
719  candidate_fan.push_back(
720  makeCCWTriangleHelper(poly_nodes, root, vertex_one, vertex_two));
721  candidate_score =
722  std::min(candidate_score,
723  min_triangle_angle(
724  poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
725  candidate_area =
726  std::min(candidate_area,
727  triangleAreaHelper(
728  poly_nodes[root], poly_nodes[vertex_one], poly_nodes[vertex_two]));
729  }
730 
731  if (!valid_fan || candidate_fan.empty())
732  continue;
733 
734  if (candidate_score > best_fan_score + TOLERANCE ||
735  (std::abs(candidate_score - best_fan_score) <= TOLERANCE &&
736  candidate_area > best_fan_area + _area_tol))
737  {
738  best_fan = std::move(candidate_fan);
739  best_fan_score = candidate_score;
740  best_fan_area = candidate_area;
741  }
742  }
743 
744  if (best_fan.empty())
745  for (unsigned int i = 1; i + 1 < remaining_vertices.size(); ++i)
746  best_fan.push_back(makeCCWTriangleHelper(poly_nodes,
747  remaining_vertices[0],
748  remaining_vertices[i],
749  remaining_vertices[i + 1]));
750 
751  triangles.insert(triangles.end(), best_fan.begin(), best_fan.end());
752  break;
753  }
754 
755  const auto prev_position =
756  (*best_position + remaining_vertices.size() - 1) % remaining_vertices.size();
757  const auto next_position = (*best_position + 1) % remaining_vertices.size();
758  triangles.push_back(makeCCWTriangleHelper(poly_nodes,
759  remaining_vertices[prev_position],
760  remaining_vertices[*best_position],
761  remaining_vertices[next_position]));
762  remaining_vertices.erase(remaining_vertices.begin() + *best_position);
763  }
764 
765  if (remaining_vertices.size() == 3)
766  triangles.push_back(makeCCWTriangleHelper(
767  poly_nodes, remaining_vertices[0], remaining_vertices[1], remaining_vertices[2]));
768 
769  if (!perform_delaunay_flips)
770  return triangles;
771 
772  std::set<std::array<unsigned int, 2>> boundary_edges;
773  for (const auto i : index_range(poly_nodes))
774  boundary_edges.insert(canonicalEdgeHelper(i, (i + 1) % poly_nodes.size()));
775 
776  performLocalDelaunayFlips(poly_nodes, boundary_edges, triangles);
777  return triangles;
778  };
779 
780  const auto is_convex_polygon = [this](const std::vector<Point> & polygon_nodes)
781  {
782  if (polygon_nodes.size() <= 3)
783  return true;
784 
785  for (const auto i : index_range(polygon_nodes))
786  {
787  const auto prev = (i + polygon_nodes.size() - 1) % polygon_nodes.size();
788  const auto next = (i + 1) % polygon_nodes.size();
789  if (orient2dHelper(polygon_nodes[prev], polygon_nodes[i], polygon_nodes[next]) <= _area_tol)
790  return false;
791  }
792 
793  return true;
794  };
795 
796  // Fewer than 3 nodes can't be triangulated
797  if (poly_nodes.size() < 3)
798  mooseError("Can't triangulate poly with fewer than 3 nodes");
799 
800  // Legacy centroid path: when the default triangulation (centroid) is selected
801  // and triangle re-tessellation is not requested, reproduce the legacy
802  // algorithm byte-for-byte so existing mortar baselines remain valid.
803  // Uses the arithmetic mean of the vertices (not the area-weighted centroid),
804  // emits one triangle per polygon edge without degeneracy filtering, and skips
805  // the canonicalization pass which would drop near-degenerate vertices and
806  // perturb integration weights in downstream test baselines.
808  {
809  if (poly_nodes.size() == 3)
810  {
811  tri_map.push_back({0, 1, 2});
812  return;
813  }
814 
815  const unsigned int n_verts = poly_nodes.size();
816  Point poly_center;
817  for (const auto & node : poly_nodes)
818  poly_center += node;
819  poly_center /= n_verts;
820 
821  for (const auto i : make_range(n_verts))
822  tri_map.push_back({i, (i + 1) % n_verts, n_verts});
823 
824  poly_nodes.push_back(poly_center);
825  return;
826  }
827 
828  canonicalize_polygon();
829  if (poly_nodes.size() < 3)
830  return;
831 
832  if (poly_nodes.size() == 3 && !_triangulate_triangles)
833  {
834  append_triangle(0, 1, 2);
835  return;
836  }
837 
838  const bool force_triangle_centroid_split = _triangulate_triangles && poly_nodes.size() == 3;
839 
841  !force_triangle_centroid_split)
842  {
843  const unsigned int n_verts = poly_nodes.size();
844  for (unsigned int i = 1; i + 1 < n_verts; ++i)
845  append_triangle(0, i, i + 1);
846  return;
847  }
848 
850  !force_triangle_centroid_split)
851  {
852 #if defined(LIBMESH_HAVE_TRIANGLE) || defined(LIBMESH_HAVE_POLY2TRI)
853  triangulateConstrainedDelaunayPolygon(poly_nodes, _area_tol, _length_tol, tri_map);
854  return;
855 #else
856  mooseError("The 'delaunay' mortar triangulation mode requires libMesh TriangleInterface or "
857  "Poly2Tri support.");
858 #endif
859  }
860 
862  !force_triangle_centroid_split)
863  {
864  for (const auto & triangle : triangulate_with_ear_clipping(true))
865  append_triangle(triangle[0], triangle[1], triangle[2]);
866  return;
867  }
868 
869  if (!force_triangle_centroid_split && !is_convex_polygon(poly_nodes))
870  {
871  for (const auto & triangle : triangulate_with_ear_clipping(true))
872  append_triangle(triangle[0], triangle[1], triangle[2]);
873  return;
874  }
875 
876  const unsigned int n_verts = poly_nodes.size();
877  const Point poly_center = polygon_centroid(poly_nodes);
878 
879  bool added_triangle = false;
880  for (const auto i : make_range(n_verts))
881  if (triangleAreaHelper(poly_nodes[i], poly_nodes[(i + 1) % n_verts], poly_center) > _area_tol)
882  {
883  tri_map.push_back({i, (i + 1) % n_verts, n_verts});
884  added_triangle = true;
885  }
886 
887  if (added_triangle)
888  poly_nodes.push_back(poly_center);
889 }
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)

Member Data Documentation

◆ _area_tol

Real MortarSegmentHelper::_area_tol
private

Tolerance times secondary area for dimensional consistency.

Definition at line 160 of file MortarSegmentHelper.h.

Referenced by clipPoly(), isDisjoint(), isInsideSecondary(), MortarSegmentHelper(), and triangulatePoly().

◆ _center

Point MortarSegmentHelper::_center
private

Geometric center of secondary element.

Definition at line 116 of file MortarSegmentHelper.h.

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

◆ _debug

bool MortarSegmentHelper::_debug
private

Definition at line 140 of file MortarSegmentHelper.h.

Referenced by getMortarSegments().

◆ _length_tol

Real MortarSegmentHelper::_length_tol
private

Tolerance times secondary area for dimensional consistency.

Definition at line 165 of file MortarSegmentHelper.h.

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

◆ _normal

Point MortarSegmentHelper::_normal
private

Normal at geometric center of secondary element.

Definition at line 121 of file MortarSegmentHelper.h.

Referenced by MortarSegmentHelper().

◆ _remaining_area_fraction

Real MortarSegmentHelper::_remaining_area_fraction
private

Fraction of area remaining after overlapping primary polygons clipped.

Definition at line 138 of file MortarSegmentHelper.h.

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

◆ _secondary_area

Real MortarSegmentHelper::_secondary_area
private

Area of projected secondary element.

Definition at line 133 of file MortarSegmentHelper.h.

Referenced by getMortarSegments(), and MortarSegmentHelper().

◆ _secondary_poly

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

List of projected points on the linearized secondary element.

Definition at line 170 of file MortarSegmentHelper.h.

Referenced by clipPoly(), isDisjoint(), isInsideSecondary(), MortarSegmentHelper(), and point().

◆ _tolerance

Real MortarSegmentHelper::_tolerance = 1e-8
private

Tolerance for intersection and clipping.

Definition at line 145 of file MortarSegmentHelper.h.

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

◆ _triangulate_triangles

const bool MortarSegmentHelper::_triangulate_triangles
private

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

Definition at line 155 of file MortarSegmentHelper.h.

Referenced by triangulatePoly().

◆ _triangulation_mode

const MortarSegmentTriangulationMode MortarSegmentHelper::_triangulation_mode
private

Triangulation mode used for clipped polygons.

Definition at line 150 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 128 of file MortarSegmentHelper.h.

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

◆ _v

Point MortarSegmentHelper::_v
private

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