-
Notifications
You must be signed in to change notification settings - Fork 2
/
best_fit_packer.go
146 lines (122 loc) · 3.36 KB
/
best_fit_packer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
package packme
import (
"sort"
)
// BestFitPacker is a packer implementation based on
// a paper by Dube, E., & Kanavathy, L. (2006)
type BestFitPacker struct{}
var _ Packer = (*BestFitPacker)(nil)
func NewBestFitPacker() BestFitPacker {
return BestFitPacker{}
}
func (bfp BestFitPacker) Pack(boxSpecs []BoxSpec, itemSpecs []ItemSpec) PackingScheme {
// Sort boxes and items by volume
sort.Sort(BoxSpecByVolume(boxSpecs))
sort.Sort(ItemSpecByVolume(itemSpecs))
// List of boxes
boxes := []*Box{}
// List of items not packed
notPacked := Items{}
for _, itemSpec := range itemSpecs {
// Loop until we pack all items in this spec
for itemQty := itemSpec.qty; itemQty > 0; itemQty-- {
// Spawn new item from spec
newItem := NewItem(itemSpec.desc, itemSpec.dims)
// Indicates whether item was packed
packed := false
// Try packing item to existing boxes
for _, box := range boxes {
packed = bfp.pack(box, newItem)
if packed {
// Item was packed, we're done
break
}
}
// Item doesn't fit in the existing boxes
if !packed {
// Try other available boxes
for i, bxSpec := range boxSpecs {
// See if we still have an available box of this kind
if bxSpec.qty > 0 {
// Spawn new box from this spec
box := NewBox(bxSpec.desc, bxSpec.dims)
// See if item fits in the new box
packed = bfp.pack(box, newItem)
if packed {
// Reduce number of available boxes
boxSpecs[i].qty--
// Add new box to existing boxes
boxes = append(boxes, box)
break
}
}
}
// Item could not be packed
if !packed {
notPacked = append(notPacked, newItem)
}
}
}
}
return PackingScheme{Boxes: boxes, NotPacked: notPacked}
}
func (bfp BestFitPacker) pack(box *Box, newItem *Item) bool {
// Box doesn't have any items yet
if len(box.items) < 1 {
return bfp.packToBox(box, newItem, NewPoint(0, 0, 0))
}
// Try possible placements for the new item
for axis := AxisX; axis <= AxisZ; axis++ {
// Try packing item next to existing items in the box
for _, boxItem := range box.items {
// Compute pivot point
pivot := computePivot(axis, boxItem.pos, boxItem.Dimensions())
// Try packing the item to the pivot point
if bfp.packToBox(box, newItem, pivot) {
return true
}
}
}
return false
}
func (bfp BestFitPacker) packToBox(bx *Box, item *Item, pivot Point) bool {
var packed bool
// Set item position to pivot
item.pos = pivot
// Try posible packing rotations
for rot := RotationLWH; rot <= RotationLHW; rot++ {
// Set item rotation
item.rot = rot
// Get item dimensions based on current rotatoin
idims := item.Dimensions()
// Box dimensions must be able accomodate
// the item in current rotation
if bx.dims.Length() < pivot.X()+idims.Length() ||
bx.dims.Width() < pivot.Y()+idims.Width() ||
bx.dims.Height() < pivot.Z()+idims.Height() {
continue
}
// Assume item is packed
packed = true
// See if current item might collide with other
// items inside the box
for _, i := range bx.items {
if i.Collision(item) {
// Can't pack coz it collides with another item
packed = false
break
}
}
// Item is packed
if packed {
// Add item to the box
bx.items = append(bx.items, item)
}
break
}
if !packed {
// Reset item position
item.pos = NewPoint(0, 0, 0)
}
return packed
}