Skip to content

Commit 0eecb20

Browse files
committed
Major commit to BentleyOttman Algorithm. I managed to get a dictionary of all vertices and the segments related to that point. There are three ways of handling this in my opinion. 1. return the dictionary and build a separate method that reconstructs the polygon 2. return all points, build a new polygon based on that (dubious on this one) 3. build the polygon with new intersections. I lean the first one because if we want to implement this into a clip algorithm, we'd need the dictionary of vertices so we can track which points belong to which polygon. This would enable two utility methods. One that takes the dictionary ofsegments and makes one polygon, then one that takes dictionary of segments + both polygons to reconstruct each polygon
1 parent 22eec48 commit 0eecb20

File tree

2 files changed

+168
-37
lines changed

2 files changed

+168
-37
lines changed

src/utils/sweepline.jl

+145-37
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
using BinaryTrees
55

6-
76
"""
87
bentleyottmann(segments)
98
@@ -41,86 +40,195 @@ function bentleyottmann(segments)
4140
haskey(𝒞, a) || (𝒞[a] = S[])
4241
haskey(𝒞, b) || (𝒞[b] = S[])
4342
end
44-
m = Point(-Inf, -Inf)
45-
M = Point(Inf, Inf)
46-
BinaryTrees.insert!(𝒯, (m, m))
47-
BinaryTrees.insert!(𝒯, (M, M))
4843

4944
# sweep line
5045
I = Dict{P,Vector{S}}()
5146
while !isnothing(BinaryTrees.root(𝒬))
52-
p = _key(BinaryTrees.root(𝒬))
47+
p = _key(_leftmost(BinaryTrees.root(𝒬)))
5348
BinaryTrees.delete!(𝒬, p)
5449
handle!(I, p, 𝒬, 𝒯, ℒ, 𝒰, 𝒞)
5550
end
5651
I
5752
end
5853

5954
function handle!(I, p, 𝒬, 𝒯, ℒ, 𝒰, 𝒞)
60-
# Segments that start, end, or intersect at p
61-
start_segments = ℒ[p]
62-
end_segments = 𝒰[p]
63-
intersection_segments = 𝒞[p]
55+
start_segments = get(ℒ, p, S[])
56+
end_segments = get(𝒰, p, S[])
57+
intersection_segments = get(𝒞, p, S[])
58+
_process_start_segments!(start_segments, 𝒬, 𝒯, 𝒞)
59+
_process_end_segments!(end_segments, 𝒬, 𝒯, 𝒞)
60+
_process_intersection_segments!(intersection_segments, 𝒬, 𝒯, 𝒞)
6461

65-
# If there are multiple segments intersecting at p, record the intersection
6662
if length(start_segments end_segments intersection_segments) > 1
6763
I[p] = start_segments end_segments intersection_segments
6864
end
65+
end
6966

70-
# Remove segments that end at p from the status structure
71-
for s in end_segments intersection_segments
72-
BinaryTrees.delete!(𝒯, s)
73-
end
74-
75-
# Insert segments that start at p into the status structure
76-
for s in start_segments intersection_segments
77-
BinaryTrees.insert!(𝒯, s)
78-
end
79-
node = BinaryTrees.root(𝒬)
80-
81-
# Find new event points caused by the insertion or deletion of segments
67+
function _process_start_segments!(start_segments, 𝒬, 𝒯, 𝒞)
68+
[BinaryTrees.insert!(𝒯, s) for s in start_segments]
8269
for s in start_segments
70+
above, below = find_above_below(BinaryTrees.root(𝒯), BinaryTrees.search(𝒯, s))
8371
s = Segment(s)
84-
pred = BinaryTrees.left(node)
85-
succ = BinaryTrees.right(node)
86-
ns = Segment(pred, succ)
87-
if pred !== nothing
88-
new_geom, new_type = _newevent(s, ns)
89-
if new_geom == IntersectionType(0)
72+
if above !== nothing && below !== nothing
73+
new_geom, new_type = _newevent(Segment(_key(above)), Segment(_key(below)))
74+
if new_type == IntersectionType(0)
9075
BinaryTrees.insert!(𝒬, new_geom)
76+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _key(above), _key(below)) : (𝒞[new_geom] = [_key(above), _key(below)])
9177
end
9278
end
93-
if succ !== nothing
94-
new_geom, new_type = _newevent(s, ns)
79+
if below !== nothing
80+
new_geom, new_type = _newevent(Segment(_key(below)), s)
9581
if new_type == IntersectionType(0)
9682
BinaryTrees.insert!(𝒬, new_geom)
83+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _key(below), _segdata(s)) : (𝒞[new_geom] = [_key(below), _segdata(s)])
84+
end
85+
end
86+
if above !== nothing
87+
new_geom, new_type = _newevent(s, Segment(_key(above)))
88+
if new_type == IntersectionType(0)
89+
BinaryTrees.insert!(𝒬, new_geom)
90+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _segdata(s), _key(above)) : (𝒞[new_geom] = [_segdata(s), _key(above)])
9791
end
9892
end
9993
end
94+
end
10095

96+
function _process_end_segments!(end_segments, 𝒬, 𝒯, 𝒞)
10197
for s in end_segments
98+
above, below = find_above_below(BinaryTrees.root(𝒯), BinaryTrees.search(𝒯, s))
99+
BinaryTrees.delete!(𝒯, s)
102100
s = Segment(s)
103-
pred = BinaryTrees.left(node)
104-
succ = BinaryTrees.right(node)
105-
ns = Segment(pred, succ)
106-
if pred !== nothing && succ !== nothing
107-
new_geom, new_type = _newevent(s, ns)
101+
if above !== nothing && below !== nothing
102+
new_geom, new_type = _newevent(Segment(_key(above)), Segment(_key(below)))
108103
if new_type == IntersectionType(0)
109104
BinaryTrees.insert!(𝒬, new_geom)
105+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _key(above), _key(below)) : (𝒞[new_geom] = [_key(above), _key(below)])
110106
end
111107
end
112108
end
113109
end
114110

111+
function _process_intersection_segments!(intersection_segments, 𝒬, 𝒯, 𝒞)
112+
for s in intersection_segments
113+
_, below = find_above_below(BinaryTrees.root(𝒯), BinaryTrees.search(𝒯, s))
114+
if below !== nothing
115+
# Swap positions of s and t in 𝒯
116+
BinaryTrees.delete!(𝒯, s)
117+
BinaryTrees.delete!(𝒯, _key(below))
118+
BinaryTrees.insert!(𝒯, _key(below))
119+
BinaryTrees.insert!(𝒯, s)
120+
121+
# Find segments r and u
122+
_, r = find_above_below(BinaryTrees.root(𝒯), BinaryTrees.search(𝒯, _key(below)))
123+
u, _ = find_above_below(BinaryTrees.root(𝒯), BinaryTrees.search(𝒯, s))
124+
125+
# Remove crossing points rs and tu from event queue
126+
if r !== nothing
127+
new_geom, new_type = _newevent(Segment(_key(r)), Segment(s))
128+
if new_type == IntersectionType(0)
129+
BinaryTrees.delete!(𝒬, new_geom)
130+
end
131+
end
132+
if u !== nothing
133+
new_geom, new_type = _newevent(Segment(_key(u)), Segment(_key(below)))
134+
if new_type == IntersectionType(0)
135+
BinaryTrees.delete!(𝒬, new_geom)
136+
end
137+
end
138+
139+
# Add crossing points rt and su to event queue
140+
if r !== nothing
141+
new_geom, new_type = _newevent(Segment(_key(r)), Segment(_key(below)))
142+
if new_type == IntersectionType(0)
143+
BinaryTrees.insert!(𝒬, new_geom)
144+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _key(r), _key(below)) : (𝒞[new_geom] = [_key(r), _key(below)])
145+
end
146+
end
147+
if u !== nothing
148+
new_geom, new_type = _newevent(Segment(_key(u)), Segment(s))
149+
if new_type == IntersectionType(0)
150+
BinaryTrees.insert!(𝒬, new_geom)
151+
haskey(𝒞, new_geom) ? push!(𝒞[new_geom], _key(u), s) : (𝒞[new_geom] = [_key(u), s])
152+
end
153+
end
154+
end
155+
end
156+
end
157+
158+
_segdata(seg::Segment) = seg.vertices.data
115159
_key(node::BinaryTrees.AVLNode) = node.key
160+
_key(node::Nothing) = nothing
161+
_leftmost(node::BinaryTrees.AVLNode) = node.left === nothing ? node : _leftmost(node.left)
116162
_geom(intersect::Intersection) = intersect.geom
117163
_type(intersect::Intersection) = intersect.type
118164
function _newevent(s₁::Segment, s₂::Segment)
119165
new_event = intersection(s₁, s₂)
120-
_geom(new_event), _type(new_event)
166+
if new_event !== nothing
167+
_geom(new_event), _type(new_event)
168+
else
169+
nothing, nothing
170+
end
171+
end
172+
173+
# Helper: return the leftmost node (minimum) in a subtree.
174+
function _bst_minimum(node::BinaryTrees.AVLNode)
175+
while node.left !== nothing
176+
node = node.left
177+
end
178+
return node
121179
end
122180

181+
# Helper: return the rightmost node (maximum) in a subtree.
182+
function _bst_maximum(node::BinaryTrees.AVLNode)
183+
while node.right !== nothing
184+
node = node.right
185+
end
186+
return node
187+
end
188+
189+
"""
190+
find_above_below(root, x)
191+
192+
Find the node above and below `x` in the binary search tree rooted at `root`.
193+
Returns a tuple `(above, below)` where `above` is the node with the smallest key
194+
greater than `x.key` and `below` is the node with the largest key smaller than `x.key`.
195+
If `x` is not found, returns the best candidates for `above` and `below`.
196+
"""
197+
function find_above_below(root::BinaryTrees.AVLNode, x::BinaryTrees.AVLNode)
198+
above = nothing
199+
below = nothing
200+
current = root
201+
# Traverse from the root to the target node, updating candidates.
202+
while current !== nothing && current.key != x.key
203+
if x.key < current.key
204+
# current is a potential above (successor)
205+
above = current
206+
current = current.left
207+
else # x.key > current.key
208+
# current is a potential below (predecessor)
209+
below = current
210+
current = current.right
211+
end
212+
end
213+
214+
# If the node wasn't found, return the best candidate values
215+
if current === nothing
216+
return (above, below)
217+
end
123218

219+
# Found the node with key equal to x.key.
220+
# Now, if there is a left subtree, the true below (predecessor) is the maximum in that subtree.
221+
if current.left !== nothing
222+
below = _bst_maximum(current.left)
223+
end
224+
# Similarly, if there is a right subtree, the true above (successor) is the minimum in that subtree.
225+
if current.right !== nothing
226+
above = _bst_minimum(current.right)
227+
end
228+
229+
(above, below)
230+
end
231+
find_above_below(root::BinaryTrees.AVLNode, x::Nothing) = (nothing, nothing)
124232
function Segment(node₁::BinaryTrees.BinaryNode, node₂::BinaryTrees.BinaryNode)
125233
node₁ = _key(node₁)
126234
node₂ = _key(node₂)

test/utils.jl

+23
Original file line numberDiff line numberDiff line change
@@ -54,4 +54,27 @@
5454
c = (T(30), T(60))
5555
p = latlon(c) |> Proj(Cartesian)
5656
@inferred Meshes.withcrs(p, c, LatLon)
57+
58+
# test bentley-ottmann algorithm
59+
S = [
60+
Segment(cart(0, 0), cart(1, 1)),
61+
Segment(cart(1, 0), cart(0, 1)),
62+
Segment(cart(0, 0), cart(0, 1)),
63+
Segment(cart(0, 0), cart(1, 0)),
64+
Segment(cart(0, 1), cart(1, 1)),
65+
Segment(cart(1, 0), cart(1, 1))
66+
]
67+
I = bentleyottmann(S)
68+
# new vertices
69+
NS = [
70+
Segment(cart(0, 0), cart(0.5, 0.5)),
71+
Segment(cart(0.5, 0.5), cart(1, 1)),
72+
Segment(cart(1, 0), cart(0, 1)),
73+
Segment(cart(1, 0), cart(0.5, 0.5)),
74+
Segment(cart(0.5, 0.5), cart(0, 1)),
75+
Segment(cart(0, 0), cart(0, 1)),
76+
Segment(cart(0, 0), cart(1, 0)),
77+
Segment(cart(1, 0), cart(1, 1))
78+
]
79+
@test I == NS
5780
end

0 commit comments

Comments
 (0)