diff --git a/Project.toml b/Project.toml index d7ab35d..ec71a4c 100644 --- a/Project.toml +++ b/Project.toml @@ -6,7 +6,7 @@ version = "0.4.1" GeometryBasics = "5c1252a2-5f33-56bf-86c9-59e7332b4326" [compat] -GeometryBasics = "0.2, 0.3" +GeometryBasics = "0.4.1" julia = "1" [extras] diff --git a/src/guillotine.jl b/src/guillotine.jl index dae212c..a4f041a 100644 --- a/src/guillotine.jl +++ b/src/guillotine.jl @@ -24,8 +24,8 @@ struct GuillotinePacker{T} merge::Bool rect_choice::FreeRectChoiceHeuristic split_method::GuillotineSplitHeuristic - free_rectangles::Vector{Rect2D{T}} - used_rectangles::Vector{Rect2D{T}} + free_rectangles::Vector{Rect2{T}} + used_rectangles::Vector{Rect2{T}} end @@ -34,12 +34,12 @@ function GuillotinePacker(width::T, height::T; merge::Bool=true, split_method::GuillotineSplitHeuristic=SplitMinimizeArea) where T packer = GuillotinePacker(width, height, merge, rect_choice, split_method, - Rect2D{T}[], Rect2D{T}[]) - push!(packer.free_rectangles, Rect2D(T(0), T(0), width, height)) + Rect2{T}[], Rect2{T}[]) + push!(packer.free_rectangles, Rect2(T(0), T(0), width, height)) return packer end -function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}}) where T +function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2{T}}) where T rects = copy(rects) free_rectangles = packer.free_rectangles used_rectangles = packer.used_rectangles @@ -104,9 +104,9 @@ function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}}) # Otherwise, we're good to go and do the actual packing. xy = minimum(free_rectangles[best_free_rect]) if best_flipped - new_node = Rect2D(xy, reverse(widths(rects[best_rect]))) + new_node = Rect2(xy, reverse(widths(rects[best_rect]))) else - new_node = Rect2D(xy, widths(rects[best_rect])) + new_node = Rect2(xy, widths(rects[best_rect])) end # Remove the free space we lost in the bin. @@ -126,7 +126,7 @@ function Base.push!(packer::GuillotinePacker, rects::AbstractVector{Rect2D{T}}) end end -function Base.push!(packer::GuillotinePacker, rect::Rect2D) +function Base.push!(packer::GuillotinePacker, rect::Rect2) w, h = widths(rect) free_rectangles = packer.free_rectangles used_rectangles = packer.used_rectangles @@ -164,7 +164,7 @@ function Occupancy(packer::GuillotinePacker) end #/ Returns the heuristic score value for placing a rectangle of size width*height into free_rect. Does not try to rotate. -function score_by_heuristic(width, height, free_rect::Rect2D, rect_choice::FreeRectChoiceHeuristic) +function score_by_heuristic(width, height, free_rect::Rect2, rect_choice::FreeRectChoiceHeuristic) rect_choice == RectBestAreaFit && return ScoreBestAreaFit(width, height, free_rect) rect_choice == RectBestShortSideFit && return ScoreBestShortSideFit(width, height, free_rect) rect_choice == RectBestLongSideFit && return ScoreBestLongSideFit(width, height, free_rect) @@ -173,18 +173,18 @@ function score_by_heuristic(width, height, free_rect::Rect2D, rect_choice::FreeR rect_choice == RectWorstLongSideFit && return ScoreWorstLongSideFit(width, height, free_rect) end -function ScoreBestAreaFit(w, h, free_rect::Rect2D) +function ScoreBestAreaFit(w, h, free_rect::Rect2) return width(free_rect) * height(free_rect) - w * h end -function ScoreBestShortSideFit(w, h, free_rect::Rect2D) +function ScoreBestShortSideFit(w, h, free_rect::Rect2) leftoverHoriz = abs(width(free_rect) - w) leftoverVert = abs(height(free_rect) - h) leftover = min(leftoverHoriz, leftoverVert) return leftover end -function ScoreBestLongSideFit(w, h, free_rect::Rect2D) +function ScoreBestLongSideFit(w, h, free_rect::Rect2) leftoverHoriz = abs(width(free_rect) - w) leftoverVert = abs(height(free_rect) - h) leftover = max(leftoverHoriz, leftoverVert) @@ -192,21 +192,21 @@ function ScoreBestLongSideFit(w, h, free_rect::Rect2D) end -function ScoreWorstAreaFit(width, height, free_rect::Rect2D) +function ScoreWorstAreaFit(width, height, free_rect::Rect2) return -ScoreBestAreaFit(width, height, free_rect) end -function ScoreWorstShortSideFit(width, height, free_rect::Rect2D) +function ScoreWorstShortSideFit(width, height, free_rect::Rect2) return -ScoreBestShortSideFit(width, height, free_rect) end -function ScoreWorstLongSideFit(width, height, free_rect::Rect2D) +function ScoreWorstLongSideFit(width, height, free_rect::Rect2) return -ScoreBestLongSideFit(width, height, free_rect) end function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::FreeRectChoiceHeuristic) free_rectangles = packer.free_rectangles - best_node = Rect2D(0,0,0,0) + best_node = Rect2(0,0,0,0) free_node_idx = 0 best_score = typemax(Int) #/ Try each free rectangle to find the best one for placement. @@ -214,12 +214,12 @@ function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::Fre rect_i = free_rectangles[i] # If this is a perfect fit upright, choose it immediately. if (w == width(rect_i) && h == height(rect_i)) - best_node = Rect2D(minimum(rect_i), Vec(w, h)) + best_node = Rect2(minimum(rect_i), Vec(w, h)) free_node_idx = i break # If this is a perfect fit sideways, choose it. elseif (h == width(rect_i) && w == height(rect_i)) - best_node = Rect2D(minimum(rect_i), Vec(h, w)) + best_node = Rect2(minimum(rect_i), Vec(h, w)) best_score = typemin(Int) free_node_idx = i break @@ -227,7 +227,7 @@ function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::Fre elseif (w <= width(rect_i) && h <= height(rect_i)) score = score_by_heuristic(w, h, rect_i, rect_choice) if (score < best_score) - best_node = Rect2D(minimum(rect_i), Vec(w, h)) + best_node = Rect2(minimum(rect_i), Vec(w, h)) best_score = score free_node_idx = i end @@ -235,7 +235,7 @@ function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::Fre elseif (h <= width(rect_i) && w <= height(rect_i)) score = score_by_heuristic(h, w, rect_i, rect_choice) if (score < best_score) - best_node = Rect2D(minimum(rect_i), Vec(h, w)) + best_node = Rect2(minimum(rect_i), Vec(h, w)) best_score = score free_node_idx = i end @@ -244,7 +244,7 @@ function FindPositionForNewNode(packer::GuillotinePacker, w, h, rect_choice::Fre return best_node, free_node_idx end -function split_freerect_by_heuristic(packer::GuillotinePacker, free_rect::Rect2D, placed_rect::Rect2D, method::GuillotineSplitHeuristic) +function split_freerect_by_heuristic(packer::GuillotinePacker, free_rect::Rect2, placed_rect::Rect2, method::GuillotineSplitHeuristic) # Compute the lengths of the leftover area. w, h = widths(free_rect) .- widths(placed_rect) @@ -283,16 +283,16 @@ end #/ This function will add the two generated rectangles into the free_rectangles array. The caller is expected to #/ remove the original rectangle from the free_rectangles array after that. -function split_freerect_along_axis(packer::GuillotinePacker, free_rect::Rect2D, placed_rect::Rect2D, split_horizontal::Bool) +function split_freerect_along_axis(packer::GuillotinePacker, free_rect::Rect2, placed_rect::Rect2, split_horizontal::Bool) free_rectangles = packer.free_rectangles # Form the two new rectangles. x, y = minimum(free_rect) if split_horizontal - bottom = Rect2D(x, y + height(placed_rect), width(free_rect), height(free_rect) - height(placed_rect)) - right = Rect2D(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(placed_rect)) + bottom = Rect2(x, y + height(placed_rect), width(free_rect), height(free_rect) - height(placed_rect)) + right = Rect2(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(placed_rect)) else # Split vertically - bottom = Rect2D(x, y + height(placed_rect), width(placed_rect), height(free_rect) - height(placed_rect)) - right = Rect2D(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(free_rect)) + bottom = Rect2(x, y + height(placed_rect), width(placed_rect), height(free_rect) - height(placed_rect)) + right = Rect2(x + width(placed_rect), y, width(free_rect) - width(placed_rect), height(free_rect)) end # Add the new rectangles into the free rectangle pool if they weren't degenerate. @@ -318,12 +318,12 @@ function MergeFreeList(packer::GuillotinePacker) if (minimum(rect_i)[2] == maximum(free_rectangles[j])[2]) new_y = minimum(rect_i)[2] - height(free_rectangles[j]) new_h = height(rect_i) + height(free_rectangles[j]) - free_rectangles[i] = Rect2D(minimum(rect_i)[1], new_y, Vec(width(rect_i), new_h)) + free_rectangles[i] = Rect2(minimum(rect_i)[1], new_y, Vec(width(rect_i), new_h)) splice!(free_rectangles, j) j -= 1 elseif (maximum(rect_i)[2] == minimum(free_rectangles[j])[2]) new_h = height(rect_i) + height(free_rectangles[j]) - free_rectangles[i] = Rect2D(minimum(rect_i), Vec(width(rect_i), new_h)) + free_rectangles[i] = Rect2(minimum(rect_i), Vec(width(rect_i), new_h)) splice!(free_rectangles, j) j -= 1 end @@ -331,12 +331,12 @@ function MergeFreeList(packer::GuillotinePacker) if (minimum(rect_i)[1] == minimum(free_rectangles[j])[1] + width(free_rectangles[j])) new_x = minimum(rect_i)[1] - width(free_rectangles[j]) new_w = width(rect_i) + width(free_rectangles[j]) - free_rectangles[i] = Rect2D(new_x, minimum(rect_i)[2], new_w, height(rect_i)) + free_rectangles[i] = Rect2(new_x, minimum(rect_i)[2], new_w, height(rect_i)) splice!(free_rectangles, j) j -= 1 elseif maximum(rect_i)[1] == minimum(free_rectangles[j])[1] new_w = Vec(width(rect_i) + width(free_rectangles[j]), height(rect_i)) - free_rectangles[i] = Rect2D(minimum(rect_i), new_w) + free_rectangles[i] = Rect2(minimum(rect_i), new_w) splice!(free_rectangles, j) j -= 1 end diff --git a/src/rectangle.jl b/src/rectangle.jl index 52baa06..8cd12ce 100644 --- a/src/rectangle.jl +++ b/src/rectangle.jl @@ -9,23 +9,23 @@ end mutable struct RectanglePacker{T} children::BinaryNode{RectanglePacker{T}} - area::Rect2D{T} + area::Rect2{T} end left(a::RectanglePacker) = a.children.left left(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.left = r) right(a::RectanglePacker) = a.children.right right(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.right = r) -RectanglePacker(area::Rect2D{T}) where {T} = RectanglePacker{T}(BinaryNode{RectanglePacker{T}}(), area) +RectanglePacker(area::Rect2{T}) where {T} = RectanglePacker{T}(BinaryNode{RectanglePacker{T}}(), area) isleaf(a::RectanglePacker) = (a.children.left) == nothing && (a.children.right == nothing) # This is rather append, but it seems odd to use another function here. # Maybe its a bad idea, to call it push regardless!? -function Base.push!(node::RectanglePacker{T}, areas::Vector{Rect2D{T}}) where T +function Base.push!(node::RectanglePacker{T}, areas::Vector{Rect2{T}}) where T sort!(areas, by=GeometryBasics.norm ∘ widths) return RectanglePacker{T}[push!(node, area) for area in areas] end -function Base.push!(node::RectanglePacker{T}, area::Rect2D{T}) where T +function Base.push!(node::RectanglePacker{T}, area::Rect2{T}) where T if !isleaf(node) l = push!(left(node), area) l !== nothing && return l @@ -41,9 +41,9 @@ function Base.push!(node::RectanglePacker{T}, area::Rect2D{T}) where T oax,oay,oaxw,oayh = xmin + neww, ymin, xmax, ymin + newh nax,nay,naxw,nayh = xmin, ymin + newh, xmax, ymax rax,ray,raxw,rayh = xmin, ymin, xmin + neww, ymin + newh - left(node, RectanglePacker(Rect2D(oax, oay, oaxw - oax, oayh - oay))) - right(node, RectanglePacker(Rect2D(nax, nay, naxw - nax, nayh - nay))) - return RectanglePacker(Rect2D(rax, ray, raxw - rax, rayh - ray)) + left(node, RectanglePacker(Rect2(oax, oay, oaxw - oax, oayh - oay))) + right(node, RectanglePacker(Rect2(nax, nay, naxw - nax, nayh - nay))) + return RectanglePacker(Rect2(rax, ray, raxw - rax, rayh - ray)) end return nothing end diff --git a/test/guillotine.jl b/test/guillotine.jl index c4eebdf..e307e42 100644 --- a/test/guillotine.jl +++ b/test/guillotine.jl @@ -4,5 +4,5 @@ push!(packer, rects) push!(packer, Rect(0, 0, 100, 100)) # using Makie -# linesegments(FRect(0, 0, 1024, 1024)) +# linesegments(Rectf(0, 0, 1024, 1024)) # poly!(packer.used_rectangles, color = (:red, 0.1), strokewidth=1, strokecolor=:black) diff --git a/test/test_rectangle.jl b/test/test_rectangle.jl index 5beaaf0..bc16d38 100644 --- a/test/test_rectangle.jl +++ b/test/test_rectangle.jl @@ -1,10 +1,10 @@ -root = RectanglePacker(Rect2D(0,0,1024,1024)) -push!(root, Rect2D(0,0,20,20)) -push!(root, Rect2D(0,0,20,20)) -push!(root, [Rect2D(0,0, rand(5:50), rand(5:50)) for i=1:20]) +root = RectanglePacker(Rect2(0,0,1024,1024)) +push!(root, Rect2(0,0,20,20)) +push!(root, Rect2(0,0,20,20)) +push!(root, [Rect2(0,0, rand(5:50), rand(5:50)) for i=1:20]) -# function get_rectangles(packer::Nothing, rectangles=IRect2D[]) +# function get_rectangles(packer::Nothing, rectangles=Rect2i[]) # return rectangles # end # @@ -15,7 +15,7 @@ push!(root, [Rect2D(0,0, rand(5:50), rand(5:50)) for i=1:20]) # return rectangles # end # function get_rectangles(packer) -# rectangles = IRect2D[] +# rectangles = Rect2i[] # get_rectangles(packer.children.left, rectangles) # get_rectangles(packer.children.right, rectangles) # return rectangles @@ -23,6 +23,6 @@ push!(root, [Rect2D(0,0, rand(5:50), rand(5:50)) for i=1:20]) # # rectangles = get_rectangles(root) # using Makie -# linesegments(FRect(0, 0, 1024, 1024)) +# linesegments(Rectf(0, 0, 1024, 1024)) # poly!(rectangles, color = (:red, 0.1), strokewidth=1, strokecolor=:black) #