Skip to content

Commit 29cf338

Browse files
authored
Fix bug of polygon calculation for voronoiplots (#234) (#235)
1 parent c0305a9 commit 29cf338

File tree

7 files changed

+139
-10
lines changed

7 files changed

+139
-10
lines changed

Project.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
name = "DelaunayTriangulation"
22
uuid = "927a84f5-c5f4-47a5-9785-b46e178433df"
33
authors = ["Daniel VandenHeuvel <[email protected]>"]
4-
version = "1.6.5"
4+
version = "1.6.6"
55

66
[deps]
77
AdaptivePredicates = "35492f91-a3bd-45ad-95db-fcad7dcfedb7"

src/algorithms/voronoi/clipped_coordinates.jl

Lines changed: 53 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -220,32 +220,78 @@ Truncates the unbounded edges of the `i`th polygon of `vorn` so that the line co
220220
- `new_vertices`: The new vertices of the polygon. This is not a circular vector.
221221
- `new_points`: The new points of the polygon. This is not a circular vector.
222222
"""
223-
function grow_polygon_outside_of_box(vorn::VoronoiTessellation, i, bounding_box, predicates::AbstractPredicateKernel = AdaptiveKernel())
223+
function grow_polygon_outside_of_box(vorn::VoronoiTessellation, i, bounding_box, predicates::AbstractPredicateKernel=AdaptiveKernel())
224+
# try provided number type first
225+
new_vertices, new_points =
226+
_grow_polygon_outside_of_box(number_type(vorn), vorn, i, bounding_box, predicates)
227+
228+
# if the default produced Inf values, rerun with BigFloat
229+
has_inf = !allfinite(new_points)
230+
231+
if has_inf
232+
return _grow_polygon_outside_of_box(BigFloat, vorn, i, bounding_box, predicates)
233+
else
234+
return new_vertices, new_points
235+
end
236+
end
237+
238+
"""
239+
_grow_polygon_outside_of_box(::Type{T}, vorn::VoronoiTessellation, i, bounding_box, predicates::AbstractPredicateKernel=AdaptiveKernel()) -> (Vector{Int}, Vector{NTuple{2,Number}})
240+
241+
Internal method that is called by [`grow_polygon_outside_of_box`](@ref). Can be used to specify the number type `T` to allow for higher precision computations.
242+
243+
# Arguments
244+
- `T`: The number type to use for computations.
245+
- `vorn`: The [`VoronoiTessellation`](@ref).
246+
- `i`: The index of the polygon. The polygon must be unbounded.
247+
- `bounding_box`: The bounding box to clip the polygon to. See also [`polygon_bounds`](@ref).
248+
- `predicates::AbstractPredicateKernel=AdaptiveKernel()`: Method to use for computing predicates. Can be one of [`FastKernel`](@ref), [`ExactKernel`](@ref), and [`AdaptiveKernel`](@ref). See the documentation for a further discussion of these methods.
249+
250+
# Outputs
251+
- `new_vertices`: The new vertices of the polygon. This is not a circular vector.
252+
- `new_points`: The new points of the polygon. This is not a circular vector.
253+
"""
254+
function _grow_polygon_outside_of_box(
255+
::Type{T},
256+
vorn::VoronoiTessellation,
257+
i,
258+
bounding_box,
259+
predicates::AbstractPredicateKernel=AdaptiveKernel(),
260+
) where {T<:AbstractFloat}
224261
a, b, c, d = bounding_box
262+
225263
vertices = get_polygon(vorn, i)
226264
new_vertices, new_points, ghost_vertices = get_new_polygon_indices(vorn, vertices)
227265
inside = true
228-
t = 1.0 # don't do 0.5 so we get t = 1 later, else we get duplicated vertices for polygons completely outside of the box
266+
t = one(T) # don't do 0.5 so we get t = 1 later, else we get duplicated vertices for polygons completely outside of the box
267+
229268
u, v = ghost_vertices
230269
u_m, u_r = _get_ray(vorn, i, u, predicates)
231270
v_m, v_r = _get_ray(vorn, i, v, predicates)
271+
232272
u_mx, u_my = getxy(u_m)
233273
u_rx, u_ry = getxy(u_r)
234274
v_mx, v_my = getxy(v_m)
235275
v_rx, v_ry = getxy(v_r)
236-
p = (0.0, 0.0)
237-
q = (0.0, 0.0)
276+
277+
p = (zero(T), zero(T))
278+
q = (zero(T), zero(T))
279+
238280
dist_to_box = maximum_distance_to_box(a, b, c, d, u_m) # this is a squared distance
239281
dist_to_box = max(dist_to_box, maximum_distance_to_box(a, b, c, d, v_m))
282+
240283
while inside
241284
t *= 2.0
242285
p = (u_mx + t * (u_rx - u_mx), u_my + t * (u_ry - u_my))
243286
q = (v_mx + t * (v_rx - v_mx), v_my + t * (v_ry - v_my))
287+
288+
(isinf(p[1]) || isinf(p[2]) || isinf(q[1]) || isinf(q[2])) && break
289+
244290
int1, int2 = liang_barsky(a, b, c, d, p, q)
245291
outside = all(isnan, int1) && all(isnan, int2)
246-
# We need to be careful of the case where the generator is outside of the bounding box. In this case,
247-
# the unbounded edge might start initially outside of the box but then find it's way inside.
248-
# So, to avoid this, we also apply a conservative check that the length of each ray is greater than
292+
# We need to be careful of the case where the generator is outside of the bounding box. In this case,
293+
# the unbounded edge might start initially outside of the box but then find it's way inside.
294+
# So, to avoid this, we also apply a conservative check that the length of each ray is greater than
249295
# the maximum distance from the generators to the bounding box.
250296
# See the example with [(-3,7),(1,6),(-1,3),(-2,4),(3,-2),(5,5),(-4,-3),(3,8)] and bb = (0,5,-15,15) with the 7th polygon.
251297
p_length = dist_sqr(p, (u_mx, u_my))

src/geometric_primitives/points.jl

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -579,3 +579,14 @@ Returns `true` if all points in `points` are two-dimensional. The default defini
579579
"""
580580
is_planar(points) = all(is_point2, each_point(points))
581581

582+
583+
"""
584+
allfinite(points) -> Bool
585+
Returns `true` if all coordinates of all points in `points` are finite.
586+
"""
587+
function allfinite(points)
588+
for p in each_point(points)
589+
all(isfinite, getxy(p)) || return false
590+
end
591+
return true
592+
end

test/interfaces/points.jl

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,41 @@ end
301301
@test !DT.is_planar([(1.0, 2.0, 3.0), (2.0, 3.0, 4.0), (3.0, 4.0, 5.0)])
302302
@test !DT.is_planar(rand(3, 50))
303303
@test !DT.is_planar([SVector{3,Float64}((1.0, 2.0, 3.0)), SVector{3,Float64}((2.0, 3.0, 4.0)), SVector{3,Float64}((3.0, 4.0, 5.0))])
304-
end
304+
end
305+
306+
@testset "allfinite" begin
307+
# All finite points
308+
@test DT.allfinite([[2.0, 3.5], [1.7, 23.3], [-1.0, 0.0]])
309+
@test DT.allfinite([(2.0, 3.5), (1.7, 23.3), (-1.0, 0.0)])
310+
@test DT.allfinite([2.0 1.7 -1.0; 3.5 23.3 0.0])
311+
@test DT.allfinite(((2.0, 3.5), (1.7, 23.3), (-1.0, 0.0)))
312+
313+
# With Inf
314+
@test !DT.allfinite([[2.0, 3.5], [Inf, 23.3], [-1.0, 0.0]])
315+
@test !DT.allfinite([(2.0, 3.5), (Inf, 23.3), (-1.0, 0.0)])
316+
@test !DT.allfinite([2.0 Inf -1.0; 3.5 23.3 0.0])
317+
@test !DT.allfinite(((2.0, 3.5), (Inf, 23.3), (-1.0, 0.0)))
318+
319+
# With NaN
320+
@test !DT.allfinite([[2.0, 3.5], [1.7, NaN], [-1.0, 0.0]])
321+
@test !DT.allfinite([(2.0, 3.5), (1.7, NaN), (-1.0, 0.0)])
322+
@test !DT.allfinite([2.0 1.7 NaN; 3.5 23.3 0.0])
323+
@test !DT.allfinite(((2.0, 3.5), (1.7, NaN), (-1.0, 0.0)))
324+
325+
# With both Inf and NaN
326+
@test !DT.allfinite([[2.0, 3.5], [Inf, 23.3], [NaN, 0.0]])
327+
328+
# Edge cases
329+
@test DT.allfinite(Vector{Vector{Float64}}()) # vacuous truth
330+
@test DT.allfinite(Matrix{Float64}(undef, 2, 0)) # vacuous truth
331+
@test DT.allfinite([[1.0, 2.0]])
332+
@test !DT.allfinite([[Inf, 2.0]])
333+
334+
# First point non-finite
335+
@test !DT.allfinite([[Inf, 1.0], [2.0, 3.0]])
336+
@test !DT.allfinite([[NaN, 1.0], [2.0, 3.0]])
337+
338+
# Last point non-finite
339+
@test !DT.allfinite([[1.0, 2.0], [3.0, 4.0], [5.0, Inf]])
340+
@test !DT.allfinite([[1.0, 2.0], [3.0, 4.0], [NaN, 6.0]])
341+
end

test/voronoi/voronoi.jl

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ using StaticArrays
1010
using LinearAlgebra
1111
using StructEquality
1212
using GeometryBasics
13+
using ReferenceTests
1314
@struct_equal DT.Queue
1415

1516
@testset "Unconstrained test" begin
@@ -1791,4 +1792,38 @@ end
17911792
vorn = voronoi(tri)
17921793
poly = get_polygon_coordinates(vorn, 3, (0.0, 1.0, 0.0, 1.0))
17931794
@test poly [(0.675, 1.0), (0.0, 1.0), (0.0, 0.95), (0.6, 0.95), (0.675, 1.0)]
1794-
end
1795+
end
1796+
1797+
@testset "Issue #234" begin
1798+
function generatePoints(n)
1799+
points = Vector{NTuple{3,Float64}}(undef, n * (n + 1) ÷ 2)
1800+
m = 1
1801+
for (k, i) in enumerate(range(0, 1, n))
1802+
for j in range(i, 1, n - k + 1)
1803+
px = i
1804+
py = j - i
1805+
pz = 1 - j
1806+
points[m] = (px, py, pz)
1807+
m += 1
1808+
end
1809+
end
1810+
return points
1811+
end
1812+
function projection(point; origin=[1 / 3, 1 / 3, 1 / 3], e1=normalize(cross(normalize([0.0, 0.0, 1.0] .- [1 / 3, 1 / 3, 1 / 3]), normalize([1 / 3, 1 / 3, 1 / 3]))), e2=normalize([0.0, 0.0, 1.0] .- [1 / 3, 1 / 3, 1 / 3]))
1813+
return sum(e1 .* (point .- origin)), sum(e2 .* (point .- origin))
1814+
end
1815+
1816+
@test_reference "voronoi_issue_234_reference_n3.png" begin
1817+
points = [projection(p) for p in generatePoints(3)]
1818+
values = [0, 1, 2, 2, 0, 1]
1819+
fig, ax, _ = voronoiplot([p[1] for p in points], [p[2] for p in points], values)
1820+
fig
1821+
end
1822+
1823+
@test_reference "voronoi_issue_234_reference_n4.png" begin
1824+
points = [projection(p) for p in generatePoints(4)]
1825+
values = [0, 1, 2, 0, 2, 0, 1, 1, 2, 0]
1826+
fig, ax, _ = voronoiplot([p[1] for p in points], [p[2] for p in points], values)
1827+
fig
1828+
end
1829+
end
49.3 KB
Loading
63.4 KB
Loading

0 commit comments

Comments
 (0)