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

Breaking cleanup #239

Draft
wants to merge 3 commits into
base: master
Choose a base branch
from
Draft
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
13 changes: 3 additions & 10 deletions src/lines.jl
Original file line number Diff line number Diff line change
Expand Up @@ -63,21 +63,14 @@ end
self_intersections(points::AbstractVector{<:Point})

Finds all self intersections of in a continuous line described by `points`.
Returns a Vector of indices where each pair `v[2i], v[2i+1]` refers two
intersecting line segments by their first point, and a Vector of intersection
points.
Returns a Vector of index tuples corresponding to the two intersecting line
segments by their first point, and a Vector of intersection points.

Note that if two points are the same, they will generate a self intersection
unless they are consecutive segments. (The first and last point are assumed to
be shared between the first and last segment.)
"""
function self_intersections(points::AbstractVector{<:VecTypes{D, T}}) where {D, T}
ti, sections = _self_intersections(points)
# convert array of tuples to flat array
return [x for t in ti for x in t], sections
end

function _self_intersections(points::AbstractVector{<:VecTypes{D, T}}) where {D, T}
sections = similar(points, 0)
intersections = Tuple{Int, Int}[]

Expand Down Expand Up @@ -108,7 +101,7 @@ Splits polygon `points` into it's self intersecting parts. Only 1 intersection
is handled right now.
"""
function split_intersections(points::AbstractVector{<:VecTypes{N, T}}) where {N, T}
intersections, sections = _self_intersections(points)
intersections, sections = self_intersections(points)
return if isempty(intersections)
return [points]
elseif length(intersections) == 1 && length(sections) == 1
Expand Down
28 changes: 16 additions & 12 deletions src/primitives/rectangles.jl
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ Formally it is the Cartesian product of intervals, which is represented by the
struct HyperRectangle{N,T} <: GeometryPrimitive{N,T}
origin::Vec{N,T}
widths::Vec{N,T}

function HyperRectangle{N, T}(origin::VecTypes, widths::VecTypes) where {N, T}
return new{N, T}(Vec{N, T}(min.(origin, origin .+ widths)), Vec{N, T}(abs.(widths)))
end
end

##
Expand Down Expand Up @@ -56,10 +60,9 @@ Rect() = Rect{2,Float32}()
RectT{T}() where {T} = Rect{2,T}()
Rect{N}() where {N} = Rect{N,Float32}()

function Rect{N,T}() where {T,N}
# empty constructor such that update will always include the first point
return Rect{N,T}(Vec{N,T}(typemax(T)), Vec{N,T}(typemin(T)))
end
Rect{N,T}() where {T <: AbstractFloat,N} = Rect{N,T}(Vec{N,T}(NaN), Vec{N,T}(0))
Rect{N,T}() where {T, N} = throw(MethodError(Rect{N,T}, tuple()))
# TODO: what about integers and other types? No reasonable default?

# Rect(numbers...)
Rect(args::Vararg{Number, N}) where {N} = Rect{div(N, 2), promote_type(typeof.(args)...)}(args...)
Expand Down Expand Up @@ -289,29 +292,27 @@ end
# return vmin, vmax
# end

function positive_widths(rect::Rect{N,T}) where {N,T}
mini, maxi = minimum(rect), maximum(rect)
realmin = min.(mini, maxi)
realmax = max.(mini, maxi)
return Rect{N,T}(realmin, realmax .- realmin)
end
positive_widths(rect::Rect{N,T}) where {N,T} = rect

###
# set operations

"""
isempty(h::Rect)

Return `true` if any of the widths of `h` are negative.
Return `true` if any of the widths of `h` are zero or negative.
"""
Base.isempty(h::Rect{N,T}) where {N,T} = any(<(zero(T)), h.widths)
Base.isempty(h::Rect{N,T}) where {N,T} = any(<=(zero(T)), h.widths)
Base.isnan(r::Rect) = isnan(origin(r)) || isnan(widths(r))

"""
union(r1::Rect{N}, r2::Rect{N})

Returns a new `Rect{N}` which contains both r1 and r2.
"""
function Base.union(h1::Rect{N}, h2::Rect{N}) where {N}
isnan(h1) && return h2
isnan(h2) && return h1
m = min.(minimum(h1), minimum(h2))
mm = max.(maximum(h1), maximum(h2))
return Rect{N}(m, mm - m)
Expand All @@ -332,6 +333,7 @@ end
Perform a intersection between two Rects.
"""
function Base.intersect(h1::Rect{N}, h2::Rect{N}) where {N}
isnan(h1) || isnan(h2) && return Rect{N}()
m = max.(minimum(h1), minimum(h2))
mm = min.(maximum(h1), maximum(h2))
return Rect{N}(m, mm - m)
Expand All @@ -342,6 +344,8 @@ function update(b::Rect{N,T}, v::VecTypes{N,T2}) where {N,T,T2}
end

function update(b::Rect{N,T}, v::VecTypes{N,T}) where {N,T}
isnan(b) && return Rect{N, T}(v, Vec{N, T}(0))

m = min.(minimum(b), v)
maxi = maximum(b)
mm = if any(isnan, maxi)
Expand Down
49 changes: 31 additions & 18 deletions test/geometrytypes.jl
Original file line number Diff line number Diff line change
Expand Up @@ -78,26 +78,36 @@ end

@testset "HyperRectangles" begin
@testset "Constructors" begin
# TODO: Do these actually make sense?
# Should they not be Rect(NaN..., 0...)?
@testset "Empty Constructors" begin
function nan_equal(r1::Rect, r2::Rect)
o1 = origin(r1); o2 = origin(r2)
return ((isnan(o1) && isnan(o2)) || (o1 == o2)) && (widths(r1) == widths(r2))
end

for constructor in [Rect, Rect{2}, Rect2, RectT, Rect2f]
@test constructor() == Rect{2, Float32}(Inf, Inf, -Inf, -Inf)
@test nan_equal(constructor(), Rect{2, Float32}(NaN, NaN, 0, 0))
end
for constructor in [Rect{3}, Rect3, Rect3f]
@test constructor() == Rect{3, Float32}((Inf, Inf, Inf), (-Inf, -Inf, -Inf))
@test nan_equal(constructor(), Rect{3, Float32}((NaN, NaN, NaN), (0, 0, 0)))
end

for T in [UInt32, Int16, Float64]
for T in [UInt32, Int16]
a = typemax(T)
b = typemin(T)
for constructor in [Rect{2, T}, Rect2{T}, RectT{T, 2}]
@test constructor() == Rect{2, T}(a, a, b, b)
@test_throws MethodError constructor()
end
for constructor in [Rect{3, T}, Rect3{T}, RectT{T, 3}]
@test constructor() == Rect{3, T}(Point(a, a, a), Vec(b, b, b))
@test_throws MethodError constructor()
end
end

for constructor in [Rect{2, Float64}, Rect2{Float64}, RectT{Float64, 2}]
@test nan_equal(constructor(), Rect{2, Float64}(NaN, NaN, 0, 0))
end
for constructor in [Rect{3, Float64}, Rect3{Float64}, RectT{Float64, 3}]
@test nan_equal(constructor(), Rect{3, Float64}(Point3(NaN), Vec3(0)))
end
end

@testset "Constructor arg conversions" begin
Expand Down Expand Up @@ -180,21 +190,24 @@ end
@test constructor(m) ≈ Rect3f(-1, -1, -1, 2, 2, 2)
end
end

r = Rect2f(10, 10, -5, -5)
@test origin(r) == Point2f(5)
@test widths(r) == Vec2f(5)
@test maximum(r) == Point2f(10)
end

# TODO: These don't really make sense...
r = Rect2f()
@test origin(r) == Vec(Inf, Inf)
@test minimum(r) == Vec(Inf, Inf)
@test isnan(origin(r))
@test isnan(minimum(r))
@test isnan(maximum(r))
@test width(r) == -Inf
@test height(r) == -Inf
@test widths(r) == Vec(-Inf, -Inf)
@test area(r) == Inf
@test volume(r) == Inf
# TODO: broken? returns NaN widths
# @test union(r, Rect2f(1,1,2,2)) == Rect2f(1,1,2,2)
# @test union(Rect2f(1,1,2,2), r) == Rect2f(1,1,2,2)
@test width(r) == 0
@test height(r) == 0
@test widths(r) == Vec2(0)
@test area(r) == 0
@test volume(r) == 0
@test union(r, Rect2f(1,1,2,2)) == Rect2f(1,1,2,2)
@test union(Rect2f(1,1,2,2), r) == Rect2f(1,1,2,2)
@test update(r, Vec2f(1,1)) == Rect2f(1,1,0,0)

a = Rect(Vec(0, 1), Vec(2, 3))
Expand Down
12 changes: 3 additions & 9 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -285,24 +285,18 @@ end
@test collect(GeometryBasics.consecutive_pairs(ps)) == collect(zip(ps[1:end-1], ps[2:end]))

ps = Point2f[(0,0), (1,0), (0,1), (1,2), (0,2), (1,1), (0,0)]
idxs, ips = GeometryBasics._self_intersections(ps)
idxs, ips = self_intersections(ps)
@test idxs == [(2, 6), (3, 5)]
@test ips == [Point2f(0.5), Point2f(0.5, 1.5)]
idxs2, ips2 = self_intersections(ps)
@test ips2 == ips
@test idxs2 == [2, 6, 3, 5]

ps = [Point2f(cos(x), sin(x)) for x in 0:4pi/5:4pi+0.1]
idxs, ips = GeometryBasics._self_intersections(ps)
idxs, ips = self_intersections(ps)
@test idxs == [(1, 3), (1, 4), (2, 4), (2, 5), (3, 5)]
@test all(ips .≈ Point2f[(0.30901694, 0.2245140), (-0.118034005, 0.36327127), (-0.38196602, 0), (-0.118033946, -0.3632713), (0.309017, -0.22451389)])
idxs2, ips2 = self_intersections(ps)
@test ips2 == ips
@test idxs2 == [1, 3, 1, 4, 2, 4, 2, 5, 3, 5]

@test_throws ErrorException split_intersections(ps)
ps = Point2f[(0,0), (1,0), (0,1), (1,1), (0, 0)]
idxs, ips = GeometryBasics._self_intersections(ps)
idxs, ips = self_intersections(ps)
sps = split_intersections(ps)
@test sps[1] == [ps[3], ps[4], ips[1]]
@test sps[2] == [ps[5], ps[1], ps[2], ips[1]]
Expand Down
Loading