G+Smo  25.01.0
Geometry + Simulation Modules
 
Loading...
Searching...
No Matches
gsCurveCurveIntersection.h
Go to the documentation of this file.
1
14#pragma once
15
17
18namespace gismo
19{
20
21namespace internal
22{
23
24#define EPSILON_CCI ( 100*std::numeric_limits<T>::epsilon() )
25
26template<class T=real_t>
27struct 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
46template<class T>
47short_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'
61template<class T>
62bool 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.
73template<class T>
74bool 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
114template<class T=real_t>
115struct 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.m_paramOnCurve1 &&
131 m_paramOnCurve2 == other.m_paramOnCurve2 &&
132 m_point == other.m_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
144private:
145 T m_paramOnCurve1;
146 T m_paramOnCurve2;
147 gsVector<T> m_point;
148};
149
151template<class T=real_t>
152class gsInterval
153{
154public:
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
177private:
178 T m_min{0}, m_max{0};
179};
180
182template<class T=real_t>
183
184class gsCurveBoundingBox
185{
186public:
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
228private:
229 gsVector<T> low, high;
230 gsInterval<T> range;
231};
232
233template<class T=real_t>
234struct 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
253template<class T=real_t>
254std::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
351template<class T=real_t>
352class gsCurveCurveDistanceSystem
353{
354public:
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
423private:
424 const gsBSpline<T> m_crv1{}, m_crv2{};
425};
426
427} // namespace internal
428
429} // namespace gismo
430
A B-spline function of one argument, with arbitrary target dimension.
Definition gsBSpline.h:51
T pseudoCurvature() const
Definition gsBSpline.hpp:189
void splitAt(T u0, gsBSpline< T > &left, gsBSpline< T > &right, T tolerance=1e-15) const
Definition gsBSpline.hpp:255
T domainEnd() const
Returns the end value of the domain of the basis.
Definition gsBSpline.h:167
T domainStart() const
Returns the starting value of the domain of the basis.
Definition gsBSpline.h:164
gsMatrix< T >::RowXpr coef(index_t i)
Returns the i-th coefficient of the geometry as a row expression.
Definition gsGeometry.h:346
short_t geoDim() const
Dimension n of the absent physical space.
Definition gsGeometry.h:292
index_t coefsSize() const
Return the number of coefficients (control points)
Definition gsGeometry.h:371
#define short_t
Definition gsConfig.h:35
#define index_t
Definition gsConfig.h:32
#define GISMO_ASSERT(cond, message)
Definition gsDebug.h:89
This is the main header file that collects wrappers of Eigen for linear algebra.
std::vector< gsBoundingBoxPair< T > > getPotentialIntersectionRanges(const gsBSpline< T > &curve1, const gsBSpline< T > &curve2, const T MAX_CURVATURE)
Definition gsCurveCurveIntersection.h:254
The G+Smo namespace, containing all definitions for the library.
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