@@ -11,6 +11,45 @@ use alloc::vec::Vec;
1111use i_float:: int:: point:: IntPoint ;
1212use i_shape:: int:: shape:: { IntContour , IntShape } ;
1313
14+ /// Checks if any vertex from a subject segment coincides with any vertex from a clip segment.
15+ ///
16+ /// Collects all endpoints, sorts by (x, y), then scans for adjacent points from different shapes.
17+ fn has_point_coincidence ( segments : & [ Segment < ShapeCountBoolean > ] ) -> bool {
18+ // Collect all endpoints with a flag indicating if they're from subj (true) or clip (false)
19+ let mut points: Vec < ( i32 , i32 , bool , bool ) > = Vec :: with_capacity ( segments. len ( ) * 2 ) ;
20+
21+ for seg in segments {
22+ let is_subj = seg. count . subj != 0 ;
23+ let is_clip = seg. count . clip != 0 ;
24+ points. push ( ( seg. x_segment . a . x , seg. x_segment . a . y , is_subj, is_clip) ) ;
25+ points. push ( ( seg. x_segment . b . x , seg. x_segment . b . y , is_subj, is_clip) ) ;
26+ }
27+
28+ // Sort by (x, y)
29+ points. sort_unstable_by ( |a, b| ( a. 0 , a. 1 ) . cmp ( & ( b. 0 , b. 1 ) ) ) ;
30+
31+ // Scan for adjacent points at the same location from different shapes
32+ let mut i = 0 ;
33+ while i < points. len ( ) {
34+ let ( x, y, _, _) = points[ i] ;
35+ let mut has_subj = false ;
36+ let mut has_clip = false ;
37+
38+ // Collect all points at this location
39+ while i < points. len ( ) && points[ i] . 0 == x && points[ i] . 1 == y {
40+ has_subj |= points[ i] . 2 ;
41+ has_clip |= points[ i] . 3 ;
42+ i += 1 ;
43+ }
44+
45+ if has_subj && has_clip {
46+ return true ;
47+ }
48+ }
49+
50+ false
51+ }
52+
1453/// Overlay structure optimized for spatial predicate evaluation.
1554///
1655/// `PredicateOverlay` provides efficient spatial relationship testing between
@@ -54,22 +93,25 @@ impl PredicateOverlay {
5493 }
5594 }
5695
57- fn evaluate < H : FillHandler < Output = bool > > ( & mut self , handler : H ) -> bool {
96+ fn evaluate < T : Default , H : FillHandler < Output = T > > ( & mut self , handler : H ) -> T {
5897 if self . segments . is_empty ( ) {
59- return false ;
98+ return T :: default ( ) ;
6099 }
61100 self . split_solver . split_segments ( & mut self . segments , & self . solver ) ;
62101 if self . segments . is_empty ( ) {
63- return false ;
102+ return T :: default ( ) ;
64103 }
65104 self . sweep_runner
66105 . run_with_fill_rule ( self . fill_rule , & self . solver , & self . segments , handler)
67106 }
68107
69108 /// Returns `true` if the subject and clip shapes intersect (share any point).
109+ ///
110+ /// This includes both interior overlap and boundary contact (including single-point touches).
70111 #[ inline]
71112 pub fn intersects ( & mut self ) -> bool {
72- self . evaluate ( IntersectsHandler )
113+ // Check sweep first (handles most cases), then fall back to point coincidence
114+ self . evaluate ( IntersectsHandler ) || has_point_coincidence ( & self . segments )
73115 }
74116
75117 /// Returns `true` if the interiors of subject and clip shapes overlap.
@@ -84,10 +126,17 @@ impl PredicateOverlay {
84126 /// Returns `true` if subject and clip shapes touch (boundaries intersect but interiors don't).
85127 ///
86128 /// This returns `true` when shapes share boundary points (edges or vertices)
87- /// but their interiors don't overlap.
129+ /// but their interiors don't overlap. This includes single-point vertex touches.
88130 #[ inline]
89131 pub fn touches ( & mut self ) -> bool {
90- self . evaluate ( TouchesHandler :: new ( ) )
132+ let ( boundary_contact, interior_overlap) = self . evaluate ( TouchesHandler :: new ( ) ) ;
133+ if interior_overlap {
134+ return false ;
135+ }
136+ if boundary_contact {
137+ return true ;
138+ }
139+ has_point_coincidence ( & self . segments )
91140 }
92141
93142 /// Returns `true` if subject is completely within clip.
@@ -238,4 +287,64 @@ mod tests {
238287 overlay. add_contour ( & square ( 5 , 5 , 10 ) , ShapeType :: Clip ) ;
239288 assert ! ( overlay. intersects( ) ) ;
240289 }
290+
291+ #[ test]
292+ fn test_point_touch_intersects ( ) {
293+ // Two squares touching at a single corner point (10, 10)
294+ let mut overlay = PredicateOverlay :: new ( 16 ) ;
295+ overlay. add_contour ( & square ( 0 , 0 , 10 ) , ShapeType :: Subject ) ;
296+ overlay. add_contour ( & square ( 10 , 10 , 10 ) , ShapeType :: Clip ) ;
297+ assert ! ( overlay. intersects( ) , "point-to-point should intersect" ) ;
298+ }
299+
300+ #[ test]
301+ fn test_point_touch_touches ( ) {
302+ // Two squares touching at a single corner point (10, 10)
303+ let mut overlay = PredicateOverlay :: new ( 16 ) ;
304+ overlay. add_contour ( & square ( 0 , 0 , 10 ) , ShapeType :: Subject ) ;
305+ overlay. add_contour ( & square ( 10 , 10 , 10 ) , ShapeType :: Clip ) ;
306+ assert ! ( overlay. touches( ) , "point-to-point should touch" ) ;
307+ }
308+
309+ #[ test]
310+ fn test_point_touch_no_interior_intersect ( ) {
311+ // Two squares touching at a single corner point (10, 10)
312+ let mut overlay = PredicateOverlay :: new ( 16 ) ;
313+ overlay. add_contour ( & square ( 0 , 0 , 10 ) , ShapeType :: Subject ) ;
314+ overlay. add_contour ( & square ( 10 , 10 , 10 ) , ShapeType :: Clip ) ;
315+ assert ! ( !overlay. interiors_intersect( ) , "point touch has no interior intersection" ) ;
316+ }
317+
318+ #[ test]
319+ fn test_segment_end_to_start_touch ( ) {
320+ // Triangle where subject's segment endpoint touches clip's segment startpoint
321+ // Subject: triangle at (0,0), (10,0), (5,10)
322+ // Clip: triangle at (10,0), (20,0), (15,10)
323+ // They touch at exactly one point: (10,0)
324+ let subj = vec ! [
325+ IntPoint :: new( 0 , 0 ) ,
326+ IntPoint :: new( 10 , 0 ) ,
327+ IntPoint :: new( 5 , 10 ) ,
328+ ] ;
329+ let clip = vec ! [
330+ IntPoint :: new( 10 , 0 ) ,
331+ IntPoint :: new( 20 , 0 ) ,
332+ IntPoint :: new( 15 , 10 ) ,
333+ ] ;
334+
335+ let mut overlay = PredicateOverlay :: new ( 16 ) ;
336+ overlay. add_contour ( & subj, ShapeType :: Subject ) ;
337+ overlay. add_contour ( & clip, ShapeType :: Clip ) ;
338+ assert ! ( overlay. intersects( ) , "segment b touching segment a should intersect" ) ;
339+
340+ overlay. clear ( ) ;
341+ overlay. add_contour ( & subj, ShapeType :: Subject ) ;
342+ overlay. add_contour ( & clip, ShapeType :: Clip ) ;
343+ assert ! ( overlay. touches( ) , "segment b touching segment a should touch" ) ;
344+
345+ overlay. clear ( ) ;
346+ overlay. add_contour ( & subj, ShapeType :: Subject ) ;
347+ overlay. add_contour ( & clip, ShapeType :: Clip ) ;
348+ assert ! ( !overlay. interiors_intersect( ) , "segment b touching segment a should not have interior intersection" ) ;
349+ }
241350}
0 commit comments