https://mooseframework.inl.gov
MortarSegmentHelper.C
Go to the documentation of this file.
1 //* This file is part of the MOOSE framework
2 //* https://mooseframework.inl.gov
3 //*
4 //* All rights reserved, see COPYRIGHT for full restrictions
5 //* https://github.com/idaholab/moose/blob/master/COPYRIGHT
6 //*
7 //* Licensed under LGPL 2.1, please see LICENSE for details
8 //* https://www.gnu.org/licenses/lgpl-2.1.html
9 #include "MortarSegmentHelper.h"
10 #include "MooseError.h"
11 
12 #include "libmesh/int_range.h"
13 
14 using namespace libMesh;
15 
16 MortarSegmentHelper::MortarSegmentHelper(const std::vector<Point> secondary_nodes,
17  const Point & center,
18  const Point & normal)
19  : _center(center), _normal(normal), _debug(false)
20 {
21  _secondary_poly.clear();
22  _secondary_poly.reserve(secondary_nodes.size());
23 
24  // Get orientation of secondary poly
25  const Point e1 = secondary_nodes[0] - secondary_nodes[1];
26  const Point e2 = secondary_nodes[2] - secondary_nodes[1];
27  const Real orient = e2.cross(e1) * _normal;
28 
29  // u and v define the tangent plane of the element (at center)
30  // Note we embed orientation into our transformation to make 2D poly always
31  // positively oriented
32  _u = _normal.cross(secondary_nodes[0] - center).unit();
33  _v = (orient > 0) ? _normal.cross(_u).unit() : _u.cross(_normal).unit();
34 
35  // Transform problem to 2D plane spanned by u and v
36  for (const auto & node : secondary_nodes)
37  {
38  Point pt = node - _center;
39  _secondary_poly.emplace_back(pt * _u, pt * _v, 0);
40  }
41 
42  // Initialize area of secondary polygon
45 
46  // Tolerance for quantities with area dimensions
48 
49  // Tolerance for quantites with length dimensions
51 }
52 
53 Point
55  const Point & p1, const Point & p2, const Point & q1, const Point & q2, Real & s) const
56 {
57  const Point dp = p2 - p1;
58  const Point dq = q2 - q1;
59  const Real cp1q1 = p1(0) * q1(1) - p1(1) * q1(0);
60  const Real cp1q2 = p1(0) * q2(1) - p1(1) * q2(0);
61  const Real cq1q2 = q1(0) * q2(1) - q1(1) * q2(0);
62  const Real alpha = 1. / (dp(0) * dq(1) - dp(1) * dq(0));
63  s = -alpha * (cp1q2 - cp1q1 - cq1q2);
64 
65  // Intersection should be between p1 and p2, if it's not (due to poor conditioning), simply
66  // move it to one of the end points
67  s = s > 1 ? 1. : s;
68  s = s < 0 ? 0. : s;
69  return p1 + s * dp;
70 }
71 
72 bool
74 {
75  for (auto i : index_range(_secondary_poly))
76  {
77  const Point & q1 = _secondary_poly[i];
78  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
79 
80  const Point e1 = q2 - q1;
81  const Point e2 = pt - q1;
82 
83  // If point corresponds to one of the secondary vertices, skip
84  if (e2.norm() < _tolerance)
85  return true;
86 
87  bool inside = (e1(0) * e2(1) - e1(1) * e2(0)) < _area_tol;
88  if (!inside)
89  return false;
90  }
91  return true;
92 }
93 
94 bool
95 MortarSegmentHelper::isDisjoint(const std::vector<Point> & poly) const
96 {
97  for (auto i : index_range(_secondary_poly))
98  {
99  // Get edge to check
100  const Point & q1 = _secondary_poly[i];
101  const Point & q2 = _secondary_poly[(i + 1) % _secondary_poly.size()];
102  const Point edg = q2 - q1;
103  const Real cp = q2(0) * q1(1) - q2(1) * q1(0);
104 
105  // If more optimization needed, could store these values for later
106  // Check if point is to the left of (or on) clip_edge
107  auto is_inside = [&edg, cp](Point & pt, Real tol)
108  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < -tol; };
109 
110  bool all_outside = true;
111  for (auto pt : poly)
112  if (is_inside(pt, _area_tol))
113  all_outside = false;
114 
115  if (all_outside)
116  return true;
117  }
118  return false;
119 }
120 
121 std::vector<Point>
122 MortarSegmentHelper::clipPoly(const std::vector<Point> & primary_nodes) const
123 {
124  // Check orientation of primary_poly
125  const Point e1 = primary_nodes[0] - primary_nodes[1];
126  const Point e2 = primary_nodes[2] - primary_nodes[1];
127 
128  // Note we use u x v here instead of normal because it may be flipped if secondary elem was
129  // negatively oriented
130  const Real orient = e2.cross(e1) * _u.cross(_v);
131 
132  // Get primary_poly (primary is clipping poly). If negatively oriented, reverse
133  std::vector<Point> primary_poly;
134  const int n_verts = primary_nodes.size();
135  for (auto n : index_range(primary_nodes))
136  {
137  Point pt = (orient > 0) ? primary_nodes[n] - _center : primary_nodes[n_verts - 1 - n] - _center;
138  primary_poly.emplace_back(pt * _u, pt * _v, 0.);
139  }
140 
141  if (isDisjoint(primary_poly))
142  {
143  primary_poly.clear();
144  return primary_poly;
145  }
146 
147  // Initialize clipped poly with secondary poly (secondary is target poly)
148  std::vector<Point> clipped_poly = _secondary_poly;
149 
150  // Loop through clipping edges
151  for (auto i : index_range(primary_poly))
152  {
153  // If clipped poly trivial, return
154  if (clipped_poly.size() < 3)
155  {
156  clipped_poly.clear();
157  return clipped_poly;
158  }
159 
160  // Set input poly to current clipped poly
161  std::vector<Point> input_poly(clipped_poly);
162  clipped_poly.clear();
163 
164  // Get clipping edge
165  const Point & clip_pt1 = primary_poly[i];
166  const Point & clip_pt2 = primary_poly[(i + 1) % primary_poly.size()];
167  const Point edg = clip_pt2 - clip_pt1;
168  const Real cp = clip_pt2(0) * clip_pt1(1) - clip_pt2(1) * clip_pt1(0);
169 
170  // Check if point is to the left of (or on) clip_edge
171  /*
172  * Note that use of tolerance here is to avoid degenerate case when lines are
173  * essentially on top of each other (common when meshes match across interface)
174  * since finding intersection is ill-conditioned in this case.
175  */
176  auto is_inside = [&edg, cp](const Point & pt, Real tol)
177  { return pt(0) * edg(1) - pt(1) * edg(0) + cp < tol; };
178 
179  // Loop through edges of target polygon (with previous clippings already included)
180  for (auto j : index_range(input_poly))
181  {
182  // Get target edge
183  const Point curr_pt = input_poly[(j + 1) % input_poly.size()];
184  const Point prev_pt = input_poly[j];
185 
186  // TODO: Don't need to calculate both each loop
187  const bool is_current_inside = is_inside(curr_pt, _area_tol);
188  const bool is_previous_inside = is_inside(prev_pt, _area_tol);
189 
190  if (is_current_inside)
191  {
192  if (!is_previous_inside)
193  {
194  Real s;
195  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
196 
197  /*
198  * s is the fraction of distance along clip poly edge that intersection lies
199  * It is used here to avoid degenerate polygon cases. For example, consider a
200  * case like:
201  * o
202  * | (inside)
203  * ------|------
204  * | (outside)
205  * when the distance is small (< 1e-7) we don't want to to add both the point
206  * and intersection. Also note that when distance on the scale of 1e-7,
207  * area on scale of 1e-14 so is insignificant if this results in dropping
208  * a tri (for example if next edge crosses again)
209  */
210  if (s < (1 - _tolerance))
211  clipped_poly.push_back(intersect);
212  }
213  clipped_poly.push_back(curr_pt);
214  }
215  else if (is_previous_inside)
216  {
217  Real s;
218  Point intersect = getIntersection(prev_pt, curr_pt, clip_pt1, clip_pt2, s);
219  if (s > _tolerance)
220  clipped_poly.push_back(intersect);
221  }
222  }
223  }
224 
225  // return clipped_poly;
226  // Make sure final clipped poly is not trivial
227  if (clipped_poly.size() < 3)
228  {
229  clipped_poly.clear();
230  return clipped_poly;
231  }
232 
233  // Clean up result by removing any duplicate nodes
234  std::vector<Point> cleaned_poly;
235 
236  cleaned_poly.push_back(clipped_poly.back());
237  for (auto i : make_range(clipped_poly.size() - 1))
238  {
239  const Point prev_pt = cleaned_poly.back();
240  const Point curr_pt = clipped_poly[i];
241 
242  // If points are sufficiently distanced, add to output
243  if ((curr_pt - prev_pt).norm() > _length_tol)
244  cleaned_poly.push_back(curr_pt);
245  }
246 
247  return cleaned_poly;
248 }
249 
250 void
251 MortarSegmentHelper::triangulatePoly(std::vector<Point> & poly_nodes,
252  const unsigned int offset,
253  std::vector<std::vector<unsigned int>> & tri_map) const
254 {
255  // Note offset is important because it converts from list of nodes on local poly to list of
256  // nodes on entire element. This is a sloppy and error-prone way to do this.
257  // Could be redesigned by just building and adding elements inside helper, but leaving for now
258  // to keep buildMortarSegmentMesh similar for 2D and 3D
259 
260  // Fewer than 3 nodes can't be triangulated
261  if (poly_nodes.size() < 3)
262  mooseError("Can't triangulate poly with fewer than 3 nodes");
263  // If 3 nodes, already a triangle, simply pass back map
264  else if (poly_nodes.size() == 3)
265  {
266  tri_map.push_back({0 + offset, 1 + offset, 2 + offset});
267  return;
268  }
269  // Otherwise use simple center point triangulation
270  // Note: Could use better algorithm to reduce number of triangles
271  // - Delaunay produces good quality triangulation but might be a bit overkill
272  // - Monotone polygon triangulation algorithm is O(N) but quality is not guaranteed
273  else
274  {
275  Point poly_center;
276  const unsigned int n_verts = poly_nodes.size();
277 
278  // Get geometric center of polygon
279  for (auto node : poly_nodes)
280  poly_center += node;
281  poly_center /= n_verts;
282 
283  // Add triangles formed by outer edge and center point
284  for (auto i : make_range(n_verts))
285  tri_map.push_back({i + offset, (i + 1) % n_verts + offset, n_verts + offset});
286 
287  // Add center point to list of polygon nodes
288  poly_nodes.push_back(poly_center);
289  return;
290  }
291 }
292 
293 void
294 MortarSegmentHelper::getMortarSegments(const std::vector<Point> & primary_nodes,
295  std::vector<Point> & nodes,
296  std::vector<std::vector<unsigned int>> & elem_to_nodes)
297 {
298  // Clip primary elem against secondary elem
299  std::vector<Point> clipped_poly = clipPoly(primary_nodes);
300  if (clipped_poly.size() < 3)
301  return;
302 
303  if (_debug)
304  for (auto pt : clipped_poly)
305  if (!isInsideSecondary(pt))
306  mooseError("Clipped polygon not inside linearized secondary element");
307 
308  // Compute area of clipped polygon, update remaining area fraction
309  _remaining_area_fraction -= area(clipped_poly) / _secondary_area;
310 
311  // Triangulate clip polygon
312  triangulatePoly(clipped_poly, nodes.size(), elem_to_nodes);
313 
314  // Transform clipped poly back to (linearized) 3d and append to list
315  for (auto pt : clipped_poly)
316  nodes.emplace_back((pt(0) * _u) + (pt(1) * _v) + _center);
317 }
318 
319 Real
320 MortarSegmentHelper::area(const std::vector<Point> & nodes) const
321 {
322  Real poly_area = 0;
323  for (auto i : index_range(nodes))
324  poly_area += nodes[i](0) * nodes[(i + 1) % nodes.size()](1) -
325  nodes[i](1) * nodes[(i + 1) % nodes.size()](0);
326  poly_area *= 0.5;
327  return poly_area;
328 }
Point _center
Geometric center of secondary element.
Real _area_tol
Tolerance times secondary area for dimensional consistency.
bool isInsideSecondary(const Point &pt) const
Check that a point is inside the secondary polygon (for verification only)
auto norm() const -> decltype(std::norm(Real()))
R poly(const C &c, const T x, const bool derivative=false)
Evaluate a polynomial with the coefficients c at x.
Definition: MathUtils.h:237
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.
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:302
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...
The following methods are specializations for using the libMesh::Parallel::packed_range_* routines fo...
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.
bool isDisjoint(const std::vector< Point > &poly) const
Checks whether polygons are disjoint for an easy out.
const Point & center() const
Get center point of 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
auto norm(const T &a) -> decltype(std::abs(a))
DIE A HORRIBLE DEATH HERE typedef LIBMESH_DEFAULT_SCALAR_TYPE Real
CTSub CT_OPERATOR_BINARY CTMul CTCompareLess CTCompareGreater CTCompareEqual _arg template * sqrt(_arg)) *_arg.template D< dtag >()) CT_SIMPLE_UNARY_FUNCTION(tanh
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...
MortarSegmentHelper(const std::vector< Point > secondary_nodes, const Point &center, const Point &normal)
Real _remaining_area_fraction
Fraction of area remaining after overlapping primary polygons clipped.
void triangulatePoly(std::vector< Point > &poly_nodes, const unsigned int offset, std::vector< std::vector< unsigned int >> &tri_map) const
Triangulate a polygon (currently uses center of polygon to define triangulation)
auto index_range(const T &sizable)