Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix planar hull with several poins in the start/end sweep line #281

Merged
merged 2 commits into from
Nov 28, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion src/planar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ function getsemihull(ps::Vector{PT}, sign_sense, counterclockwise, yray::Nothing
psj_vec = ps[j] - prev
cc = counterclockwise(cur_vec, psj_vec)
if isapproxzero(cc)
# `cur` and `ps[j]` are on the same ray from `cur`.
# `cur` and `ps[j]` are on the same ray starting from `prev`.
# The one that is closer to `prev` is redundant.
# If `j` is the last index and redundant (it may happen if this
# ray is perpendicular to the direction of sorting) then we should
Expand All @@ -29,6 +29,7 @@ function getsemihull(ps::Vector{PT}, sign_sense, counterclockwise, yray::Nothing
elseif cc < 0
break
end
# `cur` is redundant so `cur` is dropped and `prev` becomes `cur`
cur = prev
pop!(hull)
if !isempty(hull)
Expand Down Expand Up @@ -124,6 +125,23 @@ function _planar_hull(d::FullDim, points, lines, rays, counterclockwise, rotate)
sweep_norm = rotate(coord(line))
end
sort!(points, by = x -> dot(x, sweep_norm))

if !isempty(points)
# `getsemihull` fails if `points` starts or end with several points on the same sweep line that are not ordered clockwise
start_line = dot(points[1], sweep_norm)
# `isapprox` won't work well with `atol=0` (which is the default) if `start_line` is zero, so we set a nonzero `atol`.
# TODO We should also multiply it with a scaling.
end_start = something(findfirst(x -> !isapprox(dot(x, sweep_norm), start_line, atol=Base.rtoldefault(typeof(start_line))), points), length(points) + 1) - 1
if end_start > 1
sort!(view(points, 1:end_start), by = x -> counterclockwise(x, sweep_norm), rev=true)
end
end_line = dot(points[end], sweep_norm)
start_end = something(findlast(x -> !isapprox(dot(x, sweep_norm), end_line, atol=Base.rtoldefault(typeof(start_line))), points), 0) + 1
if start_end < length(points)
sort!(view(points, start_end:length(points)), by = x -> counterclockwise(x, sweep_norm))
end
end

_points = eltype(points)[]
_lines = eltype(lines)[]
_rays = eltype(rays)[]
Expand Down
27 changes: 23 additions & 4 deletions test/planar.jl
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ function test_planar_square(VT, d)
no = -ei[1] - ei[2]
ne = ei[1] - ei[2]
se = ei[1] + ei[2]
expected = [no, so, se, ne]
expected = [ne, no, so, se]
for ps in [
[no, so, ne, se],
[no, se, so, ne],
Expand Down Expand Up @@ -61,7 +61,7 @@ end

# Square + redundant points + [+-1.5, -+0.25]
function test_planar_square_with_more()
expected = [[-1.0, -1.0], [-1.0, 1.0], [1.0, 1.0], [1.0, -1.0]]
expected = [[1.0, -1.0], [-1.0, -1.0], [-1.0, 1.0], [1.0, 1.0]]
v0 = convexhull([-1.0, -1.0], [1.0, -1.0], [-1.0, 1.0], [1.0, 1.0], [0.0, 1.0])
v1 = Polyhedra.planar_hull(v0)
@test collect(points(v1)) == expected
Expand All @@ -71,11 +71,11 @@ function test_planar_square_with_more()
v0 = convexhull([0.0, 1.0 + 1e-10], [-1.0, -1.0], [1.0, -1.0], [-1.0, 1.0], [1.0, 1.0])
v1 = Polyhedra.planar_hull(v0)
@test collect(points(v1)) == expected
expected = [[-1.0, -1.0], [-1.0, 1.0], [0.0, 1.0 + 1e-4], [1.0, 1.0], [1.0, -1.0]]
expected = [[1.0, -1.0], [-1.0, -1.0], [-1.0, 1.0], [0.0, 1.0 + 1e-4], [1.0, 1.0]]
v0 = convexhull([-1.0, -1.0], [1.0, -1.0], [-1.0, 1.0], [1.0, 1.0], [0.0, 1.0 + 1e-4])
v1 = Polyhedra.planar_hull(v0)
@test collect(points(v1)) == expected
expected = [[-1.0, -1.0], [-1.5, 0.25], [-1.0, 1.0], [1.0, 1.0], [1.5, -0.25], [1.0, -1.0]]
expected = [[1.0, -1.0], [-1.0, -1.0], [-1.5, 0.25], [-1.0, 1.0], [1.0, 1.0], [1.5, -0.25]]
v0 = convexhull([-1.0, -1.0], [1.0, -1.0], [-1.0, 1.0], [1.0, 1.0], [0.0, 1.0], [-1.5, 0.25], [1.5, -0.25])
v1 = Polyhedra.planar_hull(v0)
@test collect(points(v1)) == expected
Expand All @@ -90,11 +90,30 @@ function test_planar_square_with_more()
@test collect(points(v1)) == expected
end

function test_issue_271()
expected = [[1.0, 0.0], [-0.5, 0.0], [0.5, 0.5]]
v = convexhull([0.0, 0.0], [1, 0], [0.5, 0.0], [-0.5, 0.0], [0.5, 0.5])
p = Polyhedra.planar_hull(v)
@test collect(points(p)) == expected
v = convexhull([1, 0], [0.0, 0.0], [0.5, 0.0], [-0.5, 0.0], [0.5, 0.5])
p = Polyhedra.planar_hull(v)
@test collect(points(p)) == expected

expected = [[1.0, 0.0], [-0.5, 0.0], [0.0, 0.5], [1.0, 0.5]]
v = vrep([[1, 0], [0.0, 0.0], [0.5, 0.0], [-0.5, 0.0], [0.5, 0.5], [1.0, 0.5], [0.0, 0.5], [0.2, 0.5]])
p = Polyhedra.planar_hull(v)
@test collect(points(p)) == expected
v = vrep([[1, 0], [0.0, 0.0], [0.5, 0.0], [-0.5, 0.0], [1.0, 0.5], [0.2, 0.5], [0.5, 0.5], [0.0, 0.5]])
p = Polyhedra.planar_hull(v)
@test collect(points(p)) == expected
end

@testset "Planar" begin
for T in [Float64, Int]
for (VT, d) in [(Vector{T}, 2), (SVector{2, T}, StaticArrays.Size(2))]
test_planar_square(VT, d)
end
end
test_planar_square_with_more()
test_issue_271()
end