Skip to content

Commit

Permalink
use slightly better packing (#11)
Browse files Browse the repository at this point in the history
* use slightly better packing

* add source
  • Loading branch information
SimonDanisch authored Dec 15, 2022
1 parent 850ef06 commit ea22e45
Showing 1 changed file with 57 additions and 37 deletions.
94 changes: 57 additions & 37 deletions src/rectangle.jl
Original file line number Diff line number Diff line change
@@ -1,49 +1,69 @@
mutable struct BinaryNode{T}
left::Union{Nothing, T}
right::Union{Nothing, T}
# ported from https://github.com/joelverhagen/2D-Bin-Packing/blob/master/BinPacking/PackingTreeNode.h

BinaryNode{T}() where {T} = new{T}(nothing, nothing)
BinaryNode(left::T, right::T) where {T} = new{T}(nothing, nothing)
BinaryNode{T}(left::T, right::T) where {T} = new{T}(nothing, nothing)
mutable struct RectanglePacker{T}
# the rectangle represented by this node
area::Rect{2, T}
filled::Bool
# a vector of child nodes
left::Union{RectanglePacker{T}, Nothing}
right::Union{RectanglePacker{T}, Nothing}
end

mutable struct RectanglePacker{T}
children::BinaryNode{RectanglePacker{T}}
area::Rect2{T}
function RectanglePacker(area::Rect{2, T}) where {T}
return RectanglePacker{T}(area, false, nothing, nothing)
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::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{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::Rect2{T}) where T
if !isleaf(node)
l = push!(left(node), area)
l !== nothing && return l
# if left does not have space, try right
return push!(right(node), area)
end
newarea = RectanglePacker(area).area
if all(widths(newarea) .<= widths(node.area))
neww, newh = widths(newarea)
xmin, ymin = minimum(node.area)
xmax, ymax = maximum(node.area)
w, h = widths(node.area)
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(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))
function Base.push!(node::RectanglePacker{T}, new_rect::Rect) where {T}
nwidth, nheight = widths(new_rect)
# if the node is not a leaf (has children)
if !isnothing(node.left) || !isnothing(node.right)
# try inserting into the first child
new_node = push!(node.left, new_rect)
!isnothing(new_node) && return new_node
# no room, insert into the second child
return push!(node.right, new_rect)
else

# if there's already an image here, return
node.filled && return nothing
area = node.area
awidth, aheight = widths(area)
# if the image doesn't fit, return
if nwidth > awidth || nheight > aheight
return nothing
end

# if the image fits perfectly, accept
if nwidth == awidth && nheight == aheight
node.filled = true
return node
end

# otherwise, split the node and create two children

# decide which way to split

left, bottom = minimum(area)
right, top = maximum(area)

dw = awidth - nwidth
dh = aheight - nheight

if dw > dh
left_rectangle = Rect2{T}(left, bottom, nwidth, aheight)
right_rectangle = Rect2{T}(left + nwidth, bottom, awidth - nwidth, aheight)
else
left_rectangle = Rect2{T}(left, bottom, awidth, nheight)
right_rectangle = Rect2{T}(left, bottom + nheight, awidth, aheight - nheight)
end
node.left = RectanglePacker(left_rectangle)
node.right = RectanglePacker(right_rectangle)
# insert into the first child we created
return push!(node.left, new_rect)
end
return nothing
end

0 comments on commit ea22e45

Please sign in to comment.