From 1a1f341f46571d4496bd6a3552e52e961f175ee4 Mon Sep 17 00:00:00 2001
From: ffreyer <frederic481994@hotmail.de>
Date: Tue, 11 Feb 2025 18:06:38 +0100
Subject: [PATCH 1/3] change self_intersections to return tuple vector

---
 src/lines.jl     | 13 +++----------
 test/runtests.jl | 12 +++---------
 2 files changed, 6 insertions(+), 19 deletions(-)

diff --git a/src/lines.jl b/src/lines.jl
index da06be43..d49a0e3c 100644
--- a/src/lines.jl
+++ b/src/lines.jl
@@ -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}[]
 
@@ -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
diff --git a/test/runtests.jl b/test/runtests.jl
index 5fb98ded..e522916b 100644
--- a/test/runtests.jl
+++ b/test/runtests.jl
@@ -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]]

From 1c3c42b6af59178cf3b51c246f10428699e2fb8d Mon Sep 17 00:00:00 2001
From: ffreyer <frederic481994@hotmail.de>
Date: Tue, 11 Feb 2025 18:07:28 +0100
Subject: [PATCH 2/3] change Rect() to generate NaN origin, 0 widths

---
 src/primitives/rectangles.jl | 17 +++++++++-----
 test/geometrytypes.jl        | 44 +++++++++++++++++++++---------------
 2 files changed, 37 insertions(+), 24 deletions(-)

diff --git a/src/primitives/rectangles.jl b/src/primitives/rectangles.jl
index 0aa73f36..b7521b5c 100644
--- a/src/primitives/rectangles.jl
+++ b/src/primitives/rectangles.jl
@@ -56,10 +56,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...)
@@ -302,9 +301,10 @@ end
 """
     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})
@@ -312,6 +312,8 @@ Base.isempty(h::Rect{N,T}) where {N,T} = any(<(zero(T)), h.widths)
 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)
@@ -332,6 +334,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)
@@ -342,6 +345,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)
diff --git a/test/geometrytypes.jl b/test/geometrytypes.jl
index f7bfb44e..73fc5c51 100644
--- a/test/geometrytypes.jl
+++ b/test/geometrytypes.jl
@@ -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
@@ -182,19 +192,17 @@ end
         end
     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))

From 1802ef0a71dc31a0a95fe32f60984b2fd3f01ec4 Mon Sep 17 00:00:00 2001
From: ffreyer <frederic481994@hotmail.de>
Date: Tue, 11 Feb 2025 18:07:41 +0100
Subject: [PATCH 3/3] make Rect widths strictly positive

---
 src/primitives/rectangles.jl | 11 +++++------
 test/geometrytypes.jl        |  5 +++++
 2 files changed, 10 insertions(+), 6 deletions(-)

diff --git a/src/primitives/rectangles.jl b/src/primitives/rectangles.jl
index b7521b5c..c962e6c6 100644
--- a/src/primitives/rectangles.jl
+++ b/src/primitives/rectangles.jl
@@ -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
 
 ##
@@ -288,12 +292,7 @@ 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
diff --git a/test/geometrytypes.jl b/test/geometrytypes.jl
index 73fc5c51..a197a4d3 100644
--- a/test/geometrytypes.jl
+++ b/test/geometrytypes.jl
@@ -190,6 +190,11 @@ 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
 
     r = Rect2f()