G+Smo  24.08.0
Geometry + Simulation Modules
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Groups Pages
gsCurveCurveIntersection.h
Go to the documentation of this file.
1 
14 #pragma once
15 
16 #include <gsCore/gsLinearAlgebra.h>
17 
18 namespace gismo
19 {
20 
21 namespace internal
22 {
23 
24 #define EPSILON_CCI ( 100*std::numeric_limits<T>::epsilon() )
25 
26 template<class T=real_t>
27 struct gsPoint2d
28 {
29  gsPoint2d(const T xCoord, const T yCoord)
30  {
31  pt.x() = xCoord;
32  pt.y() = yCoord;
33  }
34 
35  inline T x() const { return pt.x();}
36  inline T y() const { return pt.y();}
37 
38  gsPoint<2,T> pt;
39 };
40 
41 // To find orientation of ordered triplet (p, q, r).
42 // The function returns following values
43 // 0 --> p, q and r are collinear
44 // 1 --> Clockwise
45 // 2 --> Counterclockwise
46 template<class T>
47 short_t orientationxx(const gsPoint2d<T> &p, const gsPoint2d<T> &q, const gsPoint2d<T> &r)
48 {
49  // See https://www.geeksforgeeks.org/orientation-3-ordered-points/
50  // for details of below formula.
51  T val = (q.y() - p.y()) * (r.x() - q.x()) - (q.x() - p.x()) * (r.y() - q.y());
52 
53  // Caution: numerically val is almost never exactly zero
54  if (val == 0) return 0; // collinear
55 
56  return (val > 0) ? 1 : 2; // clock or counterclock wise
57 }
58 
59 // Given three collinear points p, q, r, the function checks if
60 // point q lies on the line segment 'pr'
61 template<class T>
62 bool onSegment(const gsPoint2d<T> &p, const gsPoint2d<T> &q, const gsPoint2d<T> &r)
63 {
64  if (q.x() <= math::max(p.x(), r.x()) && q.x() >= math::min(p.x(), r.x()) &&
65  q.y() <= math::max(p.y(), r.y()) && q.y() >= math::min(p.y(), r.y()))
66  return true;
67 
68  return false;
69 }
70 
71 // The main function that returns true if line segment 'p1q1'
72 // and 'p2q2' intersect.
73 template<class T>
74 bool doIntersect(const gsPoint2d<T> &p1, const gsPoint2d<T> &q1,
75  const gsPoint2d<T> &p2, const gsPoint2d<T> &q2)
76 {
77  // Find the four orientations needed for general and special cases
78  short_t o1 = orientationxx(p1, q1, p2);
79  short_t o2 = orientationxx(p1, q1, q2);
80  short_t o3 = orientationxx(p2, q2, p1);
81  short_t o4 = orientationxx(p2, q2, q1);
82 
83  // General case
84  if (o1 != o2 && o3 != o4)
85  return true;
86 
87  // Special Cases
88  // p1, q1 and p2 are collinear and p2 lies on segment p1q1
89  if (o1 == 0 && onSegment(p1, p2, q1)) return true;
90 
91  // p1, q1 and q2 are collinear and q2 lies on segment p1q1
92  if (o2 == 0 && onSegment(p1, q2, q1)) return true;
93 
94  // p2, q2 and p1 are collinear and p1 lies on segment p2q2
95  if (o3 == 0 && onSegment(p2, p1, q2)) return true;
96 
97  // p2, q2 and q1 are collinear and q1 lies on segment p2q2
98  if (o4 == 0 && onSegment(p2, q1, q2)) return true;
99 
100  return false; // Doesn't fall in any of the above cases
101 }
102 
114 template<class T=real_t>
115 struct gsCurveIntersectionResult
116 {
117  // Explicit constructor for direct initialization
118  explicit gsCurveIntersectionResult( const T paramOnCurve1,
119  const T paramOnCurve2,
120  const gsVector<T> & pt)
121  :
122  m_paramOnCurve1(paramOnCurve1),
123  m_paramOnCurve2(paramOnCurve2),
124  m_point(pt)
125  {}
126 
127  // Equality operators
128  bool operator==(const gsCurveIntersectionResult& other) const
129  {
130  return m_paramOnCurve1 == other.paramOnCurve1 &&
131  m_paramOnCurve2 == other.paramOnCurve2 &&
132  m_point == other.point;
133  }
134 
135  bool operator!=(const gsCurveIntersectionResult& other) const
136  {
137  return !(*this == other);
138  }
139 
140  T getParamOnCurve1() const { return m_paramOnCurve1; }
141  T getParamOnCurve2() const { return m_paramOnCurve2; }
142  gsVector<T> getPoint() const { return m_point; }
143 
144 private:
145  T m_paramOnCurve1;
146  T m_paramOnCurve2;
147  gsVector<T> m_point;
148 };
149 
151 template<class T=real_t>
152 class gsInterval
153 {
154 public:
155  gsInterval(const T min, const T max)
156  :
157  m_min(min),
158  m_max(max)
159  {}
160 
161  bool almostEqual(const T a, const T b, const T tolerance = EPSILON_CCI) const
162  {
163  return gsClose(a,b,tolerance);
164  }
165 
166  bool operator==(const gsInterval<T> &other) const
167  {
168  return almostEqual(m_min, other.getMin()) && almostEqual(m_max, other.getMax());
169  }
170 
171  T getMin() const { return m_min; }
172  T getMax() const { return m_max; }
173 
174  void setMin(T mmin) { m_min = mmin; }
175  void setMax(T mmax) { m_min = mmax; }
176 
177 private:
178  T m_min{0}, m_max{0};
179 };
180 
182 template<class T=real_t>
183 
184 class gsCurveBoundingBox
185 {
186 public:
187  explicit gsCurveBoundingBox(const gsBSpline<T> &curve)
188  :
189  range(curve.domainStart(),curve.domainEnd())
190  {
191  low.resize( curve.geoDim() );
192  high.resize(curve.geoDim() );
193  for (short_t i = 0; i != curve.geoDim(); ++i)
194  {
195  low[i] = std::numeric_limits<T>::max();
196  high[i] = std::numeric_limits<T>::lowest();
197  }
198 
199  // compute min / max from control points
200  for (index_t ipt = 0; ipt != curve.coefsSize(); ++ipt)
201  {
202  typename gsMatrix<T>::ConstRowXpr p = curve.coef(ipt); // Access once per iteration
203  for (short_t i = 0; i != curve.geoDim(); ++i)
204  {
205  high[i] = math::max(high[i], p(i));
206  low[i] = math::min(low[i], p(i));
207  }
208  }
209  }
210 
211 
212  bool intersect(const gsCurveBoundingBox<T> &other,
213  T eps = EPSILON_CCI) const
214  {
215  GISMO_ASSERT( this->getLow().size() == other.getLow().size(), "Size mismatch between this and other size" );
216  for (index_t i = 0; i != other.getLow().size(); ++i)
217  {
218  if (math::max(low[i], other.low[i]) > math::min(high[i], other.high[i]) + eps)
219  return false;
220  }
221  return true;
222  }
223 
224  gsVector<T> getLow() const { return low; }
225  gsVector<T> getHigh() const { return high; }
226  gsInterval<T> getRange() const { return range; }
227 
228 private:
229  gsVector<T> low, high;
230  gsInterval<T> range;
231 };
232 
233 template<class T=real_t>
234 struct gsBoundingBoxPair
235 {
236  gsBoundingBoxPair(const gsCurveBoundingBox<T> &i1, const gsCurveBoundingBox<T> &i2)
237  :
238  b1(i1),
239  b2(i2)
240  {}
241 
242  gsCurveBoundingBox<T> b1;
243  gsCurveBoundingBox<T> b2;
244 };
245 
253 template<class T=real_t>
254 std::vector<gsBoundingBoxPair<T>> getPotentialIntersectionRanges(const gsBSpline<T> &curve1,
255  const gsBSpline<T> &curve2,
256  const T MAX_CURVATURE)
257 {
258  gsCurveBoundingBox<T> h1(curve1);
259  gsCurveBoundingBox<T> h2(curve2);
260 
261  std::vector<gsBoundingBoxPair<T>> result;
262 
263  // Bounding boxes do not intersect. No intersection possible
264  if (!h1.intersect(h2))
265  return result;
266 
267  T crv1Curvature = curve1.pseudoCurvature();
268  T crv2Curvature = curve2.pseudoCurvature();
269  // static const T MAX_CURVATURE = 1.0 + 5e-6;
270 
271  // Check for intersection between endpoint line segments if curves are linear enough
272  if (crv1Curvature <= MAX_CURVATURE && crv2Curvature <= MAX_CURVATURE)
273  {
274  if ( curve1.geoDim() == 2 )
275  {
276  // line segment intersection check for the planar case
277  gsPoint2d<T> pt1(curve1.coef(0).x(), curve1.coef(0).y());
278  gsPoint2d<T> pt2(curve1.coef(curve1.coefsSize() - 1).x(), curve1.coef(curve1.coefsSize() - 1).y());
279  gsPoint2d<T> pt3(curve2.coef(0).x(), curve2.coef(0).y());
280  gsPoint2d<T> pt4(curve2.coef(curve2.coefsSize() - 1).x(), curve2.coef(curve2.coefsSize() - 1).y());
281  if (doIntersect(pt1, pt2, pt3, pt4))
282  {
283  result.push_back(gsBoundingBoxPair<T>(h1, h2));
284  return result;
285  }
286  }
287  else
288  {
289  result.push_back(gsBoundingBoxPair<T>(h1, h2));
290  return result;
291  }
292  }
293 
294  // Recursive subdividing for curves with high curvature
295  if (crv1Curvature > MAX_CURVATURE && crv2Curvature > MAX_CURVATURE)
296  {
297  // Subdivide both curves by splitting them in the parametric center
298  T curve1MidParm = 0.5 * (curve1.domainStart() + curve1.domainEnd());
299  T curve2MidParm = 0.5 * (curve2.domainStart() + curve2.domainEnd());
300 
301  gsBSpline<T> c11, c12, c21, c22;
302  curve1.splitAt(curve1MidParm, c11, c12);
303  curve2.splitAt(curve2MidParm, c21, c22);
304 
305  std::vector<gsBoundingBoxPair<T>> result1 = getPotentialIntersectionRanges<T>(c11, c21, MAX_CURVATURE);
306  std::vector<gsBoundingBoxPair<T>> result2 = getPotentialIntersectionRanges<T>(c11, c22, MAX_CURVATURE);
307  std::vector<gsBoundingBoxPair<T>> result3 = getPotentialIntersectionRanges<T>(c12, c21, MAX_CURVATURE);
308  std::vector<gsBoundingBoxPair<T>> result4 = getPotentialIntersectionRanges<T>(c12, c22, MAX_CURVATURE);
309 
310  // append all results
311  result.insert(result.end(), result1.begin(), result1.end());
312  result.insert(result.end(), result2.begin(), result2.end());
313  result.insert(result.end(), result3.begin(), result3.end());
314  result.insert(result.end(), result4.begin(), result4.end());
315 
316  return result;
317  }
318  else if (crv1Curvature <= MAX_CURVATURE && MAX_CURVATURE < crv2Curvature)
319  {
320  // Subdivide only curve 2
321  T curve2MidParm = 0.5 * (curve2.domainStart() + curve2.domainEnd());
322  gsBSpline<T> c21, c22;
323  curve2.splitAt(curve2MidParm, c21, c22);
324 
325  std::vector<gsBoundingBoxPair<T>> result1 = getPotentialIntersectionRanges<T>(curve1, c21, MAX_CURVATURE);
326  std::vector<gsBoundingBoxPair<T>> result2 = getPotentialIntersectionRanges<T>(curve1, c22, MAX_CURVATURE);
327 
328  result.insert(result.end(), result1.begin(), result1.end());
329  result.insert(result.end(), result2.begin(), result2.end());
330  return result;
331  }
332  else if (crv2Curvature <= MAX_CURVATURE && MAX_CURVATURE < crv1Curvature)
333  {
334  // Subdivide only curve 1
335  T curve1MidParm = 0.5 * (curve1.domainStart() + curve1.domainEnd());
336  gsBSpline<T> c11, c12;
337  curve1.splitAt(curve1MidParm, c11, c12);
338 
339  std::vector<gsBoundingBoxPair<T>> result1 = getPotentialIntersectionRanges<T>(c11, curve2, MAX_CURVATURE);
340  std::vector<gsBoundingBoxPair<T>> result2 = getPotentialIntersectionRanges<T>(c12, curve2, MAX_CURVATURE);
341 
342  result.insert(result.end(), result1.begin(), result1.end());
343  result.insert(result.end(), result2.begin(), result2.end());
344  return result;
345  }
346 
347  return result;
348 }
349 
351 template<class T=real_t>
352 class gsCurveCurveDistanceSystem
353 {
354 public:
355  gsCurveCurveDistanceSystem(const gsBSpline<T> &crv1, const gsBSpline<T> &crv2)
356  :
357  m_crv1(crv1),
358  m_crv2(crv2)
359  {
360  assert(crv1.geoDim() == crv2.geoDim());
361  }
362 
363  void Values(const gsVector<T,2> &uv,
364  gsMatrix<T> &funcVal,
365  gsMatrix<T> &invJac) const
366  {
367  std::vector<gsMatrix<T>> crv1Der = m_crv1.evalAllDers(uv.row(0), 1);
368  std::vector<gsMatrix<T>> crv2Der = m_crv2.evalAllDers(uv.row(1), 1);
369 
370  funcVal = crv1Der[0] - crv2Der[0];
371 
372  gsMatrix<T> jac(m_crv1.geoDim(), 2);
373  jac.col(0) = crv1Der[1];
374  jac.col(1) = -crv2Der[1];
375  invJac = jac.completeOrthogonalDecomposition().pseudoInverse();
376  }
377 
379  T compute(gsVector<T,2> &uv, T tolerance = 1e-5, size_t maxIter = 20) const
380  {
381  gsMatrix<T> funVal;
382  gsMatrix<T> invJac;
383  Values(uv, funVal, invJac); // Assumes Values correctly inverts the Jacobian
384  T currentResidual = funVal.norm();
385 
386  gsVector<T,2> deltaUv = invJac * funVal;
387  gsVector<T,2> newUv;
388  for (size_t iter = 1; iter != maxIter; ++iter)
389  {
390  uv -= deltaUv; // update uv
391 
392  // project uv into its parameter range
393  newUv.at(0) = math::min(math::max(uv.at(0), m_crv1.domainStart()), m_crv1.domainEnd());
394  newUv.at(1) = math::min(math::max(uv.at(1), m_crv2.domainStart()), m_crv2.domainEnd());
395  // using C++17 standard
396  // newUv(0, 0) = std::clamp(newUv(0, 0), m_crv1.domainStart(), m_crv1.domainEnd());
397  // newUv(1, 0) = std::clamp(newUv(1, 0), m_crv2.domainStart(), m_crv2.domainEnd());
398 
399  T delta = (uv - newUv).norm();
400  uv = newUv;
401 
402  if (delta > EPSILON_CCI)
403  {
404  Values(uv, funVal, invJac); // Re-evaluate funVal and invJac
405  currentResidual = funVal.norm();
406  break;
407  }
408 
409  Values(uv, funVal, invJac); // Re-evaluate funVal and invJac
410  currentResidual = funVal.norm();
411  if (currentResidual < tolerance)
412  break;
413 
414  deltaUv = invJac * funVal;
415  if (deltaUv.norm() < 1e-3*tolerance)
416  break;
417 
418  // gsInfo << "iter. = " << iter << ", residual = " << currentResidual << "\n";
419  }
420  return currentResidual;
421  }
422 
423 private:
424  const gsBSpline<T> m_crv1{}, m_crv2{};
425 };
426 
427 } // namespace internal
428 
429 } // namespace gismo
430 
gsMatrix< T >::RowXpr coef(index_t i)
Returns the i-th coefficient of the geometry as a row expression.
Definition: gsGeometry.h:346
T pseudoCurvature() const
Definition: gsBSpline.hpp:189
#define short_t
Definition: gsConfig.h:35
#define index_t
Definition: gsConfig.h:32
T domainEnd() const
Returns the end value of the domain of the basis.
Definition: gsBSpline.h:167
index_t coefsSize() const
Return the number of coefficients (control points)
Definition: gsGeometry.h:371
#define GISMO_ASSERT(cond, message)
Definition: gsDebug.h:89
A B-spline function of one argument, with arbitrary target dimension.
Definition: gsBSpline.h:50
bool operator!=(gsVertex< T > const &lhs, gsVertex< T > const &rhs)
Definition: gsVertex.h:240
bool gsClose(const T &a, const T &b, const T &tol)
tests if the difference between two numbers is below tolerance
Definition: gsMath.h:439
This is the main header file that collects wrappers of Eigen for linear algebra.
T domainStart() const
Returns the starting value of the domain of the basis.
Definition: gsBSpline.h:164
std::vector< gsBoundingBoxPair< T > > getPotentialIntersectionRanges(const gsBSpline< T > &curve1, const gsBSpline< T > &curve2, const T MAX_CURVATURE)
Definition: gsCurveCurveIntersection.h:254
short_t geoDim() const
Dimension n of the absent physical space.
Definition: gsGeometry.h:292
void splitAt(T u0, gsBSpline< T > &left, gsBSpline< T > &right, T tolerance=1e-15) const
Definition: gsBSpline.hpp:255
EIGEN_STRONG_INLINE jac_expr< E > jac(const symbol_expr< E > &u)
The Jacobian matrix of a FE variable.
Definition: gsExpressions.h:4530