|
1 |
| -mutable struct BinaryNode{T} |
2 |
| - left::Union{Nothing, T} |
3 |
| - right::Union{Nothing, T} |
| 1 | +# ported from https://github.com/joelverhagen/2D-Bin-Packing/blob/master/BinPacking/PackingTreeNode.h |
4 | 2 |
|
5 |
| - BinaryNode{T}() where {T} = new{T}(nothing, nothing) |
6 |
| - BinaryNode(left::T, right::T) where {T} = new{T}(nothing, nothing) |
7 |
| - BinaryNode{T}(left::T, right::T) where {T} = new{T}(nothing, nothing) |
| 3 | +mutable struct RectanglePacker{T} |
| 4 | + # the rectangle represented by this node |
| 5 | + area::Rect{2, T} |
| 6 | + filled::Bool |
| 7 | + # a vector of child nodes |
| 8 | + left::Union{RectanglePacker{T}, Nothing} |
| 9 | + right::Union{RectanglePacker{T}, Nothing} |
8 | 10 | end
|
9 | 11 |
|
10 |
| -mutable struct RectanglePacker{T} |
11 |
| - children::BinaryNode{RectanglePacker{T}} |
12 |
| - area::Rect2{T} |
| 12 | +function RectanglePacker(area::Rect{2, T}) where {T} |
| 13 | + return RectanglePacker{T}(area, false, nothing, nothing) |
13 | 14 | end
|
14 | 15 |
|
15 |
| -left(a::RectanglePacker) = a.children.left |
16 |
| -left(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.left = r) |
17 |
| -right(a::RectanglePacker) = a.children.right |
18 |
| -right(a::RectanglePacker{T}, r::RectanglePacker{T}) where {T} = (a.children.right = r) |
19 |
| -RectanglePacker(area::Rect2{T}) where {T} = RectanglePacker{T}(BinaryNode{RectanglePacker{T}}(), area) |
20 |
| -isleaf(a::RectanglePacker) = (a.children.left) == nothing && (a.children.right == nothing) |
21 |
| -# This is rather append, but it seems odd to use another function here. |
22 |
| -# Maybe its a bad idea, to call it push regardless!? |
23 | 16 | function Base.push!(node::RectanglePacker{T}, areas::Vector{Rect2{T}}) where T
|
24 | 17 | sort!(areas, by=GeometryBasics.norm ∘ widths)
|
25 | 18 | return RectanglePacker{T}[push!(node, area) for area in areas]
|
26 | 19 | end
|
27 | 20 |
|
28 |
| -function Base.push!(node::RectanglePacker{T}, area::Rect2{T}) where T |
29 |
| - if !isleaf(node) |
30 |
| - l = push!(left(node), area) |
31 |
| - l !== nothing && return l |
32 |
| - # if left does not have space, try right |
33 |
| - return push!(right(node), area) |
34 |
| - end |
35 |
| - newarea = RectanglePacker(area).area |
36 |
| - if all(widths(newarea) .<= widths(node.area)) |
37 |
| - neww, newh = widths(newarea) |
38 |
| - xmin, ymin = minimum(node.area) |
39 |
| - xmax, ymax = maximum(node.area) |
40 |
| - w, h = widths(node.area) |
41 |
| - oax,oay,oaxw,oayh = xmin + neww, ymin, xmax, ymin + newh |
42 |
| - nax,nay,naxw,nayh = xmin, ymin + newh, xmax, ymax |
43 |
| - rax,ray,raxw,rayh = xmin, ymin, xmin + neww, ymin + newh |
44 |
| - left(node, RectanglePacker(Rect2(oax, oay, oaxw - oax, oayh - oay))) |
45 |
| - right(node, RectanglePacker(Rect2(nax, nay, naxw - nax, nayh - nay))) |
46 |
| - return RectanglePacker(Rect2(rax, ray, raxw - rax, rayh - ray)) |
| 21 | +function Base.push!(node::RectanglePacker{T}, new_rect::Rect) where {T} |
| 22 | + nwidth, nheight = widths(new_rect) |
| 23 | + # if the node is not a leaf (has children) |
| 24 | + if !isnothing(node.left) || !isnothing(node.right) |
| 25 | + # try inserting into the first child |
| 26 | + new_node = push!(node.left, new_rect) |
| 27 | + !isnothing(new_node) && return new_node |
| 28 | + # no room, insert into the second child |
| 29 | + return push!(node.right, new_rect) |
| 30 | + else |
| 31 | + |
| 32 | + # if there's already an image here, return |
| 33 | + node.filled && return nothing |
| 34 | + area = node.area |
| 35 | + awidth, aheight = widths(area) |
| 36 | + # if the image doesn't fit, return |
| 37 | + if nwidth > awidth || nheight > aheight |
| 38 | + return nothing |
| 39 | + end |
| 40 | + |
| 41 | + # if the image fits perfectly, accept |
| 42 | + if nwidth == awidth && nheight == aheight |
| 43 | + node.filled = true |
| 44 | + return node |
| 45 | + end |
| 46 | + |
| 47 | + # otherwise, split the node and create two children |
| 48 | + |
| 49 | + # decide which way to split |
| 50 | + |
| 51 | + left, bottom = minimum(area) |
| 52 | + right, top = maximum(area) |
| 53 | + |
| 54 | + dw = awidth - nwidth |
| 55 | + dh = aheight - nheight |
| 56 | + |
| 57 | + if dw > dh |
| 58 | + left_rectangle = Rect2{T}(left, bottom, nwidth, aheight) |
| 59 | + right_rectangle = Rect2{T}(left + nwidth, bottom, awidth - nwidth, aheight) |
| 60 | + else |
| 61 | + left_rectangle = Rect2{T}(left, bottom, awidth, nheight) |
| 62 | + right_rectangle = Rect2{T}(left, bottom + nheight, awidth, aheight - nheight) |
| 63 | + end |
| 64 | + node.left = RectanglePacker(left_rectangle) |
| 65 | + node.right = RectanglePacker(right_rectangle) |
| 66 | + # insert into the first child we created |
| 67 | + return push!(node.left, new_rect) |
47 | 68 | end
|
48 |
| - return nothing |
49 | 69 | end
|
0 commit comments