-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
* use slightly better packing * add source
- Loading branch information
1 parent
850ef06
commit ea22e45
Showing
1 changed file
with
57 additions
and
37 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |