66
77namespace SixLabors . ImageSharp . Drawing . Shapes . PolygonGeometry ;
88
9- internal static class ClipperUtils
9+ internal static class PolygonClipperUtilities
1010{
11- public const float DefaultArcTolerance = .25F ;
12- public const float FloatingPointTolerance = 1e-05F ;
13- public const float DefaultMinimumEdgeLength = .1F ;
14-
15- // TODO: rename to Pow2?
16- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
17- public static float Sqr ( float value ) => value * value ;
18-
11+ /// <summary>
12+ /// Computes the signed area of a path using the shoelace formula.
13+ /// </summary>
14+ /// <remarks>
15+ /// Positive values indicate clockwise orientation in screen space.
16+ /// </remarks>
1917 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
20- public static float Area ( PathF path )
18+ public static float SignedArea ( PathF path )
2119 {
2220 // https://en.wikipedia.org/wiki/Shoelace_formula
2321 float a = 0F ;
@@ -26,7 +24,8 @@ public static float Area(PathF path)
2624 return a ;
2725 }
2826
29- Vector2 prevPt = path [ path . Count - 1 ] ;
27+ // Sum over edges (prev -> current).
28+ Vector2 prevPt = path [ ^ 1 ] ;
3029 for ( int i = 0 ; i < path . Count ; i ++ )
3130 {
3231 Vector2 pt = path [ i ] ;
@@ -37,87 +36,34 @@ public static float Area(PathF path)
3736 return a * .5F ;
3837 }
3938
40- public static PathF StripDuplicates ( PathF path , bool isClosedPath )
41- {
42- int cnt = path . Count ;
43- PathF result = new ( cnt ) ;
44- if ( cnt == 0 )
45- {
46- return result ;
47- }
48-
49- PointF lastPt = path [ 0 ] ;
50- result . Add ( lastPt ) ;
51- for ( int i = 1 ; i < cnt ; i ++ )
52- {
53- if ( lastPt != path [ i ] )
54- {
55- lastPt = path [ i ] ;
56- result . Add ( lastPt ) ;
57- }
58- }
59-
60- if ( isClosedPath && lastPt == result [ 0 ] )
61- {
62- result . RemoveAt ( result . Count - 1 ) ;
63- }
64-
65- return result ;
66- }
67-
68- public static PathF Ellipse ( Vector2 center , float radiusX , float radiusY = 0 , int steps = 0 )
69- {
70- if ( radiusX <= 0 )
71- {
72- return [ ] ;
73- }
74-
75- if ( radiusY <= 0 )
76- {
77- radiusY = radiusX ;
78- }
79-
80- if ( steps <= 2 )
81- {
82- steps = ( int ) MathF . Ceiling ( MathF . PI * MathF . Sqrt ( ( radiusX + radiusY ) * .5F ) ) ;
83- }
84-
85- float si = MathF . Sin ( 2 * MathF . PI / steps ) ;
86- float co = MathF . Cos ( 2 * MathF . PI / steps ) ;
87- float dx = co , dy = si ;
88- PathF result = new ( steps ) { new Vector2 ( center . X + radiusX , center . Y ) } ;
89- Vector2 radiusXY = new ( radiusX , radiusY ) ;
90- for ( int i = 1 ; i < steps ; ++ i )
91- {
92- result . Add ( center + ( radiusXY * new Vector2 ( dx , dy ) ) ) ;
93- float x = ( dx * co ) - ( dy * si ) ;
94- dy = ( dy * co ) + ( dx * si ) ;
95- dx = x ;
96- }
97-
98- return result ;
99- }
100-
10139 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
10240 public static float DotProduct ( Vector2 vec1 , Vector2 vec2 )
10341 => Vector2 . Dot ( vec1 , vec2 ) ;
10442
43+ /// <summary>
44+ /// Returns the dot product of the segments (pt1->pt2) and (pt2->pt3).
45+ /// </summary>
46+ [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
47+ public static float DotProduct ( Vector2 pt1 , Vector2 pt2 , Vector2 pt3 )
48+ => Vector2 . Dot ( pt2 - pt1 , pt3 - pt2 ) ;
49+
50+ /// <summary>
51+ /// Returns the 2D cross product magnitude of <paramref name="vec1" /> and <paramref name="vec2" />.
52+ /// </summary>
10553 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
10654 public static float CrossProduct ( Vector2 vec1 , Vector2 vec2 )
10755 => ( vec1 . Y * vec2 . X ) - ( vec2 . Y * vec1 . X ) ;
10856
57+ /// <summary>
58+ /// Returns the cross product of the segments (pt1->pt2) and (pt2->pt3).
59+ /// </summary>
10960 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
11061 public static float CrossProduct ( Vector2 pt1 , Vector2 pt2 , Vector2 pt3 )
11162 => ( ( pt2 . X - pt1 . X ) * ( pt3 . Y - pt2 . Y ) ) - ( ( pt2 . Y - pt1 . Y ) * ( pt3 . X - pt2 . X ) ) ;
11263
113- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
114- public static float DotProduct ( Vector2 pt1 , Vector2 pt2 , Vector2 pt3 )
115- => Vector2 . Dot ( pt2 - pt1 , pt3 - pt2 ) ;
116-
117- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
118- public static bool IsAlmostZero ( float value )
119- => MathF . Abs ( value ) <= FloatingPointTolerance ;
120-
64+ /// <summary>
65+ /// Returns the squared perpendicular distance from a point to a line segment.
66+ /// </summary>
12167 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
12268 public static float PerpendicDistFromLineSqrd ( Vector2 pt , Vector2 line1 , Vector2 line2 )
12369 {
@@ -128,9 +74,18 @@ public static float PerpendicDistFromLineSqrd(Vector2 pt, Vector2 line1, Vector2
12874 return 0 ;
12975 }
13076
131- return Sqr ( CrossProduct ( cd , ab ) ) / DotProduct ( cd , cd ) ;
77+ float cross = CrossProduct ( cd , ab ) ;
78+ return ( cross * cross ) / DotProduct ( cd , cd ) ;
13279 }
13380
81+ /// <summary>
82+ /// Returns true when two segments intersect.
83+ /// </summary>
84+ /// <param name="seg1a">First endpoint of segment 1.</param>
85+ /// <param name="seg1b">Second endpoint of segment 1.</param>
86+ /// <param name="seg2a">First endpoint of segment 2.</param>
87+ /// <param name="seg2b">Second endpoint of segment 2.</param>
88+ /// <param name="inclusive">If true, allows shared endpoints; if false, requires a proper intersection.</param>
13489 public static bool SegsIntersect ( Vector2 seg1a , Vector2 seg1b , Vector2 seg2a , Vector2 seg2b , bool inclusive = false )
13590 {
13691 if ( inclusive )
@@ -158,26 +113,7 @@ public static bool SegsIntersect(Vector2 seg1a, Vector2 seg1b, Vector2 seg2a, Ve
158113 }
159114
160115 [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
161- internal static bool GetIntersectPt ( Vector2 ln1a , Vector2 ln1b , Vector2 ln2a , Vector2 ln2b , out Vector2 ip )
162- {
163- Vector2 dxy1 = ln1b - ln1a ;
164- Vector2 dxy2 = ln2b - ln2a ;
165- float cp = CrossProduct ( dxy1 , dxy2 ) ;
166- if ( cp == 0F )
167- {
168- ip = default ;
169- return false ;
170- }
171-
172- float qx = CrossProduct ( ln1a , dxy1 ) ;
173- float qy = CrossProduct ( ln2a , dxy2 ) ;
174-
175- ip = ( ( dxy1 * qy ) - ( dxy2 * qx ) ) / cp ;
176- return ip != new Vector2 ( float . MaxValue ) ;
177- }
178-
179- [ MethodImpl ( MethodImplOptions . AggressiveInlining ) ]
180- public static bool GetIntersectPoint ( Vector2 ln1a , Vector2 ln1b , Vector2 ln2a , Vector2 ln2b , out Vector2 ip )
116+ public static bool GetLineIntersectPoint ( Vector2 ln1a , Vector2 ln1b , Vector2 ln2a , Vector2 ln2b , out Vector2 ip )
181117 {
182118 Vector2 dxy1 = ln1b - ln1a ;
183119 Vector2 dxy2 = ln2b - ln2a ;
@@ -189,6 +125,8 @@ public static bool GetIntersectPoint(Vector2 ln1a, Vector2 ln1b, Vector2 ln2a, V
189125 }
190126
191127 float t = ( ( ( ln1a . X - ln2a . X ) * dxy2 . Y ) - ( ( ln1a . Y - ln2a . Y ) * dxy2 . X ) ) / det ;
128+
129+ // Clamp intersection to the segment endpoints.
192130 if ( t <= 0F )
193131 {
194132 ip = ln1a ;
@@ -205,6 +143,12 @@ public static bool GetIntersectPoint(Vector2 ln1a, Vector2 ln1b, Vector2 ln2a, V
205143 return true ;
206144 }
207145
146+ /// <summary>
147+ /// Returns the closest point on a segment to an external point.
148+ /// </summary>
149+ /// <param name="offPt">The point to project onto the segment.</param>
150+ /// <param name="seg1">First endpoint of the segment.</param>
151+ /// <param name="seg2">Second endpoint of the segment.</param>
208152 public static Vector2 GetClosestPtOnSegment ( Vector2 offPt , Vector2 seg1 , Vector2 seg2 )
209153 {
210154 if ( seg1 == seg2 )
@@ -227,10 +171,4 @@ public static Vector2 GetClosestPtOnSegment(Vector2 offPt, Vector2 seg1, Vector2
227171
228172 return seg1 + ( dxy * q ) ;
229173 }
230-
231- public static PathF ReversePath ( PathF path )
232- {
233- path . Reverse ( ) ;
234- return path ;
235- }
236174}
0 commit comments