2
2
// Licensed under the Six Labors Split License.
3
3
4
4
using System ;
5
+ using System . Diagnostics . CodeAnalysis ;
5
6
using System . Runtime . CompilerServices ;
6
7
7
8
namespace PolygonClipper ;
@@ -20,54 +21,41 @@ public static double SignedArea(Vertex p0, Vertex p1, Vertex p2)
20
21
=> ( ( p0 . X - p2 . X ) * ( p1 . Y - p2 . Y ) ) - ( ( p1 . X - p2 . X ) * ( p0 . Y - p2 . Y ) ) ;
21
22
22
23
/// <summary>
23
- /// Finds the intersection between two intervals [u0, u1] and [v0, v1] .
24
+ /// Finds the intersection of two line segments, constraining results to their intersection bounding box .
24
25
/// </summary>
25
- /// <param name="u0">The start of the first interval.</param>
26
- /// <param name="u1">The end of the first interval.</param>
27
- /// <param name="v0">The start of the second interval.</param>
28
- /// <param name="v1">The end of the second interval.</param>
29
- /// <param name="start">
30
- /// The start of the intersection interval, or the single intersection point if there is only one.
31
- /// </param>
32
- /// <param name="end">
33
- /// The end of the intersection interval. If the intersection is a single point, this value is undefined.
34
- /// </param>
26
+ /// <param name="seg0">The first segment.</param>
27
+ /// <param name="seg1">The second segment.</param>
28
+ /// <param name="pi0">The first intersection point.</param>
29
+ /// <param name="pi1">The second intersection point (if overlap occurs).</param>
35
30
/// <returns>
36
- /// An <see cref="int"/> indicating the type of intersection:
31
+ /// An <see cref="int"/> indicating the number of intersection points :
37
32
/// - Returns 0 if there is no intersection.
38
- /// - Returns 1 if the intersection is a single point.
39
- /// - Returns 2 if the intersection is an interval .
33
+ /// - Returns 1 if the segments intersect at a single point.
34
+ /// - Returns 2 if the segments overlap .
40
35
/// </returns>
41
- public static int FindIntersection ( double u0 , double u1 , double v0 , double v1 , out double start , out double end )
36
+ public static int FindIntersection ( Segment seg0 , Segment seg1 , out Vertex pi0 , out Vertex pi1 )
42
37
{
43
- start = 0 ;
44
- end = 0 ;
38
+ pi0 = default ;
39
+ pi1 = default ;
45
40
46
- if ( ( u1 < v0 ) || ( u0 > v1 ) )
41
+ if ( ! TryGetIntersectionBoundingBox ( seg0 . Source , seg0 . Target , seg1 . Source , seg1 . Target , out Box2 ? bbox ) )
47
42
{
48
- return 0 ; // No intersection
43
+ return 0 ;
49
44
}
50
45
51
- if ( u1 > v0 )
52
- {
53
- if ( u0 < v1 )
54
- {
55
- // There is an overlapping range
56
- start = ( u0 < v0 ) ? v0 : u0 ;
57
- end = ( u1 > v1 ) ? v1 : u1 ;
58
- return 2 ; // Two endpoints defining the intersection range
59
- }
60
-
61
- // u0 == v1
62
- start = u0 ;
46
+ int interResult = FindIntersectionImpl ( seg0 , seg1 , out pi0 , out pi1 ) ;
63
47
64
- return 1 ; // Single point intersection
48
+ if ( interResult == 1 )
49
+ {
50
+ pi0 = ConstrainToBoundingBox ( pi0 , bbox . Value ) ;
51
+ }
52
+ else if ( interResult == 2 )
53
+ {
54
+ pi0 = ConstrainToBoundingBox ( pi0 , bbox . Value ) ;
55
+ pi1 = ConstrainToBoundingBox ( pi1 , bbox . Value ) ;
65
56
}
66
57
67
- // u1 == v0
68
- start = u1 ;
69
-
70
- return 1 ; // Single point intersection
58
+ return interResult ;
71
59
}
72
60
73
61
/// <summary>
@@ -88,60 +76,58 @@ public static int FindIntersection(double u0, double u1, double v0, double v1, o
88
76
/// - Returns 1 if the segments intersect at a single point.
89
77
/// - Returns 2 if the segments overlap.
90
78
/// </returns>
91
- public static int FindIntersection ( Segment seg0 , Segment seg1 , out Vertex pi0 , out Vertex pi1 )
79
+ private static int FindIntersectionImpl ( Segment seg0 , Segment seg1 , out Vertex pi0 , out Vertex pi1 )
92
80
{
93
81
pi0 = default ;
94
82
pi1 = default ;
95
83
96
- Vertex p0 = seg0 . Source ;
97
- Vertex p1 = seg1 . Source ;
84
+ Vertex a1 = seg0 . Source ;
85
+ Vertex a2 = seg1 . Source ;
98
86
99
- Vertex va = seg0 . Target - p0 ;
100
- Vertex vb = seg1 . Target - p1 ;
87
+ Vertex va = seg0 . Target - a1 ;
88
+ Vertex vb = seg1 . Target - a2 ;
89
+ Vertex e = a2 - a1 ;
101
90
102
- const double sqrEpsilon = 0.0000001 ; // Threshold for comparing-point precision
103
- Vertex e = p1 - p0 ;
104
91
double kross = Vertex . Cross ( va , vb ) ;
105
92
double sqrKross = kross * kross ;
106
93
double sqrLenA = Vertex . Dot ( va , va ) ;
107
- // double sqrLen1 = Vertex.Dot(vb, vb);
108
94
109
95
if ( sqrKross > 0 )
110
96
{
111
97
// Lines of the segments are not parallel
112
98
double s = Vertex . Cross ( e , vb ) / kross ;
113
- if ( s is < 0 or > 1 )
99
+ if ( s < 0 || s > 1 )
114
100
{
115
101
return 0 ;
116
102
}
117
103
118
104
double t = Vertex . Cross ( e , va ) / kross ;
119
- if ( t is < 0 or > 1 )
105
+ if ( t < 0 || t > 1 )
120
106
{
121
107
return 0 ;
122
108
}
123
109
110
+ // If s or t is exactly 0 or 1, the intersection is on an endpoint
124
111
if ( s == 0 || s == 1 )
125
112
{
126
- // on an endpoint of line segment a
127
- pi0 = p0 + ( s * va ) ;
113
+ // On an endpoint of line segment a
114
+ pi0 = MidPoint ( a1 , s , va ) ;
128
115
return 1 ;
129
116
}
130
117
131
118
if ( t == 0 || t == 1 )
132
119
{
133
- // on an endpoint of line segment b
134
- pi0 = p1 + ( t * vb ) ;
120
+ // On an endpoint of line segment b
121
+ pi0 = MidPoint ( a2 , t , vb ) ;
135
122
return 1 ;
136
123
}
137
124
138
125
// Intersection of lines is a point on each segment
139
- pi0 = p0 + ( s * va ) ;
126
+ pi0 = a1 + ( s * va ) ;
140
127
return 1 ;
141
128
}
142
129
143
- // Lines of the segments are parallel
144
- // double sqrLenE = (e.X * e.X) + (e.Y * e.Y);
130
+ // Lines are parallel; check if they are collinear
145
131
kross = Vertex . Cross ( e , va ) ;
146
132
sqrKross = kross * kross ;
147
133
if ( sqrKross > 0 )
@@ -150,44 +136,82 @@ public static int FindIntersection(Segment seg0, Segment seg1, out Vertex pi0, o
150
136
return 0 ;
151
137
}
152
138
153
- // Lines of the segments are the same. Need to test for overlap of segments.
154
- double s0 = Vertex . Dot ( va , e ) / sqrLenA ; // so = Dot (D0, E) * sqrLen0
155
- double s1 = s0 + ( Vertex . Dot ( va , vb ) / sqrLenA ) ; // s1 = s0 + Dot (D0, D1) * sqrLen0
156
- double smin = Math . Min ( s0 , s1 ) ;
157
- double smax = Math . Max ( s0 , s1 ) ;
158
- int imax = FindIntersection ( 0F , 1F , smin , smax , out double w0 , out double w1 ) ;
139
+ // Segments are collinear, check for overlap
140
+ double sa = Vertex . Dot ( va , e ) / sqrLenA ;
141
+ double sb = sa + ( Vertex . Dot ( va , vb ) / sqrLenA ) ;
142
+ double smin = Math . Min ( sa , sb ) ;
143
+ double smax = Math . Max ( sa , sb ) ;
159
144
160
- if ( imax > 0 )
145
+ if ( smin <= 1 && smax >= 0 )
161
146
{
162
- pi0 = new Vertex ( p0 . X + ( w0 * va . X ) , p0 . Y + ( w0 * va . Y ) ) ;
163
- SnapToSegmentEndpoint ( ref pi0 , seg0 ) ;
164
- SnapToSegmentEndpoint ( ref pi0 , seg1 ) ;
165
- if ( imax > 1 )
147
+ if ( smin == 1 )
166
148
{
167
- pi1 = new Vertex ( p0 . X + ( w1 * va . X ) , p0 . Y + ( w1 * va . Y ) ) ;
149
+ pi0 = MidPoint ( a1 , smin , va ) ;
150
+ return 1 ;
168
151
}
152
+
153
+ if ( smax == 0 )
154
+ {
155
+ pi0 = MidPoint ( a1 , smax , va ) ;
156
+ return 1 ;
157
+ }
158
+
159
+ pi0 = MidPoint ( a1 , Math . Max ( smin , 0 ) , va ) ;
160
+ pi1 = MidPoint ( a1 , Math . Min ( smax , 1 ) , va ) ;
161
+ return 2 ;
169
162
}
170
163
171
- return imax ;
164
+ return 0 ;
172
165
}
173
166
174
167
/// <summary>
175
- /// Snaps the point to the nearest endpoint of the segment if it is very close .
168
+ /// Computes the bounding box of the intersection area of two line segments .
176
169
/// </summary>
177
- /// <param name="point">The point to snap.</param>
178
- /// <param name="segment">The segment to check.</param>
179
- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
180
- private static void SnapToSegmentEndpoint ( ref Vertex point , Segment segment )
170
+ /// <param name="a1">The first point of the first segment.</param>
171
+ /// <param name="a2">The second point of the first segment.</param>
172
+ /// <param name="b1">The first point of the second segment.</param>
173
+ /// <param name="b2">The second point of the second segment.</param>
174
+ /// <param name="result">The intersection bounding box if one exists, otherwise null.</param>
175
+ /// <returns>
176
+ /// <see langword="true"/> if the segments intersect; otherwise, <see langword="false"/>.
177
+ /// </returns>
178
+ private static bool TryGetIntersectionBoundingBox ( Vertex a1 , Vertex a2 , Vertex b1 , Vertex b2 , [ NotNullWhen ( true ) ] out Box2 ? result )
181
179
{
182
- const double threshold = 0.00000001 ;
180
+ Vertex minA = Vertex . Min ( a1 , a2 ) ;
181
+ Vertex maxA = Vertex . Max ( a1 , a2 ) ;
182
+ Vertex minB = Vertex . Min ( b1 , b2 ) ;
183
+ Vertex maxB = Vertex . Max ( b1 , b2 ) ;
183
184
184
- if ( Vertex . Distance ( point , segment . Source ) < threshold )
185
- {
186
- point = segment . Source ;
187
- }
188
- else if ( Vertex . Distance ( point , segment . Target ) < threshold )
185
+ Vertex interMin = Vertex . Max ( minA , minB ) ;
186
+ Vertex interMax = Vertex . Min ( maxA , maxB ) ;
187
+
188
+ if ( interMin . X <= interMax . X && interMin . Y <= interMax . Y )
189
189
{
190
- point = segment . Target ;
190
+ result = new Box2 ( interMin , interMax ) ;
191
+ return true ;
191
192
}
193
+
194
+ result = null ;
195
+ return false ;
192
196
}
197
+
198
+ /// <summary>
199
+ /// Constrains a point to the given bounding box.
200
+ /// </summary>
201
+ /// <param name="p">The point to constrain.</param>
202
+ /// <param name="bbox">The bounding box.</param>
203
+ /// <returns>The constrained point.</returns>
204
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
205
+ private static Vertex ConstrainToBoundingBox ( Vertex p , Box2 bbox )
206
+ => Vertex . Min ( Vertex . Max ( p , bbox . Min ) , bbox . Max ) ;
207
+
208
+ /// <summary>
209
+ /// Computes the point at a given fractional distance along a directed line segment.
210
+ /// </summary>
211
+ /// <param name="p">The starting vertex of the segment.</param>
212
+ /// <param name="s">The scalar factor representing the fractional distance along the segment.</param>
213
+ /// <param name="d">The direction vector of the segment.</param>
214
+ /// <returns>The interpolated vertex at the given fractional distance.</returns>
215
+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
216
+ public static Vertex MidPoint ( Vertex p , double s , Vertex d ) => p + ( s * d ) ;
193
217
}
0 commit comments