Skip to content

Commit 2acbced

Browse files
feat: add Segment Intersection algorithm
1 parent 7e4b60b commit 2acbced

File tree

1 file changed

+110
-0
lines changed

1 file changed

+110
-0
lines changed

geometry/segment_intersection.py

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
"""
2+
Given two line segments, determine whether they intersect.
3+
4+
This is based on the algorithm described in Introduction to Algorithms
5+
(CLRS), Chapter 33.
6+
7+
Reference:
8+
- https://en.wikipedia.org/wiki/Line%E2%80%93line_intersection
9+
- https://en.wikipedia.org/wiki/Orientation_(geometry)
10+
"""
11+
12+
from __future__ import annotations
13+
14+
from typing import NamedTuple
15+
16+
17+
class Point(NamedTuple):
18+
"""A point in 2D space.
19+
20+
>>> Point(0, 0)
21+
Point(x=0, y=0)
22+
>>> Point(1, -3)
23+
Point(x=1, y=-3)
24+
"""
25+
26+
x: float
27+
y: float
28+
29+
30+
def direction(a: Point, b: Point, c: Point) -> float:
31+
"""Return the cross product of vectors (a→c) and (a→b).
32+
33+
The sign of the result encodes the orientation of the ordered triple
34+
(a, b, c):
35+
- Negative → counter-clockwise (left turn)
36+
- Positive → clockwise (right turn)
37+
- Zero → collinear
38+
39+
>>> direction(Point(0, 0), Point(1, 0), Point(0, 1))
40+
-1
41+
>>> direction(Point(0, 0), Point(0, 1), Point(1, 0))
42+
1
43+
>>> direction(Point(0, 0), Point(1, 1), Point(2, 2))
44+
0
45+
"""
46+
return (c.x - a.x) * (b.y - a.y) - (b.x - a.x) * (c.y - a.y)
47+
48+
49+
def on_segment(a: Point, b: Point, p: Point) -> bool:
50+
"""Check whether point *p*, known to be collinear with segment ab, lies on it.
51+
52+
>>> on_segment(Point(0, 0), Point(4, 4), Point(2, 2))
53+
True
54+
>>> on_segment(Point(0, 0), Point(4, 4), Point(5, 5))
55+
False
56+
>>> on_segment(Point(0, 0), Point(4, 0), Point(2, 0))
57+
True
58+
"""
59+
return min(a.x, b.x) <= p.x <= max(a.x, b.x) and min(a.y, b.y) <= p.y <= max(
60+
a.y, b.y
61+
)
62+
63+
64+
def segments_intersect(p1: Point, p2: Point, p3: Point, p4: Point) -> bool:
65+
"""Return True if line segment p1p2 intersects line segment p3p4.
66+
67+
Uses the CLRS cross-product / orientation method. Handles both the
68+
general case (proper crossing) and degenerate cases where one endpoint
69+
lies exactly on the other segment.
70+
71+
>>> segments_intersect(Point(0, 0), Point(2, 2), Point(0, 2), Point(2, 0))
72+
True
73+
>>> segments_intersect(Point(0, 0), Point(2, 2), Point(1, 1), Point(3, 3))
74+
True
75+
>>> segments_intersect(Point(0, 0), Point(1, 0), Point(2, 0), Point(3, 0))
76+
False
77+
>>> segments_intersect(Point(0, 0), Point(1, 1), Point(1, 0), Point(2, 1))
78+
False
79+
>>> segments_intersect(Point(0, 0), Point(1, 1), Point(0, 1), Point(0, 2))
80+
False
81+
>>> segments_intersect(Point(0, 0), Point(1, 0), Point(1, 0), Point(2, 0))
82+
True
83+
"""
84+
d1 = direction(p3, p4, p1)
85+
d2 = direction(p3, p4, p2)
86+
d3 = direction(p1, p2, p3)
87+
d4 = direction(p1, p2, p4)
88+
89+
if ((d1 < 0 < d2) or (d2 < 0 < d1)) and ((d3 < 0 < d4) or (d4 < 0 < d3)):
90+
return True
91+
92+
if d1 == 0 and on_segment(p3, p4, p1):
93+
return True
94+
if d2 == 0 and on_segment(p3, p4, p2):
95+
return True
96+
if d3 == 0 and on_segment(p1, p2, p3):
97+
return True
98+
return d4 == 0 and on_segment(p1, p2, p4)
99+
100+
101+
if __name__ == "__main__":
102+
import doctest
103+
104+
doctest.testmod()
105+
106+
print("Enter four points as 'x y' pairs (one per line):")
107+
points = [Point(*map(float, input().split())) for _ in range(4)]
108+
p1, p2, p3, p4 = points
109+
result = segments_intersect(p1, p2, p3, p4)
110+
print(1 if result else 0)

0 commit comments

Comments
 (0)