From cf42b45fc9222eb603154930d0b2dcb271061e79 Mon Sep 17 00:00:00 2001 From: schillic Date: Tue, 30 Jul 2024 10:56:02 +0200 Subject: [PATCH 1/6] fix intersection of LineSegments --- src/Sets/LineSegment/init_LazySets.jl | 1 + src/Sets/LineSegment/intersection.jl | 37 ++++++++++++++++----------- 2 files changed, 23 insertions(+), 15 deletions(-) diff --git a/src/Sets/LineSegment/init_LazySets.jl b/src/Sets/LineSegment/init_LazySets.jl index 14244a1651..cde629f103 100644 --- a/src/Sets/LineSegment/init_LazySets.jl +++ b/src/Sets/LineSegment/init_LazySets.jl @@ -1,3 +1,4 @@ +using .LazySets.Approximations: box_approximation using .LazySets.EmptySetModule: EmptySet using .LazySets.Line2DModule: Line2D using .LazySets.SingletonModule: Singleton diff --git a/src/Sets/LineSegment/intersection.jl b/src/Sets/LineSegment/intersection.jl index 56835ab602..0a55ba0f0c 100644 --- a/src/Sets/LineSegment/intersection.jl +++ b/src/Sets/LineSegment/intersection.jl @@ -34,24 +34,31 @@ function intersection(LS1::LineSegment, LS2::LineSegment) m = intersection(L1, L2) N = promote_type(eltype(LS1), eltype(LS2)) if m == L1 - # determine which segment is in both - p1 = max(min(LS1.p[1], LS1.q[1]), min(LS2.p[1], LS2.q[1])) - p2 = max(min(LS1.p[2], LS1.q[2]), min(LS2.p[2], LS2.q[2])) - q1 = min(max(LS1.p[1], LS1.q[1]), max(LS2.p[1], LS2.q[1])) - q2 = min(max(LS1.p[2], LS1.q[2]), max(LS2.p[2], LS2.q[2])) - if _isapprox(p1, q1) && _isapprox(p2, q2) - return Singleton([p1, p2]) # edges have a point in common - - elseif _leq(p1, q1) && _leq(p2, q2) - return LineSegment([p1, p2], [q1, q2]) - - else - return EmptySet{N}(2) # no intersection + # simple solution: compute the box approximation and find the right vertices (there are only 4) + B1 = box_approximation(LS1) + B2 = box_approximation(LS2) + B = intersection(B1, B2) + if isempty(B) + return B end - + p = nothing + vlist = [LS1.p, LS1.q, LS2.p, LS2.q] + for v1 in vertices(B) + for v2 in vlist + if _isapprox(v1, v2) + if isnothing(p) + p = v1 + elseif _isapprox(v1, p) + return Singleton(p) + else + return LineSegment(p, v1) + end + end + end + end + error("this should not happen") elseif m isa Singleton && m.element ∈ LS1 && m.element ∈ LS2 return m # the intersection point between the lines is in the segments - else return EmptySet{N}(2) # no intersection end From e86ea77ce021df06e007580330d1291ea2c60a62 Mon Sep 17 00:00:00 2001 From: schillic Date: Tue, 30 Jul 2024 12:01:26 +0200 Subject: [PATCH 2/6] add/update tests --- test/Sets/LineSegment.jl | 34 +++++++++++++++++++--------------- 1 file changed, 19 insertions(+), 15 deletions(-) diff --git a/test/Sets/LineSegment.jl b/test/Sets/LineSegment.jl index 1a6e64409a..37a82d57b1 100644 --- a/test/Sets/LineSegment.jl +++ b/test/Sets/LineSegment.jl @@ -123,34 +123,38 @@ for N in [Float64, Rational{Int}, Float32] @test !is_intersection_empty(l1, l1_copy) && !intersection_empty && point ∈ l1 # intersection - s1 = LineSegment(N[-5.0, -5.0], N[5.0, 5.0]) + s1 = LineSegment(N[-5, -5], N[5, 5]) # collinear shifted down - s2 = LineSegment(N[-6.0, -6.0], N[4.0, 4.0]) - @test intersection(s1, s2) isa LineSegment - @test isapprox(intersection(s1, s2).p, N[-5, -5]) - @test isapprox(intersection(s1, s2).q, N[4, 4]) + s2 = LineSegment(N[-6, -6], N[4, 4]) + cap = intersection(s1, s2) + @test cap isa LineSegment + @test ispermutation([cap.p, cap.q], [N[-5, -5], N[4, 4]]) # parallel, not intersect - s3 = LineSegment(N[-5.0, -4.0], N[4.0, 5.0]) + s3 = LineSegment(N[-5, -4], N[4, 5]) @test intersection(s1, s3) isa EmptySet # intersect outside of segment - s4 = LineSegment(N[0.0, 10.0], N[6.0, 5.0]) + s4 = LineSegment(N[0, 10], N[6, 5]) @test intersection(s1, s4) isa EmptySet # intersect in segment - s5 = LineSegment(N[5.0, -5.0], N[-5.0, 5.0]) + s5 = LineSegment(N[5, -5], N[-5, 5]) @test intersection(s1, s5) isa Singleton @test isapprox(intersection(s1, s5).element, N[0, 0]) # parallel, no points in common - s6 = LineSegment(N[10.0, 10.0], N[11.0, 11.0]) + s6 = LineSegment(N[10, 10], N[11, 11]) @test intersection(s1, s6) isa EmptySet # parallel one point in common - s7 = LineSegment(N[5.0, 5.0], N[6.0, 6.0]) + s7 = LineSegment(N[5, 5], N[6, 6]) @test intersection(s1, s7) isa Singleton @test isapprox(intersection(s1, s7).element, N[5, 5]) - s8 = LineSegment(N[0.0, 0.0], N[1.0, 0.0]) - s9 = LineSegment(N[0.0, 0.0], N[2.0, 0.0]) - @test intersection(s9, s8) isa LineSegment - @test isapprox(intersection(s9, s8).p, N[0, 0]) - @test isapprox(intersection(s9, s8).q, N[1, 0]) + s8 = LineSegment(N[0, 0], N[1, 0]) + s9 = LineSegment(N[0, 0], N[2, 0]) + cap = intersection(s8, s9) + @test cap isa LineSegment + @test ispermutation([cap.p, cap.q], [N[0, 0], N[1, 0]]) + # intersect in segment, different orientation + s10 = LineSegment(N[-1, 2], N[2, -1]) + s11 = LineSegment(N[0, 1], N[1, 0]) + @test intersection(s10, s11) == s11 # subset l = LineSegment(N[1, 1], N[2, 2]) From d1f37b0995127530271714dc8e5da465f6424ab7 Mon Sep 17 00:00:00 2001 From: schillic Date: Tue, 30 Jul 2024 19:10:51 +0200 Subject: [PATCH 3/6] better implementation --- src/Sets/LineSegment/init_LazySets.jl | 1 - src/Sets/LineSegment/intersection.jl | 31 +++++++++------------------ 2 files changed, 10 insertions(+), 22 deletions(-) diff --git a/src/Sets/LineSegment/init_LazySets.jl b/src/Sets/LineSegment/init_LazySets.jl index cde629f103..14244a1651 100644 --- a/src/Sets/LineSegment/init_LazySets.jl +++ b/src/Sets/LineSegment/init_LazySets.jl @@ -1,4 +1,3 @@ -using .LazySets.Approximations: box_approximation using .LazySets.EmptySetModule: EmptySet using .LazySets.Line2DModule: Line2D using .LazySets.SingletonModule: Singleton diff --git a/src/Sets/LineSegment/intersection.jl b/src/Sets/LineSegment/intersection.jl index 0a55ba0f0c..09486f5ea2 100644 --- a/src/Sets/LineSegment/intersection.jl +++ b/src/Sets/LineSegment/intersection.jl @@ -34,29 +34,18 @@ function intersection(LS1::LineSegment, LS2::LineSegment) m = intersection(L1, L2) N = promote_type(eltype(LS1), eltype(LS2)) if m == L1 - # simple solution: compute the box approximation and find the right vertices (there are only 4) - B1 = box_approximation(LS1) - B2 = box_approximation(LS2) - B = intersection(B1, B2) - if isempty(B) - return B + # find the middle two points of the four end points + points = [LS1.p, LS1.q, LS2.p, LS2.q] + sorted_points = sort(points, by = p -> (p[1], p[2])) + @inbounds begin + mid_point1 = sorted_points[2] + mid_point2 = sorted_points[3] end - p = nothing - vlist = [LS1.p, LS1.q, LS2.p, LS2.q] - for v1 in vertices(B) - for v2 in vlist - if _isapprox(v1, v2) - if isnothing(p) - p = v1 - elseif _isapprox(v1, p) - return Singleton(p) - else - return LineSegment(p, v1) - end - end - end + if _isapprox(mid_point1, mid_point2) + return Singleton(mid_point1) # only one point in common + else + return LineSegment(mid_point1, mid_point2) end - error("this should not happen") elseif m isa Singleton && m.element ∈ LS1 && m.element ∈ LS2 return m # the intersection point between the lines is in the segments else From 9ad333cabbe4201681617d6d493e24b89254c962 Mon Sep 17 00:00:00 2001 From: schillic Date: Tue, 30 Jul 2024 21:39:28 +0200 Subject: [PATCH 4/6] fix case for empty intersection --- src/Sets/LineSegment/intersection.jl | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/src/Sets/LineSegment/intersection.jl b/src/Sets/LineSegment/intersection.jl index 09486f5ea2..365638625d 100644 --- a/src/Sets/LineSegment/intersection.jl +++ b/src/Sets/LineSegment/intersection.jl @@ -34,9 +34,23 @@ function intersection(LS1::LineSegment, LS2::LineSegment) m = intersection(L1, L2) N = promote_type(eltype(LS1), eltype(LS2)) if m == L1 + # line segments are on the same line + + # check that the line segments intersect + @inbounds begin + # box approximation + l, r = LS1.p[1] < LS1.q[1] ? (LS1.p[1], LS1.q[1]) : (LS1.q[1], LS1.p[1]) + b, t = LS1.p[2] < LS1.q[2] ? (LS1.p[2], LS1.q[2]) : (LS1.q[2], LS1.p[2]) + # check that the other line segment has at least one point inside the box + if !(l <= LS2.p[1] <= r && b <= LS2.p[2] <= t) && + !(l <= LS2.q[1] <= r && b <= LS2.q[2] <= t) + return EmptySet{N}(2) + end + end + # find the middle two points of the four end points points = [LS1.p, LS1.q, LS2.p, LS2.q] - sorted_points = sort(points, by = p -> (p[1], p[2])) + sorted_points = sort(points; by=p -> (p[1], p[2])) @inbounds begin mid_point1 = sorted_points[2] mid_point2 = sorted_points[3] From 2714da39cd24b4010b05a0e5b17c0924acc8e332 Mon Sep 17 00:00:00 2001 From: schillic Date: Tue, 30 Jul 2024 21:41:34 +0200 Subject: [PATCH 5/6] improve tests --- test/Sets/LineSegment.jl | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/test/Sets/LineSegment.jl b/test/Sets/LineSegment.jl index 37a82d57b1..2f8b32417b 100644 --- a/test/Sets/LineSegment.jl +++ b/test/Sets/LineSegment.jl @@ -131,17 +131,17 @@ for N in [Float64, Rational{Int}, Float32] @test ispermutation([cap.p, cap.q], [N[-5, -5], N[4, 4]]) # parallel, not intersect s3 = LineSegment(N[-5, -4], N[4, 5]) - @test intersection(s1, s3) isa EmptySet + @test intersection(s1, s3) == EmptySet{N}(2) # intersect outside of segment s4 = LineSegment(N[0, 10], N[6, 5]) - @test intersection(s1, s4) isa EmptySet + @test intersection(s1, s4) == EmptySet{N}(2) # intersect in segment s5 = LineSegment(N[5, -5], N[-5, 5]) @test intersection(s1, s5) isa Singleton @test isapprox(intersection(s1, s5).element, N[0, 0]) # parallel, no points in common s6 = LineSegment(N[10, 10], N[11, 11]) - @test intersection(s1, s6) isa EmptySet + @test intersection(s1, s6) == EmptySet{N}(2) # parallel one point in common s7 = LineSegment(N[5, 5], N[6, 6]) @test intersection(s1, s7) isa Singleton From 15c265820bf12db7c81a201b7913a6b1f23a1762 Mon Sep 17 00:00:00 2001 From: Christian Schilling Date: Wed, 31 Jul 2024 09:20:29 +0200 Subject: [PATCH 6/6] Update src/Sets/LineSegment/intersection.jl --- src/Sets/LineSegment/intersection.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Sets/LineSegment/intersection.jl b/src/Sets/LineSegment/intersection.jl index 365638625d..3b20b648a2 100644 --- a/src/Sets/LineSegment/intersection.jl +++ b/src/Sets/LineSegment/intersection.jl @@ -39,8 +39,8 @@ function intersection(LS1::LineSegment, LS2::LineSegment) # check that the line segments intersect @inbounds begin # box approximation - l, r = LS1.p[1] < LS1.q[1] ? (LS1.p[1], LS1.q[1]) : (LS1.q[1], LS1.p[1]) - b, t = LS1.p[2] < LS1.q[2] ? (LS1.p[2], LS1.q[2]) : (LS1.q[2], LS1.p[2]) + l, r = extrema((LS1.p[1], LS1.q[1])) + b, t = extrema((LS1.p[2], LS1.q[2])) # check that the other line segment has at least one point inside the box if !(l <= LS2.p[1] <= r && b <= LS2.p[2] <= t) && !(l <= LS2.q[1] <= r && b <= LS2.q[2] <= t)